Skip to content

Commit 412d2fe

Browse files
committed
Fixes #39253 - Implement "Not Found" error handling for invalid Job Invocation IDs
1 parent 0708645 commit 412d2fe

3 files changed

Lines changed: 112 additions & 5 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import { useHistory } from 'react-router-dom';
4+
import { translate as __, sprintf } from 'foremanReact/common/I18n';
5+
import { foremanUrl } from 'foremanReact/common/helpers';
6+
import {
7+
Bullseye,
8+
Button,
9+
EmptyState,
10+
EmptyStateBody,
11+
EmptyStateIcon,
12+
PageSection,
13+
PageSectionVariants,
14+
EmptyStateVariant,
15+
EmptyStateHeader,
16+
EmptyStateActions,
17+
EmptyStateFooter,
18+
} from '@patternfly/react-core';
19+
import { SearchIcon } from '@patternfly/react-icons';
20+
21+
const jobInvocationsIndexPath = '/job_invocations';
22+
23+
const JobInvocationEmptyState = ({ jobInvocationId }) => {
24+
const history = useHistory();
25+
return (
26+
<PageSection variant={PageSectionVariants.light}>
27+
<Bullseye>
28+
<EmptyState
29+
ouiaId="job-invocation-empty-state"
30+
variant={EmptyStateVariant.lg}
31+
>
32+
<EmptyStateIcon icon={SearchIcon} />
33+
<EmptyStateHeader
34+
titleText={<>{sprintf(__('Job invocation not found'))}</>}
35+
headingLevel="h5"
36+
/>
37+
38+
<EmptyStateBody>
39+
{sprintf(
40+
__(
41+
'There is no job invocation with id %s or there are access permissions needed. Please contact your administrator if this issue continues.'
42+
),
43+
jobInvocationId
44+
)}
45+
</EmptyStateBody>
46+
<EmptyStateFooter>
47+
<EmptyStateActions>
48+
<Button
49+
ouiaId="job-invocation-empty-state-go-to-job-invocations-button"
50+
variant="primary"
51+
component="a"
52+
href={foremanUrl(jobInvocationsIndexPath)}
53+
isInline
54+
>
55+
{__('Go to job invocations')}
56+
</Button>
57+
</EmptyStateActions>
58+
<EmptyStateActions>
59+
<Button
60+
ouiaId="job-invocation-empty-state-create-new-job-invocation-button"
61+
variant="link"
62+
component="a"
63+
href={foremanUrl('/job_invocations/new')}
64+
>
65+
{__('Create a new job invocation')}
66+
</Button>
67+
<Button
68+
ouiaId="job-invocation-empty-state-return-to-last-page-button"
69+
variant="link"
70+
onClick={() => history.goBack()}
71+
>
72+
{__('Return to the last page')}
73+
</Button>
74+
</EmptyStateActions>
75+
</EmptyStateFooter>
76+
</EmptyState>
77+
</Bullseye>
78+
</PageSection>
79+
);
80+
};
81+
82+
JobInvocationEmptyState.propTypes = {
83+
jobInvocationId: PropTypes.string.isRequired,
84+
};
85+
86+
export default JobInvocationEmptyState;

webpack/JobInvocationDetail/__tests__/MainInformation.test.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ jest.spyOn(api, 'get');
3535
const originalToLocaleString = Date.prototype.toLocaleString;
3636
beforeAll(() => {
3737
// eslint-disable-next-line no-extend-native
38-
Date.prototype.toLocaleString = function (locale, options) {
39-
return originalToLocaleString.call(this, locale, { ...options, timeZone: 'UTC' });
38+
Date.prototype.toLocaleString = function(locale, options) {
39+
return originalToLocaleString.call(this, locale, {
40+
...options,
41+
timeZone: 'UTC',
42+
});
4043
};
4144
});
4245
afterAll(() => {

webpack/JobInvocationDetail/index.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import PropTypes from 'prop-types';
1313
import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
1414
import { stopInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
1515
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
16+
import { STATUS as API_STATUS } from 'foremanReact/constants';
17+
import { selectAPIStatus } from 'foremanReact/redux/API/APISelectors';
1618

1719
import { JobAdditionInfo } from './JobAdditionInfo';
1820
import JobInvocationHostTable from './JobInvocationHostTable';
1921
import JobInvocationOverview from './JobInvocationOverview';
2022
import JobInvocationSystemStatusChart from './JobInvocationSystemStatusChart';
23+
import JobInvocationEmptyState from './JobInvocationEmptyState';
2124
import JobInvocationToolbarButtons from './JobInvocationToolbarButtons';
2225
import { getJobInvocation, getTask } from './JobInvocationActions';
2326
import './JobInvocationDetail.scss';
@@ -51,9 +54,16 @@ const JobInvocationDetailPage = ({
5154
statusLabel === STATUS.SUCCEEDED ||
5255
statusLabel === STATUS.CANCELLED;
5356
const autoRefresh = task?.state === STATUS.PENDING || false;
54-
useAPI('get', currentPermissionsUrl, {
55-
key: CURRENT_PERMISSIONS,
56-
});
57+
const { status: permissionsApiStatus } = useAPI(
58+
'get',
59+
currentPermissionsUrl,
60+
{
61+
key: CURRENT_PERMISSIONS,
62+
}
63+
);
64+
const jobInvocationApiStatus = useSelector(state =>
65+
selectAPIStatus(state, JOB_INVOCATION_KEY)
66+
);
5767
const [selectedFilter, setSelectedFilter] = useState('');
5868

5969
const handleFilterChange = newFilter => {
@@ -88,6 +98,14 @@ const JobInvocationDetailPage = ({
8898
}
8999
}, [dispatch, taskId]);
90100

101+
const apiFailed =
102+
permissionsApiStatus === API_STATUS.ERROR ||
103+
jobInvocationApiStatus === API_STATUS.ERROR;
104+
105+
if (apiFailed) {
106+
return <JobInvocationEmptyState jobInvocationId={id} />;
107+
}
108+
91109
const pageStatus =
92110
items.id === undefined
93111
? STATUS_UPPERCASE.PENDING

0 commit comments

Comments
 (0)