foreman_webhooks 5.1.0 → 6.0.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.
- checksums.yaml +4 -4
- data/db/seeds.d/62_shellhooks_proxy_feature.rb +1 -1
- data/lib/foreman_webhooks/engine.rb +1 -1
- data/lib/foreman_webhooks/version.rb +1 -1
- data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/WebhookFormTabs.js +4 -6
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookTestModal.js +1 -2
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/WebhooksIndexPage.fixtures.js +2 -1
- data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/WebhooksPageSelectors.test.js +121 -16
- metadata +2 -6
- data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/FieldConstructor.js +0 -312
- data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/__tests__/FieldConstructor.test.js +0 -216
- data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/__snapshots__/WebhooksPageSelectors.test.js.snap +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2504e406a10a1be7d18fe058224497d23d5888a67c73c3a37a79a3c60d14b91d
|
|
4
|
+
data.tar.gz: f558a5e1d7f670ba489dea28d371c7ddd71a604131ffe0b50206286899794e5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c31f081144dad2899b5b54f9eb6dd7fc04a8314ba684b82925856771d80449872c859e399015c5daaa542d0a743cbe44741d11251b8d1318f1fbed034140453
|
|
7
|
+
data.tar.gz: a18a91b96ddf51b0989c85fe74a9285c0e4a341253bcdc88ea152bbef3e8cce500dfc5c00f62d8b3fe9765f3e4e4464d59339b939df183d78d7ca418f355485f
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
proxy_feature = Feature.where(name: 'Shellhooks').first_or_create
|
|
4
4
|
return unless proxy_feature.nil? || proxy_feature.errors.any?
|
|
5
5
|
|
|
6
|
-
raise "Unable to create proxy feature: #{format_errors
|
|
6
|
+
raise "Unable to create proxy feature: #{SeedHelper.format_errors(proxy_feature)}"
|
|
@@ -14,7 +14,7 @@ module ForemanWebhooks
|
|
|
14
14
|
initializer 'foreman_webhooks.register_plugin', before: :finisher_hook do |app|
|
|
15
15
|
app.reloader.to_prepare do
|
|
16
16
|
Foreman::Plugin.register :foreman_webhooks do
|
|
17
|
-
requires_foreman '>=
|
|
17
|
+
requires_foreman '>= 5.0'
|
|
18
18
|
register_gettext
|
|
19
19
|
|
|
20
20
|
apipie_documented_controllers ["#{ForemanWebhooks::Engine.root}/app/controllers/api/v2/*.rb"]
|
data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/WebhookFormTabs.js
CHANGED
|
@@ -2,10 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { Tabs, Tab, TabTitleText } from '@patternfly/react-core';
|
|
4
4
|
|
|
5
|
+
import FieldConstructor from 'foremanReact/components/common/FieldConstructor/FieldConstructor';
|
|
5
6
|
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
|
6
7
|
|
|
7
8
|
import './WebhookFormTabs.css';
|
|
8
|
-
import FieldConstructor from './FieldConstructor';
|
|
9
9
|
|
|
10
10
|
const WebhookFormTabs = ({
|
|
11
11
|
inputValues,
|
|
@@ -48,7 +48,7 @@ const WebhookFormTabs = ({
|
|
|
48
48
|
type="select"
|
|
49
49
|
label={__('Subscribe to')}
|
|
50
50
|
required
|
|
51
|
-
allowClear
|
|
51
|
+
allowClear
|
|
52
52
|
options={availableEvents}
|
|
53
53
|
isLoading={isEventsLoading}
|
|
54
54
|
placeholder={__('Start typing to search...')}
|
|
@@ -93,7 +93,7 @@ const WebhookFormTabs = ({
|
|
|
93
93
|
type="select"
|
|
94
94
|
label={__('Template')}
|
|
95
95
|
required
|
|
96
|
-
allowClear
|
|
96
|
+
allowClear
|
|
97
97
|
options={webhookTemplates}
|
|
98
98
|
isLoading={isTemplatesLoading}
|
|
99
99
|
placeholder={__('Start typing to search...')}
|
|
@@ -105,7 +105,7 @@ const WebhookFormTabs = ({
|
|
|
105
105
|
type="select"
|
|
106
106
|
label={__('HTTP Method')}
|
|
107
107
|
required
|
|
108
|
-
allowClear
|
|
108
|
+
allowClear
|
|
109
109
|
options={httpMethods}
|
|
110
110
|
placeholder={__('Start typing to search...')}
|
|
111
111
|
/>
|
|
@@ -173,7 +173,6 @@ const WebhookFormTabs = ({
|
|
|
173
173
|
placeholder={__(
|
|
174
174
|
"Optional CAs in PEM format concatenated to verify the receiver's SSL certificate"
|
|
175
175
|
)}
|
|
176
|
-
inputSizeClass="col-md-8"
|
|
177
176
|
rows={8}
|
|
178
177
|
setValue={updateFieldValue}
|
|
179
178
|
/>
|
|
@@ -200,7 +199,6 @@ const WebhookFormTabs = ({
|
|
|
200
199
|
label={__('HTTP Headers')}
|
|
201
200
|
labelHelp={__('Optional. Must be a JSON object (ERB allowed)')}
|
|
202
201
|
placeholder='{ "X-Shellhook-Arg-1": "value", "X-Shellhook-Arg-2": "<%= @object.id %>" }'
|
|
203
|
-
inputSizeClass="col-md-8"
|
|
204
202
|
rows={8}
|
|
205
203
|
setValue={updateFieldValue}
|
|
206
204
|
/>
|
data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookTestModal.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ModalVariant,
|
|
13
13
|
} from '@patternfly/react-core';
|
|
14
14
|
|
|
15
|
+
import FieldConstructor from 'foremanReact/components/common/FieldConstructor/FieldConstructor';
|
|
15
16
|
import { APIActions } from 'foremanReact/redux/API';
|
|
16
17
|
import {
|
|
17
18
|
WEBHOOK_TEST_MODAL_ID,
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
} from '../../constants';
|
|
20
21
|
|
|
21
22
|
import './WebhookModal.scss';
|
|
22
|
-
import FieldConstructor from '../../Components/WebhookForm/Components/FieldConstructor';
|
|
23
23
|
|
|
24
24
|
const WebhookTestModal = ({ toTest, modalState }) => {
|
|
25
25
|
const dispatch = useDispatch();
|
|
@@ -80,7 +80,6 @@ const WebhookTestModal = ({ toTest, modalState }) => {
|
|
|
80
80
|
label={__('Payload')}
|
|
81
81
|
labelHelp={__('Will be sent as is')}
|
|
82
82
|
placeholder="{ id: 1, name: test }"
|
|
83
|
-
inputSizeClass="col-md-8"
|
|
84
83
|
rows={8}
|
|
85
84
|
value={value.payload}
|
|
86
85
|
setValue={(key, val) => {
|
|
@@ -38,9 +38,10 @@ export const queryParams = {
|
|
|
38
38
|
...querySort,
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
export const stateFactory = state => ({
|
|
41
|
+
export const stateFactory = (state = {}, { status } = {}) => ({
|
|
42
42
|
API: {
|
|
43
43
|
[WEBHOOKS_API_REQUEST_KEY]: {
|
|
44
|
+
...(status !== undefined && { status }),
|
|
44
45
|
response: {
|
|
45
46
|
...stateParams,
|
|
46
47
|
...state,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { STATUS } from 'foremanReact/constants';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
selectWebhooks,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
selectIsLoading,
|
|
12
12
|
selectSubtotal,
|
|
13
13
|
selectMessage,
|
|
14
|
+
selectCanCreate,
|
|
14
15
|
} from '../WebhooksPageSelectors';
|
|
15
16
|
|
|
16
17
|
import {
|
|
@@ -18,28 +19,132 @@ import {
|
|
|
18
19
|
webhooks,
|
|
19
20
|
} from '../WebhooksIndexPage/__tests__/WebhooksIndexPage.fixtures';
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
+
const populatedState = stateFactory({
|
|
22
23
|
results: webhooks,
|
|
23
24
|
sort: { by: 'name', order: 'DESC' },
|
|
24
25
|
page: 1,
|
|
25
26
|
perPage: 1,
|
|
26
27
|
search: 'name ~ foo',
|
|
27
28
|
subtotal: 42,
|
|
28
|
-
|
|
29
|
+
canCreate: true,
|
|
30
|
+
message: { type: 'info', text: 'Saved successfully' },
|
|
29
31
|
});
|
|
30
32
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
const resolvedState = stateFactory(
|
|
34
|
+
{ results: webhooks, subtotal: webhooks.length },
|
|
35
|
+
{ status: STATUS.RESOLVED }
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const pendingState = stateFactory({ results: [] }, { status: STATUS.PENDING });
|
|
39
|
+
|
|
40
|
+
const errorState = {
|
|
41
|
+
API: {
|
|
42
|
+
webhooks: {
|
|
43
|
+
status: STATUS.ERROR,
|
|
44
|
+
response: {
|
|
45
|
+
message: 'Request failed with status code 500',
|
|
46
|
+
response: {
|
|
47
|
+
data: { error: { message: 'Unable to load webhooks' } },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
42
52
|
};
|
|
43
53
|
|
|
44
|
-
describe('WebhooksPage selectors', () =>
|
|
45
|
-
|
|
54
|
+
describe('WebhooksPage selectors', () => {
|
|
55
|
+
it('returns webhooks from the API response', () => {
|
|
56
|
+
expect(selectWebhooks(populatedState)).toEqual(webhooks);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns page from the API response', () => {
|
|
60
|
+
expect(selectPage(populatedState)).toBe(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns perPage from the API response', () => {
|
|
64
|
+
expect(selectPerPage(populatedState)).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('returns search from the API response', () => {
|
|
68
|
+
expect(selectSearch(populatedState)).toBe('name ~ foo');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns sort with camelCased column name', () => {
|
|
72
|
+
expect(selectSort(populatedState)).toEqual({ by: 'name', order: 'DESC' });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('camelCases snake_case sort column names', () => {
|
|
76
|
+
const state = stateFactory({
|
|
77
|
+
sort: { by: 'target_url', order: 'ASC' },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(selectSort(state)).toEqual({ by: 'targetUrl', order: 'ASC' });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('returns hasData as false when the API request is not resolved', () => {
|
|
84
|
+
expect(selectHasData(populatedState)).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('returns hasData as true when the API request is resolved with results', () => {
|
|
88
|
+
expect(selectHasData(resolvedState)).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('returns hasError as false for a successful API response', () => {
|
|
92
|
+
expect(selectHasError(populatedState)).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns hasError as true when the API request failed', () => {
|
|
96
|
+
expect(selectHasError(errorState)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('returns isLoading as true when the API status is missing or pending', () => {
|
|
100
|
+
expect(selectIsLoading(populatedState)).toBe(true);
|
|
101
|
+
expect(selectIsLoading(pendingState)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns isLoading as false when the API request is resolved', () => {
|
|
105
|
+
expect(selectIsLoading(resolvedState)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('returns subtotal from the API response', () => {
|
|
109
|
+
expect(selectSubtotal(populatedState)).toBe(42);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('returns message from the API response when there is no error', () => {
|
|
113
|
+
expect(selectMessage(populatedState)).toEqual({
|
|
114
|
+
type: 'info',
|
|
115
|
+
text: 'Saved successfully',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('returns an error message from the API when the request failed', () => {
|
|
120
|
+
expect(selectMessage(errorState)).toEqual({
|
|
121
|
+
type: 'error',
|
|
122
|
+
text: 'Unable to load webhooks',
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('returns an empty webhook list when the API request failed', () => {
|
|
127
|
+
expect(selectWebhooks(errorState)).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('returns canCreate from the API response', () => {
|
|
131
|
+
expect(selectCanCreate(populatedState)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns default pagination values when the API response is empty', () => {
|
|
135
|
+
const emptyState = {
|
|
136
|
+
API: {
|
|
137
|
+
webhooks: {
|
|
138
|
+
status: STATUS.RESOLVED,
|
|
139
|
+
response: {},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
expect(selectPage(emptyState)).toBe(1);
|
|
145
|
+
expect(selectPerPage(emptyState)).toBe(20);
|
|
146
|
+
expect(selectWebhooks(emptyState)).toEqual([]);
|
|
147
|
+
expect(selectSubtotal(emptyState)).toBe(0);
|
|
148
|
+
expect(selectCanCreate(emptyState)).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
});
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_webhooks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Timo Goebel
|
|
@@ -138,10 +138,8 @@ files:
|
|
|
138
138
|
- test/test_plugin_helper.rb
|
|
139
139
|
- test/unit/foreman_webhooks/webhook_service_test.rb
|
|
140
140
|
- webpack/ForemanWebhooks/Routes/ForemanWebhooksRoutes.js
|
|
141
|
-
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/FieldConstructor.js
|
|
142
141
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/WebhookFormTabs.css
|
|
143
142
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/WebhookFormTabs.js
|
|
144
|
-
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/__tests__/FieldConstructor.test.js
|
|
145
143
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/WebhookForm.js
|
|
146
144
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/WebhookFormSelectors.js
|
|
147
145
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/__tests__/WebhookForm.test.js
|
|
@@ -170,7 +168,6 @@ files:
|
|
|
170
168
|
- webpack/ForemanWebhooks/Routes/Webhooks/WebhooksPageSelectors.js
|
|
171
169
|
- webpack/ForemanWebhooks/Routes/Webhooks/__tests__/WebhooksPageHelpers.test.js
|
|
172
170
|
- webpack/ForemanWebhooks/Routes/Webhooks/__tests__/WebhooksPageSelectors.test.js
|
|
173
|
-
- webpack/ForemanWebhooks/Routes/Webhooks/__tests__/__snapshots__/WebhooksPageSelectors.test.js.snap
|
|
174
171
|
- webpack/ForemanWebhooks/Routes/Webhooks/constants.js
|
|
175
172
|
- webpack/index.js
|
|
176
173
|
- webpack/routes_index.js
|
|
@@ -197,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
197
194
|
- !ruby/object:Gem::Version
|
|
198
195
|
version: '0'
|
|
199
196
|
requirements: []
|
|
200
|
-
rubygems_version: 4.0.
|
|
197
|
+
rubygems_version: 4.0.20
|
|
201
198
|
specification_version: 4
|
|
202
199
|
summary: Configure webhooks for Foreman.
|
|
203
200
|
test_files:
|
|
@@ -214,7 +211,6 @@ test_files:
|
|
|
214
211
|
- test/models/webhook_test.rb
|
|
215
212
|
- test/test_plugin_helper.rb
|
|
216
213
|
- test/unit/foreman_webhooks/webhook_service_test.rb
|
|
217
|
-
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/__tests__/FieldConstructor.test.js
|
|
218
214
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/__tests__/WebhookForm.test.js
|
|
219
215
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookTable/Components/Formatters/__tests__/actionCellFormatter.test.js
|
|
220
216
|
- webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookTable/Components/Formatters/__tests__/enabledCellFormatter.test.js
|
data/webpack/ForemanWebhooks/Routes/Webhooks/Components/WebhookForm/Components/FieldConstructor.js
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-lines */
|
|
2
|
-
import React, { useEffect, useState } from 'react';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
|
-
import AutocompleteInput from 'foremanReact/components/common/AutocompleteInput/AutocompleteInput';
|
|
5
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
|
6
|
-
import {
|
|
7
|
-
ExclamationCircleIcon,
|
|
8
|
-
HelpIcon,
|
|
9
|
-
PencilAltIcon,
|
|
10
|
-
} from '@patternfly/react-icons';
|
|
11
|
-
import {
|
|
12
|
-
TextInput,
|
|
13
|
-
Checkbox,
|
|
14
|
-
Button,
|
|
15
|
-
FormHelperText,
|
|
16
|
-
HelperText,
|
|
17
|
-
HelperTextItem,
|
|
18
|
-
Icon,
|
|
19
|
-
TextArea,
|
|
20
|
-
FormGroup,
|
|
21
|
-
Popover,
|
|
22
|
-
Grid,
|
|
23
|
-
GridItem,
|
|
24
|
-
} from '@patternfly/react-core';
|
|
25
|
-
|
|
26
|
-
const FormField = ({
|
|
27
|
-
name,
|
|
28
|
-
type,
|
|
29
|
-
required,
|
|
30
|
-
options,
|
|
31
|
-
validated,
|
|
32
|
-
value,
|
|
33
|
-
disabled,
|
|
34
|
-
setValue,
|
|
35
|
-
placeholder,
|
|
36
|
-
errMsg,
|
|
37
|
-
fieldId,
|
|
38
|
-
setIsPasswordDisabled,
|
|
39
|
-
...props
|
|
40
|
-
}) => {
|
|
41
|
-
const [fieldValidated, setFieldValidated] = useState('default');
|
|
42
|
-
const [firstLoad, setFirstLoad] = useState(true);
|
|
43
|
-
|
|
44
|
-
const requiredValidate = () => {
|
|
45
|
-
if (firstLoad || !required) return;
|
|
46
|
-
if (!value || value === '' || validated === 'error')
|
|
47
|
-
setFieldValidated('error');
|
|
48
|
-
else setFieldValidated('success');
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const localHandler = (_event, newValue) => {
|
|
52
|
-
setValue(name, newValue);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
setFirstLoad(false);
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
requiredValidate();
|
|
61
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
|
-
}, [value]);
|
|
63
|
-
|
|
64
|
-
if (type === 'checkbox') {
|
|
65
|
-
return (
|
|
66
|
-
<>
|
|
67
|
-
<Checkbox
|
|
68
|
-
ouiaId={name}
|
|
69
|
-
id={fieldId ?? `id-${name}`}
|
|
70
|
-
isChecked={value || false}
|
|
71
|
-
onChange={(_, newValue) => {
|
|
72
|
-
setValue(name, newValue);
|
|
73
|
-
}}
|
|
74
|
-
isDisabled={disabled}
|
|
75
|
-
isRequired={required}
|
|
76
|
-
type={type}
|
|
77
|
-
validated={fieldValidated}
|
|
78
|
-
/>
|
|
79
|
-
<ValidationComponent localValidated={fieldValidated} errMsg={errMsg} />
|
|
80
|
-
</>
|
|
81
|
-
);
|
|
82
|
-
} else if (type === 'textarea') {
|
|
83
|
-
return (
|
|
84
|
-
<>
|
|
85
|
-
<TextArea
|
|
86
|
-
rows={6}
|
|
87
|
-
name={name}
|
|
88
|
-
id={fieldId ?? `id-${name}`}
|
|
89
|
-
value={value ?? ''}
|
|
90
|
-
onChange={(_, newValue) => setValue(name, newValue)}
|
|
91
|
-
isRequired={required}
|
|
92
|
-
isDisabled={disabled}
|
|
93
|
-
type={type}
|
|
94
|
-
validated={fieldValidated}
|
|
95
|
-
placeholder={placeholder}
|
|
96
|
-
/>
|
|
97
|
-
<ValidationComponent localValidated={fieldValidated} errMsg={errMsg} />
|
|
98
|
-
</>
|
|
99
|
-
);
|
|
100
|
-
} else if (options.length !== 0) {
|
|
101
|
-
return (
|
|
102
|
-
<>
|
|
103
|
-
<AutocompleteInput
|
|
104
|
-
options={options}
|
|
105
|
-
selected={value ?? ''}
|
|
106
|
-
onSelect={newValue => setValue(name, newValue)}
|
|
107
|
-
onChange={() => requiredValidate()}
|
|
108
|
-
name={name}
|
|
109
|
-
placeholder={placeholder}
|
|
110
|
-
validationStatus={fieldValidated}
|
|
111
|
-
validationMsg={errMsg}
|
|
112
|
-
fieldId={fieldId}
|
|
113
|
-
/>
|
|
114
|
-
</>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
return (
|
|
118
|
-
<>
|
|
119
|
-
{name === 'password' && disabled && setIsPasswordDisabled ? (
|
|
120
|
-
<Grid hasGutter={false}>
|
|
121
|
-
<GridItem span={11}>
|
|
122
|
-
<TextInput
|
|
123
|
-
name={name}
|
|
124
|
-
value={value ?? ''}
|
|
125
|
-
id={fieldId ?? `id-${name}`}
|
|
126
|
-
onChange={localHandler}
|
|
127
|
-
isDisabled={disabled}
|
|
128
|
-
isRequired={required}
|
|
129
|
-
type={type}
|
|
130
|
-
validated={fieldValidated}
|
|
131
|
-
placeholder={placeholder}
|
|
132
|
-
onBlur={requiredValidate}
|
|
133
|
-
autoComplete="off"
|
|
134
|
-
/>
|
|
135
|
-
</GridItem>
|
|
136
|
-
<GridItem span={1}>
|
|
137
|
-
<Button
|
|
138
|
-
ouiaId={`reset-${name}`}
|
|
139
|
-
onClick={() => setIsPasswordDisabled(!disabled)}
|
|
140
|
-
variant="control"
|
|
141
|
-
icon={<PencilAltIcon />}
|
|
142
|
-
/>
|
|
143
|
-
</GridItem>
|
|
144
|
-
</Grid>
|
|
145
|
-
) : (
|
|
146
|
-
<TextInput
|
|
147
|
-
name={name}
|
|
148
|
-
value={value ?? ''}
|
|
149
|
-
id={fieldId ?? `id-${name}`}
|
|
150
|
-
onChange={localHandler}
|
|
151
|
-
isRequired={required}
|
|
152
|
-
type={type}
|
|
153
|
-
validated={fieldValidated}
|
|
154
|
-
onBlur={requiredValidate}
|
|
155
|
-
autoComplete={type === 'password' ? 'new-password' : null}
|
|
156
|
-
/>
|
|
157
|
-
)}
|
|
158
|
-
|
|
159
|
-
<ValidationComponent localValidated={fieldValidated} errMsg={errMsg} />
|
|
160
|
-
</>
|
|
161
|
-
);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const ValidationComponent = ({ localValidated, errMsg }) => (
|
|
165
|
-
<>
|
|
166
|
-
{localValidated === 'error' && (
|
|
167
|
-
<FormHelperText>
|
|
168
|
-
<HelperText>
|
|
169
|
-
<HelperTextItem
|
|
170
|
-
icon={
|
|
171
|
-
<Icon>
|
|
172
|
-
<ExclamationCircleIcon />
|
|
173
|
-
</Icon>
|
|
174
|
-
}
|
|
175
|
-
variant={localValidated}
|
|
176
|
-
>
|
|
177
|
-
{errMsg ?? __('Field is required')}
|
|
178
|
-
</HelperTextItem>
|
|
179
|
-
</HelperText>
|
|
180
|
-
</FormHelperText>
|
|
181
|
-
)}
|
|
182
|
-
</>
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
const FieldConstructor = ({
|
|
186
|
-
label,
|
|
187
|
-
value,
|
|
188
|
-
required,
|
|
189
|
-
labelHelp,
|
|
190
|
-
fieldId,
|
|
191
|
-
name,
|
|
192
|
-
...props
|
|
193
|
-
}) => (
|
|
194
|
-
<FormGroup
|
|
195
|
-
isInline
|
|
196
|
-
fieldId={fieldId ?? `id-${name}`}
|
|
197
|
-
label={label}
|
|
198
|
-
isRequired={required}
|
|
199
|
-
labelIcon={
|
|
200
|
-
labelHelp ? (
|
|
201
|
-
<Popover
|
|
202
|
-
bodyContent={
|
|
203
|
-
typeof labelHelp === 'string' ? (
|
|
204
|
-
<div dangerouslySetInnerHTML={{ __html: labelHelp }} />
|
|
205
|
-
) : (
|
|
206
|
-
labelHelp
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
>
|
|
210
|
-
<Button
|
|
211
|
-
ouiaId={`label-${label}`}
|
|
212
|
-
type="button"
|
|
213
|
-
variant="plain"
|
|
214
|
-
onClick={e => e.preventDefault()}
|
|
215
|
-
>
|
|
216
|
-
<Icon>
|
|
217
|
-
<HelpIcon />
|
|
218
|
-
</Icon>
|
|
219
|
-
</Button>
|
|
220
|
-
</Popover>
|
|
221
|
-
) : (
|
|
222
|
-
''
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
>
|
|
226
|
-
<FormField
|
|
227
|
-
value={value}
|
|
228
|
-
required={required}
|
|
229
|
-
name={name}
|
|
230
|
-
{...props}
|
|
231
|
-
fieldId={fieldId}
|
|
232
|
-
/>
|
|
233
|
-
</FormGroup>
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
FieldConstructor.propTypes = {
|
|
237
|
-
label: PropTypes.string.isRequired,
|
|
238
|
-
required: PropTypes.bool,
|
|
239
|
-
labelHelp: PropTypes.oneOfType([
|
|
240
|
-
PropTypes.string,
|
|
241
|
-
PropTypes.object,
|
|
242
|
-
PropTypes.node,
|
|
243
|
-
]),
|
|
244
|
-
setValue: PropTypes.func.isRequired,
|
|
245
|
-
name: PropTypes.string.isRequired,
|
|
246
|
-
disabled: PropTypes.bool,
|
|
247
|
-
value: PropTypes.oneOfType([
|
|
248
|
-
PropTypes.string,
|
|
249
|
-
PropTypes.object,
|
|
250
|
-
PropTypes.bool,
|
|
251
|
-
PropTypes.number,
|
|
252
|
-
]),
|
|
253
|
-
fieldId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
FieldConstructor.defaultProps = {
|
|
257
|
-
labelHelp: undefined,
|
|
258
|
-
required: false,
|
|
259
|
-
disabled: false,
|
|
260
|
-
value: '',
|
|
261
|
-
fieldId: undefined,
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
FormField.propTypes = {
|
|
265
|
-
disabled: PropTypes.bool,
|
|
266
|
-
setValue: PropTypes.func.isRequired,
|
|
267
|
-
name: PropTypes.string.isRequired,
|
|
268
|
-
type: PropTypes.string.isRequired,
|
|
269
|
-
label: PropTypes.string,
|
|
270
|
-
required: PropTypes.bool,
|
|
271
|
-
allowClear: PropTypes.bool,
|
|
272
|
-
options: PropTypes.arrayOf(
|
|
273
|
-
PropTypes.shape({
|
|
274
|
-
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
275
|
-
.isRequired,
|
|
276
|
-
label: PropTypes.string.isRequired,
|
|
277
|
-
})
|
|
278
|
-
),
|
|
279
|
-
validated: PropTypes.string,
|
|
280
|
-
placeholder: PropTypes.string,
|
|
281
|
-
errMsg: PropTypes.string,
|
|
282
|
-
value: PropTypes.oneOfType([
|
|
283
|
-
PropTypes.bool,
|
|
284
|
-
PropTypes.string,
|
|
285
|
-
PropTypes.number,
|
|
286
|
-
]),
|
|
287
|
-
fieldId: PropTypes.string,
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
FormField.defaultProps = {
|
|
291
|
-
label: '',
|
|
292
|
-
errMsg: null,
|
|
293
|
-
placeholder: '',
|
|
294
|
-
validated: 'default',
|
|
295
|
-
required: false,
|
|
296
|
-
allowClear: false,
|
|
297
|
-
options: [],
|
|
298
|
-
disabled: false,
|
|
299
|
-
value: '',
|
|
300
|
-
fieldId: undefined,
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
ValidationComponent.propTypes = {
|
|
304
|
-
localValidated: PropTypes.string.isRequired,
|
|
305
|
-
errMsg: PropTypes.string,
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
ValidationComponent.defaultProps = {
|
|
309
|
-
errMsg: null,
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
export default FieldConstructor;
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
-
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import '@testing-library/jest-dom';
|
|
5
|
-
import FieldConstructor from '../FieldConstructor';
|
|
6
|
-
|
|
7
|
-
const defaultProps = {
|
|
8
|
-
name: 'test-field',
|
|
9
|
-
setValue: jest.fn(),
|
|
10
|
-
label: 'Test Field',
|
|
11
|
-
value: '',
|
|
12
|
-
type: 'text',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
describe('FieldConstructor RTL Tests', () => {
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
jest.clearAllMocks();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('Text Input Fields', () => {
|
|
21
|
-
test('renders text input field', () => {
|
|
22
|
-
render(<FieldConstructor {...defaultProps} type="text" />);
|
|
23
|
-
|
|
24
|
-
expect(screen.getByText('Test Field')).toBeInTheDocument();
|
|
25
|
-
expect(document.getElementById('id-test-field')).toHaveAttribute(
|
|
26
|
-
'type',
|
|
27
|
-
'text'
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('renders password input field', () => {
|
|
32
|
-
render(<FieldConstructor {...defaultProps} type="password" />);
|
|
33
|
-
|
|
34
|
-
expect(screen.getByText('Test Field')).toBeInTheDocument();
|
|
35
|
-
expect(document.getElementById('id-test-field')).toHaveAttribute(
|
|
36
|
-
'type',
|
|
37
|
-
'password'
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('calls setValue when text input changes', () => {
|
|
42
|
-
render(<FieldConstructor {...defaultProps} type="text" />);
|
|
43
|
-
|
|
44
|
-
const input = document.getElementById('id-test-field');
|
|
45
|
-
fireEvent.change(input, { target: { value: 'test value' } });
|
|
46
|
-
|
|
47
|
-
expect(defaultProps.setValue).toHaveBeenCalledWith(
|
|
48
|
-
'test-field',
|
|
49
|
-
'test value'
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('renders with initial value', () => {
|
|
54
|
-
render(
|
|
55
|
-
<FieldConstructor {...defaultProps} type="text" value="initial value" />
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
expect(document.getElementById('id-test-field')).toHaveValue(
|
|
59
|
-
'initial value'
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('renders as enabled when loading', () => {
|
|
64
|
-
render(<FieldConstructor {...defaultProps} type="text" isLoading />);
|
|
65
|
-
|
|
66
|
-
expect(document.getElementById('id-test-field')).toBeEnabled();
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('Textarea Fields', () => {
|
|
71
|
-
test('renders textarea field', () => {
|
|
72
|
-
render(<FieldConstructor {...defaultProps} type="textarea" />);
|
|
73
|
-
|
|
74
|
-
expect(screen.getByText('Test Field')).toBeInTheDocument();
|
|
75
|
-
expect(document.getElementById('id-test-field').tagName).toBe('TEXTAREA');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('calls setValue when textarea changes', () => {
|
|
79
|
-
render(<FieldConstructor {...defaultProps} type="textarea" />);
|
|
80
|
-
|
|
81
|
-
const textarea = document.getElementById('id-test-field');
|
|
82
|
-
fireEvent.change(textarea, { target: { value: 'textarea content' } });
|
|
83
|
-
|
|
84
|
-
expect(defaultProps.setValue).toHaveBeenCalledWith(
|
|
85
|
-
'test-field',
|
|
86
|
-
'textarea content'
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('renders with specified rows', () => {
|
|
91
|
-
render(<FieldConstructor {...defaultProps} type="textarea" rows={5} />);
|
|
92
|
-
|
|
93
|
-
expect(document.getElementsByTagName('textarea')).toHaveLength(1);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('Checkbox Fields', () => {
|
|
98
|
-
test('renders checkbox field', () => {
|
|
99
|
-
render(<FieldConstructor {...defaultProps} type="checkbox" />);
|
|
100
|
-
|
|
101
|
-
expect(screen.getByText('Test Field')).toBeInTheDocument();
|
|
102
|
-
expect(document.getElementById('id-test-field')).toHaveAttribute(
|
|
103
|
-
'type',
|
|
104
|
-
'checkbox'
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('renders unchecked checkbox', () => {
|
|
109
|
-
render(
|
|
110
|
-
<FieldConstructor {...defaultProps} type="checkbox" value={false} />
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(document.getElementById('id-test-field')).not.toBeChecked();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('Autocomplete render test', () => {
|
|
118
|
-
test('renders autocomplete', () => {
|
|
119
|
-
const props = {
|
|
120
|
-
...defaultProps,
|
|
121
|
-
onChange: defaultProps.setValue,
|
|
122
|
-
options: [
|
|
123
|
-
{ value: 'host_created.event.foreman', label: 'Host Created' },
|
|
124
|
-
{ value: 'host_updated.event.foreman', label: 'Host Updated' },
|
|
125
|
-
],
|
|
126
|
-
};
|
|
127
|
-
render(<FieldConstructor {...props} type="select" />);
|
|
128
|
-
|
|
129
|
-
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('Label Help', () => {
|
|
134
|
-
test('renders help icon when labelHelp is provided', () => {
|
|
135
|
-
render(
|
|
136
|
-
<FieldConstructor {...defaultProps} labelHelp="This is help text" />
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('does not render help icon when labelHelp is not provided', () => {
|
|
143
|
-
render(<FieldConstructor {...defaultProps} />);
|
|
144
|
-
|
|
145
|
-
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('renders help icon with JSX content', async () => {
|
|
149
|
-
const helpContent = <div>JSX help content</div>;
|
|
150
|
-
render(<FieldConstructor {...defaultProps} labelHelp={helpContent} />);
|
|
151
|
-
|
|
152
|
-
const button = screen.getByRole('button');
|
|
153
|
-
expect(button).toBeInTheDocument();
|
|
154
|
-
|
|
155
|
-
userEvent.click(button);
|
|
156
|
-
|
|
157
|
-
expect(await screen.findByText('JSX help content')).toBeInTheDocument();
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('Validation', () => {
|
|
162
|
-
test('renders error message when validated is error', async () => {
|
|
163
|
-
render(
|
|
164
|
-
<>
|
|
165
|
-
<FieldConstructor {...defaultProps} required type="text" />
|
|
166
|
-
<button>Dummy</button>
|
|
167
|
-
</>
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
const input = document.getElementById('id-test-field');
|
|
171
|
-
await userEvent.click(input);
|
|
172
|
-
expect(input).toHaveFocus();
|
|
173
|
-
|
|
174
|
-
await userEvent.tab();
|
|
175
|
-
expect(input).not.toHaveFocus();
|
|
176
|
-
|
|
177
|
-
expect(screen.getByText('Field is required')).toBeInTheDocument();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('does not render error message when validated is not error', () => {
|
|
181
|
-
render(
|
|
182
|
-
<FieldConstructor {...defaultProps} type="text" validated="success" />
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
expect(screen.queryByText('Field is required')).not.toBeInTheDocument();
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('Edge Cases', () => {
|
|
190
|
-
test('handles undefined value', () => {
|
|
191
|
-
render(
|
|
192
|
-
<FieldConstructor {...defaultProps} type="text" value={undefined} />
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
expect(document.getElementById('id-test-field')).toHaveValue('');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('handles null value', () => {
|
|
199
|
-
render(<FieldConstructor {...defaultProps} type="text" value={null} />);
|
|
200
|
-
|
|
201
|
-
expect(document.getElementById('id-test-field')).toHaveValue('');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test('handles numeric value', () => {
|
|
205
|
-
render(<FieldConstructor {...defaultProps} type="text" value={123} />);
|
|
206
|
-
|
|
207
|
-
expect(document.getElementById('id-test-field')).toHaveValue('123');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test('handles boolean value for checkbox', () => {
|
|
211
|
-
render(<FieldConstructor {...defaultProps} type="checkbox" value />);
|
|
212
|
-
|
|
213
|
-
expect(document.getElementById('id-test-field')).toBeChecked();
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`WebhooksPage selectors should return hasData 1`] = `false`;
|
|
4
|
-
|
|
5
|
-
exports[`WebhooksPage selectors should return hasError 1`] = `false`;
|
|
6
|
-
|
|
7
|
-
exports[`WebhooksPage selectors should return isLoading 1`] = `true`;
|
|
8
|
-
|
|
9
|
-
exports[`WebhooksPage selectors should return message 1`] = `
|
|
10
|
-
Object {
|
|
11
|
-
"text": "This is error",
|
|
12
|
-
"type": "error",
|
|
13
|
-
}
|
|
14
|
-
`;
|
|
15
|
-
|
|
16
|
-
exports[`WebhooksPage selectors should return page 1`] = `1`;
|
|
17
|
-
|
|
18
|
-
exports[`WebhooksPage selectors should return perPage 1`] = `1`;
|
|
19
|
-
|
|
20
|
-
exports[`WebhooksPage selectors should return search 1`] = `"name ~ foo"`;
|
|
21
|
-
|
|
22
|
-
exports[`WebhooksPage selectors should return sort 1`] = `
|
|
23
|
-
Object {
|
|
24
|
-
"by": "name",
|
|
25
|
-
"order": "DESC",
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
exports[`WebhooksPage selectors should return subtotal 1`] = `42`;
|
|
30
|
-
|
|
31
|
-
exports[`WebhooksPage selectors should return webhooks 1`] = `
|
|
32
|
-
Array [
|
|
33
|
-
Object {
|
|
34
|
-
"canDelete": true,
|
|
35
|
-
"canEdit": true,
|
|
36
|
-
"enabled": true,
|
|
37
|
-
"id": 1,
|
|
38
|
-
"name": "my-webhook",
|
|
39
|
-
"targetUrl": "https://my-machine.example.com",
|
|
40
|
-
},
|
|
41
|
-
Object {
|
|
42
|
-
"canDelete": false,
|
|
43
|
-
"canEdit": false,
|
|
44
|
-
"enabled": false,
|
|
45
|
-
"id": 2,
|
|
46
|
-
"name": "your-webhook",
|
|
47
|
-
"targetUrl": "https://your-machine.example.com",
|
|
48
|
-
},
|
|
49
|
-
]
|
|
50
|
-
`;
|