foreman_remote_execution 9.0.1 → 10.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +14 -0
  3. data/.tx/config +3 -1
  4. data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +1 -0
  5. data/app/assets/javascripts/foreman_remote_execution/locale/en/foreman_remote_execution.js +1 -0
  6. data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +1 -0
  7. data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +1 -0
  8. data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +1 -0
  9. data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +1 -0
  10. data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +1 -0
  11. data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +1 -0
  12. data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +1 -0
  13. data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +1 -0
  14. data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +1 -0
  15. data/app/assets/javascripts/foreman_remote_execution/template_invocation.js +10 -1
  16. data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
  17. data/app/controllers/job_invocations_controller.rb +30 -1
  18. data/app/controllers/ui_job_wizard_controller.rb +3 -1
  19. data/app/helpers/remote_execution_helper.rb +1 -1
  20. data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
  21. data/app/lib/actions/remote_execution/run_hosts_job.rb +28 -2
  22. data/app/models/remote_execution_feature.rb +11 -8
  23. data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
  24. data/app/views/job_invocations/_form.html.erb +1 -1
  25. data/app/views/job_invocations/show.html.erb +1 -1
  26. data/app/views/job_invocations/welcome.html.erb +1 -1
  27. data/app/views/templates/script/run_command.erb +1 -0
  28. data/config/routes.rb +2 -0
  29. data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +1 -1
  30. data/db/migrate/20220426145007_add_unique_feature_label_index.rb +14 -0
  31. data/lib/foreman_remote_execution/engine.rb +9 -9
  32. data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +12 -3
  33. data/lib/foreman_remote_execution/version.rb +1 -1
  34. data/locale/Makefile +6 -3
  35. data/locale/action_names.rb +1 -1
  36. data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  37. data/locale/de/foreman_remote_execution.po +67 -19
  38. data/locale/en/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  39. data/locale/en/foreman_remote_execution.po +56 -8
  40. data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  41. data/locale/en_GB/foreman_remote_execution.po +58 -10
  42. data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  43. data/locale/es/foreman_remote_execution.po +71 -23
  44. data/locale/foreman_remote_execution.pot +216 -141
  45. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  46. data/locale/fr/foreman_remote_execution.po +104 -56
  47. data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  48. data/locale/ja/foreman_remote_execution.po +72 -24
  49. data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  50. data/locale/ko/foreman_remote_execution.po +63 -15
  51. data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  52. data/locale/pt_BR/foreman_remote_execution.po +68 -20
  53. data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  54. data/locale/ru/foreman_remote_execution.po +63 -15
  55. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  56. data/locale/zh_CN/foreman_remote_execution.po +71 -23
  57. data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  58. data/locale/zh_TW/foreman_remote_execution.po +63 -15
  59. data/package.json +6 -6
  60. data/webpack/JobWizard/Footer.js +104 -0
  61. data/webpack/JobWizard/JobWizard.js +105 -32
  62. data/webpack/JobWizard/JobWizard.scss +6 -17
  63. data/webpack/JobWizard/JobWizardConstants.js +1 -1
  64. data/webpack/JobWizard/JobWizardPageRerun.js +2 -0
  65. data/webpack/JobWizard/StartsBeforeErrorAlert.js +17 -0
  66. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +8 -0
  67. data/webpack/JobWizard/__tests__/fixtures.js +13 -1
  68. data/webpack/JobWizard/__tests__/integration.test.js +15 -0
  69. data/webpack/JobWizard/__tests__/validation.test.js +36 -0
  70. data/webpack/JobWizard/autofill.js +1 -0
  71. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +29 -10
  72. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +8 -0
  73. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +5 -0
  74. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +28 -14
  75. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +41 -4
  76. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +16 -10
  77. data/webpack/JobWizard/steps/HostsAndInputs/index.js +51 -3
  78. data/webpack/JobWizard/steps/ReviewDetails/ReviewDetails.test.js +117 -0
  79. data/webpack/JobWizard/steps/ReviewDetails/helpers.js +43 -0
  80. data/webpack/JobWizard/steps/ReviewDetails/index.js +169 -18
  81. data/webpack/JobWizard/steps/Schedule/QueryType.js +1 -1
  82. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +0 -1
  83. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  84. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +52 -18
  85. data/webpack/JobWizard/steps/form/DateTimePicker.js +1 -2
  86. data/webpack/JobWizard/steps/form/GroupedSelectField.js +0 -1
  87. data/webpack/JobWizard/steps/form/SelectField.js +0 -1
  88. data/webpack/JobWizard/submit.js +14 -3
  89. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
  90. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +2 -2
  91. data/webpack/react_app/components/RecentJobsCard/constants.js +2 -2
  92. data/webpack/react_app/components/RegistrationExtension/RexPull.js +0 -2
  93. metadata +23 -6
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Chip, ChipGroup, Button } from '@patternfly/react-core';
4
- import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
5
5
  import { hostMethods } from '../../JobWizardConstants';
6
6
 
7
7
  const SelectedChip = ({ selected, setSelected, categoryName }) => {
@@ -10,19 +10,33 @@ const SelectedChip = ({ selected, setSelected, categoryName }) => {
10
10
  oldSelected.filter(({ id }) => id !== itemToRemove)
11
11
  );
12
12
  };
13
+ const NUM_CHIPS = 3;
13
14
  return (
14
- <ChipGroup className="hosts-chip-group" categoryName={categoryName}>
15
- {selected.map(({ name, id }, index) => (
16
- <Chip
17
- key={index}
18
- id={`${categoryName}-${id}`}
19
- onClick={() => deleteItem(id)}
20
- closeBtnAriaLabel={`Close ${name}`}
21
- >
22
- {name}
23
- </Chip>
24
- ))}
25
- </ChipGroup>
15
+ <>
16
+ <ChipGroup
17
+ className="hosts-chip-group"
18
+ categoryName={categoryName}
19
+ isClosable
20
+ closeBtnAriaLabel="Remove all"
21
+ collapsedText={sprintf(__('%s more'), selected.length - NUM_CHIPS)}
22
+ numChips={NUM_CHIPS}
23
+ onClick={() => {
24
+ setSelected(() => []);
25
+ }}
26
+ >
27
+ {selected.map(({ name, id }, index) => (
28
+ <Chip
29
+ key={index}
30
+ id={`${categoryName}-${id}`}
31
+ onClick={() => deleteItem(id)}
32
+ closeBtnAriaLabel={`Remove ${name}`}
33
+ >
34
+ {name}
35
+ </Chip>
36
+ ))}
37
+ </ChipGroup>
38
+ {selected.length > 0 && <br />}
39
+ </>
26
40
  );
27
41
  };
28
42
 
@@ -75,7 +89,7 @@ export const SelectedChips = ({
75
89
  />
76
90
  {showClear && (
77
91
  <Button variant="link" className="clear-chips" onClick={clearAll}>
78
- {__('Clear filters')}
92
+ {__('Clear all filters')}
79
93
  </Button>
80
94
  )}
81
95
  </div>
@@ -30,6 +30,27 @@ describe('Hosts', () => {
30
30
  const select = name =>
31
31
  screen.getByRole('button', { name: `${name} toggle` });
32
32
  fireEvent.click(select('hosts'));
33
+ await act(async () => {
34
+ fireEvent.click(screen.getByText('host1'));
35
+ fireEvent.click(select('hosts'));
36
+ });
37
+ expect(
38
+ screen.queryAllByText('Please select at least one host')
39
+ ).toHaveLength(0);
40
+ await act(async () => {
41
+ fireEvent.click(select('hosts'));
42
+ });
43
+ await act(async () => {
44
+ fireEvent.click(
45
+ screen.getByText('host1', {
46
+ selector: '.pf-c-select__menu-item',
47
+ })
48
+ );
49
+ fireEvent.blur(select('hosts'));
50
+ });
51
+ expect(
52
+ screen.queryAllByText('Please select at least one host')
53
+ ).toHaveLength(1);
33
54
  await act(async () => {
34
55
  fireEvent.click(screen.getByText('host1'));
35
56
  fireEvent.click(screen.getByText('host2'));
@@ -59,7 +80,7 @@ describe('Hosts', () => {
59
80
  expect(screen.queryAllByText('host1')).toHaveLength(1);
60
81
  expect(screen.queryAllByText('host2')).toHaveLength(1);
61
82
  expect(screen.queryAllByText('host3')).toHaveLength(0);
62
- const chip1 = screen.getByRole('button', { name: 'Close host1 host1' });
83
+ const chip1 = screen.getByRole('button', { name: 'Remove host1 host1' });
63
84
  await act(async () => {
64
85
  fireEvent.click(chip1);
65
86
  });
@@ -70,7 +91,7 @@ describe('Hosts', () => {
70
91
  expect(screen.queryAllByText('host_collection1')).toHaveLength(1);
71
92
 
72
93
  await act(async () => {
73
- fireEvent.click(screen.getByText('Category and Template'));
94
+ fireEvent.click(screen.getByText('Category and template'));
74
95
  });
75
96
  await act(async () => {
76
97
  fireEvent.click(screen.getByText('Target hosts and inputs'));
@@ -79,7 +100,7 @@ describe('Hosts', () => {
79
100
  expect(screen.queryAllByText('host_group1')).toHaveLength(1);
80
101
 
81
102
  await act(async () => {
82
- fireEvent.click(screen.getByText('Clear filters'));
103
+ fireEvent.click(screen.getByText('Clear all filters'));
83
104
  });
84
105
 
85
106
  expect(screen.queryAllByText('host2')).toHaveLength(0);
@@ -107,6 +128,13 @@ describe('Hosts', () => {
107
128
  expect(screen.queryAllByText('Host groups')).toHaveLength(1);
108
129
  expect(screen.queryAllByText('Search query')).toHaveLength(1);
109
130
  expect(screen.queryAllByText('Host collections')).toHaveLength(0);
131
+
132
+ await act(async () => {
133
+ fireEvent.click(
134
+ // Close the select
135
+ screen.getByText('Hosts', { selector: '.pf-c-select__toggle-text' })
136
+ );
137
+ });
110
138
  });
111
139
  it('Host fill list from url', async () => {
112
140
  routerSelectors.selectRouterLocation.mockImplementation(() => ({
@@ -151,8 +179,9 @@ describe('Hosts', () => {
151
179
 
152
180
  it('input fill from url', async () => {
153
181
  const inputText = 'test text';
182
+ const advancedInputText = 'test adv text';
154
183
  routerSelectors.selectRouterLocation.mockImplementation(() => ({
155
- search: `feature=test_feature&inputs[plain hidden]=${inputText}`,
184
+ search: `host_ids%5B%5D=host1&host_ids%5B%5D=host3&feature=test_feature&inputs[plain hidden]=${inputText}&inputs[adv plain hidden]=${advancedInputText}`,
156
185
  }));
157
186
  render(
158
187
  <MockedProvider mocks={gqlMock} addTypename={false}>
@@ -175,5 +204,13 @@ describe('Hosts', () => {
175
204
  selector: 'textarea',
176
205
  });
177
206
  expect(textField.value).toBe(inputText);
207
+
208
+ await act(async () => {
209
+ fireEvent.click(screen.getByText('Advanced fields'));
210
+ });
211
+ const advancedTextField = screen.getByLabelText('adv plain hidden', {
212
+ selector: 'textarea',
213
+ });
214
+ expect(advancedTextField.value).toBe(advancedInputText);
178
215
  });
179
216
  });
@@ -1,18 +1,24 @@
1
1
  export const buildHostQuery = (selected, search) => {
2
2
  const { hosts, hostCollections, hostGroups } = selected;
3
- const hostsSearch = `(id ^ (${hosts.map(({ id }) => id).join(',')}))`;
4
- const hostCollectionsSearch = `(host_collection_id ^ (${hostCollections
3
+ const hostsSearch = `id ^ (${hosts.map(({ id }) => id).join(',')})`;
4
+ const hostCollectionsSearch = `host_collection_id ^ (${hostCollections
5
5
  .map(({ id }) => id)
6
- .join(',')}))`;
7
- const hostGroupsSearch = `(hostgroup_id ^ (${hostGroups
6
+ .join(',')})`;
7
+ const hostGroupsSearch = `hostgroup_id ^ (${hostGroups
8
8
  .map(({ id }) => id)
9
- .join(',')}))`;
10
- return [
9
+ .join(',')})`;
10
+ const queryParts = [
11
11
  hosts.length ? hostsSearch : false,
12
12
  hostCollections.length ? hostCollectionsSearch : false,
13
13
  hostGroups.length ? hostGroupsSearch : false,
14
- search.length ? `(${search})` : false,
15
- ]
16
- .filter(Boolean)
17
- .join(' or ');
14
+ search.length ? search : false,
15
+ ].filter(Boolean);
16
+
17
+ if (queryParts.length === 0) {
18
+ return 'name=a AND name=b';
19
+ }
20
+ if (queryParts.length === 1) {
21
+ return queryParts[0] || 'name=a AND name=b';
22
+ }
23
+ return queryParts.map(p => `(${p})`).join(' or ') || 'name=a AND name=b';
18
24
  };
@@ -51,6 +51,30 @@ const HostsAndInputs = ({
51
51
  const isLoading = useSelector(selectIsLoadingHosts);
52
52
  const templateInputs = useSelector(selectTemplateInputs);
53
53
  const [hostPreviewOpen, setHostPreviewOpen] = useState(false);
54
+ const [wasFocus, setWasFocus] = useState(false);
55
+ const [isError, setIsError] = useState(false);
56
+ useEffect(() => {
57
+ if (wasFocus) {
58
+ if (
59
+ selected.hosts.length === 0 &&
60
+ selected.hostCollections.length === 0 &&
61
+ selected.hostGroups.length === 0 &&
62
+ hostsSearchQuery.length === 0
63
+ ) {
64
+ setIsError(true);
65
+ } else {
66
+ setIsError(false);
67
+ }
68
+ }
69
+ }, [
70
+ hostMethod,
71
+ hostsSearchQuery.length,
72
+ selected,
73
+ selected.hostCollections.length,
74
+ selected.hostGroups.length,
75
+ selected.hosts.length,
76
+ wasFocus,
77
+ ]);
54
78
  useEffect(() => {
55
79
  debounce(() => {
56
80
  dispatch(
@@ -99,6 +123,9 @@ const HostsAndInputs = ({
99
123
  const clearSearch = () => {
100
124
  setHostsSearchQuery('');
101
125
  };
126
+ const [errorText, setErrorText] = useState(
127
+ __('Please select at least one host')
128
+ );
102
129
  return (
103
130
  <div className="target-hosts-and-inputs">
104
131
  <WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
@@ -110,8 +137,13 @@ const HostsAndInputs = ({
110
137
  />
111
138
  )}
112
139
  <Form>
113
- <FormGroup fieldId="host_selection" id="host-selection">
114
- <InputGroup>
140
+ <FormGroup
141
+ fieldId="host_selection"
142
+ id="host-selection"
143
+ helperTextInvalid={errorText}
144
+ validated={isError ? 'error' : 'default'}
145
+ >
146
+ <InputGroup onBlur={() => setWasFocus(true)}>
115
147
  <SelectField
116
148
  isRequired
117
149
  className="target-method-select"
@@ -123,7 +155,23 @@ const HostsAndInputs = ({
123
155
  }
124
156
  return true;
125
157
  })}
126
- setValue={setHostMethod}
158
+ setValue={val => {
159
+ setHostMethod(val);
160
+ if (val === hostMethods.searchQuery) {
161
+ setErrorText(__('Please enter a search query'));
162
+ }
163
+ if (val === hostMethods.hosts) {
164
+ setErrorText(__('Please select at least one host'));
165
+ }
166
+ if (val === hostMethods.hostCollections) {
167
+ setErrorText(
168
+ __('Please select at least one host collection')
169
+ );
170
+ }
171
+ if (val === hostMethods.hostGroups) {
172
+ setErrorText(__('Please select at least one host group'));
173
+ }
174
+ }}
127
175
  value={hostMethod}
128
176
  />
129
177
  {hostMethod === hostMethods.searchQuery && (
@@ -0,0 +1,117 @@
1
+ import React from 'react';
2
+ import { Provider } from 'react-redux';
3
+ import { render, fireEvent, screen, act } from '@testing-library/react';
4
+ import { MockedProvider } from '@apollo/client/testing';
5
+ import * as APIHooks from 'foremanReact/common/hooks/API/APIHooks';
6
+ import * as api from 'foremanReact/redux/API';
7
+
8
+ import JobWizardPageRerun from '../../JobWizardPageRerun';
9
+ import * as selectors from '../../JobWizardSelectors';
10
+ import {
11
+ testSetup,
12
+ mockApi,
13
+ gqlMock,
14
+ jobInvocation,
15
+ } from '../../__tests__/fixtures';
16
+
17
+ const store = testSetup(selectors, api);
18
+ mockApi(api);
19
+ jest.spyOn(APIHooks, 'useAPI');
20
+ APIHooks.useAPI.mockImplementation((action, url) => {
21
+ if (url === '/ui_job_wizard/job_invocation?id=57') {
22
+ return { response: jobInvocation, status: 'RESOLVED' };
23
+ }
24
+ return {};
25
+ });
26
+ jest.useFakeTimers();
27
+
28
+ describe('ReviewDetails', () => {
29
+ it('should call goToStepByName function when StepButton is clicked', async () => {
30
+ render(
31
+ <MockedProvider mocks={gqlMock} addTypename={false}>
32
+ <Provider store={store}>
33
+ <JobWizardPageRerun
34
+ match={{
35
+ params: { id: '57' },
36
+ }}
37
+ />
38
+ </Provider>
39
+ </MockedProvider>
40
+ );
41
+
42
+ act(() => {
43
+ fireEvent.click(screen.getByText('Type of execution'));
44
+ });
45
+ act(() => {
46
+ fireEvent.click(screen.getByText('Future execution'));
47
+
48
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
49
+ });
50
+ act(() => {
51
+ fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
52
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
53
+ });
54
+
55
+ const newStartAtDate = '2030/03/12';
56
+ const newStartAtTime = '14:27';
57
+ const startsAtDateField = () =>
58
+ screen.getByLabelText('starts at datepicker');
59
+ const startsAtTimeField = () =>
60
+ screen.getByLabelText('starts at timepicker');
61
+
62
+ await act(async () => {
63
+ await fireEvent.change(startsAtDateField(), {
64
+ target: { value: newStartAtDate },
65
+ });
66
+ fireEvent.change(startsAtTimeField(), {
67
+ target: { value: newStartAtTime },
68
+ });
69
+ jest.advanceTimersByTime(1000);
70
+ });
71
+
72
+ act(() => {
73
+ fireEvent.click(screen.getByText('Review details'));
74
+ });
75
+ expect(screen.getAllByText('Review details')).toHaveLength(3);
76
+ fireEvent.click(
77
+ screen.getByText('Job template', {
78
+ selector: '.pf-c-button',
79
+ })
80
+ );
81
+ expect(screen.getAllByText('Category and template')).toHaveLength(3);
82
+
83
+ await act(async () => {
84
+ fireEvent.click(screen.getByText('Review details'));
85
+ jest.advanceTimersByTime(1000);
86
+ });
87
+ act(() => {
88
+ fireEvent.click(
89
+ screen.getByText('Target hosts', {
90
+ selector: '.pf-c-button',
91
+ })
92
+ );
93
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
94
+ });
95
+ expect(screen.getAllByText('Target hosts and inputs')).toHaveLength(3);
96
+ act(() => {
97
+ fireEvent.click(screen.getByText('Review details'));
98
+ });
99
+ act(() => {
100
+ fireEvent.click(
101
+ screen.getByText('Advanced fields', {
102
+ selector: '.pf-c-button',
103
+ })
104
+ );
105
+ jest.advanceTimersByTime(1000);
106
+ });
107
+ expect(screen.getAllByText('Advanced fields')).toHaveLength(3);
108
+
109
+ act(() => {
110
+ fireEvent.click(screen.getByText('Review details'));
111
+ });
112
+ act(() => {
113
+ fireEvent.click(screen.getByText('Recurrence'));
114
+ });
115
+ expect(screen.getAllByText('Schedule')).toHaveLength(3);
116
+ });
117
+ });
@@ -0,0 +1,43 @@
1
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
+ import { getWeekDays } from '../Schedule/RepeatWeek';
3
+ import { repeatTypes } from '../../JobWizardConstants';
4
+
5
+ export const parseEnd = (ends, isNeverEnds, repeatAmount) => {
6
+ if (isNeverEnds) {
7
+ return __('Never');
8
+ }
9
+ if (ends) {
10
+ const endsDate = new Date(ends);
11
+ return endsDate.toString();
12
+ }
13
+ return sprintf(__('After %s occurences'), repeatAmount);
14
+ };
15
+
16
+ export const parseRepeat = (repeatType, repeatData) => {
17
+ switch (repeatType) {
18
+ case repeatTypes.hourly:
19
+ return sprintf(__('Every hour at minute %s'), repeatData.minute);
20
+ case repeatTypes.daily:
21
+ return sprintf(__('Every day at %s'), repeatData.at);
22
+ case repeatTypes.weekly: {
23
+ const daysKeys = Object.keys(repeatData.daysOfWeek).filter(
24
+ k => repeatData.daysOfWeek[k]
25
+ );
26
+ const days = getWeekDays()
27
+ .filter((d, index) => index in daysKeys)
28
+ .join(', ');
29
+ return sprintf(__('Every week on %s at %s'), days, repeatData.at);
30
+ }
31
+ case repeatTypes.monthly:
32
+ return sprintf(
33
+ __('Every month on %s at %s'),
34
+ repeatData.days,
35
+ repeatData.at
36
+ );
37
+ case repeatTypes.cronline:
38
+ return `${__('Cron line')} - ${repeatData.cronline}`;
39
+
40
+ default:
41
+ return '';
42
+ }
43
+ };