Feature/user preference extension (#472)
* added ability to display navigation items in user profile toggle * updated naming of some extension elements * added user property table and updates for extensions to use it w/ burnettk * moved extension ui interfaces to own file and linting issues * some updates to render markdown results on load w/ burnettk * added migration merge file w/ burnettk * moved code to fix linting issues w/ burnettk * resolved db migration conflict * removed unnecessary migrations and added just one w/ burnettk --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
655d384645
commit
4cf33b62fc
|
@ -28,8 +28,8 @@ if [[ -n "${SPIFFWORKFLOW_BACKEND_DATABASE_URI:-}" ]]; then
|
|||
database_host=$(grep -oP "^[^:]+://.*@\K(.+?)[:/]" <<<"$SPIFFWORKFLOW_BACKEND_DATABASE_URI" | sed -E 's/[:\/]$//')
|
||||
fi
|
||||
|
||||
# this will fix branching conflicts
|
||||
# poetry run flask db merge heads -m "merging two heads"
|
||||
# uncomment this line to fix branching conflicts
|
||||
# poetry run flask db merge heads -m "merging heads"
|
||||
|
||||
tasks=""
|
||||
if [[ "${1:-}" == "clean" ]]; then
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 844cee572018
|
||||
Revises: 57df21dc569d
|
||||
Create Date: 2023-09-07 14:18:12.357989
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '844cee572018'
|
||||
down_revision = '57df21dc569d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('user_property',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(length=255), nullable=False),
|
||||
sa.Column('value', sa.String(length=255), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('user_id', 'key', name='user_id_key_uniq')
|
||||
)
|
||||
with op.batch_alter_table('user_property', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_user_property_key'), ['key'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_user_property_user_id'), ['user_id'], unique=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user_property', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_user_property_user_id'))
|
||||
batch_op.drop_index(batch_op.f('ix_user_property_key'))
|
||||
|
||||
op.drop_table('user_property')
|
||||
# ### end Alembic commands ###
|
|
@ -88,5 +88,8 @@ from spiffworkflow_backend.models.task_draft_data import (
|
|||
from spiffworkflow_backend.models.configuration import (
|
||||
ConfigurationModel,
|
||||
) # noqa: F401
|
||||
from spiffworkflow_backend.models.user_property import (
|
||||
UserPropertyModel,
|
||||
) # noqa: F401
|
||||
|
||||
add_listeners()
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import ForeignKey
|
||||
|
||||
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||
from spiffworkflow_backend.models.db import db
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserPropertyModel(SpiffworkflowBaseDBModel):
|
||||
__tablename__ = "user_property"
|
||||
__table_args__ = (db.UniqueConstraint("user_id", "key", name="user_id_key_uniq"),)
|
||||
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
user_id: int = db.Column(ForeignKey(UserModel.id), nullable=False, index=True) # type: ignore
|
||||
key: str = db.Column(db.String(255), nullable=False, index=True)
|
||||
value: str | None = db.Column(db.String(255))
|
|
@ -21,6 +21,7 @@ from spiffworkflow_backend.services.process_instance_processor import CustomBpmn
|
|||
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
|
||||
from spiffworkflow_backend.services.process_instance_queue_service import ProcessInstanceIsAlreadyLockedError
|
||||
from spiffworkflow_backend.services.process_instance_queue_service import ProcessInstanceIsNotEnqueuedError
|
||||
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
|
||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
||||
from spiffworkflow_backend.services.workflow_execution_service import WorkflowExecutionServiceError
|
||||
|
@ -121,17 +122,25 @@ def _run_extension(
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
ui_schema_page_definition = None
|
||||
if body and "ui_schema_page_definition" in body:
|
||||
ui_schema_page_definition = body["ui_schema_page_definition"]
|
||||
ui_schema_action = None
|
||||
persistence_level = "none"
|
||||
if body and "ui_schema_action" in body:
|
||||
ui_schema_action = body["ui_schema_action"]
|
||||
persistence_level = ui_schema_action.get("persistence_level", "none")
|
||||
|
||||
process_instance = ProcessInstanceModel(
|
||||
status=ProcessInstanceStatus.not_started.value,
|
||||
process_initiator_id=g.user.id,
|
||||
process_model_identifier=process_model.id,
|
||||
process_model_display_name=process_model.display_name,
|
||||
persistence_level="none",
|
||||
)
|
||||
process_instance = None
|
||||
if persistence_level == "none":
|
||||
process_instance = ProcessInstanceModel(
|
||||
status=ProcessInstanceStatus.not_started.value,
|
||||
process_initiator_id=g.user.id,
|
||||
process_model_identifier=process_model.id,
|
||||
process_model_display_name=process_model.display_name,
|
||||
persistence_level=persistence_level,
|
||||
)
|
||||
else:
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_identifier, g.user
|
||||
)
|
||||
|
||||
processor = None
|
||||
try:
|
||||
|
@ -170,10 +179,10 @@ def _run_extension(
|
|||
task_data = processor.get_data()
|
||||
result: dict[str, Any] = {"task_data": task_data}
|
||||
|
||||
if ui_schema_page_definition:
|
||||
if "results_markdown_filename" in ui_schema_page_definition:
|
||||
if ui_schema_action:
|
||||
if "results_markdown_filename" in ui_schema_action:
|
||||
file_contents = SpecFileService.get_data(
|
||||
process_model, ui_schema_page_definition["results_markdown_filename"]
|
||||
process_model, ui_schema_action["results_markdown_filename"]
|
||||
).decode("utf-8")
|
||||
form_contents = JinjaService.render_jinja_template(file_contents, task_data=task_data)
|
||||
result["rendered_results_markdown"] = form_contents
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
from typing import Any
|
||||
|
||||
from flask import g
|
||||
from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext
|
||||
from spiffworkflow_backend.models.user_property import UserPropertyModel
|
||||
from spiffworkflow_backend.scripts.script import Script
|
||||
|
||||
|
||||
class GetUserProperties(Script):
|
||||
@staticmethod
|
||||
def requires_privileged_permissions() -> bool:
|
||||
"""We have deemed this function safe to run without elevated permissions."""
|
||||
return False
|
||||
|
||||
def get_description(self) -> str:
|
||||
return """Gets the user properties for current user."""
|
||||
|
||||
def run(self, script_attributes_context: ScriptAttributesContext, *_args: Any, **kwargs: Any) -> Any:
|
||||
user_properties = UserPropertyModel.query.filter_by(user_id=g.user.id).all()
|
||||
dict_to_return = {}
|
||||
for up in user_properties:
|
||||
dict_to_return[up.key] = up.value
|
||||
return dict_to_return
|
|
@ -31,6 +31,10 @@ class ProcessModelIdentifierMissingError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class InvalidArgsGivenToScriptError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Script:
|
||||
"""Provides an abstract class that defines how scripts should work, this must be extended in all Script Tasks."""
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
from typing import Any
|
||||
|
||||
from flask import g
|
||||
from spiffworkflow_backend.models.db import db
|
||||
from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext
|
||||
from spiffworkflow_backend.models.user_property import UserPropertyModel
|
||||
from spiffworkflow_backend.scripts.script import InvalidArgsGivenToScriptError
|
||||
from spiffworkflow_backend.scripts.script import Script
|
||||
|
||||
|
||||
class SetUserProperties(Script):
|
||||
@staticmethod
|
||||
def requires_privileged_permissions() -> bool:
|
||||
"""We have deemed this function safe to run without elevated permissions."""
|
||||
return False
|
||||
|
||||
def get_description(self) -> str:
|
||||
return """Sets given user properties on current user."""
|
||||
|
||||
def run(self, script_attributes_context: ScriptAttributesContext, *args: Any, **kwargs: Any) -> Any:
|
||||
properties = args[0]
|
||||
if not isinstance(properties, dict):
|
||||
raise InvalidArgsGivenToScriptError(
|
||||
f"Args to set_user_properties must be a dict. '{properties}' is invalid."
|
||||
)
|
||||
# consider using engine-specific insert or update metaphor in future: https://stackoverflow.com/a/68431412/6090676
|
||||
for property_key, property_value in properties.items():
|
||||
user_property = UserPropertyModel.query.filter_by(user_id=g.user.id, key=property_key).first()
|
||||
if user_property is None:
|
||||
user_property = UserPropertyModel(
|
||||
user_id=g.user.id,
|
||||
key=property_key,
|
||||
)
|
||||
user_property.value = property_value
|
||||
db.session.add(user_property)
|
||||
db.session.commit()
|
|
@ -48,11 +48,7 @@ export default function App() {
|
|||
<Route path="/admin/*" element={<AdminRoutes />} />
|
||||
<Route path="/editor/*" element={<EditorRoutes />} />
|
||||
<Route
|
||||
path="/extensions/:process_model"
|
||||
element={<Extension />}
|
||||
/>
|
||||
<Route
|
||||
path="/extensions/:process_model/:extension_route"
|
||||
path="/extensions/:page_identifier"
|
||||
element={<Extension />}
|
||||
/>
|
||||
</Routes>
|
||||
|
|
|
@ -24,13 +24,11 @@ import { Can } from '@casl/react';
|
|||
import logo from '../logo.svg';
|
||||
import UserService from '../services/UserService';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { PermissionsToCheck, ProcessModel, ProcessFile } from '../interfaces';
|
||||
import {
|
||||
PermissionsToCheck,
|
||||
ProcessModel,
|
||||
ProcessFile,
|
||||
ExtensionUiSchema,
|
||||
UiSchemaNavItem,
|
||||
} from '../interfaces';
|
||||
UiSchemaUxElement,
|
||||
} from '../extension_ui_schema_interfaces';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
import HttpService, { UnauthenticatedError } from '../services/HttpService';
|
||||
import { DOCUMENTATION_URL, SPIFF_ENVIRONMENT } from '../config';
|
||||
|
@ -49,7 +47,7 @@ export default function NavigationBar() {
|
|||
const location = useLocation();
|
||||
const [activeKey, setActiveKey] = useState<string>('');
|
||||
const [extensionNavigationItems, setExtensionNavigationItems] = useState<
|
||||
UiSchemaNavItem[] | null
|
||||
UiSchemaUxElement[] | null
|
||||
>(null);
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
|
@ -109,7 +107,7 @@ export default function NavigationBar() {
|
|||
}
|
||||
|
||||
const processExtensionResult = (processModels: ProcessModel[]) => {
|
||||
const eni: UiSchemaNavItem[] = processModels
|
||||
const eni: UiSchemaUxElement[] = processModels
|
||||
.map((processModel: ProcessModel) => {
|
||||
const extensionUiSchemaFile = processModel.files.find(
|
||||
(file: ProcessFile) => file.name === 'extension_uischema.json'
|
||||
|
@ -119,8 +117,8 @@ export default function NavigationBar() {
|
|||
const extensionUiSchema: ExtensionUiSchema = JSON.parse(
|
||||
extensionUiSchemaFile.file_contents
|
||||
);
|
||||
if (extensionUiSchema.navigation_items) {
|
||||
return extensionUiSchema.navigation_items;
|
||||
if (extensionUiSchema.ux_elements) {
|
||||
return extensionUiSchema.ux_elements;
|
||||
}
|
||||
} catch (jsonParseError: any) {
|
||||
console.error(
|
||||
|
@ -128,7 +126,7 @@ export default function NavigationBar() {
|
|||
);
|
||||
}
|
||||
}
|
||||
return [] as UiSchemaNavItem[];
|
||||
return [] as UiSchemaUxElement[];
|
||||
})
|
||||
.flat();
|
||||
if (eni) {
|
||||
|
@ -157,6 +155,27 @@ export default function NavigationBar() {
|
|||
const userEmail = UserService.getUserEmail();
|
||||
const username = UserService.getPreferredUsername();
|
||||
|
||||
const extensionNavigationElementsForDisplayLocation = (
|
||||
displayLocation: string,
|
||||
elementCallback: Function
|
||||
) => {
|
||||
if (!extensionNavigationItems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return extensionNavigationItems.map((uxElement: UiSchemaUxElement) => {
|
||||
if (uxElement.display_location === displayLocation) {
|
||||
return elementCallback(uxElement);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
const extensionUserProfileElement = (uxElement: UiSchemaUxElement) => {
|
||||
const navItemPage = `/extensions${uxElement.page}`;
|
||||
return <a href={navItemPage}>{uxElement.label}</a>;
|
||||
};
|
||||
|
||||
const profileToggletip = (
|
||||
<div style={{ display: 'flex' }} id="user-profile-toggletip">
|
||||
<Toggletip isTabTip align="bottom-right">
|
||||
|
@ -177,6 +196,10 @@ export default function NavigationBar() {
|
|||
<a target="_blank" href={documentationUrl} rel="noreferrer">
|
||||
Documentation
|
||||
</a>
|
||||
{extensionNavigationElementsForDisplayLocation(
|
||||
'user_profile_item',
|
||||
extensionUserProfileElement
|
||||
)}
|
||||
{!UserService.authenticationDisabled() ? (
|
||||
<>
|
||||
<hr />
|
||||
|
@ -258,27 +281,21 @@ export default function NavigationBar() {
|
|||
);
|
||||
};
|
||||
|
||||
const extensionNavigationElements = () => {
|
||||
if (!extensionNavigationItems) {
|
||||
return null;
|
||||
const extensionHeaderMenuItemElement = (uxElement: UiSchemaUxElement) => {
|
||||
const navItemPage = `/extensions${uxElement.page}`;
|
||||
const regexp = new RegExp(`^${navItemPage}$`);
|
||||
if (regexp.test(location.pathname)) {
|
||||
setActiveKey(navItemPage);
|
||||
}
|
||||
|
||||
return extensionNavigationItems.map((navItem: UiSchemaNavItem) => {
|
||||
const navItemRoute = `/extensions${navItem.route}`;
|
||||
const regexp = new RegExp(`^${navItemRoute}`);
|
||||
if (regexp.test(location.pathname)) {
|
||||
setActiveKey(navItemRoute);
|
||||
}
|
||||
return (
|
||||
<HeaderMenuItem
|
||||
href={navItemRoute}
|
||||
isCurrentPage={isActivePage(navItemRoute)}
|
||||
data-qa={`extension-${slugifyString(navItem.label)}`}
|
||||
>
|
||||
{navItem.label}
|
||||
</HeaderMenuItem>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<HeaderMenuItem
|
||||
href={navItemPage}
|
||||
isCurrentPage={isActivePage(navItemPage)}
|
||||
data-qa={`extension-${slugifyString(uxElement.label)}`}
|
||||
>
|
||||
{uxElement.label}
|
||||
</HeaderMenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
const headerMenuItems = () => {
|
||||
|
@ -328,7 +345,10 @@ export default function NavigationBar() {
|
|||
</HeaderMenuItem>
|
||||
</Can>
|
||||
{configurationElement()}
|
||||
{extensionNavigationElements()}
|
||||
{extensionNavigationElementsForDisplayLocation(
|
||||
'header_menu_item',
|
||||
extensionHeaderMenuItemElement
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
export enum UiSchemaDisplayLocation {
|
||||
header_menu_item = 'header_menu_item',
|
||||
user_profile_item = 'user_profile_item',
|
||||
}
|
||||
|
||||
export enum UiSchemaPersistenceLevel {
|
||||
full = 'full',
|
||||
none = 'none',
|
||||
}
|
||||
|
||||
export interface UiSchemaUxElement {
|
||||
label: string;
|
||||
page: string;
|
||||
display_location: UiSchemaDisplayLocation;
|
||||
}
|
||||
|
||||
export interface UiSchemaAction {
|
||||
api_path: string;
|
||||
|
||||
persistence_level?: UiSchemaPersistenceLevel;
|
||||
navigate_to_on_form_submit?: string;
|
||||
results_markdown_filename?: string;
|
||||
}
|
||||
|
||||
export interface UiSchemaPageDefinition {
|
||||
header: string;
|
||||
api: string;
|
||||
|
||||
on_load?: UiSchemaAction;
|
||||
on_form_submit?: UiSchemaAction;
|
||||
form_schema_filename?: any;
|
||||
form_ui_schema_filename?: any;
|
||||
markdown_instruction_filename?: string;
|
||||
navigate_to_on_form_submit?: string;
|
||||
}
|
||||
|
||||
export interface UiSchemaPage {
|
||||
[key: string]: UiSchemaPageDefinition;
|
||||
}
|
||||
|
||||
export interface ExtensionUiSchema {
|
||||
ux_elements?: UiSchemaUxElement[];
|
||||
pages: UiSchemaPage;
|
||||
}
|
||||
|
||||
export interface ExtensionPostBody {
|
||||
extension_input: any;
|
||||
ui_schema_action?: UiSchemaAction;
|
||||
}
|
|
@ -435,29 +435,3 @@ export interface DataStore {
|
|||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface UiSchemaNavItem {
|
||||
label: string;
|
||||
route: string;
|
||||
}
|
||||
export interface UiSchemaPageDefinition {
|
||||
header: string;
|
||||
api: string;
|
||||
|
||||
form_schema_filename?: any;
|
||||
form_ui_schema_filename?: any;
|
||||
markdown_instruction_filename?: string;
|
||||
navigate_to_on_form_submit?: string;
|
||||
}
|
||||
export interface UiSchemaRoute {
|
||||
[key: string]: UiSchemaPageDefinition;
|
||||
}
|
||||
export interface ExtensionUiSchema {
|
||||
navigation_items?: UiSchemaNavItem[];
|
||||
routes: UiSchemaRoute;
|
||||
}
|
||||
|
||||
export interface ExtensionPostBody {
|
||||
extension_input: any;
|
||||
ui_schema_page_definition?: UiSchemaPageDefinition;
|
||||
}
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import MDEditor from '@uiw/react-md-editor';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Editor } from '@monaco-editor/react';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import {
|
||||
ExtensionPostBody,
|
||||
ExtensionUiSchema,
|
||||
ProcessFile,
|
||||
ProcessModel,
|
||||
UiSchemaPageDefinition,
|
||||
} from '../interfaces';
|
||||
import { ProcessFile, ProcessModel } from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
import useAPIError from '../hooks/UseApiError';
|
||||
import { recursivelyChangeNullAndUndefined } from '../helpers';
|
||||
import CustomForm from '../components/CustomForm';
|
||||
import { BACKEND_BASE_URL } from '../config';
|
||||
import {
|
||||
ExtensionPostBody,
|
||||
ExtensionUiSchema,
|
||||
UiSchemaPageDefinition,
|
||||
} from '../extension_ui_schema_interfaces';
|
||||
import ErrorDisplay from '../components/ErrorDisplay';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
|
@ -26,7 +25,12 @@ export default function Extension() {
|
|||
const [formData, setFormData] = useState<any>(null);
|
||||
const [formButtonsDisabled, setFormButtonsDisabled] = useState(false);
|
||||
const [processedTaskData, setProcessedTaskData] = useState<any>(null);
|
||||
const [markdownToRender, setMarkdownToRender] = useState<string | null>(null);
|
||||
const [markdownToRenderOnSubmit, setMarkdownToRenderOnSubmit] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [markdownToRenderOnLoad, setMarkdownToRenderOnLoad] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [filesByName] = useState<{
|
||||
[key: string]: ProcessFile;
|
||||
}>({});
|
||||
|
@ -35,18 +39,15 @@ export default function Extension() {
|
|||
|
||||
const { addError, removeError } = useAPIError();
|
||||
|
||||
useEffect(() => {
|
||||
const processExtensionResult = (pm: ProcessModel) => {
|
||||
setProcessModel(pm);
|
||||
let extensionUiSchemaFile: ProcessFile | null = null;
|
||||
pm.files.forEach((file: ProcessFile) => {
|
||||
filesByName[file.name] = file;
|
||||
if (file.name === 'extension_uischema.json') {
|
||||
extensionUiSchemaFile = file;
|
||||
const setConfigsIfDesiredSchemaFile = useCallback(
|
||||
(extensionUiSchemaFile: ProcessFile | null, pm: ProcessModel) => {
|
||||
const processLoadResult = (result: any) => {
|
||||
setFormData(result.task_data);
|
||||
if (result.rendered_results_markdown) {
|
||||
setMarkdownToRenderOnLoad(result.rendered_results_markdown);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// typescript is really confused by extensionUiSchemaFile so force it since we are properly checking
|
||||
if (
|
||||
extensionUiSchemaFile &&
|
||||
(extensionUiSchemaFile as ProcessFile).file_contents
|
||||
|
@ -55,24 +56,61 @@ export default function Extension() {
|
|||
(extensionUiSchemaFile as any).file_contents
|
||||
);
|
||||
|
||||
let routeIdentifier = `/${params.process_model}`;
|
||||
if (params.extension_route) {
|
||||
routeIdentifier = `${routeIdentifier}/${params.extension_route}`;
|
||||
const pageIdentifier = `/${params.page_identifier}`;
|
||||
if (
|
||||
extensionUiSchema.pages &&
|
||||
Object.keys(extensionUiSchema.pages).includes(pageIdentifier)
|
||||
) {
|
||||
const pageDefinition = extensionUiSchema.pages[pageIdentifier];
|
||||
setUiSchemaPageDefinition(pageDefinition);
|
||||
setProcessModel(pm);
|
||||
|
||||
const postBody: ExtensionPostBody = { extension_input: {} };
|
||||
postBody.ui_schema_action = pageDefinition.on_load;
|
||||
if (pageDefinition.on_load) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `${targetUris.extensionListPath}/${pageDefinition.on_load.api_path}`,
|
||||
successCallback: processLoadResult,
|
||||
httpMethod: 'POST',
|
||||
postBody,
|
||||
});
|
||||
}
|
||||
}
|
||||
setUiSchemaPageDefinition(extensionUiSchema.routes[routeIdentifier]);
|
||||
}
|
||||
},
|
||||
[targetUris.extensionListPath, params]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const processExtensionResult = (processModels: ProcessModel[]) => {
|
||||
processModels.forEach((pm: ProcessModel) => {
|
||||
let extensionUiSchemaFile: ProcessFile | null = null;
|
||||
pm.files.forEach((file: ProcessFile) => {
|
||||
filesByName[file.name] = file;
|
||||
if (file.name === 'extension_uischema.json') {
|
||||
extensionUiSchemaFile = file;
|
||||
}
|
||||
});
|
||||
setConfigsIfDesiredSchemaFile(extensionUiSchemaFile, pm);
|
||||
});
|
||||
};
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: targetUris.extensionPath,
|
||||
path: targetUris.extensionListPath,
|
||||
successCallback: processExtensionResult,
|
||||
});
|
||||
}, [targetUris.extensionPath, params, filesByName]);
|
||||
}, [
|
||||
filesByName,
|
||||
params,
|
||||
setConfigsIfDesiredSchemaFile,
|
||||
targetUris.extensionListPath,
|
||||
targetUris.extensionPath,
|
||||
]);
|
||||
|
||||
const processSubmitResult = (result: any) => {
|
||||
setProcessedTaskData(result.task_data);
|
||||
if (result.rendered_results_markdown) {
|
||||
setMarkdownToRender(result.rendered_results_markdown);
|
||||
setMarkdownToRenderOnSubmit(result.rendered_results_markdown);
|
||||
}
|
||||
setFormButtonsDisabled(false);
|
||||
};
|
||||
|
@ -111,15 +149,15 @@ export default function Extension() {
|
|||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
const url = `${BACKEND_BASE_URL}/extensions-get-data/${params.process_model}/${optionString}`;
|
||||
const url = `${BACKEND_BASE_URL}/extensions-get-data/${params.page_identifier}/${optionString}`;
|
||||
window.location.href = url;
|
||||
setFormButtonsDisabled(false);
|
||||
} else {
|
||||
const postBody: ExtensionPostBody = { extension_input: dataToSubmit };
|
||||
let apiPath = targetUris.extensionPath;
|
||||
if (uiSchemaPageDefinition && uiSchemaPageDefinition.api) {
|
||||
apiPath = `${targetUris.extensionListPath}/${uiSchemaPageDefinition.api}`;
|
||||
postBody.ui_schema_page_definition = uiSchemaPageDefinition;
|
||||
if (uiSchemaPageDefinition && uiSchemaPageDefinition.on_form_submit) {
|
||||
apiPath = `${targetUris.extensionListPath}/${uiSchemaPageDefinition.on_form_submit.api_path}`;
|
||||
postBody.ui_schema_action = uiSchemaPageDefinition.on_form_submit;
|
||||
}
|
||||
|
||||
// NOTE: rjsf sets blanks values to undefined and JSON.stringify removes keys with undefined values
|
||||
|
@ -141,22 +179,30 @@ export default function Extension() {
|
|||
|
||||
if (uiSchemaPageDefinition) {
|
||||
const componentsToDisplay = [<h1>{uiSchemaPageDefinition.header}</h1>];
|
||||
const markdownContentsToRender = [];
|
||||
|
||||
if (uiSchemaPageDefinition.markdown_instruction_filename) {
|
||||
const markdownFile =
|
||||
filesByName[uiSchemaPageDefinition.markdown_instruction_filename];
|
||||
|
||||
if (markdownFile.file_contents) {
|
||||
componentsToDisplay.push(
|
||||
<div data-color-mode="light">
|
||||
<MDEditor.Markdown
|
||||
linkTarget="_blank"
|
||||
source={markdownFile.file_contents}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
markdownContentsToRender.push(markdownFile.file_contents);
|
||||
}
|
||||
}
|
||||
if (markdownToRenderOnLoad) {
|
||||
markdownContentsToRender.push(markdownToRenderOnLoad);
|
||||
}
|
||||
|
||||
if (markdownContentsToRender.length > 0) {
|
||||
componentsToDisplay.push(
|
||||
<div data-color-mode="light" className="with-bottom-margin">
|
||||
<MDEditor.Markdown
|
||||
linkTarget="_blank"
|
||||
source={markdownContentsToRender.join('\n')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (uiSchemaPageDefinition.form_schema_filename) {
|
||||
const formSchemaFile =
|
||||
|
@ -180,13 +226,13 @@ export default function Extension() {
|
|||
}
|
||||
}
|
||||
if (processedTaskData) {
|
||||
if (markdownToRender) {
|
||||
if (markdownToRenderOnSubmit) {
|
||||
componentsToDisplay.push(
|
||||
<div data-color-mode="light" className="with-top-margin">
|
||||
<MDEditor.Markdown
|
||||
className="onboarding"
|
||||
linkTarget="_blank"
|
||||
source={markdownToRender}
|
||||
source={markdownToRenderOnSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue