Skip to content

Commit 6685389

Browse files
committed
Improvements to Flow diagram
1 parent 0905e39 commit 6685389

File tree

9 files changed

+102
-48
lines changed

9 files changed

+102
-48
lines changed

src/Frontend/package-lock.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"bootstrap": "^5.3.5",
2727
"bootstrap-icons": "^1.11.3",
2828
"codemirror": "^6.0.1",
29+
"hex-to-css-filter": "^6.0.0",
2930
"lodash.debounce": "^4.0.8",
3031
"lossless-json": "^4.0.2",
3132
"memoize-one": "^6.0.0",
Lines changed: 4 additions & 4 deletions
Loading

src/Frontend/src/components/TimeSince.vue

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
<script setup lang="ts">
22
import { onMounted, onUnmounted, ref } from "vue";
33
import moment from "moment";
4+
import { Tippy } from "vue-tippy";
45
56
const emptyDate = "0001-01-01T00:00:00";
67
78
const props = withDefaults(defineProps<{ dateUtc?: string; defaultTextOnFailure?: string; titleValue?: string }>(), { dateUtc: emptyDate, defaultTextOnFailure: "n/a", titleValue: undefined });
89
910
let interval: number | undefined = undefined;
1011
11-
const title = ref(),
12+
const title = ref<string[]>([]),
1213
text = ref();
1314
1415
function updateText() {
1516
if (props.dateUtc != null && props.dateUtc !== emptyDate) {
1617
const m = moment.utc(props.dateUtc);
1718
text.value = m.fromNow();
18-
title.value = props.titleValue ?? m.local().format("LLLL") + " (local)\n" + m.utc().format("LLLL") + " (UTC)";
19+
title.value = props.titleValue ? [props.titleValue] : [`${m.local().format("LLLL")} (local)`, `${m.utc().format("LLLL")} (UTC)`];
1920
} else {
2021
text.value = props.defaultTextOnFailure;
21-
title.value = props.titleValue ?? props.defaultTextOnFailure;
22+
title.value = [props.titleValue ?? props.defaultTextOnFailure];
2223
}
2324
}
2425
@@ -34,5 +35,10 @@ onUnmounted(() => window.clearInterval(interval));
3435
</script>
3536

3637
<template>
37-
<span :title="title">{{ text }}</span>
38+
<Tippy>
39+
<template #content>
40+
<div v-for="row in title" :key="row">{{ row }}</div>
41+
</template>
42+
<span>{{ text }}</span>
43+
</Tippy>
3844
</template>

src/Frontend/src/components/failedmessages/MessageList.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import TimeSince from "../TimeSince.vue";
44
import NoData from "../NoData.vue";
55
import routeLinks from "@/router/routeLinks";
66
import { FailedMessageStatus, ExtendedFailedMessage } from "@/resources/FailedMessage";
7+
import { hexToCSSFilter } from "hex-to-css-filter";
78
89
export interface IMessageList {
910
getSelectedMessages(): ExtendedFailedMessage[];
@@ -89,6 +90,7 @@ defineExpose<IMessageList>({
8990
isAnythingDisplayed,
9091
numberDisplayed,
9192
});
93+
const endpointColor = hexToCSSFilter("#929E9E").filter;
9294
</script>
9395

9496
<template>
@@ -122,7 +124,7 @@ defineExpose<IMessageList>({
122124
<span v-if="message.edited" :title="'Message was edited'" class="label sidebar-label label-info metadata-label">Edited</span>
123125

124126
<span class="metadata"><i class="fa fa-clock-o"></i> Failed: <time-since :dateUtc="message.time_of_failure"></time-since></span>
125-
<span class="metadata"><i class="fa pa-endpoint"></i> Endpoint: {{ message.receiving_endpoint.name }}</span>
127+
<span class="metadata"><i class="fa pa-endpoint" :style="{ filter: endpointColor }"></i> Endpoint: {{ message.receiving_endpoint.name }}</span>
126128
<span class="metadata"><i class="fa fa-laptop"></i> Machine: {{ message.receiving_endpoint.host }}</span>
127129
<span class="metadata" v-if="message.redirect"><i class="fa pa-redirect-source pa-redirect-small"></i> Redirect: {{ message.redirect }}</span>
128130
<!-- for deleted messages-->

src/Frontend/src/components/heartbeats/LastHeartbeat.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defineProps<{ date?: string; tooltipTarget: string }>();
1313
<p>No heartbeat data received for this {{ tooltipTarget }}.</p>
1414
<p>Have you installed and configured the <a target="_blank" href="https://docs.particular.net/monitoring/heartbeats/install-plugin">heartbeats plugin</a> for this {{ tooltipTarget }}?</p>
1515
</template>
16-
<span title="Last Heartbeat">No data available</span>
16+
<span>No data available</span>
1717
</tippy>
1818
</p>
1919
</template>

src/Frontend/src/components/messages/MessageView.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import HeadersView from "@/components/messages/HeadersView.vue";
2424
import StackTraceView from "@/components/messages/StacktraceView.vue";
2525
import { stringify, parse } from "lossless-json";
2626
import xmlFormat from "xml-formatter";
27+
import { hexToCSSFilter } from "hex-to-css-filter";
2728
2829
let refreshInterval: number | undefined;
2930
let pollingFaster = false;
@@ -278,6 +279,7 @@ onMounted(async () => {
278279
onUnmounted(() => {
279280
stopRefreshInterval();
280281
});
282+
const endpointColor = hexToCSSFilter("#929E9E").filter;
281283
</script>
282284

283285
<template>
@@ -315,7 +317,7 @@ onUnmounted(() => {
315317
<i class="fa fa-history"></i> <RouterLink :to="{ path: routeLinks.messages.failedMessage.link(failedMessage.edit_of), query: { back: route.path } }">View previous version</RouterLink>
316318
</span>
317319
<span v-if="failedMessage.time_of_failure" class="metadata"><i class="fa fa-clock-o"></i> Failed: <time-since :date-utc="failedMessage.time_of_failure"></time-since></span>
318-
<span class="metadata"><i class="fa pa-endpoint"></i> Endpoint: {{ failedMessage.receiving_endpoint.name }}</span>
320+
<span class="metadata"><i class="fa pa-endpoint" :style="{ filter: endpointColor }"></i> Endpoint: {{ failedMessage.receiving_endpoint.name }}</span>
319321
<span class="metadata"><i class="fa fa-laptop"></i> Machine: {{ failedMessage.receiving_endpoint.host }}</span>
320322
<span v-if="failedMessage.redirect" class="metadata"><i class="fa pa-redirect-source pa-redirect-small"></i> Redirect: {{ failedMessage.redirect }}</span>
321323
</div>

src/Frontend/src/components/messages2/FlowDiagram.vue

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
<script setup lang="ts">
22
import { onMounted, ref } from "vue";
3-
import { type DefaultEdge, MarkerType, VueFlow, type Styles, type Node } from "@vue-flow/core";
3+
import { type DefaultEdge, MarkerType, type Node, type Styles, VueFlow } from "@vue-flow/core";
44
import TimeSince from "../TimeSince.vue";
55
import routeLinks from "@/router/routeLinks";
6-
import Message from "@/resources/Message";
6+
import Message, { MessageStatus } from "@/resources/Message";
77
import { NServiceBusHeaders } from "@/resources/Header";
8-
import { Controls } from "@vue-flow/controls";
8+
import { ControlButton, Controls } from "@vue-flow/controls";
99
import { useMessageStore } from "@/stores/MessageStore";
1010
import LoadingSpinner from "@/components/LoadingSpinner.vue";
1111
import { storeToRefs } from "pinia";
12+
import EndpointDetails from "@/resources/EndpointDetails.ts";
13+
import { hexToCSSFilter } from "hex-to-css-filter";
14+
import TextEllipses from "@/components/TextEllipses.vue";
1215
1316
enum MessageType {
1417
Event = "Event message",
@@ -20,8 +23,8 @@ interface MappedMessage {
2023
nodeName: string;
2124
id: string;
2225
messageId: string;
23-
sendingEndpoint: string;
24-
receivingEndpoint: string;
26+
sendingEndpoint: EndpointDetails;
27+
receivingEndpoint: EndpointDetails;
2528
parentId: string;
2629
parentEndpoint: string;
2730
type: MessageType;
@@ -74,15 +77,12 @@ function mapMessage(message: Message): MappedMessage {
7477
nodeName: message.message_type,
7578
id: message.id,
7679
messageId: message.message_id,
77-
sendingEndpoint: message.sending_endpoint?.name,
78-
receivingEndpoint: message.receiving_endpoint?.name,
80+
sendingEndpoint: message.sending_endpoint,
81+
receivingEndpoint: message.receiving_endpoint,
7982
parentId,
8083
parentEndpoint,
8184
type,
82-
isError:
83-
message.headers.findIndex(function (x) {
84-
return x.key === NServiceBusHeaders.ExceptionInfoExceptionType;
85-
}) > -1,
85+
isError: message.status !== MessageStatus.Successful && message.status !== MessageStatus.ResolvedSuccessfully,
8686
sagaName,
8787
link: {
8888
name: `Link ${message.id}`,
@@ -105,8 +105,8 @@ function constructNodes(mappedMessages: MappedMessage[]): Node[] {
105105
const previousLevel = level > 0 ? messagesByLevel[level - 1] : null;
106106
return group.sort(
107107
(a, b) =>
108-
(previousLevel?.findIndex((plMessage) => a.parentId === plMessage.messageId && a.parentEndpoint === plMessage.receivingEndpoint) ?? 1) -
109-
(previousLevel?.findIndex((plMessage) => b.parentId === plMessage.messageId && b.parentEndpoint === plMessage.receivingEndpoint) ?? 1)
108+
(previousLevel?.findIndex((plMessage) => a.parentId === plMessage.messageId && a.parentEndpoint === plMessage.receivingEndpoint.name) ?? 1) -
109+
(previousLevel?.findIndex((plMessage) => b.parentId === plMessage.messageId && b.parentEndpoint === plMessage.receivingEndpoint.name) ?? 1)
110110
);
111111
})
112112
//flatten to actual flow diagram nodes, with positioning based on parent node/level
@@ -115,7 +115,7 @@ function constructNodes(mappedMessages: MappedMessage[]): Node[] {
115115
return group.reduce(
116116
({ result, currentWidth, previousParent }, message) => {
117117
//position on current level needs to be based on parent Node, so see if one exists
118-
const parentMessage = previousLevel?.find((plMessage) => message.parentId === plMessage.messageId && message.parentEndpoint === plMessage.receivingEndpoint) ?? null;
118+
const parentMessage = previousLevel?.find((plMessage) => message.parentId === plMessage.messageId && message.parentEndpoint === plMessage.receivingEndpoint.name) ?? null;
119119
//if the current parent node is the same as the previous parent node, then the current position needs to be to the right of siblings
120120
const currentParentWidth = previousParent === parentMessage ? currentWidth : 0;
121121
const startX = parentMessage == null ? 0 : parentMessage.XPos! - parentMessage.width! / 2;
@@ -125,7 +125,7 @@ function constructNodes(mappedMessages: MappedMessage[]): Node[] {
125125
result: [
126126
...result,
127127
{
128-
id: `${message.messageId}##${message.receivingEndpoint}`,
128+
id: `${message.messageId}##${message.receivingEndpoint.name}`,
129129
type: "message",
130130
data: message,
131131
label: message.nodeName,
@@ -148,7 +148,7 @@ function constructEdges(mappedMessages: MappedMessage[]): DefaultEdge[] {
148148
.map((message) => ({
149149
id: `${message.parentId}##${message.messageId}`,
150150
source: `${message.parentId}##${message.parentEndpoint}`,
151-
target: `${message.messageId}##${message.receivingEndpoint}`,
151+
target: `${message.messageId}##${message.receivingEndpoint.name}`,
152152
markerEnd: MarkerType.ArrowClosed,
153153
style: {
154154
"stroke-dasharray": message.type === MessageType.Event && "5, 3",
@@ -166,7 +166,7 @@ onMounted(async () => {
166166
167167
const assignDescendantLevelsAndWidth = (message: MappedMessage, level = 0) => {
168168
message.level = level;
169-
const children = mappedMessages.filter((mm) => mm.parentId === message.messageId && mm.parentEndpoint === message.receivingEndpoint);
169+
const children = mappedMessages.filter((mm) => mm.parentId === message.messageId && mm.parentEndpoint === message.receivingEndpoint.name);
170170
message.width =
171171
children.length === 0
172172
? 1 //leaf node
@@ -188,34 +188,52 @@ function typeIcon(type: MessageType) {
188188
return "pa-flow-command";
189189
}
190190
}
191+
192+
const showAddress = ref(false);
193+
194+
function toggleAddress() {
195+
showAddress.value = !showAddress.value;
196+
}
197+
198+
const blackColor = hexToCSSFilter("#000000").filter;
191199
</script>
192200

193201
<template>
194202
<div v-if="store.conversationData.failed_to_load" class="alert alert-info">FlowDiagram data is unavailable.</div>
195203
<LoadingSpinner v-else-if="store.conversationData.loading" />
196204
<div v-else id="tree-container">
197205
<VueFlow v-model="elements" :min-zoom="0.1" :fit-view-on-init="true">
198-
<Controls />
206+
<Controls position="top-left" class="controls">
207+
<ControlButton v-tippy="`Show endpoints`" @click="toggleAddress">
208+
<i class="fa pa-flow-endpoint" :style="{ filter: blackColor }"></i>
209+
</ControlButton>
210+
</Controls>
199211
<template #node-message="{ data }: { data: MappedMessage }">
212+
<div v-if="showAddress">
213+
<TextEllipses class="address" :text="`${data.sendingEndpoint.name}@${data.sendingEndpoint.host}`" />
214+
</div>
200215
<div class="node" :class="{ error: data.isError, 'current-message': data.id === store.state.data.id }">
201-
<div class="node-text wordwrap">
216+
<div class="node-text">
202217
<i v-if="data.isError" class="fa pa-flow-failed" />
203-
<i class="fa" :class="typeIcon(data.type)" :title="data.type" />
204-
<div class="lead right-side-ellipsis" :title="data.nodeName">
218+
<i class="fa" :class="typeIcon(data.type)" v-tippy="data.type" />
219+
<div class="lead">
205220
<strong>
206-
<RouterLink v-if="data.isError" :to="{ path: routeLinks.messages.failedMessage.link(data.id) }">{{ data.nodeName }}</RouterLink>
207-
<RouterLink v-else :to="{ path: routeLinks.messages.successMessage.link(data.messageId, data.id) }">{{ data.nodeName }}</RouterLink>
221+
<RouterLink v-if="data.isError" :to="{ path: routeLinks.messages.failedMessage.link(data.id) }"><TextEllipses style="width: 204px" :text="data.nodeName" ellipses-style="LeftSide" /></RouterLink>
222+
<RouterLink v-else :to="{ path: routeLinks.messages.successMessage.link(data.messageId, data.id) }"><TextEllipses style="width: 204px" :text="data.nodeName" ellipses-style="LeftSide" /></RouterLink>
208223
</strong>
209224
</div>
210-
<span class="time-sent">
225+
<div class="time-sent">
211226
<time-since class="time-since" :date-utc="data.timeSent" />
212-
</span>
227+
</div>
213228
<template v-if="data.sagaName">
214229
<i class="fa pa-flow-saga" />
215-
<div class="saga lead right-side-ellipsis" :title="data.sagaName">{{ data.sagaName }}</div>
230+
<div class="saga lead"><TextEllipses style="width: 182px" :text="data.sagaName" ellipses-style="LeftSide" /></div>
216231
</template>
217232
</div>
218233
</div>
234+
<div v-if="showAddress">
235+
<TextEllipses class="address" :text="`${data.receivingEndpoint.name}@${data.receivingEndpoint.host}`" />
236+
</div>
219237
</template>
220238
</VueFlow>
221239
</div>
@@ -230,6 +248,12 @@ function typeIcon(type: MessageType) {
230248
<style scoped>
231249
@import "../list.css";
232250
251+
.controls {
252+
display: flex;
253+
flex-wrap: wrap;
254+
justify-content: center;
255+
}
256+
233257
#tree-container {
234258
width: 90vw;
235259
height: 60vh;
@@ -240,7 +264,6 @@ function typeIcon(type: MessageType) {
240264
--vf-box-shadow: var(--vf-node-color, #1a192b);
241265
background: var(--vf-node-bg);
242266
border-color: var(--vf-node-color, #1a192b);
243-
padding: 10px;
244267
border-radius: 3px;
245268
font-size: 12px;
246269
border-width: 1px;
@@ -249,11 +272,6 @@ function typeIcon(type: MessageType) {
249272
text-align: left;
250273
}
251274
252-
.right-side-ellipsis {
253-
direction: rtl;
254-
text-align: left;
255-
}
256-
257275
.node {
258276
background-color: #fff;
259277
border-color: #cccbcc;
@@ -269,7 +287,6 @@ function typeIcon(type: MessageType) {
269287
}
270288
271289
.node .time-sent .time-since {
272-
display: block;
273290
margin-left: 20px;
274291
padding-top: 0;
275292
color: #777f7f;
@@ -290,7 +307,6 @@ function typeIcon(type: MessageType) {
290307
291308
.node-text .lead {
292309
display: inline-block;
293-
width: 204px;
294310
position: relative;
295311
top: 4px;
296312
}
@@ -302,7 +318,12 @@ function typeIcon(type: MessageType) {
302318
303319
.node-text .lead.saga {
304320
font-weight: normal;
305-
width: 182px;
321+
}
322+
323+
.address {
324+
color: #777f7f;
325+
font-size: 0.8em;
326+
width: 264px;
306327
}
307328
308329
.current-message {
@@ -370,6 +391,14 @@ function typeIcon(type: MessageType) {
370391
text-decoration: underline;
371392
}
372393
394+
.pa-flow-endpoint {
395+
background-image: url("@/assets/endpoint.svg");
396+
background-position: center;
397+
background-repeat: no-repeat;
398+
height: 15px;
399+
width: 15px;
400+
}
401+
373402
.pa-flow-failed {
374403
background-image: url("@/assets/failed-msg.svg");
375404
background-position: center;

0 commit comments

Comments
 (0)