Skip to content

Commit c08a217

Browse files
committed
Merge branch 'master' into pi_770_componentisation_endpointlists
2 parents d9c5224 + 7971763 commit c08a217

19 files changed

+427
-420
lines changed

.github/workflows/push-container-images.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
username: ${{ secrets.DOCKERHUB_USERNAME }}
3434
password: ${{ secrets.DOCKERHUB_TOKEN }}
3535
- name: Set up Docker Buildx
36-
uses: docker/setup-buildx-action@v3.10.0
36+
uses: docker/setup-buildx-action@v3.11.1
3737
- name: Publish to Docker Hub
3838
run: |
3939
$tags = "${{ steps.validate.outputs.container-tags }}" -Split ','

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ jobs:
140140
with:
141141
version: ${{ env.MinVerVersion }}
142142
- name: Set up Docker Buildx
143-
uses: docker/setup-buildx-action@v3.10.0
143+
uses: docker/setup-buildx-action@v3.11.1
144144
- name: Log in to GitHub container registry
145145
uses: docker/[email protected]
146146
with:

src/Frontend/package-lock.json

Lines changed: 306 additions & 285 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Frontend/package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717
"test:application": "npm run test:application:vitest"
1818
},
1919
"dependencies": {
20-
"@codemirror/lang-json": "^6.0.1",
20+
"@codemirror/lang-json": "^6.0.2",
2121
"@codemirror/lang-xml": "^6.1.0",
2222
"@codemirror/legacy-modes": "^6.5.1",
23-
"@dagrejs/dagre": "^1.1.4",
23+
"@dagrejs/dagre": "^1.1.5",
2424
"@tinyhttp/content-disposition": "^2.2.2",
2525
"@vue-flow/controls": "^1.1.2",
26-
"@vue-flow/core": "^1.44.0",
26+
"@vue-flow/core": "^1.45.0",
2727
"@vuepic/vue-datepicker": "^11.0.2",
28-
"bootstrap": "^5.3.5",
29-
"codemirror": "^6.0.1",
28+
"bootstrap": "^5.3.7",
29+
"codemirror": "^6.0.2",
3030
"diff": "^8.0.2",
3131
"hex-to-css-filter": "^6.0.0",
3232
"lodash.debounce": "^4.0.8",
@@ -35,8 +35,8 @@
3535
"memoize-one": "^6.0.0",
3636
"moment": "^2.30.1",
3737
"pinia": "^3.0.3",
38-
"vue": "^3.5.16",
39-
"vue-codemirror6": "^1.3.17",
38+
"vue": "^3.5.17",
39+
"vue-codemirror6": "^1.3.20",
4040
"vue-router": "^4.5.1",
4141
"vue-tippy": "^6.7.1",
4242
"vue-toastification": "^2.0.0-rc.5",
@@ -45,32 +45,32 @@
4545
"xml-formatter": "^3.6.6"
4646
},
4747
"devDependencies": {
48-
"@eslint/js": "^9.28.0",
48+
"@eslint/js": "^9.29.0",
4949
"@pinia/testing": "^1.0.2",
5050
"@testing-library/dom": "^10.4.0",
5151
"@testing-library/jest-dom": "^6.6.3",
5252
"@testing-library/user-event": "^14.6.1",
5353
"@testing-library/vue": "^8.1.0",
5454
"@tsconfig/node18": "^18.2.4",
5555
"@types/bootstrap": "^5.2.10",
56-
"@types/lodash": "^4.17.17",
57-
"@types/node": "^22.15.29",
56+
"@types/lodash": "^4.17.18",
57+
"@types/node": "^24.0.3",
5858
"@vitejs/plugin-vue": "^5.2.4",
59-
"@vitest/coverage-v8": "^3.2.2",
59+
"@vitest/coverage-v8": "^3.2.4",
6060
"@vue/tsconfig": "^0.7.0",
6161
"cross-env": "^7.0.3",
62-
"eslint": "^9.28.0",
62+
"eslint": "^9.29.0",
6363
"eslint-config-prettier": "^10.1.5",
64-
"eslint-plugin-prettier": "^5.4.1",
64+
"eslint-plugin-prettier": "^5.5.0",
6565
"eslint-plugin-promise": "^7.2.1",
66-
"eslint-plugin-vue": "^10.1.0",
66+
"eslint-plugin-vue": "^10.2.0",
6767
"flush-promises": "^1.0.2",
6868
"globals": "^16.2.0",
6969
"jsdom": "^26.1.0",
70-
"msw": "^2.9.0",
70+
"msw": "^2.10.2",
7171
"prettier": "^3.5.3",
7272
"typescript": "^5.8.3",
73-
"typescript-eslint": "^8.33.1",
73+
"typescript-eslint": "^8.34.1",
7474
"vite": "^6.3.4",
7575
"vite-plugin-checker": "^0.9.1",
7676
"vitest": "^3.1.2",

src/Frontend/src/components/messages/SagaDiagram.spec.ts

Lines changed: 47 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface componentDSL {
1414

1515
//Defines a domain-specific language (DSL) for checking assertions against the system under test (sut)
1616
interface componentDSLAssertions {
17-
thereAreTheFollowingSagaChangesInThisOrder(sagaUpdates: { expectedRenderedLocalTime: string }[]): void;
17+
thereAreTheFollowingSagaChangesInThisOrder(expectedDatesInOrder: Date[]): void;
1818
displayedSagaGuidIs(sagaId: string): void;
1919
displayedSagaNameIs(humanizedSagaName: string): void;
2020
linkIsShown(arg0: { withText: string; withHref: string }): void;
@@ -109,8 +109,15 @@ describe("Feature: 3 Visual Representation of Saga Timeline", () => {
109109
});
110110
});
111111

112-
describe("Rule: 3.2 Display a chronological timeline of saga events in UTC.", () => {
113-
test("EXAMPLE: Rendering a Saga with 4 changes", () => {
112+
describe("Rule: 3.3 Display a chronological timeline of saga events localized to user environment.", () => {
113+
test.each([
114+
{
115+
timezone: "UTC",
116+
},
117+
{
118+
timezone: "America/Los_Angeles",
119+
},
120+
])("EXAMPLE: Rendering a Saga with 4 changes - User Timezone $timezone", ({ timezone }) => {
114121
// Each saga event ("Saga Initiated," "Saga Updated," "Timeout Invoked," "Saga Completed") is timestamped to represent progression over time. Events are ordered by the time they ocurred.
115122
//TODO: "Incoming messages are displayed on the left, and outgoing messages are displayed on the right." in another test?
116123

@@ -127,23 +134,33 @@ describe("Feature: 3 Visual Representation of Saga Timeline", () => {
127134

128135
// Set the environment to a fixed timezone
129136
// JSDOM, used by Vitest, defaults to UTC timezone
130-
// To ensure consistency, explicitly set the timezone to UTC
137+
// To ensure consistency, explicitly set the timezone
131138
// This ensures that the rendered local time of the saga changes
132-
// will always be interpreted and displayed in UTC, avoiding flakiness
133-
process.env.TZ = "UTC";
139+
// will always be interpreted and displayed in the specified timezone, avoiding flakiness
140+
process.env.TZ = timezone;
134141

135142
//access each of the saga changes and update its start time and finish time to the same values being read from the variable declaration,
136143
// but set them again explicitly here
137144
//so that the reader of this test can see the preconditions at play
138145
//and understand the test better without having to jump around
139-
sampleSagaHistory.changes[0].start_time = new Date("2025-03-28T03:04:08.3819211Z"); // A
140-
sampleSagaHistory.changes[0].finish_time = new Date("2025-03-28T03:04:08.3836Z"); // A1
141-
sampleSagaHistory.changes[1].start_time = new Date("2025-03-28T03:04:07.5416262Z"); // B
142-
sampleSagaHistory.changes[1].finish_time = new Date("2025-03-28T03:04:07.5509712Z"); //B1
143-
sampleSagaHistory.changes[2].start_time = new Date("2025-03-28T03:04:06.3088353Z"); //C
144-
sampleSagaHistory.changes[2].finish_time = new Date("2025-03-28T03:04:06.3218175Z"); //C1
145-
sampleSagaHistory.changes[3].start_time = new Date("2025-03-28T03:04:05.3332078Z"); //D
146-
sampleSagaHistory.changes[3].finish_time = new Date("2025-03-28T03:04:05.3799483Z"); //D1
146+
147+
const startTimeA = new Date("2025-03-28T03:04:08.000Z");
148+
const finishTimeA1 = new Date("2025-03-28T03:04:08.000Z");
149+
const startTimeB = new Date("2025-03-28T03:04:07.000Z");
150+
const finishTimeB1 = new Date("2025-03-28T03:04:07.000Z");
151+
const startTimeC = new Date("2025-03-28T03:04:06.000Z");
152+
const finishTimeC1 = new Date("2025-03-28T03:04:06.000Z");
153+
const startTimeD = new Date("2025-03-28T03:04:05.000Z");
154+
const finishTimeD1 = new Date("2025-03-28T03:04:05.000Z");
155+
156+
sampleSagaHistory.changes[0].start_time = startTimeA;
157+
sampleSagaHistory.changes[0].finish_time = finishTimeA1;
158+
sampleSagaHistory.changes[1].start_time = startTimeB;
159+
sampleSagaHistory.changes[1].finish_time = finishTimeB1;
160+
sampleSagaHistory.changes[2].start_time = startTimeC;
161+
sampleSagaHistory.changes[2].finish_time = finishTimeC1;
162+
sampleSagaHistory.changes[3].start_time = startTimeD;
163+
sampleSagaHistory.changes[3].finish_time = finishTimeD1;
147164
sampleSagaHistory.changes[3].status = "new";
148165

149166
//B(1), C(2), A(0), D(3)
@@ -158,84 +175,7 @@ describe("Feature: 3 Visual Representation of Saga Timeline", () => {
158175
});
159176

160177
//assert
161-
162-
componentDriver.assert.thereAreTheFollowingSagaChangesInThisOrder([
163-
{
164-
expectedRenderedLocalTime: "3/28/2025 3:04:05 AM",
165-
},
166-
{
167-
expectedRenderedLocalTime: "3/28/2025 3:04:06 AM",
168-
},
169-
{
170-
expectedRenderedLocalTime: "3/28/2025 3:04:07 AM",
171-
},
172-
{
173-
expectedRenderedLocalTime: "3/28/2025 3:04:08 AM",
174-
},
175-
]);
176-
});
177-
});
178-
describe("Rule: 3.3 Display a chronological timeline of saga events in PST.", () => {
179-
test("EXAMPLE: Rendering a Saga with 4 changes", () => {
180-
// Each saga event ("Saga Initiated," "Saga Updated," "Timeout Invoked," "Saga Completed") is timestamped to represent progression over time. Events are ordered by the time they ocurred.
181-
//TODO: "Incoming messages are displayed on the left, and outgoing messages are displayed on the right." in another test?
182-
183-
//arragement
184-
//sampleSagaHistory already not sorted TODO: Make this more clear so the reader of this test doesn't have to go arround and figure out the preconditions
185-
const messageStore = {} as MessageStore;
186-
messageStore.state = {} as MessageStore["state"];
187-
messageStore.state.data = {} as MessageStore["state"]["data"];
188-
messageStore.state.data.invoked_saga = {
189-
has_saga: true,
190-
saga_id: "123",
191-
saga_type: "ServiceControl.SmokeTest.AuditingSaga",
192-
};
193-
194-
// Set the environment to a fixed timezone
195-
// JSDOM, used by Vitest, defaults to PST timezone
196-
// To ensure consistency, explicitly set the timezone to PST
197-
// This ensures that the rendered local time of the saga changes
198-
// will always be interpreted and displayed in "America/Los_Angeles"
199-
process.env.TZ = "America/Los_Angeles";
200-
201-
//access each of the saga changes and update its start time and finish time to the same values being read from the variable declaration,
202-
// but set them again explicitly here
203-
//so that the reader of this test can see the preconditions at play
204-
//and understand the test better without having to jump around
205-
sampleSagaHistory.changes[0].start_time = new Date("2025-03-28T03:04:08.3819211Z"); // A
206-
sampleSagaHistory.changes[0].finish_time = new Date("2025-03-28T03:04:08.3836Z"); // A1
207-
sampleSagaHistory.changes[1].start_time = new Date("2025-03-28T03:04:07.5416262Z"); // B
208-
sampleSagaHistory.changes[1].finish_time = new Date("2025-03-28T03:04:07.5509712Z"); //B1
209-
sampleSagaHistory.changes[2].start_time = new Date("2025-03-28T03:04:06.3088353Z"); //C
210-
sampleSagaHistory.changes[2].finish_time = new Date("2025-03-28T03:04:06.3218175Z"); //C1
211-
sampleSagaHistory.changes[3].start_time = new Date("2025-03-28T03:04:05.3332078Z"); //D
212-
sampleSagaHistory.changes[3].finish_time = new Date("2025-03-28T03:04:05.3799483Z"); //D1
213-
sampleSagaHistory.changes[3].status = "new";
214-
215-
// Set up the store with sample saga history
216-
const componentDriver = rendercomponent({
217-
initialState: {
218-
MessageStore: messageStore,
219-
SagaDiagramStore: { sagaHistory: sampleSagaHistory },
220-
},
221-
});
222-
223-
//assert
224-
225-
componentDriver.assert.thereAreTheFollowingSagaChangesInThisOrder([
226-
{
227-
expectedRenderedLocalTime: "3/27/2025 8:04:05 PM",
228-
},
229-
{
230-
expectedRenderedLocalTime: "3/27/2025 8:04:06 PM",
231-
},
232-
{
233-
expectedRenderedLocalTime: "3/27/2025 8:04:07 PM",
234-
},
235-
{
236-
expectedRenderedLocalTime: "3/27/2025 8:04:08 PM",
237-
},
238-
]);
178+
componentDriver.assert.thereAreTheFollowingSagaChangesInThisOrder([startTimeD, startTimeC, startTimeB, startTimeA]);
239179
});
240180
});
241181
});
@@ -317,21 +257,29 @@ function rendercomponent({ initialState = {} }: { initialState?: { MessageStore?
317257
expect(sagaGuid).toBeInTheDocument();
318258
expect(sagaGuid).toHaveTextContent(guid);
319259
},
320-
thereAreTheFollowingSagaChangesInThisOrder: function (sagaUpdates: { expectedRenderedLocalTime: string }[]): void {
260+
thereAreTheFollowingSagaChangesInThisOrder: function (expectedDatesInOrder: Date[]): void {
321261
//Retrive the main parent component that contains the saga changes
322262
const sagaChangesContainer = screen.getByRole("table", { name: /saga-sequence-list/i });
323263

324264
const sagaUpdatesElements = within(sagaChangesContainer).queryAllByRole("row");
325-
//from within each sagaUpdatesElemtns get the values of an element with aria-label="time stamp"
326-
//and check if the values are in the same order as the sagaUpdates array passed to this function
265+
//from within each sagaUpdatesElements get the values of an element with aria-label="time stamp"
266+
//and check if the values are in the same order as the expectedDatesInOrder array passed to this function
327267
const sagaUpdatesTimestamps = sagaUpdatesElements.map((item: HTMLElement) => within(item).getByLabelText("time stamp"));
328268

329-
//expect the number of found sagaUpdatesTimestamps to be the same as the number of sagaUpdates passed to this function
330-
expect(sagaUpdatesTimestamps).toHaveLength(sagaUpdates.length);
269+
//expect the number of found sagaUpdatesTimestamps to be the same as the number of expected dates passed to this function
270+
expect(sagaUpdatesTimestamps).toHaveLength(expectedDatesInOrder.length);
331271

332272
const sagaUpdatesTimestampsValues = sagaUpdatesTimestamps.map((item) => item.innerHTML);
333-
// //check if the values are in the same order as the sagaUpdates array passed to this function
334-
expect(sagaUpdatesTimestampsValues).toEqual(sagaUpdates.map((item) => item.expectedRenderedLocalTime));
273+
274+
// Verify we have the same number of rendered timestamps as expected dates
275+
expect(sagaUpdatesTimestampsValues).toHaveLength(expectedDatesInOrder.length);
276+
277+
// For each rendered timestamp, verify it matches the expected date at that position
278+
// by formatting the expected date the same way and comparing strings
279+
expectedDatesInOrder.forEach((expectedDate, index) => {
280+
const expectedFormattedString = expectedDate.toLocaleString();
281+
expect(sagaUpdatesTimestampsValues[index]).toBe(expectedFormattedString);
282+
});
335283
},
336284
},
337285
};

src/Frontend/src/components/messages/SagaDiagram.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const vm = computed<SagaViewModel>(() => {
5656
SagaCompleted: !!completedUpdate,
5757
5858
// Display data
59-
FormattedCompletionTime: completionTime ? `${completionTime.toLocaleDateString()} ${completionTime.toLocaleTimeString()}` : "",
59+
FormattedCompletionTime: completionTime ? completionTime.toLocaleString() : "",
6060
SagaUpdates: parseSagaUpdates(sagaHistory, sagaDiagramStore.messagesData),
6161
ShowMessageData: showMessageData.value,
6262
};

src/Frontend/src/components/messages/SagaDiagram/SagaDiagramParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export function parseSagaUpdates(sagaHistory: SagaHistory | null, messagesData:
9393
return {
9494
MessageType: msg.message_type || "",
9595
MessageId: msg.message_id,
96-
FormattedTimeSent: `${timeSent.toLocaleDateString()} ${timeSent.toLocaleTimeString()}`,
96+
FormattedTimeSent: timeSent.toLocaleString(),
9797
HasTimeout: hasTimeout,
9898
TimeoutSeconds: timeoutSeconds,
9999
TimeoutFriendly: getTimeoutFriendly(delivery_delay),
@@ -128,13 +128,13 @@ export function parseSagaUpdates(sagaHistory: SagaHistory | null, messagesData:
128128
MessageId: update.initiating_message?.message_id || "",
129129
StartTime: startTime,
130130
FinishTime: finishTime,
131-
FormattedStartTime: `${startTime.toLocaleDateString()} ${startTime.toLocaleTimeString()}`,
131+
FormattedStartTime: startTime.toLocaleString(),
132132
Status: update.status,
133133
StatusDisplay: update.status === "new" ? "Saga Initiated" : "Saga Updated",
134134
InitiatingMessage: <InitiatingMessageViewModel>{
135135
FriendlyTypeName: typeToName(update.initiating_message?.message_type || "Unknown Message") || "",
136136
MessageId: update.initiating_message?.message_id || "",
137-
FormattedMessageTimestamp: `${initiatingMessageTimestamp.toLocaleDateString()} ${initiatingMessageTimestamp.toLocaleTimeString()}`,
137+
FormattedMessageTimestamp: initiatingMessageTimestamp.toLocaleString(),
138138
MessageData: initiatingMessageData,
139139
IsEventMessage: update.initiating_message?.intent === "Publish",
140140
IsSagaTimeoutMessage: update.initiating_message?.is_saga_timeout_message || false,

src/Frontend/src/views/throughputreport/EndpointsView.vue

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@ import routeLinks from "@/router/routeLinks";
33
import isRouteSelected from "@/composables/isRouteSelected";
44
import { UserIndicator } from "@/views/throughputreport/endpoints/userIndicator";
55
import { userIndicatorMapper } from "@/views/throughputreport/endpoints/userIndicatorMapper";
6-
import { ref } from "vue";
6+
import { ref, type Component } from "vue";
77
import { useThroughputStore } from "@/stores/ThroughputStore";
88
import { storeToRefs } from "pinia";
9+
import LegendNServiceBusEndpoint from "./LegendNServiceBusEndpoint.vue";
10+
import LegendNServiceBusEndpointNoLongerInUse from "./LegendNServiceBusEndpointNoLongerInUse.vue";
11+
import LegendTransactionalSessionProcessorEndpoint from "./LegendTransactionalSessionProcessorEndpoint.vue";
12+
import LegendSendOnlyEndpoint from "./LegendSendOnlyEndpoint.vue";
13+
import LegendPlannedToDecommission from "./LegendPlannedToDecommission.vue";
14+
import LegendNotNServiceBusEndpoint from "./LegendNotNServiceBusEndpoint.vue";
915
1016
const { isBrokerTransport } = storeToRefs(useThroughputStore());
1117
const showLegend = ref(true);
12-
const legendOptions = new Map<UserIndicator, string>([
13-
[UserIndicator.NServiceBusEndpoint, "Known NServiceBus Endpoint"],
14-
[UserIndicator.NServiceBusEndpointNoLongerInUse, "NServiceBus Endpoint that is no longer in use, usually this would have zero throughput"],
15-
[UserIndicator.SendOnlyOrTransactionSessionEndpoint, "If the endpoint has no throughput or the endpoint has Transactional Session feature enabled"],
16-
[UserIndicator.PlannedToDecommission, "If the endpoint is planned to no longer be used in the next 30 days"],
17-
[UserIndicator.NotNServiceBusEndpoint, "Not an NServiceBus Endpoint"],
18+
19+
const legendOptions = new Map<UserIndicator, Component>([
20+
[UserIndicator.NServiceBusEndpoint, LegendNServiceBusEndpoint],
21+
[UserIndicator.NServiceBusEndpointNoLongerInUse, LegendNServiceBusEndpointNoLongerInUse],
22+
[UserIndicator.TransactionalSessionProcessorEndpoint, LegendTransactionalSessionProcessorEndpoint],
23+
[UserIndicator.SendOnlyEndpoint, LegendSendOnlyEndpoint],
24+
[UserIndicator.PlannedToDecommission, LegendPlannedToDecommission],
25+
[UserIndicator.NotNServiceBusEndpoint, LegendNotNServiceBusEndpoint],
1826
]);
1927
2028
function toggleOptionsLegendVisible() {
@@ -32,8 +40,8 @@ function toggleOptionsLegendVisible() {
3240
<a href="#" :aria-label="`${showLegend ? 'Hide' : 'Show'} Endpoint Types meaning`" @click.prevent="toggleOptionsLegendVisible()">{{ showLegend ? "Hide" : "Show" }} Endpoint Types meaning.</a>
3341
</p>
3442
<div v-show="showLegend" class="alert alert-info">
35-
<div v-for="[key, value] in legendOptions" :key="key">
36-
<strong>{{ userIndicatorMapper.get(key) }}</strong> - {{ value }}.
43+
<div v-for="[key, LegendComponent] in legendOptions" :key="key">
44+
<strong>{{ userIndicatorMapper.get(key) }}</strong> - <component :is="LegendComponent" />.
3745
</div>
3846
</div>
3947
</div>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<span>
3+
Known NServiceBus
4+
<a href="https://particular.net/endpoints" target="_blank">Endpoint</a>
5+
</span>
6+
</template>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<span>NServiceBus Endpoint that is no longer in use, usually this would have zero throughput</span>
3+
</template>

0 commit comments

Comments
 (0)