From 161c26098ec039c9129b77fba9f117ff8bfa6457 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Sat, 5 Aug 2023 19:57:24 -0500 Subject: [PATCH 01/12] Create reusable `FilterMenu` with advanced options Instead of creating a new filter/search with an advanced filtering menu for each item, this `FilterMenu` component, along with the `Filtering` class can be used in each case. Also modified the `Filtering` class so that we never need to create a `filterSettings = { "name:": "x", "create_time>": "2023" }` object and can instead just use: `filters = { name: "x", create_time_gt: "2023" }` --- client/src/components/Common/FilterMenu.vue | 394 ++++++++++++++++++ .../src/components/Common/FilterMenuRadio.vue | 42 ++ .../History/Content/model/StatesInfo.vue | 22 +- .../History/CurrentHistory/HistoryPanel.vue | 25 +- .../src/components/History/HistoryFilters.js | 81 +++- .../History/HistoryPublishedList.vue | 118 ++---- .../History/Modals/SelectorModal.vue | 57 ++- client/src/stores/historyStore.ts | 2 +- client/src/style/scss/ui.scss | 4 - client/src/utils/filtering.ts | 185 ++++---- 10 files changed, 676 insertions(+), 254 deletions(-) create mode 100644 client/src/components/Common/FilterMenu.vue create mode 100644 client/src/components/Common/FilterMenuRadio.vue diff --git a/client/src/components/Common/FilterMenu.vue b/client/src/components/Common/FilterMenu.vue new file mode 100644 index 000000000000..4d1c8dbe538a --- /dev/null +++ b/client/src/components/Common/FilterMenu.vue @@ -0,0 +1,394 @@ + + + diff --git a/client/src/components/Common/FilterMenuRadio.vue b/client/src/components/Common/FilterMenuRadio.vue new file mode 100644 index 000000000000..7acffeb6b774 --- /dev/null +++ b/client/src/components/Common/FilterMenuRadio.vue @@ -0,0 +1,42 @@ + + + diff --git a/client/src/components/History/Content/model/StatesInfo.vue b/client/src/components/History/Content/model/StatesInfo.vue index c12f5644326b..43d3a718af35 100644 --- a/client/src/components/History/Content/model/StatesInfo.vue +++ b/client/src/components/History/Content/model/StatesInfo.vue @@ -1,28 +1,15 @@ diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue index 601f039c92e9..4609f3d2365e 100644 --- a/client/src/components/History/CurrentHistory/HistoryPanel.vue +++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue @@ -27,12 +27,15 @@ @dragover.prevent @dragleave.prevent="onDragLeave"> - + :search-error="searchError" + :show-advanced.sync="showAdvanced" />
@@ -121,7 +124,7 @@ :selected="isSelected(item)" :selectable="showSelection" :filterable="filterable" - @tag-click="onTagClick" + @tag-click="updateFilterVal('tag', $event)" @tag-change="onTagChange" @toggleHighlights="updateFilterVal('related', item.hid)" @update:expand-dataset="setExpanded(item, $event)" @@ -140,11 +143,12 @@ diff --git a/client/src/components/History/CurrentHistory/HistoryFilters/HistoryFiltersDefault.vue b/client/src/components/History/CurrentHistory/HistoryFilters/HistoryFiltersDefault.vue deleted file mode 100644 index 4eed0b96ab6a..000000000000 --- a/client/src/components/History/CurrentHistory/HistoryFilters/HistoryFiltersDefault.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - diff --git a/client/src/components/History/HistoryFilters.js b/client/src/components/History/HistoryFilters.js index fbfb3d67e219..49079ef62355 100644 --- a/client/src/components/History/HistoryFilters.js +++ b/client/src/components/History/HistoryFilters.js @@ -26,9 +26,7 @@ export const validFilters = { related: { placeholder: "related", type: Number, handler: equals("related"), menuItem: true }, hid: { placeholder: "index", type: Number, handler: equals("hid"), isRangeInput: true, menuItem: true }, hid_ge: { handler: compare("hid", "ge"), menuItem: false }, - hid_gt: { handler: compare("hid", "gt"), menuItem: false }, hid_le: { handler: compare("hid", "le"), menuItem: false }, - hid_lt: { handler: compare("hid", "lt"), menuItem: false }, create_time: { placeholder: "creation time", type: Date, @@ -37,19 +35,17 @@ export const validFilters = { menuItem: true, }, create_time_ge: { handler: compare("create_time", "ge", toDate), menuItem: false }, - create_time_gt: { handler: compare("create_time", "gt", toDate), menuItem: false }, create_time_le: { handler: compare("create_time", "le", toDate), menuItem: false }, - create_time_lt: { handler: compare("create_time", "lt", toDate), menuItem: false }, deleted: { placeholder: "Deleted", - type: Boolean, + type: "Radio", options: options, handler: equals("deleted", "deleted", toBool), menuItem: true, }, visible: { placeholder: "Visible", - type: Boolean, + type: "Radio", options: options, handler: equals("visible", "visible", toBool), menuItem: true, diff --git a/client/src/components/History/HistoryPublishedList.vue b/client/src/components/History/HistoryPublishedList.vue index 897ede63fe8d..c54b6801a32a 100644 --- a/client/src/components/History/HistoryPublishedList.vue +++ b/client/src/components/History/HistoryPublishedList.vue @@ -28,9 +28,7 @@ const validFilters = { menuItem: true, }, update_time_ge: { handler: compare("update_time", "ge", toDate), menuItem: false }, - update_time_gt: { handler: compare("update_time", "gt", toDate), menuItem: false }, update_time_le: { handler: compare("update_time", "le", toDate), menuItem: false }, - update_time_lt: { handler: compare("update_time", "lt", toDate), menuItem: false }, }; const filters = new Filtering(validFilters, false); diff --git a/client/src/components/History/Modals/SelectorModal.vue b/client/src/components/History/Modals/SelectorModal.vue index b40046fdf74d..8c1b4cf9088f 100644 --- a/client/src/components/History/Modals/SelectorModal.vue +++ b/client/src/components/History/Modals/SelectorModal.vue @@ -33,9 +33,7 @@ const validFilters = { menuItem: true, }, update_time_ge: { handler: compare("update_time", "ge", toDate), menuItem: false }, - update_time_gt: { handler: compare("update_time", "gt", toDate), menuItem: false }, update_time_le: { handler: compare("update_time", "le", toDate), menuItem: false }, - update_time_lt: { handler: compare("update_time", "lt", toDate), menuItem: false }, }; const HistoriesFilters = new Filtering(validFilters, false); diff --git a/client/src/utils/filtering.ts b/client/src/utils/filtering.ts index a6cd90e6ab8f..162e19b8f99e 100644 --- a/client/src/utils/filtering.ts +++ b/client/src/utils/filtering.ts @@ -38,16 +38,20 @@ interface RadioOption { value: string | number | boolean | Date | undefined; } -/** A ValidFilter for the Filtering class, with its properties */ +/** A ValidFilter with a `handler` for the `Filtering` class, + * and remaining properties for the `FilterMenu` component + * */ export type ValidFilter = { /** The `FilterMenu` input field/tooltip/label placeholder */ placeholder?: string; /** The data type of the `FilterMenu` input field */ - type?: typeof String | typeof Number | typeof Boolean | typeof Date; + type?: typeof String | typeof Number | typeof Boolean | "Radio" | typeof Date; /** The handler function for this filter */ handler: HandlerReturn; - /** Is this an filter to include as a field in `FilterMenu`? */ - menuItem?: boolean; + /** Is this a filter to include as a field in `FilterMenu`? + * (if `false` the filter is still valid for the search bar `filterText`) + */ + menuItem: boolean; /** Is there a datalist of values for this field */ datalist?: string[]; /** Is this a `FilterMenu` range filter? @@ -56,7 +60,7 @@ export type ValidFilter = { */ isRangeInput?: boolean; /** The help info component to append to the `FilterMenu` input field */ - helpInfo?: DefineComponent | HTMLElement | string; + helpInfo?: DefineComponent | string; /** The radio options if filter is a `FilterMenu` radio input */ options?: RadioOption[]; }; @@ -210,6 +214,36 @@ export default class Filtering { this.validFilters = validFilters; this.useDefaultFilters = useDefaultFilters; this.validAliases = validAliases || defaultValidAliases; + // Add `name` filter if not present + if (this.validFilters["name"] === undefined) { + this.validFilters["name"] = { + handler: contains("name"), + menuItem: false, + }; + } + this.addRangedFiltersIfNotPresent(); + } + + /** For `FilterMenu` validFilters, if `isRangeInput`, then adds handlers + * for the `lt` & `gt` filters if not included in `validFilters` already + */ + addRangedFiltersIfNotPresent() { + Object.entries(this.validFilters).forEach(([key, filter]) => { + if (filter.isRangeInput) { + if (this.validFilters[`${key}_gt`] === undefined) { + this.validFilters[`${key}_gt`] = { + handler: compare(key, "gt"), + menuItem: false, + }; + } + if (this.validFilters[`${key}_lt`] === undefined) { + this.validFilters[`${key}_lt`] = { + handler: compare(key, "lt"), + menuItem: false, + }; + } + } + }); } /** Returns true if default filter values are not changed From 259cc1e1d152a32033ce0524cca764ffcafb7a19 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Tue, 8 Aug 2023 11:17:33 -0500 Subject: [PATCH 03/12] refactor creation of `dateKeys` and `helpModals` in `FilterMenu` --- client/src/components/Common/FilterMenu.vue | 18 +++++------------- client/src/utils/filtering.ts | 8 ++++---- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/client/src/components/Common/FilterMenu.vue b/client/src/components/Common/FilterMenu.vue index 43792867d77d..77b59bfe286e 100644 --- a/client/src/components/Common/FilterMenu.vue +++ b/client/src/components/Common/FilterMenu.vue @@ -68,11 +68,7 @@ const filters = computed(() => Object.fromEntries(props.filterClass.getFiltersFo const identifier = kebabCase(props.name); // all filters that have help info -const helpEntries: [string, any][] = Object.entries(validFilters.value).filter( - ([, value]) => (value as ValidFilter).helpInfo !== undefined -); -const helpClass: { [k: string]: ValidFilter } = Object.fromEntries(helpEntries); -const helpKeys = Object.keys(helpClass); +const helpKeys = Object.keys(validFilters.value).filter((key) => validFilters.value[key]?.helpInfo !== undefined); const defaultHelpModalsVal = helpKeys.reduce((acc: { [k: string]: boolean }, item: string) => { acc[item] = false; return acc; @@ -91,17 +87,13 @@ const showHelp = ref(false); * (This was done to make the datepickers store values in the `filters` object) */ const dateFilters: Ref<{ [key: string]: string }> = ref({}); -const dateEntries: [string, any][] = Object.entries(validFilters.value).filter( - ([, value]) => (value as ValidFilter).type == Date -); -const dateClass: { [k: string]: ValidFilter } = Object.fromEntries(dateEntries); -const dateKeys = Object.keys(dateClass); -for (const key in dateClass) { - if (dateClass[key]?.isRangeInput) { +const dateKeys = Object.keys(validFilters.value).filter((key) => validFilters.value[key]?.type == Date); +dateKeys.forEach((key: string) => { + if (validFilters.value[key]?.isRangeInput) { dateKeys.push(key + "_lt"); dateKeys.push(key + "_gt"); } -} +}); watch( () => filters.value, (newFilters: { [k: string]: any }) => { diff --git a/client/src/utils/filtering.ts b/client/src/utils/filtering.ts index 162e19b8f99e..22fd733117e2 100644 --- a/client/src/utils/filtering.ts +++ b/client/src/utils/filtering.ts @@ -548,10 +548,10 @@ export default class Filtering { if (!(key in this.validFilters)) { console.error(`Invalid filter ${key}`); } else { - const filterAttribute = this.validFilters[key]?.handler.attribute; - const filterHandler = this.validFilters[key]?.handler.handler; - const itemValue = filterAttribute && item[filterAttribute]; - if (itemValue === undefined || (filterHandler && itemValue && !filterHandler(itemValue, filterValue))) { + const filterAttribute = this.validFilters[key]!.handler.attribute; + const filterHandler = this.validFilters[key]!.handler.handler; + const itemValue = item[filterAttribute]; + if (itemValue === undefined || !filterHandler(itemValue, filterValue)) { return false; } } From 22acc06e0dbb6f256c362947bed783c741cc13f0 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Thu, 10 Aug 2023 14:36:35 -0500 Subject: [PATCH 04/12] use `FilterMenu` in `ToolSearch` and `WorkflowBox` Add functionailty for 3 types of menus. E.g.s: - `HistoryPanel` has default `linked` menu i.e.: `filters` object is reactive to `filterText` - `ToolSearch` has `separate` menu (not reactive, since `filterText` is for side panel search and menu is for advanced search in center panel) - `WorkflowBox` has a `standalone` menu (no `filterText` --- client/src/components/Common/FilterMenu.vue | 146 +++++------- .../components/Common/FilterMenuBoolean.vue | 52 +++++ .../src/components/Common/FilterMenuRadio.vue | 42 ---- .../History/CurrentHistory/HistoryPanel.vue | 1 + .../src/components/History/HistoryFilters.js | 17 +- .../History/HistoryPublishedList.vue | 3 +- .../History/Modals/SelectorModal.vue | 3 +- .../components/Panels/Common/ToolSearch.vue | 219 +++++++++--------- client/src/components/Panels/WorkflowBox.vue | 89 ++++++- client/src/components/Panels/utilities.js | 18 -- .../src/components/Workflow/WorkflowList.vue | 2 +- .../components/Workflow/WorkflowSearch.vue | 78 ------- client/src/utils/filtering.ts | 89 ++++--- 13 files changed, 371 insertions(+), 388 deletions(-) create mode 100644 client/src/components/Common/FilterMenuBoolean.vue delete mode 100644 client/src/components/Common/FilterMenuRadio.vue delete mode 100644 client/src/components/Workflow/WorkflowSearch.vue diff --git a/client/src/components/Common/FilterMenu.vue b/client/src/components/Common/FilterMenu.vue index 77b59bfe286e..d529aab49ce4 100644 --- a/client/src/components/Common/FilterMenu.vue +++ b/client/src/components/Common/FilterMenu.vue @@ -2,11 +2,11 @@ import { capitalize, kebabCase } from "lodash"; import { computed, type Ref, ref, watch } from "vue"; -import type { Alias, ValidFilter } from "@/utils/filtering"; import type Filtering from "@/utils/filtering"; -import { getOperatorForAlias } from "@/utils/filtering"; +import { type Alias, getOperatorForAlias } from "@/utils/filtering"; -import FilterMenuRadio from "@/components/Common/FilterMenuRadio.vue"; +import DelayedInput from "@/components/Common/DelayedInput.vue"; +import FilterMenuBoolean from "@/components/Common/FilterMenuBoolean.vue"; interface BackendFilterError { err_msg: string; @@ -22,48 +22,46 @@ interface BackendFilterError { const props = withDefaults( defineProps<{ + /** A name for the menu */ name?: string; + /** A placeholder for the main search input */ placeholder?: string; + /** The `Filtering` class to use */ filterClass: Filtering; - filterText: string; + /** The current filter text in the main field */ + filterText?: string; + /** Whether a help `