Refactor top-level template routes

* Fix repeated api calls from useEffect hook by wrapping the breadcrumb
setter with useCallback

* Rework the top-level routes to remove some old patterns and bring it more
into alignment with how it's done on the projects screen
This commit is contained in:
Jake McDermott
2020-12-16 13:41:46 -05:00
parent d82f68c88e
commit 5cb580be7a
5 changed files with 122 additions and 166 deletions

View File

@@ -137,10 +137,10 @@ function Project({ i18n, setBreadcrumb }) {
); );
} }
let showCardHeader = true; const showCardHeader = !(
if (['edit', 'schedules/'].some(name => location.pathname.includes(name))) { location.pathname.endsWith('edit') ||
showCardHeader = false; location.pathname.includes('schedules/')
} );
return ( return (
<PageSection> <PageSection>

View File

@@ -1,19 +1,21 @@
/* eslint react/no-unused-state: 0 */ /* eslint react/no-unused-state: 0 */
import React, { useState } from 'react'; import React, { useState } from 'react';
import { withRouter, Redirect, useHistory } from 'react-router-dom'; import { withRouter, Redirect, useHistory } from 'react-router-dom';
import { CardBody } from '../../../components/Card'; import { CardBody } from '../../../components/Card';
import { JobTemplatesAPI } from '../../../api'; import { JobTemplatesAPI } from '../../../api';
import { JobTemplate } from '../../../types'; import { JobTemplate } from '../../../types';
import { getAddedAndRemoved } from '../../../util/lists'; import { getAddedAndRemoved } from '../../../util/lists';
import JobTemplateForm from '../shared/JobTemplateForm'; import JobTemplateForm from '../shared/JobTemplateForm';
import ContentLoading from '../../../components/ContentLoading';
function JobTemplateEdit({ template }) { function JobTemplateEdit({ template }) {
const { id, type } = template;
const history = useHistory(); const history = useHistory();
const [formSubmitError, setFormSubmitError] = useState(null); const [formSubmitError, setFormSubmitError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const detailsUrl = `/templates/${type}/${id}/details`; const detailsUrl = `/templates/${template.type}/${template.id}/details`;
const handleSubmit = async values => { const handleSubmit = async values => {
const { const {
@@ -28,6 +30,7 @@ function JobTemplateEdit({ template }) {
} = values; } = values;
setFormSubmitError(null); setFormSubmitError(null);
setIsLoading(true);
remainingValues.project = values.project.id; remainingValues.project = values.project.id;
remainingValues.webhook_credential = webhook_credential?.id || null; remainingValues.webhook_credential = webhook_credential?.id || null;
try { try {
@@ -38,8 +41,10 @@ function JobTemplateEdit({ template }) {
submitCredentials(credentials), submitCredentials(credentials),
]); ]);
history.push(detailsUrl); history.push(detailsUrl);
} catch (err) { } catch (error) {
setFormSubmitError(err); setFormSubmitError(error);
} finally {
setIsLoading(false);
} }
}; };
@@ -94,12 +99,14 @@ function JobTemplateEdit({ template }) {
history.push(detailsUrl); history.push(detailsUrl);
}; };
const canEdit = template.summary_fields.user_capabilities.edit; const canEdit = template?.summary_fields?.user_capabilities?.edit;
if (!canEdit) { if (!canEdit) {
return <Redirect to={detailsUrl} />; return <Redirect to={detailsUrl} />;
} }
if (isLoading) {
return <ContentLoading />;
}
return ( return (
<CardBody> <CardBody>
<JobTemplateForm <JobTemplateForm
@@ -112,7 +119,7 @@ function JobTemplateEdit({ template }) {
); );
} }
JobTemplateForm.propTypes = { JobTemplateEdit.propTypes = {
template: JobTemplate.isRequired, template: JobTemplate.isRequired,
}; };

View File

@@ -13,6 +13,7 @@ import {
useRouteMatch, useRouteMatch,
} from 'react-router-dom'; } from 'react-router-dom';
import RoutedTabs from '../../components/RoutedTabs'; import RoutedTabs from '../../components/RoutedTabs';
import { useConfig } from '../../contexts/Config';
import useRequest from '../../util/useRequest'; import useRequest from '../../util/useRequest';
import ContentError from '../../components/ContentError'; import ContentError from '../../components/ContentError';
import JobList from '../../components/JobList'; import JobList from '../../components/JobList';
@@ -24,15 +25,16 @@ import JobTemplateEdit from './JobTemplateEdit';
import { JobTemplatesAPI, OrganizationsAPI } from '../../api'; import { JobTemplatesAPI, OrganizationsAPI } from '../../api';
import TemplateSurvey from './TemplateSurvey'; import TemplateSurvey from './TemplateSurvey';
function Template({ i18n, me, setBreadcrumb }) { function Template({ i18n, setBreadcrumb }) {
const match = useRouteMatch();
const location = useLocation(); const location = useLocation();
const { id: templateId } = useParams(); const { id: templateId } = useParams();
const match = useRouteMatch(); const { me = {} } = useConfig();
const { const {
result: { isNotifAdmin, template }, result: { isNotifAdmin, template },
isLoading: hasRolesandTemplateLoading, isLoading,
error: rolesAndTemplateError, error: contentError,
request: loadTemplateAndRoles, request: loadTemplateAndRoles,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
@@ -44,9 +46,8 @@ function Template({ i18n, me, setBreadcrumb }) {
role_level: 'notification_admin_role', role_level: 'notification_admin_role',
}), }),
]); ]);
if (actions?.data?.actions?.PUT) {
if (actions.data.actions.PUT) { if (data?.webhook_service && data?.related?.webhook_key) {
if (data.webhook_service && data?.related?.webhook_key) {
const { const {
data: { webhook_key }, data: { webhook_key },
} = await JobTemplatesAPI.readWebhookKey(templateId); } = await JobTemplatesAPI.readWebhookKey(templateId);
@@ -54,35 +55,40 @@ function Template({ i18n, me, setBreadcrumb }) {
data.webhook_key = webhook_key; data.webhook_key = webhook_key;
} }
} }
setBreadcrumb(data);
return { return {
template: data, template: data,
isNotifAdmin: notifAdminRes.data.results.length > 0, isNotifAdmin: notifAdminRes.data.results.length > 0,
}; };
}, [setBreadcrumb, templateId]), }, [templateId]),
{ isNotifAdmin: false, template: null } { isNotifAdmin: false, template: null }
); );
useEffect(() => { useEffect(() => {
loadTemplateAndRoles(); loadTemplateAndRoles();
}, [loadTemplateAndRoles, location.pathname]); }, [loadTemplateAndRoles, location.pathname]);
useEffect(() => {
if (template) {
setBreadcrumb(template);
}
}, [template, setBreadcrumb]);
const createSchedule = data => { const createSchedule = data => {
return JobTemplatesAPI.createSchedule(templateId, data); return JobTemplatesAPI.createSchedule(template.id, data);
}; };
const loadScheduleOptions = useCallback(() => { const loadScheduleOptions = useCallback(() => {
return JobTemplatesAPI.readScheduleOptions(templateId); return JobTemplatesAPI.readScheduleOptions(template.id);
}, [templateId]); }, [template]);
const loadSchedules = useCallback( const loadSchedules = useCallback(
params => { params => {
return JobTemplatesAPI.readSchedules(templateId, params); return JobTemplatesAPI.readSchedules(template.id, params);
}, },
[templateId] [template]
); );
const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin; const canSeeNotificationsTab = me?.is_system_auditor || isNotifAdmin;
const canAddAndEditSurvey = const canAddAndEditSurvey =
template?.summary_fields?.user_capabilities.edit || template?.summary_fields?.user_capabilities.edit ||
template?.summary_fields?.user_capabilities.delete; template?.summary_fields?.user_capabilities.delete;
@@ -131,17 +137,7 @@ function Template({ i18n, me, setBreadcrumb }) {
tab.id = n; tab.id = n;
}); });
let showCardHeader = true; if (contentError) {
if (
location.pathname.endsWith('edit') ||
location.pathname.includes('schedules/')
) {
showCardHeader = false;
}
const contentError = rolesAndTemplateError;
if (!hasRolesandTemplateLoading && contentError) {
return ( return (
<PageSection> <PageSection>
<Card> <Card>
@@ -158,38 +154,37 @@ function Template({ i18n, me, setBreadcrumb }) {
); );
} }
const showCardHeader = !(
location.pathname.endsWith('edit') ||
location.pathname.includes('schedules/')
);
return ( return (
<PageSection> <PageSection>
<Card> <Card>
{showCardHeader && <RoutedTabs tabsArray={tabsArray} />} {showCardHeader && <RoutedTabs tabsArray={tabsArray} />}
<Switch> {template && (
<Redirect <Switch>
from="/templates/:templateType/:id" <Redirect
to="/templates/:templateType/:id/details" from="/templates/:templateType/:id"
exact to="/templates/:templateType/:id/details"
/> exact
{template && ( />
<Route key="details" path="/templates/:templateType/:id/details"> <Route key="details" path="/templates/:templateType/:id/details">
<JobTemplateDetail <JobTemplateDetail
hasTemplateLoading={hasRolesandTemplateLoading} hasTemplateLoading={isLoading}
template={template} template={template}
/> />
</Route> </Route>
)}
{template && (
<Route key="edit" path="/templates/:templateType/:id/edit"> <Route key="edit" path="/templates/:templateType/:id/edit">
<JobTemplateEdit template={template} /> <JobTemplateEdit template={template} />
</Route> </Route>
)}
{template && (
<Route key="access" path="/templates/:templateType/:id/access"> <Route key="access" path="/templates/:templateType/:id/access">
<ResourceAccessList <ResourceAccessList
resource={template} resource={template}
apiModel={JobTemplatesAPI} apiModel={JobTemplatesAPI}
/> />
</Route> </Route>
)}
{template && (
<Route <Route
key="schedules" key="schedules"
path="/templates/:templateType/:id/schedules" path="/templates/:templateType/:id/schedules"
@@ -202,43 +197,39 @@ function Template({ i18n, me, setBreadcrumb }) {
loadScheduleOptions={loadScheduleOptions} loadScheduleOptions={loadScheduleOptions}
/> />
</Route> </Route>
)} {canSeeNotificationsTab && (
{canSeeNotificationsTab && ( <Route path="/templates/:templateType/:id/notifications">
<Route path="/templates/:templateType/:id/notifications"> <NotificationList
<NotificationList id={Number(templateId)}
id={Number(templateId)} canToggleNotifications={isNotifAdmin}
canToggleNotifications={isNotifAdmin} apiModel={JobTemplatesAPI}
apiModel={JobTemplatesAPI} />
/> </Route>
</Route> )}
)}
{template?.id && (
<Route path="/templates/:templateType/:id/completed_jobs"> <Route path="/templates/:templateType/:id/completed_jobs">
<JobList defaultParams={{ job__job_template: template.id }} /> <JobList defaultParams={{ job__job_template: template.id }} />
</Route> </Route>
)}
{template && (
<Route path="/templates/:templateType/:id/survey"> <Route path="/templates/:templateType/:id/survey">
<TemplateSurvey <TemplateSurvey
template={template} template={template}
canEdit={canAddAndEditSurvey} canEdit={canAddAndEditSurvey}
/> />
</Route> </Route>
)} {!isLoading && (
{!hasRolesandTemplateLoading && ( <Route key="not-found" path="*">
<Route key="not-found" path="*"> <ContentError isNotFound>
<ContentError isNotFound> {match.params.id && (
{match.params.id && ( <Link
<Link to={`/templates/${match?.params?.templateType}/${templateId}/details`}
to={`/templates/${match.params.templateType}/${match.params.id}/details`} >
> {i18n._(t`View Template Details`)}
{i18n._(t`View Template Details`)} </Link>
</Link> )}
)} </ContentError>
</ContentError> </Route>
</Route> )}
)} </Switch>
</Switch> )}
</Card> </Card>
</PageSection> </PageSection>
); );

View File

@@ -1,10 +1,9 @@
import React, { useState } from 'react'; import React, { useState, useCallback, useRef } from 'react';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Route, withRouter, Switch, useRouteMatch } from 'react-router-dom'; import { Route, withRouter, Switch } from 'react-router-dom';
import { PageSection } from '@patternfly/react-core'; import { PageSection } from '@patternfly/react-core';
import { Config } from '../../contexts/Config';
import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs'; import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs';
import { TemplateList } from './TemplateList'; import { TemplateList } from './TemplateList';
import Template from './Template'; import Template from './Template';
@@ -13,101 +12,58 @@ import JobTemplateAdd from './JobTemplateAdd';
import WorkflowJobTemplateAdd from './WorkflowJobTemplateAdd'; import WorkflowJobTemplateAdd from './WorkflowJobTemplateAdd';
function Templates({ i18n }) { function Templates({ i18n }) {
const match = useRouteMatch(); const initBreadcrumbs = useRef({
const [breadcrumbConfig, setBreadcrumbs] = useState({
'/templates': i18n._(t`Templates`), '/templates': i18n._(t`Templates`),
'/templates/job_template/add': i18n._(t`Create New Job Template`), '/templates/job_template/add': i18n._(t`Create New Job Template`),
'/templates/workflow_job_template/add': i18n._( '/templates/workflow_job_template/add': i18n._(
t`Create New Workflow Template` t`Create New Workflow Template`
), ),
}); });
const [breadcrumbConfig, setBreadcrumbs] = useState(initBreadcrumbs.current);
const setBreadCrumbConfig = (template, schedule) => { const setBreadcrumbConfig = useCallback(
if (!template) { (template, schedule) => {
return; if (!template) return;
} const templatePath = `/templates/${template.type}/${template.id}`;
const breadcrumb = { const schedulesPath = `${templatePath}/schedules`;
'/templates': i18n._(t`Templates`), const surveyPath = `${templatePath}/survey`;
'/templates/job_template/add': i18n._(t`Create New Job Template`), setBreadcrumbs({
'/templates/workflow_job_template/add': i18n._( ...initBreadcrumbs.current,
t`Create New Workflow Template` [templatePath]: `${template.name}`,
), [`${templatePath}/details`]: i18n._(t`Details`),
[`/templates/${template.type}/${template.id}`]: `${template.name}`, [`${templatePath}/edit`]: i18n._(t`Edit Details`),
[`/templates/${template.type}/${template.id}/details`]: i18n._( [`${templatePath}/access`]: i18n._(t`Access`),
t`Details` [`${templatePath}/notifications`]: i18n._(t`Notifications`),
), [`${templatePath}/completed_jobs`]: i18n._(t`Completed Jobs`),
[`/templates/${template.type}/${template.id}/edit`]: i18n._( [surveyPath]: i18n._(t`Survey`),
t`Edit Details` [`${surveyPath}add`]: i18n._(t`Add Question`),
), [`${surveyPath}/edit`]: i18n._(t`Edit Question`),
[`/templates/${template.type}/${template.id}/access`]: i18n._(t`Access`), [schedulesPath]: i18n._(t`Schedules`),
[`/templates/${template.type}/${template.id}/notifications`]: i18n._( [`${schedulesPath}/add`]: i18n._(t`Create New Schedule`),
t`Notifications` [`${schedulesPath}/${schedule?.id}`]: `${schedule?.name}`,
), [`${schedulesPath}/details`]: i18n._(t`Schedule Details`),
[`/templates/${template.type}/${template.id}/completed_jobs`]: i18n._( [`${schedulesPath}/edit`]: i18n._(t`Edit Details`),
t`Completed Jobs` });
), },
[`/templates/${template.type}/${template.id}/survey`]: i18n._(t`Survey`), [i18n]
[`/templates/${template.type}/${template.id}/survey/add`]: i18n._( );
t`Add Question`
),
[`/templates/${template.type}/${template.id}/survey/edit`]: i18n._(
t`Edit Question`
),
[`/templates/${template.type}/${template.id}/schedules`]: i18n._(
t`Schedules`
),
[`/templates/${template.type}/${template.id}/schedules/add`]: i18n._(
t`Create New Schedule`
),
[`/templates/${template.type}/${template.id}/schedules/${schedule &&
schedule.id}`]: `${schedule && schedule.name}`,
[`/templates/${template.type}/${template.id}/schedules/${schedule &&
schedule.id}/details`]: i18n._(t`Schedule Details`),
[`/templates/${template.type}/${template.id}/schedules/${schedule &&
schedule.id}/edit`]: i18n._(t`Edit Details`),
};
setBreadcrumbs(breadcrumb);
};
return ( return (
<> <>
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} /> <Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
<Switch> <Switch>
<Route path={`${match.path}/job_template/add`}> <Route path="/templates/job_template/add">
<JobTemplateAdd /> <JobTemplateAdd />
</Route> </Route>
<Route path={`${match.path}/workflow_job_template/add`}> <Route path="/templates/workflow_job_template/add">
<WorkflowJobTemplateAdd /> <WorkflowJobTemplateAdd />
</Route> </Route>
<Route <Route path="/templates/job_template/:id">
path={`${match.path}/job_template/:id`} <Template setBreadcrumb={setBreadcrumbConfig} />
render={({ match: newRouteMatch }) => ( </Route>
<Config> <Route path="/templates/workflow_job_template/:id">
{({ me }) => ( <WorkflowJobTemplate setBreadcrumb={setBreadcrumbConfig} />
<Template </Route>
setBreadcrumb={setBreadCrumbConfig} <Route path="/templates">
me={me || {}}
match={newRouteMatch}
/>
)}
</Config>
)}
/>
<Route
path={`${match.path}/workflow_job_template/:id`}
render={({ match: newRouteMatch }) => (
<Config>
{({ me }) => (
<WorkflowJobTemplate
setBreadcrumb={setBreadCrumbConfig}
me={me || {}}
match={newRouteMatch}
/>
)}
</Config>
)}
/>
<Route path={`${match.path}`}>
<PageSection> <PageSection>
<TemplateList /> <TemplateList />
</PageSection> </PageSection>

View File

@@ -13,6 +13,7 @@ import {
useRouteMatch, useRouteMatch,
} from 'react-router-dom'; } from 'react-router-dom';
import RoutedTabs from '../../components/RoutedTabs'; import RoutedTabs from '../../components/RoutedTabs';
import { useConfig } from '../../contexts/Config';
import useRequest from '../../util/useRequest'; import useRequest from '../../util/useRequest';
import AppendBody from '../../components/AppendBody'; import AppendBody from '../../components/AppendBody';
import ContentError from '../../components/ContentError'; import ContentError from '../../components/ContentError';
@@ -27,10 +28,11 @@ import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../api';
import TemplateSurvey from './TemplateSurvey'; import TemplateSurvey from './TemplateSurvey';
import { Visualizer } from './WorkflowJobTemplateVisualizer'; import { Visualizer } from './WorkflowJobTemplateVisualizer';
function WorkflowJobTemplate({ i18n, me, setBreadcrumb }) { function WorkflowJobTemplate({ i18n, setBreadcrumb }) {
const location = useLocation(); const location = useLocation();
const { id: templateId } = useParams();
const match = useRouteMatch(); const match = useRouteMatch();
const { id: templateId } = useParams();
const { me = {} } = useConfig();
const { const {
result: { isNotifAdmin, template }, result: { isNotifAdmin, template },