Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2165-Catalog---Add-includeexclude-shortcuts-in-Catalog-tag-list #2188

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions src/api/query-hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { getHypothesisResponse } from "../services/hypothesis";
import { getIncident } from "../services/incident";
import { LogsResponse, SearchLogsPayload, searchLogs } from "../services/logs";
import { getPagingParams } from "../services/notifications";
import { appendPagingParamsToSearchParams } from "../services/notifications";
import {
getComponentTeams,
getHealthCheckSpecByID,
Expand Down Expand Up @@ -179,24 +179,34 @@ export function prepareConfigListQuery({
pageIndex,
pageSize
}: ConfigListFilterQueryOptions) {
let query =
"select=id,type,config_class,status,health,labels,name,tags,created_at,updated_at,deleted_at,cost_per_minute,cost_total_1d,cost_total_7d,cost_total_30d,changes,analysis";
const query = new URLSearchParams({
select:
"id,type,config_class,status,health,labels,name,tags,created_at,updated_at,deleted_at,cost_per_minute,cost_total_1d,cost_total_7d,cost_total_30d,changes,analysis"
});

if (includeAgents) {
query = `${query},agent:agents(id,name)`;
query.append("select", "agent:agents(id,name)");
}

if (configType && configType !== "All") {
query = `${query}&type=eq.${configType}`;
query.append("type", `eq.${configType}`);
}

if (status && status !== "All") {
const statusParam = tristateOutputToQueryFilterParam(status, "status");
query = `${query}${statusParam}`;
query.append("status", statusParam);
}

if (health) {
const healthParam = tristateOutputToQueryFilterParam(health, "health");
query = `${query}${healthParam}`;
query.append("health", healthParam);
}

if (search) {
query = `${query}&or=(name.ilike.*${search}*,type.ilike.*${search}*,description.ilike.*${search}*,namespace.ilike.*${search}*)`;
query.append(
"or",
`(name.ilike.*${search}*,type.ilike.*${search}*,description.ilike.*${search}*,namespace.ilike.*${search}*)`
);
} else {
const filterQueries: string[] = [];
if (label && label !== "All") {
Expand All @@ -205,32 +215,35 @@ export function prepareConfigListQuery({
}
if (labels) {
labels.split(",").forEach((label) => {
const [k, v] = label.split("__:__");
const [k, v] = label.split("____");
const [realValue, operand] = v.split(":");
const operator = parseInt(operand) === -1 ? "neq" : "eq";
if (!isNull(v)) {
filterQueries.push(`labels->>${k}=eq.${encodeURIComponent(v)}`);
filterQueries.push(
`labels->>${k}.${operator}.${encodeURIComponent(realValue)}`
);
} else {
filterQueries.push(`labels->>${k}=is.null`);
}
});
}
if (filterQueries.length) {
query = `${query}&${filterQueries.join("&")}`;
query.append("or", `(and(${filterQueries.join(",")}))`);
}
}

if (sortBy && sortOrder) {
const sortField = sortBy === "type" ? `${sortBy},name` : sortBy;
query = `${query}&order=${sortField}.${sortOrder}`;
query.append("order", `${sortField}.${sortOrder}`);
}

if (hideDeletedConfigs) {
query = `${query}&deleted_at=is.null`;
query.append("deleted_at", "is.null");
}
const pagingParams = getPagingParams({ pageIndex, pageSize });

if (pagingParams) {
query = `${query}${pagingParams}`;
}
appendPagingParamsToSearchParams(query, { pageIndex, pageSize });

return query;
return query.toString();
}

export const useConfigNameQuery = (
Expand Down
28 changes: 18 additions & 10 deletions src/api/query-hooks/useConfigSummaryQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ import { useSearchParams } from "react-router-dom";
import { ConfigSummaryRequest, getConfigsSummary } from "../services/configs";
import { ConfigSummary } from "../types/configs";

export function useLabelFiltersFromParams() {
const [searchParams] = useSearchParams();
const labels = searchParams.get("labels") ?? undefined;

return useMemo(() => {
if (labels) {
return labels.split(",").reduce((acc, label) => {
const [filterValue, operand] = label.split(":");
const [key, value] = filterValue.split("____");
const symbol = parseInt(operand) === -1 ? "!" : "";
return { ...acc, [key]: `${symbol}${value}` };
}, {});
}
return undefined;
}, [labels]);
}

export function useConfigSummaryQuery({
enabled = true
}: UseQueryOptions<ConfigSummary[]> = {}) {
Expand All @@ -18,23 +35,14 @@ export function useConfigSummaryQuery({
groupBy: "config_class,type"
});
const hideDeletedConfigs = useHideDeletedConfigs();
const labels = searchParams.get("labels") ?? undefined;
const status = searchParams.get("status") ?? undefined;
const health = searchParams.get("health") ?? undefined;

const [favorites] = useAtom(configTypesFavorites);

const groupBy = useGroupBySearchParam();

const filterSummaryByLabel = useMemo(() => {
if (labels) {
return labels.split(",").reduce((acc, label) => {
const [key, value] = label.split("__:__");
return { ...acc, [key]: value };
}, {});
}
return undefined;
}, [labels]);
const filterSummaryByLabel = useLabelFiltersFromParams();

const req: ConfigSummaryRequest = {
// group by config_class is always done on the frontend
Expand Down
16 changes: 16 additions & 0 deletions src/api/services/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ export function getPagingParams({
return pagingParams;
}

export function appendPagingParamsToSearchParams(
params: URLSearchParams,
{
pageIndex,
pageSize
}: {
pageIndex?: number;
pageSize?: number;
}
) {
if (pageIndex || pageSize) {
params.append("limit", pageSize!.toString());
params.append("offset", (pageIndex! * pageSize!).toString());
}
}

export const getNotificationsSummary = async ({
pageIndex,
pageSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ConfigListTagsCellProps<T extends Pick<ConfigItem, "tags" | "id">> = Pick<
hideGroupByView?: boolean;
label?: string;
enableFilterByTag?: boolean;
filterByTagParamKey?: string;
};

export default function ConfigListTagsCell<
Expand All @@ -19,7 +20,8 @@ export default function ConfigListTagsCell<
row,
getValue,
hideGroupByView = false,
enableFilterByTag = false
enableFilterByTag = false,
filterByTagParamKey = "tags"
}: ConfigListTagsCellProps<T>): JSX.Element | null {
const [params, setParams] = useSearchParams();

Expand Down Expand Up @@ -65,10 +67,10 @@ export default function ConfigListTagsCell<
.join(",");

// Update the URL
params.set("tags", updatedValue);
params.set(filterByTagParamKey, updatedValue);
setParams(params);
},
[enableFilterByTag, params, setParams]
[enableFilterByTag, filterByTagParamKey, params, setParams]
);

const groupByProp = decodeURIComponent(params.get("groupByProp") ?? "");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type MRTConfigListTagsCellProps<
> = MRTCellProps<T> & {
hideGroupByView?: boolean;
enableFilterByTag?: boolean;
filterByTagParamKey?: string;
};

export default function MRTConfigListTagsCell<
Expand All @@ -20,7 +21,8 @@ export default function MRTConfigListTagsCell<
row,
cell,
hideGroupByView = false,
enableFilterByTag = false
enableFilterByTag = false,
filterByTagParamKey = "tags"
}: MRTConfigListTagsCellProps<T>): JSX.Element | null {
const [params, setParams] = useSearchParams();

Expand All @@ -46,7 +48,7 @@ export default function MRTConfigListTagsCell<
e.stopPropagation();

// Get the current tags from the URL
const currentTags = params.get("tags");
const currentTags = params.get(filterByTagParamKey);
const currentTagsArray = (
currentTags ? currentTags.split(",") : []
).filter((value) => {
Expand All @@ -66,10 +68,10 @@ export default function MRTConfigListTagsCell<
.join(",");

// Update the URL
params.set("tags", updatedValue);
params.set(filterByTagParamKey, updatedValue);
setParams(params);
},
[enableFilterByTag, params, setParams]
[enableFilterByTag, filterByTagParamKey, params, setParams]
);

const groupByProp = decodeURIComponent(params.get("groupByProp") ?? "");
Expand Down
8 changes: 7 additions & 1 deletion src/components/Configs/ConfigList/MRTConfigListColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,13 @@ export const mrtConfigListColumns: MRT_ColumnDef<ConfigItem>[] = [
header: "Tags",
accessorKey: "tags",
enableColumnActions: false,
Cell: (props) => <MRTConfigListTagsCell {...props} />,
Cell: (props) => (
<MRTConfigListTagsCell
{...props}
enableFilterByTag
filterByTagParamKey="labels"
/>
),
maxSize: 300,
minSize: 100
},
Expand Down
1 change: 1 addition & 0 deletions src/components/Configs/ConfigSummary/ConfigSummaryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export default function ConfigSummaryList({

const handleRowClick = useCallback(
(row: Row<ConfigSummary>) => {
params.delete("labels");
if (groupBy.includes("type")) {
const { type } = row.original;
params.set("configType", type);
Expand Down
52 changes: 25 additions & 27 deletions src/components/Configs/ConfigsListFilters/ConfigLabelsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useGetConfigLabelsListQuery } from "@flanksource-ui/api/query-hooks";
import TristateReactSelect, {
TriStateOptions
} from "@flanksource-ui/ui/Dropdowns/TristateReactSelect";
import { useField } from "formik";
import { useMemo } from "react";
import { ReactSelectDropdown } from "../../ReactSelectDropdown";

type Props = {
searchParamKey?: string;
Expand All @@ -16,30 +18,33 @@ export function ConfigLabelsDropdown({ searchParamKey = "labels" }: Props) {

const labelItems = useMemo(() => {
if (data && Array.isArray(data)) {
const options = data.map((tag) => ({
label: (
<div className="block space-x-1 text-sm">
<span className="w-auto text-gray-600">{tag.key}:</span>
<span className="w-full">{tag.value}</span>
</div>
),
value: `${tag.key}__:__${tag.value}`
}));
return [{ label: "All", value: "All" }, ...options];
return data.map(
(tag) =>
({
label: (
<span className="space-x-1 text-sm">
<span className="text-gray-600">{tag.key}:</span>
<span>{tag.value}</span>
</span>
),
value: `${tag.key}____${tag.value}`,
id: `${tag.key}____${tag.value}`
}) satisfies TriStateOptions
);
} else {
// Adding this console.error to help debug the issue I noticed happening
// inside the Saas, that's leading to the catalog page crashing
console.error("Invalid data for ConfigLabelsDropdown", data);
return [{ label: "All", value: "All" }];
return [];
}
}, [data]);

return (
<ReactSelectDropdown
items={labelItems}
name="type"
<TristateReactSelect
isLoading={isLoading}
options={labelItems}
onChange={(value) => {
if (value && value !== "All") {
if (value && value !== "all") {
field.onChange({
target: { name: searchParamKey, value: value }
});
Expand All @@ -49,17 +54,10 @@ export function ConfigLabelsDropdown({ searchParamKey = "labels" }: Props) {
});
}
}}
value={field.value ?? "All"}
className="w-auto max-w-[38rem]"
dropDownClassNames="w-auto max-w-[38rem] left-0"
hideControlBorder
isMulti
prefix={
<div className="mr-2 whitespace-nowrap text-xs text-gray-500">
Labels:
</div>
}
isLoading={isLoading}
minMenuWidth="400px"
value={field.value}
className="w-auto max-w-[400px]"
label={"Labels"}
/>
);
}
Loading