diff --git a/src/Frontend/src/components/messages/MessageView.vue b/src/Frontend/src/components/messages/MessageView.vue
index 5d88fb38a..786b428fe 100644
--- a/src/Frontend/src/components/messages/MessageView.vue
+++ b/src/Frontend/src/components/messages/MessageView.vue
@@ -10,7 +10,6 @@ import TimeSince from "../TimeSince.vue";
import moment from "moment";
import ConfirmDialog from "../ConfirmDialog.vue";
import FlowDiagram from "./FlowDiagram.vue";
-import SequenceDiagram from "./SequenceDiagram.vue";
import EditRetryDialog from "../failedmessages/EditRetryDialog.vue";
import routeLinks from "@/router/routeLinks";
import { EditAndRetryConfig } from "@/resources/Configuration";
@@ -44,7 +43,6 @@ const showEditRetryModal = ref(false);
const configuration = useConfiguration();
const isMassTransitConnected = useIsMassTransitConnected();
-const showAllMessages = window.defaultConfig.showAllMessages;
async function loadFailedMessage() {
const response = await useFetchFromServiceControl(`errors/last/${id.value}`);
@@ -351,13 +349,11 @@ onUnmounted(() => {
-
-
diff --git a/src/Frontend/src/components/messages2/FlowDiagram.vue b/src/Frontend/src/components/messages2/FlowDiagram.vue
index 93b188bd7..e7df88228 100644
--- a/src/Frontend/src/components/messages2/FlowDiagram.vue
+++ b/src/Frontend/src/components/messages2/FlowDiagram.vue
@@ -41,7 +41,7 @@ const nodeSpacingX = 300;
const nodeSpacingY = 200;
const store = useMessageStore();
-const { state } = storeToRefs(useMessageStore());
+const { state } = storeToRefs(store);
async function getConversation(conversationId: string) {
await store.loadConversation(conversationId);
diff --git a/src/Frontend/src/components/messages2/MessageView.vue b/src/Frontend/src/components/messages2/MessageView.vue
index b21613616..850d75e91 100644
--- a/src/Frontend/src/components/messages2/MessageView.vue
+++ b/src/Frontend/src/components/messages2/MessageView.vue
@@ -4,6 +4,7 @@ import { RouterLink, useRoute } from "vue-router";
import NoData from "../NoData.vue";
import TimeSince from "../TimeSince.vue";
import FlowDiagram from "./FlowDiagram.vue";
+import SequenceDiagram from "./SequenceDiagram.vue";
import routeLinks from "@/router/routeLinks";
import { useIsMassTransitConnected } from "@/composables/useIsMassTransitConnected";
import BodyView from "@/components/messages2/BodyView.vue";
@@ -51,6 +52,10 @@ const tabs = computed(() => {
text: "Flow Diagram",
component: FlowDiagram,
});
+ currentTabs.push({
+ text: "Sequence Diagram",
+ component: SequenceDiagram,
+ });
}
return currentTabs;
diff --git a/src/Frontend/src/components/messages/SequenceDiagram.vue b/src/Frontend/src/components/messages2/SequenceDiagram.vue
similarity index 87%
rename from src/Frontend/src/components/messages/SequenceDiagram.vue
rename to src/Frontend/src/components/messages2/SequenceDiagram.vue
index bcfa1b396..62a508b4a 100644
--- a/src/Frontend/src/components/messages/SequenceDiagram.vue
+++ b/src/Frontend/src/components/messages2/SequenceDiagram.vue
@@ -5,11 +5,15 @@ import Handlers from "./SequenceDiagram/HandlersComponent.vue";
import Routes from "./SequenceDiagram/RoutesComponent.vue";
import { useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
import { storeToRefs } from "pinia";
+import useTooltips from "./SequenceDiagram/tooltipOverlay.ts";
+import { onMounted } from "vue";
const store = useSequenceDiagramStore();
-store.setConversationId("39907d51-12e5-4202-82c3-b2b30077ebd4");
-
const { maxWidth, maxHeight } = storeToRefs(store);
+
+useTooltips();
+
+onMounted(() => store.refreshConversation());
diff --git a/src/Frontend/src/components/messages2/SequenceDiagram/EndpointTooltip.vue b/src/Frontend/src/components/messages2/SequenceDiagram/EndpointTooltip.vue
new file mode 100644
index 000000000..14b616567
--- /dev/null
+++ b/src/Frontend/src/components/messages2/SequenceDiagram/EndpointTooltip.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+ {{ endpoint.name }}
+
+ {{ endpoint.version }}
+
+ {{ endpoint.host }}
+
+
+
+
diff --git a/src/Frontend/src/components/messages/SequenceDiagram/EndpointsComponent.vue b/src/Frontend/src/components/messages2/SequenceDiagram/EndpointsComponent.vue
similarity index 89%
rename from src/Frontend/src/components/messages/SequenceDiagram/EndpointsComponent.vue
rename to src/Frontend/src/components/messages2/SequenceDiagram/EndpointsComponent.vue
index 09625fcbf..df2890cd7 100644
--- a/src/Frontend/src/components/messages/SequenceDiagram/EndpointsComponent.vue
+++ b/src/Frontend/src/components/messages2/SequenceDiagram/EndpointsComponent.vue
@@ -28,11 +28,11 @@ const Endpoint_Image_Width = 20;
const store = useSequenceDiagramStore();
const { startX, endpoints } = storeToRefs(store);
-const epRefs = ref([]);
+const epTextRefs = ref([]);
const endpointItems = computed(() =>
endpoints.value.map((x, index) => {
const endpoint = x as EndpointWithLocation;
- const el = epRefs.value[index];
+ const el = epTextRefs.value[index];
if (el) {
const bounds = el.getBBox();
const previousEndpoint = index > 0 ? endpointItems.value[index - 1] : undefined;
@@ -69,17 +69,17 @@ watch(endpointItems, () => {
});
watch(startX, () => {
- epRefs.value = [];
+ epTextRefs.value = [];
endpoints.value.forEach((endpoint) => ((endpoint as EndpointWithLocation).surround = undefined));
});
-function setEndpointRef(el: SVGTextElement, index: number) {
- if (el) epRefs.value[index] = el;
+function setEndpointTextRef(el: SVGTextElement, index: number) {
+ if (el) epTextRefs.value[index] = el;
}
-
+
- {{ endpoint.name }}
+ {{ endpoint.name }}
diff --git a/src/Frontend/src/components/messages2/SequenceDiagram/HandlerTooltip.vue b/src/Frontend/src/components/messages2/SequenceDiagram/HandlerTooltip.vue
new file mode 100644
index 000000000..cdcb230fa
--- /dev/null
+++ b/src/Frontend/src/components/messages2/SequenceDiagram/HandlerTooltip.vue
@@ -0,0 +1,54 @@
+
+
+
+ Start of Conversation
+
+
+
+
diff --git a/src/Frontend/src/components/messages/SequenceDiagram/HandlersComponent.vue b/src/Frontend/src/components/messages2/SequenceDiagram/HandlersComponent.vue
similarity index 78%
rename from src/Frontend/src/components/messages/SequenceDiagram/HandlersComponent.vue
rename to src/Frontend/src/components/messages2/SequenceDiagram/HandlersComponent.vue
index 3e81ef9ce..b7a23eb27 100644
--- a/src/Frontend/src/components/messages/SequenceDiagram/HandlersComponent.vue
+++ b/src/Frontend/src/components/messages2/SequenceDiagram/HandlersComponent.vue
@@ -43,16 +43,18 @@ const handlerItems = computed(() => {
//determine which side of the handler to render the messageType on. If it's the left side (for a right arrow) then we apply a negative offset
const messageTypeOffset = handler.direction === Direction.Right ? ((messageTypeElement?.getBBox().width ?? 0) + 24) * -1 : 20;
- if (messageTypeOffset < 0) {
- store.setStartX(-1 * messageTypeOffset);
+ const left = (endpoint?.centre ?? 0) - Handler_Width / 2;
+ const right = (endpoint?.centre ?? 0) + Handler_Width / 2;
+ if (left + messageTypeOffset < 0) {
+ store.setStartX(-1 * (left + messageTypeOffset) + 20);
}
return {
id: handler.id,
endpointName: handler.endpoint.name,
incomingId: handler.route?.name,
- left: (endpoint?.centre ?? 0) - Handler_Width / 2,
- right: (endpoint?.centre ?? 0) + Handler_Width / 2,
+ left,
+ right,
y,
height,
fill,
@@ -61,6 +63,7 @@ const handlerItems = computed(() => {
messageType: handler.name,
messageTypeOffset,
messageTypeHighlight: handler.route?.name === highlightId.value,
+ setUIRef: (el: SVGElement) => (handler.uiRef = el),
};
});
@@ -76,10 +79,12 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
-
+
- store.setHighlightId(handler.incomingId)" @mouseleave="() => store.setHighlightId()" />
-
+
+ store.setHighlightId(handler.incomingId)" @mouseleave="() => store.setHighlightId()" />
+
+
+import { RoutedMessage, RoutedMessageType } from "@/resources/SequenceDiagram/RoutedMessage";
+
+defineProps<{ routedMessage: RoutedMessage }>();
+
+
+
+
+
+
+
diff --git a/src/Frontend/src/components/messages/SequenceDiagram/RoutesComponent.vue b/src/Frontend/src/components/messages2/SequenceDiagram/RoutesComponent.vue
similarity index 96%
rename from src/Frontend/src/components/messages/SequenceDiagram/RoutesComponent.vue
rename to src/Frontend/src/components/messages2/SequenceDiagram/RoutesComponent.vue
index a05e429de..37178a071 100644
--- a/src/Frontend/src/components/messages/SequenceDiagram/RoutesComponent.vue
+++ b/src/Frontend/src/components/messages2/SequenceDiagram/RoutesComponent.vue
@@ -36,7 +36,7 @@ const arrows = computed(() =>
route.fromRoutedMessage.direction = direction;
if (messageTypeOffset < 0) {
- store.setStartX(-1 * messageTypeOffset);
+ store.setStartX(-1 * messageTypeOffset + 20);
}
return {
@@ -53,6 +53,7 @@ const arrows = computed(() =>
highlight: highlightId.value === route.name,
highlightTextWidth: messageTypeElementBounds?.width,
highlightTextHeight: messageTypeElementBounds?.height,
+ setUIRef: (el: SVGElement) => (route.uiRef = el),
};
})
);
@@ -78,11 +79,12 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
store.setHighlightId(arrow.id)"
@mouseleave="() => store.setHighlightId()"
+ :ref="(el) => arrow.setUIRef(el as SVGElement)"
>
diff --git a/src/Frontend/src/components/messages/SequenceDiagram/TimelineComponent.vue b/src/Frontend/src/components/messages2/SequenceDiagram/TimelineComponent.vue
similarity index 100%
rename from src/Frontend/src/components/messages/SequenceDiagram/TimelineComponent.vue
rename to src/Frontend/src/components/messages2/SequenceDiagram/TimelineComponent.vue
diff --git a/src/Frontend/src/components/messages2/SequenceDiagram/tooltipOverlay.ts b/src/Frontend/src/components/messages2/SequenceDiagram/tooltipOverlay.ts
new file mode 100644
index 000000000..f6ec4778b
--- /dev/null
+++ b/src/Frontend/src/components/messages2/SequenceDiagram/tooltipOverlay.ts
@@ -0,0 +1,59 @@
+import { useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
+import { storeToRefs } from "pinia";
+import { h, watch } from "vue";
+import { useTippy } from "vue-tippy";
+import EndpointTooltip from "./EndpointTooltip.vue";
+import HandlerTooltip from "./HandlerTooltip.vue";
+import RouteTooltip from "./RouteTooltip.vue";
+
+export default function useTooltips() {
+ const store = useSequenceDiagramStore();
+ const { endpoints, handlers, routes } = storeToRefs(store);
+
+ watch(
+ () => endpoints.value.map((endpoint) => endpoint.uiRef),
+ () =>
+ endpoints.value
+ .filter((endpoint) => endpoint.uiRef)
+ .forEach((endpoint) =>
+ useTippy(endpoint.uiRef, {
+ interactive: true,
+ appendTo: () => document.body,
+ content: h(EndpointTooltip, { endpoint }),
+ placement: "bottom",
+ delay: [800, null],
+ })
+ )
+ );
+
+ watch(
+ () => handlers.value.map((handler) => handler.uiRef),
+ () =>
+ handlers.value
+ .filter((handler) => handler.uiRef)
+ .forEach((handler) =>
+ useTippy(handler.uiRef, {
+ interactive: true,
+ appendTo: () => document.body,
+ content: h(HandlerTooltip, { handler }),
+ delay: [800, null],
+ })
+ )
+ );
+
+ watch(
+ () => routes.value.map((route) => route.uiRef),
+ () =>
+ routes.value
+ .filter((route) => route.uiRef && route.fromRoutedMessage)
+ .forEach((route) =>
+ useTippy(route.uiRef, {
+ interactive: true,
+ appendTo: () => document.body,
+ content: h(RouteTooltip, { routedMessage: route.fromRoutedMessage! }),
+ delay: [800, null],
+ maxWidth: 400,
+ })
+ )
+ );
+}
diff --git a/src/Frontend/src/resources/SequenceDiagram/Endpoint.ts b/src/Frontend/src/resources/SequenceDiagram/Endpoint.ts
index dfa1babd5..b9bfc6eed 100644
--- a/src/Frontend/src/resources/SequenceDiagram/Endpoint.ts
+++ b/src/Frontend/src/resources/SequenceDiagram/Endpoint.ts
@@ -7,6 +7,9 @@ export interface Endpoint {
readonly hosts: EndpointHost[];
readonly hostId: string;
readonly handlers: Handler[];
+ readonly host: string;
+ readonly version: string;
+ uiRef?: SVGElement;
addHandler(handler: Handler): void;
}
@@ -51,6 +54,7 @@ class EndpointItem implements Endpoint {
private _hosts: Map;
private _name: string;
private _handlers: Handler[] = [];
+ uiRef?: SVGElement;
constructor(name: string, host: string, id: string, version?: string) {
const initialHost = new Host(host, id, version);
@@ -73,6 +77,9 @@ class EndpointItem implements Endpoint {
get handlers() {
return [...this._handlers];
}
+ get version() {
+ return [...this._hosts].flatMap(([, host]) => host.versions).join(",");
+ }
addHost(host: Host) {
if (!this._hosts.has(host.equatableKey)) {
diff --git a/src/Frontend/src/resources/SequenceDiagram/Handler.ts b/src/Frontend/src/resources/SequenceDiagram/Handler.ts
index 5e13c367b..03848ca82 100644
--- a/src/Frontend/src/resources/SequenceDiagram/Handler.ts
+++ b/src/Frontend/src/resources/SequenceDiagram/Handler.ts
@@ -19,6 +19,7 @@ export interface Handler {
readonly direction: Direction;
route?: MessageProcessingRoute;
readonly selectedMessage?: Message;
+ uiRef?: SVGElement;
updateProcessedAt(timeSent: Date): void;
addOutMessage(routedMessage: RoutedMessage): void;
}
@@ -88,6 +89,7 @@ class HandlerItem implements Handler {
processedAt?: Date;
processingTime?: number;
route?: MessageProcessingRoute;
+ uiRef?: SVGElement;
constructor(id: string, endpoint: Endpoint) {
this._id = id;
diff --git a/src/Frontend/src/resources/SequenceDiagram/RoutedMessage.ts b/src/Frontend/src/resources/SequenceDiagram/RoutedMessage.ts
index 6d554d646..c4c3c8541 100644
--- a/src/Frontend/src/resources/SequenceDiagram/RoutedMessage.ts
+++ b/src/Frontend/src/resources/SequenceDiagram/RoutedMessage.ts
@@ -24,6 +24,7 @@ export interface MessageProcessingRoute {
readonly name?: string;
readonly fromRoutedMessage?: RoutedMessage;
readonly processingHandler?: Handler;
+ uiRef?: SVGElement;
}
export enum Direction {
@@ -47,7 +48,7 @@ export function createRoutedMessage(message: Message): RoutedMessage {
if (message.message_intent === MessageIntent.Publish) routedMessage.type = RoutedMessageType.Event;
else {
- const isTimeoutString = message.headers.find((h) => h.key === NServiceBusHeaders.IsSagaTimeoutMessage)?.value;
+ const isTimeoutString = message.headers.find((h) => h.key === NServiceBusHeaders.IsSagaTimeoutMessage)?.value?.toLowerCase();
const isTimeout = (isTimeoutString ?? "") === "true";
if (isTimeout) routedMessage.type = RoutedMessageType.Timeout;
else if (message.receiving_endpoint.host_id === message.sending_endpoint.host_id && message.receiving_endpoint.name === message.sending_endpoint.name) routedMessage.type = RoutedMessageType.Local;
@@ -61,6 +62,7 @@ class MessageProcessingRouteItem implements MessageProcessingRoute {
readonly name?: string;
private _fromRoutedMessage?: RoutedMessageItem;
readonly processingHandler?: Handler;
+ uiRef?: SVGElement;
constructor(routedMessage?: RoutedMessageItem, processingHandler?: Handler) {
this._fromRoutedMessage = routedMessage;
diff --git a/src/Frontend/src/stores/SequenceDiagramStore.ts b/src/Frontend/src/stores/SequenceDiagramStore.ts
index 475129c07..456b155b3 100644
--- a/src/Frontend/src/stores/SequenceDiagramStore.ts
+++ b/src/Frontend/src/stores/SequenceDiagramStore.ts
@@ -1,11 +1,10 @@
-import { useFetchFromServiceControl } from "@/composables/serviceServiceControlUrls";
import { acceptHMRUpdate, defineStore } from "pinia";
import { ref, watch } from "vue";
import { ModelCreator } from "@/resources/SequenceDiagram/SequenceModel";
-import Message from "@/resources/Message";
import { Endpoint } from "@/resources/SequenceDiagram/Endpoint";
import { Handler } from "@/resources/SequenceDiagram/Handler";
import { MessageProcessingRoute } from "@/resources/SequenceDiagram/RoutedMessage";
+import { useMessageStore } from "./MessageStore";
export interface EndpointCentrePoint {
name: string;
@@ -25,7 +24,7 @@ export interface HandlerLocation {
export const Endpoint_Width = 260;
export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () => {
- const conversationId = ref();
+ const messageStore = useMessageStore();
const startX = ref(Endpoint_Width / 2);
const endpoints = ref([]);
@@ -37,26 +36,19 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
const handlerLocations = ref([]);
const highlightId = ref();
- watch(conversationId, async () => {
- if (!conversationId.value) return;
- const response = await useFetchFromServiceControl(`conversations/${conversationId.value}`);
- if (response.status === 404) {
- return;
- }
-
- const model = new ModelCreator((await response.json()) as Message[]);
- endpoints.value = model.endpoints;
- handlers.value = model.handlers;
- routes.value = model.routes;
- });
-
- function setConversationId(id: string) {
- endpoints.value = [];
- handlers.value = [];
- routes.value = [];
- startX.value = Endpoint_Width / 2;
- conversationId.value = id;
- }
+ watch(
+ () => messageStore.conversationData.data,
+ (conversationData) => {
+ if (conversationData.length) {
+ startX.value = Endpoint_Width / 2;
+ const model = new ModelCreator(conversationData);
+ endpoints.value = model.endpoints;
+ handlers.value = model.handlers;
+ routes.value = model.routes;
+ }
+ },
+ { immediate: true }
+ );
function setStartX(offset: number) {
const newValue = Math.max(offset + Endpoint_Width / 2, startX.value);
@@ -84,8 +76,11 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
highlightId.value = id;
}
+ function refreshConversation() {
+ if (messageStore.state.data.conversation_id) messageStore.loadConversation(messageStore.state.data.conversation_id);
+ }
+
return {
- setConversationId,
startX,
endpoints,
handlers,
@@ -101,6 +96,7 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
setEndpointCentrePoints,
setHandlerLocations,
setHighlightId,
+ refreshConversation,
};
});