Skip to content

Commit 9899ec3

Browse files
committed
* Added support for user notifications
* Activated the user bug report handler
1 parent 6b63607 commit 9899ec3

File tree

22 files changed

+1509
-964
lines changed

22 files changed

+1509
-964
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Coderr.Server.Api.Core.Incidents.Commands
6+
{
7+
/// <summary>
8+
/// Notify all users that have subscribed on an incident.
9+
/// </summary>
10+
[Command]
11+
public class NotifySubscribers
12+
{
13+
public NotifySubscribers(int incidentId)
14+
{
15+
if (incidentId <= 0) throw new ArgumentOutOfRangeException(nameof(incidentId));
16+
IncidentId = incidentId;
17+
}
18+
19+
protected NotifySubscribers()
20+
{
21+
22+
}
23+
24+
/// <summary>
25+
/// Incident id
26+
/// </summary>
27+
public int IncidentId { get; private set; }
28+
/// <summary>
29+
/// Text to send as email body
30+
/// </summary>
31+
public string Body { get; set; }
32+
33+
/// <summary>
34+
/// Title of outbound notification.
35+
/// </summary>
36+
public string Title { get; set; }
37+
38+
}
39+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using Coderr.Server.Api.Core.Incidents.Commands;
4+
using Coderr.Server.Api.Core.Messaging;
5+
using Coderr.Server.Api.Core.Messaging.Commands;
6+
using Coderr.Server.Domain.Core.Feedback;
7+
using DotNetCqs;
8+
9+
namespace Coderr.Server.App.Core.Incidents.Commands
10+
{
11+
internal class NotifySubscribersHandler : IMessageHandler<NotifySubscribers>
12+
{
13+
private readonly IFeedbackRepository _feedbackRepository;
14+
15+
public NotifySubscribersHandler(IFeedbackRepository feedbackRepository)
16+
{
17+
_feedbackRepository = feedbackRepository;
18+
}
19+
20+
21+
public async Task HandleAsync(IMessageContext context, NotifySubscribers message)
22+
{
23+
var emails = await _feedbackRepository.GetEmailAddressesAsync(message.IncidentId);
24+
if (!emails.Any())
25+
return;
26+
27+
var emailMessage = new EmailMessage(emails)
28+
{
29+
Subject = message.Title,
30+
TextBody = message.Body
31+
};
32+
var sendMessage = new SendEmail(emailMessage);
33+
await context.SendAsync(sendMessage);
34+
}
35+
}
36+
}

src/Server/Coderr.Server.App/Modules/Messaging/Commands/SendEmailHandler.cs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,7 @@ public async Task HandleAsync(IMessageContext context, SendEmail command)
4848

4949
var markdownHtml = Markdown.ToHtml(command.EmailMessage.TextBody ?? "");
5050

51-
foreach (var recipient in command.EmailMessage.Recipients)
52-
{
53-
if (int.TryParse(recipient.Address, out var accountId))
54-
{
55-
var query = new GetAccountEmailById(accountId);
56-
var emailAddress = await context.QueryAsync(query);
57-
email.To.Add(new MailAddress(emailAddress, recipient.Name));
58-
}
59-
else
60-
email.To.Add(new MailAddress(recipient.Address, recipient.Name));
61-
}
51+
6252
if (string.IsNullOrEmpty(command.EmailMessage.HtmlBody) && markdownHtml == command.EmailMessage.TextBody)
6353
{
6454
email.Body = command.EmailMessage.TextBody;
@@ -87,7 +77,23 @@ public async Task HandleAsync(IMessageContext context, SendEmail command)
8777
}
8878

8979
email.AlternateViews.Add(av);
90-
await client.SendMailAsync(email);
80+
81+
// Send one email per recipient to not share addresses.
82+
foreach (var recipient in command.EmailMessage.Recipients)
83+
{
84+
email.To.Clear();
85+
if (int.TryParse(recipient.Address, out var accountId))
86+
{
87+
var query = new GetAccountEmailById(accountId);
88+
var emailAddress = await context.QueryAsync(query);
89+
email.To.Add(new MailAddress(emailAddress, recipient.Name));
90+
}
91+
else
92+
email.To.Add(new MailAddress(recipient.Address, recipient.Name));
93+
94+
await client.SendMailAsync(email);
95+
}
96+
9197
}
9298

9399
private SmtpClient CreateSmtpClient()

src/Server/Coderr.Server.ReportAnalyzer/Feedback/Handlers/SubmitFeedbackHandler.cs renamed to src/Server/Coderr.Server.SqlServer/Core/Feedback/SubmitFeedbackHandler.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
using Griffin.Data;
99
using log4net;
1010

11-
namespace Coderr.Server.ReportAnalyzer.Feedback.Handlers
11+
namespace Coderr.Server.SqlServer.Core.Feedback
1212
{
13-
//TODO: Move SQL parts to the data project.
1413
public class SubmitFeedbackHandler : IMessageHandler<SubmitFeedback>
1514
{
1615
private readonly ILog _logger = LogManager.GetLogger(typeof(SubmitFeedbackHandler));
@@ -56,8 +55,7 @@ public async Task HandleAsync(IMessageContext context, SubmitFeedback command)
5655
catch (Exception exception)
5756
{
5857
_logger.Error(
59-
string.Format("{0}: Failed to store '{1}' '{2}'", command.ErrorId, command.Email,
60-
command.Feedback), exception);
58+
$"{command.ErrorId}: Failed to store '{command.Email}' '{command.Feedback}'", exception);
6159
//hide errors.
6260
}
6361

src/Server/Coderr.Server.SqlServer/SqlServerTools.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ public SqlServerTools(Func<IDbConnection> connectionFactory)
2626
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
2727
_schemaManager = new SchemaManager(_connectionFactory);
2828
}
29-
30-
internal static string DbName { get; set; }
3129

3230
/// <summary>
3331
/// Checks if the tables exists and are for the current DB schema.

src/Server/Coderr.Server.Web/Areas/Installation/WizardSteps.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ private static int FindCurrentIndex(IUrlHelper urlHelper)
6666
if (Steps[i].IsForAbsolutePath(currentPath, urlHelper))
6767
return i;
6868
}
69-
7069
// in ASP.NET Core, the virtual path is in PathBase and the rest in Path
7170
// this we only need to check if the path is root (while the wizard link is for ./installation)
72-
if (urlHelper.ActionContext.HttpContext.Request.Path == "")
71+
// It's "/" when there is no virtual directory and "" when there is one. Go figure.
72+
if (urlHelper.ActionContext.HttpContext.Request.Path == "" || urlHelper.ActionContext.HttpContext.Request.Path == "/")
7373
return 0;
74-
74+
7575
return -1;
7676
}
7777
}

src/Server/Coderr.Server.Web/ClientApp/boot.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ const routes = [
143143
name: "analyzeFeedback",
144144
path: "incident/:incidentId/feedback/",
145145
component: require("./components/analyze/incidents/feedback.vue.html")
146+
},
147+
{
148+
name: "analyzeNotifyUsers",
149+
path: "incident/:incidentId/notify/",
150+
component: require("./components/analyze/incidents/status.vue.html")
146151
}
147152
]
148153
},

src/Server/Coderr.Server.Web/ClientApp/components/analyze/incidents/incident.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ export default class AnalyzeIncidentComponent extends Vue {
5858
closeIncident() {
5959
AppRoot.Instance.incidentService.showClose(this.incident.Id, "CloseBody")
6060
.then(x => {
61+
if (x.requiresStatusUpdate) {
62+
this.$router.push({
63+
name: 'analyzeNotifyUsers',
64+
params: { incidentId: this.incident.Id.toString() }
65+
});
66+
return;
67+
}
6168
AppRoot.Instance.incidentService.getMine(null, this.incident.Id)
6269
.then(incidents => {
6370
if (incidents.length === 0) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.IncidentView .tags span {
2+
margin-right: 5px;
3+
}
4+
.tags button {
5+
margin-right: 5px;
6+
}
7+
8+
.more {
9+
display: none;
10+
}
11+
12+
.feedback.col {
13+
min-width: 500px;
14+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { PubSubService } from "../../../services/PubSub";
2+
import { AppRoot } from '../../../services/AppRoot';
3+
import * as feedback from "../../../dto/Web/Feedback";
4+
import {NotifySubscribers} from "../../../dto/Core/Incidents";
5+
import Vue from "vue";
6+
import { Component, Watch } from "vue-property-decorator";
7+
8+
interface IFeedback {
9+
description: string;
10+
email: string;
11+
writtenAtUtc: Date;
12+
}
13+
14+
@Component
15+
export default class AnalyzeStatusUpdateComponent extends Vue {
16+
17+
incidentId = 0;
18+
emailList = "";
19+
title = "";
20+
body="";
21+
22+
created() {
23+
this.incidentId = parseInt(this.$route.params.incidentId, 10);
24+
25+
var q = new feedback.GetIncidentFeedback();
26+
q.IncidentId = this.incidentId;
27+
AppRoot.Instance.apiClient.query<feedback.GetIncidentFeedbackResult>(q)
28+
.then(result => {
29+
var items: string[] = [];
30+
result.Items.forEach(x => {
31+
if (x.EmailAddress) {
32+
items.push(x.EmailAddress);
33+
}
34+
});
35+
this.emailList = items.join(', ');
36+
});
37+
}
38+
39+
send() {
40+
var cmd = new NotifySubscribers();
41+
cmd.IncidentId = this.incidentId;
42+
cmd.Body = this.body;
43+
cmd.Title = this.title;
44+
AppRoot.Instance.apiClient.command(cmd)
45+
.then(x => {
46+
AppRoot.notify('Message have been sent.');
47+
this.$router.push({ name: 'analyzeHome' });
48+
});
49+
}
50+
51+
}

0 commit comments

Comments
 (0)