adding wasm client project, github actions, radzen
Some checks are pending
ci / build (8.0.x) (push) Waiting to run
ci / build-Docker (push) Blocked by required conditions

This commit is contained in:
Andy Pack 2024-06-09 18:43:32 +01:00
parent ff1a391599
commit 4c5fa9e0e6
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
21 changed files with 225 additions and 104 deletions

52
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: ci
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
dotnet-version: [ '8.0.x' ]
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3.0.3
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Install Dependencies
run: dotnet restore Overflow.sln
- name: Build
run: dotnet build --configuration Debug --no-restore Overflow.sln
- name: Test
run: dotnet test --no-restore --verbosity normal Overflow.sln
build-Docker:
runs-on: ubuntu-latest
needs: [build, build-Js] # for ignoring bad builds
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build Web Container
uses: docker/build-push-action@v5
with:
push: true
tags: |
sarsoo/overflow:latest
sarsoo/overflow:${{ github.ref_name }}
file: Dockerfile

View File

@ -7,9 +7,9 @@ using Overflow.SouthernWater;
var driver = new MongoClient("mongodb://localhost"); var driver = new MongoClient("mongodb://localhost");
var api = new SouthernWater(new HttpClient()); var api = new SouthernWaterApi(new HttpClient());
await api.LoadApiUrl(); await api.LoadApiUrl();
var runner = new SouthernWaterApiJobRunnerPersisting(api, NullLogger<SouthernWaterApiJobRunner>.Instance, driver.GetDatabase("overflow")); var runner = new SouthernWaterApiJobRunnerPersisting(api, NullLogger<SouthernWaterApiJobRunner>.Instance, driver.GetDatabase("overflow"));
await runner.LoadSpills(); await runner.LoadSpills(5);

View File

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Npgsql" Version="8.0.3" />
</ItemGroup>
</Project>

View File

@ -10,7 +10,7 @@ public class Tests
[Test] [Test]
public async Task Test1() public async Task Test1()
{ {
var southern = new SouthernWater.SouthernWater(new HttpClient()); var southern = new SouthernWater.SouthernWaterApi(new HttpClient());
await southern.LoadApiUrl(); await southern.LoadApiUrl();
var spills = await southern.GetSpills(); var spills = await southern.GetSpills();
} }

View File

@ -0,0 +1,43 @@
@using Overflow.SouthernWater
@rendermode RenderMode.InteractiveAuto
@if (Job == null)
{
<p>
<em>Loading...</em>
</p>
}
else
{
<RadzenDataGrid Data="@Job.Spills" TItem="Spill"
AllowFiltering="true"
AllowColumnResize="true"
AllowSorting="true"
PageSize="25"
AllowPaging="true"
ShowPagingSummary="true"
>
<Columns>
@if (ShowIds)
{
<RadzenDataGridColumn TItem="Spill" Property="sw_id" Title="Id"/>
<RadzenDataGridColumn TItem="Spill" Property="eventId" Title="Event Id"/>
<RadzenDataGridColumn TItem="Spill" Property="siteUnitNumber" Title="Site Unit Number"/>
<RadzenDataGridColumn TItem="Spill" Property="associatedSiteId" Title="Associated Site Id"/>
<RadzenDataGridColumn TItem="Spill" Property="overFlowSiteId" Title="OverFlow Site Id"/>
}
<RadzenDataGridColumn TItem="Spill" Property="bathingSite" Title="Bathing Site"/>
<RadzenDataGridColumn TItem="Spill" Property="outfallName" Title="Outfall"/>
<RadzenDataGridColumn TItem="Spill" Property="eventStart" Title="Event Start"/>
<RadzenDataGridColumn TItem="Spill" Property="eventStop" Title="Event End"/>
<RadzenDataGridColumn TItem="Spill" Property="duration" Title="Duration"/>
<RadzenDataGridColumn TItem="Spill" Property="status" Title="Status"/>
<RadzenDataGridColumn TItem="Spill" Property="isImpacting" Title="Is Impacting"/>
</Columns>
</RadzenDataGrid>
}
@code {
[Parameter] public SouthernWaterApiJob? Job { get; set; }
[Parameter] public bool ShowIds { get; set; }
}

View File

@ -5,6 +5,7 @@
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/> <base href="/"/>
<link rel="stylesheet" href="_content/Radzen.Blazor/css/material-base.css">
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/> <link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" href="app.css"/> <link rel="stylesheet" href="app.css"/>
<link rel="stylesheet" href="Overflow.Web.styles.css"/> <link rel="stylesheet" href="Overflow.Web.styles.css"/>
@ -15,6 +16,7 @@
<body> <body>
<Routes/> <Routes/>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
<div class="top-row ps-3 navbar navbar-dark"> <div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="">Overflow.Web</a> <a class="navbar-brand" href="">Overflow</a>
</div> </div>
</div> </div>
@ -21,8 +21,8 @@
</div> </div>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="weather"> <NavLink class="nav-link" href="spills">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Spills
</NavLink> </NavLink>
</div> </div>
</nav> </nav>

View File

@ -1,7 +1,5 @@
@page "/" @page "/"
<PageTitle>Home</PageTitle> <PageTitle>Overflow</PageTitle>
<h1>Hello, world!</h1> <h1>Overflow</h1>
Welcome to your new app.

View File

@ -0,0 +1,33 @@
@page "/spills"
@using MongoDB.Driver
@using Overflow.SouthernWater
@rendermode RenderMode.InteractiveServer
<PageTitle>Southern Water Spills</PageTitle>
<h1>Spills</h1>
<p>This component demonstrates showing data.</p>
<RadzenCard class="my-4">
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Start" Wrap="FlexWrap.Wrap">
<RadzenCheckBox @bind-Value=@showIds Name="ShowIds" />
<RadzenLabel Text="Show IDs" Component="ShowIds" Style="margin-left: 8px; vertical-align: middle;" />
</RadzenStack>
</RadzenCard>
<SpillsTable Job=@job ShowIds=@showIds />
@code {
private SouthernWaterApiJob? job;
[Inject] private IMongoDatabase database { get; set; }
private bool showIds;
protected override async Task OnInitializedAsync()
{
job = database.GetCollection<SouthernWaterApiJob>(Static.CollectionName)
.AsQueryable()
.OrderByDescending(j => j.EndTime)
.FirstOrDefault();
}
}

View File

@ -1,67 +0,0 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p>
<em>Loading...</em>
</p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View File

@ -7,4 +7,8 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using Overflow.Web @using Overflow.Web
@using Overflow.Web.Components @using Overflow.Web.Components
@using Overflow.Web.Client.Pages
@using Overflow.Web.Client.Components
@using Radzen
@using Radzen.Blazor

View File

@ -7,11 +7,25 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.6" />
<PackageReference Include="MongoDB.Driver" Version="2.25.0" /> <PackageReference Include="MongoDB.Driver" Version="2.25.0" />
<PackageReference Include="Quartz" Version="3.9.0" />
<PackageReference Include="Quartz.AspNetCore" Version="3.9.0" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.9.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.9.0" />
<PackageReference Include="Radzen.Blazor" Version="4.32.6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Overflow.Web.Client\Overflow.Web.Client.csproj" />
<ProjectReference Include="..\Overflow\Overflow.csproj" /> <ProjectReference Include="..\Overflow\Overflow.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\app.css" />
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\favicon.png" />
</ItemGroup>
</Project> </Project>

View File

@ -1,13 +1,18 @@
using Overflow.Web.Components; using Overflow.Web.Components;
using Overflow; using Overflow;
using MongoDB.Driver; using MongoDB.Driver;
using Overflow.SouthernWater;
using Quartz;
using Quartz.AspNetCore;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
builder.Services.AddRazorComponents() builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(); .AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
var driver = new MongoClient(builder.Configuration.GetConnectionString("Default")); var driver = new MongoClient(builder.Configuration.GetConnectionString("Default"));
builder.Services.AddSingleton(driver); builder.Services.AddSingleton(driver);
@ -15,6 +20,32 @@ builder.Services.AddScoped<IMongoDatabase>(s => s.GetRequiredService<MongoClient
builder.Services.AddControllers(); builder.Services.AddControllers();
// base configuration from appsettings.json
builder.Services.Configure<QuartzOptions>(builder.Configuration.GetSection("Quartz"));
// if you are using persistent job store, you might want to alter some options
builder.Services.Configure<QuartzOptions>(options =>
{
options.Scheduling.IgnoreDuplicates = true; // default: false
options.Scheduling.OverWriteExistingData = true; // default: true
});
builder.Services.AddQuartz(q =>
{
// base Quartz scheduler, job and trigger configuration
});
// ASP.NET Core hosting
builder.Services.AddQuartzServer(options =>
{
// when shutting down we want jobs to complete gracefully
options.WaitForJobsToComplete = false;
});
builder.Services.AddHttpClient();
builder.Services.AddSingleton<SouthernWaterApi>();
builder.Services.AddScoped<SouthernWaterApiJobRunner, SouthernWaterApiJobRunnerPersisting>();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
@ -32,6 +63,7 @@ app.UseAntiforgery();
app.MapControllers(); app.MapControllers();
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(); .AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode();
app.Run(); app.Run();

View File

@ -5,5 +5,8 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"ConnectionStrings": {
"Default": "mongodb://localhost"
},
"AllowedHosts": "*" "AllowedHosts": "*"
} }

View File

@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Overflow.Web", "Overflow.We
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Overflow.CLI", "Overflow.CLI\Overflow.CLI.csproj", "{393EF268-E745-4550-9607-DBE6B4C456DA}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Overflow.CLI", "Overflow.CLI\Overflow.CLI.csproj", "{393EF268-E745-4550-9607-DBE6B4C456DA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Overflow.Web.Client", "Overflow.Web.Client\Overflow.Web.Client.csproj", "{FDA03F0A-260B-4110-B5B6-19CCFFBB1618}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -30,5 +32,9 @@ Global
{393EF268-E745-4550-9607-DBE6B4C456DA}.Debug|Any CPU.Build.0 = Debug|Any CPU {393EF268-E745-4550-9607-DBE6B4C456DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{393EF268-E745-4550-9607-DBE6B4C456DA}.Release|Any CPU.ActiveCfg = Release|Any CPU {393EF268-E745-4550-9607-DBE6B4C456DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{393EF268-E745-4550-9607-DBE6B4C456DA}.Release|Any CPU.Build.0 = Release|Any CPU {393EF268-E745-4550-9607-DBE6B4C456DA}.Release|Any CPU.Build.0 = Release|Any CPU
{FDA03F0A-260B-4110-B5B6-19CCFFBB1618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDA03F0A-260B-4110-B5B6-19CCFFBB1618}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDA03F0A-260B-4110-B5B6-19CCFFBB1618}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDA03F0A-260B-4110-B5B6-19CCFFBB1618}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -4,7 +4,7 @@ using System.Text.RegularExpressions;
namespace Overflow.SouthernWater; namespace Overflow.SouthernWater;
public partial class SouthernWater public partial class SouthernWaterApi
{ {
private readonly HttpClient _client; private readonly HttpClient _client;
@ -13,7 +13,7 @@ public partial class SouthernWater
private string baseUrl; private string baseUrl;
private string apiKey; private string apiKey;
public SouthernWater(HttpClient client) public SouthernWaterApi(HttpClient client)
{ {
_client = client; _client = client;
} }

View File

@ -1,9 +1,11 @@
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Overflow.SouthernWater; namespace Overflow.SouthernWater;
public class SouthernWaterApiJob public class SouthernWaterApiJob
{ {
[BsonId]
public ObjectId _id { get; set; } public ObjectId _id { get; set; }
public DateTime StartTime { get; set; } public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; } public DateTime? EndTime { get; set; }

View File

@ -4,11 +4,11 @@ using MongoDB.Driver;
namespace Overflow.SouthernWater; namespace Overflow.SouthernWater;
public class SouthernWaterApiJobRunner(SouthernWater client, ILogger<SouthernWaterApiJobRunner> logger) public class SouthernWaterApiJobRunner(SouthernWaterApi client, ILogger<SouthernWaterApiJobRunner> logger)
{ {
protected readonly ILogger<SouthernWaterApiJobRunner> _logger = logger; protected readonly ILogger<SouthernWaterApiJobRunner> _logger = logger;
public async Task<SouthernWaterApiJob> LoadSpills() public async Task<SouthernWaterApiJob> LoadSpills(int? pageLimit = null)
{ {
var interval = TimeSpan.FromSeconds(30); var interval = TimeSpan.FromSeconds(30);
var job = new SouthernWaterApiJob var job = new SouthernWaterApiJob
@ -28,7 +28,7 @@ public class SouthernWaterApiJobRunner(SouthernWater client, ILogger<SouthernWat
await JobCreated(job); await JobCreated(job);
var spills = client.GetAllSpills(interval); var spills = client.GetAllSpills(interval, pageLimit: pageLimit);
await foreach (var page in spills) await foreach (var page in spills)
{ {
@ -81,12 +81,12 @@ public class SouthernWaterApiJobRunner(SouthernWater client, ILogger<SouthernWat
} }
public class SouthernWaterApiJobRunnerPersisting( public class SouthernWaterApiJobRunnerPersisting(
SouthernWater client, SouthernWaterApi client,
ILogger<SouthernWaterApiJobRunner> logger, ILogger<SouthernWaterApiJobRunner> logger,
IMongoDatabase mongo) IMongoDatabase mongo)
: SouthernWaterApiJobRunner(client, logger) : SouthernWaterApiJobRunner(client, logger)
{ {
private readonly IMongoCollection<SouthernWaterApiJob> _collection = mongo.GetCollection<SouthernWaterApiJob>("southern_water_api_job"); private readonly IMongoCollection<SouthernWaterApiJob> _collection = mongo.GetCollection<SouthernWaterApiJob>(Static.CollectionName);
protected override async Task JobCreated(SouthernWaterApiJob job) protected override async Task JobCreated(SouthernWaterApiJob job)
{ {

View File

@ -6,9 +6,6 @@ namespace Overflow.SouthernWater;
public class Spill public class Spill
{ {
[JsonIgnore]
public ObjectId _id { get; set; }
[JsonPropertyName("id")] [JsonPropertyName("id")]
public int sw_id { get; set; } public int sw_id { get; set; }
public int eventId { get; set; } public int eventId { get; set; }

7
Overflow/Static.cs Normal file
View File

@ -0,0 +1,7 @@
namespace Overflow;
public static class Static
{
public static readonly string DatabaseName = "overflow";
public static readonly string CollectionName = "southern_water_api_job";
}

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# Overflow
Tracking and analysing the storm overflow usage of Southern Water.
## Tech Stack
- .NET ASP.NET Core
- MongoDB
- Blazor Web