mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
tests allow for pages to be stored and retrieved
This commit is contained in:
@@ -252,4 +252,39 @@ public class FileStorageTests
|
|||||||
|
|
||||||
loadedCourse.Should().BeEquivalentTo(testCourse);
|
loadedCourse.Should().BeEquivalentTo(testCourse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task MarkdownStorage_CanPersistPages()
|
||||||
|
{
|
||||||
|
LocalCourse testCourse = new() {
|
||||||
|
Settings = new () {
|
||||||
|
AssignmentGroups = [],
|
||||||
|
Name = "Test Course with page",
|
||||||
|
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday],
|
||||||
|
StartDate = new DateTime(),
|
||||||
|
EndDate = new DateTime(),
|
||||||
|
DefaultDueTime = new() { Hour = 1, Minute = 59 },
|
||||||
|
},
|
||||||
|
Modules = [
|
||||||
|
new(){
|
||||||
|
Name = "page test module",
|
||||||
|
Pages = [
|
||||||
|
new () {
|
||||||
|
Name = "test page persistence",
|
||||||
|
DueDateForOrdering = new DateTime(),
|
||||||
|
Text = "this is some\n## markdown\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await fileManager.SaveCourseAsync(testCourse, null);
|
||||||
|
|
||||||
|
var loadedCourses = await fileManager.LoadSavedMarkdownCourses();
|
||||||
|
var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name);
|
||||||
|
|
||||||
|
loadedCourse.Should().BeEquivalentTo(testCourse);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
21
Management.Test/Markdown/PageMarkdownTests.cs
Normal file
21
Management.Test/Markdown/PageMarkdownTests.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using LocalModels;
|
||||||
|
|
||||||
|
public class PageMarkdownTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestCanParsePage()
|
||||||
|
{
|
||||||
|
var page = new LocalCoursePage
|
||||||
|
{
|
||||||
|
Name = "test title",
|
||||||
|
Text = "test text content",
|
||||||
|
DueDateForOrdering = new DateTime()
|
||||||
|
};
|
||||||
|
|
||||||
|
var pageMarkdown = page.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedPage = LocalCoursePage.ParseMarkdown(pageMarkdown);
|
||||||
|
|
||||||
|
parsedPage.Should().BeEquivalentTo(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Management.Web.Shared.Semester;
|
using Management.Web.Pages.Course.CourseCalendar;
|
||||||
|
|
||||||
public class MonthDetailTests
|
public class MonthDetailTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -120,13 +120,6 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div id="@accordionId" class="accordion-collapse collapse">
|
<div id="@accordionId" class="accordion-collapse collapse">
|
||||||
<div class="accordion-body pt-1">
|
<div class="accordion-body pt-1">
|
||||||
@* <textarea
|
|
||||||
class="form-control"
|
|
||||||
@bind="notes"
|
|
||||||
@bind:event="oninput"
|
|
||||||
placeholder="notes for the module"
|
|
||||||
rows="6"
|
|
||||||
/> *@
|
|
||||||
<div class="row m-1">
|
<div class="row m-1">
|
||||||
<div class="col my-auto">
|
<div class="col my-auto">
|
||||||
<RenameModule Module="Module" />
|
<RenameModule Module="Module" />
|
||||||
@@ -151,8 +144,10 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto my-auto">
|
<div class="col-auto my-auto">
|
||||||
|
<NewPage Module=Module />
|
||||||
<NewQuiz Module="Module" />
|
<NewQuiz Module="Module" />
|
||||||
<NewAssignment Module="Module" />
|
<NewAssignment Module="Module" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h5>Assignments</h5>
|
<h5>Assignments</h5>
|
||||||
|
|||||||
76
Management.Web/Pages/Course/Module/NewPage.razor
Normal file
76
Management.Web/Pages/Course/Module/NewPage.razor
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
@using Management.Web.Shared.Components
|
||||||
|
@using Management.Web.Shared.Components.Forms
|
||||||
|
|
||||||
|
@inject CoursePlanner planner
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
[EditorRequired]
|
||||||
|
public LocalModule Module { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(50, ErrorMessage = "Name too long (50 character limit).")]
|
||||||
|
private string Name { get; set; } = "";
|
||||||
|
|
||||||
|
private Modal? modal { get; set; } = null;
|
||||||
|
|
||||||
|
private void submitHandler()
|
||||||
|
{
|
||||||
|
DiagnosticsConfig.Source?.StartActivity("Creating Page");
|
||||||
|
|
||||||
|
|
||||||
|
if(planner.LocalCourse != null)
|
||||||
|
{
|
||||||
|
var newPage = new LocalCoursePage
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Text = "",
|
||||||
|
DueDateForOrdering = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
var newModules =planner.LocalCourse.Modules.Select(m =>
|
||||||
|
m.Name != Module.Name
|
||||||
|
? m
|
||||||
|
: Module with
|
||||||
|
{
|
||||||
|
Pages=Module.Pages.Append(newPage)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
planner.LocalCourse = planner.LocalCourse with
|
||||||
|
{
|
||||||
|
Modules=newModules
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Name = "";
|
||||||
|
modal?.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
@onclick="() => modal?.Show()"
|
||||||
|
>
|
||||||
|
+ Page
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Modal @ref="modal">
|
||||||
|
<Title>New Page</Title>
|
||||||
|
<Body>
|
||||||
|
<form @onsubmit:preventDefault="true" @onsubmit="submitHandler">
|
||||||
|
<label for="Page Name">Name</label>
|
||||||
|
<input id="moduleName" class="form-control" @bind="Name" />
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
</Body>
|
||||||
|
<Footer>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@onclick="submitHandler"
|
||||||
|
>
|
||||||
|
Create Page
|
||||||
|
</button>
|
||||||
|
</Footer>
|
||||||
|
</Modal>
|
||||||
@@ -4,8 +4,6 @@ using LocalModels;
|
|||||||
namespace LocalModels;
|
namespace LocalModels;
|
||||||
public static class LocalAssignmentMarkdownCreator
|
public static class LocalAssignmentMarkdownCreator
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public static string AssignmentToMarkdown(this LocalAssignment assignment)
|
public static string AssignmentToMarkdown(this LocalAssignment assignment)
|
||||||
{
|
{
|
||||||
var settingsMarkdown = assignment.settingsToMarkdown();
|
var settingsMarkdown = assignment.settingsToMarkdown();
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ public static class LocalAssignmentMarkdownParser
|
|||||||
|
|
||||||
private static (string name, string assignmentGroupName, List<string> submissionTypes, DateTime dueAt, DateTime? lockAt) parseSettings(string input)
|
private static (string name, string assignmentGroupName, List<string> submissionTypes, DateTime dueAt, DateTime? lockAt) parseSettings(string input)
|
||||||
{
|
{
|
||||||
var name = extractLabelValue(input, "Name");
|
var name = MarkdownUtils.ExtractLabelValue(input, "Name");
|
||||||
var rawLockAt = extractLabelValue(input, "LockAt");
|
var rawLockAt = MarkdownUtils.ExtractLabelValue(input, "LockAt");
|
||||||
var rawDueAt = extractLabelValue(input, "DueAt");
|
var rawDueAt = MarkdownUtils.ExtractLabelValue(input, "DueAt");
|
||||||
var localAssignmentGroupName = extractLabelValue(input, "AssignmentGroupName");
|
var localAssignmentGroupName = MarkdownUtils.ExtractLabelValue(input, "AssignmentGroupName");
|
||||||
var submissionTypes = parseSubmissionTypes(input);
|
var submissionTypes = parseSubmissionTypes(input);
|
||||||
|
|
||||||
DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt)
|
DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt)
|
||||||
@@ -76,18 +76,6 @@ public static class LocalAssignmentMarkdownParser
|
|||||||
return submissionTypes;
|
return submissionTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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 static IEnumerable<RubricItem> ParseRubricMarkdown(string rawMarkdown)
|
public static IEnumerable<RubricItem> ParseRubricMarkdown(string rawMarkdown)
|
||||||
|
|||||||
@@ -2,7 +2,45 @@ namespace LocalModels;
|
|||||||
|
|
||||||
public record LocalCoursePage
|
public record LocalCoursePage
|
||||||
{
|
{
|
||||||
public required string Title { get; init; }
|
public required string Name { get; init; }
|
||||||
public required string Text { get; set; }
|
public required string Text { get; set; }
|
||||||
public DateTime? DueDateForOrdering { get; init; }
|
public DateTime? DueDateForOrdering { get; init; }
|
||||||
|
|
||||||
|
public string ToMarkdown()
|
||||||
|
{
|
||||||
|
var printableDueDate = DueDateForOrdering.ToString()?.Replace('\u202F', ' ');
|
||||||
|
var settingsMarkdown = $"Name: {Name}\n"
|
||||||
|
+ $"DueDateForOrdering: {printableDueDate}\n"
|
||||||
|
+ "---\n";
|
||||||
|
return settingsMarkdown + Text;
|
||||||
|
}
|
||||||
|
public static LocalCoursePage ParseMarkdown(string pageMarkdown)
|
||||||
|
{
|
||||||
|
var rawSettings = pageMarkdown.Split("---")[0];
|
||||||
|
var name = MarkdownUtils.ExtractLabelValue(rawSettings, "Name");
|
||||||
|
var rawDate = MarkdownUtils.ExtractLabelValue(rawSettings, "DueDateForOrdering");
|
||||||
|
|
||||||
|
DateTime? parsedDate = DateTime.TryParse(rawDate, out DateTime parsedDueAt)
|
||||||
|
? parsedDueAt
|
||||||
|
: null;
|
||||||
|
|
||||||
|
|
||||||
|
var text = pageMarkdown.Split("---\n")[1];
|
||||||
|
|
||||||
|
return new LocalCoursePage
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
DueDateForOrdering = parsedDate,
|
||||||
|
Text = text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class LocalPageMarkdownParseException : Exception
|
||||||
|
{
|
||||||
|
public LocalPageMarkdownParseException(string message) : base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
Management/Models/Local/MarkdownUtils.cs
Normal file
20
Management/Models/Local/MarkdownUtils.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace LocalModels;
|
||||||
|
|
||||||
|
public static class MarkdownUtils
|
||||||
|
{
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ public class FileStorageManager
|
|||||||
}
|
}
|
||||||
public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse)
|
public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse)
|
||||||
{
|
{
|
||||||
|
using var activity = DiagnosticsConfig.Source.StartActivity("Saving Course");
|
||||||
|
activity?.AddTag("CourseName", course.Settings.Name);
|
||||||
await _saveMarkdownCourse.Save(course, previouslyStoredCourse);
|
await _saveMarkdownCourse.Save(course, previouslyStoredCourse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,12 +79,14 @@ public class CourseMarkdownLoader
|
|||||||
var moduleName = Path.GetFileName(modulePath);
|
var moduleName = Path.GetFileName(modulePath);
|
||||||
var assignments = await loadAssignmentsFromPath(modulePath);
|
var assignments = await loadAssignmentsFromPath(modulePath);
|
||||||
var quizzes = await loadQuizzesFromPath(modulePath);
|
var quizzes = await loadQuizzesFromPath(modulePath);
|
||||||
|
var pages = await loadPagesFromPath(modulePath);
|
||||||
|
|
||||||
return new LocalModule()
|
return new LocalModule()
|
||||||
{
|
{
|
||||||
Name = moduleName,
|
Name = moduleName,
|
||||||
Assignments = assignments,
|
Assignments = assignments,
|
||||||
Quizzes = quizzes,
|
Quizzes = quizzes,
|
||||||
|
Pages = pages,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,4 +129,23 @@ public class CourseMarkdownLoader
|
|||||||
|
|
||||||
return await Task.WhenAll(quizPromises);
|
return await Task.WhenAll(quizPromises);
|
||||||
}
|
}
|
||||||
|
private async Task<IEnumerable<LocalCoursePage>> loadPagesFromPath(string modulePath)
|
||||||
|
{
|
||||||
|
var pagesPath = $"{modulePath}/pages";
|
||||||
|
if (!Directory.Exists(pagesPath))
|
||||||
|
{
|
||||||
|
logger.Log($"pages folder does not exist in {modulePath}, creating now");
|
||||||
|
Directory.CreateDirectory(pagesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageFiles = Directory.GetFiles(pagesPath);
|
||||||
|
var pagePromises = pageFiles
|
||||||
|
.Select(async path =>
|
||||||
|
{
|
||||||
|
var rawPage = (await File.ReadAllTextAsync(path)).Replace("\r\n", "\n");
|
||||||
|
return LocalCoursePage.ParseMarkdown(rawPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
return await Task.WhenAll(pagePromises);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class MarkdownCourseSaver(MyLogger<MarkdownCourseSaver> logger, FileConfi
|
|||||||
|
|
||||||
await saveQuizzes(course, module, previouslyStoredCourse);
|
await saveQuizzes(course, module, previouslyStoredCourse);
|
||||||
await saveAssignments(course, module, previouslyStoredCourse);
|
await saveAssignments(course, module, previouslyStoredCourse);
|
||||||
|
await savePages(course, module, previouslyStoredCourse);
|
||||||
}
|
}
|
||||||
|
|
||||||
var moduleNames = course.Modules.Select(m => m.Name);
|
var moduleNames = course.Modules.Select(m => m.Name);
|
||||||
@@ -42,7 +43,7 @@ public class MarkdownCourseSaver(MyLogger<MarkdownCourseSaver> logger, FileConfi
|
|||||||
|
|
||||||
private static async Task saveSettings(LocalCourse course, string courseDirectory)
|
private static async Task saveSettings(LocalCourse course, string courseDirectory)
|
||||||
{
|
{
|
||||||
var settingsFilePath = courseDirectory + "/settings.yml"; ;
|
var settingsFilePath = courseDirectory + "/settings.yml";
|
||||||
var settingsYaml = course.Settings.ToYaml();
|
var settingsYaml = course.Settings.ToYaml();
|
||||||
await File.WriteAllTextAsync(settingsFilePath, settingsYaml);
|
await File.WriteAllTextAsync(settingsFilePath, settingsYaml);
|
||||||
}
|
}
|
||||||
@@ -140,4 +141,54 @@ public class MarkdownCourseSaver(MyLogger<MarkdownCourseSaver> logger, FileConfi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task savePages(LocalCourse course, LocalModule module, LocalCourse? previouslyStoredCourse)
|
||||||
|
{
|
||||||
|
var pagesDirectory = $"{_basePath}/{course.Settings.Name}/{module.Name}/pages";
|
||||||
|
if (!Directory.Exists(pagesDirectory))
|
||||||
|
Directory.CreateDirectory(pagesDirectory);
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var page in module.Pages)
|
||||||
|
{
|
||||||
|
var previousModule = previouslyStoredCourse?.Modules.FirstOrDefault(m => m.Name == module.Name);
|
||||||
|
var previousPage = previousModule?.Pages.FirstOrDefault(a => a == page);
|
||||||
|
|
||||||
|
if (previousPage == null)
|
||||||
|
{
|
||||||
|
var assignmentMarkdown = page.ToMarkdown();
|
||||||
|
|
||||||
|
var filePath = pagesDirectory + "/" + page.Name + ".md";
|
||||||
|
using var activity = DiagnosticsConfig.Source.StartActivity("saving page in module");
|
||||||
|
activity?.AddTag("PageName", page.Name);
|
||||||
|
_logger.Log("saving page " + filePath);
|
||||||
|
await File.WriteAllTextAsync(filePath, assignmentMarkdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeOldPages(pagesDirectory, module);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeOldPages(string path, LocalModule module)
|
||||||
|
{
|
||||||
|
var existingFiles = Directory.EnumerateFiles(path);
|
||||||
|
|
||||||
|
var filesToDelete = existingFiles.Where((f) =>
|
||||||
|
{
|
||||||
|
foreach (var page in module.Pages)
|
||||||
|
{
|
||||||
|
var markdownPath = path + "/" + page.Name + ".md";
|
||||||
|
if (f == markdownPath)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var file in filesToDelete)
|
||||||
|
{
|
||||||
|
_logger.Log($"removing old assignment, it has probably been renamed {file}");
|
||||||
|
using var activity = DiagnosticsConfig.Source.StartActivity("removing untracked page from module");
|
||||||
|
activity?.AddTag("FileName", file);
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user