mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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.", "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"],
|
||||
|
||||
@@ -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} />
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface SearchQuery {
|
||||
from?: number;
|
||||
starred?: boolean;
|
||||
permission?: PermissionLevelString;
|
||||
deleted?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardQueryResult {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ export interface SearchState {
|
||||
folderUid?: string;
|
||||
includePanels?: boolean;
|
||||
eventTrackingNamespace: EventTrackingNamespace;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
export type OnToggleChecked = (item: DashboardViewItem) => void;
|
||||
|
||||
@@ -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')
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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äľ",
|
||||
|
||||
Reference in New Issue
Block a user