Files
grafana/public/app/features/browse-dashboards/components/SearchView.tsx
Tobias Skarhed fb1acb45a2 Search: Announce to screen reader when query returns no result (#87382)
* Add alert role to not-found variant

* Provide role prop and apply for two cases
2024-05-22 14:15:56 +02:00

134 lines
4.3 KiB
TypeScript

import React, { useCallback } from 'react';
import { DataFrameView, toDataFrame } from '@grafana/data';
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 { useDispatch, useSelector } from 'app/types';
import { setAllSelection, setItemSelectionState, useHasSelection } from '../state';
interface SearchViewProps {
height: number;
width: number;
canSelect: boolean;
}
const NUM_PLACEHOLDER_ROWS = 50;
const initialLoadingView = {
view: new DataFrameView(
toDataFrame({
fields: [
{ name: 'uid', display: true, values: Array(NUM_PLACEHOLDER_ROWS).fill(null) },
{ name: 'kind', display: true, values: Array(NUM_PLACEHOLDER_ROWS).fill('dashboard') },
{ name: 'name', display: true, values: Array(NUM_PLACEHOLDER_ROWS).fill('') },
{ name: 'location', display: true, values: Array(NUM_PLACEHOLDER_ROWS).fill('') },
{ name: 'tags', display: true, values: Array(NUM_PLACEHOLDER_ROWS).fill([]) },
],
meta: {
custom: {
locationInfo: [],
},
},
})
),
loadMoreItems: () => Promise.resolve(),
// this is key and controls whether to show the skeleton in generateColumns
isItemLoaded: () => false,
totalRows: NUM_PLACEHOLDER_ROWS,
};
export function SearchView({ width, height, canSelect }: 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;
const selectionChecker = useCallback(
(kind: string | undefined, uid: string): boolean => {
if (!kind) {
return false;
}
// Currently, this indicates _some_ items are selected, not nessicarily all are
// selected.
if (kind === '*' && uid === '*') {
return hasSelection;
} else if (kind === '*') {
// Unsure how this case can happen
return false;
}
return selectedItems[assertDashboardViewItemKind(kind)][uid] ?? false;
},
[selectedItems, hasSelection]
);
const clearSelection = useCallback(() => {
dispatch(setAllSelection({ isSelected: false, folderUID: undefined }));
}, [dispatch]);
const handleItemSelectionChange = useCallback(
(kind: string, uid: string) => {
const newIsSelected = !selectionChecker(kind, uid);
dispatch(
setItemSelectionState({ item: { kind: assertDashboardViewItemKind(kind), uid }, isSelected: newIsSelected })
);
},
[selectionChecker, dispatch]
);
if (value.totalRows === 0) {
return (
<div style={{ width }}>
<EmptyState
button={
<Button variant="secondary" onClick={stateManager.onClearSearchAndFilters}>
<Trans i18nKey="browse-dashboards.no-results.clear">Clear search and filters</Trans>
</Button>
}
message={t('browse-dashboards.no-results.text', 'No results found for your query')}
variant="not-found"
role="alert"
/>
</div>
);
}
const props: SearchResultsProps = {
response: value,
selection: canSelect ? selectionChecker : undefined,
selectionToggle: canSelect ? handleItemSelectionChange : undefined,
clearSelection,
width: width,
height: height,
onTagSelected: stateManager.onAddTag,
keyboardEvents,
onDatasourceChange: searchState.datasource ? stateManager.onDatasourceChange : undefined,
onClickItem: stateManager.onSearchItemClicked,
};
return <SearchResultsTable {...props} />;
}
function assertDashboardViewItemKind(kind: string): DashboardViewItemKind {
switch (kind) {
case 'folder':
return 'folder';
case 'dashboard':
return 'dashboard';
case 'panel':
return 'panel';
}
throw new Error('Unsupported kind' + kind);
}