use a state for hotCrumbs with using ProcessBreadCrumb to avoid unnecessary renderings and network calls w/ burnettk
This commit is contained in:
parent
c00d810704
commit
c36e342d1e
|
@ -0,0 +1,146 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||||
|
import {
|
||||||
|
HotCrumbItem,
|
||||||
|
HotCrumbItemArray,
|
||||||
|
HotCrumbItemObject,
|
||||||
|
ProcessGroup,
|
||||||
|
ProcessGroupLite,
|
||||||
|
ProcessModel,
|
||||||
|
} from '../interfaces';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
hotCrumbs?: HotCrumbItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// export default function ProcessBreadcrumb({ hotCrumbs }: OwnProps) {
|
||||||
|
export const default ProcessBreadcrumb = React.memo(({ hotCrumbs }: OwnProps) => {
|
||||||
|
const [processEntity, setProcessEntity] = useState<
|
||||||
|
ProcessGroup | ProcessModel | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const explodeCrumbItemObject = (crumb: HotCrumbItem) => {
|
||||||
|
if ('entityToExplode' in crumb) {
|
||||||
|
const { entityToExplode, entityType } = crumb;
|
||||||
|
if (entityType === 'process-model-id') {
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/process-models/${modifyProcessIdentifierForPathParam(
|
||||||
|
entityToExplode as string
|
||||||
|
)}`,
|
||||||
|
successCallback: setProcessEntity,
|
||||||
|
onUnauthorized: () => {},
|
||||||
|
});
|
||||||
|
} else if (entityType === 'process-group-id') {
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||||
|
entityToExplode as string
|
||||||
|
)}`,
|
||||||
|
successCallback: setProcessEntity,
|
||||||
|
onUnauthorized: () => {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setProcessEntity(entityToExplode as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (hotCrumbs) {
|
||||||
|
hotCrumbs.forEach(explodeCrumbItemObject);
|
||||||
|
}
|
||||||
|
}, [hotCrumbs]);
|
||||||
|
|
||||||
|
const checkPermissions = (crumb: HotCrumbItemObject) => {
|
||||||
|
if (!crumb.checkPermission) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
processEntity &&
|
||||||
|
'actions' in processEntity &&
|
||||||
|
processEntity.actions &&
|
||||||
|
'read' in processEntity.actions
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
const hotCrumbElement = () => {
|
||||||
|
if (hotCrumbs) {
|
||||||
|
const leadingCrumbLinks = hotCrumbs.map(
|
||||||
|
(crumb: HotCrumbItemArray | HotCrumbItemObject) => {
|
||||||
|
if (
|
||||||
|
'entityToExplode' in crumb &&
|
||||||
|
processEntity &&
|
||||||
|
processEntity.parent_groups &&
|
||||||
|
checkPermissions(crumb)
|
||||||
|
) {
|
||||||
|
const breadcrumbs = processEntity.parent_groups.map(
|
||||||
|
(parentGroup: ProcessGroupLite) => {
|
||||||
|
const fullUrl = `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||||
|
parentGroup.id
|
||||||
|
)}`;
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem key={parentGroup.id} href={fullUrl}>
|
||||||
|
{parentGroup.display_name}
|
||||||
|
</BreadcrumbItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (crumb.linkLastItem) {
|
||||||
|
let apiBase = '/process-groups';
|
||||||
|
let dataQaTag = '';
|
||||||
|
if (crumb.entityType.startsWith('process-model')) {
|
||||||
|
apiBase = '/process-models';
|
||||||
|
dataQaTag = 'process-model-breadcrumb-link';
|
||||||
|
}
|
||||||
|
const fullUrl = `${apiBase}/${modifyProcessIdentifierForPathParam(
|
||||||
|
processEntity.id
|
||||||
|
)}`;
|
||||||
|
breadcrumbs.push(
|
||||||
|
<BreadcrumbItem
|
||||||
|
key={processEntity.id}
|
||||||
|
href={fullUrl}
|
||||||
|
data-qa={dataQaTag}
|
||||||
|
>
|
||||||
|
{processEntity.display_name}
|
||||||
|
</BreadcrumbItem>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
breadcrumbs.push(
|
||||||
|
<BreadcrumbItem key={processEntity.id} isCurrentPage>
|
||||||
|
{processEntity.display_name}
|
||||||
|
</BreadcrumbItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return breadcrumbs;
|
||||||
|
}
|
||||||
|
if (Array.isArray(crumb)) {
|
||||||
|
const valueLabel = crumb[0];
|
||||||
|
const url = crumb[1];
|
||||||
|
if (!url && valueLabel) {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||||
|
{valueLabel}
|
||||||
|
</BreadcrumbItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (url && valueLabel) {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem key={valueLabel} href={url}>
|
||||||
|
{valueLabel}
|
||||||
|
</BreadcrumbItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Breadcrumb noTrailingSlash>{hotCrumbElement()}</Breadcrumb>;
|
||||||
|
})
|
|
@ -12,6 +12,12 @@ import {
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
// it is recommend to use a state for hotCrumbs so ProcessBreadCrumb does not attmept
|
||||||
|
// to re-render. This is because javascript cannot tell if an array or object has changed
|
||||||
|
// but react states can. If we simply initialize a ProcessBreadCrumb when
|
||||||
|
// the component that uses it renders, we may get a request to process model show
|
||||||
|
// every time a state changes in the parent component (any state, not even a related state).
|
||||||
|
// For an example of usage see TaskShow.
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
hotCrumbs?: HotCrumbItem[];
|
hotCrumbs?: HotCrumbItem[];
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { Grid, Column, Button, ButtonSet, Loading } from '@carbon/react';
|
import { Grid, Column, Button, ButtonSet, Loading } from '@carbon/react';
|
||||||
|
@ -16,6 +16,7 @@ import {
|
||||||
BasicTask,
|
BasicTask,
|
||||||
ErrorForDisplay,
|
ErrorForDisplay,
|
||||||
EventDefinition,
|
EventDefinition,
|
||||||
|
HotCrumbItem,
|
||||||
Task,
|
Task,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import CustomForm from '../components/CustomForm';
|
import CustomForm from '../components/CustomForm';
|
||||||
|
@ -31,6 +32,14 @@ export default function TaskShow() {
|
||||||
const [basicTask, setBasicTask] = useState<BasicTask | null>(null);
|
const [basicTask, setBasicTask] = useState<BasicTask | null>(null);
|
||||||
const [taskWithTaskData, setTaskWithTaskData] = useState<Task | null>(null);
|
const [taskWithTaskData, setTaskWithTaskData] = useState<Task | null>(null);
|
||||||
|
|
||||||
|
// put this in a state so ProcessBreadCrumb does not attempt to re-render
|
||||||
|
// since javascript cannot tell if an array or object has changed
|
||||||
|
// but react states can. If we simply initialize a ProcessBreadCrumb when
|
||||||
|
// this parent component renders, we get a request to process model show
|
||||||
|
// every time someone types a character into a user task form (because we change ANY
|
||||||
|
// state. in this case, setTaskData).
|
||||||
|
const [hotCrumbs, setHotCrumbs] = useState<HotCrumbItem[]>([]);
|
||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [formButtonsDisabled, setFormButtonsDisabled] = useState(false);
|
const [formButtonsDisabled, setFormButtonsDisabled] = useState(false);
|
||||||
|
@ -49,20 +58,23 @@ export default function TaskShow() {
|
||||||
|
|
||||||
// if a user can complete a task then the for-me page should
|
// if a user can complete a task then the for-me page should
|
||||||
// always work for them so use that since it will work in all cases
|
// always work for them so use that since it will work in all cases
|
||||||
const navigateToInterstitial = (myTask: BasicTask) => {
|
const navigateToInterstitial = useCallback(
|
||||||
if (UserService.onlyGuestTaskCompletion()) {
|
(myTask: BasicTask) => {
|
||||||
setGuestConfirmationText('Thank you!');
|
if (UserService.onlyGuestTaskCompletion()) {
|
||||||
} else {
|
setGuestConfirmationText('Thank you!');
|
||||||
navigate(
|
} else {
|
||||||
`/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
navigate(
|
||||||
myTask.process_model_identifier
|
`/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
||||||
)}/${myTask.process_instance_id}/interstitial`
|
myTask.process_model_identifier
|
||||||
);
|
)}/${myTask.process_instance_id}/interstitial`
|
||||||
}
|
);
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const processBasicTaskResult = useCallback(
|
||||||
const processBasicTaskResult = (result: BasicTask) => {
|
(result: BasicTask) => {
|
||||||
setBasicTask(result);
|
setBasicTask(result);
|
||||||
setPageTitle([result.name_for_display]);
|
setPageTitle([result.name_for_display]);
|
||||||
if (!result.can_complete) {
|
if (!result.can_complete) {
|
||||||
|
@ -76,7 +88,28 @@ export default function TaskShow() {
|
||||||
navigateToInterstitial(result);
|
navigateToInterstitial(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
const hotCrumbList: HotCrumbItem[] = [
|
||||||
|
['Process Groups', '/process-groups'],
|
||||||
|
{
|
||||||
|
entityToExplode: result.process_model_identifier,
|
||||||
|
entityType: 'process-model-id',
|
||||||
|
linkLastItem: true,
|
||||||
|
checkPermission: true,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
`Process Instance Id: ${result.process_instance_id}`,
|
||||||
|
`/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
||||||
|
result.process_model_identifier
|
||||||
|
)}/${result.process_instance_id}`,
|
||||||
|
],
|
||||||
|
[`Task: ${result.name_for_display || result.id}`],
|
||||||
|
];
|
||||||
|
setHotCrumbs(hotCrumbList);
|
||||||
|
},
|
||||||
|
[navigateToInterstitial, navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const processTaskWithDataResult = (result: Task) => {
|
const processTaskWithDataResult = (result: Task) => {
|
||||||
setTaskWithTaskData(result);
|
setTaskWithTaskData(result);
|
||||||
|
|
||||||
|
@ -102,7 +135,7 @@ export default function TaskShow() {
|
||||||
});
|
});
|
||||||
// FIXME: not sure what to do about addError. adding it to this array causes the page to endlessly reload
|
// FIXME: not sure what to do about addError. adding it to this array causes the page to endlessly reload
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [params]);
|
}, [params, processBasicTaskResult]);
|
||||||
|
|
||||||
// Before we auto-saved form data, we remembered what data was in the form, and then created a synthetic submit event
|
// Before we auto-saved form data, we remembered what data was in the form, and then created a synthetic submit event
|
||||||
// in order to implement a "Save and close" button. That button no longer saves (since we have auto-save), but the crazy
|
// in order to implement a "Save and close" button. That button no longer saves (since we have auto-save), but the crazy
|
||||||
|
@ -422,26 +455,7 @@ export default function TaskShow() {
|
||||||
!('allowGuest' in basicTask.extensions) ||
|
!('allowGuest' in basicTask.extensions) ||
|
||||||
basicTask.extensions.allowGuest !== 'true'
|
basicTask.extensions.allowGuest !== 'true'
|
||||||
) {
|
) {
|
||||||
pageElements.push(
|
pageElements.push(<ProcessBreadcrumb hotCrumbs={hotCrumbs} />);
|
||||||
<ProcessBreadcrumb
|
|
||||||
hotCrumbs={[
|
|
||||||
['Process Groups', '/process-groups'],
|
|
||||||
{
|
|
||||||
entityToExplode: basicTask.process_model_identifier,
|
|
||||||
entityType: 'process-model-id',
|
|
||||||
linkLastItem: true,
|
|
||||||
checkPermission: true,
|
|
||||||
},
|
|
||||||
[
|
|
||||||
`Process Instance Id: ${basicTask.process_instance_id}`,
|
|
||||||
`/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
|
||||||
basicTask.process_model_identifier
|
|
||||||
)}/${basicTask.process_instance_id}`,
|
|
||||||
],
|
|
||||||
[`Task: ${basicTask.name_for_display || basicTask.id}`],
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
pageElements.push(
|
pageElements.push(
|
||||||
<h3>
|
<h3>
|
||||||
Task: {basicTask.name_for_display} (
|
Task: {basicTask.name_for_display} (
|
||||||
|
|
Loading…
Reference in New Issue