Save filters

This commit is contained in:
Laurent Nguyen 2024-07-22 16:21:26 +02:00
parent 96b2ef728a
commit 9aa55aa4e5
4 changed files with 93 additions and 31 deletions

View File

@ -1,7 +1,7 @@
// Definitions of State data // Definitions of State data
import { TableColumnSizingOptions } from "@fluentui/react-components"; import { TableColumnSizingOptions } from "@fluentui/react-components";
import { loadState, saveStateDebounced } from "Shared/AppStatePersistenceUtility"; import { loadState, saveState, saveStateDebounced } from "Shared/AppStatePersistenceUtility";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
// Component states // Component states
@ -72,3 +72,36 @@ export const saveColumnSizes = (databaseName: string, containerName: string, col
columnSizesMap, columnSizesMap,
); );
}; };
const filterHistorySubComponentName = "FilterHistory";
export type FilterHistory = string[];
export const readFilterHistory = (databaseName: string, containerName: string): FilterHistory => {
const globalAccountName = userContext.databaseAccount?.name;
// TODO what if databaseAccount doesn't exist?
const state = loadState({
globalAccountName,
databaseName,
containerName,
componentName: ComponentName,
subComponentName: filterHistorySubComponentName,
}) as FilterHistory;
return state || [];
};
export const saveFilterHistory = (databaseName: string, containerName: string, filterHistory: FilterHistory): void => {
const globalAccountName = userContext.databaseAccount?.name;
// TODO what if databaseAccount doesn't exist?
saveState(
{
componentName: ComponentName,
subComponentName: filterHistorySubComponentName,
globalAccountName,
databaseName,
containerName,
},
filterHistory,
);
};

View File

@ -24,7 +24,9 @@ import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/
import { import {
DocumentsTabStateData, DocumentsTabStateData,
readDocumentsTabState, readDocumentsTabState,
readFilterHistory,
saveDocumentsTabState, saveDocumentsTabState,
saveFilterHistory,
} from "Explorer/Tabs/DocumentsTabV2/DocumentsTabStateUtil"; } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabStateUtil";
import { getPlatformTheme } from "Explorer/Theme/ThemeUtil"; import { getPlatformTheme } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
@ -425,6 +427,24 @@ export const buildQuery = (
return QueryUtils.buildDocumentsQuery(filter, partitionKeyProperties, partitionKey); return QueryUtils.buildDocumentsQuery(filter, partitionKeyProperties, partitionKey);
}; };
/**
* Export to expose to unit tests
*
* Add array2 to array1 without duplicates
* @param array1
* @param array2
* @return array1 with array2 added without duplicates
*/
export const addStringsNoDuplicate = (array1: string[], array2: string[]): string[] => {
const result = [...array1];
array2.forEach((item) => {
if (!result.includes(item)) {
result.push(item);
}
});
return result;
};
// Export to expose to unit tests // Export to expose to unit tests
export interface IDocumentsTabComponentProps { export interface IDocumentsTabComponentProps {
isPreferredApiMongoDB: boolean; isPreferredApiMongoDB: boolean;
@ -439,6 +459,9 @@ export interface IDocumentsTabComponentProps {
isTabActive: boolean; isTabActive: boolean;
} }
const defaultSqlFilters = ['WHERE c.id = "foo"', "ORDER BY c._ts DESC", 'WHERE c.id = "foo" ORDER BY c._ts DESC'];
const defaultMongoFilters = ['{"id":"foo"}', "{ qty: { $gte: 20 } }"];
// Export to expose to unit tests // Export to expose to unit tests
export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabComponentProps> = ({ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabComponentProps> = ({
isPreferredApiMongoDB, isPreferredApiMongoDB,
@ -496,6 +519,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
// For Mongo only // For Mongo only
const [continuationToken, setContinuationToken] = useState<string>(undefined); const [continuationToken, setContinuationToken] = useState<string>(undefined);
// User's filter history
const [lastFilterContents, setLastFilterContents] = useState<string[]>(() =>
readFilterHistory(_collection.databaseId, _collection.id()),
);
const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB); const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB);
useEffect(() => { useEffect(() => {
@ -521,8 +549,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} }
}, [documentIds, clickedRowIndex, editorState]); }, [documentIds, clickedRowIndex, editorState]);
let lastFilterContents = ['WHERE c.id = "foo"', "ORDER BY c._ts DESC", 'WHERE c.id = "foo" ORDER BY c._ts DESC'];
const applyFilterButton = { const applyFilterButton = {
enabled: true, enabled: true,
visible: true, visible: true,
@ -1377,7 +1403,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
return partitionKey; return partitionKey;
}; };
lastFilterContents = ['{"id":"foo"}', "{ qty: { $gte: 20 } }"];
partitionKeyProperties = partitionKeyProperties?.map((partitionKeyProperty, i) => { partitionKeyProperties = partitionKeyProperties?.map((partitionKeyProperty, i) => {
if (partitionKeyProperty && ~partitionKeyProperty.indexOf(`"`)) { if (partitionKeyProperty && ~partitionKeyProperty.indexOf(`"`)) {
partitionKeyProperty = partitionKeyProperty.replace(/["]+/g, ""); partitionKeyProperty = partitionKeyProperty.replace(/["]+/g, "");
@ -1654,6 +1679,20 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} }
// ***************** Mongo *************************** // ***************** Mongo ***************************
const onApplyFilterClick = (): void => {
refreshDocumentsGrid(true);
// Remove duplicates, but keep order
if (lastFilterContents.includes(filterContent)) {
lastFilterContents.splice(lastFilterContents.indexOf(filterContent), 1);
}
// Save filter content to local storage
lastFilterContents.unshift(filterContent);
setLastFilterContents([...lastFilterContents]);
saveFilterHistory(_collection.databaseId, _collection.id(), lastFilterContents);
};
const refreshDocumentsGrid = useCallback( const refreshDocumentsGrid = useCallback(
(applyFilterButtonPressed: boolean): void => { (applyFilterButtonPressed: boolean): void => {
// clear documents grid // clear documents grid
@ -1734,7 +1773,10 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
/> />
<datalist id="filtersList"> <datalist id="filtersList">
{lastFilterContents.map((filter) => ( {addStringsNoDuplicate(
lastFilterContents,
isPreferredApiMongoDB ? defaultMongoFilters : defaultSqlFilters,
).map((filter) => (
<option key={filter} value={filter} /> <option key={filter} value={filter} />
))} ))}
</datalist> </datalist>
@ -1743,7 +1785,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
<Button <Button
appearance="primary" appearance="primary"
style={filterButtonStyle} style={filterButtonStyle}
onClick={() => refreshDocumentsGrid(true)} onClick={onApplyFilterClick}
disabled={!applyFilterButton.enabled} disabled={!applyFilterButton.enabled}
aria-label="Apply filter" aria-label="Apply filter"
tabIndex={0} tabIndex={0}

View File

@ -485,6 +485,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
> >
<Split <Split
mode="horizontal" mode="horizontal"
onDragEnd={[Function]}
prefixCls="w-split" prefixCls="w-split"
visiable={true} visiable={true}
> >

View File

@ -50,40 +50,26 @@ export const saveStateDebounced = (path: StorePath, state: unknown, debounceDela
timeoutId = setTimeout(() => saveState(path, state), debounceDelayMs); timeoutId = setTimeout(() => saveState(path, state), debounceDelayMs);
}; };
// Internal stored state
// interface ApplicationState {
// data: GlobalStateData;
// globalAccounts: {
// [globalAccountName: string]: {
// data: GlobalAccountStateData;
// databases: {
// [databaseName: string]: {
// data: DatabaseStateData;
// containers: {
// data: ContainerStateData;
// [containerName: string]: {
// [componentName: string]: BaseStateData;
// };
// };
// };
// };
// };
// };
// }
interface ApplicationState { interface ApplicationState {
[statePath: string]: StateData; [statePath: string]: StateData;
} }
const orderedPathSegments: (keyof StorePath)[] = [
"subComponentName",
"globalAccountName",
"databaseName",
"containerName",
];
/** /**
* /componentName/globalAccountName/databaseName/containerName/ * /componentName/subComponentName/globalAccountName/databaseName/containerName/
* Any of the path segments can be "" except componentName * Any of the path segments can be "" except componentName
* @param path * @param path
*/ */
const createKeyFromPath = (path: StorePath): string => { const createKeyFromPath = (path: StorePath): string => {
let key = `/${path.componentName}`; let key = `/${path.componentName}`; // ComponentName is always there
["subComponentName", "globalAccountName", "databaseName", "containerName"].forEach((segment) => { orderedPathSegments.forEach((segment) => {
const segmentValue = (path as any)[segment]; const segmentValue = path[segment as keyof StorePath];
key += `/${segmentValue !== undefined ? segmentValue : ""}`; key += `/${segmentValue !== undefined ? segmentValue : ""}`;
}); });
return key; return key;