mirror of
https://github.com/ansible/awx.git
synced 2026-03-09 21:49:27 -02:30
Adds spinner to loadiing and updating states
This commit is contained in:
@@ -58,7 +58,7 @@ function AdHocCredentialStep({ i18n, credentialTypeId, onEnableLaunch }) {
|
|||||||
return <ContentError error={error} />;
|
return <ContentError error={error} />;
|
||||||
}
|
}
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <ContentLoading error={error} />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
import { withI18n } from '@lingui/react';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
EmptyState as PFEmptyState,
|
EmptyState as PFEmptyState,
|
||||||
EmptyStateBody,
|
EmptyStateIcon,
|
||||||
|
Spinner,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
const EmptyState = styled(PFEmptyState)`
|
const EmptyState = styled(PFEmptyState)`
|
||||||
--pf-c-empty-state--m-lg--MaxWidth: none;
|
--pf-c-empty-state--m-lg--MaxWidth: none;
|
||||||
|
min-height: 250px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// TODO: Better loading state - skeleton lines / spinner, etc.
|
// TODO: Better loading state - skeleton lines / spinner, etc.
|
||||||
const ContentLoading = ({ className, i18n }) => (
|
const ContentLoading = ({ className }) => {
|
||||||
<EmptyState variant="full" className={className}>
|
return (
|
||||||
<EmptyStateBody>{i18n._(t`Loading...`)}</EmptyStateBody>
|
<EmptyState variant="full" className={className}>
|
||||||
</EmptyState>
|
<EmptyStateIcon variant="container" component={Spinner} />
|
||||||
);
|
</EmptyState>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export { ContentLoading as _ContentLoading };
|
export { ContentLoading as _ContentLoading };
|
||||||
export default withI18n()(ContentLoading);
|
export default ContentLoading;
|
||||||
|
|||||||
23
awx/ui_next/src/components/LoadingSpinner/LoadingSpinner.jsx
Normal file
23
awx/ui_next/src/components/LoadingSpinner/LoadingSpinner.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Spinner } from '@patternfly/react-core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const UpdatingContent = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
z-index: 300;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
& + * {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LoadingSpinner = () => (
|
||||||
|
<UpdatingContent>
|
||||||
|
<Spinner />
|
||||||
|
</UpdatingContent>
|
||||||
|
);
|
||||||
|
export default LoadingSpinner;
|
||||||
1
awx/ui_next/src/components/LoadingSpinner/index.js
Normal file
1
awx/ui_next/src/components/LoadingSpinner/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './LoadingSpinner';
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import { QSConfig, SearchColumns, SortColumns } from '../../types';
|
import { QSConfig, SearchColumns, SortColumns } from '../../types';
|
||||||
|
|
||||||
import PaginatedDataListItem from './PaginatedDataListItem';
|
import PaginatedDataListItem from './PaginatedDataListItem';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
|
||||||
function PaginatedDataList({
|
function PaginatedDataList({
|
||||||
items,
|
items,
|
||||||
@@ -100,12 +101,15 @@ function PaginatedDataList({
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Content = (
|
Content = (
|
||||||
<DataList
|
<>
|
||||||
aria-label={dataListLabel}
|
{hasContentLoading && <LoadingSpinner />}
|
||||||
onSelectDataListItem={id => handleListItemSelect(id)}
|
<DataList
|
||||||
>
|
aria-label={dataListLabel}
|
||||||
{items.map(renderItem)}
|
onSelectDataListItem={id => handleListItemSelect(id)}
|
||||||
</DataList>
|
>
|
||||||
|
{items.map(renderItem)}
|
||||||
|
</DataList>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ContentError from '../ContentError';
|
|||||||
import ContentLoading from '../ContentLoading';
|
import ContentLoading from '../ContentLoading';
|
||||||
import Pagination from '../Pagination';
|
import Pagination from '../Pagination';
|
||||||
import DataListToolbar from '../DataListToolbar';
|
import DataListToolbar from '../DataListToolbar';
|
||||||
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
encodeNonDefaultQueryString,
|
encodeNonDefaultQueryString,
|
||||||
@@ -82,10 +83,13 @@ function PaginatedTable({
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Content = (
|
Content = (
|
||||||
<TableComposable aria-label={dataListLabel}>
|
<>
|
||||||
{headerRow}
|
{hasContentLoading && <LoadingSpinner />}
|
||||||
<Tbody>{items.map(renderRow)}</Tbody>
|
<TableComposable aria-label={dataListLabel}>
|
||||||
</TableComposable>
|
{headerRow}
|
||||||
|
<Tbody>{items.map(renderRow)}</Tbody>
|
||||||
|
</TableComposable>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ function ScheduleList({
|
|||||||
scheduleActions.data.actions?.GET || {}
|
scheduleActions.data.actions?.GET || {}
|
||||||
).filter(key => scheduleActions.data.actions?.GET[key].filterable),
|
).filter(key => scheduleActions.data.actions?.GET[key].filterable),
|
||||||
};
|
};
|
||||||
}, [location, loadSchedules, loadScheduleOptions]),
|
}, [location.search, loadSchedules, loadScheduleOptions]),
|
||||||
{
|
{
|
||||||
schedules: [],
|
schedules: [],
|
||||||
itemCount: 0,
|
itemCount: 0,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import useRequest from '../../util/useRequest';
|
|||||||
import { DashboardAPI } from '../../api';
|
import { DashboardAPI } from '../../api';
|
||||||
import Breadcrumbs from '../../components/Breadcrumbs';
|
import Breadcrumbs from '../../components/Breadcrumbs';
|
||||||
import JobList from '../../components/JobList';
|
import JobList from '../../components/JobList';
|
||||||
|
import ContentLoading from '../../components/ContentLoading';
|
||||||
import LineChart from './shared/LineChart';
|
import LineChart from './shared/LineChart';
|
||||||
import Count from './shared/Count';
|
import Count from './shared/Count';
|
||||||
import DashboardTemplateList from './shared/DashboardTemplateList';
|
import DashboardTemplateList from './shared/DashboardTemplateList';
|
||||||
@@ -62,6 +62,7 @@ function Dashboard({ i18n }) {
|
|||||||
const [activeTabId, setActiveTabId] = useState(0);
|
const [activeTabId, setActiveTabId] = useState(0);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
isLoading,
|
||||||
result: { jobGraphData, countData },
|
result: { jobGraphData, countData },
|
||||||
request: fetchDashboardGraph,
|
request: fetchDashboardGraph,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
@@ -105,7 +106,15 @@ function Dashboard({ i18n }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDashboardGraph();
|
fetchDashboardGraph();
|
||||||
}, [fetchDashboardGraph, periodSelection, jobTypeSelection]);
|
}, [fetchDashboardGraph, periodSelection, jobTypeSelection]);
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<ContentLoading />
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Breadcrumbs breadcrumbConfig={{ '/home': i18n._(t`Dashboard`) }} />
|
<Breadcrumbs breadcrumbConfig={{ '/home': i18n._(t`Dashboard`) }} />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import {
|
||||||
@@ -20,29 +20,22 @@ import HostDetail from './HostDetail';
|
|||||||
import HostEdit from './HostEdit';
|
import HostEdit from './HostEdit';
|
||||||
import HostGroups from './HostGroups';
|
import HostGroups from './HostGroups';
|
||||||
import { HostsAPI } from '../../api';
|
import { HostsAPI } from '../../api';
|
||||||
|
import useRequest from '../../util/useRequest';
|
||||||
|
|
||||||
function Host({ i18n, setBreadcrumb }) {
|
function Host({ i18n, setBreadcrumb }) {
|
||||||
const [host, setHost] = useState(null);
|
|
||||||
const [contentError, setContentError] = useState(null);
|
|
||||||
const [hasContentLoading, setHasContentLoading] = useState(true);
|
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const match = useRouteMatch('/hosts/:id');
|
const match = useRouteMatch('/hosts/:id');
|
||||||
|
const { error, isLoading, result: host, request: fetchHost } = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const { data } = await HostsAPI.readDetail(match.params.id);
|
||||||
|
setBreadcrumb(data);
|
||||||
|
return data;
|
||||||
|
}, [match.params.id, setBreadcrumb])
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
fetchHost();
|
||||||
setContentError(null);
|
}, [fetchHost, location]);
|
||||||
try {
|
|
||||||
const { data } = await HostsAPI.readDetail(match.params.id);
|
|
||||||
setHost(data);
|
|
||||||
setBreadcrumb(data);
|
|
||||||
} catch (error) {
|
|
||||||
setContentError(error);
|
|
||||||
} finally {
|
|
||||||
setHasContentLoading(false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [match.params.id, location, setBreadcrumb]);
|
|
||||||
|
|
||||||
const tabsArray = [
|
const tabsArray = [
|
||||||
{
|
{
|
||||||
@@ -77,7 +70,7 @@ function Host({ i18n, setBreadcrumb }) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (hasContentLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -87,12 +80,12 @@ function Host({ i18n, setBreadcrumb }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentError) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<ContentError error={contentError}>
|
<ContentError error={error}>
|
||||||
{contentError?.response?.status === 404 && (
|
{error?.response?.status === 404 && (
|
||||||
<span>
|
<span>
|
||||||
{i18n._(t`Host not found.`)}{' '}
|
{i18n._(t`Host not found.`)}{' '}
|
||||||
<Link to="/hosts">{i18n._(t`View all Hosts.`)}</Link>
|
<Link to="/hosts">{i18n._(t`View all Hosts.`)}</Link>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Route } from 'react-router-dom';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { HostsAPI } from '../../api';
|
import { HostsAPI } from '../../api';
|
||||||
@@ -28,7 +29,11 @@ describe('<Host />', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<Host setBreadcrumb={() => {}} />);
|
wrapper = mountWithContexts(
|
||||||
|
<Route path="/hosts/:id/details">
|
||||||
|
<Host setBreadcrumb={() => {}} />
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment, useState, useEffect, useCallback } from 'react';
|
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||||
import { Link, useHistory, useParams } from 'react-router-dom';
|
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import {
|
import {
|
||||||
@@ -59,32 +59,31 @@ function JobTemplateDetail({ i18n, template }) {
|
|||||||
related: { webhook_receiver },
|
related: { webhook_receiver },
|
||||||
webhook_key,
|
webhook_key,
|
||||||
} = template;
|
} = template;
|
||||||
const [contentError, setContentError] = useState(null);
|
|
||||||
const [hasContentLoading, setHasContentLoading] = useState(false);
|
|
||||||
const [instanceGroups, setInstanceGroups] = useState([]);
|
|
||||||
const { id: templateId } = useParams();
|
const { id: templateId } = useParams();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading: isLoadingInstanceGroups,
|
||||||
|
request: fetchInstanceGroups,
|
||||||
|
error: instanceGroupsError,
|
||||||
|
result: { instanceGroups },
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await JobTemplatesAPI.readInstanceGroups(templateId);
|
||||||
|
return { instanceGroups: results };
|
||||||
|
}, [templateId]),
|
||||||
|
{ instanceGroups: [] }
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
fetchInstanceGroups();
|
||||||
setContentError(null);
|
}, [fetchInstanceGroups]);
|
||||||
setHasContentLoading(true);
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
data: { results = [] },
|
|
||||||
} = await JobTemplatesAPI.readInstanceGroups(templateId);
|
|
||||||
setInstanceGroups(results);
|
|
||||||
} catch (error) {
|
|
||||||
setContentError(error);
|
|
||||||
} finally {
|
|
||||||
setHasContentLoading(false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [templateId]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
request: deleteJobTemplate,
|
request: deleteJobTemplate,
|
||||||
isLoading,
|
isLoading: isDeleteLoading,
|
||||||
error: deleteError,
|
error: deleteError,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
@@ -154,11 +153,11 @@ function JobTemplateDetail({ i18n, template }) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (contentError) {
|
if (instanceGroupsError) {
|
||||||
return <ContentError error={contentError} />;
|
return <ContentError error={instanceGroupsError} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasContentLoading) {
|
if (isLoadingInstanceGroups || isDeleteLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +388,7 @@ function JobTemplateDetail({ i18n, template }) {
|
|||||||
name={name}
|
name={name}
|
||||||
modalTitle={i18n._(t`Delete Job Template`)}
|
modalTitle={i18n._(t`Delete Job Template`)}
|
||||||
onConfirm={deleteJobTemplate}
|
onConfirm={deleteJobTemplate}
|
||||||
isDisabled={isLoading}
|
isDisabled={isDeleteLoading}
|
||||||
>
|
>
|
||||||
{i18n._(t`Delete`)}
|
{i18n._(t`Delete`)}
|
||||||
</DeleteButton>
|
</DeleteButton>
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ describe('<JobTemplateDetail />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should hide edit button for users without edit permission', async () => {
|
test('should hide edit button for users without edit permission', async () => {
|
||||||
JobTemplatesAPI.readInstanceGroups.mockResolvedValue({ data: {} });
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<JobTemplateDetail
|
<JobTemplateDetail
|
||||||
|
|||||||
@@ -85,6 +85,48 @@ function SurveyList({
|
|||||||
const end = questions.slice(index + 2);
|
const end = questions.slice(index + 2);
|
||||||
updateSurvey([...beginning, swapWith, question, ...end]);
|
updateSurvey([...beginning, swapWith, question, ...end]);
|
||||||
};
|
};
|
||||||
|
const deleteModal = (
|
||||||
|
<AlertModal
|
||||||
|
variant="danger"
|
||||||
|
title={
|
||||||
|
isAllSelected ? i18n._(t`Delete Survey`) : i18n._(t`Delete Questions`)
|
||||||
|
}
|
||||||
|
isOpen={isDeleteModalOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setSelected([]);
|
||||||
|
}}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
key="delete"
|
||||||
|
variant="danger"
|
||||||
|
aria-label={i18n._(t`confirm delete`)}
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
{i18n._(t`Delete`)}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`cancel delete`)}
|
||||||
|
onClick={() => {
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setSelected([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n._(t`Cancel`)}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div>{i18n._(t`This action will delete the following:`)}</div>
|
||||||
|
{selected.map(question => (
|
||||||
|
<span key={question.variable}>
|
||||||
|
<strong>{question.question_name}</strong>
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</AlertModal>
|
||||||
|
);
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -105,6 +147,7 @@ function SurveyList({
|
|||||||
canEdit={canEdit}
|
canEdit={canEdit}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{isDeleteModalOpen && deleteModal}
|
||||||
{isPreviewModalOpen && (
|
{isPreviewModalOpen && (
|
||||||
<SurveyPreviewModal
|
<SurveyPreviewModal
|
||||||
isPreviewModalOpen={isPreviewModalOpen}
|
isPreviewModalOpen={isPreviewModalOpen}
|
||||||
@@ -112,7 +155,6 @@ function SurveyList({
|
|||||||
questions={questions}
|
questions={questions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setIsPreviewModalOpen(true)}
|
onClick={() => setIsPreviewModalOpen(true)}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -123,51 +165,8 @@ function SurveyList({
|
|||||||
</DataList>
|
</DataList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isDeleteModalOpen) {
|
|
||||||
return (
|
if ((!questions || questions?.length <= 0) && !isLoading) {
|
||||||
<AlertModal
|
|
||||||
variant="danger"
|
|
||||||
title={
|
|
||||||
isAllSelected ? i18n._(t`Delete Survey`) : i18n._(t`Delete Questions`)
|
|
||||||
}
|
|
||||||
isOpen={isDeleteModalOpen}
|
|
||||||
onClose={() => {
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
setSelected([]);
|
|
||||||
}}
|
|
||||||
actions={[
|
|
||||||
<Button
|
|
||||||
key="delete"
|
|
||||||
variant="danger"
|
|
||||||
aria-label={i18n._(t`confirm delete`)}
|
|
||||||
onClick={handleDelete}
|
|
||||||
>
|
|
||||||
{i18n._(t`Delete`)}
|
|
||||||
</Button>,
|
|
||||||
<Button
|
|
||||||
key="cancel"
|
|
||||||
variant="secondary"
|
|
||||||
aria-label={i18n._(t`cancel delete`)}
|
|
||||||
onClick={() => {
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
setSelected([]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n._(t`Cancel`)}
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<div>{i18n._(t`This action will delete the following:`)}</div>
|
|
||||||
{selected.map(question => (
|
|
||||||
<span key={question.variable}>
|
|
||||||
<strong>{question.question_name}</strong>
|
|
||||||
<br />
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</AlertModal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!questions || questions?.length <= 0) {
|
|
||||||
return (
|
return (
|
||||||
<EmptyState variant="full">
|
<EmptyState variant="full">
|
||||||
<EmptyStateIcon icon={CubesIcon} />
|
<EmptyStateIcon icon={CubesIcon} />
|
||||||
@@ -193,49 +192,6 @@ function SurveyList({
|
|||||||
onToggleDeleteModal={() => setIsDeleteModalOpen(true)}
|
onToggleDeleteModal={() => setIsDeleteModalOpen(true)}
|
||||||
/>
|
/>
|
||||||
{content}
|
{content}
|
||||||
{isDeleteModalOpen && (
|
|
||||||
<AlertModal
|
|
||||||
variant="danger"
|
|
||||||
title={
|
|
||||||
isAllSelected
|
|
||||||
? i18n._(t`Delete Survey`)
|
|
||||||
: i18n._(t`Delete Questions`)
|
|
||||||
}
|
|
||||||
isOpen={isDeleteModalOpen}
|
|
||||||
onClose={() => {
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
}}
|
|
||||||
actions={[
|
|
||||||
<Button
|
|
||||||
key="delete"
|
|
||||||
variant="danger"
|
|
||||||
aria-label={i18n._(t`confirm delete`)}
|
|
||||||
onClick={handleDelete}
|
|
||||||
>
|
|
||||||
{i18n._(t`Delete`)}
|
|
||||||
</Button>,
|
|
||||||
<Button
|
|
||||||
key="cancel"
|
|
||||||
variant="secondary"
|
|
||||||
aria-label={i18n._(t`cancel delete`)}
|
|
||||||
onClick={() => {
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n._(t`Cancel`)}
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<div>{i18n._(t`This action will delete the following:`)}</div>
|
|
||||||
<ul>
|
|
||||||
{selected.map(question => (
|
|
||||||
<li key={question.variable}>
|
|
||||||
<strong>{question.question_name}</strong>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,14 +91,14 @@ function Template({ i18n, setBreadcrumb }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadScheduleOptions = useCallback(() => {
|
const loadScheduleOptions = useCallback(() => {
|
||||||
return JobTemplatesAPI.readScheduleOptions(template.id);
|
return JobTemplatesAPI.readScheduleOptions(templateId);
|
||||||
}, [template]);
|
}, [templateId]);
|
||||||
|
|
||||||
const loadSchedules = useCallback(
|
const loadSchedules = useCallback(
|
||||||
params => {
|
params => {
|
||||||
return JobTemplatesAPI.readSchedules(template.id, params);
|
return JobTemplatesAPI.readSchedules(templateId, params);
|
||||||
},
|
},
|
||||||
[template]
|
[templateId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const canSeeNotificationsTab = me?.is_system_auditor || isNotifAdmin;
|
const canSeeNotificationsTab = me?.is_system_auditor || isNotifAdmin;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { Switch, Route, useParams, useLocation } from 'react-router-dom';
|
import { Switch, Route, useParams } 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 { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../api';
|
import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../api';
|
||||||
@@ -12,8 +12,7 @@ import { SurveyList, SurveyQuestionAdd, SurveyQuestionEdit } from './Survey';
|
|||||||
function TemplateSurvey({ template, canEdit, i18n }) {
|
function TemplateSurvey({ template, canEdit, i18n }) {
|
||||||
const [surveyEnabled, setSurveyEnabled] = useState(template.survey_enabled);
|
const [surveyEnabled, setSurveyEnabled] = useState(template.survey_enabled);
|
||||||
|
|
||||||
const { templateType } = useParams();
|
const { templateType, id: templateId } = useParams();
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
result: survey,
|
result: survey,
|
||||||
@@ -25,30 +24,31 @@ function TemplateSurvey({ template, canEdit, i18n }) {
|
|||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const { data } =
|
const { data } =
|
||||||
templateType === 'workflow_job_template'
|
templateType === 'workflow_job_template'
|
||||||
? await WorkflowJobTemplatesAPI.readSurvey(template.id)
|
? await WorkflowJobTemplatesAPI.readSurvey(templateId)
|
||||||
: await JobTemplatesAPI.readSurvey(template.id);
|
: await JobTemplatesAPI.readSurvey(templateId);
|
||||||
return data;
|
return data;
|
||||||
}, [template.id, templateType])
|
}, [templateId, templateType])
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSurvey();
|
fetchSurvey();
|
||||||
}, [fetchSurvey, location]);
|
}, [fetchSurvey]);
|
||||||
|
|
||||||
const { request: updateSurvey, error: updateError } = useRequest(
|
const {
|
||||||
|
request: updateSurvey,
|
||||||
|
error: updateError,
|
||||||
|
isLoading: updateLoading,
|
||||||
|
} = useRequest(
|
||||||
useCallback(
|
useCallback(
|
||||||
async updatedSurvey => {
|
async updatedSurvey => {
|
||||||
if (templateType === 'workflow_job_template') {
|
if (templateType === 'workflow_job_template') {
|
||||||
await WorkflowJobTemplatesAPI.updateSurvey(
|
await WorkflowJobTemplatesAPI.updateSurvey(templateId, updatedSurvey);
|
||||||
template.id,
|
|
||||||
updatedSurvey
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await JobTemplatesAPI.updateSurvey(template.id, updatedSurvey);
|
await JobTemplatesAPI.updateSurvey(templateId, updatedSurvey);
|
||||||
}
|
}
|
||||||
setSurvey(updatedSurvey);
|
setSurvey(updatedSurvey);
|
||||||
},
|
},
|
||||||
[template.id, setSurvey, templateType]
|
[templateId, setSurvey, templateType]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const updateSurveySpec = spec => {
|
const updateSurveySpec = spec => {
|
||||||
@@ -61,24 +61,24 @@ function TemplateSurvey({ template, canEdit, i18n }) {
|
|||||||
|
|
||||||
const { request: deleteSurvey, error: deleteError } = useRequest(
|
const { request: deleteSurvey, error: deleteError } = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
await JobTemplatesAPI.destroySurvey(template.id);
|
await JobTemplatesAPI.destroySurvey(templateId);
|
||||||
setSurvey(null);
|
setSurvey(null);
|
||||||
}, [template.id, setSurvey])
|
}, [templateId, setSurvey])
|
||||||
);
|
);
|
||||||
|
|
||||||
const { request: toggleSurvey, error: toggleError } = useRequest(
|
const { request: toggleSurvey, error: toggleError } = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
if (templateType === 'workflow_job_template') {
|
if (templateType === 'workflow_job_template') {
|
||||||
await WorkflowJobTemplatesAPI.update(template.id, {
|
await WorkflowJobTemplatesAPI.update(templateId, {
|
||||||
survey_enabled: !surveyEnabled,
|
survey_enabled: !surveyEnabled,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await JobTemplatesAPI.update(template.id, {
|
await JobTemplatesAPI.update(templateId, {
|
||||||
survey_enabled: !surveyEnabled,
|
survey_enabled: !surveyEnabled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setSurveyEnabled(!surveyEnabled);
|
setSurveyEnabled(!surveyEnabled);
|
||||||
}, [template.id, templateType, surveyEnabled])
|
}, [templateId, templateType, surveyEnabled])
|
||||||
);
|
);
|
||||||
|
|
||||||
const { error, dismissError } = useDismissableError(
|
const { error, dismissError } = useDismissableError(
|
||||||
@@ -109,7 +109,7 @@ function TemplateSurvey({ template, canEdit, i18n }) {
|
|||||||
)}
|
)}
|
||||||
<Route path="/templates/:templateType/:id/survey" exact>
|
<Route path="/templates/:templateType/:id/survey" exact>
|
||||||
<SurveyList
|
<SurveyList
|
||||||
isLoading={isLoading}
|
isLoading={isLoading || updateLoading}
|
||||||
survey={survey}
|
survey={survey}
|
||||||
surveyEnabled={surveyEnabled}
|
surveyEnabled={surveyEnabled}
|
||||||
toggleSurvey={toggleSurvey}
|
toggleSurvey={toggleSurvey}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
import { Route } from 'react-router-dom';
|
import { Route } from 'react-router-dom';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||||
import TemplateSurvey from './TemplateSurvey';
|
import TemplateSurvey from './TemplateSurvey';
|
||||||
import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../api';
|
import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../api';
|
||||||
import mockJobTemplateData from './shared/data.job_template.json';
|
import mockJobTemplateData from './shared/data.job_template.json';
|
||||||
|
import mockWorkflowJobTemplateData from './shared/data.workflow_job_template.json';
|
||||||
|
|
||||||
jest.mock('../../api/models/JobTemplates');
|
jest.mock('../../api/models/JobTemplates');
|
||||||
jest.mock('../../api/models/WorkflowJobTemplates');
|
jest.mock('../../api/models/WorkflowJobTemplates');
|
||||||
@@ -27,99 +29,8 @@ describe('<TemplateSurvey />', () => {
|
|||||||
|
|
||||||
test('should fetch survey from API', async () => {
|
test('should fetch survey from API', async () => {
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/templates/job_template/1/survey'],
|
initialEntries: ['/templates/job_template/7/survey'],
|
||||||
});
|
});
|
||||||
let wrapper;
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<TemplateSurvey template={mockJobTemplateData} />,
|
|
||||||
{
|
|
||||||
context: { router: { history } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
wrapper.update();
|
|
||||||
expect(JobTemplatesAPI.readSurvey).toBeCalledWith(7);
|
|
||||||
|
|
||||||
expect(wrapper.find('SurveyList').prop('survey')).toEqual(surveyData);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display error in retrieving survey', async () => {
|
|
||||||
JobTemplatesAPI.readSurvey.mockRejectedValue(new Error());
|
|
||||||
let wrapper;
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<TemplateSurvey template={{ ...mockJobTemplateData, id: 'a' }} />
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
expect(wrapper.find('ContentError').length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should update API with survey changes', async () => {
|
|
||||||
const history = createMemoryHistory({
|
|
||||||
initialEntries: ['/templates/job_template/1/survey'],
|
|
||||||
});
|
|
||||||
let wrapper;
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<TemplateSurvey template={mockJobTemplateData} />,
|
|
||||||
{
|
|
||||||
context: { router: { history } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await wrapper.find('SurveyList').invoke('updateSurvey')([
|
|
||||||
{ question_name: 'Foo', type: 'text', default: 'One', variable: 'foo' },
|
|
||||||
{ question_name: 'Bar', type: 'text', default: 'Two', variable: 'bar' },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
expect(JobTemplatesAPI.updateSurvey).toHaveBeenCalledWith(7, {
|
|
||||||
name: 'Survey',
|
|
||||||
description: 'description for survey',
|
|
||||||
spec: [
|
|
||||||
{ question_name: 'Foo', type: 'text', default: 'One', variable: 'foo' },
|
|
||||||
{ question_name: 'Bar', type: 'text', default: 'Two', variable: 'bar' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should toggle jt survery on', async () => {
|
|
||||||
const history = createMemoryHistory({
|
|
||||||
initialEntries: ['/templates/job_template/1/survey'],
|
|
||||||
});
|
|
||||||
let wrapper;
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<TemplateSurvey template={mockJobTemplateData} canEdit />,
|
|
||||||
{
|
|
||||||
context: { router: { history } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
wrapper.update();
|
|
||||||
await act(() =>
|
|
||||||
wrapper.find('Switch[aria-label="Survey Toggle"]').prop('onChange')()
|
|
||||||
);
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
expect(JobTemplatesAPI.update).toBeCalledWith(7, { survey_enabled: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should toggle wfjt survey on', async () => {
|
|
||||||
const history = createMemoryHistory({
|
|
||||||
initialEntries: ['/templates/workflow_job_template/1/survey'],
|
|
||||||
});
|
|
||||||
|
|
||||||
WorkflowJobTemplatesAPI.readSurvey.mockResolvedValueOnce({
|
|
||||||
data: surveyData,
|
|
||||||
});
|
|
||||||
|
|
||||||
let wrapper;
|
let wrapper;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
@@ -132,7 +43,115 @@ describe('<TemplateSurvey />', () => {
|
|||||||
history,
|
history,
|
||||||
route: {
|
route: {
|
||||||
location: history.location,
|
location: history.location,
|
||||||
match: { params: { templateType: 'workflow_job_template' } },
|
match: {
|
||||||
|
params: { templateType: 'job_template', id: 7 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(JobTemplatesAPI.readSurvey).toBeCalledWith('7');
|
||||||
|
|
||||||
|
expect(wrapper.find('SurveyList').prop('survey')).toEqual(surveyData);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display error in retrieving survey', async () => {
|
||||||
|
JobTemplatesAPI.readSurvey.mockRejectedValue(new Error());
|
||||||
|
let wrapper;
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/templates/job_template/7/survey'],
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route path="/templates/:templateType/:id/survey">
|
||||||
|
<TemplateSurvey template={{ ...mockJobTemplateData, id: 'a' }} />
|
||||||
|
</Route>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
match: {
|
||||||
|
params: { templateType: 'job_template', id: 7 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(wrapper.find('ContentError').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update API with survey changes', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/templates/job_template/7/survey'],
|
||||||
|
});
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route path="/templates/:templateType/:id/survey">
|
||||||
|
<TemplateSurvey template={mockJobTemplateData} canEdit />
|
||||||
|
</Route>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
match: {
|
||||||
|
params: { templateType: 'job_template', id: 7 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await wrapper.find('SurveyList').invoke('updateSurvey')([
|
||||||
|
{ question_name: 'Foo', type: 'text', default: 'One', variable: 'foo' },
|
||||||
|
{ question_name: 'Bar', type: 'text', default: 'Two', variable: 'bar' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
expect(JobTemplatesAPI.updateSurvey).toHaveBeenCalledWith('7', {
|
||||||
|
name: 'Survey',
|
||||||
|
description: 'description for survey',
|
||||||
|
spec: [
|
||||||
|
{ question_name: 'Foo', type: 'text', default: 'One', variable: 'foo' },
|
||||||
|
{ question_name: 'Bar', type: 'text', default: 'Two', variable: 'bar' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should toggle jt survery on', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/templates/job_template/7/survey'],
|
||||||
|
});
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route path="/templates/:templateType/:id/survey">
|
||||||
|
<TemplateSurvey template={mockJobTemplateData} canEdit />
|
||||||
|
</Route>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
match: {
|
||||||
|
params: { templateType: 'job_template', id: 7 },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -144,7 +163,49 @@ describe('<TemplateSurvey />', () => {
|
|||||||
wrapper.find('Switch[aria-label="Survey Toggle"]').prop('onChange')()
|
wrapper.find('Switch[aria-label="Survey Toggle"]').prop('onChange')()
|
||||||
);
|
);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(WorkflowJobTemplatesAPI.update).toBeCalledWith(7, {
|
|
||||||
|
expect(JobTemplatesAPI.update).toBeCalledWith('7', {
|
||||||
|
survey_enabled: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should toggle wfjt survey on', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/templates/workflow_job_template/15/survey'],
|
||||||
|
});
|
||||||
|
|
||||||
|
WorkflowJobTemplatesAPI.readSurvey.mockResolvedValueOnce({
|
||||||
|
data: surveyData,
|
||||||
|
});
|
||||||
|
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route path="/templates/:templateType/:id/survey">
|
||||||
|
<TemplateSurvey template={mockWorkflowJobTemplateData} canEdit />
|
||||||
|
</Route>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
match: {
|
||||||
|
params: { templateType: 'workflow_job_template', id: 15 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
await act(() =>
|
||||||
|
wrapper.find('Switch[aria-label="Survey Toggle"]').prop('onChange')()
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
expect(WorkflowJobTemplatesAPI.update).toBeCalledWith('15', {
|
||||||
survey_enabled: false,
|
survey_enabled: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit';
|
|||||||
import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../api';
|
import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../api';
|
||||||
import TemplateSurvey from './TemplateSurvey';
|
import TemplateSurvey from './TemplateSurvey';
|
||||||
import { Visualizer } from './WorkflowJobTemplateVisualizer';
|
import { Visualizer } from './WorkflowJobTemplateVisualizer';
|
||||||
|
import ContentLoading from '../../components/ContentLoading';
|
||||||
|
|
||||||
function WorkflowJobTemplate({ i18n, setBreadcrumb }) {
|
function WorkflowJobTemplate({ i18n, setBreadcrumb }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -150,6 +151,10 @@ function WorkflowJobTemplate({ i18n, setBreadcrumb }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const contentError = rolesAndTemplateError;
|
const contentError = rolesAndTemplateError;
|
||||||
|
|
||||||
|
if (hasRolesandTemplateLoading) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
if (!hasRolesandTemplateLoading && contentError) {
|
if (!hasRolesandTemplateLoading && contentError) {
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
"status": "never updated",
|
"status": "never updated",
|
||||||
"extra_vars": "",
|
"extra_vars": "",
|
||||||
"organization": null,
|
"organization": null,
|
||||||
"survey_enabled": false,
|
"survey_enabled": true,
|
||||||
"allow_simultaneous": false,
|
"allow_simultaneous": false,
|
||||||
"ask_variables_on_launch": false,
|
"ask_variables_on_launch": false,
|
||||||
"inventory": null,
|
"inventory": null,
|
||||||
|
|||||||
Reference in New Issue
Block a user