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.file_contents = file_contents
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}")
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="/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="/active-users/*"))
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username"))
permissions_to_assign.append(
PermissionToAssign(permission="read", target_uri="/process-instances/find-by-id/*")

View File

@ -289,6 +289,7 @@ class TestAuthorizationService(BaseTest):
) -> None:
"""Test_explode_permissions_basic."""
expected_permissions = [
("/active-users/*", "read"),
("/process-instances/find-by-id/*", "read"),
("/process-instances/for-me", "create"),
("/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>
{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>
);
}

View File

@ -29,24 +29,18 @@ import HttpService from '../services/HttpService';
import ReactDiagramEditor from '../components/ReactDiagramEditor';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import useAPIError from '../hooks/UseApiError';
import {
makeid,
modifyProcessIdentifierForPathParam,
encodeBase64,
refreshAtInterval,
REFRESH_TIMEOUT_SECONDS,
} from '../helpers';
import { makeid, modifyProcessIdentifierForPathParam } from '../helpers';
import {
CarbonComboBoxProcessSelection,
ProcessFile,
ProcessModel,
ProcessModelCaller,
ProcessReference,
User,
} from '../interfaces';
import ProcessSearch from '../components/ProcessSearch';
import { Notification } from '../components/Notification';
import { usePrompt } from '../hooks/UsePrompt';
import ActiveUsers from '../components/ActiveUsers';
export default function ProcessModelEditDiagram() {
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
@ -73,7 +67,6 @@ export default function ProcessModelEditDiagram() {
useState<boolean>(false);
const [processModelFileInvalidText, setProcessModelFileInvalidText] =
useState<string>('');
const [activeUsers, setActiveUsers] = useState<User[]>([]);
const handleShowMarkdownEditor = () => setShowMarkdownEditor(true);
@ -132,14 +125,7 @@ export default function ProcessModelEditDiagram() {
usePrompt('Changes you made may not be saved.', diagramHasChanges);
const lastVisitedIdentifier = encodeBase64(window.location.pathname);
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.
// Taken from the Process Group List
const processResults = (result: any) => {
@ -154,21 +140,6 @@ export default function ProcessModelEditDiagram() {
path: `/processes`,
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
}, []); // it is critical to only run this once.
@ -206,8 +177,11 @@ export default function ProcessModelEditDiagram() {
setProcessModelFileInvalidText('');
};
const navigateToProcessModelFile = (_result: any) => {
const navigateToProcessModelFile = (file: ProcessFile) => {
setDisplaySaveFileMessage(true);
if (file.file_contents_hash) {
setProcessModelFile(file);
}
if (!params.file_name) {
const fileNameWithExtension = `${newFileName}.${searchParams.get(
'file_type'
@ -956,20 +930,6 @@ export default function ProcessModelEditDiagram() {
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 = () => {
if (isDmn()) {
return (
@ -1014,7 +974,7 @@ export default function ProcessModelEditDiagram() {
onSearchProcessModels={onSearchProcessModels}
onElementsChanged={onElementsChanged}
callers={callers}
activeUserElement={activeUserElement()}
activeUserElement={<ActiveUsers />}
/>
);
};

View File

@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import Editor from '@monaco-editor/react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
// @ts-ignore
import { Button, Modal } from '@carbon/react';
import { Button, ButtonSet, Modal } from '@carbon/react';
import { Can } from '@casl/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
@ -15,6 +15,7 @@ import { Notification } from '../components/Notification';
import useAPIError from '../hooks/UseApiError';
import { usePermissionFetcher } from '../hooks/PermissionService';
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
// possibly be merged into it. I'm leaving as a separate file now in case it does
// end up diverging greatly
@ -87,8 +88,11 @@ export default function ReactFormEditor() {
}
}, [params, modifiedProcessModelId]);
const navigateToProcessModelFile = (_result: any) => {
const navigateToProcessModelFile = (file: ProcessFile) => {
setDisplaySaveFileMessage(true);
if (file.file_contents_hash) {
setProcessModelFile(file);
}
if (!params.file_name) {
const fileNameWithExtension = `${newFileName}.${fileExtension}`;
navigate(
@ -110,6 +114,9 @@ export default function ReactFormEditor() {
httpMethod = 'POST';
} else {
url += `/${fileNameWithExtension}`;
if (processModelFile && processModelFile.file_contents_hash) {
url += `?file_contents_hash=${processModelFile.file_contents_hash}`;
}
}
if (!fileNameWithExtension) {
handleShowFileNameEditor();
@ -225,59 +232,80 @@ export default function ReactFormEditor() {
{newFileNameBox()}
{saveFileMessage()}
<Can I="PUT" a={targetUris.processModelFileShowPath} ability={ability}>
<Button
onClick={saveFile}
variant="danger"
data-qa="file-save-button"
<ButtonSet>
<Can
I="PUT"
a={targetUris.processModelFileShowPath}
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
onClick={() =>
navigate(
`/admin/process-models/${params.process_model_id}/form-builder${formBuildFileParam}`
)
}
onClick={saveFile}
variant="danger"
data-qa="form-builder-button"
data-qa="file-save-button"
>
Form Builder
Save
</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>
<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
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
height={600}
width="auto"