Enhance event occurrence parsing with detailed issue reporting and location configuration
This commit introduces a new structure for handling parsing issues in the EventOccurrenceParser, allowing for detailed reporting of parsing problems such as unmatched lines, missing event definitions, and parsing failures for time, date, and location. A new ParsingIssue class has been added to encapsulate these details. Additionally, a LocationParsingConfiguration class has been implemented to support customizable location patterns, enhancing the flexibility of the parser. The EventOccurrenceParserService has been updated to utilize this configuration, and new tests have been added to ensure robust issue detection and reporting. Furthermore, the UI has been updated to display parsing issues, improving user feedback during the import process.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
|
||||
<PageHeader
|
||||
Title="Import Event Occurrences"
|
||||
@@ -20,7 +21,17 @@
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
|
||||
<MudText Typo="Typo.h5" Class="mb-4">Paste Event Occurrence Data</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4">
|
||||
<MudText Typo="Typo.h5">Paste Event Occurrence Data</MudText>
|
||||
<MudButton
|
||||
Variant="Variant.Text"
|
||||
Color="Color.Secondary"
|
||||
Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Settings"
|
||||
OnClick="OpenLocationSettings">
|
||||
Location Settings
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField
|
||||
T="string"
|
||||
@@ -28,7 +39,7 @@
|
||||
@bind-Value="_inputText"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="15"
|
||||
MultiLine="true"
|
||||
multiline="true"
|
||||
Placeholder="Paste event occurrence text here..."
|
||||
HelperText="Paste the event schedule text in the format expected by the parser" />
|
||||
|
||||
@@ -86,6 +97,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
@* Detailed Parsing Issues *@
|
||||
@if (_parseResult.Issues.Any())
|
||||
{
|
||||
<MudExpansionPanels Elevation="0" Class="mt-2">
|
||||
<MudExpansionPanel Text="@($"Parsing Issues ({_parseResult.Issues.Count} found on {_parseResult.Issues.Select(i => i.LineNumber).Distinct().Count()} line(s))")"
|
||||
Icon="@Icons.Material.Filled.Warning"
|
||||
iconcolor="Color.Warning">
|
||||
<MudTable Items="@_parseResult.Issues" Dense="true" Hover="true" Striped="true" sortmode="SortMode.Multiple">
|
||||
<HeaderContent>
|
||||
<MudTh>Line</MudTh>
|
||||
<MudTh>Type</MudTh>
|
||||
<MudTh>Content</MudTh>
|
||||
<MudTh>Message</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Line">@context.LineNumber</MudTd>
|
||||
<MudTd DataLabel="Type">
|
||||
<MudChip T="string" Size="Size.Small" Color="@GetIssueTypeColor(context.IssueType)">
|
||||
@context.IssueType
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Content">
|
||||
<code style="font-size: 0.85em; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block;" title="@context.LineContent">@context.LineContent</code>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Message">@context.Message</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
|
||||
@* Summary *@
|
||||
@if (_parseResult.IsSuccess && _parseResult.TotalParsed > 0)
|
||||
{
|
||||
@@ -249,5 +291,24 @@
|
||||
return "Social Gathering";
|
||||
return eventDefinition.Name;
|
||||
}
|
||||
|
||||
private Color GetIssueTypeColor(ParsingIssueType issueType)
|
||||
{
|
||||
return issueType switch
|
||||
{
|
||||
ParsingIssueType.UnmatchedLine => Color.Info,
|
||||
ParsingIssueType.MissingEventDefinition => Color.Warning,
|
||||
ParsingIssueType.TimeParseFailure => Color.Error,
|
||||
ParsingIssueType.DateParseFailure => Color.Error,
|
||||
ParsingIssueType.LocationParseFailure => Color.Warning,
|
||||
ParsingIssueType.InvalidFormat => Color.Error,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private void OpenLocationSettings()
|
||||
{
|
||||
NavigationManager.NavigateTo("/calendar/event-occurrences/import/settings");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
@page "/calendar/event-occurrences/import/settings"
|
||||
@attribute [Authorize(Roles = AuthRoles.Administrator)]
|
||||
@using Core.Models
|
||||
@using WebApp.Authentication
|
||||
@using WebApp.Components.Shared.Components
|
||||
@using System.Text.Json
|
||||
@using MudBlazor
|
||||
@inject IWebHostEnvironment Environment
|
||||
@inject IConfiguration Configuration
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageHeader
|
||||
Title="Location Parsing Settings"
|
||||
Description="Configure location prefix patterns for parsing event occurrence locations. Patterns use '*' as a wildcard (e.g., 'Room *' matches 'Room 101', 'Room 202', etc.)."
|
||||
ShowBackButton="true"
|
||||
BackButtonUrl="/calendar/event-occurrences/import" />
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
|
||||
@if (_config != null)
|
||||
{
|
||||
<MudPaper Class="pa-6 mb-4">
|
||||
<MudText Typo="Typo.h5" Class="mb-4">Location Patterns</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-4" Color="Color.Secondary">
|
||||
Add prefix patterns to match location names. Use '*' as a wildcard to match any text after the prefix.
|
||||
Examples: "Room *" matches "Room 101", "Room 202"; "Hall *" matches "Hall A", "Main Hall".
|
||||
</MudText>
|
||||
|
||||
<MudStack Spacing="2">
|
||||
@for (int i = 0; i < _config.LocationPatterns.Count; i++)
|
||||
{
|
||||
var index = i;
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudTextField @bind-Value="_config.LocationPatterns[index]"
|
||||
Label="Pattern"
|
||||
Variant="Variant.Outlined"
|
||||
Style="flex-grow: 1;"
|
||||
Placeholder="Room *"
|
||||
HelperText="Use * as wildcard" />
|
||||
<MudButton Variant="Variant.Text"
|
||||
Color="Color.Error"
|
||||
StartIcon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(() => RemovePattern(index))"
|
||||
Disabled="_isSaving">
|
||||
Remove
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="AddPattern"
|
||||
Disabled="_isSaving"
|
||||
Class="mt-2">
|
||||
Add Pattern
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-6 mb-4">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">Pattern Examples</MudText>
|
||||
<MudSimpleTable Dense="true">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pattern</th>
|
||||
<th>Matches</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Room *</code></td>
|
||||
<td>Room 101, Room 202, Room A</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Hall *</code></td>
|
||||
<td>Hall A, Hall B, Main Hall</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Conference Room *</code></td>
|
||||
<td>Conference Room A, Conference Room 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Building *</code></td>
|
||||
<td>Building 1, Building A, Building Main</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-6">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Save"
|
||||
OnClick="SaveConfiguration"
|
||||
Disabled="_isSaving">
|
||||
@if (_isSaving)
|
||||
{
|
||||
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
|
||||
<span>Saving...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Save Configuration</span>
|
||||
}
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Text"
|
||||
Class="ml-2"
|
||||
OnClick="Cancel"
|
||||
Disabled="_isSaving">
|
||||
Cancel
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_statusMessage))
|
||||
{
|
||||
<MudAlert Severity="@_statusSeverity" Class="mt-4">@_statusMessage</MudAlert>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudProgressCircular Indeterminate="true" />
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private LocationParsingConfiguration? _config;
|
||||
private bool _isSaving;
|
||||
private string? _statusMessage;
|
||||
private Severity _statusSeverity = Severity.Success;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// Load from IConfiguration
|
||||
_config = Configuration.GetSection("LocationParsingSettings").Get<LocationParsingConfiguration>()
|
||||
?? LocationParsingConfiguration.Default;
|
||||
|
||||
// Create a copy to avoid modifying the original
|
||||
_config = new LocationParsingConfiguration
|
||||
{
|
||||
LocationPatterns = new List<string>(_config.LocationPatterns)
|
||||
};
|
||||
}
|
||||
|
||||
private string GetAppSettingsPath()
|
||||
{
|
||||
return Path.Combine(
|
||||
Environment.ContentRootPath,
|
||||
"Data",
|
||||
"appsettings.json");
|
||||
}
|
||||
|
||||
private void AddPattern()
|
||||
{
|
||||
if (_config == null) return;
|
||||
_config.LocationPatterns.Add("New Pattern *");
|
||||
}
|
||||
|
||||
private void RemovePattern(int index)
|
||||
{
|
||||
if (_config == null || index < 0 || index >= _config.LocationPatterns.Count) return;
|
||||
_config.LocationPatterns.RemoveAt(index);
|
||||
}
|
||||
|
||||
private async Task SaveConfiguration()
|
||||
{
|
||||
if (_config == null) return;
|
||||
|
||||
_isSaving = true;
|
||||
_statusMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
var appSettingsPath = GetAppSettingsPath();
|
||||
|
||||
// Ensure Data directory exists
|
||||
var dataDir = Path.GetDirectoryName(appSettingsPath);
|
||||
if (dataDir != null && !Directory.Exists(dataDir))
|
||||
{
|
||||
Directory.CreateDirectory(dataDir);
|
||||
}
|
||||
|
||||
// Read existing appsettings or create new
|
||||
Dictionary<string, object?> settings;
|
||||
|
||||
if (File.Exists(appSettingsPath))
|
||||
{
|
||||
var existingJson = await File.ReadAllTextAsync(appSettingsPath);
|
||||
settings = JsonSerializer.Deserialize<Dictionary<string, object?>>(existingJson)
|
||||
?? new Dictionary<string, object?>();
|
||||
}
|
||||
else
|
||||
{
|
||||
settings = new Dictionary<string, object?>();
|
||||
}
|
||||
|
||||
// Update LocationParsingSettings section
|
||||
settings["LocationParsingSettings"] = _config;
|
||||
|
||||
// Write back to file
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(settings, options);
|
||||
await File.WriteAllTextAsync(appSettingsPath, json);
|
||||
|
||||
_statusMessage = "Configuration saved successfully! Changes will take effect on next parse operation.";
|
||||
_statusSeverity = Severity.Success;
|
||||
Snackbar.Add("Location parsing settings saved successfully", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_statusMessage = $"Error saving configuration: {ex.Message}";
|
||||
_statusSeverity = Severity.Error;
|
||||
Snackbar.Add($"Error saving settings: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
NavigationManager.NavigateTo("/calendar/event-occurrences/import");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user