From c475a6692ad9aaded2b188ab0cf475236eeff514 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Mon, 3 Jun 2024 08:34:06 +0300 Subject: [PATCH] feat: add filter by config tags for changes Fixes #1961 --- src/api/query-hooks/index.ts | 5 +- ...angesQuery.ts => useConfigChangesHooks.ts} | 31 +++++++++- src/api/services/configs.ts | 19 ++++-- .../ConfigChangesFilters.tsx | 4 +- .../ConfigTagsDropdown.tsx | 58 +++++++++++++++++++ .../FilterBar/ConfigRelatedChangesFilters.tsx | 2 + src/pages/config/ConfigChangesPage.tsx | 2 +- .../details/ConfigDetailsChangesPage.tsx | 2 +- src/ui/Dropdowns/TristateReactSelect.tsx | 4 +- 9 files changed, 111 insertions(+), 16 deletions(-) rename src/api/query-hooks/{useGetConfigsChangesQuery.ts => useConfigChangesHooks.ts} (85%) create mode 100644 src/components/Configs/Changes/ConfigChangesFilters/ConfigTagsDropdown.tsx diff --git a/src/api/query-hooks/index.ts b/src/api/query-hooks/index.ts index 3549c86c55..cb58f8c956 100644 --- a/src/api/query-hooks/index.ts +++ b/src/api/query-hooks/index.ts @@ -356,10 +356,7 @@ export function useGetConfigTagsListQuery( return useQuery({ queryKey: ["configs", "tags", "list"], queryFn: async () => { - const { error, data } = await getConfigTagsList(); - if (error) { - throw error; - } + const { data } = await getConfigTagsList(); return data ?? []; }, ...options diff --git a/src/api/query-hooks/useGetConfigsChangesQuery.ts b/src/api/query-hooks/useConfigChangesHooks.ts similarity index 85% rename from src/api/query-hooks/useGetConfigsChangesQuery.ts rename to src/api/query-hooks/useConfigChangesHooks.ts index b6478537af..315a5a5b38 100644 --- a/src/api/query-hooks/useGetConfigsChangesQuery.ts +++ b/src/api/query-hooks/useConfigChangesHooks.ts @@ -10,6 +10,27 @@ import { getConfigsChanges } from "../services/configs"; +function useConfigChangesTagsFilter() { + const [params] = useSearchParams(); + + const tags = useMemo(() => { + const allTags = params.get("tags"); + if (allTags) { + return allTags.split(",").map((tag) => { + const prefix = tag.split(":")[1] === "-1" ? "!" : ""; // expected: `key____value:-1` + const key = `${prefix}${tag.split("____")[0]}`; // expected: `key____value:-1` + const value = tag.split("____")[1].split(":")[0]; // expected: `key____value:-1` + return { + key, + value + } satisfies Required["tags"][number]; + }); + } + }, [params]); + + return tags; +} + export function useGetAllConfigsChangesQuery( queryOptions: UseQueryOptions = { enabled: true, @@ -38,6 +59,8 @@ export function useGetAllConfigsChangesQuery( const createdBy = params.get("created_by") ?? undefined; const externalCreatedBy = params.get("external_created_by") ?? undefined; + const tags = useConfigChangesTagsFilter(); + const arbitraryFilter = useMemo(() => { const filter = new Map(); if (configId) { @@ -70,7 +93,8 @@ export function useGetAllConfigsChangesQuery( sortOrder: sortDirection === "desc" ? "desc" : "asc", page: page, pageSize: pageSize, - arbitraryFilter + arbitraryFilter, + tags } satisfies GetConfigsRelatedChangesParams; return useQuery({ @@ -108,6 +132,8 @@ export function useGetConfigChangesByIDQuery( const downstream = params.get("downstream") === "true"; const all = upstream && downstream; + const tags = useConfigChangesTagsFilter(); + const relationshipType = useMemo(() => { if (all) { return "all"; @@ -133,7 +159,8 @@ export function useGetConfigChangesByIDQuery( sortBy, sortOrder: sortDirection === "desc" ? "desc" : "asc", page: page, - pageSize: pageSize + pageSize: pageSize, + tags } satisfies GetConfigsRelatedChangesParams; return useQuery({ diff --git a/src/api/services/configs.ts b/src/api/services/configs.ts index e0e24ca399..9f82658e60 100644 --- a/src/api/services/configs.ts +++ b/src/api/services/configs.ts @@ -148,8 +148,9 @@ export type ConfigsTagList = { value: any; }; -export const getConfigTagsList = () => - resolve(ConfigDB.get(`/config_tags`)); +export const getConfigTagsList = () => { + return ConfigDB.get(`/config_tags`); +}; export const getConfigLabelsList = () => resolve(ConfigDB.get(`/config_labels`)); @@ -211,6 +212,10 @@ export type GetConfigsRelatedChangesParams = { sortBy?: string; sortOrder?: "asc" | "desc"; arbitraryFilter?: Record; + tags?: { + key: string; + value: string; + }[]; }; export async function getConfigsChanges({ @@ -227,7 +232,8 @@ export async function getConfigsChanges({ pageSize, sortBy, sortOrder, - arbitraryFilter + arbitraryFilter, + tags }: GetConfigsRelatedChangesParams) { const queryParams = new URLSearchParams(); if (id) { @@ -253,7 +259,7 @@ export async function getConfigsChanges({ } if (arbitraryFilter) { Object.entries(arbitraryFilter).forEach(([key, value]) => { - const filterExpression = tristateOutputToQueryParamValue(value); + const filterExpression = tristateOutputToQueryParamValue(value, false); if (filterExpression) { queryParams.set(key, filterExpression); } @@ -276,7 +282,10 @@ export async function getConfigsChanges({ // for descending order, we need to add a "-" before the sortBy field queryParams.set("sort_by", `${sortOrder === "desc" ? "-" : ""}${sortBy}`); } - + if (tags) { + const tagList = tags.map(({ key, value }) => `${key}=${value}`); + queryParams.set("tags", tagList.join(",")); + } if (type_filter) { queryParams.set( "recursive", diff --git a/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx b/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx index 8d1bfdbe71..222130c5ed 100644 --- a/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx +++ b/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx @@ -8,6 +8,7 @@ import { paramsToReset } from "../ConfigChangeHistory"; import { ChangesTypesDropdown } from "./ChangeTypesDropdown"; import { ConfigChangeSeverity } from "./ConfigChangeSeverity"; import ConfigChangesDateRangeFilter from "./ConfigChangesDateRangeFIlter"; +import { ConfigTagsDropdown } from "./ConfigTagsDropdown"; import ConfigTypesTristateDropdown from "./ConfigTypesTristateDropdown"; type FilterBadgeProps = { @@ -81,7 +82,7 @@ export function ConfigChangeFilters({
+
diff --git a/src/components/Configs/Changes/ConfigChangesFilters/ConfigTagsDropdown.tsx b/src/components/Configs/Changes/ConfigChangesFilters/ConfigTagsDropdown.tsx new file mode 100644 index 0000000000..4c768c3590 --- /dev/null +++ b/src/components/Configs/Changes/ConfigChangesFilters/ConfigTagsDropdown.tsx @@ -0,0 +1,58 @@ +import { useGetConfigTagsListQuery } from "@flanksource-ui/api/query-hooks"; +import TristateReactSelect, { + TriStateOptions +} from "@flanksource-ui/ui/Dropdowns/TristateReactSelect"; +import { useField } from "formik"; +import { useMemo } from "react"; + +type Props = { + searchParamKey?: string; +}; + +export function ConfigTagsDropdown({ searchParamKey = "tags" }: Props) { + const [field] = useField({ + name: searchParamKey + }); + + const { data, isLoading } = useGetConfigTagsListQuery(); + + const tagItems = useMemo(() => { + if (data) { + return data.map( + (tag) => + ({ + id: `${tag.key}____${tag.value}`, + label: ( +
+ {tag.key}: + {tag.value} +
+ ), + value: `${tag.key}____${tag.value}` + } satisfies TriStateOptions) + ); + } + }, [data]); + + return ( + { + console.log(value); + if (value) { + field.onChange({ + target: { name: searchParamKey, value: value } + }); + } else { + field.onChange({ + target: { name: searchParamKey, value: undefined } + }); + } + }} + value={field.value} + className="w-auto max-w-[38rem]" + label={"Tags"} + isLoading={isLoading} + /> + ); +} diff --git a/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx b/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx index dc39385cab..0d5aa63bad 100644 --- a/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx +++ b/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx @@ -4,6 +4,7 @@ import { ChangesTypesDropdown } from "../../ConfigChangesFilters/ChangeTypesDrop import { ConfigChangeSeverity } from "../../ConfigChangesFilters/ConfigChangeSeverity"; import ConfigChangesDateRangeFilter from "../../ConfigChangesFilters/ConfigChangesDateRangeFIlter"; import { ConfigRelatedChangesToggles } from "../../ConfigChangesFilters/ConfigRelatedChangesToggles"; +import { ConfigTagsDropdown } from "../../ConfigChangesFilters/ConfigTagsDropdown"; import ConfigTypesTristateDropdown from "../../ConfigChangesFilters/ConfigTypesTristateDropdown"; import { ConfigChangesToggledDeletedItems } from "./ConfigChangesToggledDeletedItems"; @@ -25,6 +26,7 @@ export function ConfigRelatedChangesFilters({ + diff --git a/src/pages/config/ConfigChangesPage.tsx b/src/pages/config/ConfigChangesPage.tsx index f389f02309..c2a23e397a 100644 --- a/src/pages/config/ConfigChangesPage.tsx +++ b/src/pages/config/ConfigChangesPage.tsx @@ -1,4 +1,4 @@ -import { useGetAllConfigsChangesQuery } from "@flanksource-ui/api/query-hooks/useGetConfigsChangesQuery"; +import { useGetAllConfigsChangesQuery } from "@flanksource-ui/api/query-hooks/useConfigChangesHooks"; import { ConfigChangeHistory } from "@flanksource-ui/components/Configs/Changes/ConfigChangeHistory"; import { ConfigChangeFilters } from "@flanksource-ui/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters"; import ConfigPageTabs from "@flanksource-ui/components/Configs/ConfigPageTabs"; diff --git a/src/pages/config/details/ConfigDetailsChangesPage.tsx b/src/pages/config/details/ConfigDetailsChangesPage.tsx index bad5059acc..9d87a12824 100644 --- a/src/pages/config/details/ConfigDetailsChangesPage.tsx +++ b/src/pages/config/details/ConfigDetailsChangesPage.tsx @@ -1,4 +1,4 @@ -import { useGetConfigChangesByIDQuery } from "@flanksource-ui/api/query-hooks/useGetConfigsChangesQuery"; +import { useGetConfigChangesByIDQuery } from "@flanksource-ui/api/query-hooks/useConfigChangesHooks"; import { ConfigChangeHistory } from "@flanksource-ui/components/Configs/Changes/ConfigChangeHistory"; import { ConfigRelatedChangesFilters } from "@flanksource-ui/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters"; import { ConfigDetailsTabs } from "@flanksource-ui/components/Configs/ConfigDetailsTabs"; diff --git a/src/ui/Dropdowns/TristateReactSelect.tsx b/src/ui/Dropdowns/TristateReactSelect.tsx index 16ddb222e0..b495f74f6b 100644 --- a/src/ui/Dropdowns/TristateReactSelect.tsx +++ b/src/ui/Dropdowns/TristateReactSelect.tsx @@ -25,7 +25,7 @@ import { Tooltip } from "react-tooltip"; export type TriStateOptions = { id: string; - label: string; + label: React.ReactNode; icon?: any; value: string; }; @@ -189,7 +189,7 @@ function ReactSelectTriStateSingleValue({ data-tooltip-content={`${ +value === 1 ? "Include" : "Exclude" } ${v.label}`} - key={v.label} + key={v.id} className={clsx( "w-auto flex flex-row items-center gap-1 px-2 py-1 max-w-full overflow-hidden text-ellipsis text-nowrap bg-gray-200" )}