foreman_remote_execution 16.0.4 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/job_invocations_controller.rb +33 -6
- data/app/controllers/job_invocations_controller.rb +28 -1
- data/app/controllers/template_invocations_controller.rb +1 -1
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/config/routes.rb +4 -2
- data/lib/foreman_remote_execution/engine.rb +9 -256
- data/lib/foreman_remote_execution/plugin.rb +240 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +1 -1
- data/test/unit/job_invocation_report_template_test.rb +6 -6
- data/webpack/JobInvocationDetail/CheckboxesActions.js +327 -0
- data/webpack/JobInvocationDetail/{JobInvocationHostTableToolbar.js → DropdownFilter.js} +3 -6
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +8 -9
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +18 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +205 -89
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +30 -3
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +23 -28
- data/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +118 -0
- data/webpack/JobInvocationDetail/TemplateInvocation.js +54 -24
- data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +8 -10
- data/webpack/JobInvocationDetail/TemplateInvocationPage.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +2 -4
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +204 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +34 -28
- data/webpack/JobInvocationDetail/index.js +71 -41
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +26 -7
- data/webpack/Routes/routes.js +1 -1
- data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +1 -1
- metadata +8 -6
- data/webpack/JobInvocationDetail/OpenAlInvocations.js +0 -111
- data/webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js +0 -110
@@ -3,31 +3,33 @@ import {
|
|
3
3
|
Flex,
|
4
4
|
PageSection,
|
5
5
|
PageSectionVariants,
|
6
|
+
Skeleton,
|
6
7
|
} from '@patternfly/react-core';
|
7
|
-
import {
|
8
|
+
import React, { useEffect, useState } from 'react';
|
8
9
|
import { translate as __, documentLocale } from 'foremanReact/common/I18n';
|
9
|
-
import {
|
10
|
+
import { useDispatch, useSelector } from 'react-redux';
|
10
11
|
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
11
12
|
import PropTypes from 'prop-types';
|
12
|
-
import
|
13
|
-
import {
|
13
|
+
import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
|
14
|
+
import { stopInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
15
|
+
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
14
16
|
|
15
17
|
import { JobAdditionInfo } from './JobAdditionInfo';
|
18
|
+
import JobInvocationHostTable from './JobInvocationHostTable';
|
19
|
+
import JobInvocationOverview from './JobInvocationOverview';
|
20
|
+
import JobInvocationSystemStatusChart from './JobInvocationSystemStatusChart';
|
21
|
+
import JobInvocationToolbarButtons from './JobInvocationToolbarButtons';
|
16
22
|
import { getJobInvocation, getTask } from './JobInvocationActions';
|
23
|
+
import './JobInvocationDetail.scss';
|
17
24
|
import {
|
18
25
|
CURRENT_PERMISSIONS,
|
19
|
-
currentPermissionsUrl,
|
20
26
|
DATE_OPTIONS,
|
21
27
|
JOB_INVOCATION_KEY,
|
22
28
|
STATUS,
|
29
|
+
STATUS_UPPERCASE,
|
30
|
+
currentPermissionsUrl,
|
23
31
|
} from './JobInvocationConstants';
|
24
|
-
import JobInvocationHostTable from './JobInvocationHostTable';
|
25
|
-
import JobInvocationOverview from './JobInvocationOverview';
|
26
32
|
import { selectItems } from './JobInvocationSelectors';
|
27
|
-
import JobInvocationSystemStatusChart from './JobInvocationSystemStatusChart';
|
28
|
-
import JobInvocationToolbarButtons from './JobInvocationToolbarButtons';
|
29
|
-
|
30
|
-
import './JobInvocationDetail.scss';
|
31
33
|
|
32
34
|
const JobInvocationDetailPage = ({
|
33
35
|
match: {
|
@@ -38,21 +40,20 @@ const JobInvocationDetailPage = ({
|
|
38
40
|
const items = useSelector(selectItems);
|
39
41
|
const {
|
40
42
|
description,
|
43
|
+
failed = 0,
|
41
44
|
status_label: statusLabel,
|
42
45
|
task,
|
43
46
|
start_at: startAt,
|
44
|
-
targeting,
|
47
|
+
targeting = {},
|
45
48
|
} = items;
|
46
49
|
const finished =
|
47
50
|
statusLabel === STATUS.FAILED ||
|
48
51
|
statusLabel === STATUS.SUCCEEDED ||
|
49
52
|
statusLabel === STATUS.CANCELLED;
|
50
53
|
const autoRefresh = task?.state === STATUS.PENDING || false;
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
CURRENT_PERMISSIONS
|
55
|
-
);
|
54
|
+
useAPI('get', currentPermissionsUrl, {
|
55
|
+
key: CURRENT_PERMISSIONS,
|
56
|
+
});
|
56
57
|
const [selectedFilter, setSelectedFilter] = useState('');
|
57
58
|
|
58
59
|
const handleFilterChange = newFilter => {
|
@@ -88,10 +89,22 @@ const JobInvocationDetailPage = ({
|
|
88
89
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
89
90
|
}, [dispatch, task?.id]);
|
90
91
|
|
92
|
+
const pageStatus =
|
93
|
+
items.id === undefined
|
94
|
+
? STATUS_UPPERCASE.PENDING
|
95
|
+
: STATUS_UPPERCASE.RESOLVED;
|
96
|
+
|
91
97
|
const breadcrumbOptions = {
|
92
98
|
breadcrumbItems: [
|
93
99
|
{ caption: __('Jobs'), url: `/job_invocations` },
|
94
|
-
{
|
100
|
+
{
|
101
|
+
caption:
|
102
|
+
pageStatus === STATUS_UPPERCASE.PENDING ? (
|
103
|
+
<Skeleton width="350px" />
|
104
|
+
) : (
|
105
|
+
description
|
106
|
+
),
|
107
|
+
},
|
95
108
|
],
|
96
109
|
isPf4: true,
|
97
110
|
};
|
@@ -101,26 +114,27 @@ const JobInvocationDetailPage = ({
|
|
101
114
|
<PageLayout
|
102
115
|
header={description}
|
103
116
|
breadcrumbOptions={breadcrumbOptions}
|
104
|
-
toolbarButtons={
|
105
|
-
<JobInvocationToolbarButtons
|
106
|
-
jobId={id}
|
107
|
-
data={items}
|
108
|
-
currentPermissions={response.results}
|
109
|
-
permissionsStatus={status}
|
110
|
-
/>
|
111
|
-
}
|
117
|
+
toolbarButtons={<JobInvocationToolbarButtons jobId={id} data={items} />}
|
112
118
|
searchable={false}
|
113
119
|
>
|
114
120
|
<Flex
|
115
121
|
className="job-invocation-detail-flex"
|
116
122
|
alignItems={{ default: 'alignItemsFlexStart' }}
|
117
123
|
>
|
118
|
-
<
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
+
<SkeletonLoader
|
125
|
+
status={pageStatus}
|
126
|
+
skeletonProps={{
|
127
|
+
height: 105,
|
128
|
+
width: 375,
|
129
|
+
}}
|
130
|
+
>
|
131
|
+
<JobInvocationSystemStatusChart
|
132
|
+
data={items}
|
133
|
+
isAlreadyStarted={isAlreadyStarted}
|
134
|
+
formattedStartDate={formattedStartDate}
|
135
|
+
onFilterChange={handleFilterChange}
|
136
|
+
/>
|
137
|
+
</SkeletonLoader>
|
124
138
|
<Divider
|
125
139
|
orientation={{
|
126
140
|
default: 'vertical',
|
@@ -130,35 +144,51 @@ const JobInvocationDetailPage = ({
|
|
130
144
|
className="job-overview"
|
131
145
|
alignItems={{ default: 'alignItemsCenter' }}
|
132
146
|
>
|
133
|
-
<
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
147
|
+
<SkeletonLoader
|
148
|
+
status={pageStatus}
|
149
|
+
skeletonProps={{
|
150
|
+
height: 105,
|
151
|
+
width: 270,
|
152
|
+
}}
|
153
|
+
>
|
154
|
+
<JobInvocationOverview
|
155
|
+
data={items}
|
156
|
+
isAlreadyStarted={isAlreadyStarted}
|
157
|
+
formattedStartDate={formattedStartDate}
|
158
|
+
/>
|
159
|
+
</SkeletonLoader>
|
139
160
|
</Flex>
|
140
161
|
</Flex>
|
141
162
|
<PageSection
|
142
163
|
variant={PageSectionVariants.light}
|
143
164
|
className="job-additional-info"
|
144
165
|
>
|
145
|
-
|
166
|
+
<SkeletonLoader
|
167
|
+
status={pageStatus}
|
168
|
+
skeletonProps={{ height: 150, width: '100%' }}
|
169
|
+
>
|
170
|
+
<JobAdditionInfo data={items} />
|
171
|
+
</SkeletonLoader>
|
146
172
|
</PageSection>
|
147
173
|
</PageLayout>
|
148
174
|
<PageSection
|
149
175
|
variant={PageSectionVariants.light}
|
150
176
|
className="job-details-table-section table-section"
|
151
177
|
>
|
152
|
-
|
178
|
+
<SkeletonLoader
|
179
|
+
status={pageStatus}
|
180
|
+
skeletonProps={{ height: 400, width: '100%' }}
|
181
|
+
>
|
153
182
|
<JobInvocationHostTable
|
154
183
|
id={id}
|
155
184
|
targeting={targeting}
|
185
|
+
failedCount={failed}
|
156
186
|
finished={finished}
|
157
187
|
autoRefresh={autoRefresh}
|
158
188
|
initialFilter={selectedFilter}
|
159
189
|
onFilterUpdate={handleFilterChange}
|
160
190
|
/>
|
161
|
-
|
191
|
+
</SkeletonLoader>
|
162
192
|
</PageSection>
|
163
193
|
</>
|
164
194
|
);
|
@@ -1,12 +1,31 @@
|
|
1
1
|
export const buildHostQuery = (selected, search) => {
|
2
|
+
const nameEscape = name => `"${name.replaceAll('"', '\\"')}"`;
|
2
3
|
const { hosts, hostCollections, hostGroups } = selected;
|
3
|
-
const
|
4
|
-
|
5
|
-
|
6
|
-
.join(',')})`;
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
const MAX_NAME_ITEMS = 50;
|
5
|
+
let hostsSearch;
|
6
|
+
if (hosts.length < MAX_NAME_ITEMS)
|
7
|
+
hostsSearch = `name ^ (${hosts.map(({ name }) => name).join(',')})`;
|
8
|
+
else hostsSearch = `id ^ (${hosts.map(({ id }) => id).join(',')})`;
|
9
|
+
|
10
|
+
let hostCollectionsSearch;
|
11
|
+
if (hostCollections.length < MAX_NAME_ITEMS)
|
12
|
+
hostCollectionsSearch = `host_collection ^ (${hostCollections
|
13
|
+
.map(({ name }) => nameEscape(name))
|
14
|
+
.join(',')})`;
|
15
|
+
else
|
16
|
+
hostCollectionsSearch = `host_collection_id ^ (${hostCollections
|
17
|
+
.map(({ id }) => id)
|
18
|
+
.join(',')})`;
|
19
|
+
|
20
|
+
let hostGroupsSearch;
|
21
|
+
if (hostCollections.length < MAX_NAME_ITEMS)
|
22
|
+
hostGroupsSearch = `hostgroup_fullname ^ (${hostGroups
|
23
|
+
.map(({ name }) => nameEscape(name))
|
24
|
+
.join(',')})`;
|
25
|
+
else
|
26
|
+
hostGroupsSearch = `hostgroup_id ^ (${hostGroups
|
27
|
+
.map(({ id }) => id)
|
28
|
+
.join(',')})`;
|
10
29
|
const queryParts = [
|
11
30
|
hosts.length ? hostsSearch : false,
|
12
31
|
hostCollections.length ? hostCollectionsSearch : false,
|
data/webpack/Routes/routes.js
CHANGED
@@ -16,7 +16,7 @@ const ForemanREXRoutes = [
|
|
16
16
|
render: props => <JobWizardPageRerun {...props} />,
|
17
17
|
},
|
18
18
|
{
|
19
|
-
path: '/
|
19
|
+
path: '/job_invocations/:id',
|
20
20
|
exact: true,
|
21
21
|
render: props => <JobInvocationDetailPage {...props} />,
|
22
22
|
},
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 16.0
|
4
|
+
version: 16.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-08-05 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: deface
|
@@ -353,6 +353,7 @@ files:
|
|
353
353
|
- extra/cockpit/settings.yml.example
|
354
354
|
- lib/foreman_remote_execution.rb
|
355
355
|
- lib/foreman_remote_execution/engine.rb
|
356
|
+
- lib/foreman_remote_execution/plugin.rb
|
356
357
|
- lib/foreman_remote_execution/tasks/explain_proxy_selection.rake
|
357
358
|
- lib/foreman_remote_execution/version.rb
|
358
359
|
- lib/tasks/foreman_remote_execution_tasks.rake
|
@@ -419,17 +420,18 @@ files:
|
|
419
420
|
- test/unit/renderer_scope_input_test.rb
|
420
421
|
- test/unit/targeting_test.rb
|
421
422
|
- test/unit/template_invocation_input_value_test.rb
|
423
|
+
- webpack/JobInvocationDetail/CheckboxesActions.js
|
424
|
+
- webpack/JobInvocationDetail/DropdownFilter.js
|
422
425
|
- webpack/JobInvocationDetail/JobAdditionInfo.js
|
423
426
|
- webpack/JobInvocationDetail/JobInvocationActions.js
|
424
427
|
- webpack/JobInvocationDetail/JobInvocationConstants.js
|
425
428
|
- webpack/JobInvocationDetail/JobInvocationDetail.scss
|
426
429
|
- webpack/JobInvocationDetail/JobInvocationHostTable.js
|
427
|
-
- webpack/JobInvocationDetail/JobInvocationHostTableToolbar.js
|
428
430
|
- webpack/JobInvocationDetail/JobInvocationOverview.js
|
429
431
|
- webpack/JobInvocationDetail/JobInvocationSelectors.js
|
430
432
|
- webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js
|
431
433
|
- webpack/JobInvocationDetail/JobInvocationToolbarButtons.js
|
432
|
-
- webpack/JobInvocationDetail/
|
434
|
+
- webpack/JobInvocationDetail/OpenAllInvocationsModal.js
|
433
435
|
- webpack/JobInvocationDetail/TemplateInvocation.js
|
434
436
|
- webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js
|
435
437
|
- webpack/JobInvocationDetail/TemplateInvocationComponents/OutputToggleGroup.js
|
@@ -438,8 +440,8 @@ files:
|
|
438
440
|
- webpack/JobInvocationDetail/TemplateInvocationComponents/index.scss
|
439
441
|
- webpack/JobInvocationDetail/TemplateInvocationPage.js
|
440
442
|
- webpack/JobInvocationDetail/__tests__/MainInformation.test.js
|
441
|
-
- webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js
|
442
443
|
- webpack/JobInvocationDetail/__tests__/OutputCodeBlock.test.js
|
444
|
+
- webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js
|
443
445
|
- webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js
|
444
446
|
- webpack/JobInvocationDetail/__tests__/fixtures.js
|
445
447
|
- webpack/JobInvocationDetail/index.js
|
@@ -605,7 +607,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
605
607
|
- !ruby/object:Gem::Version
|
606
608
|
version: '0'
|
607
609
|
requirements: []
|
608
|
-
rubygems_version: 3.6.
|
610
|
+
rubygems_version: 3.6.9
|
609
611
|
specification_version: 4
|
610
612
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|
611
613
|
management functionality with remote management functionality.
|
@@ -1,111 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import {
|
4
|
-
Alert,
|
5
|
-
AlertActionCloseButton,
|
6
|
-
Button,
|
7
|
-
Modal,
|
8
|
-
ModalVariant,
|
9
|
-
} from '@patternfly/react-core';
|
10
|
-
import { OutlinedWindowRestoreIcon } from '@patternfly/react-icons';
|
11
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
12
|
-
import { templateInvocationPageUrl } from './JobInvocationConstants';
|
13
|
-
|
14
|
-
export const PopupAlert = ({ setShowAlert }) => (
|
15
|
-
<Alert
|
16
|
-
ouiaId="template-invocation-new-tab-popup-alert"
|
17
|
-
variant="warning"
|
18
|
-
actionClose={<AlertActionCloseButton onClose={() => setShowAlert(false)} />}
|
19
|
-
title={__(
|
20
|
-
'Popups are blocked by your browser. Please allow popups for this site to open all invocations in new tabs.'
|
21
|
-
)}
|
22
|
-
/>
|
23
|
-
);
|
24
|
-
export const OpenAlInvocations = ({ results, id, setShowAlert }) => {
|
25
|
-
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
26
|
-
const handleModalToggle = () => {
|
27
|
-
setIsModalOpen(!isModalOpen);
|
28
|
-
};
|
29
|
-
|
30
|
-
const openLink = url => {
|
31
|
-
const newWin = window.open(url);
|
32
|
-
|
33
|
-
if (!newWin || newWin.closed || typeof newWin.closed === 'undefined') {
|
34
|
-
setShowAlert(true);
|
35
|
-
}
|
36
|
-
};
|
37
|
-
const OpenAllButton = () => (
|
38
|
-
<Button
|
39
|
-
variant="link"
|
40
|
-
isInline
|
41
|
-
aria-label="open all template invocations in a new tab"
|
42
|
-
ouiaId="template-invocation-new-tab-button"
|
43
|
-
onClick={() => {
|
44
|
-
if (results.length <= 3) {
|
45
|
-
results.forEach(result => {
|
46
|
-
openLink(templateInvocationPageUrl(result.id, id), '_blank');
|
47
|
-
});
|
48
|
-
} else {
|
49
|
-
handleModalToggle();
|
50
|
-
}
|
51
|
-
}}
|
52
|
-
>
|
53
|
-
<OutlinedWindowRestoreIcon />
|
54
|
-
</Button>
|
55
|
-
);
|
56
|
-
const OpenAllModal = () => (
|
57
|
-
<Modal
|
58
|
-
ouiaId="template-invocation-new-tab-modal"
|
59
|
-
title={__('Open all invocations in new tabs')}
|
60
|
-
isOpen={isModalOpen}
|
61
|
-
onClose={handleModalToggle}
|
62
|
-
variant={ModalVariant.small}
|
63
|
-
titleIconVariant="warning"
|
64
|
-
actions={[
|
65
|
-
<Button
|
66
|
-
ouiaId="template-invocation-new-tab-modal-confirm"
|
67
|
-
key="confirm"
|
68
|
-
variant="primary"
|
69
|
-
onClick={() => {
|
70
|
-
results.forEach(result => {
|
71
|
-
openLink(templateInvocationPageUrl(result.id, id), '_blank');
|
72
|
-
});
|
73
|
-
handleModalToggle();
|
74
|
-
}}
|
75
|
-
>
|
76
|
-
{__('Open all in new tabs')}
|
77
|
-
</Button>,
|
78
|
-
<Button
|
79
|
-
ouiaId="template-invocation-new-tab-modal-cancel"
|
80
|
-
key="cancel"
|
81
|
-
variant="link"
|
82
|
-
onClick={handleModalToggle}
|
83
|
-
>
|
84
|
-
{__('Cancel')}
|
85
|
-
</Button>,
|
86
|
-
]}
|
87
|
-
>
|
88
|
-
{__('Are you sure you want to open all invocations in new tabs?')}
|
89
|
-
<br />
|
90
|
-
{__('This will open a new tab for each invocation.')}
|
91
|
-
<br />
|
92
|
-
{__('The number of invocations is:')} <b>{results.length}</b>
|
93
|
-
</Modal>
|
94
|
-
);
|
95
|
-
return (
|
96
|
-
<>
|
97
|
-
<OpenAllButton />
|
98
|
-
<OpenAllModal />
|
99
|
-
</>
|
100
|
-
);
|
101
|
-
};
|
102
|
-
|
103
|
-
OpenAlInvocations.propTypes = {
|
104
|
-
results: PropTypes.array.isRequired,
|
105
|
-
id: PropTypes.string.isRequired,
|
106
|
-
setShowAlert: PropTypes.func.isRequired,
|
107
|
-
};
|
108
|
-
|
109
|
-
PopupAlert.propTypes = {
|
110
|
-
setShowAlert: PropTypes.func.isRequired,
|
111
|
-
};
|
@@ -1,110 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
3
|
-
import '@testing-library/jest-dom/extend-expect';
|
4
|
-
import { OpenAlInvocations, PopupAlert } from '../OpenAlInvocations';
|
5
|
-
import { templateInvocationPageUrl } from '../JobInvocationConstants';
|
6
|
-
|
7
|
-
// Mock the templateInvocationPageUrl function
|
8
|
-
jest.mock('../JobInvocationConstants', () => ({
|
9
|
-
...jest.requireActual('../JobInvocationConstants'),
|
10
|
-
templateInvocationPageUrl: jest.fn((resultId, id) => `url/${resultId}/${id}`),
|
11
|
-
}));
|
12
|
-
|
13
|
-
describe('OpenAlInvocations', () => {
|
14
|
-
const mockResults = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
15
|
-
const mockSetShowAlert = jest.fn();
|
16
|
-
let windowSpy;
|
17
|
-
const windowOpen = window.open;
|
18
|
-
|
19
|
-
beforeAll(() => {
|
20
|
-
window.open = () => {};
|
21
|
-
});
|
22
|
-
afterAll(() => {
|
23
|
-
windowSpy.mockRestore();
|
24
|
-
jest.clearAllMocks();
|
25
|
-
window.open = windowOpen;
|
26
|
-
});
|
27
|
-
|
28
|
-
test('renders without crashing', () => {
|
29
|
-
render(
|
30
|
-
<OpenAlInvocations
|
31
|
-
results={mockResults}
|
32
|
-
id="test-id"
|
33
|
-
setShowAlert={mockSetShowAlert}
|
34
|
-
/>
|
35
|
-
);
|
36
|
-
});
|
37
|
-
|
38
|
-
test('opens links when results length is less than or equal to 3', () => {
|
39
|
-
render(
|
40
|
-
<OpenAlInvocations
|
41
|
-
results={mockResults}
|
42
|
-
id="test-id"
|
43
|
-
setShowAlert={mockSetShowAlert}
|
44
|
-
/>
|
45
|
-
);
|
46
|
-
|
47
|
-
const button = screen.getByRole('button', { name: /open all/i });
|
48
|
-
fireEvent.click(button);
|
49
|
-
|
50
|
-
expect(templateInvocationPageUrl).toHaveBeenCalledTimes(mockResults.length);
|
51
|
-
mockResults.forEach(result => {
|
52
|
-
expect(templateInvocationPageUrl).toHaveBeenCalledWith(
|
53
|
-
result.id,
|
54
|
-
'test-id'
|
55
|
-
);
|
56
|
-
});
|
57
|
-
});
|
58
|
-
|
59
|
-
test('shows modal when results length is greater than 3', () => {
|
60
|
-
const largeResults = [...mockResults, { id: 4 }];
|
61
|
-
render(
|
62
|
-
<OpenAlInvocations
|
63
|
-
results={largeResults}
|
64
|
-
id="test-id"
|
65
|
-
setShowAlert={mockSetShowAlert}
|
66
|
-
/>
|
67
|
-
);
|
68
|
-
|
69
|
-
const button = screen.getByRole('button', { name: /open all/i });
|
70
|
-
fireEvent.click(button);
|
71
|
-
|
72
|
-
expect(
|
73
|
-
screen.getAllByText(/open all invocations in new tabs/i)
|
74
|
-
).toHaveLength(2);
|
75
|
-
});
|
76
|
-
|
77
|
-
test('shows alert when popups are blocked', () => {
|
78
|
-
window.open = jest.fn().mockReturnValueOnce(null);
|
79
|
-
|
80
|
-
render(
|
81
|
-
<OpenAlInvocations
|
82
|
-
results={mockResults}
|
83
|
-
id="test-id"
|
84
|
-
setShowAlert={mockSetShowAlert}
|
85
|
-
/>
|
86
|
-
);
|
87
|
-
|
88
|
-
const button = screen.getByRole('button', { name: /open all/i });
|
89
|
-
fireEvent.click(button);
|
90
|
-
|
91
|
-
expect(mockSetShowAlert).toHaveBeenCalledWith(true);
|
92
|
-
});
|
93
|
-
});
|
94
|
-
|
95
|
-
describe('PopupAlert', () => {
|
96
|
-
const mockSetShowAlert = jest.fn();
|
97
|
-
|
98
|
-
test('renders without crashing', () => {
|
99
|
-
render(<PopupAlert setShowAlert={mockSetShowAlert} />);
|
100
|
-
});
|
101
|
-
|
102
|
-
test('closes alert when close button is clicked', () => {
|
103
|
-
render(<PopupAlert setShowAlert={mockSetShowAlert} />);
|
104
|
-
|
105
|
-
const closeButton = screen.getByRole('button', { name: /close/i });
|
106
|
-
fireEvent.click(closeButton);
|
107
|
-
|
108
|
-
expect(mockSetShowAlert).toHaveBeenCalledWith(false);
|
109
|
-
});
|
110
|
-
});
|