Skip to content

Commit 3f3a900

Browse files
Add error instance capability
1 parent 56fe584 commit 3f3a900

File tree

5 files changed

+170
-1
lines changed

5 files changed

+170
-1
lines changed

src/Frontend/src/components/platformcapabilities/CapabilityCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function handleButtonClick() {
2828
if (shouldShowWizard.value) {
2929
showWizard.value = true;
3030
} else {
31-
window.open(props.helpButtonUrl, "_blank");
31+
window.open(props.helpButtonUrl, props.status !== CapabilityStatus.Available ? "_blank" : "_self");
3232
}
3333
}
3434
</script>

src/Frontend/src/components/platformcapabilities/PlatformCapabilitiesDashboardItem.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ import { computed } from "vue";
33
import CapabilityCard from "@/components/platformcapabilities/CapabilityCard.vue";
44
import { useAuditingCapability } from "@/components/platformcapabilities/capabilities/AuditingCapability";
55
import { useMonitoringCapability } from "@/components/platformcapabilities/capabilities/MonitoringCapability";
6+
import { useErrorCapability } from "@/components/platformcapabilities/capabilities/ErrorCapability";
67
import { Capability } from "@/components/platformcapabilities/types";
78
import { getAuditingWizardPages } from "@/components/platformcapabilities/wizards/AuditingWizardPages";
89
import { getMonitoringWizardPages } from "@/components/platformcapabilities/wizards/MonitoringWizardPages";
10+
import { getErrorWizardPages } from "@/components/platformcapabilities/wizards/ErrorWizardPages";
911
1012
const auditing = useAuditingCapability();
1113
const monitoring = useMonitoringCapability();
14+
const error = useErrorCapability();
1215
1316
const auditingWizardPages = computed(() => getAuditingWizardPages(auditing.status.value));
1417
const monitoringWizardPages = computed(() => getMonitoringWizardPages(monitoring.status.value));
18+
const errorWizardPages = computed(() => getErrorWizardPages(error.status.value));
1519
</script>
1620

1721
<template>
@@ -45,6 +49,18 @@ const monitoringWizardPages = computed(() => getMonitoringWizardPages(monitoring
4549
:help-button-url="monitoring.helpButtonUrl.value"
4650
:wizard-pages="monitoringWizardPages"
4751
></CapabilityCard>
52+
<CapabilityCard
53+
:title="Capability.Error"
54+
subtitle="Manage and retry failed messages"
55+
:status="error.status.value"
56+
:icon="error.icon.value"
57+
:description="error.description.value"
58+
:indicators="error.indicators.value"
59+
:isLoading="error.isLoading.value"
60+
:help-button-text="error.helpButtonText.value"
61+
:help-button-url="error.helpButtonUrl.value"
62+
:wizard-pages="errorWizardPages"
63+
></CapabilityCard>
4864
</div>
4965
</div>
5066
</template>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { computed } from "vue";
2+
import { storeToRefs } from "pinia";
3+
import { CapabilityStatus, StatusIndicator } from "@/components/platformcapabilities/types";
4+
import { useConnectionsAndStatsStore } from "@/stores/ConnectionsAndStatsStore";
5+
import { type CapabilityComposable, type CapabilityStatusToStringMap, useCapabilityBase } from "./BaseCapability";
6+
7+
const ErrorDescriptions: CapabilityStatusToStringMap = {
8+
[CapabilityStatus.EndpointsNotConfigured]: "The ServiceControl Error instance is connected but no failed messages have been received yet. This may be because no failures have occurred, or recoverability is not configured.",
9+
[CapabilityStatus.Available]: "The ServiceControl Error instance is available and has received failed messages.",
10+
};
11+
12+
const ErrorHelpButtonText: CapabilityStatusToStringMap = {
13+
[CapabilityStatus.EndpointsNotConfigured]: "Learn More",
14+
[CapabilityStatus.Available]: "View Failed Messages",
15+
};
16+
17+
const ErrorHelpButtonUrl: CapabilityStatusToStringMap = {
18+
[CapabilityStatus.EndpointsNotConfigured]: "https://docs.particular.net/nservicebus/recoverability/",
19+
[CapabilityStatus.Available]: "#/failed-messages",
20+
};
21+
22+
enum ErrorIndicatorTooltip {
23+
InstanceAvailable = "The ServiceControl Error instance is configured and available",
24+
InstanceUnavailable = "The ServiceControl Error instance is not responding",
25+
FailedMessagesAvailable = "Failed messages have been received and are available for management",
26+
FailedMessagesUnavailable = "No failed messages have been received yet",
27+
}
28+
29+
export function useErrorCapability(): CapabilityComposable {
30+
const { getIconForStatus, getDescriptionForStatus, getHelpButtonTextForStatus, getHelpButtonUrlForStatus, createIndicator } = useCapabilityBase();
31+
32+
// This tells us the connection state to the ServiceControl Error instance
33+
// and the failed message count. Auto refreshed every 5 seconds.
34+
const connectionsStore = useConnectionsAndStatsStore();
35+
const connectionState = connectionsStore.connectionState;
36+
const { failedMessageCount } = storeToRefs(connectionsStore);
37+
38+
// Determine if there are any failed messages
39+
const hasFailedMessages = computed(() => failedMessageCount.value > 0);
40+
41+
// Determine overall error status
42+
const errorStatus = computed(() => {
43+
const connectionSuccessful = connectionState.connected && !connectionState.unableToConnect;
44+
45+
// 1. Check if we are connected to the error instance
46+
if (!connectionSuccessful) {
47+
return CapabilityStatus.Unavailable;
48+
}
49+
50+
// 2. Check if there are any failed messages
51+
if (!hasFailedMessages.value) {
52+
return CapabilityStatus.EndpointsNotConfigured;
53+
}
54+
55+
// 3. If connected and has failed messages, the error instance is fully available
56+
return CapabilityStatus.Available;
57+
});
58+
59+
// Icon based on status
60+
const errorIcon = computed(() => getIconForStatus(errorStatus.value));
61+
62+
// Determine description based on status
63+
const errorDescription = computed(() => getDescriptionForStatus(errorStatus.value, ErrorDescriptions));
64+
65+
// Determine help button text based on status
66+
const errorHelpButtonText = computed(() => getHelpButtonTextForStatus(errorStatus.value, ErrorHelpButtonText));
67+
68+
// Determine help button URL based on status
69+
const errorHelpButtonUrl = computed(() => getHelpButtonUrlForStatus(errorStatus.value, ErrorHelpButtonUrl));
70+
71+
// Determine indicators
72+
const errorIndicators = computed(() => {
73+
const indicators: StatusIndicator[] = [];
74+
75+
const connectionSuccessful = connectionState.connected && !connectionState.unableToConnect;
76+
const instanceTooltip = connectionSuccessful ? ErrorIndicatorTooltip.InstanceAvailable : ErrorIndicatorTooltip.InstanceUnavailable;
77+
78+
indicators.push(createIndicator("Instance", connectionSuccessful ? CapabilityStatus.Available : CapabilityStatus.Unavailable, instanceTooltip));
79+
80+
// Only show failed messages indicator if instance is connected
81+
if (connectionSuccessful) {
82+
const messagesIndicatorTooltip = hasFailedMessages.value ? ErrorIndicatorTooltip.FailedMessagesAvailable : ErrorIndicatorTooltip.FailedMessagesUnavailable;
83+
indicators.push(createIndicator("Failed Messages", hasFailedMessages.value ? CapabilityStatus.Available : CapabilityStatus.EndpointsNotConfigured, messagesIndicatorTooltip));
84+
}
85+
86+
return indicators;
87+
});
88+
89+
// Loading state - error is loading if we haven't attempted connection yet
90+
const isLoading = computed(() => !connectionState.connected && !connectionState.unableToConnect && connectionState.connecting);
91+
92+
return {
93+
status: errorStatus,
94+
icon: errorIcon,
95+
description: errorDescription,
96+
indicators: errorIndicators,
97+
isLoading,
98+
helpButtonText: errorHelpButtonText,
99+
helpButtonUrl: errorHelpButtonUrl,
100+
};
101+
}

src/Frontend/src/components/platformcapabilities/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ export enum CapabilityStatus {
1515
export enum Capability {
1616
Monitoring = "Monitoring",
1717
Auditing = "Auditing",
18+
Error = "Recoverability",
1819
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { WizardPage } from "../WizardDialog.vue";
2+
import { CapabilityStatus } from "../types";
3+
4+
const ErrorEndpointsNotConfiguredPages: WizardPage[] = [
5+
{
6+
title: "Configure Recoverability",
7+
content: `
8+
<p>Your ServiceControl Error instance is connected, but no failed messages have been received yet. This could be because no message processing failures have occurred, endpoints are not configured to send failed messages to ServiceControl, or endpoints are not currently running.</p>
9+
<p>To ensure failed messages appear in ServicePulse when failures occur, configure recoverability in your NServiceBus endpoints:</p>
10+
<ul>
11+
<li><strong>Configure the error queue</strong> - Tell your endpoints where to send failed messages</li>
12+
<li><strong>Deploy your changes</strong> - Restart endpoints with the new configuration</li>
13+
</ul>
14+
`,
15+
learnMoreUrl: "https://docs.particular.net/nservicebus/recoverability/",
16+
learnMoreText: "Learn about recoverability",
17+
},
18+
{
19+
title: "Configure Your Endpoints",
20+
content: `
21+
<p>Add error queue configuration to your endpoint setup code:</p>
22+
<p><code>endpointConfiguration.SendFailedMessagesTo("error");</code></p>
23+
<p>Make sure the error queue name matches what your ServiceControl Error instance is monitoring.</p>
24+
`,
25+
learnMoreUrl: "https://docs.particular.net/nservicebus/recoverability/configure-error-handling",
26+
learnMoreText: "View configuration options",
27+
},
28+
{
29+
title: "Verify Your Setup",
30+
content: `
31+
<p>Once configured:</p>
32+
<ul>
33+
<li><strong>Trigger a test failure</strong> - Send a message that will fail processing</li>
34+
<li><strong>Check this capability card</strong> - it will automatically update when failures are detected</li>
35+
<li><strong>View the Failed Messages tab</strong> to see and manage your failed messages</li>
36+
</ul>
37+
<p>If failed messages don't appear after a few minutes, check your endpoint logs and ServiceControl logs for any errors.</p>
38+
`,
39+
learnMoreUrl: "https://docs.particular.net/servicecontrol/troubleshooting",
40+
learnMoreText: "Troubleshooting guide",
41+
},
42+
];
43+
44+
export function getErrorWizardPages(status: CapabilityStatus): WizardPage[] {
45+
switch (status) {
46+
case CapabilityStatus.EndpointsNotConfigured:
47+
return ErrorEndpointsNotConfiguredPages;
48+
default:
49+
return [];
50+
}
51+
}

0 commit comments

Comments
 (0)