Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Admin] Component: Update event details - UI component (view) #335

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@page "/admin/events/edit/{EventId:guid}"

@using AzureOpenAIProxy.PlaygroundApp.Models;

<PageTitle>Update Event</PageTitle>

<h1>Update Event</h1>

<UpdateEventDetailsComponent Id="admin-update-event" EventId = "@EventId" @rendermode="InteractiveServer" />

@code {
[Parameter]
public Guid EventId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@using AzureOpenAIProxy.PlaygroundApp.Models

@inject NavigationManager NavigationManager

<FluentLayout Id="@Id">
@if (eventDetails == null)
{
Expand All @@ -21,7 +23,7 @@
<AdminEventIsActiveComponent IsActive="@(((AdminEventDetails)@context).IsActive)" />
</TemplateColumn>
<TemplateColumn Class="fluent-datagrid-cell" Title="Actions" Align="@Align.Center">
<FluentButton aria-label="Edit item" IconEnd="@(new Icons.Regular.Size16.Edit())" />
<FluentButton aria-label="Edit item" IconEnd="@(new Icons.Regular.Size16.Edit())" @onclick="@(() => NavigateToEdit(((AdminEventDetails)@context).EventId))" />
<FluentButton aria-label="Delete item" IconEnd="@(new Icons.Regular.Size16.Delete())" />
</TemplateColumn>
</FluentDataGrid>
Expand Down Expand Up @@ -89,4 +91,9 @@

private string? AriaCurrentValue(int pageIndex)
=> pagination.CurrentPageIndex == pageIndex ? "page" : null;

private void NavigateToEdit(Guid eventId)
{
NavigationManager.NavigateTo($"/admin/events/edit/{eventId}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
@using AzureOpenAIProxy.PlaygroundApp.Models
@using NodaTime
@using NodaTime.Extensions
@using System.Globalization

@inject NavigationManager NavigationManager

<FluentLayout Id="@Id">
@if(adminEventDetails == null)
{
<p><em>Loading...</em></p>
}
else
{
<FluentHeader>Update Event</FluentHeader>
<FluentBodyContent>
<section>
<h2>Event Infomation</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-title" Class="update-input-label" >Title</FluentLabel>
<FluentTextField Id="event-title" Name="title" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.Title" Required/>
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-summary" Class="update-input-label">Summary</FluentLabel>
<FluentTextField id="event-summary" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.Summary" Required/>
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-description" Class="update-input-label">Description</FluentLabel>
<FluentTextArea Id="event-description" Style="width:300px" @bind-Value="adminEventDetails.Description"/>
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-start-date" Class="update-input-label">Event Start Date</FluentLabel>
<FluentDatePicker Id="event-start-date" Value="@adminEventDetails.DateStart.DateTime" ValueChanged="@(e => adminEventDetails.DateStart = e.Value)" Culture="System.Globalization.CultureInfo.CurrentCulture" />
<FluentTimePicker Id="event-start-time" Value="@adminEventDetails.DateStart.DateTime" ValueChanged="@(e => adminEventDetails.DateStart = e.Value)" />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-end-date" Class="update-input-label">Event End Date</FluentLabel>
<FluentDatePicker Id="event-end-date" Value="@adminEventDetails.DateEnd.DateTime" ValueChanged="@(e => adminEventDetails.DateEnd = e.Value)" Culture="System.Globalization.CultureInfo.CurrentCulture" />
<FluentTimePicker Id="event-end-time" Value="@adminEventDetails.DateEnd.DateTime" ValueChanged="@(e => adminEventDetails.DateEnd = e.Value)" />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-timezone" Class="update-input-label">Time Zone</FluentLabel>
<FluentSelect Id="event-timezone" @bind-Value="@adminEventDetails.TimeZone" Height="500px" TOption="string" Required>
@foreach (var timeZone in timeZoneList)
{
<FluentOption Value="@timeZone.Id">@timeZone.Id</FluentOption>
}
</FluentSelect>
</FluentStack>
</section>

<section>
<h2>Event Organizer</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-organizer-name" Class="update-input-label">Organizer Name</FluentLabel>
<FluentTextField Id="event-organizer-name" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.OrganizerName" Required/>
</FluentStack>


<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-organizer-email" Class="update-input-label">Organizer Email</FluentLabel>
<FluentTextField Id="event-organizer-email" TextFieldType="TextFieldType.Email" @bind-Value="adminEventDetails.OrganizerEmail" Required/>
</FluentStack>
</section>

<section>
<h2>Event Coorganizers</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-coorgnizer-name" Class="update-input-label">Coorgnizer Name</FluentLabel>
<FluentTextField Id="event-coorgnizer-name" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.CoorganizerName" Required />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-coorgnizer-email" Class="update-input-label">Coorgnizer Email</FluentLabel>
<FluentTextField Id="event-coorgnizer-email" TextFieldType="TextFieldType.Email" @bind-Value="adminEventDetails.CoorganizerEmail" Required/>
</FluentStack>
</section>

<section>
<h2>Event Configuration</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-max-token-cap" Class="update-input-label">Max Token Cap</FluentLabel>
<FluentNumberField Id="event-max-token-cap" @bind-Value="adminEventDetails.MaxTokenCap" Required />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-daily-request-cap" Class="update-input-label">Daily Request Cap</FluentLabel>
<FluentNumberField Id="event-daily-request-cap" @bind-Value="adminEventDetails.DailyRequestCap" Required/>
</FluentStack>
</section>

<section class="button-section">
<FluentButton Id="admin-event-detail-update" Appearance="Appearance.Accent" Class="button" @onclick="UpdateEvent">Update Event</FluentButton>
<FluentButton Id="admin-event-detail-cancel" Appearance="Appearance.Outline" Class="button" @onclick="CancelUpdate">Cancel</FluentButton>
</section>
</FluentBodyContent>
}
</FluentLayout>

@code {
[Parameter]
public string? Id { get; set; }

[Parameter]
public Guid EventId { get; set;}

private List<DateTimeZone>? timeZoneList = [];
private AdminEventDetails? adminEventDetails;

protected override void OnInitialized()
{
timeZoneList = DateTimeZoneProviders.Tzdb.GetAllZones().ToList();

CultureInfo customCulture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
customCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd";
customCulture.DateTimeFormat.ShortTimePattern = "HH:mm";

CultureInfo.DefaultThreadCurrentCulture = customCulture;
CultureInfo.DefaultThreadCurrentUICulture = customCulture;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
// TODO: GET AdminEventDetails through the /admin/events/{eventId} API.
await Task.Delay(1000);
var timezoneId = GetIanaTimezoneId();
var currentTime = GetCurrentDateTimeOffset(timezoneId);

// Make dummy data
adminEventDetails = new AdminEventDetails
{
EventId = EventId,
Title = "dummy title",
Summary = "dummy summary",
Description = "dummy description",
DateStart = currentTime.AddHours(1).AddMinutes(-currentTime.Minute),
DateEnd = currentTime.AddDays(1).AddHours(1).AddMinutes(-currentTime.Minute),
TimeZone = timezoneId,
IsActive = true,
OrganizerName = $"dummy organizer",
OrganizerEmail = $"[email protected]",
CoorganizerName = $"dummy coorganizer",
CoorganizerEmail = $"[email protected]",
MaxTokenCap = 10000,
DailyRequestCap = 1000
};

await InvokeAsync(StateHasChanged);
}
}

private async Task UpdateEvent()
{
// TODO: PUT AdminEventDetails through the /admin/events/{eventId} API.
await Task.CompletedTask;
}

private void CancelUpdate()
{
NavigationManager.NavigateTo("/admin/events", forceLoad: false);
}

private string GetIanaTimezoneId()
{
string timezoneId = TimeZoneInfo.Local.Id;

if (OperatingSystem.IsWindows())
{
if (TimeZoneInfo.TryConvertWindowsIdToIanaId(timezoneId, out var ianaTimezoneId))
{
timezoneId = ianaTimezoneId;
}
}

return timezoneId;
}

private DateTimeOffset GetCurrentDateTimeOffset(string timezoneId)
{
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timezoneId);

return TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, timeZoneInfo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
section {
margin-bottom: 100px
}

::deep .update-input-label {
width: 200px;
--type-ramp-base-font-size: 22px;
}

::deep .update-fluent-stack {
height: 100px;
}

.button-section {
display: flex;
justify-content: center;
gap: 50px;
}

.button {
width: 150px;
height: 50px;
font-size: 16px;
margin: 0 10px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Net;

using AzureOpenAIProxy.AppHost.Tests.Fixtures;

using FluentAssertions;

namespace AzureOpenAIProxy.AppHost.Tests.PlaygroundApp.Pages;

public class AdminUpdateEventPageTests(AspireAppHostFixture host) : IClassFixture<AspireAppHostFixture>
{
[Fact]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_OK()
{
// Arrange
using var httpClient = host.App!.CreateHttpClient("playgroundapp");
await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));

var eventId = Guid.NewGuid();
var expectedUrl = $"/admin/events/edit/{eventId}";

// Act
var response = await httpClient.GetAsync(expectedUrl);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Theory]
[InlineData("_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_CSS_Elements(string expected)
{
// Arrange
using var httpClient = host.App!.CreateHttpClient("playgroundapp");
await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));

var eventId = Guid.NewGuid();
var expectedUrl = $"/admin/events/edit/{eventId}";

// Act
var html = await httpClient.GetStringAsync(expectedUrl);

// Assert
html.Should().Contain(expected);
}

[Theory]
[InlineData("_content/Microsoft.FluentUI.AspNetCore.Components/Microsoft.FluentUI.AspNetCore.Components.lib.module.js")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_JavaScript_Elements(string expected)
{
// Arrange
using var httpClient = host.App!.CreateHttpClient("playgroundapp");
await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));

var eventId = Guid.NewGuid();
var expectedUrl = $"/admin/events/edit/{eventId}";

// Act
var html = await httpClient.GetStringAsync(expectedUrl);

// Assert
html.Should().Contain(expected);
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
using var httpClient = host.App!.CreateHttpClient("playgroundapp");
await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));

var eventId = Guid.NewGuid();
var expectedUrl = $"/admin/events/edit/{eventId}";

// Act
var html = await httpClient.GetStringAsync(expectedUrl);

// Assert
html.Should().Contain(expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Loading
Loading