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

This commit is contained in:
jasquat 2023-05-17 09:34:21 -04:00
commit 97e0951ddf
No known key found for this signature in database
14 changed files with 93 additions and 79 deletions

View File

@ -40,15 +40,16 @@ fi
additional_args=""
if [[ "${SPIFFWORKFLOW_BACKEND_APPLICATION_ROOT:-}" != "/" ]]; then
additional_args="${additional_args} -e SCRIPT_NAME=${SPIFFWORKFLOW_BACKEND_APPLICATION_ROOT}"
app_root="${SPIFFWORKFLOW_BACKEND_APPLICATION_ROOT:-}"
if [[ -n "$app_root" ]] && [[ "${app_root}" != "/" ]]; then
additional_args="${additional_args} -e SCRIPT_NAME=${app_root}"
fi
# HACK: if loading fixtures for acceptance tests when we do not need multiple workers
# it causes issues with attempting to add duplicate data to the db
workers=3
worker_count=4
if [[ "${SPIFFWORKFLOW_BACKEND_LOAD_FIXTURE_DATA:-}" == "true" ]]; then
workers=1
worker_count=1
fi
if [[ "${SPIFFWORKFLOW_BACKEND_RUN_DATA_SETUP:-}" != "false" ]]; then
@ -67,11 +68,35 @@ fi
git init "${SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR}"
git config --global --add safe.directory "${SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR}"
if [[ -z "${SPIFFWORKFLOW_BACKEND_THREADS_PER_WORKER:-}" ]]; then
# default to 3 * 2 = 6 threads per worker
# you may want to configure threads_to_use_per_core based on whether your workload is more cpu intensive or more I/O intensive:
# cpu heavy, make it smaller
# I/O heavy, make it larger
threads_to_use_per_core=3
# just making up a number here for num_cores_multiple_for_threads
# https://stackoverflow.com/a/55423170/6090676
# if we had access to python (i'm not sure i want to run another python script here),
# we could do something like this (on linux) to get the number of cores available to this process and a better estimate of a
# reasonable num_cores_multiple_for_threads
# if hasattr(os, 'sched_getaffinity')
# number_of_available_cores = os.sched_getaffinity(0)
num_cores_multiple_for_threads=2
SPIFFWORKFLOW_BACKEND_THREADS_PER_WORKER=$((threads_to_use_per_core * num_cores_multiple_for_threads))
export SPIFFWORKFLOW_BACKEND_THREADS_PER_WORKER
fi
# --worker-class is not strictly necessary, since setting threads will automatically set the worker class to gthread, but meh
export IS_GUNICORN="true"
# THIS MUST BE THE LAST COMMAND!
exec poetry run gunicorn ${additional_args} \
--bind "0.0.0.0:$port" \
--workers="$workers" \
--preload \
--worker-class "gthread" \
--workers="$worker_count" \
--threads "$SPIFFWORKFLOW_BACKEND_THREADS_PER_WORKER" \
--limit-request-line 8192 \
--timeout "$GUNICORN_TIMEOUT_SECONDS" \
--capture-output \

View File

@ -13,8 +13,7 @@ class ConfigurationError(Exception):
"""ConfigurationError."""
def setup_database_uri(app: Flask) -> None:
"""Setup_database_uri."""
def setup_database_configs(app: Flask) -> None:
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None:
database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}"
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite":
@ -34,6 +33,25 @@ def setup_database_uri(app: Flask) -> None:
else:
app.config["SQLALCHEMY_DATABASE_URI"] = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI")
# if pool size came in from the environment, it's a string, but we need an int
# if it didn't come in from the environment, base it on the number of threads
# note that max_overflow defaults to 10, so that will give extra buffer.
pool_size = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_POOL_SIZE")
if pool_size is not None:
pool_size = int(pool_size)
else:
# this one doesn't come from app config and isn't documented in default.py because we don't want to give people the impression
# that setting it in flask python configs will work. on the contrary, it's used by a bash
# script that starts the backend, so it can only be set in the environment.
threads_per_worker_config = os.environ.get("SPIFFWORKFLOW_BACKEND_THREADS_PER_WORKER")
if threads_per_worker_config is not None:
pool_size = int(threads_per_worker_config)
else:
# this is a sqlalchemy default, if we don't have any better ideas
pool_size = 5
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {}
app.config['SQLALCHEMY_ENGINE_OPTIONS']['pool_size'] = pool_size
def load_config_file(app: Flask, env_config_module: str) -> None:
"""Load_config_file."""
@ -115,7 +133,7 @@ def setup_config(app: Flask) -> None:
app.config["PROCESS_UUID"] = uuid.uuid4()
setup_database_uri(app)
setup_database_configs(app)
setup_logger(app)
if app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"] == "":

View File

@ -33,6 +33,10 @@ SPIFFWORKFLOW_BACKEND_BACKGROUND_SCHEDULER_USER_INPUT_REQUIRED_POLLING_INTERVAL_
default="120",
)
)
# we only use this in one place, and it checks to see if it is None.
SPIFFWORKFLOW_BACKEND_DATABASE_POOL_SIZE = environ.get("SPIFFWORKFLOW_BACKEND_DATABASE_POOL_SIZE")
SPIFFWORKFLOW_BACKEND_URL_FOR_FRONTEND = environ.get(
"SPIFFWORKFLOW_BACKEND_URL_FOR_FRONTEND", default="http://localhost:7001"
)

View File

@ -9,7 +9,3 @@ def status() -> Response:
"""Status."""
ProcessInstanceModel.query.filter().first()
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

@ -577,13 +577,14 @@ class AuthorizationService:
permissions_to_assign.append(
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-models"))
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="/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:
expected_permissions = [
("/active-users/*", "read"),
("/debug/version-info", "read"),
("/process-groups", "read"),
("/process-instances/find-by-id/*", "read"),
("/process-instances/for-me", "create"),

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;
}
let buttonText = 'View';
if (hasAccessToCompleteTask && processInstance.task_id) {
buttonText = 'Go';
}
buttonElement = (
<Button kind="secondary" href={interstitialUrl}>
<Button
kind="secondary"
href={interstitialUrl}
style={{ width: '60px' }}
>
{buttonText}
</Button>
);

View File

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

View File

@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { fetchEventSource } from '@microsoft/fetch-event-source';
// @ts-ignore
import { Loading, Grid, Column, Button } from '@carbon/react';
import { Loading, Button } from '@carbon/react';
import { BACKEND_BASE_URL } from '../config';
import { getBasicHeaders } from '../services/HttpService';
@ -89,26 +89,17 @@ export default function ProcessInterstitial() {
return state;
};
const getStatusImage = () => {
switch (getStatus()) {
case 'RUNNING':
return (
<Loading description="Active loading indicator" withOverlay={false} />
);
case 'LOCKED':
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();
const getLoadingIcon = () => {
if (getStatus() === 'RUNNING') {
return (
<Loading
description="Active loading indicator"
withOverlay={false}
style={{ margin: 'auto' }}
/>
);
}
return null;
};
const getReturnHomeButton = (index: number) => {
@ -123,6 +114,7 @@ export default function ProcessInterstitial() {
kind="secondary"
data-qa="return-to-home-button"
onClick={() => navigate(`/tasks`)}
style={{ marginBottom: 30 }}
>
Return to Home
</Button>
@ -132,35 +124,14 @@ export default function ProcessInterstitial() {
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) => {
if (!processInstance || processInstance.status === 'completed') {
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
return (
<>
<h4 className="heading-compact-01">Waiting on Someone Else</h4>
<p>
This next task is assigned to a different person or team. There is
no action for you to take at this time.
</p>
</>
<p>
This next task is assigned to a different person or team. There is
no action for you to take at this time.
</p>
);
}
if (shouldRedirect(myTask)) {
@ -189,6 +160,7 @@ export default function ProcessInterstitial() {
if (state === 'CLOSED' && lastTask === null) {
navigate(`/tasks`);
}
if (lastTask) {
return (
<>
@ -206,21 +178,10 @@ export default function ProcessInterstitial() {
],
]}
/>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
{getStatusImage()}
<div>
<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}>
{getLoadingIcon()}
<div style={{ maxWidth: 800, margin: 'auto', padding: 50 }}>
{data.map((d, index) => (
<>
<div
className={
index < 4
@ -231,10 +192,9 @@ export default function ProcessInterstitial() {
{userMessage(d)}
</div>
{getReturnHomeButton(index)}
{getHr(index)}
</Column>
</Grid>
))}
</>
))}
</div>
</>
);
}