Skip to content

Commit fad5e6c

Browse files
missymessaCopilot
andcommitted
Modify AlertHookController to create AzDO work items instead of GitHub issues
Rewrites the Grafana alert webhook handler to create DNCENG Task work items in Azure DevOps (area path: .NET Engineering Services\First Responders) instead of opening GitHub issues. Key changes: - AlertHookController now uses IAzureDevOpsClient with Managed Identity auth - Removed Octokit/GitHub dependencies from the alert path - Alert fires: creates work item in Backlog state with 'Active Alert' tag - Alert resolves: moves work item to Done with 'Inactive Alert' tag - Reuses existing work items on recurrence (WIQL search by automation ID) - Added CreateWorkItemAsync, UpdateWorkItemAsync, AddWorkItemCommentAsync, and QueryWorkItemsAsync to IAzureDevOpsClient - New AzureDevOpsAlertOptions config section ('AzureDevOpsAlert') - Updated unit tests to match new controller signature Resolves: https://dnceng.visualstudio.com/internal/_workitems/edit/8579 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 862301c commit fad5e6c

8 files changed

Lines changed: 436 additions & 273 deletions

File tree

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
using System;
2-
using System.Reflection;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using AwesomeAssertions;
45
using DotNet.Status.Web.Controllers;
56
using DotNet.Status.Web.Models;
67
using DotNet.Status.Web.Options;
7-
using Microsoft.DotNet.GitHub.Authentication;
8+
using Microsoft.DotNet.Internal.AzureDevOps;
89
using Microsoft.Extensions.Logging.Abstractions;
910
using Microsoft.Extensions.Options;
1011
using Moq;
1112
using NUnit.Framework;
12-
using Octokit;
1313

1414
namespace DotNet.Status.Web.Tests;
1515

1616
[TestFixture]
1717
public class AlertHookControllerTests
1818
{
1919
[Test]
20-
public void GenerateNewIssue_WithMissingEvalMatchesAndNotificationTargets_DoesNotThrow()
20+
public void GenerateDescription_WithMissingEvalMatches_DoesNotThrow()
2121
{
22-
AlertHookController controller = CreateController(null);
22+
AlertHookController controller = CreateController();
2323
GrafanaNotification notification = new GrafanaNotification
2424
{
2525
Title = "Alert title",
@@ -29,19 +29,19 @@ public void GenerateNewIssue_WithMissingEvalMatchesAndNotificationTargets_DoesNo
2929
EvalMatches = null,
3030
};
3131

32-
Action action = () => InvokeGenerateNewIssue(controller, notification);
32+
Action action = () => controller.GenerateDescription(notification);
3333

3434
action.Should().NotThrow();
3535

36-
NewIssue issue = InvokeGenerateNewIssue(controller, notification);
37-
issue.Body.Should().Contain("Please investigate");
38-
issue.Body.Should().NotContain(", please investigate");
36+
string description = controller.GenerateDescription(notification);
37+
description.Should().Contain("Supplemental text");
38+
description.Should().Contain("Grafana-Automated-Alert-Id-");
3939
}
4040

4141
[Test]
42-
public void GenerateNewNotificationComment_WithMissingEvalMatches_DoesNotThrow()
42+
public void GenerateComment_WithMissingEvalMatches_DoesNotThrow()
4343
{
44-
AlertHookController controller = CreateController(Array.Empty<string>());
44+
AlertHookController controller = CreateController();
4545
GrafanaNotification notification = new GrafanaNotification
4646
{
4747
Title = "Alert title",
@@ -51,58 +51,75 @@ public void GenerateNewNotificationComment_WithMissingEvalMatches_DoesNotThrow()
5151
EvalMatches = null,
5252
};
5353

54-
Action action = () => InvokeGenerateNewNotificationComment(controller, notification);
54+
Action action = () => controller.GenerateComment(notification);
5555

5656
action.Should().NotThrow();
5757

58-
string comment = InvokeGenerateNewNotificationComment(controller, notification);
59-
comment.Should().Contain("Metric state changed to *alerting*");
58+
string comment = controller.GenerateComment(notification);
59+
comment.Should().Contain("Metric state changed to");
60+
comment.Should().Contain("alerting");
6061
}
6162

62-
private static AlertHookController CreateController(string[] notificationTargets)
63+
[Test]
64+
public void GenerateTitle_WithPrefix_PrependsPrefixToTitle()
6365
{
64-
Mock<IGitHubTokenProvider> tokenProvider = new(MockBehavior.Strict);
65-
IOptions<GitHubConnectionOptions> githubOptions = Microsoft.Extensions.Options.Options.Create(new GitHubConnectionOptions
66+
AlertHookController controller = CreateController();
67+
GrafanaNotification notification = new GrafanaNotification
6668
{
67-
Organization = "dotnet",
68-
Repository = "dnceng",
69-
NotificationTargets = notificationTargets,
70-
AlertLabels = Array.Empty<string>(),
71-
EnvironmentLabels = Array.Empty<string>(),
69+
Title = "CPU High",
70+
State = "alerting",
71+
};
72+
73+
string title = controller.GenerateTitle(notification);
74+
75+
title.Should().Be("[test] CPU High");
76+
}
77+
78+
[Test]
79+
public void GenerateDescription_WithEvalMatches_IncludesMetrics()
80+
{
81+
AlertHookController controller = CreateController();
82+
GrafanaNotification notification = new GrafanaNotification
83+
{
84+
Title = "Alert title",
85+
State = "alerting",
86+
Message = "High CPU",
87+
RuleUrl = "https://example/rule",
88+
EvalMatches = new List<GrafanaNotificationMatch>
89+
{
90+
new GrafanaNotificationMatch { Metric = "cpu_usage", Value = 95.5 },
91+
}.ToImmutableList(),
92+
};
93+
94+
string description = controller.GenerateDescription(notification);
95+
96+
description.Should().Contain("cpu_usage");
97+
description.Should().Contain("95.5");
98+
}
99+
100+
private static AlertHookController CreateController()
101+
{
102+
Mock<IAzureDevOpsClient> azureDevOpsClient = new(MockBehavior.Strict);
103+
IOptions<AzureDevOpsAlertOptions> alertOptions = Microsoft.Extensions.Options.Options.Create(new AzureDevOpsAlertOptions
104+
{
105+
Organization = "dnceng",
106+
Project = "internal",
107+
AreaPath = @"internal\.NET Engineering Services\First Responders",
108+
WorkItemType = "DNCENG Task",
72109
TitlePrefix = "[test] ",
73110
SupplementalBodyText = "Supplemental text",
74111
});
75-
IOptions<GitHubClientOptions> clientOptions = Microsoft.Extensions.Options.Options.Create(new GitHubClientOptions
76-
{
77-
ProductHeader = new ProductHeaderValue("DotNetStatusWebTests"),
78-
});
79112

80113
IOptions<GrafanaOptions> grafanaOptions = Microsoft.Extensions.Options.Options.Create(new GrafanaOptions
81114
{
82115
WebhookSecret = "test-secret",
83116
});
84117

85118
return new AlertHookController(
86-
tokenProvider.Object,
87-
githubOptions,
88-
clientOptions,
119+
azureDevOpsClient.Object,
120+
alertOptions,
89121
grafanaOptions,
90122
NullLogger<AlertHookController>.Instance);
91123
}
92-
93-
private static NewIssue InvokeGenerateNewIssue(AlertHookController controller, GrafanaNotification notification)
94-
{
95-
MethodInfo method = typeof(AlertHookController).GetMethod("GenerateNewIssue", BindingFlags.Instance | BindingFlags.NonPublic);
96-
method.Should().NotBeNull();
97-
98-
return (NewIssue)method.Invoke(controller, new object[] { notification });
99-
}
100-
101-
private static string InvokeGenerateNewNotificationComment(AlertHookController controller, GrafanaNotification notification)
102-
{
103-
MethodInfo method = typeof(AlertHookController).GetMethod("GenerateNewNotificationComment", BindingFlags.Instance | BindingFlags.NonPublic);
104-
method.Should().NotBeNull();
105-
106-
return (string)method.Invoke(controller, new object[] { notification });
107-
}
108124
}
125+

0 commit comments

Comments
 (0)