Skip to content

Commit 0588a71

Browse files
authored
Fixes #39167 - set category when REX feature is present (#1024)
* Refs #39167 - stabilize wizard category rerenders * Fixes #39167 - set category when REX feature is present * Fixes #39167 - set category when REX feature is present
1 parent aed7556 commit 0588a71

4 files changed

Lines changed: 69 additions & 31 deletions

File tree

webpack/JobWizard/JobWizard.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-lines */
22
/* eslint-disable camelcase */
3-
import React, { useState, useEffect, useCallback } from 'react';
3+
import React, { useState, useEffect, useCallback, useRef } from 'react';
44
import { useDispatch, useSelector } from 'react-redux';
55
import PropTypes from 'prop-types';
66
import { Wizard } from '@patternfly/react-core/deprecated';
@@ -51,6 +51,8 @@ export const JobWizard = ({ rerunData }) => {
5151
const [category, setCategory] = useState(
5252
rerunData?.job_category || jobCategoriesResponse?.default_category || ''
5353
);
54+
const categoryRef = useRef(category);
55+
categoryRef.current = category;
5456
const [advancedValues, setAdvancedValues] = useState({ templateValues: {} });
5557
const [templateValues, setTemplateValues] = useState({});
5658
const [scheduleValue, setScheduleValue] = useState(initialScheduleState);
@@ -89,6 +91,10 @@ export const JobWizard = ({ rerunData }) => {
8991
concurrency_control = {},
9092
},
9193
}) => {
94+
if (categoryRef.current !== job_category) {
95+
setCategory(job_category);
96+
}
97+
9298
const advancedTemplateValues = {};
9399
const defaultTemplateValues = {};
94100
const inputs = template_inputs;

webpack/JobWizard/JobWizardSelectors.js

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
/* eslint-disable camelcase */
12
import URI from 'urijs';
23
import { get } from 'lodash';
4+
import { createSelector } from 'reselect';
35
import {
46
selectAPIResponse,
57
selectAPIStatus,
@@ -17,8 +19,12 @@ import {
1719
JOB_API_KEY,
1820
} from './JobWizardConstants';
1921

22+
/** Stable fallbacks so useSelector does not see a new reference every run */
23+
const EMPTY_ARRAY = [];
24+
const EMPTY_OBJECT = {};
25+
2026
export const selectRerunJobInvocationResponse = state =>
21-
selectAPIResponse(state, JOB_API_KEY) || {};
27+
selectAPIResponse(state, JOB_API_KEY) || EMPTY_OBJECT;
2228

2329
export const selectRerunJobInvocationStatus = state =>
2430
selectAPIStatus(state, JOB_API_KEY);
@@ -27,19 +33,26 @@ export const selectJobTemplatesStatus = state =>
2733
selectAPIStatus(state, JOB_TEMPLATES);
2834

2935
export const filterJobTemplates = templates =>
30-
templates?.filter(template => !template.snippet) || [];
36+
templates?.filter(template => !template.snippet) || EMPTY_ARRAY;
37+
38+
const selectJobTemplatesResults = state =>
39+
selectAPIResponse(state, JOB_TEMPLATES)?.results;
3140

32-
export const selectJobTemplates = state =>
33-
filterJobTemplates(selectAPIResponse(state, JOB_TEMPLATES)?.results);
41+
export const selectJobTemplates = createSelector(
42+
[selectJobTemplatesResults],
43+
results => filterJobTemplates(results)
44+
);
3445

3546
export const selectJobTemplatesSearch = state =>
3647
selectAPIResponse(state, JOB_TEMPLATES)?.search;
3748

3849
export const selectJobCategoriesResponse = state =>
39-
selectAPIResponse(state, JOB_CATEGORIES) || {};
50+
selectAPIResponse(state, JOB_CATEGORIES) || EMPTY_OBJECT;
4051

41-
export const selectJobCategories = state =>
42-
selectJobCategoriesResponse(state).job_categories || [];
52+
export const selectJobCategories = state => {
53+
const { job_categories: jobCategories } = selectJobCategoriesResponse(state);
54+
return jobCategories || EMPTY_ARRAY;
55+
};
4356

4457
export const selectWithKatello = state =>
4558
selectJobCategoriesResponse(state).with_katello || false;
@@ -58,7 +71,7 @@ export const selectJobCategoriesMissingPermissions = state => {
5871
'data',
5972
'error',
6073
'missing_permissions',
61-
]) || []
74+
]) || EMPTY_ARRAY
6275
);
6376
};
6477

@@ -75,29 +88,32 @@ export const selectEffectiveUser = state =>
7588
selectAPIResponse(state, JOB_TEMPLATE).effective_user;
7689

7790
export const selectAdvancedTemplateInputs = state =>
78-
selectAPIResponse(state, JOB_TEMPLATE).advanced_template_inputs || [];
91+
selectAPIResponse(state, JOB_TEMPLATE)?.advanced_template_inputs ||
92+
EMPTY_ARRAY;
7993

8094
export const selectTemplateInputs = state =>
81-
selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
95+
selectAPIResponse(state, JOB_TEMPLATE)?.template_inputs || EMPTY_ARRAY;
8296

8397
export const selectHostsResponse = state => selectAPIResponse(state, HOSTS_API);
8498

8599
export const selectHostCount = state =>
86100
selectHostsResponse(state).subtotal || 0;
87101

88-
export const selectHosts = state => {
89-
const hosts = selectHostsResponse(state).results || [];
102+
const selectHostsResults = state => selectHostsResponse(state).results;
103+
104+
export const selectHosts = createSelector([selectHostsResults], results => {
105+
const hosts = results || EMPTY_ARRAY;
90106
return hosts.map(host => ({
91107
name: host.name,
92108
display_name: host.display_name,
93109
}));
94-
};
110+
});
95111

96112
export const selectHostsMissingPermissions = state => {
97113
const hostsResponse = selectHostsResponse(state);
98114
return (
99115
get(hostsResponse, ['response', 'data', 'error', 'missing_permissions']) ||
100-
[]
116+
EMPTY_ARRAY
101117
);
102118
};
103119

@@ -115,6 +131,6 @@ export const selectIsSubmitting = state =>
115131
selectAPIStatus(state, JOB_INVOCATION) === STATUS.RESOLVED;
116132

117133
export const selectRouterSearch = state => {
118-
const { search } = selectRouterLocation(state);
134+
const { search } = selectRouterLocation(state) || {};
119135
return URI.parseQuery(search);
120136
};

webpack/JobWizard/__tests__/fixtures.js

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ export const pupptetJobTemplate = {
3030

3131
export const jobTemplates = [jobTemplate];
3232

33+
export const wizardJobTemplatesMockList = [
34+
jobTemplate,
35+
pupptetJobTemplate,
36+
{ ...jobTemplate, id: 2, name: 'template2' },
37+
];
38+
3339
export const jobTemplateResponse = {
3440
job_template: jobTemplate,
3541
effective_user: {
@@ -137,11 +143,9 @@ export const testSetup = (selectors, api) => {
137143
() => jobTemplateResponse.advanced_template_inputs
138144
);
139145
selectors.selectJobCategories.mockImplementation(() => jobCategories);
140-
selectors.selectJobTemplates.mockImplementation(() => [
141-
jobTemplate,
142-
pupptetJobTemplate,
143-
{ ...jobTemplate, id: 2, name: 'template2' },
144-
]);
146+
selectors.selectJobTemplates.mockImplementation(
147+
() => wizardJobTemplatesMockList
148+
);
145149
selectors.selectJobTemplate.mockImplementation(() => jobTemplateResponse);
146150

147151
selectors.selectEffectiveUser.mockImplementation(
@@ -187,13 +191,25 @@ export const mockApi = api => {
187191
},
188192
});
189193
} else if (action.key === 'JOB_TEMPLATE') {
190-
handleSuccess &&
191-
handleSuccess({
192-
data:
193-
action.url === '/ui_job_wizard/template/163'
194-
? { ...jobTemplateResponse, job_template: pupptetJobTemplate }
195-
: jobTemplateResponse,
196-
});
194+
const url = String(action.url);
195+
let templatePayload = jobTemplateResponse;
196+
if (url.includes('/template/163')) {
197+
templatePayload = {
198+
...jobTemplateResponse,
199+
job_template: pupptetJobTemplate,
200+
};
201+
} else if (url.includes('/template/2')) {
202+
const jobTemplate2 = { ...jobTemplate, id: 2, name: 'template2' };
203+
templatePayload = {
204+
...jobTemplateResponse,
205+
job_template: jobTemplate2,
206+
effective_user: {
207+
...jobTemplateResponse.effective_user,
208+
job_template_id: 2,
209+
},
210+
};
211+
}
212+
handleSuccess && handleSuccess({ data: templatePayload });
197213
} else if (action.key === 'JOB_TEMPLATES') {
198214
handleSuccess &&
199215
handleSuccess({
@@ -202,7 +218,7 @@ export const mockApi = api => {
202218
action.url.search() ===
203219
'?search=job_category%3D%22Puppet%22&per_page=all'
204220
? [pupptetJobTemplate]
205-
: [jobTemplate],
221+
: wizardJobTemplatesMockList,
206222
},
207223
});
208224
} else if (action.key === 'HOST_IDS') {

webpack/JobWizard/steps/CategoryAndTemplate/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from 'react';
1+
import React, { useEffect, memo } from 'react';
22
import { useSelector, useDispatch } from 'react-redux';
33
import PropTypes from 'prop-types';
44
import URI from 'urijs';
@@ -133,4 +133,4 @@ ConnectedCategoryAndTemplate.propTypes = {
133133
};
134134
ConnectedCategoryAndTemplate.defaultProps = { jobTemplate: null };
135135

136-
export default ConnectedCategoryAndTemplate;
136+
export default memo(ConnectedCategoryAndTemplate);

0 commit comments

Comments
 (0)