mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 23:16:56 +00:00
Save filters
This commit is contained in:
parent
96b2ef728a
commit
9aa55aa4e5
@ -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,
|
||||
);
|
||||
};
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
>
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user