@takagaki/cortex-decisions-viewer 0.4.58 → 0.4.59
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 +158 -6
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -828,6 +828,11 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
828
828
|
.ds-badges { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; flex: 0 0 auto; }
|
|
829
829
|
.badge.ds-gold { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
|
|
830
830
|
.badge.ds-excluded { background: #eceef1; color: #6b7280; }
|
|
831
|
+
/* 保存は済んでいるが、接続状況の再計算(1〜2分)がまだのもの。**確定と同じ見た目にしない**
|
|
832
|
+
——リロードで消えたときに「戻された」と読まれるため、点線で区別して (反映中) を添える。
|
|
833
|
+
**幅と色まで指定する。** border-style だけだと、border を持たない .ds-excluded 側は
|
|
834
|
+
初期値の medium(≈3px)/currentColor に解決され、押した向きによって太さ3倍・色が別物になる */
|
|
835
|
+
.badge.is-pending { border: 1px dashed currentColor; opacity: 0.85; }
|
|
831
836
|
.badge.ds-undeclared { background: #fff4e5; color: #9a6700; }
|
|
832
837
|
.ds-toggle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
|
|
833
838
|
.ds-toggle-btn { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--text); cursor: pointer; }
|
|
@@ -1787,8 +1792,14 @@ const CLIENT_JS = `
|
|
|
1787
1792
|
// **共通部品(stateToggle)に載せる。** 以前はここだけ変更者名をダイアログで聞いており、
|
|
1788
1793
|
// 同じカードの中で2つの流儀が並んでいた。送信・エラー表示・向きの反転も個別実装で、
|
|
1789
1794
|
// 片方だけ直したときにずれる状態だった。
|
|
1795
|
+
// **控えはボタンにも効かせる。** バッジだけに効かせると、リロード後に同じカードの中で
|
|
1796
|
+
// バッジが「対象です」・ボタンが「始めますか?」と逆を主張する。しかもその状態で
|
|
1797
|
+
// ボタンの表示どおりに押すと、サーバは既に同じ値なので changed:false を返し、
|
|
1798
|
+
// ボタンが永久に反転しない——**15分(TTL切れ)まで止められなくなる**。
|
|
1799
|
+
// stateToggle の doc が要件として書いている「戻す操作を必ず残す」を破る。
|
|
1800
|
+
var pendingNow = pendingGoldFor(s);
|
|
1790
1801
|
return stateToggle({
|
|
1791
|
-
on: s.gold === true,
|
|
1802
|
+
on: pendingNow ? pendingNow.value : s.gold === true,
|
|
1792
1803
|
labelOn: "Gold昇格を止める",
|
|
1793
1804
|
labelOff: "Gold昇格を始める",
|
|
1794
1805
|
confirmOn: [
|
|
@@ -1804,6 +1815,23 @@ const CLIENT_JS = `
|
|
|
1804
1815
|
build: function (next) {
|
|
1805
1816
|
return { op: "set-channel-gold", ref: s.ref, value: next };
|
|
1806
1817
|
},
|
|
1818
|
+
// 保存できた直後に、この行のバッジだけを「反映中」に差し替える。
|
|
1819
|
+
// 一覧そのものは触らない(結果メッセージや他行の状態を巻き込まないため)。
|
|
1820
|
+
pending: function (next, toggleEl) {
|
|
1821
|
+
// **表示済みの値に戻したなら「反映中」ではない。** ON→OFF と押し直した場合、
|
|
1822
|
+
// 画面が出している値と一致するので待つものが無い。ここで消さないと、
|
|
1823
|
+
// 何も保留していないのに (反映中) が残り、リロードで消えて不可解に見える。
|
|
1824
|
+
var back = next === (s.gold === true);
|
|
1825
|
+
if (back) clearPendingGold(s.ref);
|
|
1826
|
+
else markPendingGold(s.ref, next);
|
|
1827
|
+
var card = toggleEl && toggleEl.parentNode;
|
|
1828
|
+
var badges = card && card.querySelector(".ds-badges");
|
|
1829
|
+
if (!badges) return;
|
|
1830
|
+
var b = badges.querySelector(".badge.ds-gold, .badge.ds-excluded, .badge.ds-undeclared");
|
|
1831
|
+
var spec = pendingBadgeSpec(next, back);
|
|
1832
|
+
if (b) { b.className = spec.cls; b.textContent = spec.text; }
|
|
1833
|
+
else badges.insertBefore(el("span", { class: spec.cls, text: spec.text }), badges.firstChild);
|
|
1834
|
+
},
|
|
1807
1835
|
});
|
|
1808
1836
|
}
|
|
1809
1837
|
|
|
@@ -2098,9 +2126,14 @@ const CLIENT_JS = `
|
|
|
2098
2126
|
// 片方だけ直したときにずれる(既存の goldToggle で使っている作法をそのまま一般化したもの)。
|
|
2099
2127
|
|
|
2100
2128
|
/**
|
|
2101
|
-
*
|
|
2129
|
+
* サーバへ設定変更を送る。**一覧は書き換えない**
|
|
2102
2130
|
* (接続状況は夜間バッチのスナップショットなので、「もう効いている」と誤解させないため)。
|
|
2103
|
-
* 呼び出し側は onDone
|
|
2131
|
+
* 呼び出し側は onDone で自分のボタンの向きを反転させる。
|
|
2132
|
+
*
|
|
2133
|
+
* **例外はバッジの先出し。** opts.pending を持つ操作(いまは Gold の切り替えだけ)は、
|
|
2134
|
+
* 押した内容を「(反映中)」として先に出す。出してよいのは (1) 保存の成否がサーバで
|
|
2135
|
+
* 確定していて推測が入らない (2) **確定した表示と見た目で区別できる**、の両方を満たす
|
|
2136
|
+
* ときだけ。区別しないと、リロードで消えたときに「戻された」と読まれる。
|
|
2104
2137
|
*/
|
|
2105
2138
|
async function sendConfig(payload, msg, btn, onDone) {
|
|
2106
2139
|
var intake = SITE.intake;
|
|
@@ -2167,7 +2200,9 @@ const CLIENT_JS = `
|
|
|
2167
2200
|
|
|
2168
2201
|
/**
|
|
2169
2202
|
* ON/OFF のトグル。**戻す操作を必ず残す**(誤って有効にしたとき翌朝まで止められないのは許容できない)。
|
|
2170
|
-
* @param opts { on, labelOn, labelOff, confirmOn, build(next) }
|
|
2203
|
+
* @param opts { on, labelOn, labelOff, confirmOn, build(next), pending(next, wrapEl) }
|
|
2204
|
+
* pending … 保存できた直後に、その行のバッジだけを「(反映中)」へ差し替える任意のフック。
|
|
2205
|
+
* 持たない操作は従来どおりボタンの向きだけを反転する(バッジは触らない)。
|
|
2171
2206
|
*/
|
|
2172
2207
|
function stateToggle(opts) {
|
|
2173
2208
|
var wrap = el("div", { class: "ds-toggle" });
|
|
@@ -2185,9 +2220,12 @@ const CLIENT_JS = `
|
|
|
2185
2220
|
// 読み始める方向(危険)だけ確認する。止める方向は素通し
|
|
2186
2221
|
if (next && opts.confirmOn && !confirmDanger(opts.confirmOn)) { msg.textContent = ""; return; }
|
|
2187
2222
|
sendConfig(opts.build(next), msg, btn, function () {
|
|
2188
|
-
// **押した内容をボタンに反映し、逆向きの操作を残す。**
|
|
2223
|
+
// **押した内容をボタンに反映し、逆向きの操作を残す。**
|
|
2189
2224
|
on = next;
|
|
2190
2225
|
paint();
|
|
2226
|
+
// バッジは「反映中」として先出しする(opts.pending がある操作だけ)。
|
|
2227
|
+
// **カードごと作り直さない。** 上の msg に出した結果表示が消えてしまう
|
|
2228
|
+
if (opts.pending) opts.pending(next, wrap);
|
|
2191
2229
|
});
|
|
2192
2230
|
});
|
|
2193
2231
|
wrap.appendChild(btn);
|
|
@@ -2281,11 +2319,125 @@ const CLIENT_JS = `
|
|
|
2281
2319
|
return box;
|
|
2282
2320
|
}
|
|
2283
2321
|
|
|
2322
|
+
/* ---------------------------------------------------------------------------
|
|
2323
|
+
* Gold切り替えの「反映中」表示。
|
|
2324
|
+
*
|
|
2325
|
+
* 押しても一覧のバッジが変わるのは fleet-status.yml が完走したあと(1〜2分)。
|
|
2326
|
+
* その間バッジが古いままだと「押したのに何も起きない」に見える。
|
|
2327
|
+
*
|
|
2328
|
+
* **推測ではない。** Lambda は書き込む前に Bot の在籍を確認し、ダメなら 400 を返すので、
|
|
2329
|
+
* 200 が返った時点で channels.json は確実に書き換わっている。ここで出すのは
|
|
2330
|
+
* 「確定した事実の先出し」であって楽観的な当て推量ではない。
|
|
2331
|
+
*
|
|
2332
|
+
* **確定したバッジと同じ見た目にはしない。** 30秒後にリロードした人には、付けたはずの
|
|
2333
|
+
* ラベルが消えて見える——「変えたはずなのに違う値が出ている」という、この画面が
|
|
2334
|
+
* 繰り返し警告してきた誤読そのものになる。だから (反映中) を付けて区別し、
|
|
2335
|
+
* さらに sessionStorage に持たせてリロードしても残す。
|
|
2336
|
+
*
|
|
2337
|
+
* 消えるのは「fleet-status が追いついたとき」か「TTL 切れ」。TTL が無いと、
|
|
2338
|
+
* ワークフローが落ちている案件で永久に「反映中」が残り、嘘の表示になる。
|
|
2339
|
+
* ------------------------------------------------------------------------- */
|
|
2340
|
+
// **キーは案件別にする。** Slackの共有チャンネルは複数案件のCortexに同時に載りうるので、
|
|
2341
|
+
// 案件をまたぐキーにすると、案件Aで押した「反映中」が案件Bの同じ行に出る
|
|
2342
|
+
// (チュートリアルの既読キーが同じ理由で案件別になっている)。
|
|
2343
|
+
var PENDING_KEY = "cortex.pendingGold:" + ((SITE.intake && SITE.intake.projectKey) || "");
|
|
2344
|
+
var PENDING_TTL_MS = 15 * 60 * 1000;
|
|
2345
|
+
/**
|
|
2346
|
+
* 保存する形: { "<チャンネルID>": { value: boolean, at: number } }
|
|
2347
|
+
* at が無い/0 のエントリは期限切れとして落とす。
|
|
2348
|
+
* **型では守れない**(このコードは render.ts のテンプレートリテラル内で、tsc が見ない)。
|
|
2349
|
+
* 守るのはテストの仕事。
|
|
2350
|
+
*/
|
|
2351
|
+
|
|
2352
|
+
/**
|
|
2353
|
+
* 先出しバッジの見た目。**1箇所に集約する**——文言とクラスを2箇所に書くと片方だけ直る。
|
|
2354
|
+
*
|
|
2355
|
+
* **「反映中」とは書かない。** この画面の既存語彙では「反映」が2つの意味で使われていて、
|
|
2356
|
+
* 短いほう(数分)には必ず「一覧への」が付いている(サーバ文言「次回の Gold 昇格
|
|
2357
|
+
* (毎晩22時ごろ)から反映されます。一覧への反映には数分かかります」)。修飾なしの
|
|
2358
|
+
* 「反映中」だと、22時の蒸留の話と読めてしまう。
|
|
2359
|
+
*
|
|
2360
|
+
* とくに OFF 方向で害が大きい。**止めた時点で蒸留に対しては完全に効いている**
|
|
2361
|
+
* (Slack はミラーせず Gold 昇格が直接読むため)。待っているのは画面だけなので、
|
|
2362
|
+
* 「除外(反映中)」は「まだ除外されていない」と逆に読める。
|
|
2363
|
+
*/
|
|
2364
|
+
function pendingBadgeSpec(value, settled) {
|
|
2365
|
+
return {
|
|
2366
|
+
cls: "badge " + (value ? "ds-gold" : "ds-excluded") + (settled ? "" : " is-pending"),
|
|
2367
|
+
text: (value ? "Gold昇格の読み取り対象" : "除外") + (settled ? "" : "(画面反映待ち)"),
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function readPending() {
|
|
2372
|
+
try {
|
|
2373
|
+
var raw = sessionStorage.getItem(PENDING_KEY);
|
|
2374
|
+
if (!raw) return {};
|
|
2375
|
+
var obj = JSON.parse(raw);
|
|
2376
|
+
if (!obj || typeof obj !== "object") return {};
|
|
2377
|
+
var now = Date.now();
|
|
2378
|
+
var out = {};
|
|
2379
|
+
for (var k in obj) {
|
|
2380
|
+
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
|
|
2381
|
+
var e = obj[k];
|
|
2382
|
+
if (e && typeof e === "object" && now - (e.at || 0) < PENDING_TTL_MS) out[k] = e;
|
|
2383
|
+
}
|
|
2384
|
+
return out;
|
|
2385
|
+
} catch (_) {
|
|
2386
|
+
// sessionStorage が使えない環境(プライベートモード等)でも画面は出す
|
|
2387
|
+
return {};
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
function writePending(map) {
|
|
2391
|
+
try { sessionStorage.setItem(PENDING_KEY, JSON.stringify(map)); } catch (_) {}
|
|
2392
|
+
}
|
|
2393
|
+
function clearPendingGold(ref) {
|
|
2394
|
+
if (!ref) return;
|
|
2395
|
+
var map = readPending();
|
|
2396
|
+
if (!(ref in map)) return;
|
|
2397
|
+
delete map[ref];
|
|
2398
|
+
writePending(map);
|
|
2399
|
+
}
|
|
2400
|
+
/** 押した内容を控える。fleet-status が追いつくまでの間だけ効く */
|
|
2401
|
+
function markPendingGold(ref, value) {
|
|
2402
|
+
if (!ref) return;
|
|
2403
|
+
var map = readPending();
|
|
2404
|
+
map[ref] = { value: value === true, at: Date.now() };
|
|
2405
|
+
writePending(map);
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* その行の「反映中」を引く。**実データが追いついていたら控えを捨てる。**
|
|
2409
|
+
* 捨てないと、正しくなった後も (反映中) が残り続ける。
|
|
2410
|
+
*/
|
|
2411
|
+
function pendingGoldFor(s) {
|
|
2412
|
+
if (!s || !s.ref) return null;
|
|
2413
|
+
var map = readPending();
|
|
2414
|
+
var e = map[s.ref];
|
|
2415
|
+
if (!e) return null;
|
|
2416
|
+
// **「値が一致したら捨てる」だけでは足りない。** 別の人が直後に逆を押して、
|
|
2417
|
+
// その結果で追いついた場合、値が一致しないので控えが残り、TTL切れまで
|
|
2418
|
+
// 実際とは違う値を出し続ける。**押した時点より新しいスナップショットが来たら、
|
|
2419
|
+
// 値が何であれ控えは用済み**——サーバの答えのほうが新しいので、そちらを信じる。
|
|
2420
|
+
var snapshot = fleetVal("generatedAt");
|
|
2421
|
+
var snapAt = snapshot ? Date.parse(snapshot) : NaN;
|
|
2422
|
+
var stale = !isNaN(snapAt) && snapAt >= (e.at || 0);
|
|
2423
|
+
if (s.gold === e.value || stale) {
|
|
2424
|
+
delete map[s.ref];
|
|
2425
|
+
writePending(map);
|
|
2426
|
+
return null;
|
|
2427
|
+
}
|
|
2428
|
+
return e;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2284
2431
|
function extSourceCard(s, meta) {
|
|
2285
2432
|
var badges = el("div", { class: "ds-badges" });
|
|
2433
|
+
var pending = pendingGoldFor(s);
|
|
2286
2434
|
// 「意図的に外した」と「宣言し忘れ」を分けて出す。同じ「除外」と書くと、
|
|
2287
2435
|
// 判断されていないだけのものが「判断済み」に見えてしまう。
|
|
2288
|
-
if (
|
|
2436
|
+
if (pending) {
|
|
2437
|
+
var spec = pendingBadgeSpec(pending.value, false);
|
|
2438
|
+
badges.appendChild(el("span", { class: spec.cls, text: spec.text }));
|
|
2439
|
+
}
|
|
2440
|
+
else if (s.gold === true) badges.appendChild(el("span", { class: "badge ds-gold", text: "Gold昇格の読み取り対象" }));
|
|
2289
2441
|
else if (s.goldState === "undeclared") badges.appendChild(el("span", { class: "badge ds-undeclared", text: "未宣言(対象外)" }));
|
|
2290
2442
|
else if (s.gold === false) badges.appendChild(el("span", { class: "badge ds-excluded", text: "除外" }));
|
|
2291
2443
|
if (s.notify === true) badges.appendChild(el("span", { class: "badge ds-notify", text: "通知先" }));
|
package/package.json
CHANGED