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:
@@ -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,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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')
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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äľ",
|
||||||
|
|||||||
Reference in New Issue
Block a user