<li> <a href="apps">Review pending membership applications</a></li>
<li> <a href="dir">Contact information directory</a></li>
<li> <a href="poll">Polls</a></li>
+<li> <a href="qos">Support quality statistics</a></li>
</div>
<div class="sbgroup">
--- /dev/null
+<% @header [("title", ["Support quality statistics"])];
+
+val days = case $"days" of
+ "" => 7
+ | days => Web.stoi days %>
+
+<form method="post">
+Show me the entries from the last <input name="days" size="7" value="<% days %>"> days.
+<input type="submit" value="Show">
+</form>
+
+<table>
+<tr><td><b>Kind</b></td> <td><b>Description</b></td> <td><b>User</b></td> <td><b>Placed</b></td> <td><b>Response</b></td> <td><b>Closed</b></td></tr>
+<%
+
+foreach qos in Qos.recent days do %>
+ <tr> <td><% switch #kindUrl qos of SOME url => %><a href="<% url %>"><% end %><% Web.html (#kind qos) %><% switch #kindUrl qos of SOME _ => %></a><% end %></td>
+ <td><% switch #url qos of SOME url => %><a href="<% url %>"><% end %><% Web.html (#name qos) %><% switch #url qos of SOME _ => %></a><% end %></td>
+ <td><a href="user?id=<% #usr qos %>"><% Web.html (#uname qos) %></a></td>
+ <td><% #stamp qos %></td>
+ <td><% switch #pstamp qos of NONE => %>-<% | SOME stamp => stamp end %></td>
+ <td><% switch #cstamp qos of NONE => %>-<% | SOME stamp => stamp end %></td> </tr>
+<% end %>
+
+</table>
+
+<% @footer [] %>
+
+
--- /dev/null
+signature QOS = sig
+
+ type entry = { kind : string, kindUrl : string option, name : string, url : string option, usr : int, uname : string,
+ stamp : Init.C.timestamp, pstamp : Init.C.timestamp option, cstamp : Init.C.timestamp option}
+
+ val recent : int -> entry list
+
+end
--- /dev/null
+structure Qos :> QOS = struct
+
+open Util Sql Init
+
+type entry = { kind : string, kindUrl : string option, name : string, url : string option, usr : int, uname : string,
+ stamp : C.timestamp, pstamp : C.timestamp option, cstamp : C.timestamp option}
+
+fun mkEntryRow [kind, kindUrl, name, url, usr, uname, stamp, pstamp, cstamp] =
+ {kind = C.stringFromSql kind, kindUrl = if C.isNull kindUrl then NONE else SOME (C.stringFromSql kindUrl),
+ name = C.stringFromSql name, url = if C.isNull url then NONE else SOME (C.stringFromSql url),
+ usr = C.intFromSql usr, uname = C.stringFromSql uname, stamp = C.timestampFromSql stamp,
+ pstamp = if C.isNull pstamp then NONE else SOME (C.timestampFromSql pstamp),
+ cstamp = if C.isNull cstamp then NONE else SOME (C.timestampFromSql cstamp)}
+ | mkEntryRow row = rowError ("QOS", row)
+
+fun recent days =
+ let
+ val usr = Init.getUserId ()
+ val db = getDb ()
+ in
+ C.map db mkEntryRow ($`SELECT SupCategory.name, 'issue?cat=' || SupCategory.id, title,
+ 'issue?cat=' || SupCategory.id || '&id=' || SupIssue.id, usr, WebUser.name,
+ stamp, COALESCE(pstamp, cstamp), cstamp
+ FROM SupIssue JOIN SupCategory ON SupCategory.id = cat
+ JOIN WebUser ON WebUser.id = usr
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ AND (NOT priv OR usr = ^(C.intToSql usr)
+ OR (SELECT COUNT(*) FROM Membership WHERE Membership.usr = ^(C.intToSql usr)
+ AND (Membership.grp = 0 OR Membership.grp = SupCategory.grp)) > 0)
+ UNION SELECT 'APT package', NULL, data, NULL, usr, name, stamp, cstamp, cstamp
+ FROM Apt JOIN WebUser ON WebUser.id = usr
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ UNION SELECT 'Domain', NULL, data, NULL, usr, name, stamp, cstamp, cstamp
+ FROM Domain JOIN WebUser ON WebUser.id = usr
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ UNION SELECT 'Mailing list', NULL, data, NULL, usr, name, stamp, cstamp, cstamp
+ FROM MailingList JOIN WebUser ON WebUser.id = usr
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ UNION SELECT 'Security', NULL, data, NULL, usr, name, stamp, cstamp, cstamp
+ FROM Sec JOIN WebUser ON WebUser.id = usr
+ WHERE stamp >= CURRENT_TIMESTAMP - interval '^(C.intToSql days) DAYS'
+ ORDER BY stamp DESC`)
+ end
+
+end
| INSTALLED
| REJECTED
- type request = { id : int, usr : int, data : string, msg : string, status : status, stamp : Init.C.timestamp }
+ type request = { id : int, usr : int, data : string, msg : string, status : status,
+ stamp : Init.C.timestamp, cstamp : Init.C.timestamp option }
val statusFromInt : int -> status
| INSTALLED
| REJECTED
-type request = { id : int, usr : int, data : string, msg : string, status : status, stamp : C.timestamp }
+type request = { id : int, usr : int, data : string, msg : string, status : status,
+ stamp : C.timestamp, cstamp : C.timestamp option }
val statusFromInt =
fn 0 => NEW
fun statusFromSql v = statusFromInt (C.intFromSql v)
fun statusToSql s = C.intToSql (statusToInt s)
-fun mkRow [id, usr, data, msg, status, stamp] =
+fun mkRow [id, usr, data, msg, status, stamp, cstamp] =
{id = C.intFromSql id, usr = C.intFromSql usr, data = C.stringFromSql data,
- msg = C.stringFromSql msg, status = statusFromSql status, stamp = C.timestampFromSql stamp}
+ msg = C.stringFromSql msg, status = statusFromSql status, stamp = C.timestampFromSql stamp,
+ cstamp = if C.isNull cstamp then NONE else SOME (C.timestampFromSql cstamp)}
| mkRow r = rowError ("APT request", r)
fun add (usr, data, msg) =
val db = getDb ()
val id = nextSeq (db, seq)
in
- C.dml db ($`INSERT INTO ^table (id, usr, data, msg, status, stamp)
+ C.dml db ($`INSERT INTO ^table (id, usr, data, msg, status, stamp, cstamp)
VALUES (^(C.intToSql id), ^(C.intToSql usr), ^(C.stringToSql data), ^(C.stringToSql msg),
- 0, CURRENT_TIMESTAMP)`);
+ 0, CURRENT_TIMESTAMP, NULL)`);
id
end
let
val db = getDb ()
in
+ if #status req <> NEW then
+ ignore (C.dml db ($`UPDATE ^table SET cstamp = CURRENT_TIMESTAMP WHERE id = ^(C.intToSql (#id req))`))
+ else
+ ();
ignore (C.dml db ($`UPDATE ^table SET
usr = ^(C.intToSql (#usr req)), data = ^(C.stringToSql (#data req)),
msg = ^(C.stringToSql (#msg req)), status = ^(statusToSql (#status req))
ignore (C.dml (getDb ()) ($`DELETE FROM ^table WHERE id = ^(C.intToSql id)`))
fun lookup id =
- case C.oneOrNoRows (getDb ()) ($`SELECT id, usr, data, msg, status, stamp
+ case C.oneOrNoRows (getDb ()) ($`SELECT id, usr, data, msg, status, stamp, cstamp
FROM ^table
WHERE id = ^(C.intToSql id)`) of
SOME row => mkRow row
| mkRow' r = rowError ("Apt.request'", r)
fun list () =
- C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, data, msg, status, stamp
+ C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, data, msg, status, stamp, cstamp
FROM ^table JOIN WebUser ON usr = WebUser.id
ORDER BY stamp DESC`)
fun listOpen () =
- C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, data, msg, status, stamp
+ C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, data, msg, status, stamp, cstamp
FROM ^table JOIN WebUser ON usr = WebUser.id
WHERE status = 0
ORDER BY stamp DESC`)
| INSTALLED
| REJECTED
- type request = { id : int, usr : int, node : int, data : string, msg : string, status : status, stamp : Init.C.timestamp }
+ type request = { id : int, usr : int, node : int, data : string, msg : string, status : status,
+ stamp : Init.C.timestamp, cstamp : Init.C.timestamp option }
val statusFromInt : int -> status
| INSTALLED
| REJECTED
-type request = { id : int, usr : int, node : int, data : string, msg : string, status : status, stamp : C.timestamp }
+type request = { id : int, usr : int, node : int, data : string, msg : string, status : status,
+ stamp : C.timestamp, cstamp : C.timestamp option }
val statusFromInt =
fn 0 => NEW
fun statusFromSql v = statusFromInt (C.intFromSql v)
fun statusToSql s = C.intToSql (statusToInt s)
-fun mkRow [id, usr, node, data, msg, status, stamp] =
+fun mkRow [id, usr, node, data, msg, status, stamp, cstamp] =
{id = C.intFromSql id, usr = C.intFromSql usr, node = C.intFromSql node,
data = C.stringFromSql data,
- msg = C.stringFromSql msg, status = statusFromSql status, stamp = C.timestampFromSql stamp}
+ msg = C.stringFromSql msg, status = statusFromSql status, stamp = C.timestampFromSql stamp,
+ cstamp = if C.isNull cstamp then NONE else SOME (C.timestampFromSql cstamp)}
| mkRow r = rowError ("APT request", r)
fun add {usr, node, data, msg} =
val db = getDb ()
val id = nextSeq (db, seq)
in
- C.dml db ($`INSERT INTO ^table (id, usr, node, data, msg, status, stamp)
+ C.dml db ($`INSERT INTO ^table (id, usr, node, data, msg, status, stamp, cstamp)
VALUES (^(C.intToSql id), ^(C.intToSql usr), ^(C.intToSql node), ^(C.stringToSql data), ^(C.stringToSql msg),
- 0, CURRENT_TIMESTAMP)`);
+ 0, CURRENT_TIMESTAMP, NULL)`);
id
end
let
val db = getDb ()
in
+ if #status req <> NEW then
+ ignore (C.dml db ($`UPDATE ^table SET cstamp = CURRENT_TIMESTAMP WHERE id = ^(C.intToSql (#id req))`))
+ else
+ ();
+
ignore (C.dml db ($`UPDATE ^table SET
usr = ^(C.intToSql (#usr req)), data = ^(C.stringToSql (#data req)),
node = ^(C.intToSql (#node req)),
ignore (C.dml (getDb ()) ($`DELETE FROM ^table WHERE id = ^(C.intToSql id)`))
fun lookup id =
- case C.oneOrNoRows (getDb ()) ($`SELECT id, usr, node, data, msg, status, stamp
+ case C.oneOrNoRows (getDb ()) ($`SELECT id, usr, node, data, msg, status, stamp, cstamp
FROM ^table
WHERE id = ^(C.intToSql id)`) of
SOME row => mkRow row
| mkRow' r = rowError ("Apt.request'", r)
fun list () =
- C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, node, data, msg, status, stamp
+ C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, node, data, msg, status, stamp, cstamp
FROM ^table JOIN WebUser ON usr = WebUser.id
ORDER BY stamp DESC`)
fun listOpen () =
- C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, node, data, msg, status, stamp
+ C.map (getDb ()) mkRow' ($`SELECT name, ^table.id, usr, node, data, msg, status, stamp, cstamp
FROM ^table JOIN WebUser ON usr = WebUser.id
WHERE status = 0
ORDER BY stamp DESC`)
val db = getDb ()
in
case #status iss of
- PENDING => ignore (C.dml db ($`UPDATE SupIssue SET pstamp = CURRENT_TIMESTAMP`))
- | CLOSED => ignore (C.dml db ($`UPDATE SupIssue SET cstamp = CURRENT_TIMESTAMP`))
+ PENDING => ignore (C.dml db ($`UPDATE SupIssue SET pstamp = CURRENT_TIMESTAMP WHERE id = ^(C.intToSql (#id iss))`))
+ | CLOSED => ignore (C.dml db ($`UPDATE SupIssue SET cstamp = CURRENT_TIMESTAMP WHERE id = ^(C.intToSql (#id iss))`))
| _ => ();
ignore (C.dml db ($`UPDATE SupIssue SET
usr = ^(C.intToSql (#usr iss)), cat = ^(C.intToSql (#cat iss)),
msg TEXT NOT NULL,
status INTEGER NOT NULL,
stamp TIMESTAMP NOT NULL,
+ cstamp TIMESTAMP,
FOREIGN KEY (usr) REFERENCES WebUser(id) ON DELETE CASCADE,
FOREIGN KEY (node) REFERENCES WebNode(id) ON DELETE CASCADE);
msg TEXT NOT NULL,
status INTEGER NOT NULL,
stamp TIMESTAMP NOT NULL,
+ cstamp TIMESTAMP,
FOREIGN KEY (usr) REFERENCES WebUser(id) ON DELETE CASCADE);
CREATE SEQUENCE DomainSeq START 1;
msg TEXT NOT NULL,
status INTEGER NOT NULL,
stamp TIMESTAMP NOT NULL,
+ cstamp TIMESTAMP,
FOREIGN KEY (usr) REFERENCES WebUser(id) ON DELETE CASCADE);
CREATE SEQUENCE MailingListSeq START 1;
msg TEXT NOT NULL,
status INTEGER NOT NULL,
stamp TIMESTAMP NOT NULL,
+ cstamp TIMESTAMP,
FOREIGN KEY (usr) REFERENCES WebUser(id) ON DELETE CASCADE,
FOREIGN KEY (node) REFERENCES WebNode(id) ON DELETE CASCADE);