feat(app-portal): n8n画面のワークフロー名ソート・JST表示・日本語ステータス
タグ内ワークフローを名前昇順に、実行履歴の開始日時をJST変換、 success/error等のステータスバッジを日本語ラベルに変更。
This commit is contained in:
parent
041a5e454c
commit
ecc4a1a6ca
@ -22,6 +22,9 @@ function buildTagTree(workflows) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = [...groups.values()];
|
const result = [...groups.values()];
|
||||||
|
for (const group of result) {
|
||||||
|
group.children.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
result.sort((a, b) => {
|
result.sort((a, b) => {
|
||||||
if (a.id === UNTAGGED_ID) return 1;
|
if (a.id === UNTAGGED_ID) return 1;
|
||||||
if (b.id === UNTAGGED_ID) return -1;
|
if (b.id === UNTAGGED_ID) return -1;
|
||||||
|
|||||||
@ -37,6 +37,25 @@ const EXECUTION_BADGE = {
|
|||||||
unknown: 'bg-secondary',
|
unknown: 'bg-secondary',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const EXECUTION_STATUS_LABEL = {
|
||||||
|
success: '成功',
|
||||||
|
error: 'エラー',
|
||||||
|
crashed: 'クラッシュ',
|
||||||
|
running: '実行中',
|
||||||
|
waiting: '待機中',
|
||||||
|
canceled: 'キャンセル',
|
||||||
|
new: '新規',
|
||||||
|
unknown: '不明',
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatJst(isoString) {
|
||||||
|
if (!isoString) return '';
|
||||||
|
const utc = new Date(isoString);
|
||||||
|
const jst = new Date(utc.getTime() + 9 * 60 * 60 * 1000);
|
||||||
|
const pad = (n) => String(n).padStart(2, '0');
|
||||||
|
return `${jst.getUTCFullYear()}-${pad(jst.getUTCMonth() + 1)}-${pad(jst.getUTCDate())} ${pad(jst.getUTCHours())}:${pad(jst.getUTCMinutes())}:${pad(jst.getUTCSeconds())}`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderExecutionList(executions) {
|
function renderExecutionList(executions) {
|
||||||
if (!executions || executions.length === 0) {
|
if (!executions || executions.length === 0) {
|
||||||
return '<p class="text-muted">実行履歴なし</p>';
|
return '<p class="text-muted">実行履歴なし</p>';
|
||||||
@ -44,14 +63,15 @@ function renderExecutionList(executions) {
|
|||||||
const rows = executions
|
const rows = executions
|
||||||
.map((e) => {
|
.map((e) => {
|
||||||
const badgeClass = EXECUTION_BADGE[e.status] || 'bg-secondary';
|
const badgeClass = EXECUTION_BADGE[e.status] || 'bg-secondary';
|
||||||
|
const statusLabel = EXECUTION_STATUS_LABEL[e.status] || e.status;
|
||||||
return `<tr data-execution-id="${escapeHtml(e.id)}" role="button">
|
return `<tr data-execution-id="${escapeHtml(e.id)}" role="button">
|
||||||
<td>${escapeHtml(e.startedAt || '')}</td>
|
<td>${escapeHtml(formatJst(e.startedAt))}</td>
|
||||||
<td><span class="badge ${badgeClass}">${escapeHtml(e.status)}</span></td>
|
<td><span class="badge ${badgeClass}">${escapeHtml(statusLabel)}</span></td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
})
|
})
|
||||||
.join('\n');
|
.join('\n');
|
||||||
return `<table class="table table-sm table-hover">
|
return `<table class="table table-sm table-hover">
|
||||||
<thead><tr><th>開始日時</th><th>状態</th></tr></thead>
|
<thead><tr><th>開始日時(JST)</th><th>状態</th></tr></thead>
|
||||||
<tbody>${rows}</tbody>
|
<tbody>${rows}</tbody>
|
||||||
</table>`;
|
</table>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,18 @@ test('buildTagTree puts a multi-tagged workflow into every one of its tag groups
|
|||||||
assert.strictEqual(tree[1].children.length, 1);
|
assert.strictEqual(tree[1].children.length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('buildTagTree sorts workflows within a group alphabetically by name', () => {
|
||||||
|
const workflows = [
|
||||||
|
{ id: 'w2', name: 'Zワークフロー', active: true, tags: [{ name: 'タグA' }] },
|
||||||
|
{ id: 'w1', name: 'Aワークフロー', active: true, tags: [{ name: 'タグA' }] },
|
||||||
|
];
|
||||||
|
const tree = buildTagTree(workflows);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
tree[0].children.map((w) => w.name),
|
||||||
|
['Aワークフロー', 'Zワークフロー']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('buildTagTree sorts tag groups alphabetically and puts "未分類" last', () => {
|
test('buildTagTree sorts tag groups alphabetically and puts "未分類" last', () => {
|
||||||
const workflows = [
|
const workflows = [
|
||||||
{ id: 'w1', name: 'A', active: true, tags: [{ name: 'Zタグ' }] },
|
{ id: 'w1', name: 'A', active: true, tags: [{ name: 'Zタグ' }] },
|
||||||
|
|||||||
@ -49,6 +49,20 @@ test('renderExecutionList renders a row per execution with a status badge', () =
|
|||||||
assert.ok(html.includes('bg-danger'));
|
assert.ok(html.includes('bg-danger'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('renderExecutionList shows the started time converted to JST, not raw UTC', () => {
|
||||||
|
const html = renderExecutionList([{ id: 'e1', status: 'success', startedAt: '2026-09-19T00:00:00.000Z' }]);
|
||||||
|
assert.ok(html.includes('2026-09-19 09:00:00'));
|
||||||
|
assert.ok(!html.includes('2026-09-19T00:00:00.000Z'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderExecutionList shows a Japanese label for each status', () => {
|
||||||
|
const statuses = ['success', 'error', 'running', 'waiting', 'canceled'];
|
||||||
|
for (const status of statuses) {
|
||||||
|
const html = renderExecutionList([{ id: 'e1', status, startedAt: '2026-09-19T00:00:00.000Z' }]);
|
||||||
|
assert.ok(!html.includes(`>${status}<`), `expected status "${status}" to be translated`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('renderExecutionList shows a message when there are no executions', () => {
|
test('renderExecutionList shows a message when there are no executions', () => {
|
||||||
const html = renderExecutionList([]);
|
const html = renderExecutionList([]);
|
||||||
assert.ok(html.includes('実行履歴なし'));
|
assert.ok(html.includes('実行履歴なし'));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user