Skip to content

Commit ec5e58e

Browse files
committed
Fixes #39253 - fix toolbar buttons remount
1 parent e7c3574 commit ec5e58e

3 files changed

Lines changed: 192 additions & 129 deletions

File tree

webpack/JobInvocationDetail/JobInvocationToolbarButtons.js

Lines changed: 168 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
/* eslint-disable max-lines */
12
import PropTypes from 'prop-types';
2-
import React, { useEffect, useState } from 'react';
3+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
34
import { useDispatch, useSelector } from 'react-redux';
45
import { Button, Split, SplitItem } from '@patternfly/react-core';
56
import { UndoIcon } from '@patternfly/react-icons';
@@ -44,21 +45,32 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
4445
const [isActionOpen, setIsActionOpen] = useState(false);
4546
const [reportTemplateJobId, setReportTemplateJobId] = useState(undefined);
4647
const [templateInputId, setTemplateInputId] = useState(undefined);
47-
const queryParams = new URLSearchParams({
48-
[`report_template_report[input_values][${templateInputId}][value]`]: jobId,
49-
});
5048
const dispatch = useDispatch();
49+
const reportHref = useMemo(() => {
50+
if (reportTemplateJobId === undefined || templateInputId === undefined) {
51+
return undefined;
52+
}
53+
const queryParams = new URLSearchParams({
54+
[`report_template_report[input_values][${templateInputId}][value]`]: jobId,
55+
});
56+
return foremanUrl(
57+
`/templates/report_templates/${reportTemplateJobId}/generate?${queryParams.toString()}`
58+
);
59+
}, [jobId, reportTemplateJobId, templateInputId]);
5160

52-
const onActionFocus = () => {
61+
const onActionFocus = useCallback(() => {
5362
const element = document.getElementById(
5463
`toggle-split-button-action-primary-${jobId}`
5564
);
56-
element.focus();
57-
};
58-
const onActionSelect = () => {
65+
if (element) {
66+
element.focus();
67+
}
68+
}, [jobId]);
69+
const onActionSelect = useCallback(() => {
5970
setIsActionOpen(false);
6071
onActionFocus();
61-
};
72+
}, [onActionFocus]);
73+
const onActionToggle = useCallback((_event, val) => setIsActionOpen(val), []);
6274

6375
useEffect(() => {
6476
let isMounted = true;
@@ -112,102 +124,151 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
112124
};
113125
}, [dispatch, reportTemplateJobId]);
114126

115-
const recurrenceDropdownItems = recurrence
116-
? [
117-
<DropdownSeparator ouiaId="dropdown-separator-1" key="separator-1" />,
118-
<DropdownItem
119-
ouiaId="change-enabled-recurring-dropdown-item"
120-
onClick={() =>
121-
dispatch(
122-
enableRecurringLogic(recurrence?.id, recurringEnabled, jobId)
123-
)
124-
}
125-
key="change-enabled-recurring"
126-
component="button"
127-
isDisabled={
128-
recurrence?.id === undefined ||
129-
recurrence?.state === 'cancelled' ||
130-
!canEditRecurringLogic
131-
}
132-
>
133-
{recurringEnabled ? __('Disable recurring') : __('Enable recurring')}
134-
</DropdownItem>,
135-
<DropdownItem
136-
ouiaId="cancel-recurring-dropdown-item"
137-
onClick={() => dispatch(cancelRecurringLogic(recurrence?.id, jobId))}
138-
key="cancel-recurring"
139-
component="button"
140-
isDisabled={
141-
recurrence?.id === undefined ||
142-
recurrence?.state === 'cancelled' ||
143-
!canEditRecurringLogic
144-
}
145-
>
146-
{__('Cancel recurring')}
147-
</DropdownItem>,
148-
]
149-
: [];
127+
const recurrenceDropdownItems = useMemo(
128+
() =>
129+
recurrence
130+
? [
131+
<DropdownSeparator
132+
ouiaId="dropdown-separator-1"
133+
key="separator-1"
134+
/>,
135+
<DropdownItem
136+
ouiaId="change-enabled-recurring-dropdown-item"
137+
onClick={() =>
138+
dispatch(
139+
enableRecurringLogic(recurrence?.id, recurringEnabled, jobId)
140+
)
141+
}
142+
key="change-enabled-recurring"
143+
component="button"
144+
isDisabled={
145+
recurrence?.id === undefined ||
146+
recurrence?.state === 'cancelled' ||
147+
!canEditRecurringLogic
148+
}
149+
>
150+
{recurringEnabled
151+
? __('Disable recurring')
152+
: __('Enable recurring')}
153+
</DropdownItem>,
154+
<DropdownItem
155+
ouiaId="cancel-recurring-dropdown-item"
156+
onClick={() =>
157+
dispatch(cancelRecurringLogic(recurrence?.id, jobId))
158+
}
159+
key="cancel-recurring"
160+
component="button"
161+
isDisabled={
162+
recurrence?.id === undefined ||
163+
recurrence?.state === 'cancelled' ||
164+
!canEditRecurringLogic
165+
}
166+
>
167+
{__('Cancel recurring')}
168+
</DropdownItem>,
169+
]
170+
: [],
171+
[recurrence, recurringEnabled, canEditRecurringLogic, dispatch, jobId]
172+
);
150173

151-
const dropdownItems = [
152-
<DropdownItem
153-
ouiaId="rerun-succeeded-dropdown-item"
154-
href={foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`)}
155-
key="rerun-succeeded"
156-
isDisabled={!canCreateJobInvocations || !(succeeded > 0)}
157-
description="Rerun job on successful hosts"
158-
>
159-
{__('Rerun successful')}
160-
</DropdownItem>,
161-
<DropdownItem
162-
ouiaId="rerun-failed-dropdown-item"
163-
href={foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`)}
164-
key="rerun-failed"
165-
isDisabled={!canCreateJobInvocations || !(failed > 0)}
166-
description="Rerun job on failed hosts"
167-
>
168-
{__('Rerun failed')}
169-
</DropdownItem>,
170-
<DropdownItem
171-
ouiaId="view-task-dropdown-item"
172-
href={foremanUrl(`/foreman_tasks/tasks/${task?.id}`)}
173-
key="view-task"
174-
isDisabled={!canViewForemanTasks || task === undefined}
175-
description="See details of latest task"
176-
>
177-
{__('View task')}
178-
</DropdownItem>,
179-
<DropdownSeparator ouiaId="dropdown-separator-0" key="separator-0" />,
180-
<DropdownItem
181-
ouiaId="cancel-dropdown-item"
182-
onClick={() => dispatch(cancelJob(jobId, false))}
183-
key="cancel"
184-
component="button"
185-
isDisabled={!canCancelJobInvocations || !isTaskCancelable}
186-
description="Cancel job gracefully"
187-
>
188-
{__('Cancel')}
189-
</DropdownItem>,
190-
<DropdownItem
191-
ouiaId="abort-dropdown-item"
192-
onClick={() => dispatch(cancelJob(jobId, true))}
193-
key="abort"
194-
component="button"
195-
isDisabled={!canCancelJobInvocations || !isTaskCancelable}
196-
description="Cancel job immediately"
197-
>
198-
{__('Abort')}
199-
</DropdownItem>,
200-
...recurrenceDropdownItems,
201-
<DropdownSeparator ouiaId="dropdown-separator-2" key="separator-2" />,
202-
<DropdownItem
203-
ouiaId="legacy-ui-dropdown-item"
204-
icon={<UndoIcon />}
205-
href={`/legacy/job_invocations/${jobId}`}
206-
key="legacy-ui"
207-
>
208-
{__('Legacy UI')}
209-
</DropdownItem>,
210-
];
174+
const dropdownItems = useMemo(
175+
() => [
176+
<DropdownItem
177+
ouiaId="rerun-succeeded-dropdown-item"
178+
href={foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`)}
179+
key="rerun-succeeded"
180+
isDisabled={!canCreateJobInvocations || !(succeeded > 0)}
181+
description="Rerun job on successful hosts"
182+
>
183+
{__('Rerun successful')}
184+
</DropdownItem>,
185+
<DropdownItem
186+
ouiaId="rerun-failed-dropdown-item"
187+
href={foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`)}
188+
key="rerun-failed"
189+
isDisabled={!canCreateJobInvocations || !(failed > 0)}
190+
description="Rerun job on failed hosts"
191+
>
192+
{__('Rerun failed')}
193+
</DropdownItem>,
194+
<DropdownItem
195+
ouiaId="view-task-dropdown-item"
196+
href={foremanUrl(`/foreman_tasks/tasks/${task?.id}`)}
197+
key="view-task"
198+
isDisabled={!canViewForemanTasks || task === undefined}
199+
description="See details of latest task"
200+
>
201+
{__('View task')}
202+
</DropdownItem>,
203+
<DropdownSeparator ouiaId="dropdown-separator-0" key="separator-0" />,
204+
<DropdownItem
205+
ouiaId="cancel-dropdown-item"
206+
onClick={() => dispatch(cancelJob(jobId, false))}
207+
key="cancel"
208+
component="button"
209+
isDisabled={!canCancelJobInvocations || !isTaskCancelable}
210+
description="Cancel job gracefully"
211+
>
212+
{__('Cancel')}
213+
</DropdownItem>,
214+
<DropdownItem
215+
ouiaId="abort-dropdown-item"
216+
onClick={() => dispatch(cancelJob(jobId, true))}
217+
key="abort"
218+
component="button"
219+
isDisabled={!canCancelJobInvocations || !isTaskCancelable}
220+
description="Cancel job immediately"
221+
>
222+
{__('Abort')}
223+
</DropdownItem>,
224+
...recurrenceDropdownItems,
225+
<DropdownSeparator ouiaId="dropdown-separator-2" key="separator-2" />,
226+
<DropdownItem
227+
ouiaId="legacy-ui-dropdown-item"
228+
icon={<UndoIcon />}
229+
href={`/legacy/job_invocations/${jobId}`}
230+
key="legacy-ui"
231+
>
232+
{__('Legacy UI')}
233+
</DropdownItem>,
234+
],
235+
[
236+
canCancelJobInvocations,
237+
canCreateJobInvocations,
238+
canViewForemanTasks,
239+
dispatch,
240+
failed,
241+
isTaskCancelable,
242+
jobId,
243+
recurrenceDropdownItems,
244+
succeeded,
245+
task,
246+
]
247+
);
248+
249+
const dropdownToggle = useMemo(
250+
() => (
251+
<DropdownToggle
252+
ouiaId="toggle-button-action-primary"
253+
id={`toggle-split-button-action-primary-${jobId}`}
254+
splitButtonItems={[
255+
<Button
256+
component="a"
257+
ouiaId="button-rerun-all"
258+
key="rerun"
259+
href={foremanUrl(`/job_invocations/${jobId}/rerun`)}
260+
variant="control"
261+
isDisabled={!canCreateJobInvocations}
262+
>
263+
{__(`Rerun all`)}
264+
</Button>,
265+
]}
266+
splitButtonVariant="action"
267+
onToggle={onActionToggle}
268+
/>
269+
),
270+
[canCreateJobInvocations, jobId, onActionToggle]
271+
);
211272

212273
return (
213274
<>
@@ -217,14 +278,12 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
217278
component="a"
218279
ouiaId="button-create-report"
219280
className="button-create-report"
220-
href={foremanUrl(
221-
`/templates/report_templates/${reportTemplateJobId}/generate?${queryParams.toString()}`
222-
)}
281+
href={reportHref}
223282
variant="secondary"
224283
isDisabled={
225284
!canGenerateReportTemplates ||
226285
task?.state === STATUS.PENDING ||
227-
templateInputId === undefined
286+
reportHref === undefined
228287
}
229288
>
230289
{__(`Create report`)}
@@ -235,26 +294,7 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
235294
ouiaId="job-invocation-global-actions-dropdown"
236295
onSelect={onActionSelect}
237296
position={DropdownPosition.right}
238-
toggle={
239-
<DropdownToggle
240-
ouiaId="toggle-button-action-primary"
241-
id={`toggle-split-button-action-primary-${jobId}`}
242-
splitButtonItems={[
243-
<Button
244-
component="a"
245-
ouiaId="button-rerun-all"
246-
key="rerun"
247-
href={foremanUrl(`/job_invocations/${jobId}/rerun`)}
248-
variant="control"
249-
isDisabled={!canCreateJobInvocations}
250-
>
251-
{__(`Rerun all`)}
252-
</Button>,
253-
]}
254-
splitButtonVariant="action"
255-
onToggle={(_event, val) => setIsActionOpen(val)}
256-
/>
257-
}
297+
toggle={dropdownToggle}
258298
isOpen={isActionOpen}
259299
dropdownItems={dropdownItems}
260300
/>

webpack/JobInvocationDetail/__tests__/MainInformation.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,29 @@ describe('JobInvocationDetailPage', () => {
211211
).toEqual(`/legacy/job_invocations/${jobId}`);
212212
});
213213

214+
it('keeps toolbar buttons mounted while job invocation data is refreshing', async () => {
215+
const jobId = jobInvocationData.id;
216+
const store = mockStore({
217+
...initialState,
218+
JOB_INVOCATION_KEY: {
219+
response: jobInvocationData,
220+
status: STATUS.PENDING,
221+
},
222+
});
223+
224+
render(
225+
<Provider store={store}>
226+
<JobInvocationDetailPage
227+
match={{ params: { id: `${jobId}` } }}
228+
{...props}
229+
/>
230+
</Provider>
231+
);
232+
233+
expect(screen.getByText('Create report')).toBeInTheDocument();
234+
expect(screen.getByText('Rerun all')).toBeInTheDocument();
235+
});
236+
214237
it('shows scheduled date', async () => {
215238
const store = mockStore(initialStateScheduled);
216239
render(

webpack/JobInvocationDetail/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ const JobInvocationDetailPage = ({
153153
header={description}
154154
breadcrumbOptions={breadcrumbOptions}
155155
toolbarButtons={
156-
jobInvocationApiStatus === API_STATUS.RESOLVED && (
156+
items.id !== undefined && (
157157
<JobInvocationToolbarButtons jobId={id} data={items} />
158158
)
159159
}

0 commit comments

Comments
 (0)