RestoreDashboards: Populate page (#88555)

* feat: add search input and filters

* feat: add state management v0

* feat: update search view and add to trash page

* refactor: fix some errors

* feat: page population

* feat: fix error

* Run betterer checks locally

* Run i18n abstraction

* refactor: clean up after merging main

* refactor: clean up after merging main

* Run i18n abstraction

* refactor: remove browse view
This commit is contained in:
Laura Benz
2024-06-04 12:08:18 +02:00
committed by GitHub
parent b66cd7ef79
commit adbb005d38
13 changed files with 127 additions and 15 deletions

View File

@@ -4785,9 +4785,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Styles should be written using objects.", "2"],
[0, 0, 0, "Styles should be written using objects.", "3"]
],
"public/app/features/manage-dashboards/RecentlyDeletedPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/manage-dashboards/components/ImportDashboardForm.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],

View File

@@ -135,7 +135,13 @@ const BrowseDashboardsPage = memo(({ match }: Props) => {
<AutoSizer>
{({ width, height }) =>
isSearching ? (
<SearchView canSelect={canSelect} width={width} height={height} />
<SearchView
canSelect={canSelect}
width={width}
height={height}
searchState={searchState}
searchStateManager={stateManager}
/>
) : (
<BrowseView canSelect={canSelect} width={width} height={height} folderUID={folderUID} />
)

View File

@@ -5,8 +5,8 @@ import { Button, EmptyState } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { useKeyNavigationListener } from 'app/features/search/hooks/useSearchKeyboardSelection';
import { SearchResultsProps, SearchResultsTable } from 'app/features/search/page/components/SearchResultsTable';
import { useSearchStateManager } from 'app/features/search/state/SearchStateManager';
import { DashboardViewItemKind } from 'app/features/search/types';
import { SearchStateManager } from 'app/features/search/state/SearchStateManager';
import { DashboardViewItemKind, SearchState } from 'app/features/search/types';
import { useDispatch, useSelector } from 'app/types';
import { setAllSelection, setItemSelectionState, useHasSelection } from '../state';
@@ -15,6 +15,8 @@ interface SearchViewProps {
height: number;
width: number;
canSelect: boolean;
searchState: SearchState;
searchStateManager: SearchStateManager;
}
const NUM_PLACEHOLDER_ROWS = 50;
@@ -41,13 +43,18 @@ const initialLoadingView = {
totalRows: NUM_PLACEHOLDER_ROWS,
};
export function SearchView({ width, height, canSelect }: SearchViewProps) {
export function SearchView({
width,
height,
canSelect,
searchState,
searchStateManager: stateManager,
}: SearchViewProps) {
const dispatch = useDispatch();
const selectedItems = useSelector((wholeState) => wholeState.browseDashboards.selectedItems);
const hasSelection = useHasSelection();
const { keyboardEvents } = useKeyNavigationListener();
const [searchState, stateManager] = useSearchStateManager();
const value = searchState.result ?? initialLoadingView;

View File

@@ -1,15 +1,77 @@
import React from 'react';
import React, { memo, useEffect } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Page } from '../../core/components/Page/Page';
import { FilterInput } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { t } from 'app/core/internationalization';
import { ActionRow } from 'app/features/search/page/components/ActionRow';
import { getGrafanaSearcher } from 'app/features/search/service';
import { useDispatch } from '../../types';
import { SearchView } from '../browse-dashboards/components/SearchView';
import { getFolderPermissions } from '../browse-dashboards/permissions';
import { setAllSelection } from '../browse-dashboards/state';
import { useRecentlyDeletedStateManager } from './utils/useRecentlyDeletedStateManager';
const RecentlyDeletedPage = memo(() => {
const dispatch = useDispatch();
const [searchState, stateManager] = useRecentlyDeletedStateManager();
const { canEditFolders, canEditDashboards } = getFolderPermissions();
const canSelect = canEditFolders || canEditDashboards;
useEffect(() => {
stateManager.initStateFromUrl(undefined);
// Clear selected state when folderUID changes
dispatch(
setAllSelection({
isSelected: false,
folderUID: undefined,
})
);
}, [dispatch, stateManager]);
const RecentlyDeletedPage = () => {
return (
<Page navId="dashboards/recentlyDeleted">
<Page.Contents>
<p>page content</p>
<FilterInput
placeholder={t('recentlyDeleted.filter.placeholder', 'Search for dashboards')}
value={searchState.query}
escapeRegex={false}
onChange={(e) => stateManager.onQueryChange(e)}
/>
<ActionRow
showStarredFilter={false}
state={searchState}
getTagOptions={stateManager.getTagOptions}
getSortOptions={getGrafanaSearcher().getSortOptions}
sortPlaceholder={getGrafanaSearcher().sortPlaceholder}
includePanels={false}
onLayoutChange={stateManager.onLayoutChange}
onSortChange={stateManager.onSortChange}
onTagFilterChange={stateManager.onTagFilterChange}
onDatasourceChange={stateManager.onDatasourceChange}
onPanelTypeChange={stateManager.onPanelTypeChange}
onSetIncludePanels={stateManager.onSetIncludePanels}
/>
<AutoSizer>
{({ width, height }) => (
<SearchView
canSelect={canSelect}
width={width}
height={height}
searchStateManager={stateManager}
searchState={searchState}
/>
)}
</AutoSizer>
</Page.Contents>
</Page>
);
};
});
RecentlyDeletedPage.displayName = 'RecentlyDeletedPage';
export default RecentlyDeletedPage;

View File

@@ -0,0 +1,17 @@
import { initialState, SearchStateManager } from '../../search/state/SearchStateManager';
let recentlyDeletedStateManager: SearchStateManager;
function getRecentlyDeletedStateManager() {
if (!recentlyDeletedStateManager) {
recentlyDeletedStateManager = new SearchStateManager({ ...initialState, includePanels: false, deleted: true });
}
return recentlyDeletedStateManager;
}
export function useRecentlyDeletedStateManager() {
const stateManager = getRecentlyDeletedStateManager();
const state = stateManager.useState();
return [state, stateManager] as const;
}

View File

@@ -10,6 +10,7 @@ interface QueryProps {
query: string;
tagCount: number;
includePanels?: boolean;
deleted: boolean;
}
export const reportDashboardListViewed = (eventTrackingNamespace: EventTrackingNamespace, query: QueryProps) => {
@@ -47,5 +48,6 @@ const getQuerySearchContext = (query: QueryProps) => {
tagCount: query.tagCount ?? 0,
queryLength: query.query?.length ?? 0,
includePanels: query.includePanels ?? false,
deleted: query.deleted ?? false,
};
};

View File

@@ -23,6 +23,7 @@ interface APIQuery {
sort?: string;
starred?: boolean;
permission?: PermissionLevelString;
deleted?: boolean;
}
// Internal object to hold folderId
@@ -89,6 +90,7 @@ export class SQLSearcher implements GrafanaSearcher {
sort: query.sort,
permission: query.permission,
page,
deleted: query.deleted,
},
query
);

View File

@@ -27,6 +27,7 @@ export interface SearchQuery {
from?: number;
starred?: boolean;
permission?: PermissionLevelString;
deleted?: boolean;
}
export interface DashboardQueryResult {

View File

@@ -25,6 +25,7 @@ export const initialState: SearchState = {
sort: undefined,
prevSort: undefined,
eventTrackingNamespace: 'dashboard_search',
deleted: false,
};
export const defaultQueryParams: SearchQueryParams = {
@@ -62,7 +63,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
const prevSort = localStorage.getItem(SEARCH_SELECTED_SORT) ?? undefined;
const sort = layout === SearchLayout.List ? stateFromUrl.sort || prevSort : null;
stateManager.setState({
this.setState({
...initialState,
...stateFromUrl,
layout,
@@ -70,6 +71,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
prevSort,
folderUid: folderUid,
eventTrackingNamespace: folderUid ? 'manage_dashboards' : 'dashboard_search',
deleted: this.state.deleted,
});
if (doInitialSearch && this.hasSearchFilters()) {
@@ -195,6 +197,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
this.state.starred ||
this.state.panel_type ||
this.state.sort ||
this.state.deleted ||
this.state.layout === SearchLayout.List
);
}
@@ -210,6 +213,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
explain: this.state.explain,
withAllowedActions: this.state.explain, // allowedActions are currently not used for anything on the UI and added only in `explain` mode
starred: this.state.starred,
deleted: this.state.deleted,
};
// Only dashboards have additional properties
@@ -243,6 +247,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
query: this.state.query,
tagCount: this.state.tag?.length,
includePanels: this.state.includePanels,
deleted: this.state.deleted,
};
reportSearchQueryInteraction(this.state.eventTrackingNamespace, trackingInfo);
@@ -294,6 +299,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
query: this.state.query,
tagCount: this.state.tag?.length,
includePanels: this.state.includePanels,
deleted: this.state.deleted,
});
};
@@ -308,6 +314,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
query: this.state.query,
tagCount: this.state.tag?.length,
includePanels: this.state.includePanels,
deleted: this.state.deleted,
});
};
}

View File

@@ -101,6 +101,7 @@ export interface SearchState {
folderUid?: string;
includePanels?: boolean;
eventTrackingNamespace: EventTrackingNamespace;
deleted: boolean;
}
export type OnToggleChecked = (item: DashboardViewItem) => void;

View File

@@ -439,7 +439,7 @@ export function getAppRoutes(): RouteDescriptor[] {
path: '/dashboard/recentlyDeleted',
roles: () => contextSrv.evaluatePermission([AccessControlAction.DashboardsDelete]),
component: SafeDynamicImport(
() => import(/* webpackChunkName: "TrashPage" */ '../features/manage-dashboards/RecentlyDeletedPage')
() => import(/* webpackChunkName: "RecentlyDeletedPage" */ 'app/features/manage-dashboards/RecentlyDeletedPage')
),
},
{

View File

@@ -1478,6 +1478,11 @@
},
"query-editor-not-exported": "Data source plugin does not export any Query Editor component"
},
"recentlyDeleted": {
"filter": {
"placeholder": "Search for dashboards"
}
},
"refresh-picker": {
"aria-label": {
"choose-interval": "Auto refresh turned off. Choose refresh time interval",

View File

@@ -1478,6 +1478,11 @@
},
"query-editor-not-exported": "Đäŧä şőūřčę pľūģįʼn đőęş ʼnőŧ ęχpőřŧ äʼny Qūęřy Ēđįŧőř čőmpőʼnęʼnŧ"
},
"recentlyDeleted": {
"filter": {
"placeholder": "Ŝęäřčĥ ƒőř đäşĥþőäřđş"
}
},
"refresh-picker": {
"aria-label": {
"choose-interval": "Åūŧő řęƒřęşĥ ŧūřʼnęđ őƒƒ. Cĥőőşę řęƒřęşĥ ŧįmę įʼnŧęřväľ",