/** * Copyright (c) 2026-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Developer-only diagnostic page for measuring the size and shape of the * DataSpace analytics artifact returned by the Depot server. Used to * investigate Chrome tab OOM crashes when loading large multi execution * context DataSpaces. * * Route: /dev/dataspace-inspector * * The page reuses the application's existing depot client (so it inherits * the same auth / cookies / Envoy origin headers), fetches: * GET /generations/{groupId}/{artifactId}/{versionId}/types/dataSpace-analytics?elementPath={path} * (the existing per-type endpoint, now narrowed via the `elementPath` * query parameter for QUERY-1054) and reports total uncompressed payload * bytes plus a per-file breakdown for that one DataSpace. * * Also supports loading inputs from a saved query id (hits the engine * query server `/pure/v1/query/{id}` endpoint and pulls groupId / * artifactId / versionId + the dataspace path from the saved query's * `taggedValues` / `executionContext`), and an inline JSON viewer (Monaco) * for the parsed response and individual file contents. */ import { observer } from 'mobx-react-lite'; import { useState } from 'react'; import { StoreProjectData } from '@finos/legend-server-depot'; import { LogEvent } from '@finos/legend-shared'; import { CodeEditor } from '@finos/legend-lego/code-editor'; import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor'; import { LEGEND_QUERY_APP_EVENT } from '../__lib__/LegendQueryEvent.js'; import { useLegendQueryApplicationStore, useLegendQueryBaseStore, } from './LegendQueryFrameworkProvider.js'; import { DATA_SPACE_ELEMENT_CLASSIFIER_PATH } from '@finos/legend-extension-dsl-data-space/graph'; const MB = 1024 * 1024; const fmtMB = (bytes: number): string => `${(bytes / MB).toFixed(2)} MB`; const QUERY_PROFILE_PATH = 'meta::pure::profiles::query'; const QUERY_PROFILE_TAG_DATA_SPACE = 'dataSpace'; interface DataSpaceOption { groupId: string; artifactId: string; versionId: string; path: string; label: string; } interface FileSizeRow { path: string; type: string; filePath: string; bytes: number; } interface InspectionResult { mode: 'parsed' | 'streamed' | 'streamed-headers'; totalBytes: number; fileCount: number; rows: FileSizeRow[]; fetchMs: number; sizingMs: number; streamedBodyBytes?: number | undefined; streamedCompressedBytes?: number | undefined; streamedContentEncoding?: string | undefined; streamedUrl?: string | undefined; // Only populated in parsed mode — used by the inline JSON viewer. rawFiles?: unknown[] | undefined; } interface JsonViewerState { title: string; content: string; } /** * Pretty-print arbitrary JSON-ish input. If the input is already a string * that happens to be valid JSON (depot returns `content` as a string for * files like `AnalyticsResult.json`), reparse and re-stringify so the * viewer gets a properly indented, foldable tree instead of a long * single-line escaped string. */ const prettyJson = (value: unknown): string => { if (typeof value === 'string') { const trimmed = value.trim(); if ( (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']')) ) { try { return JSON.stringify(JSON.parse(value), null, 2); } catch { // fall through, return raw string } } return value; } try { return JSON.stringify(value, null, 2); } catch { return String(value); } }; const inputStyle: React.CSSProperties = { padding: '0.4rem 0.6rem', background: '#2a2a2a', color: '#eee', border: '1px solid #444', borderRadius: 4, fontFamily: 'monospace', }; const thStyle: React.CSSProperties = { padding: '0.4rem 0.6rem', textAlign: 'left', borderBottom: '1px solid #444', }; const tdStyle: React.CSSProperties = { padding: '0.35rem 0.6rem', borderBottom: '1px solid #2a2a2a', fontFamily: 'monospace', fontSize: '0.85rem', }; const Row: React.FC<{ label: string; value: string }> = ({ label, value }) => (
Diagnostic tool: fetches the per-element depot analytics endpoint for a single DataSpace and reports total uncompressed payload size plus a per-file breakdown. Use this to identify oversized partition files that may cause Chrome tab OOM.
Paste a Legend saved query id; we'll fetch{' '}
/pure/v1/query/<id> and pre-fill groupId, artifactId,
versionId, and the dataspace path from the query's tagged values /
execution context.
Load every DataSpace registered in depot (latest version of each) via{' '}
/classifiers/meta::pure::metamodel::dataSpace::DataSpace?summary=true&latest=true
, then pick one to pre-fill all four inputs.
Per-file breakdown is only available in parsed or streamed-headers mode. Raw streaming mode only measures total bytes.
) : (| # | DataSpace path | Type | File | Size | % of total | {result.mode === 'parsed' && (View | )}
|---|---|---|---|---|---|---|
| {i + 1} | {r.path} | {r.type} | {r.filePath} | {fmtMB(r.bytes)} | {result.totalBytes ? `${((r.bytes / result.totalBytes) * 100).toFixed(1)}%` : '—'} | {result.mode === 'parsed' && ({rawIndex >= 0 ? ( ) : ( '—' )} | )}