Skip to content

Commit bb57bb6

Browse files
🌱 add date filter and replace dateRange
Signed-off-by: Carlos Feria <[email protected]>
1 parent 336d39a commit bb57bb6

File tree

10 files changed

+201
-17
lines changed

10 files changed

+201
-17
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { type FormEvent, useState } from "react";
2+
3+
import { DatePicker, Form, FormGroup } from "@patternfly/react-core";
4+
5+
import {
6+
americanDateFormat,
7+
isValidAmericanShortDate,
8+
parseAmericanDate,
9+
} from "../FilterToolbar/dateUtils";
10+
import type { IFilterControlProps } from "./FilterControl";
11+
12+
export const DateFilter = <TItem,>({
13+
filterValue,
14+
setFilterValue,
15+
isDisabled = false,
16+
}: React.PropsWithChildren<
17+
IFilterControlProps<TItem, string>
18+
>): React.JSX.Element | null => {
19+
const [date, setDate] = useState<Date>();
20+
21+
// Update it if it changes externally
22+
React.useEffect(() => {
23+
if (filterValue?.[0]) {
24+
const date = parseAmericanDate(filterValue?.[0]);
25+
setDate(date);
26+
} else {
27+
setDate(undefined);
28+
}
29+
}, [filterValue]);
30+
31+
const onDateChange = (_event: FormEvent<HTMLInputElement>, value: string) => {
32+
if (isValidAmericanShortDate(value)) {
33+
const newDate = parseAmericanDate(value);
34+
setDate(newDate);
35+
const target = americanDateFormat(newDate);
36+
if (target) {
37+
setFilterValue([target]);
38+
}
39+
}
40+
};
41+
42+
return (
43+
<Form>
44+
<FormGroup role="group" isInline>
45+
<DatePicker
46+
value={date ? americanDateFormat(date) : ""}
47+
dateFormat={americanDateFormat}
48+
dateParse={parseAmericanDate}
49+
onChange={onDateChange}
50+
aria-label="Date"
51+
placeholder="MM/DD/YYYY"
52+
// disable error text (no space in toolbar scenario)
53+
invalidFormatText={""}
54+
// default value ("parent") creates collision with sticky table header
55+
appendTo={document.body}
56+
isDisabled={isDisabled}
57+
/>
58+
</FormGroup>
59+
</Form>
60+
);
61+
};

client/src/app/components/FilterPanel/FilterControl.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "../FilterToolbar";
1111
import { AutocompleteLabelFilterControl } from "./AutocompleteLabelFilterControl";
1212
import { CheckboxFilterControl } from "./CheckboxFilterControl";
13+
import { DateFilter } from "./DateFilter";
1314
import { DateRangeFilter } from "./DateRangeFilter";
1415
import { RadioFilterControl } from "./RadioFilterControl";
1516
import { SearchFilterControl } from "./SearchFilterControl";
@@ -58,6 +59,9 @@ export const FilterControl = <TItem, TFilterCategoryKey extends string>({
5859
/>
5960
);
6061
}
62+
if (category.type === FilterType.date) {
63+
return <DateFilter category={category} {...props} />;
64+
}
6165
if (category.type === FilterType.dateRange) {
6266
return <DateRangeFilter category={category} {...props} />;
6367
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React, { type FormEvent, useState } from "react";
2+
3+
import { DatePicker, InputGroup, ToolbarFilter } from "@patternfly/react-core";
4+
5+
import type { IFilterControlProps } from "./FilterControl";
6+
import {
7+
americanDateFormat,
8+
isValidAmericanShortDate,
9+
parseAmericanDate,
10+
} from "./dateUtils";
11+
12+
export const DateFilter = <TItem,>({
13+
category,
14+
filterValue,
15+
setFilterValue,
16+
showToolbarItem,
17+
isDisabled = false,
18+
}: React.PropsWithChildren<
19+
IFilterControlProps<TItem, string>
20+
>): React.JSX.Element | null => {
21+
const selectedFilters = filterValue ?? [];
22+
const validFilters =
23+
selectedFilters?.filter((value) => isValidAmericanShortDate(value)) ?? [];
24+
25+
const [date, setDate] = useState<Date>();
26+
27+
// Update it if it changes externally
28+
React.useEffect(() => {
29+
if (filterValue?.[0]) {
30+
const date = parseAmericanDate(filterValue?.[0]);
31+
setDate(date);
32+
} else {
33+
setDate(undefined);
34+
}
35+
}, [filterValue]);
36+
37+
const onDateChange = (_event: FormEvent<HTMLInputElement>, value: string) => {
38+
if (isValidAmericanShortDate(value)) {
39+
const newDate = parseAmericanDate(value);
40+
setDate(newDate);
41+
const target = americanDateFormat(newDate);
42+
if (target) {
43+
setFilterValue([target]);
44+
}
45+
}
46+
};
47+
48+
return (
49+
<ToolbarFilter
50+
key={category.categoryKey}
51+
labels={validFilters.map((value) => ({
52+
key: value,
53+
node: value,
54+
}))}
55+
deleteLabel={() => setFilterValue([])}
56+
categoryName={category.title}
57+
showToolbarItem={showToolbarItem}
58+
>
59+
<InputGroup>
60+
<DatePicker
61+
value={date ? americanDateFormat(date) : ""}
62+
dateFormat={americanDateFormat}
63+
dateParse={parseAmericanDate}
64+
onChange={onDateChange}
65+
aria-label="Date"
66+
placeholder="MM/DD/YYYY"
67+
// disable error text (no space in toolbar scenario)
68+
invalidFormatText={""}
69+
// default value ("parent") creates collision with sticky table header
70+
appendTo={document.body}
71+
isDisabled={isDisabled}
72+
/>
73+
</InputGroup>
74+
</ToolbarFilter>
75+
);
76+
};

client/src/app/components/FilterToolbar/FilterControl.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type * as React from "react";
22

33
import { AutocompleteLabelFilterControl } from "./AutocompleteLabelFilterControl";
4+
import { DateFilter } from "./DateFilter";
45
import { DateRangeFilter } from "./DateRangeFilter";
56
import {
67
type FilterCategory,
@@ -60,6 +61,9 @@ export const FilterControl = <TItem, TFilterCategoryKey extends string>({
6061
/>
6162
);
6263
}
64+
if (category.type === FilterType.date) {
65+
return <DateFilter category={category} {...props} />;
66+
}
6367
if (category.type === FilterType.dateRange) {
6468
return <DateRangeFilter category={category} {...props} />;
6569
}

client/src/app/components/FilterToolbar/FilterToolbar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export enum FilterType {
1919
multiselect = "multiselect",
2020
search = "search",
2121
numsearch = "numsearch",
22+
date = "date",
2223
dateRange = "dateRange",
2324
autocompleteLabel = "autocompleteLabel",
2425
}

client/src/app/hooks/table-controls/filtering/getFilterHubRequestParams.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import {
33
type FilterCategory,
44
getFilterLogicOperator,
55
} from "@app/components/FilterToolbar";
6-
import { parseInterval } from "@app/components/FilterToolbar/dateUtils";
6+
import {
7+
parseAmericanDate,
8+
parseInterval,
9+
} from "@app/components/FilterToolbar/dateUtils";
710
import { objectKeys } from "@app/utils/utils";
811
import type { IFilterState } from "./useFilterState";
912

@@ -17,7 +20,7 @@ const pushOrMergeFilter = (
1720
newFilter: HubFilter,
1821
) => {
1922
const existingFilterIndex = existingFilters.findIndex(
20-
(f) => f.field === newFilter.field,
23+
(f) => f.field === newFilter.field && f.operator === newFilter.operator,
2124
);
2225
const existingFilter =
2326
existingFilterIndex === -1 ? null : existingFilters[existingFilterIndex];
@@ -140,6 +143,14 @@ export const getFilterHubRequestParams = <
140143
},
141144
});
142145
}
146+
if (filterCategory.type === "date") {
147+
const date = parseAmericanDate(serverFilterValue[0]);
148+
pushOrMergeFilter(filters, {
149+
field: serverFilterField,
150+
operator: filterCategory.operator ?? "=",
151+
value: date.toISOString(),
152+
});
153+
}
143154
if (filterCategory.type === "dateRange") {
144155
const [start, end] = parseInterval(serverFilterValue[0]);
145156
if (start) {

client/src/app/pages/advisory-list/advisory-context.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface IAdvisorySearchContext {
2727
AdvisorySummary,
2828
"identifier" | "title" | "type" | "labels" | "modified" | "vulnerabilities",
2929
"identifier" | "modified",
30-
"" | "average_severity" | "modified" | "labels",
30+
"" | "average_severity" | "modifiedBefore" | "modifiedAfter" | "labels",
3131
string
3232
>;
3333

@@ -88,9 +88,18 @@ export const AdvisorySearchProvider: React.FunctionComponent<
8888
type: FilterType.search,
8989
},
9090
{
91-
categoryKey: "modified",
92-
title: "Revision",
93-
type: FilterType.dateRange,
91+
categoryKey: "modifiedBefore",
92+
serverFilterField: "modified",
93+
title: "Revised before",
94+
type: FilterType.date,
95+
operator: "<",
96+
},
97+
{
98+
categoryKey: "modifiedAfter",
99+
serverFilterField: "modified",
100+
title: "Revised after",
101+
type: FilterType.date,
102+
operator: ">",
94103
},
95104
{
96105
categoryKey: "labels",

client/src/app/pages/sbom-list/sbom-context.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ interface ISbomSearchContext {
3131
| "labels"
3232
| "vulnerabilities",
3333
"name" | "published",
34-
"" | "published" | "labels",
34+
"" | "publishedBefore" | "publishedAfter" | "labels",
3535
string
3636
>;
3737

@@ -89,9 +89,18 @@ export const SbomSearchProvider: React.FunctionComponent<ISbomProvider> = ({
8989
type: FilterType.search,
9090
},
9191
{
92-
categoryKey: "published",
93-
title: "Created on",
94-
type: FilterType.dateRange,
92+
categoryKey: "publishedBefore",
93+
serverFilterField: "published",
94+
title: "Created before",
95+
type: FilterType.date,
96+
operator: "<",
97+
},
98+
{
99+
categoryKey: "publishedAfter",
100+
serverFilterField: "published",
101+
title: "Created after",
102+
type: FilterType.date,
103+
operator: ">",
95104
},
96105
{
97106
categoryKey: "labels",

client/src/app/pages/search/components/SearchTabs.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,19 @@ export interface SearchTabsProps {
3939
filterPanelProps: {
4040
advisoryFilterPanelProps: IFilterPanelProps<
4141
AdvisorySummary,
42-
"" | "modified" | "average_severity" | "labels"
42+
"" | "modifiedBefore" | "modifiedAfter" | "average_severity" | "labels"
4343
>;
4444
packageFilterPanelProps: IFilterPanelProps<
4545
PackageTableData,
4646
"" | "type" | "arch"
4747
>;
4848
sbomFilterPanelProps: IFilterPanelProps<
4949
SbomSummary,
50-
"" | "published" | "labels"
50+
"" | "publishedBefore" | "publishedAfter" | "labels"
5151
>;
5252
vulnerabilityFilterPanelProps: IFilterPanelProps<
5353
VulnerabilitySummary,
54-
"" | "base_severity" | "published"
54+
"" | "base_severity" | "publishedBefore" | "publishedAfter"
5555
>;
5656
};
5757

client/src/app/pages/vulnerability-list/vulnerability-context.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface IVulnerabilitySearchContext {
2121
VulnerabilitySummary,
2222
"identifier" | "title" | "severity" | "published" | "sboms",
2323
"identifier" | "published" | "severity",
24-
"" | "base_severity" | "published",
24+
"" | "base_severity" | "publishedBefore" | "publishedAfter",
2525
string
2626
>;
2727

@@ -83,9 +83,18 @@ export const VulnerabilitySearchProvider: React.FunctionComponent<
8383
],
8484
},
8585
{
86-
categoryKey: "published",
87-
title: "Created on",
88-
type: FilterType.dateRange,
86+
categoryKey: "publishedBefore",
87+
serverFilterField: "published",
88+
title: "Created before",
89+
type: FilterType.date,
90+
operator: "<",
91+
},
92+
{
93+
categoryKey: "publishedAfter",
94+
serverFilterField: "published",
95+
title: "Created after",
96+
type: FilterType.date,
97+
operator: ">",
8998
},
9099
],
91100
isExpansionEnabled: true,

0 commit comments

Comments
 (0)