commit
acc60d11e7
|
|
@ -161,6 +161,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||||
|
|
||||||
<script src="static/markdown.js"></script>
|
<script src="static/markdown.js"></script>
|
||||||
<script src="static/commands.js"></script>
|
<script src="static/commands.js"></script>
|
||||||
|
<script src="static/widgets.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
const qs = (s, el=document) => el.querySelector(s);
|
const qs = (s, el=document) => el.querySelector(s);
|
||||||
|
|
@ -361,20 +362,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||||
tickWidgets();
|
tickWidgets();
|
||||||
|
|
||||||
/* Widgets */
|
/* Widgets */
|
||||||
async function updateRepoMix() {
|
updateRepoMix(qs('#repo-mix'), fetch);
|
||||||
const el = qs('#repo-mix');
|
|
||||||
try {
|
|
||||||
const res = await fetch('api/v1/context', { headers: { Accept: 'application/json' } });
|
|
||||||
const data = await res.json();
|
|
||||||
const repos = data.repos || [];
|
|
||||||
const issues = (data.issues||[]).filter(i=>i.state==='open');
|
|
||||||
const prs = data.pull_requests || [];
|
|
||||||
el.innerHTML = '<div class="kv"><div class="label">Repos</div><div class="value">' + repos.length + '</div><div class="label">Open issues</div><div class="value">' + issues.length + '</div><div class="label">Open PRs</div><div class="value">' + prs.length + '</div></div>';
|
|
||||||
} catch (e) {
|
|
||||||
el.innerHTML = '<div class="muted">Widget unavailable.</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateRepoMix();
|
|
||||||
|
|
||||||
function widgetTick() { const el=qs('#widget-clock'); if(el) el.textContent = fmt(new Date()); }
|
function widgetTick() { const el=qs('#widget-clock'); if(el) el.textContent = fmt(new Date()); }
|
||||||
setInterval(widgetTick, 1000);
|
setInterval(widgetTick, 1000);
|
||||||
|
|
|
||||||
19
frontend/widgets.js
Normal file
19
frontend/widgets.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
async function updateRepoMix(element, fetchContext = fetch) {
|
||||||
|
try {
|
||||||
|
const response = await fetchContext('api/v1/context', {
|
||||||
|
headers: { Accept: 'application/json' },
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||||
|
const data = await response.json();
|
||||||
|
const repos = data.repos || [];
|
||||||
|
const issues = (data.issues || []).filter((issue) => issue.state === 'open');
|
||||||
|
const pullRequests = data.pull_requests || [];
|
||||||
|
element.innerHTML = '<div class="kv"><div class="label">Repos</div><div class="value">' + repos.length + '</div><div class="label">Open issues</div><div class="value">' + issues.length + '</div><div class="label">Open PRs</div><div class="value">' + pullRequests.length + '</div></div>';
|
||||||
|
} catch (error) {
|
||||||
|
element.innerHTML = '<div class="muted">Widget unavailable.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = updateRepoMix;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.views import dashboard
|
from src.views import dashboard
|
||||||
|
|
||||||
|
|
||||||
|
WIDGETS = Path(__file__).parents[1] / "frontend" / "widgets.js"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_dashboard_renders_repo_mix_placeholder_before_javascript_runs():
|
async def test_dashboard_renders_repo_mix_placeholder_before_javascript_runs():
|
||||||
html = await dashboard()
|
html = await dashboard()
|
||||||
|
|
@ -16,3 +23,25 @@ async def test_dashboard_marks_fallback_context_as_degraded_instead_of_live():
|
||||||
html = await dashboard()
|
html = await dashboard()
|
||||||
|
|
||||||
assert "data.error ? 'Degraded · ' + data.error : 'Live · updated just now'" in html
|
assert "data.error ? 'Degraded · ' + data.error : 'Live · updated just now'" in html
|
||||||
|
|
||||||
|
|
||||||
|
def test_repo_mix_reports_unavailable_for_failed_context_response():
|
||||||
|
script = f"""
|
||||||
|
const updateRepoMix = require({json.dumps(str(WIDGETS))});
|
||||||
|
const element = {{ innerHTML: 'stale counts' }};
|
||||||
|
const failedFetch = async () => ({{
|
||||||
|
ok: false,
|
||||||
|
status: 503,
|
||||||
|
json: async () => ({{ error: 'upstream unavailable' }}),
|
||||||
|
}});
|
||||||
|
|
||||||
|
updateRepoMix(element, failedFetch).then(() => {{
|
||||||
|
if (!element.innerHTML.includes('Widget unavailable.')) {{
|
||||||
|
throw new Error(`expected unavailable state, got: ${{element.innerHTML}}`);
|
||||||
|
}}
|
||||||
|
}});
|
||||||
|
"""
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
["node", "-e", script], check=True, capture_output=True, text=True
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user