From a556415f2631f169e77d928254a2f24f1fa1386d Mon Sep 17 00:00:00 2001 From: bgaeddert Date: Tue, 21 Apr 2026 05:19:19 -0500 Subject: [PATCH] feat: add sorting functionality to DataViewPanelTable and update QueryEditor context --- .../collectionView/collectionViewContext.ts | 1 + .../components/queryEditor/QueryEditor.tsx | 3 + .../resultsTab/DataViewPanelTable.tsx | 59 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/webviews/documentdb/collectionView/collectionViewContext.ts b/src/webviews/documentdb/collectionView/collectionViewContext.ts index 435396ce6..b7cc13a2a 100644 --- a/src/webviews/documentdb/collectionView/collectionViewContext.ts +++ b/src/webviews/documentdb/collectionView/collectionViewContext.ts @@ -98,6 +98,7 @@ export type CollectionViewContextType = { limit: number; }; setJsonSchema(schema: object): Promise; //monacoEditor.languages.json.DiagnosticsOptions, but we don't want to import monacoEditor here + setSort: (sort: string) => void; }; isAiRowVisible: boolean; // Controls visibility of the AI prompt row in QueryEditor queryInsights: QueryInsightsState; // Query insights state for progressive loading diff --git a/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx b/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx index 7b6d7e8ec..33cb45976 100644 --- a/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx +++ b/src/webviews/documentdb/collectionView/components/queryEditor/QueryEditor.tsx @@ -91,6 +91,9 @@ export const QueryEditor = ({ onExecuteRequest }: QueryEditorProps): JSX.Element * any pending schema update is cancelled before a new one begins, guaranteeing * a clean, predictable state and allowing the Monaco worker to initialize correctly. */ + setSort: (sort: string) => { + setSortValue(sort); + }, setJsonSchema: async (schema) => { // Use the ref to cancel the previous operation if (schemaAbortControllerRef.current) { diff --git a/src/webviews/documentdb/collectionView/components/resultsTab/DataViewPanelTable.tsx b/src/webviews/documentdb/collectionView/components/resultsTab/DataViewPanelTable.tsx index 4abb0becc..5453e2207 100644 --- a/src/webviews/documentdb/collectionView/components/resultsTab/DataViewPanelTable.tsx +++ b/src/webviews/documentdb/collectionView/components/resultsTab/DataViewPanelTable.tsx @@ -72,9 +72,62 @@ export function DataViewPanelTable({ liveHeaders, liveData, handleStepIn }: Prop field: header, minWidth: 100, formatter: cellFormatter, + sortable: true, + // No-op comparer: server-side sort replaces the dataset, so we don't want + // SlickGrid's default field-value comparer to scramble our typed cell objects. + sorter: () => 0, }; }); + // Tracks the current sort state for tri-state cycling (asc -> desc -> none). + const sortStateRef = useRef<{ field: string | null; direction: 'asc' | 'desc' | null }>({ + field: null, + direction: null, + }); + + const onSort = React.useCallback( + (event: CustomEvent<{ args: { sortCol?: { field?: string }; sortAsc?: boolean } }>): void => { + const sortCol = event.detail.args.sortCol; + const field = sortCol?.field; + if (!field) { + return; + } + const newAsc = event.detail.args.sortAsc !== false; + const prev = sortStateRef.current; + + // Tri-state cycle: when SlickGrid wraps from desc back to asc on the same column, clear it. + let nextField: string | null; + let nextDir: 'asc' | 'desc' | null; + if (prev.field === field && prev.direction === 'desc' && newAsc) { + nextField = null; + nextDir = null; + // Clear SlickGrid's sort indicator + gridRef.current?.sortService?.clearSorting?.(); + } else { + nextField = field; + nextDir = newAsc ? 'asc' : 'desc'; + } + sortStateRef.current = { field: nextField, direction: nextDir }; + + const sortJson = nextField ? `{ "${nextField}": ${nextDir === 'asc' ? 1 : -1} }` : '{ }'; + + // Update the Sort input in the QueryEditor so the user sees what was applied. + currentContext.queryEditor?.setSort?.(sortJson); + + // Trigger a refresh by updating activeQuery (mirrors the executeQuery flow). + setCurrentContext((prevCtx) => ({ + ...prevCtx, + activeQuery: { + ...prevCtx.activeQuery, + sort: sortJson, + pageNumber: 1, + executionIntent: 'refresh', + }, + })); + }, + [currentContext.queryEditor, setCurrentContext], + ); + // Update grid columns ref whenever they change React.useEffect(() => { gridColumnsRef.current = gridColumns; @@ -139,6 +192,9 @@ export function DataViewPanelTable({ liveHeaders, liveData, handleStepIn }: Prop enableCellNavigation: true, enableTextSelectionOnCells: true, + enableSorting: true, + multiColumnSort: false, + enableCheckboxSelector: false, // todo: [post MVP] this is failing, it looks like it happens when we're defining columns after the grid has been created.. we're deleting the 'checkbox' column. we can work around it, but it needs a bit more attention to get it done right. enableRowSelection: true, multiSelect: true, @@ -215,6 +271,9 @@ export function DataViewPanelTable({ liveHeaders, liveData, handleStepIn }: Prop columns={gridColumns} dataset={liveData} onDblClick={(event) => onCellDblClick(event)} + onSort={(event) => + onSort(event as CustomEvent<{ args: { sortCol?: { field?: string }; sortAsc?: boolean } }>) + } onSelectedRowsChanged={debounce( (event: { detail: { eventData: unknown; args: OnSelectedRowsChangedEventArgs } }) => onSelectedRowsChanged(event.detail.eventData, event.detail.args),