diff --git a/src/Frontend/src/components/messages2/SagaDiagram.spec.ts b/src/Frontend/src/components/messages2/SagaDiagram.spec.ts index 4731842a6..423008194 100644 --- a/src/Frontend/src/components/messages2/SagaDiagram.spec.ts +++ b/src/Frontend/src/components/messages2/SagaDiagram.spec.ts @@ -4,6 +4,7 @@ import { SagaHistory } from "@/resources/SagaHistory"; import makeRouter from "@/router"; import { createTestingPinia } from "@pinia/testing"; import { MessageStore } from "@/stores/MessageStore"; +import { MessageStatus } from "@/resources/Message"; //Defines a domain-specific language (DSL) for interacting with the system under test (sut) interface componentDSL { @@ -38,7 +39,7 @@ describe("Feature: Message not involved in Saga", () => { const componentDriver = rendercomponent({ initialState: { MessageStore: messageStore, - sagaHistory: undefined, // Lets pass undefined to simulate no saga data available + SagaDiagramStore: undefined, // Lets pass undefined to simulate no saga data available }, }); @@ -63,7 +64,7 @@ describe("Feature: Detecting no Audited Saga Data Available", () => { const componentDriver = rendercomponent({ initialState: { MessageStore: messageStore, - sagaHistory: undefined, // Lets pass undefined to simulate no saga data available + SagaDiagramStore: undefined, // Lets pass undefined to simulate no saga data available }, }); @@ -97,7 +98,7 @@ describe("Feature: Navigation and Contextual Information", () => { const componentDriver = rendercomponent({ initialState: { MessageStore: messageStore, - sagaHistory: { sagaHistory: sampleSagaHistory }, + SagaDiagramStore: { sagaHistory: sampleSagaHistory }, }, }); @@ -120,7 +121,7 @@ describe("Feature: Navigation and Contextual Information", () => { const componentDriver = rendercomponent({ initialState: { MessageStore: messageStore, - sagaHistory: { sagaHistory: sampleSagaHistory }, + SagaDiagramStore: { sagaHistory: sampleSagaHistory }, }, }); @@ -181,7 +182,7 @@ describe("Feature: 3 Visual Representation of Saga Timeline", () => { const componentDriver = rendercomponent({ initialState: { MessageStore: messageStore, - sagaHistory: { sagaHistory: sampleSagaHistory }, + SagaDiagramStore: { sagaHistory: sampleSagaHistory }, }, }); @@ -247,7 +248,7 @@ describe("Feature: 3 Visual Representation of Saga Timeline", () => { const componentDriver = rendercomponent({ initialState: { MessageStore: messageStore, - sagaHistory: { sagaHistory: sampleSagaHistory }, + SagaDiagramStore: { sagaHistory: sampleSagaHistory }, }, }); @@ -271,7 +272,7 @@ describe("Feature: 3 Visual Representation of Saga Timeline", () => { }); }); -function rendercomponent({ initialState = {} }: { initialState?: { MessageStore?: MessageStore; sagaHistory?: { sagaHistory: SagaHistory } } }): componentDSL { +function rendercomponent({ initialState = {} }: { initialState?: { MessageStore?: MessageStore; SagaDiagramStore?: { sagaHistory: SagaHistory } } }): componentDSL { const router = makeRouter(); // Render with createTestingPinia @@ -380,6 +381,8 @@ const sampleSagaHistory: SagaHistory = { time_sent: new Date("2025-03-28T03:04:06.321561Z"), message_type: "ServiceControl.SmokeTest.MyCustomTimeout", intent: "Send", + body_url: "body_url", + message_status: MessageStatus.Successful, }, outgoing_messages: [], endpoint: "Endpoint1", @@ -397,6 +400,8 @@ const sampleSagaHistory: SagaHistory = { time_sent: new Date("2025-03-28T03:04:05.37723Z"), message_type: "ServiceControl.SmokeTest.MyCustomTimeout", intent: "Send", + body_url: "body_url", + message_status: MessageStatus.Successful, }, outgoing_messages: [], endpoint: "Endpoint1", @@ -414,6 +419,8 @@ const sampleSagaHistory: SagaHistory = { time_sent: new Date("2025-03-28T03:04:06.293765Z"), message_type: "ServiceControl.SmokeTest.SagaMessage2", intent: "Send", + body_url: "body_url", + message_status: MessageStatus.Successful, }, outgoing_messages: [ { @@ -423,6 +430,12 @@ const sampleSagaHistory: SagaHistory = { time_sent: new Date("2025-03-28T03:04:06.3214397Z"), message_type: "ServiceControl.SmokeTest.MyCustomTimeout", intent: "Send", + deliver_at: new Date("2025-03-28T03:04:06.293765Z"), + is_saga_timeout_message: false, + originating_endpoint: "Sender", + originating_machine: "mobvm2", + body_url: "body_url", + message_status: MessageStatus.Successful, }, ], endpoint: "Endpoint1", @@ -440,6 +453,8 @@ const sampleSagaHistory: SagaHistory = { time_sent: new Date("2025-03-28T03:04:05.235534Z"), message_type: "ServiceControl.SmokeTest.SagaMessage1", intent: "Send", + body_url: "body_url", + message_status: MessageStatus.Successful, }, outgoing_messages: [ { @@ -449,6 +464,12 @@ const sampleSagaHistory: SagaHistory = { time_sent: new Date("2025-03-28T03:04:05.3715034Z"), message_type: "ServiceControl.SmokeTest.MyCustomTimeout", intent: "Send", + deliver_at: new Date("2025-03-28T03:04:06.293765Z"), + is_saga_timeout_message: false, + originating_endpoint: "Sender", + originating_machine: "mobvm2", + body_url: "body_url", + message_status: MessageStatus.Successful, }, ], endpoint: "Endpoint1", diff --git a/src/Frontend/src/components/messages2/SagaDiagram.vue b/src/Frontend/src/components/messages2/SagaDiagram.vue index 1edd63fd0..d186a64ff 100644 --- a/src/Frontend/src/components/messages2/SagaDiagram.vue +++ b/src/Frontend/src/components/messages2/SagaDiagram.vue @@ -1,12 +1,13 @@ @@ -32,4 +43,16 @@ defineProps<{ white-space: nowrap; text-overflow: ellipsis; } + +.message-data-box-text--empty { + display: inline-block; + width: 100%; + text-align: center; +} + +.message-data-loading { + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/Frontend/src/components/messages2/SagaDiagram/useSagaDiagramParser.ts b/src/Frontend/src/components/messages2/SagaDiagram/SagaDiagramParser.ts similarity index 54% rename from src/Frontend/src/components/messages2/SagaDiagram/useSagaDiagramParser.ts rename to src/Frontend/src/components/messages2/SagaDiagram/SagaDiagramParser.ts index 0814c3d5f..33270100a 100644 --- a/src/Frontend/src/components/messages2/SagaDiagram/useSagaDiagramParser.ts +++ b/src/Frontend/src/components/messages2/SagaDiagram/SagaDiagramParser.ts @@ -1,37 +1,40 @@ import { SagaHistory } from "@/resources/SagaHistory"; import { typeToName } from "@/composables/typeHumanizer"; +import { SagaMessageData, SagaMessageDataItem } from "@/stores/SagaDiagramStore"; +import { getTimeoutFriendly } from "@/composables/deliveryDelayParser"; -export interface SagaMessageDataItem { - Key: string; - Value: string; -} - -export interface SagaMessage { +export interface SagaMessageViewModel { + MessageId: string; MessageFriendlyTypeName: string; FormattedTimeSent: string; Data: SagaMessageDataItem[]; IsEventMessage: boolean; IsCommandMessage: boolean; } - -export interface SagaTimeoutMessage extends SagaMessage { +export interface InitiatingMessageViewModel { + MessageType: string; + IsSagaTimeoutMessage: boolean; + FormattedMessageTimestamp: string; + MessageData: SagaMessageDataItem[]; +} +export interface SagaTimeoutMessageViewModel extends SagaMessageViewModel { TimeoutFriendly: string; } export interface SagaUpdateViewModel { + MessageId: string; StartTime: Date; FinishTime: Date; FormattedStartTime: string; - InitiatingMessageType: string; - FormattedInitiatingMessageTimestamp: string; + InitiatingMessage: InitiatingMessageViewModel; Status: string; StatusDisplay: string; HasTimeout: boolean; IsFirstNode: boolean; - NonTimeoutMessages: SagaMessage[]; - TimeoutMessages: SagaTimeoutMessage[]; - HasNonTimeoutMessages: boolean; - HasTimeoutMessages: boolean; + OutgoingMessages: SagaMessageViewModel[]; + OutgoingTimeoutMessages: SagaTimeoutMessageViewModel[]; + HasOutgoingMessages: boolean; + HasOutgoingTimeoutMessages: boolean; } export interface SagaViewModel { @@ -47,7 +50,7 @@ export interface SagaViewModel { ShowMessageData: boolean; } -export function parseSagaUpdates(sagaHistory: SagaHistory | null): SagaUpdateViewModel[] { +export function parseSagaUpdates(sagaHistory: SagaHistory | null, messagesData: SagaMessageData[]): SagaUpdateViewModel[] { if (!sagaHistory || !sagaHistory.changes || !sagaHistory.changes.length) return []; return sagaHistory.changes @@ -56,6 +59,9 @@ export function parseSagaUpdates(sagaHistory: SagaHistory | null): SagaUpdateVie const finishTime = new Date(update.finish_time); const initiatingMessageTimestamp = new Date(update.initiating_message?.time_sent || Date.now()); + // Find message data for initiating message + const initiatingMessageData = update.initiating_message ? messagesData.find((m) => m.message_id === update.initiating_message.message_id)?.data || [] : []; + // Create common base message objects with shared properties const outgoingMessages = update.outgoing_messages.map((msg) => { const delivery_delay = msg.delivery_delay || "00:00:00"; @@ -64,47 +70,55 @@ export function parseSagaUpdates(sagaHistory: SagaHistory | null): SagaUpdateVie const timeoutSeconds = delivery_delay.split(":")[2] || "0"; const isEventMessage = msg.intent === "Publish"; + // Find corresponding message data + const messageData = messagesData.find((m) => m.message_id === msg.message_id)?.data || []; return { MessageType: msg.message_type || "", MessageId: msg.message_id, FormattedTimeSent: `${timeSent.toLocaleDateString()} ${timeSent.toLocaleTimeString()}`, HasTimeout: hasTimeout, TimeoutSeconds: timeoutSeconds, + TimeoutFriendly: getTimeoutFriendly(delivery_delay), MessageFriendlyTypeName: typeToName(msg.message_type || ""), - Data: [] as SagaMessageDataItem[], + Data: messageData, IsEventMessage: isEventMessage, IsCommandMessage: !isEventMessage, }; }); - const timeoutMessages = outgoingMessages + const outgoingTimeoutMessages = outgoingMessages .filter((msg) => msg.HasTimeout) .map( (msg) => ({ ...msg, - TimeoutFriendly: `${msg.TimeoutSeconds}s`, //TODO: Update with logic from ServiceInsight - }) as SagaTimeoutMessage + TimeoutFriendly: `${msg.TimeoutFriendly}`, + }) as SagaTimeoutMessageViewModel ); - const nonTimeoutMessages = outgoingMessages.filter((msg) => !msg.HasTimeout) as SagaMessage[]; + const regularMessages = outgoingMessages.filter((msg) => !msg.HasTimeout) as SagaMessageViewModel[]; - const hasTimeout = timeoutMessages.length > 0; + const hasTimeout = outgoingTimeoutMessages.length > 0; return { + MessageId: update.initiating_message?.message_id || "", StartTime: startTime, FinishTime: finishTime, FormattedStartTime: `${startTime.toLocaleDateString()} ${startTime.toLocaleTimeString()}`, Status: update.status, StatusDisplay: update.status === "new" ? "Saga Initiated" : "Saga Updated", - InitiatingMessageType: typeToName(update.initiating_message?.message_type || "Unknown Message") || "", - FormattedInitiatingMessageTimestamp: `${initiatingMessageTimestamp.toLocaleDateString()} ${initiatingMessageTimestamp.toLocaleTimeString()}`, + InitiatingMessage: { + MessageType: typeToName(update.initiating_message?.message_type || "Unknown Message") || "", + FormattedMessageTimestamp: `${initiatingMessageTimestamp.toLocaleDateString()} ${initiatingMessageTimestamp.toLocaleTimeString()}`, + MessageData: initiatingMessageData, + IsSagaTimeoutMessage: update.initiating_message?.is_saga_timeout_message || false, + }, HasTimeout: hasTimeout, IsFirstNode: update.status === "new", - TimeoutMessages: timeoutMessages, - NonTimeoutMessages: nonTimeoutMessages, - HasNonTimeoutMessages: nonTimeoutMessages.length > 0, - HasTimeoutMessages: timeoutMessages.length > 0, + OutgoingTimeoutMessages: outgoingTimeoutMessages, + OutgoingMessages: regularMessages, + HasOutgoingMessages: regularMessages.length > 0, + HasOutgoingTimeoutMessages: outgoingTimeoutMessages.length > 0, }; }) .sort((a, b) => a.StartTime.getTime() - b.StartTime.getTime()) diff --git a/src/Frontend/src/components/messages2/SagaDiagram/SagaOutgoingMessage.vue b/src/Frontend/src/components/messages2/SagaDiagram/SagaOutgoingMessage.vue new file mode 100644 index 000000000..433a84d78 --- /dev/null +++ b/src/Frontend/src/components/messages2/SagaDiagram/SagaOutgoingMessage.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/Frontend/src/components/messages2/SagaDiagram/SagaTimeoutMessage.vue b/src/Frontend/src/components/messages2/SagaDiagram/SagaOutgoingTimeoutMessage.vue similarity index 95% rename from src/Frontend/src/components/messages2/SagaDiagram/SagaTimeoutMessage.vue rename to src/Frontend/src/components/messages2/SagaDiagram/SagaOutgoingTimeoutMessage.vue index ed253fca7..76e255ebe 100644 --- a/src/Frontend/src/components/messages2/SagaDiagram/SagaTimeoutMessage.vue +++ b/src/Frontend/src/components/messages2/SagaDiagram/SagaOutgoingTimeoutMessage.vue @@ -1,11 +1,11 @@