Merge remote-tracking branch 'origin/main' into feature/interstitial_do_not_update_pi_status
This commit is contained in:
commit
6cdc6913c4
|
@ -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)
|
||||
|
|
|
@ -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/*")
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue