Skip to content

Commit e0c2a2b

Browse files
committed
Fix Mock DB real-time rendering tool filters and HR group colors - PR_26157_009-mock-db-persistence-and-tool-hr-colors
1 parent 8d2c07b commit e0c2a2b

13 files changed

Lines changed: 774 additions & 93 deletions

admin/db-viewer.html

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="utf-8">
66
<base href="/">
77
<meta name="viewport" content="width=device-width, initial-scale=1">
8-
<title>DB Viewer - GameFoundryStudio</title>
8+
<title>Mock DB - GameFoundryStudio</title>
99
<meta name="description" content="Admin-only read-only mock DB viewer for GameFoundryStudio project data diagnostics.">
1010
<link rel="icon" href="/favicon.svg">
1111
<link rel="stylesheet" href="assets/theme-v2/css/theme.css">
@@ -17,7 +17,7 @@
1717
<section class="page-title">
1818
<div class="container">
1919
<div class="kicker">Admin Only / Mock DB</div>
20-
<h1>Mock DB Project Journey Tables</h1>
20+
<h1>Mock DB</h1>
2121
<p class="lede">Read-only mock DB dump for project tables, relationships, and data diagnostics.</p>
2222
</div>
2323
</section>
@@ -26,13 +26,13 @@ <h1>Mock DB Project Journey Tables</h1>
2626
<div class="tool-workspace tool-workspace--wide">
2727
<aside class="tool-column tool-group-build">
2828
<div class="tool-column-header">
29-
<h2>DB Viewer</h2>
29+
<h2>Mock DB</h2>
3030
</div>
3131
<div class="accordion-stack">
3232
<details class="vertical-accordion" open>
3333
<summary>Scope</summary>
3434
<div class="accordion-body content-stack">
35-
<p>Project Journey mock DB records are displayed as a read-only human-readable dump.</p>
35+
<p>Current tool mock DB records are displayed as a read-only human-readable dump.</p>
3636
<p>Use the diagnostics panel to confirm table attribution, relationships, and table bleed checks.</p>
3737
</div>
3838
</details>
@@ -43,10 +43,11 @@ <h2>DB Viewer</h2>
4343
<div class="content-cluster">
4444
<div>
4545
<div class="kicker">Mock DB</div>
46-
<h2 id="admin-db-viewer-title">Project Journey Tables</h2>
46+
<h2 id="admin-db-viewer-title">Mock DB</h2>
4747
</div>
4848
<span class="status" role="status" data-admin-db-status>Loading mock DB records.</span>
4949
</div>
50+
<div class="action-group" role="group" aria-label="Mock DB table filters" data-admin-db-filters></div>
5051
<section class="content-stack" aria-label="Mock DB tables" data-admin-db-tables></section>
5152
</section>
5253
<aside class="tool-column tool-group-build">

admin/db-viewer.js

Lines changed: 192 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,49 @@
11
import { createProjectJourneyMockRepository } from "../toolbox/project-journey/project-journey-mock-repository.js";
2+
import { createAssetToolMockRepository } from "../toolbox/assets/assets-mock-repository.js";
3+
import { createProjectWorkspacePaletteRepository } from "../toolbox/colors/palette-workspace-repository.js";
4+
import {
5+
getAllPersistedMockDbTables,
6+
getStandaloneMockDbTables,
7+
} from "../src/shared/mock-db/mock-db-store.js";
28

39
const AUDIT_FIELDS = ["createdAt", "updatedAt", "createdByType", "updatedByType"];
10+
const TOOL_GROUP_ORDER = ["project-journey", "palette", "asset"];
11+
const TOOL_GROUP_LABELS = Object.freeze({
12+
asset: "Asset",
13+
palette: "Palette",
14+
"project-journey": "Project Journey",
15+
});
16+
const STANDALONE_TABLE_LABELS = Object.freeze({
17+
users: "Users",
18+
actors: "Actors",
19+
});
20+
const AUDIT_REQUIRED_TABLES = new Set(["users", "actors"]);
421

522
class AdminDbViewer {
623
constructor(documentRef = document) {
724
this.document = documentRef;
8-
this.repository = createProjectJourneyMockRepository();
25+
this.projectJourneyRepository = createProjectJourneyMockRepository();
26+
this.paletteRepository = createProjectWorkspacePaletteRepository();
27+
this.assetRepository = createAssetToolMockRepository();
28+
this.activeFilter = "all";
29+
this.filterRoot = documentRef.querySelector("[data-admin-db-filters]");
930
this.status = documentRef.querySelector("[data-admin-db-status]");
1031
this.diagnostics = documentRef.querySelector("[data-admin-db-diagnostics]");
1132
this.relationships = documentRef.querySelector("[data-admin-db-relationships]");
1233
this.tablesRoot = documentRef.querySelector("[data-admin-db-tables]");
1334
}
1435

1536
start() {
16-
this.repository.openProject("demo-project");
17-
const tables = this.repository.getTables();
18-
this.renderDiagnostics(tables);
19-
this.renderRelationships(tables);
20-
this.renderTables(tables);
21-
if (this.status) {
22-
this.status.textContent = "Read-only mock DB dump loaded for Demo Project.";
23-
}
37+
this.projectJourneyRepository.openProject("demo-project");
38+
this.render();
39+
this.filterRoot?.addEventListener("click", (event) => {
40+
const button = event.target.closest("[data-admin-db-filter]");
41+
if (!button) {
42+
return;
43+
}
44+
this.activeFilter = button.dataset.adminDbFilter || "all";
45+
this.render();
46+
});
2447
}
2548

2649
createElement(tagName, options = {}) {
@@ -72,6 +95,55 @@ class AdminDbViewer {
7295
return fields;
7396
}
7497

98+
collectSnapshot() {
99+
const projectJourneyTables = this.projectJourneyRepository.getTables();
100+
const paletteTables = this.paletteRepository.getTables();
101+
const assetTables = this.assetRepository.getTables();
102+
const standaloneTables = getStandaloneMockDbTables();
103+
const persistedTables = getAllPersistedMockDbTables();
104+
const tables = {
105+
...persistedTables,
106+
...projectJourneyTables,
107+
...paletteTables,
108+
...assetTables,
109+
...standaloneTables,
110+
};
111+
const groups = [
112+
{
113+
id: "all",
114+
label: "All",
115+
tableNames: Object.keys(tables).sort(),
116+
type: "all",
117+
},
118+
...TOOL_GROUP_ORDER.map((id) => ({
119+
id,
120+
label: TOOL_GROUP_LABELS[id],
121+
tableNames: Object.keys(
122+
id === "project-journey"
123+
? projectJourneyTables
124+
: id === "palette"
125+
? paletteTables
126+
: assetTables,
127+
).sort(),
128+
type: "tool",
129+
})),
130+
...Object.entries(STANDALONE_TABLE_LABELS).map(([tableName, label]) => ({
131+
id: tableName,
132+
label,
133+
tableNames: [tableName],
134+
type: "table",
135+
})),
136+
];
137+
return {
138+
groups,
139+
tables,
140+
};
141+
}
142+
143+
activeGroup(groups) {
144+
return groups.find((group) => group.id === this.activeFilter) || groups[0];
145+
}
146+
75147
renderTable(tableName, records) {
76148
const details = this.createElement("details", {
77149
className: "vertical-accordion",
@@ -134,9 +206,29 @@ class AdminDbViewer {
134206
});
135207
}
136208

209+
renderFilters(groups) {
210+
if (!this.filterRoot) {
211+
return;
212+
}
213+
this.filterRoot.replaceChildren();
214+
groups.forEach((group) => {
215+
const button = this.createElement("button", {
216+
className: group.id === this.activeFilter ? "btn btn--compact primary" : "btn btn--compact",
217+
text: group.label,
218+
});
219+
button.type = "button";
220+
button.dataset.adminDbFilter = group.id;
221+
button.setAttribute("aria-pressed", String(group.id === this.activeFilter));
222+
this.filterRoot.append(button);
223+
});
224+
}
225+
137226
auditFindings(tables) {
138227
const findings = [];
139228
Object.entries(tables).forEach(([tableName, records]) => {
229+
if (!tableName.startsWith("project_journey_") && !AUDIT_REQUIRED_TABLES.has(tableName)) {
230+
return;
231+
}
140232
records.forEach((record) => {
141233
AUDIT_FIELDS.forEach((field) => {
142234
if (!Object.hasOwn(record, field)) {
@@ -149,49 +241,95 @@ class AdminDbViewer {
149241
}
150242

151243
relationshipsForTables(tables) {
152-
const noteTypeKeys = new Set(tables.project_journey_note_types.map((type) => type.key));
153-
const noteKeys = new Set(tables.project_journey_notes.map((note) => note.key));
244+
const noteTypeKeys = new Set((tables.project_journey_note_types || []).map((type) => type.key));
245+
const noteKeys = new Set((tables.project_journey_notes || []).map((note) => note.key));
246+
const userKeys = new Set((tables.users || []).map((user) => user.key));
247+
const actorUserKeys = new Set((tables.actors || []).map((actor) => actor.userKey));
154248
const activeTemplateKeys = new Set(
155-
tables.project_journey_templates
249+
(tables.project_journey_templates || [])
156250
.filter((template) => template.isActive)
157251
.map((template) => template.key),
158252
);
253+
const paletteGlobals = new Set((tables.project_workspace_palette_globals || []).map((row) => row.projectId));
254+
const paletteColorKeys = new Set((tables.palette_colors || []).map((row) => `${row.projectId}:${row.symbol}`));
255+
const assetIds = new Set((tables.asset_library_items || []).map((asset) => asset.id));
256+
const assetStorageIds = new Set((tables.asset_storage_objects || []).map((object) => object.id));
159257
return [
160258
{
161259
name: "project_journey_notes.typeKey -> project_journey_note_types.key",
162-
checked: tables.project_journey_notes.length,
163-
missing: tables.project_journey_notes.filter((note) => !noteTypeKeys.has(note.typeKey)),
260+
checked: (tables.project_journey_notes || []).length,
261+
missing: (tables.project_journey_notes || []).filter((note) => !noteTypeKeys.has(note.typeKey)),
262+
},
263+
{
264+
name: "project_journey_notes.ownerKey -> users.key",
265+
checked: (tables.project_journey_notes || []).length,
266+
missing: (tables.project_journey_notes || []).filter((note) => !userKeys.has(note.ownerKey)),
164267
},
165268
{
166269
name: "project_journey_items.noteKey -> project_journey_notes.key",
167-
checked: tables.project_journey_items.length,
168-
missing: tables.project_journey_items.filter((item) => !noteKeys.has(item.noteKey)),
270+
checked: (tables.project_journey_items || []).length,
271+
missing: (tables.project_journey_items || []).filter((item) => !noteKeys.has(item.noteKey)),
169272
},
170273
{
171274
name: "system project_journey_items.templateKey -> active project_journey_templates.key",
172-
checked: tables.project_journey_items.filter((item) => item.createdByType === "system").length,
173-
missing: tables.project_journey_items.filter(
275+
checked: (tables.project_journey_items || []).filter((item) => item.createdByType === "system").length,
276+
missing: (tables.project_journey_items || []).filter(
174277
(item) => item.createdByType === "system" && !activeTemplateKeys.has(item.templateKey),
175278
),
176279
},
177280
{
178281
name: "project_journey_activity.noteKey -> project_journey_notes.key",
179-
checked: tables.project_journey_activity.length,
180-
missing: tables.project_journey_activity.filter((activity) => !noteKeys.has(activity.noteKey)),
282+
checked: (tables.project_journey_activity || []).length,
283+
missing: (tables.project_journey_activity || []).filter((activity) => !noteKeys.has(activity.noteKey)),
284+
},
285+
{
286+
name: "actors.userKey -> users.key",
287+
checked: (tables.actors || []).length,
288+
missing: (tables.actors || []).filter((actor) => !userKeys.has(actor.userKey)),
289+
},
290+
{
291+
name: "users.key -> actors.userKey",
292+
checked: (tables.users || []).length,
293+
missing: (tables.users || []).filter((user) => !actorUserKeys.has(user.key)),
294+
},
295+
{
296+
name: "palette_colors.projectId -> project_workspace_palette_globals.projectId",
297+
checked: (tables.palette_colors || []).length,
298+
missing: (tables.palette_colors || []).filter((row) => !paletteGlobals.has(row.projectId)),
299+
},
300+
{
301+
name: "palette_swatch_usages.swatchSymbol -> palette_colors.symbol",
302+
checked: (tables.palette_swatch_usages || []).length,
303+
missing: (tables.palette_swatch_usages || []).filter((row) => !paletteColorKeys.has(`${row.projectId}:${row.swatchSymbol}`)),
304+
},
305+
{
306+
name: "palette_swatch_usages.assetId -> asset_library_items.id",
307+
checked: (tables.palette_swatch_usages || []).length,
308+
missing: (tables.palette_swatch_usages || []).filter((row) => !assetIds.has(row.assetId)),
309+
},
310+
{
311+
name: "asset_library_items.storageObjectId -> asset_storage_objects.id",
312+
checked: (tables.asset_library_items || []).length,
313+
missing: (tables.asset_library_items || []).filter((asset) => !assetStorageIds.has(asset.storageObjectId)),
314+
},
315+
{
316+
name: "asset_import_events.assetId -> asset_library_items.id",
317+
checked: (tables.asset_import_events || []).length,
318+
missing: (tables.asset_import_events || []).filter((event) => !assetIds.has(event.assetId)),
181319
},
182320
];
183321
}
184322

185323
tableBleedFindings(tables) {
186-
const notesByKey = new Map(tables.project_journey_notes.map((note) => [note.key, note]));
324+
const notesByKey = new Map((tables.project_journey_notes || []).map((note) => [note.key, note]));
187325
const findings = [];
188-
tables.project_journey_items.forEach((item) => {
326+
(tables.project_journey_items || []).forEach((item) => {
189327
const note = notesByKey.get(item.noteKey);
190328
if (note && note.projectKey !== item.projectKey) {
191329
findings.push(`${item.key} projectKey ${item.projectKey} does not match note ${note.key} projectKey ${note.projectKey}.`);
192330
}
193331
});
194-
tables.project_journey_activity.forEach((activity) => {
332+
(tables.project_journey_activity || []).forEach((activity) => {
195333
const note = notesByKey.get(activity.noteKey);
196334
if (note && note.projectKey !== activity.projectKey) {
197335
findings.push(`${activity.key} projectKey ${activity.projectKey} does not match note ${note.key} projectKey ${note.projectKey}.`);
@@ -200,6 +338,15 @@ class AdminDbViewer {
200338
return findings;
201339
}
202340

341+
staleDisplayFindings(tables, groups) {
342+
const groupTableNames = new Set(groups.flatMap((group) => group.tableNames));
343+
const unownedTables = Object.keys(tables).filter((tableName) => !groupTableNames.has(tableName));
344+
if (unownedTables.length) {
345+
return unownedTables.map((tableName) => `${tableName} has live data but no Mock DB filter owner.`);
346+
}
347+
return ["No stale display data detected; tables are rendered from current mock DB snapshots."];
348+
}
349+
203350
renderList(messages, dataName) {
204351
const list = this.createElement("ul");
205352
list.dataset[dataName] = "";
@@ -209,17 +356,19 @@ class AdminDbViewer {
209356
return list;
210357
}
211358

212-
renderDiagnostics(tables) {
359+
renderDiagnostics(tables, groups) {
213360
this.diagnostics.replaceChildren();
214361
const auditFindings = this.auditFindings(tables);
215362
const bleedFindings = this.tableBleedFindings(tables);
363+
const staleFindings = this.staleDisplayFindings(tables, groups);
216364
const auditSummary = auditFindings.length
217365
? auditFindings
218-
: ["All Project Journey mock DB tables include createdAt, updatedAt, createdByType, and updatedByType."];
366+
: ["All current mock DB tables include createdAt, updatedAt, createdByType, and updatedByType where required by the owning mock model."];
219367
const bleedSummary = bleedFindings.length ? bleedFindings : ["No table bleed detected."];
220368
this.diagnostics.append(
221369
this.renderList(auditSummary, "adminDbAuditFindings"),
222370
this.renderList(bleedSummary, "adminDbBleedFindings"),
371+
this.renderList(staleFindings, "adminDbStaleDisplayFindings"),
223372
);
224373
}
225374

@@ -244,6 +393,24 @@ class AdminDbViewer {
244393
this.renderList(missingLinks.length ? missingLinks : ["No missing links detected."], "adminDbMissingLinks"),
245394
);
246395
}
396+
397+
render() {
398+
const snapshot = this.collectSnapshot();
399+
const group = this.activeGroup(snapshot.groups);
400+
const visibleTables = Object.fromEntries(
401+
group.tableNames
402+
.filter((tableName) => Object.hasOwn(snapshot.tables, tableName))
403+
.map((tableName) => [tableName, snapshot.tables[tableName]]),
404+
);
405+
this.renderFilters(snapshot.groups);
406+
this.renderDiagnostics(snapshot.tables, snapshot.groups);
407+
this.renderRelationships(snapshot.tables);
408+
this.renderTables(visibleTables);
409+
if (this.status) {
410+
const recordCount = Object.values(visibleTables).reduce((total, rows) => total + rows.length, 0);
411+
this.status.textContent = `Mock DB loaded ${Object.keys(visibleTables).length} table${Object.keys(visibleTables).length === 1 ? "" : "s"} and ${recordCount} record${recordCount === 1 ? "" : "s"} for ${group.label}.`;
412+
}
413+
}
247414
}
248415

249416
new AdminDbViewer().start();

assets/theme-v2/css/panels.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@
159159
color: var(--tool-group-color)
160160
}
161161

162+
.tool-column[class*="tool-group-"] hr {
163+
border-color: var(--tool-group-accent)
164+
}
165+
162166
.tool-column .accordion-stack {
163167
padding: var(--space-8) var(--space-12);
164168
gap: var(--space-8)
@@ -436,6 +440,10 @@ body.tool-focus-mode .tool-display-mode__badge {
436440
min-height: var(--space-0)
437441
}
438442

443+
.tool-column[class*="tool-group-"] .tool-stat-divider {
444+
border-top-color: var(--tool-group-accent)
445+
}
446+
439447
.brand-color-code {
440448
align-items: center;
441449
display: flex;

0 commit comments

Comments
 (0)