Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Execution Environments into a few screens #9394

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { useCallback, useEffect } from 'react';
import { string, func, bool } from 'prop-types';
import { withRouter, useLocation } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { FormGroup, Tooltip } from '@patternfly/react-core';

import { ExecutionEnvironmentsAPI } from '../../api';
import { ExecutionEnvironment } from '../../types';
import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs';
import Popover from '../Popover';
import OptionsList from '../OptionsList';
import useRequest from '../../util/useRequest';

import Lookup from './Lookup';
import LookupErrorMessage from './shared/LookupErrorMessage';

const QS_CONFIG = getQSConfig('execution_environments', {
page: 1,
page_size: 5,
order_by: 'name',
});

function ExecutionEnvironmentLookup({
globallyAvailable,
i18n,
isDefaultEnvironment,
isDisabled,
onChange,
organizationId,
popoverContent,
tooltip,
value,
onBlur,
}) {
const location = useLocation();

const {
result: {
executionEnvironments,
count,
relatedSearchableKeys,
searchableKeys,
},
request: fetchExecutionEnvironments,
error,
isLoading,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const globallyAvailableParams = globallyAvailable
? { or__organization__isnull: 'True' }
: {};
const organizationIdParams = organizationId
? { or__organization__id: organizationId }
: {};
const [{ data }, actionsResponse] = await Promise.all([
ExecutionEnvironmentsAPI.read(
mergeParams(params, {
...globallyAvailableParams,
...organizationIdParams,
})
),
ExecutionEnvironmentsAPI.readOptions(),
]);
return {
executionEnvironments: data.results,
count: data.count,
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
};
}, [location, globallyAvailable, organizationId]),
{
executionEnvironments: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);

useEffect(() => {
fetchExecutionEnvironments();
}, [fetchExecutionEnvironments]);

const renderLookup = () => (
<>
<Lookup
id="execution-environments"
header={i18n._(t`Execution Environments`)}
value={value}
onBlur={onBlur}
onChange={onChange}
qsConfig={QS_CONFIG}
isLoading={isLoading}
isDisabled={isDisabled}
renderOptionsList={({ state, dispatch, canDelete }) => (
<OptionsList
value={state.selectedItems}
options={executionEnvironments}
optionCount={count}
searchColumns={[
{
name: i18n._(t`Name`),
key: 'name__icontains',
isDefault: true,
},
]}
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys}
multiple={state.multiple}
header={i18n._(t`Execution Environment`)}
name="executionEnvironments"
qsConfig={QS_CONFIG}
readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
/>
)}
/>
</>
);

return (
<FormGroup
fieldId="execution-environment-lookup"
label={
isDefaultEnvironment
? i18n._(t`Default Execution Environment`)
: i18n._(t`Execution Environment`)
}
labelIcon={popoverContent && <Popover content={popoverContent} />}
>
{isDisabled ? (
<Tooltip content={tooltip}>{renderLookup()}</Tooltip>
) : (
renderLookup()
)}

<LookupErrorMessage error={error} />
</FormGroup>
);
}

ExecutionEnvironmentLookup.propTypes = {
value: ExecutionEnvironment,
popoverContent: string,
onChange: func.isRequired,
isDefaultEnvironment: bool,
};

ExecutionEnvironmentLookup.defaultProps = {
popoverContent: '',
isDefaultEnvironment: false,
value: null,
};

export default withI18n()(withRouter(ExecutionEnvironmentLookup));
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ExecutionEnvironmentLookup from './ExecutionEnvironmentLookup';
import { ExecutionEnvironmentsAPI } from '../../api';

jest.mock('../../api');

const mockedExecutionEnvironments = {
count: 1,
results: [
{
id: 2,
name: 'Foo',
image: 'quay.io/ansible/awx-ee',
pull: 'missing',
},
],
};

const executionEnvironment = {
id: 42,
name: 'Bar',
image: 'quay.io/ansible/bar',
pull: 'missing',
};

describe('ExecutionEnvironmentLookup', () => {
let wrapper;

beforeEach(() => {
ExecutionEnvironmentsAPI.read.mockResolvedValue(
mockedExecutionEnvironments
);
});

afterEach(() => {
jest.clearAllMocks();
wrapper.unmount();
});

test('should render successfully', async () => {
ExecutionEnvironmentsAPI.readOptions.mockReturnValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<ExecutionEnvironmentLookup
value={executionEnvironment}
onChange={() => {}}
/>
);
});
wrapper.update();
expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1);
expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1);
});

test('should fetch execution environments', async () => {
await act(async () => {
wrapper = mountWithContexts(
<ExecutionEnvironmentLookup
value={executionEnvironment}
onChange={() => {}}
/>
);
});
expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1);
});
});
1 change: 1 addition & 0 deletions awx/ui_next/src/components/Lookup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as CredentialLookup } from './CredentialLookup';
export { default as ApplicationLookup } from './ApplicationLookup';
export { default as HostFilterLookup } from './HostFilterLookup';
export { default as OrganizationLookup } from './OrganizationLookup';
export { default as ExecutionEnvironmentLookup } from './ExecutionEnvironmentLookup';
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ function OrganizationAdd() {

const handleSubmit = async (values, groupsToAssociate) => {
try {
const { data: response } = await OrganizationsAPI.create(values);
const { data: response } = await OrganizationsAPI.create({
...values,
default_environment: values.default_environment?.id,
});
await Promise.all(
groupsToAssociate
.map(id => OrganizationsAPI.associateInstanceGroup(response.id, id))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ describe('<OrganizationAdd />', () => {
description: 'new description',
custom_virtualenv: 'Buzz',
galaxy_credentials: [],
default_environment: { id: 1, name: 'Foo' },
};
OrganizationsAPI.create.mockResolvedValueOnce({ data: {} });
await act(async () => {
const wrapper = mountWithContexts(<OrganizationAdd />);
wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, []);
});
expect(OrganizationsAPI.create).toHaveBeenCalledWith(updatedOrgData);
expect(OrganizationsAPI.create).toHaveBeenCalledWith({
...updatedOrgData,
default_environment: 1,
});
expect(OrganizationsAPI.create).toHaveBeenCalledTimes(1);
});

test('should navigate to organizations list when cancel is clicked', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ function OrganizationDetail({ i18n, organization }) {
label={i18n._(t`Ansible Environment`)}
value={custom_virtualenv}
/>
{summary_fields?.default_environment?.name && (
<Detail
label={i18n._(t`Default Execution Environment`)}
value={summary_fields.default_environment.name}
/>
)}
<UserDateDetail
label={i18n._(t`Created`)}
date={created}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jest.mock('../../../api');

describe('<OrganizationDetail />', () => {
const mockOrganization = {
id: 12,
name: 'Foo',
description: 'Bar',
custom_virtualenv: 'Fizz',
Expand All @@ -24,7 +25,14 @@ describe('<OrganizationDetail />', () => {
edit: true,
delete: true,
},
default_environment: {
id: 1,
name: 'Default EE',
description: '',
image: 'quay.io/ansible/awx-ee',
},
},
default_environment: 1,
};
const mockInstanceGroups = {
data: {
Expand All @@ -43,7 +51,7 @@ describe('<OrganizationDetail />', () => {
jest.clearAllMocks();
});

test('initially renders succesfully', async () => {
test('initially renders successfully', async () => {
await act(async () => {
mountWithContexts(<OrganizationDetail organization={mockOrganization} />);
});
Expand Down Expand Up @@ -86,6 +94,7 @@ describe('<OrganizationDetail />', () => {
{ label: 'Created', value: '7/7/2015, 5:21:26 PM' },
{ label: 'Last Modified', value: '8/11/2019, 7:47:37 PM' },
{ label: 'Max Hosts', value: '0' },
{ label: 'Default Execution Environment', value: 'Default EE' },
];
for (let i = 0; i < testParams.length; i++) {
const { label, value } = testParams[i];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ function OrganizationEdit({ organization }) {
const addedCredentialIds = addedCredentials.map(({ id }) => id);
const removedCredentialIds = removedCredentials.map(({ id }) => id);

await OrganizationsAPI.update(organization.id, values);
await OrganizationsAPI.update(organization.id, {
...values,
default_environment: values.default_environment?.id || null,
});
await Promise.all(
groupsToAssociate
.map(id =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ describe('<OrganizationEdit />', () => {
related: {
instance_groups: '/api/v2/organizations/1/instance_groups',
},
default_environment: 1,
summary_fields: {
default_environment: {
id: 1,
name: 'Baz',
},
},
};

test('onSubmit should call api update', async () => {
Expand All @@ -31,6 +38,7 @@ describe('<OrganizationEdit />', () => {
name: 'new name',
description: 'new description',
custom_virtualenv: 'Buzz',
default_environment: null,
};
wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, [], []);

Expand Down
Loading