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),
|
script_engine=CustomBpmnScriptEngine(use_restricted_script_engine=False),
|
||||||
process_id_to_run=process_id_to_run,
|
process_id_to_run=process_id_to_run,
|
||||||
)
|
)
|
||||||
|
save_to_db = process_instance.persistence_level != "none"
|
||||||
if body and "extension_input" in body:
|
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 = processor.next_task()
|
||||||
next_task.update_data(body["extension_input"])
|
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 (
|
except (
|
||||||
ApiError,
|
ApiError,
|
||||||
ProcessInstanceIsNotEnqueuedError,
|
ProcessInstanceIsNotEnqueuedError,
|
||||||
|
|
|
@ -643,6 +643,10 @@ class ProcessInstanceReportService:
|
||||||
join_conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
|
join_conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
|
||||||
elif filter_for_column["operator"] == "not_equals":
|
elif filter_for_column["operator"] == "not_equals":
|
||||||
join_conditions.append(instance_metadata_alias.value != filter_for_column["field_value"])
|
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":
|
elif filter_for_column["operator"] == "contains":
|
||||||
join_conditions.append(instance_metadata_alias.value.like(f"%{filter_for_column['field_value']}%"))
|
join_conditions.append(instance_metadata_alias.value.like(f"%{filter_for_column['field_value']}%"))
|
||||||
elif filter_for_column["operator"] == "is_empty":
|
elif filter_for_column["operator"] == "is_empty":
|
||||||
|
|
|
@ -488,6 +488,7 @@ class BaseTest:
|
||||||
process_instance: ProcessInstanceModel,
|
process_instance: ProcessInstanceModel,
|
||||||
operator: str,
|
operator: str,
|
||||||
filter_field_value: str = "",
|
filter_field_value: str = "",
|
||||||
|
expect_to_find_instance: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
report_metadata: ReportMetadata = {
|
report_metadata: ReportMetadata = {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
@ -506,8 +507,17 @@ class BaseTest:
|
||||||
response = self.post_to_process_instance_list(
|
response = self.post_to_process_instance_list(
|
||||||
client, user, report_metadata=process_instance_report.get_report_metadata()
|
client, user, report_metadata=process_instance_report.get_report_metadata()
|
||||||
)
|
)
|
||||||
assert len(response.json["results"]) == 1
|
|
||||||
assert response.json["results"][0]["id"] == process_instance.id
|
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.delete(process_instance_report)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
|
@ -2937,10 +2937,102 @@ class TestProcessApi(BaseTest):
|
||||||
operator="not_equals",
|
operator="not_equals",
|
||||||
filter_field_value="hey",
|
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(
|
self.assert_report_with_process_metadata_operator_includes_instance(
|
||||||
client=client, user=with_super_admin_user, process_instance=process_instance_two, operator="is_empty"
|
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(
|
def test_can_get_process_instance_list_with_report_metadata_and_process_initiator(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import MDEditor from '@uiw/react-md-editor';
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
import { Toggle } from '@carbon/react';
|
import { Toggle } from '@carbon/react';
|
||||||
|
import FormattingService from '../services/FormattingService';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
task: any;
|
task: any;
|
||||||
|
@ -25,6 +26,7 @@ export default function InstructionsForEndUser({
|
||||||
if (instructionsForEndUser) {
|
if (instructionsForEndUser) {
|
||||||
instructions = instructionsForEndUser;
|
instructions = instructionsForEndUser;
|
||||||
}
|
}
|
||||||
|
instructions = FormattingService.checkForSpiffFormats(instructions);
|
||||||
|
|
||||||
const maxLineCount: number = 8;
|
const maxLineCount: number = 8;
|
||||||
const maxWordCount: number = 75;
|
const maxWordCount: number = 75;
|
||||||
|
|
|
@ -50,8 +50,8 @@ import {
|
||||||
REFRESH_TIMEOUT_SECONDS,
|
REFRESH_TIMEOUT_SECONDS,
|
||||||
titleizeString,
|
titleizeString,
|
||||||
truncateString,
|
truncateString,
|
||||||
isANumber,
|
|
||||||
formatDurationForDisplay,
|
formatDurationForDisplay,
|
||||||
|
formatDateTime,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||||
|
|
||||||
|
@ -1697,24 +1697,6 @@ export default function ProcessInstanceListTable({
|
||||||
return value;
|
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 formattedColumn = (row: ProcessInstance, column: ReportColumn) => {
|
||||||
const reportColumnFormatters: Record<string, any> = {
|
const reportColumnFormatters: Record<string, any> = {
|
||||||
id: formatProcessInstanceId,
|
id: formatProcessInstanceId,
|
||||||
|
|
|
@ -442,3 +442,21 @@ export const formatDurationForDisplay = (_row: any, value: any) => {
|
||||||
}
|
}
|
||||||
return durationTimes.join(' ');
|
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,
|
UiSchemaPageDefinition,
|
||||||
} from '../extension_ui_schema_interfaces';
|
} from '../extension_ui_schema_interfaces';
|
||||||
import ErrorDisplay from '../components/ErrorDisplay';
|
import ErrorDisplay from '../components/ErrorDisplay';
|
||||||
|
import FormattingService from '../services/FormattingService';
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
export default function Extension() {
|
export default function Extension() {
|
||||||
|
@ -47,7 +48,10 @@ export default function Extension() {
|
||||||
const processLoadResult = (result: any) => {
|
const processLoadResult = (result: any) => {
|
||||||
setFormData(result.task_data);
|
setFormData(result.task_data);
|
||||||
if (result.rendered_results_markdown) {
|
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 {
|
} else {
|
||||||
setProcessedTaskData(result.task_data);
|
setProcessedTaskData(result.task_data);
|
||||||
if (result.rendered_results_markdown) {
|
if (result.rendered_results_markdown) {
|
||||||
setMarkdownToRenderOnSubmit(result.rendered_results_markdown);
|
const newMarkdown = FormattingService.checkForSpiffFormats(
|
||||||
|
result.rendered_results_markdown
|
||||||
|
);
|
||||||
|
setMarkdownToRenderOnSubmit(newMarkdown);
|
||||||
}
|
}
|
||||||
setFormButtonsDisabled(false);
|
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",
|
"module": "commonjs",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"target": "es2016",
|
"target": "es2021",
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue