Default WAN window to 24h + surface /voicediag in /help
Bumps the shared default WAN look-back from 15m to 24h (1440m) so both /phonestatus follow-ups and /voicediag pick up a full day of voice-quality signal by default — better for after-the-fact ticket triage than a live snapshot. Operators wanting real-time behavior can set WAN_STANDARD_WINDOW_MINUTES=15 or pass --window 15m to /voicediag. Also fills in a long-standing help gap: /voicediag was fully implemented but never listed in /help. Adds it under "AV & phones" with usage, examples, and notes covering detail mode, --only, --window, thresholds, kill switch, HTTP shape, and audit logging. Updates /help phonestatus to call out the 24h WAN follow-up + cross-link to /voicediag. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b802383441
commit
44ab6559ac
5 changed files with 69 additions and 23 deletions
13
.env.example
13
.env.example
|
|
@ -146,12 +146,13 @@ WEBEX_TOKENS_PATH=./config/webex-service-tokens.json
|
||||||
# WAN_STANDARD_ENABLED=true
|
# WAN_STANDARD_ENABLED=true
|
||||||
|
|
||||||
# --- WAN look-back window (minutes) used when the operator doesn't
|
# --- WAN look-back window (minutes) used when the operator doesn't
|
||||||
# pass `--window` on /voicediag. Applies to healthscore + LQM.
|
# pass `--window` on /voicediag (and always used by /phonestatus).
|
||||||
# Alarms window is max(60, WAN_STANDARD_WINDOW_MINUTES) since
|
# Applies to healthscore + LQM. Alarms window is
|
||||||
# sub-hour alarm queries are usually too noisy to be actionable.
|
# max(60, WAN_STANDARD_WINDOW_MINUTES) since sub-hour alarm queries
|
||||||
# Prisma's finest LQM bucket is 5m; anything <= 5 is snapped up.
|
# are usually too noisy to be actionable. Prisma's finest LQM
|
||||||
# Common values: 15 (default, real-time), 60, 360 (6h), 1440 (24h).
|
# bucket is 5m; anything <= 5 is snapped up.
|
||||||
# WAN_STANDARD_WINDOW_MINUTES=15
|
# Common values: 15 (real-time), 60, 360 (6h), 1440 (24h — default).
|
||||||
|
# WAN_STANDARD_WINDOW_MINUTES=1440
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# /voicediag — Store voice-line standards
|
# /voicediag — Store voice-line standards
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ const SHORT_HELP = {
|
||||||
|
|
||||||
// AV / phones
|
// AV / phones
|
||||||
avstatus: 'AV / device status for a store (alias: /wostatus)',
|
avstatus: 'AV / device status for a store (alias: /wostatus)',
|
||||||
phonestatus: 'DECT + IP phone status for a store',
|
phonestatus: 'DECT + IP phone status for a store (plus 24h WAN follow-up)',
|
||||||
|
voicediag: 'Deep voice diagnostic: Webex Calling features + SD-WAN quality with fix cards',
|
||||||
|
|
||||||
// Jira
|
// Jira
|
||||||
jirahistory: 'Recent Jira tickets for a store (optionally filtered by component)',
|
jirahistory: 'Recent Jira tickets for a store (optionally filtered by component)',
|
||||||
|
|
@ -56,9 +57,39 @@ const LONG_HELP = {
|
||||||
notes: [
|
notes: [
|
||||||
'Shows DECT basestations + IP phones with Meraki links.',
|
'Shows DECT basestations + IP phones with Meraki links.',
|
||||||
'Detailed mode adds firmware, serial, SIP details and errors.',
|
'Detailed mode adds firmware, serial, SIP details and errors.',
|
||||||
|
'When the store is Prisma SD-WAN managed (site name `CG<store>` padded to 5 digits), a follow-up **WAN Diagnostics** message arrives with per-path latency/jitter/loss/MOS, site healthscore, and any alarms — averaged over the last 24 hours (override via `WAN_STANDARD_WINDOW_MINUTES`).',
|
||||||
|
'For an in-depth voice diagnostic with per-user Webex Calling checks + fix cards, use `/voicediag <store>`.',
|
||||||
'Web dashboard: `/phone-store-dashboard.html`.',
|
'Web dashboard: `/phone-store-dashboard.html`.',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
voicediag: {
|
||||||
|
title: '/voicediag',
|
||||||
|
usage: [
|
||||||
|
'/voicediag <store>',
|
||||||
|
'/voicediag <store> detail',
|
||||||
|
'/voicediag <store> --only <check1,check2>',
|
||||||
|
'/voicediag <store> --window <duration>',
|
||||||
|
'/voicediag list-checks',
|
||||||
|
],
|
||||||
|
examples: [
|
||||||
|
'/voicediag 782',
|
||||||
|
'/voicediag 782 detail',
|
||||||
|
'/voicediag 782 --window 15m',
|
||||||
|
'/voicediag 782 --window 6h --only wanLatency,wanJitter,wanLoss,wanMos',
|
||||||
|
'/voicediag 782 --only dnd,callForwarding,voicemail',
|
||||||
|
'/voicediag list-checks',
|
||||||
|
],
|
||||||
|
notes: [
|
||||||
|
'Runs a full battery of per-user Webex Calling checks (DND, call forwarding, voicemail, call intercept, call waiting, outgoing permission, etc.) plus eight SD-WAN checks (site, healthscore, link state, latency, jitter, loss, MOS, alarms) against the store\'s Prisma tenant.',
|
||||||
|
'Default view hides OK checks and highlights errors/warnings/skipped. Pass `detail` (or `detailed`) to also see OK checks with expanded per-link tables + thresholds + roll-ups.',
|
||||||
|
'Fixable issues (e.g. DND on, forwarding to wrong number) post a per-issue confirmation card. A single "apply all" card lets you fix everything at once after reviewing.',
|
||||||
|
'`--only` restricts the run to specific check ids (comma-separated). Use `/voicediag list-checks` to see every registered id + its scope.',
|
||||||
|
'`--window` overrides the WAN look-back window (accepts `15m`, `1h`, `6h`, `24h`, `1d`, or a bare minute count). Applies to healthscore + LQM (latency/jitter/loss/MOS) fetches. Alarms are floored at 60m. Default is 24h — set `WAN_STANDARD_WINDOW_MINUTES` for a different global default.',
|
||||||
|
'Voice-quality thresholds default to ITU-T G.114 / RFC 3550. Override any of them via `WAN_STANDARD_*` env vars (see `.env.example`). Kill-switch: `WAN_STANDARD_ENABLED=false` silences the whole WAN bucket.',
|
||||||
|
'HTTP equivalent: `?storeNum=<n>[&detailed=true][&only=id1,id2][&window=15m]`. HTTP callers see the markdown snapshot only — cards are chat-only.',
|
||||||
|
'Audit log: every remediation apply/cancel is logged under `voicediag:audit` with the requester identity.',
|
||||||
|
],
|
||||||
|
},
|
||||||
'provision-dect': {
|
'provision-dect': {
|
||||||
title: '/provision-dect',
|
title: '/provision-dect',
|
||||||
usage: ['/provision-dect <store>'],
|
usage: ['/provision-dect <store>'],
|
||||||
|
|
@ -196,7 +227,7 @@ const LONG_HELP = {
|
||||||
|
|
||||||
const GROUPS = [
|
const GROUPS = [
|
||||||
{ title: 'Work orders', keys: ['wohistory', 'wosummary', 'woattachments'] },
|
{ title: 'Work orders', keys: ['wohistory', 'wosummary', 'woattachments'] },
|
||||||
{ title: 'AV & phones', keys: ['avstatus', 'phonestatus'] },
|
{ title: 'AV & phones', keys: ['avstatus', 'phonestatus', 'voicediag'] },
|
||||||
{ title: 'Jira', keys: ['jirahistory', 'jiraticket', 'jirapoll'] },
|
{ title: 'Jira', keys: ['jirahistory', 'jiraticket', 'jirapoll'] },
|
||||||
{ title: 'Provisioning', keys: ['provision-dect', 'provision-vc', 'vcmonitor'] },
|
{ title: 'Provisioning', keys: ['provision-dect', 'provision-vc', 'vcmonitor'] },
|
||||||
{ title: 'Admin & bulk', keys: ['offboarduser', 'webexhost', 'bulkavstatuscsv', 'bulkavswitchcsv', 'devicesbymodel'] },
|
{ title: 'Admin & bulk', keys: ['offboarduser', 'webexhost', 'bulkavstatuscsv', 'bulkavswitchcsv', 'devicesbymodel'] },
|
||||||
|
|
|
||||||
|
|
@ -243,17 +243,25 @@ export async function collectSdwanForStore(storeNum, opts = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default WAN look-back window (24h in minutes). Chosen because the
|
||||||
|
// primary users of /phonestatus + /voicediag are triaging trouble
|
||||||
|
// tickets after the fact, where "was voice quality bad today?" is
|
||||||
|
// more useful than "how does it look right this second?". Operators
|
||||||
|
// wanting a live snapshot can pass `--window 15m` on /voicediag or
|
||||||
|
// set WAN_STANDARD_WINDOW_MINUTES=15 in the environment.
|
||||||
|
const DEFAULT_WINDOW_MINUTES = 1440;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Snap an arbitrary window request to a reasonable, sane value.
|
* Snap an arbitrary window request to a reasonable, sane value.
|
||||||
* Falls back to the env default (WAN_STANDARD_WINDOW_MINUTES) or 15.
|
* Falls back to the env default (WAN_STANDARD_WINDOW_MINUTES) or 1440
|
||||||
* Prisma's own upper bound seems to sit around 30 days for aiops
|
* (24h). Prisma's own upper bound seems to sit around 30 days for
|
||||||
* queries, but voice-quality signal degrades past a day of averaging
|
* aiops queries, but voice-quality signal degrades past a day of
|
||||||
* so we cap at 24h for now.
|
* averaging so we cap at 24h for now.
|
||||||
*/
|
*/
|
||||||
function normalizeWindowMinutes(m) {
|
function normalizeWindowMinutes(m) {
|
||||||
const raw = Number.isFinite(Number(m)) && Number(m) > 0
|
const raw = Number.isFinite(Number(m)) && Number(m) > 0
|
||||||
? Number(m)
|
? Number(m)
|
||||||
: Number(process.env.WAN_STANDARD_WINDOW_MINUTES) || 15;
|
: Number(process.env.WAN_STANDARD_WINDOW_MINUTES) || DEFAULT_WINDOW_MINUTES;
|
||||||
// Clamp to [1, 1440] (1 min .. 24 h).
|
// Clamp to [1, 1440] (1 min .. 24 h).
|
||||||
return Math.max(1, Math.min(1440, Math.floor(raw)));
|
return Math.max(1, Math.min(1440, Math.floor(raw)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,21 @@ objects, and hands them to the renderer + adaptive-card layer in
|
||||||
/voicediag <storeNum> default: hides OK checks, posts fixable cards
|
/voicediag <storeNum> default: hides OK checks, posts fixable cards
|
||||||
/voicediag <storeNum> detail include OK checks + expand every details block
|
/voicediag <storeNum> detail include OK checks + expand every details block
|
||||||
/voicediag <storeNum> --only dnd,callForwarding
|
/voicediag <storeNum> --only dnd,callForwarding
|
||||||
/voicediag <storeNum> --window 24h override the WAN look-back window (default 15m)
|
/voicediag <storeNum> --window 15m narrow the WAN look-back (default 24h)
|
||||||
/voicediag list-checks enumerate every registered check + its scope
|
/voicediag list-checks enumerate every registered check + its scope
|
||||||
```
|
```
|
||||||
|
|
||||||
HTTP path: `GET /voicediag?storeNum=<n>[&detailed=true][&only=dnd,callWaiting][&window=24h]`.
|
HTTP path: `GET /voicediag?storeNum=<n>[&detailed=true][&only=dnd,callWaiting][&window=15m]`.
|
||||||
|
|
||||||
`--window` accepts `Nm` / `Nh` / `Nd` shorthand (e.g. `15m`, `1h`, `6h`,
|
`--window` accepts `Nm` / `Nh` / `Nd` shorthand (e.g. `15m`, `1h`, `6h`,
|
||||||
`24h`, `1d`) or a bare integer of minutes. Applies to the WAN
|
`24h`, `1d`) or a bare integer of minutes. Applies to the WAN
|
||||||
healthscore + LQM (per-path latency/jitter/loss/MOS) fetches. Alarms
|
healthscore + LQM (per-path latency/jitter/loss/MOS) fetches. Alarms
|
||||||
are floored at 60m regardless — sub-hour alarm queries are usually
|
are floored at 60m regardless — sub-hour alarm queries are usually
|
||||||
too noisy to be actionable. Global default is
|
too noisy to be actionable. Global default is
|
||||||
`WAN_STANDARD_WINDOW_MINUTES` (env, defaults to 15).
|
`WAN_STANDARD_WINDOW_MINUTES` (env, defaults to 1440 / 24h). The
|
||||||
|
same default is used by the `/phonestatus` WAN follow-up (no CLI
|
||||||
|
override on that surface — set the env if you want a different value
|
||||||
|
globally).
|
||||||
HTTP callers get the markdown snapshot only — remediation cards are
|
HTTP callers get the markdown snapshot only — remediation cards are
|
||||||
chat-only.
|
chat-only.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -768,23 +768,26 @@ test('collectSdwanForStore: reports the effective window on the returned payload
|
||||||
});
|
});
|
||||||
setupSaseEnv(fake.baseUrl);
|
setupSaseEnv(fake.baseUrl);
|
||||||
try {
|
try {
|
||||||
// Default (no opts): 15 min (also the env default)
|
// Default (no opts): 24h — the shipping default optimised for
|
||||||
|
// "was the site healthy today" over "is it healthy right now".
|
||||||
|
// Override via WAN_STANDARD_WINDOW_MINUTES or --window on /voicediag.
|
||||||
let data = await collectSdwanForStore(782);
|
let data = await collectSdwanForStore(782);
|
||||||
|
assert.equal(data.window?.minutes, 1440);
|
||||||
|
assert.equal(data.window?.alarmMinutes, 1440,
|
||||||
|
'alarms match the requested window when it is >= 60m');
|
||||||
|
|
||||||
|
// Explicit 15m override → both windows go to 15/60 (alarm floor)
|
||||||
|
data = await collectSdwanForStore(782, { windowMinutes: 15 });
|
||||||
assert.equal(data.window?.minutes, 15);
|
assert.equal(data.window?.minutes, 15);
|
||||||
assert.equal(data.window?.alarmMinutes, 60,
|
assert.equal(data.window?.alarmMinutes, 60,
|
||||||
'alarms floor at 60m even when the requested window is smaller');
|
'alarms floor at 60m even when the requested window is smaller');
|
||||||
|
|
||||||
// Explicit 24h override → both windows go to 1440
|
|
||||||
data = await collectSdwanForStore(782, { windowMinutes: 1440 });
|
|
||||||
assert.equal(data.window?.minutes, 1440);
|
|
||||||
assert.equal(data.window?.alarmMinutes, 1440);
|
|
||||||
|
|
||||||
// Out-of-range values are clamped to [1, 1440]
|
// Out-of-range values are clamped to [1, 1440]
|
||||||
data = await collectSdwanForStore(782, { windowMinutes: 99999 });
|
data = await collectSdwanForStore(782, { windowMinutes: 99999 });
|
||||||
assert.equal(data.window?.minutes, 1440, 'capped at 24h');
|
assert.equal(data.window?.minutes, 1440, 'capped at 24h');
|
||||||
|
|
||||||
data = await collectSdwanForStore(782, { windowMinutes: -5 });
|
data = await collectSdwanForStore(782, { windowMinutes: -5 });
|
||||||
assert.equal(data.window?.minutes, 15, 'invalid → falls to env default');
|
assert.equal(data.window?.minutes, 1440, 'invalid → falls to env default (24h)');
|
||||||
} finally {
|
} finally {
|
||||||
await fake.close(); _resetSitesCache(); _resetPrismaAuthCache(); clearEnv();
|
await fake.close(); _resetSitesCache(); _resetPrismaAuthCache(); clearEnv();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue