From 0bda287c813b4ca8f8de6245bbefbf48859f9f9c Mon Sep 17 00:00:00 2001 From: jasquat Date: Fri, 12 May 2023 15:14:35 -0400 Subject: [PATCH 01/48] removed conditional to not load useEffect on process instance list table page so page changes and switching tabs works w/ burnettk --- .../components/ProcessInstanceListTable.tsx | 108 +++++++++--------- .../src/hooks/useEffectDebugger.tsx | 44 +++++++ 2 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 spiffworkflow-frontend/src/hooks/useEffectDebugger.tsx diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 0ee6db3ea..af70ff483 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -162,9 +162,6 @@ export default function ProcessInstanceListTable({ const [requiresRefilter, setRequiresRefilter] = useState(false); const [lastColumnFilter, setLastColumnFilter] = useState(''); - const [listHasBeenFiltered, setListHasBeenFiltered] = - useState(false); - const preferredUsername = UserService.getPreferredUsername(); const userEmail = UserService.getUserEmail(); @@ -223,6 +220,9 @@ export default function ProcessInstanceListTable({ ]; }, []); + // this is used from pages like the home page that have multiple tables + // and cannot store the report hash in the query params. + // it can be used to create a link to the process instances list page to reconstruct the report. const [reportHash, setReportHash] = useState(null); const [ @@ -321,14 +321,35 @@ export default function ProcessInstanceListTable({ // so use a variable instead const processModelSelectionItemsForUseEffect = useRef([]); + const clearFilters = useCallback((updateRequiresRefilter: boolean = true) => { + setProcessModelSelection(null); + setProcessStatusSelection([]); + setStartFromDate(''); + setStartFromTime(''); + setStartToDate(''); + setStartToTime(''); + setEndFromDate(''); + setEndFromTime(''); + setEndToDate(''); + setEndToTime(''); + setProcessInitiatorSelection(null); + setWithOldestOpenTask(false); + setSystemReport(null); + setSelectedUserGroup(null); + if (updateRequiresRefilter) { + setRequiresRefilter(true); + } + if (reportMetadata) { + reportMetadata.filter_by = []; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const getProcessInstances = useCallback( ( processInstanceReport: ProcessInstanceReport | null = null // eslint-disable-next-line sonarjs/cognitive-complexity ) => { - if (listHasBeenFiltered) { - return; - } let reportMetadataBodyToUse: ReportMetadata = { columns: [], filter_by: [], @@ -341,6 +362,11 @@ export default function ProcessInstanceListTable({ } } + // a bit hacky, clear out all filters before setting them from report metadata + // to ensure old filters are cleared out. + // this is really for going between the 'For Me' and 'All' tabs. + clearFilters(false); + // this is the code to re-populate the widgets on the page // with values from the report metadata, which is derived // from the searchParams (often report_hash) @@ -433,7 +459,6 @@ export default function ProcessInstanceListTable({ additionalReportFilters, dateParametersToAlwaysFilterBy, filtersEnabled, - listHasBeenFiltered, paginationQueryParamPrefix, perPageOptions, processInstanceApiSearchPath, @@ -441,6 +466,7 @@ export default function ProcessInstanceListTable({ setProcessInstancesFromResult, stopRefreshing, systemReportOptions, + clearFilters, ] ); @@ -448,11 +474,7 @@ export default function ProcessInstanceListTable({ if (!permissionsLoaded) { return undefined; } - function getReportMetadataWithReportHash() { - if (listHasBeenFiltered) { - return; - } const queryParams: string[] = []; ['report_hash', 'report_id'].forEach((paramName: string) => { if (searchParams.get(paramName)) { @@ -517,7 +539,6 @@ export default function ProcessInstanceListTable({ autoReload, filtersEnabled, getProcessInstances, - listHasBeenFiltered, permissionsLoaded, reportIdentifier, searchParams, @@ -632,17 +653,34 @@ export default function ProcessInstanceListTable({ reportMetadataToUse.filter_by = filtersToKeep; }; + const getFilterByFromReportMetadata = (reportColumnAccessor: string) => { + if (reportMetadata) { + return reportMetadata.filter_by.find((reportFilter: ReportFilter) => { + return reportColumnAccessor === reportFilter.field_name; + }); + } + return null; + }; + const insertOrUpdateFieldInReportMetadata = ( reportMetadataToUse: ReportMetadata, fieldName: string, fieldValue: any ) => { - removeFieldFromReportMetadata(reportMetadataToUse, fieldName); if (fieldValue) { - reportMetadataToUse.filter_by.push({ - field_name: fieldName, - field_value: fieldValue, - }); + let existingReportFilter = getFilterByFromReportMetadata(fieldName); + if (existingReportFilter) { + existingReportFilter.field_value = fieldValue; + } else { + existingReportFilter = { + field_name: fieldName, + field_value: fieldValue, + operator: 'equals', + }; + reportMetadataToUse.filter_by.push(existingReportFilter); + } + } else { + removeFieldFromReportMetadata(reportMetadataToUse, fieldName); } }; @@ -747,11 +785,7 @@ export default function ProcessInstanceListTable({ page = 1; // Reset page back to 0 const newReportMetadata = getNewReportMetadataBasedOnPageWidgets(); - setListHasBeenFiltered(true); setReportMetadata(newReportMetadata); - searchParams.set('per_page', perPage.toString()); - searchParams.set('page', page.toString()); - setSearchParams(searchParams); const queryParamString = `per_page=${perPage}&page=${page}`; HttpService.makeCallToBackend({ @@ -820,6 +854,7 @@ export default function ProcessInstanceListTable({ const formatProcessInstanceStatus = (_row: any, value: any) => { return titleizeString((value || '').replaceAll('_', ' ')); }; + const processStatusSearch = () => { return ( { - setProcessModelSelection(null); - setProcessStatusSelection([]); - setStartFromDate(''); - setStartFromTime(''); - setStartToDate(''); - setStartToTime(''); - setEndFromDate(''); - setEndFromTime(''); - setEndToDate(''); - setEndToTime(''); - setProcessInitiatorSelection(null); - setWithOldestOpenTask(false); - setSystemReport(null); - setSelectedUserGroup(null); - setRequiresRefilter(true); - if (reportMetadata) { - reportMetadata.filter_by = []; - } - }; - const processInstanceReportDidChange = (selection: any, mode?: string) => { clearFilters(); const selectedReport = selection.selectedItem; setProcessInstanceReportSelection(selectedReport); removeError(); setProcessInstanceReportJustSaved(mode || null); - setListHasBeenFiltered(false); let queryParamString = ''; if (selectedReport) { @@ -939,15 +952,6 @@ export default function ProcessInstanceListTable({ setReportColumnToOperateOn(null); }; - const getFilterByFromReportMetadata = (reportColumnAccessor: string) => { - if (reportMetadata) { - return reportMetadata.filter_by.find((reportFilter: ReportFilter) => { - return reportColumnAccessor === reportFilter.field_name; - }); - } - return null; - }; - const getNewFiltersFromReportForEditing = ( reportColumnForEditing: ReportColumnForEditing ) => { diff --git a/spiffworkflow-frontend/src/hooks/useEffectDebugger.tsx b/spiffworkflow-frontend/src/hooks/useEffectDebugger.tsx new file mode 100644 index 000000000..facdce38f --- /dev/null +++ b/spiffworkflow-frontend/src/hooks/useEffectDebugger.tsx @@ -0,0 +1,44 @@ +import { useEffect, useRef } from 'react'; + +// https://stackoverflow.com/a/59843241/6090676 +// useful for determining what state has changed that caused useEffect to trigger. +// just change the useEffect in question to useEffectDebugger +const usePrevious = (value: any, initialValue: any) => { + const ref = useRef(initialValue); + useEffect(() => { + ref.current = value; + }); + return ref.current; +}; +export default function useEffectDebugger( + effectHook: any, + dependencies: any, + dependencyNames: any = [] +) { + const previousDeps = usePrevious(dependencies, []); + + const changedDeps = dependencies.reduce( + (accum: any, dependency: any, index: any) => { + if (dependency !== previousDeps[index]) { + const keyName = dependencyNames[index] || index; + return { + ...accum, + [keyName]: { + before: previousDeps[index], + after: dependency, + }, + }; + } + + return accum; + }, + {} + ); + + if (Object.keys(changedDeps).length) { + console.log('[use-effect-debugger] ', changedDeps); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(effectHook, dependencies); +} From 02aa87cec5402d7d56e1a6c47c585b5b1e515ab2 Mon Sep 17 00:00:00 2001 From: burnettk Date: Fri, 12 May 2023 17:07:53 -0400 Subject: [PATCH 02/48] mostly frontend stuff: add version and doc links, autofix unused imports --- .../src/spiffworkflow_backend/__init__.py | 14 +++-- .../src/spiffworkflow_backend/api.yml | 13 ++++ .../spiffworkflow_backend/config/__init__.py | 12 ++-- .../models/process_instance.py | 6 +- .../routes/debug_controller.py | 7 +++ .../services/process_instance_processor.py | 12 ++-- spiffworkflow-frontend/.eslintrc.js | 12 +++- spiffworkflow-frontend/package-lock.json | 46 ++++++++++++++ spiffworkflow-frontend/package.json | 1 + spiffworkflow-frontend/src/App.tsx | 2 + .../src/components/NavigationBar.tsx | 21 +++++++ .../src/helpers/appVersionInfo.ts | 25 ++++++++ spiffworkflow-frontend/src/index.css | 4 ++ spiffworkflow-frontend/src/interfaces.ts | 2 + spiffworkflow-frontend/src/routes/About.tsx | 63 +++++++++++++++++++ 15 files changed, 220 insertions(+), 20 deletions(-) create mode 100644 spiffworkflow-frontend/src/helpers/appVersionInfo.ts create mode 100644 spiffworkflow-frontend/src/routes/About.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py index 72355fe97..ce8a3970b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py @@ -185,13 +185,19 @@ def create_app() -> flask.app.Flask: return app # type: ignore +def get_version_info_data() -> dict[str, Any]: + version_info_data_dict = {} + if os.path.isfile("version_info.json"): + with open("version_info.json") as f: + version_info_data_dict = json.load(f) + return version_info_data_dict + + def _setup_prometheus_metrics(app: flask.app.Flask, connexion_app: connexion.apps.flask_app.FlaskApp) -> None: metrics = ConnexionPrometheusMetrics(connexion_app) app.config["PROMETHEUS_METRICS"] = metrics - if os.path.isfile("version_info.json"): - version_info_data = {} - with open("version_info.json") as f: - version_info_data = json.load(f) + version_info_data = get_version_info_data() + if len(version_info_data) > 0: # prometheus does not allow periods in key names version_info_data_normalized = {k.replace(".", "_"): v for k, v in version_info_data.items()} metrics.info("version_info", "Application Version Info", **version_info_data_normalized) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index a43736292..7dcb81ce8 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -162,6 +162,19 @@ paths: schema: $ref: "#/components/schemas/OkTrue" + /debug/version-info: + get: + operationId: spiffworkflow_backend.routes.debug_controller.version_info + summary: Returns information about the version of the application + tags: + - Status + responses: + "200": + description: Returns version info if it exists. + content: + application/json: + schema: + $ref: "#/components/schemas/OkTrue" /active-users/updates/{last_visited_identifier}: parameters: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index a71157458..d61fa0850 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -18,13 +18,13 @@ def setup_database_uri(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": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + ) elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + ) else: # use pswd to trick flake8 with hardcoded passwords db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index 078925e70..1668565c9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -129,9 +129,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel): def serialized_with_metadata(self) -> dict[str, Any]: process_instance_attributes = self.serialized process_instance_attributes["process_metadata"] = self.process_metadata - process_instance_attributes[ - "process_model_with_diagram_identifier" - ] = self.process_model_with_diagram_identifier + process_instance_attributes["process_model_with_diagram_identifier"] = ( + self.process_model_with_diagram_identifier + ) return process_instance_attributes @property diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/debug_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/debug_controller.py index 29a81447b..f9507af6a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/debug_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/debug_controller.py @@ -1,6 +1,13 @@ """APIs for dealing with process groups, process models, and process instances.""" +from flask import make_response from flask.wrappers import Response +from spiffworkflow_backend import get_version_info_data + def test_raise_error() -> Response: raise Exception("This exception was generated by /debug/test-raise-error for testing purposes. Please ignore.") + + +def version_info() -> Response: + return make_response(get_version_info_data(), 200) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 2020c9d68..b64cedfdf 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -415,9 +415,9 @@ class ProcessInstanceProcessor: tld.process_instance_id = process_instance_model.id # we want this to be the fully qualified path to the process model including all group subcomponents - current_app.config[ - "THREAD_LOCAL_DATA" - ].process_model_identifier = f"{process_instance_model.process_model_identifier}" + current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = ( + f"{process_instance_model.process_model_identifier}" + ) self.process_instance_model = process_instance_model self.process_model_service = ProcessModelService() @@ -577,9 +577,9 @@ class ProcessInstanceProcessor: bpmn_subprocess_definition.bpmn_identifier ] = bpmn_process_definition_dict spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {} - bpmn_subprocess_definition_bpmn_identifiers[ - bpmn_subprocess_definition.id - ] = bpmn_subprocess_definition.bpmn_identifier + bpmn_subprocess_definition_bpmn_identifiers[bpmn_subprocess_definition.id] = ( + bpmn_subprocess_definition.bpmn_identifier + ) task_definitions = TaskDefinitionModel.query.filter( TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore diff --git a/spiffworkflow-frontend/.eslintrc.js b/spiffworkflow-frontend/.eslintrc.js index 75ad6cbc3..a7a64af3a 100644 --- a/spiffworkflow-frontend/.eslintrc.js +++ b/spiffworkflow-frontend/.eslintrc.js @@ -23,7 +23,7 @@ module.exports = { ecmaVersion: 'latest', sourceType: 'module', }, - plugins: ['react', 'sonarjs', '@typescript-eslint'], + plugins: ['react', 'sonarjs', '@typescript-eslint', 'unused-imports'], rules: { // according to https://github.com/typescript-eslint/typescript-eslint/issues/2621, You should turn off the eslint core rule and turn on the typescript-eslint rule // but not sure which of the above "extends" statements is maybe bringing in eslint core @@ -43,6 +43,16 @@ module.exports = { 'react/require-default-props': 'off', 'import/prefer-default-export': 'off', 'no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', + }, + ], '@typescript-eslint/no-unused-vars': [ 'error', { diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index 9642f5f91..c2900d302 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -85,6 +85,7 @@ "eslint-plugin-react": "^7.31.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-sonarjs": "^0.15.0", + "eslint-plugin-unused-imports": "^2.0.0", "prettier": "^2.7.1", "safe-regex": "^2.1.1", "ts-migrate": "^0.1.30" @@ -13286,6 +13287,36 @@ "eslint": "^7.5.0 || ^8.0.0" } }, + "node_modules/eslint-plugin-unused-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz", + "integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==", + "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -42460,6 +42491,21 @@ "@typescript-eslint/utils": "^5.58.0" } }, + "eslint-plugin-unused-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz", + "integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, + "eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/spiffworkflow-frontend/package.json b/spiffworkflow-frontend/package.json index 85e00d59a..5b85e25cf 100644 --- a/spiffworkflow-frontend/package.json +++ b/spiffworkflow-frontend/package.json @@ -113,6 +113,7 @@ "eslint-plugin-react": "^7.31.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-sonarjs": "^0.15.0", + "eslint-plugin-unused-imports": "^2.0.0", "prettier": "^2.7.1", "safe-regex": "^2.1.1", "ts-migrate": "^0.1.30" diff --git a/spiffworkflow-frontend/src/App.tsx b/spiffworkflow-frontend/src/App.tsx index 8031802c0..b7eaca800 100644 --- a/spiffworkflow-frontend/src/App.tsx +++ b/spiffworkflow-frontend/src/App.tsx @@ -6,6 +6,7 @@ import { defineAbility } from '@casl/ability'; import NavigationBar from './components/NavigationBar'; import HomePageRoutes from './routes/HomePageRoutes'; +import About from './routes/About'; import ErrorBoundary from './components/ErrorBoundary'; import AdminRoutes from './routes/AdminRoutes'; import ProcessRoutes from './routes/ProcessRoutes'; @@ -35,6 +36,7 @@ export default function App() { } /> + } /> } /> } /> } /> diff --git a/spiffworkflow-frontend/src/components/NavigationBar.tsx b/spiffworkflow-frontend/src/components/NavigationBar.tsx index b00bee848..54064dc17 100644 --- a/spiffworkflow-frontend/src/components/NavigationBar.tsx +++ b/spiffworkflow-frontend/src/components/NavigationBar.tsx @@ -30,6 +30,7 @@ import { PermissionsToCheck } from '../interfaces'; import { usePermissionFetcher } from '../hooks/PermissionService'; import { UnauthenticatedError } from '../services/HttpService'; import { SPIFF_ENVIRONMENT } from '../config'; +import appVersionInfo from '../helpers/appVersionInfo'; // for ref: https://react-bootstrap.github.io/components/navbar/ export default function NavigationBar() { @@ -57,6 +58,15 @@ export default function NavigationBar() { }; const { ability } = usePermissionFetcher(permissionRequestData); + // default to readthedocs and let someone specify an environment variable to override: + // + let documentationUrl = 'https://spiffworkflow.readthedocs.io'; + if ('DOCUMENTATION_URL' in window.spiffworkflowFrontendJsenv) { + documentationUrl = window.spiffworkflowFrontendJsenv.DOCUMENTATION_URL; + } + + const versionInfo = appVersionInfo(); + useEffect(() => { let newActiveKey = '/admin/process-groups'; if (location.pathname.match(/^\/admin\/messages\b/)) { @@ -81,6 +91,12 @@ export default function NavigationBar() { return activeKey === menuItemPath; }; + let aboutLinkElement = null; + + if (Object.keys(versionInfo).length) { + aboutLinkElement = About; + } + const profileToggletip = (
@@ -99,6 +115,11 @@ export default function NavigationBar() {

{UserService.getUserEmail()}


+ {aboutLinkElement} + + Documentation + +
@@ -406,10 +388,8 @@ export default function TaskShow() { uiSchema={formUiSchema} widgets={widgets} validator={validator} - onChange={updateFormData} customValidate={customValidate} omitExtraData - liveOmit > {reactFragmentToHideSubmitButton} From a84b77b2033b2d94cd4d7fd5d65f6abbbdb46a68 Mon Sep 17 00:00:00 2001 From: danfunk Date: Mon, 15 May 2023 16:53:27 -0400 Subject: [PATCH 15/48] I'm yanking the general exception catching because it made it impossible to debug a problem we ran into - and it results in roughtly the same error message presented to the end user. Updating SpiffWorkflow which will now handle the situation where a task does not have a parent task. --- .../src/spiffworkflow_backend/routes/tasks_controller.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 0c0b2c7a3..3af7a412b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -438,14 +438,6 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st ) yield render_data("error", api_error) 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() ready_engine_task_count = get_ready_engine_step_count(processor.bpmn_process_instance) tasks = get_reportable_tasks() From 93eb5131cdbfb582a51d8c1880df001b9c2cabc4 Mon Sep 17 00:00:00 2001 From: danfunk Date: Mon, 15 May 2023 16:55:52 -0400 Subject: [PATCH 16/48] update spiffworkflow --- spiffworkflow-backend/poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock index 0b20dffea..e7e0ba378 100644 --- a/spiffworkflow-backend/poetry.lock +++ b/spiffworkflow-backend/poetry.lock @@ -3089,7 +3089,7 @@ lxml = "*" type = "git" url = "https://github.com/sartography/SpiffWorkflow" reference = "main" -resolved_reference = "a68dec77ebb0960dd8097341df7575a34e435501" +resolved_reference = "340e9983b5afd2e6e71df883e74f7dc20d4474dd" [[package]] name = "sqlalchemy" From 8e1a29c62456da0a43e8320d39f726c80b904e07 Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 16 May 2023 10:13:29 -0400 Subject: [PATCH 17/48] update Close button on task show page to save and close and fixed task test --- spiffworkflow-frontend/cypress/e2e/tasks.cy.js | 2 +- spiffworkflow-frontend/src/routes/TaskShow.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spiffworkflow-frontend/cypress/e2e/tasks.cy.js b/spiffworkflow-frontend/cypress/e2e/tasks.cy.js index ac9f9a474..fde678738 100644 --- a/spiffworkflow-frontend/cypress/e2e/tasks.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/tasks.cy.js @@ -81,7 +81,7 @@ describe('tasks', () => { cy.navigateToHome(); // 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 cy.contains('Go').click(); diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 1eb30fac8..268f85552 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -364,7 +364,7 @@ export default function TaskShow() { handleFormSubmit(currentFormObject, null, FormSubmitType.Draft) } > - Close + Save and Close ); } From c029f4b6d5d85d84f69c6650f2445e1c84b46d2e Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 16 May 2023 10:49:25 -0400 Subject: [PATCH 18/48] added additional details to why we remove the root node from the spiff spec w/ burnettk --- .../src/spiffworkflow_backend/services/task_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py index 135838e30..017cd59ee 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py @@ -432,7 +432,8 @@ class TaskService: for task_id, task_properties in tasks.items(): # 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 - # 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": continue From cf09321432b441966770481eb7a4edebe06e8cd3 Mon Sep 17 00:00:00 2001 From: danfunk Date: Tue, 16 May 2023 13:19:22 -0400 Subject: [PATCH 19/48] Force Go and View buttons to be the same width. --- .../src/components/ProcessInstanceListTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index af70ff483..c1e62fcb6 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -1601,14 +1601,14 @@ export default function ProcessInstanceListTable({ ) { hasAccessToCompleteTask = true; } - + console.log("Has Access to complete task?", hasAccessToCompleteTask, regex, processInstance.potential_owner_usernames) let buttonText = 'View'; if (hasAccessToCompleteTask && processInstance.task_id) { buttonText = 'Go'; } buttonElement = ( - ); From 382e0916f16b00673f6c1b6a7fa71ebc41244881 Mon Sep 17 00:00:00 2001 From: danfunk Date: Tue, 16 May 2023 13:21:40 -0400 Subject: [PATCH 20/48] keep buttons from growing too large. --- .../src/components/ProcessInstanceListTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index c1e62fcb6..d2fa2e0a7 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -1608,7 +1608,7 @@ export default function ProcessInstanceListTable({ } buttonElement = ( - ); From cd99837378ac6ba3b7b87b00a7bac0976ce7bba7 Mon Sep 17 00:00:00 2001 From: danfunk Date: Tue, 16 May 2023 15:26:48 -0400 Subject: [PATCH 21/48] Requested simplification of the interstitial page. --- spiffworkflow-frontend/src/index.css | 6 ++++ .../src/routes/ProcessInterstitial.tsx | 36 +++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/spiffworkflow-frontend/src/index.css b/spiffworkflow-frontend/src/index.css index f0413204b..cfea25f48 100644 --- a/spiffworkflow-frontend/src/index.css +++ b/spiffworkflow-frontend/src/index.css @@ -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 { diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx index 368c3285d..49c2a9bd5 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx @@ -111,6 +111,16 @@ export default function ProcessInterstitial() { } }; + const getLoadingIcon = () => { + if(getStatus() === 'RUNNING') { + return ( + + ); + } else { + return null; + } + } + const getReturnHomeButton = (index: number) => { if ( index === 0 && @@ -123,6 +133,7 @@ export default function ProcessInterstitial() { kind="secondary" data-qa="return-to-home-button" onClick={() => navigate(`/tasks`)} + style={{marginBottom:30}} > Return to Home @@ -155,7 +166,6 @@ export default function ProcessInterstitial() { if (!myTask.can_complete && userTasks.includes(myTask.type)) { return ( <> -

Waiting on Someone Else

This next task is assigned to a different person or team. There is no action for you to take at this time. @@ -189,6 +199,8 @@ export default function ProcessInterstitial() { if (state === 'CLOSED' && lastTask === null) { navigate(`/tasks`); } + + if (lastTask) { return ( <> @@ -206,21 +218,10 @@ export default function ProcessInterstitial() { ], ]} /> -

- {getStatusImage()} -
-

- {lastTask.process_model_display_name}:{' '} - {lastTask.process_instance_id} -

-
Status: {capitalize(getStatus())}
-
-
-
-
+ {getLoadingIcon()} +
{data.map((d, index) => ( - - + <>
{getReturnHomeButton(index)} - {getHr(index)} - - + ))} +
); } From 02bfa701c5dc96093bc7543d0da3754c96689707 Mon Sep 17 00:00:00 2001 From: danfunk Date: Tue, 16 May 2023 15:29:43 -0400 Subject: [PATCH 22/48] run_pyl, and a little cleanup. --- .../spiffworkflow_backend/config/__init__.py | 12 +-- .../models/process_instance.py | 6 +- .../services/process_instance_processor.py | 12 +-- .../public/interstitial/completed.png | Bin 2997 -> 0 bytes .../public/interstitial/errored.png | Bin 2796 -> 0 bytes .../public/interstitial/locked.png | Bin 2612 -> 0 bytes .../public/interstitial/redirect.png | Bin 2586 -> 0 bytes .../public/interstitial/waiting.png | Bin 2518 -> 0 bytes .../components/ProcessInstanceListTable.tsx | 13 ++- .../src/routes/ProcessInterstitial.tsx | 82 +++++------------- 10 files changed, 47 insertions(+), 78 deletions(-) delete mode 100644 spiffworkflow-frontend/public/interstitial/completed.png delete mode 100644 spiffworkflow-frontend/public/interstitial/errored.png delete mode 100644 spiffworkflow-frontend/public/interstitial/locked.png delete mode 100644 spiffworkflow-frontend/public/interstitial/redirect.png delete mode 100644 spiffworkflow-frontend/public/interstitial/waiting.png diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index d61fa0850..a71157458 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -18,13 +18,13 @@ def setup_database_uri(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": - app.config["SQLALCHEMY_DATABASE_URI"] = ( - f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" - ) + app.config[ + "SQLALCHEMY_DATABASE_URI" + ] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres": - app.config["SQLALCHEMY_DATABASE_URI"] = ( - f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" - ) + app.config[ + "SQLALCHEMY_DATABASE_URI" + ] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" else: # use pswd to trick flake8 with hardcoded passwords db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index 1668565c9..078925e70 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -129,9 +129,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel): def serialized_with_metadata(self) -> dict[str, Any]: process_instance_attributes = self.serialized process_instance_attributes["process_metadata"] = self.process_metadata - process_instance_attributes["process_model_with_diagram_identifier"] = ( - self.process_model_with_diagram_identifier - ) + process_instance_attributes[ + "process_model_with_diagram_identifier" + ] = self.process_model_with_diagram_identifier return process_instance_attributes @property diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index b64cedfdf..2020c9d68 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -415,9 +415,9 @@ class ProcessInstanceProcessor: tld.process_instance_id = process_instance_model.id # we want this to be the fully qualified path to the process model including all group subcomponents - current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = ( - f"{process_instance_model.process_model_identifier}" - ) + current_app.config[ + "THREAD_LOCAL_DATA" + ].process_model_identifier = f"{process_instance_model.process_model_identifier}" self.process_instance_model = process_instance_model self.process_model_service = ProcessModelService() @@ -577,9 +577,9 @@ class ProcessInstanceProcessor: bpmn_subprocess_definition.bpmn_identifier ] = bpmn_process_definition_dict spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {} - bpmn_subprocess_definition_bpmn_identifiers[bpmn_subprocess_definition.id] = ( - bpmn_subprocess_definition.bpmn_identifier - ) + bpmn_subprocess_definition_bpmn_identifiers[ + bpmn_subprocess_definition.id + ] = bpmn_subprocess_definition.bpmn_identifier task_definitions = TaskDefinitionModel.query.filter( TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore diff --git a/spiffworkflow-frontend/public/interstitial/completed.png b/spiffworkflow-frontend/public/interstitial/completed.png deleted file mode 100644 index 5dafcebc14d2054e6df49bb0d6a0a555e2e0f8bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2997 zcmXArc|6m99LK+NY+=q^bJJEKHgau6v0;u1A=cs8R%Y1DW>z^yu8ihPjtU9C{Gyy8 zB;@GONlE4iNkS!2{OI?``}2N$UZ3~t{e1oTNp?AGFDb4f4gi3p10LtL*}XQ)dmDK3 zZ0emz-)v%ZymuG?=sW!l1&lDwDn>hrJZK7{TO6`&1M@?La1RxKRVfv#yFEd zzh7l@2)Z?7&0+@77yykGM)4~uL#%G<23tCg97beN=u8SN1PG!Lsam_~ek4*5S!?T} zt3@HwLXdO{kx4tl@C)9?%ktYaYHt~_46@&rOUs=SOa_cmMtcp6PzES7cOz2_$`oU4 z8X{2Lw>fP0e?%68NCpC!OnMjwiKNlVA;cgG-H*ZC%84YAnSPWYq>+V*nXxHyi)^q3 zHt^>d{0(gI|4RTQgRr^L>o1;0=P)Q|0+;|6i_&so`3F&mS~yy;A0U|e$qps zJNoC%_?w8x8`3XoYh!9(zld?aWl?USNFm`Wc9aY273yMQ!wVif?QwQhUVQB;LKW@I z0d=w?vt)5NSALOa=Mb)<9`PwI;ifc|5}08&c;LUcw^s1==EJl zd$)0>ZnaP@7P z{@}PmJjwIyba}I$2D-NQ`*yJ(?W}ew$a6SpJ3S}%=Lz5DnoB;!HEffM^!u2{mtLC% zR?LBJ9us^3YSF9%63(dO{V~w-4klXcCV~xKcqViS!wT0%&UT`dR^l>Gyt{kA7y3CS zDzQ~V5GR#ivFIv5K<)%OM)oROiK>Oqpuy+6g73+g2({7q|J46(CaKYYaLjk8khJWA zTT0VFuEhS_4Vd{VcO>ari?qenjh`Kb=Asq~xlj6!aR@VX} zC|lsqvtNY^qy%~-vN@rb2pu1Y7HL%aRdV463$WNRY9+CMYVtRdl&!SyL&{fM7ro*{L6m*T6;$^br6V@nz!uHGYa2>$P0y}qt~7|@b#?~BP14Jf*O4F&6VCp zp~an*#1&*UMy%tjc3x<7f(RoJbf`USr8~XHF<5rsGrU&xN)1)i8$Lb-b5a$ne3iE- zY6<2Jb2BG)%F8y4<+Irt2QO4=gxXe;mUUz|2oK{|Y;J5=E9%^tP?a(cPKqW_bGQ9g zUyC@bW#~KCxQ+AJfTRKLMp*9z@8{7zM2g?{x!o-?Cws>}ss0=v?l~s$okomsmD6vf zz?YQksPE%fPQ6%5C(}Vr{K9mfsc6GRIgF|@O6Kjw%4w?X1V7c~RwyKeER z#fT+gu<;-oT&U%WTfwtfddXTe4Q*r2H$nq)Iib$y~uauOi3< z|B=@j=qtalYpy%^p;ArMeYa>$+rp2?{4iYI?5TT?LfXen7}S;R9G?i7}mbdjU4lT8?4*&tnhvR*y}#4#Hqm4xTwe0 zB%QlH3iSyM&?p$goWL7Sd-bs^-j3XcJ#v019G@mQo1j$?-vTwkg_oT3KGc5IAyFUcyll0rabu-E%66bk zg~8N^)4|Sgi>ECr$6qXqks9_@WKUQ0eLf{(R9!#W>Ja+GiL)q3<(*i5YCo&B#{>5a zxE9VB@FlDu4z71UDQ17Ki`RP-j|F|>XM2KRaN_9%>qikk(v*4(*rKXN_DeS=fFDkJpbCH~N zoNS&@`#ecGe*JrnZ1Za+UDYXm<8AN z8NHc1-@sNx4RNzEN?DFlB@$;VjbvW-otE1zUI{pLy{_@=3-Q$J1C4s}^HW7K$BjeG z@1h*HOT$(SB#xRXM4C>9PA0{n_WNmhIe^p0+e|{Qa7QI}3;)g$e-?bpO3tViFOlgLIYhPj{;&D0 zezqo|=TVGm>hIfi;V6yaiRX!GR`M5MD~yP+o)4aRtbxl#KRIFeC0_q6?~oaI;^bj> zg578c{3^C1r{8k2(2IA!^{^_S!iAbCm7E+*4eByjt>Wt}|>TN&Dw?^t_F6tOopKG+TH1AqJDIi})-~e1z_lxQD@gjp$Us z>_yh3tH;%bS}s>mV~n$LuJl{!2d&@egb)mhWmjZdL%X6@-PIYqi;0gt;nPJqbfYFL zuD^Hn20qpPnvu8OF1}K%nf^}Qg0w6er<;#8la;e1(J<@ZG{W%m^E+QuDOyVQ3chPd zUcn0eu4vg&tb$H#xictKb%=ikym)#ZvFh5Ak6-B*D$Px6B)H<0ZU~EQGtgO890(xU zawOJizFB<}Lso0wjB)5d^w zf9WVQdCCI|Mnj*58J;yI?~;>?=<=0YQUE?{!zfO)t7i(3-R?#Z-O_(dv6`S(JJ*Y! z^wnQ-k#2t~h)Q?W-TQYb+S#aQ1Vm_liuC$NDLLw>3hcg!_q`zfZuy_Au_Yjc@FK_SRMI diff --git a/spiffworkflow-frontend/public/interstitial/errored.png b/spiffworkflow-frontend/public/interstitial/errored.png deleted file mode 100644 index a9aaba1e212e779d3b1103290a7ab59b30752560..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2796 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 diff --git a/spiffworkflow-frontend/public/interstitial/locked.png b/spiffworkflow-frontend/public/interstitial/locked.png deleted file mode 100644 index 5c7ab7580ec13aa4903da937a8a086227760343f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2612 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 Jsk0^}{U6skn@Rux diff --git a/spiffworkflow-frontend/public/interstitial/redirect.png b/spiffworkflow-frontend/public/interstitial/redirect.png deleted file mode 100644 index 2d81b3f5e371c17a66d2d5a8bec4b651477e691e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2586 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 lJ=PHkNtn|}ZR diff --git a/spiffworkflow-frontend/public/interstitial/waiting.png b/spiffworkflow-frontend/public/interstitial/waiting.png deleted file mode 100644 index 0701cdc996053c635f4b78e1d77522a493e87793..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2518 zcmX9=c|6qX8~u)D7-P#8V`5|^>&)1bHOr7|ZCpka#$YguG4^$cteGKuNGPtZ;-oHS9&T6(F_;(t020nl_Fnw%dr+ca z{uy0{HsUu3%?Td^06K051#+U!Nh69EG>#!wG~)0;~ib^9=2vHcIu(V{N28vuJp2j(q9|v3TU^4GceVIJoVRpN z-$qrCv|@-i66_zSXqzz6r6a~`R6A|2E)bn)qruS&&$Thq)(>*VB}8T>-*z=-lld*9wLQ+U&i}^ z#Tw=t8~3YH6Kxa!Nd=1B7MT-{0{o!qX#v~2)|j0)8U~PgYcaDPgltAy-PS%RMZ7nF zHO`cD?>BmeLfbG0JSUNo}&c1X+*{Hf33KGtu;aCTlhSP=8s!%(oXJ1sOL*7IEQ1 z=QfwN%$HzG6QZd{g#4i??>LKVc|re-Du_QY+Y#>k+VaQkMJO-mvp?AEu(= zG)+n26$|nbEX%60EWT2b3c$l!BW36&%Le}TnP+v6jDVml=V9gTfKmKfN1PR;zZ`;4%rz@pE@dFQeCI}cT{>Z8arBIlm$rQG+)Eh1mq z0f7M*yN{PkKlQWQpDHt_6`t1jk4G>Ar2xn(Q+`Z^_OXFs=cKDo&0&`{zj^HL6ayL54xK@hbn(@T(eh4;_~~9 zNsHJPcnMN*N&LBqy3YlzDBzKyM^}Fsbp%1mBPtF)wNr_+o62b!N-<!wx#gop*JEV zZfVUIHPWmM#R}eGe{q^b9$u=HuFr4QD6pDa!6L`L*gQ+R0F`j8lhk%$AQ$9rY{wRQ<6$!N$%vE@O^tcx}!mYAY%2`-;?G4VfozG(@ zdaC&8SKG{#RL?#vjyLUbwBEwrU|Kja;+|ykhtqj3dX4i`%Kivw(Dm)mS zMdWz_H5rC%@unwEaxWE-7hlNjF?ZRlNkjM^R|S>H|xl7 zn*_OPb6UWF7nvSmC|;YVT#7FE9lCm4P}#7|{Jy8$o6XLD8Z@q3x&aH!6GJeZ%jReB z&Z5V9O6zt5e&iy3$P#ZHh~AW24uhp%v)+UiruEk2zb%Eovr9yNxnE6_DS5c)olY^G zh_w^8Y&PiJ=7@Ky%vfy8V?yhaPk|wyQI~udp|7*jR@e>{10#5w^IOa5Qjw6BD_s6oz;&$|X~9jkO4^%b4>znezx{I< zC+P5r7q%GTC>x6RIis!BW(N-c-Jta9YQ&RN-q*R@wk_Y_Q$6BQo*81ZjfH}kE97gdexXTr~smu<4Vl%A+m#sa-DHrkZC+rbz` zvbX|$?iC<^ z{V(*bL+odE&PuGu;WcmTU0N?qKe2hPdB=k89DM<$c?@sH!j{w8O}jjUbwgdaHs-kq zpX9o1bq?}2i$QLy$w=<#)2)>)BNXsgqB?aXq@)%w9O4uWgRkLKa=9Gc%_~_kiO81m zduA;Ki)#XH@YM8jMSbP&<#SBq;wHNQ?49_Oi=f%YzPuLX_#p!Mi>}P($90B^l1PPs zkD7B@yCTHD2v^j3gfJ=r{gvAOSAyB$?@xI5xcFd;ZD7i6b=ST}jGUxi`kuo`!$kK%55sn)b|(&15veCSK7j;qRE$0xTkj=OeBDM`XC zVpil|IBlnKL#jr0E3tmZau5@bn}1F6=jOo6eO4e#`+pDJEwNxX%QVRi*5)Wf*4g<9 zqiA>Zmevtr)$1N-ef+<3q*qHzqtga&NmOO5xUD*;yw=(eUr+efJh0)IA1b8kx+kt? z9*1t<8 diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index d2fa2e0a7..5b9826f70 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -1601,14 +1601,23 @@ export default function ProcessInstanceListTable({ ) { hasAccessToCompleteTask = true; } - console.log("Has Access to complete task?", hasAccessToCompleteTask, regex, processInstance.potential_owner_usernames) + console.log( + 'Has Access to complete task?', + hasAccessToCompleteTask, + regex, + processInstance.potential_owner_usernames + ); let buttonText = 'View'; if (hasAccessToCompleteTask && processInstance.task_id) { buttonText = 'Go'; } buttonElement = ( - ); diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx index 49c2a9bd5..c906a0cd8 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx @@ -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,37 +89,18 @@ export default function ProcessInterstitial() { return state; }; - const getStatusImage = () => { - switch (getStatus()) { - case 'RUNNING': - return ( - - ); - case 'LOCKED': - return Locked; - case 'READY': - case 'REDIRECTING': - return Redirecting ....; - case 'WAITING': - return Waiting ....; - case 'COMPLETED': - return Completed; - case 'ERROR': - return Errored; - default: - return getStatus(); - } - }; - const getLoadingIcon = () => { - if(getStatus() === 'RUNNING') { - return ( - - ); - } else { - return null; + if (getStatus() === 'RUNNING') { + return ( + + ); } - } + return null; + }; const getReturnHomeButton = (index: number) => { if ( @@ -133,7 +114,7 @@ export default function ProcessInterstitial() { kind="secondary" data-qa="return-to-home-button" onClick={() => navigate(`/tasks`)} - style={{marginBottom:30}} + style={{ marginBottom: 30 }} > Return to Home @@ -143,34 +124,14 @@ export default function ProcessInterstitial() { return ''; }; - const getHr = (index: number) => { - if (index === 0) { - return ( -
-
-
- ); - } - 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 ( - <> -

- This next task is assigned to a different person or team. There is - no action for you to take at this time. -

- +

+ This next task is assigned to a different person or team. There is + no action for you to take at this time. +

); } if (shouldRedirect(myTask)) { @@ -200,7 +161,6 @@ export default function ProcessInterstitial() { navigate(`/tasks`); } - if (lastTask) { return ( <> @@ -219,9 +179,9 @@ export default function ProcessInterstitial() { ]} /> {getLoadingIcon()} -
- {data.map((d, index) => ( - <> +
+ {data.map((d, index) => ( + <>
{getReturnHomeButton(index)} - - ))} + + ))}
); From 3937d4a258db75a738729f80c731b32eded5dbc2 Mon Sep 17 00:00:00 2001 From: burnettk Date: Tue, 16 May 2023 15:35:01 -0400 Subject: [PATCH 23/48] debug --- .../src/spiffworkflow_backend/config/default.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py index c1498e862..0c4ceeb26 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py @@ -57,6 +57,8 @@ SPIFFWORKFLOW_BACKEND_OPEN_ID_SERVER_URL = environ.get( SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_ID = environ.get( "SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_ID", default="spiffworkflow-backend" ) +SQLALCHEMY_POOL_SIZE=20 +SQLALCHEMY_ENGINE_OPTIONS={"pool_pre_ping": True, "pool_size": 20} SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_SECRET_KEY = environ.get( "SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_SECRET_KEY", default="JXeQExm0JhQPLumgHtIIqf52bDalHz0q", From df2b22a6d9cc1d09a30a382aeae1488d7052837f Mon Sep 17 00:00:00 2001 From: burnettk Date: Tue, 16 May 2023 15:37:53 -0400 Subject: [PATCH 24/48] remove console --- .../src/components/ProcessInstanceListTable.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 5b9826f70..2b53d724a 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -1601,12 +1601,6 @@ export default function ProcessInstanceListTable({ ) { hasAccessToCompleteTask = true; } - console.log( - 'Has Access to complete task?', - hasAccessToCompleteTask, - regex, - processInstance.potential_owner_usernames - ); let buttonText = 'View'; if (hasAccessToCompleteTask && processInstance.task_id) { buttonText = 'Go'; From 77c6e8005507c06f41b2c588f5880ab85b1bc960 Mon Sep 17 00:00:00 2001 From: burnettk Date: Tue, 16 May 2023 16:20:40 -0400 Subject: [PATCH 25/48] let people use version-info, remove duplicate method, lint --- .../src/spiffworkflow_backend/config/__init__.py | 12 ++++++------ .../spiffworkflow_backend/models/process_instance.py | 6 +++--- .../routes/health_controller.py | 4 ---- .../services/authorization_service.py | 3 ++- .../services/process_instance_processor.py | 12 ++++++------ .../unit/test_authorization_service.py | 1 + 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index a71157458..d61fa0850 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -18,13 +18,13 @@ def setup_database_uri(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": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + ) elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + ) else: # use pswd to trick flake8 with hardcoded passwords db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index 078925e70..1668565c9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -129,9 +129,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel): def serialized_with_metadata(self) -> dict[str, Any]: process_instance_attributes = self.serialized process_instance_attributes["process_metadata"] = self.process_metadata - process_instance_attributes[ - "process_model_with_diagram_identifier" - ] = self.process_model_with_diagram_identifier + process_instance_attributes["process_model_with_diagram_identifier"] = ( + self.process_model_with_diagram_identifier + ) return process_instance_attributes @property diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/health_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/health_controller.py index 05408a4f8..2cb4092bd 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/health_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/health_controller.py @@ -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.") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 5a5bc44d4..761f14a2f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -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/*") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 2020c9d68..b64cedfdf 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -415,9 +415,9 @@ class ProcessInstanceProcessor: tld.process_instance_id = process_instance_model.id # we want this to be the fully qualified path to the process model including all group subcomponents - current_app.config[ - "THREAD_LOCAL_DATA" - ].process_model_identifier = f"{process_instance_model.process_model_identifier}" + current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = ( + f"{process_instance_model.process_model_identifier}" + ) self.process_instance_model = process_instance_model self.process_model_service = ProcessModelService() @@ -577,9 +577,9 @@ class ProcessInstanceProcessor: bpmn_subprocess_definition.bpmn_identifier ] = bpmn_process_definition_dict spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {} - bpmn_subprocess_definition_bpmn_identifiers[ - bpmn_subprocess_definition.id - ] = bpmn_subprocess_definition.bpmn_identifier + bpmn_subprocess_definition_bpmn_identifiers[bpmn_subprocess_definition.id] = ( + bpmn_subprocess_definition.bpmn_identifier + ) task_definitions = TaskDefinitionModel.query.filter( TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index f30e3bf40..b742c4639 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -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"), From 957af50f31e737ef2629fb2fcbddbf90066eac75 Mon Sep 17 00:00:00 2001 From: burnettk Date: Tue, 16 May 2023 19:24:55 -0400 Subject: [PATCH 26/48] default to 6 threads and set db pool the same, allowing overrides --- .../bin/boot_server_in_docker | 37 +++++++++++-------- .../spiffworkflow_backend/config/__init__.py | 24 ++++++++++-- .../spiffworkflow_backend/config/default.py | 6 ++- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/spiffworkflow-backend/bin/boot_server_in_docker b/spiffworkflow-backend/bin/boot_server_in_docker index 865c7de6c..a26adc739 100755 --- a/spiffworkflow-backend/bin/boot_server_in_docker +++ b/spiffworkflow-backend/bin/boot_server_in_docker @@ -40,8 +40,9 @@ 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 @@ -67,21 +68,25 @@ fi git init "${SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR}" git config --global --add safe.directory "${SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR}" -# 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 -num_cores_multiple_for_threads=2 +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 -# 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) + # 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 -threads_per_worker=$((threads_to_use_per_core * num_cores_multiple_for_threads)) + 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" @@ -91,7 +96,7 @@ exec poetry run gunicorn ${additional_args} \ --preload \ --worker-class "gthread" \ --workers="$worker_count" \ - --threads "$threads_per_worker" \ + --threads "$SPIFFWORKFLOW_BACKEND_THREADS_PER_WORKER" \ --limit-request-line 8192 \ --timeout "$GUNICORN_TIMEOUT_SECONDS" \ --capture-output \ diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index d61fa0850..55c958977 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -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"] == "": diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py index 0c4ceeb26..88a34faf8 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py @@ -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" ) @@ -57,8 +61,6 @@ SPIFFWORKFLOW_BACKEND_OPEN_ID_SERVER_URL = environ.get( SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_ID = environ.get( "SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_ID", default="spiffworkflow-backend" ) -SQLALCHEMY_POOL_SIZE=20 -SQLALCHEMY_ENGINE_OPTIONS={"pool_pre_ping": True, "pool_size": 20} SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_SECRET_KEY = environ.get( "SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_SECRET_KEY", default="JXeQExm0JhQPLumgHtIIqf52bDalHz0q", From 0db5980f09c68eced317b717d998d91d9dfcedcd Mon Sep 17 00:00:00 2001 From: danfunk Date: Wed, 17 May 2023 11:58:17 -0400 Subject: [PATCH 27/48] Force all pages back to the top, rather than just a few of them. --- spiffworkflow-frontend/src/App.tsx | 3 +++ .../src/components/ErrorDisplay.tsx | 1 - .../src/components/ScrollToTop.tsx | 12 ++++++++++++ spiffworkflow-frontend/src/routes/TaskShow.tsx | 1 - 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 spiffworkflow-frontend/src/components/ScrollToTop.tsx diff --git a/spiffworkflow-frontend/src/App.tsx b/spiffworkflow-frontend/src/App.tsx index b7eaca800..17afca14d 100644 --- a/spiffworkflow-frontend/src/App.tsx +++ b/spiffworkflow-frontend/src/App.tsx @@ -15,6 +15,8 @@ import { AbilityContext } from './contexts/Can'; import UserService from './services/UserService'; import ErrorDisplay from './components/ErrorDisplay'; import APIErrorProvider from './contexts/APIErrorContext'; +import ScrollToTop from "./components/ScrollToTop"; +import React from "react"; export default function App() { if (!UserService.isLoggedIn()) { @@ -32,6 +34,7 @@ export default function App() { + diff --git a/spiffworkflow-frontend/src/components/ErrorDisplay.tsx b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx index 401169733..67e6e18dc 100644 --- a/spiffworkflow-frontend/src/components/ErrorDisplay.tsx +++ b/spiffworkflow-frontend/src/components/ErrorDisplay.tsx @@ -109,7 +109,6 @@ export default function ErrorDisplay() { if (errorObject) { const title = 'Error:'; - window.scrollTo(0, 0); // Scroll back to the top of the page errorTag = ( removeError()} type="error"> diff --git a/spiffworkflow-frontend/src/components/ScrollToTop.tsx b/spiffworkflow-frontend/src/components/ScrollToTop.tsx new file mode 100644 index 000000000..01d0b5806 --- /dev/null +++ b/spiffworkflow-frontend/src/components/ScrollToTop.tsx @@ -0,0 +1,12 @@ +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; + +export default function ScrollToTop() { + const { pathname } = useLocation(); + + useEffect(() => { + window.scrollTo(0, 0); + }, [pathname]); + + return null; +} \ No newline at end of file diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index a74aa829b..aa507d036 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -112,7 +112,6 @@ export default function TaskShow() { if (!result.can_complete) { navigateToInterstitial(result); } - window.scrollTo(0, 0); // Scroll back to the top of the page /* Disable call to load previous tasks -- do not display menu. const url = `/v1.0/process-instances/for-me/${modifyProcessIdentifierForPathParam( From 505e6dc8b5657768ede365aab1909aeddde75db0 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 17 May 2023 12:14:44 -0400 Subject: [PATCH 28/48] do not run validations on save and close on a form w/ burnettk --- .../spiffworkflow_backend/config/__init__.py | 8 +++-- .../src/routes/TaskShow.tsx | 35 ++++++++++++++++--- .../src/services/HttpService.ts | 1 + 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index 55c958977..224791108 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -40,7 +40,8 @@ def setup_database_configs(app: Flask) -> None: 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 + # 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") @@ -50,8 +51,9 @@ def setup_database_configs(app: Flask) -> None: # 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 + 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.""" diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index a74aa829b..5a422e5fa 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -94,6 +94,9 @@ export default function TaskShow() { const params = useParams(); const navigate = useNavigate(); const [disabled, setDisabled] = useState(false); + const [noValidate, setNoValidate] = useState(false); + + const [taskData, setTaskData] = useState(null); const { addError, removeError } = useAPIError(); @@ -108,6 +111,7 @@ export default function TaskShow() { useEffect(() => { const processResult = (result: Task) => { setTask(result); + setTaskData(result.data); setDisabled(false); if (!result.can_complete) { navigateToInterstitial(result); @@ -160,22 +164,30 @@ export default function TaskShow() { } }; - const handleFormSubmit = (formObject: any, event: any) => { + const handleFormSubmit = (formObject: any, _event: any) => { if (disabled) { return; } + const dataToSubmit = formObject?.formData; if (!dataToSubmit) { navigate(`/tasks`); return; } let queryParams = ''; - if (event && event.submitter.id === 'close-button') { + + // if validations are turned off then save as draft + if (noValidate) { queryParams = '?save_as_draft=true'; } setDisabled(true); removeError(); delete dataToSubmit.isManualTask; + + // NOTE: rjsf sets blanks values to undefined and JSON.stringify removes keys with undefined values + // so there is no way to clear out a field that previously had a value. + // To resolve this, we could potentially go through the object that we are posting (either in here or in + // HttpService) and translate all undefined values to null. HttpService.makeCallToBackend({ path: `/tasks/${params.process_instance_id}/${params.task_id}${queryParams}`, successCallback: processSubmitResult, @@ -290,17 +302,27 @@ export default function TaskShow() { return errors; }; + // This turns off validations and then dispatches the click event after + // waiting a second to give the state time to update. + // This is to allow saving the form without validations causing issues. + const handleSaveAndCloseButton = () => { + setNoValidate(true); + setTimeout(() => { + (document.getElementById('our-very-own-form') as any).dispatchEvent( + new Event('submit', { cancelable: true, bubbles: true }) + ); + }, 1000); + }; + const formElement = () => { if (!task) { return null; } let formUiSchema; - let taskData = task.data; let jsonSchema = task.form_schema; let reactFragmentToHideSubmitButton = null; if (task.typename === 'ManualTask') { - taskData = {}; jsonSchema = { type: 'object', required: [], @@ -341,7 +363,7 @@ export default function TaskShow() { closeButton = (