foreman_remote_execution 4.2.2 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/js_ci.yml +29 -0
  3. data/.github/workflows/{ci.yml → ruby_ci.yml} +22 -50
  4. data/.prettierrc +4 -0
  5. data/.rubocop.yml +13 -49
  6. data/.rubocop_todo.yml +326 -102
  7. data/Gemfile +1 -4
  8. data/app/controllers/api/v2/job_invocations_controller.rb +28 -23
  9. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
  10. data/app/controllers/job_templates_controller.rb +4 -4
  11. data/app/controllers/ui_job_wizard_controller.rb +30 -0
  12. data/app/helpers/job_invocations_helper.rb +2 -2
  13. data/app/helpers/remote_execution_helper.rb +35 -8
  14. data/app/lib/actions/remote_execution/run_host_job.rb +68 -5
  15. data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
  16. data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
  17. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +5 -5
  18. data/app/models/host_status/execution_status.rb +12 -5
  19. data/app/models/invocation_provider_input_value.rb +12 -0
  20. data/app/models/job_invocation.rb +28 -8
  21. data/app/models/job_invocation_composer.rb +72 -17
  22. data/app/models/remote_execution_provider.rb +17 -2
  23. data/app/models/setting/remote_execution.rb +10 -0
  24. data/app/models/ssh_execution_provider.rb +4 -4
  25. data/app/models/template_invocation.rb +2 -0
  26. data/app/overrides/execution_interface.rb +8 -8
  27. data/app/overrides/subnet_proxies.rb +6 -6
  28. data/app/services/renderer_methods.rb +12 -0
  29. data/app/views/job_invocations/_form.html.erb +8 -0
  30. data/app/views/template_invocations/show.html.erb +30 -23
  31. data/config/routes.rb +5 -0
  32. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  33. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  34. data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
  35. data/extra/cockpit/foreman-cockpit-session +6 -6
  36. data/foreman_remote_execution.gemspec +1 -2
  37. data/lib/foreman_remote_execution/engine.rb +22 -7
  38. data/lib/foreman_remote_execution/version.rb +1 -1
  39. data/lib/tasks/foreman_remote_execution_tasks.rake +1 -18
  40. data/locale/action_names.rb +1 -0
  41. data/locale/de/foreman_remote_execution.po +77 -27
  42. data/locale/en/foreman_remote_execution.po +77 -27
  43. data/locale/en_GB/foreman_remote_execution.po +77 -27
  44. data/locale/es/foreman_remote_execution.po +77 -27
  45. data/locale/foreman_remote_execution.pot +241 -163
  46. data/locale/fr/foreman_remote_execution.po +77 -27
  47. data/locale/ja/foreman_remote_execution.po +77 -27
  48. data/locale/ko/foreman_remote_execution.po +77 -27
  49. data/locale/pt_BR/foreman_remote_execution.po +77 -27
  50. data/locale/ru/foreman_remote_execution.po +77 -27
  51. data/locale/zh_CN/foreman_remote_execution.po +77 -27
  52. data/locale/zh_TW/foreman_remote_execution.po +77 -27
  53. data/package.json +4 -2
  54. data/test/functional/api/v2/job_invocations_controller_test.rb +38 -5
  55. data/test/functional/api/v2/registration_controller_test.rb +4 -13
  56. data/test/functional/ui_job_wizard_controller_test.rb +16 -0
  57. data/test/helpers/remote_execution_helper_test.rb +16 -0
  58. data/test/unit/job_invocation_composer_test.rb +86 -2
  59. data/test/unit/job_invocation_report_template_test.rb +57 -0
  60. data/webpack/JobWizard/JobWizard.js +96 -0
  61. data/webpack/JobWizard/JobWizard.scss +14 -0
  62. data/webpack/JobWizard/JobWizardConstants.js +6 -0
  63. data/webpack/JobWizard/JobWizardSelectors.js +38 -0
  64. data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
  65. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
  66. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  67. data/webpack/JobWizard/__tests__/fixtures.js +26 -0
  68. data/webpack/JobWizard/__tests__/integration.test.js +156 -0
  69. data/webpack/JobWizard/index.js +32 -0
  70. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +93 -0
  71. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +181 -0
  72. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +25 -0
  73. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
  74. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
  75. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +52 -0
  76. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
  77. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
  78. data/webpack/JobWizard/steps/form/FormHelpers.js +19 -0
  79. data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
  80. data/webpack/JobWizard/steps/form/SelectField.js +48 -0
  81. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
  82. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
  83. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
  84. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
  85. data/webpack/Routes/routes.js +12 -0
  86. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  87. data/webpack/__mocks__/foremanReact/history.js +1 -0
  88. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  89. data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
  90. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  91. data/webpack/global_index.js +10 -0
  92. data/webpack/index.js +3 -4
  93. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
  94. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  95. data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
  96. data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
  97. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
  98. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
  99. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
  100. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +1 -1
  101. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +0 -3
  102. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  103. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -1
  104. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  105. data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
  106. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  107. data/webpack/react_app/extend/reducers.js +5 -0
  108. metadata +58 -20
  109. data/app/views/api/v2/registration/_form.html.erb +0 -12
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { Provider } from 'react-redux';
3
+ import configureMockStore from 'redux-mock-store';
4
+ import * as patternfly from '@patternfly/react-core';
5
+ import { mount } from '@theforeman/test';
6
+ import { AdvancedFields } from '../AdvancedFields';
7
+
8
+ jest.spyOn(patternfly, 'FormGroup');
9
+ patternfly.FormGroup.mockImplementation(props => (
10
+ <div>{props.navAriaLabel}</div>
11
+ ));
12
+ const mockStore = configureMockStore([]);
13
+ const store = mockStore({
14
+ JOB_TEMPLATE: { response: { effective_user: { overridable: true } } },
15
+ });
16
+ describe('AdvancedFields', () => {
17
+ it('rendring', () => {
18
+ const component = mount(
19
+ <Provider store={store}>
20
+ <AdvancedFields advancedValues={{}} setAdvancedValues={jest.fn()} />
21
+ </Provider>
22
+ );
23
+ expect(component).toMatchSnapshot();
24
+ });
25
+ });
@@ -0,0 +1,249 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`AdvancedFields rendring 1`] = `
4
+ <Provider
5
+ store={
6
+ Object {
7
+ "clearActions": [Function],
8
+ "dispatch": [Function],
9
+ "getActions": [Function],
10
+ "getState": [Function],
11
+ "replaceReducer": [Function],
12
+ "subscribe": [Function],
13
+ }
14
+ }
15
+ >
16
+ <AdvancedFields
17
+ advancedValues={Object {}}
18
+ setAdvancedValues={[MockFunction]}
19
+ >
20
+ <Title
21
+ className="advanced-fields-title"
22
+ headingLevel="h2"
23
+ >
24
+ <h2
25
+ className="pf-c-title pf-m-xl advanced-fields-title"
26
+ >
27
+ Advanced Fields
28
+ </h2>
29
+ </Title>
30
+ <Form>
31
+ <form
32
+ className="pf-c-form"
33
+ noValidate={true}
34
+ >
35
+ <EffectiveUserField
36
+ setValue={[Function]}
37
+ value=""
38
+ >
39
+ <mockConstructor
40
+ fieldId="effective-user"
41
+ label="Effective user"
42
+ labelIcon={
43
+ <Popover
44
+ aria-label="help-text"
45
+ bodyContent="A user to be used for executing the script. If it differs from the SSH user, su or sudo is used to switch the accounts."
46
+ id="effective-user-help"
47
+ >
48
+ <button
49
+ aria-label="open-help-tooltip-button"
50
+ className="pf-c-form__group-label-help"
51
+ onClick={[Function]}
52
+ >
53
+ <HelpIcon
54
+ color="currentColor"
55
+ noVerticalAlign={true}
56
+ size="sm"
57
+ />
58
+ </button>
59
+ </Popover>
60
+ }
61
+ >
62
+ <div />
63
+ </mockConstructor>
64
+ </EffectiveUserField>
65
+ <TimeoutToKillField
66
+ setValue={[Function]}
67
+ value=""
68
+ >
69
+ <mockConstructor
70
+ fieldId="timeout-to-kill"
71
+ label="Timeout to kill"
72
+ labelIcon={
73
+ <Popover
74
+ aria-label="help-text"
75
+ bodyContent="Time in seconds from the start on the remote host after which the job should be killed."
76
+ id="timeout-to-kill-help"
77
+ >
78
+ <button
79
+ aria-label="open-help-tooltip-button"
80
+ className="pf-c-form__group-label-help"
81
+ onClick={[Function]}
82
+ >
83
+ <HelpIcon
84
+ color="currentColor"
85
+ noVerticalAlign={true}
86
+ size="sm"
87
+ />
88
+ </button>
89
+ </Popover>
90
+ }
91
+ >
92
+ <div />
93
+ </mockConstructor>
94
+ </TimeoutToKillField>
95
+ <PasswordField
96
+ setValue={[Function]}
97
+ value=""
98
+ >
99
+ <mockConstructor
100
+ fieldId="password"
101
+ label="Password"
102
+ labelIcon={
103
+ <Popover
104
+ aria-label="help-text"
105
+ bodyContent="Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution."
106
+ id="password-help"
107
+ >
108
+ <button
109
+ aria-label="open-help-tooltip-button"
110
+ className="pf-c-form__group-label-help"
111
+ onClick={[Function]}
112
+ >
113
+ <HelpIcon
114
+ color="currentColor"
115
+ noVerticalAlign={true}
116
+ size="sm"
117
+ />
118
+ </button>
119
+ </Popover>
120
+ }
121
+ >
122
+ <div />
123
+ </mockConstructor>
124
+ </PasswordField>
125
+ <KeyPassphraseField
126
+ setValue={[Function]}
127
+ value=""
128
+ >
129
+ <mockConstructor
130
+ fieldId="key-passphrase"
131
+ label="Private key passphrase"
132
+ labelIcon={
133
+ <Popover
134
+ aria-label="help-text"
135
+ bodyContent="Key passphrase is only applicable for SSH provider. Other providers ignore this field. Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution."
136
+ id="key-passphrase-help"
137
+ >
138
+ <button
139
+ aria-label="open-help-tooltip-button"
140
+ className="pf-c-form__group-label-help"
141
+ onClick={[Function]}
142
+ >
143
+ <HelpIcon
144
+ color="currentColor"
145
+ noVerticalAlign={true}
146
+ size="sm"
147
+ />
148
+ </button>
149
+ </Popover>
150
+ }
151
+ >
152
+ <div />
153
+ </mockConstructor>
154
+ </KeyPassphraseField>
155
+ <EffectiveUserPasswordField
156
+ setValue={[Function]}
157
+ value=""
158
+ >
159
+ <mockConstructor
160
+ fieldId="effective-user-password"
161
+ label="Effective user password"
162
+ labelIcon={
163
+ <Popover
164
+ aria-label="help-text"
165
+ bodyContent="Effective user password is only applicable for SSH provider. Other providers ignore this field. Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution."
166
+ id="effective-user-password-help"
167
+ >
168
+ <button
169
+ aria-label="open-help-tooltip-button"
170
+ className="pf-c-form__group-label-help"
171
+ onClick={[Function]}
172
+ >
173
+ <HelpIcon
174
+ color="currentColor"
175
+ noVerticalAlign={true}
176
+ size="sm"
177
+ />
178
+ </button>
179
+ </Popover>
180
+ }
181
+ >
182
+ <div />
183
+ </mockConstructor>
184
+ </EffectiveUserPasswordField>
185
+ <ConcurrencyLevelField
186
+ setValue={[Function]}
187
+ value=""
188
+ >
189
+ <mockConstructor
190
+ fieldId="concurrency-level"
191
+ label="Concurrency level"
192
+ labelIcon={
193
+ <Popover
194
+ aria-label="help-text"
195
+ bodyContent="Run at most N tasks at a time. If this is set and proxy batch triggering is enabled, then tasks are triggered on the smart proxy in batches of size 1."
196
+ id="concurrency-level-help"
197
+ >
198
+ <button
199
+ aria-label="open-help-tooltip-button"
200
+ className="pf-c-form__group-label-help"
201
+ onClick={[Function]}
202
+ >
203
+ <HelpIcon
204
+ color="currentColor"
205
+ noVerticalAlign={true}
206
+ size="sm"
207
+ />
208
+ </button>
209
+ </Popover>
210
+ }
211
+ >
212
+ <div />
213
+ </mockConstructor>
214
+ </ConcurrencyLevelField>
215
+ <TimeSpanLevelField
216
+ setValue={[Function]}
217
+ value=""
218
+ >
219
+ <mockConstructor
220
+ fieldId="time-span"
221
+ label="Time span"
222
+ labelIcon={
223
+ <Popover
224
+ aria-label="help-text"
225
+ bodyContent="Distribute execution over N seconds. If this is set and proxy batch triggering is enabled, then tasks are triggered on the smart proxy in batches of size 1."
226
+ id="time-span-help"
227
+ >
228
+ <button
229
+ aria-label="open-help-tooltip-button"
230
+ className="pf-c-form__group-label-help"
231
+ onClick={[Function]}
232
+ >
233
+ <HelpIcon
234
+ color="currentColor"
235
+ noVerticalAlign={true}
236
+ size="sm"
237
+ />
238
+ </button>
239
+ </Popover>
240
+ }
241
+ >
242
+ <div />
243
+ </mockConstructor>
244
+ </TimeSpanLevelField>
245
+ </form>
246
+ </Form>
247
+ </AdvancedFields>
248
+ </Provider>
249
+ `;
@@ -0,0 +1,109 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Title, Text, TextVariants, Form, Alert } from '@patternfly/react-core';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { SelectField } from '../form/SelectField';
6
+ import { GroupedSelectField } from '../form/GroupedSelectField';
7
+
8
+ export const CategoryAndTemplate = ({
9
+ jobCategories,
10
+ jobTemplates,
11
+ setJobTemplate,
12
+ selectedTemplateID,
13
+ selectedCategory,
14
+ setCategory,
15
+ errors,
16
+ }) => {
17
+ const templatesGroups = {};
18
+ jobTemplates.forEach(template => {
19
+ if (templatesGroups[template.provider_type]?.options)
20
+ templatesGroups[template.provider_type].options.push({
21
+ label: template.name,
22
+ value: template.id,
23
+ });
24
+ else
25
+ templatesGroups[template.provider_type] = {
26
+ options: [{ label: template.name, value: template.id }],
27
+ groupLabel: template.provider_type,
28
+ };
29
+ });
30
+
31
+ const selectedTemplate = jobTemplates.find(
32
+ template => template.id === selectedTemplateID
33
+ )?.name;
34
+
35
+ const onSelectCategory = newCategory => {
36
+ setCategory(newCategory);
37
+ setJobTemplate(null);
38
+ };
39
+ const { categoryError, allTemplatesError, templateError } = errors;
40
+ const isError = !!(categoryError || allTemplatesError || templateError);
41
+ return (
42
+ <>
43
+ <Title headingLevel="h2">{__('Category and Template')}</Title>
44
+ <Text component={TextVariants.p}>{__('All fields are required.')}</Text>
45
+ <Form>
46
+ <SelectField
47
+ label={__('Job category')}
48
+ fieldId="job_category"
49
+ options={jobCategories}
50
+ setValue={onSelectCategory}
51
+ value={selectedCategory}
52
+ placeholderText={categoryError ? __('Error') : ''}
53
+ isDisabled={!!categoryError}
54
+ />
55
+ <GroupedSelectField
56
+ label={__('Job template')}
57
+ fieldId="job_template"
58
+ groups={Object.values(templatesGroups)}
59
+ setSelected={setJobTemplate}
60
+ selected={selectedTemplate}
61
+ isDisabled={!!(categoryError || allTemplatesError)}
62
+ placeholderText={allTemplatesError ? __('Error') : ''}
63
+ />
64
+ {isError && (
65
+ <Alert variant="danger" title={__('Errors:')}>
66
+ {categoryError && (
67
+ <span>
68
+ {__('Categories list failed with:')} {categoryError}
69
+ </span>
70
+ )}
71
+ {allTemplatesError && (
72
+ <span>
73
+ {__('Templates list failed with:')} {allTemplatesError}
74
+ </span>
75
+ )}
76
+ {templateError && (
77
+ <span>
78
+ {__('Template failed with:')} {templateError}
79
+ </span>
80
+ )}
81
+ </Alert>
82
+ )}
83
+ </Form>
84
+ </>
85
+ );
86
+ };
87
+
88
+ CategoryAndTemplate.propTypes = {
89
+ jobCategories: PropTypes.array,
90
+ jobTemplates: PropTypes.array,
91
+ setJobTemplate: PropTypes.func.isRequired,
92
+ selectedTemplateID: PropTypes.number,
93
+ setCategory: PropTypes.func.isRequired,
94
+ selectedCategory: PropTypes.string,
95
+ errors: PropTypes.shape({
96
+ categoryError: PropTypes.string,
97
+ allTemplatesError: PropTypes.string,
98
+ templateError: PropTypes.string,
99
+ }),
100
+ };
101
+ CategoryAndTemplate.defaultProps = {
102
+ jobCategories: [],
103
+ jobTemplates: [],
104
+ selectedTemplateID: null,
105
+ selectedCategory: null,
106
+ errors: {},
107
+ };
108
+
109
+ export default CategoryAndTemplate;
@@ -0,0 +1,52 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+ import { CategoryAndTemplate } from './CategoryAndTemplate';
3
+
4
+ const baseProps = {
5
+ setJobTemplate: jest.fn(),
6
+ selectedTemplateID: 190,
7
+ setCategory: jest.fn(),
8
+ };
9
+ const fixtures = {
10
+ 'renders with props': {
11
+ ...baseProps,
12
+ jobCategories: [
13
+ 'Commands',
14
+ 'Ansible Playbook',
15
+ 'Ansible Galaxy',
16
+ 'Ansible Roles Installation',
17
+ ],
18
+ jobTemplates: [
19
+ {
20
+ id: 190,
21
+ name: 'ab Run Command - SSH Default clone',
22
+ job_category: 'Commands',
23
+ provider_type: 'SSH',
24
+ snippet: false,
25
+ },
26
+ {
27
+ id: 168,
28
+ name: 'Ansible Roles - Ansible Default',
29
+ job_category: 'Ansible Playbook',
30
+ provider_type: 'Ansible',
31
+ snippet: false,
32
+ },
33
+ {
34
+ id: 170,
35
+ name: 'Ansible Roles - Install from git',
36
+ job_category: 'Ansible Roles Installation',
37
+ provider_type: 'Ansible',
38
+ snippet: false,
39
+ },
40
+ ],
41
+ selectedCategory: 'I am a category',
42
+ },
43
+ 'render with error': {
44
+ ...baseProps,
45
+ errors: { allTemplatesError: 'I have an error' },
46
+ },
47
+ };
48
+
49
+ describe('CategoryAndTemplate', () => {
50
+ describe('rendering', () =>
51
+ testComponentSnapshotsWithFixtures(CategoryAndTemplate, fixtures));
52
+ });
@@ -0,0 +1,113 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`CategoryAndTemplate rendering render with error 1`] = `
4
+ <Fragment>
5
+ <Title
6
+ headingLevel="h2"
7
+ >
8
+ Category and Template
9
+ </Title>
10
+ <Text
11
+ component="p"
12
+ >
13
+ All fields are required.
14
+ </Text>
15
+ <Form>
16
+ <SelectField
17
+ fieldId="job_category"
18
+ isDisabled={false}
19
+ label="Job category"
20
+ options={Array []}
21
+ placeholderText=""
22
+ setValue={[Function]}
23
+ value={null}
24
+ />
25
+ <GroupedSelectField
26
+ fieldId="job_template"
27
+ groups={Array []}
28
+ isDisabled={true}
29
+ label="Job template"
30
+ placeholderText="Error"
31
+ selected={null}
32
+ setSelected={[MockFunction]}
33
+ />
34
+ <Alert
35
+ title="Errors:"
36
+ variant="danger"
37
+ >
38
+ <span>
39
+ Templates list failed with:
40
+
41
+ I have an error
42
+ </span>
43
+ </Alert>
44
+ </Form>
45
+ </Fragment>
46
+ `;
47
+
48
+ exports[`CategoryAndTemplate rendering renders with props 1`] = `
49
+ <Fragment>
50
+ <Title
51
+ headingLevel="h2"
52
+ >
53
+ Category and Template
54
+ </Title>
55
+ <Text
56
+ component="p"
57
+ >
58
+ All fields are required.
59
+ </Text>
60
+ <Form>
61
+ <SelectField
62
+ fieldId="job_category"
63
+ isDisabled={false}
64
+ label="Job category"
65
+ options={
66
+ Array [
67
+ "Commands",
68
+ "Ansible Playbook",
69
+ "Ansible Galaxy",
70
+ "Ansible Roles Installation",
71
+ ]
72
+ }
73
+ placeholderText=""
74
+ setValue={[Function]}
75
+ value="I am a category"
76
+ />
77
+ <GroupedSelectField
78
+ fieldId="job_template"
79
+ groups={
80
+ Array [
81
+ Object {
82
+ "groupLabel": "SSH",
83
+ "options": Array [
84
+ Object {
85
+ "label": "ab Run Command - SSH Default clone",
86
+ "value": 190,
87
+ },
88
+ ],
89
+ },
90
+ Object {
91
+ "groupLabel": "Ansible",
92
+ "options": Array [
93
+ Object {
94
+ "label": "Ansible Roles - Ansible Default",
95
+ "value": 168,
96
+ },
97
+ Object {
98
+ "label": "Ansible Roles - Install from git",
99
+ "value": 170,
100
+ },
101
+ ],
102
+ },
103
+ ]
104
+ }
105
+ isDisabled={false}
106
+ label="Job template"
107
+ placeholderText=""
108
+ selected="ab Run Command - SSH Default clone"
109
+ setSelected={[MockFunction]}
110
+ />
111
+ </Form>
112
+ </Fragment>
113
+ `;