|
| 1 | +import { acceptHMRUpdate, defineStore } from "pinia"; |
| 2 | +import { reactive, ref } from "vue"; |
| 3 | +import Header from "@/resources/Header.ts"; |
| 4 | +import type EndpointDetails from "@/resources/EndpointDetails.ts"; |
| 5 | +import FailedMessage, { ExceptionDetails, FailedMessageStatus } from "@/resources/FailedMessage.ts"; |
| 6 | +import useEditAndRetry from "@/composables/useEditAndRetry.ts"; |
| 7 | +import { useFetchFromServiceControl, useTypedFetchFromServiceControl } from "@/composables/serviceServiceControlUrls.ts"; |
| 8 | +import Message from "@/resources/Message.ts"; |
| 9 | +import moment from "moment/moment"; |
| 10 | +import { useConfiguration } from "@/composables/configuration.ts"; |
| 11 | +import { parse, stringify } from "lossless-json"; |
| 12 | +import xmlFormat from "xml-formatter"; |
| 13 | + |
| 14 | +interface DataContainer<T> { |
| 15 | + loading?: boolean; |
| 16 | + failed_to_load?: boolean; |
| 17 | + not_found?: boolean; |
| 18 | + data: T; |
| 19 | +} |
| 20 | + |
| 21 | +interface Model { |
| 22 | + id?: string; |
| 23 | + message_id?: string; |
| 24 | + conversation_id?: string; |
| 25 | + message_type?: string; |
| 26 | + sending_endpoint?: EndpointDetails; |
| 27 | + receiving_endpoint?: EndpointDetails; |
| 28 | + body_url?: string; |
| 29 | + failure_status: Partial<{ |
| 30 | + retried: boolean; |
| 31 | + archiving: boolean; |
| 32 | + restoring: boolean; |
| 33 | + archived: boolean; |
| 34 | + resolved: boolean; |
| 35 | + delete_soon: boolean; |
| 36 | + retry_in_progress: boolean; |
| 37 | + delete_in_progress: boolean; |
| 38 | + restore_in_progress: boolean; |
| 39 | + submitted_for_retrial: boolean; |
| 40 | + }>; |
| 41 | + failure_metadata: Partial<{ |
| 42 | + exception: ExceptionDetails; |
| 43 | + number_of_processing_attempts: number; |
| 44 | + status: FailedMessageStatus; |
| 45 | + time_of_failure: string; |
| 46 | + last_modified: string; |
| 47 | + edited: boolean; |
| 48 | + edit_of: string; |
| 49 | + deleted_in: string; |
| 50 | + redirect: boolean; |
| 51 | + }>; |
| 52 | + dialog_status: Partial<{ |
| 53 | + show_delete_confirm: boolean; |
| 54 | + show_restore_confirm: boolean; |
| 55 | + show_retry_confirm: boolean; |
| 56 | + show_edit_retry_modal: boolean; |
| 57 | + }>; |
| 58 | +} |
| 59 | + |
| 60 | +export const useMessageViewStore = defineStore("MessageViewStore", () => { |
| 61 | + const headers = ref<DataContainer<Header[]>>({ data: [] }); |
| 62 | + const body = ref<DataContainer<{ value?: string; content_type?: string }>>({ data: {} }); |
| 63 | + const state = reactive<DataContainer<Model>>({ data: { failure_metadata: {}, failure_status: {}, dialog_status: {} } }); |
| 64 | + |
| 65 | + async function loadFailedMessage(id: string) { |
| 66 | + state.loading = true; |
| 67 | + state.failed_to_load = false; |
| 68 | + state.not_found = false; |
| 69 | + |
| 70 | + try { |
| 71 | + const response = await useFetchFromServiceControl(`errors/last/${id}`); |
| 72 | + if (response.status === 404) { |
| 73 | + state.not_found = true; |
| 74 | + return; |
| 75 | + } else if (!response.ok) { |
| 76 | + state.failed_to_load = true; |
| 77 | + return; |
| 78 | + } |
| 79 | + |
| 80 | + const message = (await response.json()) as FailedMessage; |
| 81 | + state.data.failure_status.archived = message.status === FailedMessageStatus.Archived; |
| 82 | + state.data.failure_status.resolved = message.status === FailedMessageStatus.Resolved; |
| 83 | + state.data.failure_status.retried = message.status === FailedMessageStatus.RetryIssued; |
| 84 | + state.data.failure_metadata.last_modified = message.last_modified; |
| 85 | + } catch { |
| 86 | + state.failed_to_load = headers.value.failed_to_load = true; |
| 87 | + return; |
| 88 | + } finally { |
| 89 | + state.loading = headers.value.loading = false; |
| 90 | + } |
| 91 | + |
| 92 | + const countdown = moment(state.data.failure_metadata.last_modified).add(error_retention_period, "hours"); |
| 93 | + state.data.failure_status.delete_soon = countdown < moment(); |
| 94 | + state.data.failure_metadata.deleted_in = countdown.format(); |
| 95 | + |
| 96 | + // TODO: Maintain the mutations of the message in memory until the api returns a newer modified message |
| 97 | + } |
| 98 | + |
| 99 | + async function loadMessage(messageId: string, receivingEndpointName: string) { |
| 100 | + state.loading = headers.value.loading = true; |
| 101 | + state.failed_to_load = headers.value.failed_to_load = false; |
| 102 | + state.not_found = headers.value.not_found = false; |
| 103 | + |
| 104 | + try { |
| 105 | + const [, data] = await useTypedFetchFromServiceControl<Message[]>(`messages/search/${messageId}`); |
| 106 | + |
| 107 | + const message = data.find((value) => value.receiving_endpoint.name === receivingEndpointName); |
| 108 | + |
| 109 | + if (!message) { |
| 110 | + state.not_found = headers.value.not_found = true; |
| 111 | + return; |
| 112 | + } |
| 113 | + |
| 114 | + state.data.message_id = message.message_id; |
| 115 | + state.data.conversation_id = message.conversation_id; |
| 116 | + state.data.body_url = message.body_url; |
| 117 | + state.data.message_type = message.message_type; |
| 118 | + state.data.sending_endpoint = message.sending_endpoint; |
| 119 | + state.data.receiving_endpoint = message.receiving_endpoint; |
| 120 | + |
| 121 | + headers.value.data = message.headers; |
| 122 | + } catch { |
| 123 | + state.failed_to_load = headers.value.failed_to_load = true; |
| 124 | + } finally { |
| 125 | + state.loading = headers.value.loading = false; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + async function downloadBody() { |
| 130 | + if (body.value.not_found) { |
| 131 | + return; |
| 132 | + } |
| 133 | + |
| 134 | + body.value.loading = true; |
| 135 | + body.value.failed_to_load = false; |
| 136 | + |
| 137 | + try { |
| 138 | + if (!state.data.body_url) { |
| 139 | + return; |
| 140 | + } |
| 141 | + const response = await useFetchFromServiceControl(state.data.body_url.substring(1)); |
| 142 | + if (response.status === 404) { |
| 143 | + body.value.not_found = true; |
| 144 | + |
| 145 | + return; |
| 146 | + } |
| 147 | + |
| 148 | + const contentType = response.headers.get("content-type"); |
| 149 | + body.value.data.content_type = contentType ?? "text/plain"; |
| 150 | + body.value.data.value = await response.text(); |
| 151 | + |
| 152 | + if (contentType === "application/json") { |
| 153 | + body.value.data.value = stringify(parse(body.value.data.value), null, 2) ?? body.value.data.value; |
| 154 | + } |
| 155 | + if (contentType === "text/xml") { |
| 156 | + body.value.data.value = xmlFormat(body.value.data.value, { indentation: " ", collapseContent: true }); |
| 157 | + } |
| 158 | + } catch { |
| 159 | + body.value.failed_to_load = true; |
| 160 | + } finally { |
| 161 | + body.value.loading = false; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + const configuration = useConfiguration(); |
| 166 | + const error_retention_period = moment.duration(configuration.value?.data_retention.error_retention_period).asHours(); |
| 167 | + |
| 168 | + return { |
| 169 | + headers, |
| 170 | + body, |
| 171 | + state, |
| 172 | + edit_and_retry_config: useEditAndRetry(), |
| 173 | + loadMessage, |
| 174 | + loadFailedMessage, |
| 175 | + downloadBody, |
| 176 | + }; |
| 177 | +}); |
| 178 | + |
| 179 | +if (import.meta.hot) { |
| 180 | + import.meta.hot.accept(acceptHMRUpdate(useMessageViewStore, import.meta.hot)); |
| 181 | +} |
| 182 | + |
| 183 | +export type MessageViewStore = ReturnType<typeof useMessageViewStore>; |
0 commit comments