Skip to content

Commit 6c8d244

Browse files
authored
Merge pull request #104 from VirtualFlyBrain/force-cache-clear-docs-checkbox
Turn the docs page's force_refresh into a self-resetting cache-clear checkbox
2 parents 9c85dfd + 9405164 commit 6c8d244

1 file changed

Lines changed: 51 additions & 10 deletions

File tree

‎src/vfbquery/api_docs.py‎

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,30 @@
2323
#
2424
# Parameter fields: name, doc, required (default False), example (pre-filled
2525
# in the form), enum (renders a dropdown; "dynamic:<key>" pulls the list
26-
# injected by build_docs_spec).
26+
# injected by build_docs_spec), header (renders a checkbox that sends the
27+
# named request header instead of a query parameter — see FORCE_REFRESH_PARAM
28+
# below for why force_refresh needs this).
2729
# ---------------------------------------------------------------------------
2830

31+
#: Shared definition for every endpoint that honours a cache refresh signal.
32+
#: Rendered as a checkbox (see paramRow's "header" branch in DOCS_HTML) that
33+
#: sets the ``X-Force-Refresh`` request header rather than a ``force_refresh``
34+
#: query parameter. That distinction matters: appending ``&force_refresh=true``
35+
#: to the URL changes nginx's cache key, so the refreshed response is written
36+
#: into a *different* edge-cache slot and the canonical URL people actually
37+
#: call is never healed. The header leaves the URL untouched, so one ticked
38+
#: "Run" refreshes the v3-cached edge slot and the VFBquery-level cache
39+
#: together, at the URL everyone else hits next — see ha_api._FORCE_REFRESH_HEADER
40+
#: and CACHING.md for the server-side half of this.
41+
FORCE_REFRESH_PARAM = {
42+
"name": "force_refresh",
43+
"header": "X-Force-Refresh",
44+
"doc": ("Force a cache clear: bypasses the edge cache and makes VFBquery "
45+
"recompute now. Refreshes the canonical URL directly, so this "
46+
"call and the very next one — even unticked — both return fresh "
47+
"data."),
48+
}
49+
2950
ENDPOINT_GROUPS = [
3051
{
3152
"group": "Term information",
@@ -42,9 +63,7 @@
4263
{"name": "id", "required": True,
4364
"doc": "Ontology class or individual short_form",
4465
"example": "FBbt_00003748"},
45-
{"name": "force_refresh",
46-
"doc": "true bypasses the result cache",
47-
"example": ""},
66+
FORCE_REFRESH_PARAM,
4867
],
4968
},
5069
{
@@ -69,8 +88,7 @@
6988
{"name": "include_graph",
7089
"doc": "true adds a graph rendering of the rows",
7190
"example": ""},
72-
{"name": "force_refresh",
73-
"doc": "true bypasses the result cache", "example": ""},
91+
FORCE_REFRESH_PARAM,
7492
],
7593
},
7694
{
@@ -203,6 +221,7 @@
203221
"example": ""},
204222
{"name": "include_graph",
205223
"doc": "true adds a graph rendering", "example": ""},
224+
FORCE_REFRESH_PARAM,
206225
],
207226
},
208227
],
@@ -512,7 +531,13 @@ def build_docs_spec(version, query_types=None, catmaid_commands=None):
512531
function paramRow(param, kind){
513532
const vocab = SPEC.vocabularies || {};
514533
let input;
515-
if (param.enum && String(param.enum).startsWith("dynamic:")){
534+
if (param.header){
535+
// A checkbox that sends a request header instead of a query parameter —
536+
// see FORCE_REFRESH_PARAM in the Python source for why force_refresh
537+
// needs this rather than the plain text input below.
538+
input = el("input", {type: "checkbox", "data-name": param.name,
539+
"data-kind": "header", "data-header-name": param.header});
540+
} else if (param.enum && String(param.enum).startsWith("dynamic:")){
516541
const key = param.enum.split(":")[1];
517542
let values = [];
518543
if (key === "query_types") values = vocab.query_types || [];
@@ -537,7 +562,14 @@ def build_docs_spec(version, query_types=None, catmaid_commands=None):
537562
function buildUrl(root, endpoint){
538563
let path = endpoint.path;
539564
const query = new URLSearchParams();
565+
const headers = {};
540566
for (const input of root.querySelectorAll("[data-name]")){
567+
if (input.dataset.kind === "header"){
568+
// Ticked -> the named header, at the unmodified URL below, never a
569+
// query parameter — that's the whole point (see FORCE_REFRESH_PARAM).
570+
if (input.checked) headers[input.dataset.headerName] = "true";
571+
continue;
572+
}
541573
const value = input.value.trim();
542574
if (input.dataset.kind === "path"){
543575
if (!value) return {error: input.dataset.name + " is required"};
@@ -552,7 +584,7 @@ def build_docs_spec(version, query_types=None, catmaid_commands=None):
552584
}
553585
}
554586
const qs = query.toString();
555-
return {url: path + (qs ? "?" + qs : "")};
587+
return {url: path + (qs ? "?" + qs : ""), headers: headers};
556588
}
557589
558590
async function run(root, endpoint){
@@ -561,13 +593,22 @@ def build_docs_spec(version, query_types=None, catmaid_commands=None):
561593
const out = root.querySelector("pre.result");
562594
const built = buildUrl(root, endpoint);
563595
if (built.error){ meta.innerHTML = '<span class="bad">' + built.error + "</span>"; return; }
564-
urlBox.textContent = new URL(built.url, window.location.href).href;
596+
// A bypass checkbox is a one-shot action, not a sticky setting: reset it to
597+
// off as soon as this run has captured whether it was ticked, so a bypass
598+
// never carries over into someone re-running the same card a second time
599+
// (e.g. to try a different id) without deliberately ticking it again.
600+
for (const box of root.querySelectorAll('[data-kind="header"]')) box.checked = false;
601+
const headerNames = Object.keys(built.headers || {});
602+
urlBox.textContent = new URL(built.url, window.location.href).href +
603+
(headerNames.length ? " (+ header " + headerNames.map(
604+
(h) => h + ": " + built.headers[h]).join(", ") + ")" : "");
565605
meta.textContent = "running…";
566606
out.hidden = false;
567607
out.textContent = "";
568608
const started = performance.now();
569609
try {
570-
const resp = await fetch(built.url, {headers: {Accept: "application/json"}});
610+
const resp = await fetch(built.url, {headers: Object.assign(
611+
{Accept: "application/json"}, built.headers || {})});
571612
const ms = Math.round(performance.now() - started);
572613
const text = await resp.text();
573614
let shown = text;

0 commit comments

Comments
 (0)