Merge remote-tracking branch 'origin/main' into feature/gunicorn-configs

This commit is contained in:
burnettk 2023-05-16 17:37:33 -04:00
commit 63999da13a
20 changed files with 74 additions and 116 deletions

14
Jenkinsfile vendored
View File

@ -49,12 +49,14 @@ pipeline {
stages { stages {
stage('Prep') { stage('Prep') {
steps { script { steps { script {
def jobMetaJson = new JsonBuilder([ dir("spiffworkflow-${params.COMPONENT}") {
git_commit: env.GIT_COMMIT.take(7), def jobMetaJson = new JsonBuilder([
git_branch: env.GIT_BRANCH, git_commit: env.GIT_COMMIT.take(7),
build_id: env.BUILD_ID, git_branch: env.GIT_BRANCH,
]).toPrettyString() build_id: env.BUILD_ID,
sh "echo '${jobMetaJson}' > version_info.json" ]).toPrettyString()
sh "echo '${jobMetaJson}' > version_info.json"
}
} } } }
} }

View File

@ -3089,7 +3089,7 @@ lxml = "*"
type = "git" type = "git"
url = "https://github.com/sartography/SpiffWorkflow" url = "https://github.com/sartography/SpiffWorkflow"
reference = "main" reference = "main"
resolved_reference = "a68dec77ebb0960dd8097341df7575a34e435501" resolved_reference = "340e9983b5afd2e6e71df883e74f7dc20d4474dd"
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"

View File

@ -9,7 +9,3 @@ def status() -> Response:
"""Status.""" """Status."""
ProcessInstanceModel.query.filter().first() ProcessInstanceModel.query.filter().first()
return make_response({"ok": True}, 200) return make_response({"ok": True}, 200)
def test_raise_error() -> Response:
raise Exception("This exception was generated by /status/test-raise-error for testing purposes. Please ignore.")

View File

@ -438,14 +438,6 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
) )
yield render_data("error", api_error) yield render_data("error", api_error)
return return
except Exception as e:
api_error = ApiError(
error_code="engine_steps_error",
message=f"Failed to complete an automated task. Error was: {str(e)}",
status_code=400,
)
yield render_data("error", api_error)
return
processor.refresh_waiting_tasks() processor.refresh_waiting_tasks()
ready_engine_task_count = get_ready_engine_step_count(processor.bpmn_process_instance) ready_engine_task_count = get_ready_engine_step_count(processor.bpmn_process_instance)
tasks = get_reportable_tasks() tasks = get_reportable_tasks()

View File

@ -577,13 +577,14 @@ class AuthorizationService:
permissions_to_assign.append( permissions_to_assign.append(
PermissionToAssign(permission="read", target_uri="/process-instances/report-metadata") PermissionToAssign(permission="read", target_uri="/process-instances/report-metadata")
) )
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/active-users/*"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/debug/version-info"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-groups")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-groups"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-models")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-models"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes"))
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

@ -432,7 +432,8 @@ class TaskService:
for task_id, task_properties in tasks.items(): for task_id, task_properties in tasks.items():
# The Root task is added to the spec by Spiff when the bpmn process is instantiated # The Root task is added to the spec by Spiff when the bpmn process is instantiated
# within Spiff. We do not actually need it and it's missing from our initial # within Spiff. We do not actually need it and it's missing from our initial
# bpmn process defintion so let's avoid using it. # bpmn process defintion so let's avoid using it. This causes issues with the hashing
# since it is not there when we initially take the hash and save the definition.
if task_properties["task_spec"] == "Root": if task_properties["task_spec"] == "Root":
continue continue

View File

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

View File

@ -81,7 +81,7 @@ describe('tasks', () => {
cy.navigateToHome(); cy.navigateToHome();
// look for somethig to make sure the homepage has loaded // look for somethig to make sure the homepage has loaded
cy.contains('Instances with tasks waiting for me').should('exist'); cy.contains('Waiting for me').should('exist');
// FIXME: this will probably need a better way to link to the proper form that we want // FIXME: this will probably need a better way to link to the proper form that we want
cy.contains('Go').click(); cy.contains('Go').click();

View File

@ -871,6 +871,19 @@ describe.only('Learning and Development Path - Without Files', () => {
'Task: Reminder: Check Existing Budget', 'Task: Reminder: Check Existing Budget',
'approve' 'approve'
); );
const peopleOpsUsername = Cypress.env('peopleopssme_username');
const peopleOpsPassword = Cypress.env('peopleopssme_password');
cy.log(`=====peopleOpsUsername : ${peopleOpsUsername}`);
cy.log(`=====peopleOpsPassword : ${peopleOpsPassword}`);
submitWithUser(
peopleOpsUsername,
peopleOpsPassword,
processInstanceId,
null,
'approve'
);
checkProcessInstanceCompleted(username, password, processInstanceId); checkProcessInstanceCompleted(username, password, processInstanceId);
}); });
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1601,14 +1601,17 @@ export default function ProcessInstanceListTable({
) { ) {
hasAccessToCompleteTask = true; hasAccessToCompleteTask = true;
} }
let buttonText = 'View'; let buttonText = 'View';
if (hasAccessToCompleteTask && processInstance.task_id) { if (hasAccessToCompleteTask && processInstance.task_id) {
buttonText = 'Go'; buttonText = 'Go';
} }
buttonElement = ( buttonElement = (
<Button kind="secondary" href={interstitialUrl}> <Button
kind="secondary"
href={interstitialUrl}
style={{ width: '60px' }}
>
{buttonText} {buttonText}
</Button> </Button>
); );

View File

@ -478,22 +478,28 @@ svg.notification-icon {
.user_instructions_0 { .user_instructions_0 {
filter: opacity(1); filter: opacity(1);
font-size: 1.2em;
margin-bottom: 30px;
} }
.user_instructions_1 { .user_instructions_1 {
filter: opacity(60%); filter: opacity(60%);
font-size: 1.1em;
} }
.user_instructions_2 { .user_instructions_2 {
filter: opacity(40%); filter: opacity(40%);
font-size: 1em;
} }
.user_instructions_3 { .user_instructions_3 {
filter: opacity(20%); filter: opacity(20%);
font-size: 9em;
} }
.user_instructions_4 { .user_instructions_4 {
filter: opacity(10%); filter: opacity(10%);
font-size: 8em;
} }
.float-right { .float-right {

View File

@ -22,7 +22,7 @@ export default function InProgressInstances() {
const titleText = `This is a list of instances with tasks that are waiting for the ${userGroup} group.`; const titleText = `This is a list of instances with tasks that are waiting for the ${userGroup} group.`;
const headerElement = ( const headerElement = (
<h2 title={titleText} className="process-instance-table-header"> <h2 title={titleText} className="process-instance-table-header">
Instances with tasks waiting for <strong>{userGroup}</strong> Waiting for <strong>{userGroup}</strong>
</h2> </h2>
); );
return ( return (
@ -61,7 +61,7 @@ export default function InProgressInstances() {
'This is a list of instances that have tasks that you can complete.'; 'This is a list of instances that have tasks that you can complete.';
const waitingForMeHeaderElement = ( const waitingForMeHeaderElement = (
<h2 title={waitingForMeTitleText} className="process-instance-table-header"> <h2 title={waitingForMeTitleText} className="process-instance-table-header">
Instances with tasks waiting for me Waiting for me
</h2> </h2>
); );

View File

@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { fetchEventSource } from '@microsoft/fetch-event-source'; import { fetchEventSource } from '@microsoft/fetch-event-source';
// @ts-ignore // @ts-ignore
import { Loading, Grid, Column, Button } from '@carbon/react'; import { Loading, Button } from '@carbon/react';
import { BACKEND_BASE_URL } from '../config'; import { BACKEND_BASE_URL } from '../config';
import { getBasicHeaders } from '../services/HttpService'; import { getBasicHeaders } from '../services/HttpService';
@ -89,26 +89,17 @@ export default function ProcessInterstitial() {
return state; return state;
}; };
const getStatusImage = () => { const getLoadingIcon = () => {
switch (getStatus()) { if (getStatus() === 'RUNNING') {
case 'RUNNING': return (
return ( <Loading
<Loading description="Active loading indicator" withOverlay={false} /> description="Active loading indicator"
); withOverlay={false}
case 'LOCKED': style={{ margin: 'auto' }}
return <img src="/interstitial/locked.png" alt="Locked" />; />
case 'READY': );
case 'REDIRECTING':
return <img src="/interstitial/redirect.png" alt="Redirecting ...." />;
case 'WAITING':
return <img src="/interstitial/waiting.png" alt="Waiting ...." />;
case 'COMPLETED':
return <img src="/interstitial/completed.png" alt="Completed" />;
case 'ERROR':
return <img src="/interstitial/errored.png" alt="Errored" />;
default:
return getStatus();
} }
return null;
}; };
const getReturnHomeButton = (index: number) => { const getReturnHomeButton = (index: number) => {
@ -123,6 +114,7 @@ export default function ProcessInterstitial() {
kind="secondary" kind="secondary"
data-qa="return-to-home-button" data-qa="return-to-home-button"
onClick={() => navigate(`/tasks`)} onClick={() => navigate(`/tasks`)}
style={{ marginBottom: 30 }}
> >
Return to Home Return to Home
</Button> </Button>
@ -132,35 +124,14 @@ export default function ProcessInterstitial() {
return ''; return '';
}; };
const getHr = (index: number) => {
if (index === 0) {
return (
<div style={{ padding: '10px 0 50px 0' }}>
<hr />
</div>
);
}
return '';
};
function capitalize(str: string): string {
if (str && str.length > 0) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
return '';
}
const userMessage = (myTask: ProcessInstanceTask) => { const userMessage = (myTask: ProcessInstanceTask) => {
if (!processInstance || processInstance.status === 'completed') { if (!processInstance || processInstance.status === 'completed') {
if (!myTask.can_complete && userTasks.includes(myTask.type)) { if (!myTask.can_complete && userTasks.includes(myTask.type)) {
return ( return (
<> <p>
<h4 className="heading-compact-01">Waiting on Someone Else</h4> This next task is assigned to a different person or team. There is
<p> no action for you to take at this time.
This next task is assigned to a different person or team. There is </p>
no action for you to take at this time.
</p>
</>
); );
} }
if (shouldRedirect(myTask)) { if (shouldRedirect(myTask)) {
@ -189,6 +160,7 @@ export default function ProcessInterstitial() {
if (state === 'CLOSED' && lastTask === null) { if (state === 'CLOSED' && lastTask === null) {
navigate(`/tasks`); navigate(`/tasks`);
} }
if (lastTask) { if (lastTask) {
return ( return (
<> <>
@ -206,21 +178,10 @@ export default function ProcessInterstitial() {
], ],
]} ]}
/> />
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}> {getLoadingIcon()}
{getStatusImage()} <div style={{ maxWidth: 800, margin: 'auto', padding: 50 }}>
<div> {data.map((d, index) => (
<h1 style={{ marginBottom: '0em' }}> <>
{lastTask.process_model_display_name}:{' '}
{lastTask.process_instance_id}
</h1>
<div>Status: {capitalize(getStatus())}</div>
</div>
</div>
<br />
<br />
{data.map((d, index) => (
<Grid fullWidth style={{ marginBottom: '1em' }}>
<Column md={6} lg={8} sm={4}>
<div <div
className={ className={
index < 4 index < 4
@ -231,10 +192,9 @@ export default function ProcessInterstitial() {
{userMessage(d)} {userMessage(d)}
</div> </div>
{getReturnHomeButton(index)} {getReturnHomeButton(index)}
{getHr(index)} </>
</Column> ))}
</Grid> </div>
))}
</> </>
); );
} }

View File

@ -88,19 +88,12 @@ function TypeaheadWidget({
); );
} }
enum FormSubmitType {
Default,
Draft,
}
export default function TaskShow() { export default function TaskShow() {
const [task, setTask] = useState<Task | null>(null); const [task, setTask] = useState<Task | null>(null);
const [userTasks] = useState(null); const [userTasks] = useState(null);
const params = useParams(); const params = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
// save current form data so that we can avoid validations in certain situations
const [currentFormObject, setCurrentFormObject] = useState<any>({});
const { addError, removeError } = useAPIError(); const { addError, removeError } = useAPIError();
@ -167,11 +160,7 @@ export default function TaskShow() {
} }
}; };
const handleFormSubmit = ( const handleFormSubmit = (formObject: any, event: any) => {
formObject: any,
_event: any,
submitType: FormSubmitType = FormSubmitType.Default
) => {
if (disabled) { if (disabled) {
return; return;
} }
@ -181,7 +170,7 @@ export default function TaskShow() {
return; return;
} }
let queryParams = ''; let queryParams = '';
if (submitType === FormSubmitType.Draft) { if (event && event.submitter.id === 'close-button') {
queryParams = '?save_as_draft=true'; queryParams = '?save_as_draft=true';
} }
setDisabled(true); setDisabled(true);
@ -301,11 +290,6 @@ export default function TaskShow() {
return errors; return errors;
}; };
const updateFormData = (formObject: any) => {
currentFormObject.formData = formObject.formData;
setCurrentFormObject(currentFormObject);
};
const formElement = () => { const formElement = () => {
if (!task) { if (!task) {
return null; return null;
@ -357,14 +341,12 @@ export default function TaskShow() {
closeButton = ( closeButton = (
<Button <Button
id="close-button" id="close-button"
type="submit"
disabled={disabled} disabled={disabled}
kind="secondary" kind="secondary"
title="Save changes without submitting." title="Save changes without submitting."
onClick={() =>
handleFormSubmit(currentFormObject, null, FormSubmitType.Draft)
}
> >
Close Save and Close
</Button> </Button>
); );
} }
@ -406,10 +388,8 @@ export default function TaskShow() {
uiSchema={formUiSchema} uiSchema={formUiSchema}
widgets={widgets} widgets={widgets}
validator={validator} validator={validator}
onChange={updateFormData}
customValidate={customValidate} customValidate={customValidate}
omitExtraData omitExtraData
liveOmit
> >
{reactFragmentToHideSubmitButton} {reactFragmentToHideSubmitButton}
</Form> </Form>

View File

@ -100,12 +100,15 @@ export default function BaseInputTemplate<
if (type === 'date') { if (type === 'date') {
// display the date in a date input box as the config wants. // display the date in a date input box as the config wants.
// it should in be y-m-d when it gets here. // it should in be y-m-d when it gets here.
let dateValue: string | null = ''; let dateValue: string | null = value;
if (value || value === 0) { if (value || value === 0) {
if (value.length < 10) { if (value.length < 10) {
dateValue = value; dateValue = value;
} else { } else {
dateValue = ymdDateStringToConfiguredFormat(value); try {
dateValue = ymdDateStringToConfiguredFormat(value);
// let the date component and form validators handle bad dates and do not blow up
} catch (RangeError) {}
} }
} }