oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "bytes"
5 "net/http"
6
7 "amadan.net/rastrillo/rastrillo/jobs"
8)
9
10// jobPageView is the job status page's payload.
11type jobPageView struct {
12 // Status is the dict rastrillo's own job-status partial reads.
13 Status map[string]any
14 // Running drives the noscript meta refresh. It is a separate field
15 // rather than a lookup into Status because the refresh must be
16 // emitted ONLY while the job is running: on a finished page it
17 // reloads forever, which is the failure rastrillo's own docs warn
18 // about.
19 Running bool
20 // PollSeconds is that refresh's interval, kept in step with the
21 // shim's by coming from the same PageData.
22 PollSeconds int
23}
24
25// jobStatusData shapes a job for the job-status partial.
26//
27// Status is converted to a plain string deliberately. The partial
28// compares it with {{if eq .Status "running"}}, and template's eq
29// refuses to compare a named type (jobs.Status) against an untyped
30// string constant — it errors at execute time rather than quietly
31// taking the wrong branch, which would be a 500 on every job page and
32// nowhere else.
33func jobStatusData(d jobs.PageData) map[string]any {
34 return map[string]any{
35 "Name": d.Job.Name,
36 "Status": string(d.Job.Status),
37 "Progress": d.Job.Progress,
38 "Err": d.Job.Err,
39 "PollURL": d.FragmentPath,
40 "PollSeconds": d.PollSeconds,
41 // Set, so a browser that supports it rides server push and
42 // stops polling. Unset, the shim polls exactly as before, so
43 // naming it costs nothing where it is not supported.
44 "PushURL": d.EventsPath,
45 }
46}
47
48// renderJobPage draws the whole status page. rastrillo 303s away from
49// here by itself once the job is done and has somewhere to go, so this
50// only ever draws a job that is still running or has failed.
51func (a *App) renderJobPage(w http.ResponseWriter, r *http.Request, d jobs.PageData) {
52 a.render(w, r, "job", titled("Importing a spreadsheet"), http.StatusOK, jobPageView{
53 Status: jobStatusData(d),
54 Running: d.Job.Status == jobs.Running,
55 PollSeconds: d.PollSeconds,
56 })
57}
58
59// renderJobFragment draws the polled partial ALONE — no layout.
60//
61// Alone is the whole contract: the shim replaces the polling element
62// with whatever comes back, so a fragment wrapped in the page layout
63// nests a second copy of the page inside the first on every tick.
64//
65// It executes out of the "job" tree because every page tree is parsed
66// with rastrillo's partials alongside this app's own (parsePages), so
67// "job-status" is defined there — no second tree, and no template file
68// of ours that exists only to hold somebody else's partial.
69func (a *App) renderJobFragment(w http.ResponseWriter, _ *http.Request, d jobs.PageData) {
70 t, ok := pages["job"]
71 if !ok {
72 http.Error(w, "Something went wrong.", http.StatusInternalServerError)
73 return
74 }
75 var buf bytes.Buffer
76 if err := t.ExecuteTemplate(&buf, "job-status", jobStatusData(d)); err != nil {
77 http.Error(w, "Something went wrong.", http.StatusInternalServerError)
78 return
79 }
80 w.Header().Set("Content-Type", "text/html; charset=utf-8")
81 w.Write(buf.Bytes())
82}
83