Skip to content

Commit 3880318

Browse files
committed
TD-5154 Implements basic pre-process logic and view and adds validation to exported sheet
1 parent 6ff7840 commit 3880318

File tree

12 files changed

+208
-123
lines changed

12 files changed

+208
-123
lines changed

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace DigitalLearningSolutions.Data.Models.Frameworks.Import
22
{
33
using ClosedXML.Excel;
4+
using Org.BouncyCastle.Asn1.X509;
5+
46
public enum RowStatus
57
{
68
NotYetProcessed,
@@ -18,16 +20,16 @@ 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");
3133
FlagsCsv = FindFieldValue("FlagsCSV");
3234
RowStatus = RowStatus.NotYetProcessed;
3335
}

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: 23 additions & 6 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
}
@@ -72,7 +71,25 @@ public IActionResult StartImport(ImportCompetenciesViewModel model, string tabna
7271
[Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")]
7372
public IActionResult ImportCompleted()
7473
{
75-
return View("Developer/ImportCompleted");
74+
var data = GetBulkUploadData();
75+
var uploadDir = Path.Combine(webHostEnvironment.WebRootPath, "Uploads\\");
76+
var filePath = Path.Combine(uploadDir, data.CompetenciesFileName);
77+
var workbook = new XLWorkbook(filePath);
78+
try
79+
{
80+
var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook);
81+
var resultsModel = new ImportCompetenciesPreProcessViewModel(results);
82+
data.CompetenciesToProcessCount = resultsModel.ToProcessCount;
83+
data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount;
84+
data.CompetenciesToUpdateCount = resultsModel.CompetenciesToUpdateCount;
85+
setBulkUploadData(data);
86+
return View("Developer/ImportCompleted", resultsModel);
87+
}
88+
catch (InvalidHeadersException)
89+
{
90+
FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName);
91+
return View("ImportFailed");
92+
}
7693
}
7794

7895
private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName)

DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ public BulkCompetenciesData(int frameworkId, int adminUserId, string competencie
1919
public string CompetenciesFileName { get; set; }
2020
public List<int> AssessmentQuestionIDs { get; set; }
2121
public int? AddAssessmentQuestionOption { get; set; }
22-
public int ToProcessCount { get; set; }
23-
public int ToAddCount { get; set; }
24-
public int ToUpdateCount { get; set; }
22+
public int CompetenciesToProcessCount { get; set; }
23+
public int CompetenciesToAddCount { get; set; }
24+
public int CompetenciesToUpdateCount { get; set; }
2525
public int LastRowProcessed { get; set; }
2626
public int SubtotalCompetenciesAdded { get; set; }
2727
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: 43 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,38 @@ 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+
if (competencyRow.id == null)
48+
{
49+
competencyRow.RowStatus = RowStatus.CompetencyInserted;
50+
}
51+
else
52+
{
53+
competencyRow.RowStatus = RowStatus.CompetencyUpdated;
54+
}
55+
}
56+
public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId)
3457
{
3558
int maxFrameworkCompetencyId = frameworkService.GetMaxFrameworkCompetencyID();
3659
int maxFrameworkCompetencyGroupId = frameworkService.GetMaxFrameworkCompetencyGroupID();
37-
var table = OpenCompetenciesTable(file);
60+
var table = OpenCompetenciesTable(workbook);
3861
return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId);
3962
}
40-
internal IXLTable OpenCompetenciesTable(IFormFile file)
63+
internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook)
4164
{
42-
var workbook = new XLWorkbook(file.OpenReadStream());
4365
var worksheet = workbook.Worksheet(1);
66+
worksheet.Columns(1, 15).Unhide();
4467
if (worksheet.Tables.Count() == 0)
4568
{
4669
throw new InvalidHeadersException();
@@ -112,11 +135,15 @@ private static bool ValidateHeaders(IXLTable table)
112135
{
113136
var expectedHeaders = new List<string>
114137
{
115-
"competency group",
116-
"competency name",
117-
"competency description"
138+
"ID",
139+
"CompetencyGroup",
140+
"GroupDescription",
141+
"Competency",
142+
"CompetencyDescription",
143+
"AlwaysShowDescription",
144+
"FlagsCSV"
118145
}.OrderBy(x => x);
119-
var actualHeaders = table.Fields.Select(x => x.Name.ToLower()).OrderBy(x => x);
146+
var actualHeaders = table.Fields.Select(x => x.Name).OrderBy(x => x);
120147
return actualHeaders.SequenceEqual(expectedHeaders);
121148
}
122149

@@ -128,6 +155,13 @@ public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank)
128155
{
129156
ClosedXmlHelper.HideWorkSheetColumn(workbook, "ID");
130157
}
158+
var options = new List<string> { "TRUE", "FALSE" };
159+
ClosedXmlHelper.AddValidationListToWorksheetColumn(workbook, 6, options);
160+
var rowCount = workbook.Worksheet(1).RangeUsed().RowCount();
161+
ClosedXmlHelper.AddValidationRangeToWorksheetColumn(workbook, 1, 1, rowCount, 1);
162+
// Calculate the workbook
163+
workbook.CalculateMode = XLCalculateMode.Auto;
164+
workbook.RecalculateAllFormulas();
131165
using var stream = new MemoryStream();
132166
workbook.SaveAs(stream);
133167
return stream.ToArray();

DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
using System;
33
using DigitalLearningSolutions.Web.Models;
44
using System.Linq;
5+
using DigitalLearningSolutions.Data.Models.Frameworks.Import;
56

67
namespace DigitalLearningSolutions.Web.ViewModels.Frameworks
78
{
89
public class ImportCompetenciesPreProcessViewModel
910
{
10-
public ImportCompetenciesPreProcessViewModel(BulkCompetenciesResult bulkCompetenciesResult)
11+
public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompetenciesResult)
1112
{
1213
ToProcessCount = bulkCompetenciesResult.ProcessedCount;
1314
CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount;
@@ -25,20 +26,21 @@ public ImportCompetenciesPreProcessViewModel(BulkCompetenciesResult bulkCompeten
2526
public int CompetencyGroupsToAddCount { get; set; }
2627
public int CompetencyGroupsToUpdateCount { get; set; }
2728
public int ToUpdateOrSkipCount { get; set; }
29+
public string? CompetenciesFileName { get; set; }
2830

29-
private static string MapReasonToErrorMessage(BulkCompetenciesResult.ErrorReason reason)
31+
private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason)
3032
{
3133
return reason switch
3234
{
33-
BulkCompetenciesResult.ErrorReason.TooLongCompetencyGroupName =>
35+
ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName =>
3436
"Group name must be 255 characters or less.",
35-
BulkCompetenciesResult.ErrorReason.MissingCompetencyName =>
37+
ImportCompetenciesResult.ErrorReason.MissingCompetencyName =>
3638
"Competency is blank. Competency is a required field and cannot be left blank",
37-
BulkCompetenciesResult.ErrorReason.InvalidId =>
39+
ImportCompetenciesResult.ErrorReason.InvalidId =>
3840
"The ID provided does not match a Competency ID in this Framework",
39-
BulkCompetenciesResult.ErrorReason.TooLongCompetencyName =>
41+
ImportCompetenciesResult.ErrorReason.TooLongCompetencyName =>
4042
"Competency must be 255 characters or less.",
41-
BulkCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription =>
43+
ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription =>
4244
"Always show description is invalid. The Always show description field must contain 'TRUE' or 'FALSE'",
4345
_ => "Unspecified error.",
4446
};

DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public class ImportCompetenciesResultsViewModel
99
public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompetenciesResult)
1010
{
1111
ProcessedCount = importCompetenciesResult.ProcessedCount;
12-
CompetenciesInsertedCount = importCompetenciesResult.CompetenciesInsertedCount;
13-
CompetencyGroupsInsertedCount = importCompetenciesResult.CompetencyGroupsInsertedCount;
12+
CompetenciesInsertedCount = importCompetenciesResult.CompetencyAddedCount;
13+
CompetencyGroupsInsertedCount = importCompetenciesResult.GroupAddedCount;
1414
SkippedCount = importCompetenciesResult.SkippedCount;
1515
Errors = importCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason)));
1616
}

DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
public class ImportCompetenciesViewModel
88
{
99
public int FrameworkId { get; set; }
10+
public bool IsNotBlank { get; set; }
1011
[Required(ErrorMessage = "Import competencies file is required")]
1112
[AllowedExtensions(new[] { ".xlsx" }, "Import competencies file must be in xlsx format")]
1213
[MaxFileSize(5 * 1024 * 1024, "Maximum allowed file size is 5MB")]

0 commit comments

Comments
 (0)