feature/formatting-in-extension-md (#559)
* support formatting data client side in markdown and support greater than and less than for metadata column filters w/ burnettk * moved spiff conversion functions to FormattingService and use it in InstructionsForEndUser w/ burnettk * added tests for greater than and less than metadata operators and added negative tests w/ burnettk * removed unneeded useEffect w/ burnettk --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
6664b52985
commit
54b7c5c3ec
|
@ -153,11 +153,12 @@ def _run_extension(
|
|||
script_engine=CustomBpmnScriptEngine(use_restricted_script_engine=False),
|
||||
process_id_to_run=process_id_to_run,
|
||||
)
|
||||
save_to_db = process_instance.persistence_level != "none"
|
||||
if body and "extension_input" in body:
|
||||
processor.do_engine_steps(save=False, execution_strategy_name="run_current_ready_tasks")
|
||||
processor.do_engine_steps(save=save_to_db, execution_strategy_name="run_current_ready_tasks")
|
||||
next_task = processor.next_task()
|
||||
next_task.update_data(body["extension_input"])
|
||||
processor.do_engine_steps(save=False, execution_strategy_name="greedy")
|
||||
processor.do_engine_steps(save=save_to_db, execution_strategy_name="greedy")
|
||||
except (
|
||||
ApiError,
|
||||
ProcessInstanceIsNotEnqueuedError,
|
||||
|
|
|
@ -643,6 +643,10 @@ class ProcessInstanceReportService:
|
|||
join_conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
|
||||
elif filter_for_column["operator"] == "not_equals":
|
||||
join_conditions.append(instance_metadata_alias.value != filter_for_column["field_value"])
|
||||
elif filter_for_column["operator"] == "greater_than_or_equal_to":
|
||||
join_conditions.append(instance_metadata_alias.value >= filter_for_column["field_value"])
|
||||
elif filter_for_column["operator"] == "less_than":
|
||||
join_conditions.append(instance_metadata_alias.value < filter_for_column["field_value"])
|
||||
elif filter_for_column["operator"] == "contains":
|
||||
join_conditions.append(instance_metadata_alias.value.like(f"%{filter_for_column['field_value']}%"))
|
||||
elif filter_for_column["operator"] == "is_empty":
|
||||
|
|
|
@ -488,6 +488,7 @@ class BaseTest:
|
|||
process_instance: ProcessInstanceModel,
|
||||
operator: str,
|
||||
filter_field_value: str = "",
|
||||
expect_to_find_instance: bool = True,
|
||||
) -> None:
|
||||
report_metadata: ReportMetadata = {
|
||||
"columns": [
|
||||
|
@ -506,8 +507,17 @@ class BaseTest:
|
|||
response = self.post_to_process_instance_list(
|
||||
client, user, report_metadata=process_instance_report.get_report_metadata()
|
||||
)
|
||||
|
||||
if expect_to_find_instance is True:
|
||||
assert len(response.json["results"]) == 1
|
||||
assert response.json["results"][0]["id"] == process_instance.id
|
||||
else:
|
||||
if len(response.json["results"]) == 1:
|
||||
assert (
|
||||
response.json["results"][0]["id"] != process_instance.id
|
||||
), "expected not to find a specific process instance, but we found it"
|
||||
else:
|
||||
assert len(response.json["results"]) == 0
|
||||
db.session.delete(process_instance_report)
|
||||
db.session.commit()
|
||||
|
||||
|
|
|
@ -2937,10 +2937,102 @@ class TestProcessApi(BaseTest):
|
|||
operator="not_equals",
|
||||
filter_field_value="hey",
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="less_than",
|
||||
filter_field_value="value4",
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="greater_than_or_equal_to",
|
||||
filter_field_value="value1",
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client, user=with_super_admin_user, process_instance=process_instance_two, operator="is_empty"
|
||||
)
|
||||
|
||||
def test_can_get_process_instance_list_with_report_metadata_using_different_operators_when_no_matches(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
process_model = load_test_spec(
|
||||
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
|
||||
bpmn_file_name="save_process_instance_metadata.bpmn",
|
||||
process_model_source_directory="save_process_instance_metadata",
|
||||
)
|
||||
|
||||
process_instance_one_metadata = {"key1": "value1"}
|
||||
process_instance_one = self.create_process_instance_with_synthetic_metadata(
|
||||
process_model=process_model, process_instance_metadata_dict=process_instance_one_metadata
|
||||
)
|
||||
|
||||
process_instance_two_metadata = {"key2": "value2"}
|
||||
process_instance_two = self.create_process_instance_with_synthetic_metadata(
|
||||
process_model=process_model, process_instance_metadata_dict=process_instance_two_metadata
|
||||
)
|
||||
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_two,
|
||||
operator="is_not_empty",
|
||||
expect_to_find_instance=False,
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="equals",
|
||||
filter_field_value="value2",
|
||||
expect_to_find_instance=False,
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="contains",
|
||||
filter_field_value="alunooo",
|
||||
expect_to_find_instance=False,
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="not_equals",
|
||||
filter_field_value="value1",
|
||||
expect_to_find_instance=False,
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="less_than",
|
||||
filter_field_value="value1",
|
||||
expect_to_find_instance=False,
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="greater_than_or_equal_to",
|
||||
filter_field_value="value2",
|
||||
expect_to_find_instance=False,
|
||||
)
|
||||
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_instance=process_instance_one,
|
||||
operator="is_empty",
|
||||
expect_to_find_instance=False,
|
||||
)
|
||||
|
||||
def test_can_get_process_instance_list_with_report_metadata_and_process_initiator(
|
||||
self,
|
||||
app: Flask,
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||
// @ts-ignore
|
||||
import MDEditor from '@uiw/react-md-editor';
|
||||
import { Toggle } from '@carbon/react';
|
||||
import FormattingService from '../services/FormattingService';
|
||||
|
||||
type OwnProps = {
|
||||
task: any;
|
||||
|
@ -25,6 +26,7 @@ export default function InstructionsForEndUser({
|
|||
if (instructionsForEndUser) {
|
||||
instructions = instructionsForEndUser;
|
||||
}
|
||||
instructions = FormattingService.checkForSpiffFormats(instructions);
|
||||
|
||||
const maxLineCount: number = 8;
|
||||
const maxWordCount: number = 75;
|
||||
|
|
|
@ -50,8 +50,8 @@ import {
|
|||
REFRESH_TIMEOUT_SECONDS,
|
||||
titleizeString,
|
||||
truncateString,
|
||||
isANumber,
|
||||
formatDurationForDisplay,
|
||||
formatDateTime,
|
||||
} from '../helpers';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
|
||||
|
@ -1697,24 +1697,6 @@ export default function ProcessInstanceListTable({
|
|||
return value;
|
||||
};
|
||||
|
||||
const formatDateTime = (_row: ProcessInstance, value: any) => {
|
||||
if (value === undefined || value === null) {
|
||||
return value;
|
||||
}
|
||||
let dateInSeconds = value;
|
||||
if (!isANumber(value)) {
|
||||
const timeArgs = value.split('T');
|
||||
dateInSeconds = convertDateAndTimeStringsToSeconds(
|
||||
timeArgs[0],
|
||||
timeArgs[1]
|
||||
);
|
||||
}
|
||||
if (dateInSeconds) {
|
||||
return convertSecondsToFormattedDateTime(dateInSeconds);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const formattedColumn = (row: ProcessInstance, column: ReportColumn) => {
|
||||
const reportColumnFormatters: Record<string, any> = {
|
||||
id: formatProcessInstanceId,
|
||||
|
|
|
@ -442,3 +442,21 @@ export const formatDurationForDisplay = (_row: any, value: any) => {
|
|||
}
|
||||
return durationTimes.join(' ');
|
||||
};
|
||||
|
||||
export const formatDateTime = (_row: any, value: any) => {
|
||||
if (value === undefined || value === null) {
|
||||
return value;
|
||||
}
|
||||
let dateInSeconds = value;
|
||||
if (!isANumber(value)) {
|
||||
const timeArgs = value.split('T');
|
||||
dateInSeconds = convertDateAndTimeStringsToSeconds(
|
||||
timeArgs[0],
|
||||
timeArgs[1]
|
||||
);
|
||||
}
|
||||
if (dateInSeconds) {
|
||||
return convertSecondsToFormattedDateTime(dateInSeconds);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
UiSchemaPageDefinition,
|
||||
} from '../extension_ui_schema_interfaces';
|
||||
import ErrorDisplay from '../components/ErrorDisplay';
|
||||
import FormattingService from '../services/FormattingService';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function Extension() {
|
||||
|
@ -47,7 +48,10 @@ export default function Extension() {
|
|||
const processLoadResult = (result: any) => {
|
||||
setFormData(result.task_data);
|
||||
if (result.rendered_results_markdown) {
|
||||
setMarkdownToRenderOnLoad(result.rendered_results_markdown);
|
||||
const newMarkdown = FormattingService.checkForSpiffFormats(
|
||||
result.rendered_results_markdown
|
||||
);
|
||||
setMarkdownToRenderOnLoad(newMarkdown);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -162,7 +166,10 @@ export default function Extension() {
|
|||
} else {
|
||||
setProcessedTaskData(result.task_data);
|
||||
if (result.rendered_results_markdown) {
|
||||
setMarkdownToRenderOnSubmit(result.rendered_results_markdown);
|
||||
const newMarkdown = FormattingService.checkForSpiffFormats(
|
||||
result.rendered_results_markdown
|
||||
);
|
||||
setMarkdownToRenderOnSubmit(newMarkdown);
|
||||
}
|
||||
setFormButtonsDisabled(false);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { formatDateTime, formatDurationForDisplay } from '../helpers';
|
||||
|
||||
const spiffFormatFunctions: { [key: string]: Function } = {
|
||||
convert_seconds_to_date_time_for_display: formatDateTime,
|
||||
convert_seconds_to_duration_for_display: formatDurationForDisplay,
|
||||
};
|
||||
|
||||
const checkForSpiffFormats = (markdown: string) => {
|
||||
const replacer = (
|
||||
match: string,
|
||||
spiffFormat: string,
|
||||
originalValue: string
|
||||
) => {
|
||||
if (spiffFormat in spiffFormatFunctions) {
|
||||
return spiffFormatFunctions[spiffFormat](undefined, originalValue);
|
||||
}
|
||||
console.warn(
|
||||
`attempted: ${match}, but ${spiffFormat} is not a valid conversion function`
|
||||
);
|
||||
|
||||
return match;
|
||||
};
|
||||
return markdown.replaceAll(/SPIFF_FORMAT:::(\w+)\(([^)]+)\)/g, replacer);
|
||||
};
|
||||
|
||||
const FormattingService = {
|
||||
checkForSpiffFormats,
|
||||
};
|
||||
|
||||
export default FormattingService;
|
|
@ -6,7 +6,7 @@
|
|||
"module": "commonjs",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "es2016",
|
||||
"target": "es2021",
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue