made activeuser component which can be used wherever and only use it if the user can save the file on the form and diagram edit pages

This commit is contained in:
jasquat 2023-05-05 09:52:52 -04:00
parent 02242b4c15
commit 7366ae5cc8
No known key found for this signature in database
4 changed files with 134 additions and 96 deletions

View File

@ -0,0 +1,52 @@
import { useEffect, useState } from 'react';
import HttpService from '../services/HttpService';
import {
encodeBase64,
refreshAtInterval,
REFRESH_TIMEOUT_SECONDS,
} from '../helpers';
import { User } from '../interfaces';
export default function ActiveUsers() {
// Handles getting and displaying active users.
const [activeUsers, setActiveUsers] = useState<User[]>([]);
const lastVisitedIdentifier = encodeBase64(window.location.pathname);
useEffect(() => {
const updateActiveUsers = () => {
HttpService.makeCallToBackend({
path: `/active-users/updates/${lastVisitedIdentifier}`,
successCallback: setActiveUsers,
});
};
const unregisterUser = () => {
HttpService.makeCallToBackend({
path: `/active-users/unregister/${lastVisitedIdentifier}`,
successCallback: setActiveUsers,
});
};
updateActiveUsers();
return refreshAtInterval(
15,
REFRESH_TIMEOUT_SECONDS,
updateActiveUsers,
unregisterUser
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // it is critical to only run this once.
const au = activeUsers.map((activeUser: User) => {
return (
<div
title={`${activeUser.username} is also viewing this page`}
className="user-circle"
>
{activeUser.username.charAt(0).toUpperCase()}
</div>
);
});
return <div className="user-list">{au}</div>;
}

View File

@ -674,7 +674,14 @@ export default function ReactDiagramEditor({
)} )}
</Can> </Can>
{getReferencesButton()} {getReferencesButton()}
{activeUserElement || null} {/* only show other users if the current user can save the current diagram */}
<Can
I="PUT"
a={targetUris.processModelFileShowPath}
ability={ability}
>
{activeUserElement || null}
</Can>
</ButtonSet> </ButtonSet>
); );
} }

View File

@ -29,24 +29,18 @@ import HttpService from '../services/HttpService';
import ReactDiagramEditor from '../components/ReactDiagramEditor'; import ReactDiagramEditor from '../components/ReactDiagramEditor';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import useAPIError from '../hooks/UseApiError'; import useAPIError from '../hooks/UseApiError';
import { import { makeid, modifyProcessIdentifierForPathParam } from '../helpers';
makeid,
modifyProcessIdentifierForPathParam,
encodeBase64,
refreshAtInterval,
REFRESH_TIMEOUT_SECONDS,
} from '../helpers';
import { import {
CarbonComboBoxProcessSelection, CarbonComboBoxProcessSelection,
ProcessFile, ProcessFile,
ProcessModel, ProcessModel,
ProcessModelCaller, ProcessModelCaller,
ProcessReference, ProcessReference,
User,
} from '../interfaces'; } from '../interfaces';
import ProcessSearch from '../components/ProcessSearch'; import ProcessSearch from '../components/ProcessSearch';
import { Notification } from '../components/Notification'; import { Notification } from '../components/Notification';
import { usePrompt } from '../hooks/UsePrompt'; import { usePrompt } from '../hooks/UsePrompt';
import ActiveUsers from '../components/ActiveUsers';
export default function ProcessModelEditDiagram() { export default function ProcessModelEditDiagram() {
const [showFileNameEditor, setShowFileNameEditor] = useState(false); const [showFileNameEditor, setShowFileNameEditor] = useState(false);
@ -73,7 +67,6 @@ export default function ProcessModelEditDiagram() {
useState<boolean>(false); useState<boolean>(false);
const [processModelFileInvalidText, setProcessModelFileInvalidText] = const [processModelFileInvalidText, setProcessModelFileInvalidText] =
useState<string>(''); useState<string>('');
const [activeUsers, setActiveUsers] = useState<User[]>([]);
const handleShowMarkdownEditor = () => setShowMarkdownEditor(true); const handleShowMarkdownEditor = () => setShowMarkdownEditor(true);
@ -132,14 +125,7 @@ export default function ProcessModelEditDiagram() {
usePrompt('Changes you made may not be saved.', diagramHasChanges); usePrompt('Changes you made may not be saved.', diagramHasChanges);
const lastVisitedIdentifier = encodeBase64(window.location.pathname);
useEffect(() => { useEffect(() => {
const updateActiveUsers = () => {
HttpService.makeCallToBackend({
path: `/active-users/updates/${lastVisitedIdentifier}`,
successCallback: setActiveUsers,
});
};
// Grab all available process models in case we need to search for them. // Grab all available process models in case we need to search for them.
// Taken from the Process Group List // Taken from the Process Group List
const processResults = (result: any) => { const processResults = (result: any) => {
@ -154,21 +140,6 @@ export default function ProcessModelEditDiagram() {
path: `/processes`, path: `/processes`,
successCallback: processResults, successCallback: processResults,
}); });
const unregisterUser = () => {
HttpService.makeCallToBackend({
path: `/active-users/unregister/${lastVisitedIdentifier}`,
successCallback: setActiveUsers,
});
};
updateActiveUsers();
return refreshAtInterval(
15,
REFRESH_TIMEOUT_SECONDS,
updateActiveUsers,
unregisterUser
);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // it is critical to only run this once. }, []); // it is critical to only run this once.
@ -959,20 +930,6 @@ export default function ProcessModelEditDiagram() {
return searchParams.get('file_type') === 'dmn' || fileName.endsWith('.dmn'); return searchParams.get('file_type') === 'dmn' || fileName.endsWith('.dmn');
}; };
const activeUserElement = () => {
const au = activeUsers.map((activeUser: User) => {
return (
<div
title={`${activeUser.username} is also viewing this page`}
className="user-circle"
>
{activeUser.username.charAt(0).toUpperCase()}
</div>
);
});
return <div className="user-list">{au}</div>;
};
const appropriateEditor = () => { const appropriateEditor = () => {
if (isDmn()) { if (isDmn()) {
return ( return (
@ -1017,7 +974,7 @@ export default function ProcessModelEditDiagram() {
onSearchProcessModels={onSearchProcessModels} onSearchProcessModels={onSearchProcessModels}
onElementsChanged={onElementsChanged} onElementsChanged={onElementsChanged}
callers={callers} callers={callers}
activeUserElement={activeUserElement()} activeUserElement={<ActiveUsers />}
/> />
); );
}; };

View File

@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import Editor from '@monaco-editor/react'; import Editor from '@monaco-editor/react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
// @ts-ignore // @ts-ignore
import { Button, Modal } from '@carbon/react'; import { Button, ButtonSet, Modal } from '@carbon/react';
import { Can } from '@casl/react'; import { Can } from '@casl/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
@ -15,6 +15,7 @@ import { Notification } from '../components/Notification';
import useAPIError from '../hooks/UseApiError'; import useAPIError from '../hooks/UseApiError';
import { usePermissionFetcher } from '../hooks/PermissionService'; import { usePermissionFetcher } from '../hooks/PermissionService';
import { useUriListForPermissions } from '../hooks/UriListForPermissions'; import { useUriListForPermissions } from '../hooks/UriListForPermissions';
import ActiveUsers from '../components/ActiveUsers';
// NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could // NOTE: This is mostly the same as ProcessModelEditDiagram and if we go this route could
// possibly be merged into it. I'm leaving as a separate file now in case it does // possibly be merged into it. I'm leaving as a separate file now in case it does
// end up diverging greatly // end up diverging greatly
@ -231,59 +232,80 @@ export default function ReactFormEditor() {
{newFileNameBox()} {newFileNameBox()}
{saveFileMessage()} {saveFileMessage()}
<Can I="PUT" a={targetUris.processModelFileShowPath} ability={ability}> <ButtonSet>
<Button <Can
onClick={saveFile} I="PUT"
variant="danger" a={targetUris.processModelFileShowPath}
data-qa="file-save-button" ability={ability}
> >
Save
</Button>
</Can>
<Can
I="DELETE"
a={targetUris.processModelFileShowPath}
ability={ability}
>
{params.file_name ? (
<ButtonWithConfirmation
data-qa="delete-process-model-file"
description={`Delete file ${params.file_name}?`}
onConfirmation={deleteFile}
buttonLabel="Delete"
/>
) : null}
</Can>
<Can I="PUT" a={targetUris.processModelFileShowPath} ability={ability}>
{hasFormBuilder ? (
<Button <Button
onClick={() => onClick={saveFile}
navigate(
`/admin/process-models/${params.process_model_id}/form-builder${formBuildFileParam}`
)
}
variant="danger" variant="danger"
data-qa="form-builder-button" data-qa="file-save-button"
> >
Form Builder Save
</Button> </Button>
) : null} </Can>
</Can> <Can
<Can I="GET" a={targetUris.processModelFileShowPath} ability={ability}> I="DELETE"
{hasDiagram ? ( a={targetUris.processModelFileShowPath}
<Button ability={ability}
onClick={() => >
navigate( {params.file_name ? (
`/admin/process-models/${modifiedProcessModelId}/files/${params.file_name}` <ButtonWithConfirmation
) data-qa="delete-process-model-file"
} description={`Delete file ${params.file_name}?`}
variant="danger" onConfirmation={deleteFile}
data-qa="view-diagram-button" buttonLabel="Delete"
> />
View Diagram ) : null}
</Button> </Can>
) : null} <Can
</Can> I="PUT"
a={targetUris.processModelFileShowPath}
ability={ability}
>
{hasFormBuilder ? (
<Button
onClick={() =>
navigate(
`/admin/process-models/${params.process_model_id}/form-builder${formBuildFileParam}`
)
}
variant="danger"
data-qa="form-builder-button"
>
Form Builder
</Button>
) : null}
</Can>
<Can
I="GET"
a={targetUris.processModelFileShowPath}
ability={ability}
>
{hasDiagram ? (
<Button
onClick={() =>
navigate(
`/admin/process-models/${modifiedProcessModelId}/files/${params.file_name}`
)
}
variant="danger"
data-qa="view-diagram-button"
>
View Diagram
</Button>
) : null}
</Can>
<Can
I="PUT"
a={targetUris.processModelFileShowPath}
ability={ability}
>
<ActiveUsers />
</Can>
</ButtonSet>
<Editor <Editor
height={600} height={600}
width="auto" width="auto"