@takagaki/cortex-decisions-viewer 0.4.68 → 0.4.70
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/build.js +33 -1
- package/dist/render.js +383 -65
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -260,11 +260,42 @@ async function readFleetSources(repoRoot) {
|
|
|
260
260
|
if (!Object.keys(tools).length)
|
|
261
261
|
tools = undefined;
|
|
262
262
|
}
|
|
263
|
+
// 能力ごとの導出済みの状態(#125①が載せ、#125②で画面の正本になった)。
|
|
264
|
+
//
|
|
265
|
+
// **列挙し忘れは静かに消える。** このマッピングは素通しではなく明示の許可リストで、
|
|
266
|
+
// 落ちても例外も型エラーも出ない——画面は「capabilities が無い古いデータ」と解釈して
|
|
267
|
+
// 旧表示へ劣化するので、**壊れているようには見えない**。origin / figmaFiles /
|
|
268
|
+
// driveFolderIds で3回踏んでいる罠なので、実 build を通した配線テストで見張っている。
|
|
269
|
+
//
|
|
270
|
+
// **知らないフィールドは黙って捨てる。** engine が adapters[] / byTargets を足す余地が
|
|
271
|
+
// ある(#107 / #127)が、読み方を「知っているキーだけ」に留めておけば、増えた回に
|
|
272
|
+
// 画面が壊れることはない。使うようになった時点でここへ足す。
|
|
273
|
+
let capabilities;
|
|
274
|
+
if (obj.capabilities != null && typeof obj.capabilities === "object" && !Array.isArray(obj.capabilities)) {
|
|
275
|
+
const out = {};
|
|
276
|
+
for (const [cap, v] of Object.entries(obj.capabilities)) {
|
|
277
|
+
if (v == null || typeof v !== "object" || Array.isArray(v))
|
|
278
|
+
continue;
|
|
279
|
+
const c = v;
|
|
280
|
+
// **state が無い要素は通さない。** 状態を持たない能力を渡すと、画面が
|
|
281
|
+
// 「導出はあるが答えていない」という、どちらでもない状態を描くことになる
|
|
282
|
+
const state = c.state != null ? String(c.state) : "";
|
|
283
|
+
if (!state)
|
|
284
|
+
continue;
|
|
285
|
+
out[cap] = {
|
|
286
|
+
adapter: c.adapter != null ? String(c.adapter) : "",
|
|
287
|
+
state,
|
|
288
|
+
detail: c.detail != null ? String(c.detail) : undefined,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (Object.keys(out).length)
|
|
292
|
+
capabilities = out;
|
|
293
|
+
}
|
|
263
294
|
const fleetGeneratedAt = typeof obj.generatedAt === "string" ? obj.generatedAt : undefined;
|
|
264
295
|
const secretSources = obj.secretSources && typeof obj.secretSources === "object"
|
|
265
296
|
? obj.secretSources
|
|
266
297
|
: undefined;
|
|
267
|
-
return { externalSources, internalSources, pipelines, tools, fleetGeneratedAt, secretSources, checks, githubOwners };
|
|
298
|
+
return { externalSources, internalSources, pipelines, tools, capabilities, fleetGeneratedAt, secretSources, checks, githubOwners };
|
|
268
299
|
}
|
|
269
300
|
/** パスがディレクトリとして存在するか */
|
|
270
301
|
async function isDirectory(dirAbs) {
|
|
@@ -1145,6 +1176,7 @@ async function build(opts) {
|
|
|
1145
1176
|
internalSources: fleet.internalSources,
|
|
1146
1177
|
pipelines: fleet.pipelines,
|
|
1147
1178
|
tools: fleet.tools,
|
|
1179
|
+
capabilities: fleet.capabilities,
|
|
1148
1180
|
checks: fleet.checks,
|
|
1149
1181
|
githubOwners: fleet.githubOwners,
|
|
1150
1182
|
};
|
package/dist/render.js
CHANGED
|
@@ -82,6 +82,47 @@ const ADAPTER_ICONS = {
|
|
|
82
82
|
backlog: `<svg width="20" height="20" aria-hidden="true" viewBox="0 0 142 142" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Nulab-logos-" transform="translate(-1348.000000, -2619.000000)"> <g id="Group-7" transform="translate(1348.000000, 2619.000000)"> <path d="M112.518975,141.27641 L29.5477542,141.27641 C13.8226507,141.27641 0.955380933,128.389559 0.955380933,112.638806 L0.955380933,29.5308457 C0.955380933,13.7800933 13.8226507,0.893241768 29.5477542,0.893241768 L112.518975,0.893241768 C128.244079,0.893241768 141.109945,13.7800933 141.109945,29.5308457 L141.109945,112.638806 C141.109945,128.389559 128.244079,141.27641 112.518975,141.27641" id="Fill-16" fill="#42CE9F"></path> <path d="M60.1988625,61.3236616 C60.1441201,72.9746913 60.1146434,84.3121962 60.1441201,91.0958596 C70.2756738,90.5461368 75.2937266,88.482216 79.2632519,83.7132655 C82.0775724,80.3319784 82.686757,76.4586121 80.9799172,72.8073843 C78.7467083,68.0328101 72.1804284,62.9897012 60.1988625,61.3236616 M53.5357306,104.795345 L53.5357306,104.795345 C51.7348462,104.795345 50.0069517,104.075503 48.7380511,102.796097 L48.7352438,102.793285 C46.7434629,100.784196 46.7153899,98.8622754 46.6648584,95.3741368 C46.6395927,93.6504536 46.6241525,91.2083348 46.6171343,88.2896022 C46.6059051,83.1719784 46.6185379,76.0705725 46.6536292,67.1808101 C46.7153899,51.8855824 46.8262783,36.6381566 46.8262783,36.6381566 L60.3448422,36.7365725 C60.3195765,40.1150477 60.2957144,43.8295428 60.2718523,47.705721 C76.2678604,49.5531269 88.283114,56.4998794 93.2225623,67.065523 C97.1640146,75.4941368 95.8263351,84.9589289 89.6474629,92.3837012 C80.1166718,103.836493 66.5812641,104.795345 53.5357306,104.795345" id="Fill-18" fill="#FFFFFF"></path> </g> </g> </g> </svg>`,
|
|
83
83
|
github: GITHUB_ICON,
|
|
84
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* **アダプタのカタログ(能力→この製品に存在するアダプタ)。#125②**
|
|
87
|
+
*
|
|
88
|
+
* これは「この案件が何を使っているか」ではなく「製品として何があるか」の定義。
|
|
89
|
+
* 案件ごとの状態は engine が設定ファイルから導出して `capabilities` に載せる
|
|
90
|
+
* (宣言 `tools` は廃止した。宣言と実態は必ずずれる——配線ゼロの Teams が
|
|
91
|
+
* 「接続OK」と画面に出た #124 がその帰結)。
|
|
92
|
+
*
|
|
93
|
+
* **カタログに無いツール名は画面に一文字も出さない。** teams / jira / box / local は
|
|
94
|
+
* アダプタが存在しないので、名前を出した瞬間「対応している」と読まれる。
|
|
95
|
+
* 状態を出す場所すら無いのに名前だけ出るのが #124 の症状そのもの。
|
|
96
|
+
*
|
|
97
|
+
* **値は配列。** 今日はどの能力も1個だが、「能力=1アダプタ」を焼き込むと
|
|
98
|
+
* #107 / #127(複数アダプタ・複数 Backlog)で読み手の書き換えが要る。
|
|
99
|
+
* ロゴは上の ADAPTER_ICONS を、表示名は CM_TOOL_LABEL を引く(キーはここの値)。
|
|
100
|
+
*/
|
|
101
|
+
const ADAPTER_CATALOG = {
|
|
102
|
+
"課題管理": ["backlog"],
|
|
103
|
+
"会議": ["google-meet"],
|
|
104
|
+
"共有資料": ["google-drive"],
|
|
105
|
+
"デザイン": ["figma"],
|
|
106
|
+
"チャット": ["slack"],
|
|
107
|
+
"開発": ["github"],
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* 導出された状態(5値)→ 画面に出す文言。**マップとタイルで同じ語を使う。**
|
|
111
|
+
*
|
|
112
|
+
* - `off` は「OFFに設定されています」。**接続へ誘わない**——意図的に止めたものに
|
|
113
|
+
* 「接続しましょう」と出すのは、設定する必要が無いものを押させるのと同じ誤り。
|
|
114
|
+
* - `unset` は「未接続」。こちらは入口を出してよい(まだ何も決めていない状態)。
|
|
115
|
+
* - `empty` を `unset` に潰さない。設定ファイルはあるので「置き直す」ではなく「中身を足す」。
|
|
116
|
+
*/
|
|
117
|
+
const CAP_STATE_TEXT = {
|
|
118
|
+
unset: "未接続",
|
|
119
|
+
off: "OFFに設定されています",
|
|
120
|
+
broken: "設定に異常があります",
|
|
121
|
+
empty: "設定はありますが、対象がありません",
|
|
122
|
+
on: "接続OK",
|
|
123
|
+
};
|
|
124
|
+
/** 状態の印。CAP_STATE_TEXT の前に付ける(マップは付けない——⚠️バッジと二重になる) */
|
|
125
|
+
const CAP_STATE_MARK = { ok: "● ", idle: "⦿ ", warn: "⚠️ ", off: "" };
|
|
85
126
|
const CM_CAPS = [
|
|
86
127
|
{ cap: "課題管理", icon: "📋", pipe: /sync-backlog/ },
|
|
87
128
|
{ cap: "会議", icon: "🎙", pipe: /ingest-minutes/ },
|
|
@@ -169,6 +210,39 @@ function externalCapability(type) {
|
|
|
169
210
|
return "開発";
|
|
170
211
|
return CAP_OTHER;
|
|
171
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* その能力をいま担っているアダプタ(#125②)。
|
|
215
|
+
*
|
|
216
|
+
* **カタログにある値しか返さない。** `capabilities[cap].adapter` を素通しすると、
|
|
217
|
+
* engine が古い宣言を引きずった回や、将来 teams のような未対応値が入った回に、
|
|
218
|
+
* 対応していないツール名が顧客の画面へ出る(#124)。カタログ外なら既定
|
|
219
|
+
* (そのカタログの先頭)へ倒す——「名前は出ないが状態は出る」ほうが、
|
|
220
|
+
* 「名前だけ出て何も繋がっていない」より読み手を誤らせない。
|
|
221
|
+
*
|
|
222
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
223
|
+
*/
|
|
224
|
+
function capabilityAdapter(cap, caps) {
|
|
225
|
+
var list = ADAPTER_CATALOG[cap] || [];
|
|
226
|
+
var c = caps ? caps[cap] : null;
|
|
227
|
+
var a = c && c.adapter ? String(c.adapter) : "";
|
|
228
|
+
if (a && list.indexOf(a) >= 0)
|
|
229
|
+
return a;
|
|
230
|
+
return list.length ? list[0] : "";
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* engine が導出した5値の状態。**capabilities を持たない古いデータでは null。**
|
|
234
|
+
*
|
|
235
|
+
* null は「まだ判定材料が無い」であって「未接続」ではない。呼び出し側は null のときだけ
|
|
236
|
+
* 旧来(宣言 tools + 行の有無)の判定へ落ちる——**判定の分岐はここ1箇所**で、
|
|
237
|
+
* 状態の導出を viewer 側にもう一組作らない(導出の正本は engine の1本)。
|
|
238
|
+
*/
|
|
239
|
+
function capabilityRawState(cap, caps) {
|
|
240
|
+
var c = caps ? caps[cap] : null;
|
|
241
|
+
if (!c)
|
|
242
|
+
return null;
|
|
243
|
+
var s = c.state == null ? "" : String(c.state);
|
|
244
|
+
return s ? s : null;
|
|
245
|
+
}
|
|
172
246
|
/**
|
|
173
247
|
* 貼られたURLから Backlog の接続先を読む。
|
|
174
248
|
*
|
|
@@ -218,6 +292,35 @@ function capabilityState(cap, ctx) {
|
|
|
218
292
|
const toolVal = ctx.tools[cap];
|
|
219
293
|
const internal = ctx.internals.filter(function (s) { return s.kind === cap; })[0];
|
|
220
294
|
const pipeRe = PIPE[cap];
|
|
295
|
+
// ---- #125②: 導出(capabilities)があるならそれが状態の正本 ----
|
|
296
|
+
//
|
|
297
|
+
// **ここが唯一の分岐点。** 導出の有無で描き分けるのはこの1箇所に閉じ、
|
|
298
|
+
// 呼び出し側(マップ・タイル・鍵カード)は4値の答えだけを受け取る。
|
|
299
|
+
// 導出を viewer 側にもう一組作らない(設定ファイルの再パース禁止)。
|
|
300
|
+
//
|
|
301
|
+
// **「問題あり(warn)」は別軸なので導出に混ぜない。** 設定は済んでいる(on)のに
|
|
302
|
+
// 読めていない・同期が失敗している、は設定ファイルからは分からない。
|
|
303
|
+
// broken(設定そのものが壊れている)だけは導出が答えを持っている。
|
|
304
|
+
const derived = capabilityRawState(cap, ctx.capabilities);
|
|
305
|
+
if (derived !== null) {
|
|
306
|
+
if (derived === "off")
|
|
307
|
+
return "off";
|
|
308
|
+
if (derived === "broken")
|
|
309
|
+
return "warn";
|
|
310
|
+
if (derived === "on") {
|
|
311
|
+
return (isLive
|
|
312
|
+
? liveExts.some(function (s) { return sourceNeedsAttention(s); })
|
|
313
|
+
: ctx.pipes.some(function (p) {
|
|
314
|
+
return pipeRe != null && pipeRe.test(p.id) && p.applicable !== false && p.lastConclusion === "failure";
|
|
315
|
+
}))
|
|
316
|
+
? "warn"
|
|
317
|
+
: "ok";
|
|
318
|
+
}
|
|
319
|
+
// unset / empty / 未知の値。**未知を ok にも off にも寄せない**——どちらも嘘になる。
|
|
320
|
+
// 「まだ繋がっていない」側に倒すのが安全(繋がっていないのに線を引かない)
|
|
321
|
+
return "idle";
|
|
322
|
+
}
|
|
323
|
+
// ---- ここから下は capabilities を持たない古い fleet-status.json のための劣化表示 ----
|
|
221
324
|
// 使用中か: tools宣言がnone以外 / internalSourceが存在(enabled:false除く) / ライブ参照の実体がある
|
|
222
325
|
const used = (toolVal != null && toolVal !== "none") ||
|
|
223
326
|
(internal != null && internal.enabled !== false) ||
|
|
@@ -271,9 +374,19 @@ function renderConnectionMap(site) {
|
|
|
271
374
|
const externals = site.externalSources ?? [];
|
|
272
375
|
const pipes = site.pipelines ?? [];
|
|
273
376
|
const tools = site.tools ?? {};
|
|
274
|
-
|
|
377
|
+
const caps = site.capabilities ?? {};
|
|
378
|
+
// **fleet のデータが1つでも取れているか、だけで判定する(#125②)。**
|
|
379
|
+
// 素の Decisions ビュー(fleet-status.json が無い構成)ではマップを出さない現行の意図は
|
|
380
|
+
// そのまま。`tools` を条件から外さないのは、capabilities を持たない古いスナップショットで
|
|
381
|
+
// マップが丸ごと消えるのを防ぐため(移行中は両方が現物として存在する)。
|
|
382
|
+
if (!internals.length &&
|
|
383
|
+
!externals.length &&
|
|
384
|
+
!pipes.length &&
|
|
385
|
+
!Object.keys(tools).length &&
|
|
386
|
+
!Object.keys(caps).length) {
|
|
275
387
|
return null;
|
|
276
388
|
}
|
|
389
|
+
const hasCaps = Object.keys(caps).length > 0;
|
|
277
390
|
const slackExts = externals.filter((s) => s.type === "slack");
|
|
278
391
|
const ghExts = externals.filter((s) => s.type.startsWith("github"));
|
|
279
392
|
const nodes = [];
|
|
@@ -288,26 +401,32 @@ function renderConnectionMap(site) {
|
|
|
288
401
|
const liveExts = c.cap === "チャット" ? slackExts : c.cap === "開発" ? ghExts : [];
|
|
289
402
|
// **判定は capabilityState に一本化している。** 画面側(クライアントJS)も同じ関数を
|
|
290
403
|
// 呼ぶので、ここで写経すると「同じ画面でマップが⚠️・直下が『接続OK』」が復活する。
|
|
291
|
-
const st = capabilityState(c.cap, { tools, internals, externals, pipes });
|
|
404
|
+
const st = capabilityState(c.cap, { tools, capabilities: caps, internals, externals, pipes });
|
|
292
405
|
const off = st === "off";
|
|
293
406
|
const idle = st === "idle";
|
|
294
407
|
const warn = st === "warn";
|
|
295
408
|
const state = "cm-" + st;
|
|
409
|
+
const derived = capabilityRawState(c.cap, caps);
|
|
296
410
|
// **ラベルは能力名(会議・課題管理…)。ツール名は下に小さく置く。**
|
|
297
411
|
// ツールは「その能力をいま何で実現しているか」という差し替え可能なアダプタで、
|
|
298
412
|
// 主語ではない。ツール名を主にすると、Teams のように複数の能力に使われるものが
|
|
299
413
|
// 同じ名前で並び(会議とチャットに Teams が2つ)、何が繋がっているのか読めなくなる。
|
|
300
414
|
// "none"(未使用の宣言)はツール名ではないので出さない。そのまま表示すると
|
|
301
415
|
// 「ツール: none」という読めない行になる(実際にそう出ていた)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
416
|
+
//
|
|
417
|
+
// **導出があるならカタログのアダプタを常に出す(#125②)。** 未接続でも「何で繋ぐのか」が
|
|
418
|
+
// 分からないと入口にならない。カタログ外の値(teams 等)は capabilityAdapter が弾く。
|
|
419
|
+
const rawTool = hasCaps
|
|
420
|
+
? capabilityAdapter(c.cap, caps)
|
|
421
|
+
: toolVal && toolVal !== "none"
|
|
422
|
+
? toolVal
|
|
423
|
+
: internal && internal.tool && internal.tool !== "none"
|
|
424
|
+
? internal.tool
|
|
425
|
+
: isLive && liveExts.length
|
|
426
|
+
? c.cap === "チャット"
|
|
427
|
+
? "slack"
|
|
428
|
+
: "github"
|
|
429
|
+
: "";
|
|
311
430
|
const toolName = rawTool ? CM_TOOL_LABEL[rawTool] ?? rawTool : "";
|
|
312
431
|
const label = `${c.icon} ${c.cap}`;
|
|
313
432
|
// 2行目=ツール名(小さく)/3行目=状態(未使用・自動連携なし・ライブ参照・最終同期)
|
|
@@ -316,7 +435,13 @@ function renderConnectionMap(site) {
|
|
|
316
435
|
: "";
|
|
317
436
|
let statusText = "";
|
|
318
437
|
let statusTs = "";
|
|
319
|
-
if (
|
|
438
|
+
if (derived !== null && derived !== "on") {
|
|
439
|
+
// **導出の5値をそのままの語で出す(#125②)。** 「未使用」に丸めると、意図的なOFFと
|
|
440
|
+
// 未設置と壊れているのが同じ1語になる——直せる人が「何を直すか」を決められない。
|
|
441
|
+
// タイル側と同じ CAP_STATE_TEXT を引くので、マップと直下の一覧で語が割れない。
|
|
442
|
+
statusText = CAP_STATE_TEXT[derived] || "状態が判定できません";
|
|
443
|
+
}
|
|
444
|
+
else if (off) {
|
|
320
445
|
statusText = "未使用";
|
|
321
446
|
}
|
|
322
447
|
else if (idle) {
|
|
@@ -404,8 +529,19 @@ function buildTutorialData(site, codeUrl, repoSlug) {
|
|
|
404
529
|
];
|
|
405
530
|
// ここから先は「書き込み・外部連携」の体験。案件の構成に無い能力のチャレンジは出さない(表示条件つき)
|
|
406
531
|
const tools = site.tools ?? {};
|
|
532
|
+
const caps = site.capabilities ?? {};
|
|
407
533
|
const internals = site.internalSources ?? [];
|
|
408
534
|
const externals = site.externalSources ?? [];
|
|
535
|
+
/**
|
|
536
|
+
* その能力が配線済みか。**導出(capabilities)があるならそれだけで決める(#125②)。**
|
|
537
|
+
* 宣言でチャレンジを出していた頃は、Backlog を宣言しているだけで鍵も入っていない案件に
|
|
538
|
+
* 「記票してみよう」が出ていた——押すと必ず失敗する体験を最初の5分に置くことになる。
|
|
539
|
+
* 導出が無い古いデータでは null を返し、呼び出し側が従来の判定へ落ちる。
|
|
540
|
+
*/
|
|
541
|
+
const capOn = (cap) => {
|
|
542
|
+
const s = capabilityRawState(cap, caps);
|
|
543
|
+
return s === null ? null : s === "on";
|
|
544
|
+
};
|
|
409
545
|
// メンバーの自分のレコードを直す体験(Members セクションにレコードがある案件のみ出す)
|
|
410
546
|
if ((site.sections ?? []).some((s) => (s.name === "Members" || s.name === "メンバー") && s.records.length)) {
|
|
411
547
|
specs.push({
|
|
@@ -415,8 +551,9 @@ function buildTutorialData(site, codeUrl, repoSlug) {
|
|
|
415
551
|
});
|
|
416
552
|
}
|
|
417
553
|
// Backlogに記票する体験(課題管理にBacklogを使う案件のみ出す)
|
|
418
|
-
const usesBacklog =
|
|
419
|
-
|
|
554
|
+
const usesBacklog = capOn("課題管理") ??
|
|
555
|
+
(tools["課題管理"] === "backlog" ||
|
|
556
|
+
internals.some((s) => s.kind === "課題管理" && s.tool === "backlog" && s.enabled !== false));
|
|
420
557
|
if (usesBacklog) {
|
|
421
558
|
specs.push({
|
|
422
559
|
title: "Backlogに記票してみよう",
|
|
@@ -425,8 +562,9 @@ function buildTutorialData(site, codeUrl, repoSlug) {
|
|
|
425
562
|
});
|
|
426
563
|
}
|
|
427
564
|
// 画面インベントリを読む体験(デザインを同期している案件のみ出す)
|
|
428
|
-
const usesDesign = (
|
|
429
|
-
|
|
565
|
+
const usesDesign = capOn("デザイン") ??
|
|
566
|
+
((tools["デザイン"] != null && tools["デザイン"] !== "none") ||
|
|
567
|
+
internals.some((s) => s.kind === "デザイン" && s.enabled !== false));
|
|
430
568
|
if (usesDesign) {
|
|
431
569
|
specs.push({
|
|
432
570
|
title: "デザインを読んでみよう",
|
|
@@ -435,7 +573,7 @@ function buildTutorialData(site, codeUrl, repoSlug) {
|
|
|
435
573
|
});
|
|
436
574
|
}
|
|
437
575
|
// 完了をSlackに知らせる体験(チャットにSlackを宣言している案件のみ出す。最終チャレンジ)
|
|
438
|
-
const usesSlack = tools["チャット"] === "slack" || externals.some((s) => s.type === "slack");
|
|
576
|
+
const usesSlack = capOn("チャット") ?? (tools["チャット"] === "slack" || externals.some((s) => s.type === "slack"));
|
|
439
577
|
if (usesSlack) {
|
|
440
578
|
specs.push({
|
|
441
579
|
title: "Slackに完了を知らせよう",
|
|
@@ -1046,6 +1184,9 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1046
1184
|
.ds-gate-ok { color: #1f7a45; }
|
|
1047
1185
|
.ds-gate-warn { color: #b45309; }
|
|
1048
1186
|
.ds-gate-muted { color: #6b7280; }
|
|
1187
|
+
/* 意図的にOFFにした能力(#125②)。**グレーにして誘わない**——未接続(入口を出してよい)
|
|
1188
|
+
とは別物で、同じ色にすると「押して繋ぎましょう」と読まれる */
|
|
1189
|
+
.ds-gate-off { color: #9ca3af; }
|
|
1049
1190
|
.ds-pipes { display: flex; flex-direction: column; gap: 8px; }
|
|
1050
1191
|
/* 組織ごとの接続状況。トークンは組織ごとにしか効かないので、ここが判断の単位になる */
|
|
1051
1192
|
.ds-owners { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
|
|
@@ -1095,6 +1236,20 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1095
1236
|
/* URLは長いので、入力欄を伸ばしてボタンを右に置く(折り返しても崩れないように wrap) */
|
|
1096
1237
|
.rt-url-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
1097
1238
|
.rt-url { flex: 1 1 260px; margin-bottom: 0; font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; }
|
|
1239
|
+
/* 稼働中の表示(#123)。緑は「動いている」の合図で、見出しの右に置いて先に目に入れる */
|
|
1240
|
+
.rt-live-badge {
|
|
1241
|
+
margin-left: auto; flex: 0 0 auto; padding: 3px 10px; border-radius: 999px;
|
|
1242
|
+
background: #e8f6ed; border: 1px solid #b7e0c6; color: #1c7a43;
|
|
1243
|
+
font-size: 12px; font-weight: 700; white-space: nowrap;
|
|
1244
|
+
}
|
|
1245
|
+
.rt-live-lines { display: flex; flex-direction: column; gap: 4px; }
|
|
1246
|
+
.rt-live-line { margin: 0; color: var(--text); font-size: 13px; font-weight: 600; }
|
|
1247
|
+
/* まだ受信していない接続先(複数接続のとき混在する)。稼働中の行と見分けが付く濃さに落とす */
|
|
1248
|
+
.rt-live-line.is-waiting { color: var(--muted); font-weight: 500; }
|
|
1249
|
+
/* 手順は畳んで残す(張り替え・再確認用)。開いたときだけ間隔を空ける */
|
|
1250
|
+
.rt-steps-fold > summary { cursor: pointer; color: var(--muted); font-size: 12px; }
|
|
1251
|
+
.rt-steps-fold[open] > summary { margin-bottom: 10px; }
|
|
1252
|
+
.rt-steps-fold[open] > *:not(summary) { margin-top: 10px; }
|
|
1098
1253
|
.ops-card-head { display: flex; align-items: center; gap: 10px; }
|
|
1099
1254
|
.ops-card-icon { font-size: 22px; line-height: 1; }
|
|
1100
1255
|
.ops-card-title { font-size: 15px; font-weight: 700; color: var(--text); margin: 0; }
|
|
@@ -1205,7 +1360,12 @@ const CLIENT_JS = `
|
|
|
1205
1360
|
// **この2つはクロージャを持たない純関数に保つこと。** 外の変数を掴むと、
|
|
1206
1361
|
// 直列化した先(ここ)で undefined を参照して壊れる。
|
|
1207
1362
|
var CAP_OTHER = ${JSON.stringify(CAP_OTHER)};
|
|
1363
|
+
var ADAPTER_CATALOG = ${JSON.stringify(ADAPTER_CATALOG)};
|
|
1364
|
+
var CAP_STATE_TEXT = ${JSON.stringify(CAP_STATE_TEXT)};
|
|
1365
|
+
var CAP_STATE_MARK = ${JSON.stringify(CAP_STATE_MARK)};
|
|
1208
1366
|
${externalCapability.toString()}
|
|
1367
|
+
${capabilityAdapter.toString()}
|
|
1368
|
+
${capabilityRawState.toString()}
|
|
1209
1369
|
${backlogTargetOf.toString()}
|
|
1210
1370
|
${sourceNeedsAttention.toString()}
|
|
1211
1371
|
${capabilityState.toString()}
|
|
@@ -1258,8 +1418,11 @@ ${capabilityState.toString()}
|
|
|
1258
1418
|
}
|
|
1259
1419
|
// Cortex自体を操作するアクションと、接続・自動化の情報の受け皿(取り込み・接続マップ・データソース)。
|
|
1260
1420
|
// 投入フォーム設定 or fleet-status由来の接続情報のどちらかがあれば出す。
|
|
1421
|
+
// **fleet のデータが取れているか、だけで決める(#125②)。** 能力の宣言(tools)は廃止したので
|
|
1422
|
+
// capabilities を条件に足す。**常に真にはしない**——fleet-status.json を持たない素の
|
|
1423
|
+
// Decisions ビューでは操作タブそのものを出さない現行の意図を保つ
|
|
1261
1424
|
var HAS_FLEET = !!((SITE.internalSources || []).length || (SITE.externalSources || []).length ||
|
|
1262
|
-
(SITE.pipelines || []).length || SITE.tools);
|
|
1425
|
+
(SITE.pipelines || []).length || SITE.capabilities || SITE.tools);
|
|
1263
1426
|
if (SITE.intake || HAS_FLEET) {
|
|
1264
1427
|
TABS.push({ key: "ops", label: "操作" });
|
|
1265
1428
|
}
|
|
@@ -1704,6 +1867,22 @@ ${capabilityState.toString()}
|
|
|
1704
1867
|
var mm = ("0" + d.getMinutes()).slice(-2);
|
|
1705
1868
|
return (d.getMonth() + 1) + "/" + d.getDate() + " " + hh + ":" + mm;
|
|
1706
1869
|
}
|
|
1870
|
+
/**
|
|
1871
|
+
* 「いつだったか」を相対で言う(#123)。読めない値は空文字(呼び側が出し分けを切る)。
|
|
1872
|
+
*
|
|
1873
|
+
* **1日以上前は絶対時刻に切り替える。** 「32日前」は数えないと日付にならないし、
|
|
1874
|
+
* そこまで空いていれば知りたいのは「いつ止まったか」のほう。
|
|
1875
|
+
* 未来の時刻(端末とサーバの時計ずれ)は「たった今」に丸める——「-3分前」を出さない。
|
|
1876
|
+
*/
|
|
1877
|
+
function rtAgo(iso) {
|
|
1878
|
+
var d = new Date(iso);
|
|
1879
|
+
if (isNaN(d.getTime())) return "";
|
|
1880
|
+
var min = Math.floor((Date.now() - d.getTime()) / 60000);
|
|
1881
|
+
if (min < 1) return "たった今";
|
|
1882
|
+
if (min < 60) return min + "分前";
|
|
1883
|
+
if (min < 60 * 24) return Math.floor(min / 60) + "時間前";
|
|
1884
|
+
return dsFmtTime(iso);
|
|
1885
|
+
}
|
|
1707
1886
|
// 名前表示: url があれば新しいタブで正本を開くリンクにする
|
|
1708
1887
|
function dsNameEl(text, url) {
|
|
1709
1888
|
if (url) return el("a", { class: "ds-name", href: url, target: "_blank", rel: "noopener", text: text });
|
|
@@ -1786,6 +1965,25 @@ ${capabilityState.toString()}
|
|
|
1786
1965
|
return SITE[key === "generatedAt" ? "fleetGeneratedAt" : key];
|
|
1787
1966
|
}
|
|
1788
1967
|
|
|
1968
|
+
/**
|
|
1969
|
+
* その能力にそのアダプタの操作を出してよいか(#125②)。
|
|
1970
|
+
*
|
|
1971
|
+
* **カタログにあるなら常に出す。** 以前は宣言(Home.md の tools)でゲートしていたが、
|
|
1972
|
+
* 宣言は実態とずれる二重帳簿で、「teams と書いてあるから Slack の追加口を消す」ような
|
|
1973
|
+
* 誤った封鎖を生んでいた(実際に Slack を渡されているのに追加できない案件があった)。
|
|
1974
|
+
* カタログはこの製品が実装しているアダプタの定義なので、押して必ず失敗することはない。
|
|
1975
|
+
*
|
|
1976
|
+
* **capabilities を持たない古いデータでは、従来どおり宣言で見る。** 移行前の
|
|
1977
|
+
* スナップショットには宣言しか無く、そこで全面開放すると別ツール宣言の案件に
|
|
1978
|
+
* 必ず400になるボタンが復活する。
|
|
1979
|
+
*/
|
|
1980
|
+
function capabilityUsable(cap, tool) {
|
|
1981
|
+
var caps = fleetVal("capabilities") || {};
|
|
1982
|
+
if (Object.keys(caps).length) return (ADAPTER_CATALOG[cap] || []).indexOf(tool) >= 0;
|
|
1983
|
+
var v = (fleetVal("tools") || {})[cap];
|
|
1984
|
+
return !v || v === "none" || v === tool;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1789
1987
|
/** 組み立てだけを行う(設置は呼び出し側)。ライブ差し替えで同じ節を作り直すため分けている */
|
|
1790
1988
|
/** ツールid→表示名(クライアント側。接続マップの CM_TOOL_LABEL と同じ内容) */
|
|
1791
1989
|
var CM_TOOL_LABEL_C = {
|
|
@@ -1798,12 +1996,16 @@ ${capabilityState.toString()}
|
|
|
1798
1996
|
* 能力モーダル。**中身は既存の部品をそのまま入れる。**
|
|
1799
1997
|
* 一覧・追加フォーム・鍵・手動投入は作り直さない(先出しバッジやライブ差し替えを壊さないため)。
|
|
1800
1998
|
*/
|
|
1801
|
-
function openCapabilityModal(cap, items) {
|
|
1999
|
+
function openCapabilityModal(cap, items, note) {
|
|
1802
2000
|
var m = makeModal(cap, true);
|
|
1803
2001
|
// **凡例を必ず入れる。** goldState の4状態は凡例があって初めて読める
|
|
1804
2002
|
if (cap === "チャット" || cap === "開発") {
|
|
1805
2003
|
m.modal.appendChild(el("p", { class: "modal-desc", text: "「Gold昇格の読み取り対象」のものだけを毎晩の Gold 昇格が読み取ります。「除外」は意図的に外したもの、「未宣言(対象外)」はまだ判断されていないもので、どちらも読み取りません。" }));
|
|
1806
2004
|
}
|
|
2005
|
+
// **なぜこの状態なのかを、押した先でも読めるようにする(#125②)。** タイルは常時出す
|
|
2006
|
+
// ようになったので、未接続の能力を押したときに中身が空になる場面が増えた。
|
|
2007
|
+
// 「設定できるものがありません」だけだと、繋げないのか繋ぐ必要が無いのかが分からない
|
|
2008
|
+
if (note) m.modal.appendChild(el("p", { class: "modal-desc", text: note }));
|
|
1807
2009
|
if (!items.length) {
|
|
1808
2010
|
m.modal.appendChild(el("p", { class: "ds-empty", text: "この能力にはまだ設定できるものがありません。" }));
|
|
1809
2011
|
}
|
|
@@ -1823,8 +2025,12 @@ ${capabilityState.toString()}
|
|
|
1823
2025
|
var internals = fleetVal("internalSources") || [];
|
|
1824
2026
|
var sources = fleetVal("externalSources") || [];
|
|
1825
2027
|
var pipes = fleetVal("pipelines") || [];
|
|
1826
|
-
//
|
|
1827
|
-
var
|
|
2028
|
+
// 能力ごとの導出済みの状態(#125② の正本)。持たない古いデータでは {}
|
|
2029
|
+
var caps = fleetVal("capabilities") || {};
|
|
2030
|
+
var hasCaps = Object.keys(caps).length > 0;
|
|
2031
|
+
// 能力→ツールの宣言(例: チャット: "none")。**capabilities が無いときだけ効く劣化表示。**
|
|
2032
|
+
// "none" の能力は「未使用」のグレー行で存在だけ見せる
|
|
2033
|
+
var tools = (!hasCaps && fleetVal("tools")) || {};
|
|
1828
2034
|
var chatOff = tools["チャット"] === "none";
|
|
1829
2035
|
var devOff = tools["開発"] === "none";
|
|
1830
2036
|
// **この節に入るものが1つでもあるなら出す。**
|
|
@@ -1836,18 +2042,20 @@ ${capabilityState.toString()}
|
|
|
1836
2042
|
//
|
|
1837
2043
|
// 個別に足し続けると次も漏らすので、**この節に出す候補を列挙して判定する**形にする。
|
|
1838
2044
|
var hasSecrets = ((SITE.secretKinds || []).filter(function (k) { return SECRET_FORMS[k]; })).length > 0;
|
|
1839
|
-
|
|
2045
|
+
// **fleet のデータが取れているか、だけを見る(#125②)。** 宣言(tools)は廃止したが、
|
|
2046
|
+
// capabilities を持たない古いスナップショットで節ごと消えないよう条件には残す
|
|
2047
|
+
var hasCapModel = hasCaps || Object.keys(tools).length > 0;
|
|
1840
2048
|
// **投入の口も候補。** fleet-status.json はGitHub Actionsが夜間に生成するので、
|
|
1841
2049
|
// 立ち上げ直後の案件には存在しない。それを理由に節ごと消すと、
|
|
1842
2050
|
// 文字起こし・資料を入れる口が**一番必要な瞬間に**画面から無くなる
|
|
1843
2051
|
var hasIntake = !!SITE.intake;
|
|
1844
|
-
if (!internals.length && !sources.length && !pipes.length && !
|
|
2052
|
+
if (!internals.length && !sources.length && !pipes.length && !hasCapModel && !hasSecrets && !hasIntake) return null;
|
|
1845
2053
|
|
|
1846
2054
|
var sec = el("section", { class: "datasources" });
|
|
1847
2055
|
sec.appendChild(el("h2", { class: "datasources-head", text: "データソースと自動化" }));
|
|
1848
2056
|
// **まだ何も繋がっていない案件では、文言を変える。** 接続がゼロの状態で
|
|
1849
2057
|
// 「接続している情報源の一覧です」と書くと、中身(手動投入だけ)と噛み合わない
|
|
1850
|
-
var connected = internals.length > 0 || sources.length > 0 ||
|
|
2058
|
+
var connected = internals.length > 0 || sources.length > 0 || hasCapModel;
|
|
1851
2059
|
sec.appendChild(el("p", { class: "datasources-intro", text: connected
|
|
1852
2060
|
? "このCortexが接続している情報源と、毎晩自動でGoldに昇格する処理の一覧です。"
|
|
1853
2061
|
: "情報源はまだ接続されていません。下から手元のファイルを取り込めます(自動連携の設定はこれから行います)。" }));
|
|
@@ -1870,12 +2078,12 @@ ${capabilityState.toString()}
|
|
|
1870
2078
|
|
|
1871
2079
|
// ---- 能力ごとの受け皿 ----
|
|
1872
2080
|
//
|
|
1873
|
-
//
|
|
2081
|
+
// **能力の一覧はカタログ(ADAPTER_CATALOG)で決める。データ行の有無で決めない。**
|
|
1874
2082
|
// internalSources に現れるのは 課題管理/会議/共有資料/デザイン の4つだけで、
|
|
1875
|
-
// チャットと開発は externalSources
|
|
1876
|
-
//
|
|
1877
|
-
//
|
|
1878
|
-
//
|
|
2083
|
+
// チャットと開発は externalSources 側にしかない。さらに未接続の能力は
|
|
2084
|
+
// **どちらにも行が無い**。行の有無でセクションを作ると、その案件でチャットが丸ごと消え、
|
|
2085
|
+
// 「これから繋げる入口」も接続マップのアンカーの受け皿も無くなる。
|
|
2086
|
+
// 何を繋げるかは製品が決めていて、案件データでは変わらない(#125②)。
|
|
1879
2087
|
var CAP_ORDER = ["課題管理", "会議", "共有資料", "チャット", "デザイン", "開発"];
|
|
1880
2088
|
var capBox = {};
|
|
1881
2089
|
var manualBox = {};
|
|
@@ -2139,6 +2347,16 @@ ${capabilityState.toString()}
|
|
|
2139
2347
|
}));
|
|
2140
2348
|
return;
|
|
2141
2349
|
}
|
|
2350
|
+
// **未接続(unset)の能力には入力欄を出さない(#125②)。** 鍵は「繋いだものを読むため」の
|
|
2351
|
+
// ものなので、繋ぐ先が無い状態で入れても効かない。何も出さないと「鍵はどこで入れるのか」が
|
|
2352
|
+
// 分からなくなるので、順序だけを1行で伝える
|
|
2353
|
+
if (capabilityRawState(SECRET_CAP[k], caps) === "unset") {
|
|
2354
|
+
pushCap(SECRET_CAP[k], el("p", {
|
|
2355
|
+
class: "sec-how",
|
|
2356
|
+
text: "この能力はまだ接続されていません。接続すると、ここに鍵の入力欄が出ます。",
|
|
2357
|
+
}));
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2142
2360
|
pushCap(SECRET_CAP[k], secretCard(k, SECRET_FORMS[k]));
|
|
2143
2361
|
});
|
|
2144
2362
|
|
|
@@ -2209,12 +2427,23 @@ ${capabilityState.toString()}
|
|
|
2209
2427
|
return row;
|
|
2210
2428
|
}
|
|
2211
2429
|
|
|
2212
|
-
/**
|
|
2213
|
-
|
|
2430
|
+
/**
|
|
2431
|
+
* タイルの2行目(状態と読み取り範囲)。**件数は出せる能力だけ出す**
|
|
2432
|
+
*
|
|
2433
|
+
* @param derived engine が導出した5値(capabilities 由来)。古いデータでは null
|
|
2434
|
+
* @param adapter このタイルのアダプタ(件数の出し分けに使う)
|
|
2435
|
+
*/
|
|
2436
|
+
function tileSub(cap, st, derived, adapter) {
|
|
2214
2437
|
var internal = internals.filter(function (s) { return s.kind === cap; })[0];
|
|
2215
2438
|
var live = sources.filter(function (s) { return externalCapability(s.type) === cap; });
|
|
2216
2439
|
var parts = [];
|
|
2217
|
-
|
|
2440
|
+
// **導出が on 以外なら、導出の語を優先する(#125②)。** broken は状態としては
|
|
2441
|
+
// 警告(⚠️)だが、原因は「設定そのものが壊れている」であって同期の失敗ではない。
|
|
2442
|
+
// 下の warn の文(同期が失敗しています/N件が読めていません)を出すと、
|
|
2443
|
+
// 直す場所が違うところを指す
|
|
2444
|
+
if (derived && derived !== "on") {
|
|
2445
|
+
parts.push((CAP_STATE_MARK[st] || "") + (CAP_STATE_TEXT[derived] || "状態が判定できません"));
|
|
2446
|
+
} else if (st === "warn") {
|
|
2218
2447
|
if (live.length) {
|
|
2219
2448
|
var bad = 0, noToken = 0;
|
|
2220
2449
|
live.forEach(function (s) {
|
|
@@ -2229,6 +2458,10 @@ ${capabilityState.toString()}
|
|
|
2229
2458
|
// 内部能力の warn はパイプラインの失敗由来。数えるものが無いので件数を出さない
|
|
2230
2459
|
parts.push("⚠️ 同期が失敗しています");
|
|
2231
2460
|
}
|
|
2461
|
+
} else if (derived) {
|
|
2462
|
+
// 導出が on で、問題も無い。接続マップと同じ CAP_STATE_TEXT を引くので、
|
|
2463
|
+
// 上のマップと直下の一覧で語が割れない
|
|
2464
|
+
parts.push((CAP_STATE_MARK[st] || "") + (CAP_STATE_TEXT[derived] || "状態が判定できません"));
|
|
2232
2465
|
} else {
|
|
2233
2466
|
// **off には2つの意味が同居している。** 「使わないと宣言した」(tools が none)と
|
|
2234
2467
|
// 「まだ何も分かっていない」(fleet-status.json を夜間バッチがまだ作っていない)。
|
|
@@ -2251,9 +2484,10 @@ ${capabilityState.toString()}
|
|
|
2251
2484
|
var unit = cap === "チャット" ? "チャンネル" : cap === "開発" ? "リポジトリ" : "件";
|
|
2252
2485
|
var gold = live.filter(function (s) { return s.gold === true; }).length;
|
|
2253
2486
|
parts.push(live.length + unit + "/" + gold + "件をGoldに使用");
|
|
2254
|
-
} else if (cap === "共有資料" &&
|
|
2255
|
-
//
|
|
2256
|
-
|
|
2487
|
+
} else if (cap === "共有資料" && adapter === "google-drive" && internal && internal.driveFolderIds) {
|
|
2488
|
+
// **そのタイルのアダプタを見る。** 宣言(tools)を見ていた頃は、local の案件
|
|
2489
|
+
//(gdo・三菱)で「ローカル」と名乗ったタイルが「Driveフォルダ 0件」と語っていた。
|
|
2490
|
+
// いまカタログにあるのは google-drive だけなので、ここは常に真になる
|
|
2257
2491
|
parts.push("Driveフォルダ " + internal.driveFolderIds.length + "件");
|
|
2258
2492
|
} else if (cap === "デザイン" && internal && internal.figmaFiles) {
|
|
2259
2493
|
// **キーが無ければ何も出さない。** 未取得を「登録ゼロ」と断定して顧客に見せない
|
|
@@ -2274,38 +2508,68 @@ ${capabilityState.toString()}
|
|
|
2274
2508
|
var anyCap = false;
|
|
2275
2509
|
CAP_ORDER.concat(extraCaps).forEach(function (cap) {
|
|
2276
2510
|
var items = capBox[cap] || [];
|
|
2511
|
+
// **カタログにある能力のタイルは常時出す(#125②)。** 使っているかどうかで出し分けない
|
|
2512
|
+
// ——未接続のタイルは「これから繋げる入口」で、消すと繋ぎ方を知る場所が無くなる。
|
|
2513
|
+
// カタログは製品の定義なので、案件データが何であってもここは変わらない
|
|
2514
|
+
var adapters = (hasCaps && ADAPTER_CATALOG[cap]) || [];
|
|
2515
|
+
// 旧データ(capabilities 無し)は従来どおり「宣言があれば行が無くても出す」
|
|
2277
2516
|
var declared = tools[cap] != null;
|
|
2278
|
-
// **tools に宣言があれば、行が無くてもタイルを出す。** 出さないと
|
|
2279
|
-
// Teams案件でチャットが丸ごと消え、「未対応です」と伝える場所も無くなる。
|
|
2280
|
-
// 接続マップのアンカー(ds-<能力>)の受け皿にもなる(いま2案件で解決できていない)
|
|
2281
2517
|
// **手動投入はアダプタに依存しない。** 宣言や行の有無で弾くと、
|
|
2282
2518
|
// fleet-status.json がまだ無い新規案件で投入の口ごと消える
|
|
2283
|
-
if (!items.length && !declared && !manualBox[cap]) return;
|
|
2519
|
+
if (!adapters.length && !items.length && !declared && !manualBox[cap]) return;
|
|
2284
2520
|
anyCap = true;
|
|
2285
2521
|
sec.appendChild(el("div", { class: "ds-cap", text: cap }));
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2522
|
+
if (adapters.length) {
|
|
2523
|
+
// **カタログの全アダプタを並べる(今日は各1個)。** 能力=1アダプタを
|
|
2524
|
+
// 焼き込まないための形(#107 / #127)。導出が答えているアダプタにだけ
|
|
2525
|
+
// その状態を割り当て、残りは未接続——「知らない」を「繋がっている」に寄せない
|
|
2526
|
+
var active = capabilityAdapter(cap, caps);
|
|
2527
|
+
adapters.forEach(function (a, ai) {
|
|
2528
|
+
renderAdapterTile(cap, items, a, a === active ? capabilityRawState(cap, caps) : "unset", ai);
|
|
2529
|
+
});
|
|
2530
|
+
} else if (items.length || declared) {
|
|
2531
|
+
// 旧データ: アダプタのタイルは宣言か行があるときだけ。手動投入だけが理由でここへ来た
|
|
2532
|
+
// 新規案件に、中身の無い「会議」タイルを立てても押す先が無い
|
|
2533
|
+
var legacyTool = tools[cap] && tools[cap] !== "none" ? tools[cap] : "";
|
|
2534
|
+
renderAdapterTile(cap, items, legacyTool, null, 0);
|
|
2535
|
+
}
|
|
2289
2536
|
renderManualTile(cap);
|
|
2290
2537
|
});
|
|
2291
2538
|
|
|
2292
|
-
|
|
2539
|
+
/**
|
|
2540
|
+
* 能力×アダプタ1件のタイル。
|
|
2541
|
+
*
|
|
2542
|
+
* @param adapter アダプタ識別子(カタログ由来。旧データでは宣言 tools 由来)
|
|
2543
|
+
* @param derived engine が導出した5値。旧データ(capabilities 無し)では null
|
|
2544
|
+
* @param ai その能力の中での並び順。先頭だけが接続マップの飛び先(ds-<能力>)になる
|
|
2545
|
+
*/
|
|
2546
|
+
function renderAdapterTile(cap, items, adapter, derived, ai) {
|
|
2293
2547
|
var meta = CAP_META[cap] || { icon: "🔗", desc: "" };
|
|
2294
|
-
var st =
|
|
2548
|
+
var st = derived === "unset" && capabilityRawState(cap, caps) !== "unset"
|
|
2549
|
+
// カタログにあるが導出が答えていないアダプタ(今日は発生しない。#107 の前方互換)
|
|
2550
|
+
? "idle"
|
|
2551
|
+
: capabilityState(cap, { tools: tools, capabilities: caps, internals: internals, externals: sources, pipes: pipes });
|
|
2295
2552
|
// **ロゴはタイルの左に独立して置く。** 名前の行に入れると名前の高さに
|
|
2296
2553
|
// 縛られて小さくなる。ロゴで能力を見分けられるのが一覧の主な用途
|
|
2297
|
-
var logo = ADAPTER_ICONS[
|
|
2554
|
+
var logo = ADAPTER_ICONS[adapter];
|
|
2298
2555
|
var ic = el("span", { class: "ds-tile-logo" });
|
|
2299
2556
|
if (logo) ic.innerHTML = logo; // 公式ロゴ。ビルド時に埋め込み済みで外部通信は起きない
|
|
2300
2557
|
else ic.textContent = meta.icon;
|
|
2301
|
-
// "none"
|
|
2302
|
-
//
|
|
2303
|
-
var rawTool =
|
|
2558
|
+
// カタログ外の値はここへ来ない(capabilityAdapter が弾く)。旧データの "none" だけは
|
|
2559
|
+
// 素通しすると顧客の画面に「none」というタイル名が出るので、上で空にしてある
|
|
2560
|
+
var rawTool = adapter || "";
|
|
2304
2561
|
var body = el("div", { class: "ds-tile-body" }, [
|
|
2305
2562
|
el("div", { class: "ds-tile-name", text: (rawTool && (CM_TOOL_LABEL_C[rawTool] || rawTool)) || cap }),
|
|
2306
2563
|
el("div", { class: "ds-tile-desc", text: meta.desc }),
|
|
2307
|
-
el("div", { class: "ds-tile-sub ds-gate-" + st, text: tileSub(cap, st) }),
|
|
2564
|
+
el("div", { class: "ds-tile-sub ds-gate-" + st, text: tileSub(cap, st, derived, adapter) }),
|
|
2308
2565
|
]);
|
|
2566
|
+
// **なぜその状態なのかを添える(#125②)。** engine が書いた一文(detail)で、
|
|
2567
|
+
// 「未接続」だけでは分からない「何が無いのか」を出す。on のときは
|
|
2568
|
+
// 読み取り範囲(件数)が既に同じ行にあるので添えない
|
|
2569
|
+
var capInfo = caps[cap];
|
|
2570
|
+
if (derived && derived !== "on" && capInfo && capInfo.detail && adapter === capabilityAdapter(cap, caps)) {
|
|
2571
|
+
body.appendChild(el("div", { class: "ds-tile-note ds-tile-todo", text: capInfo.detail }));
|
|
2572
|
+
}
|
|
2309
2573
|
// トークン未設定は**状態とは別軸**。状態機械に混ぜると判定が1箇所でなくなる。
|
|
2310
2574
|
// ただし「使わない」と**宣言した**能力では出さない——設定する必要が無いものに
|
|
2311
2575
|
// ついて設定を促す偽の警告になる(sushiro のデザインで出ていた)。
|
|
@@ -2319,7 +2583,11 @@ ${capabilityState.toString()}
|
|
|
2319
2583
|
// 案件に未設定の警告を出すと、直す必要が無いものを直させることになる。
|
|
2320
2584
|
// 読めていない組織があるときは、この下の checks とタイルの状態(warn)が拾う
|
|
2321
2585
|
var tokenIsFallback = sk === "external-sources" && !githubTokenNeeded();
|
|
2322
|
-
|
|
2586
|
+
// **#125②: 導出があるときは導出で判断する。** OFF(意図的に止めた)と unset(未接続で、
|
|
2587
|
+
// 鍵の入力欄そのものを案内文に畳んである)には出さない——入れる先が無い、あるいは
|
|
2588
|
+
// 入れる必要が無いものについて設定を促す偽の警告になる
|
|
2589
|
+
var tokenRelevant = derived != null ? derived !== "off" && derived !== "unset" : tools[cap] !== "none";
|
|
2590
|
+
if (!tokenIsFallback && tokenRelevant && sk && (SITE.secretKinds || []).indexOf(sk) >= 0 && !((fleetVal("secretSources") || {})[sk])) {
|
|
2323
2591
|
body.appendChild(el("div", { class: "ds-tile-note", text: "⚠ トークンが未設定です" }));
|
|
2324
2592
|
}
|
|
2325
2593
|
// セットアップの残りのうち、この能力のもの。**鍵の注記と同じ位置**に出して
|
|
@@ -2334,12 +2602,17 @@ ${capabilityState.toString()}
|
|
|
2334
2602
|
shownOnTile[c.id] = true;
|
|
2335
2603
|
body.appendChild(el("div", { class: "ds-tile-note ds-tile-todo", text: "→ " + c.action }));
|
|
2336
2604
|
});
|
|
2337
|
-
|
|
2605
|
+
// **id は能力ごとに1つ(先頭のアダプタ)。** 接続マップのノードの飛び先で、
|
|
2606
|
+
// 同じ id が2つある不正なHTMLになると getElementById が思わぬ側を指す
|
|
2607
|
+
var tile = el("button", { class: "ds-tile", type: "button", id: ai ? "ds-" + cap + "-" + adapter : "ds-" + cap }, [
|
|
2338
2608
|
ic,
|
|
2339
2609
|
body,
|
|
2340
2610
|
el("span", { class: "ds-tile-chev", text: "›" }),
|
|
2341
2611
|
]);
|
|
2342
|
-
|
|
2612
|
+
var modalNote = derived && derived !== "on" && capInfo && capInfo.detail
|
|
2613
|
+
? CAP_STATE_TEXT[derived] + "(" + capInfo.detail + ")"
|
|
2614
|
+
: "";
|
|
2615
|
+
tile.addEventListener("click", function () { openCapabilityModal(cap, items, modalNote); });
|
|
2343
2616
|
sec.appendChild(tile);
|
|
2344
2617
|
}
|
|
2345
2618
|
|
|
@@ -2523,8 +2796,7 @@ ${capabilityState.toString()}
|
|
|
2523
2796
|
// 「足したい案件でだけ足せない」という逆転になっていた。
|
|
2524
2797
|
// 押しても必ず失敗するもの(別ツールが宣言済み)は usable() で弾く。
|
|
2525
2798
|
if (!SITE.intake) return null;
|
|
2526
|
-
var
|
|
2527
|
-
var usable = function (cap, tool) { var v = tools[cap]; return !v || v === "none" || v === tool; };
|
|
2799
|
+
var usable = capabilityUsable;
|
|
2528
2800
|
var box = el("div", {});
|
|
2529
2801
|
var used = false;
|
|
2530
2802
|
|
|
@@ -2778,14 +3050,14 @@ ${capabilityState.toString()}
|
|
|
2778
3050
|
/**
|
|
2779
3051
|
* 外部ソースの追加フォーム(Slack・GitHub)。
|
|
2780
3052
|
*
|
|
2781
|
-
*
|
|
2782
|
-
*
|
|
3053
|
+
* **カタログでゲートする(#125②)。** 宣言(tools)は廃止した。古いデータのときだけ
|
|
3054
|
+
* 従来どおり宣言を見る(capabilityUsable の項を参照)——別のツールが宣言されている
|
|
3055
|
+
* 案件ではサーバが必ず400で断るので、押せるのに必ず失敗するボタンを出さない。
|
|
2783
3056
|
*/
|
|
2784
3057
|
function renderExternalAdders() {
|
|
2785
3058
|
var box = el("div", {});
|
|
2786
3059
|
if (!SITE.intake) return box;
|
|
2787
|
-
var
|
|
2788
|
-
var usable = function (cap, tool) { var v = tools[cap]; return !v || v === "none" || v === tool; };
|
|
3060
|
+
var usable = capabilityUsable;
|
|
2789
3061
|
|
|
2790
3062
|
// **Slack は tools 宣言で隠さない。** tools.チャット は能力ごとに1値しか持てないので、
|
|
2791
3063
|
// 「会議はTeams・連絡はSlack」という普通の構成を表せない。teams を宣言した案件では
|
|
@@ -3433,6 +3705,10 @@ ${capabilityState.toString()}
|
|
|
3433
3705
|
*
|
|
3434
3706
|
* **URLに載せるトークンは案件別**(SITE.backlogWebhook)。艦隊共通トークンを載せると、
|
|
3435
3707
|
* この画面を見られる人が全案件ぶんの偽イベントを投げられる(types.ts の backlogWebhook 参照)。
|
|
3708
|
+
*
|
|
3709
|
+
* **既に回っている案件には、手順より先に「動いている」ことを出す(#123)。** 設定手順は
|
|
3710
|
+
* まだ繋いでいない人のためのもので、繋ぎ終わった案件の担当者が知りたいのは
|
|
3711
|
+
* 「ちゃんと届いているか」。手順は畳んで残す(URLの再確認・張り替えに要る)。
|
|
3436
3712
|
*/
|
|
3437
3713
|
function realtimeSyncCard() {
|
|
3438
3714
|
var rt = LIVE_REALTIME;
|
|
@@ -3446,6 +3722,13 @@ ${capabilityState.toString()}
|
|
|
3446
3722
|
var wh = SITE.backlogWebhook;
|
|
3447
3723
|
if (!rt.ready || !wh || !wh.url || !wh.token) return null;
|
|
3448
3724
|
var url = wh.url + (wh.url.indexOf("?") === -1 ? "?" : "&") + "t=" + encodeURIComponent(wh.token);
|
|
3725
|
+
// 接続先ごとの受信実績(#123)。**配列で受ける**——1案件=複数 Backlog プロジェクトは
|
|
3726
|
+
// 確定した将来要件(#107)で、今日は必ず1件。**時刻が読めない値は稼働中に数えない**
|
|
3727
|
+
// ——「最終受信: NaN分前」を出すくらいなら、未設定と同じ手順の画面のほうがまだ読める
|
|
3728
|
+
var targets = (rt.targets || []).map(function (t) {
|
|
3729
|
+
return { projectKey: t && t.projectKey ? String(t.projectKey) : "", ago: t && t.lastEventAt ? rtAgo(t.lastEventAt) : "" };
|
|
3730
|
+
});
|
|
3731
|
+
var live = targets.some(function (t) { return t.ago; });
|
|
3449
3732
|
|
|
3450
3733
|
// URLは**選択できる形**で出す(コピーボタンが使えない環境でも手で取れるように)
|
|
3451
3734
|
var field = el("input", { class: "sec-input rt-url", type: "text", readonly: "readonly", spellcheck: "false" });
|
|
@@ -3472,12 +3755,8 @@ ${capabilityState.toString()}
|
|
|
3472
3755
|
} catch (e) { fallback(); }
|
|
3473
3756
|
});
|
|
3474
3757
|
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
el("span", { class: "ops-card-icon", text: "⚡" }),
|
|
3478
|
-
el("h3", { class: "ops-card-title", text: "リアルタイム同期" }),
|
|
3479
|
-
]),
|
|
3480
|
-
el("p", { class: "ops-card-desc", text: "Backlog で課題やコメントが変わったときに、その内容をすぐ取り込みます(毎晩の同期を待たずに反映されます)。Backlog 側で一度だけ設定してください。" }),
|
|
3758
|
+
// 手順ひとそろい(貼り付け先・URL・注意書き)。稼働中の案件ではこれを畳む
|
|
3759
|
+
var steps = [
|
|
3481
3760
|
el("ol", { class: "rt-steps" }, [
|
|
3482
3761
|
el("li", { text: "Backlog のプロジェクト設定 → インテグレーション → Webhook を開く" }),
|
|
3483
3762
|
el("li", { text: "「WebHook URL」に下の案件専用URLを貼り付ける" }),
|
|
@@ -3488,7 +3767,46 @@ ${capabilityState.toString()}
|
|
|
3488
3767
|
// **この画面は顧客も見る。** URLに秘密が入っていることと、その扱いを書いておく
|
|
3489
3768
|
el("p", { class: "sec-note", text: "このURLはこの案件専用です(他の案件には届きません)。秘密のトークンを含むので、貼り付け先は Backlog のこのプロジェクトの設定欄だけにしてください。" }),
|
|
3490
3769
|
el("p", { class: "sec-how", text: "反映は受信から約1分後です(1分ごとのミニバッチでまとめて取り込みます)。初回は上の接続先の設定と、夜間同期が一度済んでいることが前提になります。" }),
|
|
3491
|
-
]
|
|
3770
|
+
];
|
|
3771
|
+
|
|
3772
|
+
if (live) {
|
|
3773
|
+
// 稼働中。**先頭は状態、手順は畳む。**「畳んである中に何があるか」を summary に書く
|
|
3774
|
+
// ——ただの「詳細」だと、URLを張り替えたい人がここを開く手掛かりを持てない
|
|
3775
|
+
var det = el("details", { class: "rt-steps-fold" }, [
|
|
3776
|
+
el("summary", { text: "設定手順と案件専用URLを表示(URLの再確認・張り替え用)" }),
|
|
3777
|
+
]);
|
|
3778
|
+
for (var i = 0; i < steps.length; i++) det.appendChild(steps[i]);
|
|
3779
|
+
// 接続先ごとに1行。**1件のときはプロジェクトキーを出さない**——どれのことかは
|
|
3780
|
+
// 自明なので、キーを添えると「他にもあるのか」と読ませる余計な情報になる。
|
|
3781
|
+
// 複数のときは、どの接続先の話かが分からないと行が読めないので必ず添える
|
|
3782
|
+
var lines = el("div", { class: "rt-live-lines" }, targets.map(function (t) {
|
|
3783
|
+
var label = targets.length > 1 && t.projectKey ? t.projectKey + ": " : "";
|
|
3784
|
+
// 受信実績がまだ無い接続先も**黙って落とさない**。1つでも動いていれば
|
|
3785
|
+
// カードは稼働中の形になるので、落とすと「増やした接続先が来ていない」に気づけない
|
|
3786
|
+
return el("p", {
|
|
3787
|
+
class: t.ago ? "rt-live-line" : "rt-live-line is-waiting",
|
|
3788
|
+
text: t.ago ? label + "最終受信: " + t.ago : label + "まだ受信していません",
|
|
3789
|
+
});
|
|
3790
|
+
}));
|
|
3791
|
+
return el("div", { class: "ops-card rt-card is-live" }, [
|
|
3792
|
+
el("div", { class: "ops-card-head" }, [
|
|
3793
|
+
el("span", { class: "ops-card-icon", text: "⚡" }),
|
|
3794
|
+
el("h3", { class: "ops-card-title", text: "リアルタイム同期" }),
|
|
3795
|
+
el("span", { class: "rt-live-badge", text: "✓ 稼働中" }),
|
|
3796
|
+
]),
|
|
3797
|
+
lines,
|
|
3798
|
+
el("p", { class: "ops-card-desc", text: "Backlog の変更をすぐ取り込んでいます(毎晩の同期を待ちません)。設定は済んでいるので、このままお使いください。" }),
|
|
3799
|
+
det,
|
|
3800
|
+
]);
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
return el("div", { class: "ops-card rt-card" }, [
|
|
3804
|
+
el("div", { class: "ops-card-head" }, [
|
|
3805
|
+
el("span", { class: "ops-card-icon", text: "⚡" }),
|
|
3806
|
+
el("h3", { class: "ops-card-title", text: "リアルタイム同期" }),
|
|
3807
|
+
]),
|
|
3808
|
+
el("p", { class: "ops-card-desc", text: "Backlog で課題やコメントが変わったときに、その内容をすぐ取り込みます(毎晩の同期を待たずに反映されます)。Backlog 側で一度だけ設定してください。" }),
|
|
3809
|
+
].concat(steps));
|
|
3492
3810
|
}
|
|
3493
3811
|
|
|
3494
3812
|
/** 鍵の入力欄に出す説明。kind は Lambda 側(resolveSecretTarget)と綴りを合わせること */
|
package/package.json
CHANGED