foreman_remote_execution 18.0.1 → 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/fr/foreman_remote_execution.js +28 -28
- data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +48 -48
- data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +29 -29
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +22 -22
- 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/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/foreman_remote_execution.pot +2 -2
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +28 -28
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +48 -48
- data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ko/foreman_remote_execution.po +29 -29
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +22 -22
- data/package.json +1 -14
- data/test/functional/job_invocations_controller_test.rb +0 -77
- 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,150 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
|
2
|
-
import { useSelector, useDispatch } from 'react-redux';
|
|
3
|
-
|
|
4
|
-
import { get } from 'foremanReact/redux/API';
|
|
5
|
-
import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
|
|
6
|
-
import {
|
|
7
|
-
withInterval,
|
|
8
|
-
stopInterval,
|
|
9
|
-
} from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
|
10
|
-
import { MS_PER_SECOND } from 'foremanReact/constants';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
selectItems,
|
|
14
|
-
selectApiStatus,
|
|
15
|
-
selectAutoRefresh,
|
|
16
|
-
selectTotalHosts,
|
|
17
|
-
selectIntervalExists,
|
|
18
|
-
selectStatusFilter,
|
|
19
|
-
} from './TargetingHostsSelectors';
|
|
20
|
-
import { getApiUrl } from './TargetingHostsHelpers';
|
|
21
|
-
import { TARGETING_HOSTS } from './TargetingHostsConsts';
|
|
22
|
-
import TargetingHostsPage from './TargetingHostsPage';
|
|
23
|
-
import { chartFilter } from '../../redux/actions/jobInvocations';
|
|
24
|
-
|
|
25
|
-
const buildSearchQuery = (query, stateFilter) => {
|
|
26
|
-
const filters = Object.entries(stateFilter).map(
|
|
27
|
-
([key, value]) => `${key} = ${value}`
|
|
28
|
-
);
|
|
29
|
-
return [query, filters]
|
|
30
|
-
.flat()
|
|
31
|
-
.filter(x => x)
|
|
32
|
-
.map(x => `(${x})`)
|
|
33
|
-
.join(' AND ');
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const WrappedTargetingHosts = () => {
|
|
37
|
-
const dispatch = useDispatch();
|
|
38
|
-
const { perPage, perPageOptions } = useForemanSettings();
|
|
39
|
-
|
|
40
|
-
const autoRefresh = useSelector(selectAutoRefresh);
|
|
41
|
-
const items = useSelector(selectItems);
|
|
42
|
-
const apiStatus = useSelector(selectApiStatus);
|
|
43
|
-
const totalHosts = useSelector(selectTotalHosts);
|
|
44
|
-
const statusFilter = useSelector(selectStatusFilter);
|
|
45
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
46
|
-
const [pagination, setPagination] = useState({
|
|
47
|
-
page: 1,
|
|
48
|
-
per_page: perPage,
|
|
49
|
-
perPageOptions,
|
|
50
|
-
});
|
|
51
|
-
const [apiUrl, setApiUrl] = useState(getApiUrl(searchQuery, pagination));
|
|
52
|
-
const intervalExists = useSelector(selectIntervalExists);
|
|
53
|
-
|
|
54
|
-
const stopApiInterval = useCallback(() => {
|
|
55
|
-
if (intervalExists) {
|
|
56
|
-
dispatch(stopInterval(TARGETING_HOSTS));
|
|
57
|
-
}
|
|
58
|
-
}, [dispatch, intervalExists]);
|
|
59
|
-
|
|
60
|
-
// Use refs to avoid infinite loop from handleSearch depending on pagination.per_page and searchQuery
|
|
61
|
-
const perPageRef = useRef(pagination.per_page);
|
|
62
|
-
const searchQueryRef = useRef(searchQuery);
|
|
63
|
-
|
|
64
|
-
const handleSearch = useCallback(
|
|
65
|
-
(query, status) => {
|
|
66
|
-
const defaultPagination = { page: 1, per_page: perPageRef.current };
|
|
67
|
-
stopApiInterval();
|
|
68
|
-
|
|
69
|
-
setApiUrl(getApiUrl(buildSearchQuery(query, status), defaultPagination));
|
|
70
|
-
setSearchQuery(query);
|
|
71
|
-
setPagination(defaultPagination);
|
|
72
|
-
},
|
|
73
|
-
[stopApiInterval]
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// Keep refs in sync with state
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
perPageRef.current = pagination.per_page;
|
|
79
|
-
}, [pagination.per_page]);
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
searchQueryRef.current = searchQuery;
|
|
83
|
-
}, [searchQuery]);
|
|
84
|
-
|
|
85
|
-
const handlePagination = useCallback(
|
|
86
|
-
args => {
|
|
87
|
-
stopApiInterval();
|
|
88
|
-
setPagination(args);
|
|
89
|
-
setApiUrl(getApiUrl(buildSearchQuery(searchQuery, statusFilter), args));
|
|
90
|
-
},
|
|
91
|
-
[searchQuery, statusFilter, stopApiInterval]
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const getData = useCallback(
|
|
95
|
-
url =>
|
|
96
|
-
withInterval(
|
|
97
|
-
get({
|
|
98
|
-
key: TARGETING_HOSTS,
|
|
99
|
-
url,
|
|
100
|
-
handleError: () => {
|
|
101
|
-
dispatch(stopInterval(TARGETING_HOSTS));
|
|
102
|
-
},
|
|
103
|
-
}),
|
|
104
|
-
MS_PER_SECOND
|
|
105
|
-
),
|
|
106
|
-
[dispatch]
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
dispatch(getData(apiUrl));
|
|
111
|
-
|
|
112
|
-
if (autoRefresh === 'false') {
|
|
113
|
-
dispatch(stopInterval(TARGETING_HOSTS));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return () => {
|
|
117
|
-
dispatch(stopInterval(TARGETING_HOSTS));
|
|
118
|
-
};
|
|
119
|
-
}, [dispatch, apiUrl, autoRefresh, getData]);
|
|
120
|
-
|
|
121
|
-
// Only respond to external statusFilter changes (from chart clicks)
|
|
122
|
-
// searchQuery changes are handled directly by handleSearch calls from the UI
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
dispatch(stopInterval(TARGETING_HOSTS));
|
|
125
|
-
const defaultPagination = { page: 1, per_page: perPageRef.current };
|
|
126
|
-
setApiUrl(
|
|
127
|
-
getApiUrl(
|
|
128
|
-
buildSearchQuery(searchQueryRef.current, statusFilter),
|
|
129
|
-
defaultPagination
|
|
130
|
-
)
|
|
131
|
-
);
|
|
132
|
-
setPagination(defaultPagination);
|
|
133
|
-
}, [statusFilter, dispatch]);
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<TargetingHostsPage
|
|
137
|
-
handleSearch={handleSearch}
|
|
138
|
-
searchQuery={searchQuery}
|
|
139
|
-
statusFilter={statusFilter}
|
|
140
|
-
statusFilterReset={_x => chartFilter(null)(dispatch, null)}
|
|
141
|
-
apiStatus={apiStatus}
|
|
142
|
-
items={items}
|
|
143
|
-
totalHosts={totalHosts}
|
|
144
|
-
handlePagination={handlePagination}
|
|
145
|
-
page={pagination.page}
|
|
146
|
-
/>
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
export default WrappedTargetingHosts;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
|
|
4
|
-
const AggregateStatus = ({ statuses, chartFilter }) => (
|
|
5
|
-
<div id="aggregate_statuses">
|
|
6
|
-
<p className="card-pf-aggregate-status-notifications">
|
|
7
|
-
<a
|
|
8
|
-
className="card-pf-aggregate-status-notification"
|
|
9
|
-
onClick={() => chartFilter('success')}
|
|
10
|
-
>
|
|
11
|
-
<span id="success_count">
|
|
12
|
-
<span className="pficon pficon-ok" />
|
|
13
|
-
{statuses.success}
|
|
14
|
-
</span>
|
|
15
|
-
</a>
|
|
16
|
-
|
|
17
|
-
<a
|
|
18
|
-
className="card-pf-aggregate-status-notification"
|
|
19
|
-
onClick={() => chartFilter('failed')}
|
|
20
|
-
>
|
|
21
|
-
<span id="failed_count">
|
|
22
|
-
<span className="pficon pficon-error-circle-o" />
|
|
23
|
-
{statuses.failed}
|
|
24
|
-
</span>
|
|
25
|
-
</a>
|
|
26
|
-
|
|
27
|
-
<a
|
|
28
|
-
className="card-pf-aggregate-status-notification"
|
|
29
|
-
onClick={() => chartFilter('pending')}
|
|
30
|
-
>
|
|
31
|
-
<span id="pending_count">
|
|
32
|
-
<span className="pficon pficon-running" />
|
|
33
|
-
{statuses.pending}
|
|
34
|
-
</span>
|
|
35
|
-
</a>
|
|
36
|
-
|
|
37
|
-
<a
|
|
38
|
-
className="card-pf-aggregate-status-notification"
|
|
39
|
-
onClick={() => chartFilter('cancelled')}
|
|
40
|
-
>
|
|
41
|
-
<span id="cancelled_count">
|
|
42
|
-
<span className="pficon pficon-close" />
|
|
43
|
-
{statuses.cancelled}
|
|
44
|
-
</span>
|
|
45
|
-
</a>
|
|
46
|
-
</p>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
AggregateStatus.propTypes = {
|
|
51
|
-
statuses: PropTypes.shape({
|
|
52
|
-
cancelled: PropTypes.number,
|
|
53
|
-
failed: PropTypes.number,
|
|
54
|
-
pending: PropTypes.number,
|
|
55
|
-
success: PropTypes.number,
|
|
56
|
-
}).isRequired,
|
|
57
|
-
chartFilter: PropTypes.func.isRequired,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export default AggregateStatus;
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import '@testing-library/jest-dom';
|
|
5
|
-
import AggregateStatus from './index';
|
|
6
|
-
|
|
7
|
-
const defaultStatuses = {
|
|
8
|
-
success: 19,
|
|
9
|
-
failed: 20,
|
|
10
|
-
pending: 3,
|
|
11
|
-
cancelled: 31,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const renderAggregateStatus = ({
|
|
15
|
-
statuses = defaultStatuses,
|
|
16
|
-
chartFilter = jest.fn(),
|
|
17
|
-
} = {}) => {
|
|
18
|
-
const view = render(
|
|
19
|
-
<AggregateStatus statuses={statuses} chartFilter={chartFilter} />
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
return { chartFilter, ...view };
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
describe('AggregateStatus', () => {
|
|
26
|
-
it('renders status notifications without counts when statuses are empty', () => {
|
|
27
|
-
const { container } = renderAggregateStatus({ statuses: {} });
|
|
28
|
-
|
|
29
|
-
expect(container.textContent.trim()).toBe('');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('renders status counts from props', () => {
|
|
33
|
-
renderAggregateStatus();
|
|
34
|
-
|
|
35
|
-
expect(screen.getByText('19')).toBeInTheDocument();
|
|
36
|
-
expect(screen.getByText('20')).toBeInTheDocument();
|
|
37
|
-
expect(screen.getByText('3')).toBeInTheDocument();
|
|
38
|
-
expect(screen.getByText('31')).toBeInTheDocument();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('calls chartFilter with success when the success count is clicked', async () => {
|
|
42
|
-
const { chartFilter } = renderAggregateStatus();
|
|
43
|
-
|
|
44
|
-
await userEvent.click(screen.getByText('19'));
|
|
45
|
-
|
|
46
|
-
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
47
|
-
expect(chartFilter).toHaveBeenCalledWith('success');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('calls chartFilter with failed when the failed count is clicked', async () => {
|
|
51
|
-
const { chartFilter } = renderAggregateStatus();
|
|
52
|
-
|
|
53
|
-
await userEvent.click(screen.getByText('20'));
|
|
54
|
-
|
|
55
|
-
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
56
|
-
expect(chartFilter).toHaveBeenCalledWith('failed');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('calls chartFilter with pending when the pending count is clicked', async () => {
|
|
60
|
-
const { chartFilter } = renderAggregateStatus();
|
|
61
|
-
|
|
62
|
-
await userEvent.click(screen.getByText('3'));
|
|
63
|
-
|
|
64
|
-
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
65
|
-
expect(chartFilter).toHaveBeenCalledWith('pending');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('calls chartFilter with cancelled when the cancelled count is clicked', async () => {
|
|
69
|
-
const { chartFilter } = renderAggregateStatus();
|
|
70
|
-
|
|
71
|
-
await userEvent.click(screen.getByText('31'));
|
|
72
|
-
|
|
73
|
-
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
74
|
-
expect(chartFilter).toHaveBeenCalledWith('cancelled');
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { connect } from 'react-redux';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import Immutable from 'seamless-immutable';
|
|
4
|
-
import PropTypes from 'prop-types';
|
|
5
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
6
|
-
import DonutChart from 'foremanReact/components/common/charts/DonutChart';
|
|
7
|
-
import AggregateStatus from './AggregateStatus';
|
|
8
|
-
import * as JobInvocationActions from '../../redux/actions/jobInvocations';
|
|
9
|
-
|
|
10
|
-
const colIndexOfMaxValue = columns =>
|
|
11
|
-
columns.reduce((iMax, x, i, arr) => (x[1] > arr[iMax][1] ? i : iMax), 0);
|
|
12
|
-
|
|
13
|
-
const colorMap = {
|
|
14
|
-
'#5CB85C': 'success',
|
|
15
|
-
'#D9534F': 'failed',
|
|
16
|
-
'#DEDEDE': 'pending',
|
|
17
|
-
'#B7312D': 'cancelled',
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
class JobInvocationContainer extends React.Component {
|
|
21
|
-
componentDidMount() {
|
|
22
|
-
const {
|
|
23
|
-
startJobInvocationsPolling,
|
|
24
|
-
data: { url },
|
|
25
|
-
} = this.props;
|
|
26
|
-
|
|
27
|
-
startJobInvocationsPolling(url);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
render() {
|
|
31
|
-
const { jobInvocations, statuses, chartFilter } = this.props;
|
|
32
|
-
const iMax = colIndexOfMaxValue(jobInvocations);
|
|
33
|
-
|
|
34
|
-
const map = jobInvocations.reduce(
|
|
35
|
-
(acc, [label, _count, color]) => ({ [label]: colorMap[color], ...acc }),
|
|
36
|
-
{}
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div id="job_invocations_chart_container">
|
|
41
|
-
<DonutChart
|
|
42
|
-
data={Immutable.asMutable(jobInvocations)}
|
|
43
|
-
title={{
|
|
44
|
-
type: 'percent',
|
|
45
|
-
secondary: (jobInvocations[iMax] || [])[0],
|
|
46
|
-
}}
|
|
47
|
-
onclick={(d, element) => {
|
|
48
|
-
chartFilter(map[d.name]);
|
|
49
|
-
}}
|
|
50
|
-
/>
|
|
51
|
-
<AggregateStatus statuses={statuses} chartFilter={chartFilter} />
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const mapStateToProps = state => {
|
|
58
|
-
const {
|
|
59
|
-
jobInvocations,
|
|
60
|
-
statuses,
|
|
61
|
-
} = state.foremanRemoteExecutionReducers.jobInvocations;
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
jobInvocations,
|
|
65
|
-
statuses,
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
JobInvocationContainer.propTypes = {
|
|
70
|
-
startJobInvocationsPolling: PropTypes.func,
|
|
71
|
-
data: PropTypes.shape({
|
|
72
|
-
url: PropTypes.string,
|
|
73
|
-
}),
|
|
74
|
-
jobInvocations: PropTypes.arrayOf(PropTypes.array),
|
|
75
|
-
statuses: PropTypes.shape({
|
|
76
|
-
cancelled: PropTypes.number,
|
|
77
|
-
failed: PropTypes.number,
|
|
78
|
-
pending: PropTypes.number,
|
|
79
|
-
success: PropTypes.number,
|
|
80
|
-
}),
|
|
81
|
-
chartFilter: PropTypes.func,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
JobInvocationContainer.defaultProps = {
|
|
85
|
-
startJobInvocationsPolling: JobInvocationActions.startJobInvocationsPolling,
|
|
86
|
-
chartFilter: JobInvocationActions.chartFilter,
|
|
87
|
-
data: {},
|
|
88
|
-
jobInvocations: [['property', 3, 'color']],
|
|
89
|
-
statuses: {},
|
|
90
|
-
};
|
|
91
|
-
export default connect(
|
|
92
|
-
mapStateToProps,
|
|
93
|
-
JobInvocationActions
|
|
94
|
-
)(JobInvocationContainer);
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import API from 'foremanReact/API';
|
|
3
|
-
import { HTTP_STATUS_CODES, MS_PER_SECOND } from 'foremanReact/constants';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
|
|
7
|
-
JOB_INVOCATIONS_POLLING_STARTED,
|
|
8
|
-
JOB_INVOCATIONS_JOB_FINISHED,
|
|
9
|
-
} from '../../consts';
|
|
10
|
-
|
|
11
|
-
const defaultJobInvocationsPollingInterval = MS_PER_SECOND;
|
|
12
|
-
const jobInvocationsInterval =
|
|
13
|
-
process.env.JOB_INVOCATIONS_POLLING || defaultJobInvocationsPollingInterval;
|
|
14
|
-
|
|
15
|
-
const getJobInvocations = url => async (dispatch, getState) => {
|
|
16
|
-
function onGetJobInvocationsSuccess({ data }) {
|
|
17
|
-
// If the job has finished, stop polling
|
|
18
|
-
if (data.finished) {
|
|
19
|
-
dispatch({
|
|
20
|
-
type: JOB_INVOCATIONS_JOB_FINISHED,
|
|
21
|
-
payload: {
|
|
22
|
-
jobInvocations: data,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
} else {
|
|
26
|
-
dispatch({
|
|
27
|
-
type: JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
|
|
28
|
-
payload: {
|
|
29
|
-
jobInvocations: data,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function onGetJobInvocationsFailed(error) {
|
|
36
|
-
if (error.response.status === HTTP_STATUS_CODES.UNAUTHORIZED) {
|
|
37
|
-
window.location.replace('/users/login');
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function triggerPolling() {
|
|
42
|
-
if (jobInvocationsInterval) {
|
|
43
|
-
setTimeout(
|
|
44
|
-
() => dispatch(getJobInvocations(url)),
|
|
45
|
-
jobInvocationsInterval
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const isDocumentVisible =
|
|
51
|
-
document.visibilityState === 'visible' ||
|
|
52
|
-
document.visibilityState === 'prerender';
|
|
53
|
-
|
|
54
|
-
if (getState().foremanRemoteExecutionReducers.jobInvocations.isPolling) {
|
|
55
|
-
if (isDocumentVisible) {
|
|
56
|
-
try {
|
|
57
|
-
const data = await API.get(url);
|
|
58
|
-
onGetJobInvocationsSuccess(data);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
onGetJobInvocationsFailed(error);
|
|
61
|
-
} finally {
|
|
62
|
-
triggerPolling();
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
// document is not visible, keep polling without api call
|
|
66
|
-
triggerPolling();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export const startJobInvocationsPolling = url => (dispatch, getState) => {
|
|
72
|
-
if (getState().foremanRemoteExecutionReducers.jobInvocations.isPolling) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
dispatch({
|
|
76
|
-
type: JOB_INVOCATIONS_POLLING_STARTED,
|
|
77
|
-
});
|
|
78
|
-
dispatch(getJobInvocations(url));
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export const chartFilter = state => (dispatch, getState) => {
|
|
82
|
-
const filter = state ? { 'job_invocation.result': state.toLowerCase() } : {};
|
|
83
|
-
dispatch({
|
|
84
|
-
type: 'JOB_INVOCATION_CHART_FILTER',
|
|
85
|
-
payload: { filter },
|
|
86
|
-
});
|
|
87
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import Immutable from 'seamless-immutable';
|
|
2
|
-
|
|
3
|
-
export const initialState = Immutable({
|
|
4
|
-
isPolling: false,
|
|
5
|
-
jobInvocations: [],
|
|
6
|
-
statuses: {},
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export const pollingStarted = Immutable({
|
|
10
|
-
isPolling: true,
|
|
11
|
-
jobInvocations: [],
|
|
12
|
-
statuses: {},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export const jobInvocationsPayload = Immutable({
|
|
16
|
-
jobInvocations: {
|
|
17
|
-
job_invocations: [
|
|
18
|
-
['Success', 100, '#B7312D'],
|
|
19
|
-
['Failed', 20, '#B7312D'],
|
|
20
|
-
['Pending', 40, '#B7312D'],
|
|
21
|
-
['Cancelled', 0, '#B7312D'],
|
|
22
|
-
],
|
|
23
|
-
statuses: {
|
|
24
|
-
cancelled: 0,
|
|
25
|
-
failed: 0,
|
|
26
|
-
pending: 0,
|
|
27
|
-
success: 1,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export const jobInvocationsReceived = Immutable({
|
|
33
|
-
isPolling: true,
|
|
34
|
-
jobInvocations: [
|
|
35
|
-
['Success', 100, '#B7312D'],
|
|
36
|
-
['Failed', 20, '#B7312D'],
|
|
37
|
-
['Pending', 40, '#B7312D'],
|
|
38
|
-
['Cancelled', 0, '#B7312D'],
|
|
39
|
-
],
|
|
40
|
-
statuses: {
|
|
41
|
-
cancelled: 0,
|
|
42
|
-
failed: 0,
|
|
43
|
-
pending: 0,
|
|
44
|
-
success: 1,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import Immutable from 'seamless-immutable';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
|
|
5
|
-
JOB_INVOCATIONS_POLLING_STARTED,
|
|
6
|
-
JOB_INVOCATIONS_JOB_FINISHED,
|
|
7
|
-
} from '../../consts';
|
|
8
|
-
|
|
9
|
-
const initialState = Immutable({
|
|
10
|
-
isPolling: false,
|
|
11
|
-
jobInvocations: [],
|
|
12
|
-
statuses: {},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export default (state = initialState, action) => {
|
|
16
|
-
const { payload } = action;
|
|
17
|
-
switch (action.type) {
|
|
18
|
-
case JOB_INVOCATIONS_POLLING_STARTED:
|
|
19
|
-
return state.set('isPolling', true);
|
|
20
|
-
case JOB_INVOCATIONS_JOB_FINISHED:
|
|
21
|
-
return state
|
|
22
|
-
.set('isPolling', false)
|
|
23
|
-
.set('jobInvocations', payload.jobInvocations.job_invocations)
|
|
24
|
-
.set('statuses', payload.jobInvocations.statuses);
|
|
25
|
-
case JOB_INVOCATIONS_GET_JOB_INVOCATIONS:
|
|
26
|
-
return state
|
|
27
|
-
.set('jobInvocations', payload.jobInvocations.job_invocations)
|
|
28
|
-
.set('statuses', payload.jobInvocations.statuses);
|
|
29
|
-
case 'JOB_INVOCATION_CHART_FILTER':
|
|
30
|
-
return state.set('jobInvocationStateFilter', payload.filter);
|
|
31
|
-
default:
|
|
32
|
-
return state;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
|
|
3
|
-
JOB_INVOCATIONS_POLLING_STARTED,
|
|
4
|
-
JOB_INVOCATIONS_JOB_FINISHED,
|
|
5
|
-
} from '../../consts';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
initialState,
|
|
9
|
-
pollingStarted,
|
|
10
|
-
jobInvocationsPayload,
|
|
11
|
-
jobInvocationsReceived,
|
|
12
|
-
} from './index.fixtures';
|
|
13
|
-
|
|
14
|
-
import reducer from './index';
|
|
15
|
-
|
|
16
|
-
describe('job invocations chart reducer', () => {
|
|
17
|
-
it('should return the initial state', () => {
|
|
18
|
-
expect(reducer(undefined, {})).toEqual(initialState);
|
|
19
|
-
});
|
|
20
|
-
it('should start polling given POLLING_STARTED', () => {
|
|
21
|
-
expect(
|
|
22
|
-
reducer(initialState, {
|
|
23
|
-
type: JOB_INVOCATIONS_POLLING_STARTED,
|
|
24
|
-
})
|
|
25
|
-
).toEqual(pollingStarted);
|
|
26
|
-
});
|
|
27
|
-
it('should stop polling given JOB_FINISHED', () => {
|
|
28
|
-
expect(
|
|
29
|
-
reducer(pollingStarted, {
|
|
30
|
-
type: JOB_INVOCATIONS_JOB_FINISHED,
|
|
31
|
-
payload: { jobInvocations: { job_invocations: [], statuses: {} } },
|
|
32
|
-
})
|
|
33
|
-
).toEqual(initialState);
|
|
34
|
-
});
|
|
35
|
-
it('should receive job invocations given GET_JOB_INVOCATIONS', () => {
|
|
36
|
-
expect(
|
|
37
|
-
reducer(pollingStarted, {
|
|
38
|
-
type: JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
|
|
39
|
-
payload: jobInvocationsPayload,
|
|
40
|
-
})
|
|
41
|
-
).toEqual(jobInvocationsReceived);
|
|
42
|
-
});
|
|
43
|
-
});
|