diff --git a/src/Frontend/src/components/messages/BodyView.vue b/src/Frontend/src/components/messages/BodyView.vue index ceb1e8d8b..135ae6cf5 100644 --- a/src/Frontend/src/components/messages/BodyView.vue +++ b/src/Frontend/src/components/messages/BodyView.vue @@ -1,13 +1,17 @@ @@ -38,4 +60,8 @@ const body = computed(() => bodyState.value.data.value); .gap { margin-top: 5px; } + +.btn-group { + margin-bottom: 5px; +} diff --git a/src/Frontend/src/components/messages/HexView.vue b/src/Frontend/src/components/messages/HexView.vue new file mode 100644 index 000000000..c5779d74e --- /dev/null +++ b/src/Frontend/src/components/messages/HexView.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/src/Frontend/src/stores/MessageStore.ts b/src/Frontend/src/stores/MessageStore.ts index 447b9d3e4..446086f3c 100644 --- a/src/Frontend/src/stores/MessageStore.ts +++ b/src/Frontend/src/stores/MessageStore.ts @@ -64,7 +64,7 @@ interface Model { export const useMessageStore = defineStore("MessageStore", () => { const headers = ref>({ data: [] }); - const body = ref>({ data: {} }); + const body = ref>({ data: {} }); const state = reactive>({ data: { failure_metadata: {}, failure_status: {}, dialog_status: {}, invoked_saga: {} } }); const edit_and_retry_config = ref({ enabled: false, locked_headers: [], sensitive_headers: [] }); const conversationData = ref>({ data: [] }); @@ -227,13 +227,21 @@ export const useMessageStore = defineStore("MessageStore", () => { 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 }); + const arrayBuffer = await response.arrayBuffer(); + body.value.data.rawBytes = new Uint8Array(arrayBuffer); + const charset = contentType?.match(/charset=([^\s;]+)/i)?.[1] ?? "utf-8"; + body.value.data.value = new TextDecoder(charset).decode(arrayBuffer); + + try { + 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.data.parse_failed = true; } } catch { body.value.failed_to_load = true; diff --git a/src/Frontend/test/mocks/scenarios/recoverability/recoverability-available.ts b/src/Frontend/test/mocks/scenarios/recoverability/recoverability-available.ts index a442c8333..9079e4c12 100644 --- a/src/Frontend/test/mocks/scenarios/recoverability/recoverability-available.ts +++ b/src/Frontend/test/mocks/scenarios/recoverability/recoverability-available.ts @@ -12,6 +12,7 @@ * 2. Navigate to Failed Messages view * 3. Recoverability capability card should show "Available" status * 4. Failed message recovery features should work + * 5. Click a failed message → Body tab → toggle "Hex" to see hex view */ import { createScenario } from "../scenario-helper"; import * as precondition from "../../../preconditions"; @@ -19,4 +20,18 @@ import * as precondition from "../../../preconditions"; const { worker, runScenario } = createScenario(); export { worker }; -export const setupComplete = runScenario(precondition.scenarioRecoverabilityAvailable); +export const setupComplete = runScenario(async ({ driver }) => { + await driver.setUp(precondition.scenarioRecoverabilityAvailable); + + // Add a failed message with body so the Body tab (and Hex view) can be tested + // Note: withGroupId and withMessageId must match because the mock's body_url + // uses the groupId but the body endpoint handler uses the messageId + await driver.setUp( + precondition.hasFailedMessage({ + withGroupId: "hex-test-1", + withMessageId: "hex-test-1", + withContentType: "application/json", + withBody: { orderId: 12345, customerName: "Alice", amount: 99.95, shipped: false, notes: "express delivery" }, + }) + ); +});