@takagaki/cortex-decisions-viewer 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/render.js +121 -6
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -6,6 +6,8 @@ exports.livePending = livePending;
|
|
|
6
6
|
exports.nextCheckDelay = nextCheckDelay;
|
|
7
7
|
exports.settledPending = settledPending;
|
|
8
8
|
exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
|
|
9
|
+
exports.sanitizeStateNow = sanitizeStateNow;
|
|
10
|
+
exports.overlayStateOnFleet = overlayStateOnFleet;
|
|
9
11
|
exports.chatRowsToAdd = chatRowsToAdd;
|
|
10
12
|
exports.devRowsToAdd = devRowsToAdd;
|
|
11
13
|
exports.designRowsToAdd = designRowsToAdd;
|
|
@@ -564,6 +566,66 @@ function sanitizeRegisteredNow(v) {
|
|
|
564
566
|
}
|
|
565
567
|
return any ? out : null;
|
|
566
568
|
}
|
|
569
|
+
/** 接続状態ストアの語彙(サーバ側 connection-state.mjs と同じ。増やさない) */
|
|
570
|
+
const STATE_STATUSES = {
|
|
571
|
+
reachable: ["ok", "not_in_channel", "missing_scope", "no_token", "unreachable", "unknown"],
|
|
572
|
+
ingested: ["ok", "failed", "skipped"],
|
|
573
|
+
};
|
|
574
|
+
const STATE_ROW_FIELDS = ["cap", "ref", "workspace", "question", "status", "at", "by", "needed", "lastOkAt", "lastFailAt"];
|
|
575
|
+
/** stateNow はライブ専用キーで build.ts の網にかからない。画面側でも許可リストを通す */
|
|
576
|
+
function sanitizeStateNow(v) {
|
|
577
|
+
if (!Array.isArray(v))
|
|
578
|
+
return null;
|
|
579
|
+
const out = [];
|
|
580
|
+
for (const r of v) {
|
|
581
|
+
if (out.length >= 500)
|
|
582
|
+
break;
|
|
583
|
+
if (r == null || typeof r !== "object" || Array.isArray(r))
|
|
584
|
+
continue;
|
|
585
|
+
const item = {};
|
|
586
|
+
for (const f of STATE_ROW_FIELDS) {
|
|
587
|
+
const fv = r[f];
|
|
588
|
+
if (typeof fv === "string" && fv !== "")
|
|
589
|
+
item[f] = fv;
|
|
590
|
+
}
|
|
591
|
+
const vocab = STATE_STATUSES[item.question];
|
|
592
|
+
if (!vocab || vocab.indexOf(item.status) < 0)
|
|
593
|
+
continue; // 知らない語は「不明」に化けさせず落とす
|
|
594
|
+
if (!item.cap || !item.ref || !item.at)
|
|
595
|
+
continue;
|
|
596
|
+
out.push(item);
|
|
597
|
+
}
|
|
598
|
+
return out.length ? out : null;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* ストアの「届くか」を行に重ねる。**まとめ表より新しいときだけ**——古い確認結果で
|
|
602
|
+
* 新しい実測を潰さない(時刻の比較は ISO 文字列の辞書順で足りる)。
|
|
603
|
+
* 対象はチャットの行だけ(段A。確認ボタンが書くのがチャットだけのため)。
|
|
604
|
+
*/
|
|
605
|
+
function overlayStateOnFleet(view, rows, generatedAt) {
|
|
606
|
+
const v = view || {};
|
|
607
|
+
if (!rows || !rows.length || !Array.isArray(v.externalSources))
|
|
608
|
+
return v;
|
|
609
|
+
const byKey = {};
|
|
610
|
+
for (const r of rows) {
|
|
611
|
+
if (r.cap !== "chat" || r.question !== "reachable")
|
|
612
|
+
continue;
|
|
613
|
+
byKey[String(r.workspace || "") + "\t" + String(r.ref)] = r;
|
|
614
|
+
}
|
|
615
|
+
let changed = false;
|
|
616
|
+
const externals = v.externalSources.map((s) => {
|
|
617
|
+
if (!s || s.type !== "slack" || !s.ref)
|
|
618
|
+
return s;
|
|
619
|
+
const hit = byKey[String(s.workspace || "") + "\t" + String(s.ref)];
|
|
620
|
+
if (!hit)
|
|
621
|
+
return s;
|
|
622
|
+
if (generatedAt && hit.at <= generatedAt)
|
|
623
|
+
return s;
|
|
624
|
+
changed = true;
|
|
625
|
+
return Object.assign({}, s, { gate: hit.status, gateAt: hit.at, gateBy: hit.by });
|
|
626
|
+
});
|
|
627
|
+
return changed ? Object.assign({}, v, { externalSources: externals }) : v;
|
|
628
|
+
}
|
|
567
629
|
/**
|
|
568
630
|
* チャットの行で、まとめ表がまだ知らないもの。
|
|
569
631
|
*
|
|
@@ -1830,6 +1892,7 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1830
1892
|
/* 全組織が接続済みのときに、トークン入力欄の代わりに出す1行(#118) */
|
|
1831
1893
|
.ds-all-connected { color: var(--muted); }
|
|
1832
1894
|
.ds-gate { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; flex: 0 0 auto; }
|
|
1895
|
+
.ds-gate-at { font-size: 11px; color: var(--muted); flex: 0 0 auto; }
|
|
1833
1896
|
.ds-gate::before { content: ""; width: 8px; height: 8px; border-radius: 999px; background: currentColor; }
|
|
1834
1897
|
.ds-gate-ok { color: #1f7a45; }
|
|
1835
1898
|
.ds-gate-warn { color: #b45309; }
|
|
@@ -2124,6 +2187,10 @@ ${internalOf.toString()}
|
|
|
2124
2187
|
${registeredRowsToAdd.toString()}
|
|
2125
2188
|
${unsettledRegisteredCount.toString()}
|
|
2126
2189
|
${mergeRegisteredFleet.toString()}
|
|
2190
|
+
var STATE_STATUSES = ${JSON.stringify(STATE_STATUSES)};
|
|
2191
|
+
var STATE_ROW_FIELDS = ${JSON.stringify(STATE_ROW_FIELDS)};
|
|
2192
|
+
${sanitizeStateNow.toString()}
|
|
2193
|
+
${overlayStateOnFleet.toString()}
|
|
2127
2194
|
${nextStateDelay.toString()}
|
|
2128
2195
|
// 「はじめに」セクションのチュートリアルデータ(ビルド時に埋め込み。ボタンURL・チャレンジ4の決定は解決済み)
|
|
2129
2196
|
var TUTORIAL = (function () {
|
|
@@ -3520,6 +3587,8 @@ ${nextStateDelay.toString()}
|
|
|
3520
3587
|
* 指すのか説明できなくなる(realtime を別に持っているのと同じ理由)。
|
|
3521
3588
|
*/
|
|
3522
3589
|
var LIVE_REGISTERED = null;
|
|
3590
|
+
// 接続状態ストアの行(届くか・取り込めたか)。op=state の stateNow から
|
|
3591
|
+
var LIVE_STATE = null;
|
|
3523
3592
|
/** マージ済みの3キー(作り直しは setLive* だけが行う) */
|
|
3524
3593
|
var MERGED = null;
|
|
3525
3594
|
/** マージの対象になるキー。これ以外は素の値をそのまま返す */
|
|
@@ -3547,8 +3616,16 @@ ${nextStateDelay.toString()}
|
|
|
3547
3616
|
* 足す場所を描画側に散らすと「タイルは1件・一覧は2件」のような食い違いが必ず出る。
|
|
3548
3617
|
*/
|
|
3549
3618
|
function fleetVal(key) {
|
|
3550
|
-
if (LIVE_REGISTERED && MERGE_KEYS[key]) {
|
|
3551
|
-
|
|
3619
|
+
if ((LIVE_REGISTERED || LIVE_STATE) && MERGE_KEYS[key]) {
|
|
3620
|
+
// 登録の追加 → 状態の上書き、の順で1回だけ合成する。
|
|
3621
|
+
// 状態ストアの答えは、まとめ表より新しいときだけ勝つ(overlayStateOnFleet が判定)
|
|
3622
|
+
if (!MERGED) {
|
|
3623
|
+
MERGED = overlayStateOnFleet(
|
|
3624
|
+
mergeRegisteredFleet(rawView(), LIVE_REGISTERED),
|
|
3625
|
+
LIVE_STATE,
|
|
3626
|
+
String(rawFleetVal("generatedAt") || ""),
|
|
3627
|
+
);
|
|
3628
|
+
}
|
|
3552
3629
|
return MERGED[key];
|
|
3553
3630
|
}
|
|
3554
3631
|
return rawFleetVal(key);
|
|
@@ -3599,10 +3676,19 @@ ${nextStateDelay.toString()}
|
|
|
3599
3676
|
* 変わったとき(例えば pending の語をやめたとき)に「接続OK」が復活しないようにする。
|
|
3600
3677
|
*/
|
|
3601
3678
|
function stateCtx() {
|
|
3679
|
+
// 🔴 **ストアの上書き(overlayStateOnFleet)だけは適用する。** 行の一覧は fleetVal
|
|
3680
|
+
// (上書き済み)で描くので、ここが素のままだと「行は接続OK・タイルは⚠️」という
|
|
3681
|
+
// 同じ画面での食い違いが出る(実装レビューで指摘)。registeredNow の**行の追加**を
|
|
3682
|
+
// 混ぜない、という上の注意はそのまま——追加はここでは適用しない
|
|
3683
|
+
var ov = overlayStateOnFleet(
|
|
3684
|
+
{ externalSources: rawFleetVal("externalSources") || [] },
|
|
3685
|
+
LIVE_STATE,
|
|
3686
|
+
String(rawFleetVal("generatedAt") || ""),
|
|
3687
|
+
);
|
|
3602
3688
|
return {
|
|
3603
3689
|
capabilities: rawFleetVal("capabilities") || {},
|
|
3604
3690
|
internals: rawFleetVal("internalSources") || [],
|
|
3605
|
-
externals:
|
|
3691
|
+
externals: ov.externalSources || [],
|
|
3606
3692
|
pipes: rawFleetVal("pipelines") || [],
|
|
3607
3693
|
};
|
|
3608
3694
|
}
|
|
@@ -5107,6 +5193,9 @@ ${nextStateDelay.toString()}
|
|
|
5107
5193
|
var v = probeVerdict(s, j);
|
|
5108
5194
|
gateEl.className = "ds-gate " + v.cls;
|
|
5109
5195
|
gateEl.textContent = v.text;
|
|
5196
|
+
// 古い「X分前に確認」を残さない(下に出るいまの結果と、時刻が2つ並ぶ)
|
|
5197
|
+
var oldAt = card.querySelector(".ds-gate-at");
|
|
5198
|
+
if (oldAt) oldAt.remove();
|
|
5110
5199
|
// 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
|
|
5111
5200
|
// 本文へ落とすと、いちばん要る情報が説明文に埋もれる
|
|
5112
5201
|
if (j.gate === "missing_scope") {
|
|
@@ -6081,7 +6170,7 @@ ${nextStateDelay.toString()}
|
|
|
6081
6170
|
// 返ってきて**足りない行として一覧に出る**——さらに「まとめ表がまだ答えていない行」が
|
|
6082
6171
|
// できるので、まとめ表が追いつくまで取り直しが続く。
|
|
6083
6172
|
// 取得中なら何もしない(応答後に catchUpOpenCaps が拾う)
|
|
6084
|
-
if (j.changed !== false) loadLiveFleet();
|
|
6173
|
+
if (j.changed !== false) loadLiveFleet(true);
|
|
6085
6174
|
// **何も変わっていないなら「追加した」と見せない。** 既に同じものが入っていた場合
|
|
6086
6175
|
// サーバは 200 + changed:false を返す。ここを res.ok だけで判定すると
|
|
6087
6176
|
// 「変更はありません」と言いながら追加した行が出る
|
|
@@ -6530,6 +6619,10 @@ ${nextStateDelay.toString()}
|
|
|
6530
6619
|
// **「接続を確認」が押されたら、この要素を live の判定で置き換える**(probeSection)。
|
|
6531
6620
|
// 2つの判定を同じ行に並べないため、参照を持っておく
|
|
6532
6621
|
var gateEl = el("span", { class: "ds-gate " + gate.cls, text: gate.text });
|
|
6622
|
+
// 状態ストア由来の判定には「いつ確かめたか」を必ず添える
|
|
6623
|
+
var gateAtEl = s.gateAt
|
|
6624
|
+
? el("span", { class: "ds-gate-at", text: rtAgo(s.gateAt) + "に確認" })
|
|
6625
|
+
: null;
|
|
6533
6626
|
var card = el("div", { class: "ds-card" }, [
|
|
6534
6627
|
el("div", { class: "ds-card-main" }, [
|
|
6535
6628
|
el("span", { class: "ds-icon", text: meta.icon }),
|
|
@@ -6540,6 +6633,7 @@ ${nextStateDelay.toString()}
|
|
|
6540
6633
|
badges,
|
|
6541
6634
|
gateEl,
|
|
6542
6635
|
]);
|
|
6636
|
+
if (gateAtEl) card.appendChild(gateAtEl);
|
|
6543
6637
|
// チャンネルの登録解除。書き込み経路(intake Lambda)がある案件でだけ出す。
|
|
6544
6638
|
// **Slack だけ。** github は登録ファイルが別(dev-sources.json)で、下のトグルと
|
|
6545
6639
|
// 「登録から削除」が担う。
|
|
@@ -6805,7 +6899,19 @@ ${nextStateDelay.toString()}
|
|
|
6805
6899
|
* (しかも次の取り直しは未確定の行が無いので張られない=二度と追いつかない)。
|
|
6806
6900
|
*/
|
|
6807
6901
|
var liveFleetSentCaps = [];
|
|
6808
|
-
|
|
6902
|
+
/**
|
|
6903
|
+
* 次の取得で据え置きを迂回するか(保存が効いた直後の1回だけ)。
|
|
6904
|
+
*
|
|
6905
|
+
* 🔴 **通常のポーリングには付けない。** サーバの60秒の据え置きは、GitHub の枠を
|
|
6906
|
+
* 守るために効かせたままにする(枠は組織で共有していて、枯渇すると設定変更・
|
|
6907
|
+
* Amplifyビルド・Backlog同期が同時に止まる)。
|
|
6908
|
+
*
|
|
6909
|
+
* **取得中に保存が終わった分を取りこぼさない。** 取得中の loadLiveFleet は何もせずに
|
|
6910
|
+
* 戻るので、旗として残し、応答後にもう一度取りに行く
|
|
6911
|
+
*/
|
|
6912
|
+
var freshWanted = false;
|
|
6913
|
+
function loadLiveFleet(fresh) {
|
|
6914
|
+
if (fresh) freshWanted = true;
|
|
6809
6915
|
if (liveFleetRequested) return;
|
|
6810
6916
|
var intake = SITE.intake;
|
|
6811
6917
|
if (!intake) return;
|
|
@@ -6814,13 +6920,17 @@ ${nextStateDelay.toString()}
|
|
|
6814
6920
|
if (typeof fetch !== "function") return;
|
|
6815
6921
|
liveFleetRequested = true;
|
|
6816
6922
|
liveFleetSentCaps = OPEN_CAPS.slice();
|
|
6923
|
+
var useFresh = freshWanted;
|
|
6924
|
+
freshWanted = false;
|
|
6817
6925
|
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
6818
6926
|
fetch(pfx + "op=state&t=" + encodeURIComponent(intake.token), {
|
|
6819
6927
|
method: "POST",
|
|
6820
6928
|
headers: { "content-type": "application/json" },
|
|
6821
6929
|
// **開いている能力を伝える。** 知らない値は Lambda 側が黙って捨てる(断らない)ので、
|
|
6822
6930
|
// 画面と Lambda の版が食い違っても接続状況の表示は消えない
|
|
6823
|
-
|
|
6931
|
+
// **保存直後だけ据え置きを迂回させる。** これが無いと、サーバが保存前の答えを返し、
|
|
6932
|
+
// 足りない行が0件になって取り直しも張られない=手で再読み込みするまで追いつかない
|
|
6933
|
+
body: JSON.stringify(useFresh ? { caps: OPEN_CAPS, fresh: true } : { caps: OPEN_CAPS }),
|
|
6824
6934
|
})
|
|
6825
6935
|
// **非2xxも失敗として扱う。** ビューアとLambdaの版は独立に進むので、
|
|
6826
6936
|
// op=state 未対応のLambdaは 400 を返す。通信エラーだけを見ていると空表示になる。
|
|
@@ -6833,6 +6943,7 @@ ${nextStateDelay.toString()}
|
|
|
6833
6943
|
// (=立ち上げ直後=この仕組みが一番効く案件)は下で throw するので、
|
|
6834
6944
|
// ここより後に置くと**その案件だけ素通り**する
|
|
6835
6945
|
LIVE_REGISTERED = sanitizeRegisteredNow(j && j.registeredNow);
|
|
6946
|
+
LIVE_STATE = sanitizeStateNow(j && j.stateNow);
|
|
6836
6947
|
MERGED = null;
|
|
6837
6948
|
if (!j || !j.fleet) throw new Error("no fleet");
|
|
6838
6949
|
LIVE_FLEET = normalizeFleet(j.fleet);
|
|
@@ -6847,6 +6958,8 @@ ${nextStateDelay.toString()}
|
|
|
6847
6958
|
repaintOpenLists();
|
|
6848
6959
|
// まだ足りない行があるなら取り直しを続ける(無くなったら止める)
|
|
6849
6960
|
refreshStateTimer();
|
|
6961
|
+
// 取得中に保存が効いていたら、迂回付きでもう一度取りに行く
|
|
6962
|
+
if (freshWanted) loadLiveFleet();
|
|
6850
6963
|
catchUpOpenCaps();
|
|
6851
6964
|
})
|
|
6852
6965
|
.catch(function () {
|
|
@@ -6861,6 +6974,8 @@ ${nextStateDelay.toString()}
|
|
|
6861
6974
|
// **失敗しても取り直しは続ける**(期限が来れば止まる)。ここで止めると、
|
|
6862
6975
|
// 一時的な失敗のあと画面が二度と追いつかない
|
|
6863
6976
|
refreshStateTimer();
|
|
6977
|
+
// 取得中に保存が効いていたら、迂回付きでもう一度取りに行く
|
|
6978
|
+
if (freshWanted) loadLiveFleet();
|
|
6864
6979
|
catchUpOpenCaps();
|
|
6865
6980
|
});
|
|
6866
6981
|
}
|