Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
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,35 @@ 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 { ref, watch } from "vue";
const isActive = ref(false);

defineProps<{
const store = useSagaDiagramStore();

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

watch(
() => store.selectedMessageId,
(newMessageId) => {
// Check if this node contains the selected message
isActive.value = newMessageId === props.message.MessageId;
},
{ immediate: true }
);
</script>

<template>
<div class="cell-inner cell-inner-side">
<div
:class="{
'cell-inner': true,
'cell-inner-side': true,
'cell-inner-side--active': isActive,
}"
>
<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 +83,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 +161,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 +176,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,43 @@ const props = defineProps<{
}>();

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

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

// 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, () => shouldHighlightAndScroll.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 }
);

watch(
() => store.selectedMessageId,
(newMessageId) => {
// Check if this node contains the selected message
isActive.value = newMessageId === props.message.MessageId;
},
{ immediate: true }
);
</script>

<template>
Expand All @@ -35,7 +68,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': isActive,
}"
>
<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 +136,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 +217,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 @@ -52,24 +52,37 @@ const initiatingMessageRef = ref<HTMLElement | null>(null);
const isActive = ref(false);
const hasParsingError = ref(false);

// Watch for changes to selectedMessageId
const shouldHighlightAndScroll = computed(() => {
return props.update.MessageId === store.selectedMessageId;
});

const navigateToTimeoutRequest = () => {
store.setSelectedMessageId(props.update.InitiatingMessage.MessageId);
store.scrollToTimeoutRequest = true;
};

watch(
() => store.selectedMessageId,
(newMessageId) => {
// Check if this node contains the selected message
const isSelected = props.update.InitiatingMessage.IsSagaTimeoutMessage && newMessageId === props.update.MessageId;

// Update active state
isActive.value = isSelected;
isActive.value = newMessageId === props.update.MessageId;
},
{ immediate: true }
);

// If this is the selected message, scroll to it
if (isSelected && initiatingMessageRef.value) {
watch(
[() => store.scrollToTimeout, () => shouldHighlightAndScroll.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 @@ -168,7 +181,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 +326,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 +422,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 +469,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>
21 changes: 21 additions & 0 deletions src/Frontend/src/stores/SagaDiagramStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ 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";

// Watch the sagaId and fetch saga history when it changes
Expand Down Expand Up @@ -190,6 +192,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 @@ -254,6 +257,22 @@ export const useSagaDiagramStore = defineStore("SagaDiagramStore", () => {
selectedMessageId.value = messageId;
}

watch(scrollToTimeoutRequest, (newValue) => {
if (newValue) {
setTimeout(() => {
scrollToTimeoutRequest.value = false;
}, 1000);
}
});

watch(scrollToTimeout, (newValue) => {
if (newValue) {
setTimeout(() => {
scrollToTimeout.value = false;
}, 1000);
}
});

return {
sagaHistory,
sagaId,
Expand All @@ -263,6 +282,8 @@ export const useSagaDiagramStore = defineStore("SagaDiagramStore", () => {
showMessageData,
messagesData,
selectedMessageId,
scrollToTimeoutRequest,
scrollToTimeout,
setSagaId,
clearSagaHistory,
toggleMessageData,
Expand Down