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
|
// 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,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
>
|
>
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user