foreman_remote_execution 18.0.0 → 18.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/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +54 -9
- data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +54 -9
- data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +54 -9
- data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +66 -21
- data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +86 -41
- data/app/assets/javascripts/foreman_remote_execution/locale/ka/foreman_remote_execution.js +111 -66
- data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +67 -22
- data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +54 -9
- data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +54 -9
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +60 -15
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +54 -9
- data/app/assets/javascripts/foreman_remote_execution/template_invocation.js +0 -170
- data/app/assets/stylesheets/foreman_remote_execution/job_invocations.scss +0 -6
- data/app/controllers/job_invocations_controller.rb +1 -98
- data/app/helpers/job_invocations_helper.rb +0 -49
- data/app/helpers/remote_execution_helper.rb +23 -123
- data/app/models/remote_execution_provider.rb +1 -1
- data/config/routes.rb +0 -6
- data/lib/foreman_remote_execution/plugin.rb +2 -2
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/de/foreman_remote_execution.po +54 -9
- data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en_GB/foreman_remote_execution.po +54 -9
- data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/es/foreman_remote_execution.po +54 -9
- data/locale/foreman_remote_execution.pot +296 -230
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +66 -21
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +86 -41
- data/locale/ka/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ka/foreman_remote_execution.po +111 -66
- data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ko/foreman_remote_execution.po +67 -22
- data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/pt_BR/foreman_remote_execution.po +54 -9
- data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ru/foreman_remote_execution.po +54 -9
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +60 -15
- data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_TW/foreman_remote_execution.po +54 -9
- data/package.json +1 -14
- data/test/functional/job_invocations_controller_test.rb +0 -77
- data/test/unit/remote_execution_provider_test.rb +1 -1
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +5 -0
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +0 -10
- data/webpack/JobInvocationDetail/TemplateInvocation.js +12 -69
- data/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js +94 -72
- data/webpack/JobInvocationDetail/TemplateInvocationHelpers.js +33 -0
- data/webpack/JobInvocationDetail/__tests__/JobInvocationDetailEmptyState.test.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +0 -6
- data/webpack/JobInvocationDetail/__tests__/OutputCodeBlock.test.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +5 -1
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocationHelpers.test.js +94 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocationPolling.test.js +338 -139
- data/webpack/JobInvocationDetail/useTemplateInvocationOutputPolling.js +185 -0
- data/webpack/JobWizard/JobWizardPageRerun.js +0 -10
- data/webpack/JobWizard/__tests__/autofill.test.js +23 -0
- data/webpack/JobWizard/__tests__/integration.test.js +1 -1
- data/webpack/JobWizard/autofill.js +18 -4
- data/webpack/JobWizard/index.js +1 -18
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +49 -1
- data/webpack/global_index.js +0 -2
- data/webpack/index.js +0 -15
- data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +1 -1
- metadata +6 -53
- data/app/assets/stylesheets/foreman_remote_execution/modal_window.scss +0 -4
- data/app/helpers/job_invocations_chart_helper.rb +0 -80
- data/app/views/job_invocations/_card_results.html.erb +0 -12
- data/app/views/job_invocations/_card_schedule.html.erb +0 -33
- data/app/views/job_invocations/_card_target_hosts.html.erb +0 -57
- data/app/views/job_invocations/_card_user_input.html.erb +0 -16
- data/app/views/job_invocations/_description_fields.html.erb +0 -4
- data/app/views/job_invocations/_form.html.erb +0 -144
- data/app/views/job_invocations/_preview_hosts_modal.html.erb +0 -15
- data/app/views/job_invocations/_rerun_taxonomies.html.erb +0 -22
- data/app/views/job_invocations/_tab_hosts.html.erb +0 -11
- data/app/views/job_invocations/_tab_overview.html.erb +0 -41
- data/app/views/job_invocations/_tab_preview_templates.html.erb +0 -20
- data/app/views/job_invocations/_user_input.html.erb +0 -21
- data/app/views/job_invocations/new.html.erb +0 -9
- data/app/views/job_invocations/refresh.js.erb +0 -4
- data/app/views/job_invocations/show.html.erb +0 -59
- data/app/views/job_invocations/show.js.erb +0 -12
- data/app/views/job_invocations/show.json.erb +0 -5
- data/test/support/remote_execution_helper.rb +0 -5
- data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +0 -62
- data/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js +0 -2
- data/webpack/react_app/components/TargetingHosts/TargetingHostsHelpers.js +0 -10
- data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.js +0 -45
- data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +0 -30
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +0 -78
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +0 -3
- data/webpack/react_app/components/TargetingHosts/TargetingHostsSelectors.js +0 -25
- data/webpack/react_app/components/TargetingHosts/__tests__/HostItem.test.js +0 -76
- data/webpack/react_app/components/TargetingHosts/__tests__/HostStatus.test.js +0 -6
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHosts.test.js +0 -6
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsPage.test.js +0 -9
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +0 -31
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostStatus.test.js.snap +0 -12
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap +0 -96
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +0 -75
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +0 -16
- data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +0 -56
- data/webpack/react_app/components/TargetingHosts/components/HostItem.js +0 -39
- data/webpack/react_app/components/TargetingHosts/components/HostStatus.js +0 -54
- data/webpack/react_app/components/TargetingHosts/index.js +0 -150
- data/webpack/react_app/components/jobInvocations/AggregateStatus/index.js +0 -60
- data/webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js +0 -76
- data/webpack/react_app/components/jobInvocations/index.js +0 -94
- data/webpack/react_app/extend/reducers.js +0 -5
- data/webpack/react_app/redux/actions/jobInvocations/index.js +0 -87
- data/webpack/react_app/redux/consts.js +0 -5
- data/webpack/react_app/redux/reducers/index.js +0 -6
- data/webpack/react_app/redux/reducers/jobInvocations/index.fixtures.js +0 -46
- data/webpack/react_app/redux/reducers/jobInvocations/index.js +0 -34
- data/webpack/react_app/redux/reducers/jobInvocations/index.test.js +0 -43
|
@@ -1,25 +1,40 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import { createStore, applyMiddleware } from 'redux';
|
|
3
4
|
import thunk from 'redux-thunk';
|
|
4
5
|
import { Provider } from 'react-redux';
|
|
5
|
-
import { render, act } from '@testing-library/react';
|
|
6
|
-
import '@testing-library/jest-dom
|
|
6
|
+
import { render, act, cleanup, screen } from '@testing-library/react';
|
|
7
|
+
import '@testing-library/jest-dom';
|
|
8
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
|
+
import API from 'foremanReact/API';
|
|
10
|
+
import { addToast } from 'foremanReact/components/ToastsList';
|
|
7
11
|
import * as api from 'foremanReact/redux/API';
|
|
8
12
|
import * as selectors from '../JobInvocationSelectors';
|
|
9
13
|
import { TemplateInvocation } from '../TemplateInvocation';
|
|
10
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
OUTPUT_MAX_REFRESH_INTERVAL_MS,
|
|
16
|
+
OUTPUT_REFRESH_INTERVAL_MS,
|
|
17
|
+
} from '../JobInvocationConstants';
|
|
18
|
+
import {
|
|
19
|
+
jobInvocationOutput,
|
|
20
|
+
mockTemplateInvocationResponse,
|
|
21
|
+
} from './fixtures';
|
|
11
22
|
|
|
12
|
-
jest.
|
|
23
|
+
jest.mock('foremanReact/API');
|
|
13
24
|
jest.mock('../JobInvocationSelectors');
|
|
14
25
|
|
|
15
26
|
jest.mock('foremanReact/components/ToastsList', () => ({
|
|
16
27
|
addToast: jest.fn(payload => ({ type: 'ADD_TOAST', payload })),
|
|
17
28
|
}));
|
|
18
29
|
|
|
30
|
+
const apiActionsGetSpy = jest.spyOn(api.APIActions, 'get');
|
|
31
|
+
|
|
19
32
|
describe('TemplateInvocation polling', () => {
|
|
20
33
|
const noop = () => {};
|
|
21
34
|
const reducer = (state = {}) => state;
|
|
22
35
|
const makeStore = () => createStore(reducer, applyMiddleware(thunk));
|
|
36
|
+
const outputURL = '/api/job_invocations/1/hosts/1';
|
|
37
|
+
const detailsURL = '/show_template_invocation_by_host/1/job_invocation/1';
|
|
23
38
|
|
|
24
39
|
const pollingProps = {
|
|
25
40
|
hostID: '1',
|
|
@@ -35,8 +50,28 @@ describe('TemplateInvocation polling', () => {
|
|
|
35
50
|
showCommand: false,
|
|
36
51
|
setShowCommand: noop,
|
|
37
52
|
};
|
|
53
|
+
const renderInvocation = (props = {}) =>
|
|
54
|
+
render(
|
|
55
|
+
<Provider store={makeStore()}>
|
|
56
|
+
<TemplateInvocation {...pollingProps} {...props} />
|
|
57
|
+
</Provider>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const respond = (request, overrides = {}) => {
|
|
61
|
+
const data = {
|
|
62
|
+
...mockTemplateInvocationResponse,
|
|
63
|
+
finished: false,
|
|
64
|
+
auto_refresh: true,
|
|
65
|
+
...overrides.details,
|
|
66
|
+
};
|
|
67
|
+
if (request.handleSuccess) request.handleSuccess({ data });
|
|
68
|
+
return { type: 'MOCK_GET' };
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const outputResponse = (overrides = {}) =>
|
|
72
|
+
Promise.resolve({ data: { output: [], refresh: true, ...overrides } });
|
|
38
73
|
|
|
39
|
-
|
|
74
|
+
const flushPromises = () => act(async () => {});
|
|
40
75
|
|
|
41
76
|
beforeEach(() => {
|
|
42
77
|
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
@@ -46,209 +81,373 @@ describe('TemplateInvocation polling', () => {
|
|
|
46
81
|
selectors.selectTemplateInvocation.mockImplementation(() => () =>
|
|
47
82
|
mockTemplateInvocationResponse
|
|
48
83
|
);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.mockImplementation(({ handleSuccess }) => {
|
|
52
|
-
handleSuccess &&
|
|
53
|
-
handleSuccess({ data: { finished: false, auto_refresh: true } });
|
|
54
|
-
return { type: 'MOCK_GET' };
|
|
55
|
-
});
|
|
84
|
+
API.get.mockImplementation(() => outputResponse());
|
|
85
|
+
apiActionsGetSpy.mockImplementation(request => respond(request));
|
|
56
86
|
});
|
|
57
87
|
|
|
58
88
|
afterEach(() => {
|
|
89
|
+
cleanup();
|
|
59
90
|
jest.clearAllTimers();
|
|
60
91
|
jest.useRealTimers();
|
|
61
|
-
|
|
92
|
+
API.get.mockReset();
|
|
93
|
+
apiActionsGetSpy.mockReset();
|
|
94
|
+
addToast.mockClear();
|
|
62
95
|
});
|
|
63
96
|
|
|
64
|
-
it('
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
it('requests only output added since the latest displayed chunk', () => {
|
|
98
|
+
renderInvocation();
|
|
99
|
+
|
|
100
|
+
expect(API.get).toHaveBeenCalledWith(
|
|
101
|
+
outputURL,
|
|
102
|
+
{},
|
|
103
|
+
{
|
|
104
|
+
since: jobInvocationOutput[jobInvocationOutput.length - 1].timestamp,
|
|
105
|
+
}
|
|
70
106
|
);
|
|
71
|
-
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
72
107
|
});
|
|
73
108
|
|
|
74
|
-
it('
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
109
|
+
it('uses newly appended output as the cursor for the next poll', async () => {
|
|
110
|
+
const latestTimestamp = 1733931150.2044532;
|
|
111
|
+
API.get.mockImplementation(() =>
|
|
112
|
+
outputResponse({
|
|
113
|
+
output: [
|
|
114
|
+
{
|
|
115
|
+
output_type: 'stdout',
|
|
116
|
+
output: 'New live output\n',
|
|
117
|
+
timestamp: latestTimestamp,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
})
|
|
80
121
|
);
|
|
81
|
-
|
|
122
|
+
|
|
123
|
+
renderInvocation();
|
|
124
|
+
await flushPromises();
|
|
125
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
126
|
+
|
|
127
|
+
expect(API.get.mock.calls[1]).toEqual([
|
|
128
|
+
outputURL,
|
|
129
|
+
{},
|
|
130
|
+
{ since: latestTimestamp },
|
|
131
|
+
]);
|
|
82
132
|
});
|
|
83
133
|
|
|
84
|
-
it('
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
);
|
|
91
|
-
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
134
|
+
it('polls output once per output refresh interval', async () => {
|
|
135
|
+
renderInvocation();
|
|
136
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
137
|
+
|
|
138
|
+
await flushPromises();
|
|
139
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
92
140
|
|
|
93
|
-
|
|
94
|
-
expect(
|
|
141
|
+
expect(API.get).toHaveBeenCalledTimes(2);
|
|
142
|
+
expect(API.get.mock.calls[1][0]).toBe(outputURL);
|
|
95
143
|
});
|
|
96
144
|
|
|
97
|
-
it('
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
145
|
+
it('gradually backs off while the job produces no output', async () => {
|
|
146
|
+
renderInvocation();
|
|
147
|
+
await flushPromises();
|
|
148
|
+
|
|
149
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
150
|
+
expect(API.get).toHaveBeenCalledTimes(2);
|
|
151
|
+
await flushPromises();
|
|
152
|
+
|
|
153
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
154
|
+
expect(API.get).toHaveBeenCalledTimes(2);
|
|
155
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
156
|
+
expect(API.get).toHaveBeenCalledTimes(3);
|
|
157
|
+
await flushPromises();
|
|
158
|
+
|
|
159
|
+
act(() =>
|
|
160
|
+
jest.advanceTimersByTime(
|
|
161
|
+
OUTPUT_MAX_REFRESH_INTERVAL_MS - OUTPUT_REFRESH_INTERVAL_MS
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
expect(API.get).toHaveBeenCalledTimes(3);
|
|
165
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
166
|
+
expect(API.get).toHaveBeenCalledTimes(4);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('returns to fast polling when new output arrives', async () => {
|
|
170
|
+
let outputRequests = 0;
|
|
171
|
+
API.get.mockImplementation(() => {
|
|
172
|
+
outputRequests += 1;
|
|
173
|
+
return outputResponse({
|
|
174
|
+
output:
|
|
175
|
+
outputRequests === 3
|
|
176
|
+
? [
|
|
177
|
+
{
|
|
178
|
+
output_type: 'stdout',
|
|
179
|
+
output: 'New live output\n',
|
|
180
|
+
timestamp: 1733931150.2044532,
|
|
181
|
+
},
|
|
182
|
+
]
|
|
183
|
+
: [],
|
|
184
|
+
});
|
|
102
185
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
186
|
+
|
|
187
|
+
renderInvocation();
|
|
188
|
+
await flushPromises();
|
|
189
|
+
|
|
190
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
191
|
+
await flushPromises();
|
|
192
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS * 2));
|
|
193
|
+
expect(API.get).toHaveBeenCalledTimes(3);
|
|
194
|
+
await flushPromises();
|
|
195
|
+
|
|
196
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
197
|
+
expect(API.get).toHaveBeenCalledTimes(4);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('appends new output without replacing the displayed output', async () => {
|
|
201
|
+
API.get.mockImplementation(() =>
|
|
202
|
+
outputResponse({
|
|
203
|
+
output: [
|
|
204
|
+
{
|
|
205
|
+
output_type: 'stdout',
|
|
206
|
+
output: 'New live output\n',
|
|
207
|
+
timestamp: 1733931150.2044532,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
})
|
|
108
211
|
);
|
|
109
|
-
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
110
212
|
|
|
111
|
-
|
|
112
|
-
|
|
213
|
+
renderInvocation();
|
|
214
|
+
await flushPromises();
|
|
215
|
+
|
|
216
|
+
expect(screen.getByText('This is red text')).toBeInTheDocument();
|
|
217
|
+
expect(screen.getByText('New live output')).toBeInTheDocument();
|
|
113
218
|
});
|
|
114
219
|
|
|
115
|
-
it('does not
|
|
116
|
-
|
|
117
|
-
handleSuccess &&
|
|
118
|
-
handleSuccess({ data: { finished: false, auto_refresh: false } });
|
|
119
|
-
return { type: 'MOCK_GET' };
|
|
120
|
-
});
|
|
220
|
+
it('does not replace live output with a later Redux update', async () => {
|
|
221
|
+
let response = mockTemplateInvocationResponse;
|
|
121
222
|
const localStore = makeStore();
|
|
223
|
+
selectors.selectTemplateInvocation.mockImplementation(() => () => response);
|
|
224
|
+
API.get.mockImplementation(() =>
|
|
225
|
+
outputResponse({
|
|
226
|
+
output: [
|
|
227
|
+
{
|
|
228
|
+
output_type: 'stdout',
|
|
229
|
+
output: 'New live output\n',
|
|
230
|
+
timestamp: 1733931150.2044532,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
|
|
122
236
|
render(
|
|
123
237
|
<Provider store={localStore}>
|
|
124
238
|
<TemplateInvocation {...pollingProps} />
|
|
125
239
|
</Provider>
|
|
126
240
|
);
|
|
127
|
-
|
|
241
|
+
await flushPromises();
|
|
242
|
+
|
|
243
|
+
response = {
|
|
244
|
+
...mockTemplateInvocationResponse,
|
|
245
|
+
output: [
|
|
246
|
+
{
|
|
247
|
+
output_type: 'stdout',
|
|
248
|
+
output: 'Stale Redux output\n',
|
|
249
|
+
timestamp: 1,
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
};
|
|
253
|
+
act(() => {
|
|
254
|
+
localStore.dispatch({ type: 'UPDATE' });
|
|
255
|
+
});
|
|
128
256
|
|
|
129
|
-
|
|
130
|
-
expect(
|
|
257
|
+
expect(screen.getByText('New live output')).toBeInTheDocument();
|
|
258
|
+
expect(screen.queryByText('Stale Redux output')).not.toBeInTheDocument();
|
|
131
259
|
});
|
|
132
260
|
|
|
133
|
-
it('
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<TemplateInvocation {...pollingProps} />
|
|
138
|
-
</Provider>
|
|
261
|
+
it('refreshes the complete details once after host output finishes', async () => {
|
|
262
|
+
API.get.mockImplementation(() => outputResponse({ refresh: false }));
|
|
263
|
+
apiActionsGetSpy.mockImplementation(request =>
|
|
264
|
+
respond(request, { details: { finished: false, auto_refresh: true } })
|
|
139
265
|
);
|
|
140
|
-
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
141
266
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
267
|
+
renderInvocation();
|
|
268
|
+
await flushPromises();
|
|
269
|
+
|
|
270
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
271
|
+
expect(API.get.mock.calls[0][0]).toBe(outputURL);
|
|
272
|
+
expect(apiActionsGetSpy).toHaveBeenCalledTimes(1);
|
|
273
|
+
expect(apiActionsGetSpy.mock.calls[0][0].url).toBe(detailsURL);
|
|
274
|
+
|
|
275
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS * 2));
|
|
276
|
+
|
|
277
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
278
|
+
expect(apiActionsGetSpy).toHaveBeenCalledTimes(1);
|
|
145
279
|
});
|
|
146
280
|
|
|
147
|
-
it('
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return { type: 'MOCK_GET' };
|
|
152
|
-
});
|
|
281
|
+
it('loads complete details first when no cached response exists', () => {
|
|
282
|
+
selectors.selectTemplateInvocation.mockImplementation(() => () =>
|
|
283
|
+
undefined
|
|
284
|
+
);
|
|
153
285
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
286
|
+
renderInvocation();
|
|
287
|
+
|
|
288
|
+
expect(apiActionsGetSpy).toHaveBeenCalledTimes(1);
|
|
289
|
+
expect(apiActionsGetSpy.mock.calls[0][0].url).toBe(detailsURL);
|
|
290
|
+
|
|
291
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
292
|
+
|
|
293
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
294
|
+
expect(API.get.mock.calls[0][0]).toBe(outputURL);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('does not poll output when complete details disable auto refresh', () => {
|
|
298
|
+
selectors.selectTemplateInvocation.mockImplementation(() => () =>
|
|
299
|
+
undefined
|
|
300
|
+
);
|
|
301
|
+
apiActionsGetSpy.mockImplementation(request =>
|
|
302
|
+
respond(request, { details: { auto_refresh: false } })
|
|
159
303
|
);
|
|
160
|
-
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
161
304
|
|
|
162
|
-
|
|
305
|
+
renderInvocation();
|
|
163
306
|
|
|
164
|
-
act(() =>
|
|
165
|
-
|
|
166
|
-
|
|
307
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS * 2));
|
|
308
|
+
|
|
309
|
+
expect(apiActionsGetSpy).toHaveBeenCalledTimes(1);
|
|
310
|
+
expect(apiActionsGetSpy.mock.calls[0][0].url).toBe(detailsURL);
|
|
311
|
+
expect(API.get).not.toHaveBeenCalled();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('retries failed output requests with backoff and one toast', async () => {
|
|
315
|
+
API.get.mockRejectedValue({
|
|
316
|
+
response: { data: { error: { message: 'Output request failed' } } },
|
|
167
317
|
});
|
|
168
318
|
|
|
169
|
-
|
|
319
|
+
renderInvocation();
|
|
320
|
+
await flushPromises();
|
|
321
|
+
|
|
322
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
323
|
+
expect(addToast).toHaveBeenCalledWith(
|
|
324
|
+
expect.objectContaining({
|
|
325
|
+
type: 'danger',
|
|
326
|
+
message: 'Output request failed',
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
331
|
+
expect(API.get).toHaveBeenCalledTimes(2);
|
|
332
|
+
await flushPromises();
|
|
333
|
+
|
|
334
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
335
|
+
expect(API.get).toHaveBeenCalledTimes(2);
|
|
336
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
337
|
+
|
|
338
|
+
expect(API.get).toHaveBeenCalledTimes(3);
|
|
339
|
+
expect(addToast).toHaveBeenCalledTimes(1);
|
|
170
340
|
});
|
|
171
341
|
|
|
172
|
-
it('
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<Provider store={localStore}>
|
|
176
|
-
<TemplateInvocation {...pollingProps} isExpanded={false} />
|
|
177
|
-
</Provider>
|
|
342
|
+
it('does not start output polling after a details request fails', () => {
|
|
343
|
+
selectors.selectTemplateInvocation.mockImplementation(() => () =>
|
|
344
|
+
undefined
|
|
178
345
|
);
|
|
179
|
-
|
|
346
|
+
apiActionsGetSpy.mockImplementation(request => {
|
|
347
|
+
request.handleError();
|
|
348
|
+
return { type: 'MOCK_GET' };
|
|
349
|
+
});
|
|
180
350
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
351
|
+
renderInvocation();
|
|
352
|
+
act(() => jest.advanceTimersByTime(OUTPUT_MAX_REFRESH_INTERVAL_MS));
|
|
353
|
+
|
|
354
|
+
expect(apiActionsGetSpy).toHaveBeenCalledTimes(1);
|
|
355
|
+
expect(apiActionsGetSpy.mock.calls[0][0].url).toBe(detailsURL);
|
|
356
|
+
expect(API.get).not.toHaveBeenCalled();
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('does not fetch while collapsed', () => {
|
|
360
|
+
renderInvocation({ isExpanded: false });
|
|
361
|
+
|
|
362
|
+
expect(API.get).not.toHaveBeenCalled();
|
|
363
|
+
expect(apiActionsGetSpy).not.toHaveBeenCalled();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('pauses output requests while the document is hidden', () => {
|
|
367
|
+
const visibilityState = Object.getOwnPropertyDescriptor(
|
|
368
|
+
document,
|
|
369
|
+
'visibilityState'
|
|
185
370
|
);
|
|
186
|
-
|
|
371
|
+
Object.defineProperty(document, 'visibilityState', {
|
|
372
|
+
configurable: true,
|
|
373
|
+
value: 'hidden',
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
renderInvocation();
|
|
378
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
379
|
+
expect(API.get).not.toHaveBeenCalled();
|
|
380
|
+
|
|
381
|
+
Object.defineProperty(document, 'visibilityState', {
|
|
382
|
+
configurable: true,
|
|
383
|
+
value: 'visible',
|
|
384
|
+
});
|
|
385
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
386
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
387
|
+
} finally {
|
|
388
|
+
if (visibilityState) {
|
|
389
|
+
Object.defineProperty(document, 'visibilityState', visibilityState);
|
|
390
|
+
} else {
|
|
391
|
+
delete document.visibilityState;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
187
394
|
});
|
|
188
395
|
|
|
189
|
-
it('does not
|
|
396
|
+
it('does not fetch when the cached response is already finished', () => {
|
|
190
397
|
selectors.selectTemplateInvocation.mockImplementation(() => () => ({
|
|
191
398
|
...mockTemplateInvocationResponse,
|
|
192
399
|
finished: true,
|
|
193
400
|
}));
|
|
194
|
-
apiGetSpy.mockImplementation(({ handleSuccess }) => {
|
|
195
|
-
handleSuccess &&
|
|
196
|
-
handleSuccess({ data: { finished: true, auto_refresh: false } });
|
|
197
|
-
return { type: 'MOCK_GET' };
|
|
198
|
-
});
|
|
199
401
|
|
|
402
|
+
renderInvocation();
|
|
403
|
+
|
|
404
|
+
expect(API.get).not.toHaveBeenCalled();
|
|
405
|
+
expect(apiActionsGetSpy).not.toHaveBeenCalled();
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('cancels a scheduled poll when collapsed', () => {
|
|
200
409
|
const localStore = makeStore();
|
|
201
410
|
const { rerender } = render(
|
|
202
411
|
<Provider store={localStore}>
|
|
203
|
-
<TemplateInvocation {...pollingProps}
|
|
204
|
-
</Provider>
|
|
205
|
-
);
|
|
206
|
-
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
207
|
-
|
|
208
|
-
rerender(
|
|
209
|
-
<Provider store={localStore}>
|
|
210
|
-
<TemplateInvocation {...pollingProps} isExpanded />
|
|
412
|
+
<TemplateInvocation {...pollingProps} />
|
|
211
413
|
</Provider>
|
|
212
414
|
);
|
|
213
|
-
|
|
214
|
-
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
415
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
215
416
|
|
|
216
417
|
rerender(
|
|
217
418
|
<Provider store={localStore}>
|
|
218
419
|
<TemplateInvocation {...pollingProps} isExpanded={false} />
|
|
219
420
|
</Provider>
|
|
220
421
|
);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</Provider>
|
|
225
|
-
);
|
|
226
|
-
// Second expand: still finished → still no fetch
|
|
227
|
-
expect(apiGetSpy).not.toHaveBeenCalled();
|
|
422
|
+
act(() => jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS));
|
|
423
|
+
|
|
424
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
228
425
|
});
|
|
229
426
|
|
|
230
|
-
it('
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
427
|
+
it('ignores an in-flight response after unmount', async () => {
|
|
428
|
+
let resolveRequest;
|
|
429
|
+
API.get.mockImplementation(
|
|
430
|
+
() =>
|
|
431
|
+
new Promise(resolve => {
|
|
432
|
+
resolveRequest = resolve;
|
|
433
|
+
})
|
|
236
434
|
);
|
|
237
|
-
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
238
435
|
|
|
239
|
-
|
|
240
|
-
<Provider store={
|
|
241
|
-
<TemplateInvocation {...pollingProps}
|
|
436
|
+
const { unmount } = render(
|
|
437
|
+
<Provider store={makeStore()}>
|
|
438
|
+
<TemplateInvocation {...pollingProps} />
|
|
242
439
|
</Provider>
|
|
243
440
|
);
|
|
244
|
-
|
|
245
|
-
expect(apiGetSpy).toHaveBeenCalledTimes(1);
|
|
441
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
246
442
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
)
|
|
252
|
-
|
|
443
|
+
unmount();
|
|
444
|
+
await act(async () => {
|
|
445
|
+
resolveRequest({ data: { output: [], refresh: true } });
|
|
446
|
+
});
|
|
447
|
+
act(() => {
|
|
448
|
+
jest.advanceTimersByTime(OUTPUT_REFRESH_INTERVAL_MS);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
expect(API.get).toHaveBeenCalledTimes(1);
|
|
253
452
|
});
|
|
254
453
|
});
|