added some support to add process model metadata. need to fix frontend w/ burnettk

This commit is contained in:
jasquat 2022-12-02 15:46:05 -05:00
parent 20ca5a2978
commit 6b75fc32a3
6 changed files with 130 additions and 29 deletions

View File

@ -38,6 +38,7 @@ class ProcessModelInfo:
fault_or_suspend_on_exception: str = NotificationType.fault.value fault_or_suspend_on_exception: str = NotificationType.fault.value
exception_notification_addresses: list[str] = field(default_factory=list) exception_notification_addresses: list[str] = field(default_factory=list)
parent_groups: list[dict] | None = None parent_groups: list[dict] | None = None
metadata_extraction_paths: dict[str, str] | None = None
def __post_init__(self) -> None: def __post_init__(self) -> None:
"""__post_init__.""" """__post_init__."""
@ -76,6 +77,8 @@ class ProcessModelInfoSchema(Schema):
exception_notification_addresses = marshmallow.fields.List( exception_notification_addresses = marshmallow.fields.List(
marshmallow.fields.String marshmallow.fields.String
) )
metadata_extraction_paths = marshmallow.fields.Dict(keys=marshmallow.fields.Str(required=False), values=marshmallow.fields.Str(required=False), required=False)
@post_load @post_load
def make_spec( def make_spec(

View File

@ -261,19 +261,26 @@ def process_model_create(
modified_process_group_id: str, body: Dict[str, Union[str, bool, int]] modified_process_group_id: str, body: Dict[str, Union[str, bool, int]]
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Process_model_create.""" """Process_model_create."""
process_model_info = ProcessModelInfoSchema().load(body) body_include_list = [
"id",
"display_name",
"primary_file_name",
"primary_process_id",
"description",
"metadata_extraction_paths",
]
body_filtered = {
include_item: body[include_item]
for include_item in body_include_list
if include_item in body
}
if modified_process_group_id is None: if modified_process_group_id is None:
raise ApiError( raise ApiError(
error_code="process_group_id_not_specified", error_code="process_group_id_not_specified",
message="Process Model could not be created when process_group_id path param is unspecified", message="Process Model could not be created when process_group_id path param is unspecified",
status_code=400, status_code=400,
) )
if process_model_info is None:
raise ApiError(
error_code="process_model_could_not_be_created",
message=f"Process Model could not be created from given body: {body}",
status_code=400,
)
unmodified_process_group_id = un_modify_modified_process_model_id( unmodified_process_group_id = un_modify_modified_process_model_id(
modified_process_group_id modified_process_group_id
@ -286,6 +293,14 @@ def process_model_create(
status_code=400, status_code=400,
) )
process_model_info = ProcessModelInfo(**body_filtered) # type: ignore
if process_model_info is None:
raise ApiError(
error_code="process_model_could_not_be_created",
message=f"Process Model could not be created from given body: {body}",
status_code=400,
)
ProcessModelService.add_process_model(process_model_info) ProcessModelService.add_process_model(process_model_info)
return Response( return Response(
json.dumps(ProcessModelInfoSchema().dump(process_model_info)), json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
@ -299,7 +314,6 @@ def process_model_delete(
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Process_model_delete.""" """Process_model_delete."""
process_model_identifier = modified_process_model_identifier.replace(":", "/") process_model_identifier = modified_process_model_identifier.replace(":", "/")
# process_model_identifier = f"{process_group_id}/{process_model_id}"
ProcessModelService().process_model_delete(process_model_identifier) ProcessModelService().process_model_delete(process_model_identifier)
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
@ -314,6 +328,7 @@ def process_model_update(
"primary_file_name", "primary_file_name",
"primary_process_id", "primary_process_id",
"description", "description",
"metadata_extraction_paths",
] ]
body_filtered = { body_filtered = {
include_item: body[include_item] include_item: body[include_item]
@ -321,7 +336,6 @@ def process_model_update(
if include_item in body if include_item in body
} }
# process_model_identifier = f"{process_group_id}/{process_model_id}"
process_model = get_process_model(process_model_identifier) process_model = get_process_model(process_model_identifier)
ProcessModelService.update_process_model(process_model, body_filtered) ProcessModelService.update_process_model(process_model, body_filtered)
return ProcessModelInfoSchema().dump(process_model) return ProcessModelInfoSchema().dump(process_model)
@ -330,10 +344,7 @@ def process_model_update(
def process_model_show(modified_process_model_identifier: str) -> Any: def process_model_show(modified_process_model_identifier: str) -> Any:
"""Process_model_show.""" """Process_model_show."""
process_model_identifier = modified_process_model_identifier.replace(":", "/") process_model_identifier = modified_process_model_identifier.replace(":", "/")
# process_model_identifier = f"{process_group_id}/{process_model_id}"
process_model = get_process_model(process_model_identifier) process_model = get_process_model(process_model_identifier)
# TODO: Temporary. Should not need the next line once models have correct ids
# process_model.id = process_model_identifier
files = sorted(SpecFileService.get_files(process_model)) files = sorted(SpecFileService.get_files(process_model))
process_model.files = files process_model.files = files
for file in process_model.files: for file in process_model.files:
@ -425,7 +436,6 @@ def process_model_file_update(
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Process_model_file_update.""" """Process_model_file_update."""
process_model_identifier = modified_process_model_id.replace(":", "/") process_model_identifier = modified_process_model_id.replace(":", "/")
# process_model_identifier = f"{process_group_id}/{process_model_id}"
process_model = get_process_model(process_model_identifier) process_model = get_process_model(process_model_identifier)
request_file = get_file_from_request() request_file = get_file_from_request()
@ -1142,7 +1152,7 @@ def process_instance_report_show(
per_page: int = 100, per_page: int = 100,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Process_instance_report_show.""" """Process_instance_report_show."""
process_instances = ProcessInstanceModel.query.order_by( # .filter_by(process_model_identifier=process_model.id) process_instances = ProcessInstanceModel.query.order_by(
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
).paginate( ).paginate(
page=page, per_page=per_page, error_out=False page=page, per_page=per_page, error_out=False

View File

@ -333,6 +333,7 @@ class TestProcessApi(BaseTest):
process_model.display_name = "Updated Display Name" process_model.display_name = "Updated Display Name"
process_model.primary_file_name = "superduper.bpmn" process_model.primary_file_name = "superduper.bpmn"
process_model.primary_process_id = "superduper" process_model.primary_process_id = "superduper"
process_model.metadata_extraction_paths = {'extraction1': 'path1'}
modified_process_model_identifier = process_model_identifier.replace("/", ":") modified_process_model_identifier = process_model_identifier.replace("/", ":")
response = client.put( response = client.put(
@ -346,6 +347,7 @@ class TestProcessApi(BaseTest):
assert response.json["display_name"] == "Updated Display Name" assert response.json["display_name"] == "Updated Display Name"
assert response.json["primary_file_name"] == "superduper.bpmn" assert response.json["primary_file_name"] == "superduper.bpmn"
assert response.json["primary_process_id"] == "superduper" assert response.json["primary_process_id"] == "superduper"
assert response.json["metadata_extraction_paths"] == {'extraction1': 'path1'}
def test_process_model_list_all( def test_process_model_list_all(
self, self,

View File

@ -142,7 +142,7 @@ export default function ProcessInstanceListTable({
ReportColumn[] ReportColumn[]
>([]); >([]);
const [processInstanceReportJustSaved, setProcessInstanceReportJustSaved] = const [processInstanceReportJustSaved, setProcessInstanceReportJustSaved] =
useState<boolean>(false); useState<string | null>(null);
const [showReportColumnForm, setShowReportColumnForm] = const [showReportColumnForm, setShowReportColumnForm] =
useState<boolean>(false); useState<boolean>(false);
const [reportColumnToOperateOn, setReportColumnToOperateOn] = const [reportColumnToOperateOn, setReportColumnToOperateOn] =
@ -367,10 +367,14 @@ export default function ProcessInstanceListTable({
const processInstanceReportSaveTag = () => { const processInstanceReportSaveTag = () => {
if (processInstanceReportJustSaved) { if (processInstanceReportJustSaved) {
let titleOperation = 'Updated';
if (processInstanceReportJustSaved === 'new') {
titleOperation = 'Created';
}
return ( return (
<InlineNotification <InlineNotification
title="Perspective Saved" title={`Perspective ${titleOperation}:`}
subtitle={`as '${ subtitle={`'${
processInstanceReportSelection processInstanceReportSelection
? processInstanceReportSelection.identifier ? processInstanceReportSelection.identifier
: '' : ''
@ -498,7 +502,7 @@ export default function ProcessInstanceListTable({
} }
setErrorMessage(null); setErrorMessage(null);
setProcessInstanceReportJustSaved(false); setProcessInstanceReportJustSaved(null);
navigate(`/admin/process-instances?${queryParamString}`); navigate(`/admin/process-instances?${queryParamString}`);
}; };
@ -586,10 +590,7 @@ export default function ProcessInstanceListTable({
setEndToTime(''); setEndToTime('');
}; };
const processInstanceReportDidChange = ( const processInstanceReportDidChange = (selection: any, mode?: string) => {
selection: any,
savedReport: boolean = false
) => {
clearFilters(); clearFilters();
const selectedReport = selection.selectedItem; const selectedReport = selection.selectedItem;
setProcessInstanceReportSelection(selectedReport); setProcessInstanceReportSelection(selectedReport);
@ -600,7 +601,7 @@ export default function ProcessInstanceListTable({
} }
setErrorMessage(null); setErrorMessage(null);
setProcessInstanceReportJustSaved(savedReport); setProcessInstanceReportJustSaved(mode || null);
navigate(`/admin/process-instances${queryParamString}`); navigate(`/admin/process-instances${queryParamString}`);
}; };
@ -615,12 +616,12 @@ export default function ProcessInstanceListTable({
}; };
// TODO onSuccess reload/select the new report in the report search // TODO onSuccess reload/select the new report in the report search
const onSaveReportSuccess = (result: any) => { const onSaveReportSuccess = (result: any, mode: string) => {
processInstanceReportDidChange( processInstanceReportDidChange(
{ {
selectedItem: result, selectedItem: result,
}, },
true mode
); );
}; };
@ -638,7 +639,7 @@ export default function ProcessInstanceListTable({
} }
return ( return (
<ProcessInstanceListSaveAsReport <ProcessInstanceListSaveAsReport
onSuccess={onSaveReportSuccess} onSuccess={(result: any) => onSaveReportSuccess(result, 'new')}
buttonClassName="narrow-button" buttonClassName="narrow-button"
columnArray={reportColumns()} columnArray={reportColumns()}
orderBy="" orderBy=""
@ -705,7 +706,7 @@ export default function ProcessInstanceListTable({
} else { } else {
newReportFilters.splice(existingReportFilterIndex, 1); newReportFilters.splice(existingReportFilterIndex, 1);
} }
} else { } else if (reportColumnForEditing.filter_field_value) {
newReportFilters = newReportFilters.concat([newReportFilter]); newReportFilters = newReportFilters.concat([newReportFilter]);
} }
} }
@ -1157,7 +1158,7 @@ export default function ProcessInstanceListTable({
<Column sm={2} md={4} lg={2}> <Column sm={2} md={4} lg={2}>
<ProcessInstanceListSaveAsReport <ProcessInstanceListSaveAsReport
buttonClassName="with-tiny-top-margin" buttonClassName="with-tiny-top-margin"
onSuccess={onSaveReportSuccess} onSuccess={(result: any) => onSaveReportSuccess(result, 'edit')}
columnArray={reportColumns()} columnArray={reportColumns()}
orderBy="" orderBy=""
buttonText="Save" buttonText="Save"

View File

@ -2,9 +2,11 @@ import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
// @ts-ignore // @ts-ignore
import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react'; import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
// @ts-ignore
import { AddAlt } from '@carbon/icons-react';
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers'; import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
import { ProcessModel } from '../interfaces'; import { MetadataExtractionPaths, ProcessModel } from '../interfaces';
type OwnProps = { type OwnProps = {
mode: string; mode: string;
@ -23,6 +25,7 @@ export default function ProcessModelForm({
const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] = const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] =
useState<boolean>(false); useState<boolean>(false);
const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false); const [displayNameInvalid, setDisplayNameInvalid] = useState<boolean>(false);
useState<boolean>(false);
const navigate = useNavigate(); const navigate = useNavigate();
const navigateToProcessModel = (result: ProcessModel) => { const navigateToProcessModel = (result: ProcessModel) => {
@ -64,6 +67,7 @@ export default function ProcessModelForm({
const postBody = { const postBody = {
display_name: processModel.display_name, display_name: processModel.display_name,
description: processModel.description, description: processModel.description,
metadata_extraction_paths: processModel.metadata_extraction_paths,
}; };
if (mode === 'new') { if (mode === 'new') {
Object.assign(postBody, { Object.assign(postBody, {
@ -87,6 +91,66 @@ export default function ProcessModelForm({
setProcessModel(processModelToCopy); setProcessModel(processModelToCopy);
}; };
const metadataExtractionPathForm = (
metadataKey: string,
metadataPath: string
) => {
return (
<>
<TextInput
id="process-model-metadata-extraction-path-key"
labelText="Extraction Key"
value={metadataKey}
onChange={(event: any) => {
const cep: MetadataExtractionPaths =
processModel.metadata_extraction_paths || {};
delete cep[metadataKey];
cep[event.target.value] = metadataPath;
updateProcessModel({ metadata_extraction_paths: cep });
}}
/>
<TextInput
id="process-model-metadata-extraction-path"
labelText="Extraction Path"
value={metadataPath}
onChange={(event: any) => {
const cep: MetadataExtractionPaths =
processModel.metadata_extraction_paths || {};
cep[metadataKey] = event.target.value;
updateProcessModel({ metadata_extraction_paths: cep });
}}
/>
</>
);
};
const metadataExtractionPathFormArea = () => {
if (processModel.metadata_extraction_paths) {
console.log(
'processModel.metadata_extraction_paths',
processModel.metadata_extraction_paths
);
return Object.keys(processModel.metadata_extraction_paths).map(
(metadataKey: string) => {
return metadataExtractionPathForm(
metadataKey,
processModel.metadata_extraction_paths
? processModel.metadata_extraction_paths[metadataKey]
: ''
);
}
);
}
return null;
};
const addBlankMetadataExtractionPath = () => {
const cep: MetadataExtractionPaths =
processModel.metadata_extraction_paths || {};
Object.assign(cep, { '': '' });
updateProcessModel({ metadata_extraction_paths: cep });
};
const onDisplayNameChanged = (newDisplayName: any) => { const onDisplayNameChanged = (newDisplayName: any) => {
setDisplayNameInvalid(false); setDisplayNameInvalid(false);
const updateDict = { display_name: newDisplayName }; const updateDict = { display_name: newDisplayName };
@ -145,6 +209,22 @@ export default function ProcessModelForm({
/> />
); );
textInputs.push(<>{metadataExtractionPathFormArea()}</>);
textInputs.push(
<Button
data-qa="add-metadata-extraction-path-button"
renderIcon={AddAlt}
className="button-white-background"
kind=""
size="sm"
onClick={() => {
addBlankMetadataExtractionPath();
}}
>
Add Metadata Extraction Path
</Button>
);
return textInputs; return textInputs;
}; };

View File

@ -98,6 +98,10 @@ export interface ProcessGroupLite {
display_name: string; display_name: string;
} }
export interface MetadataExtractionPaths {
[key: string]: string;
}
export interface ProcessModel { export interface ProcessModel {
id: string; id: string;
description: string; description: string;
@ -105,6 +109,7 @@ export interface ProcessModel {
primary_file_name: string; primary_file_name: string;
files: ProcessFile[]; files: ProcessFile[];
parent_groups?: ProcessGroupLite[]; parent_groups?: ProcessGroupLite[];
metadata_extraction_paths?: MetadataExtractionPaths;
} }
export interface ProcessGroup { export interface ProcessGroup {