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
-3
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.", "2"],
[0, 0, 0, "Styles should be written using objects.", "3"] [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": [ "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 />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"], [0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
@@ -135,7 +135,13 @@ const BrowseDashboardsPage = memo(({ match }: Props) => {
<AutoSizer> <AutoSizer>
{({ width, height }) => {({ width, height }) =>
isSearching ? ( 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} /> <BrowseView canSelect={canSelect} width={width} height={height} folderUID={folderUID} />
) )
@@ -5,8 +5,8 @@ import { Button, EmptyState } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization'; import { Trans, t } from 'app/core/internationalization';
import { useKeyNavigationListener } from 'app/features/search/hooks/useSearchKeyboardSelection'; import { useKeyNavigationListener } from 'app/features/search/hooks/useSearchKeyboardSelection';
import { SearchResultsProps, SearchResultsTable } from 'app/features/search/page/components/SearchResultsTable'; import { SearchResultsProps, SearchResultsTable } from 'app/features/search/page/components/SearchResultsTable';
import { useSearchStateManager } from 'app/features/search/state/SearchStateManager'; import { SearchStateManager } from 'app/features/search/state/SearchStateManager';
import { DashboardViewItemKind } from 'app/features/search/types'; import { DashboardViewItemKind, SearchState } from 'app/features/search/types';
import { useDispatch, useSelector } from 'app/types'; import { useDispatch, useSelector } from 'app/types';
import { setAllSelection, setItemSelectionState, useHasSelection } from '../state'; import { setAllSelection, setItemSelectionState, useHasSelection } from '../state';
@@ -15,6 +15,8 @@ interface SearchViewProps {
height: number; height: number;
width: number; width: number;
canSelect: boolean; canSelect: boolean;
searchState: SearchState;
searchStateManager: SearchStateManager;
} }
const NUM_PLACEHOLDER_ROWS = 50; const NUM_PLACEHOLDER_ROWS = 50;
@@ -41,13 +43,18 @@ const initialLoadingView = {
totalRows: NUM_PLACEHOLDER_ROWS, 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 dispatch = useDispatch();
const selectedItems = useSelector((wholeState) => wholeState.browseDashboards.selectedItems); const selectedItems = useSelector((wholeState) => wholeState.browseDashboards.selectedItems);
const hasSelection = useHasSelection(); const hasSelection = useHasSelection();
const { keyboardEvents } = useKeyNavigationListener(); const { keyboardEvents } = useKeyNavigationListener();
const [searchState, stateManager] = useSearchStateManager();
const value = searchState.result ?? initialLoadingView; const value = searchState.result ?? initialLoadingView;
@@ -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 ( return (
<Page navId="dashboards/recentlyDeleted"> <Page navId="dashboards/recentlyDeleted">
<Page.Contents> <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.Contents>
</Page> </Page>
); );
}; });
RecentlyDeletedPage.displayName = 'RecentlyDeletedPage';
export default RecentlyDeletedPage; export default RecentlyDeletedPage;
@@ -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;
}
@@ -10,6 +10,7 @@ interface QueryProps {
query: string; query: string;
tagCount: number; tagCount: number;
includePanels?: boolean; includePanels?: boolean;
deleted: boolean;
} }
export const reportDashboardListViewed = (eventTrackingNamespace: EventTrackingNamespace, query: QueryProps) => { export const reportDashboardListViewed = (eventTrackingNamespace: EventTrackingNamespace, query: QueryProps) => {
@@ -47,5 +48,6 @@ const getQuerySearchContext = (query: QueryProps) => {
tagCount: query.tagCount ?? 0, tagCount: query.tagCount ?? 0,
queryLength: query.query?.length ?? 0, queryLength: query.query?.length ?? 0,
includePanels: query.includePanels ?? false, includePanels: query.includePanels ?? false,
deleted: query.deleted ?? false,
}; };
}; };
@@ -23,6 +23,7 @@ interface APIQuery {
sort?: string; sort?: string;
starred?: boolean; starred?: boolean;
permission?: PermissionLevelString; permission?: PermissionLevelString;
deleted?: boolean;
} }
// Internal object to hold folderId // Internal object to hold folderId
@@ -89,6 +90,7 @@ export class SQLSearcher implements GrafanaSearcher {
sort: query.sort, sort: query.sort,
permission: query.permission, permission: query.permission,
page, page,
deleted: query.deleted,
}, },
query query
); );
@@ -27,6 +27,7 @@ export interface SearchQuery {
from?: number; from?: number;
starred?: boolean; starred?: boolean;
permission?: PermissionLevelString; permission?: PermissionLevelString;
deleted?: boolean;
} }
export interface DashboardQueryResult { export interface DashboardQueryResult {
@@ -25,6 +25,7 @@ export const initialState: SearchState = {
sort: undefined, sort: undefined,
prevSort: undefined, prevSort: undefined,
eventTrackingNamespace: 'dashboard_search', eventTrackingNamespace: 'dashboard_search',
deleted: false,
}; };
export const defaultQueryParams: SearchQueryParams = { export const defaultQueryParams: SearchQueryParams = {
@@ -62,7 +63,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
const prevSort = localStorage.getItem(SEARCH_SELECTED_SORT) ?? undefined; const prevSort = localStorage.getItem(SEARCH_SELECTED_SORT) ?? undefined;
const sort = layout === SearchLayout.List ? stateFromUrl.sort || prevSort : null; const sort = layout === SearchLayout.List ? stateFromUrl.sort || prevSort : null;
stateManager.setState({ this.setState({
...initialState, ...initialState,
...stateFromUrl, ...stateFromUrl,
layout, layout,
@@ -70,6 +71,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
prevSort, prevSort,
folderUid: folderUid, folderUid: folderUid,
eventTrackingNamespace: folderUid ? 'manage_dashboards' : 'dashboard_search', eventTrackingNamespace: folderUid ? 'manage_dashboards' : 'dashboard_search',
deleted: this.state.deleted,
}); });
if (doInitialSearch && this.hasSearchFilters()) { if (doInitialSearch && this.hasSearchFilters()) {
@@ -195,6 +197,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
this.state.starred || this.state.starred ||
this.state.panel_type || this.state.panel_type ||
this.state.sort || this.state.sort ||
this.state.deleted ||
this.state.layout === SearchLayout.List this.state.layout === SearchLayout.List
); );
} }
@@ -210,6 +213,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
explain: this.state.explain, explain: this.state.explain,
withAllowedActions: this.state.explain, // allowedActions are currently not used for anything on the UI and added only in `explain` mode withAllowedActions: this.state.explain, // allowedActions are currently not used for anything on the UI and added only in `explain` mode
starred: this.state.starred, starred: this.state.starred,
deleted: this.state.deleted,
}; };
// Only dashboards have additional properties // Only dashboards have additional properties
@@ -243,6 +247,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
query: this.state.query, query: this.state.query,
tagCount: this.state.tag?.length, tagCount: this.state.tag?.length,
includePanels: this.state.includePanels, includePanels: this.state.includePanels,
deleted: this.state.deleted,
}; };
reportSearchQueryInteraction(this.state.eventTrackingNamespace, trackingInfo); reportSearchQueryInteraction(this.state.eventTrackingNamespace, trackingInfo);
@@ -294,6 +299,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
query: this.state.query, query: this.state.query,
tagCount: this.state.tag?.length, tagCount: this.state.tag?.length,
includePanels: this.state.includePanels, includePanels: this.state.includePanels,
deleted: this.state.deleted,
}); });
}; };
@@ -308,6 +314,7 @@ export class SearchStateManager extends StateManagerBase<SearchState> {
query: this.state.query, query: this.state.query,
tagCount: this.state.tag?.length, tagCount: this.state.tag?.length,
includePanels: this.state.includePanels, includePanels: this.state.includePanels,
deleted: this.state.deleted,
}); });
}; };
} }
+1
View File
@@ -101,6 +101,7 @@ export interface SearchState {
folderUid?: string; folderUid?: string;
includePanels?: boolean; includePanels?: boolean;
eventTrackingNamespace: EventTrackingNamespace; eventTrackingNamespace: EventTrackingNamespace;
deleted: boolean;
} }
export type OnToggleChecked = (item: DashboardViewItem) => void; export type OnToggleChecked = (item: DashboardViewItem) => void;
+1 -1
View File
@@ -439,7 +439,7 @@ export function getAppRoutes(): RouteDescriptor[] {
path: '/dashboard/recentlyDeleted', path: '/dashboard/recentlyDeleted',
roles: () => contextSrv.evaluatePermission([AccessControlAction.DashboardsDelete]), roles: () => contextSrv.evaluatePermission([AccessControlAction.DashboardsDelete]),
component: SafeDynamicImport( component: SafeDynamicImport(
() => import(/* webpackChunkName: "TrashPage" */ '../features/manage-dashboards/RecentlyDeletedPage') () => import(/* webpackChunkName: "RecentlyDeletedPage" */ 'app/features/manage-dashboards/RecentlyDeletedPage')
), ),
}, },
{ {
+5
View File
@@ -1478,6 +1478,11 @@
}, },
"query-editor-not-exported": "Data source plugin does not export any Query Editor component" "query-editor-not-exported": "Data source plugin does not export any Query Editor component"
}, },
"recentlyDeleted": {
"filter": {
"placeholder": "Search for dashboards"
}
},
"refresh-picker": { "refresh-picker": {
"aria-label": { "aria-label": {
"choose-interval": "Auto refresh turned off. Choose refresh time interval", "choose-interval": "Auto refresh turned off. Choose refresh time interval",
@@ -1478,6 +1478,11 @@
}, },
"query-editor-not-exported": "Đäŧä şőūřčę pľūģįʼn đőęş ʼnőŧ ęχpőřŧ äʼny Qūęřy Ēđįŧőř čőmpőʼnęʼnŧ" "query-editor-not-exported": "Đäŧä şőūřčę pľūģįʼn đőęş ʼnőŧ ęχpőřŧ äʼny Qūęřy Ēđįŧőř čőmpőʼnęʼnŧ"
}, },
"recentlyDeleted": {
"filter": {
"placeholder": "Ŝęäřčĥ ƒőř đäşĥþőäřđş"
}
},
"refresh-picker": { "refresh-picker": {
"aria-label": { "aria-label": {
"choose-interval": "Åūŧő řęƒřęşĥ ŧūřʼnęđ őƒƒ. Cĥőőşę řęƒřęşĥ ŧįmę įʼnŧęřväľ", "choose-interval": "Åūŧő řęƒřęşĥ ŧūřʼnęđ őƒƒ. Cĥőőşę řęƒřęşĥ ŧįmę įʼnŧęřväľ",