Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions api/Vote.Monitor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.CitizenReports.Atta
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.CitizenReports.Notes.UnitTests", "tests\Feature.CitizenReports.Notes.UnitTests\Feature.CitizenReports.Notes.UnitTests.csproj", "{CCE23C74-3E33-40B7-A1E8-7672BAC5F814}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Locations", "src\Feature.Locations\Feature.Locations.csproj", "{54B0E751-AD8A-48F5-998C-E6A5701E3EF0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Locations.UnitTests", "tests\Feature.Locations.UnitTests\Feature.Locations.UnitTests.csproj", "{6DC3922B-5AC8-4968-AE5C-557315576720}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -416,6 +420,14 @@ Global
{CCE23C74-3E33-40B7-A1E8-7672BAC5F814}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CCE23C74-3E33-40B7-A1E8-7672BAC5F814}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CCE23C74-3E33-40B7-A1E8-7672BAC5F814}.Release|Any CPU.Build.0 = Release|Any CPU
{54B0E751-AD8A-48F5-998C-E6A5701E3EF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{54B0E751-AD8A-48F5-998C-E6A5701E3EF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54B0E751-AD8A-48F5-998C-E6A5701E3EF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54B0E751-AD8A-48F5-998C-E6A5701E3EF0}.Release|Any CPU.Build.0 = Release|Any CPU
{6DC3922B-5AC8-4968-AE5C-557315576720}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6DC3922B-5AC8-4968-AE5C-557315576720}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6DC3922B-5AC8-4968-AE5C-557315576720}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6DC3922B-5AC8-4968-AE5C-557315576720}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -487,6 +499,8 @@ Global
{BCBCA6CB-2CEB-4C8A-8078-EC511B812CC9} = {3441EE1D-E3C6-45BE-A020-553816015081}
{E0828B1D-4698-4D4B-85BA-1B2346A7B04F} = {3441EE1D-E3C6-45BE-A020-553816015081}
{CCE23C74-3E33-40B7-A1E8-7672BAC5F814} = {3441EE1D-E3C6-45BE-A020-553816015081}
{54B0E751-AD8A-48F5-998C-E6A5701E3EF0} = {17945B3C-5A4C-4279-8022-65ABC606A510}
{6DC3922B-5AC8-4968-AE5C-557315576720} = {3441EE1D-E3C6-45BE-A020-553816015081}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {50C20C9F-F2AF-45D8-994A-06661772B31C}
Expand Down
2 changes: 1 addition & 1 deletion api/src/Feature.CitizenReports/CitizenReportsInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ public static IServiceCollection AddCitizenReportsFeature(this IServiceCollectio
{
return services;
}
}
}
2 changes: 1 addition & 1 deletion api/src/Feature.CitizenReports/EnableTesting.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[assembly: InternalsVisibleTo("Feature.CitizenReports.UnitTests")]
[assembly: InternalsVisibleTo("Vote.Monitor.Api.IntegrationTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
10 changes: 9 additions & 1 deletion api/src/Feature.CitizenReports/GetById/Endpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public override async Task<Results<Ok<Response>, NotFound>> ExecuteAsync(Request
.CitizenReports
.Include(x => x.Attachments)
.Include(x => x.Notes)
.Include(x => x.Location)
.Where(x =>
x.ElectionRoundId == req.ElectionRoundId
&& x.Form.MonitoringNgo.NgoId == req.NgoId
Expand Down Expand Up @@ -73,9 +74,16 @@ public override async Task<Results<Ok<Response>, NotFound>> ExecuteAsync(Request
Notes = citizenReport.Notes.Select(NoteModel.FromEntity).ToArray(),
Attachments = attachments,
Questions = form.Questions.Select(QuestionsMapper.ToModel).ToArray(),

TimeSubmitted = citizenReport.LastModifiedOn ?? citizenReport.CreatedOn,
FollowUpStatus = citizenReport.FollowUpStatus,

LocationId = citizenReport.Location.Level1,
LocationLevel1 = citizenReport.Location.Level1,
LocationLevel2 = citizenReport.Location.Level2,
LocationLevel3 = citizenReport.Location.Level3,
LocationLevel4 = citizenReport.Location.Level4,
LocationLevel5 = citizenReport.Location.Level5,
};

return TypedResults.Ok(response);
Expand Down
3 changes: 2 additions & 1 deletion api/src/Feature.CitizenReports/GetById/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public class Request

[FromClaim(ApplicationClaimTypes.NgoId)]
public Guid NgoId { get; set; }

public Guid CitizenReportId { get; set; }
}
}
19 changes: 13 additions & 6 deletions api/src/Feature.CitizenReports/GetById/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@ namespace Feature.CitizenReports.GetById;

public class Response
{
public Guid CitizenReportId { get; set; }
public DateTime TimeSubmitted { get; set; }
public Guid FormId { get; set; }
public string FormCode { get; set; }
public TranslatedString FormName { get; set; }
public string FormDefaultLanguage { get; set; }
public Guid CitizenReportId { get; init; }
public DateTime TimeSubmitted { get; init; }
public Guid FormId { get; init; }
public string FormCode { get; init; }
public TranslatedString FormName { get; init; }
public string FormDefaultLanguage { get; init; }

[JsonConverter(typeof(SmartEnumNameConverter<CitizenReportFollowUpStatus, string>))]
public CitizenReportFollowUpStatus FollowUpStatus { get; init; }

public string LocationId { get; init; }
public string LocationLevel1 { get; init; }
public string LocationLevel2 { get; init; }
public string LocationLevel3 { get; init; }
public string LocationLevel4 { get; init; }
public string LocationLevel5 { get; init; }

public BaseQuestionModel[] Questions { get; init; } = [];
public BaseAnswerModel[] Answers { get; init; } = [];
public NoteModel[] Notes { get; init; } = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,33 @@ public override async Task<Results<Ok<Response>, NotFound>> ExecuteAsync(Request
return TypedResults.NotFound();
}

return await AggregateNgoFormSubmissionsAsync(form, req.ElectionRoundId, req.NgoId, req.FormId, ct);
return await AggregateNgoFormSubmissionsAsync(form, req, ct);
}

private async Task<Results<Ok<Response>, NotFound>> AggregateNgoFormSubmissionsAsync(FormAggregate form,
Guid electionRoundId,
Guid ngoId,
Guid formId,
Request req,
CancellationToken ct)
{
var citizenReports = await context.CitizenReports
.Include(x => x.Notes)
.Include(x => x.Attachments)
.Where(x => x.ElectionRoundId == electionRoundId
&& x.Form.MonitoringNgo.NgoId == ngoId
&& x.FormId == formId)
.Where(x => x.ElectionRoundId == req.ElectionRoundId
&& x.Form.MonitoringNgo.NgoId == req.NgoId
&& x.FormId == req.FormId)
.Where(x => string.IsNullOrWhiteSpace(req.Level1Filter) ||
EF.Functions.ILike(x.Location.Level1, req.Level1Filter))
.Where(x => string.IsNullOrWhiteSpace(req.Level2Filter) ||
EF.Functions.ILike(x.Location.Level2, req.Level2Filter))
.Where(x => string.IsNullOrWhiteSpace(req.Level3Filter) ||
EF.Functions.ILike(x.Location.Level3, req.Level3Filter))
.Where(x => string.IsNullOrWhiteSpace(req.Level4Filter) ||
EF.Functions.ILike(x.Location.Level4, req.Level4Filter))
.Where(x => string.IsNullOrWhiteSpace(req.Level5Filter) ||
EF.Functions.ILike(x.Location.Level5, req.Level5Filter))
.Where(x => req.HasFlaggedAnswers == null || req.HasFlaggedAnswers.Value
? x.NumberOfFlaggedAnswers > 0
: x.NumberOfFlaggedAnswers == 0)
.Where(x => req.FollowUpStatus == null || x.FollowUpStatus == req.FollowUpStatus)
.AsNoTracking()
.ToListAsync(ct);

Expand All @@ -74,7 +86,7 @@ private async Task<Results<Ok<Response>, NotFound>> AggregateNgoFormSubmissionsA
return attachment;
});

var attachments= await Task.WhenAll(tasks);
var attachments = await Task.WhenAll(tasks);

return TypedResults.Ok(new Response
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Vote.Monitor.Core.Security;
using Vote.Monitor.Domain.Entities.CitizenReportAggregate;

namespace Feature.CitizenReports.GetSubmissionsAggregated;

Expand All @@ -10,4 +11,18 @@ public class Request
public Guid NgoId { get; set; }

public Guid FormId { get; set; }
}

[QueryParam] public string? Level1Filter { get; set; }

[QueryParam] public string? Level2Filter { get; set; }

[QueryParam] public string? Level3Filter { get; set; }

[QueryParam] public string? Level4Filter { get; set; }

[QueryParam] public string? Level5Filter { get; set; }

[QueryParam] public bool? HasFlaggedAnswers { get; set; }

[QueryParam] public CitizenReportFollowUpStatus? FollowUpStatus { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ public Validator()
RuleFor(x => x.NgoId).NotEmpty();
RuleFor(x => x.FormId).NotEmpty();
}
}
}
2 changes: 1 addition & 1 deletion api/src/Feature.CitizenReports/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
global using Vote.Monitor.Core.Models;
global using Vote.Monitor.Domain.Repository;
global using CitizenReportAggregate = Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport;
global using FormAggregate = Vote.Monitor.Domain.Entities.FormAggregate.Form;
global using FormAggregate = Vote.Monitor.Domain.Entities.FormAggregate.Form;
83 changes: 78 additions & 5 deletions api/src/Feature.CitizenReports/ListEntries/Endpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ public override async Task<PagedResponse<CitizenReportEntryModel>> ExecuteAsync(
"CitizenReports" CR
INNER JOIN "ElectionRounds" ER ON ER."Id" = CR."ElectionRoundId"
INNER JOIN "MonitoringNgos" MN ON MN."Id" = ER."MonitoringNgoForCitizenReportingId"
INNER JOIN "Locations" L on L."Id" = CR."LocationId"
WHERE
MN."ElectionRoundId" = @electionRoundId
AND MN."NgoId" = @ngoId
AND (
@followUpStatus IS NULL
OR CR."FollowUpStatus" = @followUpStatus
);
)
AND (@level1 IS NULL OR L."Level1" = @level1)
AND (@level2 IS NULL OR L."Level2" = @level2)
AND (@level3 IS NULL OR L."Level3" = @level3)
AND (@level4 IS NULL OR L."Level4" = @level4)
AND (@level5 IS NULL OR L."Level5" = @level5)
;

WITH
CITIZENREPORTS AS (
Expand Down Expand Up @@ -62,12 +69,18 @@ CITIZENREPORTS AS (
AND CRA."IsCompleted" = TRUE
AND CRA."IsDeleted" = FALSE
) AS "MediaFilesCount",
CR."FollowUpStatus"
CR."FollowUpStatus",
L."Level1",
L."Level2",
L."Level3",
L."Level4",
L."Level5"
FROM
"CitizenReports" CR
INNER JOIN "Forms" F ON F."Id" = CR."FormId"
INNER JOIN "ElectionRounds" ER ON ER."Id" = CR."ElectionRoundId"
INNER JOIN "MonitoringNgos" MN ON MN."Id" = ER."MonitoringNgoForCitizenReportingId"
INNER JOIN "Locations" L on L."Id" = CR."LocationId"
WHERE
CR."ElectionRoundId" = @electionRoundId
AND MN."NgoId" = @ngoId
Expand All @@ -86,6 +99,12 @@ @hasFlaggedAnswers IS NULL
@followUpStatus IS NULL
OR "FollowUpStatus" = @followUpStatus
)
AND (@level1 IS NULL OR L."Level1" = @level1)
AND (@level2 IS NULL OR L."Level2" = @level2)
AND (@level3 IS NULL OR L."Level3" = @level3)
AND (@level4 IS NULL OR L."Level4" = @level4)
AND (@level5 IS NULL OR L."Level5" = @level5)
AND (@hasFlaggedAnswers is NULL OR @hasFlaggedAnswers = false OR 1 = 2)
)
SELECT
"CitizenReportId",
Expand All @@ -97,7 +116,12 @@ @followUpStatus IS NULL
"NumberOfFlaggedAnswers",
"NotesCount",
"MediaFilesCount",
"FollowUpStatus"
"FollowUpStatus",
"Level1",
"Level2",
"Level3",
"Level4",
"Level5"
FROM
CITIZENREPORTS
ORDER BY
Expand Down Expand Up @@ -136,7 +160,21 @@ ORDER BY
END ASC,
CASE
WHEN @sortExpression = 'NotesCount DESC' THEN "NotesCount"
END DESC
END DESC,
CASE WHEN @sortExpression = 'Level1 ASC' THEN "Level1" END ASC,
CASE WHEN @sortExpression = 'Level1 DESC' THEN "Level1" END DESC,

CASE WHEN @sortExpression = 'Level2 ASC' THEN "Level2" END ASC,
CASE WHEN @sortExpression = 'Level2 DESC' THEN "Level2" END DESC,

CASE WHEN @sortExpression = 'Level3 ASC' THEN "Level3" END ASC,
CASE WHEN @sortExpression = 'Level3 DESC' THEN "Level3" END DESC,

CASE WHEN @sortExpression = 'Level4 ASC' THEN "Level4" END ASC,
CASE WHEN @sortExpression = 'Level4 DESC' THEN "Level4" END DESC,

CASE WHEN @sortExpression = 'Level5 ASC' THEN "Level5" END ASC,
CASE WHEN @sortExpression = 'Level5 DESC' THEN "Level5" END DESC
OFFSET
@offset
ROWS
Expand All @@ -153,7 +191,12 @@ FETCH NEXT
searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%",
hasFlaggedAnswers = req.HasFlaggedAnswers,
followUpStatus = req.FollowUpStatus?.ToString(),
sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting)
level1 = req.Level1Filter,
level2 = req.Level2Filter,
level3 = req.Level3Filter,
level4 = req.Level4Filter,
level5 = req.Level5Filter,
sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting),
};

int totalRowCount;
Expand Down Expand Up @@ -214,6 +257,36 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending
return $"{nameof(CitizenReportEntryModel.TimeSubmitted)} {sortOrder}";
}

if (string.Equals(sortColumnName, nameof(CitizenReportEntryModel.Level1),
StringComparison.InvariantCultureIgnoreCase))
{
return $"{nameof(CitizenReportEntryModel.Level1)} {sortOrder}";
}

if (string.Equals(sortColumnName, nameof(CitizenReportEntryModel.Level2),
StringComparison.InvariantCultureIgnoreCase))
{
return $"{nameof(CitizenReportEntryModel.Level2)} {sortOrder}";
}

if (string.Equals(sortColumnName, nameof(CitizenReportEntryModel.Level3),
StringComparison.InvariantCultureIgnoreCase))
{
return $"{nameof(CitizenReportEntryModel.Level3)} {sortOrder}";
}

if (string.Equals(sortColumnName, nameof(CitizenReportEntryModel.Level4),
StringComparison.InvariantCultureIgnoreCase))
{
return $"{nameof(CitizenReportEntryModel.Level4)} {sortOrder}";
}

if (string.Equals(sortColumnName, nameof(CitizenReportEntryModel.Level5),
StringComparison.InvariantCultureIgnoreCase))
{
return $"{nameof(CitizenReportEntryModel.Level5)} {sortOrder}";
}

return $"{nameof(CitizenReportEntryModel.TimeSubmitted)} DESC";
}
}
12 changes: 12 additions & 0 deletions api/src/Feature.CitizenReports/ListEntries/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@ public class Request : BaseSortPaginatedRequest

[QueryParam] public string? SearchText { get; set; }

[QueryParam] public string? Level1Filter { get; set; }

[QueryParam] public string? Level2Filter { get; set; }

[QueryParam] public string? Level3Filter { get; set; }

[QueryParam] public string? Level4Filter { get; set; }

[QueryParam] public string? Level5Filter { get; set; }


[QueryParam] public bool? HasFlaggedAnswers { get; set; }


[QueryParam] public CitizenReportFollowUpStatus? FollowUpStatus { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ public class Request

[FromClaim(ApplicationClaimTypes.NgoId)]
public Guid NgoId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ public Validator()
RuleFor(x => x.ElectionRoundId).NotEmpty();
RuleFor(x => x.NgoId).NotEmpty();
}
}
}
Loading