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
import { TableColumnSizingOptions } from "@fluentui/react-components";
import { loadState, saveStateDebounced } from "Shared/AppStatePersistenceUtility";
import { loadState, saveState, saveStateDebounced } from "Shared/AppStatePersistenceUtility";
import { userContext } from "UserContext";
// Component states
@ -72,3 +72,36 @@ export const saveColumnSizes = (databaseName: string, containerName: string, col
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 {
DocumentsTabStateData,
readDocumentsTabState,
readFilterHistory,
saveDocumentsTabState,
saveFilterHistory,
} from "Explorer/Tabs/DocumentsTabV2/DocumentsTabStateUtil";
import { getPlatformTheme } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode";
@ -425,6 +427,24 @@ export const buildQuery = (
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 interface IDocumentsTabComponentProps {
isPreferredApiMongoDB: boolean;
@ -439,6 +459,9 @@ export interface IDocumentsTabComponentProps {
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 const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabComponentProps> = ({
isPreferredApiMongoDB,
@ -496,6 +519,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
// For Mongo only
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);
useEffect(() => {
@ -521,8 +549,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
}
}, [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 = {
enabled: true,
visible: true,
@ -1377,7 +1403,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
return partitionKey;
};
lastFilterContents = ['{"id":"foo"}', "{ qty: { $gte: 20 } }"];
partitionKeyProperties = partitionKeyProperties?.map((partitionKeyProperty, i) => {
if (partitionKeyProperty && ~partitionKeyProperty.indexOf(`"`)) {
partitionKeyProperty = partitionKeyProperty.replace(/["]+/g, "");
@ -1654,6 +1679,20 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
}
// ***************** 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(
(applyFilterButtonPressed: boolean): void => {
// clear documents grid
@ -1734,7 +1773,10 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
/>
<datalist id="filtersList">
{lastFilterContents.map((filter) => (
{addStringsNoDuplicate(
lastFilterContents,
isPreferredApiMongoDB ? defaultMongoFilters : defaultSqlFilters,
).map((filter) => (
<option key={filter} value={filter} />
))}
</datalist>
@ -1743,7 +1785,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
<Button
appearance="primary"
style={filterButtonStyle}
onClick={() => refreshDocumentsGrid(true)}
onClick={onApplyFilterClick}
disabled={!applyFilterButton.enabled}
aria-label="Apply filter"
tabIndex={0}

View File

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

View File

@ -50,40 +50,26 @@ export const saveStateDebounced = (path: StorePath, state: unknown, debounceDela
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 {
[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
* @param path
*/
const createKeyFromPath = (path: StorePath): string => {
let key = `/${path.componentName}`;
["subComponentName", "globalAccountName", "databaseName", "containerName"].forEach((segment) => {
const segmentValue = (path as any)[segment];
let key = `/${path.componentName}`; // ComponentName is always there
orderedPathSegments.forEach((segment) => {
const segmentValue = path[segment as keyof StorePath];
key += `/${segmentValue !== undefined ? segmentValue : ""}`;
});
return key;