diff --git a/client/src/components/Grid/GridList.vue b/client/src/components/Grid/GridList.vue index ada4cd8133a4..5d4f54d286cb 100644 --- a/client/src/components/Grid/GridList.vue +++ b/client/src/components/Grid/GridList.vue @@ -13,6 +13,7 @@ import { FieldKeyHandler, Operation, RowData } from "./configs/types"; import GridOperations from "./GridElements/GridOperations.vue"; import GridSharing from "./GridElements/GridSharing.vue"; import GridText from "./GridElements/GridText.vue"; +import FilterMenu from "@/components/Common/FilterMenu.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue"; //@ts-ignore @@ -46,9 +47,6 @@ const totalRows = ref(0); // loading indicator const loading = ref(true); -// search term -const searchTerm = ref(""); - // current grid configuration const gridConfig = computed(() => { return registry[props.id]; @@ -61,6 +59,11 @@ const isAvailable = computed(() => !loading.value && totalRows.value > 0); const sortBy = ref(gridConfig.value ? gridConfig.value.sortBy : ""); const sortDesc = ref(gridConfig.value ? gridConfig.value.sortDesc : false); +// filtering refs and handlers +const filterText = ref(""); +const searchTerm = ref(""); +const showAdvanced = ref(false); + /** * Request grid data */ @@ -91,6 +94,14 @@ async function getGridData() { } } +/** + * Apply backend formatted filter and execute grid search + */ +async function onSearch(query: string) { + searchTerm.value = query; + await getGridData(); +} + /** * Execute grid operation and display message if available */ @@ -119,7 +130,13 @@ async function onTagInput(data: RowData, tags: Array, tagsHandler: Field data.tags = tags; } -function onTagClick() {} +function applyFilter(filter: string, value: string, quoted = false) { + if (quoted) { + filterText.value = gridConfig.value?.filterClass.setFilterValue(filterText.value, filter, `'${value}'`) || ""; + } else { + filterText.value = gridConfig.value?.filterClass.setFilterValue(filterText.value, filter, value) || ""; + } +} /** * Initialize grid data @@ -152,6 +169,15 @@ watch(operationMessage, () => { {{ gridConfig.title }} + No entries found. @@ -193,7 +219,7 @@ watch(operationMessage, () => { :value="rowData[fieldEntry.key]" :disabled="rowData.published" @input="(tags) => onTagInput(rowData, tags, fieldEntry.handler)" - @tag-click="onTagClick" /> + @tag-click="(t) => applyFilter('tag', t, true)" />
@@ -230,7 +256,7 @@ watch(operationMessage, () => { } .grid-header { @extend .grid-sticky; - @extend .pb-3; + @extend .pb-2; position: sticky; top: 0; h1 { diff --git a/client/src/components/Grid/configs/types.ts b/client/src/components/Grid/configs/types.ts index 53965df6d3d5..438028ddb05b 100644 --- a/client/src/components/Grid/configs/types.ts +++ b/client/src/components/Grid/configs/types.ts @@ -1,3 +1,5 @@ +import Filtering from "@/utils/filtering"; + type Field = FieldKey | FieldOperations; // TODO: type FieldType = "date" | "operations" | "sharing" | "tags" | "text" | undefined; @@ -24,6 +26,7 @@ export interface Config { fields: Array; sortBy: string; sortKeys: Array; + filterClass: Filtering; sortDesc: boolean; } diff --git a/client/src/components/Grid/configs/visualizations.js b/client/src/components/Grid/configs/visualizations.js index 1170dbd4c49a..b94e7190038c 100644 --- a/client/src/components/Grid/configs/visualizations.js +++ b/client/src/components/Grid/configs/visualizations.js @@ -1,12 +1,44 @@ import axios from "axios"; +import Filtering, { contains, equals, expandNameTagWithQuotes, toBool } from "@/utils/filtering"; import { withPrefix } from "@/utils/redirect"; import { errorMessageAsString, rethrowSimple } from "@/utils/simple-error"; +const validFilters = { + title: { placeholder: "title", type: String, handler: contains("title"), menuItem: true }, + slug: { handler: contains("slug"), menuItem: false }, + tag: { + placeholder: "tag(s)", + type: "MultiTags", + handler: contains("tag", "tag", expandNameTagWithQuotes), + menuItem: true, + }, + user: { placeholder: "user name", type: String, handler: contains("user"), menuItem: true }, + published: { + placeholder: "Filter on published visualizations", + type: Boolean, + boolType: "is", + handler: equals("published", "published", toBool), + menuItem: true, + }, + importable: { + placeholder: "Filter on importable visualizations", + type: Boolean, + boolType: "is", + handler: equals("importable", "importable", toBool), + menuItem: true, + }, +}; +const PageFilters = new Filtering(validFilters, undefined, false, false); + export const VisualizationsGrid = { getUrl: (currentPage, perPage, sortBy, sortDesc, searchTerm) => { const offset = perPage * (currentPage - 1); - return `/api/visualizations/detailed?limit=${perPage}&offset=${offset}&sort_by=${sortBy}&sort_desc=${sortDesc}`; + let q = `/api/visualizations/detailed?limit=${perPage}&offset=${offset}&sort_by=${sortBy}&sort_desc=${sortDesc}`; + if (searchTerm) { + q += `&search=${searchTerm}`; + } + return q; }, resource: "visualizations", item: "visualization", @@ -15,6 +47,7 @@ export const VisualizationsGrid = { sortBy: "update_time", sortDesc: true, sortKeys: ["create_time", "title", "update_time"], + filterClass: PageFilters, fields: [ { title: "Title",