val days = case $"days" of
"" => 7
- | days => Web.stoi days %>
+ | days => Web.stoi days;
+
+if $"cmd" = "list" then %>
<form method="post">
+<input type="hidden" name="cmd" value="list">
Show me the entries from the last <input name="days" size="7" value="<% days %>"> days.
<input type="submit" value="Show">
</form>
</table>
+<% else %>
+
+<p><a href="qos?cmd=list">List all recent support requests</a></p>
+
+<form method="post">
+Refresh statistics to include the last <input name="days" size="7" value="<% days %>"> days.
+<input type="submit" value="Show">
+</form>
+
+<br><hr><br>
+
+<% val stats = Qos.reportCard days %>
+<table>
+<tr> <td><b>Kind</b></td> <td><b>Total resolved issues</b></td> <td><b>Minutes to resolve</b></td> </tr>
+<tr> <td>APT packages</td> <td><% #count (#apt stats) %></td> <td><% #minutes (#apt stats) %></td> </tr>
+<tr> <td>Domains</td> <td><% #count (#domain stats) %></td> <td><% #minutes (#domain stats) %></td> </tr>
+<tr> <td>Mailing lists</td> <td><% #count (#mailingList stats) %></td> <td><% #minutes (#mailingList stats) %></td> </tr>
+<tr> <td>Security</td> <td><% #count (#sec stats) %></td> <td><% #minutes (#sec stats) %></td> </tr>
+<tr> <td>Miscellaneous</td> <td><% #count (#closed (#misc stats)) %></td> <td><% #minutes (#closed (#misc stats)) %></td> </tr>
+</table>
+
+<p>Additionally, <% #count (#pending (#misc stats)) %> miscellaneous support requests placed in this period have at some time been marked as pending or closed, and it took on average <% #minutes (#pending (#misc stats)) %> minutes to do so.</p>
+
+<% end %>
+
<% @footer [] %>
val recent : int -> entry list
+ type grade = { count : int, minutes : int }
+ type grades = { pending : grade, closed : grade }
+ type reportCard = { misc : grades,
+ apt : grade,
+ domain : grade,
+ mailingList : grade,
+ sec : grade }
+
+ val reportCard : int -> reportCard
+
end
ORDER BY stamp DESC`)
end
+type grade = { count : int, minutes : int }
+type grades = { pending : grade, closed : grade }
+type reportCard = { misc : grades,
+ apt : grade,
+ domain : grade,
+ mailingList : grade,
+ sec : grade }
+
+fun mkGradeRow [count, minutes] =
+ {count = C.intFromSql count,
+ minutes = if C.isNull minutes then 0 else C.intFromSql minutes}
+ | mkGradeRow row = rowError ("grade", row)
+
+fun reportCard days =
+ let
+ val db = getDb ()
+
+ fun gradeRow s = mkGradeRow (C.oneRow db s)
+
+ fun default tab =
+ gradeRow ($`SELECT COUNT(*), AVG(cstamp - stamp)
+ FROM ^tab
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ AND cstamp IS NOT NULL`)
+ in
+ {misc = {pending = gradeRow
+ ($`SELECT COUNT(*), AVG(COALESCE(pstamp, cstamp) - stamp)
+ FROM SupIssue
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ AND COALESCE(pstamp, cstamp) IS NOT NULL`),
+ closed = gradeRow
+ ($`SELECT COUNT(*), AVG(cstamp - stamp)
+ FROM SupIssue
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ AND cstamp IS NOT NULL`)},
+ apt = default "Apt",
+ domain = default "Domain",
+ mailingList = default "MailingList",
+ sec = default "Sec"}
+ end
+
end