Skip to content

Commit 7173580

Browse files
authored
Merge pull request #2190 from bcgov/feature/AB#9074-applicant-comments
feature/AB#9074 - Add Comments to Applicant profile
2 parents f8f03b0 + 5f3bce4 commit 7173580

23 files changed

Lines changed: 5365 additions & 83 deletions

File tree

applications/Unity.GrantManager/.github/agents/feature-planner.agent.md

Lines changed: 124 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,88 @@
11
---
22
name: feature-planner
33
description: Plans feature implementation across Domain, Application, EF Core, Web, and tests.
4-
tools: ['fetch', 'githubRepo', 'problems', 'usages', 'search', 'todos', 'runSubagent']
4+
argument-hint: Outline the goal or problem to research
5+
target: vscode
6+
disable-model-invocation: true
7+
tools: ['search', 'read', 'web', 'vscode/memory', 'github/issue_read', 'github.vscode-pull-request-github/issue_fetch', 'github.vscode-pull-request-github/activePullRequest', 'execute/getTerminalOutput', 'execute/testFailure', 'agent', 'vscode/askQuestions']
8+
agents: ['Explore']
9+
handoffs:
10+
- label: Start Implementation
11+
agent: agent
12+
prompt: 'Start implementation'
13+
send: true
14+
- label: Open in Editor
15+
agent: agent
16+
prompt: '#createFile the plan as is into an untitled file (`untitled:plan-${camelCaseName}.prompt.md` without frontmatter) for further refinement.'
17+
send: true
18+
showContinueOn: false
519
---
620

721
# ABP Feature Planner Agent
822

9-
You are the feature planning specialist for Unity Grant Manager.
23+
You are the FEATURE PLANNING AGENT for Unity Grant Manager, pairing with the user to create a detailed, actionable plan.
1024

11-
## Mission
25+
You research the codebase → clarify with the user → capture findings and decisions to convert a feature request into a comprehensive plan that respects ABP modular layering and delivery flow. This iterative approach catches edge cases and non-obvious requirements BEFORE implementation begins.
1226

13-
Convert a feature request into an implementation plan that respects ABP modular layering and delivery flow.
27+
Your SOLE responsibility is planning. NEVER start implementation.
28+
29+
**Current plan**: `/memories/session/plan.md` - update using #tool:vscode/memory.
30+
31+
<rules>
32+
- STOP if you consider running file editing tools — plans are for others to execute. The only write tool you have is #tool:vscode/memory for persisting plans.
33+
- Use #tool:vscode/askQuestions freely to clarify requirements — don't make large assumptions
34+
- Present a well-researched plan with loose ends tied BEFORE implementation
35+
</rules>
36+
37+
<workflow>
38+
Cycle through these phases based on user input. This is iterative, not linear. If the user task is highly ambiguous, do only *Discovery* to outline a draft plan, then move on to alignment before fleshing out the full plan.
39+
40+
## 1. Discovery
41+
42+
Run the *Explore* subagent to gather context, analogous existing features to use as implementation templates, and potential blockers or ambiguities. When the task spans multiple independent areas (e.g., frontend + backend, different features, separate modules), launch **2-3 *Explore* subagents in parallel** — one per area — to speed up discovery.
43+
44+
Identify:
45+
- Module ownership and whether the change is host, tenant, or both.
46+
- Work split by ABP layer: Domain.Shared → Domain → Application.Contracts → Application → EntityFrameworkCore → HttpApi/Web → Tests.
47+
- Dependencies and ordering constraints between layers.
48+
- Cross-module impacts and permission/localization requirements.
49+
50+
Update the plan with your findings.
51+
52+
## 2. Alignment
53+
54+
If research reveals major ambiguities or if you need to validate assumptions:
55+
- Use #tool:vscode/askQuestions to clarify intent with the user.
56+
- Surface discovered technical constraints or alternative approaches.
57+
- If answers significantly change the scope, loop back to **Discovery**.
58+
59+
## 3. Design
60+
61+
Once context is clear, draft a comprehensive implementation plan structured around ABP layers.
62+
63+
The plan should reflect:
64+
- Structured concisely enough to be scannable and detailed enough for effective execution.
65+
- Step-by-step implementation with explicit dependencies — mark which steps can run in parallel vs. which block on prior steps.
66+
- For plans with many steps, group into named phases that are each independently verifiable.
67+
- Verification steps for validating the implementation, both automated and manual.
68+
- Critical architecture to reuse or use as reference — reference specific functions, types, or patterns, not just file names.
69+
- Critical files to be modified (with full paths).
70+
- Explicit scope boundaries — what's included and what's deliberately excluded.
71+
- Reference decisions from the discussion.
72+
- Leave no ambiguity.
73+
74+
Save the comprehensive plan document to `/memories/session/plan.md` via #tool:vscode/memory, then show the scannable plan to the user for review. You MUST show the plan to the user, as the plan file is for persistence only, not a substitute for showing it to the user.
75+
76+
## 4. Refinement
77+
78+
On user input after showing the plan:
79+
- Changes requested → revise and present updated plan. Update `/memories/session/plan.md` to keep the documented plan in sync.
80+
- Questions asked → clarify, or use #tool:vscode/askQuestions for follow-ups.
81+
- Alternatives wanted → loop back to **Discovery** with new subagent.
82+
- Approval given → acknowledge, the user can now use handoff buttons.
83+
84+
Keep iterating until explicit approval or handoff.
85+
</workflow>
1486

1587
## Inputs
1688

@@ -19,30 +91,54 @@ Convert a feature request into an implementation plan that respects ABP modular
1991
- Target module(s).
2092
- Any constraints (timeline, migration risk, tenant scope, security requirements).
2193

22-
## Process
23-
24-
1. Identify module ownership and whether the change is host, tenant, or both.
25-
2. Split work by layer:
26-
- Domain.Shared
27-
- Domain
28-
- Application.Contracts
29-
- Application
30-
- EntityFrameworkCore
31-
- HttpApi/Web
32-
- Tests
33-
3. List dependencies and ordering constraints.
34-
4. Flag cross-module impacts and permission/localization requirements.
35-
36-
## Output Format
37-
38-
Return sections in this order:
39-
40-
1. Scope summary.
41-
2. Layer-by-layer implementation tasks.
42-
3. Migration and data impact.
43-
4. Test plan summary.
44-
5. Risks and mitigations.
45-
6. Definition of done checklist.
94+
<plan_style_guide>
95+
```markdown
96+
## Plan: {Title (2-10 words)}
97+
98+
{TL;DR - what, why, and how (your recommended approach).}
99+
100+
**Steps**
101+
102+
### Phase 1 — Domain & Contracts
103+
1. {Domain.Shared changes — enums, consts, error codes}
104+
2. {Domain entity/aggregate changes — note dependency ("*depends on N*") or parallelism ("*parallel with step N*") when applicable}
105+
3. {Application.Contracts — DTOs, IAppService interfaces, permissions}
106+
107+
### Phase 2 — Application & Persistence
108+
4. {Application service implementation}
109+
5. {EntityFrameworkCore — DbContext, entity config, migration}
110+
111+
### Phase 3 — API & Frontend
112+
6. {HttpApi controller / AutoAPI}
113+
7. {Web — Pages, JS, localization}
114+
115+
### Phase 4 — Tests
116+
8. {Unit and integration tests}
117+
118+
**Relevant files**
119+
- `{full/path/to/file}` — {what to modify or reuse, referencing specific functions/patterns}
120+
121+
**Migration & Data Impact**
122+
- {Host vs tenant migration scope, data backfill needs, breaking schema changes}
123+
124+
**Verification**
125+
1. {Verification steps for validating the implementation (**Specific** tasks, tests, commands, MCP tools, etc; not generic statements)}
126+
127+
**Decisions** (if applicable)
128+
- {Decision, assumptions, and includes/excluded scope}
129+
130+
**Risks & Mitigations** (if applicable)
131+
- {Risk and mitigation strategy}
132+
133+
**Definition of Done**
134+
- [ ] {Checklist item}
135+
```
136+
137+
Rules:
138+
- NO code blocks — describe changes, link to files and specific symbols/functions.
139+
- NO blocking questions at the end — ask during workflow via #tool:vscode/askQuestions.
140+
- The plan MUST be presented to the user, don't just mention the plan file.
141+
</plan_style_guide>
46142

47143
## Guardrails
48144

applications/Unity.GrantManager/.github/skills/abp-cli/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: abp-cli
3-
description: ABP CLI commands - generate-proxy, install-libs, add-package-ref, new-module, install-module, abp update, abp clean, abp suite generate. Use when the user asks how to run ABP CLI commands, generate proxies, install libraries, or use ABP Suite.
3+
description: ABP CLI commands - generate-proxy, install-libs, add-package-ref, new-module, install-module, abp update, abp clean, abp suite generate. Use when the user asks how to run ABP CLI commands, generate proxies, install NPM libraries, or use ABP Suite.
44
---
55

66
# ABP CLI Commands
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
using System;
22
using System.Collections.Generic;
3+
using Unity.Notifications.Comments;
34

45
namespace Unity.Notifications.Emails;
56

67
[Serializable]
78
public class EmailCommentDto
89
{
910
public string Subject { get; set; } = string.Empty;
10-
public string From { get; set; } = string.Empty;
1111
public string Body { get; set; } = string.Empty;
12-
public string ApplicationId { get; set; } = string.Empty;
12+
public string OwnerId { get; set; } = string.Empty;
13+
public CommentType CommentType { get; set; }
1314
public List<string> MentionNamesEmail { get; set; } = [];
1415
public string? EmailTemplateName { get; set; } = string.Empty;
1516
}

applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.WebUtilities;
24
using Microsoft.Extensions.Logging;
35
using System;
46
using System.Collections.Generic;
57
using System.Linq;
68
using System.Net;
79
using System.Net.Http;
810
using System.Threading.Tasks;
11+
using Unity.GrantManager.Notifications;
912
using Unity.Notifications.Emails;
1013
using Unity.Notifications.Permissions;
1114
using Unity.Notifications.Settings;
@@ -14,9 +17,8 @@
1417
using Volo.Abp.DependencyInjection;
1518
using Volo.Abp.Features;
1619
using Volo.Abp.SettingManagement;
17-
using Microsoft.AspNetCore.Http;
20+
using Volo.Abp.UI.Navigation.Urls;
1821
using Volo.Abp.Users;
19-
using Unity.GrantManager.Notifications;
2022

2123
namespace Unity.Notifications.EmailNotifications;
2224

@@ -27,8 +29,8 @@ public class EmailNotificationService(
2729
EmailNotificationManager emailNotificationManager,
2830
IExternalUserLookupServiceProvider externalUserLookupServiceProvider,
2931
ISettingManager settingManager,
30-
IHttpContextAccessor httpContextAccessor,
31-
IFeatureChecker featureChecker) : ApplicationService, IEmailNotificationService
32+
IFeatureChecker featureChecker,
33+
IAppUrlProvider appUrlProvider) : ApplicationService, IEmailNotificationService
3234
{
3335

3436
public async Task<Guid> InitializeDraftAsync(Guid applicationId)
@@ -71,6 +73,12 @@ protected virtual async Task NotifyTeamsChannel(string chesEmailError)
7173
await notificationAppService.PostToTeamsAsync(activityTitle, activitySubtitle);
7274
}
7375

76+
public async Task<string> GetBaseUrlAsync()
77+
{
78+
var appUrl = await appUrlProvider.GetUrlAsync(appName: "MVC");
79+
return appUrl;
80+
}
81+
7482
public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto input)
7583
{
7684
HttpResponseMessage res = new();
@@ -79,18 +87,33 @@ public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto i
7987
if (await featureChecker.IsEnabledAsync("Unity.Notifications"))
8088
{
8189
var defaultFromAddress = await SettingProvider.GetOrNullAsync(NotificationsSettings.Mailing.DefaultFromAddress);
82-
var scheme = "https";
83-
var request = (httpContextAccessor.HttpContext?.Request) ?? throw new InvalidOperationException("HttpContext or Request is null.");
84-
var host = request.Host.ToUriComponent();
85-
var pathBase = "/GrantApplications/Details?ApplicationId=";
86-
var baseUrl = $"{scheme}://{host}{pathBase}";
87-
var commentLink = $"{baseUrl}{input.ApplicationId}";
90+
var baseUrl = await GetBaseUrlAsync();
91+
92+
string commentLink = input.CommentType switch
93+
{
94+
Comments.CommentType.ApplicationComment or Comments.CommentType.AssessmentComment =>
95+
QueryHelpers.AddQueryString($"{baseUrl}/GrantApplications/Details", "ApplicationId", input.OwnerId),
96+
Comments.CommentType.ApplicantComment =>
97+
QueryHelpers.AddQueryString($"{baseUrl}/GrantApplicants/Details", "ApplicantId", input.OwnerId),
98+
_ => throw new InvalidOperationException("Invalid comment type.")
99+
};
100+
88101
var subject = $"Unity-Comment: {input.Subject}";
89102
var fromEmail = defaultFromAddress ?? "NoReply@gov.bc.ca";
103+
104+
var hasSurname = !string.IsNullOrWhiteSpace(CurrentUser.SurName);
105+
var hasName = !string.IsNullOrWhiteSpace(CurrentUser.Name);
106+
107+
var currentUserText = (hasSurname, hasName) switch
108+
{
109+
(true, true) => $"{CurrentUser.SurName}, {CurrentUser.Name}",
110+
_ => CurrentUser.UserName ?? "Unknown User"
111+
};
112+
90113
string htmlBody = $@"
91114
<html lang='en' xmlns='http://www.w3.org/1999/xhtml' xmlns:v='urn:schemas-microsoft-com:vml' xmlns:o='urn:schemas-microsoft-com:office:office'>
92115
<body style='font-family: Arial, sans-serif;'>
93-
<h3 style='color: #0a58ca;'>{input.From} mentioned you in a comment.</h3>
116+
<h3 style='color: #0a58ca;'>{currentUserText} mentioned you in a comment.</h3>
94117
<table style='width: 100%; background-color: #f9f9f9; border-left: 3px solid #ccc;'>
95118
<tr>
96119
<td style='padding: 15px;'>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Unity.Notifications.Comments;
4+
5+
/// Edit Unity.GrantManager.Comments.CommentType if changing this enum as it is shared between the two projects and used in the API layer.
6+
[JsonConverter(typeof(JsonStringEnumConverter))]
7+
public enum CommentType
8+
{
9+
ApplicationComment,
10+
AssessmentComment,
11+
ApplicantComment,
12+
}

applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public GrantManagerApplicationAutoMapperProfile()
4545
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.AssessmentId));
4646
CreateMap<ApplicationComment, CommentDto>()
4747
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.ApplicationId));
48+
CreateMap<ApplicantComment, CommentDto>()
49+
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.ApplicantId));
4850
CreateMap<CommentListItem, CommentDto>()
4951
.ForMember(dest => dest.Badge, opt => opt.MapFrom(src => src.CommenterBadge))
5052
.ForMember(dest => dest.Commenter, opt => opt.MapFrom(src => src.CommenterDisplayName))
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
namespace Unity.GrantManager.Comments
1+
using System.Text.Json.Serialization;
2+
3+
namespace Unity.GrantManager.Comments;
4+
5+
/// If changing this enum, also update the corresponding Unity.Notifications.Comments.CommentType enum, as it is shared between the two projects and used in the API layer.
6+
[JsonConverter(typeof(JsonStringEnumConverter))]
7+
public enum CommentType
28
{
3-
public enum CommentType
4-
{
5-
ApplicationComment,
6-
AssessmentComment
7-
}
9+
ApplicationComment,
10+
AssessmentComment,
11+
ApplicantComment,
812
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace Unity.GrantManager.Comments;
4+
5+
public class ApplicantComment : CommentBase
6+
{
7+
public Guid ApplicantId { get; set; }
8+
}
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
using System;
22

3-
namespace Unity.GrantManager.Comments
3+
namespace Unity.GrantManager.Comments;
4+
5+
public class ApplicationComment : CommentBase
46
{
5-
public class ApplicationComment : CommentBase
6-
{
7-
public Guid ApplicationId { get; set; }
8-
}
7+
public Guid ApplicationId { get; set; }
98
}
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
using System;
22

3-
namespace Unity.GrantManager.Comments
3+
namespace Unity.GrantManager.Comments;
4+
5+
public class AssessmentComment : CommentBase
46
{
5-
public class AssessmentComment : CommentBase
6-
{
7-
public Guid AssessmentId { get; set; }
8-
}
7+
public Guid AssessmentId { get; set; }
98
}

0 commit comments

Comments
 (0)