Skip to content

Commit 37fb001

Browse files
authored
Added support for environment tracking (#66)
* Done with the new design * Updated ASP.NET Core * AssignIncidentHandler did not use the contract value for AssignedBy * Make sure that servics are registered correctly in the IoC container. * Allow GetApplicationInfo to be called with appKey * Pushed down things from Live/Premise * Environment tracking added
1 parent 57733aa commit 37fb001

File tree

104 files changed

+1453
-678
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+1453
-678
lines changed

src/Server/Coderr.Server.Abstractions/Boot/RegisterExtensions.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,23 @@ public static class RegisterExtensions
99
{
1010
public static void RegisterContainerServices(this IServiceCollection serviceCollection, Assembly assembly)
1111
{
12-
var containerServices = assembly.GetTypes()
13-
.Where(x => x.GetCustomAttribute<ContainerServiceAttribute>(true) != null)
14-
.ToList();
12+
var containerServices = assembly.GetTypes();
1513
foreach (var containerService in containerServices)
1614
{
15+
var gotWrongAttribute = containerService
16+
.GetCustomAttributes()
17+
.Count(x => x.GetType().FullName == "Griffin.Container.ContainerService") == 1;
18+
if (gotWrongAttribute)
19+
{
20+
throw new InvalidOperationException(
21+
$"Type \'{containerService}\' was decorated with the wrong attribute");
22+
;
23+
}
24+
1725
var attr = containerService.GetCustomAttribute<ContainerServiceAttribute>();
26+
if (attr == null)
27+
continue;
28+
1829
var interfaces = containerService.GetInterfaces();
1930

2031
// Hack so that the same instance is resolved for each interface
@@ -37,7 +48,14 @@ public static void RegisterMessageHandlers(this IServiceCollection serviceCollec
3748
foreach (var type in types)
3849
{
3950
serviceCollection.AddScoped(type, type);
40-
serviceCollection.AddScoped(type.GetInterfaces()[0], type);
51+
52+
var ifs = type.GetInterfaces()
53+
.Where(x => x.Name.Contains("IMessageHandler") || x.Name.Contains("IQueryHandler"))
54+
.ToList();
55+
foreach (var @if in ifs)
56+
{
57+
serviceCollection.AddScoped(@if, type);
58+
}
4159
}
4260
}
4361

src/Server/Coderr.Server.Api.Client/ServerApiClient.cs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Net;
3+
using System.Net.Http;
34
using System.Security.Claims;
45
using System.Security.Cryptography;
56
using System.Text;
@@ -29,19 +30,25 @@ public class ServerApiClient : IMessageBus, IQueryBus
2930
private Uri _uri;
3031

3132

33+
/// <summary>
34+
/// Send a query to the server.
35+
/// </summary>
36+
/// <param name="principal">Principal for the user making the request</param>
37+
/// <param name="message">Message being sent</param>
38+
/// <returns>task</returns>
3239
async Task IMessageBus.SendAsync(ClaimsPrincipal principal, object message)
3340
{
34-
await RequestAsync("POST", "send", message);
41+
await RequestAsync(HttpMethod.Post, "send", message);
3542
}
3643

3744
async Task IMessageBus.SendAsync(ClaimsPrincipal principal, Message message)
3845
{
39-
await RequestAsync("POST", "send", message.Body);
46+
await RequestAsync(HttpMethod.Post, "send", message.Body);
4047
}
4148

4249
async Task IMessageBus.SendAsync(Message message)
4350
{
44-
await RequestAsync("POST", "send", message.Body);
51+
await RequestAsync(HttpMethod.Post, "send", message.Body);
4552
}
4653

4754
/// <summary>
@@ -51,15 +58,17 @@ async Task IMessageBus.SendAsync(Message message)
5158
/// <returns>task</returns>
5259
public async Task SendAsync(object message)
5360
{
54-
await RequestAsync("POST", "send", message);
61+
await RequestAsync(HttpMethod.Post, "send", message);
5562
}
5663

5764
async Task<TResult> IQueryBus.QueryAsync<TResult>(ClaimsPrincipal user, Query<TResult> query)
5865
{
5966
//TODO: Unwrap the cqs object to query parameters instead
6067
//to allow caching in the server
61-
var response = await RequestAsync("POST", "query", query);
62-
return await DeserializeResponse<TResult>(response);
68+
var response = await RequestAsync(HttpMethod.Post, "query", query);
69+
if (response.StatusCode == HttpStatusCode.NotFound)
70+
return default(TResult);
71+
return await DeserializeResponse<TResult>(response.Content);
6372
}
6473

6574
/// <summary>
@@ -72,8 +81,11 @@ public async Task<TResult> QueryAsync<TResult>(Query<TResult> query)
7281
{
7382
//TODO: Unwrap the cqs object to query parameters instead
7483
//to allow caching in the server
75-
var response = await RequestAsync("POST", "query", query);
76-
return await DeserializeResponse<TResult>(response);
84+
var response = await RequestAsync(HttpMethod.Post, "query", query);
85+
if (response.StatusCode == HttpStatusCode.NotFound)
86+
return default(TResult);
87+
response.EnsureSuccessStatusCode();
88+
return await DeserializeResponse<TResult>(response.Content);
7789
}
7890

7991

@@ -91,36 +103,34 @@ public void Open(Uri uri, string apiKey, string sharedSecret)
91103
}
92104

93105

94-
private async Task<TResult> DeserializeResponse<TResult>(HttpWebResponse response)
106+
private async Task<TResult> DeserializeResponse<TResult>(HttpContent content)
95107
{
96-
var responseStream = response.GetResponseStream();
97-
var jsonBuf = new byte[response.ContentLength];
98-
await responseStream.ReadAsync(jsonBuf, 0, jsonBuf.Length);
99-
var jsonStr = Encoding.UTF8.GetString(jsonBuf);
108+
var jsonStr = await content.ReadAsStringAsync();
100109
var responseObj = JsonConvert.DeserializeObject(jsonStr, typeof(TResult), _jsonSerializerSettings);
101-
return (TResult) responseObj;
110+
return (TResult)responseObj;
102111
}
103112

104-
private async Task<HttpWebResponse> RequestAsync(string httpMethod, string cqsType, object cqsObject)
113+
private async Task<HttpResponseMessage> RequestAsync(HttpMethod httpMethod, string cqsType, object cqsObject)
105114
{
106-
var request = WebRequest.CreateHttp(_uri + "api/cqs");
107-
request.Method = httpMethod;
115+
var request = new HttpRequestMessage(httpMethod, $"{_uri}api/cqs");
108116
request.Headers.Add("X-Api-Key", _apiKey);
109117
request.Headers.Add("X-Cqs-Name", cqsObject.GetType().Name);
110118

111-
var stream = await request.GetRequestStreamAsync();
112119
var json = JsonConvert.SerializeObject(cqsObject, _jsonSerializerSettings);
113120
var buffer = Encoding.UTF8.GetBytes(json);
114-
115121
var hamc = new HMACSHA256(Encoding.UTF8.GetBytes(_sharedSecret.ToLower()));
116122
var hash = hamc.ComputeHash(buffer);
117123
var signature = Convert.ToBase64String(hash);
118-
119-
await stream.WriteAsync(buffer, 0, buffer.Length);
120-
124+
request.Headers.Add("Authorization", "ApiKey " + _apiKey + " " + signature);
121125
request.Headers.Add("X-Api-Signature", signature);
122126

123-
return (HttpWebResponse) await request.GetResponseAsync();
127+
request.Content = new ByteArrayContent(buffer);
128+
129+
var client = new HttpClient();
130+
var response = await client.SendAsync(request);
131+
if ((int)response.StatusCode >= 500)
132+
throw new HttpRequestException(response.ReasonPhrase);
133+
return response;
124134
}
125135
}
126136
}

src/Server/Coderr.Server.Api/Core/Applications/Queries/GetApplicationInfo.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ public int ApplicationId
6565
get { return _applicationId; }
6666
set
6767
{
68-
if (value <= 0)
68+
// Will be 0 when appKey is specified.
69+
if (value < 0)
6970
throw new ArgumentOutOfRangeException("value", value, "Not a valid id.");
7071
_applicationId = value;
7172
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using Coderr.Server.Api.Core.Environments.Queries;
3+
4+
namespace Coderr.Server.Api.Core.Environments.Commands
5+
{
6+
/// <summary>
7+
/// Delete all incidents in a specific environment
8+
/// </summary>
9+
[Command]
10+
public class ResetEnvironment
11+
{
12+
public ResetEnvironment(int applicationId, int environmentId)
13+
{
14+
if (applicationId <= 0) throw new ArgumentOutOfRangeException(nameof(applicationId));
15+
if (environmentId <= 0) throw new ArgumentOutOfRangeException(nameof(environmentId));
16+
17+
ApplicationId = applicationId;
18+
EnvironmentId = environmentId;
19+
}
20+
21+
protected ResetEnvironment()
22+
{
23+
}
24+
25+
public int ApplicationId { get; private set; }
26+
27+
/// <summary>
28+
/// Environment to reset. Id comes from <see cref="GetEnvironments" />.
29+
/// </summary>
30+
public int EnvironmentId { get; private set; }
31+
}
32+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using DotNetCqs;
5+
6+
namespace Coderr.Server.Api.Core.Environments.Queries
7+
{
8+
/// <summary>
9+
/// Get all environments that we've received error reports in
10+
/// </summary>
11+
[Message]
12+
public class GetEnvironments : Query<GetEnvironmentsResult>
13+
{
14+
/// <summary>
15+
/// Fetch all environments for a specific application.
16+
/// </summary>
17+
public int? ApplicationId { get; set; }
18+
}
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Coderr.Server.Api.Core.Environments.Queries
2+
{
3+
/// <summary>
4+
/// Result for <see cref="GetEnvironments" />
5+
/// </summary>
6+
public class GetEnvironmentsResult
7+
{
8+
/// <summary>
9+
/// Get name of each environment
10+
/// </summary>
11+
public GetEnvironmentsResultItem[] Items { get; set; }
12+
}
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Coderr.Server.Api.Core.Environments.Queries
2+
{
3+
/// <summary>
4+
/// Item for <see cref="GetEnvironmentsResult"/>.
5+
/// </summary>
6+
public class GetEnvironmentsResultItem
7+
{
8+
/// <summary>
9+
/// ID
10+
/// </summary>
11+
public int Id { get; set; }
12+
13+
/// <summary>
14+
/// Name, like "Production" or "Test"
15+
/// </summary>
16+
public string Name { get; set; }
17+
}
18+
}

src/Server/Coderr.Server.Api/Core/Incidents/Events/IncidentAssigned.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public class IncidentAssigned
1414
/// <param name="incidentId">Incident being assigned</param>
1515
/// <param name="assignedById">User assigning the incident</param>
1616
/// <param name="assignedToId">User that should start working with the incident</param>
17-
public IncidentAssigned(int incidentId, int assignedById, int assignedToId)
17+
/// <param name="assignedAtUtc">When incident was assigned</param>
18+
public IncidentAssigned(int incidentId, int assignedById, int assignedToId, DateTime assignedAtUtc)
1819
{
1920
if (incidentId <= 0) throw new ArgumentOutOfRangeException(nameof(incidentId));
2021
if (assignedById <= 0) throw new ArgumentOutOfRangeException(nameof(assignedById));
@@ -23,6 +24,12 @@ public IncidentAssigned(int incidentId, int assignedById, int assignedToId)
2324
IncidentId = incidentId;
2425
AssignedById = assignedById;
2526
AssignedToId = assignedToId;
27+
AssignedAtUtc = assignedAtUtc;
28+
}
29+
30+
protected IncidentAssigned()
31+
{
32+
2633
}
2734

2835
/// <summary>
@@ -35,6 +42,11 @@ public IncidentAssigned(int incidentId, int assignedById, int assignedToId)
3542
/// </summary>
3643
public int AssignedToId { get; private set; }
3744

45+
/// <summary>
46+
/// When the incident was assigned (client side)
47+
/// </summary>
48+
public DateTime AssignedAtUtc { get; }
49+
3850
/// <summary>
3951
/// Incident being assigned
4052
/// </summary>

src/Server/Coderr.Server.Api/Core/Incidents/Events/IncidentClosed.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
namespace Coderr.Server.Api.Core.Incidents.Events
66
{
7+
[Event]
78
public class IncidentClosed
89
{
9-
public IncidentClosed(int incidentId, int userId, string solution, string applicationVersion)
10+
public IncidentClosed(int incidentId, int userId, string solution, string applicationVersion, DateTime closedAtUtc)
1011
{
1112
if (incidentId <= 0) throw new ArgumentOutOfRangeException(nameof(incidentId));
1213
if (userId <= 0) throw new ArgumentOutOfRangeException(nameof(userId));
@@ -15,6 +16,7 @@ public IncidentClosed(int incidentId, int userId, string solution, string applic
1516
ClosedById = userId;
1617
Solution = solution;
1718
ApplicationVersion = applicationVersion;
19+
ClosedAtUtc = closedAtUtc;
1820
}
1921

2022
protected IncidentClosed()
@@ -26,5 +28,6 @@ protected IncidentClosed()
2628
public int IncidentId { get;private set; }
2729
public string Solution { get;private set; }
2830
public string ApplicationVersion { get; private set; }
31+
public DateTime ClosedAtUtc { get; }
2932
}
3033
}

src/Server/Coderr.Server.Api/Core/Incidents/Queries/FindIncidents.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ public FindIncidents()
6767
/// </remarks>
6868
public string ContextCollectionPropertyValue { get; set; }
6969

70+
/// <summary>
71+
/// Which environments we should search in.
72+
/// </summary>
73+
public int[] EnvironmentIds { get; set; }
74+
7075
/// <summary>
7176
/// Will be searched in incident.message and report.stacktrace.
7277
/// </summary>

0 commit comments

Comments
 (0)