Line data Source code
1 : import * as React from 'react';
2 :
3 : import type { QueryExecutionState } from '../../query/query_execution_state.js';
4 : import { TabHeader, formatRowCountDetail } from '../../session/tab_header.js';
5 : import { QueryResultView } from './query_result_view.js';
6 : import { TableColumnHeader } from './data_table_cell.js';
7 : import { classNames } from '../../utils/classnames.js';
8 : import { SET_CROSS_FILTERS } from '../../compute/computation_state.js';
9 : import { CrossFilters } from '../../compute/cross_filters.js';
10 : import { useComputationRegistry } from '../../compute/computation_registry.js';
11 : import { ButtonSize, ButtonVariant, IconButton } from '../../ui/foundations/button.js';
12 : import { SymbolIcon } from '../../ui/foundations/symbol_icon.js';
13 : import * as styles from './query_result_details.module.css';
14 : import { QueryResultToolbar, useQueryResultRowCounts } from './query_result_toolbar.js';
15 : import { QueryResultDownloadButton } from './query_result_download_button.js';
16 : import { QueryResultActionMenu } from './query_result_action_menu.js';
17 : import { formatResultDetail } from './query_result_header.js';
18 :
19 : export interface QueryResultDetailsDependencies {
20 : QueryResultToolbar: typeof QueryResultToolbar;
21 : QueryResultView: typeof QueryResultView;
22 : useComputationRegistry(): [
23 : ReturnType<typeof useComputationRegistry>[0],
24 : ReturnType<typeof useComputationRegistry>[1],
25 : ];
26 : useQueryResultRowCounts: typeof useQueryResultRowCounts;
27 : QueryResultDownloadButton: typeof QueryResultDownloadButton;
28 : QueryResultActionMenu: typeof QueryResultActionMenu;
29 : }
30 :
31 1 : const DEFAULT_DEPENDENCIES: QueryResultDetailsDependencies = {
32 1 : QueryResultToolbar,
33 1 : QueryResultView,
34 1 : useComputationRegistry: () => {
35 0 : const [state, dispatch] = useComputationRegistry();
36 0 : return [state, dispatch];
37 0 : },
38 1 : useQueryResultRowCounts,
39 1 : QueryResultDownloadButton,
40 1 : QueryResultActionMenu,
41 1 : };
42 :
43 : interface Props {
44 : query: QueryExecutionState;
45 : debugMode: boolean;
46 : actions?: React.ReactNode;
47 : fitHeight?: boolean;
48 : maxHeight?: number;
49 : columnHeader?: TableColumnHeader;
50 : dependencies?: QueryResultDetailsDependencies;
51 : }
52 :
53 1 : const HEADER_HEIGHT = 32;
54 1 : const ClearFiltersIcon = SymbolIcon('filter_remove_24');
55 :
56 1 : export const QueryResultDetails: React.FC<Props> = ({ query, debugMode, actions, fitHeight, maxHeight, columnHeader, dependencies = DEFAULT_DEPENDENCIES }) => {
57 1 : const searchRows = dependencies.useQueryResultRowCounts(query);
58 1 : const [computationState, computationDispatch] = dependencies.useComputationRegistry();
59 1 : const QueryResultToolbarComponent = dependencies.QueryResultToolbar;
60 1 : const QueryResultViewComponent = dependencies.QueryResultView;
61 1 : const QueryResultDownloadButtonComponent = dependencies.QueryResultDownloadButton;
62 1 : const QueryResultActionMenuComponent = dependencies.QueryResultActionMenu;
63 1 : const tableComputation = computationState.tableComputations[query.queryId] ?? null;
64 1 : const hasCrossFilters = tableComputation != null
65 1 : && Object.keys(tableComputation.crossFilters.columnFilters).length > 0;
66 1 : const clearCrossFilters = React.useCallback(() => {
67 1 : computationDispatch({ type: SET_CROSS_FILTERS, value: [query.queryId, new CrossFilters()] });
68 1 : }, [computationDispatch, query.queryId]);
69 1 : return (
70 1 : <div className={classNames(styles.root, { [styles.root_fit_height]: fitHeight })}>
71 1 : <TabHeader
72 1 : title="Query Results"
73 1 : detail={formatResultDetail(searchRows.matchingRows == null
74 1 : ? formatRowCountDetail(searchRows.totalRows)
75 1 : : `${searchRows.matchingRows} of ${searchRows.currentRows ?? searchRows.totalRows ?? 0} rows`, query.resultTable)}
76 1 : actions={(
77 1 : <>
78 1 : <QueryResultToolbarComponent query={query} />
79 1 : <IconButton
80 1 : variant={ButtonVariant.Invisible}
81 1 : size={ButtonSize.Small}
82 1 : aria-label="Clear all cross-filters"
83 1 : data-testid="clear-cross-filters"
84 1 : onClick={clearCrossFilters}
85 1 : disabled={!hasCrossFilters}
86 : >
87 1 : <ClearFiltersIcon size={16} />
88 1 : </IconButton>
89 1 : <QueryResultDownloadButtonComponent query={query} />
90 1 : <QueryResultActionMenuComponent query={query} />
91 1 : {actions}
92 1 : </>
93 : )}
94 1 : />
95 1 : <div className={classNames(styles.body, { [styles.body_fit_height]: fitHeight })}>
96 1 : <QueryResultViewComponent
97 1 : query={query}
98 1 : debugMode={debugMode}
99 1 : compact
100 1 : fitHeight={fitHeight}
101 1 : maxHeight={maxHeight == null ? undefined : Math.max(0, maxHeight - HEADER_HEIGHT)}
102 1 : columnHeader={columnHeader}
103 1 : />
104 1 : </div>
105 1 : </div>
106 : );
107 1 : };
|