foreman_remote_execution 6.2.0 → 7.1.1

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +2 -0
  3. data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
  4. data/app/helpers/hosts_extensions_helper.rb +62 -0
  5. data/app/lib/actions/remote_execution/run_host_job.rb +4 -0
  6. data/app/lib/actions/remote_execution/run_hosts_job.rb +4 -0
  7. data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +6 -0
  8. data/app/models/host_status/execution_status.rb +2 -1
  9. data/app/models/job_invocation.rb +1 -1
  10. data/app/models/job_invocation_composer.rb +7 -3
  11. data/app/models/job_template.rb +6 -1
  12. data/app/models/remote_execution_provider.rb +4 -0
  13. data/app/models/ssh_execution_provider.rb +3 -3
  14. data/app/models/template_invocation.rb +2 -0
  15. data/app/services/remote_execution_proxy_selector.rb +1 -1
  16. data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
  17. data/app/views/job_invocations/_card_target_hosts.html.erb +8 -0
  18. data/app/views/job_invocations/_form.html.erb +2 -0
  19. data/app/views/overrides/subnets/_rex_tab_pane.html.erb +1 -1
  20. data/db/migrate/20220331112719_add_ssh_user_to_job_invocation.rb +5 -0
  21. data/lib/foreman_remote_execution/engine.rb +6 -3
  22. data/lib/foreman_remote_execution/version.rb +1 -1
  23. data/locale/action_names.rb +3 -3
  24. data/locale/de/foreman_remote_execution.po +23 -23
  25. data/locale/en/foreman_remote_execution.po +23 -23
  26. data/locale/en_GB/foreman_remote_execution.po +23 -23
  27. data/locale/es/foreman_remote_execution.po +23 -23
  28. data/locale/foreman_remote_execution.pot +64 -66
  29. data/locale/fr/foreman_remote_execution.po +23 -23
  30. data/locale/ja/foreman_remote_execution.po +23 -23
  31. data/locale/ko/foreman_remote_execution.po +23 -23
  32. data/locale/pt_BR/foreman_remote_execution.po +23 -23
  33. data/locale/ru/foreman_remote_execution.po +23 -23
  34. data/locale/zh_CN/foreman_remote_execution.po +23 -23
  35. data/locale/zh_TW/foreman_remote_execution.po +23 -23
  36. data/test/unit/api_params_test.rb +33 -0
  37. data/test/unit/remote_execution_provider_test.rb +26 -0
  38. data/webpack/JobWizard/JobWizardConstants.js +2 -2
  39. data/webpack/JobWizard/__tests__/fixtures.js +8 -4
  40. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +9 -0
  41. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +21 -0
  42. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +7 -2
  43. data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +2 -1
  44. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +1 -1
  45. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +1 -1
  46. data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +1 -0
  47. data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +1 -0
  48. data/webpack/JobWizard/steps/ReviewDetails/index.js +2 -1
  49. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +15 -15
  50. data/webpack/JobWizard/steps/form/SearchSelect.js +0 -1
  51. data/webpack/JobWizard/submit.js +2 -0
  52. data/webpack/__mocks__/foremanReact/common/globalIdHelpers.js +1 -0
  53. data/webpack/global_index.js +2 -8
  54. data/webpack/react_app/components/FeaturesDropdown/index.js +1 -1
  55. data/webpack/react_app/components/HostKebab/KebabItems.js +6 -1
  56. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
  57. data/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js +1 -0
  58. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +8 -3
  59. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +9 -1
  60. data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +1 -4
  61. data/webpack/react_app/components/TargetingHosts/index.js +1 -0
  62. data/webpack/react_app/extend/Fills.js +48 -0
  63. metadata +9 -8
  64. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +0 -62
  65. data/webpack/react_app/extend/fillKebabItems.js +0 -11
  66. data/webpack/react_app/extend/fillRecentJobsCard.js +0 -11
  67. data/webpack/react_app/extend/fillRexFeaturesDropdown.js +0 -11
  68. data/webpack/react_app/extend/fillregistrationAdvanced.js +0 -11
@@ -23,6 +23,9 @@ msgstr "%s"
23
23
  msgid "%s ago"
24
24
  msgstr "%s 之前"
25
25
 
26
+ msgid "%s job has been invoked"
27
+ msgstr ""
28
+
26
29
  msgid "%{description} on %{host}"
27
30
  msgstr "%{description} 於 %{host} 之上"
28
31
 
@@ -67,15 +70,15 @@ msgstr "要使用執行這 script 的使用者。如果使用者與 SSH 使用
67
70
  msgid "Abort Job"
68
71
  msgstr ""
69
72
 
73
+ msgid "Action with sub plans"
74
+ msgstr ""
75
+
70
76
  msgid "Actions"
71
77
  msgstr "動作"
72
78
 
73
79
  msgid "Add Foreign Input Set"
74
80
  msgstr "新增外部輸入集"
75
81
 
76
- msgid "Advanced Fields"
77
- msgstr ""
78
-
79
82
  msgid "Advanced fields"
80
83
  msgstr ""
81
84
 
@@ -466,9 +469,15 @@ msgstr ""
466
469
  msgid "Import"
467
470
  msgstr "匯入"
468
471
 
472
+ msgid "Import Puppet classes"
473
+ msgstr ""
474
+
469
475
  msgid "Import a job template from ERB"
470
476
  msgstr ""
471
477
 
478
+ msgid "Import facts"
479
+ msgstr ""
480
+
472
481
  msgid "Include all inputs from the foreign template"
473
482
  msgstr ""
474
483
 
@@ -502,9 +511,6 @@ msgstr "祈願類型,%s 之一"
502
511
  msgid "Job"
503
512
  msgstr "工作"
504
513
 
505
- msgid "Job Category"
506
- msgstr ""
507
-
508
514
  msgid "Job Details"
509
515
  msgstr ""
510
516
 
@@ -826,7 +832,7 @@ msgstr "解析至"
826
832
  msgid "Results"
827
833
  msgstr ""
828
834
 
829
- msgid "Review Details"
835
+ msgid "Review details"
830
836
  msgstr ""
831
837
 
832
838
  msgid "Run"
@@ -850,9 +856,6 @@ msgstr ""
850
856
  msgid "Running"
851
857
  msgstr ""
852
858
 
853
- msgid "SSH"
854
- msgstr "SSH"
855
-
856
859
  msgid "SSH Port"
857
860
  msgstr ""
858
861
 
@@ -868,6 +871,9 @@ msgstr "排程"
868
871
  msgid "Schedule Remote Job"
869
872
  msgstr ""
870
873
 
874
+ msgid "Schedule a job"
875
+ msgstr ""
876
+
871
877
  msgid "Schedule for future execution"
872
878
  msgstr ""
873
879
 
@@ -889,6 +895,9 @@ msgstr ""
889
895
  msgid "Scheduled to start before"
890
896
  msgstr ""
891
897
 
898
+ msgid "Script"
899
+ msgstr ""
900
+
892
901
  msgid "Scroll to bottom"
893
902
  msgstr "捲動至底端"
894
903
 
@@ -1209,16 +1218,16 @@ msgstr "使用者輸入"
1209
1218
  msgid "Value"
1210
1219
  msgstr "值"
1211
1220
 
1212
- msgid "View All Jobs"
1221
+ msgid "View all jobs"
1213
1222
  msgstr ""
1214
1223
 
1215
- msgid "View Finished Jobs"
1224
+ msgid "View finished jobs"
1216
1225
  msgstr ""
1217
1226
 
1218
- msgid "View Running Jobs"
1227
+ msgid "View running jobs"
1219
1228
  msgstr ""
1220
1229
 
1221
- msgid "View Scheduled Jobs"
1230
+ msgid "View scheduled jobs"
1222
1231
  msgstr ""
1223
1232
 
1224
1233
  msgid "Web Console"
@@ -1364,12 +1373,3 @@ msgstr ""
1364
1373
 
1365
1374
  msgid "using Smart Proxy"
1366
1375
  msgstr ""
1367
-
1368
- msgid "»Action with sub plans«"
1369
- msgstr ""
1370
-
1371
- msgid "»Import Puppet classes«"
1372
- msgstr ""
1373
-
1374
- msgid "»Import facts«"
1375
- msgstr ""
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class ApiParamsTest < ActiveSupport::TestCase
6
+ describe '#format_datetime' do
7
+ let(:params) { JobInvocationComposer::ApiParams.allocate }
8
+
9
+ it 'leaves empty string as is' do
10
+ assert_equal params.send(:format_datetime, ''), ''
11
+ end
12
+
13
+ it 'honors explicitly supplied time zone' do
14
+ in_time_zone(ActiveSupport::TimeZone['America/New_York']) do
15
+ assert_equal '2022-07-08 08:53', params.send(:format_datetime, '2022-07-08 12:53:20 UTC')
16
+ end
17
+ end
18
+
19
+ it 'implicitly honors current user\'s time zone' do
20
+ in_time_zone(ActiveSupport::TimeZone['America/New_York']) do
21
+ assert_equal '2022-07-08 12:53', params.send(:format_datetime, '2022-07-08 12:53:20')
22
+ end
23
+ end
24
+ end
25
+
26
+ def in_time_zone(zone)
27
+ old_tz = Time.zone
28
+ Time.zone = zone
29
+ yield
30
+ ensure
31
+ Time.zone = old_tz
32
+ end
33
+ end
@@ -33,6 +33,10 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
33
33
  it 'accepts strings' do
34
34
  RemoteExecutionProvider.provider_for('SSH').must_equal SSHExecutionProvider
35
35
  end
36
+
37
+ it 'returns a default one if unknown value is provided' do
38
+ RemoteExecutionProvider.provider_for('WinRM').must_equal ScriptExecutionProvider
39
+ end
36
40
  end
37
41
 
38
42
  describe '.provider_names' do
@@ -52,6 +56,28 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
52
56
  end
53
57
  end
54
58
 
59
+ describe '.provider_proxy_features' do
60
+ it 'returns correct values' do
61
+ RemoteExecutionProvider.stubs(:providers).returns(
62
+ :SSH => SSHExecutionProvider,
63
+ :script => ScriptExecutionProvider
64
+ )
65
+
66
+ features = RemoteExecutionProvider.provider_proxy_features
67
+ _(features).must_include 'SSH'
68
+ _(features).must_include 'Script'
69
+ RemoteExecutionProvider.unstub(:providers)
70
+ end
71
+
72
+ it 'can deal with non-arrays' do
73
+ provider = OpenStruct.new(proxy_feature: 'Testing')
74
+ RemoteExecutionProvider.stubs(:providers).returns(:testing => provider)
75
+ features = RemoteExecutionProvider.provider_proxy_features
76
+ _(features).must_include 'Testing'
77
+ RemoteExecutionProvider.unstub(:providers)
78
+ end
79
+ end
80
+
55
81
  describe '.host_setting' do
56
82
  let(:host) { FactoryBot.create(:host) }
57
83
 
@@ -19,9 +19,9 @@ export const repeatTypes = {
19
19
  export const WIZARD_TITLES = {
20
20
  categoryAndTemplate: __('Category and Template'),
21
21
  hostsAndInputs: __('Target hosts and inputs'),
22
- advanced: __('Advanced Fields'),
22
+ advanced: __('Advanced fields'),
23
23
  schedule: __('Schedule'),
24
- review: __('Review Details'),
24
+ review: __('Review details'),
25
25
  };
26
26
 
27
27
  export const initialScheduleState = {
@@ -193,7 +193,11 @@ export const gqlMock = [
193
193
  data: {
194
194
  hosts: {
195
195
  totalCount: 3,
196
- nodes: [{ name: 'host1' }, { name: 'host2' }, { name: 'host3' }],
196
+ nodes: [
197
+ { id: 'MDE6SG9zdC0x', name: 'host1' },
198
+ { id: 'MDE6SG9zdC0y', name: 'host2' },
199
+ { id: 'MDE6SG9zdC0z', name: 'host3' },
200
+ ],
197
201
  },
198
202
  },
199
203
  },
@@ -211,9 +215,9 @@ export const gqlMock = [
211
215
  hostgroups: {
212
216
  totalCount: 3,
213
217
  nodes: [
214
- { name: 'host_group1' },
215
- { name: 'host_group2' },
216
- { name: 'host_group3' },
218
+ { id: 'MDE6SG9zdGdyb3VwLTE=', name: 'host_group1' },
219
+ { id: 'MDE6SG9zdGdyb3VwLTI=', name: 'host_group2' },
220
+ { id: 'MDE6SG9zdGdyb3VwLTM=', name: 'host_group3' },
217
221
  ],
218
222
  },
219
223
  },
@@ -16,6 +16,7 @@ import {
16
16
  TimeSpanLevelField,
17
17
  TemplateInputsFields,
18
18
  ExecutionOrderingField,
19
+ SSHUserField,
19
20
  } from './Fields';
20
21
  import { DescriptionField } from './DescriptionField';
21
22
  import { WIZARD_TITLES } from '../../JobWizardConstants';
@@ -40,6 +41,14 @@ export const AdvancedFields = ({
40
41
  value={advancedValues.templateValues}
41
42
  setValue={newValue => setAdvancedValues({ templateValues: newValue })}
42
43
  />
44
+ <SSHUserField
45
+ value={advancedValues.sshUser}
46
+ setValue={newValue =>
47
+ setAdvancedValues({
48
+ sshUser: newValue,
49
+ })
50
+ }
51
+ />
43
52
  {effectiveUser?.overridable && (
44
53
  <EffectiveUserField
45
54
  value={advancedValues.effectiveUserValue}
@@ -203,6 +203,24 @@ export const ExecutionOrderingField = ({ isRandomizedOrdering, setValue }) => (
203
203
  export const TemplateInputsFields = ({ inputs, value, setValue }) => (
204
204
  <>{inputs?.map(input => formatter(input, value, setValue))}</>
205
205
  );
206
+
207
+ export const SSHUserField = ({ value, setValue }) => (
208
+ <FormGroup
209
+ label={__('SSH user')}
210
+ labelIcon={helpLabel(__('A user to be used for SSH.'), 'ssh-user')}
211
+ fieldId="ssh-user"
212
+ >
213
+ <TextInput
214
+ aria-label="ssh user"
215
+ autoComplete="ssh-user"
216
+ id="ssh-user"
217
+ type="text"
218
+ value={value}
219
+ onChange={newValue => setValue(newValue)}
220
+ />
221
+ </FormGroup>
222
+ );
223
+
206
224
  EffectiveUserField.propTypes = {
207
225
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
208
226
  setValue: PropTypes.func.isRequired,
@@ -240,3 +258,6 @@ TemplateInputsFields.propTypes = {
240
258
  TemplateInputsFields.defaultProps = {
241
259
  value: {},
242
260
  };
261
+
262
+ SSHUserField.propTypes = EffectiveUserField.propTypes;
263
+ SSHUserField.defaultProps = EffectiveUserField.defaultProps;
@@ -143,7 +143,7 @@ describe('AdvancedFields', () => {
143
143
  );
144
144
 
145
145
  await act(async () => {
146
- fireEvent.click(screen.getByText('Advanced Fields'));
146
+ fireEvent.click(screen.getByText('Advanced fields'));
147
147
  });
148
148
  expect(textField.value).toBe(textValue);
149
149
  expect(searchField.value).toBe(searchValue);
@@ -162,9 +162,14 @@ describe('AdvancedFields', () => {
162
162
  </MockedProvider>
163
163
  );
164
164
  await act(async () => {
165
- fireEvent.click(screen.getByText('Advanced Fields'));
165
+ fireEvent.click(screen.getByText('Advanced fields'));
166
166
  });
167
167
 
168
+ expect(
169
+ screen.getByLabelText('ssh user', {
170
+ selector: 'input',
171
+ }).value
172
+ ).toBe('');
168
173
  expect(
169
174
  screen.getByLabelText('effective user', {
170
175
  selector: 'input',
@@ -5,6 +5,7 @@ import {
5
5
  useForemanOrganization,
6
6
  useForemanLocation,
7
7
  } from 'foremanReact/Root/Context/ForemanContext';
8
+ import { decodeId } from 'foremanReact/common/globalIdHelpers';
8
9
  import { HOSTS, HOST_GROUPS, dataName } from '../../JobWizardConstants';
9
10
  import { SearchSelect } from '../form/SearchSelect';
10
11
  import hostsQuery from './hosts.gql';
@@ -35,7 +36,7 @@ export const useNameSearchGQL = apiKey => {
35
36
  subtotal: data?.[dataName[apiKey]]?.totalCount,
36
37
  results:
37
38
  data?.[dataName[apiKey]]?.nodes.map(node => ({
38
- id: node.name,
39
+ id: decodeId(node.id),
39
40
  name: node.name,
40
41
  })) || [],
41
42
  },
@@ -15,7 +15,7 @@ const SelectedChip = ({ selected, setSelected, categoryName }) => {
15
15
  {selected.map(({ name, id }, index) => (
16
16
  <Chip
17
17
  key={index}
18
- id={id}
18
+ id={`${categoryName}-${id}`}
19
19
  onClick={() => deleteItem(id)}
20
20
  closeBtnAriaLabel={`Close ${name}`}
21
21
  >
@@ -1,6 +1,6 @@
1
1
  export const buildHostQuery = (selected, search) => {
2
2
  const { hosts, hostCollections, hostGroups } = selected;
3
- const hostsSearch = `(name ^ (${hosts.map(({ id }) => id).join(',')}))`;
3
+ const hostsSearch = `(id ^ (${hosts.map(({ id }) => id).join(',')}))`;
4
4
  const hostCollectionsSearch = `(host_collection_id ^ (${hostCollections
5
5
  .map(({ id }) => id)
6
6
  .join(',')}))`;
@@ -2,6 +2,7 @@ query($search: String!) {
2
2
  hostgroups(first: 100, last: 100, search: $search) {
3
3
  totalCount
4
4
  nodes {
5
+ id
5
6
  name
6
7
  }
7
8
  }
@@ -2,6 +2,7 @@ query($search: String!) {
2
2
  hosts(first: 100, last: 100, search: $search) {
3
3
  totalCount
4
4
  nodes {
5
+ id
5
6
  name
6
7
  }
7
8
  }
@@ -72,7 +72,7 @@ const ReviewDetails = ({
72
72
  };
73
73
  const [isAdvancedShown, setIsAdvancedShown] = useState(false);
74
74
  const detailsFirstHalf = [
75
- { label: __('Job Category'), value: jobCategory },
75
+ { label: __('Job category'), value: jobCategory },
76
76
  { label: __('Job template'), value: jobTemplate },
77
77
  { label: __('Target hosts'), value: stringHosts() },
78
78
  ...templateInputs.map(({ name }) => ({
@@ -125,6 +125,7 @@ const ReviewDetails = ({
125
125
  ].filter(d => d);
126
126
 
127
127
  const advancedFields = [
128
+ { label: __('SSH user'), value: advancedValues.sshUser },
128
129
  { label: __('Effective user'), value: advancedValues.effectiveUserValue },
129
130
  { label: __('Description Template'), value: advancedValues.description },
130
131
  { label: __('Timeout to kill'), value: advancedValues.timeoutToKill },
@@ -186,7 +186,7 @@ describe('Schedule', () => {
186
186
  expect(
187
187
  screen.getByPlaceholderText('Repeat N times').hasAttribute('disabled')
188
188
  ).toBeTruthy();
189
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
189
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
190
190
  await act(async () => {
191
191
  fireEvent.click(
192
192
  screen.getByLabelText('Does not repeat', { selector: 'button' })
@@ -196,7 +196,7 @@ describe('Schedule', () => {
196
196
  await act(async () => {
197
197
  fireEvent.click(screen.getByText('Cronline'));
198
198
  });
199
- expect(screen.getByText('Review Details').disabled).toBeTruthy();
199
+ expect(screen.getByText('Review details').disabled).toBeTruthy();
200
200
  const newRepeatTimes = '3';
201
201
  const repeatNTimes = screen.getByPlaceholderText('Repeat N times');
202
202
  expect(repeatNTimes.value).toBe('');
@@ -216,7 +216,7 @@ describe('Schedule', () => {
216
216
  });
217
217
  });
218
218
  expect(cronline.value).toBe(newCronline);
219
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
219
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
220
220
 
221
221
  await act(async () => {
222
222
  fireEvent.click(screen.getByText('Category and Template'));
@@ -236,7 +236,7 @@ describe('Schedule', () => {
236
236
  fireEvent.click(screen.getByText('Monthly'));
237
237
  });
238
238
 
239
- expect(screen.getByText('Review Details').disabled).toBeTruthy();
239
+ expect(screen.getByText('Review details').disabled).toBeTruthy();
240
240
  const newDays = '1,2,3';
241
241
  const days = screen.getByLabelText('days');
242
242
  expect(days.value).toBe('');
@@ -248,7 +248,7 @@ describe('Schedule', () => {
248
248
  });
249
249
  expect(days.value).toBe(newDays);
250
250
 
251
- expect(screen.getByText('Review Details').disabled).toBeTruthy();
251
+ expect(screen.getByText('Review details').disabled).toBeTruthy();
252
252
  const newAtMonthly = '13:07';
253
253
  const at = () => screen.getByLabelText('repeat-at');
254
254
  expect(at().value).toBe('');
@@ -259,13 +259,13 @@ describe('Schedule', () => {
259
259
  });
260
260
  expect(at().value).toBe(newAtMonthly);
261
261
 
262
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
262
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
263
263
  fireEvent.click(screen.getByText('Monthly'));
264
264
  await act(async () => {
265
265
  fireEvent.click(screen.getByText('Weekly'));
266
266
  });
267
267
 
268
- expect(screen.getByText('Review Details').disabled).toBeTruthy();
268
+ expect(screen.getByText('Review details').disabled).toBeTruthy();
269
269
  const dayTue = screen.getByLabelText('Tue checkbox');
270
270
  const daySat = screen.getByLabelText('Sat checkbox');
271
271
  expect(dayTue.checked).toBe(false);
@@ -293,19 +293,19 @@ describe('Schedule', () => {
293
293
  });
294
294
  expect(at().value).toBe(newAtWeekly);
295
295
 
296
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
296
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
297
297
  fireEvent.click(screen.getByText('Weekly'));
298
298
  await act(async () => {
299
299
  fireEvent.click(screen.getByText('Daily'));
300
300
  });
301
301
 
302
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
302
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
303
303
  await act(async () => {
304
304
  fireEvent.change(at(), {
305
305
  target: { value: '' },
306
306
  });
307
307
  });
308
- expect(screen.getByText('Review Details').disabled).toBeTruthy();
308
+ expect(screen.getByText('Review details').disabled).toBeTruthy();
309
309
  const newAtDaily = '17:07';
310
310
  expect(at().value).toBe('');
311
311
  await act(async () => {
@@ -314,14 +314,14 @@ describe('Schedule', () => {
314
314
  });
315
315
  });
316
316
  expect(at().value).toBe(newAtDaily);
317
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
317
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
318
318
 
319
319
  fireEvent.click(screen.getByText('Daily'));
320
320
  await act(async () => {
321
321
  fireEvent.click(screen.getByText('Hourly'));
322
322
  });
323
323
 
324
- expect(screen.getByText('Review Details').disabled).toBeTruthy();
324
+ expect(screen.getByText('Review details').disabled).toBeTruthy();
325
325
  const newMinutes = '6';
326
326
  const atHourly = screen.getByLabelText('repeat-at-minute-typeahead');
327
327
  expect(atHourly.value).toBe('');
@@ -331,7 +331,7 @@ describe('Schedule', () => {
331
331
  await act(async () => {
332
332
  fireEvent.click(screen.getByText(newMinutes));
333
333
  });
334
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
334
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
335
335
  expect(atHourly.value).toBe(newMinutes);
336
336
  });
337
337
  it('should show invalid error on start date after end', async () => {
@@ -353,7 +353,7 @@ describe('Schedule', () => {
353
353
  expect(
354
354
  screen.queryAllByText('End time needs to be after start time')
355
355
  ).toHaveLength(0);
356
- expect(screen.getByText('Review Details').disabled).toBeFalsy();
356
+ expect(screen.getByText('Review details').disabled).toBeFalsy();
357
357
  await act(async () => {
358
358
  await fireEvent.change(startsDateField, {
359
359
  target: { value: '2020/10/15' },
@@ -368,7 +368,7 @@ describe('Schedule', () => {
368
368
  screen.queryAllByText('End time needs to be after start time')
369
369
  ).toHaveLength(1);
370
370
 
371
- expect(screen.getByText('Review Details').disabled).toBeTruthy();
371
+ expect(screen.getByText('Review details').disabled).toBeTruthy();
372
372
  });
373
373
  it('purpose and ends should be disabled when no reaccurence ', async () => {
374
374
  render(
@@ -93,7 +93,6 @@ export const SearchSelect = ({
93
93
  autoSearch(value || '');
94
94
  }}
95
95
  placeholderText={placeholderText}
96
- onFilter={() => null} // https://github.com/patternfly/patternfly-react/issues/6321
97
96
  typeAheadAriaLabel={`${name} typeahead input`}
98
97
  >
99
98
  {selectOptions}
@@ -23,6 +23,7 @@ export const submit = ({
23
23
  purpose,
24
24
  } = scheduleValue;
25
25
  const {
26
+ sshUser,
26
27
  effectiveUserValue,
27
28
  effectiveUserPassword,
28
29
  description,
@@ -64,6 +65,7 @@ export const submit = ({
64
65
  : 'dynamic_query',
65
66
  randomized_ordering: isRandomizedOrdering,
66
67
  inputs: { ...templateValues, ...advancedTemplateValues },
68
+ ssh_user: sshUser,
67
69
  ssh: {
68
70
  effective_user: effectiveUserValue,
69
71
  effective_user_password: effectiveUserPassword,
@@ -0,0 +1 @@
1
+ export const decodeId = whatever => whatever;
@@ -1,14 +1,8 @@
1
1
  import { registerRoutes } from 'foremanReact/routes/RoutingService';
2
2
  import routes from './Routes/routes';
3
- import fillregistrationAdvanced from './react_app/extend/fillregistrationAdvanced';
4
- import fillRecentJobsCard from './react_app/extend/fillRecentJobsCard';
5
- import fillFeaturesDropdown from './react_app/extend/fillRexFeaturesDropdown';
6
- import fillKebabItems from './react_app/extend/fillKebabItems';
7
3
  import registerReducers from './react_app/extend/reducers';
4
+ import registerFills from './react_app/extend/Fills';
8
5
 
9
6
  registerReducers();
10
7
  registerRoutes('foreman_remote_execution', routes);
11
- fillFeaturesDropdown();
12
- fillRecentJobsCard();
13
- fillregistrationAdvanced();
14
- fillKebabItems();
8
+ registerFills();
@@ -52,7 +52,7 @@ const FeaturesDropdown = ({ hostId }) => {
52
52
  toggle={
53
53
  <DropdownToggle
54
54
  splitButtonItems={scheduleJob}
55
- toggleVariant="primary"
55
+ toggleVariant="secondary"
56
56
  onToggle={() => setIsOpen(prev => !prev)}
57
57
  isDisabled={status === STATUS.PENDING}
58
58
  splitButtonVariant="action"
@@ -13,7 +13,12 @@ const HostKebabItems = () => {
13
13
 
14
14
  if (!consoleUrl) return null;
15
15
  return (
16
- <DropdownItem icon={<CodeIcon />} href={consoleUrl}>
16
+ <DropdownItem
17
+ icon={<CodeIcon />}
18
+ href={consoleUrl}
19
+ target="_blank"
20
+ rel="noreferrer"
21
+ >
17
22
  {__('Web Console')}
18
23
  </DropdownItem>
19
24
  );
@@ -28,7 +28,7 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
28
28
  href={foremanUrl(`${JOB_BASE_URL}${name}`)}
29
29
  key="link-to-all"
30
30
  >
31
- {__('View All Jobs')}
31
+ {__('View all jobs')}
32
32
  </DropdownItem>,
33
33
  <DropdownItem
34
34
  href={foremanUrl(
@@ -36,19 +36,19 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
36
36
  )}
37
37
  key="link-to-finished"
38
38
  >
39
- {__('View Finished Jobs')}
39
+ {__('View finished jobs')}
40
40
  </DropdownItem>,
41
41
  <DropdownItem
42
42
  href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+running`)}
43
43
  key="link-to-running"
44
44
  >
45
- {__('View Running Jobs')}
45
+ {__('View running jobs')}
46
46
  </DropdownItem>,
47
47
  <DropdownItem
48
48
  href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+queued`)}
49
49
  key="link-to-scheduled"
50
50
  >
51
- {__('View Scheduled Jobs')}
51
+ {__('View scheduled jobs')}
52
52
  </DropdownItem>,
53
53
  ]}
54
54
  >
@@ -1 +1,2 @@
1
1
  export const TARGETING_HOSTS = 'TARGETING_HOSTS';
2
+ export const TARGETING_HOSTS_AUTOCOMPLETE = 'targeting_hosts_search';