foreman_remote_execution 4.5.0 → 4.5.4
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/controllers/job_invocations_controller.rb +1 -1
- data/app/controllers/ui_job_wizard_controller.rb +7 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
- data/app/helpers/remote_execution_helper.rb +9 -3
- data/app/lib/actions/remote_execution/run_host_job.rb +5 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +2 -0
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
- data/app/models/host_proxy_invocation.rb +4 -0
- data/app/models/host_status/execution_status.rb +3 -3
- data/app/models/job_invocation.rb +9 -6
- data/app/models/job_invocation_composer.rb +4 -4
- data/app/models/remote_execution_feature.rb +5 -1
- data/app/models/remote_execution_provider.rb +1 -1
- data/app/models/setting/remote_execution.rb +2 -2
- data/app/models/targeting.rb +5 -1
- data/app/views/job_invocations/index.html.erb +1 -1
- data/app/views/templates/ssh/module_action.erb +1 -0
- data/app/views/templates/ssh/power_action.erb +2 -0
- data/app/views/templates/ssh/puppet_run_once.erb +1 -0
- data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
- data/lib/foreman_remote_execution/engine.rb +0 -2
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/unit/job_invocation_composer_test.rb +14 -1
- data/test/unit/job_invocation_test.rb +1 -1
- data/webpack/JobWizard/JobWizard.js +28 -8
- data/webpack/JobWizard/JobWizard.scss +39 -0
- data/webpack/JobWizard/JobWizardConstants.js +10 -0
- data/webpack/JobWizard/JobWizardSelectors.js +9 -0
- data/webpack/JobWizard/__tests__/fixtures.js +104 -2
- data/webpack/JobWizard/__tests__/integration.test.js +13 -85
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
- data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
- data/webpack/JobWizard/steps/Schedule/index.js +41 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
- data/webpack/JobWizard/steps/form/Formatter.js +149 -0
- data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
- data/webpack/JobWizard/steps/form/SelectField.js +14 -2
- data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
- metadata +15 -13
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
- data/test/models/orchestration/ssh_test.rb +0 -56
- data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
@@ -10,5 +10,44 @@
|
|
10
10
|
.advanced-fields-title {
|
11
11
|
margin-bottom: 10px;
|
12
12
|
}
|
13
|
+
#advanced-fields-job-template {
|
14
|
+
.foreman-search-field {
|
15
|
+
// Giving pf3 search bar a pf4 look
|
16
|
+
.search-bar {
|
17
|
+
display: block;
|
18
|
+
}
|
19
|
+
.input-group-btn {
|
20
|
+
display: none;
|
21
|
+
}
|
22
|
+
li {
|
23
|
+
font-size: 16px;
|
24
|
+
}
|
25
|
+
input {
|
26
|
+
font-size: 16px;
|
27
|
+
height: 36px;
|
28
|
+
}
|
29
|
+
.foreman-autocomplete .autocomplete-focus-shortcut {
|
30
|
+
top: 8px;
|
31
|
+
font-size: 16px;
|
32
|
+
}
|
33
|
+
.foreman-autocomplete .autocomplete-aux {
|
34
|
+
top: 8px;
|
35
|
+
font-size: 16px;
|
36
|
+
.autocomplete-clear-button {
|
37
|
+
font-size: 16px;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
.schedule-tab {
|
45
|
+
input[type='radio'],
|
46
|
+
input[type='checkbox'] {
|
47
|
+
margin: 0;
|
48
|
+
}
|
49
|
+
.advanced-scheduling-button {
|
50
|
+
text-align: start;
|
51
|
+
}
|
13
52
|
}
|
14
53
|
}
|
@@ -1,6 +1,16 @@
|
|
1
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
1
2
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
2
3
|
|
3
4
|
export const JOB_TEMPLATES = 'JOB_TEMPLATES';
|
4
5
|
export const JOB_CATEGORIES = 'JOB_CATEGORIES';
|
5
6
|
export const JOB_TEMPLATE = 'JOB_TEMPLATE';
|
6
7
|
export const templatesUrl = foremanUrl('/api/v2/job_templates');
|
8
|
+
|
9
|
+
export const repeatTypes = {
|
10
|
+
noRepeat: __('Does not repeat'),
|
11
|
+
cronline: __('Cronline'),
|
12
|
+
monthly: __('Monthly'),
|
13
|
+
weekly: __('Weekly'),
|
14
|
+
daily: __('Daily'),
|
15
|
+
hourly: __('Hourly'),
|
16
|
+
};
|
@@ -36,3 +36,12 @@ export const selectTemplateError = state =>
|
|
36
36
|
|
37
37
|
export const selectJobTemplate = state =>
|
38
38
|
selectAPIResponse(state, JOB_TEMPLATE);
|
39
|
+
|
40
|
+
export const selectEffectiveUser = state =>
|
41
|
+
selectAPIResponse(state, JOB_TEMPLATE).effective_user;
|
42
|
+
|
43
|
+
export const selectAdvancedTemplateInputs = state =>
|
44
|
+
selectAPIResponse(state, JOB_TEMPLATE).advanced_template_inputs || [];
|
45
|
+
|
46
|
+
export const selectTemplateInputs = state =>
|
47
|
+
selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
|
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
import configureMockStore from 'redux-mock-store';
|
2
|
+
|
3
|
+
export const jobTemplate = {
|
2
4
|
id: 178,
|
3
|
-
name: '
|
5
|
+
name: 'template1',
|
4
6
|
template:
|
5
7
|
"---\n- hosts: all\n tasks:\n - shell:\n cmd: |\n<%= indent(10) { input('command') } %>\n register: out\n - debug: var=out",
|
6
8
|
snippet: false,
|
@@ -23,4 +25,104 @@ export const jobTemplateResponse = {
|
|
23
25
|
overridable: true,
|
24
26
|
current_user: false,
|
25
27
|
},
|
28
|
+
advanced_template_inputs: [
|
29
|
+
{
|
30
|
+
name: 'adv plain hidden',
|
31
|
+
required: true,
|
32
|
+
input_type: 'user',
|
33
|
+
description: 'some Description',
|
34
|
+
advanced: true,
|
35
|
+
value_type: 'plain',
|
36
|
+
resource_type: 'ansible_roles',
|
37
|
+
default: 'Default val',
|
38
|
+
hidden_value: true,
|
39
|
+
},
|
40
|
+
{
|
41
|
+
name: 'adv plain select',
|
42
|
+
required: false,
|
43
|
+
input_type: 'user',
|
44
|
+
options: 'option 1\r\noption 2\r\noption 3\r\noption 4',
|
45
|
+
advanced: true,
|
46
|
+
value_type: 'plain',
|
47
|
+
resource_type: 'ansible_roles',
|
48
|
+
default: '',
|
49
|
+
hidden_value: false,
|
50
|
+
},
|
51
|
+
{
|
52
|
+
name: 'adv search',
|
53
|
+
required: false,
|
54
|
+
options: '',
|
55
|
+
advanced: true,
|
56
|
+
value_type: 'search',
|
57
|
+
resource_type: 'foreman_tasks/tasks',
|
58
|
+
default: '',
|
59
|
+
hidden_value: false,
|
60
|
+
},
|
61
|
+
{
|
62
|
+
name: 'adv date',
|
63
|
+
required: false,
|
64
|
+
options: '',
|
65
|
+
advanced: true,
|
66
|
+
value_type: 'date',
|
67
|
+
resource_type: 'ansible_roles',
|
68
|
+
default: '',
|
69
|
+
hidden_value: false,
|
70
|
+
},
|
71
|
+
],
|
72
|
+
template_inputs: [
|
73
|
+
{
|
74
|
+
name: 'plain hidden',
|
75
|
+
required: true,
|
76
|
+
input_type: 'user',
|
77
|
+
description: 'some Description',
|
78
|
+
advanced: false,
|
79
|
+
value_type: 'plain',
|
80
|
+
resource_type: 'ansible_roles',
|
81
|
+
default: 'Default val',
|
82
|
+
hidden_value: true,
|
83
|
+
},
|
84
|
+
],
|
85
|
+
};
|
86
|
+
|
87
|
+
export const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
|
88
|
+
|
89
|
+
export const testSetup = (selectors, api) => {
|
90
|
+
jest.spyOn(api, 'get');
|
91
|
+
jest.spyOn(selectors, 'selectJobTemplate');
|
92
|
+
jest.spyOn(selectors, 'selectJobTemplates');
|
93
|
+
jest.spyOn(selectors, 'selectJobCategories');
|
94
|
+
jest.spyOn(selectors, 'selectJobCategoriesStatus');
|
95
|
+
|
96
|
+
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
97
|
+
selectors.selectJobTemplates.mockImplementation(() => [
|
98
|
+
jobTemplate,
|
99
|
+
{ ...jobTemplate, id: 2, name: 'template2' },
|
100
|
+
]);
|
101
|
+
const mockStore = configureMockStore([]);
|
102
|
+
const store = mockStore({});
|
103
|
+
return store;
|
104
|
+
};
|
105
|
+
|
106
|
+
export const mockTemplate = selectors => {
|
107
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
108
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
109
|
+
};
|
110
|
+
export const mockApi = api => {
|
111
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
112
|
+
if (action.key === 'JOB_CATEGORIES') {
|
113
|
+
handleSuccess &&
|
114
|
+
handleSuccess({ data: { job_categories: jobCategories } });
|
115
|
+
} else if (action.key === 'JOB_TEMPLATE') {
|
116
|
+
handleSuccess &&
|
117
|
+
handleSuccess({
|
118
|
+
data: jobTemplateResponse,
|
119
|
+
});
|
120
|
+
} else if (action.key === 'JOB_TEMPLATES') {
|
121
|
+
handleSuccess &&
|
122
|
+
handleSuccess({
|
123
|
+
data: { results: [jobTemplate] },
|
124
|
+
});
|
125
|
+
}
|
126
|
+
return { type: 'get', ...action };
|
127
|
+
});
|
26
128
|
};
|
@@ -1,40 +1,27 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { Provider } from 'react-redux';
|
3
|
-
import configureMockStore from 'redux-mock-store';
|
4
3
|
import { mount } from '@theforeman/test';
|
5
4
|
import { render, fireEvent, screen, act } from '@testing-library/react';
|
6
5
|
import * as api from 'foremanReact/redux/API';
|
7
6
|
import { JobWizard } from '../JobWizard';
|
8
7
|
import * as selectors from '../JobWizardSelectors';
|
9
|
-
import {
|
8
|
+
import {
|
9
|
+
testSetup,
|
10
|
+
mockApi,
|
11
|
+
jobCategories,
|
12
|
+
jobTemplateResponse as jobTemplate,
|
13
|
+
} from './fixtures';
|
10
14
|
|
11
|
-
|
12
|
-
jest.spyOn(selectors, 'selectJobTemplate');
|
13
|
-
jest.spyOn(selectors, 'selectJobTemplates');
|
14
|
-
jest.spyOn(selectors, 'selectJobCategories');
|
15
|
-
jest.spyOn(selectors, 'selectJobCategoriesStatus');
|
15
|
+
const store = testSetup(selectors, api);
|
16
16
|
|
17
|
-
|
17
|
+
selectors.selectJobTemplate.mockImplementation(() => {});
|
18
18
|
|
19
19
|
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
20
20
|
if (action.key === 'JOB_CATEGORIES') {
|
21
21
|
handleSuccess && handleSuccess({ data: { job_categories: jobCategories } });
|
22
|
-
} else if (action.key === 'JOB_TEMPLATE') {
|
23
|
-
handleSuccess &&
|
24
|
-
handleSuccess({
|
25
|
-
data: jobTemplate,
|
26
|
-
});
|
27
22
|
}
|
28
23
|
return { type: 'get', ...action };
|
29
24
|
});
|
30
|
-
|
31
|
-
selectors.selectJobTemplate.mockImplementation(() => null);
|
32
|
-
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
33
|
-
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
34
|
-
selectors.selectJobTemplates.mockImplementation(() => jobTemplates);
|
35
|
-
|
36
|
-
const mockStore = configureMockStore([]);
|
37
|
-
const store = mockStore({});
|
38
25
|
describe('Job wizard fill', () => {
|
39
26
|
it('should select template', async () => {
|
40
27
|
const wrapper = mount(
|
@@ -51,7 +38,10 @@ describe('Job wizard fill', () => {
|
|
51
38
|
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
52
39
|
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
53
40
|
await act(async () => {
|
54
|
-
await wrapper
|
41
|
+
await wrapper
|
42
|
+
.find('.pf-c-select__menu-item')
|
43
|
+
.first()
|
44
|
+
.simulate('click');
|
55
45
|
await wrapper.update();
|
56
46
|
});
|
57
47
|
expect(store.getActions().slice(-1)).toMatchSnapshot('select template');
|
@@ -60,74 +50,12 @@ describe('Job wizard fill', () => {
|
|
60
50
|
);
|
61
51
|
});
|
62
52
|
|
63
|
-
it('should save data between steps for advanced fields', async () => {
|
64
|
-
const wrapper = mount(
|
65
|
-
<Provider store={store}>
|
66
|
-
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
67
|
-
</Provider>
|
68
|
-
);
|
69
|
-
// setup
|
70
|
-
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
71
|
-
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
72
|
-
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
73
|
-
wrapper.find('.pf-c-select__menu-item').simulate('click');
|
74
|
-
|
75
|
-
// test
|
76
|
-
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
77
|
-
0
|
78
|
-
);
|
79
|
-
wrapper
|
80
|
-
.find('.pf-c-wizard__nav-link')
|
81
|
-
.at(2)
|
82
|
-
.simulate('click'); // Advanced step
|
83
|
-
const effectiveUserInput = () => wrapper.find('input#effective-user');
|
84
|
-
const effectiveUesrValue = 'effective user new value';
|
85
|
-
effectiveUserInput().getDOMNode().value = effectiveUesrValue;
|
86
|
-
await act(async () => {
|
87
|
-
await effectiveUserInput().simulate('change');
|
88
|
-
wrapper.update();
|
89
|
-
});
|
90
|
-
|
91
|
-
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
92
|
-
|
93
|
-
wrapper
|
94
|
-
.find('.pf-c-wizard__nav-link')
|
95
|
-
.at(1)
|
96
|
-
.simulate('click');
|
97
|
-
|
98
|
-
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-current').text()).toEqual(
|
99
|
-
'Target Hosts'
|
100
|
-
);
|
101
|
-
wrapper
|
102
|
-
.find('.pf-c-wizard__nav-link')
|
103
|
-
.at(2)
|
104
|
-
.simulate('click'); // Advanced step
|
105
|
-
|
106
|
-
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
107
|
-
});
|
108
|
-
|
109
53
|
it('have all steps', async () => {
|
110
54
|
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
111
55
|
selectors.selectJobTemplate.mockRestore();
|
112
56
|
selectors.selectJobTemplates.mockRestore();
|
113
57
|
selectors.selectJobCategories.mockRestore();
|
114
|
-
api
|
115
|
-
if (action.key === 'JOB_CATEGORIES') {
|
116
|
-
handleSuccess &&
|
117
|
-
handleSuccess({ data: { job_categories: jobCategories } });
|
118
|
-
} else if (action.key === 'JOB_TEMPLATE') {
|
119
|
-
handleSuccess &&
|
120
|
-
handleSuccess({
|
121
|
-
data: jobTemplate,
|
122
|
-
});
|
123
|
-
} else if (action.key === 'JOB_TEMPLATES') {
|
124
|
-
handleSuccess &&
|
125
|
-
handleSuccess({
|
126
|
-
data: { results: [jobTemplate.job_template] },
|
127
|
-
});
|
128
|
-
}
|
129
|
-
return { type: 'get', ...action };
|
130
|
-
});
|
58
|
+
mockApi(api);
|
131
59
|
|
132
60
|
render(
|
133
61
|
<Provider store={store}>
|
@@ -3,7 +3,11 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import { useSelector } from 'react-redux';
|
4
4
|
import { Title, Form } from '@patternfly/react-core';
|
5
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
6
|
-
import {
|
6
|
+
import {
|
7
|
+
selectEffectiveUser,
|
8
|
+
selectAdvancedTemplateInputs,
|
9
|
+
selectTemplateInputs,
|
10
|
+
} from '../../JobWizardSelectors';
|
7
11
|
import {
|
8
12
|
EffectiveUserField,
|
9
13
|
TimeoutToKillField,
|
@@ -12,17 +16,25 @@ import {
|
|
12
16
|
EffectiveUserPasswordField,
|
13
17
|
ConcurrencyLevelField,
|
14
18
|
TimeSpanLevelField,
|
19
|
+
TemplateInputsFields,
|
15
20
|
} from './Fields';
|
21
|
+
import { DescriptionField } from './DescriptionField';
|
16
22
|
|
17
23
|
export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => {
|
18
|
-
const
|
19
|
-
const
|
24
|
+
const effectiveUser = useSelector(selectEffectiveUser);
|
25
|
+
const advancedTemplateInputs = useSelector(selectAdvancedTemplateInputs);
|
26
|
+
const templateInputs = useSelector(selectTemplateInputs);
|
20
27
|
return (
|
21
28
|
<>
|
22
29
|
<Title headingLevel="h2" className="advanced-fields-title">
|
23
30
|
{__('Advanced Fields')}
|
24
31
|
</Title>
|
25
|
-
<Form>
|
32
|
+
<Form id="advanced-fields-job-template" autoComplete="off">
|
33
|
+
<TemplateInputsFields
|
34
|
+
inputs={advancedTemplateInputs}
|
35
|
+
value={advancedValues.templateValues}
|
36
|
+
setValue={newValue => setAdvancedValues({ templateValues: newValue })}
|
37
|
+
/>
|
26
38
|
{effectiveUser?.overridable && (
|
27
39
|
<EffectiveUserField
|
28
40
|
value={advancedValues.effectiveUserValue}
|
@@ -33,6 +45,11 @@ export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => {
|
|
33
45
|
}
|
34
46
|
/>
|
35
47
|
)}
|
48
|
+
<DescriptionField
|
49
|
+
inputs={templateInputs}
|
50
|
+
value={advancedValues.description}
|
51
|
+
setValue={newValue => setAdvancedValues({ description: newValue })}
|
52
|
+
/>
|
36
53
|
<TimeoutToKillField
|
37
54
|
value={advancedValues.timeoutToKill}
|
38
55
|
setValue={newValue =>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { FormGroup, TextInput, Button } from '@patternfly/react-core';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
|
6
|
+
export const DescriptionField = ({ inputs, value, setValue }) => {
|
7
|
+
const generateDesc = () => {
|
8
|
+
let newDesc = value;
|
9
|
+
if (value) {
|
10
|
+
const re = new RegExp('%\\{([^\\}]+)\\}', 'gm');
|
11
|
+
const results = [...newDesc.matchAll(re)].map(result => ({
|
12
|
+
name: result[1],
|
13
|
+
text: result[0],
|
14
|
+
}));
|
15
|
+
results.forEach(result => {
|
16
|
+
newDesc = newDesc.replace(
|
17
|
+
result.text,
|
18
|
+
// TODO: Replace with the value of the input from Target Hosts step
|
19
|
+
inputs.find(input => input.name === result.name)?.name || result.text
|
20
|
+
);
|
21
|
+
});
|
22
|
+
}
|
23
|
+
return newDesc;
|
24
|
+
};
|
25
|
+
const [generatedDesc, setGeneratedDesc] = useState(generateDesc());
|
26
|
+
const [isPreview, setIsPreview] = useState(true);
|
27
|
+
|
28
|
+
const togglePreview = () => {
|
29
|
+
setGeneratedDesc(generateDesc());
|
30
|
+
setIsPreview(v => !v);
|
31
|
+
};
|
32
|
+
|
33
|
+
return (
|
34
|
+
<FormGroup
|
35
|
+
label={__('Description')}
|
36
|
+
fieldId="description"
|
37
|
+
helperText={
|
38
|
+
<Button variant="link" isInline onClick={togglePreview}>
|
39
|
+
{isPreview
|
40
|
+
? __('Edit job description template')
|
41
|
+
: __('Preview job description')}
|
42
|
+
</Button>
|
43
|
+
}
|
44
|
+
>
|
45
|
+
{isPreview ? (
|
46
|
+
<TextInput id="description-preview" value={generatedDesc} isDisabled />
|
47
|
+
) : (
|
48
|
+
<TextInput
|
49
|
+
type="text"
|
50
|
+
autoComplete="description"
|
51
|
+
id="description"
|
52
|
+
value={value}
|
53
|
+
onChange={newValue => setValue(newValue)}
|
54
|
+
/>
|
55
|
+
)}
|
56
|
+
</FormGroup>
|
57
|
+
);
|
58
|
+
};
|
59
|
+
|
60
|
+
DescriptionField.propTypes = {
|
61
|
+
inputs: PropTypes.array.isRequired,
|
62
|
+
value: PropTypes.string,
|
63
|
+
setValue: PropTypes.func.isRequired,
|
64
|
+
};
|
65
|
+
DescriptionField.defaultProps = {
|
66
|
+
value: '',
|
67
|
+
};
|