Merge pull request #9488 from nixocio/ui_fix_rerererender

Fix extra re-render for Job Template

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2021-03-05 15:12:05 +00:00
committed by GitHub
5 changed files with 83 additions and 47 deletions

View File

@@ -1,11 +1,11 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { string, func, bool } from 'prop-types'; import { string, func, bool, oneOfType, number } from 'prop-types';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { FormGroup, Tooltip } from '@patternfly/react-core'; import { FormGroup, Tooltip } from '@patternfly/react-core';
import { ExecutionEnvironmentsAPI } from '../../api'; import { ExecutionEnvironmentsAPI, ProjectsAPI } from '../../api';
import { ExecutionEnvironment } from '../../types'; import { ExecutionEnvironment } from '../../types';
import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs'; import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs';
import Popover from '../Popover'; import Popover from '../Popover';
@@ -26,15 +26,38 @@ function ExecutionEnvironmentLookup({
i18n, i18n,
isDefaultEnvironment, isDefaultEnvironment,
isDisabled, isDisabled,
onBlur,
onChange, onChange,
organizationId, organizationId,
popoverContent, popoverContent,
projectId,
tooltip, tooltip,
value, value,
onBlur,
}) { }) {
const location = useLocation(); const location = useLocation();
const {
request: fetchProject,
error: fetchProjectError,
isLoading: fetchProjectLoading,
result: project,
} = useRequest(
useCallback(async () => {
if (!projectId) {
return {};
}
const { data } = await ProjectsAPI.readDetail(projectId);
return data;
}, [projectId]),
{
project: null,
}
);
useEffect(() => {
fetchProject();
}, [fetchProject]);
const { const {
result: { result: {
executionEnvironments, executionEnvironments,
@@ -51,9 +74,10 @@ function ExecutionEnvironmentLookup({
const globallyAvailableParams = globallyAvailable const globallyAvailableParams = globallyAvailable
? { or__organization__isnull: 'True' } ? { or__organization__isnull: 'True' }
: {}; : {};
const organizationIdParams = organizationId const organizationIdParams =
? { or__organization__id: organizationId } organizationId || project?.organization
: {}; ? { or__organization__id: organizationId }
: {};
const [{ data }, actionsResponse] = await Promise.all([ const [{ data }, actionsResponse] = await Promise.all([
ExecutionEnvironmentsAPI.read( ExecutionEnvironmentsAPI.read(
mergeParams(params, { mergeParams(params, {
@@ -73,7 +97,7 @@ function ExecutionEnvironmentLookup({
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, globallyAvailable, organizationId]), }, [location, globallyAvailable, organizationId, project]),
{ {
executionEnvironments: [], executionEnvironments: [],
count: 0, count: 0,
@@ -95,7 +119,7 @@ function ExecutionEnvironmentLookup({
onBlur={onBlur} onBlur={onBlur}
onChange={onChange} onChange={onChange}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
isLoading={isLoading} isLoading={isLoading || fetchProjectLoading}
isDisabled={isDisabled} isDisabled={isDisabled}
renderOptionsList={({ state, dispatch, canDelete }) => ( renderOptionsList={({ state, dispatch, canDelete }) => (
<OptionsList <OptionsList
@@ -146,7 +170,7 @@ function ExecutionEnvironmentLookup({
renderLookup() renderLookup()
)} )}
<LookupErrorMessage error={error} /> <LookupErrorMessage error={error || fetchProjectError} />
</FormGroup> </FormGroup>
); );
} }
@@ -156,12 +180,16 @@ ExecutionEnvironmentLookup.propTypes = {
popoverContent: string, popoverContent: string,
onChange: func.isRequired, onChange: func.isRequired,
isDefaultEnvironment: bool, isDefaultEnvironment: bool,
projectId: oneOfType([number, string]),
organizationId: oneOfType([number, string]),
}; };
ExecutionEnvironmentLookup.defaultProps = { ExecutionEnvironmentLookup.defaultProps = {
popoverContent: '', popoverContent: '',
isDefaultEnvironment: false, isDefaultEnvironment: false,
value: null, value: null,
projectId: null,
organizationId: null,
}; };
export default withI18n()(ExecutionEnvironmentLookup); export default withI18n()(ExecutionEnvironmentLookup);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ExecutionEnvironmentLookup from './ExecutionEnvironmentLookup'; import ExecutionEnvironmentLookup from './ExecutionEnvironmentLookup';
import { ExecutionEnvironmentsAPI } from '../../api'; import { ExecutionEnvironmentsAPI, ProjectsAPI } from '../../api';
jest.mock('../../api'); jest.mock('../../api');
@@ -32,6 +32,17 @@ describe('ExecutionEnvironmentLookup', () => {
ExecutionEnvironmentsAPI.read.mockResolvedValue( ExecutionEnvironmentsAPI.read.mockResolvedValue(
mockedExecutionEnvironments mockedExecutionEnvironments
); );
ProjectsAPI.read.mockResolvedValue({
data: {
count: 1,
results: [
{
id: 1,
name: 'Fuz',
},
],
},
});
}); });
afterEach(() => { afterEach(() => {
@@ -52,14 +63,21 @@ describe('ExecutionEnvironmentLookup', () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ExecutionEnvironmentLookup <ExecutionEnvironmentLookup
isDefaultEnvironment
value={executionEnvironment} value={executionEnvironment}
onChange={() => {}} onChange={() => {}}
/> />
); );
}); });
wrapper.update(); wrapper.update();
expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1); expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(2);
expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1); expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1);
expect(
wrapper.find('FormGroup[label="Default Execution Environment"]').length
).toBe(1);
expect(
wrapper.find('FormGroup[label="Execution Environment"]').length
).toBe(0);
}); });
test('should fetch execution environments', async () => { test('should fetch execution environments', async () => {
@@ -71,6 +89,12 @@ describe('ExecutionEnvironmentLookup', () => {
/> />
); );
}); });
expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1); expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(2);
expect(
wrapper.find('FormGroup[label="Default Execution Environment"]').length
).toBe(0);
expect(
wrapper.find('FormGroup[label="Execution Environment"]').length
).toBe(1);
}); });
}); });

View File

@@ -58,7 +58,7 @@ const jobTemplateData = {
timeout: 0, timeout: 0,
use_fact_cache: false, use_fact_cache: false,
verbosity: '0', verbosity: '0',
execution_environment: { id: 1, name: 'Foo' }, execution_environment: { id: 1, name: 'Foo', image: 'localhost.com' },
}; };
describe('<JobTemplateAdd />', () => { describe('<JobTemplateAdd />', () => {
@@ -139,7 +139,7 @@ describe('<JobTemplateAdd />', () => {
wrapper = mountWithContexts(<JobTemplateAdd />); wrapper = mountWithContexts(<JobTemplateAdd />);
}); });
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
await act(() => { await act(async () => {
wrapper.find('input#template-name').simulate('change', { wrapper.find('input#template-name').simulate('change', {
target: { value: 'Bar', name: 'name' }, target: { value: 'Bar', name: 'name' },
}); });

View File

@@ -6,6 +6,7 @@ import {
WorkflowJobTemplatesAPI, WorkflowJobTemplatesAPI,
OrganizationsAPI, OrganizationsAPI,
LabelsAPI, LabelsAPI,
ExecutionEnvironmentsAPI,
} from '../../../api'; } from '../../../api';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
@@ -15,6 +16,7 @@ jest.mock('../../../api/models/WorkflowJobTemplates');
jest.mock('../../../api/models/Organizations'); jest.mock('../../../api/models/Organizations');
jest.mock('../../../api/models/Labels'); jest.mock('../../../api/models/Labels');
jest.mock('../../../api/models/Inventories'); jest.mock('../../../api/models/Inventories');
jest.mock('../../../api/models/ExecutionEnvironments');
describe('<WorkflowJobTemplateAdd/>', () => { describe('<WorkflowJobTemplateAdd/>', () => {
let wrapper; let wrapper;
@@ -34,6 +36,10 @@ describe('<WorkflowJobTemplateAdd/>', () => {
}, },
}); });
ExecutionEnvironmentsAPI.read.mockResolvedValue({
data: { results: [{ id: 1, name: 'Foo', image: 'localhost.com' }] },
});
await act(async () => { await act(async () => {
history = createMemoryHistory({ history = createMemoryHistory({
initialEntries: ['/templates/workflow_job_template/add'], initialEntries: ['/templates/workflow_job_template/add'],
@@ -82,6 +88,11 @@ describe('<WorkflowJobTemplateAdd/>', () => {
.find('LabelSelect') .find('LabelSelect')
.find('SelectToggle') .find('SelectToggle')
.simulate('click'); .simulate('click');
wrapper.find('ExecutionEnvironmentLookup').invoke('onChange')({
id: 1,
name: 'Foo',
});
}); });
wrapper.update(); wrapper.update();
@@ -113,6 +124,7 @@ describe('<WorkflowJobTemplateAdd/>', () => {
webhook_credential: undefined, webhook_credential: undefined,
webhook_service: '', webhook_service: '',
webhook_url: '', webhook_url: '',
execution_environment: 1,
}); });
expect(WorkflowJobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(1); expect(WorkflowJobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(1);

View File

@@ -40,7 +40,7 @@ import {
ExecutionEnvironmentLookup, ExecutionEnvironmentLookup,
} from '../../../components/Lookup'; } from '../../../components/Lookup';
import Popover from '../../../components/Popover'; import Popover from '../../../components/Popover';
import { JobTemplatesAPI, ProjectsAPI } from '../../../api'; import { JobTemplatesAPI } from '../../../api';
import LabelSelect from './LabelSelect'; import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect'; import PlaybookSelect from './PlaybookSelect';
import WebhookSubForm from './WebhookSubForm'; import WebhookSubForm from './WebhookSubForm';
@@ -108,30 +108,6 @@ function JobTemplateForm({
executionEnvironmentHelpers, executionEnvironmentHelpers,
] = useField({ name: 'execution_environment' }); ] = useField({ name: 'execution_environment' });
const projectId = projectField.value?.id;
const {
request: fetchProject,
error: fetchProjectError,
isLoading: fetchProjectLoading,
result: projectData,
} = useRequest(
useCallback(async () => {
if (!projectId) {
return {};
}
const { data } = await ProjectsAPI.readDetail(projectId);
return data;
}, [projectId]),
{
projectData: null,
}
);
useEffect(() => {
fetchProject();
}, [fetchProject]);
const { const {
request: loadRelatedInstanceGroups, request: loadRelatedInstanceGroups,
error: instanceGroupError, error: instanceGroupError,
@@ -213,16 +189,12 @@ function JobTemplateForm({
callbackUrl = `${origin}${path}`; callbackUrl = `${origin}${path}`;
} }
if (instanceGroupLoading || fetchProjectLoading) { if (instanceGroupLoading) {
return <ContentLoading />; return <ContentLoading />;
} }
if (contentError || instanceGroupError || fetchProjectError) { if (contentError || instanceGroupError) {
return ( return <ContentError error={contentError || instanceGroupError} />;
<ContentError
error={contentError || instanceGroupError || fetchProjectError}
/>
);
} }
return ( return (
@@ -323,7 +295,7 @@ function JobTemplateForm({
)} )}
globallyAvailable globallyAvailable
isDisabled={!projectField.value} isDisabled={!projectField.value}
organizationId={projectData?.organization} projectId={projectField.value?.id}
/> />
{projectField.value?.allow_override && ( {projectField.value?.allow_override && (