From 02f37c68e27ebb31b361e1675febef164c6eb1fe Mon Sep 17 00:00:00 2001 From: John Simons Date: Tue, 1 Apr 2025 10:39:37 +1000 Subject: [PATCH 01/16] Introducing the messageview store --- .../src/components/messages/MessageView.vue | 8 +- .../src/composables/useEditAndRetry.ts | 16 ++ src/Frontend/src/resources/Message.ts | 6 - src/Frontend/src/stores/MessageViewStore.ts | 183 ++++++++++++++++++ 4 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 src/Frontend/src/composables/useEditAndRetry.ts create mode 100644 src/Frontend/src/stores/MessageViewStore.ts diff --git a/src/Frontend/src/components/messages/MessageView.vue b/src/Frontend/src/components/messages/MessageView.vue index b54f6679d..6a3d8df08 100644 --- a/src/Frontend/src/components/messages/MessageView.vue +++ b/src/Frontend/src/components/messages/MessageView.vue @@ -310,10 +310,10 @@ onUnmounted(() => { {{ failedMessage.number_of_processing_attempts - 1 }} Retry Failures - Edited - - View previous version - + Failed: Endpoint: {{ failedMessage.receiving_endpoint.name }} Machine: {{ failedMessage.receiving_endpoint.host }} diff --git a/src/Frontend/src/composables/useEditAndRetry.ts b/src/Frontend/src/composables/useEditAndRetry.ts new file mode 100644 index 000000000..7192bd5c0 --- /dev/null +++ b/src/Frontend/src/composables/useEditAndRetry.ts @@ -0,0 +1,16 @@ +import { useTypedFetchFromServiceControl } from "@/composables/serviceServiceControlUrls.ts"; +import { EditAndRetryConfig } from "@/resources/Configuration.ts"; +import { ref } from "vue"; + +const editAndRetry = ref(); + +async function populate() { + const [, data] = await useTypedFetchFromServiceControl("edit/config"); + + editAndRetry.value = data; +} +populate(); + +export default function useEditAndRetry(): EditAndRetryConfig { + return editAndRetry.value ?? { enabled: false, locked_headers: [], sensitive_headers: [] }; +} diff --git a/src/Frontend/src/resources/Message.ts b/src/Frontend/src/resources/Message.ts index 977497bbb..00fdb3cad 100644 --- a/src/Frontend/src/resources/Message.ts +++ b/src/Frontend/src/resources/Message.ts @@ -21,12 +21,6 @@ export default interface Message { body_size: number; instance_id: string; } -export interface ExtendedMessage extends Message { - notFound: boolean; - error: boolean; - headersNotFound: boolean; - messageBodyNotFound: boolean; -} export enum MessageStatus { Failed = "failed", diff --git a/src/Frontend/src/stores/MessageViewStore.ts b/src/Frontend/src/stores/MessageViewStore.ts new file mode 100644 index 000000000..2786ae9fc --- /dev/null +++ b/src/Frontend/src/stores/MessageViewStore.ts @@ -0,0 +1,183 @@ +import { acceptHMRUpdate, defineStore } from "pinia"; +import { reactive, ref } from "vue"; +import Header from "@/resources/Header.ts"; +import type EndpointDetails from "@/resources/EndpointDetails.ts"; +import FailedMessage, { ExceptionDetails, FailedMessageStatus } from "@/resources/FailedMessage.ts"; +import useEditAndRetry from "@/composables/useEditAndRetry.ts"; +import { useFetchFromServiceControl, useTypedFetchFromServiceControl } from "@/composables/serviceServiceControlUrls.ts"; +import Message from "@/resources/Message.ts"; +import moment from "moment/moment"; +import { useConfiguration } from "@/composables/configuration.ts"; +import { parse, stringify } from "lossless-json"; +import xmlFormat from "xml-formatter"; + +interface DataContainer { + loading?: boolean; + failed_to_load?: boolean; + not_found?: boolean; + data: T; +} + +interface Model { + id?: string; + message_id?: string; + conversation_id?: string; + message_type?: string; + sending_endpoint?: EndpointDetails; + receiving_endpoint?: EndpointDetails; + body_url?: string; + failure_status: Partial<{ + retried: boolean; + archiving: boolean; + restoring: boolean; + archived: boolean; + resolved: boolean; + delete_soon: boolean; + retry_in_progress: boolean; + delete_in_progress: boolean; + restore_in_progress: boolean; + submitted_for_retrial: boolean; + }>; + failure_metadata: Partial<{ + exception: ExceptionDetails; + number_of_processing_attempts: number; + status: FailedMessageStatus; + time_of_failure: string; + last_modified: string; + edited: boolean; + edit_of: string; + deleted_in: string; + redirect: boolean; + }>; + dialog_status: Partial<{ + show_delete_confirm: boolean; + show_restore_confirm: boolean; + show_retry_confirm: boolean; + show_edit_retry_modal: boolean; + }>; +} + +export const useMessageViewStore = defineStore("MessageViewStore", () => { + const headers = ref>({ data: [] }); + const body = ref>({ data: {} }); + const state = reactive>({ data: { failure_metadata: {}, failure_status: {}, dialog_status: {} } }); + + async function loadFailedMessage(id: string) { + state.loading = true; + state.failed_to_load = false; + state.not_found = false; + + try { + const response = await useFetchFromServiceControl(`errors/last/${id}`); + if (response.status === 404) { + state.not_found = true; + return; + } else if (!response.ok) { + state.failed_to_load = true; + return; + } + + const message = (await response.json()) as FailedMessage; + state.data.failure_status.archived = message.status === FailedMessageStatus.Archived; + state.data.failure_status.resolved = message.status === FailedMessageStatus.Resolved; + state.data.failure_status.retried = message.status === FailedMessageStatus.RetryIssued; + state.data.failure_metadata.last_modified = message.last_modified; + } catch { + state.failed_to_load = headers.value.failed_to_load = true; + return; + } finally { + state.loading = headers.value.loading = false; + } + + const countdown = moment(state.data.failure_metadata.last_modified).add(error_retention_period, "hours"); + state.data.failure_status.delete_soon = countdown < moment(); + state.data.failure_metadata.deleted_in = countdown.format(); + + // TODO: Maintain the mutations of the message in memory until the api returns a newer modified message + } + + async function loadMessage(messageId: string, receivingEndpointName: string) { + state.loading = headers.value.loading = true; + state.failed_to_load = headers.value.failed_to_load = false; + state.not_found = headers.value.not_found = false; + + try { + const [, data] = await useTypedFetchFromServiceControl(`messages/search/${messageId}`); + + const message = data.find((value) => value.receiving_endpoint.name === receivingEndpointName); + + if (!message) { + state.not_found = headers.value.not_found = true; + return; + } + + state.data.message_id = message.message_id; + state.data.conversation_id = message.conversation_id; + state.data.body_url = message.body_url; + state.data.message_type = message.message_type; + state.data.sending_endpoint = message.sending_endpoint; + state.data.receiving_endpoint = message.receiving_endpoint; + + headers.value.data = message.headers; + } catch { + state.failed_to_load = headers.value.failed_to_load = true; + } finally { + state.loading = headers.value.loading = false; + } + } + + async function downloadBody() { + if (body.value.not_found) { + return; + } + + body.value.loading = true; + body.value.failed_to_load = false; + + try { + if (!state.data.body_url) { + return; + } + const response = await useFetchFromServiceControl(state.data.body_url.substring(1)); + if (response.status === 404) { + body.value.not_found = true; + + return; + } + + const contentType = response.headers.get("content-type"); + body.value.data.content_type = contentType ?? "text/plain"; + body.value.data.value = await response.text(); + + if (contentType === "application/json") { + body.value.data.value = stringify(parse(body.value.data.value), null, 2) ?? body.value.data.value; + } + if (contentType === "text/xml") { + body.value.data.value = xmlFormat(body.value.data.value, { indentation: " ", collapseContent: true }); + } + } catch { + body.value.failed_to_load = true; + } finally { + body.value.loading = false; + } + } + + const configuration = useConfiguration(); + const error_retention_period = moment.duration(configuration.value?.data_retention.error_retention_period).asHours(); + + return { + headers, + body, + state, + edit_and_retry_config: useEditAndRetry(), + loadMessage, + loadFailedMessage, + downloadBody, + }; +}); + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useMessageViewStore, import.meta.hot)); +} + +export type MessageViewStore = ReturnType; From 14111b2d6ac273568d40eb5d82d3cbd725a577ad Mon Sep 17 00:00:00 2001 From: John Simons Date: Tue, 1 Apr 2025 15:15:37 +1000 Subject: [PATCH 02/16] Modifying view to use store More changes --- src/Frontend/public/js/app.constants.js | 1 + .../src/components/CopyToClipboard.vue | 10 +- src/Frontend/src/components/EventLogItem.vue | 5 +- .../src/components/LoadingSpinner.vue | 9 + .../src/components/audit/AuditList.vue | 10 +- .../failedmessages/EditRetryDialog.vue | 8 +- .../failedmessages/EditRetryDialog2.vue | 345 ++++++++++++++++++ .../components/failedmessages/MessageList.vue | 5 +- .../src/components/messages/BodyView.vue | 28 +- .../messages/DeleteMessageButton.vue | 37 ++ .../messages/EditAndRetryButton.vue | 31 ++ .../messages/ExportMessageButton.vue | 34 ++ .../src/components/messages/FlowDiagram.vue | 56 +-- .../src/components/messages/HeadersView.vue | 18 +- .../src/components/messages/MessageView.vue | 2 +- .../src/components/messages/MessageView2.vue | 186 ++++++++++ .../messages/RestoreMessageButton.vue | 35 ++ .../messages/RetryMessageButton.vue | 32 ++ .../components/messages/StacktraceView.vue | 11 +- src/Frontend/src/composables/toast.ts | 5 + .../src/composables/useEditAndRetry.ts | 8 +- src/Frontend/src/router/config.ts | 11 +- src/Frontend/src/router/routeLinks.ts | 3 +- src/Frontend/src/stores/MessageViewStore.ts | 127 ++++++- 24 files changed, 928 insertions(+), 89 deletions(-) create mode 100644 src/Frontend/src/components/LoadingSpinner.vue create mode 100644 src/Frontend/src/components/failedmessages/EditRetryDialog2.vue create mode 100644 src/Frontend/src/components/messages/DeleteMessageButton.vue create mode 100644 src/Frontend/src/components/messages/EditAndRetryButton.vue create mode 100644 src/Frontend/src/components/messages/ExportMessageButton.vue create mode 100644 src/Frontend/src/components/messages/MessageView2.vue create mode 100644 src/Frontend/src/components/messages/RestoreMessageButton.vue create mode 100644 src/Frontend/src/components/messages/RetryMessageButton.vue diff --git a/src/Frontend/public/js/app.constants.js b/src/Frontend/public/js/app.constants.js index d138f9910..ca0fe5457 100644 --- a/src/Frontend/public/js/app.constants.js +++ b/src/Frontend/public/js/app.constants.js @@ -4,4 +4,5 @@ window.defaultConfig = { service_control_url: 'http://localhost:33333/api/', monitoring_urls: ['http://localhost:33633/'], showPendingRetry: false, + showAllMessages: true, }; diff --git a/src/Frontend/src/components/CopyToClipboard.vue b/src/Frontend/src/components/CopyToClipboard.vue index 4ec0c1323..3f43a1a63 100644 --- a/src/Frontend/src/components/CopyToClipboard.vue +++ b/src/Frontend/src/components/CopyToClipboard.vue @@ -1,6 +1,6 @@