Merge pull request #22 from sartography/feature/carbon_ui

Feature/carbon UI
This commit is contained in:
Kevin Burnett 2022-11-04 22:33:38 +00:00 committed by GitHub
commit 2db70303c2
47 changed files with 1539 additions and 762 deletions

View File

@ -153,7 +153,6 @@ paths:
description: The number of groups to show per page. Defaults to page 10.
schema:
type: integer
# process_groups_list
get:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_groups_list
summary: get list
@ -168,7 +167,6 @@ paths:
type: array
items:
$ref: "#/components/schemas/ProcessModelCategory"
# process_group_add
post:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_group_add
summary: Add process group
@ -429,7 +427,7 @@ paths:
description: For filtering - beginning of start window - in seconds since epoch
schema:
type: integer
- name: start_till
- name: start_to
in: query
required: false
description: For filtering - end of start window - in seconds since epoch
@ -441,7 +439,7 @@ paths:
description: For filtering - beginning of end window - in seconds since epoch
schema:
type: integer
- name: end_till
- name: end_to
in: query
required: false
description: For filtering - end of end window - in seconds since epoch

View File

@ -1,6 +1,7 @@
"""Process_group."""
from __future__ import annotations
import dataclasses
from dataclasses import dataclass
from dataclasses import field
from typing import Any
@ -20,6 +21,7 @@ class ProcessGroup:
id: str # A unique string name, lower case, under scores (ie, 'my_group')
display_name: str
description: str | None = None
display_order: int | None = 0
admin: bool | None = False
process_models: list[ProcessModelInfo] = field(
@ -38,6 +40,12 @@ class ProcessGroup:
return True
return False
@property
def serialized(self) -> dict:
"""Serialized."""
original_dict = dataclasses.asdict(self)
return {x: original_dict[x] for x in original_dict if x not in ["sort_index"]}
class ProcessGroupSchema(Schema):
"""ProcessGroupSchema."""

View File

@ -30,7 +30,6 @@ class ProcessModelInfo:
display_name: str
description: str
process_group_id: str = ""
process_group: Any | None = None
primary_file_name: str | None = None
primary_process_id: str | None = None
display_order: int | None = 0

View File

@ -43,6 +43,7 @@ from spiffworkflow_backend.models.message_triggerable_process_model import (
MessageTriggerableProcessModel,
)
from spiffworkflow_backend.models.principal import PrincipalModel
from spiffworkflow_backend.models.process_group import ProcessGroup
from spiffworkflow_backend.models.process_group import ProcessGroupSchema
from spiffworkflow_backend.models.process_instance import ProcessInstanceApiSchema
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
@ -135,18 +136,12 @@ def permissions_check(body: Dict[str, Dict[str, list[str]]]) -> flask.wrappers.R
return make_response(jsonify({"results": response_dict}), 200)
def process_group_add(
body: Dict[str, Union[str, bool, int]]
) -> flask.wrappers.Response:
def process_group_add(body: dict) -> flask.wrappers.Response:
"""Add_process_group."""
process_model_service = ProcessModelService()
process_group = ProcessGroupSchema().load(body)
process_group = ProcessGroup(**body)
process_model_service.add_process_group(process_group)
return Response(
json.dumps(ProcessGroupSchema().dump(process_group)),
status=201,
mimetype="application/json",
)
return make_response(jsonify(process_group), 201)
def process_group_delete(process_group_id: str) -> flask.wrappers.Response:
@ -155,13 +150,18 @@ def process_group_delete(process_group_id: str) -> flask.wrappers.Response:
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
def process_group_update(
process_group_id: str, body: Dict[str, Union[str, bool, int]]
) -> Dict[str, Union[str, bool, int]]:
def process_group_update(process_group_id: str, body: dict) -> flask.wrappers.Response:
"""Process Group Update."""
process_group = ProcessGroupSchema().load(body)
body_include_list = ["display_name", "description"]
body_filtered = {
include_item: body[include_item]
for include_item in body_include_list
if include_item in body
}
process_group = ProcessGroup(id=process_group_id, **body_filtered)
ProcessModelService().update_process_group(process_group)
return ProcessGroupSchema().dump(process_group) # type: ignore
return make_response(jsonify(process_group), 200)
def process_groups_list(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
@ -174,6 +174,7 @@ def process_groups_list(page: int = 1, per_page: int = 100) -> flask.wrappers.Re
remainder = len(process_groups) % per_page
if remainder > 0:
pages += 1
response_json = {
"results": ProcessGroupSchema(many=True).dump(batch),
"pagination": {
@ -199,7 +200,7 @@ def process_group_show(
status_code=400,
)
) from exception
return ProcessGroupSchema().dump(process_group)
return make_response(jsonify(process_group), 200)
def process_model_add(
@ -225,7 +226,6 @@ def process_model_add(
status_code=400,
)
process_model_info.process_group = process_group
process_model_service.add_spec(process_model_info)
return Response(
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
@ -651,9 +651,9 @@ def process_instance_list(
page: int = 1,
per_page: int = 100,
start_from: Optional[int] = None,
start_till: Optional[int] = None,
start_to: Optional[int] = None,
end_from: Optional[int] = None,
end_till: Optional[int] = None,
end_to: Optional[int] = None,
process_status: Optional[str] = None,
) -> flask.wrappers.Response:
"""Process_instance_list."""
@ -684,17 +684,17 @@ def process_instance_list(
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.start_in_seconds >= start_from
)
if start_till is not None:
if start_to is not None:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.start_in_seconds <= start_till
ProcessInstanceModel.start_in_seconds <= start_to
)
if end_from is not None:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.end_in_seconds >= end_from
)
if end_till is not None:
if end_to is not None:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.end_in_seconds <= end_till
ProcessInstanceModel.end_in_seconds <= end_to
)
if process_status is not None:
process_status_array = process_status.split(",")

View File

@ -170,7 +170,7 @@ class ProcessModelService(FileSystemService):
json_path = os.path.join(cat_path, self.CAT_JSON_FILE)
with open(json_path, "w") as cat_json:
json.dump(
self.GROUP_SCHEMA.dump(process_group),
process_group.serialized,
cat_json,
indent=4,
sort_keys=True,
@ -274,6 +274,5 @@ class ProcessModelService(FileSystemService):
with open(spec_path, "w") as wf_json:
json.dump(self.WF_SCHEMA.dump(spec), wf_json, indent=4)
if process_group:
spec.process_group = process_group
spec.process_group_id = process_group.id
return spec

View File

@ -17,7 +17,6 @@ from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
from spiffworkflow_backend.models.active_task import ActiveTaskModel
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.process_group import ProcessGroup
from spiffworkflow_backend.models.process_group import ProcessGroupSchema
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
from spiffworkflow_backend.models.process_instance_report import (
@ -388,25 +387,29 @@ class TestProcessApi(BaseTest):
display_name="Another Test Category",
display_order=0,
admin=False,
description="Test Description",
)
response = client.post(
"/v1.0/process-groups",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group)),
data=json.dumps(process_group.serialized),
)
assert response.status_code == 201
assert response.json
# Check what is returned
result = ProcessGroupSchema().loads(response.get_data(as_text=True))
result = ProcessGroup(**response.json)
assert result is not None
assert result.display_name == "Another Test Category"
assert result.id == "test"
assert result.description == "Test Description"
# Check what is persisted
persisted = ProcessModelService().get_process_group("test")
assert persisted.display_name == "Another Test Category"
assert persisted.id == "test"
assert persisted.description == "Test Description"
def test_process_group_delete(
self,
@ -461,7 +464,7 @@ class TestProcessApi(BaseTest):
f"/v1.0/process-groups/{group_id}",
headers=self.logged_in_headers(with_super_admin_user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group)),
data=json.dumps(process_group.serialized),
)
assert response.status_code == 200
@ -788,6 +791,7 @@ class TestProcessApi(BaseTest):
f"/v1.0/process-groups/{test_process_group_id}",
headers=self.logged_in_headers(with_super_admin_user),
)
assert response.status_code == 200
assert response.json is not None
assert response.json["id"] == test_process_group_id
@ -1299,7 +1303,7 @@ class TestProcessApi(BaseTest):
# start > 2000, end < 5000 - this should eliminate the first 2 and the last
response = client.get(
"/v1.0/process-instances?start_from=2001&end_till=5999",
"/v1.0/process-instances?start_from=2001&end_to=5999",
headers=self.logged_in_headers(with_super_admin_user),
)
assert response.json is not None
@ -1310,7 +1314,7 @@ class TestProcessApi(BaseTest):
# start > 1000, start < 4000 - this should eliminate the first and the last 2
response = client.get(
"/v1.0/process-instances?start_from=1001&start_till=3999",
"/v1.0/process-instances?start_from=1001&start_to=3999",
headers=self.logged_in_headers(with_super_admin_user),
)
assert response.json is not None
@ -1321,7 +1325,7 @@ class TestProcessApi(BaseTest):
# end > 2000, end < 6000 - this should eliminate the first and the last
response = client.get(
"/v1.0/process-instances?end_from=2001&end_till=5999",
"/v1.0/process-instances?end_from=2001&end_to=5999",
headers=self.logged_in_headers(with_super_admin_user),
)
assert response.json is not None

View File

@ -10,4 +10,9 @@ module.exports = defineConfig({
// implement node event listeners here
},
},
// this scrolls away from the elements for some reason with carbon when set to top
// https://github.com/cypress-io/cypress/issues/2353
// https://docs.cypress.io/guides/core-concepts/interacting-with-elements#Scrolling
scrollBehavior: "center",
});

View File

@ -32,7 +32,7 @@ describe('process-groups', () => {
cy.contains('Delete').click();
cy.contains('Are you sure');
cy.contains('OK').click();
cy.getBySel('modal-confirmation-dialog').find('.cds--btn--danger').click();
cy.url().should('include', `process-groups`);
cy.contains(groupId).should('not.exist');
});

View File

@ -43,12 +43,19 @@ const updateBpmnPythonScriptWithMonaco = (
cy.contains('Launch Editor').click();
// sometimes, Loading... appears for more than 4 seconds. not sure why.
cy.contains('Loading...').should('not.exist');
// the delay 30 is because, at some point, monaco started automatically
// adding a second double quote when we type a double quote. when it does
// that, there is a race condition where it sometimes gets in more text
// before the second double quote appears because the robot is typing faster
// than a human being could, so we artificially slow it down to make it more
// human.
cy.get('.monaco-editor textarea:first')
.click()
.focused() // change subject to currently focused element
// .type('{ctrl}a') // had been doing it this way, but it turns out to be flaky relative to clear()
.clear()
.type(pythonScript);
.type(pythonScript, { delay: 30 });
cy.contains('Close').click();
// wait for a little bit for the xml to get set before saving
@ -111,8 +118,10 @@ describe('process-instances', () => {
});
it('can create a new instance and can modify with monaco text editor', () => {
const originalPythonScript = 'person = "Kevin"';
const newPythonScript = 'person = "Mike"';
// leave off the ending double quote since manco adds it
const originalPythonScript = 'person = "Kevin';
const newPythonScript = 'person = "Mike';
const bpmnFile = 'process_model_one.bpmn';
// Change bpmn

View File

@ -99,7 +99,7 @@ describe('process-models', () => {
cy.contains(/^Process Model File$/);
// Some reason, cypress evals json strings so we have to escape it it with '{{}'
cy.get('.view-line').type('{{} "test_key": "test_value" }');
cy.contains('Save').click();
cy.getBySel('file-save-button').click();
cy.get('input[name=file_name]').type(jsonFileName);
cy.contains('Save Changes').click();
cy.contains(`Process Model File: ${jsonFileName}`);
@ -168,12 +168,8 @@ describe('process-models', () => {
});
it('can allow searching for model', () => {
cy.get('[name=process-model-selection]').click();
cy.get('[name=process-model-selection]').type('model-3');
cy.get(
`[aria-label="acceptance-tests-group-one/acceptance-tests-model-3"]`
).click();
cy.contains('Process Instances').click();
cy.getBySel('process-model-selection').click().type('model-3');
cy.contains('acceptance-tests-group-one/acceptance-tests-model-3').click();
cy.contains('List').click();
});
});

View File

@ -21,6 +21,7 @@ describe('tasks', () => {
cy.logout();
});
// TODO: need to fix the next_task thing to make this pass
it('can complete and navigate a form', () => {
const groupDisplayName = 'Acceptance Tests Group One';
const modelId = `acceptance-tests-model-2`;

View File

@ -31,11 +31,13 @@ Cypress.Commands.add('getBySel', (selector, ...args) => {
});
Cypress.Commands.add('navigateToHome', () => {
cy.getBySel('nav-home').click();
cy.get('button[aria-label="Open menu"]').click();
cy.getBySel('side-nav-items').contains('Home').click();
// cy.getBySel('nav-home').click();
});
Cypress.Commands.add('navigateToAdmin', () => {
cy.getBySel('spiffworkflow-logo').click();
cy.visit('/admin');
});
Cypress.Commands.add('login', (selector, ...args) => {
@ -101,18 +103,15 @@ Cypress.Commands.add(
);
Cypress.Commands.add('basicPaginationTest', () => {
cy.get('#pagination-page-dropdown')
.type('typing_to_open_dropdown_box....FIXME')
.find('.dropdown-item')
.contains(/^2$/)
.click();
cy.getBySel('pagination-options').scrollIntoView();
cy.get('.cds--select__item-count').find('.cds--select-input').select('2');
cy.contains(/^1-2 of \d+$/);
cy.getBySel('pagination-previous-button-inactive');
cy.getBySel('pagination-next-button').click();
cy.contains(/^3-4 of \d+$/);
cy.getBySel('pagination-previous-button').click();
cy.contains(/^1-2 of \d+$/);
// NOTE: this is a em dash instead of en dash
cy.contains(/\b12 of \d+/);
cy.get('.cds--pagination__button--forward').click();
cy.contains(/\b34 of \d+/);
cy.get('.cds--pagination__button--backward').click();
cy.contains(/\b12 of \d+/);
});
Cypress.Commands.add('assertAtLeastOneItemInPaginatedResults', () => {

View File

@ -11,6 +11,9 @@
"@babel/core": "^7.18.10",
"@babel/plugin-transform-react-jsx": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@carbon/icons-react": "^11.10.0",
"@carbon/react": "^1.16.0",
"@carbon/styles": "^1.16.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@monaco-editor/react": "^4.4.5",
"@rjsf/core": "^4.2.0",
@ -2140,6 +2143,138 @@
"resolved": "https://registry.npmjs.org/@camunda/zeebe-element-templates-json-schema/-/zeebe-element-templates-json-schema-0.6.0.tgz",
"integrity": "sha512-qawIFM52lp1hW2vWrHaX8ywguZsp2olE0DRTHUY+KWH5GwszZwGWECP3tji1KVih2TasQyf28kcQVh8TeQ6dAg=="
},
"node_modules/@carbon/colors": {
"version": "11.7.0",
"resolved": "https://registry.npmjs.org/@carbon/colors/-/colors-11.7.0.tgz",
"integrity": "sha512-2B57vtirYTpxGg7p9yIO+s4cwL5QB8ogGQB5Pz+zdaHoVxrLsGlxAg28CQgk7yxw7doqST79bRVYS6FaJq+qJw=="
},
"node_modules/@carbon/feature-flags": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@carbon/feature-flags/-/feature-flags-0.9.0.tgz",
"integrity": "sha512-uNCRkxsNwLdJVNwQ5S5vrLAm4yawWzhFBwyP9EgXVmRzJH85Aim+miC+QNjRXntJDYhZYDuLABTeb3VD692ypA=="
},
"node_modules/@carbon/grid": {
"version": "11.7.0",
"resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.7.0.tgz",
"integrity": "sha512-OCDXQNSPVOoBQN08Qn5wo2WpKRGRm/kEBo6ZTl2NoDCl21mcYJZ0IDbKPTukixJB1sUNDUlnrpwMoaznPj48dw==",
"dependencies": {
"@carbon/layout": "^11.7.0"
}
},
"node_modules/@carbon/icon-helpers": {
"version": "10.34.0",
"resolved": "https://registry.npmjs.org/@carbon/icon-helpers/-/icon-helpers-10.34.0.tgz",
"integrity": "sha512-Ov9EBc1tR/DDrMI0pN1drj2jb27ISmYFBLdDji+aivVJkLPy8R/jikJOsOBgIq2kUjQJYNN199k2acHKjZdYIg=="
},
"node_modules/@carbon/icons-react": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/@carbon/icons-react/-/icons-react-11.10.0.tgz",
"integrity": "sha512-807RWTfbvVzmsDg2DJ4FjwYNbJSgkrEd1Ui8I07YheJVb3sbYGGZMG7aCS0qXVlrQOhB2hggtxSW1w9NksUXNA==",
"hasInstallScript": true,
"dependencies": {
"@carbon/icon-helpers": "^10.34.0",
"@carbon/telemetry": "0.1.0",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": ">=16"
}
},
"node_modules/@carbon/layout": {
"version": "11.7.0",
"resolved": "https://registry.npmjs.org/@carbon/layout/-/layout-11.7.0.tgz",
"integrity": "sha512-p4YQvW8U5Go0Tz1PZZgllGSPmoq8xBB5PHByuHiAjzwGclxPsBmY6Ea7tftINFW8VlcjDcampyT8VfZXhP2lFg=="
},
"node_modules/@carbon/motion": {
"version": "11.5.0",
"resolved": "https://registry.npmjs.org/@carbon/motion/-/motion-11.5.0.tgz",
"integrity": "sha512-5QEALh+xZzICdgVLanSpiDfBTErzVgEze/xUKs7ZdSbd6p1FaDKDGvCmj9RCsaz/CMVHIWo65MshIglSWX5Xvw=="
},
"node_modules/@carbon/react": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.16.0.tgz",
"integrity": "sha512-kVeL/iyVqN2tfcoE1xliIm4w2ex9L4m/b8KuGo7ZuqBmRzANQdfSYGfj11KU0TXT1CbOVFrqsT/aBxApsP5IDg==",
"hasInstallScript": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@carbon/feature-flags": "^0.9.0",
"@carbon/icons-react": "^11.10.0",
"@carbon/layout": "^11.7.0",
"@carbon/styles": "^1.16.0",
"@carbon/telemetry": "0.1.0",
"classnames": "2.3.2",
"copy-to-clipboard": "^3.3.1",
"downshift": "5.2.1",
"flatpickr": "4.6.9",
"invariant": "^2.2.3",
"lodash.debounce": "^4.0.8",
"lodash.findlast": "^4.5.0",
"lodash.isequal": "^4.5.0",
"lodash.omit": "^4.5.0",
"lodash.throttle": "^4.1.1",
"prop-types": "^15.7.2",
"react-is": "^17.0.2",
"use-resize-observer": "^6.0.0",
"wicg-inert": "^3.1.1",
"window-or-global": "^1.0.1"
},
"peerDependencies": {
"react": "^16.8.6 || ^17.0.1",
"react-dom": "^16.8.6 || ^17.0.1",
"sass": "^1.33.0"
}
},
"node_modules/@carbon/react/node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/@carbon/styles": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@carbon/styles/-/styles-1.16.0.tgz",
"integrity": "sha512-LSjRw2Ws8rWI1a96KYUuX10jG+rpSn68dHlZhhDq+RJWsMGpjFhKUxPoTiJli2hHXyxey6rXV0hfr7oBJ0ud7w==",
"dependencies": {
"@carbon/colors": "^11.7.0",
"@carbon/feature-flags": "^0.9.0",
"@carbon/grid": "^11.7.0",
"@carbon/layout": "^11.7.0",
"@carbon/motion": "^11.5.0",
"@carbon/themes": "^11.11.0",
"@carbon/type": "^11.11.0",
"@ibm/plex": "6.0.0-next.6"
},
"peerDependencies": {
"sass": "^1.33.0"
}
},
"node_modules/@carbon/telemetry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@carbon/telemetry/-/telemetry-0.1.0.tgz",
"integrity": "sha512-kNWt0bkgPwGW0i5h7HFuljbKRXPvIhsKbB+1tEURAYLXoJg9iJLF1eGvWN5iVoFCS2zje4GR3OGOsvvKVe7Hlg==",
"bin": {
"carbon-telemetry": "bin/carbon-telemetry.js"
}
},
"node_modules/@carbon/themes": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.11.0.tgz",
"integrity": "sha512-EhLizr2oMqJXNubI2zWw09qcSPLZdWoBOQ6uNcjHzpXgoGNMwbVJE/qGMy/ivr+EOs6Fe0z5T0u97v+ZhSmRhg==",
"dependencies": {
"@carbon/colors": "^11.7.0",
"@carbon/layout": "^11.7.0",
"@carbon/type": "^11.11.0",
"color": "^4.0.0"
}
},
"node_modules/@carbon/type": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.11.0.tgz",
"integrity": "sha512-eX6z8BibP1En1xBm2wUd01Nzk0Tm1jftR2QSD4JBn4xhnkGR824gpcbLTAIMGx9/Mr3R65Enbam3uFO0OOaH8g==",
"dependencies": {
"@carbon/grid": "^11.7.0",
"@carbon/layout": "^11.7.0"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.3.0.tgz",
@ -2899,6 +3034,14 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
},
"node_modules/@ibm/plex": {
"version": "6.0.0-next.6",
"resolved": "https://registry.npmjs.org/@ibm/plex/-/plex-6.0.0-next.6.tgz",
"integrity": "sha512-B3uGruTn2rS5gweynLmfSe7yCawSRsJguJJQHVQiqf4rh2RNgJFu8YLE2Zd/JHV0ZXoVMOslcXP2k3hMkxKEyA==",
"engines": {
"node": ">=14"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@ -8009,9 +8152,9 @@
}
},
"node_modules/classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"node_modules/clean-css": {
"version": "5.3.1",
@ -8207,6 +8350,18 @@
"node": ">=0.10.0"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -8220,6 +8375,31 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
@ -8484,6 +8664,14 @@
"node": ">=0.10.0"
}
},
"node_modules/copy-to-clipboard": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz",
"integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==",
"dependencies": {
"toggle-selection": "^1.0.6"
}
},
"node_modules/core_d": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/core_d/-/core_d-5.0.1.tgz",
@ -9005,9 +9193,9 @@
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
},
"node_modules/cypress": {
"version": "10.8.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.8.0.tgz",
"integrity": "sha512-QVse0dnLm018hgti2enKMVZR9qbIO488YGX06nH5j3Dg1isL38DwrBtyrax02CANU6y8F4EJUuyW6HJKw1jsFA==",
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz",
"integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@ -9872,6 +10060,25 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/downshift": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/downshift/-/downshift-5.2.1.tgz",
"integrity": "sha512-uHX2OLbWthLR8QbR8NCI8OmjvvJxq8+PrA95KblFd9JyB1zVZh1HnszzsWMMCnMuH6IvsUtVfF5HY7XfijJ2dw==",
"dependencies": {
"@babel/runtime": "^7.9.1",
"compute-scroll-into-view": "^1.0.13",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
},
"peerDependencies": {
"react": ">=0.14.9"
}
},
"node_modules/downshift/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@ -11596,6 +11803,11 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
},
"node_modules/flatted": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
@ -12794,6 +13006,12 @@
"url": "https://opencollective.com/immer"
}
},
"node_modules/immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
"peer": true
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -18240,6 +18458,16 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"node_modules/lodash.findlast": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.findlast/-/lodash.findlast-4.6.0.tgz",
"integrity": "sha512-+OGwb1FVKjhc2aIEQ9vKqNDW1a0/HaCLr0iCIK10jfVif3dBE0nhQD0jOZNZLh7zOlmFUTrk+vt85eXoH4vKuA=="
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -18250,6 +18478,11 @@
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"node_modules/lodash.omit": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
"integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@ -18261,6 +18494,11 @@
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="
},
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
},
"node_modules/lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@ -23919,6 +24157,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -24247,6 +24490,23 @@
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz",
"integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA=="
},
"node_modules/sass": {
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz",
"integrity": "sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==",
"peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@ -24717,6 +24977,19 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-swizzle/node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@ -26202,6 +26475,11 @@
"ret": "~0.1.10"
}
},
"node_modules/toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -27747,6 +28025,18 @@
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/use-resize-observer": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-6.1.0.tgz",
"integrity": "sha512-SiPcWHiIQ1CnHmb6PxbYtygqiZXR0U9dNkkbpX9VYnlstUwF8+QqpUTrzh13pjPwcjMVGR+QIC+nvF5ujfFNng==",
"dependencies": {
"resize-observer-polyfill": "^1.5.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
@ -28868,6 +29158,11 @@
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==",
"dev": true
},
"node_modules/wicg-inert": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.2.tgz",
"integrity": "sha512-Ba9tGNYxXwaqKEi9sJJvPMKuo063umUPsHN0JJsjrs2j8KDSzkWLMZGZ+MH1Jf1Fq4OWZ5HsESJID6nRza2ang=="
},
"node_modules/widest-line": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
@ -28923,6 +29218,11 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/window-or-global": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/window-or-global/-/window-or-global-1.0.1.tgz",
"integrity": "sha512-tE12J/NenOv4xdVobD+AD3fT06T4KNqnzRhkv5nBIu7K+pvOH2oLCEgYP+i+5mF2jtI6FEADheOdZkA8YWET9w=="
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@ -30836,6 +31136,124 @@
"resolved": "https://registry.npmjs.org/@camunda/zeebe-element-templates-json-schema/-/zeebe-element-templates-json-schema-0.6.0.tgz",
"integrity": "sha512-qawIFM52lp1hW2vWrHaX8ywguZsp2olE0DRTHUY+KWH5GwszZwGWECP3tji1KVih2TasQyf28kcQVh8TeQ6dAg=="
},
"@carbon/colors": {
"version": "11.7.0",
"resolved": "https://registry.npmjs.org/@carbon/colors/-/colors-11.7.0.tgz",
"integrity": "sha512-2B57vtirYTpxGg7p9yIO+s4cwL5QB8ogGQB5Pz+zdaHoVxrLsGlxAg28CQgk7yxw7doqST79bRVYS6FaJq+qJw=="
},
"@carbon/feature-flags": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@carbon/feature-flags/-/feature-flags-0.9.0.tgz",
"integrity": "sha512-uNCRkxsNwLdJVNwQ5S5vrLAm4yawWzhFBwyP9EgXVmRzJH85Aim+miC+QNjRXntJDYhZYDuLABTeb3VD692ypA=="
},
"@carbon/grid": {
"version": "11.7.0",
"resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.7.0.tgz",
"integrity": "sha512-OCDXQNSPVOoBQN08Qn5wo2WpKRGRm/kEBo6ZTl2NoDCl21mcYJZ0IDbKPTukixJB1sUNDUlnrpwMoaznPj48dw==",
"requires": {
"@carbon/layout": "^11.7.0"
}
},
"@carbon/icon-helpers": {
"version": "10.34.0",
"resolved": "https://registry.npmjs.org/@carbon/icon-helpers/-/icon-helpers-10.34.0.tgz",
"integrity": "sha512-Ov9EBc1tR/DDrMI0pN1drj2jb27ISmYFBLdDji+aivVJkLPy8R/jikJOsOBgIq2kUjQJYNN199k2acHKjZdYIg=="
},
"@carbon/icons-react": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/@carbon/icons-react/-/icons-react-11.10.0.tgz",
"integrity": "sha512-807RWTfbvVzmsDg2DJ4FjwYNbJSgkrEd1Ui8I07YheJVb3sbYGGZMG7aCS0qXVlrQOhB2hggtxSW1w9NksUXNA==",
"requires": {
"@carbon/icon-helpers": "^10.34.0",
"@carbon/telemetry": "0.1.0",
"prop-types": "^15.7.2"
}
},
"@carbon/layout": {
"version": "11.7.0",
"resolved": "https://registry.npmjs.org/@carbon/layout/-/layout-11.7.0.tgz",
"integrity": "sha512-p4YQvW8U5Go0Tz1PZZgllGSPmoq8xBB5PHByuHiAjzwGclxPsBmY6Ea7tftINFW8VlcjDcampyT8VfZXhP2lFg=="
},
"@carbon/motion": {
"version": "11.5.0",
"resolved": "https://registry.npmjs.org/@carbon/motion/-/motion-11.5.0.tgz",
"integrity": "sha512-5QEALh+xZzICdgVLanSpiDfBTErzVgEze/xUKs7ZdSbd6p1FaDKDGvCmj9RCsaz/CMVHIWo65MshIglSWX5Xvw=="
},
"@carbon/react": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.16.0.tgz",
"integrity": "sha512-kVeL/iyVqN2tfcoE1xliIm4w2ex9L4m/b8KuGo7ZuqBmRzANQdfSYGfj11KU0TXT1CbOVFrqsT/aBxApsP5IDg==",
"requires": {
"@babel/runtime": "^7.18.3",
"@carbon/feature-flags": "^0.9.0",
"@carbon/icons-react": "^11.10.0",
"@carbon/layout": "^11.7.0",
"@carbon/styles": "^1.16.0",
"@carbon/telemetry": "0.1.0",
"classnames": "2.3.2",
"copy-to-clipboard": "^3.3.1",
"downshift": "5.2.1",
"flatpickr": "4.6.9",
"invariant": "^2.2.3",
"lodash.debounce": "^4.0.8",
"lodash.findlast": "^4.5.0",
"lodash.isequal": "^4.5.0",
"lodash.omit": "^4.5.0",
"lodash.throttle": "^4.1.1",
"prop-types": "^15.7.2",
"react-is": "^17.0.2",
"use-resize-observer": "^6.0.0",
"wicg-inert": "^3.1.1",
"window-or-global": "^1.0.1"
},
"dependencies": {
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}
}
},
"@carbon/styles": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@carbon/styles/-/styles-1.16.0.tgz",
"integrity": "sha512-LSjRw2Ws8rWI1a96KYUuX10jG+rpSn68dHlZhhDq+RJWsMGpjFhKUxPoTiJli2hHXyxey6rXV0hfr7oBJ0ud7w==",
"requires": {
"@carbon/colors": "^11.7.0",
"@carbon/feature-flags": "^0.9.0",
"@carbon/grid": "^11.7.0",
"@carbon/layout": "^11.7.0",
"@carbon/motion": "^11.5.0",
"@carbon/themes": "^11.11.0",
"@carbon/type": "^11.11.0",
"@ibm/plex": "6.0.0-next.6"
}
},
"@carbon/telemetry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@carbon/telemetry/-/telemetry-0.1.0.tgz",
"integrity": "sha512-kNWt0bkgPwGW0i5h7HFuljbKRXPvIhsKbB+1tEURAYLXoJg9iJLF1eGvWN5iVoFCS2zje4GR3OGOsvvKVe7Hlg=="
},
"@carbon/themes": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.11.0.tgz",
"integrity": "sha512-EhLizr2oMqJXNubI2zWw09qcSPLZdWoBOQ6uNcjHzpXgoGNMwbVJE/qGMy/ivr+EOs6Fe0z5T0u97v+ZhSmRhg==",
"requires": {
"@carbon/colors": "^11.7.0",
"@carbon/layout": "^11.7.0",
"@carbon/type": "^11.11.0",
"color": "^4.0.0"
}
},
"@carbon/type": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.11.0.tgz",
"integrity": "sha512-eX6z8BibP1En1xBm2wUd01Nzk0Tm1jftR2QSD4JBn4xhnkGR824gpcbLTAIMGx9/Mr3R65Enbam3uFO0OOaH8g==",
"requires": {
"@carbon/grid": "^11.7.0",
"@carbon/layout": "^11.7.0"
}
},
"@codemirror/autocomplete": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.3.0.tgz",
@ -31364,6 +31782,11 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
},
"@ibm/plex": {
"version": "6.0.0-next.6",
"resolved": "https://registry.npmjs.org/@ibm/plex/-/plex-6.0.0-next.6.tgz",
"integrity": "sha512-B3uGruTn2rS5gweynLmfSe7yCawSRsJguJJQHVQiqf4rh2RNgJFu8YLE2Zd/JHV0ZXoVMOslcXP2k3hMkxKEyA=="
},
"@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@ -35236,9 +35659,9 @@
}
},
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"clean-css": {
"version": "5.3.1",
@ -35376,6 +35799,30 @@
"object-visit": "^1.0.0"
}
},
"color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"requires": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"dependencies": {
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
}
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -35389,6 +35836,15 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
@ -35614,6 +36070,14 @@
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
"integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw=="
},
"copy-to-clipboard": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz",
"integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==",
"requires": {
"toggle-selection": "^1.0.6"
}
},
"core_d": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/core_d/-/core_d-5.0.1.tgz",
@ -36020,9 +36484,9 @@
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
},
"cypress": {
"version": "10.8.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.8.0.tgz",
"integrity": "sha512-QVse0dnLm018hgti2enKMVZR9qbIO488YGX06nH5j3Dg1isL38DwrBtyrax02CANU6y8F4EJUuyW6HJKw1jsFA==",
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz",
"integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==",
"dev": true,
"requires": {
"@cypress/request": "^2.88.10",
@ -36715,6 +37179,24 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"downshift": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/downshift/-/downshift-5.2.1.tgz",
"integrity": "sha512-uHX2OLbWthLR8QbR8NCI8OmjvvJxq8+PrA95KblFd9JyB1zVZh1HnszzsWMMCnMuH6IvsUtVfF5HY7XfijJ2dw==",
"requires": {
"@babel/runtime": "^7.9.1",
"compute-scroll-into-view": "^1.0.13",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@ -38020,6 +38502,11 @@
"rimraf": "^3.0.2"
}
},
"flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
},
"flatted": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
@ -38876,6 +39363,12 @@
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz",
"integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ=="
},
"immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
"peer": true
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -43000,6 +43493,16 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"lodash.findlast": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.findlast/-/lodash.findlast-4.6.0.tgz",
"integrity": "sha512-+OGwb1FVKjhc2aIEQ9vKqNDW1a0/HaCLr0iCIK10jfVif3dBE0nhQD0jOZNZLh7zOlmFUTrk+vt85eXoH4vKuA=="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -43010,6 +43513,11 @@
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"lodash.omit": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
"integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@ -43021,6 +43529,11 @@
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
},
"lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@ -46934,6 +47447,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -47167,6 +47685,17 @@
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz",
"integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA=="
},
"sass": {
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz",
"integrity": "sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==",
"peer": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@ -47565,6 +48094,21 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"requires": {
"is-arrayish": "^0.3.1"
},
"dependencies": {
"is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
}
}
},
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@ -48736,6 +49280,11 @@
"is-number": "^7.0.0"
}
},
"toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
"toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -49905,6 +50454,14 @@
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
"requires": {}
},
"use-resize-observer": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-6.1.0.tgz",
"integrity": "sha512-SiPcWHiIQ1CnHmb6PxbYtygqiZXR0U9dNkkbpX9VYnlstUwF8+QqpUTrzh13pjPwcjMVGR+QIC+nvF5ujfFNng==",
"requires": {
"resize-observer-polyfill": "^1.5.1"
}
},
"util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
@ -50782,6 +51339,11 @@
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==",
"dev": true
},
"wicg-inert": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.2.tgz",
"integrity": "sha512-Ba9tGNYxXwaqKEi9sJJvPMKuo063umUPsHN0JJsjrs2j8KDSzkWLMZGZ+MH1Jf1Fq4OWZ5HsESJID6nRza2ang=="
},
"widest-line": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
@ -50815,6 +51377,11 @@
}
}
},
"window-or-global": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/window-or-global/-/window-or-global-1.0.1.tgz",
"integrity": "sha512-tE12J/NenOv4xdVobD+AD3fT06T4KNqnzRhkv5nBIu7K+pvOH2oLCEgYP+i+5mF2jtI6FEADheOdZkA8YWET9w=="
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View File

@ -6,6 +6,9 @@
"@babel/core": "^7.18.10",
"@babel/plugin-transform-react-jsx": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@carbon/icons-react": "^11.10.0",
"@carbon/react": "^1.16.0",
"@carbon/styles": "^1.16.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@monaco-editor/react": "^4.4.5",
"@rjsf/core": "^4.2.0",
@ -55,6 +58,10 @@
"@ginkgo-bioworks/react-json-schema-form-builder": {
"react": "^18.2.0",
"bootstrap": "^5.2.0-beta1"
},
"@carbon/react": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
},
"scripts": {

View File

@ -1,5 +1,6 @@
import { useMemo, useState } from 'react';
import { Container } from 'react-bootstrap';
// @ts-ignore
import { Content } from '@carbon/react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import ErrorContext from './contexts/ErrorContext';
@ -9,7 +10,6 @@ import HomePage from './routes/HomePage';
import TaskShow from './routes/TaskShow';
import ErrorBoundary from './components/ErrorBoundary';
import AdminRoutes from './routes/AdminRoutes';
import SubNavigation from './components/SubNavigation';
import { ErrorForDisplay } from './interfaces';
export default function App() {
@ -47,30 +47,27 @@ export default function App() {
return (
<ErrorContext.Provider value={errorContextValueArray}>
<NavigationBar />
<Container>
{errorTag}
<ErrorBoundary>
<BrowserRouter>
<SubNavigation />
<main style={{ padding: '1rem 0' }}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/tasks" element={<HomePage />} />
<Route path="/admin/*" element={<AdminRoutes />} />
<Route
path="/tasks/:process_instance_id/:task_id"
element={<TaskShow />}
/>
<Route
path="/tasks/:process_instance_id/:task_id"
element={<TaskShow />}
/>
</Routes>
</main>
</BrowserRouter>
</ErrorBoundary>
</Container>
<BrowserRouter>
<NavigationBar />
<Content>
{errorTag}
<ErrorBoundary>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/tasks" element={<HomePage />} />
<Route path="/admin/*" element={<AdminRoutes />} />
<Route
path="/tasks/:process_instance_id/:task_id"
element={<TaskShow />}
/>
<Route
path="/tasks/:process_instance_id/:task_id"
element={<TaskShow />}
/>
</Routes>
</ErrorBoundary>
</Content>
</BrowserRouter>
</ErrorContext.Provider>
);
}

View File

@ -1,5 +1,6 @@
import { useState } from 'react';
import { Button, Modal } from 'react-bootstrap';
// @ts-ignore
import { Button, Modal } from '@carbon/react';
type OwnProps = {
description?: string;
@ -25,13 +26,6 @@ export default function ButtonWithConfirmation({
setShowConfirmationPrompt(false);
};
const modalBodyElement = () => {
if (description) {
return <Modal.Body>{description}</Modal.Body>;
}
return null;
};
const handleConfirmation = () => {
onConfirmation();
setShowConfirmationPrompt(false);
@ -40,28 +34,22 @@ export default function ButtonWithConfirmation({
const confirmationDialog = () => {
return (
<Modal
show={showConfirmationPrompt}
onHide={handleConfirmationPromptCancel}
>
<Modal.Header closeButton>
<Modal.Title>{title}</Modal.Title>
</Modal.Header>
{modalBodyElement()}
<Modal.Footer>
<Button variant="secondary" onClick={handleConfirmationPromptCancel}>
Cancel
</Button>
<Button variant="primary" onClick={handleConfirmation}>
{confirmButtonLabel}
</Button>
</Modal.Footer>
</Modal>
open={showConfirmationPrompt}
danger
data-qa="modal-confirmation-dialog"
modalHeading={description}
modalLabel={title}
primaryButtonText={confirmButtonLabel}
secondaryButtonText="Cancel"
onSecondarySubmit={handleConfirmationPromptCancel}
onRequestSubmit={handleConfirmation}
/>
);
};
return (
<>
<Button onClick={handleShowConfirmationPrompt} variant="danger">
<Button onClick={handleShowConfirmationPrompt} kind="danger">
{buttonLabel}
</Button>
{confirmationDialog()}

View File

@ -1,12 +1,28 @@
import { Button, Navbar, Nav, Container } from 'react-bootstrap';
import {
Header,
HeaderContainer,
HeaderMenuButton,
SkipToContent,
SideNav,
SideNavItems,
HeaderSideNavItems,
HeaderName,
HeaderNavigation,
HeaderMenuItem,
HeaderGlobalAction,
HeaderGlobalBar,
// @ts-ignore
} from '@carbon/react';
// @ts-ignore
import { Logout, Login } from '@carbon/icons-react';
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
// @ts-expect-error TS(2307) FIXME: Cannot find module '../logo.svg' or its correspond... Remove this comment to see the full error message
import logo from '../logo.svg';
import UserService from '../services/UserService';
// for ref: https://react-bootstrap.github.io/components/navbar/
export default function NavigationBar() {
const navElements = null;
const handleLogout = () => {
UserService.doLogout();
};
@ -15,56 +31,116 @@ export default function NavigationBar() {
UserService.doLogin();
};
const loginLink = () => {
if (!UserService.isLoggedIn()) {
return (
<Navbar.Collapse className="justify-content-end">
<Navbar.Text>
<Button variant="link" onClick={handleLogin}>
Login
</Button>
</Navbar.Text>
</Navbar.Collapse>
);
const location = useLocation();
const [activeKey, setActiveKey] = useState<string>('');
useEffect(() => {
let newActiveKey = '/admin/process-groups';
if (location.pathname.match(/^\/admin\/messages\b/)) {
newActiveKey = '/admin/messages';
} else if (location.pathname.match(/^\/admin\/process-instances\b/)) {
newActiveKey = '/admin/process-instances';
} else if (location.pathname.match(/^\/admin\/secrets\b/)) {
newActiveKey = '/admin/secrets';
} else if (location.pathname.match(/^\/admin\/authentications\b/)) {
newActiveKey = '/admin/authentications';
} else if (location.pathname === '/') {
newActiveKey = '/';
} else if (location.pathname.match(/^\/tasks\b/)) {
newActiveKey = '/';
}
return null;
setActiveKey(newActiveKey);
}, [location]);
const isActivePage = (menuItemPath: string) => {
return activeKey === menuItemPath;
};
const logoutLink = () => {
const loginAndLogoutAction = () => {
if (UserService.isLoggedIn()) {
return (
<Navbar.Collapse className="justify-content-end">
<Navbar.Text>
Signed in as: <strong>{UserService.getUsername()}</strong>
</Navbar.Text>
<Navbar.Text>
<Button
variant="link"
onClick={handleLogout}
data-qa="logout-button"
>
Logout
</Button>
</Navbar.Text>
</Navbar.Collapse>
<>
<HeaderGlobalAction>{UserService.getUsername()}</HeaderGlobalAction>
<HeaderGlobalAction
aria-label="Logout"
onClick={handleLogout}
data-qa="logout-button"
>
<Logout />
</HeaderGlobalAction>
</>
);
}
return null;
return (
<HeaderGlobalAction
data-qa="login-button"
aria-label="Login"
onClick={handleLogin}
>
<Login />
</HeaderGlobalAction>
);
};
return (
<Navbar bg="dark" expand="lg" variant="dark">
<Container>
<Navbar.Brand data-qa="spiffworkflow-logo" href="/admin">
<img src={logo} className="app-logo" alt="logo" />
</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">{navElements}</Nav>
</Navbar.Collapse>
{loginLink()}
{logoutLink()}
</Container>
</Navbar>
);
const headerMenuItems = () => {
return (
<>
<HeaderMenuItem href="/" isCurrentPage={isActivePage('/')}>
Home
</HeaderMenuItem>
<HeaderMenuItem
href="/admin/process-groups"
isCurrentPage={isActivePage('/admin/process-groups')}
data-qa="header-nav-processes"
>
Processes
</HeaderMenuItem>
<HeaderMenuItem
href="/admin/process-instances"
isCurrentPage={isActivePage('/admin/process-instances')}
>
Process Instances
</HeaderMenuItem>
</>
);
};
if (activeKey) {
// TODO: apply theme g100 to the header
return (
<HeaderContainer
render={({ isSideNavExpanded, onClickSideNavExpand }: any) => (
<Header aria-label="IBM Platform Name">
<SkipToContent />
<HeaderMenuButton
aria-label="Open menu"
onClick={onClickSideNavExpand}
isActive={isSideNavExpanded}
/>
<HeaderName href="/" prefix="" data-qa="spiffworkflow-logo">
<img src={logo} className="app-logo" alt="logo" />
</HeaderName>
<HeaderNavigation
data-qa="main-nav-header"
aria-label="Spiffworkflow"
>
{headerMenuItems()}
</HeaderNavigation>
<SideNav
data-qa="side-nav-items"
aria-label="Side navigation"
expanded={isSideNavExpanded}
isPersistent={false}
>
<SideNavItems>
<HeaderSideNavItems>{headerMenuItems()}</HeaderSideNavItems>
</SideNavItems>
</SideNav>
<HeaderGlobalBar>{loginAndLogoutAction()}</HeaderGlobalBar>
</Header>
)}
/>
);
}
return null;
}

View File

@ -1,7 +1,8 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Dropdown, Stack } from 'react-bootstrap';
// @ts-ignore
import { Pagination } from '@carbon/react';
import { PaginationObject } from '../interfaces';
export const DEFAULT_PER_PAGE = 50;
export const DEFAULT_PAGE = 1;
@ -10,9 +11,7 @@ type OwnProps = {
page: number;
perPage: number;
perPageOptions?: number[];
pagination: {
[key: string]: number;
};
pagination: PaginationObject | null;
tableToDisplay: any;
queryParamString?: string;
path: string;
@ -28,140 +27,32 @@ export default function PaginationForTable({
path,
}: OwnProps) {
const PER_PAGE_OPTIONS = [2, 10, 50, 100];
const navigate = useNavigate();
const buildPerPageDropdown = () => {
const perPageDropdownRows = (perPageOptions || PER_PAGE_OPTIONS).map(
(perPageOption) => {
if (perPageOption === perPage) {
return (
<Dropdown.Item
key={perPageOption}
href={`${path}?page=1&per_page=${perPageOption}`}
active
>
{perPageOption}
</Dropdown.Item>
);
}
return (
<Dropdown.Item
key={perPageOption}
href={`${path}?page=1&per_page=${perPageOption}`}
>
{perPageOption}
</Dropdown.Item>
);
}
);
return (
<Stack direction="horizontal" gap={3}>
<Dropdown className="ms-auto" id="pagination-page-dropdown">
<Dropdown.Toggle
id="process-instances-per-page"
variant="light border"
>
Number to show: {perPage}
</Dropdown.Toggle>
<Dropdown.Menu variant="light">{perPageDropdownRows}</Dropdown.Menu>
</Dropdown>
</Stack>
);
const updateRows = (args: any) => {
const newPage = args.page;
const { pageSize } = args;
navigate(`${path}?page=${newPage}&per_page=${pageSize}${queryParamString}`);
};
const buildPaginationNav = () => {
let previousPageTag = null;
if (page === 1) {
previousPageTag = (
<li
data-qa="pagination-previous-button-inactive"
className="page-item disabled"
key="previous"
>
<span style={{ fontSize: '1.5em' }} className="page-link">
&laquo;
</span>
</li>
);
} else {
previousPageTag = (
<li className="page-item" key="previous">
<Link
data-qa="pagination-previous-button"
className="page-link"
style={{ fontSize: '1.5em' }}
to={`${path}?page=${
page - 1
}&per_page=${perPage}${queryParamString}`}
>
&laquo;
</Link>
</li>
);
}
let nextPageTag = null;
if (page >= pagination.pages) {
nextPageTag = (
<li
data-qa="pagination-next-button-inactive"
className="page-item disabled"
key="next"
>
<span style={{ fontSize: '1.5em' }} className="page-link">
&raquo;
</span>
</li>
);
} else {
nextPageTag = (
<li className="page-item" key="next">
<Link
data-qa="pagination-next-button"
className="page-link"
style={{ fontSize: '1.5em' }}
to={`${path}?page=${
page + 1
}&per_page=${perPage}${queryParamString}`}
>
&raquo;
</Link>
</li>
);
}
let startingNumber = (page - 1) * perPage + 1;
let endingNumber = page * perPage;
if (endingNumber > pagination.total) {
endingNumber = pagination.total;
}
if (startingNumber > pagination.total) {
startingNumber = pagination.total;
}
if (pagination) {
return (
<Stack direction="horizontal" gap={3}>
<p className="ms-auto">
{startingNumber}-{endingNumber} of{' '}
<span data-qa="total-paginated-items">{pagination.total}</span>
</p>
<nav aria-label="Page navigation">
<div>
<ul className="pagination">
{previousPageTag}
{nextPageTag}
</ul>
</div>
</nav>
</Stack>
<>
{tableToDisplay}
<Pagination
data-qa="pagination-options"
backwardText="Previous page"
forwardText="Next page"
itemsPerPageText="Items per page:"
page={page}
pageNumberText="Page Number"
pageSize={perPage}
pageSizes={perPageOptions || PER_PAGE_OPTIONS}
totalItems={pagination.total}
onChange={updateRows}
/>
</>
);
};
return (
<main>
{buildPaginationNav()}
{tableToDisplay}
{buildPerPageDropdown()}
</main>
);
}
return null;
}

View File

@ -0,0 +1,181 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
// @ts-ignore
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
import { slugifyString } from '../helpers';
import HttpService from '../services/HttpService';
import { ProcessGroup } from '../interfaces';
import ButtonWithConfirmation from './ButtonWithConfirmation';
type OwnProps = {
mode: string;
processGroup: ProcessGroup;
setProcessGroup: (..._args: any[]) => any;
};
export default function ProcessGroupForm({
mode,
processGroup,
setProcessGroup,
}: OwnProps) {
const [identifierInvalid, setIdentifierInvalid] = useState<boolean>(false);
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
useState<boolean>(false);
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
const navigate = useNavigate();
const navigateToProcessGroup = (_result: any) => {
if (processGroup) {
navigate(`/admin/process-groups/${processGroup.id}`);
}
};
const navigateToProcessGroups = (_result: any) => {
navigate(`/admin/process-groups`);
};
const hasValidIdentifier = (identifierToCheck: string) => {
return identifierToCheck.match(/^[a-z0-9][0-9a-z-]+[a-z0-9]$/);
};
const deleteProcessGroup = () => {
HttpService.makeCallToBackend({
path: `/process-groups/${processGroup.id}`,
successCallback: navigateToProcessGroups,
httpMethod: 'DELETE',
});
};
const handleFormSubmission = (event: any) => {
event.preventDefault();
let hasErrors = false;
if (!hasValidIdentifier(processGroup.id)) {
setIdentifierInvalid(true);
hasErrors = true;
}
if (processGroup.display_name === '') {
setDisplayNameInvalid(true);
hasErrors = true;
}
if (hasErrors) {
return;
}
let path = '/process-groups';
if (mode === 'edit') {
path = `/process-groups/${processGroup.id}`;
}
let httpMethod = 'POST';
if (mode === 'edit') {
httpMethod = 'PUT';
}
const postBody = {
display_name: processGroup.display_name,
description: processGroup.description,
};
if (mode === 'new') {
Object.assign(postBody, { id: processGroup.id });
}
HttpService.makeCallToBackend({
path,
successCallback: navigateToProcessGroup,
httpMethod,
postBody,
});
};
const updateProcessGroup = (newValues: any) => {
const processGroupToCopy = {
...processGroup,
};
Object.assign(processGroupToCopy, newValues);
setProcessGroup(processGroupToCopy);
};
const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayNameInvalid(false);
const updateDict = { display_name: newDisplayName };
if (!idHasBeenUpdatedByUser) {
Object.assign(updateDict, { id: slugifyString(newDisplayName) });
}
updateProcessGroup(updateDict);
};
const formElements = () => {
const textInputs = [
<TextInput
id="process-group-display-name"
name="display_name"
invalidText="Display Name is required."
invalid={displayNameInvalid}
labelText="Display Name*"
value={processGroup.display_name}
onChange={(event: any) => onDisplayNameChanged(event.target.value)}
onBlur={(event: any) => console.log('event', event)}
/>,
];
if (mode === 'new') {
textInputs.push(
<TextInput
id="process-group-identifier"
name="id"
invalidText="Identifier is required and must be all lowercase characters and hyphens."
invalid={identifierInvalid}
labelText="Identifier*"
value={processGroup.id}
onChange={(event: any) => {
updateProcessGroup({ id: event.target.value });
// was invalid, and now valid
if (identifierInvalid && hasValidIdentifier(event.target.value)) {
setIdentifierInvalid(false);
}
setIdHasBeenUpdatedByUser(true);
}}
/>
);
}
textInputs.push(
<TextInput
id="process-group-description"
name="description"
labelText="Description"
value={processGroup.description}
onChange={(event: any) =>
updateProcessGroup({ description: event.target.value })
}
/>
);
return textInputs;
};
const formButtons = () => {
const buttons = [
<Button kind="secondary" type="submit">
Submit
</Button>,
];
if (mode === 'edit') {
buttons.push(
<ButtonWithConfirmation
description={`Delete Process Group ${processGroup.id}?`}
onConfirmation={deleteProcessGroup}
buttonLabel="Delete"
confirmButtonLabel="Delete"
/>
);
}
return <ButtonSet>{buttons}</ButtonSet>;
};
return (
<Form onSubmit={handleFormSubmission}>
<Stack gap={5}>
{formElements()}
{formButtons()}
</Stack>
</Form>
);
}

View File

@ -0,0 +1,48 @@
import {
ComboBox,
// @ts-ignore
} from '@carbon/react';
import { truncateString } from '../helpers';
import { ProcessModel } from '../interfaces';
type OwnProps = {
onChange: (..._args: any[]) => any;
processModels: ProcessModel[];
selectedItem?: ProcessModel | null;
titleText?: string;
};
export default function ProcessModelSearch({
processModels,
selectedItem,
onChange,
titleText = 'Process model',
}: OwnProps) {
const shouldFilterProcessModel = (options: any) => {
const processModel: ProcessModel = options.item;
const { inputValue } = options;
return `${processModel.process_group_id}/${processModel.id} (${processModel.display_name})`.includes(
inputValue
);
};
return (
<ComboBox
onChange={onChange}
id="process-model-select"
data-qa="process-model-selection"
items={processModels}
itemToString={(processModel: ProcessModel) => {
if (processModel) {
return `${processModel.process_group_id}/${
processModel.id
} (${truncateString(processModel.display_name, 20)})`;
}
return null;
}}
shouldFilterItem={shouldFilterProcessModel}
placeholder="Choose a process model"
titleText={titleText}
selectedItem={selectedItem}
/>
);
}

View File

@ -18,7 +18,8 @@ import {
} from 'dmn-js-properties-panel';
import React, { useRef, useEffect, useState } from 'react';
import Button from 'react-bootstrap/Button';
// @ts-ignore
import { Button } from '@carbon/react';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';

View File

@ -16,4 +16,6 @@ export const PROCESS_STATUSES = [
'suspended',
];
export const DATE_FORMAT = 'yyyy-MM-dd HH:mm:ss';
// with time: yyyy-MM-dd HH:mm:ss
export const DATE_FORMAT = 'yyyy-MM-dd';
export const DATE_FORMAT_CARBON = 'Y-m-d';

View File

@ -20,7 +20,10 @@ export const capitalizeFirstLetter = (string: any) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
export const convertDateToSeconds = (date: any, onChangeFunction: any) => {
export const convertDateToSeconds = (
date: any,
onChangeFunction: any = null
) => {
let dateInSeconds = date;
if (date !== null) {
let dateInMilliseconds = date;
@ -39,14 +42,26 @@ export const convertDateToSeconds = (date: any, onChangeFunction: any) => {
return null;
};
export const convertSecondsToFormattedDate = (seconds: number) => {
if (seconds) {
const startDate = new Date(seconds * 1000);
return format(startDate, DATE_FORMAT);
export const convertStringToDate = (dateString: string) => {
if (dateString) {
return new Date(dateString);
}
return null;
};
export const convertSecondsToFormattedDate = (seconds: number) => {
if (seconds) {
const dateObject = new Date(seconds * 1000);
return format(dateObject, DATE_FORMAT);
}
return null;
};
export const convertDateStringToSeconds = (dateString: string) => {
const dateObject = convertStringToDate(dateString);
return convertDateToSeconds(dateObject);
};
export const objectIsEmpty = (obj: object) => {
return Object.keys(obj).length === 0;
};
@ -90,3 +105,11 @@ export const getProcessModelFullIdentifierFromSearchParams = (
}
return processModelFullIdentifier;
};
// https://stackoverflow.com/a/71352046/6090676
export const truncateString = (text: string, len: number) => {
if (text.length > len && text.length > 0) {
return `${text.split(' ').slice(0, len).join(' ')} ...`;
}
return text;
};

View File

@ -20,12 +20,15 @@ span.bjs-crumb {
}
.app-logo {
height: 35%;
width: 35%;
height: 85%;
width: 85%;
margin-top: 1em;
margin-bottom: 1em;
}
.spiffworkflow-header-container {
margin-bottom: 2em;
}
.active-task-highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: yellow !important;
@ -44,6 +47,17 @@ span.bjs-crumb {
margin:auto;
}
.cds--btn.button-white-background {
color: #393939;
background: #FFFFFF;
background-blend-mode: multiply;
border: 1px solid #393939;
}
.with-bottom-margin {
margin-bottom: 1em;
}
.diagram-viewer-canvas {
border:1px solid #000000;
height:70vh;

View File

@ -0,0 +1,5 @@
// @use '@carbon/react/scss/themes';
// @use '@carbon/react/scss/theme' with ($theme: themes.$g100);
@use '@carbon/react';
@use '@carbon/styles';
// @include grid.flex-grid();

View File

@ -4,6 +4,7 @@ import App from './App';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import './index.scss';
import reportWebVitals from './reportWebVitals';
import UserService from './services/UserService';

View File

@ -14,6 +14,7 @@ export interface RecentProcessModel {
export interface ProcessGroup {
id: string;
display_name: string;
description?: string | null;
}
export interface ProcessModel {
@ -41,3 +42,13 @@ export interface AuthenticationItem {
id: string;
parameters: AuthenticationParam[];
}
export interface PaginationObject {
count: number;
total: number;
pages: number;
}
export interface CarbonComboBoxSelection {
selectedItem: ProcessModel;
}

View File

@ -1,5 +1,6 @@
import { useContext, useEffect, useState } from 'react';
import { Table } from 'react-bootstrap';
// @ts-ignore
import { Table } from '@carbon/react';
import ErrorContext from '../contexts/ErrorContext';
import { AuthenticationItem } from '../interfaces';
import HttpService from '../services/HttpService';

View File

@ -1,17 +1,18 @@
import { useEffect, useState } from 'react';
import { Button, Table } from 'react-bootstrap';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from '../components/PaginationForTable';
import { getPageInfoFromSearchParams } from '../helpers';
import HttpService from '../services/HttpService';
import { RecentProcessModel } from '../interfaces';
import { PaginationObject, RecentProcessModel } from '../interfaces';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
export default function HomePage() {
const [searchParams] = useSearchParams();
const [tasks, setTasks] = useState([]);
const [pagination, setPagination] = useState(null);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
useEffect(() => {
const { page, perPage } = getPageInfoFromSearchParams(
@ -53,7 +54,7 @@ export default function HomePage() {
data-qa="process-instance-show-link"
to={`/admin/process-models/${rowToUse.process_group_identifier}/${rowToUse.process_model_identifier}/process-instances/${rowToUse.process_instance_id}`}
>
View
View {rowToUse.process_instance_id}
</Link>
</td>
<td
@ -125,10 +126,10 @@ export default function HomePage() {
);
};
const relevantProcessModelSection =
recentProcessModels.length > 0 && buildRecentProcessModelSection();
if (pagination) {
const tasksWaitingForMeComponent = () => {
if (pagination && pagination.total < 1) {
return null;
}
const { page, perPage } = getPageInfoFromSearchParams(
searchParams,
PER_PAGE_FOR_TASKS_ON_HOME_PAGE
@ -144,6 +145,17 @@ export default function HomePage() {
tableToDisplay={buildTable()}
path="/tasks"
/>
</>
);
};
const relevantProcessModelSection =
recentProcessModels.length > 0 && buildRecentProcessModelSection();
if (pagination) {
return (
<>
{tasksWaitingForMeComponent()}
{relevantProcessModelSection}
</>
);

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Table } from 'react-bootstrap';
// @ts-ignore
import { Table } from '@carbon/react';
import { Link, useParams, useSearchParams } from 'react-router-dom';
import PaginationForTable from '../components/PaginationForTable';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';

View File

@ -1,20 +1,18 @@
import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, Stack } from 'react-bootstrap';
import { useParams } from 'react-router-dom';
// @ts-ignore
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import ProcessGroupForm from '../components/ProcessGroupForm';
import { ProcessGroup } from '../interfaces';
export default function ProcessGroupEdit() {
const [displayName, setDisplayName] = useState('');
const params = useParams();
const navigate = useNavigate();
const [processGroup, setProcessGroup] = useState(null);
const [processGroup, setProcessGroup] = useState<ProcessGroup | null>(null);
useEffect(() => {
const setProcessGroupsFromResult = (result: any) => {
setProcessGroup(result);
setDisplayName(result.display_name);
};
HttpService.makeCallToBackend({
@ -23,69 +21,16 @@ export default function ProcessGroupEdit() {
});
}, [params]);
const navigateToProcessGroup = (_result: any) => {
navigate(`/admin/process-groups/${(processGroup as any).id}`);
};
const navigateToProcessGroups = (_result: any) => {
navigate(`/admin/process-groups`);
};
const updateProcessGroup = (event: any) => {
event.preventDefault();
HttpService.makeCallToBackend({
path: `/process-groups/${(processGroup as any).id}`,
successCallback: navigateToProcessGroup,
httpMethod: 'PUT',
postBody: {
display_name: displayName,
id: (processGroup as any).id,
},
});
};
const deleteProcessGroup = () => {
HttpService.makeCallToBackend({
path: `/process-groups/${(processGroup as any).id}`,
successCallback: navigateToProcessGroups,
httpMethod: 'DELETE',
});
};
const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayName(newDisplayName);
};
if (processGroup) {
return (
<>
<ProcessBreadcrumb processGroupId={(processGroup as any).id} />
<h2>Edit Process Group: {(processGroup as any).id}</h2>
<form onSubmit={updateProcessGroup}>
<label>Display Name:</label>
<input
name="display_name"
type="text"
value={displayName}
onChange={(e) => onDisplayNameChanged(e.target.value)}
/>
<br />
<br />
<Stack direction="horizontal" gap={3}>
<Button type="submit">Submit</Button>
<Button
variant="secondary"
href={`/admin/process-groups/${(processGroup as any).id}`}
>
Cancel
</Button>
<ButtonWithConfirmation
description={`Delete Process Group ${(processGroup as any).id}?`}
onConfirmation={deleteProcessGroup}
buttonLabel="Delete"
/>
</Stack>
</form>
<ProcessGroupForm
mode="edit"
processGroup={processGroup}
setProcessGroup={setProcessGroup}
/>
</>
);
}

View File

@ -1,13 +1,21 @@
import { useEffect, useState } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { Button, Form, InputGroup, Table } from 'react-bootstrap';
import { Typeahead } from 'react-bootstrap-typeahead';
import { Option } from 'react-bootstrap-typeahead/types/types';
import {
Button,
Table,
// ExpandableTile,
// TileAboveTheFoldContent,
// TileBelowTheFoldContent,
// TextInput,
// ClickableTile,
// @ts-ignore
} from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import PaginationForTable from '../components/PaginationForTable';
import HttpService from '../services/HttpService';
import { getPageInfoFromSearchParams } from '../helpers';
import { ProcessModel } from '../interfaces';
import { CarbonComboBoxSelection, ProcessGroup } from '../interfaces';
import ProcessModelSearch from '../components/ProcessModelSearch';
// Example process group json
// {'process_group_id': 'sure', 'display_name': 'Test Workflows', 'id': 'test_process_group'}
@ -17,8 +25,9 @@ export default function ProcessGroupList() {
const [processGroups, setProcessGroups] = useState([]);
const [pagination, setPagination] = useState(null);
const [processModeleSelectionOptions, setProcessModelSelectionOptions] =
useState([]);
const [processModelAvailableItems, setProcessModelAvailableItems] = useState(
[]
);
useEffect(() => {
const setProcessGroupsFromResult = (result: any) => {
@ -31,7 +40,7 @@ export default function ProcessGroupList() {
Object.assign(item, { label });
return item;
});
setProcessModelSelectionOptions(selectionArray);
setProcessModelAvailableItems(selectionArray);
};
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
@ -48,7 +57,7 @@ export default function ProcessGroupList() {
}, [searchParams]);
const buildTable = () => {
const rows = processGroups.map((row) => {
const rows = processGroups.map((row: ProcessGroup) => {
return (
<tr key={(row as any).id}>
<td>
@ -72,6 +81,17 @@ export default function ProcessGroupList() {
<tbody>{rows}</tbody>
</Table>
);
// const rows = processGroups.map((row: ProcessGroup) => {
// return (
// <span>
// <ClickableTile href={`/admin/process-groups/${row.id}`}>
// {row.display_name}
// </ClickableTile>
// </span>
// );
// });
//
// return <div style={{ width: '400px' }}>{rows}</div>;
};
const processGroupsDisplayArea = () => {
@ -97,35 +117,18 @@ export default function ProcessGroupList() {
};
const processModelSearchArea = () => {
const processModelSearchOnChange = (selected: Option[]) => {
const processModel = selected[0] as ProcessModel;
const processModelSearchOnChange = (selection: CarbonComboBoxSelection) => {
const processModel = selection.selectedItem;
navigate(
`/admin/process-models/${processModel.process_group_id}/${processModel.id}`
);
};
return (
<form onSubmit={function hey() {}}>
<h3>Search</h3>
<Form.Group>
<InputGroup>
<InputGroup.Text className="text-nowrap">
Process Model:{' '}
</InputGroup.Text>
<Typeahead
style={{ width: 500 }}
id="process-model-selection"
labelKey="label"
onChange={processModelSearchOnChange}
// for cypress tests since data-qa does not work
inputProps={{
name: 'process-model-selection',
}}
options={processModeleSelectionOptions}
placeholder="Choose a process model..."
/>
</InputGroup>
</Form.Group>
</form>
<ProcessModelSearch
onChange={processModelSearchOnChange}
processModels={processModelAvailableItems}
titleText="Process model search"
/>
);
};
@ -133,7 +136,9 @@ export default function ProcessGroupList() {
return (
<>
<ProcessBreadcrumb hotCrumbs={[['Process Groups']]} />
<Button href="/admin/process-groups/new">Add a process group</Button>
<Button kind="secondary" href="/admin/process-groups/new">
Add a process group
</Button>
<br />
<br />
{processModelSearchArea()}

View File

@ -1,71 +1,24 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import { slugifyString } from '../helpers';
import HttpService from '../services/HttpService';
import ProcessGroupForm from '../components/ProcessGroupForm';
import { ProcessGroup } from '../interfaces';
export default function ProcessGroupNew() {
const [identifier, setIdentifier] = useState('');
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] = useState(false);
const [displayName, setDisplayName] = useState('');
const navigate = useNavigate();
const navigateToProcessGroup = (_result: any) => {
navigate(`/admin/process-groups/${identifier}`);
};
const addProcessGroup = (event: any) => {
event.preventDefault();
HttpService.makeCallToBackend({
path: `/process-groups`,
successCallback: navigateToProcessGroup,
httpMethod: 'POST',
postBody: {
id: identifier,
display_name: displayName,
},
});
};
const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayName(newDisplayName);
if (!idHasBeenUpdatedByUser) {
setIdentifier(slugifyString(newDisplayName));
}
};
const [processGroup, setProcessGroup] = useState<ProcessGroup>({
id: '',
display_name: '',
description: '',
});
return (
<>
<ProcessBreadcrumb />
<h2>Add Process Group</h2>
<Form onSubmit={addProcessGroup}>
<Form.Group className="mb-3" controlId="display_name">
<Form.Label>Display Name:</Form.Label>
<Form.Control
type="text"
name="display_name"
value={displayName}
onChange={(e) => onDisplayNameChanged(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="identifier">
<Form.Label>ID:</Form.Label>
<Form.Control
type="text"
name="id"
value={identifier}
onChange={(e) => {
setIdentifier(e.target.value);
setIdHasBeenUpdatedByUser(true);
}}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
<ProcessGroupForm
mode="new"
processGroup={processGroup}
setProcessGroup={setProcessGroup}
/>
</>
);
}

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { Link, useSearchParams, useParams } from 'react-router-dom';
import { Button, Table, Stack } from 'react-bootstrap';
// @ts-ignore
import { Button, Table, Stack } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import PaginationForTable from '../components/PaginationForTable';
import HttpService from '../services/HttpService';
@ -81,7 +82,7 @@ export default function ProcessGroupShow() {
]}
/>
<ul>
<Stack direction="horizontal" gap={3}>
<Stack orientation="horizontal" gap={3}>
<Button
href={`/admin/process-models/${(processGroup as any).id}/new`}
>

View File

@ -6,14 +6,27 @@ import {
useSearchParams,
} from 'react-router-dom';
import { Button, Table, Stack, Form, InputGroup } from 'react-bootstrap';
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'reac... Remove this comment to see the full error message
import DatePicker from 'react-datepicker';
import { Typeahead } from 'react-bootstrap-typeahead';
import { Option } from 'react-bootstrap-typeahead/types/types';
import { PROCESS_STATUSES, DATE_FORMAT } from '../config';
// @ts-ignore
import { Filter } from '@carbon/icons-react';
import {
convertDateToSeconds,
Button,
ButtonSet,
DatePicker,
DatePickerInput,
Table,
Grid,
Column,
MultiSelect,
// TableHeader,
// TableHead,
// TableRow,
// TableBody,
// TableCell,
// @ts-ignore
} from '@carbon/react';
import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config';
import {
convertDateStringToSeconds,
convertSecondsToFormattedDate,
getPageInfoFromSearchParams,
getProcessModelFullIdentifierFromSearchParams,
@ -27,6 +40,8 @@ import HttpService from '../services/HttpService';
import 'react-bootstrap-typeahead/css/Typeahead.css';
import 'react-bootstrap-typeahead/css/Typeahead.bs5.css';
import { PaginationObject, ProcessModel } from '../interfaces';
import ProcessModelSearch from '../components/ProcessModelSearch';
export default function ProcessInstanceList() {
const params = useParams();
@ -34,36 +49,38 @@ export default function ProcessInstanceList() {
const navigate = useNavigate();
const [processInstances, setProcessInstances] = useState([]);
const [pagination, setPagination] = useState(null);
const [pagination, setPagination] = useState<PaginationObject | null>(null);
const oneHourInSeconds = 3600;
const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
const [startFrom, setStartFrom] = useState(null);
const [startTill, setStartTill] = useState(null);
const [endFrom, setEndFrom] = useState(null);
const [endTill, setEndTill] = useState(null);
const [startFrom, setStartFrom] = useState<string>('');
const [startTo, setStartTo] = useState<string>('');
const [endFrom, setEndFrom] = useState<string>('');
const [endTo, setEndTo] = useState<string>('');
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [processStatuseSelectionOptions, setProcessStatusSelectionOptions] =
useState<any[]>([]);
const [processStatusSelection, setProcessStatusSelection] = useState<
Option[]
>([]);
const [processModeleSelectionOptions, setProcessModelSelectionOptions] =
useState([]);
const [processModelSelection, setProcessModelSelection] = useState<Option[]>(
const [processStatusAllOptions, setProcessStatusAllOptions] = useState<any[]>(
[]
);
const [processStatusSelection, setProcessStatusSelection] = useState<
string[]
>([]);
const [processModelAvailableItems, setProcessModelAvailableItems] = useState<
ProcessModel[]
>([]);
const [processModelSelection, setProcessModelSelection] =
useState<ProcessModel | null>(null);
const parametersToAlwaysFilterBy = useMemo(() => {
return {
start_from: setStartFrom,
start_till: setStartTill,
start_to: setStartTo,
end_from: setEndFrom,
end_till: setEndTill,
end_to: setEndTo,
};
}, [setStartFrom, setStartTill, setEndFrom, setEndTill]);
}, [setStartFrom, setStartTo, setEndFrom, setEndTo]);
const parametersToGetFromSearchParams = useMemo(() => {
return {
@ -90,7 +107,10 @@ export default function ProcessInstanceList() {
const searchParamValue = searchParams.get(paramName);
if (searchParamValue) {
queryParamString += `&${paramName}=${searchParamValue}`;
functionToCall(searchParamValue);
const dateString = convertSecondsToFormattedDate(
searchParamValue as any
);
functionToCall(dateString);
}
});
@ -118,24 +138,24 @@ export default function ProcessInstanceList() {
const label = `${item.process_group_id}/${item.id}`;
Object.assign(item, { label });
if (label === processModelFullIdentifier) {
setProcessModelSelection([item]);
setProcessModelSelection(item);
}
return item;
});
setProcessModelSelectionOptions(selectionArray);
setProcessModelAvailableItems(selectionArray);
const processStatusSelectedArray: Option[] = [];
const processStatusSelectionArray = PROCESS_STATUSES.map(
const processStatusSelectedArray: string[] = [];
const processStatusAllOptionsArray = PROCESS_STATUSES.map(
(processStatusOption: any) => {
const regex = new RegExp(`\\b${processStatusOption}\\b`);
if ((searchParams.get('process_status') || '').match(regex)) {
processStatusSelectedArray.push({ label: processStatusOption });
processStatusSelectedArray.push(processStatusOption);
}
return { label: processStatusOption };
return processStatusOption;
}
);
setProcessStatusSelection(processStatusSelectedArray);
setProcessStatusSelectionOptions(processStatusSelectionArray);
setProcessStatusAllOptions(processStatusAllOptionsArray);
getProcessInstances();
}
@ -170,51 +190,58 @@ export default function ProcessInstanceList() {
}
};
const handleFilter = (event: any) => {
const applyFilter = (event: any) => {
event.preventDefault();
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
let queryParamString = `per_page=${perPage}&page=${page}`;
if (isTrueComparison(startFrom, '>', startTill)) {
setErrorMessage({ message: 'startFrom cannot be after startTill' });
const startFromSeconds = convertDateStringToSeconds(startFrom);
const endFromSeconds = convertDateStringToSeconds(endFrom);
const startToSeconds = convertDateStringToSeconds(startTo);
const endToSeconds = convertDateStringToSeconds(endTo);
if (isTrueComparison(startFromSeconds, '>', startToSeconds)) {
setErrorMessage({
message: '"Start date from" cannot be after "start date to"',
});
return;
}
if (isTrueComparison(endFrom, '>', endTill)) {
setErrorMessage({ message: 'endFrom cannot be after endTill' });
if (isTrueComparison(endFromSeconds, '>', endToSeconds)) {
setErrorMessage({
message: '"End date from" cannot be after "end date to"',
});
return;
}
if (isTrueComparison(startFrom, '>', endFrom)) {
setErrorMessage({ message: 'startFrom cannot be after endFrom' });
if (isTrueComparison(startFromSeconds, '>', endFromSeconds)) {
setErrorMessage({
message: '"Start date from" cannot be after "end date from"',
});
return;
}
if (isTrueComparison(startTill, '>', endTill)) {
setErrorMessage({ message: 'startTill cannot be after endTill' });
if (isTrueComparison(startToSeconds, '>', endToSeconds)) {
setErrorMessage({
message: '"Start date to" cannot be after "end date to"',
});
return;
}
if (startFrom) {
queryParamString += `&start_from=${startFrom}`;
if (startFromSeconds) {
queryParamString += `&start_from=${startFromSeconds}`;
}
if (startTill) {
queryParamString += `&start_till=${startTill}`;
if (startToSeconds) {
queryParamString += `&start_to=${startToSeconds}`;
}
if (endFrom) {
queryParamString += `&end_from=${endFrom}`;
if (endFromSeconds) {
queryParamString += `&end_from=${endFromSeconds}`;
}
if (endTill) {
queryParamString += `&end_till=${endTill}`;
if (endToSeconds) {
queryParamString += `&end_to=${endToSeconds}`;
}
if (processStatusSelection.length > 0) {
const processStatusSelectionString = processStatusSelection.map(
(pss: any) => {
return pss.label;
}
);
queryParamString += `&process_status=${processStatusSelectionString}`;
queryParamString += `&process_status=${processStatusSelection}`;
}
if (processModelSelection.length > 0) {
const currentProcessModel: any = processModelSelection[0];
queryParamString += `&process_group_identifier=${currentProcessModel.process_group_id}&process_model_identifier=${currentProcessModel.id}`;
if (processModelSelection) {
queryParamString += `&process_group_identifier=${processModelSelection.process_group_id}&process_model_identifier=${processModelSelection.id}`;
}
setErrorMessage(null);
@ -227,30 +254,22 @@ export default function ProcessInstanceList() {
initialDate: any,
onChangeFunction: any
) => {
let selectedDate = null;
if (initialDate) {
selectedDate = new Date(initialDate * 1000);
}
return (
<Form.Group>
<InputGroup>
<Stack className="ms-auto" direction="horizontal" gap={3}>
<InputGroup.Text className="text-nowrap">
{labelString}
{'\u00A0'}
</InputGroup.Text>
<DatePicker
id={`date-picker-${name}`}
selected={selectedDate}
onChange={(date: any) =>
convertDateToSeconds(date, onChangeFunction)
}
showTimeSelect
dateFormat={DATE_FORMAT}
/>
</Stack>
</InputGroup>
</Form.Group>
<DatePicker dateFormat={DATE_FORMAT_CARBON} datePickerType="single">
<DatePickerInput
id={`date-picker-${name}`}
placeholder={DATE_FORMAT}
labelText={labelString}
type="text"
size="md"
autocomplete="off"
allowInput={false}
onChange={(dateChangeEvent: any) => {
onChangeFunction(dateChangeEvent.srcElement.value);
}}
value={initialDate}
/>
</DatePicker>
);
};
@ -273,96 +292,88 @@ export default function ProcessInstanceList() {
return queryParamString;
};
const processModelSearch = () => {
const processStatusSearch = () => {
return (
<Form.Group>
<InputGroup>
<InputGroup.Text className="text-nowrap">
Process Model:{' '}
</InputGroup.Text>
<Typeahead
style={{ width: 500 }}
id="process-model-selection"
labelKey="label"
onChange={setProcessModelSelection}
options={processModeleSelectionOptions}
placeholder="Choose a process model..."
selected={processModelSelection}
/>
</InputGroup>
</Form.Group>
<MultiSelect
label="Choose Status"
id="process-instance-status-select"
titleText="Status"
items={processStatusAllOptions}
onChange={(selection: any) => {
setProcessStatusSelection(selection.selectedItems);
}}
itemToString={(item: any) => {
return item || '';
}}
selectionFeedback="top-after-reopen"
selectedItems={processStatusSelection}
/>
);
};
const processStatusSearch = () => {
return (
<Form.Group>
<InputGroup>
<InputGroup.Text className="text-nowrap">
Process Status:{' '}
</InputGroup.Text>
<Typeahead
multiple
style={{ width: 500 }}
id="process-status-selection"
// for cypress tests since data-qa does not work
inputProps={{
name: 'process-status-selection',
}}
labelKey="label"
onChange={setProcessStatusSelection}
options={processStatuseSelectionOptions}
placeholder="Choose process statuses..."
selected={processStatusSelection}
/>
</InputGroup>
</Form.Group>
);
const clearFilters = () => {
setProcessModelSelection(null);
setProcessStatusSelection([]);
setStartFrom('');
setStartTo('');
setEndFrom('');
setEndTo('');
};
const filterOptions = () => {
if (!showFilterOptions) {
return null;
}
return (
<div className="container">
<div className="row">
<div className="col">
<form onSubmit={handleFilter}>
<Stack direction="horizontal" gap={3}>
{processModelSearch()}
</Stack>
<br />
<Stack direction="horizontal" gap={3}>
{dateComponent(
'Start Range: ',
'start-from',
startFrom,
setStartFrom
)}
{dateComponent('-', 'start-till', startTill, setStartTill)}
</Stack>
<br />
<Stack direction="horizontal" gap={3}>
{dateComponent(
'End Range: \u00A0\u00A0',
'end-from',
endFrom,
setEndFrom
)}
{dateComponent('-', 'end-till', endTill, setEndTill)}
</Stack>
<br />
<Stack direction="horizontal" gap={3}>
{processStatusSearch()}
</Stack>
<Stack direction="horizontal" gap={3}>
<Button className="ms-auto" variant="secondary" type="submit">
Filter
</Button>
</Stack>
</form>
</div>
<div className="col" />
</div>
</div>
<>
<Grid fullWidth className="with-bottom-margin">
<Column md={8}>
<ProcessModelSearch
onChange={(selection: any) =>
setProcessModelSelection(selection.selectedItem)
}
processModels={processModelAvailableItems}
selectedItem={processModelSelection}
/>
</Column>
<Column md={8}>{processStatusSearch()}</Column>
</Grid>
<Grid fullWidth className="with-bottom-margin">
<Column md={4}>
{dateComponent(
'Start date from',
'start-from',
startFrom,
setStartFrom
)}
</Column>
<Column md={4}>
{dateComponent('Start date to', 'start-to', startTo, setStartTo)}
</Column>
<Column md={4}>
{dateComponent('End date from', 'end-from', endFrom, setEndFrom)}
</Column>
<Column md={4}>
{dateComponent('End date to', 'end-to', endTo, setEndTo)}
</Column>
</Grid>
<Grid fullWidth className="with-bottom-margin">
<Column md={4}>
<ButtonSet>
<Button
kind=""
className="button-white-background"
onClick={clearFilters}
>
Clear
</Button>
<Button kind="secondary" onClick={applyFilter}>
Filter
</Button>
</ButtonSet>
</Column>
</Grid>
</>
);
};
@ -404,7 +415,7 @@ export default function ProcessInstanceList() {
);
});
return (
<Table striped bordered>
<Table size="lg">
<thead>
<tr>
<th>Process Instance Id</th>
@ -436,20 +447,42 @@ export default function ProcessInstanceList() {
);
};
const toggleShowFilterOptions = () => {
setShowFilterOptions(!showFilterOptions);
};
if (pagination) {
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
return (
<>
{processInstanceTitleElement()}
<Grid fullWidth>
<Column lg={15} />
<Column lg={1}>
<Button
kind="ghost"
renderIcon={Filter}
iconDescription="Filter Options"
hasIconOnly
size="lg"
onClick={toggleShowFilterOptions}
/>
</Column>
</Grid>
{filterOptions()}
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
queryParamString={getSearchParamsAsQueryString()}
path="/admin/process-instances"
/>
<br />
<Grid fullWidth>
<Column lg={16}>
<PaginationForTable
page={page}
perPage={perPage}
pagination={pagination}
tableToDisplay={buildTable()}
queryParamString={getSearchParamsAsQueryString()}
path="/admin/process-instances"
/>
</Column>
</Grid>
</>
);
}

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Table } from 'react-bootstrap';
// @ts-ignore
import { Table } from '@carbon/react';
import { useParams, useSearchParams, Link } from 'react-router-dom';
import PaginationForTable from '../components/PaginationForTable';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Button, Table } from 'react-bootstrap';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { useParams, Link } from 'react-router-dom';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';

View File

@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { Button, Table } from 'react-bootstrap';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import PaginationForTable from '../components/PaginationForTable';

View File

@ -1,7 +1,8 @@
import { useContext, useEffect, useState } from 'react';
import Editor from '@monaco-editor/react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { Button, Modal, Stack } from 'react-bootstrap';
// @ts-ignore
import { Button, Modal, Stack } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ReactDiagramEditor from '../components/ReactDiagramEditor';
@ -408,15 +409,15 @@ export default function ProcessInstanceShow() {
const taskToUse: any = { ...taskToDisplay, data: taskDataToDisplay };
if (taskToDisplay) {
return (
<Modal show={!!taskToUse} onHide={handleTaskDataDisplayClose}>
<Modal.Header closeButton>
<Modal.Title>
<Stack direction="horizontal" gap={2}>
{taskToUse.name} ({taskToUse.type}): {taskToUse.state}
{taskDataButtons(taskToUse)}
</Stack>
</Modal.Title>
</Modal.Header>
<Modal
open={!!taskToUse}
passiveModal
onRequestClose={handleTaskDataDisplayClose}
>
<Stack orientation="horizontal" gap={2}>
{taskToUse.name} ({taskToUse.type}): {taskToUse.state}
{taskDataButtons(taskToUse)}
</Stack>
{taskDataContainer()}
</Modal>
);
@ -435,7 +436,7 @@ export default function ProcessInstanceShow() {
processGroupId={params.process_group_id}
linkProcessModel
/>
<Stack direction="horizontal" gap={3}>
<Stack orientation="horizontal" gap={3}>
<h2>Process Instance Id: {processInstanceToUse.id}</h2>
<ButtonWithConfirmation
description="Delete Process Instance?"

View File

@ -1,6 +1,7 @@
import { useState, useEffect, useContext } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, Stack } from 'react-bootstrap';
// @ts-ignore
import { Button, Stack } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
@ -79,7 +80,7 @@ export default function ProcessModelEdit() {
/>
<br />
<br />
<Stack direction="horizontal" gap={3}>
<Stack orientation="horizontal" gap={3}>
<Button type="submit">Submit</Button>
<Button variant="secondary" href={`/admin/${processModelPath}`}>
Cancel

View File

@ -1,7 +1,8 @@
import { useContext, useEffect, useRef, useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { Button, Modal, Stack } from 'react-bootstrap';
import Container from 'react-bootstrap/Container';
// @ts-ignore
import { Button, Modal, Stack, Content } from '@carbon/react';
// import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
@ -193,31 +194,25 @@ export default function ProcessModelEditDiagram() {
const newFileNameBox = () => {
const fileExtension = `.${searchParams.get('file_type')}`;
return (
<Modal show={showFileNameEditor} onHide={handleFileNameCancel}>
<Modal.Header closeButton>
<Modal.Title>Process Model File Name</Modal.Title>
</Modal.Header>
<form onSubmit={handleFileNameSave}>
<label>File Name:</label>
<span>
<input
name="file_name"
type="text"
value={newFileName}
onChange={(e) => setNewFileName(e.target.value)}
autoFocus
/>
{fileExtension}
</span>
<Modal.Footer>
<Button variant="secondary" onClick={handleFileNameCancel}>
Cancel
</Button>
<Button variant="primary" type="submit">
Save Changes
</Button>
</Modal.Footer>
</form>
<Modal
open={showFileNameEditor}
modalHeading="Processs Model File Name"
primaryButtonText="Save Changes"
secondaryButtonText="Cancel"
onSecondarySubmit={handleFileNameCancel}
onRequestSubmit={handleFileNameSave}
>
<label>File Name:</label>
<span>
<input
name="file_name"
type="text"
value={newFileName}
onChange={(e) => setNewFileName(e.target.value)}
autoFocus
/>
{fileExtension}
</span>
</Modal>
);
};
@ -288,7 +283,6 @@ export default function ProcessModelEditDiagram() {
// we should update this to act like updating scripts
// where we pass an event to bpmn-js
setScriptModeling(modeling);
setScriptText(script || '');
setScriptType(scriptTypeString);
setScriptEventBus(eventBus);
@ -478,7 +472,7 @@ export default function ProcessModelEditDiagram() {
}
return (
<main>
<Container>
<Content>
<Row>
<Col xs={8}>
<Button variant="link" disabled style={{ fontSize: '1.5em' }}>
@ -519,11 +513,11 @@ export default function ProcessModelEditDiagram() {
</Col>
<Col xs={1}>{scriptUnitTestResultBoolElement}</Col>
</Row>
</Container>
<Stack direction="horizontal" gap={3}>
</Content>
<Stack orientation="horizontal" gap={3}>
{unitTestFailureElement()}
</Stack>
<Stack direction="horizontal" gap={3}>
<Stack orientation="horizontal" gap={3}>
<Stack>
<div>Input Json:</div>
<div>
@ -564,28 +558,25 @@ export default function ProcessModelEditDiagram() {
if (scriptElement) {
scriptName = (scriptElement as any).di.bpmnElement.name;
}
return (
<Modal size="xl" show={showScriptEditor} onHide={handleScriptEditorClose}>
<Modal.Header closeButton>
<Modal.Title>Editing Script: {scriptName}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Editor
height={500}
width="auto"
options={generalEditorOptions()}
defaultLanguage="python"
defaultValue={scriptText}
onChange={handleEditorScriptChange}
onMount={handleEditorDidMount}
/>
{scriptUnitTestEditorElement()}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleScriptEditorClose}>
Close
</Button>
</Modal.Footer>
<Modal
open={showScriptEditor}
modalHeading={`Editing Script: ${scriptName}`}
primaryButtonText="Close"
onRequestSubmit={handleScriptEditorClose}
size="lg"
>
<Editor
height={500}
width="auto"
options={generalEditorOptions()}
defaultLanguage="python"
value={scriptText}
onChange={handleEditorScriptChange}
onMount={handleEditorDidMount}
/>
{scriptUnitTestEditorElement()}
</Modal>
);
};

View File

@ -1,6 +1,7 @@
import { useContext, useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import { Button, Stack } from 'react-bootstrap';
// @ts-ignore
import { Button, Stack } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import FileInput from '../components/FileInput';
import HttpService from '../services/HttpService';
@ -210,7 +211,7 @@ export default function ProcessModelShow() {
const processModelButtons = () => {
return (
<Stack direction="horizontal" gap={3}>
<Stack orientation="horizontal" gap={3}>
<Button onClick={processInstanceCreateAndRun} variant="primary">
Run
</Button>

View File

@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import Editor from '@monaco-editor/react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { Button, Modal } from 'react-bootstrap';
// @ts-ignore
import { Button, Modal } from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
@ -118,31 +119,25 @@ export default function ReactFormEditor() {
const newFileNameBox = () => {
return (
<Modal show={showFileNameEditor} onHide={handleFileNameCancel}>
<Modal.Header closeButton>
<Modal.Title>Process Model File Name</Modal.Title>
</Modal.Header>
<form onSubmit={handleFileNameSave}>
<label>File Name:</label>
<span>
<input
name="file_name"
type="text"
value={newFileName}
onChange={(e) => setNewFileName(e.target.value)}
autoFocus
/>
.{fileExtension}
</span>
<Modal.Footer>
<Button variant="secondary" onClick={handleFileNameCancel}>
Cancel
</Button>
<Button variant="primary" type="submit">
Save Changes
</Button>
</Modal.Footer>
</form>
<Modal
open={showFileNameEditor}
modalHeading="Processs Model File Name"
primaryButtonText="Save Changes"
secondaryButtonText="Cancel"
onSecondarySubmit={handleFileNameCancel}
onRequestSubmit={handleFileNameSave}
>
<label>File Name:</label>
<span>
<input
name="file_name"
type="text"
value={newFileName}
onChange={(e) => setNewFileName(e.target.value)}
autoFocus
/>
{fileExtension}
</span>
</Modal>
);
};
@ -160,7 +155,7 @@ export default function ReactFormEditor() {
{processModelFile ? `: ${(processModelFile as any).name}` : ''}
</h2>
{newFileNameBox()}
<Button onClick={saveFile} variant="danger">
<Button onClick={saveFile} variant="danger" data-qa="file-save-button">
Save
</Button>
{params.file_name ? (

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { Button, Table } from 'react-bootstrap';
// @ts-ignore
import { Button, Table } from '@carbon/react';
import { MdDelete } from 'react-icons/md';
import PaginationForTable from '../components/PaginationForTable';
import HttpService from '../services/HttpService';

View File

@ -1,6 +1,7 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Stack } from 'react-bootstrap';
// @ts-ignore
import { Stack } from '@carbon/react';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import HttpService from '../services/HttpService';
@ -67,7 +68,7 @@ export default function SecretNew() {
}}
/>
</Form.Group>
<Stack direction="horizontal" gap={3}>
<Stack orientation="horizontal" gap={3}>
<Button variant="primary" type="submit">
Submit
</Button>

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Stack, Table, Button } from 'react-bootstrap';
// @ts-ignore
import { Stack, Table, Button } from '@carbon/react';
import HttpService from '../services/HttpService';
import { Secret } from '../interfaces';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
@ -64,8 +65,8 @@ export default function SecretShow() {
if (secret) {
return (
<>
<Stack direction="horizontal" gap={3}>
<h2>Secret Key: {secret.key}</h2>
<h2>Secret Key: {secret.key}</h2>
<Stack orientation="horizontal" gap={3}>
<ButtonWithConfirmation
description="Delete Secret?"
onConfirmation={deleteSecret}

View File

@ -1,7 +1,8 @@
import { useContext, useEffect, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import Form from '@rjsf/core';
import { Button, Stack } from 'react-bootstrap';
// @ts-ignore
import { Button, Stack } from '@carbon/react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
@ -84,7 +85,7 @@ export default function TaskShow() {
});
}
return (
<Stack direction="horizontal" gap={3}>
<Stack orientation="horizontal" gap={3}>
<Button href="/tasks">Go Back To List</Button>
{userTasksElement}
</Stack>