tests allow for pages to be stored and retrieved

This commit is contained in:
2024-01-12 13:34:42 -07:00
parent 1eda91eeb0
commit a4179e6d52
13 changed files with 279 additions and 34 deletions

View File

@@ -252,4 +252,39 @@ public class FileStorageTests
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);
}
}

View 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);
}
}

View File

@@ -1,4 +1,4 @@
using Management.Web.Shared.Semester;
using Management.Web.Pages.Course.CourseCalendar;
public class MonthDetailTests
{

View File

@@ -120,13 +120,6 @@
</h2>
<div id="@accordionId" class="accordion-collapse collapse">
<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="col my-auto">
<RenameModule Module="Module" />
@@ -151,8 +144,10 @@
}
</div>
<div class="col-auto my-auto">
<NewPage Module=Module />
<NewQuiz Module="Module" />
<NewAssignment Module="Module" />
</div>
</div>
<h5>Assignments</h5>

View 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>

View File

@@ -4,8 +4,6 @@ using LocalModels;
namespace LocalModels;
public static class LocalAssignmentMarkdownCreator
{
public static string AssignmentToMarkdown(this LocalAssignment assignment)
{
var settingsMarkdown = assignment.settingsToMarkdown();

View File

@@ -29,10 +29,10 @@ public static class LocalAssignmentMarkdownParser
private static (string name, string assignmentGroupName, List<string> 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 name = MarkdownUtils.ExtractLabelValue(input, "Name");
var rawLockAt = MarkdownUtils.ExtractLabelValue(input, "LockAt");
var rawDueAt = MarkdownUtils.ExtractLabelValue(input, "DueAt");
var localAssignmentGroupName = MarkdownUtils.ExtractLabelValue(input, "AssignmentGroupName");
var submissionTypes = parseSubmissionTypes(input);
DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt)
@@ -76,18 +76,6 @@ public static class LocalAssignmentMarkdownParser
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)

View File

@@ -2,7 +2,45 @@ namespace LocalModels;
public record LocalCoursePage
{
public required string Title { get; init; }
public required string Name { get; init; }
public required string Text { get; set; }
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)
{
}
}

View 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;
}
}

View File

@@ -30,6 +30,8 @@ public class FileStorageManager
}
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);
}

View File

@@ -79,12 +79,14 @@ public class CourseMarkdownLoader
var moduleName = Path.GetFileName(modulePath);
var assignments = await loadAssignmentsFromPath(modulePath);
var quizzes = await loadQuizzesFromPath(modulePath);
var pages = await loadPagesFromPath(modulePath);
return new LocalModule()
{
Name = moduleName,
Assignments = assignments,
Quizzes = quizzes,
Pages = pages,
};
}
@@ -127,4 +129,23 @@ public class CourseMarkdownLoader
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);
}
}

View File

@@ -26,6 +26,7 @@ public class MarkdownCourseSaver(MyLogger<MarkdownCourseSaver> logger, FileConfi
await saveQuizzes(course, module, previouslyStoredCourse);
await saveAssignments(course, module, previouslyStoredCourse);
await savePages(course, module, previouslyStoredCourse);
}
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)
{
var settingsFilePath = courseDirectory + "/settings.yml"; ;
var settingsFilePath = courseDirectory + "/settings.yml";
var settingsYaml = course.Settings.ToYaml();
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);
}
}
}