Skip to content

Commit

Permalink
Add EE to the settings page
Browse files Browse the repository at this point in the history
Allow a system admin to set the global default execution environment.

See: #9088
  • Loading branch information
nixocio committed Mar 22, 2021
1 parent f11bc11 commit e57b3de
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useCallback } from 'react';
import React, { useEffect, useCallback, useState } from 'react';
import { Link } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
Expand All @@ -7,9 +7,9 @@ import { CaretLeftIcon } from '@patternfly/react-icons';
import { CardBody, CardActionsRow } from '../../../../components/Card';
import ContentError from '../../../../components/ContentError';
import ContentLoading from '../../../../components/ContentLoading';
import { DetailList } from '../../../../components/DetailList';
import { DetailList, Detail } from '../../../../components/DetailList';
import RoutedTabs from '../../../../components/RoutedTabs';
import { SettingsAPI } from '../../../../api';
import { SettingsAPI, ExecutionEnvironmentsAPI } from '../../../../api';
import useRequest from '../../../../util/useRequest';
import { useConfig } from '../../../../contexts/Config';
import { useSettings } from '../../../../contexts/Settings';
Expand All @@ -20,6 +20,25 @@ function MiscSystemDetail({ i18n }) {
const { me } = useConfig();
const { GET: allOptions } = useSettings();

const [executionEnvironmentId, setExecutionEnvironmentId] = useState('');

const {
isLoading: isLoadingExecutionEnvironment,
error: errorExecutionEnvironment,
request: fetchExecutionEnvironment,
result: executionEnvironment,
} = useRequest(
useCallback(async () => {
if (!executionEnvironmentId) {
return '';
}
const { data } = await ExecutionEnvironmentsAPI.readDetail(
executionEnvironmentId
);
return data;
}, [executionEnvironmentId])
);

const { isLoading, error, request, result: system } = useRequest(
useCallback(async () => {
const { data } = await SettingsAPI.readCategory('all');
Expand Down Expand Up @@ -47,7 +66,8 @@ function MiscSystemDetail({ i18n }) {
'REMOTE_HOST_HEADERS',
'SESSIONS_PER_USER',
'SESSION_COOKIE_AGE',
'TOWER_URL_BASE'
'TOWER_URL_BASE',
'DEFAULT_EXECUTION_ENVIRONMENT'
);

const systemData = {
Expand Down Expand Up @@ -86,6 +106,10 @@ function MiscSystemDetail({ i18n }) {
mergedData[key] = systemOptions[key];
mergedData[key].value = systemData[key];
});

setExecutionEnvironmentId(
mergedData.DEFAULT_EXECUTION_ENVIRONMENT?.value
);
return sortNestedDetails(mergedData);
}, [allOptions, i18n]),
null
Expand All @@ -95,6 +119,10 @@ function MiscSystemDetail({ i18n }) {
request();
}, [request]);

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

const tabsArray = [
{
name: (
Expand All @@ -113,25 +141,48 @@ function MiscSystemDetail({ i18n }) {
},
];

const renderDetail = (key, detail) => {
if (key !== 'DEFAULT_EXECUTION_ENVIRONMENT') {
return (
<SettingDetail
key={key}
id={key}
helpText={detail?.help_text}
label={detail?.label}
type={detail?.type}
unit={detail?.unit}
value={detail?.value}
/>
);
}
return (
<Detail
alwaysVisible
dataCy={key}
helpText={detail?.help_text}
isNotConfigured={!detail?.value}
label={detail?.label}
value={
executionEnvironment?.name
? executionEnvironment.name
: i18n._(t`Not configured`)
}
/>
);
};

return (
<>
<RoutedTabs tabsArray={tabsArray} />
<CardBody>
{isLoading && <ContentLoading />}
{!isLoading && error && <ContentError error={error} />}
{!isLoading && system && (
{(isLoading || isLoadingExecutionEnvironment) && <ContentLoading />}
{!(isLoading || isLoadingExecutionEnvironment) &&
(error || errorExecutionEnvironment) && (
<ContentError error={error || errorExecutionEnvironment} />
)}
{!(isLoading || isLoadingExecutionEnvironment) && system && (
<DetailList>
{system.map(([key, detail]) => (
<SettingDetail
key={key}
id={key}
helpText={detail?.help_text}
label={detail?.label}
type={detail?.type}
unit={detail?.unit}
value={detail?.value}
/>
))}
{system.map(([key, detail]) => renderDetail(key, detail))}
</DetailList>
)}
{me?.is_superuser && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
waitForElement,
} from '../../../../../testUtils/enzymeHelpers';
import { SettingsProvider } from '../../../../contexts/Settings';
import { SettingsAPI } from '../../../../api';
import { SettingsAPI, ExecutionEnvironmentsAPI } from '../../../../api';
import {
assertDetail,
assertVariableDetail,
Expand All @@ -14,13 +14,14 @@ import mockAllOptions from '../../shared/data.allSettingOptions.json';
import MiscSystemDetail from './MiscSystemDetail';

jest.mock('../../../../api/models/Settings');
jest.mock('../../../../api/models/ExecutionEnvironments');

SettingsAPI.readCategory.mockResolvedValue({
data: {
ALLOW_OAUTH2_FOR_EXTERNAL_USERS: false,
AUTH_BASIC_ENABLED: true,
AUTOMATION_ANALYTICS_GATHER_INTERVAL: 14400,
AUTOMATION_ANALYTICS_URL: 'https://example.com',
CUSTOM_VENV_PATHS: [],
INSIGHTS_TRACKING_STATE: false,
LOGIN_REDIRECT_OVERRIDE: 'https://redirect.com',
MANAGE_ORGANIZATION_AUTH: true,
Expand All @@ -36,13 +37,29 @@ SettingsAPI.readCategory.mockResolvedValue({
SESSIONS_PER_USER: -1,
SESSION_COOKIE_AGE: 30000000000,
TOWER_URL_BASE: 'https://towerhost',
DEFAULT_EXECUTION_ENVIRONMENT: 1,
},
});

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

describe('<MiscSystemDetail />', () => {
let wrapper;

beforeAll(async () => {
ExecutionEnvironmentsAPI.readDetail.mockResolvedValue(
mockedExecutionEnvironments
);
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
Expand All @@ -68,7 +85,8 @@ describe('<MiscSystemDetail />', () => {
});
});

test('should render expected details', () => {
test.only('should render expected details', () => {
wrapper.debug();
assertDetail(wrapper, 'Access Token Expiration', '1 seconds');
assertDetail(wrapper, 'All Users Visible to Organization Admins', 'On');
assertDetail(
Expand Down Expand Up @@ -110,6 +128,11 @@ describe('<MiscSystemDetail />', () => {
assertDetail(wrapper, 'Red Hat customer username', 'mock name');
assertDetail(wrapper, 'Refresh Token Expiration', '3 seconds');
assertVariableDetail(wrapper, 'Remote Host Headers', '[]');
expect(
wrapper
.find('Detail[label="Global default execution environment"]')
.prop('value')
).toBe('Not configured');
});

test('should hide edit button from non-superusers', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Formik } from 'formik';
import { Formik, useField } from 'formik';
import { Form } from '@patternfly/react-core';
import { CardBody } from '../../../../components/Card';
import ContentError from '../../../../components/ContentError';
Expand All @@ -20,8 +20,31 @@ import {
} from '../../shared';
import useModal from '../../../../util/useModal';
import useRequest from '../../../../util/useRequest';
import { SettingsAPI } from '../../../../api';
import { SettingsAPI, ExecutionEnvironmentsAPI } from '../../../../api';
import { pluck, formatJson } from '../../shared/settingUtils';
import { ExecutionEnvironmentLookup } from '../../../../components/Lookup';

const ExecutionEnvironmentLookupField = () => {
const [
executionEnvironmentField,
executionEnvironmentMeta,
executionEnvironmentHelpers,
] = useField({
name: 'DEFAULT_EXECUTION_ENVIRONMENT',
});

return (
<ExecutionEnvironmentLookup
helperTextInvalid={executionEnvironmentMeta.error}
isValid={
!executionEnvironmentMeta.touched || !executionEnvironmentMeta.error
}
onBlur={() => executionEnvironmentHelpers.setTouched()}
value={executionEnvironmentField.value}
onChange={value => executionEnvironmentHelpers.setValue(value)}
/>
);
};

function MiscSystemEdit({ i18n }) {
const history = useHistory();
Expand All @@ -44,7 +67,6 @@ function MiscSystemEdit({ i18n }) {
'AUTH_BASIC_ENABLED',
'AUTOMATION_ANALYTICS_GATHER_INTERVAL',
'AUTOMATION_ANALYTICS_URL',
'CUSTOM_VENV_PATHS',
'INSIGHTS_TRACKING_STATE',
'LOGIN_REDIRECT_OVERRIDE',
'MANAGE_ORGANIZATION_AUTH',
Expand All @@ -55,7 +77,8 @@ function MiscSystemEdit({ i18n }) {
'REMOTE_HOST_HEADERS',
'SESSIONS_PER_USER',
'SESSION_COOKIE_AGE',
'TOWER_URL_BASE'
'TOWER_URL_BASE',
'DEFAULT_EXECUTION_ENVIRONMENT'
);

const systemData = {
Expand Down Expand Up @@ -128,6 +151,7 @@ function MiscSystemEdit({ i18n }) {
AUTHORIZATION_CODE_EXPIRE_SECONDS,
...formData
} = form;

await submitForm({
...formData,
REMOTE_HOST_HEADERS: formatJson(formData.REMOTE_HOST_HEADERS),
Expand All @@ -136,6 +160,8 @@ function MiscSystemEdit({ i18n }) {
REFRESH_TOKEN_EXPIRE_SECONDS,
AUTHORIZATION_CODE_EXPIRE_SECONDS,
},
DEFAULT_EXECUTION_ENVIRONMENT:
formData.DEFAULT_EXECUTION_ENVIRONMENT?.id || null,
});
};

Expand Down Expand Up @@ -178,16 +204,51 @@ function MiscSystemEdit({ i18n }) {
return acc;
}, {});

const executionEnvironmentId =
system?.DEFAULT_EXECUTION_ENVIRONMENT?.value || null;

const {
isLoading: isLoadingExecutionEnvironment,
error: errorExecutionEnvironment,
request: fetchExecutionEnvironment,
result: executionEnvironment,
} = useRequest(
useCallback(async () => {
if (!executionEnvironmentId) {
return '';
}
const { data } = await ExecutionEnvironmentsAPI.readDetail(
executionEnvironmentId
);
return data;
}, [executionEnvironmentId])
);

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

return (
<CardBody>
{isLoading && <ContentLoading />}
{!isLoading && error && <ContentError error={error} />}
{!isLoading && system && (
<Formik initialValues={initialValues(system)} onSubmit={handleSubmit}>
{(isLoading || isLoadingExecutionEnvironment) && <ContentLoading />}
{!(isLoading || isLoadingExecutionEnvironment) && error && (
<ContentError error={error || errorExecutionEnvironment} />
)}
{!(isLoading || isLoadingExecutionEnvironment) && system && (
<Formik
initialValues={{
...initialValues(system),
DEFAULT_EXECUTION_ENVIRONMENT: executionEnvironment
? { id: executionEnvironment.id, name: executionEnvironment.name }
: null,
}}
onSubmit={handleSubmit}
>
{formik => {
return (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<ExecutionEnvironmentLookupField />
<InputField
name="TOWER_URL_BASE"
config={system.TOWER_URL_BASE}
Expand Down
Loading

0 comments on commit e57b3de

Please sign in to comment.