Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions APSIM.POStats.Collector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# APSIM.POStats.Collector

Console app that uploads validation data for pull request reports.

Prerequisites
- .NET SDK 8.0+

Build
- `dotnet build APSIM.PerformanceTests.sln`

Run (direct)
- `dotnet run --project APSIM.POStats.Collector/APSIM.POStats.Collector.csproj -- <pullRequestNumber> <commitId> <author> <validationPath>`

Run (VS Code)
- Use the `Collector` launch configuration (or the compound `Portal + Collector`).

Notes
- The collector is intended to be run regularly (CI or scheduled) or launched from the debugger to insert PR validation data into the portal database.
- Shared utilities and DB context are in the `APSIM.POStats.Shared` project.
45 changes: 38 additions & 7 deletions APSIM.POStats.Portal/Pages/Index.cshtml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@page "{pullRequestNumber:int=7867}"
@page "{pullRequestNumber:int?}"
@model IndexModel
@{
ViewData["Title"] = "Home";
ViewData["ID"] = @Model.PullRequest.Id;
ViewData["ID"] = @Model.PullRequest?.Id;
}
@using APSIM.POStats.Shared;
@using APSIM.POStats.Shared.Comparison;
Expand All @@ -16,6 +16,36 @@

<div class="text-left">
<h1 class="display-4">Predicted / Observed Stats</h1>
@if (Model.PullRequest == null)
{
<p>This site publishes validation and performance statistics for APSIM pull requests.</p>
<p>It is connected to the ApsimX GitHub repository and maintained by the APSIM Initiative to help contributors quickly review statistical changes in pull request results.</p>
<p>@Model.HomeMessage</p>

<h3>Recent Pull Requests</h3>
@if (Model.RecentPullRequests.Any())
{
<ul>
@foreach (var pullRequest in Model.RecentPullRequests)
{
<li>
<a asp-page="/Index" asp-route-pullRequestNumber="@pullRequest.PullRequest">
PR #@pullRequest.PullRequest
</a>
<span> - @pullRequest.DateRun.ToString("yyyy-MM-dd HH:mm")</span>
<span> - by @pullRequest.Author</span>
<span> - commit @((pullRequest.Commit?.Length ?? 0) > 7 ? pullRequest.Commit.Substring(0, 7) : pullRequest.Commit)</span>
</li>
}
</ul>
}
else
{
<p>No pull requests were found in the stats database yet.</p>
}
}
else
{
<div>
<br />

Expand All @@ -33,19 +63,19 @@
<td class="ratings-table-column">***</td>
<td class="ratings-table-column">Very Good</td>
<td class="ratings-table-column">0.00 ≤ RSR ≤ 0.50</td>
<td class="ratings-table-column">0.75 < NSE ≤ 1.00</td>
<td class="ratings-table-column">0.75 &lt; NSE ≤ 1.00</td>
</tr>
<tr class="ratings-table-row">
<td class="ratings-table-column">**</td>
<td class="ratings-table-column">Good</td>
<td class="ratings-table-column">0.50 < RSR ≤ 0.60</td>
<td class="ratings-table-column">0.65 < NSE ≤ 0.75</td>
<td class="ratings-table-column">0.50 &lt; RSR ≤ 0.60</td>
<td class="ratings-table-column">0.65 &lt; NSE ≤ 0.75</td>
</tr>
<tr class="ratings-table-row">
<td class="ratings-table-column">*</td>
<td class="ratings-table-column">Satisfactory</td>
<td class="ratings-table-column">0.60 < RSR ≤ 0.70</td>
<td class="ratings-table-column">0.50 < NSE ≤ 0.65</td>
<td class="ratings-table-column">0.60 &lt; RSR ≤ 0.70</td>
<td class="ratings-table-column">0.50 &lt; NSE ≤ 0.65</td>
</tr>
<tr class="ratings-table-bottomrow">
<td class="ratings-table-column">&nbsp;</td>
Expand Down Expand Up @@ -234,4 +264,5 @@
}
</div>
</div>
}
</div>
26 changes: 23 additions & 3 deletions APSIM.POStats.Portal/Pages/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,38 @@ public IndexModel(StatsDbContext stats)
/// <summary>The pull request being analysed.</summary>
public PullRequestDetails PullRequest { get; private set; }

/// <summary>Message shown on the home page when no pull request is selected.</summary>
public string HomeMessage { get; private set; }

/// <summary>Recent pull requests sorted by date (most recent first).</summary>
public List<PullRequestDetails> RecentPullRequests { get; private set; } = new List<PullRequestDetails>();

/// <summary>The Url for the web site.</summary>
public string BaseUrl { get { return $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}"; } }

public string Filter { get; set; } = "";

/// <summary>Invoked when page is first loaded.</summary>
/// <param name="pullRequestNumber">The pull request to work with.</param>
public void OnGet(int pullRequestNumber)
public void OnGet(int? pullRequestNumber)
{
PullRequest = statsDb.PullRequests.FirstOrDefault(pr => pr.PullRequest == pullRequestNumber);
RecentPullRequests = statsDb.PullRequests
.OrderByDescending(pr => pr.DateRun)
.Take(20)
.ToList();

if (!pullRequestNumber.HasValue)
{
HomeMessage = "Enter a pull request number in the URL to view validation stats.";
return;
}

PullRequest = statsDb.PullRequests.FirstOrDefault(pr => pr.PullRequest == pullRequestNumber.Value);
if (PullRequest == null)
throw new Exception($"Cannot find pull request #{pullRequestNumber} in stats database");
{
HomeMessage = $"Cannot find pull request #{pullRequestNumber.Value} in stats database.";
return;
}

VariableComparison.Status status = PullRequestFunctions.GetStatus(PullRequest);
}
Expand Down
27 changes: 27 additions & 0 deletions APSIM.POStats.Portal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# APSIM.POStats.Portal

ASP.NET Core web portal for viewing predicted vs observed statistics for pull requests.

Prerequisites
- .NET SDK 8.0+
- Environment variable `PORTAL_DB` (see notes below)

Database
- For local development set `PORTAL_DB=portal.db` to use SQLite (create `portal.db` at the repo root).
- To use MySQL provide a full connection string in `PORTAL_DB` (e.g. `Server=...;Database=...;User=...;Password=...;`).

Build & Run (local)
- `dotnet build APSIM.PerformanceTests.sln`
- `dotnet run --project APSIM.POStats.Portal/APSIM.POStats.Portal.csproj`

Development
- Default dev URLs: `https://localhost:5001` and `http://localhost:5000`.
- Open `/` to see the landing page; open `/{pullRequestNumber}` to view a PR report.
- Use the `Portal` launch configuration or the compound `Portal + Collector` for debugging both services.

Docker
- Build and run via `./build.sh` and `./deploy.sh`. Local service runs at `http://localhost:8081/`.

Troubleshooting
- Error: Cannot find environment variable `PORTAL_DB` — ensure it is set in your environment or run profile.
- HTTPS cert warnings: `dotnet dev-certs https --trust`.
23 changes: 22 additions & 1 deletion APSIM.POStats.Portal/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,28 @@ public void ConfigureServices(IServiceCollection services)
if (connectionString.Contains(".db"))
{
Console.WriteLine("Using SQLite database");
services.AddDbContext<StatsDbContext>(options => options.UseLazyLoadingProxies().UseSqlite(connectionString));

// If the connection string is just a file path (e.g. "portal.db"),
// convert it to a proper Data Source connection string and ensure
// the directory and file exist so the provider can open it.
string sqliteConnectionString = connectionString;
if (!connectionString.Contains("="))
{
var dbPath = System.IO.Path.IsPathRooted(connectionString)
? connectionString
: System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), connectionString);

var dbDir = System.IO.Path.GetDirectoryName(dbPath);
if (!string.IsNullOrEmpty(dbDir) && !System.IO.Directory.Exists(dbDir))
System.IO.Directory.CreateDirectory(dbDir);

if (!System.IO.File.Exists(dbPath))
System.IO.File.Create(dbPath).Dispose();

sqliteConnectionString = $"Data Source={dbPath}";
}

services.AddDbContext<StatsDbContext>(options => options.UseLazyLoadingProxies().UseSqlite(sqliteConnectionString));
}
else
{
Expand Down
18 changes: 18 additions & 0 deletions APSIM.POStats.Shared/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# APSIM.POStats.Shared

Shared models, utilities, and database helpers used by the Portal and Collector projects.

Contents (high level)
- `Collector.cs`, `PullRequestTimer.cs` — collector orchestration helpers
- `StatsDbContext.cs`, `SqliteUtilities.cs` — database helpers and EF context
- `GitHub/` — GitHub API helpers and PR models
- `Comparison/` — comparison utilities used for validation reports

Build
- This project is built as part of the solution: `dotnet build APSIM.PerformanceTests.sln`.

Usage
- Refer to this project from `APSIM.POStats.Collector` and `APSIM.POStats.Portal` for models and utilities.

Notes
- Keep shared, framework-agnostic code here. Avoid adding UI or app-host-specific behaviour.
116 changes: 115 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,118 @@ Service runs on http://localhost:8081/

## To build, run and test docker image on the web server

Refer to apsim-web repo which has a server-wide deployment script that is used.
Refer to apsim-web repo which has a server-wide deployment script that is used.


## Local development

This repository contains:

- [APSIM.POStats.Portal/README.md](APSIM.POStats.Portal/README.md): ASP.NET Core web portal for viewing predicted/observed stats.
- [APSIM.POStats.Collector/README.md](APSIM.POStats.Collector/README.md): console app that uploads validation data.
- [APSIM.POStats.Shared/README.md](APSIM.POStats.Shared/README.md): shared models and utilities.
- APSIM.POStats.Tests: automated tests.

### Prerequisites

- .NET SDK 8.0+
- Git
- Optional: Docker Desktop (for container workflow)
- Optional: VS Code with C# Dev Kit

### Quick start (local, no Docker)

1. Clone and enter the repository.
2. Create or confirm a local sqlite database file at the repo root named portal.db.
3. Set environment variables (required).
4. Build and run the portal.

#### 1) Create local environment file

Create or update .env in the repo root with at least:

PORTAL_DB=portal.db

How it works:

- If PORTAL_DB contains .db, the app uses sqlite.
- Otherwise, the app treats PORTAL_DB as a MySQL connection string.

#### 2) Build the solution

dotnet build APSIM.PerformanceTests.sln

#### 3) Run the portal

dotnet run --project APSIM.POStats.Portal/APSIM.POStats.Portal.csproj

By default, development profile URLs are:

- https://localhost:5001
- http://localhost:5000

Home page behavior:

- Navigate to / to see the landing page and recent pull requests.
- Navigate to /{pullRequestNumber} to open a specific PR report.

### Run with VS Code (recommended)

The repo includes launch and task configuration:

- Launch config: Portal
- Launch config: Collector
- Compound launch: Portal + Collector
- Pre-launch task: Build Solution

From Run and Debug, start Portal + Collector to launch both apps together.

### Running the collector locally

Collector expects command-line arguments when run directly. Example shape:

pullRequestNumber commitId author validationPath

If you use VS Code launch configuration, arguments can be supplied in launch settings or debugger UI.

### Docker workflow (existing)

Local image build and run:

- ./build.sh
- ./deploy.sh
- Service: http://localhost:8081/

Server deployment:

- Use apsim-web repository deployment process.

### Tests

Run all tests:

dotnet test APSIM.PerformanceTests.sln

### Troubleshooting

- Error: Cannot find environment variable PORTAL_DB
- Ensure PORTAL_DB is set in your environment or injected by your run profile.

- Portal starts but no PR data appears
- This is expected until collector/API has inserted pull request data.

- HTTPS certificate warnings locally
- Trust the local .NET dev certificate if prompted:
- dotnet dev-certs https --trust

- Database provider confusion
- sqlite: PORTAL_DB=portal.db
- mysql: PORTAL_DB=Server=...;Port=...;Database=...;User=...;Password=...;

### Suggested local workflow

1. Set PORTAL_DB=portal.db.
2. Build solution.
3. Start Portal + Collector in VS Code.
4. Open portal at http://localhost:5000.
5. Use root page to navigate recent pull requests.