foreman_remote_execution 16.7.0 → 17.0.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/views/api/v2/job_invocations/main.json.rabl +1 -0
- data/app/views/job_templates/_alerts.html.erb +4 -0
- data/lib/foreman_remote_execution/plugin.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/webpack/JobInvocationDetail/JobInvocationActions.js +80 -91
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +3 -2
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +76 -42
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +1 -4
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +10 -12
- data/webpack/JobInvocationDetail/TemplateInvocation.js +31 -25
- data/webpack/JobInvocationDetail/__tests__/JobInvocationHostTablePolling.test.js +336 -0
- data/webpack/JobInvocationDetail/__tests__/JobInvocationPolling.test.js +223 -0
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +205 -154
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocationPolling.test.js +254 -0
- data/webpack/JobInvocationDetail/__tests__/fixtures.js +2 -0
- data/webpack/JobInvocationDetail/index.js +12 -25
- data/webpack/test_setup.js +1 -13
- metadata +7 -3
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createStore, applyMiddleware } from 'redux';
|
|
3
|
+
import thunk from 'redux-thunk';
|
|
4
|
+
import { Provider } from 'react-redux';
|
|
5
|
+
import { render, act } from '@testing-library/react';
|
|
6
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
7
|
+
import * as api from 'foremanReact/redux/API';
|
|
8
|
+
import * as selectors from '../JobInvocationSelectors';
|
|
9
|
+
import { TemplateInvocation } from '../TemplateInvocation';
|
|
10
|
+
import { mockTemplateInvocationResponse } from './fixtures';
|
|
11
|
+
|
|
12
|
+
jest.spyOn(api, 'get');
|
|
13
|
+
jest.mock('../JobInvocationSelectors');
|
|
14
|
+
|
|
15
|
+
jest.mock('foremanReact/components/ToastsList', () => ({
|
|
16
|
+
addToast: jest.fn(payload => ({ type: 'ADD_TOAST', payload })),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe('TemplateInvocation polling', () => {
|
|
20
|
+
const noop = () => {};
|
|
21
|
+
const reducer = (state = {}) => state;
|
|
22
|
+
const makeStore = () => createStore(reducer, applyMiddleware(thunk));
|
|
23
|
+
|
|
24
|
+
const pollingProps = {
|
|
25
|
+
hostID: '1',
|
|
26
|
+
jobID: '1',
|
|
27
|
+
isInTableView: false,
|
|
28
|
+
isExpanded: true,
|
|
29
|
+
hostName: 'example-host',
|
|
30
|
+
hostProxy: { name: 'example-proxy', href: '#' },
|
|
31
|
+
showOutputType: { stderr: true, stdout: true, debug: true },
|
|
32
|
+
setShowOutputType: noop,
|
|
33
|
+
showTemplatePreview: false,
|
|
34
|
+
setShowTemplatePreview: noop,
|
|
35
|
+
showCommand: false,
|
|
36
|
+
setShowCommand: noop,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let apiGetSpy;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
43
|
+
selectors.selectTemplateInvocationStatus.mockImplementation(() => () =>
|
|
44
|
+
'RESOLVED'
|
|
45
|
+
);
|
|
46
|
+
selectors.selectTemplateInvocation.mockImplementation(() => () =>
|
|
47
|
+
mockTemplateInvocationResponse
|
|
48
|
+
);
|
|
49
|
+
apiGetSpy = jest
|
|
50
|
+
.spyOn(api.APIActions, 'get')
|
|
51
|
+
.mockImplementation(({ handleSuccess }) => {
|
|
52
|
+
handleSuccess &&
|
|
53
|
+
handleSuccess({ data: { finished: false, auto_refresh: true } });
|
|
54
|
+
return { type: 'MOCK_GET' };
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
jest.clearAllTimers();
|
|
60
|
+
jest.useRealTimers();
|
|
61
|
+
apiGetSpy.mockRestore();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('fetches on mount when isExpanded is true', () => {
|
|
65
|
+
const localStore = makeStore();
|
|
66
|
+
render(
|
|
67
|
+
<Provider store={localStore}>
|
|
68
|
+
<TemplateInvocation {...pollingProps} isExpanded />
|
|
69
|
+
</Provider>
|
|
70
|
+
);
|
|
71
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('does not fetch on mount when isExpanded is false', () => {
|
|
75
|
+
const localStore = makeStore();
|
|
76
|
+
render(
|
|
77
|
+
<Provider store={localStore}>
|
|
78
|
+
<TemplateInvocation {...pollingProps} isExpanded={false} />
|
|
79
|
+
</Provider>
|
|
80
|
+
);
|
|
81
|
+
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('schedules next poll via setTimeout when auto_refresh is true and not finished', () => {
|
|
85
|
+
const localStore = makeStore();
|
|
86
|
+
render(
|
|
87
|
+
<Provider store={localStore}>
|
|
88
|
+
<TemplateInvocation {...pollingProps} />
|
|
89
|
+
</Provider>
|
|
90
|
+
);
|
|
91
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
92
|
+
|
|
93
|
+
act(() => jest.advanceTimersByTime(5000));
|
|
94
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(2);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('does not schedule next poll when finished is true', () => {
|
|
98
|
+
apiGetSpy.mockImplementation(({ handleSuccess }) => {
|
|
99
|
+
handleSuccess &&
|
|
100
|
+
handleSuccess({ data: { finished: true, auto_refresh: true } });
|
|
101
|
+
return { type: 'MOCK_GET' };
|
|
102
|
+
});
|
|
103
|
+
const localStore = makeStore();
|
|
104
|
+
render(
|
|
105
|
+
<Provider store={localStore}>
|
|
106
|
+
<TemplateInvocation {...pollingProps} />
|
|
107
|
+
</Provider>
|
|
108
|
+
);
|
|
109
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
110
|
+
|
|
111
|
+
act(() => jest.advanceTimersByTime(5000));
|
|
112
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('does not schedule next poll when auto_refresh is false', () => {
|
|
116
|
+
apiGetSpy.mockImplementation(({ handleSuccess }) => {
|
|
117
|
+
handleSuccess &&
|
|
118
|
+
handleSuccess({ data: { finished: false, auto_refresh: false } });
|
|
119
|
+
return { type: 'MOCK_GET' };
|
|
120
|
+
});
|
|
121
|
+
const localStore = makeStore();
|
|
122
|
+
render(
|
|
123
|
+
<Provider store={localStore}>
|
|
124
|
+
<TemplateInvocation {...pollingProps} />
|
|
125
|
+
</Provider>
|
|
126
|
+
);
|
|
127
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
128
|
+
|
|
129
|
+
act(() => jest.advanceTimersByTime(5000));
|
|
130
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('clears the polling timeout and sets cancelled on unmount', () => {
|
|
134
|
+
const localStore = makeStore();
|
|
135
|
+
const { unmount } = render(
|
|
136
|
+
<Provider store={localStore}>
|
|
137
|
+
<TemplateInvocation {...pollingProps} />
|
|
138
|
+
</Provider>
|
|
139
|
+
);
|
|
140
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
141
|
+
|
|
142
|
+
unmount();
|
|
143
|
+
act(() => jest.advanceTimersByTime(5000));
|
|
144
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('cancels in-flight callback when unmounted before handleSuccess runs', () => {
|
|
148
|
+
let capturedHandleSuccess;
|
|
149
|
+
apiGetSpy.mockImplementation(({ handleSuccess }) => {
|
|
150
|
+
capturedHandleSuccess = handleSuccess;
|
|
151
|
+
return { type: 'MOCK_GET' };
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const localStore = makeStore();
|
|
155
|
+
const { unmount } = render(
|
|
156
|
+
<Provider store={localStore}>
|
|
157
|
+
<TemplateInvocation {...pollingProps} />
|
|
158
|
+
</Provider>
|
|
159
|
+
);
|
|
160
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
161
|
+
|
|
162
|
+
unmount();
|
|
163
|
+
|
|
164
|
+
act(() => {
|
|
165
|
+
capturedHandleSuccess({ data: { finished: false, auto_refresh: true } });
|
|
166
|
+
jest.advanceTimersByTime(5000);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('re-fetches when isExpanded changes from false to true', () => {
|
|
173
|
+
const localStore = makeStore();
|
|
174
|
+
const { rerender } = render(
|
|
175
|
+
<Provider store={localStore}>
|
|
176
|
+
<TemplateInvocation {...pollingProps} isExpanded={false} />
|
|
177
|
+
</Provider>
|
|
178
|
+
);
|
|
179
|
+
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
180
|
+
|
|
181
|
+
rerender(
|
|
182
|
+
<Provider store={localStore}>
|
|
183
|
+
<TemplateInvocation {...pollingProps} isExpanded />
|
|
184
|
+
</Provider>
|
|
185
|
+
);
|
|
186
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('does not re-fetch on expand when response is already finished', () => {
|
|
190
|
+
selectors.selectTemplateInvocation.mockImplementation(() => () => ({
|
|
191
|
+
...mockTemplateInvocationResponse,
|
|
192
|
+
finished: true,
|
|
193
|
+
}));
|
|
194
|
+
apiGetSpy.mockImplementation(({ handleSuccess }) => {
|
|
195
|
+
handleSuccess &&
|
|
196
|
+
handleSuccess({ data: { finished: true, auto_refresh: false } });
|
|
197
|
+
return { type: 'MOCK_GET' };
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const localStore = makeStore();
|
|
201
|
+
const { rerender } = render(
|
|
202
|
+
<Provider store={localStore}>
|
|
203
|
+
<TemplateInvocation {...pollingProps} isExpanded={false} />
|
|
204
|
+
</Provider>
|
|
205
|
+
);
|
|
206
|
+
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
207
|
+
|
|
208
|
+
rerender(
|
|
209
|
+
<Provider store={localStore}>
|
|
210
|
+
<TemplateInvocation {...pollingProps} isExpanded />
|
|
211
|
+
</Provider>
|
|
212
|
+
);
|
|
213
|
+
// Selector already returns finished=true → guard skips dispatchFetch
|
|
214
|
+
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
215
|
+
|
|
216
|
+
rerender(
|
|
217
|
+
<Provider store={localStore}>
|
|
218
|
+
<TemplateInvocation {...pollingProps} isExpanded={false} />
|
|
219
|
+
</Provider>
|
|
220
|
+
);
|
|
221
|
+
rerender(
|
|
222
|
+
<Provider store={localStore}>
|
|
223
|
+
<TemplateInvocation {...pollingProps} isExpanded />
|
|
224
|
+
</Provider>
|
|
225
|
+
);
|
|
226
|
+
// Second expand: still finished → still no fetch
|
|
227
|
+
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('cancels existing poll and starts fresh when isExpanded changes', () => {
|
|
231
|
+
const localStore = makeStore();
|
|
232
|
+
const { rerender } = render(
|
|
233
|
+
<Provider store={localStore}>
|
|
234
|
+
<TemplateInvocation {...pollingProps} isExpanded />
|
|
235
|
+
</Provider>
|
|
236
|
+
);
|
|
237
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
238
|
+
|
|
239
|
+
rerender(
|
|
240
|
+
<Provider store={localStore}>
|
|
241
|
+
<TemplateInvocation {...pollingProps} isExpanded={false} />
|
|
242
|
+
</Provider>
|
|
243
|
+
);
|
|
244
|
+
act(() => jest.advanceTimersByTime(5000));
|
|
245
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
246
|
+
|
|
247
|
+
rerender(
|
|
248
|
+
<Provider store={localStore}>
|
|
249
|
+
<TemplateInvocation {...pollingProps} isExpanded />
|
|
250
|
+
</Provider>
|
|
251
|
+
);
|
|
252
|
+
expect(apiGetSpy).toHaveBeenCalledTimes(2);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -42,6 +42,7 @@ export const jobInvocationData = {
|
|
|
42
42
|
id: '37ad5ead-51de-4798-bc73-a17687c4d5aa',
|
|
43
43
|
state: 'stopped',
|
|
44
44
|
started_at: '2024-01-01 12:34:56 +0100',
|
|
45
|
+
cancellable: true,
|
|
45
46
|
},
|
|
46
47
|
template_invocations: [
|
|
47
48
|
{
|
|
@@ -113,6 +114,7 @@ export const jobInvocationDataRecurring = {
|
|
|
113
114
|
task: {
|
|
114
115
|
id: '37ad5ead-51de-4798-bc73-a17687c4d5aa',
|
|
115
116
|
state: 'scheduled',
|
|
117
|
+
cancellable: true,
|
|
116
118
|
},
|
|
117
119
|
mode: 'recurring',
|
|
118
120
|
recurrence: {
|
|
@@ -5,13 +5,12 @@ import {
|
|
|
5
5
|
PageSectionVariants,
|
|
6
6
|
Skeleton,
|
|
7
7
|
} from '@patternfly/react-core';
|
|
8
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
8
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
9
9
|
import { translate as __, documentLocale } from 'foremanReact/common/I18n';
|
|
10
10
|
import { useDispatch, useSelector } from 'react-redux';
|
|
11
11
|
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
|
12
12
|
import PropTypes from 'prop-types';
|
|
13
13
|
import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
|
|
14
|
-
import { stopInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
|
15
14
|
import { STATUS as API_STATUS } from 'foremanReact/constants';
|
|
16
15
|
import {
|
|
17
16
|
selectAPIErrorMessage,
|
|
@@ -25,12 +24,15 @@ import JobInvocationOverview from './JobInvocationOverview';
|
|
|
25
24
|
import JobInvocationSystemStatusChart from './JobInvocationSystemStatusChart';
|
|
26
25
|
import JobInvocationEmptyState from './JobInvocationEmptyState';
|
|
27
26
|
import JobInvocationToolbarButtons from './JobInvocationToolbarButtons';
|
|
28
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
getJobInvocation,
|
|
29
|
+
stopJobInvocationPolling,
|
|
30
|
+
isJobFinished,
|
|
31
|
+
} from './JobInvocationActions';
|
|
29
32
|
import './JobInvocationDetail.scss';
|
|
30
33
|
import {
|
|
31
34
|
DATE_OPTIONS,
|
|
32
35
|
JOB_INVOCATION_KEY,
|
|
33
|
-
STATUS,
|
|
34
36
|
STATUS_UPPERCASE,
|
|
35
37
|
} from './JobInvocationConstants';
|
|
36
38
|
import { selectItems } from './JobInvocationSelectors';
|
|
@@ -50,11 +52,8 @@ const JobInvocationDetailPage = ({
|
|
|
50
52
|
start_at: startAt,
|
|
51
53
|
targeting = {},
|
|
52
54
|
} = items;
|
|
53
|
-
const finished =
|
|
54
|
-
|
|
55
|
-
statusLabel === STATUS.SUCCEEDED ||
|
|
56
|
-
statusLabel === STATUS.CANCELLED;
|
|
57
|
-
const autoRefresh = task?.state === STATUS.PENDING || false;
|
|
55
|
+
const finished = isJobFinished(statusLabel);
|
|
56
|
+
const pollTimeoutRef = useRef({ timeoutId: null, cancel: () => {} });
|
|
58
57
|
const jobInvocationApiStatus = useSelector(state =>
|
|
59
58
|
selectAPIStatus(state, JOB_INVOCATION_KEY)
|
|
60
59
|
);
|
|
@@ -82,21 +81,11 @@ const JobInvocationDetailPage = ({
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
useEffect(() => {
|
|
85
|
-
dispatch(getJobInvocation(`/api/job_invocations/${id}
|
|
86
|
-
if (finished && !autoRefresh) {
|
|
87
|
-
dispatch(stopInterval(JOB_INVOCATION_KEY));
|
|
88
|
-
}
|
|
84
|
+
dispatch(getJobInvocation(`/api/job_invocations/${id}`, pollTimeoutRef));
|
|
89
85
|
return () => {
|
|
90
|
-
|
|
86
|
+
stopJobInvocationPolling(pollTimeoutRef);
|
|
91
87
|
};
|
|
92
|
-
}, [dispatch, id
|
|
93
|
-
|
|
94
|
-
const taskId = task?.id;
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (taskId !== undefined) {
|
|
97
|
-
dispatch(getTask(`${taskId}`));
|
|
98
|
-
}
|
|
99
|
-
}, [dispatch, taskId]);
|
|
88
|
+
}, [dispatch, id]);
|
|
100
89
|
|
|
101
90
|
const apiFailed = jobInvocationApiStatus === API_STATUS.ERROR;
|
|
102
91
|
|
|
@@ -216,10 +205,8 @@ const JobInvocationDetailPage = ({
|
|
|
216
205
|
<JobInvocationHostTable
|
|
217
206
|
id={id}
|
|
218
207
|
targeting={targeting}
|
|
219
|
-
finished={finished}
|
|
220
|
-
autoRefresh={autoRefresh}
|
|
221
208
|
initialFilter={selectedFilter}
|
|
222
|
-
|
|
209
|
+
jobFinished={finished}
|
|
223
210
|
onFilterUpdate={handleFilterChange}
|
|
224
211
|
/>
|
|
225
212
|
</SkeletonLoader>
|
data/webpack/test_setup.js
CHANGED
|
@@ -1,13 +1 @@
|
|
|
1
|
-
import '
|
|
2
|
-
import 'regenerator-runtime/runtime';
|
|
3
|
-
|
|
4
|
-
import { configure } from '@theforeman/test';
|
|
5
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
6
|
-
import Adapter from 'enzyme-adapter-react-16';
|
|
7
|
-
|
|
8
|
-
configure({ adapter: new Adapter() });
|
|
9
|
-
|
|
10
|
-
// Mocking translation function
|
|
11
|
-
global.__ = str => str;
|
|
12
|
-
global.n__ = str => str;
|
|
13
|
-
global.Jed = { sprintf: str => str };
|
|
1
|
+
import 'foremanJSTestSetup';
|
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:
|
|
4
|
+
version: 17.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Foreman Remote Execution team
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-08-03 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: deface
|
|
@@ -202,6 +202,7 @@ files:
|
|
|
202
202
|
- app/views/job_invocations/show.js.erb
|
|
203
203
|
- app/views/job_invocations/show.json.erb
|
|
204
204
|
- app/views/job_invocations/welcome.html.erb
|
|
205
|
+
- app/views/job_templates/_alerts.html.erb
|
|
205
206
|
- app/views/job_templates/_custom_tab_headers.html.erb
|
|
206
207
|
- app/views/job_templates/_custom_tabs.html.erb
|
|
207
208
|
- app/views/job_templates/_import_job_template_modal.html.erb
|
|
@@ -397,10 +398,13 @@ files:
|
|
|
397
398
|
- webpack/JobInvocationDetail/TemplateInvocationComponents/index.scss
|
|
398
399
|
- webpack/JobInvocationDetail/TemplateInvocationPage.js
|
|
399
400
|
- webpack/JobInvocationDetail/__tests__/JobInvocationDetailEmptyState.test.js
|
|
401
|
+
- webpack/JobInvocationDetail/__tests__/JobInvocationHostTablePolling.test.js
|
|
402
|
+
- webpack/JobInvocationDetail/__tests__/JobInvocationPolling.test.js
|
|
400
403
|
- webpack/JobInvocationDetail/__tests__/MainInformation.test.js
|
|
401
404
|
- webpack/JobInvocationDetail/__tests__/OutputCodeBlock.test.js
|
|
402
405
|
- webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js
|
|
403
406
|
- webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js
|
|
407
|
+
- webpack/JobInvocationDetail/__tests__/TemplateInvocationPolling.test.js
|
|
404
408
|
- webpack/JobInvocationDetail/__tests__/fixtures.js
|
|
405
409
|
- webpack/JobInvocationDetail/__tests__/foremanTestHelpers.js
|
|
406
410
|
- webpack/JobInvocationDetail/index.js
|
|
@@ -564,7 +568,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
564
568
|
- !ruby/object:Gem::Version
|
|
565
569
|
version: '0'
|
|
566
570
|
requirements: []
|
|
567
|
-
rubygems_version: 4.0.
|
|
571
|
+
rubygems_version: 4.0.16
|
|
568
572
|
specification_version: 4
|
|
569
573
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|
|
570
574
|
management functionality with remote management functionality.
|