foreman_remote_execution 16.0.3 → 16.0.5
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_templates_controller.rb +2 -1
- data/app/controllers/cockpit_controller.rb +2 -2
- data/app/controllers/job_invocations_controller.rb +28 -1
- data/app/controllers/template_invocations_controller.rb +1 -1
- data/app/helpers/hosts_extensions_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +5 -28
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -1
- data/app/models/job_template.rb +5 -0
- data/app/models/{ssh_execution_provider.rb → script_execution_provider.rb} +2 -4
- data/config/initializers/inflections.rb +0 -1
- data/config/routes.rb +1 -0
- data/lib/foreman_remote_execution/engine.rb +10 -257
- data/lib/foreman_remote_execution/plugin.rb +246 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +1 -1
- data/test/functional/api/v2/job_templates_controller_test.rb +29 -0
- data/test/unit/actions/run_host_job_test.rb +1 -1
- data/test/unit/job_invocation_report_template_test.rb +6 -6
- data/test/unit/remote_execution_provider_test.rb +19 -19
- data/webpack/JobInvocationDetail/CheckboxesActions.js +196 -0
- data/webpack/JobInvocationDetail/{JobInvocationHostTableToolbar.js → DropdownFilter.js} +3 -6
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +7 -7
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +32 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +221 -91
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +30 -3
- data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +8 -22
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +20 -27
- 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 +1 -1
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +202 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +34 -28
- data/webpack/JobInvocationDetail/index.js +64 -31
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +26 -7
- data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +1 -1
- metadata +9 -7
- data/webpack/JobInvocationDetail/OpenAlInvocations.js +0 -111
- data/webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js +0 -110
@@ -3,11 +3,13 @@ import {
|
|
3
3
|
Flex,
|
4
4
|
PageSection,
|
5
5
|
PageSectionVariants,
|
6
|
+
Skeleton,
|
6
7
|
} from '@patternfly/react-core';
|
7
8
|
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
8
9
|
import { translate as __, documentLocale } from 'foremanReact/common/I18n';
|
9
10
|
import { stopInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
10
11
|
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
12
|
+
import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
|
11
13
|
import PropTypes from 'prop-types';
|
12
14
|
import React, { useEffect, useState } from 'react';
|
13
15
|
import { useDispatch, useSelector } from 'react-redux';
|
@@ -20,6 +22,7 @@ import {
|
|
20
22
|
DATE_OPTIONS,
|
21
23
|
JOB_INVOCATION_KEY,
|
22
24
|
STATUS,
|
25
|
+
STATUS_UPPERCASE,
|
23
26
|
} from './JobInvocationConstants';
|
24
27
|
import JobInvocationHostTable from './JobInvocationHostTable';
|
25
28
|
import JobInvocationOverview from './JobInvocationOverview';
|
@@ -38,25 +41,24 @@ const JobInvocationDetailPage = ({
|
|
38
41
|
const items = useSelector(selectItems);
|
39
42
|
const {
|
40
43
|
description,
|
44
|
+
failed = 0,
|
41
45
|
status_label: statusLabel,
|
42
46
|
task,
|
43
47
|
start_at: startAt,
|
44
|
-
targeting,
|
48
|
+
targeting = {},
|
45
49
|
} = items;
|
46
50
|
const finished =
|
47
51
|
statusLabel === STATUS.FAILED ||
|
48
52
|
statusLabel === STATUS.SUCCEEDED ||
|
49
53
|
statusLabel === STATUS.CANCELLED;
|
50
54
|
const autoRefresh = task?.state === STATUS.PENDING || false;
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
CURRENT_PERMISSIONS
|
55
|
-
);
|
55
|
+
useAPI('get', currentPermissionsUrl, {
|
56
|
+
key: CURRENT_PERMISSIONS,
|
57
|
+
});
|
56
58
|
const [selectedFilter, setSelectedFilter] = useState('');
|
57
59
|
|
58
|
-
const handleFilterChange =
|
59
|
-
setSelectedFilter(
|
60
|
+
const handleFilterChange = newFilter => {
|
61
|
+
setSelectedFilter(newFilter);
|
60
62
|
};
|
61
63
|
|
62
64
|
let isAlreadyStarted = false;
|
@@ -88,10 +90,22 @@ const JobInvocationDetailPage = ({
|
|
88
90
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
89
91
|
}, [dispatch, task?.id]);
|
90
92
|
|
93
|
+
const pageStatus =
|
94
|
+
items.id === undefined
|
95
|
+
? STATUS_UPPERCASE.PENDING
|
96
|
+
: STATUS_UPPERCASE.RESOLVED;
|
97
|
+
|
91
98
|
const breadcrumbOptions = {
|
92
99
|
breadcrumbItems: [
|
93
100
|
{ caption: __('Jobs'), url: `/job_invocations` },
|
94
|
-
{
|
101
|
+
{
|
102
|
+
caption:
|
103
|
+
pageStatus === STATUS_UPPERCASE.PENDING ? (
|
104
|
+
<Skeleton width="350px" />
|
105
|
+
) : (
|
106
|
+
description
|
107
|
+
),
|
108
|
+
},
|
95
109
|
],
|
96
110
|
isPf4: true,
|
97
111
|
};
|
@@ -101,25 +115,27 @@ const JobInvocationDetailPage = ({
|
|
101
115
|
<PageLayout
|
102
116
|
header={description}
|
103
117
|
breadcrumbOptions={breadcrumbOptions}
|
104
|
-
toolbarButtons={
|
105
|
-
<JobInvocationToolbarButtons
|
106
|
-
jobId={id}
|
107
|
-
data={items}
|
108
|
-
currentPermissions={response.results}
|
109
|
-
permissionsStatus={status}
|
110
|
-
/>
|
111
|
-
}
|
118
|
+
toolbarButtons={<JobInvocationToolbarButtons jobId={id} data={items} />}
|
112
119
|
searchable={false}
|
113
120
|
>
|
114
121
|
<Flex
|
115
122
|
className="job-invocation-detail-flex"
|
116
123
|
alignItems={{ default: 'alignItemsFlexStart' }}
|
117
124
|
>
|
118
|
-
<
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
125
|
+
<SkeletonLoader
|
126
|
+
status={pageStatus}
|
127
|
+
skeletonProps={{
|
128
|
+
height: 105,
|
129
|
+
width: 375,
|
130
|
+
}}
|
131
|
+
>
|
132
|
+
<JobInvocationSystemStatusChart
|
133
|
+
data={items}
|
134
|
+
isAlreadyStarted={isAlreadyStarted}
|
135
|
+
formattedStartDate={formattedStartDate}
|
136
|
+
onFilterChange={handleFilterChange}
|
137
|
+
/>
|
138
|
+
</SkeletonLoader>
|
123
139
|
<Divider
|
124
140
|
orientation={{
|
125
141
|
default: 'vertical',
|
@@ -129,34 +145,51 @@ const JobInvocationDetailPage = ({
|
|
129
145
|
className="job-overview"
|
130
146
|
alignItems={{ default: 'alignItemsCenter' }}
|
131
147
|
>
|
132
|
-
<
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
148
|
+
<SkeletonLoader
|
149
|
+
status={pageStatus}
|
150
|
+
skeletonProps={{
|
151
|
+
height: 105,
|
152
|
+
width: 270,
|
153
|
+
}}
|
154
|
+
>
|
155
|
+
<JobInvocationOverview
|
156
|
+
data={items}
|
157
|
+
isAlreadyStarted={isAlreadyStarted}
|
158
|
+
formattedStartDate={formattedStartDate}
|
159
|
+
/>
|
160
|
+
</SkeletonLoader>
|
138
161
|
</Flex>
|
139
162
|
</Flex>
|
140
163
|
<PageSection
|
141
164
|
variant={PageSectionVariants.light}
|
142
165
|
className="job-additional-info"
|
143
166
|
>
|
144
|
-
|
167
|
+
<SkeletonLoader
|
168
|
+
status={pageStatus}
|
169
|
+
skeletonProps={{ height: 150, width: '100%' }}
|
170
|
+
>
|
171
|
+
<JobAdditionInfo data={items} />
|
172
|
+
</SkeletonLoader>
|
145
173
|
</PageSection>
|
146
174
|
</PageLayout>
|
147
175
|
<PageSection
|
148
176
|
variant={PageSectionVariants.light}
|
149
177
|
className="job-details-table-section table-section"
|
150
178
|
>
|
151
|
-
|
179
|
+
<SkeletonLoader
|
180
|
+
status={pageStatus}
|
181
|
+
skeletonProps={{ height: 400, width: '100%' }}
|
182
|
+
>
|
152
183
|
<JobInvocationHostTable
|
153
184
|
id={id}
|
154
185
|
targeting={targeting}
|
186
|
+
failedCount={failed}
|
155
187
|
finished={finished}
|
156
188
|
autoRefresh={autoRefresh}
|
157
189
|
initialFilter={selectedFilter}
|
190
|
+
onFilterUpdate={handleFilterChange}
|
158
191
|
/>
|
159
|
-
|
192
|
+
</SkeletonLoader>
|
160
193
|
</PageSection>
|
161
194
|
</>
|
162
195
|
);
|
@@ -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,
|
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.0.5
|
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-07-28 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: deface
|
@@ -187,7 +187,7 @@ files:
|
|
187
187
|
- app/models/remote_execution_feature.rb
|
188
188
|
- app/models/remote_execution_provider.rb
|
189
189
|
- app/models/rex_mail_notification.rb
|
190
|
-
- app/models/
|
190
|
+
- app/models/script_execution_provider.rb
|
191
191
|
- app/models/target_remote_execution_proxy.rb
|
192
192
|
- app/models/targeting.rb
|
193
193
|
- app/models/targeting_host.rb
|
@@ -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
|
-
});
|