Skip to content
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b148921
include audit section displaying audit messages
PhilBastian Mar 6, 2025
105cc68
show status icons
PhilBastian Mar 10, 2025
337fdef
Merge branch 'master' into si_integration
PhilBastian Mar 11, 2025
dd2cd69
add feature flag
PhilBastian Mar 11, 2025
36d7815
format message type as per SI
PhilBastian Mar 11, 2025
b281cf3
change name from audit to all messages and include message view route
PhilBastian Mar 11, 2025
f706a7f
display time_sent and processing_time values
PhilBastian Mar 11, 2025
db87e15
fix the body of the popup message in retry dialog
soujay Mar 13, 2025
ef84c78
Revert "fix the body of the popup message in retry dialog"
soujay Mar 13, 2025
2616724
enable auto refresh to be paused and to be configured after creation
PhilBastian Mar 13, 2025
0065abb
manual/auto refresh control
PhilBastian Mar 13, 2025
6933c0f
Merge branch 'si_integration' of https://github.com/Particular/Servic…
PhilBastian Mar 13, 2025
10ae9af
stripe results grid
PhilBastian Mar 13, 2025
0a510d0
change to use emits
PhilBastian Mar 13, 2025
ba62324
remove commented code
PhilBastian Mar 14, 2025
60e4bfa
add paging
PhilBastian Mar 14, 2025
322f801
implement server side sorting
PhilBastian Mar 14, 2025
9cf0ac4
scroll on list instead of view, allowing the table headers to always …
PhilBastian Mar 14, 2025
338d370
Merge branch 'master' into si_integration
PhilBastian Mar 16, 2025
3b206eb
change to use messages routing
PhilBastian Mar 16, 2025
9e77b04
increase default page size
PhilBastian Mar 16, 2025
be36fd2
add failedmessage redirect and remove unused message components
PhilBastian Mar 17, 2025
c2f286e
start converting conversation model from SI
PhilBastian Mar 18, 2025
83592dc
order messages
PhilBastian Mar 18, 2025
0a07896
Merge branch 'master' into sequence_diagram
PhilBastian Mar 19, 2025
6a3fdab
add endpoint host versions
PhilBastian Mar 19, 2025
b2e1204
convert handleritem logic from SI
PhilBastian Mar 19, 2025
72df26f
convert routedMessages (arrows) from SI
PhilBastian Mar 19, 2025
a89730a
display endpoints
PhilBastian Apr 1, 2025
90a0052
timeline
PhilBastian Apr 1, 2025
eb5ffa5
handlers
PhilBastian Apr 2, 2025
7350293
arrow (left to right)
PhilBastian Apr 2, 2025
19171da
expand SVG to fit larger diagrams
PhilBastian Apr 2, 2025
f24f540
fix handler map
PhilBastian Apr 3, 2025
a9b6488
fix messagetype text side
PhilBastian Apr 3, 2025
5108f2a
replace props and emits with a store
PhilBastian Apr 3, 2025
39929c2
highlight on handlers
PhilBastian Apr 3, 2025
29f435a
implement route highlight
PhilBastian Apr 3, 2025
2de64d6
hide sequence diagram behind feature flag
PhilBastian Apr 3, 2025
c926f54
Merge branch 'master' into sequence_diagram
PhilBastian Apr 3, 2025
9303f63
support local and response message flow
PhilBastian Apr 4, 2025
ff1666f
handle long message names being pushed off the svg surface
PhilBastian Apr 4, 2025
35e4232
handle handler messagetext offset less than 0 (buggy)
PhilBastian Apr 4, 2025
b32a4fd
display event as dashed line
PhilBastian Apr 4, 2025
9645d4a
handle events in SequenceDiagram
PhilBastian Apr 4, 2025
0b0f590
address comments from review
PhilBastian Apr 7, 2025
c62f833
Merge branch 'sequence_diagram' into reverse_local_events_timeout
PhilBastian Apr 7, 2025
821947e
add comment for clarity
PhilBastian Apr 7, 2025
799eebb
Merge branch 'master' into reverse_local_events_timeout
PhilBastian Apr 7, 2025
20f3a71
fix merge issue
PhilBastian Apr 7, 2025
ec1a5b7
simplify name uniqueness
PhilBastian Apr 7, 2025
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 @@ -89,7 +89,7 @@ async function deleteInstance(instance: EndpointsView) {

async function deleteAllInstances() {
try {
await Promise.all(sortedInstances.value.filter((instance) => instance.name === endpointName).map((instance) => store.deleteEndpointInstance(instance)));
await Promise.all(sortedInstances.value.filter((instance: EndpointsView) => instance.name === endpointName).map((instance: EndpointsView) => store.deleteEndpointInstance(instance)));
useShowToast(TYPE.SUCCESS, "Endpoint deleted", "", false, { timeout: 1000 });
await router.replace(backLink.value);
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import HeartbeatsList from "./HeartbeatsList.vue";
import { ref } from "vue";
import ConfirmDialog from "@/components/ConfirmDialog.vue";
import ResultsCount from "../ResultsCount.vue";
import { LogicalEndpoint } from "@/resources/Heartbeat";

enum Operation {
Track = "track",
Expand All @@ -35,7 +36,7 @@ async function proceedWarningDialog() {

try {
await store.updateEndpointSettings(
filteredEndpoints.value.filter((endpoint) => (dialogWarningOperation.value === Operation.Track && !endpoint.track_instances) || (dialogWarningOperation.value === Operation.DoNotTrack && endpoint.track_instances))
filteredEndpoints.value.filter((endpoint: LogicalEndpoint) => (dialogWarningOperation.value === Operation.Track && !endpoint.track_instances) || (dialogWarningOperation.value === Operation.DoNotTrack && endpoint.track_instances))
);
useShowToast(TYPE.SUCCESS, `All endpoints set to '${dialogWarningOperation.value}'`, "", false, { timeout: 1000 });
} catch {
Expand Down
8 changes: 6 additions & 2 deletions src/Frontend/src/components/messages/MessageView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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";
Expand Down Expand Up @@ -43,6 +44,7 @@ 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}`);
Expand Down Expand Up @@ -71,7 +73,7 @@ async function loadFailedMessage() {
}

updateMessageDeleteDate(message);
await downloadHeadersAndBody(message);
await fetchMessageDetails(message);
failedMessage.value = message;
}

Expand Down Expand Up @@ -115,7 +117,7 @@ async function retryMessage() {
}
}

async function downloadHeadersAndBody(message: ExtendedFailedMessage) {
async function fetchMessageDetails(message: ExtendedFailedMessage) {
if (isError(message)) return;

try {
Expand Down Expand Up @@ -349,11 +351,13 @@ onUnmounted(() => {
<h5 :class="{ active: panel === 2 }" class="nav-item" @click.prevent="togglePanel(2)"><a href="#">Message body</a></h5>
<h5 :class="{ active: panel === 3 }" class="nav-item" @click.prevent="togglePanel(3)"><a href="#">Headers</a></h5>
<h5 v-if="!isMassTransitConnected" :class="{ active: panel === 4 }" class="nav-item" @click.prevent="togglePanel(4)"><a href="#">Flow Diagram</a></h5>
<h5 v-if="showAllMessages" :class="{ active: panel === 5 }" class="nav-item" @click.prevent="togglePanel(5)"><a href="#">Sequence Diagram</a></h5>
</div>
<StackTraceView v-if="panel === 1 && failedMessage.exception?.stack_trace" :message="failedMessage" />
<BodyView v-if="panel === 2" :message="failedMessage" />
<HeadersView v-if="panel === 3" :message="failedMessage" />
<FlowDiagram v-if="panel === 4" :message="failedMessage" />
<SequenceDiagram v-if="showAllMessages && panel === 5" />
</div>
</div>

Expand Down
46 changes: 46 additions & 0 deletions src/Frontend/src/components/messages/SequenceDiagram.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import Endpoints from "./SequenceDiagram/EndpointsComponent.vue";
import Timeline from "./SequenceDiagram/TimelineComponent.vue";
import Handlers from "./SequenceDiagram/HandlersComponent.vue";
import Routes from "./SequenceDiagram/RoutesComponent.vue";
import { useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
import { storeToRefs } from "pinia";

const store = useSequenceDiagramStore();
store.setConversationId("39907d51-12e5-4202-82c3-b2b30077ebd4");

const { maxWidth, maxHeight } = storeToRefs(store);
</script>

<template>
<div class="outer">
<svg class="sequence-diagram" :width="`max(100%, ${isNaN(maxWidth) ? 0 : maxWidth}px)`" :height="maxHeight + 20">
<Endpoints />
<Timeline />
<Handlers />
<Routes />
</svg>
</div>
</template>

<style scoped>
.outer {
max-width: 100%;
max-height: calc(100vh - 27em);
overflow: auto;
}

.sequence-diagram {
--error: red;
--gray20: #333333;
--gray30: #444444;
--gray40: #666666;
--gray60: #999999;
--gray80: #cccccc;
--gray90: #e6e6e6;
--gray95: #b3b3b3;
--highlight: #0b6eef;
--highlight-background: #c5dee9;
background: white;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<script setup lang="ts">
import { Endpoint } from "@/resources/SequenceDiagram/Endpoint";
import { Endpoint_Width, EndpointCentrePoint, useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
import { storeToRefs } from "pinia";
import { computed, ref, watch } from "vue";

interface EndpointWithLocation extends Endpoint {
width: number;
textWidth: number;
x?: number;
surround?: EndpointSurround;
}

interface EndpointSurround {
x: number;
y: number;
width: number;
height: number;
fill: string;
rx: string;
strokeWidth: string;
stroke: string;
}

const Endpoint_Gap = 30;
const Endpoint_Image_Width = 20;

const store = useSequenceDiagramStore();
const { startX, endpoints } = storeToRefs(store);

const epRefs = ref<SVGTextElement[]>([]);
const endpointItems = computed(() =>
endpoints.value.map((x, index) => {
const endpoint = x as EndpointWithLocation;
const el = epRefs.value[index];
if (el) {
const bounds = el.getBBox();
const previousEndpoint = index > 0 ? endpointItems.value[index - 1] : undefined;
endpoint.width = Math.max(Endpoint_Width, bounds.width);
endpoint.textWidth = bounds.width;
endpoint.x = (previousEndpoint?.x ?? startX.value) + (previousEndpoint?.width ?? 0) + Endpoint_Gap;

if (!endpoint.surround && el.isConnected) {
const style = getComputedStyle(el);
const padding_top = parseInt(style.getPropertyValue("padding-top"));
const padding_left = parseInt(style.getPropertyValue("padding-left"));
const padding_right = parseInt(style.getPropertyValue("padding-right"));
const padding_bottom = parseInt(style.getPropertyValue("padding-bottom"));
endpoint.surround = {
x: endpoint.x - endpoint.width / 2 - padding_left,
y: bounds.y - padding_top,
width: endpoint.width + padding_left + padding_right,
height: bounds.height + padding_top + padding_bottom,
fill: style.getPropertyValue("background-color"),
rx: style.getPropertyValue("border-radius"),
strokeWidth: style.getPropertyValue("border-top-width"),
stroke: style.getPropertyValue("border-top-color"),
};
}
}
return endpoint;
})
);

watch(endpointItems, () => {
store.setEndpointCentrePoints(endpointItems.value.map((endpoint) => ({ name: endpoint.name, centre: endpoint.x ?? 0, top: (endpoint.surround?.y ?? 0) + (endpoint.surround?.height ?? 0) + 15 }) as EndpointCentrePoint));
const lastEndpoint = endpointItems.value[endpointItems.value.length - 1];
store.setMaxWidth((lastEndpoint.x ?? 0) + lastEndpoint.width);
});

watch(startX, () => {
epRefs.value = [];
endpoints.value.forEach((endpoint) => ((endpoint as EndpointWithLocation).surround = undefined));
});

function setEndpointRef(el: SVGTextElement, index: number) {
if (el) epRefs.value[index] = el;
}
</script>

<template>
<g v-for="(endpoint, i) in endpointItems" :key="endpoint.name" transform="translate(0,15)">
<rect
v-if="endpoint.surround"
:x="endpoint.surround.x"
:y="endpoint.surround.y"
:width="endpoint.surround.width"
:height="endpoint.surround.height"
:fill="endpoint.surround.fill"
:rx="endpoint.surround.rx"
:stroke-width="endpoint.surround.strokeWidth"
:stroke="endpoint.surround.stroke"
></rect>
<g :transform="`translate(${(endpoint.x ?? Endpoint_Width / 2) - ((endpoint.textWidth ?? 0) + Endpoint_Image_Width) / 2},0)`">
<path fill="var(--gray40)" d="M 0,0 M 18,18 M 0,2 v 14 h 14 v -4 h -6 v -6 h 6 v -4 h -14 M 9,7 v 4 h 9 v -4"></path>
<text :x="Endpoint_Image_Width" y="10" alignment-baseline="middle" text-anchor="start" :ref="(el) => setEndpointRef(el as SVGTextElement, i)">{{ endpoint.name }}</text>
</g>
</g>
</template>

<style scoped>
text {
background: var(--gray90);
border-radius: 5px;
padding: 0.5em;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<script setup lang="ts">
import { HandlerState } from "@/resources/SequenceDiagram/Handler";
import { computed, ref } from "vue";
import { Direction } from "@/resources/SequenceDiagram/RoutedMessage";
import { useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
import { storeToRefs } from "pinia";

const Height_Per_Out = 40;
const Handler_Gap = 20;
const Handler_Width = 14;

const store = useSequenceDiagramStore();
const { handlers, endpointCentrePoints, highlightId } = storeToRefs(store);

const messageTypeRefs = ref<SVGTextElement[]>([]);

const handlerItems = computed(() => {
let nextY = 0;
const result = handlers.value.map((handler, index) => {
const endpoint = endpointCentrePoints.value.find((cp) => cp.name === handler.endpoint.name)!;
const messageTypeElement = messageTypeRefs.value[index];
const count = handler.outMessages.length;
const height = (count === 0 ? 1 : count) * Height_Per_Out;
if (nextY === 0) nextY += Handler_Gap + (endpoint?.top ?? 0);
const y = nextY;
nextY += height + Handler_Gap;
const fill = (() => {
if (handler.id === "First") return "black";
if (handler.state === HandlerState.Fail) return "var(--error)";
if (handler.route?.name === highlightId.value) return "var(--highlight-background)";
return "var(--gray60)";
})();
const icon = (() => {
if (handler.id === "First") return "M0,0L8,4 0,8z";
if (handler.state === HandlerState.Fail) return "M6,0L0,6 6,12 12,6 6,0z M7,9L5,9 5,8 7,8 7,9z M5,7L5,3 7,3 7,7 5,7z";
return null;
})();
const iconSize = (() => {
if (handler.id === "First") return 8;
if (handler.state === HandlerState.Fail) return 12;
return 0;
})();

const messageTypeOffset = handler.direction === Direction.Right ? ((messageTypeElement?.getBBox().width ?? 0) + 24) * -1 : 20;
if (messageTypeOffset < 0) {
store.setStartX(-1 * messageTypeOffset);
}

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,
y,
height,
fill,
icon,
iconSize,
messageType: handler.name,
messageTypeOffset,
messageTypeHighlight: handler.route?.name === highlightId.value,
};
});

store.setMaxHeight(nextY);
store.setHandlerLocations(result.map((handler) => ({ id: handler.id, endpointName: handler.endpointName, left: handler.left, right: handler.right, y: handler.y, height: handler.height })));

return result;
});

function setMessageTypeRef(el: SVGTextElement, index: number) {
if (el) messageTypeRefs.value[index] = el;
}
</script>

<template>
<g>
<g v-for="(handler, i) in handlerItems" :key="handler.id" :transform="`translate(${handler.left}, ${handler.y})`">
<!--Handler Activation Box-->
<rect :width="Handler_Width" :height="handler.height" :class="handler.incomingId && 'clickable'" :fill="handler.fill" @mouseover="() => store.setHighlightId(handler.incomingId)" @mouseleave="() => store.setHighlightId()" />
<path v-if="handler.icon" :d="handler.icon" fill="white" :transform="`translate(${Handler_Width / 2 - handler.iconSize / 2}, ${handler.height / 2 - handler.iconSize / 2})`" />
<!--Message Type and Icon-->
<g
v-if="handler.messageType"
:transform="`translate(${handler.messageTypeOffset}, 4)`"
class="clickable"
:fill="handler.messageTypeHighlight ? 'var(--highlight)' : 'var(--gray40)'"
@mouseover="() => store.setHighlightId(handler.incomingId)"
@mouseleave="() => store.setHighlightId()"
>
<path d="M9,3L9,3 9,0 0,0 0,3 4,3 4,6 0,6 0,9 4,9 4,12 0,12 0,15 9,15 9,12 5,12 5,9 9,9 9,6 5,6 5,3z" />
<text x="14" y="10" alignment-baseline="middle" :ref="(el) => setMessageTypeRef(el as SVGTextElement, i)">{{ handler.messageType }}</text>
</g>
</g>
</g>
</template>

<style scoped>
.clickable {
cursor: pointer;
}
</style>
Loading