diff --git a/web/src/modules/Jobs/Helper.tsx b/web/src/modules/Jobs/Helper.tsx index b1c5698a..3785a1cb 100644 --- a/web/src/modules/Jobs/Helper.tsx +++ b/web/src/modules/Jobs/Helper.tsx @@ -19,9 +19,36 @@ export type ApiParams = { cluster?: string command?: string status?: string[] + tags?: string page?: string } +export type TagPair = { + key: string + value: string +} + +export const serializeTags = (pairs: TagPair[]): string => + pairs + .filter((p) => p.key.trim() && p.value.trim()) + .map((p) => `${p.key.trim()}:${p.value.trim()}`) + .join(',') + +export const parseTags = (value: string): TagPair[] => + value + .split(',') + .map((raw) => raw.trim()) + .reduce((acc, tag) => { + const idx = tag.indexOf(':') + if (idx > 0) { + acc.push({ + key: tag.slice(0, idx).trim(), + value: tag.slice(idx + 1).trim(), + }) + } + return acc + }, []) + export type JobType = { id: string name: string diff --git a/web/src/modules/Jobs/Jobs.tsx b/web/src/modules/Jobs/Jobs.tsx index 33bf285c..c4bf4cb6 100644 --- a/web/src/modules/Jobs/Jobs.tsx +++ b/web/src/modules/Jobs/Jobs.tsx @@ -15,7 +15,14 @@ import { import { BreadcrumbContext } from '@/common/BreadCrumbsProvider/context' import { fetchJobs, getJobStatus } from '@/app/api/jobs/jobs' import { useInfiniteQuery, useQuery } from '@tanstack/react-query' -import { ApiParams, JobType, useJobConfig } from './Helper' +import { + ApiParams, + JobType, + TagPair, + parseTags, + serializeTags, + useJobConfig, +} from './Helper' import { useQueryState } from 'nuqs' import { FilterStatesType } from '@patterninc/react-ui' import { @@ -24,6 +31,7 @@ import { sortData, } from '@/common/Services' import { AutoRefreshContext } from '@/common/AutoRefreshProvider/context' +import TagFilter from './TagFilter' type FilterType = { id: string @@ -33,6 +41,7 @@ type FilterType = { clusterId: string commandId: string status: string[] + tags: TagPair[] } const Jobs = (): React.JSX.Element => { @@ -56,6 +65,11 @@ const Jobs = (): React.JSX.Element => { parse: (value) => (value ? value.split(',') : []), serialize: (value) => value?.join(',') ?? '', }) + const [tags, setTags] = useQueryState('tags', { + defaultValue: [], + parse: (value) => (value ? parseTags(value) : []), + serialize: (value) => serializeTags(value), + }) const [filter, setFilter] = useState({ id: jobId, @@ -65,6 +79,7 @@ const Jobs = (): React.JSX.Element => { clusterId: clusterId, commandId: commandId, status: status, + tags: tags, }) useEffect(() => { @@ -81,8 +96,10 @@ const Jobs = (): React.JSX.Element => { if (clusterId) params.cluster = clusterId if (commandId) params.command = commandId if (status.length > 0) params.status = status + const serializedTags = serializeTags(tags) + if (serializedTags) params.tags = serializedTags return params - }, [jobId, name, user, version, clusterId, commandId, status]) + }, [jobId, name, user, version, clusterId, commandId, status, tags]) // Fetch Jobs with filters applied const { data, isLoading, fetchNextPage, hasNextPage, isSuccess } = @@ -130,6 +147,7 @@ const Jobs = (): React.JSX.Element => { setClusterId(filter.clusterId) setCommandId(filter.commandId) setStatus(filter.status) + setTags(filter.tags) if (filter?.user) queryParams.append('user', filter.user) if (filter?.name) queryParams.append('name', filter.name) @@ -140,6 +158,8 @@ const Jobs = (): React.JSX.Element => { if (filter?.status && filter.status.length > 0) { queryParams.append('status', filter.status.join(',')) } + const serializedTags = serializeTags(filter.tags) + if (serializedTags) queryParams.append('tags', serializedTags) updateBreadcrumbs({ name: 'Jobs', link: `/jobs?${queryParams.toString()}`, @@ -151,6 +171,7 @@ const Jobs = (): React.JSX.Element => { filter.id, filter.name, filter.status, + filter.tags, filter.user, filter.version, setClusterId, @@ -158,6 +179,7 @@ const Jobs = (): React.JSX.Element => { setJobId, setName, setStatus, + setTags, setUser, setVersion, updateBreadcrumbs, @@ -264,6 +286,7 @@ const Jobs = (): React.JSX.Element => { setClusterId(null) setCommandId(null) setStatus(null) + setTags(null) setFilter({ id: '', name: '', @@ -272,6 +295,7 @@ const Jobs = (): React.JSX.Element => { clusterId: '', commandId: '', status: [], + tags: [], }) }, [ setJobId, @@ -281,6 +305,7 @@ const Jobs = (): React.JSX.Element => { setClusterId, setCommandId, setStatus, + setTags, ]) // Compute applied filter count @@ -293,8 +318,18 @@ const Jobs = (): React.JSX.Element => { commandId, clusterId, status.length > 0, + tags.length > 0, ].filter(Boolean).length - }, [jobId, name, user, version, commandId, clusterId, status.length]) + }, [ + jobId, + name, + user, + version, + commandId, + clusterId, + status.length, + tags.length, + ]) const updateFormField = useCallback( (...params: unknown[]) => { @@ -308,6 +343,10 @@ const Jobs = (): React.JSX.Element => { [setFilter], ) + const updateTags = useCallback((newTags: TagPair[]) => { + setFilter((prevFilter) => ({ ...prevFilter, tags: newTags })) + }, []) + return (
{ resetCallout: resetFilters, cancelCallout: () => {}, onChangeCallout: updateFormField, + children: () => ( + + ), }, }} /> diff --git a/web/src/modules/Jobs/TagFilter.tsx b/web/src/modules/Jobs/TagFilter.tsx new file mode 100644 index 00000000..eedbaef6 --- /dev/null +++ b/web/src/modules/Jobs/TagFilter.tsx @@ -0,0 +1,65 @@ +import React from 'react' +import { Button, FormLabel, Icon, TextInput } from '@patterninc/react-ui' +import { TagPair } from './Helper' + +type TagFilterProps = { + tags: TagPair[] + onChange: (tags: TagPair[]) => void +} + +const emptyPair: TagPair = { key: '', value: '' } + +const TagFilter = ({ tags, onChange }: TagFilterProps): React.JSX.Element => { + const rows = tags.length > 0 ? tags : [emptyPair] + + const updateRow = (index: number, field: keyof TagPair, value: string) => { + onChange( + rows.map((row, i) => (i === index ? { ...row, [field]: value } : row)), + ) + } + + const addRow = () => onChange([...rows, { ...emptyPair }]) + + const removeRow = (index: number) => { + onChange(rows.filter((_, i) => i !== index)) + } + + return ( +
+ + {rows.map((row, index) => ( +
+
+ updateRow(index, 'key', value as string)} + /> +
+ : +
+ updateRow(index, 'value', value as string)} + /> +
+ +
+ ))} +
+ +
+
+ ) +} + +export default TagFilter