diff --git a/Management.Test/Markdown/AssignmentMarkdownTests.cs b/Management.Test/Markdown/AssignmentMarkdownTests.cs index a6085bf..1a5c650 100644 --- a/Management.Test/Markdown/AssignmentMarkdownTests.cs +++ b/Management.Test/Markdown/AssignmentMarkdownTests.cs @@ -9,7 +9,104 @@ public class AssignmentMarkdownTests { Name="test assignment", Description ="here is the description", - // LockAtDueDate = false + DueAt = new DateTime(), + LockAt = new DateTime(), + SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], + LocalAssignmentGroupName = "Final Project", + Rubric = new List() { + new RubricItem() {Points = 4, Label="do task 1"}, + new RubricItem() {Points = 2, Label="do task 2"}, + } }; + + var assignmentMarkdown = assignment.ToMarkdown(); + + var parsedAssignment = LocalAssignment.FromMarkdown(assignmentMarkdown); + parsedAssignment.Should().BeEquivalentTo(assignment); + } + [Test] + public void AssignmentWithEmptyRubric_CanBeParsed() + { + var assignment = new LocalAssignment() + { + Name="test assignment", + Description ="here is the description", + DueAt = new DateTime(), + LockAt = new DateTime(), + SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], + LocalAssignmentGroupName = "Final Project", + Rubric = new List() {} + }; + + var assignmentMarkdown = assignment.ToMarkdown(); + + var parsedAssignment = LocalAssignment.FromMarkdown(assignmentMarkdown); + parsedAssignment.Should().BeEquivalentTo(assignment); + } + [Test] + public void AssignmentWithEmptySubmissionTypes_CanBeParsed() + { + var assignment = new LocalAssignment() + { + Name="test assignment", + Description ="here is the description", + DueAt = new DateTime(), + LockAt = new DateTime(), + SubmissionTypes = [], + LocalAssignmentGroupName = "Final Project", + Rubric = new List() { + new RubricItem() {Points = 4, Label="do task 1"}, + new RubricItem() {Points = 2, Label="do task 2"}, + } + }; + + var assignmentMarkdown = assignment.ToMarkdown(); + + var parsedAssignment = LocalAssignment.FromMarkdown(assignmentMarkdown); + parsedAssignment.Should().BeEquivalentTo(assignment); + } + [Test] + public void AssignmentWithoutLockAtDate_CanBeParsed() + { + var assignment = new LocalAssignment() + { + Name="test assignment", + Description ="here is the description", + DueAt = new DateTime(), + LockAt = null, + SubmissionTypes = [], + LocalAssignmentGroupName = "Final Project", + Rubric = new List() { + new RubricItem() {Points = 4, Label="do task 1"}, + new RubricItem() {Points = 2, Label="do task 2"}, + } + }; + + var assignmentMarkdown = assignment.ToMarkdown(); + + var parsedAssignment = LocalAssignment.FromMarkdown(assignmentMarkdown); + parsedAssignment.Should().BeEquivalentTo(assignment); + } + [Test] + public void AssignmentWithoutDescription_CanBeParsed() + { + var assignment = new LocalAssignment() + { + Name="test assignment", + Description = "", + DueAt = new DateTime(), + LockAt = new DateTime(), + SubmissionTypes = [], + LocalAssignmentGroupName = "Final Project", + Rubric = new List() { + new RubricItem() {Points = 4, Label="do task 1"}, + new RubricItem() {Points = 2, Label="do task 2"}, + } + }; + + var assignmentMarkdown = assignment.ToMarkdown(); + + var parsedAssignment = LocalAssignment.FromMarkdown(assignmentMarkdown); + parsedAssignment.Should().BeEquivalentTo(assignment); } } \ No newline at end of file diff --git a/Management.Web/Shared/Components/AssignmentForm/SubmissionTypeSelector.razor b/Management.Web/Shared/Components/AssignmentForm/SubmissionTypeSelector.razor index cc43cea..cace1d1 100644 --- a/Management.Web/Shared/Components/AssignmentForm/SubmissionTypeSelector.razor +++ b/Management.Web/Shared/Components/AssignmentForm/SubmissionTypeSelector.razor @@ -31,7 +31,7 @@ private bool discussionIsSelected { get => types.FirstOrDefault( - t => t == SubmissionType.DISCUSSION_TOPIC + t => t == AssignmentSubmissionType.DISCUSSION_TOPIC ) != null; } private void saveTypes(IEnumerable newTypes) @@ -51,9 +51,9 @@
Submission Types
- @foreach (var submissionType in SubmissionType.AllTypes) + @foreach (var submissionType in AssignmentSubmissionType.AllTypes) { - var isDiscussion = submissionType == SubmissionType.DISCUSSION_TOPIC; + var isDiscussion = submissionType == AssignmentSubmissionType.DISCUSSION_TOPIC; var allowedToBeChecked = !discussionIsSelected || isDiscussion;
diff --git a/Management.Web/Shared/Module/NewAssignment.razor b/Management.Web/Shared/Module/NewAssignment.razor index 29c9c4e..d2d051d 100644 --- a/Management.Web/Shared/Module/NewAssignment.razor +++ b/Management.Web/Shared/Module/NewAssignment.razor @@ -27,7 +27,7 @@ Rubric = new RubricItem[] { }, LockAt = DateTime.Now, DueAt = DateTime.Now, - SubmissionTypes = new string[] { SubmissionType.ONLINE_TEXT_ENTRY }, + SubmissionTypes = new string[] { AssignmentSubmissionType.ONLINE_TEXT_ENTRY }, LocalAssignmentGroupName = selectedAssignmentGroup?.Name, }; diff --git a/Management/Features/Configuration/CoursePlannerValidationExtensions.cs b/Management/Features/Configuration/CoursePlannerValidationExtensions.cs index 927463a..3697c48 100644 --- a/Management/Features/Configuration/CoursePlannerValidationExtensions.cs +++ b/Management/Features/Configuration/CoursePlannerValidationExtensions.cs @@ -48,10 +48,10 @@ public static class CoursePlannerExtensions public static LocalAssignment validateSubmissionTypes(this LocalAssignment assignment) { var containsDiscussion = - assignment.SubmissionTypes.FirstOrDefault(t => t == SubmissionType.DISCUSSION_TOPIC) != null; + assignment.SubmissionTypes.FirstOrDefault(t => t == AssignmentSubmissionType.DISCUSSION_TOPIC) != null; if (containsDiscussion) - return assignment with { SubmissionTypes = new string[] { SubmissionType.DISCUSSION_TOPIC } }; + return assignment with { SubmissionTypes = new string[] { AssignmentSubmissionType.DISCUSSION_TOPIC } }; return assignment; } diff --git a/Management/Models/Local/LocalAssignment.cs b/Management/Models/Local/LocalAssignment.cs index 2ab4392..b15062a 100644 --- a/Management/Models/Local/LocalAssignment.cs +++ b/Management/Models/Local/LocalAssignment.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Reflection.Metadata.Ecma335; using System.Text; using System.Text.RegularExpressions; using YamlDotNet.Serialization; @@ -13,7 +14,7 @@ public record RubricItem public bool IsExtraCredit => Label.Contains(extraCredit.ToLower(), StringComparison.CurrentCultureIgnoreCase); } -public static class SubmissionType +public static class AssignmentSubmissionType { public static readonly string ONLINE_TEXT_ENTRY = "online_text_entry"; public static readonly string ONLINE_UPLOAD = "online_upload"; @@ -46,12 +47,12 @@ public record LocalAssignment public string Name { get; init; } = ""; public string Description { get; init; } = ""; // public bool LockAtDueDate { get; init; } - public IEnumerable Rubric { get; init; } = Array.Empty(); public DateTime? LockAt { get; init; } public DateTime DueAt { get; init; } public string? LocalAssignmentGroupName { get; init; } - public int PointsPossible => Rubric.Sum(r => r.IsExtraCredit ? 0 : r.Points); public IEnumerable SubmissionTypes { get; init; } = Array.Empty(); + public IEnumerable Rubric { get; init; } = Array.Empty(); + public int PointsPossible => Rubric.Sum(r => r.IsExtraCredit ? 0 : r.Points); public string GetRubricHtml() { @@ -85,15 +86,97 @@ public record LocalAssignment return yaml; } + public static LocalAssignment FromMarkdown(string input) + { + var settingsString = input.Split("---")[0]; + var (name, localAssignmentGroupName, submissionTypes, dueAt, lockAt) = parseSettings(settingsString); + + var description = input.Split("---" + Environment.NewLine)[1].Split("## Rubric")[0]; + + var rubricString = input.Split("## Rubric" + Environment.NewLine)[1]; + var rubric = ParseRubricMarkdown(rubricString); + return new LocalAssignment() + { + Name=name.Trim(), + LocalAssignmentGroupName=localAssignmentGroupName.Trim(), + SubmissionTypes=submissionTypes, + DueAt=dueAt, + LockAt=lockAt, + Rubric=rubric, + Description=description.Trim() + }; + } + + private static (string name, string assignmentGroupName, List submissionTypes, DateTime dueAt, DateTime? lockAt) parseSettings(string input) + { + var name = extractLabelValue(input, "Name"); + var rawLockAt = extractLabelValue(input, "LockAt"); + var rawDueAt = extractLabelValue(input, "DueAt"); + var localAssignmentGroupName = extractLabelValue(input, "AssignmentGroupName"); + var submissionTypes = parseSubmissionTypes(input); + + DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt) + ? parsedLockAt + : null; + var dueAt = DateTime.TryParse(rawDueAt, out DateTime parsedDueAt) + ? parsedDueAt + : throw new QuizMarkdownParseException($"Error with DueAt: {rawDueAt}"); + + return (name, localAssignmentGroupName, submissionTypes, dueAt, lockAt); + + + } + + private static List parseSubmissionTypes(string input) + { + List submissionTypes = new List(); + + // Define a regular expression pattern to match the bulleted list items + string startOfTypePattern = @"- (.+)"; + Regex regex = new Regex(startOfTypePattern); + + var inputAfterSubmissionTypes = input.Split("SubmissionTypes:" + Environment.NewLine)[1]; + + string[] lines = inputAfterSubmissionTypes.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + + foreach (string line in lines) + { + string trimmedLine = line.Trim(); + Match match = regex.Match(trimmedLine); + + if (!match.Success) + break; + + string type = match.Groups[1].Value.Trim(); + submissionTypes.Add(type); + } + + return submissionTypes; + } + + static string extractLabelValue(string input, string label) + { + string pattern = $@"{label}: (.*?)\n"; + Match match = Regex.Match(input, pattern); + + if (match.Success) + { + return match.Groups[1].Value; + } + + return string.Empty; + } + public string ToMarkdown() { - var assignmentYaml = ToYaml(); + var settingsMarkdown = settingsToMarkdown(); + var rubricMarkdown = RubricToMarkdown(); var assignmentMarkdown = - "```yaml" + Environment.NewLine - + assignmentYaml - + "```" + Environment.NewLine - + "" + Environment.NewLine - + Description; + settingsMarkdown + Environment.NewLine + + "---" + Environment.NewLine + + Description + Environment.NewLine + + "## Rubric" + Environment.NewLine + + rubricMarkdown; return assignmentMarkdown; } @@ -109,8 +192,25 @@ public record LocalAssignment return builder.ToString(); } + private string settingsToMarkdown() + { + var builder = new StringBuilder(); + builder.Append($"Name: {Name}" + Environment.NewLine); + builder.Append($"LockAt: {LockAt}" + Environment.NewLine); + builder.Append($"DueAt: {DueAt}" + Environment.NewLine); + builder.Append($"AssignmentGroupName: {LocalAssignmentGroupName}" + Environment.NewLine); + builder.Append($"SubmissionTypes:" + Environment.NewLine); + foreach (var submissionType in SubmissionTypes) + { + builder.Append($"- {submissionType}" + Environment.NewLine); + } + return builder.ToString(); + } + public static IEnumerable ParseRubricMarkdown(string rawMarkdown) { + if(rawMarkdown.Trim() == string.Empty) + return []; var lines = rawMarkdown.Trim().Split(Environment.NewLine); var items = lines.Select(parseIndividualRubricItemMarkdown).ToArray(); return items; diff --git a/Management/Services/Canvas/CanvasQuizService.cs b/Management/Services/Canvas/CanvasQuizService.cs index b35c9d8..418315c 100644 --- a/Management/Services/Canvas/CanvasQuizService.cs +++ b/Management/Services/Canvas/CanvasQuizService.cs @@ -83,7 +83,7 @@ public class CanvasQuizService( .Where( assignment => !assignment.IsQuizAssignment - && assignment.SubmissionTypes.Contains(SubmissionType.ONLINE_QUIZ) + && assignment.SubmissionTypes.Contains(AssignmentSubmissionType.ONLINE_QUIZ) ) .ToArray(); var tasks = assignmentsToDelete.Select(