Skip to content

Commit 153fe89

Browse files
committed
Render report HTML with Razor
1 parent b31bbe3 commit 153fe89

21 files changed

+305
-293
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
@using TS.NET.Sequencer
2+
@using static TS.NET.Sequencer.ReportHelpers
3+
@using static TS.NET.Sequencer.TimeSpanUtility
4+
@{
5+
bool d3Charts = false;
6+
}
7+
<!DOCTYPE html>
8+
<html lang="en">
9+
<head>
10+
<meta charset="UTF-8">
11+
<meta name="viewport" content="width =device-width, initial-scale=1.0">
12+
<title>@($"Report - {Model.Name} - {Model.StartTimestamp:yyyy-MM-dd HHmmss}")</title>
13+
</head>
14+
<body class="bg-gray-200 py-4 print:p-0 print:bg-white">
15+
<div class="max-w-4xl mx-auto bg-white px-10 py-15 print:p-0">
16+
<h1 class="text-4xl text-black p-2 mb-4 text-center bg-gray-200 border-2 border-gray-900">@Model.Name</h1>
17+
<h1 class="text-4xl @StatusTextColour(Model.Status) p-2 mb-8 text-center bg-gray-200 border-2 border-gray-900">@Model.Status</h1>
18+
19+
<div class="flex flex-row gap-2 mb-8 text-sm text-black">
20+
<div class="flex flex-col gap-1">
21+
<div>Sequence name:</div>
22+
<div>Start date/time:</div>
23+
<div>Duration:</div>
24+
<div>Device:</div>
25+
</div>
26+
<div class="flex flex-col gap-1">
27+
<div>@Model.Name</div>
28+
<div>@Model.StartTimestamp.ToString("yyyy-MM-dd HH:mm:ssZ") (@Model.TzId)</div>
29+
<div>@TimeSpanUtility.HumanDuration(Model.Duration)</div>
30+
<div>TS0019</div>
31+
</div>
32+
</div>
33+
34+
<table class="min-w-full bg-white">
35+
<thead class="bg-gray-200 text-black text-sm leading-normal">
36+
<tr>
37+
<th class="p-1 pl-2 text-left font-normal">#</th>
38+
<th class="p-1 pl-2 text-left font-normal">Step name</th>
39+
<th class="p-1 text-left font-normal">Duration</th>
40+
<th class="p-1 text-left font-normal">Summary</th>
41+
<th class="p-1 pr-2 text-left font-normal">Status</th>
42+
</tr>
43+
</thead>
44+
<tbody class="text-black text-sm ">
45+
46+
@if (Model.Steps != null)
47+
{
48+
@foreach (var step in Model.Steps)
49+
{
50+
var hasMetadata = step.Result?.Metadata != null && step.Result.Metadata.Count > 0;
51+
52+
<tr class="@(hasMetadata ? "" : "border-b border-gray-300")">
53+
<td class="p-1 pl-2 text-left whitespace-nowrap">@step.Index</td>
54+
<td class="p-1 pl-2 text-left">@step.Name</td>
55+
<td class="p-1 text-left">@TimeSpanUtility.HumanDuration(step.Result?.Duration)</td>
56+
<td class="p-1 text-left">@(step.Result?.Summary ?? "-")</td>
57+
<td class="p-1 pr-2 text-left @StatusTextColour(step.Result?.Status)">@(step.Result?.Status.ToString() ?? "-")</td>
58+
</tr>
59+
60+
@if (hasMetadata)
61+
{
62+
bool firstMetadataItem = true;
63+
64+
<tr class="border-b border-gray-300">
65+
<td class="p-2 bg-gray-100"></td>
66+
<td colspan="4" class="p-2">
67+
<div class="text-xs">
68+
@if (step.Result.Metadata != null)
69+
{
70+
@foreach (var meta in step.Result.Metadata)
71+
{
72+
@if (!meta.ShowInReport)
73+
{
74+
continue;
75+
}
76+
@switch (meta)
77+
{
78+
case ResultMetadataException exception:
79+
{
80+
<div class="@(firstMetadataItem ? "pb-2" : "py-2") text-xs underline">Exception:</div>
81+
<pre class="bg-red-100 text-xs p-2 whitespace-pre-wrap overflow-auto max-w-full break-words">@exception.Exception</pre>
82+
}
83+
break;
84+
case ResultMetadataTable table:
85+
{
86+
<div class="@(firstMetadataItem ? "pb-2" : "py-2") text-xs underline">@table.Name</div>
87+
<table class="text-xs border border-gray-400">
88+
@if (table.Headers != null && table.Headers.Length > 0)
89+
{
90+
<thead>
91+
<tr>
92+
@foreach (var h in table.Headers)
93+
{
94+
<th class="px-2 py-1 border-b border-gray-400 text-left font-normal">@h</th>
95+
}
96+
</tr>
97+
</thead>
98+
}
99+
@if (table.Rows != null && table.Rows.Length > 0)
100+
{
101+
<tbody>
102+
@foreach (var row in table.Rows)
103+
{
104+
<tr>
105+
@if (row != null)
106+
{
107+
@foreach (var cell in row)
108+
{
109+
<td class="px-2 py-1 border-b border-gray-300">@cell</td>
110+
}
111+
}
112+
</tr>
113+
}
114+
</tbody>
115+
}
116+
</table>
117+
}
118+
break;
119+
case ResultMetadataXYChart chart:
120+
{
121+
<div>Chart here</div>
122+
}
123+
break;
124+
default:
125+
<em class="text-gray-500">Metadata type not implemented in report generator.</em>
126+
break;
127+
}
128+
firstMetadataItem = false;
129+
}
130+
}
131+
132+
</div>
133+
</td>
134+
</tr>
135+
}
136+
}
137+
}
138+
</tbody>
139+
</table>
140+
</div>
141+
@Raw(GetStyles(Model))
142+
</body>
143+
</html>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using RazorLight;
2+
3+
namespace TS.NET.Sequencer;
4+
5+
public class HtmlReportGenerator
6+
{
7+
public async void Render(Sequence sequence, string outputFilePath)
8+
{
9+
var engine = new RazorLightEngineBuilder()
10+
.UseEmbeddedResourcesProject(typeof(HtmlReportGenerator).Assembly)
11+
.UseMemoryCachingProvider()
12+
.Build();
13+
14+
string html = await engine.CompileRenderAsync("TS.NET.Sequencer.HTML.HtmlReport", sequence);
15+
File.WriteAllText(outputFilePath, html);
16+
}
17+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Reflection;
2+
using System.Text;
3+
4+
namespace TS.NET.Sequencer;
5+
6+
public static class ReportHelpers
7+
{
8+
public static string StatusTextColour(Status? status)
9+
{
10+
return status switch
11+
{
12+
Status.Running or Status.Cancelled => "text-yellow-600",
13+
Status.Passed or Status.Done => "text-green-600",
14+
Status.Failed or Status.Error => "text-red-600",
15+
Status.Skipped or null => "text-black",
16+
_ => throw new NotImplementedException()
17+
};
18+
}
19+
20+
public static string GetStyles(Sequence sequence)
21+
{
22+
var sb = new StringBuilder();
23+
sb.AppendLine("<style>");
24+
25+
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("TS.NET.Sequencer.report.css"))
26+
{
27+
if (stream != null)
28+
{
29+
using (var reader = new StreamReader(stream))
30+
{
31+
var content = reader.ReadToEnd();
32+
content = content.Replace("[@top-center content]", sequence.Name);
33+
content = content.Replace("[@top-right content]", "Device: TS0019");
34+
content = content.Replace("[@bottom-left content]", sequence.StartTimestamp.ToString("yyyy-MM-dd HH:mm:ss"));
35+
sb.Append(content);
36+
}
37+
}
38+
}
39+
40+
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("TS.NET.Sequencer.report-tailwind.css"))
41+
{
42+
if (stream != null)
43+
{
44+
using (var reader = new StreamReader(stream))
45+
{
46+
sb.Append(reader.ReadToEnd());
47+
}
48+
}
49+
}
50+
51+
sb.AppendLine("</style>");
52+
return sb.ToString();
53+
}
54+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace TS.NET.Sequencer;
2+
3+
public static class TimeSpanUtility
4+
{
5+
public static string HumanDuration(TimeSpan? duration)
6+
{
7+
if (duration == null)
8+
return "-";
9+
string format;
10+
if (duration?.Hours > 0)
11+
format = @"h\h\ m\m\ s\s";
12+
else if (duration?.Minutes > 0)
13+
format = @"m\m\ s\s";
14+
else if (duration?.Seconds > 10)
15+
format = @"s\s";
16+
else
17+
format = @"s\.fff\s";
18+
return ((TimeSpan)duration!).ToString(format);
19+
}
20+
}

0 commit comments

Comments
 (0)