Skip to content

Commit ad511ab

Browse files
committed
FE fixes and error handling
1 parent 802c997 commit ad511ab

File tree

5 files changed

+89
-31
lines changed

5 files changed

+89
-31
lines changed

src/frontend/app/Components/AddEditView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,10 @@ const AddEditView: React.FunctionComponent<AddEditViewProps> = (props: AddEditVi
135135
setIsMenuOpen(!isMenuOpen);
136136
};
137137

138-
const onFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
138+
const onFormSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
139139
event.preventDefault();
140140
if (modalVariant === 'create' || modalVariant === 'edit') {
141-
saveFilter().then();
141+
await saveFilter();
142142
}
143143
};
144144

src/frontend/app/Components/Filters.tsx

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from 'react';
22
import {
3+
Alert, AlertActionCloseButton,
34
Button,
45
Label,
56
LabelGroup,
@@ -8,7 +9,7 @@ import {
89
Toolbar,
910
ToolbarContent,
1011
ToolbarGroup,
11-
ToolbarItem
12+
ToolbarItem,
1213
} from '@patternfly/react-core';
1314
import { AddEditView, BaseDropdown, DateRangePicker, MultiChoiceDropdown } from '@app/Components';
1415
import { FilterComponentProps, FilterOption, FilterOptionWithId, FilterProps, RequestFilter } from '@app/Types';
@@ -32,6 +33,7 @@ import {
3233
} from '@app/Store/commonSelectors';
3334
import { useRef } from 'react';
3435
import { useAuthStore } from '@app/Store/authStore';
36+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
3537

3638
export const Filters: React.FunctionComponent<FilterComponentProps> = (props: FilterComponentProps) => {
3739
const filterOptionsList = useFilterStore((state) => state.filterOptions);
@@ -44,6 +46,7 @@ export const Filters: React.FunctionComponent<FilterComponentProps> = (props: Fi
4446
const filterChoicesDataByOption = useFilterChoicesDataById();
4547
const fetchOneFilterOption = fetchOneOption();
4648
const fetchNextPageData = useFetchNextPage();
49+
const [errors, setErrors] = React.useState<string[]>([]);
4750
const [filterSelection, selectFilter] = React.useState<FilterProps>({
4851
organization: [],
4952
job_template: [],
@@ -68,11 +71,17 @@ export const Filters: React.FunctionComponent<FilterComponentProps> = (props: Fi
6871
await fetchTemplateOptions();
6972
await Promise.all(filterOptionsList.map(async (option) => {
7073
if (fetchFilterOptionsData[option.key]) {
71-
await fetchFilterOptionsData[option.key]();
74+
try {
75+
await fetchFilterOptionsData[option.key]();
76+
}catch(e){
77+
setErrors((errors) => [...errors, (e as { message?: string })?.message ?? 'Something went wrong. Please try again later.']);
78+
}
7279
}
7380
}));
7481
};
7582

83+
const dateChoices = useFilterStore((state) => state.dateRangeOptions);
84+
7685
React.useEffect(() => {
7786
const execute = async () => {
7887
await fetchFilters();
@@ -133,13 +142,21 @@ export const Filters: React.FunctionComponent<FilterComponentProps> = (props: Fi
133142
};
134143

135144
const clearFilters = () => {
136-
selectFilter((prevState) => ({
137-
...prevState,
138-
['organization']: [],
139-
['job_template']: [],
140-
['project']: [],
141-
['label']: []
142-
}));
145+
let range: string | null = null;
146+
if (dateChoices?.length) {
147+
const index = dateChoices.findIndex((v) => v.key === 'month_to_date');
148+
range = (index > -1 ? dateChoices[index].key : dateChoices[0].key).toString();
149+
}
150+
const filters = {
151+
organization: [],
152+
job_template: [],
153+
label: [],
154+
project: [],
155+
date_range: range,
156+
start_date: undefined,
157+
end_date: undefined
158+
} as FilterProps;
159+
selectFilter(()=>filters);
143160
setView(null);
144161
};
145162

@@ -153,14 +170,18 @@ export const Filters: React.FunctionComponent<FilterComponentProps> = (props: Fi
153170
const viewOptions = {};
154171
await Promise.all(filterOptions.map(async (option) => {
155172
viewOptions[option] = [];
156-
if (view.filters?.[option]?.length){
157-
await Promise.all(view.filters[option].map(async (key: number)=>{
173+
if (view.filters?.[option]?.length) {
174+
await Promise.all(view.filters[option].map(async (key: number) => {
158175
let value = filterChoicesDataByOption[option][key];
159-
if (value){
176+
if (value) {
160177
viewOptions[option].push(value);
161178
} else {
162-
value = await fetchOneFilterOption[option](key);
163-
if (value){
179+
try {
180+
value = await fetchOneFilterOption[option](key);
181+
} catch(e){
182+
setErrors((errors) => [...errors, (e as { message?: string })?.message ?? 'Something went wrong. Please try again later.']);
183+
}
184+
if (value) {
164185
viewOptions[option].push(value);
165186
}
166187
}
@@ -240,6 +261,12 @@ export const Filters: React.FunctionComponent<FilterComponentProps> = (props: Fi
240261
}
241262
};
242263

264+
const closeAlert = (errorIndex:number):void=>{
265+
const newList = [...errors];
266+
newList.splice(errorIndex, 1);
267+
setErrors(newList);
268+
};
269+
243270
const filterSelector = (
244271
<div className="pf-v6-u-mr-xs">
245272
<BaseDropdown
@@ -322,6 +349,23 @@ export const Filters: React.FunctionComponent<FilterComponentProps> = (props: Fi
322349
</ToolbarGroup>
323350
);
324351

352+
const errorElements = (
353+
<div
354+
style={{
355+
marginTop: '16px',
356+
}}>
357+
{errors.map((error: string, i:number) => (
358+
<Alert
359+
style={{
360+
marginBottom: '16px',
361+
}}
362+
key={i}
363+
title={error}
364+
variant={'danger'}
365+
actionClose={<AlertActionCloseButton onClose={() => closeAlert(i)} />}>
366+
</Alert>))}
367+
</div>
368+
);
325369
const toolBar = (
326370
<Toolbar id="filter-toolbar" clearAllFilters={clearFilters} className="pf-v6-l-flex pf-m-row-gap-md pf-v6-u-pb-0">
327371
<ToolbarContent>
@@ -349,7 +393,8 @@ export const Filters: React.FunctionComponent<FilterComponentProps> = (props: Fi
349393

350394
return (
351395
<div>
352-
<React.Fragment>{!error && toolBar}</React.Fragment>
396+
<React.Fragment>{!error && (toolBar)}</React.Fragment>
397+
{errorElements}
353398
</div>
354399
);
355400
};

src/frontend/app/Components/MultiChoiceDropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ export const MultiChoiceDropdown: React.FunctionComponent<MultiChoiceDropdownPro
100100
icon={props?.icon && props.icon}
101101
style={props?.style}
102102
isExpanded={isMenuOpen}
103-
{...(props.selections.length > 0 && {
103+
{...(props.selections.length > 0 ? {
104104
badge: <Badge isRead>{props.selections.length}</Badge>
105-
})}
105+
} : null)}
106106
>
107107
{props.label ? <span>{props.label}</span> : ''}
108108
</MenuToggle>

src/frontend/app/Store/filterOptionsStore.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ type FilterOptionActions = {
1212

1313
const createActions: StateCreator<FilterOptionsState & FilterOptionActions> = (set, get) => ({
1414
endPoint: '',
15+
errorMessage: '',
16+
errorRetrieveOneMessage: '',
1517
options: [],
1618
currentPage: 1,
1719
pageSize: 10,
@@ -46,16 +48,22 @@ const createActions: StateCreator<FilterOptionsState & FilterOptionActions> = (s
4648
...response.results
4749
]
4850
});
49-
} catch {
51+
} catch(error) {
5052
set({ loading: 'failed', error: true });
53+
console.error(state.errorMessage, error);
54+
throw new Error(state.errorMessage);
5155
}
5256
},
5357
fetchOne: async (id: number): Promise<FilterOptionWithId | null> => {
5458
const state = get();
59+
set({ error: false });
5560
try {
5661
return await RestService.fetchFilterOption(state.endPoint, id);
57-
} catch {
58-
return null;
62+
} catch(error) {
63+
set({ error: true });
64+
const errorMessage = state.errorRetrieveOneMessage.replace('${id}', String(id));
65+
console.error(errorMessage);
66+
throw new Error(errorMessage);
5967
}
6068
},
6169
fetchNextPage: async () => {
@@ -75,22 +83,30 @@ const createActions: StateCreator<FilterOptionsState & FilterOptionActions> = (s
7583

7684
const useJobTemplateStore = create<FilterOptionsState & FilterOptionActions>()((a, b, state) => ({
7785
...createActions(a, b, state),
78-
endPoint: 'api/v1/templates/'
86+
endPoint: 'api/v1/templates/',
87+
errorMessage: 'Error retrieving job templates data.',
88+
errorRetrieveOneMessage: 'Error retrieving job template data with id ${id}.',
7989
}));
8090

8191
const useLabelStore = create<FilterOptionsState & FilterOptionActions>()((a, b, state) => ({
8292
...createActions(a, b, state),
83-
endPoint: 'api/v1/labels/'
93+
endPoint: 'api/v1/labels/',
94+
errorMessage: 'Error retrieving labels data.',
95+
errorRetrieveOneMessage: 'Error retrieving label data with id ${id}.',
8496
}));
8597

8698
const useOrganizationStore = create<FilterOptionsState & FilterOptionActions>()((a, b, state) => ({
8799
...createActions(a, b, state),
88-
endPoint: 'api/v1/organizations/'
100+
endPoint: 'api/v1/organizations/',
101+
errorMessage: 'Error retrieving organizations data.',
102+
errorRetrieveOneMessage: 'Error retrieving organization data with id ${id}.',
89103
}));
90104

91105
const useProjectStore = create<FilterOptionsState & FilterOptionActions>()((a, b, state) => ({
92106
...createActions(a, b, state),
93-
endPoint: 'api/v1/projects/'
107+
endPoint: 'api/v1/projects/',
108+
errorMessage: 'Error retrieving projects data.',
109+
errorRetrieveOneMessage: 'Error retrieving project data with id ${id}.',
94110
}));
95111

96112
export { useJobTemplateStore, useLabelStore, useOrganizationStore, useProjectStore };

src/frontend/app/Types/FilterTypes.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,7 @@ export interface FilterState {
4747
automatedProcessCost: number | string;
4848
clusters: ClusterOption[];
4949
dateRangeOptions: FilterOption[];
50-
//templateOptions: FilterOptionWithId[];
51-
//labelOptions: FilterOptionWithId[];
52-
//instanceOptions: FilterOptionWithId[];
53-
//projectOptions: FilterOptionWithId[];
5450
manualCostAutomation: number | string;
55-
//organizationOptions: FilterOption[];
5651
loading: 'idle' | 'pending' | 'succeeded' | 'failed';
5752
error: boolean;
5853
max_pdf_job_templates: number,
@@ -61,6 +56,8 @@ export interface FilterState {
6156

6257
export interface FilterOptionsState {
6358
endPoint: string;
59+
errorMessage: string;
60+
errorRetrieveOneMessage: string;
6461
options: FilterOption[];
6562
currentPage: number,
6663
pageSize: number,

0 commit comments

Comments
 (0)