updates several dependencies

This commit is contained in:
Alex Corey
2021-07-20 12:06:45 -04:00
parent e77d297a28
commit 3def23883e
660 changed files with 30212 additions and 5437 deletions

View File

@@ -12,7 +12,6 @@
"extends": [ "extends": [
"airbnb", "airbnb",
"prettier", "prettier",
"prettier/react",
"plugin:jsx-a11y/strict", "plugin:jsx-a11y/strict",
"plugin:i18next/recommended" "plugin:i18next/recommended"
], ],
@@ -22,7 +21,7 @@
}, },
"import/resolver": { "import/resolver": {
"node": { "node": {
paths: ["src"] "paths": ["src"]
} }
} }
}, },
@@ -134,6 +133,7 @@
"object-curly-newline": "off", "object-curly-newline": "off",
"no-trailing-spaces": ["error"], "no-trailing-spaces": ["error"],
"no-unused-expressions": ["error", { "allowShortCircuit": true }], "no-unused-expressions": ["error", { "allowShortCircuit": true }],
"react/jsx-props-no-spreading":["off"],
"react/prefer-stateless-function": "off", "react/prefer-stateless-function": "off",
"react/prop-types": "off", "react/prop-types": "off",
"react/sort-comp": ["error", {}], "react/sort-comp": ["error", {}],

File diff suppressed because it is too large Load Diff

View File

@@ -44,19 +44,19 @@
"enzyme": "^3.10.0", "enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.3.5", "enzyme-to-json": "^3.3.5",
"eslint": "^7.11.0", "eslint": "7.30.0",
"eslint-config-airbnb": "^17.1.0", "eslint-config-airbnb": "18.2.1",
"eslint-config-prettier": "^5.0.0", "eslint-config-prettier": "8.3.0",
"eslint-import-resolver-webpack": "0.11.1", "eslint-import-resolver-webpack": "0.11.1",
"eslint-plugin-i18next": "^5.0.0", "eslint-plugin-i18next": "^5.0.0",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.11.1", "eslint-plugin-react": "^7.11.1",
"eslint-plugin-react-hooks": "^2.2.0", "eslint-plugin-react-hooks": "4.2.0",
"http-proxy-middleware": "^1.0.3", "http-proxy-middleware": "^1.0.3",
"jest-websocket-mock": "^2.0.2", "jest-websocket-mock": "^2.0.2",
"mock-socket": "^9.0.3", "mock-socket": "^9.0.3",
"prettier": "^1.18.2", "prettier": "2.3.2",
"react-scripts": "^4.0.3" "react-scripts": "^4.0.3"
}, },
"scripts": { "scripts": {

View File

@@ -16,7 +16,7 @@ const defaultHttp = axios.create({
}, },
}); });
defaultHttp.interceptors.response.use(response => { defaultHttp.interceptors.response.use((response) => {
const timeout = response?.headers['session-timeout']; const timeout = response?.headers['session-timeout'];
if (timeout) { if (timeout) {
const timeoutDate = new Date().getTime() + timeout * 1000; const timeoutDate = new Date().getTime() + timeout * 1000;

View File

@@ -1,13 +1,11 @@
function isEqual(array1, array2) { function isEqual(array1, array2) {
return ( return (
array1.length === array2.length && array1.length === array2.length &&
array1.every((element, index) => { array1.every((element, index) => element.id === array2[index].id)
return element.id === array2[index].id;
})
); );
} }
const InstanceGroupsMixin = parent => const InstanceGroupsMixin = (parent) =>
class extends parent { class extends parent {
readInstanceGroups(resourceId, params) { readInstanceGroups(resourceId, params) {
return this.http.get(`${this.baseUrl}${resourceId}/instance_groups/`, { return this.http.get(`${this.baseUrl}${resourceId}/instance_groups/`, {

View File

@@ -1,4 +1,4 @@
const LaunchUpdateMixin = parent => const LaunchUpdateMixin = (parent) =>
class extends parent { class extends parent {
launchUpdate(id, data) { launchUpdate(id, data) {
return this.http.post(`${this.baseUrl}${id}/update/`, data); return this.http.post(`${this.baseUrl}${id}/update/`, data);

View File

@@ -1,4 +1,4 @@
const NotificationsMixin = parent => const NotificationsMixin = (parent) =>
class extends parent { class extends parent {
readOptionsNotificationTemplates(id) { readOptionsNotificationTemplates(id) {
return this.http.options(`${this.baseUrl}${id}/notification_templates/`); return this.http.options(`${this.baseUrl}${id}/notification_templates/`);

View File

@@ -1,4 +1,4 @@
const Runnable = parent => const Runnable = (parent) =>
class extends parent { class extends parent {
jobEventSlug = '/events/'; jobEventSlug = '/events/';

View File

@@ -1,4 +1,4 @@
const SchedulesMixin = parent => const SchedulesMixin = (parent) =>
class extends parent { class extends parent {
createSchedule(id, data) { createSchedule(id, data) {
return this.http.post(`${this.baseUrl}${id}/schedules/`, data); return this.http.post(`${this.baseUrl}${id}/schedules/`, data);

View File

@@ -25,7 +25,7 @@ class CredentialTypes extends Base {
} }
return results return results
.concat(nextResults) .concat(nextResults)
.filter(type => acceptableKinds.includes(type.kind)); .filter((type) => acceptableKinds.includes(type.kind));
} }
test(id, data) { test(id, data) {

View File

@@ -22,9 +22,10 @@ describe('TeamsAPI', () => {
await TeamsAPI.associateRole(teamId, roleId); await TeamsAPI.associateRole(teamId, roleId);
expect(mockHttp.post).toHaveBeenCalledTimes(1); expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect( expect(mockHttp.post.mock.calls[0]).toContainEqual(
mockHttp.post.mock.calls[0] `/api/v2/teams/${teamId}/roles/`,
).toContainEqual(`/api/v2/teams/${teamId}/roles/`, { id: roleId }); { id: roleId }
);
}); });
test('read teams calls post with expected params', async () => { test('read teams calls post with expected params', async () => {

View File

@@ -19,9 +19,10 @@ describe('UsersAPI', () => {
await UsersAPI.associateRole(userId, roleId); await UsersAPI.associateRole(userId, roleId);
expect(mockHttp.post).toHaveBeenCalledTimes(1); expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect( expect(mockHttp.post.mock.calls[0]).toContainEqual(
mockHttp.post.mock.calls[0] `/api/v2/users/${userId}/roles/`,
).toContainEqual(`/api/v2/users/${userId}/roles/`, { id: roleId }); { id: roleId }
);
}); });
test('read users calls post with expected params', async () => { test('read users calls post with expected params', async () => {

View File

@@ -65,7 +65,7 @@ function AdHocCommands({
request: launchAdHocCommands, request: launchAdHocCommands,
} = useRequest( } = useRequest(
useCallback( useCallback(
async values => { async (values) => {
const { data } = await InventoriesAPI.launchAdHocCommands(id, values); const { data } = await InventoriesAPI.launchAdHocCommands(id, values);
history.push(`/jobs/command/${data.id}/output`); history.push(`/jobs/command/${data.id}/output`);
}, },
@@ -74,7 +74,11 @@ function AdHocCommands({
) )
); );
const handleSubmit = async values => { const { error, dismissError } = useDismissableError(
launchError || fetchError
);
const handleSubmit = async (values) => {
const { credential, execution_environment, ...remainingValues } = values; const { credential, execution_environment, ...remainingValues } = values;
const newCredential = credential[0].id; const newCredential = credential[0].id;
@@ -85,13 +89,9 @@ function AdHocCommands({
}; };
await launchAdHocCommands(manipulatedValues); await launchAdHocCommands(manipulatedValues);
}; };
useEffect(() => onLaunchLoading(isLaunchLoading), [ useEffect(
isLaunchLoading, () => onLaunchLoading(isLaunchLoading),
onLaunchLoading, [isLaunchLoading, onLaunchLoading]
]);
const { error, dismissError } = useDismissableError(
launchError || fetchError
); );
if (error && isWizardOpen) { if (error && isWizardOpen) {

View File

@@ -109,7 +109,7 @@ describe('<AdHocCommands />', () => {
await waitForElement( await waitForElement(
wrapper, wrapper,
'button[aria-label="Run Command"]', 'button[aria-label="Run Command"]',
el => el.length === 1 (el) => el.length === 1
); );
await act(async () => await act(async () =>
wrapper.find('button[aria-label="Run Command"]').prop('onClick')() wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
@@ -164,7 +164,7 @@ describe('<AdHocCommands />', () => {
await waitForElement( await waitForElement(
wrapper, wrapper,
'button[aria-label="Run Command"]', 'button[aria-label="Run Command"]',
el => el.length === 1 (el) => el.length === 1
); );
await act(async () => await act(async () =>
wrapper.find('button[aria-label="Run Command"]').prop('onClick')() wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
@@ -190,15 +190,12 @@ describe('<AdHocCommands />', () => {
await act(async () => await act(async () =>
wrapper.find('Button[type="submit"]').prop('onClick')() wrapper.find('Button[type="submit"]').prop('onClick')()
); );
await waitForElement(wrapper, 'ContentEmpty', el => el.length === 0); await waitForElement(wrapper, 'ContentEmpty', (el) => el.length === 0);
// second step of wizard // second step of wizard
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-2').find('input').simulate('click');
.find('td#check-action-item-2')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
@@ -211,13 +208,10 @@ describe('<AdHocCommands />', () => {
wrapper.find('Button[type="submit"]').prop('onClick')() wrapper.find('Button[type="submit"]').prop('onClick')()
); );
// third step of wizard // third step of wizard
await waitForElement(wrapper, 'ContentEmpty', el => el.length === 0); await waitForElement(wrapper, 'ContentEmpty', (el) => el.length === 0);
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-4').find('input').simulate('click');
.find('td#check-action-item-4')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
@@ -322,7 +316,7 @@ describe('<AdHocCommands />', () => {
await waitForElement( await waitForElement(
wrapper, wrapper,
'button[aria-label="Run Command"]', 'button[aria-label="Run Command"]',
el => el.length === 1 (el) => el.length === 1
); );
await act(async () => await act(async () =>
wrapper.find('button[aria-label="Run Command"]').prop('onClick')() wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
@@ -353,15 +347,12 @@ describe('<AdHocCommands />', () => {
wrapper.find('Button[type="submit"]').prop('onClick')() wrapper.find('Button[type="submit"]').prop('onClick')()
); );
await waitForElement(wrapper, 'ContentEmpty', el => el.length === 0); await waitForElement(wrapper, 'ContentEmpty', (el) => el.length === 0);
// second step of wizard // second step of wizard
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-2').find('input').simulate('click');
.find('td#check-action-item-2')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
@@ -374,13 +365,10 @@ describe('<AdHocCommands />', () => {
wrapper.find('Button[type="submit"]').prop('onClick')() wrapper.find('Button[type="submit"]').prop('onClick')()
); );
// third step of wizard // third step of wizard
await waitForElement(wrapper, 'ContentEmpty', el => el.length === 0); await waitForElement(wrapper, 'ContentEmpty', (el) => el.length === 0);
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-4').find('input').simulate('click');
.find('td#check-action-item-4')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
@@ -399,7 +387,7 @@ describe('<AdHocCommands />', () => {
await act(async () => await act(async () =>
wrapper.find('Button[type="submit"]').prop('onClick')() wrapper.find('Button[type="submit"]').prop('onClick')()
); );
await waitForElement(wrapper, 'ErrorDetail', el => el.length > 0); await waitForElement(wrapper, 'ErrorDetail', (el) => el.length > 0);
}); });
test('should disable run command button due to lack of list items', async () => { test('should disable run command button due to lack of list items', async () => {
@@ -420,7 +408,7 @@ describe('<AdHocCommands />', () => {
/> />
); );
}); });
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
const runCommandsButton = wrapper.find('button[aria-label="Run Command"]'); const runCommandsButton = wrapper.find('button[aria-label="Run Command"]');
expect(runCommandsButton.prop('disabled')).toBe(true); expect(runCommandsButton.prop('disabled')).toBe(true);
}); });

View File

@@ -58,7 +58,7 @@ function AdHocCommandsWizard({
const FormikApp = withFormik({ const FormikApp = withFormik({
mapPropsToValues({ adHocItems, verbosityOptions }) { mapPropsToValues({ adHocItems, verbosityOptions }) {
const adHocItemStrings = adHocItems.map(item => item.name).join(', '); const adHocItemStrings = adHocItems.map((item) => item.name).join(', ');
return { return {
limit: adHocItemStrings || 'all', limit: adHocItemStrings || 'all',
credential: [], credential: [],

View File

@@ -61,7 +61,7 @@ describe('<AdHocCommandsWizard/>', () => {
}); });
test('launch button should be disabled', async () => { test('launch button should be disabled', async () => {
waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); waitForElement(wrapper, 'WizardNavItem', (el) => el.length > 0);
expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(
false false
@@ -108,7 +108,7 @@ describe('<AdHocCommandsWizard/>', () => {
CredentialsAPI.readOptions.mockResolvedValue({ CredentialsAPI.readOptions.mockResolvedValue({
data: { actions: { GET: {} } }, data: { actions: { GET: {} } },
}); });
await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); await waitForElement(wrapper, 'WizardNavItem', (el) => el.length > 0);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
@@ -132,17 +132,14 @@ describe('<AdHocCommandsWizard/>', () => {
// step 2 // step 2
await waitForElement(wrapper, 'OptionsList', el => el.length > 0); await waitForElement(wrapper, 'OptionsList', (el) => el.length > 0);
expect(wrapper.find('CheckboxListItem').length).toBe(2); expect(wrapper.find('CheckboxListItem').length).toBe(2);
expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(
false false
); );
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-1').find('input').simulate('click');
.find('td#check-action-item-1')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
@@ -161,14 +158,11 @@ describe('<AdHocCommandsWizard/>', () => {
wrapper.update(); wrapper.update();
// step 3 // step 3
await waitForElement(wrapper, 'OptionsList', el => el.length > 0); await waitForElement(wrapper, 'OptionsList', (el) => el.length > 0);
expect(wrapper.find('CheckboxListItem').length).toBe(2); expect(wrapper.find('CheckboxListItem').length).toBe(2);
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-1').find('input').simulate('click');
.find('td#check-action-item-1')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
@@ -204,7 +198,7 @@ describe('<AdHocCommandsWizard/>', () => {
}); });
test('should show error in navigation bar', async () => { test('should show error in navigation bar', async () => {
await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); await waitForElement(wrapper, 'WizardNavItem', (el) => el.length > 0);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
@@ -215,7 +209,7 @@ describe('<AdHocCommandsWizard/>', () => {
target: { value: '', name: 'module_args' }, target: { value: '', name: 'module_args' },
}); });
}); });
waitForElement(wrapper, 'ExclamationCircleIcon', el => el.length > 0); waitForElement(wrapper, 'ExclamationCircleIcon', (el) => el.length > 0);
}); });
test('expect credential step to throw error', async () => { test('expect credential step to throw error', async () => {
@@ -234,7 +228,7 @@ describe('<AdHocCommandsWizard/>', () => {
CredentialsAPI.readOptions.mockResolvedValue({ CredentialsAPI.readOptions.mockResolvedValue({
data: { actions: { GET: {} } }, data: { actions: { GET: {} } },
}); });
await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); await waitForElement(wrapper, 'WizardNavItem', (el) => el.length > 0);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(

View File

@@ -58,10 +58,10 @@ function AdHocCredentialStep({ credentialTypeId }) {
credentialCount: count, credentialCount: count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [credentialTypeId, history.location.search]), }, [credentialTypeId, history.location.search]),
{ {
@@ -135,7 +135,7 @@ function AdHocCredentialStep({ credentialTypeId }) {
}, },
]} ]}
name="credential" name="credential"
selectItem={value => { selectItem={(value) => {
helpers.setValue([value]); helpers.setValue([value]);
}} }}
deselectItem={() => { deselectItem={() => {

View File

@@ -42,11 +42,11 @@ describe('<AdHocCredentialStep />', () => {
}); });
test('should mount properly', async () => { test('should mount properly', async () => {
await waitForElement(wrapper, 'OptionsList', el => el.length > 0); await waitForElement(wrapper, 'OptionsList', (el) => el.length > 0);
}); });
test('should call api', async () => { test('should call api', async () => {
await waitForElement(wrapper, 'OptionsList', el => el.length > 0); await waitForElement(wrapper, 'OptionsList', (el) => el.length > 0);
expect(CredentialsAPI.read).toHaveBeenCalled(); expect(CredentialsAPI.read).toHaveBeenCalled();
expect(wrapper.find('CheckboxListItem').length).toBe(2); expect(wrapper.find('CheckboxListItem').length).toBe(2);
}); });

View File

@@ -30,9 +30,8 @@ function AdHocDetailsStep({ verbosityOptions, moduleOptions }) {
const [variablesField] = useField('extra_vars'); const [variablesField] = useField('extra_vars');
const [diffModeField, , diffModeHelpers] = useField('diff_mode'); const [diffModeField, , diffModeHelpers] = useField('diff_mode');
const [becomeEnabledField, , becomeEnabledHelpers] = useField( const [becomeEnabledField, , becomeEnabledHelpers] =
'become_enabled' useField('become_enabled');
);
const [verbosityField, verbosityMeta, verbosityHelpers] = useField({ const [verbosityField, verbosityMeta, verbosityHelpers] = useField({
name: 'verbosity', name: 'verbosity',
validate: required(null), validate: required(null),
@@ -82,7 +81,7 @@ function AdHocDetailsStep({ verbosityOptions, moduleOptions }) {
label: t`Choose a module`, label: t`Choose a module`,
isDisabled: true, isDisabled: true,
}, },
...moduleOptions.map(value => ({ ...moduleOptions.map((value) => ({
value: value[0], value: value[0],
label: value[0], label: value[0],
key: value[0], key: value[0],
@@ -238,7 +237,7 @@ function AdHocDetailsStep({ verbosityOptions, moduleOptions }) {
} }
id="become_enabled" id="become_enabled"
isChecked={becomeEnabledField.value} isChecked={becomeEnabledField.value}
onChange={checked => { onChange={(checked) => {
becomeEnabledHelpers.setValue(checked); becomeEnabledHelpers.setValue(checked);
}} }}
/> />

View File

@@ -39,11 +39,11 @@ describe('<AdHocExecutionEnvironmentStep />', () => {
}); });
test('should mount properly', async () => { test('should mount properly', async () => {
await waitForElement(wrapper, 'OptionsList', el => el.length > 0); await waitForElement(wrapper, 'OptionsList', (el) => el.length > 0);
}); });
test('should call api', async () => { test('should call api', async () => {
await waitForElement(wrapper, 'OptionsList', el => el.length > 0); await waitForElement(wrapper, 'OptionsList', (el) => el.length > 0);
expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalled(); expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalled();
expect(wrapper.find('CheckboxListItem').length).toBe(2); expect(wrapper.find('CheckboxListItem').length).toBe(2);
}); });

View File

@@ -59,10 +59,10 @@ function AdHocExecutionEnvironmentStep({ organizationId }) {
executionEnvironmentsCount: count, executionEnvironmentsCount: count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [history.location.search, organizationId]), }, [history.location.search, organizationId]),
{ {
@@ -127,7 +127,7 @@ function AdHocExecutionEnvironmentStep({ organizationId }) {
name="execution_environment" name="execution_environment"
searchableKeys={searchableKeys} searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys} relatedSearchableKeys={relatedSearchableKeys}
selectItem={value => { selectItem={(value) => {
executionEnvironmentHelpers.setValue([value]); executionEnvironmentHelpers.setValue([value]);
}} }}
deselectItem={() => { deselectItem={() => {

View File

@@ -34,7 +34,7 @@ export default function useAdHocExecutionEnvironmentStep(
helpers.setError('A credential must be selected'); helpers.setError('A credential must be selected');
} }
}, },
setTouched: setFieldTouched => { setTouched: (setFieldTouched) => {
setFieldTouched('credential', true, false); setFieldTouched('credential', true, false);
}, },
}; };

View File

@@ -62,7 +62,7 @@ export default function useAdHocDetailsStep(
} }
} }
}, },
setTouched: setFieldTouched => { setTouched: (setFieldTouched) => {
setFieldTouched('module_name', true, false); setFieldTouched('module_name', true, false);
setFieldTouched('module_args', true, false); setFieldTouched('module_args', true, false);
}, },

View File

@@ -17,32 +17,28 @@ export default function useAdHocLaunchSteps(
useAdHocCredentialStep(visited, credentialTypeId), useAdHocCredentialStep(visited, credentialTypeId),
]; ];
const hasErrors = steps.some(step => step.hasError); const hasErrors = steps.some((step) => step.hasError);
steps.push(useAdHocPreviewStep(hasErrors)); steps.push(useAdHocPreviewStep(hasErrors));
return { return {
steps: steps.map(s => s.step).filter(s => s != null), steps: steps.map((s) => s.step).filter((s) => s != null),
validateStep: stepId => validateStep: (stepId) =>
steps steps.find((s) => s?.step.id === stepId).validate(),
.find(s => {
return s?.step.id === stepId;
})
.validate(),
visitStep: (prevStepId, setFieldTouched) => { visitStep: (prevStepId, setFieldTouched) => {
setVisited({ setVisited({
...visited, ...visited,
[prevStepId]: true, [prevStepId]: true,
}); });
steps.find(s => s?.step?.id === prevStepId).setTouched(setFieldTouched); steps.find((s) => s?.step?.id === prevStepId).setTouched(setFieldTouched);
}, },
visitAllSteps: setFieldTouched => { visitAllSteps: (setFieldTouched) => {
setVisited({ setVisited({
details: true, details: true,
executionEnvironment: true, executionEnvironment: true,
credential: true, credential: true,
preview: true, preview: true,
}); });
steps.forEach(s => s.setTouched(setFieldTouched)); steps.forEach((s) => s.setTouched(setFieldTouched));
}, },
}; };
} }

View File

@@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect, Fragment } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Dropdown, DropdownPosition } from '@patternfly/react-core'; import { Dropdown, DropdownPosition } from '@patternfly/react-core';
@@ -11,7 +11,7 @@ function AddDropDownButton({ dropdownItems, ouiaId }) {
const element = useRef(null); const element = useRef(null);
useEffect(() => { useEffect(() => {
const toggle = e => { const toggle = (e) => {
if (!isKebabified && (!element || !element.current.contains(e.target))) { if (!isKebabified && (!element || !element.current.contains(e.target))) {
setIsOpen(false); setIsOpen(false);
} }
@@ -24,7 +24,7 @@ function AddDropDownButton({ dropdownItems, ouiaId }) {
}, [isKebabified]); }, [isKebabified]);
if (isKebabified) { if (isKebabified) {
return <Fragment>{dropdownItems}</Fragment>; return <>{dropdownItems}</>;
} }
return ( return (

View File

@@ -1,4 +1,4 @@
import React, { Fragment, useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -8,12 +8,12 @@ import Wizard from '../Wizard';
import SelectResourceStep from './SelectResourceStep'; import SelectResourceStep from './SelectResourceStep';
import SelectRoleStep from './SelectRoleStep'; import SelectRoleStep from './SelectRoleStep';
const readUsers = async queryParams => const readUsers = async (queryParams) =>
UsersAPI.read(Object.assign(queryParams, { is_superuser: false })); UsersAPI.read(Object.assign(queryParams, { is_superuser: false }));
const readUsersOptions = async () => UsersAPI.readOptions(); const readUsersOptions = async () => UsersAPI.readOptions();
const readTeams = async queryParams => TeamsAPI.read(queryParams); const readTeams = async (queryParams) => TeamsAPI.read(queryParams);
const readTeamsOptions = async () => TeamsAPI.readOptions(); const readTeamsOptions = async () => TeamsAPI.readOptions();
@@ -77,9 +77,9 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
const [currentStepId, setCurrentStepId] = useState(1); const [currentStepId, setCurrentStepId] = useState(1);
const [maxEnabledStep, setMaxEnabledStep] = useState(1); const [maxEnabledStep, setMaxEnabledStep] = useState(1);
const handleResourceCheckboxClick = user => { const handleResourceCheckboxClick = (user) => {
const selectedIndex = selectedResourceRows.findIndex( const selectedIndex = selectedResourceRows.findIndex(
selectedRow => selectedRow.id === user.id (selectedRow) => selectedRow.id === user.id
); );
if (selectedIndex > -1) { if (selectedIndex > -1) {
selectedResourceRows.splice(selectedIndex, 1); selectedResourceRows.splice(selectedIndex, 1);
@@ -98,9 +98,9 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
} }
}, [currentStepId, history, maxEnabledStep]); }, [currentStepId, history, maxEnabledStep]);
const handleRoleCheckboxClick = role => { const handleRoleCheckboxClick = (role) => {
const selectedIndex = selectedRoleRows.findIndex( const selectedIndex = selectedRoleRows.findIndex(
selectedRow => selectedRow.id === role.id (selectedRow) => selectedRow.id === role.id
); );
if (selectedIndex > -1) { if (selectedIndex > -1) {
@@ -112,18 +112,18 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
} }
}; };
const handleResourceSelect = resourceType => { const handleResourceSelect = (resourceType) => {
setSelectedResource(resourceType); setSelectedResource(resourceType);
setSelectedResourceRows([]); setSelectedResourceRows([]);
setSelectedRoleRows([]); setSelectedRoleRows([]);
}; };
const handleWizardNext = step => { const handleWizardNext = (step) => {
setCurrentStepId(step.id); setCurrentStepId(step.id);
setMaxEnabledStep(step.id); setMaxEnabledStep(step.id);
}; };
const handleWizardGoToStep = step => { const handleWizardGoToStep = (step) => {
setCurrentStepId(step.id); setCurrentStepId(step.id);
}; };
@@ -163,7 +163,7 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
// showing role choices for team access // showing role choices for team access
const selectableRoles = { ...roles }; const selectableRoles = { ...roles };
if (selectedResource === 'teams') { if (selectedResource === 'teams') {
Object.keys(roles).forEach(key => { Object.keys(roles).forEach((key) => {
if (selectableRoles[key].user_only) { if (selectableRoles[key].user_only) {
delete selectableRoles[key]; delete selectableRoles[key];
} }
@@ -219,7 +219,7 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
id: 2, id: 2,
name: t`Select Items from List`, name: t`Select Items from List`,
component: ( component: (
<Fragment> <>
{selectedResource === 'users' && ( {selectedResource === 'users' && (
<SelectResourceStep <SelectResourceStep
searchColumns={userSearchColumns} searchColumns={userSearchColumns}
@@ -244,7 +244,7 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
selectedResourceRows={selectedResourceRows} selectedResourceRows={selectedResourceRows}
/> />
)} )}
</Fragment> </>
), ),
enableNext: selectedResourceRows.length > 0, enableNext: selectedResourceRows.length > 0,
nextButtonText: t`Next`, nextButtonText: t`Next`,
@@ -269,17 +269,17 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
}, },
]; ];
const currentStep = steps.find(step => step.id === currentStepId); const currentStep = steps.find((step) => step.id === currentStepId);
return ( return (
<Wizard <Wizard
style={{ overflow: 'scroll' }} style={{ overflow: 'scroll' }}
isOpen isOpen
onNext={handleWizardNext} onNext={handleWizardNext}
onBack={step => setCurrentStepId(step.id)} onBack={(step) => setCurrentStepId(step.id)}
onClose={onClose} onClose={onClose}
onSave={handleWizardSave} onSave={handleWizardSave}
onGoToStep={step => handleWizardGoToStep(step)} onGoToStep={(step) => handleWizardGoToStep(step)}
steps={steps} steps={steps}
title={wizardTitle} title={wizardTitle}
nextButtonText={currentStep.nextButtonText || undefined} nextButtonText={currentStep.nextButtonText || undefined}

View File

@@ -68,7 +68,7 @@ describe('<_AddResourceRole />', () => {
onClose={() => {}} onClose={() => {}}
onSave={() => {}} onSave={() => {}}
roles={roles} roles={roles}
i18n={{ _: val => val.toString() }} i18n={{ _: (val) => val.toString() }}
/> />
); );
}); });
@@ -93,7 +93,7 @@ describe('<_AddResourceRole />', () => {
wrapper.update(); wrapper.update();
// Step 2 // Step 2
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
expect(wrapper.find('Chip').length).toBe(0); expect(wrapper.find('Chip').length).toBe(0);
act(() => act(() =>
wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true) wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true)
@@ -161,7 +161,7 @@ describe('<_AddResourceRole />', () => {
wrapper.update(); wrapper.update();
// Step 2 // Step 2
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
act(() => act(() =>
wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true) wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true)
); );
@@ -213,7 +213,7 @@ describe('<_AddResourceRole />', () => {
wrapper.update(); wrapper.update();
// Step 2 // Step 2
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
act(() => act(() =>
wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true) wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true)
); );
@@ -246,7 +246,7 @@ describe('<_AddResourceRole />', () => {
await waitForElement( await waitForElement(
wrapper, wrapper,
'SelectableCard[label="Users"]', 'SelectableCard[label="Users"]',
el => el.prop('isSelected') === true (el) => el.prop('isSelected') === true
); );
act(() => wrapper.find('SelectableCard[label="Teams"]').prop('onClick')()); act(() => wrapper.find('SelectableCard[label="Teams"]').prop('onClick')());
wrapper.update(); wrapper.update();
@@ -254,7 +254,7 @@ describe('<_AddResourceRole />', () => {
await waitForElement( await waitForElement(
wrapper, wrapper,
'SelectableCard[label="Teams"]', 'SelectableCard[label="Teams"]',
el => el.prop('isSelected') === true (el) => el.prop('isSelected') === true
); );
}); });
@@ -279,7 +279,7 @@ describe('<_AddResourceRole />', () => {
wrapper.update(); wrapper.update();
// Step 2 // Step 2
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
act(() => act(() =>
wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true) wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true)
); );
@@ -322,17 +322,17 @@ describe('<_AddResourceRole />', () => {
wrapper.update(); wrapper.update();
// Make sure no teams have been selected // Make sure no teams have been selected
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
wrapper wrapper
.find('DataListCheck') .find('DataListCheck')
.map(item => expect(item.prop('checked')).toBe(false)); .map((item) => expect(item.prop('checked')).toBe(false));
act(() => wrapper.find('Button[type="submit"]').prop('onClick')()); act(() => wrapper.find('Button[type="submit"]').prop('onClick')());
wrapper.update(); wrapper.update();
// Make sure that no roles have been selected // Make sure that no roles have been selected
wrapper wrapper
.find('Checkbox') .find('Checkbox')
.map(card => expect(card.prop('isChecked')).toBe(false)); .map((card) => expect(card.prop('isChecked')).toBe(false));
// Make sure the save button is disabled // Make sure the save button is disabled
expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true); expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true);
@@ -379,7 +379,7 @@ describe('<_AddResourceRole />', () => {
wrapper.update(); wrapper.update();
// Step 2 // Step 2
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
expect(wrapper.find('Chip').length).toBe(0); expect(wrapper.find('Chip').length).toBe(0);
act(() => act(() =>
wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true) wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true)

View File

@@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Checkbox as PFCheckbox } from '@patternfly/react-core'; import { Checkbox as PFCheckbox } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -28,10 +28,10 @@ class CheckboxCard extends Component {
aria-label={name} aria-label={name}
id={`checkbox-card-${itemId}`} id={`checkbox-card-${itemId}`}
label={ label={
<Fragment> <>
<div style={{ fontWeight: 'bold' }}>{name}</div> <div style={{ fontWeight: 'bold' }}>{name}</div>
<div>{description}</div> <div>{description}</div>
</Fragment> </>
} }
value={itemId} value={itemId}
/> />

View File

@@ -1,4 +1,4 @@
import React, { Fragment, useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withRouter, useLocation } from 'react-router-dom'; import { withRouter, useLocation } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -10,15 +10,16 @@ import CheckboxListItem from '../CheckboxListItem';
import { SelectedList } from '../SelectedList'; import { SelectedList } from '../SelectedList';
import PaginatedTable, { HeaderCell, HeaderRow } from '../PaginatedTable'; import PaginatedTable, { HeaderCell, HeaderRow } from '../PaginatedTable';
const QS_Config = sortColumns => { const QS_Config = (sortColumns) =>
return getQSConfig('resource', { getQSConfig('resource', {
page: 1, page: 1,
page_size: 5, page_size: 5,
order_by: `${ order_by: `${
sortColumns.filter(col => col.key === 'name').length ? 'name' : 'username' sortColumns.filter((col) => col.key === 'name').length
? 'name'
: 'username'
}`, }`,
}); });
};
function SelectResourceStep({ function SelectResourceStep({
searchColumns, searchColumns,
sortColumns, sortColumns,
@@ -54,10 +55,10 @@ function SelectResourceStep({
itemCount: count, itemCount: count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [location, fetchItems, fetchOptions, sortColumns]), }, [location, fetchItems, fetchOptions, sortColumns]),
{ {
@@ -73,7 +74,7 @@ function SelectResourceStep({
}, [readResourceList]); }, [readResourceList]);
return ( return (
<Fragment> <>
<div> <div>
{t`Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step.`} {t`Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step.`}
</div> </div>
@@ -108,7 +109,7 @@ function SelectResourceStep({
} }
renderRow={(item, index) => ( renderRow={(item, index) => (
<CheckboxListItem <CheckboxListItem
isSelected={selectedResourceRows.some(i => i.id === item.id)} isSelected={selectedResourceRows.some((i) => i.id === item.id)}
itemId={item.id} itemId={item.id}
item={item} item={item}
rowIndex={index} rowIndex={index}
@@ -120,10 +121,10 @@ function SelectResourceStep({
onDeselect={() => onRowClick(item)} onDeselect={() => onRowClick(item)}
/> />
)} )}
renderToolbar={props => <DataListToolbar {...props} fillWidth />} renderToolbar={(props) => <DataListToolbar {...props} fillWidth />}
showPageSizeOptions={false} showPageSizeOptions={false}
/> />
</Fragment> </>
); );
} }

View File

@@ -79,7 +79,7 @@ describe('<SelectResourceStep />', () => {
page: 1, page: 1,
page_size: 5, page_size: 5,
}); });
waitForElement(wrapper, 'CheckBoxListItem', el => el.length === 2); waitForElement(wrapper, 'CheckBoxListItem', (el) => el.length === 2);
}); });
test('clicking on row fires callback with correct params', async () => { test('clicking on row fires callback with correct params', async () => {

View File

@@ -15,7 +15,7 @@ function RolesStep({
selectedRoleRows, selectedRoleRows,
}) { }) {
return ( return (
<Fragment> <>
<div> <div>
{t`Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources.`} {t`Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources.`}
</div> </div>
@@ -37,12 +37,12 @@ function RolesStep({
marginTop: '20px', marginTop: '20px',
}} }}
> >
{Object.keys(roles).map(role => ( {Object.keys(roles).map((role) => (
<CheckboxCard <CheckboxCard
description={roles[role].description} description={roles[role].description}
itemId={roles[role].id} itemId={roles[role].id}
isSelected={selectedRoleRows.some( isSelected={selectedRoleRows.some(
item => item.id === roles[role].id (item) => item.id === roles[role].id
)} )}
key={roles[role].id} key={roles[role].id}
name={roles[role].name} name={roles[role].name}
@@ -50,7 +50,7 @@ function RolesStep({
/> />
))} ))}
</div> </div>
</Fragment> </>
); );
} }

View File

@@ -39,7 +39,7 @@ function AnsibleSelect({
className={className} className={className}
isDisabled={isDisabled} isDisabled={isDisabled}
> >
{data.map(option => ( {data.map((option) => (
<FormSelectOption <FormSelectOption
key={option.key} key={option.key}
value={option.value} value={option.value}

View File

@@ -166,7 +166,7 @@ describe('<AppContainer />', () => {
// open about modal // open about modal
( (
await waitForElement(wrapper, aboutButton, el => !el.props().disabled) await waitForElement(wrapper, aboutButton, (el) => !el.props().disabled)
).simulate('click'); ).simulate('click');
// check about modal content // check about modal content

View File

@@ -47,21 +47,19 @@ function PageHeaderToolbar({
const [isUserOpen, setIsUserOpen] = useState(false); const [isUserOpen, setIsUserOpen] = useState(false);
const config = useConfig(); const config = useConfig();
const { const { request: fetchPendingApprovalCount, result: pendingApprovals } =
request: fetchPendingApprovalCount, useRequest(
result: pendingApprovals, useCallback(async () => {
} = useRequest( const {
useCallback(async () => { data: { count },
const { } = await WorkflowApprovalsAPI.read({
data: { count }, status: 'pending',
} = await WorkflowApprovalsAPI.read({ page_size: 1,
status: 'pending', });
page_size: 1, return count;
}); }, []),
return count; 0
}, []), );
0
);
const pendingApprovalsCount = useWsPendingApprovalCount( const pendingApprovalsCount = useWsPendingApprovalCount(
pendingApprovals, pendingApprovals,

View File

@@ -6,9 +6,8 @@ export default function useWsPendingApprovalCount(
initialCount, initialCount,
fetchApprovalsCount fetchApprovalsCount
) { ) {
const [pendingApprovalCount, setPendingApprovalCount] = useState( const [pendingApprovalCount, setPendingApprovalCount] =
initialCount useState(initialCount);
);
const [reloadCount, setReloadCount] = useState(false); const [reloadCount, setReloadCount] = useState(false);
const throttledFetch = useThrottle(reloadCount, 1000); const throttledFetch = useThrottle(reloadCount, 1000);
const lastMessage = useWebsocket({ const lastMessage = useWebsocket({
@@ -20,27 +19,21 @@ export default function useWsPendingApprovalCount(
setPendingApprovalCount(initialCount); setPendingApprovalCount(initialCount);
}, [initialCount]); }, [initialCount]);
useEffect( useEffect(() => {
function reloadTheCount() { (async () => {
(async () => { if (!throttledFetch) {
if (!throttledFetch) { return;
return;
}
setReloadCount(false);
fetchApprovalsCount();
})();
},
[throttledFetch, fetchApprovalsCount]
);
useEffect(
function processWsMessage() {
if (lastMessage?.type === 'workflow_approval') {
setReloadCount(true);
} }
}, setReloadCount(false);
[lastMessage] fetchApprovalsCount();
); })();
}, [throttledFetch, fetchApprovalsCount]);
useEffect(() => {
if (lastMessage?.type === 'workflow_approval') {
setReloadCount(true);
}
}, [lastMessage]);
return pendingApprovalCount; return pendingApprovalCount;
} }

View File

@@ -25,7 +25,7 @@ describe('useWsPendingApprovalCount hook', () => {
*/ */
jest.mock('../../hooks/useThrottle', () => ({ jest.mock('../../hooks/useThrottle', () => ({
__esModule: true, __esModule: true,
default: jest.fn(val => val), default: jest.fn((val) => val),
})); }));
debug = global.console.debug; // eslint-disable-line prefer-destructuring debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {}; global.console.debug = () => {};

View File

@@ -1,4 +1,4 @@
import React, { Fragment, useEffect, useCallback } from 'react'; import React, { useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -8,13 +8,12 @@ import { getQSConfig, parseQueryString } from 'util/qs';
import useSelected from 'hooks/useSelected'; import useSelected from 'hooks/useSelected';
import OptionsList from '../OptionsList'; import OptionsList from '../OptionsList';
const QS_CONFIG = (order_by = 'name') => { const QS_CONFIG = (order_by = 'name') =>
return getQSConfig('associate', { getQSConfig('associate', {
page: 1, page: 1,
page_size: 5, page_size: 5,
order_by, order_by,
}); });
};
function AssociateModal({ function AssociateModal({
header = t`Items`, header = t`Items`,
@@ -53,10 +52,10 @@ function AssociateModal({
itemCount: count, itemCount: count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [fetchRequest, optionsRequest, history.location.search, displayKey]), }, [fetchRequest, optionsRequest, history.location.search, displayKey]),
{ {
@@ -75,7 +74,7 @@ function AssociateModal({
const parts = history.location.search.replace(/^\?/, '').split('&'); const parts = history.location.search.replace(/^\?/, '').split('&');
const { namespace } = QS_CONFIG(displayKey); const { namespace } = QS_CONFIG(displayKey);
const otherParts = parts.filter( const otherParts = parts.filter(
param => !param.startsWith(`${namespace}.`) (param) => !param.startsWith(`${namespace}.`)
); );
history.replace(`${history.location.pathname}?${otherParts.join('&')}`); history.replace(`${history.location.pathname}?${otherParts.join('&')}`);
}; };
@@ -92,7 +91,7 @@ function AssociateModal({
}; };
return ( return (
<Fragment> <>
<Modal <Modal
ouiaId={ouiaId} ouiaId={ouiaId}
variant="large" variant="large"
@@ -160,7 +159,7 @@ function AssociateModal({
relatedSearchableKeys={relatedSearchableKeys} relatedSearchableKeys={relatedSearchableKeys}
/> />
</Modal> </Modal>
</Fragment> </>
); );
} }

View File

@@ -41,7 +41,7 @@ describe('<AssociateModal />', () => {
/> />
); );
}); });
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
}); });
afterEach(() => { afterEach(() => {
@@ -61,10 +61,7 @@ describe('<AssociateModal />', () => {
test('should update selected list chips when items are selected', () => { test('should update selected list chips when items are selected', () => {
expect(wrapper.find('SelectedList Chip')).toHaveLength(0); expect(wrapper.find('SelectedList Chip')).toHaveLength(0);
act(() => { act(() => {
wrapper wrapper.find('CheckboxListItem').first().invoke('onSelect')();
.find('CheckboxListItem')
.first()
.invoke('onSelect')();
}); });
wrapper.update(); wrapper.update();
expect(wrapper.find('SelectedList Chip')).toHaveLength(1); expect(wrapper.find('SelectedList Chip')).toHaveLength(1);
@@ -74,10 +71,7 @@ describe('<AssociateModal />', () => {
test('save button should call onAssociate', () => { test('save button should call onAssociate', () => {
act(() => { act(() => {
wrapper wrapper.find('CheckboxListItem').first().invoke('onSelect')();
.find('CheckboxListItem')
.first()
.invoke('onSelect')();
}); });
wrapper.find('button[aria-label="Save"]').simulate('click'); wrapper.find('button[aria-label="Save"]').simulate('click');
expect(onAssociate).toHaveBeenCalledTimes(1); expect(onAssociate).toHaveBeenCalledTimes(1);

View File

@@ -3,8 +3,8 @@ import React, { Fragment } from 'react';
import { BackgroundImage } from '@patternfly/react-core'; import { BackgroundImage } from '@patternfly/react-core';
export default ({ children }) => ( export default ({ children }) => (
<Fragment> <>
<BackgroundImage /> <BackgroundImage />
{children} {children}
</Fragment> </>
); );

View File

@@ -43,7 +43,7 @@ const CheckboxListItem = ({
/> />
{columns?.length > 0 ? ( {columns?.length > 0 ? (
columns.map(col => ( columns.map((col) => (
<Td aria-label={col.name} dataLabel={col.key} key={col.key}> <Td aria-label={col.name} dataLabel={col.key} key={col.key}>
{item[col.key]} {item[col.key]}
</Td> </Td>

View File

@@ -7,11 +7,8 @@ describe('ChipGroup', () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<ChipGroup numChips={5} totalChips={10} /> <ChipGroup numChips={5} totalChips={10} />
); );
expect( expect(wrapper.find('ChipGroup').at(1).props().collapsedText).toEqual(
wrapper '5 more'
.find('ChipGroup') );
.at(1)
.props().collapsedText
).toEqual('5 more');
}); });
}); });

View File

@@ -47,7 +47,7 @@ const AceEditor = styled(ReactAce)`
display: none; display: none;
} }
${props => ${(props) =>
props.hasErrors && props.hasErrors &&
` `
&& { && {
@@ -59,7 +59,7 @@ const AceEditor = styled(ReactAce)`
border-bottom-width: var(--pf-c-form-control--invalid--BorderBottomWidth); border-bottom-width: var(--pf-c-form-control--invalid--BorderBottomWidth);
}`} }`}
${props => ${(props) =>
props.setOptions.readOnly && props.setOptions.readOnly &&
` `
&& .ace_cursor { && .ace_cursor {
@@ -92,21 +92,18 @@ function CodeEditor({
const wrapper = useRef(null); const wrapper = useRef(null);
const editor = useRef(null); const editor = useRef(null);
useEffect( useEffect(() => {
function removeTextareaTabIndex() { const editorInput = editor.current.refEditor?.querySelector('textarea');
const editorInput = editor.current.refEditor?.querySelector('textarea'); if (!editorInput) {
if (!editorInput) { return;
return; }
} if (!readOnly) {
if (!readOnly) { editorInput.tabIndex = -1;
editorInput.tabIndex = -1; }
} editorInput.id = id;
editorInput.id = id; }, [readOnly, id]);
},
[readOnly, id]
);
const listen = useCallback(event => { const listen = useCallback((event) => {
if (wrapper.current === document.activeElement && event.key === 'Enter') { if (wrapper.current === document.activeElement && event.key === 'Enter') {
const editorInput = editor.current.refEditor?.querySelector('textarea'); const editorInput = editor.current.refEditor?.querySelector('textarea');
if (!editorInput) { if (!editorInput) {
@@ -118,7 +115,7 @@ function CodeEditor({
} }
}, []); }, []);
useEffect(function addKeyEventListeners() { useEffect(() => {
const wrapperEl = wrapper.current; const wrapperEl = wrapper.current;
wrapperEl.addEventListener('keydown', listen); wrapperEl.addEventListener('keydown', listen);

View File

@@ -22,7 +22,7 @@ describe('CodeEditor', () => {
}); });
it('should trigger onChange prop', () => { it('should trigger onChange prop', () => {
debounce.mockImplementation(fn => fn); debounce.mockImplementation((fn) => fn);
const onChange = jest.fn(); const onChange = jest.fn();
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<CodeEditor value="---" onChange={onChange} mode="yaml" /> <CodeEditor value="---" onChange={onChange} mode="yaml" />

View File

@@ -43,7 +43,7 @@ function CodeEditorField({
id={id} id={id}
{...rest} {...rest}
{...field} {...field}
onChange={value => { onChange={(value) => {
helpers.setValue(value); helpers.setValue(value);
}} }}
mode={mode} mode={mode}

View File

@@ -196,7 +196,7 @@ function ModeToggle({
[JSON_MODE, 'JSON'], [JSON_MODE, 'JSON'],
]} ]}
value={mode} value={mode}
onChange={newMode => { onChange={(newMode) => {
setMode(newMode); setMode(newMode);
}} }}
name={name} name={name}

View File

@@ -39,7 +39,7 @@ function VariablesField({
const [shouldValidate, setShouldValidate] = useState(false); const [shouldValidate, setShouldValidate] = useState(false);
const [mode, setMode] = useState(initialMode || YAML_MODE); const [mode, setMode] = useState(initialMode || YAML_MODE);
const validate = useCallback( const validate = useCallback(
value => { (value) => {
if (!shouldValidate) { if (!shouldValidate) {
return undefined; return undefined;
} }
@@ -58,7 +58,7 @@ function VariablesField({
); );
const [field, meta, helpers] = useField({ name, validate }); const [field, meta, helpers] = useField({ name, validate });
useEffect(function initializeJSON() { useEffect(() => {
if (isJsonString(field.value)) { if (isJsonString(field.value)) {
// mode's useState above couldn't be initialized to JSON_MODE because // mode's useState above couldn't be initialized to JSON_MODE because
// the field value had to be defined below it // the field value had to be defined below it
@@ -69,7 +69,7 @@ function VariablesField({
}, []); // eslint-disable-line react-hooks/exhaustive-deps }, []); // eslint-disable-line react-hooks/exhaustive-deps
useEffect( useEffect(
function validateOnBlur() { () => {
if (shouldValidate) { if (shouldValidate) {
helpers.setError(validate(field.value)); helpers.setError(validate(field.value));
} }
@@ -82,7 +82,7 @@ function VariablesField({
const [isJsonEdited, setIsJsonEdited] = useState(false); const [isJsonEdited, setIsJsonEdited] = useState(false);
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const handleModeChange = newMode => { const handleModeChange = (newMode) => {
if (newMode === YAML_MODE && !isJsonEdited && lastYamlValue !== null) { if (newMode === YAML_MODE && !isJsonEdited && lastYamlValue !== null) {
helpers.setValue(lastYamlValue, false); helpers.setValue(lastYamlValue, false);
setMode(newMode); setMode(newMode);
@@ -103,7 +103,7 @@ function VariablesField({
} }
}; };
const handleChange = newVal => { const handleChange = (newVal) => {
helpers.setValue(newVal); helpers.setValue(newVal);
if (mode === JSON_MODE) { if (mode === JSON_MODE) {
setIsJsonEdited(true); setIsJsonEdited(true);
@@ -201,7 +201,7 @@ function VariablesFieldInternals({
}) { }) {
const [field, meta, helpers] = useField(name); const [field, meta, helpers] = useField(name);
useEffect(function formatJsonString() { useEffect(() => {
if (mode === YAML_MODE) { if (mode === YAML_MODE) {
return; return;
} }

View File

@@ -140,10 +140,7 @@ describe('VariablesField', () => {
)} )}
</Formik> </Formik>
); );
wrapper wrapper.find('Button').at(1).simulate('click');
.find('Button')
.at(1)
.simulate('click');
wrapper.update(); wrapper.update();
const field = wrapper.find('CodeEditor'); const field = wrapper.find('CodeEditor');
@@ -173,7 +170,7 @@ describe('VariablesField', () => {
const handleSubmit = jest.fn(); const handleSubmit = jest.fn();
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<Formik initialValues={{ variables: value }} onSubmit={handleSubmit}> <Formik initialValues={{ variables: value }} onSubmit={handleSubmit}>
{formik => ( {(formik) => (
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<VariablesField id="the-field" name="variables" label="Variables" /> <VariablesField id="the-field" name="variables" label="Variables" />
<button type="submit" id="submit"> <button type="submit" id="submit">
@@ -200,7 +197,7 @@ describe('VariablesField', () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<Formik initialValues={{ variables: value }} onSubmit={jest.fn()}> <Formik initialValues={{ variables: value }} onSubmit={jest.fn()}>
{formik => ( {(formik) => (
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<VariablesField <VariablesField
id="the-field" id="the-field"
@@ -224,7 +221,7 @@ describe('VariablesField', () => {
const value = '---'; const value = '---';
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<Formik initialValues={{ variables: value }} onSubmit={jest.fn()}> <Formik initialValues={{ variables: value }} onSubmit={jest.fn()}>
{formik => ( {(formik) => (
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<VariablesField id="the-field" name="variables" label="Variables" /> <VariablesField id="the-field" name="variables" label="Variables" />
<button type="submit" id="submit"> <button type="submit" id="submit">
@@ -249,7 +246,7 @@ describe('VariablesField', () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<Formik initialValues={{ variables: value }} onSubmit={jest.fn()}> <Formik initialValues={{ variables: value }} onSubmit={jest.fn()}>
{formik => ( {(formik) => (
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<VariablesField <VariablesField
id="the-field" id="the-field"

View File

@@ -26,7 +26,7 @@ function VariablesInput(props) {
const isControlled = !!props.onChange; const isControlled = !!props.onChange;
/* eslint-enable react/destructuring-assignment */ /* eslint-enable react/destructuring-assignment */
const onChange = newValue => { const onChange = (newValue) => {
if (isControlled) { if (isControlled) {
props.onChange(newValue); props.onChange(newValue);
} }
@@ -48,7 +48,7 @@ function VariablesInput(props) {
[JSON_MODE, 'JSON'], [JSON_MODE, 'JSON'],
]} ]}
value={mode} value={mode}
onChange={newMode => { onChange={(newMode) => {
try { try {
if (mode === JSON_MODE) { if (mode === JSON_MODE) {
onChange(jsonToYaml(value)); onChange(jsonToYaml(value));

View File

@@ -26,7 +26,7 @@ function ContentError({ error, children, isNotFound }) {
isNotFound || (error && error.response && error.response.status === 404); isNotFound || (error && error.response && error.response.status === 404);
const is401 = error && error.response && error.response.status === 401; const is401 = error && error.response && error.response.status === 401;
return ( return (
<Fragment> <>
{is401 ? ( {is401 ? (
<Redirect to="/login" /> <Redirect to="/login" />
) : ( ) : (
@@ -44,7 +44,7 @@ function ContentError({ error, children, isNotFound }) {
{error && <ErrorDetail error={error} />} {error && <ErrorDetail error={error} />}
</EmptyState> </EmptyState>
)} )}
</Fragment> </>
); );
} }
ContentError.propTypes = { ContentError.propTypes = {

View File

@@ -13,13 +13,11 @@ const EmptyState = styled(PFEmptyState)`
`; `;
// TODO: Better loading state - skeleton lines / spinner, etc. // TODO: Better loading state - skeleton lines / spinner, etc.
const ContentLoading = ({ className }) => { const ContentLoading = ({ className }) => (
return ( <EmptyState variant="full" className={className}>
<EmptyState variant="full" className={className}> <EmptyStateIcon variant="container" component={Spinner} />
<EmptyStateIcon variant="container" component={Spinner} /> </EmptyState>
</EmptyState> );
);
};
export { ContentLoading as _ContentLoading }; export { ContentLoading as _ContentLoading };
export default ContentLoading; export default ContentLoading;

View File

@@ -16,9 +16,11 @@ function CopyButton({
errorMessage, errorMessage,
ouiaId, ouiaId,
}) { }) {
const { isLoading, error: copyError, request: copyItemToAPI } = useRequest( const {
copyItem isLoading,
); error: copyError,
request: copyItemToAPI,
} = useRequest(copyItem);
useEffect(() => { useEffect(() => {
if (isLoading) { if (isLoading) {

View File

@@ -66,7 +66,7 @@ function DataListToolbar({
const dropdownPosition = const dropdownPosition =
viewportWidth >= 992 ? DropdownPosition.right : DropdownPosition.left; viewportWidth >= 992 ? DropdownPosition.right : DropdownPosition.left;
const onShowAdvancedSearch = shown => { const onShowAdvancedSearch = (shown) => {
setIsAdvancedSearchShown(shown); setIsAdvancedSearchShown(shown);
setIsKebabOpen(false); setIsKebabOpen(false);
}; };
@@ -165,7 +165,7 @@ function DataListToolbar({
toggle={ toggle={
<KebabToggle <KebabToggle
data-cy="actions-kebab-toogle" data-cy="actions-kebab-toogle"
onToggle={isOpen => { onToggle={(isOpen) => {
if (!isKebabModalOpen) { if (!isKebabModalOpen) {
setIsKebabOpen(isOpen); setIsKebabOpen(isOpen);
} }
@@ -182,7 +182,7 @@ function DataListToolbar({
)} )}
{!isAdvancedSearchShown && ( {!isAdvancedSearchShown && (
<ToolbarGroup> <ToolbarGroup>
{additionalControls.map(control => ( {additionalControls.map((control) => (
<ToolbarItem key={control.key}>{control}</ToolbarItem> <ToolbarItem key={control.key}>{control}</ToolbarItem>
))} ))}
</ToolbarGroup> </ToolbarGroup>

View File

@@ -245,7 +245,7 @@ describe('<DataListToolbar />', () => {
const search = toolbar.find('Search'); const search = toolbar.find('Search');
expect( expect(
search.prop('columns').filter(col => col.key === 'advanced').length search.prop('columns').filter((col) => col.key === 'advanced').length
).toBe(1); ).toBe(1);
}); });

View File

@@ -34,7 +34,7 @@ function DeleteButton({
const [deleteDetails, setDeleteDetails] = useState({}); const [deleteDetails, setDeleteDetails] = useState({});
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const toggleModal = async isModalOpen => { const toggleModal = async (isModalOpen) => {
setIsLoading(true); setIsLoading(true);
if (deleteDetailsRequests?.length && isModalOpen) { if (deleteDetailsRequests?.length && isModalOpen) {
const { results, error } = await getRelatedResourceDeleteCounts( const { results, error } = await getRelatedResourceDeleteCounts(

View File

@@ -42,7 +42,7 @@ describe('<DeleteButton />', () => {
wrapper.find('button').prop('onClick')(); wrapper.find('button').prop('onClick')();
}); });
await waitForElement(wrapper, 'Modal', el => el.length > 0); await waitForElement(wrapper, 'Modal', (el) => el.length > 0);
expect(wrapper.find('Modal')).toHaveLength(1); expect(wrapper.find('Modal')).toHaveLength(1);
expect(wrapper.find('div[aria-label="Delete this?"]')).toHaveLength(1); expect(wrapper.find('div[aria-label="Delete this?"]')).toHaveLength(1);

View File

@@ -24,7 +24,7 @@ function ArrayDetail({ label, value, dataCy }) {
{label} {label}
</DetailName> </DetailName>
<Value component={TextListItemVariants.dd} data-cy={valueCy}> <Value component={TextListItemVariants.dd} data-cy={valueCy}>
{vals.map(v => ( {vals.map((v) => (
<div key={v}>{v}</div> <div key={v}>{v}</div>
))} ))}
</Value> </Value>

View File

@@ -8,7 +8,7 @@ const DetailName = styled(({ fullWidth, ...props }) => (
<TextListItem {...props} /> <TextListItem {...props} />
))` ))`
font-weight: var(--pf-global--FontWeight--bold); font-weight: var(--pf-global--FontWeight--bold);
${props => ${(props) =>
props.fullWidth && props.fullWidth &&
` `
grid-column: 1; grid-column: 1;
@@ -21,12 +21,12 @@ const DetailValue = styled(
) )
)` )`
word-break: break-all; word-break: break-all;
${props => ${(props) =>
props.fullWidth && props.fullWidth &&
` `
grid-column: 2 / -1; grid-column: 2 / -1;
`} `}
${props => ${(props) =>
(props.isEncrypted || props.isNotConfigured) && (props.isEncrypted || props.isNotConfigured) &&
` `
color: var(--pf-global--Color--400); color: var(--pf-global--Color--400);

View File

@@ -12,7 +12,7 @@ export default styled(DetailList)`
display: grid; display: grid;
grid-gap: 20px; grid-gap: 20px;
align-items: start; align-items: start;
${props => ${(props) =>
props.stacked props.stacked
? ` ? `
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;

View File

@@ -41,7 +41,7 @@ function DisassociateButton({
if (verifyCannotDisassociate) { if (verifyCannotDisassociate) {
const itemsUnableToDisassociate = itemsToDisassociate const itemsUnableToDisassociate = itemsToDisassociate
.filter(cannotDisassociate) .filter(cannotDisassociate)
.map(item => item.name) .map((item) => item.name)
.join(', '); .join(', ');
if (itemsToDisassociate.some(cannotDisassociate)) { if (itemsToDisassociate.some(cannotDisassociate)) {
@@ -130,7 +130,7 @@ function DisassociateButton({
<div>{t`This action will disassociate the following:`}</div> <div>{t`This action will disassociate the following:`}</div>
{itemsToDisassociate.map(item => ( {itemsToDisassociate.map((item) => (
<span key={item.id}> <span key={item.id}>
<strong>{item.hostname ? item.hostname : item.name}</strong> <strong>{item.hostname ? item.hostname : item.name}</strong>
<br /> <br />

View File

@@ -1,4 +1,4 @@
import React, { useState, Fragment } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -48,7 +48,7 @@ function ErrorDetail({ error }) {
const message = getErrorMessage(response); const message = getErrorMessage(response);
return ( return (
<Fragment> <>
<CardBody> <CardBody>
{response?.config?.method.toUpperCase()} {response?.config?.url}{' '} {response?.config?.method.toUpperCase()} {response?.config?.url}{' '}
<strong>{response?.status}</strong> <strong>{response?.status}</strong>
@@ -56,7 +56,7 @@ function ErrorDetail({ error }) {
<CardBody> <CardBody>
{Array.isArray(message) ? ( {Array.isArray(message) ? (
<ul> <ul>
{message.map(m => {message.map((m) =>
typeof m === 'string' ? <li key={m}>{m}</li> : null typeof m === 'string' ? <li key={m}>{m}</li> : null
)} )}
</ul> </ul>
@@ -64,13 +64,11 @@ function ErrorDetail({ error }) {
message message
)} )}
</CardBody> </CardBody>
</Fragment> </>
); );
}; };
const renderStack = () => { const renderStack = () => <CardBody>{error.stack}</CardBody>;
return <CardBody>{error.stack}</CardBody>;
};
return ( return (
<Expandable <Expandable

View File

@@ -14,7 +14,7 @@ const Button = styled(PFButton)`
margin: 0; margin: 0;
height: 30px; height: 30px;
width: 30px; width: 30px;
${props => ${(props) =>
props.isActive props.isActive
? ` ? `
background-color: #007bba; background-color: #007bba;
@@ -33,7 +33,7 @@ const ToolbarItem = styled(PFToolbarItem)`
// with ExpandingContainer // with ExpandingContainer
function ExpandCollapse({ isCompact, onCompact, onExpand }) { function ExpandCollapse({ isCompact, onCompact, onExpand }) {
return ( return (
<Fragment> <>
<ToolbarItem> <ToolbarItem>
<Button <Button
ouiaId="toolbar-collapse-button" ouiaId="toolbar-collapse-button"
@@ -56,7 +56,7 @@ function ExpandCollapse({ isCompact, onCompact, onExpand }) {
<EqualsIcon /> <EqualsIcon />
</Button> </Button>
</ToolbarItem> </ToolbarItem>
</Fragment> </>
); );
} }

View File

@@ -5,33 +5,31 @@ import { t } from '@lingui/macro';
import { ActionGroup, Button } from '@patternfly/react-core'; import { ActionGroup, Button } from '@patternfly/react-core';
import { FormFullWidthLayout } from '../FormLayout'; import { FormFullWidthLayout } from '../FormLayout';
const FormActionGroup = ({ onCancel, onSubmit, submitDisabled }) => { const FormActionGroup = ({ onCancel, onSubmit, submitDisabled }) => (
return ( <FormFullWidthLayout>
<FormFullWidthLayout> <ActionGroup>
<ActionGroup> <Button
<Button ouiaId="form-save-button"
ouiaId="form-save-button" aria-label={t`Save`}
aria-label={t`Save`} variant="primary"
variant="primary" type="button"
type="button" onClick={onSubmit}
onClick={onSubmit} isDisabled={submitDisabled}
isDisabled={submitDisabled} >
> {t`Save`}
{t`Save`} </Button>
</Button> <Button
<Button ouiaId="form-cancel-button"
ouiaId="form-cancel-button" aria-label={t`Cancel`}
aria-label={t`Cancel`} variant="link"
variant="link" type="button"
type="button" onClick={onCancel}
onClick={onCancel} >
> {t`Cancel`}
{t`Cancel`} </Button>
</Button> </ActionGroup>
</ActionGroup> </FormFullWidthLayout>
</FormFullWidthLayout> );
);
};
FormActionGroup.propTypes = { FormActionGroup.propTypes = {
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,

View File

@@ -40,8 +40,8 @@ function ArrayTextField(props) {
{...rest} {...rest}
{...field} {...field}
value={value.join('\n')} value={value.join('\n')}
onChange={val => { onChange={(val) => {
helpers.setValue(val.split('\n').map(v => v.trim())); helpers.setValue(val.split('\n').map((v) => v.trim()));
}} }}
/> />
</FormGroup> </FormGroup>

View File

@@ -29,7 +29,7 @@ function FormSubmitError({ error }) {
isInline isInline
title={ title={
Array.isArray(errorMessage) Array.isArray(errorMessage)
? errorMessage.map(msg => <div key={msg}>{msg}</div>) ? errorMessage.map((msg) => <div key={msg}>{msg}</div>)
: errorMessage : errorMessage
} }
/> />

View File

@@ -28,7 +28,7 @@ export default function sortErrorMessages(error, formValues = {}) {
function parseFieldErrors(obj, formValues) { function parseFieldErrors(obj, formValues) {
let fieldErrors = {}; let fieldErrors = {};
let formErrors = []; let formErrors = [];
Object.keys(obj).forEach(key => { Object.keys(obj).forEach((key) => {
const value = obj[key]; const value = obj[key];
if (typeof value === 'string') { if (typeof value === 'string') {
if (typeof formValues[key] === 'undefined') { if (typeof formValues[key] === 'undefined') {

View File

@@ -13,12 +13,11 @@ import Popover from '../Popover';
const InventoryLookupField = ({ isDisabled }) => { const InventoryLookupField = ({ isDisabled }) => {
const { setFieldValue, setFieldTouched } = useFormikContext(); const { setFieldValue, setFieldTouched } = useFormikContext();
const [inventoryField, inventoryMeta, inventoryHelpers] = useField( const [inventoryField, inventoryMeta, inventoryHelpers] =
'inventory' useField('inventory');
);
const handleInventoryUpdate = useCallback( const handleInventoryUpdate = useCallback(
value => { (value) => {
setFieldValue('inventory', value); setFieldValue('inventory', value);
setFieldTouched('inventory', true, false); setFieldTouched('inventory', true, false);
}, },
@@ -76,55 +75,53 @@ const HostForm = ({
isInventoryVisible, isInventoryVisible,
submitError, submitError,
disableInventoryLookup, disableInventoryLookup,
}) => { }) => (
return ( <Formik
<Formik initialValues={{
initialValues={{ name: host.name,
name: host.name, description: host.description,
description: host.description, inventory: host.summary_fields?.inventory || null,
inventory: host.summary_fields?.inventory || null, variables: host.variables,
variables: host.variables, }}
}} onSubmit={handleSubmit}
onSubmit={handleSubmit} >
> {(formik) => (
{formik => ( <Form autoComplete="off" onSubmit={formik.handleSubmit}>
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <FormColumnLayout>
<FormColumnLayout> <FormField
<FormField id="host-name"
id="host-name" name="name"
name="name" type="text"
type="text" label={t`Name`}
label={t`Name`} validate={required(null)}
validate={required(null)} isRequired
isRequired />
<FormField
id="host-description"
name="description"
type="text"
label={t`Description`}
/>
{isInventoryVisible && (
<InventoryLookupField isDisabled={disableInventoryLookup} />
)}
<FormFullWidthLayout>
<VariablesField
id="host-variables"
name="variables"
label={t`Variables`}
/> />
<FormField </FormFullWidthLayout>
id="host-description" {submitError && <FormSubmitError error={submitError} />}
name="description" <FormActionGroup
type="text" onCancel={handleCancel}
label={t`Description`} onSubmit={formik.handleSubmit}
/> />
{isInventoryVisible && ( </FormColumnLayout>
<InventoryLookupField isDisabled={disableInventoryLookup} /> </Form>
)} )}
<FormFullWidthLayout> </Formik>
<VariablesField );
id="host-variables"
name="variables"
label={t`Variables`}
/>
</FormFullWidthLayout>
{submitError && <FormSubmitError error={submitError} />}
<FormActionGroup
onCancel={handleCancel}
onSubmit={formik.handleSubmit}
/>
</FormColumnLayout>
</Form>
)}
</Formik>
);
};
HostForm.propTypes = { HostForm.propTypes = {
handleCancel: func.isRequired, handleCancel: func.isRequired,

View File

@@ -1,5 +1,5 @@
import 'styled-components/macro'; import 'styled-components/macro';
import React, { Fragment, useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Switch, Tooltip } from '@patternfly/react-core'; import { Switch, Tooltip } from '@patternfly/react-core';
@@ -20,7 +20,12 @@ function HostToggle({
const [isEnabled, setIsEnabled] = useState(host.enabled); const [isEnabled, setIsEnabled] = useState(host.enabled);
const [showError, setShowError] = useState(false); const [showError, setShowError] = useState(false);
const { result, isLoading, error, request: toggleHost } = useRequest( const {
result,
isLoading,
error,
request: toggleHost,
} = useRequest(
useCallback(async () => { useCallback(async () => {
await HostsAPI.update(host.id, { await HostsAPI.update(host.id, {
enabled: !isEnabled, enabled: !isEnabled,
@@ -46,7 +51,7 @@ function HostToggle({
}, [error]); }, [error]);
return ( return (
<Fragment> <>
<Tooltip content={tooltip} position="top"> <Tooltip content={tooltip} position="top">
<Switch <Switch
className={className} className={className}
@@ -75,7 +80,7 @@ function HostToggle({
<ErrorDetail error={error} /> <ErrorDetail error={error} />
</AlertModal> </AlertModal>
)} )}
</Fragment> </>
); );
} }

View File

@@ -13,7 +13,12 @@ function InstanceToggle({ className, fetchInstances, instance, onToggle }) {
const [isEnabled, setIsEnabled] = useState(instance.enabled); const [isEnabled, setIsEnabled] = useState(instance.enabled);
const [showError, setShowError] = useState(false); const [showError, setShowError] = useState(false);
const { result, isLoading, error, request: toggleInstance } = useRequest( const {
result,
isLoading,
error,
request: toggleInstance,
} = useRequest(
useCallback(async () => { useCallback(async () => {
await InstancesAPI.update(instance.id, { enabled: !isEnabled }); await InstancesAPI.update(instance.id, { enabled: !isEnabled });
await fetchInstances(); await fetchInstances();

View File

@@ -23,9 +23,8 @@ function JobCancelButton({
}, [job.id, job.type]), }, [job.id, job.type]),
{} {}
); );
const { error, dismissError: dismissCancelError } = useDismissableError( const { error, dismissError: dismissCancelError } =
cancelError useDismissableError(cancelError);
);
const isAlreadyCancelled = cancelError?.response?.status === 405; const isAlreadyCancelled = cancelError?.response?.status === 405;

View File

@@ -58,10 +58,10 @@ function JobList({ defaultParams, showTypeColumn = false }) {
count: response.data.count, count: response.data.count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, },
[location] // eslint-disable-line react-hooks/exhaustive-deps [location] // eslint-disable-line react-hooks/exhaustive-deps
@@ -79,7 +79,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
// TODO: update QS_CONFIG to be safe for deps array // TODO: update QS_CONFIG to be safe for deps array
const fetchJobsById = useCallback( const fetchJobsById = useCallback(
async ids => { async (ids) => {
const params = parseQueryString(qsConfig, location.search); const params = parseQueryString(qsConfig, location.search);
params.id__in = ids.join(','); params.id__in = ids.join(',');
const { data } = await UnifiedJobsAPI.read(params); const { data } = await UnifiedJobsAPI.read(params);
@@ -90,40 +90,34 @@ function JobList({ defaultParams, showTypeColumn = false }) {
const jobs = useWsJobs(results, fetchJobsById, qsConfig); const jobs = useWsJobs(results, fetchJobsById, qsConfig);
const { const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
selected, useSelected(jobs);
isAllSelected,
handleSelect,
selectAll,
clearSelected,
} = useSelected(jobs);
const { expanded, isAllExpanded, handleExpand, expandAll } = useExpanded( const { expanded, isAllExpanded, handleExpand, expandAll } =
jobs useExpanded(jobs);
);
const { const {
error: cancelJobsError, error: cancelJobsError,
isLoading: isCancelLoading, isLoading: isCancelLoading,
request: cancelJobs, request: cancelJobs,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(
return Promise.all( async () =>
selected.map(job => { Promise.all(
if (isJobRunning(job.status)) { selected.map((job) => {
return getJobModel(job.type).cancel(job.id); if (isJobRunning(job.status)) {
} return getJobModel(job.type).cancel(job.id);
return Promise.resolve(); }
}) return Promise.resolve();
); })
}, [selected]), ),
[selected]
),
{} {}
); );
const { const { error: cancelError, dismissError: dismissCancelError } =
error: cancelError, useDismissableError(cancelJobsError);
dismissError: dismissCancelError,
} = useDismissableError(cancelJobsError);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
@@ -131,13 +125,13 @@ function JobList({ defaultParams, showTypeColumn = false }) {
deletionError, deletionError,
clearDeletionError, clearDeletionError,
} = useDeleteItems( } = useDeleteItems(
useCallback(() => { useCallback(
return Promise.all( () =>
selected.map(({ type, id }) => { Promise.all(
return getJobModel(type).destroy(id); selected.map(({ type, id }) => getJobModel(type).destroy(id))
}) ),
); [selected]
}, [selected]), ),
{ {
qsConfig, qsConfig,
allItemsSelected: isAllSelected, allItemsSelected: isAllSelected,
@@ -155,7 +149,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
clearSelected(); clearSelected();
}; };
const cannotDeleteItems = selected.filter(job => isJobRunning(job.status)); const cannotDeleteItems = selected.filter((job) => isJobRunning(job.status));
return ( return (
<> <>
@@ -229,7 +223,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
clearSelected={clearSelected} clearSelected={clearSelected}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={(props) => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}
isAllExpanded={isAllExpanded} isAllExpanded={isAllExpanded}
@@ -246,7 +240,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
return item; return item;
})} })}
pluralizedItemName={t`Jobs`} pluralizedItemName={t`Jobs`}
cannotDelete={item => cannotDelete={(item) =>
isJobRunning(item.status) || isJobRunning(item.status) ||
!item.summary_fields.user_capabilities.delete !item.summary_fields.user_capabilities.delete
} }
@@ -270,12 +264,12 @@ function JobList({ defaultParams, showTypeColumn = false }) {
<JobListItem <JobListItem
key={job.id} key={job.id}
job={job} job={job}
isExpanded={expanded.some(row => row.id === job.id)} isExpanded={expanded.some((row) => row.id === job.id)}
onExpand={() => handleExpand(job)} onExpand={() => handleExpand(job)}
isSuperUser={me?.is_superuser} isSuperUser={me?.is_superuser}
showTypeColumn={showTypeColumn} showTypeColumn={showTypeColumn}
onSelect={() => handleSelect(job)} onSelect={() => handleSelect(job)}
isSelected={selected.some(row => row.id === job.id)} isSelected={selected.some((row) => row.id === job.id)}
rowIndex={index} rowIndex={index}
/> />
)} )}

View File

@@ -120,7 +120,7 @@ function waitForLoaded(wrapper) {
return waitForElement( return waitForElement(
wrapper, wrapper,
'JobList', 'JobList',
el => el.find('ContentLoading').length === 0 (el) => el.find('ContentLoading').length === 0
); );
} }
@@ -167,35 +167,23 @@ describe('<JobList />', () => {
await waitForLoaded(wrapper); await waitForLoaded(wrapper);
act(() => { act(() => {
wrapper wrapper.find('JobListItem').first().invoke('onSelect')(mockItem);
.find('JobListItem')
.first()
.invoke('onSelect')(mockItem);
}); });
wrapper.update(); wrapper.update();
expect( expect(wrapper.find('JobListItem').first().prop('isSelected')).toEqual(
wrapper true
.find('JobListItem') );
.first()
.prop('isSelected')
).toEqual(true);
expect( expect(
wrapper.find('ToolbarDeleteButton').prop('itemsToDelete') wrapper.find('ToolbarDeleteButton').prop('itemsToDelete')
).toHaveLength(1); ).toHaveLength(1);
act(() => { act(() => {
wrapper wrapper.find('JobListItem').first().invoke('onSelect')(mockItem);
.find('JobListItem')
.first()
.invoke('onSelect')(mockItem);
}); });
wrapper.update(); wrapper.update();
expect( expect(wrapper.find('JobListItem').first().prop('isSelected')).toEqual(
wrapper false
.find('JobListItem') );
.first()
.prop('isSelected')
).toEqual(false);
expect( expect(
wrapper.find('ToolbarDeleteButton').prop('itemsToDelete') wrapper.find('ToolbarDeleteButton').prop('itemsToDelete')
).toHaveLength(0); ).toHaveLength(0);
@@ -344,10 +332,7 @@ describe('<JobList />', () => {
}); });
await waitForLoaded(wrapper); await waitForLoaded(wrapper);
await act(async () => { await act(async () => {
wrapper wrapper.find('JobListItem').at(1).invoke('onSelect')();
.find('JobListItem')
.at(1)
.invoke('onSelect')();
}); });
wrapper.update(); wrapper.update();
@@ -358,7 +343,7 @@ describe('<JobList />', () => {
await waitForElement( await waitForElement(
wrapper, wrapper,
'Modal', 'Modal',
el => el.props().isOpen === true && el.props().title === 'Error!' (el) => el.props().isOpen === true && el.props().title === 'Error!'
); );
}); });
@@ -411,10 +396,7 @@ describe('<JobList />', () => {
}); });
await waitForLoaded(wrapper); await waitForLoaded(wrapper);
await act(async () => { await act(async () => {
wrapper wrapper.find('JobListItem').at(1).invoke('onSelect')();
.find('JobListItem')
.at(1)
.invoke('onSelect')();
}); });
wrapper.update(); wrapper.update();
@@ -425,7 +407,7 @@ describe('<JobList />', () => {
await waitForElement( await waitForElement(
wrapper, wrapper,
'Modal', 'Modal',
el => el.props().isOpen === true && el.props().title === 'Error!' (el) => el.props().isOpen === true && el.props().title === 'Error!'
); );
}); });
}); });

View File

@@ -41,10 +41,10 @@ function JobListCancelButton({ jobsToCancel, onCancel }) {
const renderTooltip = () => { const renderTooltip = () => {
const cannotCancelPermissions = jobsToCancel const cannotCancelPermissions = jobsToCancel
.filter(cannotCancelBecausePermissions) .filter(cannotCancelBecausePermissions)
.map(job => job.name); .map((job) => job.name);
const cannotCancelNotRunning = jobsToCancel const cannotCancelNotRunning = jobsToCancel
.filter(cannotCancelBecauseNotRunning) .filter(cannotCancelBecauseNotRunning)
.map(job => job.name); .map((job) => job.name);
const numJobsUnableToCancel = cannotCancelPermissions.concat( const numJobsUnableToCancel = cannotCancelPermissions.concat(
cannotCancelNotRunning cannotCancelNotRunning
).length; ).length;
@@ -170,7 +170,7 @@ function JobListCancelButton({ jobsToCancel, onCancel }) {
other="This action will cancel the following jobs:" other="This action will cancel the following jobs:"
/> />
</div> </div>
{jobsToCancel.map(job => ( {jobsToCancel.map((job) => (
<span key={job.id}> <span key={job.id}>
<strong>{job.name}</strong> <strong>{job.name}</strong>
<br /> <br />

View File

@@ -222,7 +222,7 @@ function JobListItem({
label={t`Credentials`} label={t`Credentials`}
value={ value={
<ChipGroup numChips={5} totalChips={credentials.length}> <ChipGroup numChips={5} totalChips={credentials.length}>
{credentials.map(c => ( {credentials.map((c) => (
<CredentialChip key={c.id} credential={c} isReadOnly /> <CredentialChip key={c.id} credential={c} isReadOnly />
))} ))}
</ChipGroup> </ChipGroup>
@@ -235,7 +235,7 @@ function JobListItem({
label={t`Labels`} label={t`Labels`}
value={ value={
<ChipGroup numChips={5} totalChips={labels.results.length}> <ChipGroup numChips={5} totalChips={labels.results.length}>
{labels.results.map(l => ( {labels.results.map((l) => (
<Chip key={l.id} isReadOnly> <Chip key={l.id} isReadOnly>
{l.name} {l.name}
</Chip> </Chip>

View File

@@ -20,9 +20,9 @@ export default function useWsJobs(initialJobs, fetchJobsById, qsConfig) {
setJobs(initialJobs); setJobs(initialJobs);
}, [initialJobs]); }, [initialJobs]);
const enqueueJobId = id => { const enqueueJobId = (id) => {
if (!jobsToFetch.includes(id)) { if (!jobsToFetch.includes(id)) {
setJobsToFetch(ids => ids.concat(id)); setJobsToFetch((ids) => ids.concat(id));
} }
}; };
useEffect(() => { useEffect(() => {
@@ -33,7 +33,7 @@ export default function useWsJobs(initialJobs, fetchJobsById, qsConfig) {
setJobsToFetch([]); setJobsToFetch([]);
const newJobs = await fetchJobsById(throttledJobsToFetch); const newJobs = await fetchJobsById(throttledJobsToFetch);
const deduplicated = newJobs.filter( const deduplicated = newJobs.filter(
job => !jobs.find(j => j.id === job.id) (job) => !jobs.find((j) => j.id === job.id)
); );
if (deduplicated.length) { if (deduplicated.length) {
const params = parseQueryString(qsConfig, location.search); const params = parseQueryString(qsConfig, location.search);
@@ -56,7 +56,7 @@ export default function useWsJobs(initialJobs, fetchJobsById, qsConfig) {
} }
const jobId = lastMessage.unified_job_id; const jobId = lastMessage.unified_job_id;
const index = jobs.findIndex(j => j.id === jobId); const index = jobs.findIndex((j) => j.id === jobId);
if (index > -1) { if (index > -1) {
setJobs(sortJobs(updateJob(jobs, index, lastMessage), params)); setJobs(sortJobs(updateJob(jobs, index, lastMessage), params));
} else { } else {

View File

@@ -23,7 +23,7 @@ describe('useWsJobs hook', () => {
*/ */
jest.mock('../../hooks/useThrottle', () => ({ jest.mock('../../hooks/useThrottle', () => ({
__esModule: true, __esModule: true,
default: jest.fn(val => val), default: jest.fn((val) => val),
})); }));
debug = global.console.debug; // eslint-disable-line prefer-destructuring debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {}; global.console.debug = () => {};

View File

@@ -1,4 +1,4 @@
import React, { Fragment, useState } from 'react'; import React, { useState } from 'react';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { number, shape } from 'prop-types'; import { number, shape } from 'prop-types';
@@ -71,7 +71,7 @@ function LaunchButton({ resource, children, history }) {
} }
}; };
const launchWithParams = async params => { const launchWithParams = async (params) => {
try { try {
let jobPromise; let jobPromise;
@@ -94,7 +94,7 @@ function LaunchButton({ resource, children, history }) {
} }
}; };
const handleRelaunch = async params => { const handleRelaunch = async (params) => {
let readRelaunch; let readRelaunch;
let relaunch; let relaunch;
@@ -148,7 +148,7 @@ function LaunchButton({ resource, children, history }) {
}; };
return ( return (
<Fragment> <>
{children({ {children({
handleLaunch, handleLaunch,
handleRelaunch, handleRelaunch,
@@ -174,7 +174,7 @@ function LaunchButton({ resource, children, history }) {
onCancel={() => setShowLaunchPrompt(false)} onCancel={() => setShowLaunchPrompt(false)}
/> />
)} )}
</Fragment> </>
); );
} }

View File

@@ -21,7 +21,7 @@ function ReLaunchDropDown({
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const onToggle = () => { const onToggle = () => {
setIsOpen(prev => !prev); setIsOpen((prev) => !prev);
}; };
const dropdownItems = [ const dropdownItems = [

View File

@@ -41,7 +41,7 @@ function PromptModalForm({
setValue('inventory_id', values.inventory?.id); setValue('inventory_id', values.inventory?.id);
setValue( setValue(
'credentials', 'credentials',
values.credentials?.map(c => c.id) values.credentials?.map((c) => c.id)
); );
setValue('job_type', values.job_type); setValue('job_type', values.job_type);
setValue('limit', values.limit); setValue('limit', values.limit);
@@ -78,7 +78,7 @@ function PromptModalForm({
isOpen isOpen
onClose={onCancel} onClose={onCancel}
onSave={handleSubmit} onSave={handleSubmit}
onBack={async nextStep => { onBack={async (nextStep) => {
validateStep(nextStep.id); validateStep(nextStep.id);
}} }}
onNext={async (nextStep, prevStep) => { onNext={async (nextStep, prevStep) => {
@@ -141,9 +141,9 @@ function LaunchPrompt({
resourceDefaultCredentials = [], resourceDefaultCredentials = [],
}) { }) {
return ( return (
<Formik initialValues={{}} onSubmit={values => onLaunch(values)}> <Formik initialValues={{}} onSubmit={(values) => onLaunch(values)}>
<PromptModalForm <PromptModalForm
onSubmit={values => onLaunch(values)} onSubmit={(values) => onLaunch(values)}
onCancel={onCancel} onCancel={onCancel}
launchConfig={launchConfig} launchConfig={launchConfig}
surveyConfig={surveyConfig} surveyConfig={surveyConfig}

View File

@@ -19,7 +19,7 @@ function CredentialPasswordsStep({ launchConfig }) {
!launchConfig.ask_credential_on_launch && !launchConfig.ask_credential_on_launch &&
launchConfig.passwords_needed_to_start launchConfig.passwords_needed_to_start
) { ) {
launchConfig.passwords_needed_to_start.forEach(password => { launchConfig.passwords_needed_to_start.forEach((password) => {
if (password === 'ssh_password') { if (password === 'ssh_password') {
showcredentialPasswordSsh = true; showcredentialPasswordSsh = true;
} else if (password === 'become_password') { } else if (password === 'become_password') {
@@ -32,10 +32,10 @@ function CredentialPasswordsStep({ launchConfig }) {
} }
}); });
} else if (credentials) { } else if (credentials) {
credentials.forEach(credential => { credentials.forEach((credential) => {
if (!credential.inputs) { if (!credential.inputs) {
const launchConfigCredential = launchConfig.defaults.credentials.find( const launchConfigCredential = launchConfig.defaults.credentials.find(
defaultCred => defaultCred.id === credential.id (defaultCred) => defaultCred.id === credential.id
); );
if (launchConfigCredential?.passwords_needed.length > 0) { if (launchConfigCredential?.passwords_needed.length > 0) {
@@ -56,10 +56,10 @@ function CredentialPasswordsStep({ launchConfig }) {
} }
const vaultPasswordIds = launchConfigCredential.passwords_needed const vaultPasswordIds = launchConfigCredential.passwords_needed
.filter(passwordNeeded => .filter((passwordNeeded) =>
passwordNeeded.startsWith('vault_password') passwordNeeded.startsWith('vault_password')
) )
.map(vaultPassword => vaultPassword.split(/\.(.+)/)[1] || ''); .map((vaultPassword) => vaultPassword.split(/\.(.+)/)[1] || '');
vaultsThatPrompt.push(...vaultPasswordIds); vaultsThatPrompt.push(...vaultPasswordIds);
} }
@@ -85,7 +85,7 @@ function CredentialPasswordsStep({ launchConfig }) {
return ( return (
<Form <Form
onSubmit={e => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
}} }}
> >
@@ -113,7 +113,7 @@ function CredentialPasswordsStep({ launchConfig }) {
isRequired isRequired
/> />
)} )}
{vaultsThatPrompt.map(credId => ( {vaultsThatPrompt.map((credId) => (
<PasswordField <PasswordField
id={`launch-vault-password-${credId}`} id={`launch-vault-password-${credId}`}
key={credId} key={credId}

View File

@@ -32,13 +32,12 @@ function CredentialsStep({
}) { }) {
const [field, meta, helpers] = useField({ const [field, meta, helpers] = useField({
name: 'credentials', name: 'credentials',
validate: val => { validate: (val) =>
return credentialsValidator( credentialsValidator(
defaultCredentials, defaultCredentials,
allowCredentialsWithPasswords, allowCredentialsWithPasswords,
val val
); ),
},
}); });
const [selectedType, setSelectedType] = useState(null); const [selectedType, setSelectedType] = useState(null);
const history = useHistory(); const history = useHistory();
@@ -53,7 +52,7 @@ function CredentialsStep({
const loadedTypes = await CredentialTypesAPI.loadAllTypes(); const loadedTypes = await CredentialTypesAPI.loadAllTypes();
if (loadedTypes.length) { if (loadedTypes.length) {
const match = const match =
loadedTypes.find(type => type.kind === 'ssh') || loadedTypes[0]; loadedTypes.find((type) => type.kind === 'ssh') || loadedTypes[0];
setSelectedType(match); setSelectedType(match);
} }
return loadedTypes; return loadedTypes;
@@ -88,10 +87,10 @@ function CredentialsStep({
count: data.count, count: data.count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [selectedType, history.location.search]), }, [selectedType, history.location.search]),
{ credentials: [], count: 0, relatedSearchableKeys: [], searchableKeys: [] } { credentials: [], count: 0, relatedSearchableKeys: [], searchableKeys: [] }
@@ -122,17 +121,15 @@ function CredentialsStep({
const isVault = selectedType?.kind === 'vault'; const isVault = selectedType?.kind === 'vault';
const renderChip = ({ item, removeItem, canDelete }) => { const renderChip = ({ item, removeItem, canDelete }) => (
return ( <CredentialChip
<CredentialChip id={`credential-chip-${item.id}`}
id={`credential-chip-${item.id}`} key={item.id}
key={item.id} onClick={() => removeItem(item)}
onClick={() => removeItem(item)} isReadOnly={!canDelete}
isReadOnly={!canDelete} credential={item}
credential={item} />
/> );
);
};
return ( return (
<> <>
@@ -148,7 +145,7 @@ function CredentialsStep({
css="flex: 1 1 75%;" css="flex: 1 1 75%;"
id="multiCredentialsLookUp-select" id="multiCredentialsLookUp-select"
label={t`Selected Category`} label={t`Selected Category`}
data={types.map(type => ({ data={types.map((type) => ({
key: type.id, key: type.id,
value: type.id, value: type.id,
label: type.name, label: type.name,
@@ -156,7 +153,7 @@ function CredentialsStep({
}))} }))}
value={selectedType && selectedType.id} value={selectedType && selectedType.id}
onChange={(e, id) => { onChange={(e, id) => {
setSelectedType(types.find(o => o.id === parseInt(id, 10))); setSelectedType(types.find((o) => o.id === parseInt(id, 10)));
}} }}
/> />
</ToolbarItem> </ToolbarItem>
@@ -194,20 +191,20 @@ function CredentialsStep({
name="credentials" name="credentials"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
readOnly={false} readOnly={false}
selectItem={item => { selectItem={(item) => {
const hasSameVaultID = val => const hasSameVaultID = (val) =>
val?.inputs?.vault_id !== undefined && val?.inputs?.vault_id !== undefined &&
val?.inputs?.vault_id === item?.inputs?.vault_id; val?.inputs?.vault_id === item?.inputs?.vault_id;
const hasSameCredentialType = val => const hasSameCredentialType = (val) =>
val.credential_type === item.credential_type; val.credential_type === item.credential_type;
const newItems = field.value.filter(i => const newItems = field.value.filter((i) =>
isVault ? !hasSameVaultID(i) : !hasSameCredentialType(i) isVault ? !hasSameVaultID(i) : !hasSameCredentialType(i)
); );
newItems.push(item); newItems.push(item);
helpers.setValue(newItems); helpers.setValue(newItems);
}} }}
deselectItem={item => { deselectItem={(item) => {
helpers.setValue(field.value.filter(i => i.id !== item.id)); helpers.setValue(field.value.filter((i) => i.id !== item.id));
}} }}
renderItemChip={renderChip} renderItemChip={renderChip}
/> />

View File

@@ -180,19 +180,11 @@ describe('CredentialsStep', () => {
wrapper.update(); wrapper.update();
expect(wrapper.find('Alert').length).toBe(0); expect(wrapper.find('Alert').length).toBe(0);
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-2').find('input').simulate('click');
.find('td#check-action-item-2')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
expect(wrapper.find('Alert').length).toBe(1); expect(wrapper.find('Alert').length).toBe(1);
expect( expect(wrapper.find('Alert').text().includes('Cred 2')).toBe(true);
wrapper
.find('Alert')
.text()
.includes('Cred 2')
).toBe(true);
}); });
test('error should be toggled when default machine credential is removed and then replaced', async () => { test('error should be toggled when default machine credential is removed and then replaced', async () => {
@@ -234,17 +226,9 @@ describe('CredentialsStep', () => {
}); });
wrapper.update(); wrapper.update();
expect(wrapper.find('Alert').length).toBe(1); expect(wrapper.find('Alert').length).toBe(1);
expect( expect(wrapper.find('Alert').text().includes('Machine')).toBe(true);
wrapper
.find('Alert')
.text()
.includes('Machine')
).toBe(true);
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-5').find('input').simulate('click');
.find('td#check-action-item-5')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
expect(wrapper.find('Alert').length).toBe(0); expect(wrapper.find('Alert').length).toBe(0);
@@ -307,21 +291,13 @@ describe('CredentialsStep', () => {
wrapper.update(); wrapper.update();
expect(wrapper.find('CredentialChip').length).toBe(1); expect(wrapper.find('CredentialChip').length).toBe(1);
expect(wrapper.find('Alert').length).toBe(1); expect(wrapper.find('Alert').length).toBe(1);
expect( expect(wrapper.find('Alert').text().includes('Vault | foo')).toBe(true);
wrapper
.find('Alert')
.text()
.includes('Vault | foo')
).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect').invoke('onChange')({}, 3); wrapper.find('AnsibleSelect').invoke('onChange')({}, 3);
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {
wrapper wrapper.find('td#check-action-item-33').find('input').simulate('click');
.find('td#check-action-item-33')
.find('input')
.simulate('click');
}); });
wrapper.update(); wrapper.update();
expect(wrapper.find('Alert').length).toBe(0); expect(wrapper.find('Alert').length).toBe(0);

View File

@@ -44,10 +44,10 @@ function InventoryStep({ warningMessage = null }) {
count: data.count, count: data.count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [history.location]), }, [history.location]),
{ {

View File

@@ -23,7 +23,7 @@ const FieldHeader = styled.div`
function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) { function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
return ( return (
<Form <Form
onSubmit={e => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
}} }}
> >

View File

@@ -37,8 +37,8 @@ function PreviewStep({ resource, launchConfig, surveyConfig, formErrors }) {
launchConfig.ask_variables_on_launch && (overrides.extra_vars || '---'); launchConfig.ask_variables_on_launch && (overrides.extra_vars || '---');
if (surveyConfig?.spec) { if (surveyConfig?.spec) {
const passwordFields = surveyConfig.spec const passwordFields = surveyConfig.spec
.filter(q => q.type === 'password') .filter((q) => q.type === 'password')
.map(q => q.variable); .map((q) => q.variable);
const masked = maskPasswords(surveyValues, passwordFields); const masked = maskPasswords(surveyValues, passwordFields);
overrides.extra_vars = yaml.safeDump( overrides.extra_vars = yaml.safeDump(
mergeExtraVars(initialExtraVars, masked) mergeExtraVars(initialExtraVars, masked)
@@ -54,7 +54,7 @@ function PreviewStep({ resource, launchConfig, surveyConfig, formErrors }) {
} }
return ( return (
<Fragment> <>
{formErrors && ( {formErrors && (
<ErrorMessageWrapper> <ErrorMessageWrapper>
{t`Some of the previous step(s) have errors`} {t`Some of the previous step(s) have errors`}
@@ -72,7 +72,7 @@ function PreviewStep({ resource, launchConfig, surveyConfig, formErrors }) {
launchConfig={launchConfig} launchConfig={launchConfig}
overrides={overrides} overrides={overrides}
/> />
</Fragment> </>
); );
} }

View File

@@ -32,11 +32,11 @@ function SurveyStep({ surveyConfig }) {
}; };
return ( return (
<Form <Form
onSubmit={e => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
}} }}
> >
{surveyConfig.spec.map(question => { {surveyConfig.spec.map((question) => {
const Field = fieldTypes[question.type]; const Field = fieldTypes[question.type];
return <Field key={question.variable} question={question} />; return <Field key={question.variable} question={question} />;
})} })}
@@ -131,7 +131,7 @@ function MultipleChoiceField({ question }) {
helpers.setValue(''); helpers.setValue('');
}} }}
> >
{options.map(opt => ( {options.map((opt) => (
<SelectOption key={opt} value={opt} /> <SelectOption key={opt} value={opt} />
))} ))}
</Select> </Select>
@@ -175,7 +175,7 @@ function MultiSelectField({ question }) {
onToggle={setIsOpen} onToggle={setIsOpen}
onSelect={(event, option) => { onSelect={(event, option) => {
if (field?.value?.includes(option)) { if (field?.value?.includes(option)) {
helpers.setValue(field.value.filter(o => o !== option)); helpers.setValue(field.value.filter((o) => o !== option));
} else { } else {
helpers.setValue(field.value.concat(option)); helpers.setValue(field.value.concat(option));
} }
@@ -188,7 +188,7 @@ function MultiSelectField({ question }) {
helpers.setValue([]); helpers.setValue([]);
}} }}
> >
{options.map(opt => ( {options.map((opt) => (
<SelectOption key={opt} value={opt} /> <SelectOption key={opt} value={opt} />
))} ))}
</Select> </Select>

View File

@@ -1,6 +1,6 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
const credentialPromptsForPassword = credential => const credentialPromptsForPassword = (credential) =>
credential?.inputs?.password === 'ASK' || credential?.inputs?.password === 'ASK' ||
credential?.inputs?.ssh_key_unlock === 'ASK' || credential?.inputs?.ssh_key_unlock === 'ASK' ||
credential?.inputs?.become_password === 'ASK' || credential?.inputs?.become_password === 'ASK' ||
@@ -13,10 +13,10 @@ export default function credentialsValidator(
) { ) {
if (defaultCredentials.length > 0 && selectedCredentials) { if (defaultCredentials.length > 0 && selectedCredentials) {
const missingCredentialTypes = []; const missingCredentialTypes = [];
defaultCredentials.forEach(defaultCredential => { defaultCredentials.forEach((defaultCredential) => {
if ( if (
!selectedCredentials.find(selectedCredential => { !selectedCredentials.find(
return ( (selectedCredential) =>
(selectedCredential?.credential_type === (selectedCredential?.credential_type ===
defaultCredential?.credential_type && defaultCredential?.credential_type &&
!selectedCredential.inputs?.vault_id && !selectedCredential.inputs?.vault_id &&
@@ -24,8 +24,7 @@ export default function credentialsValidator(
(defaultCredential.inputs?.vault_id && (defaultCredential.inputs?.vault_id &&
selectedCredential.inputs?.vault_id === selectedCredential.inputs?.vault_id ===
defaultCredential.inputs?.vault_id) defaultCredential.inputs?.vault_id)
); )
})
) { ) {
missingCredentialTypes.push( missingCredentialTypes.push(
defaultCredential.inputs?.vault_id defaultCredential.inputs?.vault_id
@@ -44,7 +43,7 @@ export default function credentialsValidator(
if (!allowCredentialsWithPasswords && selectedCredentials) { if (!allowCredentialsWithPasswords && selectedCredentials) {
const credentialsThatPrompt = []; const credentialsThatPrompt = [];
selectedCredentials.forEach(selectedCredential => { selectedCredentials.forEach((selectedCredential) => {
if (credentialPromptsForPassword(selectedCredential)) { if (credentialPromptsForPassword(selectedCredential)) {
credentialsThatPrompt.push(selectedCredential.name); credentialsThatPrompt.push(selectedCredential.name);
} }

View File

@@ -6,9 +6,7 @@ import StepName from './StepName';
const STEP_ID = 'credentialPasswords'; const STEP_ID = 'credentialPasswords';
const isValueMissing = val => { const isValueMissing = (val) => !val || val === '';
return !val || val === '';
};
export default function useCredentialPasswordsStep( export default function useCredentialPasswordsStep(
launchConfig, launchConfig,
@@ -38,8 +36,8 @@ export default function useCredentialPasswordsStep(
isReady: true, isReady: true,
contentError: null, contentError: null,
hasError, hasError,
setTouched: setFieldTouched => { setTouched: (setFieldTouched) => {
Object.keys(values.credential_passwords).forEach(credentialValueKey => Object.keys(values.credential_passwords).forEach((credentialValueKey) =>
setFieldTouched( setFieldTouched(
`credential_passwords['${credentialValueKey}']`, `credential_passwords['${credentialValueKey}']`,
true, true,
@@ -48,7 +46,7 @@ export default function useCredentialPasswordsStep(
); );
}, },
validate: () => { validate: () => {
const setPasswordFieldError = fieldName => { const setPasswordFieldError = (fieldName) => {
setFieldError(fieldName, t`This field may not be blank`); setFieldError(fieldName, t`This field may not be blank`);
}; };
@@ -56,20 +54,21 @@ export default function useCredentialPasswordsStep(
!launchConfig.ask_credential_on_launch && !launchConfig.ask_credential_on_launch &&
launchConfig.passwords_needed_to_start launchConfig.passwords_needed_to_start
) { ) {
launchConfig.passwords_needed_to_start.forEach(password => { launchConfig.passwords_needed_to_start.forEach((password) => {
if (isValueMissing(values.credential_passwords[password])) { if (isValueMissing(values.credential_passwords[password])) {
setPasswordFieldError(`credential_passwords['${password}']`); setPasswordFieldError(`credential_passwords['${password}']`);
} }
}); });
} else if (values.credentials) { } else if (values.credentials) {
values.credentials.forEach(credential => { values.credentials.forEach((credential) => {
if (!credential.inputs) { if (!credential.inputs) {
const launchConfigCredential = launchConfig.defaults.credentials.find( const launchConfigCredential =
defaultCred => defaultCred.id === credential.id launchConfig.defaults.credentials.find(
); (defaultCred) => defaultCred.id === credential.id
);
if (launchConfigCredential?.passwords_needed.length > 0) { if (launchConfigCredential?.passwords_needed.length > 0) {
launchConfigCredential.passwords_needed.forEach(password => { launchConfigCredential.passwords_needed.forEach((password) => {
if (isValueMissing(values.credential_passwords[password])) { if (isValueMissing(values.credential_passwords[password])) {
setPasswordFieldError(`credential_passwords['${password}']`); setPasswordFieldError(`credential_passwords['${password}']`);
} }
@@ -137,20 +136,20 @@ function getInitialValues(launchConfig, selectedCredentials = []) {
!launchConfig.ask_credential_on_launch && !launchConfig.ask_credential_on_launch &&
launchConfig.passwords_needed_to_start launchConfig.passwords_needed_to_start
) { ) {
launchConfig.passwords_needed_to_start.forEach(password => { launchConfig.passwords_needed_to_start.forEach((password) => {
initialValues.credential_passwords[password] = ''; initialValues.credential_passwords[password] = '';
}); });
return initialValues; return initialValues;
} }
selectedCredentials.forEach(credential => { selectedCredentials.forEach((credential) => {
if (!credential.inputs) { if (!credential.inputs) {
const launchConfigCredential = launchConfig.defaults.credentials.find( const launchConfigCredential = launchConfig.defaults.credentials.find(
defaultCred => defaultCred.id === credential.id (defaultCred) => defaultCred.id === credential.id
); );
if (launchConfigCredential?.passwords_needed.length > 0) { if (launchConfigCredential?.passwords_needed.length > 0) {
launchConfigCredential.passwords_needed.forEach(password => { launchConfigCredential.passwords_needed.forEach((password) => {
initialValues.credential_passwords[password] = ''; initialValues.credential_passwords[password] = '';
}); });
} }
@@ -189,20 +188,20 @@ function checkForError(launchConfig, values) {
!launchConfig.ask_credential_on_launch && !launchConfig.ask_credential_on_launch &&
launchConfig.passwords_needed_to_start launchConfig.passwords_needed_to_start
) { ) {
launchConfig.passwords_needed_to_start.forEach(password => { launchConfig.passwords_needed_to_start.forEach((password) => {
if (isValueMissing(values.credential_passwords[password])) { if (isValueMissing(values.credential_passwords[password])) {
hasError = true; hasError = true;
} }
}); });
} else if (values.credentials) { } else if (values.credentials) {
values.credentials.forEach(credential => { values.credentials.forEach((credential) => {
if (!credential.inputs) { if (!credential.inputs) {
const launchConfigCredential = launchConfig.defaults.credentials.find( const launchConfigCredential = launchConfig.defaults.credentials.find(
defaultCred => defaultCred.id === credential.id (defaultCred) => defaultCred.id === credential.id
); );
if (launchConfigCredential?.passwords_needed.length > 0) { if (launchConfigCredential?.passwords_needed.length > 0) {
launchConfigCredential.passwords_needed.forEach(password => { launchConfigCredential.passwords_needed.forEach((password) => {
if (isValueMissing(values.credential_passwords[password])) { if (isValueMissing(values.credential_passwords[password])) {
hasError = true; hasError = true;
} }

View File

@@ -29,7 +29,7 @@ export default function useCredentialsStep(
isReady: true, isReady: true,
contentError: null, contentError: null,
hasError: launchConfig.ask_credential_on_launch && formError, hasError: launchConfig.ask_credential_on_launch && formError,
setTouched: setFieldTouched => { setTouched: (setFieldTouched) => {
setFieldTouched('credentials', true, false); setFieldTouched('credentials', true, false);
}, },
validate: () => { validate: () => {

View File

@@ -27,7 +27,7 @@ export default function useInventoryStep(launchConfig, resource, visitedSteps) {
isReady: true, isReady: true,
contentError: null, contentError: null,
hasError: launchConfig.ask_inventory_on_launch && formError, hasError: launchConfig.ask_inventory_on_launch && formError,
setTouched: setFieldTouched => { setTouched: (setFieldTouched) => {
setFieldTouched('inventory', true, false); setFieldTouched('inventory', true, false);
}, },
validate: () => { validate: () => {

View File

@@ -9,7 +9,7 @@ const STEP_ID = 'other';
export const YAML_MODE = 'yaml'; export const YAML_MODE = 'yaml';
export const JSON_MODE = 'javascript'; export const JSON_MODE = 'javascript';
const getVariablesData = resource => { const getVariablesData = (resource) => {
if (resource?.extra_data) { if (resource?.extra_data) {
return jsonToYaml(JSON.stringify(resource.extra_data)); return jsonToYaml(JSON.stringify(resource.extra_data));
} }
@@ -34,7 +34,7 @@ export default function useOtherPromptsStep(launchConfig, resource) {
const [variablesMode, setVariablesMode] = useState(null); const [variablesMode, setVariablesMode] = useState(null);
const [isTouched, setIsTouched] = useState(false); const [isTouched, setIsTouched] = useState(false);
const handleModeChange = mode => { const handleModeChange = (mode) => {
setVariablesMode(mode); setVariablesMode(mode);
}; };
@@ -63,9 +63,11 @@ export default function useOtherPromptsStep(launchConfig, resource) {
isReady: true, isReady: true,
contentError: null, contentError: null,
hasError, hasError,
setTouched: setFieldTouched => { setTouched: (setFieldTouched) => {
setIsTouched(true); setIsTouched(true);
FIELD_NAMES.forEach(fieldName => setFieldTouched(fieldName, true, false)); FIELD_NAMES.forEach((fieldName) =>
setFieldTouched(fieldName, true, false)
);
}, },
validate: () => {}, validate: () => {},
}; };

View File

@@ -35,17 +35,17 @@ export default function useSurveyStep(
isReady: true, isReady: true,
contentError: null, contentError: null,
hasError, hasError,
setTouched: setFieldTouched => { setTouched: (setFieldTouched) => {
if (!surveyConfig?.spec) { if (!surveyConfig?.spec) {
return; return;
} }
surveyConfig.spec.forEach(question => { surveyConfig.spec.forEach((question) => {
setFieldTouched(`survey_${question.variable}`, true, false); setFieldTouched(`survey_${question.variable}`, true, false);
}); });
}, },
validate: () => { validate: () => {
if (launchConfig.survey_enabled && surveyConfig.spec) { if (launchConfig.survey_enabled && surveyConfig.spec) {
surveyConfig.spec.forEach(question => { surveyConfig.spec.forEach((question) => {
const errMessage = validateSurveyField( const errMessage = validateSurveyField(
question, question,
values[`survey_${question.variable}`] values[`survey_${question.variable}`]
@@ -66,7 +66,7 @@ function getInitialValues(launchConfig, surveyConfig, resource) {
const values = {}; const values = {};
if (surveyConfig?.spec) { if (surveyConfig?.spec) {
surveyConfig.spec.forEach(question => { surveyConfig.spec.forEach((question) => {
if (question.type === 'multiselect') { if (question.type === 'multiselect') {
values[`survey_${question.variable}`] = question.default values[`survey_${question.variable}`] = question.default
? question.default.split('\n') ? question.default.split('\n')
@@ -116,7 +116,7 @@ function validateSurveyField(question, value) {
function checkForError(launchConfig, surveyConfig, values) { function checkForError(launchConfig, surveyConfig, values) {
let hasError = false; let hasError = false;
if (launchConfig.survey_enabled && surveyConfig.spec) { if (launchConfig.survey_enabled && surveyConfig.spec) {
surveyConfig.spec.forEach(question => { surveyConfig.spec.forEach((question) => {
const value = values[`survey_${question.variable}`]; const value = values[`survey_${question.variable}`];
const isTextField = ['text', 'textarea'].includes(question.type); const isTextField = ['text', 'textarea'].includes(question.type);
const isNumeric = ['integer', 'float'].includes(question.type); const isNumeric = ['integer', 'float'].includes(question.type);

View File

@@ -17,10 +17,10 @@ function showCredentialPasswordsStep(credentials = [], launchConfig) {
let credentialPasswordStepRequired = false; let credentialPasswordStepRequired = false;
credentials.forEach(credential => { credentials.forEach((credential) => {
if (!credential.inputs) { if (!credential.inputs) {
const launchConfigCredential = launchConfig.defaults.credentials.find( const launchConfigCredential = launchConfig.defaults.credentials.find(
defaultCred => defaultCred.id === credential.id (defaultCred) => defaultCred.id === credential.id
); );
if (launchConfigCredential?.passwords_needed.length > 0) { if (launchConfigCredential?.passwords_needed.length > 0) {
@@ -60,30 +60,31 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
useSurveyStep(launchConfig, surveyConfig, resource, visited), useSurveyStep(launchConfig, surveyConfig, resource, visited),
]; ];
const { resetForm } = useFormikContext(); const { resetForm } = useFormikContext();
const hasErrors = steps.some(step => step.hasError); const hasErrors = steps.some((step) => step.hasError);
steps.push( steps.push(
usePreviewStep(launchConfig, resource, surveyConfig, hasErrors, true) usePreviewStep(launchConfig, resource, surveyConfig, hasErrors, true)
); );
const pfSteps = steps.map(s => s.step).filter(s => s != null); const pfSteps = steps.map((s) => s.step).filter((s) => s != null);
const stepsAreReady = !steps.some(s => !s.isReady); const stepsAreReady = !steps.some((s) => !s.isReady);
useEffect(() => { useEffect(() => {
if (!stepsAreReady) { if (!stepsAreReady) {
return; return;
} }
const initialValues = steps.reduce((acc, cur) => { const initialValues = steps.reduce(
return { (acc, cur) => ({
...acc, ...acc,
...cur.initialValues, ...cur.initialValues,
}; }),
}, {}); {}
);
const newFormValues = { ...initialValues }; const newFormValues = { ...initialValues };
Object.keys(formikValues).forEach(formikValueKey => { Object.keys(formikValues).forEach((formikValueKey) => {
if ( if (
formikValueKey === 'credential_passwords' && formikValueKey === 'credential_passwords' &&
Object.prototype.hasOwnProperty.call( Object.prototype.hasOwnProperty.call(
@@ -93,7 +94,7 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
) { ) {
const formikCredentialPasswords = formikValues.credential_passwords; const formikCredentialPasswords = formikValues.credential_passwords;
Object.keys(formikCredentialPasswords).forEach( Object.keys(formikCredentialPasswords).forEach(
credentialPasswordValueKey => { (credentialPasswordValueKey) => {
if ( if (
Object.prototype.hasOwnProperty.call( Object.prototype.hasOwnProperty.call(
newFormValues.credential_passwords, newFormValues.credential_passwords,
@@ -121,23 +122,23 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [formikValues.credentials, stepsAreReady]); }, [formikValues.credentials, stepsAreReady]);
const stepWithError = steps.find(s => s.contentError); const stepWithError = steps.find((s) => s.contentError);
const contentError = stepWithError ? stepWithError.contentError : null; const contentError = stepWithError ? stepWithError.contentError : null;
return { return {
steps: pfSteps, steps: pfSteps,
isReady, isReady,
validateStep: stepId => { validateStep: (stepId) => {
steps.find(s => s?.step?.id === stepId).validate(); steps.find((s) => s?.step?.id === stepId).validate();
}, },
visitStep: (prevStepId, setFieldTouched) => { visitStep: (prevStepId, setFieldTouched) => {
setVisited({ setVisited({
...visited, ...visited,
[prevStepId]: true, [prevStepId]: true,
}); });
steps.find(s => s?.step?.id === prevStepId).setTouched(setFieldTouched); steps.find((s) => s?.step?.id === prevStepId).setTouched(setFieldTouched);
}, },
visitAllSteps: setFieldTouched => { visitAllSteps: (setFieldTouched) => {
setVisited({ setVisited({
inventory: true, inventory: true,
credentials: true, credentials: true,
@@ -146,7 +147,7 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
survey: true, survey: true,
preview: true, preview: true,
}); });
steps.forEach(s => s.setTouched(setFieldTouched)); steps.forEach((s) => s.setTouched(setFieldTouched));
}, },
contentError, contentError,
}; };

View File

@@ -66,7 +66,7 @@ class ListHeader extends React.Component {
handleRemoveAll() { handleRemoveAll() {
const { location, qsConfig } = this.props; const { location, qsConfig } = this.props;
const oldParams = parseQueryString(qsConfig, location.search); const oldParams = parseQueryString(qsConfig, location.search);
Object.keys(oldParams).forEach(key => { Object.keys(oldParams).forEach((key) => {
oldParams[key] = null; oldParams[key] = null;
}); });
delete oldParams.page_size; delete oldParams.page_size;
@@ -106,7 +106,7 @@ class ListHeader extends React.Component {
const params = parseQueryString(qsConfig, location.search); const params = parseQueryString(qsConfig, location.search);
const isEmpty = itemCount === 0 && Object.keys(params).length === 0; const isEmpty = itemCount === 0 && Object.keys(params).length === 0;
return ( return (
<Fragment> <>
{isEmpty ? ( {isEmpty ? (
<Toolbar <Toolbar
id={`${qsConfig.namespace}-list-toolbar`} id={`${qsConfig.namespace}-list-toolbar`}
@@ -120,7 +120,7 @@ class ListHeader extends React.Component {
</ToolbarContent> </ToolbarContent>
</Toolbar> </Toolbar>
) : ( ) : (
<Fragment> <>
{renderToolbar({ {renderToolbar({
itemCount, itemCount,
searchColumns, searchColumns,
@@ -135,9 +135,9 @@ class ListHeader extends React.Component {
qsConfig, qsConfig,
pagination, pagination,
})} })}
</Fragment> </>
)} )}
</Fragment> </>
); );
} }
} }
@@ -153,7 +153,7 @@ ListHeader.propTypes = {
}; };
ListHeader.defaultProps = { ListHeader.defaultProps = {
renderToolbar: props => <DataListToolbar {...props} />, renderToolbar: (props) => <DataListToolbar {...props} />,
searchableKeys: [], searchableKeys: [],
sortColumns: null, sortColumns: null,
relatedSearchableKeys: [], relatedSearchableKeys: [],

View File

@@ -41,10 +41,10 @@ function ApplicationLookup({ onChange, value, label, fieldName, validate }) {
itemCount: count, itemCount: count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse?.data?.actions?.GET || {} actionsResponse?.data?.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [location]), }, [location]),
{ {
@@ -56,7 +56,7 @@ function ApplicationLookup({ onChange, value, label, fieldName, validate }) {
); );
const checkApplicationName = useCallback( const checkApplicationName = useCallback(
async name => { async (name) => {
if (!name) { if (!name) {
onChange(null); onChange(null);
return; return;
@@ -128,8 +128,8 @@ function ApplicationLookup({ onChange, value, label, fieldName, validate }) {
relatedSearchableKeys={relatedSearchableKeys} relatedSearchableKeys={relatedSearchableKeys}
readOnly={!canDelete} readOnly={!canDelete}
name="application" name="application"
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
/> />
)} )}
/> />

View File

@@ -84,7 +84,7 @@ function CredentialLookup({
const searchKeys = Object.keys( const searchKeys = Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable); ).filter((key) => actionsResponse.data.actions?.GET[key].filterable);
const item = searchKeys.indexOf('type'); const item = searchKeys.indexOf('type');
if (item) { if (item) {
searchKeys[item] = 'credential_type__kind'; searchKeys[item] = 'credential_type__kind';
@@ -95,7 +95,7 @@ function CredentialLookup({
credentials: data.results, credentials: data.results,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: searchKeys, searchableKeys: searchKeys,
}; };
}, [ }, [
@@ -115,7 +115,7 @@ function CredentialLookup({
); );
const checkCredentialName = useCallback( const checkCredentialName = useCallback(
async name => { async (name) => {
if (!name) { if (!name) {
onChange(null); onChange(null);
return; return;
@@ -209,9 +209,9 @@ function CredentialLookup({
relatedSearchableKeys={relatedSearchableKeys} relatedSearchableKeys={relatedSearchableKeys}
readOnly={!canDelete} readOnly={!canDelete}
name="credential" name="credential"
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
sortSelectedItems={selectedItems => sortSelectedItems={(selectedItems) =>
dispatch({ type: 'SET_SELECTED_ITEMS', selectedItems }) dispatch({ type: 'SET_SELECTED_ITEMS', selectedItems })
} }
multiple={multiple} multiple={multiple}

View File

@@ -107,10 +107,10 @@ function ExecutionEnvironmentLookup({
count: data.count, count: data.count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [ }, [
location, location,
@@ -129,7 +129,7 @@ function ExecutionEnvironmentLookup({
); );
const checkExecutionEnvironmentName = useCallback( const checkExecutionEnvironmentName = useCallback(
async name => { async (name) => {
if (!name) { if (!name) {
onChange(null); onChange(null);
return; return;
@@ -190,8 +190,8 @@ function ExecutionEnvironmentLookup({
name="executionEnvironments" name="executionEnvironments"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
readOnly={!canDelete} readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
/> />
)} )}
/> />

View File

@@ -129,7 +129,7 @@ function HostFilterLookup({
const { isModalOpen, toggleModal, closeModal } = useModal(); const { isModalOpen, toggleModal, closeModal } = useModal();
const searchColumns = buildSearchColumns(); const searchColumns = buildSearchColumns();
const parseRelatedSearchFields = searchFields => { const parseRelatedSearchFields = (searchFields) => {
if (searchFields.indexOf('__search') !== -1) { if (searchFields.indexOf('__search') !== -1) {
return searchFields.slice(0, -8); return searchFields.slice(0, -8);
} }
@@ -143,7 +143,7 @@ function HostFilterLookup({
isLoading, isLoading,
} = useRequest( } = useRequest(
useCallback( useCallback(
async orgId => { async (orgId) => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
const [{ data }, { data: actions }] = await Promise.all([ const [{ data }, { data: actions }] = await Promise.all([
HostsAPI.read( HostsAPI.read(
@@ -158,7 +158,7 @@ function HostFilterLookup({
parseRelatedSearchFields parseRelatedSearchFields
), ),
searchableKeys: Object.keys(actions?.actions.GET || {}).filter( searchableKeys: Object.keys(actions?.actions.GET || {}).filter(
key => actions.actions?.GET[key].filterable (key) => actions.actions?.GET[key].filterable
), ),
}; };
}, },
@@ -213,7 +213,7 @@ function HostFilterLookup({
const chipsArray = []; const chipsArray = [];
if (Array.isArray(filter[param])) { if (Array.isArray(filter[param])) {
filter[param].forEach(val => filter[param].forEach((val) =>
chipsArray.push({ chipsArray.push({
key: `${param}:${val}`, key: `${param}:${val}`,
node: `${val}`, node: `${val}`,
@@ -274,7 +274,7 @@ function HostFilterLookup({
numChips={5} numChips={5}
totalChips={chips[key]?.chips?.length || 0} totalChips={chips[key]?.chips?.length || 0}
> >
{chips[key]?.chips?.map(chip => ( {chips[key]?.chips?.map((chip) => (
<Chip key={chip.key} isReadOnly> <Chip key={chip.key} isReadOnly>
{chip.node} {chip.node}
</Chip> </Chip>
@@ -284,18 +284,18 @@ function HostFilterLookup({
{/* Parse advanced search chips */} {/* Parse advanced search chips */}
{Object.keys(chips).length > 0 && {Object.keys(chips).length > 0 &&
Object.keys(chips) Object.keys(chips)
.filter(val => chips[val].chips.length > 0) .filter((val) => chips[val].chips.length > 0)
.filter( .filter(
val => searchColumns.map(val2 => val2.key).indexOf(val) === -1 (val) => searchColumns.map((val2) => val2.key).indexOf(val) === -1
) )
.map(leftoverKey => ( .map((leftoverKey) => (
<ChipGroup <ChipGroup
categoryName={chips[leftoverKey].key} categoryName={chips[leftoverKey].key}
key={chips[leftoverKey].key} key={chips[leftoverKey].key}
numChips={5} numChips={5}
totalChips={chips[leftoverKey]?.chips?.length || 0} totalChips={chips[leftoverKey]?.chips?.length || 0}
> >
{chips[leftoverKey]?.chips?.map(chip => ( {chips[leftoverKey]?.chips?.map((chip) => (
<Chip key={chip.key} isReadOnly> <Chip key={chip.key} isReadOnly>
{chip.node} {chip.node}
</Chip> </Chip>
@@ -372,8 +372,8 @@ function HostFilterLookup({
<HeaderCell>{t`Inventory`}</HeaderCell> <HeaderCell>{t`Inventory`}</HeaderCell>
</HeaderRow> </HeaderRow>
} }
renderRow={item => <HostListItem key={item.id} item={item} />} renderRow={(item) => <HostListItem key={item.id} item={item} />}
renderToolbar={props => ( renderToolbar={(props) => (
<DataListToolbar <DataListToolbar
{...props} {...props}
fillWidth fillWidth

View File

@@ -23,17 +23,7 @@ describe('HostListItem', () => {
</table> </table>
); );
expect(wrapper.find('HostListItem').length).toBe(1); expect(wrapper.find('HostListItem').length).toBe(1);
expect( expect(wrapper.find('Td').at(0).text()).toBe('Foo');
wrapper expect(wrapper.find('Td').at(1).text()).toBe('Bar');
.find('Td')
.at(0)
.text()
).toBe('Foo');
expect(
wrapper
.find('Td')
.at(1)
.text()
).toBe('Bar');
}); });
}); });

View File

@@ -46,10 +46,10 @@ function InstanceGroupsLookup({
count: data.count, count: data.count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable), ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
}; };
}, [history.location]), }, [history.location]),
{ {
@@ -123,9 +123,9 @@ function InstanceGroupsLookup({
name="instanceGroups" name="instanceGroups"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
readOnly={!canDelete} readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
sortSelectedItems={selectedItems => sortSelectedItems={(selectedItems) =>
dispatch({ type: 'SET_SELECTED_ITEMS', selectedItems }) dispatch({ type: 'SET_SELECTED_ITEMS', selectedItems })
} }
isSelectedDraggable isSelectedDraggable

View File

@@ -65,10 +65,10 @@ function InventoryLookup({
count: data.count, count: data.count,
relatedSearchableKeys: ( relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || [] actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)), ).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys( searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {} actionsResponse.data.actions?.GET || {}
).filter(key => { ).filter((key) => {
if (['kind', 'host_filter'].includes(key) && hideSmartInventories) { if (['kind', 'host_filter'].includes(key) && hideSmartInventories) {
return false; return false;
} }
@@ -89,7 +89,7 @@ function InventoryLookup({
); );
const checkInventoryName = useCallback( const checkInventoryName = useCallback(
async name => { async (name) => {
if (!name) { if (!name) {
onChange(null); onChange(null);
return; return;
@@ -169,8 +169,8 @@ function InventoryLookup({
name="inventory" name="inventory"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
readOnly={!canDelete} readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
/> />
)} )}
/> />
@@ -225,8 +225,8 @@ function InventoryLookup({
name="inventory" name="inventory"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
readOnly={!canDelete} readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
/> />
)} )}
/> />

Some files were not shown because too many files have changed in this diff Show More