Skip to content

Commit 60eccd1

Browse files
authored
Merge pull request #3036 from TechnologyEnhancedLearning/Develop/Features/TD-5154-CompetenciesUploadedPage
TD-5154 Implements competencies uploaded page
2 parents 6ff7840 + 0ee3d02 commit 60eccd1

File tree

13 files changed

+232
-132
lines changed

13 files changed

+232
-132
lines changed

DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace DigitalLearningSolutions.Data.Models.Frameworks.Import
22
{
33
using ClosedXML.Excel;
4+
45
public enum RowStatus
56
{
67
NotYetProcessed,
@@ -10,28 +11,32 @@ public enum RowStatus
1011
CompetencyUpdated,
1112
CompetencyGroupInserted,
1213
CompetencyGroupUpdated,
13-
CompetencyGroupAndCompetencyUpdated
14+
CompetencyGroupAndCompetencyUpdated,
15+
InvalidAlwaysShowDescription
1416
}
1517
public class CompetencyTableRow : BulkCompetency
1618
{
1719
public CompetencyTableRow(IXLTable table, IXLRangeRow row)
1820
{
1921
string? FindFieldValue(string name)
2022
{
21-
var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name).ColumnNumber();
23+
var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name.ToLower()).ColumnNumber();
2224
return row.Cell(colNumber).GetValue<string?>();
2325
}
2426

2527
RowNumber = row.RowNumber();
26-
id = int.Parse(FindFieldValue("ID"));
28+
id = row.Cell(1).GetValue<int?>();
2729
CompetencyGroup = FindFieldValue("CompetencyGroup");
2830
Competency = FindFieldValue("Competency");
2931
CompetencyDescription = FindFieldValue("CompetencyDescription");
30-
GroupDescription = FindFieldValue("CompetencyGroupDescription");
32+
GroupDescription = FindFieldValue("GroupDescription");
33+
AlwaysShowDescriptionRaw = FindFieldValue("AlwaysShowDescription");
34+
AlwaysShowDescription = bool.TryParse(AlwaysShowDescriptionRaw, out var hasPrn) ? hasPrn : (bool?)null;
3135
FlagsCsv = FindFieldValue("FlagsCSV");
3236
RowStatus = RowStatus.NotYetProcessed;
3337
}
3438
public int RowNumber { get; set; }
39+
public string? AlwaysShowDescriptionRaw { get; set; }
3540
public ImportCompetenciesResult.ErrorReason? Error { get; set; }
3641
public RowStatus RowStatus { get; set; }
3742
public bool Validate()
@@ -48,6 +53,10 @@ public bool Validate()
4853
{
4954
Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyName;
5055
}
56+
else if (!string.IsNullOrWhiteSpace(AlwaysShowDescriptionRaw) && !bool.TryParse(AlwaysShowDescriptionRaw, out _))
57+
{
58+
Error = ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription;
59+
}
5160

5261
return !Error.HasValue;
5362
}

DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ public class ImportCompetenciesResult
88
{
99
public enum ErrorReason
1010
{
11+
InvalidId,
1112
MissingCompetencyName,
12-
TooLongCompetencyGroupName,
1313
TooLongCompetencyName,
14-
AlreadyExists
14+
TooLongCompetencyGroupName,
15+
InvalidAlwaysShowDescription
1516
}
1617
public ImportCompetenciesResult() { }
1718

@@ -20,16 +21,18 @@ IReadOnlyCollection<CompetencyTableRow> competencyTableRows
2021
)
2122
{
2223
ProcessedCount = competencyTableRows.Count;
23-
CompetenciesInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
24-
CompetencyGroupsInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
24+
CompetencyAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
25+
GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
2526
SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped);
2627
Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value));
2728
}
2829

2930
public IEnumerable<(int RowNumber, ErrorReason Reason)>? Errors { get; set; }
3031
public int ProcessedCount { get; set; }
31-
public int CompetenciesInsertedCount { get; set; }
32-
public int CompetencyGroupsInsertedCount { get; set; }
32+
public int CompetencyAddedCount { get; set; }
33+
public int CompetencyUpdatedCount { get; set; }
34+
public int GroupAddedCount { get; set; }
35+
public int GroupUpdatedCount { get; set; }
3336
public int SkippedCount { get; set; }
3437
}
3538
}

DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
using ClosedXML.Excel;
22
using DigitalLearningSolutions.Data.Exceptions;
3-
using DigitalLearningSolutions.Data.Migrations;
4-
using DigitalLearningSolutions.Data.Utilities;
53
using DigitalLearningSolutions.Web.Helpers;
64
using DigitalLearningSolutions.Web.Models;
75
using DigitalLearningSolutions.Web.Services;
86
using DigitalLearningSolutions.Web.ViewModels.Frameworks;
97
using GDS.MultiPageFormData.Enums;
10-
using Microsoft.AspNetCore.Hosting;
118
using Microsoft.AspNetCore.Mvc;
9+
using System.IO;
1210

1311
namespace DigitalLearningSolutions.Web.Controllers.FrameworksController
1412
{
1513
public partial class FrameworksController
1614
{
1715
[Route("/Framework/{frameworkId}/{tabname}/Import")]
18-
public IActionResult ImportCompetencies(int frameworkId, string tabname)
16+
public IActionResult ImportCompetencies(int frameworkId, string tabname, bool isNotBlank)
1917
{
2018
var adminId = GetAdminId();
2119
var userRole = frameworkService.GetAdminUserRoleForFrameworkId(adminId, frameworkId);
2220
if (userRole < 2)
2321
return StatusCode(403);
2422
var model = new ImportCompetenciesViewModel()
2523
{
26-
FrameworkId = frameworkId
24+
FrameworkId = frameworkId,
25+
IsNotBlank = isNotBlank
2726
};
2827
return View("Developer/ImportCompetencies", model);
2928
}
@@ -41,7 +40,8 @@ public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption)
4140
}
4241
[HttpPost]
4342
[Route("/Framework/{frameworkId}/{tabname}/Import")]
44-
public IActionResult StartImport(ImportCompetenciesViewModel model, string tabname)
43+
[Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")]
44+
public IActionResult StartImport(ImportCompetenciesViewModel model, string tabname, bool isNotBlank)
4545
{
4646
if (!ModelState.IsValid)
4747
return View("Developer/ImportCompetencies", model);
@@ -51,11 +51,11 @@ public IActionResult StartImport(ImportCompetenciesViewModel model, string tabna
5151
var workbook = new XLWorkbook(model.ImportFile.OpenReadStream());
5252
if (!workbook.Worksheets.Contains(ImportCompetenciesFromFileService.CompetenciesSheetName))
5353
{
54-
ModelState.AddModelError("InvalidWorksheet", CommonValidationErrorMessages.InvalidCompetenciesUploadExcelFile);
54+
ModelState.AddModelError("ImportFile", CommonValidationErrorMessages.InvalidCompetenciesUploadExcelFile);
5555
return View("Developer/ImportCompetencies", model);
5656
}
5757
var competenciesFileName = FileHelper.UploadFile(webHostEnvironment, model.ImportFile);
58-
setupBulkUploadData(model.FrameworkId, adminUserID, competenciesFileName, tabname);
58+
setupBulkUploadData(model.FrameworkId, adminUserID, competenciesFileName, tabname, isNotBlank);
5959

6060
return RedirectToAction("ImportCompleted", "Frameworks", new { frameworkId = model.FrameworkId, tabname });
6161
}
@@ -72,15 +72,33 @@ public IActionResult StartImport(ImportCompetenciesViewModel model, string tabna
7272
[Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")]
7373
public IActionResult ImportCompleted()
7474
{
75-
return View("Developer/ImportCompleted");
75+
var data = GetBulkUploadData();
76+
var uploadDir = Path.Combine(webHostEnvironment.WebRootPath, "Uploads\\");
77+
var filePath = Path.Combine(uploadDir, data.CompetenciesFileName);
78+
var workbook = new XLWorkbook(filePath);
79+
try
80+
{
81+
var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook);
82+
var resultsModel = new ImportCompetenciesPreProcessViewModel(results) { IsNotBlank = data.IsNotBlank, TabName = data.TabName };
83+
data.CompetenciesToProcessCount = resultsModel.ToProcessCount;
84+
data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount;
85+
data.CompetenciesToUpdateCount = resultsModel.CompetenciesToUpdateCount;
86+
setBulkUploadData(data);
87+
return View("Developer/ImportCompleted", resultsModel);
88+
}
89+
catch (InvalidHeadersException)
90+
{
91+
FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName);
92+
return View("ImportFailed");
93+
}
7694
}
7795

78-
private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName)
96+
private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank)
7997
{
8098
TempData.Clear();
8199
multiPageFormService.ClearMultiPageFormData(MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), TempData);
82100
var today = clockUtility.UtcToday;
83-
var bulkUploadData = new BulkCompetenciesData(frameworkId, adminUserID, competenciessFileName, tabName);
101+
var bulkUploadData = new BulkCompetenciesData(frameworkId, adminUserID, competenciessFileName, tabName, isNotBlank);
84102
setBulkUploadData(bulkUploadData);
85103
}
86104
private void setBulkUploadData(BulkCompetenciesData bulkUploadData)

DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static class CommonValidationErrorMessages
3636
public const string CentreNameAlreadyExist = "The centre name you have entered already exists, please enter a different centre name";
3737
public const string MaxBulkUploadRowsLimit = "File must contain no more than {0} rows";
3838
public const string InvalidBulkUploadExcelFile = "The uploaded file must contain a \"DelegatesBulkUpload\" worksheet. Use the \"Download delegates\" button to generate a template.";
39-
public const string InvalidCompetenciesUploadExcelFile = "The uploaded file must contain a \"CompetenciesBulkUpload\" worksheet. Use the \"Download competencies\" button to generate a template.";
39+
public const string InvalidCompetenciesUploadExcelFile = "The uploaded file must contain a \"CompetenciesBulkUpload\" worksheet. Use the \"Download template\" button to generate a template.";
4040
public const string ReportFilterReturnsTooManyRows = "The report frequency is too high for the date range. Choose a lower report frequency (or shorten the date range)";
4141
}
4242
}

DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,24 @@ namespace DigitalLearningSolutions.Web.Models
66
public class BulkCompetenciesData
77
{
88
public BulkCompetenciesData() { }
9-
public BulkCompetenciesData(int frameworkId, int adminUserId, string competenciesFileName, string tabName)
9+
public BulkCompetenciesData(int frameworkId, int adminUserId, string competenciesFileName, string tabName, bool isNotBlank)
1010
{
1111
FrameworkId = frameworkId;
1212
AdminUserId = adminUserId;
1313
CompetenciesFileName = competenciesFileName;
1414
TabName = tabName;
15+
IsNotBlank = isNotBlank;
1516
}
1617
public int FrameworkId { get; set; }
1718
public string TabName { get; set; }
1819
public int AdminUserId { get; set; }
20+
public bool IsNotBlank { get; set; }
1921
public string CompetenciesFileName { get; set; }
2022
public List<int> AssessmentQuestionIDs { get; set; }
2123
public int? AddAssessmentQuestionOption { get; set; }
22-
public int ToProcessCount { get; set; }
23-
public int ToAddCount { get; set; }
24-
public int ToUpdateCount { get; set; }
24+
public int CompetenciesToProcessCount { get; set; }
25+
public int CompetenciesToAddCount { get; set; }
26+
public int CompetenciesToUpdateCount { get; set; }
2527
public int LastRowProcessed { get; set; }
2628
public int SubtotalCompetenciesAdded { get; set; }
2729
public int SubtotalCompetenciesUpdated { get; set; }

DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs

Lines changed: 0 additions & 40 deletions
This file was deleted.

DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ namespace DigitalLearningSolutions.Web.Services
1212
using DigitalLearningSolutions.Data.Exceptions;
1313
using DigitalLearningSolutions.Data.Helpers;
1414
using DigitalLearningSolutions.Data.Models.Frameworks.Import;
15+
using DigitalLearningSolutions.Web.Models;
1516
using Microsoft.AspNetCore.Http;
1617

1718
public interface IImportCompetenciesFromFileService
1819
{
1920
byte[] GetCompetencyFileForFramework(int frameworkId, bool v);
20-
public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId);
21+
public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook);
22+
public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId);
2123
}
2224
public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService
2325
{
@@ -30,17 +32,39 @@ IFrameworkService frameworkService
3032
{
3133
this.frameworkService = frameworkService;
3234
}
33-
public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId)
35+
public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook)
36+
{
37+
var table = OpenCompetenciesTable(workbook);
38+
var competencyRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList();
39+
foreach (var competencyRow in competencyRows)
40+
{
41+
PreProcessCompetencyRow(competencyRow);
42+
}
43+
return new ImportCompetenciesResult(competencyRows);
44+
}
45+
private void PreProcessCompetencyRow(CompetencyTableRow competencyRow)
46+
{
47+
competencyRow.Validate();
48+
if (competencyRow.id == null)
49+
{
50+
competencyRow.RowStatus = RowStatus.CompetencyInserted;
51+
}
52+
else
53+
{
54+
competencyRow.RowStatus = RowStatus.CompetencyUpdated;
55+
}
56+
}
57+
public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId)
3458
{
3559
int maxFrameworkCompetencyId = frameworkService.GetMaxFrameworkCompetencyID();
3660
int maxFrameworkCompetencyGroupId = frameworkService.GetMaxFrameworkCompetencyGroupID();
37-
var table = OpenCompetenciesTable(file);
61+
var table = OpenCompetenciesTable(workbook);
3862
return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId);
3963
}
40-
internal IXLTable OpenCompetenciesTable(IFormFile file)
64+
internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook)
4165
{
42-
var workbook = new XLWorkbook(file.OpenReadStream());
4366
var worksheet = workbook.Worksheet(1);
67+
worksheet.Columns(1, 15).Unhide();
4468
if (worksheet.Tables.Count() == 0)
4569
{
4670
throw new InvalidHeadersException();
@@ -112,11 +136,15 @@ private static bool ValidateHeaders(IXLTable table)
112136
{
113137
var expectedHeaders = new List<string>
114138
{
115-
"competency group",
116-
"competency name",
117-
"competency description"
139+
"ID",
140+
"CompetencyGroup",
141+
"GroupDescription",
142+
"Competency",
143+
"CompetencyDescription",
144+
"AlwaysShowDescription",
145+
"FlagsCSV"
118146
}.OrderBy(x => x);
119-
var actualHeaders = table.Fields.Select(x => x.Name.ToLower()).OrderBy(x => x);
147+
var actualHeaders = table.Fields.Select(x => x.Name).OrderBy(x => x);
120148
return actualHeaders.SequenceEqual(expectedHeaders);
121149
}
122150

@@ -128,6 +156,13 @@ public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank)
128156
{
129157
ClosedXmlHelper.HideWorkSheetColumn(workbook, "ID");
130158
}
159+
var options = new List<string> { "TRUE", "FALSE" };
160+
ClosedXmlHelper.AddValidationListToWorksheetColumn(workbook, 6, options);
161+
var rowCount = workbook.Worksheet(1).RangeUsed().RowCount();
162+
ClosedXmlHelper.AddValidationRangeToWorksheetColumn(workbook, 1, 1, rowCount, 1);
163+
// Calculate the workbook
164+
workbook.CalculateMode = XLCalculateMode.Auto;
165+
workbook.RecalculateAllFormulas();
131166
using var stream = new MemoryStream();
132167
workbook.SaveAs(stream);
133168
return stream.ToArray();

0 commit comments

Comments
 (0)