Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Frontend/src/components/messages2/SagaDiagram.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ function rendercomponent({ initialState = {} }: { initialState?: { MessageStore?
],
stubs: {
CodeEditor: true,
CopyToClipboard: true,
},
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface InitiatingMessageViewModel {
FormattedMessageTimestamp: string;
IsEventMessage: boolean;
MessageData: SagaMessageDataItem[];
HasRelatedTimeoutRequest?: boolean;
MessageId: string;
}
export interface SagaTimeoutMessageViewModel extends SagaMessageViewModel {
TimeoutFriendly: string;
Expand Down Expand Up @@ -57,6 +59,18 @@ export interface SagaViewModel {
export function parseSagaUpdates(sagaHistory: SagaHistory | null, messagesData: SagaMessageData[]): SagaUpdateViewModel[] {
if (!sagaHistory || !sagaHistory.changes || !sagaHistory.changes.length) return [];

const timeoutMessageIds = new Set<string>();
sagaHistory.changes.forEach((update) => {
if (update.outgoing_messages) {
update.outgoing_messages.forEach((msg) => {
const delivery_delay = msg.delivery_delay || "00:00:00";
if (delivery_delay && delivery_delay !== "00:00:00") {
timeoutMessageIds.add(msg.message_id);
}
});
}
});

const updates = sagaHistory.changes
.map((update) => {
const startTime = new Date(update.start_time);
Expand Down Expand Up @@ -107,6 +121,9 @@ export function parseSagaUpdates(sagaHistory: SagaHistory | null, messagesData:

const hasTimeout = outgoingTimeoutMessages.length > 0;

// Check if initiating message is a timeout and if so, if it has a corresponding request in the diagram
const hasRelatedTimeoutRequest = update.initiating_message?.is_saga_timeout_message && timeoutMessageIds.has(update.initiating_message?.message_id);

return <SagaUpdateViewModel>{
MessageId: update.initiating_message?.message_id || "",
StartTime: startTime,
Expand All @@ -115,11 +132,13 @@ export function parseSagaUpdates(sagaHistory: SagaHistory | null, messagesData:
Status: update.status,
StatusDisplay: update.status === "new" ? "Saga Initiated" : "Saga Updated",
InitiatingMessage: <InitiatingMessageViewModel>{
MessageId: update.initiating_message?.message_id || "",
MessageType: typeToName(update.initiating_message?.message_type || "Unknown Message") || "",
FormattedMessageTimestamp: `${initiatingMessageTimestamp.toLocaleDateString()} ${initiatingMessageTimestamp.toLocaleTimeString()}`,
MessageData: initiatingMessageData,
IsEventMessage: update.initiating_message?.intent === "Publish",
IsSagaTimeoutMessage: update.initiating_message?.is_saga_timeout_message || false,
HasRelatedTimeoutRequest: hasRelatedTimeoutRequest,
},
HasTimeout: hasTimeout,
IsFirstNode: update.status === "new",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,29 @@ import MessageDataBox from "./MessageDataBox.vue";
import CommandIcon from "@/assets/command.svg";
import EventIcon from "@/assets/event.svg";
import { SagaMessageViewModel } from "./SagaDiagramParser";
import { useSagaDiagramStore } from "@/stores/SagaDiagramStore";
import { computed } from "vue";

defineProps<{
const shouldBeActive = computed(() => {
return store.selectedMessageId === props.message.MessageId;
});

const store = useSagaDiagramStore();

const props = defineProps<{
message: SagaMessageViewModel;
showMessageData?: boolean;
}>();
</script>

<template>
<div class="cell-inner cell-inner-side">
<div
:class="{
'cell-inner': true,
'cell-inner-side': true,
'cell-inner-side--active': shouldBeActive,
}"
>
<img class="saga-icon saga-icon--side-cell" :src="message.IsEventMessage ? EventIcon : CommandIcon" :alt="message.IsEventMessage ? 'Event' : 'Command'" />
<h2 class="message-title">{{ message.MessageFriendlyTypeName }}</h2>
<div class="timestamp">{{ message.FormattedTimeSent }}</div>
Expand Down Expand Up @@ -63,10 +77,6 @@ defineProps<{
margin-left: 1rem;
}

.cell-inner-side--active {
border: solid 2px #000000;
}

.cell-inner-right {
position: relative;
min-height: 2.5rem;
Expand Down Expand Up @@ -145,8 +155,10 @@ defineProps<{
}

.cell-inner-side--active {
border: solid 2px #000000;
border: solid 5px #0b6eef;
animation: blink-border 1.8s ease-in-out;
}

.saga-icon {
display: block;
float: left;
Expand All @@ -158,4 +170,18 @@ defineProps<{
height: 2rem;
padding: 0.23rem;
}
@keyframes blink-border {
0%,
100% {
border-color: #0b6eef;
}
20%,
60% {
border-color: #cccccc;
}
40%,
80% {
border-color: #0b6eef;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import MessageDataBox from "./MessageDataBox.vue";
import TimeoutIcon from "@/assets/TimeoutIcon.svg";
import SagaTimeoutIcon from "@/assets/SagaTimeoutIcon.svg";
import { useSagaDiagramStore } from "@/stores/SagaDiagramStore";
import { computed, ref, watch } from "vue";

const props = defineProps<{
message: SagaTimeoutMessageViewModel;
Expand All @@ -12,11 +13,32 @@ const props = defineProps<{
}>();

const store = useSagaDiagramStore();
const timeoutMessageRef = ref<HTMLElement | null>(null);
const shouldBeActive = computed(() => {
return store.selectedMessageId === props.message.MessageId;
});

// This sets the store with the required values so the timeout invocation node exists, it will react by scrolling to the node
const navigateToTimeout = () => {
// Set the selected message ID in the store
store.setSelectedMessageId(props.message.MessageId);
store.scrollToTimeout = true;
};

watch(
[() => store.scrollToTimeoutRequest, () => shouldBeActive.value, () => timeoutMessageRef.value !== null],
([scrollRequest, shouldScroll, refExists]) => {
if (scrollRequest && shouldScroll && refExists && timeoutMessageRef.value) {
timeoutMessageRef.value.scrollIntoView({
behavior: "smooth",
block: "center",
});

store.scrollToTimeoutRequest = false;
}
},
{ immediate: true }
);
</script>

<template>
Expand All @@ -35,7 +57,14 @@ const navigateToTimeout = () => {
</div>
<div class="cell cell--side">
<div class="cell-inner cell-inner-right"></div>
<div class="cell-inner cell-inner-side">
<div
ref="timeoutMessageRef"
:class="{
'cell-inner': true,
'cell-inner-side': true,
'cell-inner-side--active': shouldBeActive,
}"
>
<img class="saga-icon saga-icon--side-cell" :src="TimeoutIcon" alt="" />
<h2 class="message-title" aria-label="timeout message type">{{ message.MessageFriendlyTypeName }}</h2>
<div class="timestamp" aria-label="timeout message timestamp">{{ message.FormattedTimeSent }}</div>
Expand Down Expand Up @@ -96,7 +125,8 @@ const navigateToTimeout = () => {
}

.cell-inner-side--active {
border: solid 2px #000000;
border: solid 5px #0b6eef;
animation: blink-border 1.8s ease-in-out;
}

.cell-inner-right {
Expand Down Expand Up @@ -176,4 +206,19 @@ const navigateToTimeout = () => {
.saga-icon--overlap {
margin-left: -1rem;
}

@keyframes blink-border {
0%,
100% {
border-color: #0b6eef;
}
20%,
60% {
border-color: #cccccc;
}
40%,
80% {
border-color: #0b6eef;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,30 @@ const props = defineProps<{

const store = useSagaDiagramStore();
const initiatingMessageRef = ref<HTMLElement | null>(null);
const isActive = ref(false);
const hasParsingError = ref(false);

// Watch for changes to selectedMessageId
watch(
() => store.selectedMessageId,
(newMessageId) => {
// Check if this node contains the selected message
const isSelected = props.update.InitiatingMessage.IsSagaTimeoutMessage && newMessageId === props.update.MessageId;
const shouldBeActive = computed(() => {
return store.selectedMessageId === props.update.MessageId;
});

// Update active state
isActive.value = isSelected;
const navigateToTimeoutRequest = () => {
store.setSelectedMessageId(props.update.InitiatingMessage.MessageId);
store.scrollToTimeoutRequest = true;
};

// If this is the selected message, scroll to it
if (isSelected && initiatingMessageRef.value) {
watch(
[() => store.scrollToTimeout, () => shouldBeActive.value, () => initiatingMessageRef.value !== null],
([scrollTimeout, shouldScroll, refExists]) => {
if (scrollTimeout && shouldScroll && refExists && initiatingMessageRef.value) {
initiatingMessageRef.value.scrollIntoView({
behavior: "smooth",
block: "center",
});

store.scrollToTimeout = false;
}
}
},
{ immediate: true }
);

// Format a JSON value for display
Expand Down Expand Up @@ -155,7 +158,7 @@ const hasStateChanges = computed(() => {
:class="{
'cell-inner': true,
'cell-inner-side': true,
'cell-inner-side--active': isActive || (update.InitiatingMessage.IsSagaTimeoutMessage && update.MessageId === store.selectedMessageId),
'cell-inner-side--active': shouldBeActive || (update.InitiatingMessage.IsSagaTimeoutMessage && update.MessageId === store.selectedMessageId),
}"
:data-message-id="update.InitiatingMessage.IsSagaTimeoutMessage ? update.MessageId : ''"
>
Expand All @@ -168,7 +171,8 @@ const hasStateChanges = computed(() => {
<div class="cell-inner cell-inner-center cell-inner--align-bottom">
<template v-if="update.InitiatingMessage.IsSagaTimeoutMessage">
<img class="saga-icon saga-icon--center-cell" :src="SagaTimeoutIcon" alt="" />
<h2 class="saga-status-title saga-status-title--inline timeout-status" aria-label="timeout invoked">Timeout Invoked</h2>
<a v-if="update.InitiatingMessage.HasRelatedTimeoutRequest" href="#" @click.prevent="navigateToTimeoutRequest" class="saga-status-title saga-status-title--inline timeout-status" aria-label="timeout invoked"> Timeout Invoked </a>
<h2 v-else class="saga-status-title saga-status-title--inline timeout-status" aria-label="timeout invoked">Timeout Invoked</h2>
<br />
</template>
<img class="saga-icon saga-icon--center-cell" :src="update.IsFirstNode ? SagaInitiatedIcon : SagaUpdatedIcon" alt="" />
Expand Down Expand Up @@ -312,7 +316,7 @@ const hasStateChanges = computed(() => {
}

.cell-inner-side--active {
border: solid 5px #00a3c4;
border: solid 5px #0b6eef;
animation: blink-border 1.8s ease-in-out;
}

Expand Down Expand Up @@ -408,7 +412,6 @@ const hasStateChanges = computed(() => {
display: inline-block;
font-size: 1rem;
font-weight: 900;
color: #00a3c4;
}

/* Styles for DiffViewer integration */
Expand Down Expand Up @@ -456,15 +459,15 @@ const hasStateChanges = computed(() => {
@keyframes blink-border {
0%,
100% {
border-color: #00a3c4;
border-color: #0b6eef;
}
20%,
60% {
border-color: #cccccc;
}
40%,
80% {
border-color: #00a3c4;
border-color: #0b6eef;
}
}
</style>
20 changes: 20 additions & 0 deletions src/Frontend/src/stores/SagaDiagramStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { SagaHistory, SagaMessage } from "@/resources/SagaHistory";
import { useFetchFromServiceControl } from "@/composables/serviceServiceControlUrls";
import Message from "@/resources/Message";
import { parse } from "lossless-json";
import { useMessageStore } from "@/stores/MessageStore";

const StandardKeys = ["$type", "Id", "Originator", "OriginalMessageId"];
export interface SagaMessageDataItem {
Expand All @@ -24,8 +25,24 @@ export const useSagaDiagramStore = defineStore("SagaDiagramStore", () => {
const fetchedMessages = ref(new Set<string>());
const messagesData = ref<SagaMessageData[]>([]);
const selectedMessageId = ref<string | null>(null);
const scrollToTimeoutRequest = ref(false);
const scrollToTimeout = ref(false);
const MessageBodyEndpoint = "messages/{0}/body";

// Get message store to watch for changes
const messageStore = useMessageStore();

// Watch for message_id changes in the MessageStore and update selectedMessageId
watch(
() => messageStore.state.data.message_id,
(newMessageId) => {
if (newMessageId) {
setSelectedMessageId(newMessageId);
}
},
{ immediate: true }
);

// Watch the sagaId and fetch saga history when it changes
watch(sagaId, async (newSagaId) => {
if (newSagaId) {
Expand Down Expand Up @@ -190,6 +207,7 @@ export const useSagaDiagramStore = defineStore("SagaDiagramStore", () => {
fetchedMessages.value.clear();
messagesData.value = [];
selectedMessageId.value = null;
scrollToTimeoutRequest.value = false;
}

function formatUrl(template: string, id: string): string {
Expand Down Expand Up @@ -263,6 +281,8 @@ export const useSagaDiagramStore = defineStore("SagaDiagramStore", () => {
showMessageData,
messagesData,
selectedMessageId,
scrollToTimeoutRequest,
scrollToTimeout,
setSagaId,
clearSagaHistory,
toggleMessageData,
Expand Down