Use vite to build (#1390)

* some base updates for vite w/ burnettk

* i can log in w/ burnettk

* a couple more fixes w/ burnettk

* make sure selectedTabIndex has been set before using it w/ burnettk

* fixed active-users db issue, added type module to package json to fix prerender issues, and various other issues w/ burnettk

* fixed issues with building and running from compiled w/ burnettk

* pyl

* eslint fix is running and removed both inferno and navigationBar warnings

* vim likes the Dockerfile suffix by default

* use process.env.HOME

* probably do not need alias

* a little clean up and fixed font warnings w/ burnettk

* updated elements to remove warnings in the console w/ burnettk

* fixed es lint issues w/ burnettk

* update docker build in frontend w/ burnettk

* set the specific tag of nginx w/ burnettk

* build docker imgaes for this branch to test w/ burnettk

* added vitest and updated Dockerfile to be nginx w/ burnettk

* tests are passing w/ burnettk

* add prefresh and more keys

* added cypress-vite to attempt to get cypress working again

* some coderabbit suggestions

* hopefully there is no reason to use PUBLIC_URL at all when using vite

* use the correct location of the index file in the docker image

* spaces are fine in index.html file variable declaration

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
Co-authored-by: burnettk <burnettk@users.noreply.github.com>
This commit is contained in:
jasquat 2024-04-15 18:22:34 +00:00 committed by GitHub
parent fd11e627f2
commit 9147a8db8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 5366 additions and 803 deletions

View File

@ -32,6 +32,7 @@ on:
- main - main
- spiffdemo - spiffdemo
- GSA-TTS-fix-path-routing-in-generated-openid-urls - GSA-TTS-fix-path-routing-in-generated-openid-urls
- use-vite-to-build
jobs: jobs:
create_frontend_docker_image: create_frontend_docker_image:

View File

@ -2,6 +2,7 @@ import json
import time import time
import flask.wrappers import flask.wrappers
import sqlalchemy
from flask import g from flask import g
from flask import jsonify from flask import jsonify
from flask import make_response from flask import make_response
@ -13,17 +14,30 @@ from spiffworkflow_backend.models.user import UserModel
def active_user_updates(last_visited_identifier: str) -> Response: def active_user_updates(last_visited_identifier: str) -> Response:
active_user = ActiveUserModel.query.filter_by(user_id=g.user.id, last_visited_identifier=last_visited_identifier).first() current_time = round(time.time())
query_args = {"user_id": g.user.id, "last_visited_identifier": last_visited_identifier}
active_user = ActiveUserModel.query.filter_by(**query_args).first()
if active_user is None: if active_user is None:
active_user = ActiveUserModel( active_user = ActiveUserModel(
user_id=g.user.id, last_visited_identifier=last_visited_identifier, last_seen_in_seconds=round(time.time()) user_id=g.user.id, last_visited_identifier=last_visited_identifier, last_seen_in_seconds=current_time
) )
db.session.add(active_user) db.session.add(active_user)
db.session.commit() try:
db.session.commit()
active_user.last_seen_in_seconds = round(time.time()) except sqlalchemy.exc.IntegrityError:
db.session.add(active_user) # duplicate entry. two processes are trying to create the same entry at the same time. it is fine to drop one request.
db.session.commit() db.session.rollback()
else:
try:
db.session.query(ActiveUserModel).filter_by(**query_args).update({"last_seen_in_seconds": current_time})
db.session.commit()
except sqlalchemy.exc.OperationalError as exception:
if "Deadlock" in str(exception):
# two processes are trying to update the same entry at the same time. it is fine to drop one request.
db.session.rollback()
else:
raise
cutoff_time_in_seconds = time.time() - 30 cutoff_time_in_seconds = time.time() - 30
active_users = ( active_users = (

View File

@ -13,6 +13,7 @@
# production # production
/build /build
/dist
# misc # misc
.DS_Store .DS_Store

View File

@ -9,12 +9,12 @@ WORKDIR /app
# procps for debugging # procps for debugging
# vim ftw # vim ftw
RUN apt-get update \ RUN apt-get update \
&& apt-get clean -y \ && apt-get clean -y \
&& apt-get install -y -q \ && apt-get install -y -q \
curl \ curl \
procps \ procps \
vim-tiny \ vim-tiny \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# this matches total memory on spiffworkflow-demo # this matches total memory on spiffworkflow-demo
ENV NODE_OPTIONS=--max_old_space_size=2048 ENV NODE_OPTIONS=--max_old_space_size=2048
@ -44,19 +44,17 @@ RUN ./bin/build
######################## - FINAL ######################## - FINAL
# Final image without setup dependencies. # Use nginx as the base image
FROM base AS final FROM nginx:1.25.4-bookworm
LABEL description="Frontend component of SpiffWorkflow, a software development platform for building, running, and monitoring executable diagrams" # Remove default nginx configuration
RUN rm -rf /etc/nginx/conf.d/*
# WARNING: On localhost frontend assumes backend is one port lower. # Copy the nginx configuration file
ENV PORT0=7001 COPY docker_build/nginx.conf.template /var/tmp
COPY --from=setup /app/build /app/build # Copy the built static files into the nginx directory
COPY --from=setup /app/dist /usr/share/nginx/html
COPY --from=setup /app/bin /app/bin COPY --from=setup /app/bin /app/bin
COPY --from=setup /app/node_modules.justserve /app/node_modules
RUN chown -R node:node /app
USER node
CMD ["/app/bin/boot_server_in_docker"] CMD ["/app/bin/boot_server_in_docker"]

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
function error_handler() { function error_handler() {
>&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}." echo >&2 "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}."
exit "$2" exit "$2"
} }
trap 'error_handler ${LINENO} $?' ERR trap 'error_handler ${LINENO} $?' ERR
@ -10,19 +10,19 @@ set -o errtrace -o errexit -o nounset -o pipefail
# sort of like https://lithic.tech/blog/2020-05/react-dynamic-config, but without golang # sort of like https://lithic.tech/blog/2020-05/react-dynamic-config, but without golang
react_configs=$(env | grep -E "^SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_" || echo '') react_configs=$(env | grep -E "^SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_" || echo '')
if [[ -n "$react_configs" ]]; then if [[ -n "$react_configs" ]]; then
index_html_file="./build/index.html" index_html_file="/usr/share/nginx/html/index.html"
if [[ ! -f "$index_html_file" ]]; then if [[ ! -f "$index_html_file" ]]; then
>&2 echo "ERROR: Could not find '${index_html_file}'. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it." echo >&2 "ERROR: Could not find '${index_html_file}'. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it."
exit 1 exit 1
fi fi
if ! command -v sed >/dev/null ; then if ! command -v sed >/dev/null; then
>&2 echo "ERROR: sed command not found. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it." echo >&2 "ERROR: sed command not found. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it."
exit 1 exit 1
fi fi
if ! command -v perl >/dev/null ; then if ! command -v perl >/dev/null; then
>&2 echo "ERROR: perl command not found. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it." echo >&2 "ERROR: perl command not found. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it."
exit 1 exit 1
fi fi
@ -30,19 +30,25 @@ if [[ -n "$react_configs" ]]; then
react_config_without_prefix=$(sed -E 's/^SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_([^=]*)=(.*)/\1=\\"\2\\"/' <<<"${react_config}") react_config_without_prefix=$(sed -E 's/^SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_([^=]*)=(.*)/\1=\\"\2\\"/' <<<"${react_config}")
if [[ -z "$react_config_without_prefix" ]]; then if [[ -z "$react_config_without_prefix" ]]; then
>&2 echo "ERROR: Could not parse react config line: '${react_config}'." echo >&2 "ERROR: Could not parse react config line: '${react_config}'."
exit 1 exit 1
fi fi
escaped_react_config=$(sed -E 's|/|\\/|g' <<<"${react_config_without_prefix}") escaped_react_config=$(sed -E 's|/|\\/|g' <<<"${react_config_without_prefix}")
# actually do the search and replace to add the js config to the html page # actually do the search and replace to add the js config to the html page
perl -pi -e "s/(window.spiffworkflowFrontendJsenv=\{\})/\1;window.spiffworkflowFrontendJsenv.${escaped_react_config}/" "$index_html_file" perl -pi -e "s/(window.spiffworkflowFrontendJsenv *= *\{\})/\1;window.spiffworkflowFrontendJsenv.${escaped_react_config}/" "$index_html_file"
if ! grep -Eq "${react_config_without_prefix}" "$index_html_file"; then if ! grep -Eq "${react_config_without_prefix}" "$index_html_file"; then
>&2 echo "ERROR: Could not find '${react_config_without_prefix}' in '${index_html_file}' after search and replace. It is likely that the assumptions in boot_server_in_docker about the contents of the html page have changed. Fix the glitch in boot_server_in_docker." echo >&2 "ERROR: Could not find '${react_config_without_prefix}' in '${index_html_file}' after search and replace. It is likely that the assumptions in boot_server_in_docker about the contents of the html page have changed. Fix the glitch in boot_server_in_docker."
exit 1 exit 1
fi fi
done done
fi fi
exec ./node_modules/.bin/serve -s build -l "$PORT0" port_to_use="${PORT0:-80}"
if [[ -n "${SPIFFWORKFLOW_FRONTEND_INTERNAL_PORT:-}" ]]; then
port_to_use="$SPIFFWORKFLOW_FRONTEND_INTERNAL_PORT"
fi
perl -p -e "s/{{SPIFFWORKFLOW_FRONTEND_INTERNAL_PORT}}/${port_to_use}/" /var/tmp/nginx.conf.template >/etc/nginx/conf.d/default.conf
exec nginx -g "daemon off;"

View File

@ -9,7 +9,7 @@ set -o errtrace -o errexit -o nounset -o pipefail
if [[ -f "version_info.json" ]]; then if [[ -f "version_info.json" ]]; then
version_info=$(cat version_info.json) version_info=$(cat version_info.json)
export REACT_APP_VERSION_INFO="$version_info" export VITE_VERSION_INFO="$version_info"
fi fi
npm run build npm run build

BIN
spiffworkflow-frontend/bun.lockb Executable file

Binary file not shown.

View File

@ -1,63 +0,0 @@
module.exports = {
module: {
rules: [
{
test: /\.m?[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
[
'@babel/plugin-transform-react-jsx',
{
pragma: 'h',
pragmaFrag: 'Fragment',
},
],
'@babel/preset-react',
'@babel/plugin-transform-typescript',
{
importSource: '@bpmn-io/properties-panel/preact',
runtime: 'automatic',
},
'@babel/plugin-proposal-class-properties',
{ loose: true },
'@babel/plugin-proposal-private-methods',
{ loose: true },
'@babel/plugin-proposal-private-property-in-object',
{ loose: true },
],
},
},
},
],
},
webpack: {
configure: {
resolve: {
alias: {
inferno:
process.env.NODE_ENV !== 'production'
? 'inferno/dist/index.dev.esm.js'
: 'inferno/dist/index.esm.js',
react: 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react-dom': 'preact/compat', // Must be below test-utils
'react/jsx-runtime': 'preact/jsx-runtime',
},
},
},
},
babel: {
presets: [
'@babel/preset-env',
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
// plugins: [],
loaderOptions: (babelLoaderOptions) => {
return babelLoaderOptions;
},
},
};

View File

@ -1,6 +1,7 @@
/* eslint-disable */ /* eslint-disable */
const { defineConfig } = require('cypress'); import { defineConfig } from 'cypress';
const { rm } = require('fs/promises'); import { rm } from 'fs/promises';
import config from '@cypress/grep/src/plugin';
// yes use video compression in CI, where we will set the env var so we upload to cypress dashboard // yes use video compression in CI, where we will set the env var so we upload to cypress dashboard
const useVideoCompression = !!process.env.CYPRESS_RECORD_KEY; const useVideoCompression = !!process.env.CYPRESS_RECORD_KEY;
@ -40,7 +41,6 @@ const cypressConfig = {
baseUrl: spiffWorkflowFrontendUrl, baseUrl: spiffWorkflowFrontendUrl,
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
deleteVideosOnSuccess(on); deleteVideosOnSuccess(on);
require('@cypress/grep/src/plugin')(config);
return config; return config;
}, },
}, },
@ -56,4 +56,4 @@ if (!process.env.CYPRESS_RECORD_KEY) {
cypressConfig.videoCompression = false; cypressConfig.videoCompression = false;
} }
module.exports = defineConfig(cypressConfig); export default defineConfig(cypressConfig);

View File

@ -0,0 +1,9 @@
server {
listen {{SPIFFWORKFLOW_FRONTEND_INTERNAL_PORT}};
server_name localhost;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}

View File

@ -2,38 +2,29 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta name="version-info" content='%REACT_APP_VERSION_INFO%' /> <meta name="version-info" content='%VITE_VERSION_INFO%' />
<meta <meta
name="description" name="description"
content="A turnkey solution for building and executing the workflows that drive your business" content="A turnkey solution for building and executing the workflows that drive your business"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="/logo192.png" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>SpiffWorkflow</title> <title>SpiffWorkflow</title>
<script> <script>
window.spiffworkflowFrontendJsenv = {}; window.spiffworkflowFrontendJsenv = {};
</script> </script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
<!-- <!--
This HTML file is a template. This HTML file is a template.
If you open it directly in the browser, you will see an empty page. If you open it directly in the browser, you will see an empty page.

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
"name": "spiffworkflow-frontend", "name": "spiffworkflow-frontend",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module",
"dependencies": { "dependencies": {
"@babel/core": "^7.24.3", "@babel/core": "^7.24.3",
"@babel/plugin-transform-react-jsx": "^7.18.6", "@babel/plugin-transform-react-jsx": "^7.18.6",
@ -15,6 +16,7 @@
"@microsoft/fetch-event-source": "^2.0.1", "@microsoft/fetch-event-source": "^2.0.1",
"@monaco-editor/react": "^4.4.5", "@monaco-editor/react": "^4.4.5",
"@mui/material": "^5.10.14", "@mui/material": "^5.10.14",
"@prefresh/vite": "^2.4.5",
"@react-icons/all-files": "^4.1.0", "@react-icons/all-files": "^4.1.0",
"@rjsf/core": "5.0.0-beta.20", "@rjsf/core": "5.0.0-beta.20",
"@rjsf/mui": "5.0.0-beta.20", "@rjsf/mui": "5.0.0-beta.20",
@ -26,15 +28,15 @@
"@testing-library/react": "^14.2.1", "@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^20.12.6",
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@uiw/react-md-editor": "^3.20.2", "@uiw/react-md-editor": "^3.20.2",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"axios": "^0.27.2", "axios": "^0.27.2",
"bpmn-js": "^13.2.2", "bpmn-js": "^13.2.2",
"bpmn-js-properties-panel": "^1.22.0", "bpmn-js-properties-panel": "^1.22.0",
"bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#main", "bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#vite-support",
"cookie": "^0.6.0", "cookie": "^0.6.0",
"craco": "^0.0.3", "craco": "^0.0.3",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
@ -55,11 +57,12 @@
"react-jsonschema-form": "^1.8.1", "react-jsonschema-form": "^1.8.1",
"react-router": "^6.22.2", "react-router": "^6.22.2",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-scripts": "^5.0.1",
"serve": "^14.0.0", "serve": "^14.0.0",
"timepicker": "^1.13.18", "timepicker": "^1.13.18",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.0",
"vite": "^5.2.8",
"vite-tsconfig-paths": "^4.3.2",
"web-vitals": "^3.5.2" "web-vitals": "^3.5.2"
}, },
"overrides": { "overrides": {
@ -68,12 +71,10 @@
} }
}, },
"scripts": { "scripts": {
"start": "ESLINT_NO_DEV_ERRORS=true PORT=7001 craco start", "start": "VITE_VERSION_INFO='{\"version\":\"local\"}' vite",
"docker:start": "ESLINT_NO_DEV_ERRORS=true craco start", "build": "vite build",
"build": "craco build", "serve": "vite preview",
"test": "react-scripts test --coverage", "test": "vitest run --coverage",
"t": "npm test -- --watchAll=false",
"eject": "craco eject",
"format": "prettier --write src/**/*.[tj]s{,x}", "format": "prettier --write src/**/*.[tj]s{,x}",
"lint": "./node_modules/.bin/eslint src", "lint": "./node_modules/.bin/eslint src",
"lint:fix": "./node_modules/.bin/eslint --fix src" "lint:fix": "./node_modules/.bin/eslint --fix src"
@ -98,15 +99,19 @@
}, },
"devDependencies": { "devDependencies": {
"@cypress/grep": "^3.1.0", "@cypress/grep": "^3.1.0",
"@preact/preset-vite": "^2.8.2",
"@tanstack/eslint-plugin-query": "^5.28.6", "@tanstack/eslint-plugin-query": "^5.28.6",
"@types/carbon__colors": "^10.31.3", "@types/carbon__colors": "^10.31.3",
"@types/cookie": "^0.6.0", "@types/cookie": "^0.6.0",
"@types/lodash.merge": "^4.6.7", "@types/lodash.merge": "^4.6.7",
"@types/node": "^20.12.7",
"@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/parser": "^5.62.0",
"@vitest/coverage-v8": "^1.5.0",
"cypress": "^13", "cypress": "^13",
"cypress-file-upload": "^5.0.8", "cypress-file-upload": "^5.0.8",
"cypress-slow-down": "^1.3.1", "cypress-slow-down": "^1.3.1",
"cypress-vite": "^1.5.0",
"eslint": "^8.19.0", "eslint": "^8.19.0",
"eslint_d": "^12.2.0", "eslint_d": "^12.2.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
@ -119,8 +124,11 @@
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-sonarjs": "^0.15.0", "eslint-plugin-sonarjs": "^0.15.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
"inherits-browser": "^0.0.1",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"safe-regex": "^2.1.1", "safe-regex": "^2.1.1",
"ts-migrate": "^0.1.30" "tiny-svg": "^2.2.3",
"ts-migrate": "^0.1.30",
"vitest": "^1.5.0"
} }
} }

View File

@ -66,6 +66,7 @@ export default function ContainerForExtensions() {
extensionUiSchemaFile.file_contents extensionUiSchemaFile.file_contents
); );
if ( if (
extensionUiSchema &&
extensionUiSchema.ux_elements && extensionUiSchema.ux_elements &&
!extensionUiSchema.disabled !extensionUiSchema.disabled
) { ) {

View File

@ -10,7 +10,7 @@ type OwnProps = {
title?: string; title?: string;
confirmButtonLabel?: string; confirmButtonLabel?: string;
kind?: string; kind?: string;
renderIcon?: boolean; renderIcon?: Element;
iconDescription?: string | null; iconDescription?: string | null;
hasIconOnly?: boolean; hasIconOnly?: boolean;
classNameForModal?: string; classNameForModal?: string;
@ -24,7 +24,7 @@ export default function ButtonWithConfirmation({
title = 'Are you sure?', title = 'Are you sure?',
confirmButtonLabel = 'OK', confirmButtonLabel = 'OK',
kind = 'danger', kind = 'danger',
renderIcon = false, renderIcon,
iconDescription = null, iconDescription = null,
hasIconOnly = false, hasIconOnly = false,
classNameForModal, classNameForModal,

View File

@ -4,14 +4,12 @@ type OwnProps = {
displayLocation: string; displayLocation: string;
elementCallback: Function; elementCallback: Function;
extensionUxElements?: UiSchemaUxElement[] | null; extensionUxElements?: UiSchemaUxElement[] | null;
elementCallbackIfNotFound?: Function;
}; };
export function ExtensionUxElementMap({ export function ExtensionUxElementMap({
displayLocation, displayLocation,
elementCallback, elementCallback,
extensionUxElements, extensionUxElements,
elementCallbackIfNotFound,
}: OwnProps) { }: OwnProps) {
if (!extensionUxElements) { if (!extensionUxElements) {
return null; return null;
@ -23,15 +21,11 @@ export function ExtensionUxElementMap({
return uxElement.display_location === displayLocation; return uxElement.display_location === displayLocation;
} }
); );
const elementMap = elementsForDisplayLocation.map( return elementsForDisplayLocation.map(
(uxElement: UiSchemaUxElement, index: number) => { (uxElement: UiSchemaUxElement, index: number) => {
return elementCallback(uxElement, index); return elementCallback(uxElement, index);
} }
); );
if (elementMap.length === 0 && elementCallbackIfNotFound) {
return elementCallbackIfNotFound();
}
return elementMap;
}; };
return mainElement(); return mainElement();
} }

View File

@ -41,7 +41,7 @@ export default function Filters({
elements.push( elements.push(
<Button <Button
onClick={copyReportLink} onClick={copyReportLink}
kind="" kind="secondary"
renderIcon={LinkIcon} renderIcon={LinkIcon}
iconDescription="Copy shareable link" iconDescription="Copy shareable link"
hasIconOnly hasIconOnly

View File

@ -101,7 +101,11 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
const extensionUserProfileElement = (uxElement: UiSchemaUxElement) => { const extensionUserProfileElement = (uxElement: UiSchemaUxElement) => {
const navItemPage = `/extensions${uxElement.page}`; const navItemPage = `/extensions${uxElement.page}`;
return <Link to={navItemPage}>{uxElement.label}</Link>; return (
<Link key={navItemPage} to={navItemPage}>
{uxElement.label}
</Link>
);
}; };
const profileToggletip = () => { const profileToggletip = () => {
@ -163,6 +167,7 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
<HeaderGlobalAction <HeaderGlobalAction
title={`The current SpiffWorkflow environment is: ${SPIFF_ENVIRONMENT}`} title={`The current SpiffWorkflow environment is: ${SPIFF_ENVIRONMENT}`}
className="spiff-environment-header-text unclickable-text" className="spiff-environment-header-text unclickable-text"
aria-label="our-aria-label"
> >
{SPIFF_ENVIRONMENT} {SPIFF_ENVIRONMENT}
</HeaderGlobalAction> </HeaderGlobalAction>
@ -195,10 +200,10 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
return ( return (
<SpiffTooltip title="Manage Secrets and Authentication information for Service Tasks"> <SpiffTooltip title="Manage Secrets and Authentication information for Service Tasks">
<HeaderMenuItem <HeaderMenuItem
element={Link} as={Link}
to="/configuration" to="/configuration"
onClick={closeSideNavMenuIfExpanded} onClick={closeSideNavMenuIfExpanded}
isCurrentPage={isActivePage('/configuration')} isActive={isActivePage('/configuration')}
> >
Configuration Configuration
</HeaderMenuItem> </HeaderMenuItem>
@ -221,11 +226,11 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
setActiveKey(navItemPage); setActiveKey(navItemPage);
} }
return ( return (
<SpiffTooltip title={uxElement?.tooltip}> <SpiffTooltip key={navItemPage} title={uxElement?.tooltip}>
<HeaderMenuItem <HeaderMenuItem
element={Link} as={Link}
to={navItemPage} to={navItemPage}
isCurrentPage={isActivePage(navItemPage)} isActive={isActivePage(navItemPage)}
data-qa={`extension-${slugifyString( data-qa={`extension-${slugifyString(
uxElement.label || uxElement.page uxElement.label || uxElement.page
)}`} )}`}
@ -244,10 +249,10 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
<> <>
<SpiffTooltip title="View and start Process Instances"> <SpiffTooltip title="View and start Process Instances">
<HeaderMenuItem<LinkProps> <HeaderMenuItem<LinkProps>
element={Link} as={Link}
to="/" to="/"
onClick={closeSideNavMenuIfExpanded} onClick={closeSideNavMenuIfExpanded}
isCurrentPage={isActivePage('/')} isActive={isActivePage('/')}
> >
<div>Home</div> <div>Home</div>
</HeaderMenuItem> </HeaderMenuItem>
@ -256,10 +261,10 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
<Can I="GET" a={targetUris.processGroupListPath} ability={ability}> <Can I="GET" a={targetUris.processGroupListPath} ability={ability}>
<SpiffTooltip title="Find and organize Process Groups and Process Models"> <SpiffTooltip title="Find and organize Process Groups and Process Models">
<HeaderMenuItem <HeaderMenuItem
element={Link} as={Link}
to={processGroupPath} to={processGroupPath}
onClick={closeSideNavMenuIfExpanded} onClick={closeSideNavMenuIfExpanded}
isCurrentPage={isActivePage(processGroupPath)} isActive={isActivePage(processGroupPath)}
data-qa="header-nav-processes" data-qa="header-nav-processes"
> >
Processes Processes
@ -273,10 +278,10 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
> >
<SpiffTooltip title="List of active and completed Process Instances"> <SpiffTooltip title="List of active and completed Process Instances">
<HeaderMenuItem <HeaderMenuItem
element={Link} as={Link}
to="/process-instances" to="/process-instances"
onClick={closeSideNavMenuIfExpanded} onClick={closeSideNavMenuIfExpanded}
isCurrentPage={isActivePage('/process-instances')} isActive={isActivePage('/process-instances')}
> >
Process Instances Process Instances
</HeaderMenuItem> </HeaderMenuItem>
@ -285,10 +290,10 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
<Can I="GET" a={targetUris.messageInstanceListPath} ability={ability}> <Can I="GET" a={targetUris.messageInstanceListPath} ability={ability}>
<SpiffTooltip title="Browse messages being sent and received"> <SpiffTooltip title="Browse messages being sent and received">
<HeaderMenuItem <HeaderMenuItem
element={Link} as={Link}
to="/messages" to="/messages"
onClick={closeSideNavMenuIfExpanded} onClick={closeSideNavMenuIfExpanded}
isCurrentPage={isActivePage('/messages')} isActive={isActivePage('/messages')}
> >
Messages Messages
</HeaderMenuItem> </HeaderMenuItem>
@ -297,10 +302,10 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
<Can I="GET" a={targetUris.dataStoreListPath} ability={ability}> <Can I="GET" a={targetUris.dataStoreListPath} ability={ability}>
<SpiffTooltip title="Browse data that has been saved to Data Stores"> <SpiffTooltip title="Browse data that has been saved to Data Stores">
<HeaderMenuItem <HeaderMenuItem
element={Link} as={Link}
to="/data-stores" to="/data-stores"
onClick={closeSideNavMenuIfExpanded} onClick={closeSideNavMenuIfExpanded}
isCurrentPage={isActivePage('/data-stores')} isActive={isActivePage('/data-stores')}
> >
Data Stores Data Stores
</HeaderMenuItem> </HeaderMenuItem>
@ -322,12 +327,7 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
<HeaderContainer <HeaderContainer
render={() => ( render={() => (
<Header aria-label="IBM Platform Name" className="cds--g100"> <Header aria-label="IBM Platform Name" className="cds--g100">
<HeaderName <HeaderName as={Link} to="/" prefix="" data-qa="spiffworkflow-logo">
element={Link}
to="/"
prefix=""
data-qa="spiffworkflow-logo"
>
<img src={logo} className="app-logo" alt="logo" /> <img src={logo} className="app-logo" alt="logo" />
</HeaderName> </HeaderName>
</Header> </Header>
@ -360,7 +360,7 @@ export default function NavigationBar({ extensionUxElements }: OwnProps) {
isActive={isSideNavExpanded} isActive={isSideNavExpanded}
/> />
<HeaderName <HeaderName
element={Link} as={Link}
to="/" to="/"
onClick={closeSideNavMenuIfExpanded} onClick={closeSideNavMenuIfExpanded}
prefix="" prefix=""

View File

@ -77,7 +77,7 @@ export function Notification({
className="cds--inline-notification__close-button" className="cds--inline-notification__close-button"
hasIconOnly hasIconOnly
size="sm" size="sm"
kind="" kind="ghost"
onClick={onClose} onClick={onClose}
/> />
)} )}
@ -86,7 +86,7 @@ export function Notification({
data-qa="close-publish-notification" data-qa="close-publish-notification"
className="cds--inline-notification__close-button" className="cds--inline-notification__close-button"
size="sm" size="sm"
kind="" kind="ghost"
onClick={() => setShowMessage(!showMessage)} onClick={() => setShowMessage(!showMessage)}
> >
{showMessage ? 'Hide' : 'Details'}&nbsp; {showMessage ? 'Hide' : 'Details'}&nbsp;

View File

@ -65,6 +65,7 @@ export default function ProcessGroupListTiles({
return ( return (
<ClickableTile <ClickableTile
id={`process-group-tile-${row.id}`} id={`process-group-tile-${row.id}`}
key={`process-group-tile-${row.id}`}
className="tile-process-group" className="tile-process-group"
onClick={() => onClick={() =>
navigateToProcessGroup( navigateToProcessGroup(

View File

@ -99,12 +99,13 @@ export default function ProcessInstanceListSaveAsReport({
onRequestSubmit={addProcessInstanceReport} onRequestSubmit={addProcessInstanceReport}
onRequestClose={handleSaveFormClose} onRequestClose={handleSaveFormClose}
hasScrollingContent hasScrollingContent
aria-label="save perspective"
> >
<p className="data-table-description">{descriptionText}</p> <p className="data-table-description">{descriptionText}</p>
{textInputComponent} {textInputComponent}
</Modal> </Modal>
<Button <Button
kind="" kind="tertiary"
className={buttonClassName} className={buttonClassName}
onClick={() => { onClick={() => {
setIdentifier(processInstanceReportSelection?.identifier || ''); setIdentifier(processInstanceReportSelection?.identifier || '');

View File

@ -386,7 +386,7 @@ export default function ProcessInstanceListTable({
return ( return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<td <td
key={processInstance.id} key={`td-${columnAccessor}-${processInstance.id}`}
onClick={() => navigateToProcessInstance(processInstance)} onClick={() => navigateToProcessInstance(processInstance)}
onKeyDown={() => navigateToProcessInstance(processInstance)} onKeyDown={() => navigateToProcessInstance(processInstance)}
data-qa={`process-instance-show-link-${columnAccessor}`} data-qa={`process-instance-show-link-${columnAccessor}`}
@ -512,7 +512,7 @@ export default function ProcessInstanceListTable({
if (hasAccessToCompleteTask && processInstance.task_id) { if (hasAccessToCompleteTask && processInstance.task_id) {
goButtonElement = ( goButtonElement = (
<Button <Button
kind="secondary" kind="primary"
href={taskShowUrl} href={taskShowUrl}
style={{ width: '60px' }} style={{ width: '60px' }}
size="sm" size="sm"

View File

@ -796,7 +796,7 @@ export default function ProcessInstanceListTableWithFilters({
return ( return (
<ProcessInstanceListSaveAsReport <ProcessInstanceListSaveAsReport
onSuccess={onSaveReportSuccess} onSuccess={onSaveReportSuccess}
buttonClassName="button-white-background narrow-button" buttonClassName="narrow-button"
buttonText="Save" buttonText="Save"
processInstanceReportSelection={processInstanceReportSelection} processInstanceReportSelection={processInstanceReportSelection}
reportMetadata={reportMetadata} reportMetadata={reportMetadata}
@ -1040,6 +1040,7 @@ export default function ProcessInstanceListTableWithFilters({
formElements.push( formElements.push(
<Dropdown <Dropdown
titleText="Display type" titleText="Display type"
label="Display type"
id="report-column-display-type" id="report-column-display-type"
items={[''].concat(Object.values(filterDisplayTypes))} items={[''].concat(Object.values(filterDisplayTypes))}
selectedItem={ selectedItem={
@ -1064,6 +1065,7 @@ export default function ProcessInstanceListTableWithFilters({
formElements.push( formElements.push(
<Dropdown <Dropdown
titleText="Operator" titleText="Operator"
label="Operator"
id="report-column-condition-operator" id="report-column-condition-operator"
items={Object.keys(filterOperatorMappings)} items={Object.keys(filterOperatorMappings)}
selectedItem={operator || null} selectedItem={operator || null}
@ -1110,6 +1112,7 @@ export default function ProcessInstanceListTableWithFilters({
onRequestSubmit={handleUpdateReportColumn} onRequestSubmit={handleUpdateReportColumn}
onRequestClose={handleColumnFormClose} onRequestClose={handleColumnFormClose}
hasScrollingContent hasScrollingContent
aria-label={modalHeading}
> >
{formElements} {formElements}
</Modal> </Modal>
@ -1202,6 +1205,7 @@ export default function ProcessInstanceListTableWithFilters({
<Dropdown <Dropdown
id="system-report-dropdown" id="system-report-dropdown"
titleText="System report" titleText="System report"
label="System report"
items={['', ...systemReportOptions]} items={['', ...systemReportOptions]}
itemToString={(item: any) => titleizeString(item)} itemToString={(item: any) => titleizeString(item)}
selectedItem={systemReport} selectedItem={systemReport}
@ -1221,6 +1225,7 @@ export default function ProcessInstanceListTableWithFilters({
<Dropdown <Dropdown
id="user-group-dropdown" id="user-group-dropdown"
titleText="Assigned user group" titleText="Assigned user group"
label="Assigned user group"
items={['', ...userGroups]} items={['', ...userGroups]}
itemToString={(item: any) => item} itemToString={(item: any) => item}
selectedItem={selectedUserGroup} selectedItem={selectedUserGroup}
@ -1282,6 +1287,7 @@ export default function ProcessInstanceListTableWithFilters({
onRequestSubmit={handleAdvancedOptionsClose} onRequestSubmit={handleAdvancedOptionsClose}
onRequestClose={handleAdvancedOptionsClose} onRequestClose={handleAdvancedOptionsClose}
hasScrollingContent hasScrollingContent
aria-label="advanced filter options"
size="lg" size="lg"
> >
{formElements} {formElements}
@ -1457,8 +1463,8 @@ export default function ProcessInstanceListTableWithFilters({
<Column sm={4} md={4} lg={8}> <Column sm={4} md={4} lg={8}>
<ButtonSet> <ButtonSet>
<Button <Button
kind="" kind="tertiary"
className="button-white-background narrow-button" className="narrow-button"
onClick={clearFilters} onClick={clearFilters}
> >
Clear Clear

View File

@ -499,16 +499,16 @@ export default function ProcessInstanceLogList({
<Column sm={4} md={4} lg={8}> <Column sm={4} md={4} lg={8}>
<ButtonSet> <ButtonSet>
<Button <Button
kind="" kind="tertiary"
className="button-white-background narrow-button" className="narrow-button"
onClick={resetFiltersAndRun} onClick={resetFiltersAndRun}
> >
Reset Reset
</Button> </Button>
{shouldDisplayClearButton && ( {shouldDisplayClearButton && (
<Button <Button
kind="" kind="tertiary"
className="button-white-background narrow-button" className="narrow-button"
onClick={clearFilters} onClick={clearFilters}
> >
Clear Clear

View File

@ -333,8 +333,7 @@ export default function ProcessModelForm({
<Button <Button
data-qa="add-notification-address-button" data-qa="add-notification-address-button"
renderIcon={AddAlt} renderIcon={AddAlt}
className="button-white-background" kind="tertiary"
kind=""
size="sm" size="sm"
onClick={() => { onClick={() => {
addBlankNotificationAddress(); addBlankNotificationAddress();
@ -365,8 +364,7 @@ export default function ProcessModelForm({
<Button <Button
data-qa="add-metadata-extraction-path-button" data-qa="add-metadata-extraction-path-button"
renderIcon={AddAlt} renderIcon={AddAlt}
className="button-white-background" kind="tertiary"
kind=""
size="sm" size="sm"
onClick={() => { onClick={() => {
addBlankMetadataExtractionPath(); addBlankMetadataExtractionPath();
@ -383,7 +381,7 @@ export default function ProcessModelForm({
const formButtons = () => { const formButtons = () => {
return ( return (
<Button kind="secondary" type="submit"> <Button kind="primary" type="submit">
Submit Submit
</Button> </Button>
); );

View File

@ -155,18 +155,13 @@ export default function ReactDiagramEditor({
if (diagramType === 'dmn') { if (diagramType === 'dmn') {
modeler = (diagramModelerState as any).getActiveViewer(); modeler = (diagramModelerState as any).getActiveViewer();
} }
try { if (modeler) {
if (amount === 0) { if (amount === 0) {
const canvas = modeler.get('canvas'); const canvas = modeler.get('canvas');
canvas.zoom(FitViewport, 'auto'); canvas.zoom(FitViewport, 'auto');
} else { } else {
modeler.get('zoomScroll').stepZoom(amount); modeler.get('zoomScroll').stepZoom(amount);
} }
} catch (e) {
console.error(
'zoom failed, certain modes in DMN do not support zooming.',
e
);
} }
} }
}, },
@ -595,7 +590,7 @@ export default function ReactDiagramEditor({
if (diagramType === 'dmn') { if (diagramType === 'dmn') {
newDiagramFileName = 'new_dmn_diagram.dmn'; newDiagramFileName = 'new_dmn_diagram.dmn';
} }
fetchDiagramFromURL(`${process.env.PUBLIC_URL}/${newDiagramFileName}`); fetchDiagramFromURL(`/${newDiagramFileName}`);
return undefined; return undefined;
} }

View File

@ -8,53 +8,77 @@ import {
Button, Button,
} from '@carbon/react'; } from '@carbon/react';
import { JsonSchemaExample } from '../../interfaces'; import { JsonSchemaExample } from '../../interfaces';
import textSchema from '../../resources/json_schema_examples/text-schema.json';
import textUiSchema from '../../resources/json_schema_examples/text-uischema.json';
import textareaSchema from '../../resources/json_schema_examples/textarea-schema.json';
import textareaUiSchema from '../../resources/json_schema_examples/textarea-uischema.json';
import dateSchema from '../../resources/json_schema_examples/date-schema.json';
import dateUiSchema from '../../resources/json_schema_examples/date-uischema.json';
import choiceSchema from '../../resources/json_schema_examples/multiple-choice-schema.json';
import choiceUiSchema from '../../resources/json_schema_examples/multiple-choice-uischema.json';
import passwordSchema from '../../resources/json_schema_examples/password-schema.json';
import passwordUiSchema from '../../resources/json_schema_examples/password-uischema.json';
import typeaheadSchema from '../../resources/json_schema_examples/typeahead-schema.json';
import typeaheadUiSchema from '../../resources/json_schema_examples/typeahead-uischema.json';
import checkboxSchema from '../../resources/json_schema_examples/checkbox-schema.json';
import dropdownSchema from '../../resources/json_schema_examples/dropdown-schema.json';
import dropdownData from '../../resources/json_schema_examples/dropdown-exampledata.json';
import nestedSchema from '../../resources/json_schema_examples/nested-schema.json';
const examples: JsonSchemaExample[] = []; const examples: JsonSchemaExample[] = [];
examples.push({
schema: require('../../resources/json_schema_examples/text-schema.json'), // eslint-disable-line global-require examples.push(
ui: require('../../resources/json_schema_examples/text-uischema.json'), // eslint-disable-line global-require {
data: {}, schema: textSchema,
}); ui: textUiSchema,
examples.push({ data: {},
schema: require('../../resources/json_schema_examples/textarea-schema.json'), // eslint-disable-line global-require },
ui: require('../../resources/json_schema_examples/textarea-uischema.json'), // eslint-disable-line global-require {
data: {}, schema: textareaSchema,
}); ui: textareaUiSchema,
examples.push({ data: {},
schema: require('../../resources/json_schema_examples/checkbox-schema.json'), // eslint-disable-line global-require },
ui: {}, {
data: {}, schema: checkboxSchema,
}); ui: {},
examples.push({ data: {},
schema: require('../../resources/json_schema_examples/date-schema.json'), // eslint-disable-line global-require },
ui: require('../../resources/json_schema_examples/date-uischema.json'), // eslint-disable-line global-require {
data: {}, schema: dateSchema,
}); ui: dateUiSchema,
examples.push({ data: {},
schema: require('../../resources/json_schema_examples/dropdown-schema.json'), // eslint-disable-line global-require },
ui: {}, {
data: require('../../resources/json_schema_examples/dropdown-exampledata.json'), // eslint-disable-line global-require schema: dropdownSchema,
}); ui: {},
examples.push({ data: dropdownData,
schema: require('../../resources/json_schema_examples/multiple-choice-schema.json'), // eslint-disable-line global-require },
ui: require('../../resources/json_schema_examples/multiple-choice-uischema.json'), // eslint-disable-line global-require {
data: {}, schema: choiceSchema,
}); ui: choiceUiSchema,
examples.push({ data: {},
schema: require('../../resources/json_schema_examples/password-schema.json'), // eslint-disable-line global-require },
ui: require('../../resources/json_schema_examples/password-uischema.json'), // eslint-disable-line global-require {
data: {}, schema: passwordSchema,
}); ui: passwordUiSchema,
examples.push({ data: {},
schema: require('../../resources/json_schema_examples/nested-schema.json'), // eslint-disable-line global-require },
ui: {}, {
data: {}, schema: nestedSchema,
}); ui: {},
examples.push({ data: {},
schema: require('../../resources/json_schema_examples/typeahead-schema.json'), // eslint-disable-line global-require },
ui: require('../../resources/json_schema_examples/typeahead-uischema.json'), // eslint-disable-line global-require {
data: {}, schema: typeaheadSchema,
}); ui: typeaheadUiSchema,
data: {},
}
);
type OwnProps = { type OwnProps = {
onSelect: Function; onSelect: Function;
@ -72,11 +96,7 @@ export default function ExamplesTable({ onSelect }: OwnProps) {
<td>{example.schema.title}</td> <td>{example.schema.title}</td>
<td>{example.schema.description}</td> <td>{example.schema.description}</td>
<td> <td>
<Button <Button kind="primary" size="sm" onClick={() => selectExample(index)}>
kind="secondary"
size="sm"
onClick={() => selectExample(index)}
>
Load Load
</Button> </Button>
</td> </td>

View File

@ -2,7 +2,6 @@ import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { Tabs, TabList, Tab } from '@carbon/react'; import { Tabs, TabList, Tab } from '@carbon/react';
import { SpiffTab } from '../interfaces'; import { SpiffTab } from '../interfaces';
import SpiffTooltip from './SpiffTooltip';
type OwnProps = { type OwnProps = {
tabs: SpiffTab[]; tabs: SpiffTab[];
@ -10,7 +9,7 @@ type OwnProps = {
export default function SpiffTabs({ tabs }: OwnProps) { export default function SpiffTabs({ tabs }: OwnProps) {
const location = useLocation(); const location = useLocation();
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0); const [selectedTabIndex, setSelectedTabIndex] = useState<number | null>(null);
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
@ -25,20 +24,19 @@ export default function SpiffTabs({ tabs }: OwnProps) {
const tabComponents = tabs.map((spiffTab: SpiffTab) => { const tabComponents = tabs.map((spiffTab: SpiffTab) => {
return ( return (
<SpiffTooltip title={spiffTab?.tooltip}> <Tab onClick={() => navigate(spiffTab.path)}>{spiffTab.display_name}</Tab>
<Tab onClick={() => navigate(spiffTab.path)}>
{spiffTab.display_name}
</Tab>
</SpiffTooltip>
); );
}); });
return ( if (selectedTabIndex !== null && tabComponents.length > selectedTabIndex) {
<> return (
<Tabs selectedIndex={selectedTabIndex}> <>
<TabList aria-label="List of tabs">{tabComponents}</TabList> <Tabs selectedIndex={selectedTabIndex}>
</Tabs> <TabList aria-label="List of tabs">{tabComponents}</TabList>
<br /> </Tabs>
</> <br />
); </>
);
}
return null;
} }

View File

@ -1,3 +1,5 @@
declare const window: Window & typeof globalThis;
const { port, hostname } = window.location; const { port, hostname } = window.location;
let protocol = 'https'; let protocol = 'https';
@ -56,23 +58,16 @@ if (!backendBaseUrl) {
} }
backendBaseUrl = `${protocol}://${hostAndPortAndPathPrefix}/v1.0`; backendBaseUrl = `${protocol}://${hostAndPortAndPathPrefix}/v1.0`;
// this can only ever work locally since this is a static site.
// use spiffworkflowFrontendJsenv if you want to inject env vars
// that can be read by the static site.
if (process.env.REACT_APP_BACKEND_BASE_URL) {
backendBaseUrl = process.env.REACT_APP_BACKEND_BASE_URL;
}
} }
if (!backendBaseUrl.endsWith('/v1.0')) { if (!backendBaseUrl.endsWith('/v1.0')) {
backendBaseUrl += '/v1.0'; backendBaseUrl += '/v1.0';
} }
export const BACKEND_BASE_URL = backendBaseUrl; const BACKEND_BASE_URL = backendBaseUrl;
export const DOCUMENTATION_URL = documentationUrl; const DOCUMENTATION_URL = documentationUrl;
export const PROCESS_STATUSES = [ const PROCESS_STATUSES = [
'complete', 'complete',
'error', 'error',
'not_started', 'not_started',
@ -122,11 +117,23 @@ const carbonDateFormat = generalDateFormat
.replace(/\bMMM\b/, 'M') .replace(/\bMMM\b/, 'M')
.replace(/\bMMMM\b/, 'F') .replace(/\bMMMM\b/, 'F')
.replace(/\bdd\b/, 'd'); .replace(/\bdd\b/, 'd');
export const DATE_TIME_FORMAT = `${generalDateFormat} HH:mm:ss`; const DATE_TIME_FORMAT = `${generalDateFormat} HH:mm:ss`;
export const TIME_FORMAT_HOURS_MINUTES = 'HH:mm'; const TIME_FORMAT_HOURS_MINUTES = 'HH:mm';
export const DATE_FORMAT = generalDateFormat; const DATE_FORMAT = generalDateFormat;
export const DATE_FORMAT_CARBON = carbonDateFormat; const DATE_FORMAT_CARBON = carbonDateFormat;
export const DATE_FORMAT_FOR_DISPLAY = generalDateFormat.toLowerCase(); const DATE_FORMAT_FOR_DISPLAY = generalDateFormat.toLowerCase();
export const DATE_RANGE_DELIMITER = ':::'; const DATE_RANGE_DELIMITER = ':::';
export const SPIFF_ENVIRONMENT = spiffEnvironment; const SPIFF_ENVIRONMENT = spiffEnvironment;
export {
DATE_TIME_FORMAT,
TIME_FORMAT_HOURS_MINUTES,
DATE_FORMAT,
DATE_FORMAT_CARBON,
DATE_FORMAT_FOR_DISPLAY,
DATE_RANGE_DELIMITER,
BACKEND_BASE_URL,
DOCUMENTATION_URL,
PROCESS_STATUSES,
SPIFF_ENVIRONMENT,
};

View File

@ -10,7 +10,7 @@ const appVersionInfo = () => {
versionInfoFromHtmlMetaTag.getAttribute('content'); versionInfoFromHtmlMetaTag.getAttribute('content');
if ( if (
versionInfoContentString && versionInfoContentString &&
versionInfoContentString !== '%REACT_APP_VERSION_INFO%' versionInfoContentString !== '%VITE_VERSION_INFO%'
) { ) {
versionInfo = JSON.parse(versionInfoContentString); versionInfo = JSON.parse(versionInfoContentString);
} }

View File

@ -144,17 +144,69 @@ h3 {
* so they match our normal scheme better * so they match our normal scheme better
*/ */
.cds--btn--tertiary:focus { .cds--btn--tertiary:focus {
color: white; color: #161616;
border-color: #161616;
background-color: #f4f4f4;
}
.cds--btn--tertiary:hover {
color: #161616;
border-color: #161616;
background-color: #f4f4f4;
}
.cds--btn--tertiary:visited:hover {
color: #161616;
border-color: #161616;
background-color: #f4f4f4;
}
.cds--btn--secondary {
color: #161616;
border-color: #efefef;
background-color: #efefef;
}
.cds--btn--secondary:visited {
color: #161616;
border-color: #efefef;
background-color: #efefef;
}
.cds--btn--secondary:focus {
color: #161616;
border-color: #efefef;
background-color: #efefef;
}
.cds--btn--secondary:hover {
color: #161616;
border-color: #dddddd;
background-color: #dddddd;
}
.cds--btn--secondary:visited:hover {
color: #161616;
border-color: #dddddd;
background-color: #dddddd;
}
.cds--modal-footer .cds--btn--secondary {
color: #ffffff;
border-color: #393939; border-color: #393939;
background-color: #393939; background-color: #393939;
} }
.cds--btn--tertiary:hover { .cds--modal-footer .cds--btn--secondary:visited {
color: white; color: #ffffff;
border-color: #393939;
background-color: #393939;
}
.cds--modal-footer .cds--btn--secondary:focus {
color: #ffffff;
border-color: #393939;
background-color: #393939;
}
.cds--modal-footer .cds--btn--secondary:hover {
color: #ffffff;
border-color: #474747; border-color: #474747;
background-color: #474747; background-color: #474747;
} }
.cds--btn--tertiary:visited:hover { .cds--modal-footer .cds--btn--secondary:visited:hover {
color: white; color: #ffffff;
border-color: #474747; border-color: #474747;
background-color: #474747; background-color: #474747;
} }

View File

@ -8,7 +8,9 @@
// ) // )
// ); // );
@use '@carbon/react'; @use '@carbon/react' with (
$font-path: '@ibm/plex'
);
@use '@carbon/styles'; @use '@carbon/styles';
// @include grid.flex-grid(); // @include grid.flex-grid();

View File

@ -146,6 +146,7 @@ export default function BaseInputTemplate<
<TextInput <TextInput
id={id} id={id}
className="text-input" className="text-input"
labelText=""
helperText={commonAttributes.helperText} helperText={commonAttributes.helperText}
invalid={commonAttributes.invalid} invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField} invalidText={commonAttributes.errorMessageForField}

View File

@ -1,5 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
// @ts-ignore
import { Table } from '@carbon/react'; import { Table } from '@carbon/react';
import { AuthenticationItem } from '../interfaces'; import { AuthenticationItem } from '../interfaces';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
@ -92,10 +91,10 @@ export default function AuthenticationList() {
return ( return (
<> <>
{buildTable()} {buildTable()}
{AuthenticationConfiguration()} <AuthenticationConfiguration />
</> </>
); );
} }
return <main />; return null;
} }

View File

@ -27,6 +27,7 @@ export default function BaseRoutes({ extensionUxElements }: OwnProps) {
return ( return (
<Route <Route
path={uxElement.page} path={uxElement.page}
key={uxElement.page}
element={<Extension pageIdentifier={uxElement.page} />} element={<Extension pageIdentifier={uxElement.page} />}
/> />
); );

View File

@ -74,7 +74,7 @@ export default function ProcessGroupList() {
<ProcessBreadcrumb hotCrumbs={[['Process Groups']]} /> <ProcessBreadcrumb hotCrumbs={[['Process Groups']]} />
<Stack orientation="horizontal" gap={3}> <Stack orientation="horizontal" gap={3}>
<Can I="POST" a={targetUris.processGroupListPath} ability={ability}> <Can I="POST" a={targetUris.processGroupListPath} ability={ability}>
<Button kind="secondary" href="/process-groups/new"> <Button kind="primary" href="/process-groups/new">
Add a process group Add a process group
</Button> </Button>
</Can> </Can>

View File

@ -915,7 +915,7 @@ export default function ProcessModelEditDiagram() {
)} )}
<Button <Button
className="m-top-10" className="m-top-10"
kind="secondary" kind="primary"
onClick={() => handleProcessScriptAssist()} onClick={() => handleProcessScriptAssist()}
disabled={scriptAssistLoading} disabled={scriptAssistLoading}
> >

View File

@ -64,7 +64,7 @@ export default function ProcessModelNewExperimental() {
setProcessModelDescriptiveText(event.target.value) setProcessModelDescriptiveText(event.target.value)
} }
/> />
<Button kind="secondary" type="submit"> <Button kind="primary" type="submit">
Submit Submit
</Button> </Button>
</Form> </Form>

View File

@ -339,7 +339,7 @@ export default function ProcessModelShow() {
let actionsTableCell = null; let actionsTableCell = null;
if (processModelFile.name.match(/\.(dmn|bpmn|json|md)$/)) { if (processModelFile.name.match(/\.(dmn|bpmn|json|md)$/)) {
actionsTableCell = ( actionsTableCell = (
<TableCell key={`${processModelFile.name}-cell`} align="right"> <TableCell key={`${processModelFile.name}-action`} align="right">
{renderButtonElements(processModelFile, isPrimaryBpmnFile)} {renderButtonElements(processModelFile, isPrimaryBpmnFile)}
</TableCell> </TableCell>
); );

View File

@ -71,11 +71,7 @@ export default function SecretNew() {
}} }}
/> />
<ButtonSet> <ButtonSet>
<Button <Button kind="tertiary" onClick={navigateToSecrets}>
kind=""
className="button-white-background"
onClick={navigateToSecrets}
>
Cancel Cancel
</Button> </Button>
<Button kind="primary" type="submit"> <Button kind="primary" type="submit">

View File

@ -159,6 +159,7 @@ export default function SecretShow() {
<TextInput <TextInput
id="secret_value" id="secret_value"
name="secret_value" name="secret_value"
labelText="Secret value"
value={secret.value} value={secret.value}
onChange={handleSecretValueChange} onChange={handleSecretValueChange}
disabled={ disabled={

View File

@ -360,7 +360,7 @@ export default function TaskShow() {
id="close-button" id="close-button"
onClick={handleCloseButton} onClick={handleCloseButton}
disabled={formButtonsDisabled} disabled={formButtonsDisabled}
kind="secondary" kind="primary"
title="Save data as draft and close the form." title="Save data as draft and close the form."
> >
Save and close Save and close

View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1 @@
import '@testing-library/jest-dom';

View File

@ -3,11 +3,18 @@
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"module": "commonjs",
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"target": "es2021",
"resolveJsonModule": true, "resolveJsonModule": true,
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["vite/client", "node", "vitest/globals"],
"module": "esnext",
"target": "ESNext",
"moduleResolution": "Node",
"isolatedModules": true
}, },
"include": ["src/**/*"] "include": ["src", "test/vitest.setup.ts"]
} }

View File

@ -0,0 +1,42 @@
import preact from '@preact/preset-vite';
import prefresh from '@prefresh/vite';
import { defineConfig } from 'vite';
// import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
// depending on your application, base can also be "/"
base: '/',
plugins: [
// react(),
// seems to replace preact. hot module replacement doesn't work, so commented out. also causes errors when navigating with TabList:
// Cannot read properties of undefined (reading 'disabled')
// prefresh(),
// we need preact for bpmn-js-spiffworkflow. see https://forum.bpmn.io/t/custom-prop-for-service-tasks-typeerror-cannot-add-property-object-is-not-extensible/8487
preact(),
viteTsconfigPaths(),
],
// for prefresh, from https://github.com/preactjs/prefresh/issues/454#issuecomment-1456491801, not working
// optimizeDeps: {
// include: ['preact/hooks', 'preact/compat', 'preact']
// },
server: {
// this ensures that the browser DOES NOT open upon server start
open: false,
port: 7001,
},
preview: {
port: 7001,
},
resolve: {
alias: {
inferno:
process.env.NODE_ENV !== 'production'
? 'inferno/dist/index.dev.esm.js'
: 'inferno/dist/index.esm.js',
},
preserveSymlinks: true,
},
});

View File

@ -0,0 +1,11 @@
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
test: {
include: ['./src/**/*.test.ts', './src/**/*.test.tsx'],
setupFiles: ['test/vitest.setup.ts'],
globals: true,
environment: 'jsdom',
},
});