added api to get error details for an event and added simple modal on frontend to show it
This commit is contained in:
parent
320d1b4083
commit
6747d9df3d
|
@ -2010,6 +2010,39 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/ProcessInstanceLog"
|
||||
|
||||
/event-error-details/{modified_process_model_identifier}/{process_instance_id}/{process_instance_event_id}:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
required: true
|
||||
description: the id of the process instance
|
||||
schema:
|
||||
type: integer
|
||||
- name: modified_process_model_identifier
|
||||
in: path
|
||||
required: true
|
||||
description: The process_model_id, modified to replace slashes (/)
|
||||
schema:
|
||||
type: string
|
||||
- name: process_instance_event_id
|
||||
in: path
|
||||
required: true
|
||||
description: the id of the process instance event
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
tags:
|
||||
- Process Instance Events
|
||||
operationId: spiffworkflow_backend.routes.process_instance_events_controller.error_details
|
||||
summary: returns the error details for a given process instance event.
|
||||
responses:
|
||||
"200":
|
||||
description: list of types
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ProcessInstanceLog"
|
||||
|
||||
/secrets:
|
||||
parameters:
|
||||
- name: page
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from dataclasses import dataclass
|
||||
from sqlalchemy.orm import relationship
|
||||
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||
from sqlalchemy import ForeignKey
|
||||
from spiffworkflow_backend.models.db import db
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcessInstanceErrorDetailModel(SpiffworkflowBaseDBModel):
|
||||
__tablename__ = "process_instance_error_detail"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
|
|
|
@ -39,7 +39,7 @@ class ProcessInstanceEventModel(SpiffworkflowBaseDBModel):
|
|||
|
||||
user_id = db.Column(ForeignKey(UserModel.id), nullable=True, index=True) # type: ignore
|
||||
|
||||
error_deatils = relationship("ProcessInstanceErrorDetailModel", cascade="delete") # type: ignore
|
||||
error_details = relationship("ProcessInstanceErrorDetailModel", cascade="delete") # type: ignore
|
||||
|
||||
@validates("event_type")
|
||||
def validate_event_type(self, key: str, value: Any) -> Any:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Optional
|
||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||
|
||||
import flask.wrappers
|
||||
from flask import jsonify
|
||||
|
@ -91,3 +92,20 @@ def types() -> flask.wrappers.Response:
|
|||
task_types = [t.typename for t in query]
|
||||
event_types = ProcessInstanceEventType.list()
|
||||
return make_response(jsonify({"task_types": task_types, "event_types": event_types}), 200)
|
||||
|
||||
|
||||
def error_details(
|
||||
modified_process_model_identifier: str,
|
||||
process_instance_id: int,
|
||||
process_instance_event_id: int,
|
||||
) -> flask.wrappers.Response:
|
||||
process_instance_event = ProcessInstanceEventModel.query.filter_by(id=process_instance_event_id).first()
|
||||
if process_instance_event is None:
|
||||
raise (
|
||||
ApiError(
|
||||
error_code="process_instance_event_cannot_be_found",
|
||||
message=f"Process instance event cannot be found: {process_instance_event_id}",
|
||||
status_code=400,
|
||||
)
|
||||
)
|
||||
return make_response(jsonify(process_instance_event.error_details[0]), 200)
|
||||
|
|
|
@ -10,18 +10,19 @@ export const useUriListForPermissions = () => {
|
|||
processGroupListPath: '/v1.0/process-groups',
|
||||
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceCompleteTaskPath: `/v1.0/complete-task/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceCreatePath: `/v1.0/process-instances/${params.process_model_id}`,
|
||||
processInstanceErrorEventDetails: `/v1.0/event-error-details/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceReportListPath: '/v1.0/process-instances/reports',
|
||||
processInstanceResumePath: `/v1.0/process-instance-resume/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceSuspendPath: `/v1.0/process-instance-suspend/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceResetPath: `/v1.0/process-instance-reset/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceTaskDataPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceResumePath: `/v1.0/process-instance-resume/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceSendEventPath: `/v1.0/send-event/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceCompleteTaskPath: `/v1.0/complete-task/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
||||
processInstanceSuspendPath: `/v1.0/process-instance-suspend/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceTaskDataPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processInstanceTaskListForMePath: `/v1.0/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
||||
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
||||
processInstanceTerminatePath: `/v1.0/process-instance-terminate/${params.process_model_id}/${params.process_instance_id}`,
|
||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||
|
|
|
@ -297,6 +297,12 @@ export interface JsonSchemaForm {
|
|||
required: string[];
|
||||
}
|
||||
|
||||
export interface ProcessInstanceEventErrorDetail {
|
||||
id: number;
|
||||
message: string;
|
||||
stacktrace: string;
|
||||
}
|
||||
|
||||
export interface ProcessInstanceLogEntry {
|
||||
bpmn_process_definition_identifier: string;
|
||||
bpmn_process_definition_name: string;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { ErrorOutline } from '@carbon/icons-react';
|
||||
import {
|
||||
Table,
|
||||
Tabs,
|
||||
|
@ -10,6 +11,8 @@ import {
|
|||
Button,
|
||||
TextInput,
|
||||
ComboBox,
|
||||
Modal,
|
||||
Loading,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import {
|
||||
|
@ -28,8 +31,13 @@ import {
|
|||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||
import { ProcessInstanceLogEntry } from '../interfaces';
|
||||
import {
|
||||
PermissionsToCheck,
|
||||
ProcessInstanceEventErrorDetail,
|
||||
ProcessInstanceLogEntry,
|
||||
} from '../interfaces';
|
||||
import Filters from '../components/Filters';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
type OwnProps = {
|
||||
variant: string;
|
||||
|
@ -46,11 +54,16 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
|||
|
||||
const [taskTypes, setTaskTypes] = useState<string[]>([]);
|
||||
const [eventTypes, setEventTypes] = useState<string[]>([]);
|
||||
const [eventForModal, setEventForModal] =
|
||||
useState<ProcessInstanceLogEntry | null>(null);
|
||||
const [eventErrorDetails, setEventErrorDetails] =
|
||||
useState<ProcessInstanceEventErrorDetail | null>(null);
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const isDetailedView = searchParams.get('detailed') === 'true';
|
||||
|
||||
const taskNameHeader = isDetailedView ? 'Task Name' : 'Milestone';
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.processInstanceErrorEventDetails]: ['GET'],
|
||||
};
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
||||
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
|
||||
|
||||
|
@ -58,6 +71,8 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
|||
if (variant === 'all') {
|
||||
processInstanceShowPageBaseUrl = `/admin/process-instances/${params.process_model_id}`;
|
||||
}
|
||||
const isDetailedView = searchParams.get('detailed') === 'true';
|
||||
const taskNameHeader = isDetailedView ? 'Task Name' : 'Milestone';
|
||||
|
||||
const updateSearchParams = (value: string, key: string) => {
|
||||
if (value) {
|
||||
|
@ -128,6 +143,92 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
|||
isDetailedView,
|
||||
]);
|
||||
|
||||
const handleErrorEventModalClose = () => {
|
||||
setEventForModal(null);
|
||||
setEventErrorDetails(null);
|
||||
};
|
||||
|
||||
const errorEventModal = () => {
|
||||
if (eventForModal) {
|
||||
const modalHeading = `Event Error Details for`;
|
||||
let errorMessageTag = (
|
||||
<Loading className="some-class" withOverlay={false} small />
|
||||
);
|
||||
if (eventErrorDetails) {
|
||||
errorMessageTag = (
|
||||
<>
|
||||
<p className="failure-string">{eventErrorDetails.message} NOOO</p>
|
||||
<br />
|
||||
<pre>{eventErrorDetails.stacktrace}</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
open={!!eventForModal}
|
||||
passiveModal
|
||||
onRequestClose={handleErrorEventModalClose}
|
||||
modalHeading={modalHeading}
|
||||
modalLabel="Error Details"
|
||||
>
|
||||
{errorMessageTag}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleErrorDetailsReponse = (
|
||||
results: ProcessInstanceEventErrorDetail
|
||||
) => {
|
||||
setEventErrorDetails(results);
|
||||
};
|
||||
|
||||
const getErrorDetailsForEvent = (logEntry: ProcessInstanceLogEntry) => {
|
||||
setEventForModal(logEntry);
|
||||
if (ability.can('GET', targetUris.processInstanceErrorEventDetails)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `${targetUris.processInstanceErrorEventDetails}/${logEntry.id}`,
|
||||
httpMethod: 'GET',
|
||||
successCallback: handleErrorDetailsReponse,
|
||||
failureCallback: (error: any) => {
|
||||
const errorObject: ProcessInstanceEventErrorDetail = {
|
||||
id: 0,
|
||||
message: `ERROR: ${error.message}`,
|
||||
stacktrace: '',
|
||||
};
|
||||
setEventErrorDetails(errorObject);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const eventTypeCell = (logEntry: ProcessInstanceLogEntry) => {
|
||||
if (
|
||||
['process_instance_error', 'task_error'].includes(logEntry.event_type)
|
||||
) {
|
||||
const errorTitle = 'Event has an error';
|
||||
const errorIcon = (
|
||||
<>
|
||||
|
||||
<ErrorOutline className="red-icon" />
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
kind="ghost"
|
||||
className="button-link"
|
||||
onClick={() => getErrorDetailsForEvent(logEntry)}
|
||||
title={errorTitle}
|
||||
>
|
||||
{logEntry.event_type}
|
||||
{errorIcon}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return logEntry.event_type;
|
||||
};
|
||||
|
||||
const getTableRow = (logEntry: ProcessInstanceLogEntry) => {
|
||||
const tableRow = [];
|
||||
const taskNameCell = (
|
||||
|
@ -164,7 +265,7 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
|||
<>
|
||||
<td>{logEntry.task_definition_identifier}</td>
|
||||
<td>{logEntry.bpmn_task_type}</td>
|
||||
<td>{logEntry.event_type}</td>
|
||||
<td>{eventTypeCell(logEntry)}</td>
|
||||
<td>
|
||||
{logEntry.username || (
|
||||
<span className="system-user-log-entry">system</span>
|
||||
|
@ -405,6 +506,7 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
|||
]}
|
||||
/>
|
||||
{tabs()}
|
||||
{errorEventModal()}
|
||||
<Filters
|
||||
filterOptions={filterOptions}
|
||||
showFilterOptions={showFilterOptions}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
// carbon/react is not very typescript safe so ignore it
|
||||
declare module '@carbon/react';
|
||||
declare module '@carbon/icons-react';
|
||||
|
|
Loading…
Reference in New Issue