@emeryld/rrroutes-export 1.0.12 → 1.0.14
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/README.md +16 -0
- package/dist/exportFinalizedLeaves.bundle.d.ts +21 -0
- package/dist/exportFinalizedLeaves.changelog.d.ts +1 -0
- package/dist/index.cjs +136 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +133 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/tools/finalized-leaves-viewer.html +390 -139
|
@@ -529,6 +529,23 @@
|
|
|
529
529
|
</div>
|
|
530
530
|
</details>
|
|
531
531
|
|
|
532
|
+
<div class="scope-row">
|
|
533
|
+
<div class="scope-group">
|
|
534
|
+
<div class="scope-group-title">Views</div>
|
|
535
|
+
<div class="scope-group-chips">
|
|
536
|
+
<button id="tabLeaves" class="field-chip" type="button" aria-pressed="true">
|
|
537
|
+
Leaves
|
|
538
|
+
</button>
|
|
539
|
+
<button id="tabChangelogRoute" class="field-chip" type="button" aria-pressed="false">
|
|
540
|
+
Changelog / By Route
|
|
541
|
+
</button>
|
|
542
|
+
<button id="tabChangelogSource" class="field-chip" type="button" aria-pressed="false">
|
|
543
|
+
Changelog / By Source
|
|
544
|
+
</button>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
|
|
532
549
|
<div class="active-filters-row">
|
|
533
550
|
<div class="active-filters-title">Active filters</div>
|
|
534
551
|
<div id="activeFilterChips" class="chips filters"></div>
|
|
@@ -612,6 +629,68 @@
|
|
|
612
629
|
},
|
|
613
630
|
]
|
|
614
631
|
|
|
632
|
+
const CHANGELOG_ROUTE_FIELDS = [
|
|
633
|
+
{
|
|
634
|
+
id: 'routeKey',
|
|
635
|
+
label: 'route',
|
|
636
|
+
get: (item) => [item.routeKey],
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
id: 'eventType',
|
|
640
|
+
label: 'event type',
|
|
641
|
+
get: (item) => item.events.map((event) => event.type),
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
id: 'schemaDiff',
|
|
645
|
+
label: 'schema diff',
|
|
646
|
+
get: (item) =>
|
|
647
|
+
item.events.flatMap((event) =>
|
|
648
|
+
Array.isArray(event.schemaDiff)
|
|
649
|
+
? event.schemaDiff.map((delta) => JSON.stringify(delta))
|
|
650
|
+
: [],
|
|
651
|
+
),
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
id: 'cfgDiff',
|
|
655
|
+
label: 'cfg diff',
|
|
656
|
+
get: (item) =>
|
|
657
|
+
item.events.flatMap((event) =>
|
|
658
|
+
Array.isArray(event.cfgDiff)
|
|
659
|
+
? event.cfgDiff.map((delta) => JSON.stringify(delta))
|
|
660
|
+
: [],
|
|
661
|
+
),
|
|
662
|
+
},
|
|
663
|
+
]
|
|
664
|
+
|
|
665
|
+
const CHANGELOG_SOURCE_FIELDS = [
|
|
666
|
+
{
|
|
667
|
+
id: 'sourceObject',
|
|
668
|
+
label: 'source object',
|
|
669
|
+
get: (item) => [item.sourceId, item.displayName],
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
id: 'eventType',
|
|
673
|
+
label: 'event type',
|
|
674
|
+
get: (item) => item.events.map((event) => event.type),
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
id: 'impactedRoutes',
|
|
678
|
+
label: 'impacted routes',
|
|
679
|
+
get: (item) =>
|
|
680
|
+
item.events.flatMap((event) => (Array.isArray(event.impactedRoutes) ? event.impactedRoutes : [])),
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
id: 'schemaDiff',
|
|
684
|
+
label: 'schema diff',
|
|
685
|
+
get: (item) =>
|
|
686
|
+
item.events.flatMap((event) =>
|
|
687
|
+
Array.isArray(event.schemaDiff)
|
|
688
|
+
? event.schemaDiff.map((delta) => JSON.stringify(delta))
|
|
689
|
+
: [],
|
|
690
|
+
),
|
|
691
|
+
},
|
|
692
|
+
]
|
|
693
|
+
|
|
615
694
|
const SCHEMA_SECTIONS = ['params', 'query', 'body', 'output']
|
|
616
695
|
const SCHEMA_FIELD_IDS = new Set(SCHEMA_SECTIONS)
|
|
617
696
|
const SOURCE_FIELD_IDS = new Set(['sourceDefinition', 'sourceSchemas'])
|
|
@@ -645,11 +724,39 @@
|
|
|
645
724
|
},
|
|
646
725
|
]
|
|
647
726
|
|
|
727
|
+
const VIEW_IDS = {
|
|
728
|
+
leaves: 'leaves',
|
|
729
|
+
changelogRoute: 'changelogRoute',
|
|
730
|
+
changelogSource: 'changelogSource',
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const TAB_CONFIG = {
|
|
734
|
+
[VIEW_IDS.leaves]: {
|
|
735
|
+
fieldDefs: SEARCH_FIELDS,
|
|
736
|
+
defaultSelected: new Set(SEARCH_FIELDS.map((field) => field.id)),
|
|
737
|
+
},
|
|
738
|
+
[VIEW_IDS.changelogRoute]: {
|
|
739
|
+
fieldDefs: CHANGELOG_ROUTE_FIELDS,
|
|
740
|
+
defaultSelected: new Set(CHANGELOG_ROUTE_FIELDS.map((field) => field.id)),
|
|
741
|
+
},
|
|
742
|
+
[VIEW_IDS.changelogSource]: {
|
|
743
|
+
fieldDefs: CHANGELOG_SOURCE_FIELDS,
|
|
744
|
+
defaultSelected: new Set(CHANGELOG_SOURCE_FIELDS.map((field) => field.id)),
|
|
745
|
+
},
|
|
746
|
+
}
|
|
747
|
+
|
|
648
748
|
const state = {
|
|
649
749
|
payload: null,
|
|
750
|
+
leavesPayload: null,
|
|
751
|
+
changelogPayload: null,
|
|
650
752
|
leaves: [],
|
|
651
|
-
|
|
652
|
-
|
|
753
|
+
activeTab: VIEW_IDS.leaves,
|
|
754
|
+
availableTabs: new Set([VIEW_IDS.leaves]),
|
|
755
|
+
selectedFieldIdsByTab: {
|
|
756
|
+
[VIEW_IDS.leaves]: new Set(TAB_CONFIG[VIEW_IDS.leaves].defaultSelected),
|
|
757
|
+
[VIEW_IDS.changelogRoute]: new Set(TAB_CONFIG[VIEW_IDS.changelogRoute].defaultSelected),
|
|
758
|
+
[VIEW_IDS.changelogSource]: new Set(TAB_CONFIG[VIEW_IDS.changelogSource].defaultSelected),
|
|
759
|
+
},
|
|
653
760
|
}
|
|
654
761
|
|
|
655
762
|
const fileInput = document.getElementById('fileInput')
|
|
@@ -669,6 +776,9 @@
|
|
|
669
776
|
const statusEl = document.getElementById('status')
|
|
670
777
|
const resultsEl = document.getElementById('results')
|
|
671
778
|
const controlsEl = document.querySelector('.controls')
|
|
779
|
+
const tabLeavesBtn = document.getElementById('tabLeaves')
|
|
780
|
+
const tabChangelogRouteBtn = document.getElementById('tabChangelogRoute')
|
|
781
|
+
const tabChangelogSourceBtn = document.getElementById('tabChangelogSource')
|
|
672
782
|
|
|
673
783
|
const URL_PARAM_KEY = 'filters'
|
|
674
784
|
let isHydratingFromUrl = false
|
|
@@ -1316,107 +1426,153 @@
|
|
|
1316
1426
|
)
|
|
1317
1427
|
}
|
|
1318
1428
|
|
|
1319
|
-
function
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
controlsEl.style.display = 'none'
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
const commitBySha = new Map(
|
|
1329
|
-
(payload.commits || []).map((commit) => [commit.sha, commit]),
|
|
1330
|
-
)
|
|
1331
|
-
const routeEntries = Object.entries(payload.byRoute || {}).sort(
|
|
1332
|
-
([a], [b]) => a.localeCompare(b),
|
|
1333
|
-
)
|
|
1334
|
-
const sourceEntries = Object.entries(payload.bySourceObject || {}).sort(
|
|
1335
|
-
([a], [b]) => a.localeCompare(b),
|
|
1429
|
+
function isBundlePayload(value) {
|
|
1430
|
+
return (
|
|
1431
|
+
value &&
|
|
1432
|
+
value.kind === 'finalized-leaves-bundle' &&
|
|
1433
|
+
value.leavesPayload &&
|
|
1434
|
+
value.changelogPayload
|
|
1336
1435
|
)
|
|
1436
|
+
}
|
|
1337
1437
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1438
|
+
function currentFieldDefs() {
|
|
1439
|
+
return TAB_CONFIG[state.activeTab]?.fieldDefs || []
|
|
1440
|
+
}
|
|
1340
1441
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
tabs.appendChild(bySourceBtn)
|
|
1348
|
-
resultsEl.appendChild(tabs)
|
|
1349
|
-
|
|
1350
|
-
const view = el('div')
|
|
1351
|
-
resultsEl.appendChild(view)
|
|
1352
|
-
|
|
1353
|
-
const renderEventDetails = (event) => {
|
|
1354
|
-
const card = el('div', 'leaf-content')
|
|
1355
|
-
const commit = commitBySha.get(event.commitSha)
|
|
1356
|
-
card.appendChild(
|
|
1357
|
-
el(
|
|
1358
|
-
'div',
|
|
1359
|
-
'meta',
|
|
1360
|
-
`${event.commitSha.slice(0, 12)}${commit?.subject ? ` - ${commit.subject}` : ''}`,
|
|
1361
|
-
),
|
|
1362
|
-
)
|
|
1363
|
-
card.appendChild(el('div', 'mono', event.type))
|
|
1364
|
-
if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
|
|
1365
|
-
card.appendChild(el('div', 'meta', `schema changes: ${event.schemaDiff.length}`))
|
|
1366
|
-
}
|
|
1367
|
-
if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
|
|
1368
|
-
card.appendChild(el('div', 'meta', `cfg changes: ${event.cfgDiff.length}`))
|
|
1369
|
-
}
|
|
1370
|
-
if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
|
|
1371
|
-
card.appendChild(
|
|
1372
|
-
el('div', 'meta', `impacted routes: ${event.impactedRoutes.length}`),
|
|
1373
|
-
)
|
|
1374
|
-
}
|
|
1375
|
-
return card
|
|
1376
|
-
}
|
|
1442
|
+
function formatCommitDateTime(value) {
|
|
1443
|
+
if (!value) return null
|
|
1444
|
+
const parsed = new Date(value)
|
|
1445
|
+
if (Number.isNaN(parsed.getTime())) return String(value)
|
|
1446
|
+
return parsed.toLocaleString()
|
|
1447
|
+
}
|
|
1377
1448
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1449
|
+
function formatRouteSchemaEntry(entry) {
|
|
1450
|
+
if (!entry || typeof entry !== 'object') return 'n/a'
|
|
1451
|
+
const parts = [
|
|
1452
|
+
`type=${entry.type}`,
|
|
1453
|
+
`nullable=${Boolean(entry.nullable)}`,
|
|
1454
|
+
`optional=${Boolean(entry.optional)}`,
|
|
1455
|
+
]
|
|
1456
|
+
if ('literal' in entry) {
|
|
1457
|
+
parts.push(`literal=${JSON.stringify(entry.literal)}`)
|
|
1458
|
+
}
|
|
1459
|
+
return parts.join(', ')
|
|
1460
|
+
}
|
|
1384
1461
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1462
|
+
function formatSchemaDiffLine(delta) {
|
|
1463
|
+
if (!delta || typeof delta !== 'object') return null
|
|
1464
|
+
if ('section' in delta) {
|
|
1465
|
+
const location = `${delta.section}.${delta.path}`
|
|
1466
|
+
if (delta.op === 'added') return `added ${location}: ${JSON.stringify(delta.after || [])}`
|
|
1467
|
+
if (delta.op === 'removed') return `removed ${location}: ${JSON.stringify(delta.before || [])}`
|
|
1468
|
+
return `changed ${location}: ${JSON.stringify(delta.before || [])} -> ${JSON.stringify(delta.after || [])}`
|
|
1469
|
+
}
|
|
1470
|
+
if ('path' in delta) {
|
|
1471
|
+
if (delta.op === 'added') return `added ${delta.path}: ${formatRouteSchemaEntry(delta.after)}`
|
|
1472
|
+
if (delta.op === 'removed') return `removed ${delta.path}: ${formatRouteSchemaEntry(delta.before)}`
|
|
1473
|
+
return `changed ${delta.path}: ${formatRouteSchemaEntry(delta.before)} -> ${formatRouteSchemaEntry(delta.after)}`
|
|
1474
|
+
}
|
|
1475
|
+
return null
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function formatCfgDiffLine(diff) {
|
|
1479
|
+
if (!diff || typeof diff !== 'object') return null
|
|
1480
|
+
return `${diff.field}: ${JSON.stringify(diff.before)} -> ${JSON.stringify(diff.after)}`
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function renderEventCard(event, commitBySha) {
|
|
1484
|
+
const card = el('div', 'leaf-content')
|
|
1485
|
+
const commit = commitBySha.get(event.commitSha)
|
|
1486
|
+
const commitDateTime = formatCommitDateTime(commit?.authorDate)
|
|
1487
|
+
const commitMeta = [
|
|
1488
|
+
event.commitSha.slice(0, 12),
|
|
1489
|
+
commitDateTime,
|
|
1490
|
+
commit?.authorName || null,
|
|
1491
|
+
commit?.subject || null,
|
|
1492
|
+
]
|
|
1493
|
+
.filter(Boolean)
|
|
1494
|
+
.join(' - ')
|
|
1495
|
+
card.appendChild(el('div', 'meta', commitMeta))
|
|
1496
|
+
card.appendChild(el('div', 'mono', event.type))
|
|
1497
|
+
if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
|
|
1498
|
+
card.appendChild(el('div', 'meta', `schema changes: ${event.schemaDiff.length}`))
|
|
1499
|
+
const schemaChanges = el('div')
|
|
1500
|
+
event.schemaDiff.forEach((delta) => {
|
|
1501
|
+
const line = formatSchemaDiffLine(delta)
|
|
1502
|
+
if (!line) return
|
|
1503
|
+
schemaChanges.appendChild(el('div', 'meta', line))
|
|
1390
1504
|
})
|
|
1505
|
+
card.appendChild(schemaChanges)
|
|
1391
1506
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
)
|
|
1399
|
-
return
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
sourceEntries.forEach(([sourceId, events]) => {
|
|
1403
|
-
const details = el('details', 'leaf')
|
|
1404
|
-
const display = events[0]?.displayName || sourceId
|
|
1405
|
-
details.appendChild(el('summary', '', `${display} (${events.length})`))
|
|
1406
|
-
events.forEach((event) => details.appendChild(renderEventDetails(event)))
|
|
1407
|
-
view.appendChild(details)
|
|
1507
|
+
if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
|
|
1508
|
+
card.appendChild(el('div', 'meta', `cfg changes: ${event.cfgDiff.length}`))
|
|
1509
|
+
const cfgChanges = el('div')
|
|
1510
|
+
event.cfgDiff.forEach((diff) => {
|
|
1511
|
+
const line = formatCfgDiffLine(diff)
|
|
1512
|
+
if (!line) return
|
|
1513
|
+
cfgChanges.appendChild(el('div', 'meta', line))
|
|
1408
1514
|
})
|
|
1515
|
+
card.appendChild(cfgChanges)
|
|
1516
|
+
}
|
|
1517
|
+
if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
|
|
1518
|
+
card.appendChild(el('div', 'meta', `impacted routes: ${event.impactedRoutes.length}`))
|
|
1409
1519
|
}
|
|
1520
|
+
return card
|
|
1521
|
+
}
|
|
1410
1522
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1523
|
+
function changelogItemsByTab(tabId) {
|
|
1524
|
+
const payload = state.changelogPayload
|
|
1525
|
+
if (!payload) return []
|
|
1526
|
+
if (tabId === VIEW_IDS.changelogRoute) {
|
|
1527
|
+
return Object.entries(payload.byRoute || {})
|
|
1528
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
1529
|
+
.map(([routeKey, events]) => ({ routeKey, events }))
|
|
1530
|
+
}
|
|
1531
|
+
if (tabId === VIEW_IDS.changelogSource) {
|
|
1532
|
+
return Object.entries(payload.bySourceObject || {})
|
|
1533
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
1534
|
+
.map(([sourceId, events]) => ({
|
|
1535
|
+
sourceId,
|
|
1536
|
+
events,
|
|
1537
|
+
displayName: events[0]?.displayName || sourceId,
|
|
1538
|
+
}))
|
|
1539
|
+
}
|
|
1540
|
+
return []
|
|
1414
1541
|
}
|
|
1415
1542
|
|
|
1416
|
-
function
|
|
1417
|
-
if (
|
|
1543
|
+
function matchesGenericItem(item, fieldDefs, engine, selectedIds) {
|
|
1544
|
+
if (!engine.active) return true
|
|
1545
|
+
return fieldDefs.some((field) => {
|
|
1546
|
+
if (selectedIds.length > 0 && !selectedIds.includes(field.id)) return false
|
|
1547
|
+
const tokens = toTokens(field.get(item))
|
|
1548
|
+
return tokens.some((token) => engine.test(token))
|
|
1549
|
+
})
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function renderCollection(items, renderItem, emptyMessage) {
|
|
1553
|
+
if (items.length === 0) {
|
|
1554
|
+
resultsEl.appendChild(el('div', 'empty', emptyMessage))
|
|
1418
1555
|
return
|
|
1419
1556
|
}
|
|
1557
|
+
items.forEach((item) => {
|
|
1558
|
+
resultsEl.appendChild(renderItem(item))
|
|
1559
|
+
})
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function renderTabButtons() {
|
|
1563
|
+
setPressed(tabLeavesBtn, state.activeTab === VIEW_IDS.leaves)
|
|
1564
|
+
setPressed(tabChangelogRouteBtn, state.activeTab === VIEW_IDS.changelogRoute)
|
|
1565
|
+
setPressed(tabChangelogSourceBtn, state.activeTab === VIEW_IDS.changelogSource)
|
|
1566
|
+
tabLeavesBtn.style.display = state.availableTabs.has(VIEW_IDS.leaves) ? '' : 'none'
|
|
1567
|
+
tabChangelogRouteBtn.style.display = state.availableTabs.has(VIEW_IDS.changelogRoute) ? '' : 'none'
|
|
1568
|
+
tabChangelogSourceBtn.style.display = state.availableTabs.has(VIEW_IDS.changelogSource) ? '' : 'none'
|
|
1569
|
+
const isLeavesTab = state.activeTab === VIEW_IDS.leaves
|
|
1570
|
+
coreFieldsBtn.style.display = isLeavesTab ? '' : 'none'
|
|
1571
|
+
schemasOnlyFieldsBtn.style.display = isLeavesTab ? '' : 'none'
|
|
1572
|
+
sourceOnlyFieldsBtn.style.display = isLeavesTab ? '' : 'none'
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function renderResults() {
|
|
1420
1576
|
const engine = createSearchEngine(searchInput.value.trim(), {
|
|
1421
1577
|
caseSensitive: isPressed(caseSensitiveToggle),
|
|
1422
1578
|
regex: isPressed(regexSearchToggle),
|
|
@@ -1434,51 +1590,84 @@
|
|
|
1434
1590
|
}
|
|
1435
1591
|
|
|
1436
1592
|
const selectedIds = selectedFieldIds()
|
|
1437
|
-
const filtered = state.leaves.filter((leaf) => matchesLeaf(leaf, engine, selectedIds))
|
|
1438
|
-
|
|
1439
|
-
statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
|
|
1440
1593
|
resultsEl.innerHTML = ''
|
|
1594
|
+
renderTabButtons()
|
|
1441
1595
|
renderActiveFilterChips({ selectedIds, hasRegexError: false })
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1596
|
+
if (state.activeTab === VIEW_IDS.leaves) {
|
|
1597
|
+
const filtered = state.leaves.filter((leaf) => matchesLeaf(leaf, engine, selectedIds))
|
|
1598
|
+
statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
|
|
1599
|
+
renderCollection(filtered, (leaf) => renderLeaf(leaf, engine, selectedIds), 'No matches.')
|
|
1600
|
+
} else {
|
|
1601
|
+
const fieldDefs = currentFieldDefs()
|
|
1602
|
+
const items = changelogItemsByTab(state.activeTab)
|
|
1603
|
+
const filtered = items.filter((item) => matchesGenericItem(item, fieldDefs, engine, selectedIds))
|
|
1604
|
+
statusEl.textContent =
|
|
1605
|
+
state.activeTab === VIEW_IDS.changelogRoute
|
|
1606
|
+
? `${filtered.length} / ${items.length} route changelog groups matched.`
|
|
1607
|
+
: `${filtered.length} / ${items.length} source changelog groups matched.`
|
|
1608
|
+
const commitBySha = new Map(
|
|
1609
|
+
(state.changelogPayload?.commits || []).map((commit) => [commit.sha, commit]),
|
|
1610
|
+
)
|
|
1611
|
+
renderCollection(
|
|
1612
|
+
filtered,
|
|
1613
|
+
(item) => {
|
|
1614
|
+
const details = el('details', 'leaf')
|
|
1615
|
+
const title =
|
|
1616
|
+
state.activeTab === VIEW_IDS.changelogRoute
|
|
1617
|
+
? `${item.routeKey} (${item.events.length})`
|
|
1618
|
+
: `${item.displayName} (${item.events.length})`
|
|
1619
|
+
details.appendChild(el('summary', '', title))
|
|
1620
|
+
item.events.forEach((event) => details.appendChild(renderEventCard(event, commitBySha)))
|
|
1621
|
+
return details
|
|
1622
|
+
},
|
|
1623
|
+
'No matches.',
|
|
1624
|
+
)
|
|
1447
1625
|
}
|
|
1448
|
-
|
|
1449
|
-
filtered.forEach((leaf) => {
|
|
1450
|
-
resultsEl.appendChild(renderLeaf(leaf, engine, selectedIds))
|
|
1451
|
-
})
|
|
1452
1626
|
syncFilterStateToUrl()
|
|
1453
1627
|
}
|
|
1454
1628
|
|
|
1455
1629
|
function selectedFieldIds() {
|
|
1456
|
-
|
|
1630
|
+
const selected = state.selectedFieldIdsByTab[state.activeTab] || new Set()
|
|
1631
|
+
return currentFieldDefs().filter((field) => selected.has(field.id)).map(
|
|
1457
1632
|
(field) => field.id,
|
|
1458
1633
|
)
|
|
1459
1634
|
}
|
|
1460
1635
|
|
|
1461
1636
|
function renderFieldChips() {
|
|
1637
|
+
const fieldDefs = currentFieldDefs()
|
|
1638
|
+
const selected = state.selectedFieldIdsByTab[state.activeTab] || new Set()
|
|
1462
1639
|
fieldChipGroups.innerHTML = ''
|
|
1463
|
-
|
|
1640
|
+
const groups =
|
|
1641
|
+
state.activeTab === VIEW_IDS.leaves
|
|
1642
|
+
? FIELD_GROUPS
|
|
1643
|
+
: [
|
|
1644
|
+
{
|
|
1645
|
+
id: 'tabFields',
|
|
1646
|
+
label: state.activeTab === VIEW_IDS.changelogRoute ? 'Route fields' : 'Source fields',
|
|
1647
|
+
fieldIds: fieldDefs.map((field) => field.id),
|
|
1648
|
+
},
|
|
1649
|
+
]
|
|
1650
|
+
|
|
1651
|
+
groups.forEach((group) => {
|
|
1464
1652
|
const groupWrap = el('div', 'scope-group')
|
|
1465
1653
|
groupWrap.appendChild(el('div', 'scope-group-title', group.label))
|
|
1466
1654
|
const chipsWrap = el('div', 'scope-group-chips')
|
|
1467
1655
|
|
|
1468
1656
|
group.fieldIds.forEach((fieldId) => {
|
|
1469
|
-
const field =
|
|
1657
|
+
const field = fieldDefs.find((item) => item.id === fieldId)
|
|
1470
1658
|
if (!field) return
|
|
1471
1659
|
const chip = document.createElement('button')
|
|
1472
1660
|
chip.type = 'button'
|
|
1473
1661
|
chip.className = 'field-chip'
|
|
1474
1662
|
chip.textContent = field.label
|
|
1475
|
-
setPressed(chip,
|
|
1663
|
+
setPressed(chip, selected.has(field.id))
|
|
1476
1664
|
chip.addEventListener('click', () => {
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1665
|
+
if (selected.has(field.id)) {
|
|
1666
|
+
selected.delete(field.id)
|
|
1479
1667
|
} else {
|
|
1480
|
-
|
|
1668
|
+
selected.add(field.id)
|
|
1481
1669
|
}
|
|
1670
|
+
state.selectedFieldIdsByTab[state.activeTab] = selected
|
|
1482
1671
|
renderFieldChips()
|
|
1483
1672
|
renderResults()
|
|
1484
1673
|
})
|
|
@@ -1492,8 +1681,8 @@
|
|
|
1492
1681
|
|
|
1493
1682
|
function setFieldSelection(allowedIds, options = {}) {
|
|
1494
1683
|
const { rerender = true } = options
|
|
1495
|
-
state.
|
|
1496
|
-
|
|
1684
|
+
state.selectedFieldIdsByTab[state.activeTab] = new Set(
|
|
1685
|
+
currentFieldDefs().map((field) => field.id).filter((id) => allowedIds.has(id)),
|
|
1497
1686
|
)
|
|
1498
1687
|
renderFieldChips()
|
|
1499
1688
|
if (rerender) renderResults()
|
|
@@ -1505,7 +1694,7 @@
|
|
|
1505
1694
|
setPressed(regexSearchToggle, false)
|
|
1506
1695
|
typeMatchModeInput.value = 'contains'
|
|
1507
1696
|
setPressed(schemaRowsMatchOnlyToggle, false)
|
|
1508
|
-
setFieldSelection(new Set(
|
|
1697
|
+
setFieldSelection(new Set(currentFieldDefs().map((field) => field.id)), { rerender: false })
|
|
1509
1698
|
renderResults()
|
|
1510
1699
|
}
|
|
1511
1700
|
|
|
@@ -1561,7 +1750,7 @@
|
|
|
1561
1750
|
}
|
|
1562
1751
|
if (hasRegexError) chipModels.push({ text: 'regex error' })
|
|
1563
1752
|
|
|
1564
|
-
const allIds =
|
|
1753
|
+
const allIds = currentFieldDefs().map((field) => field.id)
|
|
1565
1754
|
if (selectedIds.length !== allIds.length) {
|
|
1566
1755
|
chipModels.push({
|
|
1567
1756
|
text: `fields: ${selectedIds.join(', ') || 'none'}`,
|
|
@@ -1588,12 +1777,15 @@
|
|
|
1588
1777
|
|
|
1589
1778
|
function collectFilterState() {
|
|
1590
1779
|
return {
|
|
1780
|
+
activeTab: state.activeTab,
|
|
1591
1781
|
search: searchInput.value,
|
|
1592
1782
|
caseSensitive: isPressed(caseSensitiveToggle),
|
|
1593
1783
|
regex: isPressed(regexSearchToggle),
|
|
1594
1784
|
typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
|
|
1595
1785
|
schemaRowsMatchOnly: isPressed(schemaRowsMatchOnlyToggle),
|
|
1596
|
-
|
|
1786
|
+
selectedFieldsByTab: Object.fromEntries(
|
|
1787
|
+
Object.entries(state.selectedFieldIdsByTab).map(([key, value]) => [key, Array.from(value)]),
|
|
1788
|
+
),
|
|
1597
1789
|
}
|
|
1598
1790
|
}
|
|
1599
1791
|
|
|
@@ -1618,6 +1810,13 @@
|
|
|
1618
1810
|
try {
|
|
1619
1811
|
const parsed = JSON.parse(decodeURIComponent(raw))
|
|
1620
1812
|
isHydratingFromUrl = true
|
|
1813
|
+
if (
|
|
1814
|
+
parsed.activeTab === VIEW_IDS.leaves ||
|
|
1815
|
+
parsed.activeTab === VIEW_IDS.changelogRoute ||
|
|
1816
|
+
parsed.activeTab === VIEW_IDS.changelogSource
|
|
1817
|
+
) {
|
|
1818
|
+
state.activeTab = parsed.activeTab
|
|
1819
|
+
}
|
|
1621
1820
|
if (typeof parsed.search === 'string') searchInput.value = parsed.search
|
|
1622
1821
|
setPressed(caseSensitiveToggle, Boolean(parsed.caseSensitive))
|
|
1623
1822
|
setPressed(regexSearchToggle, Boolean(parsed.regex))
|
|
@@ -1626,8 +1825,12 @@
|
|
|
1626
1825
|
}
|
|
1627
1826
|
setPressed(schemaRowsMatchOnlyToggle, Boolean(parsed.schemaRowsMatchOnly))
|
|
1628
1827
|
|
|
1629
|
-
if (
|
|
1630
|
-
|
|
1828
|
+
if (parsed.selectedFieldsByTab && typeof parsed.selectedFieldsByTab === 'object') {
|
|
1829
|
+
Object.entries(parsed.selectedFieldsByTab).forEach(([tabId, ids]) => {
|
|
1830
|
+
if (!Array.isArray(ids)) return
|
|
1831
|
+
if (!state.selectedFieldIdsByTab[tabId]) return
|
|
1832
|
+
state.selectedFieldIdsByTab[tabId] = new Set(ids)
|
|
1833
|
+
})
|
|
1631
1834
|
}
|
|
1632
1835
|
} catch (error) {
|
|
1633
1836
|
// Ignore malformed hash state.
|
|
@@ -1639,40 +1842,84 @@
|
|
|
1639
1842
|
async function handleFile(file) {
|
|
1640
1843
|
const text = await file.text()
|
|
1641
1844
|
const parsed = JSON.parse(text)
|
|
1642
|
-
if (
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1845
|
+
if (
|
|
1846
|
+
!isBundlePayload(parsed) &&
|
|
1847
|
+
!isChangelogPayload(parsed) &&
|
|
1848
|
+
(!parsed || !Array.isArray(parsed.leaves))
|
|
1849
|
+
) {
|
|
1647
1850
|
throw new Error(
|
|
1648
|
-
'Invalid export file: expected
|
|
1851
|
+
'Invalid export file: expected leaves payload, changelog payload, or unified bundle.',
|
|
1649
1852
|
)
|
|
1650
1853
|
}
|
|
1854
|
+
applyLoadedPayload(parsed)
|
|
1855
|
+
renderResults()
|
|
1856
|
+
}
|
|
1651
1857
|
|
|
1652
|
-
|
|
1653
|
-
if (
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
state.payload = parsed
|
|
1657
|
-
state.leaves = parsed.leaves
|
|
1858
|
+
function setActiveTab(nextTab) {
|
|
1859
|
+
if (!state.availableTabs.has(nextTab)) return
|
|
1860
|
+
state.activeTab = nextTab
|
|
1861
|
+
renderFieldChips()
|
|
1658
1862
|
renderResults()
|
|
1659
1863
|
}
|
|
1660
1864
|
|
|
1865
|
+
function applyLoadedPayload(payload) {
|
|
1866
|
+
state.payload = payload
|
|
1867
|
+
state.availableTabs = new Set()
|
|
1868
|
+
state.leavesPayload = null
|
|
1869
|
+
state.changelogPayload = null
|
|
1870
|
+
state.leaves = []
|
|
1871
|
+
|
|
1872
|
+
if (isBundlePayload(payload)) {
|
|
1873
|
+
state.leavesPayload = payload.leavesPayload
|
|
1874
|
+
state.changelogPayload = payload.changelogPayload
|
|
1875
|
+
} else if (isChangelogPayload(payload)) {
|
|
1876
|
+
state.changelogPayload = payload
|
|
1877
|
+
} else if (payload && Array.isArray(payload.leaves)) {
|
|
1878
|
+
state.leavesPayload = payload
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
if (state.leavesPayload) {
|
|
1882
|
+
state.availableTabs.add(VIEW_IDS.leaves)
|
|
1883
|
+
state.leaves = state.leavesPayload.leaves || []
|
|
1884
|
+
}
|
|
1885
|
+
if (state.changelogPayload) {
|
|
1886
|
+
state.availableTabs.add(VIEW_IDS.changelogRoute)
|
|
1887
|
+
state.availableTabs.add(VIEW_IDS.changelogSource)
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
if (!state.availableTabs.has(state.activeTab)) {
|
|
1891
|
+
state.activeTab = state.availableTabs.has(VIEW_IDS.leaves)
|
|
1892
|
+
? VIEW_IDS.leaves
|
|
1893
|
+
: state.availableTabs.has(VIEW_IDS.changelogRoute)
|
|
1894
|
+
? VIEW_IDS.changelogRoute
|
|
1895
|
+
: VIEW_IDS.changelogSource
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
if (state.leavesPayload && state.changelogPayload) {
|
|
1899
|
+
statusEl.textContent =
|
|
1900
|
+
`Loaded unified bundle with ${state.leaves.length} routes and ${
|
|
1901
|
+
state.changelogPayload.commits?.length || 0
|
|
1902
|
+
} commits.`
|
|
1903
|
+
} else if (state.leavesPayload) {
|
|
1904
|
+
statusEl.textContent = `Loaded payload with ${state.leaves.length} routes.`
|
|
1905
|
+
} else if (state.changelogPayload) {
|
|
1906
|
+
statusEl.textContent =
|
|
1907
|
+
`Loaded changelog payload with ${state.changelogPayload.commits?.length || 0} commits.`
|
|
1908
|
+
} else {
|
|
1909
|
+
statusEl.textContent = 'No payload loaded.'
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1661
1913
|
function initializeFromBakedPayload() {
|
|
1662
1914
|
const baked = window.__FINALIZED_LEAVES_PAYLOAD
|
|
1663
|
-
if (
|
|
1664
|
-
|
|
1915
|
+
if (
|
|
1916
|
+
!isBundlePayload(baked) &&
|
|
1917
|
+
!isChangelogPayload(baked) &&
|
|
1918
|
+
(!baked || !Array.isArray(baked.leaves))
|
|
1919
|
+
) {
|
|
1665
1920
|
return
|
|
1666
1921
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
state.mode = 'snapshot'
|
|
1670
|
-
if (controlsEl) {
|
|
1671
|
-
controlsEl.style.display = ''
|
|
1672
|
-
}
|
|
1673
|
-
state.payload = baked
|
|
1674
|
-
state.leaves = baked.leaves
|
|
1675
|
-
statusEl.textContent = `Loaded baked payload with ${state.leaves.length} routes.`
|
|
1922
|
+
applyLoadedPayload(baked)
|
|
1676
1923
|
renderResults()
|
|
1677
1924
|
}
|
|
1678
1925
|
|
|
@@ -1703,8 +1950,12 @@
|
|
|
1703
1950
|
renderResults()
|
|
1704
1951
|
})
|
|
1705
1952
|
|
|
1953
|
+
tabLeavesBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.leaves))
|
|
1954
|
+
tabChangelogRouteBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.changelogRoute))
|
|
1955
|
+
tabChangelogSourceBtn.addEventListener('click', () => setActiveTab(VIEW_IDS.changelogSource))
|
|
1956
|
+
|
|
1706
1957
|
selectAllFieldsBtn.addEventListener('click', () =>
|
|
1707
|
-
setFieldSelection(new Set(
|
|
1958
|
+
setFieldSelection(new Set(currentFieldDefs().map((field) => field.id))),
|
|
1708
1959
|
)
|
|
1709
1960
|
clearAllFieldsBtn.addEventListener('click', () => setFieldSelection(new Set()))
|
|
1710
1961
|
coreFieldsBtn.addEventListener('click', () => setFieldSelection(CORE_FIELD_IDS))
|