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_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)
|
||||||
|
|
|
@ -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/*")
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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>
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,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"
|
||||||
|
|
Loading…
Reference in New Issue