Merge remote-tracking branch 'origin/main' into feature/interstitial_do_not_update_pi_status

This commit is contained in:
jasquat 2023-05-05 13:12:44 -04:00
commit 6cdc6913c4
7 changed files with 149 additions and 98 deletions

View File

@ -530,6 +530,8 @@ def _create_or_update_process_model_file(
file_contents = SpecFileService.get_data(process_model, file.name) file_contents = SpecFileService.get_data(process_model, file.name)
file.file_contents = file_contents file.file_contents = file_contents
file.process_model_id = process_model.id file.process_model_id = process_model.id
file_contents_hash = sha256(file_contents).hexdigest()
file.file_contents_hash = file_contents_hash
_commit_and_push_to_git(f"{message_for_git_commit} {process_model_identifier}/{file.name}") _commit_and_push_to_git(f"{message_for_git_commit} {process_model_identifier}/{file.name}")
return make_response(jsonify(file), http_status_to_return) return make_response(jsonify(file), http_status_to_return)

View File

@ -581,6 +581,7 @@ class AuthorizationService:
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes/callers")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes/callers"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/active-users/*"))
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username")) permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username"))
permissions_to_assign.append( permissions_to_assign.append(
PermissionToAssign(permission="read", target_uri="/process-instances/find-by-id/*") PermissionToAssign(permission="read", target_uri="/process-instances/find-by-id/*")

View File

@ -289,6 +289,7 @@ class TestAuthorizationService(BaseTest):
) -> None: ) -> None:
"""Test_explode_permissions_basic.""" """Test_explode_permissions_basic."""
expected_permissions = [ expected_permissions = [
("/active-users/*", "read"),
("/process-instances/find-by-id/*", "read"), ("/process-instances/find-by-id/*", "read"),
("/process-instances/for-me", "create"), ("/process-instances/for-me", "create"),
("/process-instances/report-metadata", "read"), ("/process-instances/report-metadata", "read"),

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()}
{/* only show other users if the current user can save the current diagram */}
<Can
I="PUT"
a={targetUris.processModelFileShowPath}
ability={ability}
>
{activeUserElement || null} {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.
@ -206,8 +177,11 @@ export default function ProcessModelEditDiagram() {
setProcessModelFileInvalidText(''); setProcessModelFileInvalidText('');
}; };
const navigateToProcessModelFile = (_result: any) => { const navigateToProcessModelFile = (file: ProcessFile) => {
setDisplaySaveFileMessage(true); setDisplaySaveFileMessage(true);
if (file.file_contents_hash) {
setProcessModelFile(file);
}
if (!params.file_name) { if (!params.file_name) {
const fileNameWithExtension = `${newFileName}.${searchParams.get( const fileNameWithExtension = `${newFileName}.${searchParams.get(
'file_type' 'file_type'
@ -956,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 (
@ -1014,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
@ -87,8 +88,11 @@ export default function ReactFormEditor() {
} }
}, [params, modifiedProcessModelId]); }, [params, modifiedProcessModelId]);
const navigateToProcessModelFile = (_result: any) => { const navigateToProcessModelFile = (file: ProcessFile) => {
setDisplaySaveFileMessage(true); setDisplaySaveFileMessage(true);
if (file.file_contents_hash) {
setProcessModelFile(file);
}
if (!params.file_name) { if (!params.file_name) {
const fileNameWithExtension = `${newFileName}.${fileExtension}`; const fileNameWithExtension = `${newFileName}.${fileExtension}`;
navigate( navigate(
@ -110,6 +114,9 @@ export default function ReactFormEditor() {
httpMethod = 'POST'; httpMethod = 'POST';
} else { } else {
url += `/${fileNameWithExtension}`; url += `/${fileNameWithExtension}`;
if (processModelFile && processModelFile.file_contents_hash) {
url += `?file_contents_hash=${processModelFile.file_contents_hash}`;
}
} }
if (!fileNameWithExtension) { if (!fileNameWithExtension) {
handleShowFileNameEditor(); handleShowFileNameEditor();
@ -225,7 +232,12 @@ export default function ReactFormEditor() {
{newFileNameBox()} {newFileNameBox()}
{saveFileMessage()} {saveFileMessage()}
<Can I="PUT" a={targetUris.processModelFileShowPath} ability={ability}> <ButtonSet>
<Can
I="PUT"
a={targetUris.processModelFileShowPath}
ability={ability}
>
<Button <Button
onClick={saveFile} onClick={saveFile}
variant="danger" variant="danger"
@ -248,7 +260,11 @@ export default function ReactFormEditor() {
/> />
) : null} ) : null}
</Can> </Can>
<Can I="PUT" a={targetUris.processModelFileShowPath} ability={ability}> <Can
I="PUT"
a={targetUris.processModelFileShowPath}
ability={ability}
>
{hasFormBuilder ? ( {hasFormBuilder ? (
<Button <Button
onClick={() => onClick={() =>
@ -263,7 +279,11 @@ export default function ReactFormEditor() {
</Button> </Button>
) : null} ) : null}
</Can> </Can>
<Can I="GET" a={targetUris.processModelFileShowPath} ability={ability}> <Can
I="GET"
a={targetUris.processModelFileShowPath}
ability={ability}
>
{hasDiagram ? ( {hasDiagram ? (
<Button <Button
onClick={() => onClick={() =>
@ -278,6 +298,14 @@ export default function ReactFormEditor() {
</Button> </Button>
) : null} ) : null}
</Can> </Can>
<Can
I="PUT"
a={targetUris.processModelFileShowPath}
ability={ability}
>
<ActiveUsers />
</Can>
</ButtonSet>
<Editor <Editor
height={600} height={600}
width="auto" width="auto"