@emeryld/rrroutes-export 1.0.13 → 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/index.cjs +132 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +129 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/tools/finalized-leaves-viewer.html +386 -216
|
@@ -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,188 +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 formatCommitDateTime = (value) => {
|
|
1354
|
-
if (!value) return null
|
|
1355
|
-
const parsed = new Date(value)
|
|
1356
|
-
if (Number.isNaN(parsed.getTime())) return String(value)
|
|
1357
|
-
return parsed.toLocaleString()
|
|
1358
|
-
}
|
|
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
|
+
}
|
|
1359
1448
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
}
|
|
1370
|
-
return parts.join(', ')
|
|
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)}`)
|
|
1371
1458
|
}
|
|
1459
|
+
return parts.join(', ')
|
|
1460
|
+
}
|
|
1372
1461
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
if ('
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
return `added ${location}: ${JSON.stringify(delta.after || [])}`
|
|
1381
|
-
}
|
|
1382
|
-
if (delta.op === 'removed') {
|
|
1383
|
-
return `removed ${location}: ${JSON.stringify(delta.before || [])}`
|
|
1384
|
-
}
|
|
1385
|
-
return `changed ${location}: ${JSON.stringify(delta.before || [])} -> ${JSON.stringify(
|
|
1386
|
-
delta.after || [],
|
|
1387
|
-
)}`
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
// Route schema diff shape: { path, op, before?, after? }
|
|
1391
|
-
if ('path' in delta) {
|
|
1392
|
-
if (delta.op === 'added') {
|
|
1393
|
-
return `added ${delta.path}: ${formatRouteSchemaEntry(delta.after)}`
|
|
1394
|
-
}
|
|
1395
|
-
if (delta.op === 'removed') {
|
|
1396
|
-
return `removed ${delta.path}: ${formatRouteSchemaEntry(delta.before)}`
|
|
1397
|
-
}
|
|
1398
|
-
return `changed ${delta.path}: ${formatRouteSchemaEntry(
|
|
1399
|
-
delta.before,
|
|
1400
|
-
)} -> ${formatRouteSchemaEntry(delta.after)}`
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
return null
|
|
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 || [])}`
|
|
1404
1469
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
if (
|
|
1408
|
-
return
|
|
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)}`
|
|
1409
1474
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
)
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
if (Array.isArray(event.cfgDiff) && event.cfgDiff.length > 0) {
|
|
1442
|
-
card.appendChild(el('div', 'meta', `cfg changes: ${event.cfgDiff.length}`))
|
|
1443
|
-
const cfgChanges = el('div')
|
|
1444
|
-
event.cfgDiff.forEach((diff) => {
|
|
1445
|
-
const line = formatCfgDiffLine(diff)
|
|
1446
|
-
if (!line) return
|
|
1447
|
-
cfgChanges.appendChild(el('div', 'meta', line))
|
|
1448
|
-
})
|
|
1449
|
-
card.appendChild(cfgChanges)
|
|
1450
|
-
}
|
|
1451
|
-
if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
|
|
1452
|
-
card.appendChild(
|
|
1453
|
-
el('div', 'meta', `impacted routes: ${event.impactedRoutes.length}`),
|
|
1454
|
-
)
|
|
1455
|
-
}
|
|
1456
|
-
return card
|
|
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))
|
|
1504
|
+
})
|
|
1505
|
+
card.appendChild(schemaChanges)
|
|
1457
1506
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
return
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
routeEntries.forEach(([routeKey, events]) => {
|
|
1467
|
-
const details = el('details', 'leaf')
|
|
1468
|
-
details.appendChild(el('summary', '', `${routeKey} (${events.length})`))
|
|
1469
|
-
events.forEach((event) => details.appendChild(renderEventDetails(event)))
|
|
1470
|
-
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))
|
|
1471
1514
|
})
|
|
1515
|
+
card.appendChild(cfgChanges)
|
|
1472
1516
|
}
|
|
1517
|
+
if (Array.isArray(event.impactedRoutes) && event.impactedRoutes.length > 0) {
|
|
1518
|
+
card.appendChild(el('div', 'meta', `impacted routes: ${event.impactedRoutes.length}`))
|
|
1519
|
+
}
|
|
1520
|
+
return card
|
|
1521
|
+
}
|
|
1473
1522
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
)
|
|
1480
|
-
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
sourceEntries.forEach(([sourceId, events]) => {
|
|
1484
|
-
const details = el('details', 'leaf')
|
|
1485
|
-
const display = events[0]?.displayName || sourceId
|
|
1486
|
-
details.appendChild(el('summary', '', `${display} (${events.length})`))
|
|
1487
|
-
events.forEach((event) => details.appendChild(renderEventDetails(event)))
|
|
1488
|
-
view.appendChild(details)
|
|
1489
|
-
})
|
|
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 }))
|
|
1490
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 []
|
|
1541
|
+
}
|
|
1491
1542
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
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
|
+
})
|
|
1495
1550
|
}
|
|
1496
1551
|
|
|
1497
|
-
function
|
|
1498
|
-
if (
|
|
1552
|
+
function renderCollection(items, renderItem, emptyMessage) {
|
|
1553
|
+
if (items.length === 0) {
|
|
1554
|
+
resultsEl.appendChild(el('div', 'empty', emptyMessage))
|
|
1499
1555
|
return
|
|
1500
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() {
|
|
1501
1576
|
const engine = createSearchEngine(searchInput.value.trim(), {
|
|
1502
1577
|
caseSensitive: isPressed(caseSensitiveToggle),
|
|
1503
1578
|
regex: isPressed(regexSearchToggle),
|
|
@@ -1515,51 +1590,84 @@
|
|
|
1515
1590
|
}
|
|
1516
1591
|
|
|
1517
1592
|
const selectedIds = selectedFieldIds()
|
|
1518
|
-
const filtered = state.leaves.filter((leaf) => matchesLeaf(leaf, engine, selectedIds))
|
|
1519
|
-
|
|
1520
|
-
statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
|
|
1521
1593
|
resultsEl.innerHTML = ''
|
|
1594
|
+
renderTabButtons()
|
|
1522
1595
|
renderActiveFilterChips({ selectedIds, hasRegexError: false })
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
+
)
|
|
1528
1625
|
}
|
|
1529
|
-
|
|
1530
|
-
filtered.forEach((leaf) => {
|
|
1531
|
-
resultsEl.appendChild(renderLeaf(leaf, engine, selectedIds))
|
|
1532
|
-
})
|
|
1533
1626
|
syncFilterStateToUrl()
|
|
1534
1627
|
}
|
|
1535
1628
|
|
|
1536
1629
|
function selectedFieldIds() {
|
|
1537
|
-
|
|
1630
|
+
const selected = state.selectedFieldIdsByTab[state.activeTab] || new Set()
|
|
1631
|
+
return currentFieldDefs().filter((field) => selected.has(field.id)).map(
|
|
1538
1632
|
(field) => field.id,
|
|
1539
1633
|
)
|
|
1540
1634
|
}
|
|
1541
1635
|
|
|
1542
1636
|
function renderFieldChips() {
|
|
1637
|
+
const fieldDefs = currentFieldDefs()
|
|
1638
|
+
const selected = state.selectedFieldIdsByTab[state.activeTab] || new Set()
|
|
1543
1639
|
fieldChipGroups.innerHTML = ''
|
|
1544
|
-
|
|
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) => {
|
|
1545
1652
|
const groupWrap = el('div', 'scope-group')
|
|
1546
1653
|
groupWrap.appendChild(el('div', 'scope-group-title', group.label))
|
|
1547
1654
|
const chipsWrap = el('div', 'scope-group-chips')
|
|
1548
1655
|
|
|
1549
1656
|
group.fieldIds.forEach((fieldId) => {
|
|
1550
|
-
const field =
|
|
1657
|
+
const field = fieldDefs.find((item) => item.id === fieldId)
|
|
1551
1658
|
if (!field) return
|
|
1552
1659
|
const chip = document.createElement('button')
|
|
1553
1660
|
chip.type = 'button'
|
|
1554
1661
|
chip.className = 'field-chip'
|
|
1555
1662
|
chip.textContent = field.label
|
|
1556
|
-
setPressed(chip,
|
|
1663
|
+
setPressed(chip, selected.has(field.id))
|
|
1557
1664
|
chip.addEventListener('click', () => {
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1665
|
+
if (selected.has(field.id)) {
|
|
1666
|
+
selected.delete(field.id)
|
|
1560
1667
|
} else {
|
|
1561
|
-
|
|
1668
|
+
selected.add(field.id)
|
|
1562
1669
|
}
|
|
1670
|
+
state.selectedFieldIdsByTab[state.activeTab] = selected
|
|
1563
1671
|
renderFieldChips()
|
|
1564
1672
|
renderResults()
|
|
1565
1673
|
})
|
|
@@ -1573,8 +1681,8 @@
|
|
|
1573
1681
|
|
|
1574
1682
|
function setFieldSelection(allowedIds, options = {}) {
|
|
1575
1683
|
const { rerender = true } = options
|
|
1576
|
-
state.
|
|
1577
|
-
|
|
1684
|
+
state.selectedFieldIdsByTab[state.activeTab] = new Set(
|
|
1685
|
+
currentFieldDefs().map((field) => field.id).filter((id) => allowedIds.has(id)),
|
|
1578
1686
|
)
|
|
1579
1687
|
renderFieldChips()
|
|
1580
1688
|
if (rerender) renderResults()
|
|
@@ -1586,7 +1694,7 @@
|
|
|
1586
1694
|
setPressed(regexSearchToggle, false)
|
|
1587
1695
|
typeMatchModeInput.value = 'contains'
|
|
1588
1696
|
setPressed(schemaRowsMatchOnlyToggle, false)
|
|
1589
|
-
setFieldSelection(new Set(
|
|
1697
|
+
setFieldSelection(new Set(currentFieldDefs().map((field) => field.id)), { rerender: false })
|
|
1590
1698
|
renderResults()
|
|
1591
1699
|
}
|
|
1592
1700
|
|
|
@@ -1642,7 +1750,7 @@
|
|
|
1642
1750
|
}
|
|
1643
1751
|
if (hasRegexError) chipModels.push({ text: 'regex error' })
|
|
1644
1752
|
|
|
1645
|
-
const allIds =
|
|
1753
|
+
const allIds = currentFieldDefs().map((field) => field.id)
|
|
1646
1754
|
if (selectedIds.length !== allIds.length) {
|
|
1647
1755
|
chipModels.push({
|
|
1648
1756
|
text: `fields: ${selectedIds.join(', ') || 'none'}`,
|
|
@@ -1669,12 +1777,15 @@
|
|
|
1669
1777
|
|
|
1670
1778
|
function collectFilterState() {
|
|
1671
1779
|
return {
|
|
1780
|
+
activeTab: state.activeTab,
|
|
1672
1781
|
search: searchInput.value,
|
|
1673
1782
|
caseSensitive: isPressed(caseSensitiveToggle),
|
|
1674
1783
|
regex: isPressed(regexSearchToggle),
|
|
1675
1784
|
typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
|
|
1676
1785
|
schemaRowsMatchOnly: isPressed(schemaRowsMatchOnlyToggle),
|
|
1677
|
-
|
|
1786
|
+
selectedFieldsByTab: Object.fromEntries(
|
|
1787
|
+
Object.entries(state.selectedFieldIdsByTab).map(([key, value]) => [key, Array.from(value)]),
|
|
1788
|
+
),
|
|
1678
1789
|
}
|
|
1679
1790
|
}
|
|
1680
1791
|
|
|
@@ -1699,6 +1810,13 @@
|
|
|
1699
1810
|
try {
|
|
1700
1811
|
const parsed = JSON.parse(decodeURIComponent(raw))
|
|
1701
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
|
+
}
|
|
1702
1820
|
if (typeof parsed.search === 'string') searchInput.value = parsed.search
|
|
1703
1821
|
setPressed(caseSensitiveToggle, Boolean(parsed.caseSensitive))
|
|
1704
1822
|
setPressed(regexSearchToggle, Boolean(parsed.regex))
|
|
@@ -1707,8 +1825,12 @@
|
|
|
1707
1825
|
}
|
|
1708
1826
|
setPressed(schemaRowsMatchOnlyToggle, Boolean(parsed.schemaRowsMatchOnly))
|
|
1709
1827
|
|
|
1710
|
-
if (
|
|
1711
|
-
|
|
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
|
+
})
|
|
1712
1834
|
}
|
|
1713
1835
|
} catch (error) {
|
|
1714
1836
|
// Ignore malformed hash state.
|
|
@@ -1720,40 +1842,84 @@
|
|
|
1720
1842
|
async function handleFile(file) {
|
|
1721
1843
|
const text = await file.text()
|
|
1722
1844
|
const parsed = JSON.parse(text)
|
|
1723
|
-
if (
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1845
|
+
if (
|
|
1846
|
+
!isBundlePayload(parsed) &&
|
|
1847
|
+
!isChangelogPayload(parsed) &&
|
|
1848
|
+
(!parsed || !Array.isArray(parsed.leaves))
|
|
1849
|
+
) {
|
|
1728
1850
|
throw new Error(
|
|
1729
|
-
'Invalid export file: expected
|
|
1851
|
+
'Invalid export file: expected leaves payload, changelog payload, or unified bundle.',
|
|
1730
1852
|
)
|
|
1731
1853
|
}
|
|
1854
|
+
applyLoadedPayload(parsed)
|
|
1855
|
+
renderResults()
|
|
1856
|
+
}
|
|
1732
1857
|
|
|
1733
|
-
|
|
1734
|
-
if (
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
state.payload = parsed
|
|
1738
|
-
state.leaves = parsed.leaves
|
|
1858
|
+
function setActiveTab(nextTab) {
|
|
1859
|
+
if (!state.availableTabs.has(nextTab)) return
|
|
1860
|
+
state.activeTab = nextTab
|
|
1861
|
+
renderFieldChips()
|
|
1739
1862
|
renderResults()
|
|
1740
1863
|
}
|
|
1741
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
|
+
|
|
1742
1913
|
function initializeFromBakedPayload() {
|
|
1743
1914
|
const baked = window.__FINALIZED_LEAVES_PAYLOAD
|
|
1744
|
-
if (
|
|
1745
|
-
|
|
1915
|
+
if (
|
|
1916
|
+
!isBundlePayload(baked) &&
|
|
1917
|
+
!isChangelogPayload(baked) &&
|
|
1918
|
+
(!baked || !Array.isArray(baked.leaves))
|
|
1919
|
+
) {
|
|
1746
1920
|
return
|
|
1747
1921
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
state.mode = 'snapshot'
|
|
1751
|
-
if (controlsEl) {
|
|
1752
|
-
controlsEl.style.display = ''
|
|
1753
|
-
}
|
|
1754
|
-
state.payload = baked
|
|
1755
|
-
state.leaves = baked.leaves
|
|
1756
|
-
statusEl.textContent = `Loaded baked payload with ${state.leaves.length} routes.`
|
|
1922
|
+
applyLoadedPayload(baked)
|
|
1757
1923
|
renderResults()
|
|
1758
1924
|
}
|
|
1759
1925
|
|
|
@@ -1784,8 +1950,12 @@
|
|
|
1784
1950
|
renderResults()
|
|
1785
1951
|
})
|
|
1786
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
|
+
|
|
1787
1957
|
selectAllFieldsBtn.addEventListener('click', () =>
|
|
1788
|
-
setFieldSelection(new Set(
|
|
1958
|
+
setFieldSelection(new Set(currentFieldDefs().map((field) => field.id))),
|
|
1789
1959
|
)
|
|
1790
1960
|
clearAllFieldsBtn.addEventListener('click', () => setFieldSelection(new Set()))
|
|
1791
1961
|
coreFieldsBtn.addEventListener('click', () => setFieldSelection(CORE_FIELD_IDS))
|