mirror of
https://github.com/ansible/awx.git
synced 2026-01-16 12:20:45 -03:30
Remove custom virtual env
Remove custom virtual from the UI. Also, surface missing-resource warnings on list items for UJTs that were using custom virtualenvs. Fix some uni-tests warnings. See: https://github.com/ansible/awx/issues/9190 Also: https://github.com/ansible/awx/issues/9207
This commit is contained in:
parent
de52adedef
commit
babea5d599
@ -11,14 +11,12 @@ jest.mock('../../api');
|
||||
|
||||
describe('<AppContainer />', () => {
|
||||
const ansible_version = '111';
|
||||
const custom_virtualenvs = [];
|
||||
const version = '222';
|
||||
|
||||
beforeEach(() => {
|
||||
ConfigAPI.read.mockResolvedValue({
|
||||
data: {
|
||||
ansible_version,
|
||||
custom_virtualenvs,
|
||||
version,
|
||||
},
|
||||
});
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { bool, string } from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Tooltip } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ExclamationTriangleIcon as PFExclamationTriangleIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { Detail } from '../DetailList';
|
||||
import { ExecutionEnvironment } from '../../types';
|
||||
|
||||
const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
|
||||
color: var(--pf-global--warning-color--100);
|
||||
margin-left: 18px;
|
||||
`;
|
||||
|
||||
function ExecutionEnvironmentDetail({
|
||||
virtualEnvironment,
|
||||
executionEnvironment,
|
||||
isDefaultEnvironment,
|
||||
i18n,
|
||||
}) {
|
||||
const label = isDefaultEnvironment
|
||||
? i18n._(t`Default Execution Environment`)
|
||||
: i18n._(t`Execution Environment`);
|
||||
|
||||
if (executionEnvironment) {
|
||||
return (
|
||||
<Detail
|
||||
label={label}
|
||||
value={
|
||||
<Link
|
||||
to={`/execution_environments/${executionEnvironment.id}/details`}
|
||||
>
|
||||
{executionEnvironment.name}
|
||||
</Link>
|
||||
}
|
||||
dataCy="execution-environment-detail"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (virtualEnvironment && !executionEnvironment) {
|
||||
return (
|
||||
<Detail
|
||||
label={label}
|
||||
value={
|
||||
<>
|
||||
{i18n._(t`Missing resource`)}
|
||||
<span>
|
||||
<Tooltip
|
||||
content={i18n._(
|
||||
t`Custom virtual environment ${virtualEnvironment} must be replaced by an execution environment.`
|
||||
)}
|
||||
position="right"
|
||||
>
|
||||
<ExclamationTriangleIcon />
|
||||
</Tooltip>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
dataCy="missing-execution-environment-detail"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ExecutionEnvironmentDetail.propTypes = {
|
||||
executionEnvironment: ExecutionEnvironment,
|
||||
isDefaultEnvironment: bool,
|
||||
virtualEnvironment: string,
|
||||
};
|
||||
|
||||
ExecutionEnvironmentDetail.defaultProps = {
|
||||
isDefaultEnvironment: false,
|
||||
executionEnvironment: null,
|
||||
virtualEnvironment: '',
|
||||
};
|
||||
|
||||
export default withI18n()(ExecutionEnvironmentDetail);
|
||||
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
|
||||
import ExecutionEnvironmentDetail from './ExecutionEnvironmentDetail';
|
||||
|
||||
const mockExecutionEnvironment = {
|
||||
id: 2,
|
||||
name: 'Foo',
|
||||
image: 'quay.io/ansible/awx-ee',
|
||||
pull: 'missing',
|
||||
description: '',
|
||||
};
|
||||
|
||||
const virtualEnvironment = 'var/lib/awx/custom_env';
|
||||
|
||||
describe('<ExecutionEnvironmentDetail/>', () => {
|
||||
test('should display execution environment detail', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<ExecutionEnvironmentDetail
|
||||
executionEnvironment={mockExecutionEnvironment}
|
||||
/>
|
||||
);
|
||||
const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
|
||||
expect(executionEnvironment).toHaveLength(1);
|
||||
expect(executionEnvironment.find('dt').text()).toEqual(
|
||||
'Execution Environment'
|
||||
);
|
||||
expect(executionEnvironment.find('dd').text()).toEqual(
|
||||
mockExecutionEnvironment.name
|
||||
);
|
||||
});
|
||||
|
||||
test('should display execution environment detail even with a previous virtual env present', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<ExecutionEnvironmentDetail
|
||||
executionEnvironment={mockExecutionEnvironment}
|
||||
virtualEnvironment={virtualEnvironment}
|
||||
/>
|
||||
);
|
||||
const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
|
||||
expect(executionEnvironment).toHaveLength(1);
|
||||
expect(executionEnvironment.find('dt').text()).toEqual(
|
||||
'Execution Environment'
|
||||
);
|
||||
expect(executionEnvironment.find('dd').text()).toEqual(
|
||||
mockExecutionEnvironment.name
|
||||
);
|
||||
});
|
||||
|
||||
test('should display warning missing execution environment', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<ExecutionEnvironmentDetail virtualEnvironment={virtualEnvironment} />
|
||||
);
|
||||
const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
|
||||
expect(executionEnvironment).toHaveLength(1);
|
||||
expect(executionEnvironment.find('dt').text()).toEqual(
|
||||
'Execution Environment'
|
||||
);
|
||||
expect(executionEnvironment.find('dd').text()).toEqual('Missing resource');
|
||||
expect(wrapper.find('Tooltip').prop('content')).toEqual(
|
||||
`Custom virtual environment ${virtualEnvironment} must be replaced by an execution environment.`
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
export { default } from './ExecutionEnvironmentDetail';
|
||||
@ -8,6 +8,7 @@ import { Detail, DeletedDetail } from '../DetailList';
|
||||
import { VariablesDetail } from '../CodeEditor';
|
||||
import CredentialChip from '../CredentialChip';
|
||||
import ChipGroup from '../ChipGroup';
|
||||
import ExecutionEnvironmentDetail from '../ExecutionEnvironmentDetail';
|
||||
|
||||
function PromptInventorySourceDetail({ i18n, resource }) {
|
||||
const {
|
||||
@ -83,10 +84,6 @@ function PromptInventorySourceDetail({ i18n, resource }) {
|
||||
/>
|
||||
)}
|
||||
<Detail label={i18n._(t`Source`)} value={source} />
|
||||
<Detail
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
value={custom_virtualenv}
|
||||
/>
|
||||
{summary_fields?.source_project && (
|
||||
<Detail
|
||||
label={i18n._(t`Project`)}
|
||||
@ -97,6 +94,10 @@ function PromptInventorySourceDetail({ i18n, resource }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={custom_virtualenv}
|
||||
executionEnvironment={summary_fields?.execution_environment}
|
||||
/>
|
||||
<Detail label={i18n._(t`Inventory File`)} value={source_path} />
|
||||
<Detail label={i18n._(t`Verbosity`)} value={VERBOSITY[verbosity]} />
|
||||
<Detail
|
||||
|
||||
@ -9,6 +9,7 @@ import ChipGroup from '../ChipGroup';
|
||||
import Sparkline from '../Sparkline';
|
||||
import { Detail, DeletedDetail } from '../DetailList';
|
||||
import { VariablesDetail } from '../CodeEditor';
|
||||
import ExecutionEnvironmentDetail from '../ExecutionEnvironmentDetail';
|
||||
import { toTitleCase } from '../../util/strings';
|
||||
|
||||
function PromptJobTemplateDetail({ i18n, resource }) {
|
||||
@ -34,6 +35,7 @@ function PromptJobTemplateDetail({ i18n, resource }) {
|
||||
verbosity,
|
||||
webhook_key,
|
||||
webhook_service,
|
||||
custom_virtualenv,
|
||||
} = resource;
|
||||
|
||||
const VERBOSITY = {
|
||||
@ -128,6 +130,10 @@ function PromptJobTemplateDetail({ i18n, resource }) {
|
||||
) : (
|
||||
<DeletedDetail label={i18n._(t`Project`)} />
|
||||
)}
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={custom_virtualenv}
|
||||
executionEnvironment={summary_fields?.execution_environment}
|
||||
/>
|
||||
<Detail label={i18n._(t`Source Control Branch`)} value={scm_branch} />
|
||||
<Detail label={i18n._(t`Playbook`)} value={playbook} />
|
||||
<Detail label={i18n._(t`Forks`)} value={forks || '0'} />
|
||||
|
||||
@ -8,6 +8,7 @@ import { Config } from '../../contexts/Config';
|
||||
import { Detail, DeletedDetail } from '../DetailList';
|
||||
import CredentialChip from '../CredentialChip';
|
||||
import { toTitleCase } from '../../util/strings';
|
||||
import ExecutionEnvironmentDetail from '../ExecutionEnvironmentDetail';
|
||||
|
||||
function PromptProjectDetail({ i18n, resource }) {
|
||||
const {
|
||||
@ -64,6 +65,11 @@ function PromptProjectDetail({ i18n, resource }) {
|
||||
) : (
|
||||
<DeletedDetail label={i18n._(t`Organization`)} />
|
||||
)}
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={custom_virtualenv}
|
||||
executionEnvironment={summary_fields?.default_environment}
|
||||
isDefaultEnvironment
|
||||
/>
|
||||
<Detail
|
||||
label={i18n._(t`Source Control Type`)}
|
||||
value={scm_type === '' ? i18n._(t`Manual`) : toTitleCase(scm_type)}
|
||||
@ -88,10 +94,6 @@ function PromptProjectDetail({ i18n, resource }) {
|
||||
label={i18n._(t`Cache Timeout`)}
|
||||
value={`${scm_update_cache_timeout} ${i18n._(t`Seconds`)}`}
|
||||
/>
|
||||
<Detail
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
value={custom_virtualenv}
|
||||
/>
|
||||
<Config>
|
||||
{({ project_base_dir }) => (
|
||||
<Detail
|
||||
|
||||
@ -41,10 +41,17 @@ describe('PromptProjectDetail', () => {
|
||||
assertDetail(wrapper, 'Source Control Branch', 'foo');
|
||||
assertDetail(wrapper, 'Source Control Refspec', 'refs/');
|
||||
assertDetail(wrapper, 'Cache Timeout', '3 Seconds');
|
||||
assertDetail(wrapper, 'Ansible Environment', 'mock virtual env');
|
||||
assertDetail(wrapper, 'Project Base Path', 'dir/foo/bar');
|
||||
assertDetail(wrapper, 'Playbook Directory', '_6__demo_project');
|
||||
assertDetail(wrapper, 'Source Control Credential', 'Scm: mock scm');
|
||||
const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
|
||||
expect(executionEnvironment).toHaveLength(1);
|
||||
expect(executionEnvironment.find('dt').text()).toEqual(
|
||||
'Default Execution Environment'
|
||||
);
|
||||
expect(executionEnvironment.find('dd').text()).toEqual(
|
||||
mockProject.summary_fields.default_environment.name
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('Detail[label="Options"]')
|
||||
|
||||
@ -45,6 +45,12 @@
|
||||
"organization_id": 1,
|
||||
"kind": ""
|
||||
},
|
||||
"execution_environment": {
|
||||
"id": 1,
|
||||
"name": "Default EE",
|
||||
"description": "",
|
||||
"image": "quay.io/ansible/awx-ee"
|
||||
},
|
||||
"project": {
|
||||
"id": 6,
|
||||
"name": "Mock Project",
|
||||
@ -192,5 +198,6 @@
|
||||
"custom_virtualenv": null,
|
||||
"job_slice_count": 1,
|
||||
"webhook_service": "github",
|
||||
"webhook_credential": 8
|
||||
"webhook_credential": 8,
|
||||
"execution_environment": 1
|
||||
}
|
||||
|
||||
@ -28,6 +28,12 @@
|
||||
"name":"Default",
|
||||
"description":""
|
||||
},
|
||||
"default_environment": {
|
||||
"id": 1,
|
||||
"name": "Default EE",
|
||||
"description": "",
|
||||
"image": "quay.io/ansible/awx-ee"
|
||||
},
|
||||
"credential": {
|
||||
"id": 9,
|
||||
"name": "mock scm",
|
||||
@ -103,5 +109,6 @@
|
||||
"allow_override":true,
|
||||
"custom_virtualenv": "mock virtual env",
|
||||
"last_update_failed":false,
|
||||
"last_updated":"2020-03-11T20:18:14Z"
|
||||
"last_updated":"2020-03-11T20:18:14Z",
|
||||
"default_environment": 1
|
||||
}
|
||||
@ -11,6 +11,8 @@ import {
|
||||
ProjectDiagramIcon,
|
||||
RocketIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ActionsTd, ActionItem } from '../PaginatedTable';
|
||||
import { DetailList, Detail, DeletedDetail } from '../DetailList';
|
||||
import ChipGroup from '../ChipGroup';
|
||||
@ -23,6 +25,11 @@ import Sparkline from '../Sparkline';
|
||||
import { toTitleCase } from '../../util/strings';
|
||||
import CopyButton from '../CopyButton';
|
||||
|
||||
const ExclamationTriangleIconWarning = styled(ExclamationTriangleIcon)`
|
||||
color: var(--pf-global--warning-color--100);
|
||||
margin-left: 18px;
|
||||
`;
|
||||
|
||||
function TemplateListItem({
|
||||
i18n,
|
||||
template,
|
||||
@ -67,6 +74,11 @@ function TemplateListItem({
|
||||
(!summaryFields.project ||
|
||||
(!summaryFields.inventory && !askInventoryOnLaunch));
|
||||
|
||||
const missingExecutionEnvironment =
|
||||
template.type === 'job_template' &&
|
||||
template.custom_virtualenv &&
|
||||
!template.execution_environment;
|
||||
|
||||
const inventoryValue = (kind, id) => {
|
||||
const inventorykind = kind === 'smart' ? 'smart_inventory' : 'inventory';
|
||||
|
||||
@ -125,6 +137,19 @@ function TemplateListItem({
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
{missingExecutionEnvironment && (
|
||||
<span>
|
||||
<Tooltip
|
||||
className="missing-execution-environment"
|
||||
content={i18n._(
|
||||
t`Custom virtual environment ${template.custom_virtualenv} must be replaced by an execution environment.`
|
||||
)}
|
||||
position="right"
|
||||
>
|
||||
<ExclamationTriangleIconWarning />
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td dataLabel={i18n._(t`Type`)}>{toTitleCase(template.type)}</Td>
|
||||
<Td dataLabel={i18n._(t`Last Ran`)}>{lastRun}</Td>
|
||||
|
||||
@ -320,4 +320,61 @@ describe('<TemplateListItem />', () => {
|
||||
);
|
||||
expect(wrapper.find('ProjectDiagramIcon').length).toBe(0);
|
||||
});
|
||||
|
||||
test('should render warning about missing execution environment', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<table>
|
||||
<tbody>
|
||||
<TemplateListItem
|
||||
isSelected={false}
|
||||
template={{
|
||||
id: 1,
|
||||
name: 'Template 1',
|
||||
url: '/templates/job_template/1',
|
||||
type: 'job_template',
|
||||
summary_fields: {
|
||||
inventory: {
|
||||
id: 1,
|
||||
name: 'Demo Inventory',
|
||||
description: '',
|
||||
has_active_failures: false,
|
||||
total_hosts: 0,
|
||||
hosts_with_active_failures: 0,
|
||||
total_groups: 0,
|
||||
has_inventory_sources: false,
|
||||
total_inventory_sources: 0,
|
||||
inventory_sources_with_failures: 0,
|
||||
organization_id: 1,
|
||||
kind: '',
|
||||
},
|
||||
project: {
|
||||
id: 6,
|
||||
name: 'Demo Project',
|
||||
description: '',
|
||||
status: 'never updated',
|
||||
scm_type: 'git',
|
||||
},
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
delete: true,
|
||||
start: true,
|
||||
schedule: true,
|
||||
copy: true,
|
||||
},
|
||||
},
|
||||
custom_virtualenv: '/var/lib/awx/env',
|
||||
execution_environment: null,
|
||||
project: 6,
|
||||
inventory: 1,
|
||||
}}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
expect(
|
||||
wrapper.find('.missing-execution-environment').prop('content')
|
||||
).toEqual(
|
||||
'Custom virtual environment /var/lib/awx/env must be replaced by an execution environment.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -74,15 +74,9 @@ describe('<InventorySourceAdd />', () => {
|
||||
});
|
||||
|
||||
test('new form displays primary form fields', async () => {
|
||||
const config = {
|
||||
custom_virtualenvs: ['venv/foo', 'venv/bar'],
|
||||
};
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<InventorySourceAdd inventory={mockInventory} />,
|
||||
{
|
||||
context: { config },
|
||||
}
|
||||
<InventorySourceAdd inventory={mockInventory} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
@ -90,7 +84,7 @@ describe('<InventorySourceAdd />', () => {
|
||||
expect(wrapper.find('FormGroup[label="Description"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Source"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Ansible Environment"]')).toHaveLength(
|
||||
1
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import ContentError from '../../../components/ContentError';
|
||||
import ContentLoading from '../../../components/ContentLoading';
|
||||
import CredentialChip from '../../../components/CredentialChip';
|
||||
import DeleteButton from '../../../components/DeleteButton';
|
||||
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
|
||||
import InventorySourceSyncButton from '../shared/InventorySourceSyncButton';
|
||||
import {
|
||||
DetailList,
|
||||
@ -201,9 +202,9 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Detail
|
||||
label={i18n._(t`Ansible environment`)}
|
||||
value={custom_virtualenv}
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={custom_virtualenv}
|
||||
executionEnvironment={execution_environment}
|
||||
/>
|
||||
{source_project && (
|
||||
<Detail
|
||||
@ -215,18 +216,6 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{execution_environment?.name && (
|
||||
<Detail
|
||||
label={i18n._(t`Execution Environment`)}
|
||||
value={
|
||||
<Link
|
||||
to={`/execution_environments/${execution_environment.id}/details`}
|
||||
>
|
||||
{execution_environment.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{source === 'scm' ? (
|
||||
<Detail
|
||||
label={i18n._(t`Inventory file`)}
|
||||
|
||||
@ -58,11 +58,19 @@ describe('InventorySourceDetail', () => {
|
||||
assertDetail(wrapper, 'Description', 'mock description');
|
||||
assertDetail(wrapper, 'Source', 'Sourced from a Project');
|
||||
assertDetail(wrapper, 'Organization', 'Mock Org');
|
||||
assertDetail(wrapper, 'Ansible environment', '/var/lib/awx/venv/custom');
|
||||
assertDetail(wrapper, 'Project', 'Mock Project');
|
||||
assertDetail(wrapper, 'Inventory file', 'foo');
|
||||
assertDetail(wrapper, 'Verbosity', '2 (Debug)');
|
||||
assertDetail(wrapper, 'Cache timeout', '2 seconds');
|
||||
const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
|
||||
expect(executionEnvironment).toHaveLength(1);
|
||||
expect(executionEnvironment.find('dt').text()).toEqual(
|
||||
'Execution Environment'
|
||||
);
|
||||
expect(executionEnvironment.find('dd').text()).toEqual(
|
||||
mockInvSource.summary_fields.execution_environment.name
|
||||
);
|
||||
|
||||
expect(wrapper.find('CredentialChip').text()).toBe('Cloud: mock cred');
|
||||
expect(wrapper.find('VariablesDetail').prop('value')).toEqual(
|
||||
'---\nfoo: bar'
|
||||
|
||||
@ -12,10 +12,20 @@ import {
|
||||
DataListAction,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
import { PencilAltIcon } from '@patternfly/react-icons';
|
||||
import {
|
||||
ExclamationTriangleIcon as PFExclamationTriangleIcon,
|
||||
PencilAltIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import StatusIcon from '../../../components/StatusIcon';
|
||||
import InventorySourceSyncButton from '../shared/InventorySourceSyncButton';
|
||||
|
||||
const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
|
||||
color: var(--pf-global--warning-color--100);
|
||||
margin-left: 18px;
|
||||
`;
|
||||
|
||||
function InventorySourceListItem({
|
||||
source,
|
||||
isSelected,
|
||||
@ -42,6 +52,10 @@ function InventorySourceListItem({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const missingExecutionEnvironment =
|
||||
source.custom_virtualenv && !source.execution_environment;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataListItem aria-labelledby={`check-action-${source.id}`}>
|
||||
@ -79,6 +93,19 @@ function InventorySourceListItem({
|
||||
<b>{source.name}</b>
|
||||
</Link>
|
||||
</span>
|
||||
{missingExecutionEnvironment && (
|
||||
<span>
|
||||
<Tooltip
|
||||
className="missing-execution-environment"
|
||||
content={i18n._(
|
||||
t`Custom virtual environment ${source.custom_virtualenv} must be replaced by an execution environment.`
|
||||
)}
|
||||
position="right"
|
||||
>
|
||||
<ExclamationTriangleIcon />
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</DataListCell>,
|
||||
<DataListCell aria-label={i18n._(t`type`)} key="type">
|
||||
{label}
|
||||
|
||||
@ -139,4 +139,25 @@ describe('<InventorySourceListItem />', () => {
|
||||
expect(wrapper.find('Button[aria-label="Edit Source"]').length).toBe(0);
|
||||
expect(wrapper.find('InventorySourceSyncButton').length).toBe(1);
|
||||
});
|
||||
|
||||
test('should render warning about missing execution environment', () => {
|
||||
const onSelect = jest.fn();
|
||||
wrapper = mountWithContexts(
|
||||
<InventorySourceListItem
|
||||
source={{
|
||||
...source,
|
||||
custom_virtualenv: '/var/lib/awx/env',
|
||||
execution_environment: null,
|
||||
}}
|
||||
isSelected={false}
|
||||
onSelect={onSelect}
|
||||
label="Source Bar"
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
wrapper.find('.missing-execution-environment').prop('content')
|
||||
).toEqual(
|
||||
'Custom virtual environment /var/lib/awx/env must be replaced by an execution environment.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import React, { useEffect, useCallback, useContext } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { func, shape } from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Form, FormGroup, Title } from '@patternfly/react-core';
|
||||
import { InventorySourcesAPI } from '../../../api';
|
||||
import { ConfigContext } from '../../../contexts/Config';
|
||||
import useRequest from '../../../util/useRequest';
|
||||
import { required } from '../../../util/validators';
|
||||
|
||||
@ -18,7 +17,6 @@ import {
|
||||
FormColumnLayout,
|
||||
SubFormLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
import Popover from '../../../components/Popover';
|
||||
|
||||
import {
|
||||
AzureSubForm,
|
||||
@ -64,13 +62,6 @@ const InventorySourceFormFields = ({
|
||||
] = useField({
|
||||
name: 'execution_environment',
|
||||
});
|
||||
const { custom_virtualenvs } = useContext(ConfigContext);
|
||||
const [venvField] = useField('custom_virtualenv');
|
||||
const defaultVenv = {
|
||||
label: i18n._(t`Use Default Ansible Environment`),
|
||||
value: '/var/lib/awx/venv/ansible/',
|
||||
key: 'default',
|
||||
};
|
||||
|
||||
const resetSubFormFields = sourceType => {
|
||||
if (sourceType === initialValues.source) {
|
||||
@ -79,7 +70,6 @@ const InventorySourceFormFields = ({
|
||||
...initialValues,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
custom_virtualenv: values.custom_virtualenv,
|
||||
source: sourceType,
|
||||
},
|
||||
});
|
||||
@ -161,30 +151,6 @@ const InventorySourceFormFields = ({
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
{custom_virtualenvs && custom_virtualenvs.length > 1 && (
|
||||
<FormGroup
|
||||
fieldId="custom-virtualenv"
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
labelIcon={
|
||||
<Popover
|
||||
content={i18n._(t`Select the custom
|
||||
Python virtual environment for this
|
||||
inventory source sync to run on.`)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AnsibleSelect
|
||||
id="custom-virtualenv"
|
||||
data={[
|
||||
defaultVenv,
|
||||
...custom_virtualenvs
|
||||
.filter(value => value !== defaultVenv.value)
|
||||
.map(value => ({ value, label: value, key: value })),
|
||||
]}
|
||||
{...venvField}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{!['', 'custom'].includes(sourceField.value) && (
|
||||
<SubFormLayout>
|
||||
<Title size="md" headingLevel="h4">
|
||||
@ -272,7 +238,6 @@ const InventorySourceForm = ({
|
||||
}) => {
|
||||
const initialValues = {
|
||||
credential: source?.summary_fields?.credential || null,
|
||||
custom_virtualenv: source?.custom_virtualenv || '',
|
||||
description: source?.description || '',
|
||||
name: source?.name || '',
|
||||
overwrite: source?.overwrite || false,
|
||||
|
||||
@ -46,15 +46,9 @@ describe('<InventorySourceForm />', () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
beforeAll(async () => {
|
||||
const config = {
|
||||
custom_virtualenvs: ['venv/foo', 'venv/bar'],
|
||||
};
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<InventorySourceForm onCancel={() => {}} onSubmit={onSubmit} />,
|
||||
{
|
||||
context: { config },
|
||||
}
|
||||
<InventorySourceForm onCancel={() => {}} onSubmit={onSubmit} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
@ -71,7 +65,7 @@ describe('<InventorySourceForm />', () => {
|
||||
expect(wrapper.find('FormGroup[label="Source"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Ansible Environment"]')
|
||||
).toHaveLength(1);
|
||||
).toHaveLength(0);
|
||||
expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -10,7 +10,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
@ -115,7 +114,6 @@ describe('<SCMSubForm />', () => {
|
||||
test('should be able to create custom source path', async () => {
|
||||
const customInitialValues = {
|
||||
credential: { id: 1, name: 'Credential' },
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '/path',
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -9,7 +9,6 @@ jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
|
||||
@ -39,6 +39,12 @@
|
||||
"organization_id":1,
|
||||
"kind":""
|
||||
},
|
||||
"execution_environment": {
|
||||
"id": 1,
|
||||
"name": "Default EE",
|
||||
"description": "",
|
||||
"image": "quay.io/ansible/awx-ee"
|
||||
},
|
||||
"source_project":{
|
||||
"id":8,
|
||||
"name":"Mock Project",
|
||||
@ -111,5 +117,6 @@
|
||||
"source_project":8,
|
||||
"update_on_project_update":true,
|
||||
"last_update_failed": true,
|
||||
"last_updated":null
|
||||
"last_updated":null,
|
||||
"execution_environment": 1
|
||||
}
|
||||
@ -24,6 +24,7 @@ import {
|
||||
ReLaunchDropDown,
|
||||
} from '../../../components/LaunchButton';
|
||||
import StatusIcon from '../../../components/StatusIcon';
|
||||
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
|
||||
import { toTitleCase } from '../../../util/strings';
|
||||
import { formatDateString } from '../../../util/dates';
|
||||
import { Job } from '../../../types';
|
||||
@ -71,6 +72,7 @@ function JobDetail({ job, i18n }) {
|
||||
labels,
|
||||
project,
|
||||
source_workflow_job,
|
||||
execution_environment: executionEnvironment,
|
||||
} = job.summary_fields;
|
||||
const [errorMsg, setErrorMsg] = useState();
|
||||
const history = useHistory();
|
||||
@ -250,7 +252,10 @@ function JobDetail({ job, i18n }) {
|
||||
<Detail label={i18n._(t`Playbook`)} value={job.playbook} />
|
||||
<Detail label={i18n._(t`Limit`)} value={job.limit} />
|
||||
<Detail label={i18n._(t`Verbosity`)} value={VERBOSITY[job.verbosity]} />
|
||||
<Detail label={i18n._(t`Environment`)} value={job.custom_virtualenv} />
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={job.custom_virtualenv}
|
||||
executionEnvironment={executionEnvironment}
|
||||
/>
|
||||
<Detail label={i18n._(t`Execution Node`)} value={job.execution_node} />
|
||||
{instanceGroup && !instanceGroup?.is_container_group && (
|
||||
<Detail
|
||||
|
||||
@ -60,7 +60,6 @@ describe('<JobDetail />', () => {
|
||||
assertDetail('Revision', mockJobData.scm_revision);
|
||||
assertDetail('Playbook', mockJobData.playbook);
|
||||
assertDetail('Verbosity', '0 (Normal)');
|
||||
assertDetail('Environment', mockJobData.custom_virtualenv);
|
||||
assertDetail('Execution Node', mockJobData.execution_node);
|
||||
assertDetail(
|
||||
'Instance Group',
|
||||
@ -70,6 +69,15 @@ describe('<JobDetail />', () => {
|
||||
assertDetail('Credentials', 'SSH: Demo Credential');
|
||||
assertDetail('Machine Credential', 'SSH: Machine cred');
|
||||
|
||||
const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
|
||||
expect(executionEnvironment).toHaveLength(1);
|
||||
expect(executionEnvironment.find('dt').text()).toEqual(
|
||||
'Execution Environment'
|
||||
);
|
||||
expect(executionEnvironment.find('dd').text()).toEqual(
|
||||
mockJobData.summary_fields.execution_environment.name
|
||||
);
|
||||
|
||||
const credentialChip = wrapper.find(
|
||||
`Detail[label="Credentials"] CredentialChip`
|
||||
);
|
||||
|
||||
@ -36,6 +36,12 @@
|
||||
"organization_id": 1,
|
||||
"kind": ""
|
||||
},
|
||||
"execution_environment": {
|
||||
"id": 1,
|
||||
"name": "Default EE",
|
||||
"description": "",
|
||||
"image": "quay.io/ansible/awx-ee"
|
||||
},
|
||||
"project": {
|
||||
"id": 6,
|
||||
"name": "Demo Project",
|
||||
@ -184,5 +190,6 @@
|
||||
"play_count": 1,
|
||||
"task_count": 1
|
||||
},
|
||||
"custom_virtualenv": "/var/lib/awx/venv/ansible"
|
||||
"custom_virtualenv": "/var/lib/awx/venv/ansible",
|
||||
"execution_environment": 1
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useHistory, Link } from 'react-router-dom';
|
||||
import { t } from '@lingui/macro';
|
||||
import { withI18n } from '@lingui/react';
|
||||
@ -78,9 +77,5 @@ function NotificationTemplateAdd({ i18n }) {
|
||||
);
|
||||
}
|
||||
|
||||
NotificationTemplateAdd.contextTypes = {
|
||||
custom_virtualenvs: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export { NotificationTemplateAdd as _NotificationTemplateAdd };
|
||||
export default withI18n()(NotificationTemplateAdd);
|
||||
|
||||
@ -40,9 +40,5 @@ NotificationTemplateEdit.propTypes = {
|
||||
template: PropTypes.shape().isRequired,
|
||||
};
|
||||
|
||||
NotificationTemplateEdit.contextTypes = {
|
||||
custom_virtualenvs: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export { NotificationTemplateEdit as _NotificationTemplateEdit };
|
||||
export default NotificationTemplateEdit;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { PageSection, Card } from '@patternfly/react-core';
|
||||
|
||||
@ -51,9 +50,5 @@ function OrganizationAdd() {
|
||||
);
|
||||
}
|
||||
|
||||
OrganizationAdd.contextTypes = {
|
||||
custom_virtualenvs: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export { OrganizationAdd as _OrganizationAdd };
|
||||
export default OrganizationAdd;
|
||||
|
||||
@ -15,7 +15,6 @@ describe('<OrganizationAdd />', () => {
|
||||
const updatedOrgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
galaxy_credentials: [],
|
||||
default_environment: { id: 1, name: 'Foo' },
|
||||
};
|
||||
@ -51,7 +50,6 @@ describe('<OrganizationAdd />', () => {
|
||||
const orgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
galaxy_credentials: [],
|
||||
};
|
||||
OrganizationsAPI.create.mockResolvedValueOnce({
|
||||
@ -78,7 +76,6 @@ describe('<OrganizationAdd />', () => {
|
||||
const orgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
galaxy_credentials: [],
|
||||
};
|
||||
OrganizationsAPI.create.mockResolvedValueOnce({
|
||||
@ -103,7 +100,6 @@ describe('<OrganizationAdd />', () => {
|
||||
const orgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
galaxy_credentials: [
|
||||
{
|
||||
id: 9000,
|
||||
@ -131,36 +127,6 @@ describe('<OrganizationAdd />', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('AnsibleSelect component renders if there are virtual environments', async () => {
|
||||
const mockInstanceGroups = [
|
||||
{ name: 'One', id: 1 },
|
||||
{ name: 'Two', id: 2 },
|
||||
];
|
||||
OrganizationsAPI.readInstanceGroups.mockReturnValue({
|
||||
data: {
|
||||
results: mockInstanceGroups,
|
||||
},
|
||||
});
|
||||
const config = {
|
||||
custom_virtualenvs: ['foo', 'bar'],
|
||||
};
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { config },
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
expect(wrapper.find('FormSelect')).toHaveLength(1);
|
||||
expect(wrapper.find('FormSelectOption')).toHaveLength(3);
|
||||
expect(
|
||||
wrapper
|
||||
.find('FormSelectOption')
|
||||
.first()
|
||||
.prop('value')
|
||||
).toEqual('/var/lib/awx/venv/ansible/');
|
||||
});
|
||||
|
||||
test('AnsibleSelect component does not render if there are 0 virtual environments', async () => {
|
||||
const mockInstanceGroups = [
|
||||
{ name: 'One', id: 1 },
|
||||
@ -171,14 +137,9 @@ describe('<OrganizationAdd />', () => {
|
||||
results: mockInstanceGroups,
|
||||
},
|
||||
});
|
||||
const config = {
|
||||
custom_virtualenvs: [],
|
||||
};
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { config },
|
||||
});
|
||||
wrapper = mountWithContexts(<OrganizationAdd />, {});
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
expect(wrapper.find('AnsibleSelect FormSelect')).toHaveLength(0);
|
||||
|
||||
@ -19,6 +19,7 @@ import DeleteButton from '../../../components/DeleteButton';
|
||||
import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import { useConfig } from '../../../contexts/Config';
|
||||
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
|
||||
|
||||
function OrganizationDetail({ i18n, organization }) {
|
||||
const {
|
||||
@ -90,22 +91,11 @@ function OrganizationDetail({ i18n, organization }) {
|
||||
{license_info?.license_type !== 'open' && (
|
||||
<Detail label={i18n._(t`Max Hosts`)} value={`${max_hosts}`} />
|
||||
)}
|
||||
<Detail
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
value={custom_virtualenv}
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={custom_virtualenv}
|
||||
executionEnvironment={summary_fields?.default_environment}
|
||||
isDefaultEnvironment
|
||||
/>
|
||||
{summary_fields?.default_environment?.name && (
|
||||
<Detail
|
||||
label={i18n._(t`Default Execution Environment`)}
|
||||
value={
|
||||
<Link
|
||||
to={`/execution_environments/${summary_fields.default_environment.id}/details`}
|
||||
>
|
||||
{summary_fields.default_environment.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<UserDateDetail
|
||||
label={i18n._(t`Created`)}
|
||||
date={created}
|
||||
|
||||
@ -90,7 +90,6 @@ describe('<OrganizationDetail />', () => {
|
||||
const testParams = [
|
||||
{ label: 'Name', value: 'Foo' },
|
||||
{ label: 'Description', value: 'Bar' },
|
||||
{ label: 'Ansible Environment', value: 'Fizz' },
|
||||
{ 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' },
|
||||
|
||||
@ -80,9 +80,5 @@ OrganizationEdit.propTypes = {
|
||||
organization: PropTypes.shape().isRequired,
|
||||
};
|
||||
|
||||
OrganizationEdit.contextTypes = {
|
||||
custom_virtualenvs: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export { OrganizationEdit as _OrganizationEdit };
|
||||
export default OrganizationEdit;
|
||||
|
||||
@ -14,7 +14,6 @@ describe('<OrganizationEdit />', () => {
|
||||
const mockData = {
|
||||
name: 'Foo',
|
||||
description: 'Bar',
|
||||
custom_virtualenv: 'Fizz',
|
||||
id: 1,
|
||||
related: {
|
||||
instance_groups: '/api/v2/organizations/1/instance_groups',
|
||||
@ -24,6 +23,7 @@ describe('<OrganizationEdit />', () => {
|
||||
default_environment: {
|
||||
id: 1,
|
||||
name: 'Baz',
|
||||
image: 'quay.io/ansible/awx-ee',
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -37,7 +37,6 @@ describe('<OrganizationEdit />', () => {
|
||||
const updatedOrgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
default_environment: null,
|
||||
};
|
||||
wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, [], []);
|
||||
@ -54,7 +53,6 @@ describe('<OrganizationEdit />', () => {
|
||||
const updatedOrgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
};
|
||||
await act(async () => {
|
||||
wrapper.find('OrganizationForm').invoke('onSubmit')(
|
||||
|
||||
@ -2,14 +2,22 @@ import React from 'react';
|
||||
import { string, bool, func } from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
import { Button, Tooltip } from '@patternfly/react-core';
|
||||
import { Tr, Td } from '@patternfly/react-table';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PencilAltIcon } from '@patternfly/react-icons';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
ExclamationTriangleIcon as PFExclamationTriangleIcon,
|
||||
PencilAltIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import { ActionsTd, ActionItem } from '../../../components/PaginatedTable';
|
||||
|
||||
import { Organization } from '../../../types';
|
||||
|
||||
const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
|
||||
color: var(--pf-global--warning-color--100);
|
||||
margin-left: 18px;
|
||||
`;
|
||||
function OrganizationListItem({
|
||||
organization,
|
||||
isSelected,
|
||||
@ -19,6 +27,10 @@ function OrganizationListItem({
|
||||
i18n,
|
||||
}) {
|
||||
const labelId = `check-action-${organization.id}`;
|
||||
|
||||
const missingExecutionEnvironment =
|
||||
organization.custom_virtualenv && !organization.default_environment;
|
||||
|
||||
return (
|
||||
<Tr id={`org-row-${organization.id}`}>
|
||||
<Td
|
||||
@ -31,9 +43,24 @@ function OrganizationListItem({
|
||||
dataLabel={i18n._(t`Selected`)}
|
||||
/>
|
||||
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
|
||||
<Link to={`${detailUrl}`}>
|
||||
<b>{organization.name}</b>
|
||||
</Link>
|
||||
<span>
|
||||
<Link to={`${detailUrl}`}>
|
||||
<b>{organization.name}</b>
|
||||
</Link>
|
||||
</span>
|
||||
{missingExecutionEnvironment && (
|
||||
<span>
|
||||
<Tooltip
|
||||
className="missing-execution-environment"
|
||||
content={i18n._(
|
||||
t`Custom virtual environment ${organization.custom_virtualenv} must be replaced by an execution environment.`
|
||||
)}
|
||||
position="right"
|
||||
>
|
||||
<ExclamationTriangleIcon />
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td dataLabel={i18n._(t`Members`)}>
|
||||
{organization.summary_fields.related_field_counts.users}
|
||||
|
||||
@ -7,7 +7,7 @@ import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
import OrganizationListItem from './OrganizationListItem';
|
||||
|
||||
describe('<OrganizationListItem />', () => {
|
||||
test('initially renders succesfully', () => {
|
||||
test('initially renders successfully', () => {
|
||||
mountWithContexts(
|
||||
<I18nProvider>
|
||||
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
|
||||
@ -101,4 +101,38 @@ describe('<OrganizationListItem />', () => {
|
||||
);
|
||||
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should render warning about missing execution environment', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<table>
|
||||
<tbody>
|
||||
<OrganizationListItem
|
||||
organization={{
|
||||
id: 1,
|
||||
name: 'Org',
|
||||
summary_fields: {
|
||||
related_field_counts: {
|
||||
users: 1,
|
||||
teams: 1,
|
||||
},
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
},
|
||||
},
|
||||
custom_virtualenv: '/var/lib/awx/env',
|
||||
default_environment: null,
|
||||
}}
|
||||
detailUrl="/organization/1"
|
||||
isSelected
|
||||
onSelect={() => {}}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
expect(
|
||||
wrapper.find('.missing-execution-environment').prop('content')
|
||||
).toEqual(
|
||||
'Custom virtual environment /var/lib/awx/env must be replaced by an execution environment.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Form, FormGroup } from '@patternfly/react-core';
|
||||
import { Form } from '@patternfly/react-core';
|
||||
|
||||
import { OrganizationsAPI } from '../../../api';
|
||||
import { ConfigContext, useConfig } from '../../../contexts/Config';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
import { useConfig } from '../../../contexts/Config';
|
||||
import ContentError from '../../../components/ContentError';
|
||||
import ContentLoading from '../../../components/ContentLoading';
|
||||
import FormField, { FormSubmitError } from '../../../components/FormField';
|
||||
@ -23,10 +22,8 @@ import CredentialLookup from '../../../components/Lookup/CredentialLookup';
|
||||
|
||||
function OrganizationFormFields({ i18n, instanceGroups, setInstanceGroups }) {
|
||||
const { license_info = {}, me = {} } = useConfig();
|
||||
const { custom_virtualenvs } = useContext(ConfigContext);
|
||||
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [venvField] = useField('custom_virtualenv');
|
||||
|
||||
const [
|
||||
galaxyCredentialsField,
|
||||
@ -42,12 +39,6 @@ function OrganizationFormFields({ i18n, instanceGroups, setInstanceGroups }) {
|
||||
name: 'default_environment',
|
||||
});
|
||||
|
||||
const defaultVenv = {
|
||||
label: i18n._(t`Use Default Ansible Environment`),
|
||||
value: '/var/lib/awx/venv/ansible/',
|
||||
key: 'default',
|
||||
};
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('galaxy_credentials', value);
|
||||
@ -87,24 +78,6 @@ function OrganizationFormFields({ i18n, instanceGroups, setInstanceGroups }) {
|
||||
isDisabled={!me.is_superuser}
|
||||
/>
|
||||
)}
|
||||
|
||||
{custom_virtualenvs && custom_virtualenvs.length > 1 && (
|
||||
<FormGroup
|
||||
fieldId="org-custom-virtualenv"
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
>
|
||||
<AnsibleSelect
|
||||
id="org-custom-virtualenv"
|
||||
data={[
|
||||
defaultVenv,
|
||||
...custom_virtualenvs
|
||||
.filter(value => value !== defaultVenv.value)
|
||||
.map(value => ({ value, label: value, key: value })),
|
||||
]}
|
||||
{...venvField}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
<InstanceGroupsLookup
|
||||
value={instanceGroups}
|
||||
onChange={setInstanceGroups}
|
||||
@ -208,11 +181,10 @@ function OrganizationForm({
|
||||
initialValues={{
|
||||
name: organization.name,
|
||||
description: organization.description,
|
||||
custom_virtualenv: organization.custom_virtualenv || '',
|
||||
max_hosts: organization.max_hosts || '0',
|
||||
galaxy_credentials: organization.galaxy_credentials || [],
|
||||
default_environment:
|
||||
organization.summary_fields?.default_environment || '',
|
||||
organization.summary_fields?.default_environment || null,
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
@ -248,15 +220,10 @@ OrganizationForm.defaultProps = {
|
||||
name: '',
|
||||
description: '',
|
||||
max_hosts: '0',
|
||||
custom_virtualenv: '',
|
||||
default_environment: '',
|
||||
},
|
||||
submitError: null,
|
||||
};
|
||||
|
||||
OrganizationForm.contextTypes = {
|
||||
custom_virtualenvs: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export { OrganizationForm as _OrganizationForm };
|
||||
export default withI18n()(OrganizationForm);
|
||||
|
||||
@ -22,7 +22,6 @@ describe('<OrganizationForm />', () => {
|
||||
name: 'Foo',
|
||||
description: 'Bar',
|
||||
max_hosts: 1,
|
||||
custom_virtualenv: 'Fizz',
|
||||
related: {
|
||||
instance_groups: '/api/v2/organizations/1/instance_groups',
|
||||
},
|
||||
@ -32,7 +31,9 @@ describe('<OrganizationForm />', () => {
|
||||
{ name: 'Two', id: 2 },
|
||||
];
|
||||
|
||||
const mockExecutionEnvironment = [{ name: 'EE' }];
|
||||
const mockExecutionEnvironment = [
|
||||
{ id: 1, name: 'EE', image: 'quay.io/ansible/awx-ee' },
|
||||
];
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@ -176,46 +177,11 @@ describe('<OrganizationForm />', () => {
|
||||
name: 'new foo',
|
||||
description: 'new bar',
|
||||
galaxy_credentials: [],
|
||||
custom_virtualenv: 'Fizz',
|
||||
max_hosts: 134,
|
||||
default_environment: { id: 1, name: 'Test EE' },
|
||||
});
|
||||
});
|
||||
|
||||
test('AnsibleSelect component renders if there are virtual environments', async () => {
|
||||
const config = {
|
||||
custom_virtualenvs: ['foo', 'bar'],
|
||||
};
|
||||
OrganizationsAPI.readInstanceGroups.mockReturnValue({
|
||||
data: {
|
||||
results: mockInstanceGroups,
|
||||
},
|
||||
});
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationForm
|
||||
organization={mockData}
|
||||
onSubmit={jest.fn()}
|
||||
onCancel={jest.fn()}
|
||||
me={meConfig.me}
|
||||
/>,
|
||||
{
|
||||
context: { config },
|
||||
}
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
expect(wrapper.find('FormSelect')).toHaveLength(1);
|
||||
expect(wrapper.find('FormSelectOption')).toHaveLength(3);
|
||||
expect(
|
||||
wrapper
|
||||
.find('FormSelectOption')
|
||||
.first()
|
||||
.prop('value')
|
||||
).toEqual('/var/lib/awx/venv/ansible/');
|
||||
});
|
||||
|
||||
test('onSubmit associates and disassociates instance groups', async () => {
|
||||
OrganizationsAPI.readInstanceGroups.mockReturnValue({
|
||||
data: {
|
||||
@ -230,8 +196,7 @@ describe('<OrganizationForm />', () => {
|
||||
description: 'Bar',
|
||||
galaxy_credentials: [],
|
||||
max_hosts: 1,
|
||||
custom_virtualenv: 'Fizz',
|
||||
default_environment: '',
|
||||
default_environment: null,
|
||||
};
|
||||
const onSubmit = jest.fn();
|
||||
OrganizationsAPI.update.mockResolvedValue(1, mockDataForm);
|
||||
@ -336,8 +301,7 @@ describe('<OrganizationForm />', () => {
|
||||
description: 'Bar',
|
||||
galaxy_credentials: [],
|
||||
max_hosts: 0,
|
||||
custom_virtualenv: 'Fizz',
|
||||
default_environment: '',
|
||||
default_environment: null,
|
||||
},
|
||||
[],
|
||||
[]
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
UserDateDetail,
|
||||
} from '../../../components/DetailList';
|
||||
import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
|
||||
import CredentialChip from '../../../components/CredentialChip';
|
||||
import { ProjectsAPI } from '../../../api';
|
||||
import { toTitleCase } from '../../../util/strings';
|
||||
@ -124,23 +125,11 @@ function ProjectDetail({ project, i18n }) {
|
||||
label={i18n._(t`Cache Timeout`)}
|
||||
value={`${scm_update_cache_timeout} ${i18n._(t`Seconds`)}`}
|
||||
/>
|
||||
|
||||
<Detail
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
value={custom_virtualenv}
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={custom_virtualenv}
|
||||
executionEnvironment={summary_fields?.default_environment}
|
||||
isDefaultEnvironment
|
||||
/>
|
||||
{summary_fields?.default_environment?.name && (
|
||||
<Detail
|
||||
label={i18n._(t`Execution Environment`)}
|
||||
value={
|
||||
<Link
|
||||
to={`/execution_environments/${summary_fields.default_environment.id}/details`}
|
||||
>
|
||||
{summary_fields.default_environment.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Config>
|
||||
{({ project_base_dir }) => (
|
||||
<Detail
|
||||
|
||||
@ -100,11 +100,15 @@ describe('<ProjectDetail />', () => {
|
||||
'Cache Timeout',
|
||||
`${mockProject.scm_update_cache_timeout} Seconds`
|
||||
);
|
||||
assertDetail('Ansible Environment', mockProject.custom_virtualenv);
|
||||
assertDetail(
|
||||
'Execution Environment',
|
||||
const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
|
||||
expect(executionEnvironment).toHaveLength(1);
|
||||
expect(executionEnvironment.find('dt').text()).toEqual(
|
||||
'Default Execution Environment'
|
||||
);
|
||||
expect(executionEnvironment.find('dd').text()).toEqual(
|
||||
mockProject.summary_fields.default_environment.name
|
||||
);
|
||||
|
||||
const dateDetails = wrapper.find('UserDateDetail');
|
||||
expect(dateDetails).toHaveLength(2);
|
||||
expect(dateDetails.at(0).prop('label')).toEqual('Created');
|
||||
|
||||
@ -31,6 +31,11 @@ const DataListAction = styled(_DataListAction)`
|
||||
grid-template-columns: repeat(2, 40px);
|
||||
`;
|
||||
|
||||
const ExclamationTriangleIconWarning = styled(ExclamationTriangleIcon)`
|
||||
color: var(--pf-global--warning-color--100);
|
||||
margin-left: 18px;
|
||||
`;
|
||||
|
||||
function ProjectJobTemplateListItem({
|
||||
i18n,
|
||||
template,
|
||||
@ -47,6 +52,11 @@ function ProjectJobTemplateListItem({
|
||||
(!template.summary_fields.inventory &&
|
||||
!template.ask_inventory_on_launch));
|
||||
|
||||
const missingExecutionEnvironment =
|
||||
template.type === 'job_template' &&
|
||||
template.custom_virtualenv &&
|
||||
!template.execution_environment;
|
||||
|
||||
return (
|
||||
<DataListItem aria-labelledby={labelId} id={`${template.id}`}>
|
||||
<DataListItemRow>
|
||||
@ -76,6 +86,19 @@ function ProjectJobTemplateListItem({
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
{missingExecutionEnvironment && (
|
||||
<span>
|
||||
<Tooltip
|
||||
content={i18n._(
|
||||
t`Custom virtual environment ${template.custom_virtualenv} must be replaced by an execution environment.`
|
||||
)}
|
||||
position="right"
|
||||
className="missing-execution-environment"
|
||||
>
|
||||
<ExclamationTriangleIconWarning />
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</DataListCell>,
|
||||
<DataListCell key="type">
|
||||
{toTitleCase(template.type)}
|
||||
|
||||
@ -186,4 +186,31 @@ describe('<ProjectJobTemplatesListItem />', () => {
|
||||
'/templates/job_template/2/details'
|
||||
);
|
||||
});
|
||||
|
||||
test('should render warning about missing execution environment', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<ProjectJobTemplatesListItem
|
||||
isSelected={false}
|
||||
template={{
|
||||
id: 1,
|
||||
name: 'Template 1',
|
||||
url: '/templates/job_template/1',
|
||||
type: 'job_template',
|
||||
summary_fields: {
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
},
|
||||
},
|
||||
custom_virtualenv: '/var/lib/awx/env',
|
||||
execution_environment: null,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('.missing-execution-environment').prop('content')
|
||||
).toEqual(
|
||||
'Custom virtual environment /var/lib/awx/env must be replaced by an execution environment.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -6,7 +6,10 @@ import { Button, Tooltip } from '@patternfly/react-core';
|
||||
import { Tr, Td } from '@patternfly/react-table';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PencilAltIcon } from '@patternfly/react-icons';
|
||||
import {
|
||||
PencilAltIcon,
|
||||
ExclamationTriangleIcon as PFExclamationTriangleIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import styled from 'styled-components';
|
||||
import { ActionsTd, ActionItem } from '../../../components/PaginatedTable';
|
||||
import { formatDateString, timeOfDay } from '../../../util/dates';
|
||||
@ -22,6 +25,11 @@ const Label = styled.span`
|
||||
color: var(--pf-global--disabled-color--100);
|
||||
`;
|
||||
|
||||
const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
|
||||
color: var(--pf-global--warning-color--100);
|
||||
margin-left: 18px;
|
||||
`;
|
||||
|
||||
function ProjectListItem({
|
||||
project,
|
||||
isSelected,
|
||||
@ -75,6 +83,9 @@ function ProjectListItem({
|
||||
|
||||
const labelId = `check-action-${project.id}`;
|
||||
|
||||
const missingExecutionEnvironment =
|
||||
project.custom_virtualenv && !project.default_environment;
|
||||
|
||||
return (
|
||||
<Tr id={`${project.id}`}>
|
||||
<Td
|
||||
@ -86,9 +97,24 @@ function ProjectListItem({
|
||||
dataLabel={i18n._(t`Selected`)}
|
||||
/>
|
||||
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
|
||||
<Link id={labelId} to={`${detailUrl}`}>
|
||||
<b>{project.name}</b>
|
||||
</Link>
|
||||
<span>
|
||||
<Link id={labelId} to={`${detailUrl}`}>
|
||||
<b>{project.name}</b>
|
||||
</Link>
|
||||
</span>
|
||||
{missingExecutionEnvironment && (
|
||||
<span>
|
||||
<Tooltip
|
||||
content={i18n._(
|
||||
t`Custom virtual environment ${project.custom_virtualenv} must be replaced by an execution environment.`
|
||||
)}
|
||||
position="right"
|
||||
className="missing-execution-environment"
|
||||
>
|
||||
<ExclamationTriangleIcon />
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td dataLabel={i18n._(t`Status`)}>
|
||||
{project.summary_fields.last_job && (
|
||||
|
||||
@ -40,6 +40,45 @@ describe('<ProjectsListItem />', () => {
|
||||
expect(wrapper.find('ProjectSyncButton').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should render warning about missing execution environment', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<table>
|
||||
<tbody>
|
||||
<ProjectsListItem
|
||||
isSelected={false}
|
||||
detailUrl="/project/1"
|
||||
onSelect={() => {}}
|
||||
project={{
|
||||
id: 1,
|
||||
name: 'Project 1',
|
||||
url: '/api/v2/projects/1',
|
||||
type: 'project',
|
||||
scm_type: 'git',
|
||||
scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
|
||||
summary_fields: {
|
||||
last_job: {
|
||||
id: 9000,
|
||||
status: 'successful',
|
||||
},
|
||||
user_capabilities: {
|
||||
start: true,
|
||||
},
|
||||
},
|
||||
custom_virtualenv: '/var/lib/awx/env',
|
||||
default_environment: null,
|
||||
}}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('.missing-execution-environment').prop('content')
|
||||
).toEqual(
|
||||
'Custom virtual environment /var/lib/awx/env must be replaced by an execution environment.'
|
||||
);
|
||||
});
|
||||
|
||||
test('launch button hidden from users without start capabilities', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<table>
|
||||
|
||||
@ -30,6 +30,12 @@
|
||||
"name": "Default",
|
||||
"description": ""
|
||||
},
|
||||
"execution_environment": {
|
||||
"id": 1,
|
||||
"name": "Default EE",
|
||||
"description": "",
|
||||
"image": "quay.io/ansible/awx-ee"
|
||||
},
|
||||
"last_job": {
|
||||
"id": 8,
|
||||
"name": "Mike's Project",
|
||||
@ -111,5 +117,6 @@
|
||||
"allow_override": false,
|
||||
"custom_virtualenv": null,
|
||||
"last_update_failed": false,
|
||||
"last_updated": "2019-09-30T18:06:34.713654Z"
|
||||
"last_updated": "2019-09-30T18:06:34.713654Z",
|
||||
"execution_environment": 1
|
||||
}
|
||||
@ -19,7 +19,6 @@ import {
|
||||
FormColumnLayout,
|
||||
SubFormLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
import Popover from '../../../components/Popover';
|
||||
import {
|
||||
GitSubForm,
|
||||
SvnSubForm,
|
||||
@ -96,7 +95,6 @@ function ProjectFormFields({
|
||||
name: 'scm_type',
|
||||
validate: required(i18n._(t`Set a value for this field`), i18n),
|
||||
});
|
||||
const [venvField] = useField('custom_virtualenv');
|
||||
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
||||
name: 'organization',
|
||||
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||
@ -293,42 +291,6 @@ function ProjectFormFields({
|
||||
</FormColumnLayout>
|
||||
</SubFormLayout>
|
||||
)}
|
||||
<Config>
|
||||
{({ custom_virtualenvs }) =>
|
||||
custom_virtualenvs &&
|
||||
custom_virtualenvs.length > 1 && (
|
||||
<FormGroup
|
||||
fieldId="project-custom-virtualenv"
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
labelIcon={
|
||||
<Popover
|
||||
content={i18n._(t`Select the playbook to be executed by
|
||||
this job.`)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AnsibleSelect
|
||||
id="project-custom-virtualenv"
|
||||
data={[
|
||||
{
|
||||
label: i18n._(t`Use Default Ansible Environment`),
|
||||
value: '/var/lib/awx/venv/ansible/',
|
||||
key: 'default',
|
||||
},
|
||||
...custom_virtualenvs
|
||||
.filter(datum => datum !== '/var/lib/awx/venv/ansible/')
|
||||
.map(datum => ({
|
||||
label: datum,
|
||||
value: datum,
|
||||
key: datum,
|
||||
})),
|
||||
]}
|
||||
{...venvField}
|
||||
/>
|
||||
</FormGroup>
|
||||
)
|
||||
}
|
||||
</Config>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -397,7 +359,6 @@ function ProjectForm({ i18n, project, submitError, ...props }) {
|
||||
allow_override: project.allow_override || false,
|
||||
base_dir: project_base_dir || '',
|
||||
credential: project.credential || '',
|
||||
custom_virtualenv: project.custom_virtualenv || '',
|
||||
description: project.description || '',
|
||||
local_path: project.local_path || '',
|
||||
name: project.name || '',
|
||||
|
||||
@ -107,15 +107,9 @@ describe('<ProjectForm />', () => {
|
||||
});
|
||||
|
||||
test('new form displays primary form fields', async () => {
|
||||
const config = {
|
||||
custom_virtualenvs: ['venv/foo', 'venv/bar'],
|
||||
};
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ProjectForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />,
|
||||
{
|
||||
context: { config },
|
||||
}
|
||||
<ProjectForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
@ -126,7 +120,7 @@ describe('<ProjectForm />', () => {
|
||||
wrapper.find('FormGroup[label="Source Control Credential Type"]').length
|
||||
).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Ansible Environment"]').length).toBe(
|
||||
1
|
||||
0
|
||||
);
|
||||
expect(wrapper.find('FormGroup[label="Options"]').length).toBe(0);
|
||||
});
|
||||
|
||||
@ -37,7 +37,6 @@ function MiscSystemDetail({ i18n }) {
|
||||
'AUTH_BASIC_ENABLED',
|
||||
'AUTOMATION_ANALYTICS_GATHER_INTERVAL',
|
||||
'AUTOMATION_ANALYTICS_URL',
|
||||
'CUSTOM_VENV_PATHS',
|
||||
'INSIGHTS_TRACKING_STATE',
|
||||
'LOGIN_REDIRECT_OVERRIDE',
|
||||
'MANAGE_ORGANIZATION_AUTH',
|
||||
|
||||
@ -110,7 +110,6 @@ describe('<MiscSystemDetail />', () => {
|
||||
assertDetail(wrapper, 'Red Hat customer username', 'mock name');
|
||||
assertDetail(wrapper, 'Refresh Token Expiration', '3 seconds');
|
||||
assertVariableDetail(wrapper, 'Remote Host Headers', '[]');
|
||||
assertVariableDetail(wrapper, 'Custom virtual environment paths', '[]');
|
||||
});
|
||||
|
||||
test('should hide edit button from non-superusers', async () => {
|
||||
|
||||
@ -130,7 +130,6 @@ function MiscSystemEdit({ i18n }) {
|
||||
} = form;
|
||||
await submitForm({
|
||||
...formData,
|
||||
CUSTOM_VENV_PATHS: formatJson(formData.CUSTOM_VENV_PATHS),
|
||||
REMOTE_HOST_HEADERS: formatJson(formData.REMOTE_HOST_HEADERS),
|
||||
OAUTH2_PROVIDER: {
|
||||
ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||
@ -271,10 +270,6 @@ function MiscSystemEdit({ i18n }) {
|
||||
config={system.REMOTE_HOST_HEADERS}
|
||||
isRequired
|
||||
/>
|
||||
<ObjectField
|
||||
name="CUSTOM_VENV_PATHS"
|
||||
config={system.CUSTOM_VENV_PATHS}
|
||||
/>
|
||||
{submitError && <FormSubmitError error={submitError} />}
|
||||
</FormColumnLayout>
|
||||
<RevertFormActionGroup
|
||||
|
||||
@ -46,8 +46,4 @@ TeamEdit.propTypes = {
|
||||
team: PropTypes.shape().isRequired,
|
||||
};
|
||||
|
||||
TeamEdit.contextTypes = {
|
||||
custom_virtualenvs: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export default TeamEdit;
|
||||
|
||||
@ -30,6 +30,7 @@ import { LaunchButton } from '../../../components/LaunchButton';
|
||||
import { VariablesDetail } from '../../../components/CodeEditor';
|
||||
import { JobTemplatesAPI } from '../../../api';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
|
||||
|
||||
function JobTemplateDetail({ i18n, template }) {
|
||||
const {
|
||||
@ -58,6 +59,7 @@ function JobTemplateDetail({ i18n, template }) {
|
||||
webhook_service,
|
||||
related: { webhook_receiver },
|
||||
webhook_key,
|
||||
custom_virtualenv,
|
||||
} = template;
|
||||
const { id: templateId } = useParams();
|
||||
const history = useHistory();
|
||||
@ -206,18 +208,10 @@ function JobTemplateDetail({ i18n, template }) {
|
||||
) : (
|
||||
<DeletedDetail label={i18n._(t`Project`)} />
|
||||
)}
|
||||
{summary_fields?.execution_environment && (
|
||||
<Detail
|
||||
label={i18n._(t`Execution Environment`)}
|
||||
value={
|
||||
<Link
|
||||
to={`/execution_environments/${summary_fields.execution_environment.id}/details`}
|
||||
>
|
||||
{summary_fields.execution_environment.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ExecutionEnvironmentDetail
|
||||
virtualEnvironment={custom_virtualenv}
|
||||
executionEnvironment={summary_fields?.execution_environment}
|
||||
/>
|
||||
<Detail
|
||||
label={i18n._(t`Source Control Branch`)}
|
||||
value={template.scm_branch}
|
||||
|
||||
@ -215,7 +215,10 @@ CredentialsAPI.read.mockResolvedValue({
|
||||
CredentialTypesAPI.loadAllTypes.mockResolvedValue([]);
|
||||
|
||||
ExecutionEnvironmentsAPI.read.mockResolvedValue({
|
||||
data: mockExecutionEnvironment,
|
||||
data: {
|
||||
results: mockExecutionEnvironment,
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
|
||||
describe('<JobTemplateEdit />', () => {
|
||||
|
||||
@ -68,7 +68,10 @@ describe('<WorkflowJobTemplateEdit/>', () => {
|
||||
});
|
||||
OrganizationsAPI.read.mockResolvedValue({ results: [{ id: 1 }] });
|
||||
ExecutionEnvironmentsAPI.read.mockResolvedValue({
|
||||
data: mockExecutionEnvironment,
|
||||
data: {
|
||||
results: mockExecutionEnvironment,
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@ -732,7 +732,7 @@ const FormikApp = withFormik({
|
||||
i18n._(t`a new webhook key will be generated on save.`).toUpperCase(),
|
||||
webhook_credential: template?.summary_fields?.webhook_credential || null,
|
||||
execution_environment:
|
||||
template.summary_fields?.execution_environment || '',
|
||||
template.summary_fields?.execution_environment || null,
|
||||
};
|
||||
},
|
||||
handleSubmit: async (values, { props, setErrors }) => {
|
||||
|
||||
@ -322,7 +322,7 @@ const FormikApp = withFormik({
|
||||
: '',
|
||||
webhook_key: template.webhook_key || '',
|
||||
execution_environment:
|
||||
template.summary_fields?.execution_environment || '',
|
||||
template.summary_fields?.execution_environment || null,
|
||||
};
|
||||
},
|
||||
handleSubmit: async (values, { props, setErrors }) => {
|
||||
|
||||
@ -410,9 +410,10 @@ export const WorkflowApproval = shape({
|
||||
|
||||
export const ExecutionEnvironment = shape({
|
||||
id: number.isRequired,
|
||||
name: string,
|
||||
organization: number,
|
||||
credential: number,
|
||||
image: string.isRequired,
|
||||
image: string,
|
||||
url: string,
|
||||
summary_fields: shape({}),
|
||||
description: string,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user