@burdenoff/microfe-cosmicintersection 2026.716.1 → 2026.720.1
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/dist/cosmicintersection/CosmicIntersectionRoot.js.map +1 -1
- package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.d.ts.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
- package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
- package/dist/cosmicintersection/components/ActivityFeedPanel.js.map +1 -1
- package/dist/cosmicintersection/components/DiscussionPanel.js.map +1 -1
- package/dist/cosmicintersection/components/EmptyState.js.map +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -1
- package/dist/cosmicintersection/components/EntityTagsField.js.map +1 -1
- package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
- package/dist/cosmicintersection/components/FacetPanel.js.map +1 -1
- package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
- package/dist/cosmicintersection/components/LineageGraph.d.ts.map +1 -1
- package/dist/cosmicintersection/components/LineageGraph.js.map +1 -1
- package/dist/cosmicintersection/components/LoadingState.js.map +1 -1
- package/dist/cosmicintersection/components/PageLayout.js.map +1 -1
- package/dist/cosmicintersection/components/Pagination.js.map +1 -1
- package/dist/cosmicintersection/components/ResultsChart.js.map +1 -1
- package/dist/cosmicintersection/components/SafeLink.js.map +1 -1
- package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
- package/dist/cosmicintersection/components/StepDAG.js.map +1 -1
- package/dist/cosmicintersection/context/CosmicIntersectionContext.js.map +1 -1
- package/dist/cosmicintersection/lib/mutationResult.js.map +1 -1
- package/dist/cosmicintersection/lib/toast.js.map +1 -1
- package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
- package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourceFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
- package/dist/cosmicintersection/utils/cn.js.map +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatasetsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/datasets/DatasetsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Compass, Database, Plus } from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveDatasetMutation,\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosPublishDatasetMutation,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n type CosDatasetStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst DATASET_STATUSES: CosDatasetStatus[] = [\n 'DRAFT',\n 'INGESTING',\n 'AVAILABLE',\n 'DEPRECATED',\n 'ARCHIVED',\n];\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\nconst ALL = '__all__';\n\n/** Generous ceiling for deriving the distinct-format dropdown options. */\nconst FORMAT_POOL_LIMIT = 200;\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatDate(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const date = new Date(raw);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString();\n}\n\nexport default function DatasetsPage() {\n const { navigate } = useCosmicIntersection();\n\n const [status, setStatus] = useState<string>(ALL);\n const [sourceId, setSourceId] = useState<string>(ALL);\n const [collectionId, setCollectionId] = useState<string>(ALL);\n const [format, setFormat] = useState<string>(ALL);\n const [accessLevel, setAccessLevel] = useState<string>(ALL);\n\n const pagination = usePagination({\n pageSize: 20,\n resetKey: `${status}|${sourceId}|${collectionId}|${format}|${accessLevel}`,\n });\n\n const { data, loading, error, refetch } = useCosDatasetsQuery({\n status: status === ALL ? undefined : (status as CosDatasetStatus),\n sourceId: sourceId === ALL ? undefined : sourceId,\n collectionId: collectionId === ALL ? undefined : collectionId,\n format: format === ALL ? undefined : format,\n accessLevel: accessLevel === ALL ? undefined : (accessLevel as CosAccessLevel),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: 100, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n // Distinct format values are derived from a real sample of the catalog so the\n // Format dropdown reflects what actually exists (format is free-text, no enum).\n const formatsPool = useCosDatasetsQuery({\n pagination: { limit: FORMAT_POOL_LIMIT, offset: 0 },\n });\n\n const [publishDataset] = useCosPublishDatasetMutation();\n const [archiveDataset] = useCosArchiveDatasetMutation();\n\n const datasets = useMemo(() => data?.cosListDatasets?.items ?? [], [data]);\n const pageInfo = data?.cosListDatasets?.pageInfo;\n const sources = sourcesQuery.data?.cosListSources?.items ?? [];\n const collections = collectionsQuery.data?.cosListCollections ?? [];\n const formats = useMemo(() => {\n const seen = new Set<string>();\n for (const dataset of formatsPool.data?.cosListDatasets?.items ?? []) {\n if (dataset.format) {\n seen.add(dataset.format);\n }\n }\n return [...seen].sort((a, b) => a.localeCompare(b));\n }, [formatsPool.data]);\n\n const noFilters =\n status === ALL &&\n sourceId === ALL &&\n collectionId === ALL &&\n format === ALL &&\n accessLevel === ALL;\n\n const handlePublish = async (dataset: CosDataset) => {\n const response = await publishDataset({ variables: { id: dataset.id } });\n if (response.data?.cosPublishDataset) {\n notify.success('Dataset published', `“${dataset.name}” is now available.`);\n await refetch();\n }\n };\n\n const handleArchive = async (dataset: CosDataset) => {\n const response = await archiveDataset({ variables: { id: dataset.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveDataset) {\n notify.error('Could not archive dataset', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Dataset archived');\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Datasets\"\n description=\"Federated science datasets — catalog, schema, lineage and access, all from live data.\"\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('discovery')}>\n <Compass className=\"size-4\" />\n Discover\n </Button>\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Status</span>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {DATASET_STATUSES.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Source</span>\n <Select value={sourceId} onValueChange={setSourceId}>\n <SelectTrigger aria-label=\"Filter by source\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sources</SelectItem>\n {sources.map((source) => (\n <SelectItem key={source.id} value={source.id}>\n {source.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Collection</span>\n <Select value={collectionId} onValueChange={setCollectionId}>\n <SelectTrigger aria-label=\"Filter by collection\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All collections</SelectItem>\n {collections.map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Format</span>\n <Select value={format} onValueChange={setFormat} disabled={formats.length === 0}>\n <SelectTrigger aria-label=\"Filter by format\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All formats</SelectItem>\n {formats.map((value) => (\n <SelectItem key={value} value={value}>\n {value}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Access</span>\n <Select value={accessLevel} onValueChange={setAccessLevel}>\n <SelectTrigger aria-label=\"Filter by access level\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All access levels</SelectItem>\n {ACCESS_LEVELS.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Loading datasets…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not load datasets. Please try again.\"\n onRetry={() => void refetch()}\n />\n ) : datasets.length === 0 ? (\n <EmptyState\n icon={Database}\n title={noFilters ? 'No datasets yet.' : 'No datasets match these filters.'}\n description={\n noFilters\n ? 'Register your first dataset to start federating science data across sources.'\n : 'Try a different status, source, collection, format or access level.'\n }\n action={\n noFilters ? (\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Format</TableHead>\n <TableHead className=\"text-right\">Size</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead>Updated</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell>\n <span className=\"font-medium text-text-primary\">{dataset.name}</span>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">\n {formatEnumLabel(dataset.accessLevel)}\n </span>\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">{dataset.format || '—'}</span>\n </TableCell>\n <TableCell className=\"text-right text-sm tabular-nums text-text-secondary\">\n {formatBytes(dataset.sizeBytes)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {dataset.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n <>\n {dataset.tags.slice(0, 2).map((tag) => (\n <Badge key={tag} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 2 ? (\n <Badge variant=\"secondary\">+{dataset.tags.length - 2}</Badge>\n ) : null}\n </>\n )}\n </div>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${dataset.name}`}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: 'Publish',\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel=\"Archive dataset\"\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards */}\n <ul className=\"space-y-3 md:hidden\">\n {datasets.map((dataset) => (\n <li key={dataset.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${dataset.name}`}\n onClick={() => navigate(`datasets/${dataset.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`datasets/${dataset.id}`);\n }\n }}\n className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </div>\n <div onClick={(event) => event.stopPropagation()}>\n <ActionMenu\n ariaLabel={`Actions for ${dataset.name}`}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: 'Publish',\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel=\"Archive dataset\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n <dl className=\"grid grid-cols-2 gap-x-4 gap-y-1 text-xs\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Format</dt>\n <dd className=\"text-text-primary\">{dataset.format || '—'}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Size</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatBytes(dataset.sizeBytes)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Updated</dt>\n <dd className=\"text-text-primary\">{formatDate(dataset.updatedAt)}</dd>\n </div>\n </dl>\n\n {dataset.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {dataset.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={Boolean(pageInfo?.hasMore)}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"datasets\"\n loading={loading}\n />\n </>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DatasetsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/datasets/DatasetsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Compass, Database, Plus } from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveDatasetMutation,\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosPublishDatasetMutation,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n type CosDatasetStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst DATASET_STATUSES: CosDatasetStatus[] = [\n 'DRAFT',\n 'INGESTING',\n 'AVAILABLE',\n 'DEPRECATED',\n 'ARCHIVED',\n];\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\nconst ALL = '__all__';\n\n/** Generous ceiling for deriving the distinct-format dropdown options. */\nconst FORMAT_POOL_LIMIT = 200;\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatDate(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const date = new Date(raw);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString();\n}\n\nexport default function DatasetsPage() {\n const { navigate } = useCosmicIntersection();\n\n const [status, setStatus] = useState<string>(ALL);\n const [sourceId, setSourceId] = useState<string>(ALL);\n const [collectionId, setCollectionId] = useState<string>(ALL);\n const [format, setFormat] = useState<string>(ALL);\n const [accessLevel, setAccessLevel] = useState<string>(ALL);\n\n const pagination = usePagination({\n pageSize: 20,\n resetKey: `${status}|${sourceId}|${collectionId}|${format}|${accessLevel}`,\n });\n\n const { data, loading, error, refetch } = useCosDatasetsQuery({\n status: status === ALL ? undefined : (status as CosDatasetStatus),\n sourceId: sourceId === ALL ? undefined : sourceId,\n collectionId: collectionId === ALL ? undefined : collectionId,\n format: format === ALL ? undefined : format,\n accessLevel: accessLevel === ALL ? undefined : (accessLevel as CosAccessLevel),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: 100, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n // Distinct format values are derived from a real sample of the catalog so the\n // Format dropdown reflects what actually exists (format is free-text, no enum).\n const formatsPool = useCosDatasetsQuery({\n pagination: { limit: FORMAT_POOL_LIMIT, offset: 0 },\n });\n\n const [publishDataset] = useCosPublishDatasetMutation();\n const [archiveDataset] = useCosArchiveDatasetMutation();\n\n const datasets = useMemo(() => data?.cosListDatasets?.items ?? [], [data]);\n const pageInfo = data?.cosListDatasets?.pageInfo;\n const sources = sourcesQuery.data?.cosListSources?.items ?? [];\n const collections = collectionsQuery.data?.cosListCollections ?? [];\n const formats = useMemo(() => {\n const seen = new Set<string>();\n for (const dataset of formatsPool.data?.cosListDatasets?.items ?? []) {\n if (dataset.format) {\n seen.add(dataset.format);\n }\n }\n return [...seen].sort((a, b) => a.localeCompare(b));\n }, [formatsPool.data]);\n\n const noFilters =\n status === ALL &&\n sourceId === ALL &&\n collectionId === ALL &&\n format === ALL &&\n accessLevel === ALL;\n\n const handlePublish = async (dataset: CosDataset) => {\n const response = await publishDataset({ variables: { id: dataset.id } });\n if (response.data?.cosPublishDataset) {\n notify.success('Dataset published', `“${dataset.name}” is now available.`);\n await refetch();\n }\n };\n\n const handleArchive = async (dataset: CosDataset) => {\n const response = await archiveDataset({ variables: { id: dataset.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveDataset) {\n notify.error('Could not archive dataset', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Dataset archived');\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Datasets\"\n description=\"Federated science datasets — catalog, schema, lineage and access, all from live data.\"\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('discovery')}>\n <Compass className=\"size-4\" />\n Discover\n </Button>\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Status</span>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {DATASET_STATUSES.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Source</span>\n <Select value={sourceId} onValueChange={setSourceId}>\n <SelectTrigger aria-label=\"Filter by source\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sources</SelectItem>\n {sources.map((source) => (\n <SelectItem key={source.id} value={source.id}>\n {source.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Collection</span>\n <Select value={collectionId} onValueChange={setCollectionId}>\n <SelectTrigger aria-label=\"Filter by collection\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All collections</SelectItem>\n {collections.map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Format</span>\n <Select value={format} onValueChange={setFormat} disabled={formats.length === 0}>\n <SelectTrigger aria-label=\"Filter by format\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All formats</SelectItem>\n {formats.map((value) => (\n <SelectItem key={value} value={value}>\n {value}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Access</span>\n <Select value={accessLevel} onValueChange={setAccessLevel}>\n <SelectTrigger aria-label=\"Filter by access level\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All access levels</SelectItem>\n {ACCESS_LEVELS.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Loading datasets…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not load datasets. Please try again.\"\n onRetry={() => void refetch()}\n />\n ) : datasets.length === 0 ? (\n <EmptyState\n icon={Database}\n title={noFilters ? 'No datasets yet.' : 'No datasets match these filters.'}\n description={\n noFilters\n ? 'Register your first dataset to start federating science data across sources.'\n : 'Try a different status, source, collection, format or access level.'\n }\n action={\n noFilters ? (\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Format</TableHead>\n <TableHead className=\"text-right\">Size</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead>Updated</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell>\n <span className=\"font-medium text-text-primary\">{dataset.name}</span>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">\n {formatEnumLabel(dataset.accessLevel)}\n </span>\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">{dataset.format || '—'}</span>\n </TableCell>\n <TableCell className=\"text-right text-sm tabular-nums text-text-secondary\">\n {formatBytes(dataset.sizeBytes)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {dataset.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n <>\n {dataset.tags.slice(0, 2).map((tag) => (\n <Badge key={tag} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 2 ? (\n <Badge variant=\"secondary\">+{dataset.tags.length - 2}</Badge>\n ) : null}\n </>\n )}\n </div>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${dataset.name}`}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: 'Publish',\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel=\"Archive dataset\"\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards */}\n <ul className=\"space-y-3 md:hidden\">\n {datasets.map((dataset) => (\n <li key={dataset.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${dataset.name}`}\n onClick={() => navigate(`datasets/${dataset.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`datasets/${dataset.id}`);\n }\n }}\n className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </div>\n <div onClick={(event) => event.stopPropagation()}>\n <ActionMenu\n ariaLabel={`Actions for ${dataset.name}`}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: 'Publish',\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel=\"Archive dataset\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n <dl className=\"grid grid-cols-2 gap-x-4 gap-y-1 text-xs\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Format</dt>\n <dd className=\"text-text-primary\">{dataset.format || '—'}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Size</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatBytes(dataset.sizeBytes)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Updated</dt>\n <dd className=\"text-text-primary\">{formatDate(dataset.updatedAt)}</dd>\n </div>\n </dl>\n\n {dataset.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {dataset.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={Boolean(pageInfo?.hasMore)}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"datasets\"\n loading={loading}\n />\n </>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"sourcesContent":["/**\n * DiscoveryPage — faceted full-text discovery over the federated dataset catalog\n * (mockup 05).\n *\n * A prominent debounced search bar drives the backend `q` full-text argument on\n * `cosListDatasets`; the matched datasets form a \"pool\" (capped at a generous\n * limit) from which every facet count and the result grid are derived — so counts\n * and results always agree and nothing is fabricated. Facets are multi-select and\n * cross-filtered (each group's counts reflect the other selected groups), matching\n * the standard faceted-search behaviour:\n * - Instrument → distinct source types (via cosListSources), mapped from each\n * dataset's sourceId\n * - Access → CosAccessLevel enum\n * - Format → distinct formats present in the pool\n * - Mission/Survey → collections (via cosListCollections)\n *\n * Everything renders from live backend data with honest loading / empty / error\n * states. Tokens only — no raw palette colours.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n ArrowUpDown,\n Database,\n FileText,\n Rows3,\n Search,\n SlidersHorizontal,\n Table as TableIcon,\n X,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { FacetPanel, type FacetGroup } from '../../components/FacetPanel';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { cn } from '../../utils/cn';\n\n/** Generous ceiling for the search pool — facet counts reflect this window. */\nconst POOL_LIMIT = 200;\nconst SEARCH_DEBOUNCE_MS = 300;\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\ntype SortKey = 'relevance' | 'name' | 'updated' | 'size';\n\nconst SORT_LABELS: Record<SortKey, string> = {\n relevance: 'Best match',\n name: 'Name A–Z',\n updated: 'Recently updated',\n size: 'Largest first',\n};\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatRows(raw: string | null | undefined): string {\n const rows = Number(raw ?? 0);\n return Number.isFinite(rows) && rows > 0 ? `${rows.toLocaleString()} rows` : 'no rows';\n}\n\nexport default function DiscoveryPage() {\n const { navigate } = useCosmicIntersection();\n\n const [searchInput, setSearchInput] = useState('');\n const [q, setQ] = useState('');\n const [selected, setSelected] = useState<Record<string, string[]>>({});\n const [sort, setSort] = useState<SortKey>('relevance');\n const [facetsOpen, setFacetsOpen] = useState(false);\n\n // Debounce the raw input into the backend `q` argument.\n useEffect(() => {\n const handle = window.setTimeout(() => setQ(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const poolQuery = useCosDatasetsQuery({\n q: q || undefined,\n pagination: { limit: POOL_LIMIT, offset: 0 },\n });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: POOL_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const pool = useMemo(() => poolQuery.data?.cosListDatasets?.items ?? [], [poolQuery.data]);\n const poolHasMore = Boolean(poolQuery.data?.cosListDatasets?.pageInfo?.hasMore);\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n\n // sourceId → source type, so a dataset can be bucketed by instrument type.\n const sourceTypeById = useMemo(() => {\n const map = new Map<string, string>();\n for (const source of sources) {\n map.set(source.id, source.type);\n }\n return map;\n }, [sources]);\n\n const collectionNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const collection of collections) {\n map.set(collection.id, collection.name);\n }\n return map;\n }, [collections]);\n\n // Per-group predicates — a dataset \"matches\" a group if the group has no\n // selection or the dataset's value is among the selected values.\n const matchers = useMemo(() => {\n const instrument = selected.instrument ?? [];\n const access = selected.access ?? [];\n const format = selected.format ?? [];\n const mission = selected.mission ?? [];\n return {\n instrument: (d: CosDataset) => {\n if (instrument.length === 0) return true;\n const type = d.sourceId ? sourceTypeById.get(d.sourceId) : undefined;\n return type ? instrument.includes(type) : false;\n },\n access: (d: CosDataset) => access.length === 0 || access.includes(d.accessLevel),\n format: (d: CosDataset) =>\n format.length === 0 || (d.format ? format.includes(d.format) : false),\n mission: (d: CosDataset) =>\n mission.length === 0 || (d.collectionId ? mission.includes(d.collectionId) : false),\n } as const;\n }, [selected, sourceTypeById]);\n\n // Cross-filtered count: items passing every OTHER group, then the predicate.\n const countFor = useMemo(() => {\n return (groupKey: keyof typeof matchers, predicate: (d: CosDataset) => boolean): number => {\n let total = 0;\n for (const dataset of pool) {\n let ok = true;\n for (const key of Object.keys(matchers) as (keyof typeof matchers)[]) {\n if (key === groupKey) continue;\n if (!matchers[key](dataset)) {\n ok = false;\n break;\n }\n }\n if (ok && predicate(dataset)) {\n total += 1;\n }\n }\n return total;\n };\n }, [pool, matchers]);\n\n const facetGroups = useMemo<FacetGroup[]>(() => {\n // Universe of option values from the unfiltered pool (stable while toggling).\n const instrumentTypes = new Set<string>();\n const formats = new Set<string>();\n const missionIds = new Set<string>();\n for (const dataset of pool) {\n const type = dataset.sourceId ? sourceTypeById.get(dataset.sourceId) : undefined;\n if (type) instrumentTypes.add(type);\n if (dataset.format) formats.add(dataset.format);\n if (dataset.collectionId) missionIds.add(dataset.collectionId);\n }\n\n const instrument: FacetGroup = {\n key: 'instrument',\n label: 'Instrument',\n options: [...instrumentTypes]\n .map((type) => ({\n value: type,\n label: formatEnumLabel(type),\n count: countFor('instrument', (d) =>\n d.sourceId ? sourceTypeById.get(d.sourceId) === type : false\n ),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n const access: FacetGroup = {\n key: 'access',\n label: 'Access',\n options: ACCESS_LEVELS.map((level) => ({\n value: level,\n label: formatEnumLabel(level),\n count: countFor('access', (d) => d.accessLevel === level),\n })),\n };\n\n const format: FacetGroup = {\n key: 'format',\n label: 'Format',\n options: [...formats]\n .sort((a, b) => a.localeCompare(b))\n .map((fmt) => ({\n value: fmt,\n label: fmt,\n count: countFor('format', (d) => d.format === fmt),\n })),\n };\n\n const mission: FacetGroup = {\n key: 'mission',\n label: 'Mission / Survey',\n options: [...missionIds]\n .map((id) => ({\n value: id,\n label: collectionNameById.get(id) ?? 'Unknown collection',\n count: countFor('mission', (d) => d.collectionId === id),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n return [instrument, access, format, mission];\n }, [pool, sourceTypeById, collectionNameById, countFor]);\n\n // Displayed results: the pool filtered by all selected facets, then sorted.\n const results = useMemo(() => {\n const filtered = pool.filter(\n (dataset) =>\n matchers.instrument(dataset) &&\n matchers.access(dataset) &&\n matchers.format(dataset) &&\n matchers.mission(dataset)\n );\n const sorted = [...filtered];\n switch (sort) {\n case 'name':\n sorted.sort((a, b) => a.name.localeCompare(b.name));\n break;\n case 'updated':\n sorted.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n break;\n case 'size':\n sorted.sort((a, b) => Number(b.sizeBytes) - Number(a.sizeBytes));\n break;\n case 'relevance':\n default:\n break; // preserve backend order (relevance when `q` is present)\n }\n return sorted;\n }, [pool, matchers, sort]);\n\n const totalSelected = Object.values(selected).reduce((sum, values) => sum + values.length, 0);\n\n const handleToggle = (groupKey: string, value: string) => {\n setSelected((prev) => {\n const current = prev[groupKey] ?? [];\n const next = current.includes(value)\n ? current.filter((item) => item !== value)\n : [...current, value];\n return { ...prev, [groupKey]: next };\n });\n };\n\n const handleClear = () => setSelected({});\n\n const loading = poolQuery.loading && pool.length === 0;\n const error = poolQuery.error;\n\n const facetPanel = (\n <FacetPanel\n groups={facetGroups}\n selected={selected}\n onToggle={handleToggle}\n onClear={handleClear}\n />\n );\n\n return (\n <PageLayout\n title=\"Discovery\"\n description=\"Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('datasets')}>\n <TableIcon className=\"size-4\" />\n Table view\n </Button>\n }\n >\n <div className=\"space-y-5\">\n {/* Search bar */}\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder=\"Search datasets by name, description or tag…\"\n aria-label=\"Search datasets\"\n className=\"h-12 rounded-card pl-12 pr-11 text-base\"\n />\n {searchInput ? (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n aria-label=\"Clear search\"\n className=\"absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <X className=\"size-4\" />\n </button>\n ) : null}\n </div>\n\n {/* Mobile facet toggle */}\n <div className=\"flex items-center justify-between gap-3 lg:hidden\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setFacetsOpen((open) => !open)}\n aria-expanded={facetsOpen}\n >\n <SlidersHorizontal className=\"size-4\" />\n Filters{totalSelected > 0 ? ` (${totalSelected})` : ''}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-6 lg:flex-row\">\n {/* Facets — sticky sidebar on desktop, collapsible on mobile */}\n <aside\n className={cn(\n 'shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6',\n facetsOpen ? 'block' : 'hidden'\n )}\n >\n <div className=\"lg:sticky lg:top-0\">{facetPanel}</div>\n </aside>\n\n {/* Results */}\n <div className=\"min-w-0 flex-1 space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {results.length}\n </span>\n <span className=\"text-sm text-text-secondary\">\n {results.length === 1 ? 'dataset' : 'datasets'}\n {q ? ` matching “${q}”` : ''}\n </span>\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <ArrowUpDown className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <Select value={sort} onValueChange={(value) => setSort(value as SortKey)}>\n <SelectTrigger aria-label=\"Sort results\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SORT_LABELS) as SortKey[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SORT_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {poolHasMore ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n Showing facets and results for the first {POOL_LIMIT} matches. Refine your search to\n narrow the set.\n </p>\n ) : null}\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Searching the catalog…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not run that search. Please try again.\"\n onRetry={() => void poolQuery.refetch()}\n />\n ) : results.length === 0 ? (\n <EmptyState\n icon={Search}\n title={q || totalSelected > 0 ? 'No datasets match' : 'Start exploring'}\n description={\n q || totalSelected > 0\n ? 'Try a broader search term or clear some facets.'\n : 'Search by name, description or tag, or pick a facet to browse the catalog.'\n }\n action={\n totalSelected > 0 ? (\n <Button type=\"button\" variant=\"outline\" onClick={handleClear}>\n Clear filters\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3\">\n {results.map((dataset) => (\n <button\n key={dataset.id}\n type=\"button\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {dataset.name}\n </h3>\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <FileText className=\"size-3.5\" aria-hidden=\"true\" />\n {dataset.format || 'No format'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n {formatBytes(dataset.sizeBytes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Rows3 className=\"size-3.5\" aria-hidden=\"true\" />\n {formatRows(dataset.rowCount)}\n </span>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n {dataset.tags.length > 0 ? (\n <div className=\"mt-auto flex flex-wrap gap-1 pt-1\">\n {dataset.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"sourcesContent":["/**\n * DiscoveryPage — faceted full-text discovery over the federated dataset catalog\n * (mockup 05).\n *\n * A prominent debounced search bar drives the backend `q` full-text argument on\n * `cosListDatasets`; the matched datasets form a \"pool\" (capped at a generous\n * limit) from which every facet count and the result grid are derived — so counts\n * and results always agree and nothing is fabricated. Facets are multi-select and\n * cross-filtered (each group's counts reflect the other selected groups), matching\n * the standard faceted-search behaviour:\n * - Instrument → distinct source types (via cosListSources), mapped from each\n * dataset's sourceId\n * - Access → CosAccessLevel enum\n * - Format → distinct formats present in the pool\n * - Mission/Survey → collections (via cosListCollections)\n *\n * Everything renders from live backend data with honest loading / empty / error\n * states. Tokens only — no raw palette colours.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n ArrowUpDown,\n Database,\n FileText,\n Rows3,\n Search,\n SlidersHorizontal,\n Table as TableIcon,\n X,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { FacetPanel, type FacetGroup } from '../../components/FacetPanel';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { cn } from '../../utils/cn';\n\n/** Generous ceiling for the search pool — facet counts reflect this window. */\nconst POOL_LIMIT = 200;\nconst SEARCH_DEBOUNCE_MS = 300;\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\ntype SortKey = 'relevance' | 'name' | 'updated' | 'size';\n\nconst SORT_LABELS: Record<SortKey, string> = {\n relevance: 'Best match',\n name: 'Name A–Z',\n updated: 'Recently updated',\n size: 'Largest first',\n};\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatRows(raw: string | null | undefined): string {\n const rows = Number(raw ?? 0);\n return Number.isFinite(rows) && rows > 0 ? `${rows.toLocaleString()} rows` : 'no rows';\n}\n\nexport default function DiscoveryPage() {\n const { navigate } = useCosmicIntersection();\n\n const [searchInput, setSearchInput] = useState('');\n const [q, setQ] = useState('');\n const [selected, setSelected] = useState<Record<string, string[]>>({});\n const [sort, setSort] = useState<SortKey>('relevance');\n const [facetsOpen, setFacetsOpen] = useState(false);\n\n // Debounce the raw input into the backend `q` argument.\n useEffect(() => {\n const handle = window.setTimeout(() => setQ(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const poolQuery = useCosDatasetsQuery({\n q: q || undefined,\n pagination: { limit: POOL_LIMIT, offset: 0 },\n });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: POOL_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const pool = useMemo(() => poolQuery.data?.cosListDatasets?.items ?? [], [poolQuery.data]);\n const poolHasMore = Boolean(poolQuery.data?.cosListDatasets?.pageInfo?.hasMore);\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n\n // sourceId → source type, so a dataset can be bucketed by instrument type.\n const sourceTypeById = useMemo(() => {\n const map = new Map<string, string>();\n for (const source of sources) {\n map.set(source.id, source.type);\n }\n return map;\n }, [sources]);\n\n const collectionNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const collection of collections) {\n map.set(collection.id, collection.name);\n }\n return map;\n }, [collections]);\n\n // Per-group predicates — a dataset \"matches\" a group if the group has no\n // selection or the dataset's value is among the selected values.\n const matchers = useMemo(() => {\n const instrument = selected.instrument ?? [];\n const access = selected.access ?? [];\n const format = selected.format ?? [];\n const mission = selected.mission ?? [];\n return {\n instrument: (d: CosDataset) => {\n if (instrument.length === 0) return true;\n const type = d.sourceId ? sourceTypeById.get(d.sourceId) : undefined;\n return type ? instrument.includes(type) : false;\n },\n access: (d: CosDataset) => access.length === 0 || access.includes(d.accessLevel),\n format: (d: CosDataset) =>\n format.length === 0 || (d.format ? format.includes(d.format) : false),\n mission: (d: CosDataset) =>\n mission.length === 0 || (d.collectionId ? mission.includes(d.collectionId) : false),\n } as const;\n }, [selected, sourceTypeById]);\n\n // Cross-filtered count: items passing every OTHER group, then the predicate.\n const countFor = useMemo(() => {\n return (groupKey: keyof typeof matchers, predicate: (d: CosDataset) => boolean): number => {\n let total = 0;\n for (const dataset of pool) {\n let ok = true;\n for (const key of Object.keys(matchers) as (keyof typeof matchers)[]) {\n if (key === groupKey) continue;\n if (!matchers[key](dataset)) {\n ok = false;\n break;\n }\n }\n if (ok && predicate(dataset)) {\n total += 1;\n }\n }\n return total;\n };\n }, [pool, matchers]);\n\n const facetGroups = useMemo<FacetGroup[]>(() => {\n // Universe of option values from the unfiltered pool (stable while toggling).\n const instrumentTypes = new Set<string>();\n const formats = new Set<string>();\n const missionIds = new Set<string>();\n for (const dataset of pool) {\n const type = dataset.sourceId ? sourceTypeById.get(dataset.sourceId) : undefined;\n if (type) instrumentTypes.add(type);\n if (dataset.format) formats.add(dataset.format);\n if (dataset.collectionId) missionIds.add(dataset.collectionId);\n }\n\n const instrument: FacetGroup = {\n key: 'instrument',\n label: 'Instrument',\n options: [...instrumentTypes]\n .map((type) => ({\n value: type,\n label: formatEnumLabel(type),\n count: countFor('instrument', (d) =>\n d.sourceId ? sourceTypeById.get(d.sourceId) === type : false\n ),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n const access: FacetGroup = {\n key: 'access',\n label: 'Access',\n options: ACCESS_LEVELS.map((level) => ({\n value: level,\n label: formatEnumLabel(level),\n count: countFor('access', (d) => d.accessLevel === level),\n })),\n };\n\n const format: FacetGroup = {\n key: 'format',\n label: 'Format',\n options: [...formats]\n .sort((a, b) => a.localeCompare(b))\n .map((fmt) => ({\n value: fmt,\n label: fmt,\n count: countFor('format', (d) => d.format === fmt),\n })),\n };\n\n const mission: FacetGroup = {\n key: 'mission',\n label: 'Mission / Survey',\n options: [...missionIds]\n .map((id) => ({\n value: id,\n label: collectionNameById.get(id) ?? 'Unknown collection',\n count: countFor('mission', (d) => d.collectionId === id),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n return [instrument, access, format, mission];\n }, [pool, sourceTypeById, collectionNameById, countFor]);\n\n // Displayed results: the pool filtered by all selected facets, then sorted.\n const results = useMemo(() => {\n const filtered = pool.filter(\n (dataset) =>\n matchers.instrument(dataset) &&\n matchers.access(dataset) &&\n matchers.format(dataset) &&\n matchers.mission(dataset)\n );\n const sorted = [...filtered];\n switch (sort) {\n case 'name':\n sorted.sort((a, b) => a.name.localeCompare(b.name));\n break;\n case 'updated':\n sorted.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n break;\n case 'size':\n sorted.sort((a, b) => Number(b.sizeBytes) - Number(a.sizeBytes));\n break;\n case 'relevance':\n default:\n break; // preserve backend order (relevance when `q` is present)\n }\n return sorted;\n }, [pool, matchers, sort]);\n\n const totalSelected = Object.values(selected).reduce((sum, values) => sum + values.length, 0);\n\n const handleToggle = (groupKey: string, value: string) => {\n setSelected((prev) => {\n const current = prev[groupKey] ?? [];\n const next = current.includes(value)\n ? current.filter((item) => item !== value)\n : [...current, value];\n return { ...prev, [groupKey]: next };\n });\n };\n\n const handleClear = () => setSelected({});\n\n const loading = poolQuery.loading && pool.length === 0;\n const error = poolQuery.error;\n\n const facetPanel = (\n <FacetPanel\n groups={facetGroups}\n selected={selected}\n onToggle={handleToggle}\n onClear={handleClear}\n />\n );\n\n return (\n <PageLayout\n title=\"Discovery\"\n description=\"Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('datasets')}>\n <TableIcon className=\"size-4\" />\n Table view\n </Button>\n }\n >\n <div className=\"space-y-5\">\n {/* Search bar */}\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder=\"Search datasets by name, description or tag…\"\n aria-label=\"Search datasets\"\n className=\"h-12 rounded-card pl-12 pr-11 text-base\"\n />\n {searchInput ? (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n aria-label=\"Clear search\"\n className=\"absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <X className=\"size-4\" />\n </button>\n ) : null}\n </div>\n\n {/* Mobile facet toggle */}\n <div className=\"flex items-center justify-between gap-3 lg:hidden\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setFacetsOpen((open) => !open)}\n aria-expanded={facetsOpen}\n >\n <SlidersHorizontal className=\"size-4\" />\n Filters{totalSelected > 0 ? ` (${totalSelected})` : ''}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-6 lg:flex-row\">\n {/* Facets — sticky sidebar on desktop, collapsible on mobile */}\n <aside\n className={cn(\n 'shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6',\n facetsOpen ? 'block' : 'hidden'\n )}\n >\n <div className=\"lg:sticky lg:top-0\">{facetPanel}</div>\n </aside>\n\n {/* Results */}\n <div className=\"min-w-0 flex-1 space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {results.length}\n </span>\n <span className=\"text-sm text-text-secondary\">\n {results.length === 1 ? 'dataset' : 'datasets'}\n {q ? ` matching “${q}”` : ''}\n </span>\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <ArrowUpDown className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <Select value={sort} onValueChange={(value) => setSort(value as SortKey)}>\n <SelectTrigger aria-label=\"Sort results\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SORT_LABELS) as SortKey[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SORT_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {poolHasMore ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n Showing facets and results for the first {POOL_LIMIT} matches. Refine your search to\n narrow the set.\n </p>\n ) : null}\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Searching the catalog…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not run that search. Please try again.\"\n onRetry={() => void poolQuery.refetch()}\n />\n ) : results.length === 0 ? (\n <EmptyState\n icon={Search}\n title={q || totalSelected > 0 ? 'No datasets match' : 'Start exploring'}\n description={\n q || totalSelected > 0\n ? 'Try a broader search term or clear some facets.'\n : 'Search by name, description or tag, or pick a facet to browse the catalog.'\n }\n action={\n totalSelected > 0 ? (\n <Button type=\"button\" variant=\"outline\" onClick={handleClear}>\n Clear filters\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3\">\n {results.map((dataset) => (\n <button\n key={dataset.id}\n type=\"button\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {dataset.name}\n </h3>\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <FileText className=\"size-3.5\" aria-hidden=\"true\" />\n {dataset.format || 'No format'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n {formatBytes(dataset.sizeBytes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Rows3 className=\"size-3.5\" aria-hidden=\"true\" />\n {formatRows(dataset.rowCount)}\n </span>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n {dataset.tags.length > 0 ? (\n <div className=\"mt-auto flex flex-wrap gap-1 pt-1\">\n {dataset.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LineagePage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Info, Share2, Workflow } from 'lucide-react';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosPipelinesQuery,\n useCosReleasesQuery,\n useCosRunsQuery,\n useCosAnalysesQuery,\n useCosSourcesQuery,\n} from '../../api/cosmicApi';\nimport { EmptyState, ErrorState, LoadingState, PageLayout } from '../../components';\nimport {\n LineageGraph,\n buildDatasetLineage,\n buildReleaseLineage,\n type LineageDataPools,\n} from '../../components/LineageGraph';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\ntype RootKind = 'dataset' | 'release';\n\nconst PAGE_LIMIT = 100;\n\nexport default function LineagePage() {\n const { navigate } = useCosmicIntersection();\n // Optional deep-link: /lineage/:datasetId preselects that dataset as the root.\n const { datasetId: routeDatasetId } = useParams<{ datasetId?: string }>();\n\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const releasesQuery = useCosReleasesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n const runsQuery = useCosRunsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const analysesQuery = useCosAnalysesQuery({});\n const pipelinesQuery = useCosPipelinesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n\n const datasets = useMemo(\n () => datasetsQuery.data?.cosListDatasets?.items ?? [],\n [datasetsQuery.data]\n );\n const releases = useMemo(\n () => releasesQuery.data?.cosListReleases?.items ?? [],\n [releasesQuery.data]\n );\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n const runs = useMemo(() => runsQuery.data?.cosListRuns?.items ?? [], [runsQuery.data]);\n const notebooks = useMemo(\n () => notebooksQuery.data?.cosListNotebooks?.items ?? [],\n [notebooksQuery.data]\n );\n const analyses = useMemo(() => analysesQuery.data?.cosListAnalyses ?? [], [analysesQuery.data]);\n const pipelines = useMemo(\n () => pipelinesQuery.data?.cosListPipelines?.items ?? [],\n [pipelinesQuery.data]\n );\n\n const pools: LineageDataPools = useMemo(\n () => ({\n datasetsById: new Map(datasets.map((item) => [item.id, item])),\n sourcesById: new Map(sources.map((item) => [item.id, item])),\n collectionsById: new Map(collections.map((item) => [item.id, item])),\n runs,\n notebooks,\n analyses,\n releases,\n pipelinesById: new Map(pipelines.map((item) => [item.id, item])),\n }),\n [datasets, sources, collections, runs, notebooks, analyses, releases, pipelines]\n );\n\n const [rootKind, setRootKind] = useState<RootKind>('dataset');\n const [selectedId, setSelectedId] = useState<string>(routeDatasetId ?? '');\n\n // Auto-select the first available root of the current kind once lists load —\n // the documented derive-state-during-render pattern (no effect required).\n const options = rootKind === 'dataset' ? datasets : releases;\n const firstOption = options[0]?.id ?? '';\n const selectionValid = options.some((item) => item.id === selectedId);\n if (!selectionValid && firstOption) {\n setSelectedId(firstOption);\n }\n const activeId = selectionValid ? selectedId : firstOption;\n\n const model = useMemo(() => {\n if (!activeId) {\n return { nodes: [], edges: [] };\n }\n return rootKind === 'dataset'\n ? buildDatasetLineage(activeId, pools)\n : buildReleaseLineage(activeId, pools);\n }, [activeId, rootKind, pools]);\n\n const primaryLoading =\n (datasetsQuery.loading && datasets.length === 0) ||\n (releasesQuery.loading && releases.length === 0);\n const primaryError = datasetsQuery.error || releasesQuery.error;\n\n const handleKindChange = (value: string) => {\n setRootKind(value as RootKind);\n setSelectedId('');\n };\n\n const activeLabel =\n rootKind === 'dataset'\n ? datasets.find((item) => item.id === activeId)?.name\n : releases.find((item) => item.id === activeId)?.title;\n\n return (\n <PageLayout\n title=\"Lineage\"\n description=\"Trace any result from raw observation to citable release.\"\n >\n <div className=\"space-y-6\">\n {/* Root picker */}\n <div className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 sm:flex-row sm:items-end\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Trace from</span>\n <Select value={rootKind} onValueChange={handleKindChange}>\n <SelectTrigger aria-label=\"Root entity kind\" className=\"w-full sm:w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"dataset\">Dataset</SelectItem>\n <SelectItem value=\"release\">Release</SelectItem>\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm sm:flex-1\">\n <span className=\"block font-medium text-text-primary\">\n {rootKind === 'dataset' ? 'Dataset' : 'Release'}\n </span>\n <Select value={activeId} onValueChange={setSelectedId} disabled={options.length === 0}>\n <SelectTrigger aria-label={`Select ${rootKind}`} className=\"w-full sm:max-w-md\">\n <SelectValue\n placeholder={\n options.length === 0 ? `No ${rootKind}s available` : `Select a ${rootKind}`\n }\n />\n </SelectTrigger>\n <SelectContent>\n {options.map((item) => (\n <SelectItem key={item.id} value={item.id}>\n {rootKind === 'dataset'\n ? (item as (typeof datasets)[number]).name\n : (item as (typeof releases)[number]).title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {primaryError ? (\n <ErrorState\n message=\"We could not load lineage data. Please try again.\"\n onRetry={() => {\n void datasetsQuery.refetch();\n void releasesQuery.refetch();\n }}\n />\n ) : primaryLoading ? (\n <LoadingState label=\"Loading lineage…\" />\n ) : datasets.length === 0 && releases.length === 0 ? (\n <EmptyState\n icon={Workflow}\n title=\"Nothing to trace yet.\"\n description=\"Register a dataset or publish a release to build a provenance graph from raw observation to citable output.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigate('datasets')}\n className=\"text-sm font-medium text-text-link underline-offset-2 hover:underline\"\n >\n Go to datasets\n </button>\n }\n />\n ) : model.nodes.length === 0 ? (\n <EmptyState\n icon={Share2}\n title={`Select a ${rootKind} to trace`}\n description=\"Choose a root above to render its provenance graph.\"\n />\n ) : (\n <div className=\"space-y-4\">\n {activeLabel ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Share2 className=\"size-4 text-text-tertiary\" aria-hidden=\"true\" />\n <span>\n Provenance for{' '}\n <span className=\"font-medium text-text-primary\">{activeLabel}</span>\n </span>\n </div>\n ) : null}\n\n <LineageGraph model={model} mode=\"full\" onNavigate={navigate} />\n\n <p className=\"flex items-start gap-2 text-xs text-text-tertiary\">\n <Info className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span>\n This graph is derived live from your workspace’s cos entities. Connected runs,\n notebooks, analyses and releases are matched across the {PAGE_LIMIT} most recent\n records of each type; older items may not appear.\n </span>\n </p>\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LineagePage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/lineage/LineagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Info, Share2, Workflow } from 'lucide-react';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosNotebooksQuery,\n useCosPipelinesQuery,\n useCosReleasesQuery,\n useCosRunsQuery,\n useCosAnalysesQuery,\n useCosSourcesQuery,\n} from '../../api/cosmicApi';\nimport { EmptyState, ErrorState, LoadingState, PageLayout } from '../../components';\nimport {\n LineageGraph,\n buildDatasetLineage,\n buildReleaseLineage,\n type LineageDataPools,\n} from '../../components/LineageGraph';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\n\ntype RootKind = 'dataset' | 'release';\n\nconst PAGE_LIMIT = 100;\n\nexport default function LineagePage() {\n const { navigate } = useCosmicIntersection();\n // Optional deep-link: /lineage/:datasetId preselects that dataset as the root.\n const { datasetId: routeDatasetId } = useParams<{ datasetId?: string }>();\n\n const datasetsQuery = useCosDatasetsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const releasesQuery = useCosReleasesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n const runsQuery = useCosRunsQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const notebooksQuery = useCosNotebooksQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n const analysesQuery = useCosAnalysesQuery({});\n const pipelinesQuery = useCosPipelinesQuery({ pagination: { limit: PAGE_LIMIT, offset: 0 } });\n\n const datasets = useMemo(\n () => datasetsQuery.data?.cosListDatasets?.items ?? [],\n [datasetsQuery.data]\n );\n const releases = useMemo(\n () => releasesQuery.data?.cosListReleases?.items ?? [],\n [releasesQuery.data]\n );\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n const runs = useMemo(() => runsQuery.data?.cosListRuns?.items ?? [], [runsQuery.data]);\n const notebooks = useMemo(\n () => notebooksQuery.data?.cosListNotebooks?.items ?? [],\n [notebooksQuery.data]\n );\n const analyses = useMemo(() => analysesQuery.data?.cosListAnalyses ?? [], [analysesQuery.data]);\n const pipelines = useMemo(\n () => pipelinesQuery.data?.cosListPipelines?.items ?? [],\n [pipelinesQuery.data]\n );\n\n const pools: LineageDataPools = useMemo(\n () => ({\n datasetsById: new Map(datasets.map((item) => [item.id, item])),\n sourcesById: new Map(sources.map((item) => [item.id, item])),\n collectionsById: new Map(collections.map((item) => [item.id, item])),\n runs,\n notebooks,\n analyses,\n releases,\n pipelinesById: new Map(pipelines.map((item) => [item.id, item])),\n }),\n [datasets, sources, collections, runs, notebooks, analyses, releases, pipelines]\n );\n\n const [rootKind, setRootKind] = useState<RootKind>('dataset');\n const [selectedId, setSelectedId] = useState<string>(routeDatasetId ?? '');\n\n // Auto-select the first available root of the current kind once lists load —\n // the documented derive-state-during-render pattern (no effect required).\n const options = rootKind === 'dataset' ? datasets : releases;\n const firstOption = options[0]?.id ?? '';\n const selectionValid = options.some((item) => item.id === selectedId);\n if (!selectionValid && firstOption) {\n setSelectedId(firstOption);\n }\n const activeId = selectionValid ? selectedId : firstOption;\n\n const model = useMemo(() => {\n if (!activeId) {\n return { nodes: [], edges: [] };\n }\n return rootKind === 'dataset'\n ? buildDatasetLineage(activeId, pools)\n : buildReleaseLineage(activeId, pools);\n }, [activeId, rootKind, pools]);\n\n const primaryLoading =\n (datasetsQuery.loading && datasets.length === 0) ||\n (releasesQuery.loading && releases.length === 0);\n const primaryError = datasetsQuery.error || releasesQuery.error;\n\n const handleKindChange = (value: string) => {\n setRootKind(value as RootKind);\n setSelectedId('');\n };\n\n const activeLabel =\n rootKind === 'dataset'\n ? datasets.find((item) => item.id === activeId)?.name\n : releases.find((item) => item.id === activeId)?.title;\n\n return (\n <PageLayout\n title=\"Lineage\"\n description=\"Trace any result from raw observation to citable release.\"\n >\n <div className=\"space-y-6\">\n {/* Root picker */}\n <div className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 sm:flex-row sm:items-end\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Trace from</span>\n <Select value={rootKind} onValueChange={handleKindChange}>\n <SelectTrigger aria-label=\"Root entity kind\" className=\"w-full sm:w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"dataset\">Dataset</SelectItem>\n <SelectItem value=\"release\">Release</SelectItem>\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm sm:flex-1\">\n <span className=\"block font-medium text-text-primary\">\n {rootKind === 'dataset' ? 'Dataset' : 'Release'}\n </span>\n <Select value={activeId} onValueChange={setSelectedId} disabled={options.length === 0}>\n <SelectTrigger aria-label={`Select ${rootKind}`} className=\"w-full sm:max-w-md\">\n <SelectValue\n placeholder={\n options.length === 0 ? `No ${rootKind}s available` : `Select a ${rootKind}`\n }\n />\n </SelectTrigger>\n <SelectContent>\n {options.map((item) => (\n <SelectItem key={item.id} value={item.id}>\n {rootKind === 'dataset'\n ? (item as (typeof datasets)[number]).name\n : (item as (typeof releases)[number]).title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {primaryError ? (\n <ErrorState\n message=\"We could not load lineage data. Please try again.\"\n onRetry={() => {\n void datasetsQuery.refetch();\n void releasesQuery.refetch();\n }}\n />\n ) : primaryLoading ? (\n <LoadingState label=\"Loading lineage…\" />\n ) : datasets.length === 0 && releases.length === 0 ? (\n <EmptyState\n icon={Workflow}\n title=\"Nothing to trace yet.\"\n description=\"Register a dataset or publish a release to build a provenance graph from raw observation to citable output.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigate('datasets')}\n className=\"text-sm font-medium text-text-link underline-offset-2 hover:underline\"\n >\n Go to datasets\n </button>\n }\n />\n ) : model.nodes.length === 0 ? (\n <EmptyState\n icon={Share2}\n title={`Select a ${rootKind} to trace`}\n description=\"Choose a root above to render its provenance graph.\"\n />\n ) : (\n <div className=\"space-y-4\">\n {activeLabel ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Share2 className=\"size-4 text-text-tertiary\" aria-hidden=\"true\" />\n <span>\n Provenance for{' '}\n <span className=\"font-medium text-text-primary\">{activeLabel}</span>\n </span>\n </div>\n ) : null}\n\n <LineageGraph model={model} mode=\"full\" onNavigate={navigate} />\n\n <p className=\"flex items-start gap-2 text-xs text-text-tertiary\">\n <Info className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span>\n This graph is derived live from your workspace’s cos entities. Connected runs,\n notebooks, analyses and releases are matched across the {PAGE_LIMIT} most recent\n records of each type; older items may not appear.\n </span>\n </p>\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotebookDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/notebooks/NotebookDetailPage.tsx"],"sourcesContent":["/**\n * NotebookDetailPage — full record for one notebook.\n *\n * Header renders the notebook's metadata (status, runtime, environment JSON,\n * description, sharing, attached file download). Below it an Analyses section\n * lists `cosListAnalyses(notebookId)` and a \"Run analysis\" dialog kicks off a\n * new `cosRunAnalysis` bound to this notebook. Archive is available from the\n * header action menu. No fabricated data — everything is read from the backend.\n */\nimport { useMemo, useState, type FormEvent } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Beaker,\n Database,\n Download,\n FileCode2,\n MessageSquare,\n Pencil,\n Play,\n Share2,\n Sparkles,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Switch,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAnalysesQuery,\n useCosArchiveNotebookMutation,\n useCosDatasetsQuery,\n useCosNotebookQuery,\n useCosRunAnalysisMutation,\n type CosAnalysis,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n DiscussionPanel,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n useFileDownloadUrl,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nfunction formatDateTime(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return '—';\n }\n return date.toLocaleString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\n/** Renders a presigned download link for an attached wspace-files artifact. */\nfunction FileDownloadLink({ fileId, label }: { fileId: string; label: string }) {\n const { data, loading } = useFileDownloadUrl(fileId);\n const url = data?.getFileDownloadUrl ?? null;\n if (loading) {\n return <span className=\"text-xs text-text-secondary\">Resolving link…</span>;\n }\n if (!url) {\n return <span className=\"text-xs text-text-secondary\">File {fileId}</span>;\n }\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n {label}\n </a>\n );\n}\n\ninterface RunAnalysisDialogProps {\n notebookId: string;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCompleted: () => void;\n}\n\n/** Dialog to launch a new analysis on this notebook. */\nfunction RunAnalysisDialog({\n notebookId,\n open,\n onOpenChange,\n onCompleted,\n}: RunAnalysisDialogProps) {\n const [title, setTitle] = useState('');\n const [method, setMethod] = useState('');\n const [aiAssisted, setAiAssisted] = useState(false);\n const [runAnalysis, { loading }] = useCosRunAnalysisMutation();\n\n const reset = () => {\n setTitle('');\n setMethod('');\n setAiAssisted(false);\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const trimmed = title.trim();\n if (!trimmed) {\n notify.error('Title is required', 'Give the analysis a descriptive title.');\n return;\n }\n const response = await runAnalysis({\n variables: {\n input: {\n notebookId,\n title: trimmed,\n method: method.trim() || undefined,\n aiAssisted,\n },\n },\n });\n if (response.error || !response.data?.cosRunAnalysis?.id) {\n return;\n }\n notify.success('Analysis started', `“${trimmed}” is now running.`);\n reset();\n onOpenChange(false);\n onCompleted();\n };\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!loading) {\n onOpenChange(next);\n }\n }}\n >\n <DialogContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <DialogHeader>\n <DialogTitle>Run analysis</DialogTitle>\n <DialogDescription>\n Launch a new analysis run against this notebook. It starts in the running state and\n can be completed later with results.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-analysis-title\">Title</Label>\n <Input\n id=\"run-analysis-title\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder=\"e.g. Period-folding search\"\n required\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-analysis-method\">Method</Label>\n <Input\n id=\"run-analysis-method\"\n value={method}\n onChange={(event) => setMethod(event.target.value)}\n placeholder=\"e.g. box-least-squares\"\n />\n </div>\n <div className=\"flex items-center gap-3\">\n <Switch\n id=\"run-analysis-ai\"\n checked={aiAssisted}\n onCheckedChange={(checked) => setAiAssisted(checked)}\n />\n <Label htmlFor=\"run-analysis-ai\" className=\"cursor-pointer\">\n AI-assisted analysis\n </Label>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={loading}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={loading}>\n <Play className=\"size-4\" />\n {loading ? 'Starting…' : 'Run analysis'}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport default function NotebookDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n const [runOpen, setRunOpen] = useState(false);\n\n const { data, loading, error, refetch } = useCosNotebookQuery(id);\n const {\n data: analysesData,\n loading: analysesLoading,\n error: analysesError,\n refetch: refetchAnalyses,\n } = useCosAnalysesQuery({ notebookId: id });\n // Resolve the notebook's linked dataset UUIDs to human names for chips.\n const { data: datasetsData } = useCosDatasetsQuery({ pagination: { limit: 100, offset: 0 } });\n const [archiveNotebook] = useCosArchiveNotebookMutation();\n\n const notebook = data?.cosNotebook ?? null;\n const analyses = analysesData?.cosListAnalyses ?? [];\n\n const datasetNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasetsData?.cosListDatasets.items ?? []) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasetsData]);\n\n const handleArchive = async () => {\n if (!notebook) {\n return;\n }\n const response = await archiveNotebook({ variables: { id: notebook.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveNotebook) {\n notify.error('Could not archive notebook', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n await refetch();\n notify.success('Notebook archived', `“${notebook.title}” was archived.`);\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Notebook\" backPath=\"notebooks\">\n <LoadingState label=\"Loading notebook…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Notebook\" backPath=\"notebooks\">\n <ErrorState\n message=\"We could not load this notebook. Please try again.\"\n onRetry={() => void refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!notebook) {\n return (\n <PageLayout title=\"Notebook\" backPath=\"notebooks\">\n <EmptyState\n icon={FileCode2}\n title=\"Notebook not found.\"\n description=\"This notebook may have been removed or is outside your current scope.\"\n action={\n <Button type=\"button\" onClick={() => navigate('notebooks')}>\n Back to notebooks\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const environmentJson =\n notebook.environment && Object.keys(notebook.environment).length > 0\n ? JSON.stringify(notebook.environment, null, 2)\n : null;\n\n const actions = (\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`notebooks/${notebook.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n <ActionMenu\n ariaLabel={`Actions for ${notebook.title}`}\n entityName={notebook.title}\n onDelete={notebook.status === 'ARCHIVED' ? undefined : handleArchive}\n deleteLabel=\"Archive notebook\"\n />\n </div>\n );\n\n return (\n <PageLayout\n title={notebook.title}\n description=\"Notebook detail\"\n backPath=\"notebooks\"\n actions={actions}\n >\n <div className=\"space-y-6\">\n {/* Overview */}\n <section className=\"rounded-card border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <StatusBadge status={notebook.status} />\n {notebook.runtime ? <Badge variant=\"outline\">Runtime: {notebook.runtime}</Badge> : null}\n {notebook.isShared ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Share2 className=\"size-3\" aria-hidden=\"true\" />\n Shared{notebook.shareSlug ? `: ${notebook.shareSlug}` : ''}\n </Badge>\n ) : (\n <Badge variant=\"outline\">Private</Badge>\n )}\n </div>\n\n {notebook.description ? (\n <p className=\"mt-4 text-sm text-text-secondary\">{notebook.description}</p>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No description provided.</p>\n )}\n\n <div className=\"mt-6\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Linked datasets</p>\n {notebook.datasetIds.length === 0 ? (\n <p className=\"mt-2 text-sm text-text-secondary\">No datasets linked.</p>\n ) : (\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {notebook.datasetIds.map((datasetId) => {\n const name = datasetNameById.get(datasetId);\n return (\n <Button\n key={datasetId}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1.5 px-2.5 py-1\"\n onClick={() => navigate(`datasets/${datasetId}`)}\n >\n <Database className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className={name ? 'max-w-[16rem] truncate' : 'font-mono text-xs'}>\n {name ?? datasetId}\n </span>\n </Button>\n );\n })}\n </div>\n )}\n </div>\n\n <dl className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <div>\n <dt className=\"text-xs uppercase tracking-wide text-text-secondary\">Created</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">\n {formatDateTime(notebook.createdAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs uppercase tracking-wide text-text-secondary\">Updated</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">\n {formatDateTime(notebook.updatedAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs uppercase tracking-wide text-text-secondary\">Notebook file</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">\n {notebook.file?.id ? (\n <FileDownloadLink fileId={notebook.file.id} label=\"Download notebook\" />\n ) : (\n <span className=\"text-text-secondary\">No file attached</span>\n )}\n </dd>\n </div>\n </dl>\n\n {notebook.tags.length > 0 ? (\n <div className=\"mt-6\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Tags</p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {notebook.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\">\n {tag}\n </Badge>\n ))}\n </div>\n </div>\n ) : null}\n\n {environmentJson ? (\n <div className=\"mt-6\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Environment</p>\n <pre className=\"mt-2 max-h-64 overflow-auto rounded-card border border-border-default bg-bg-sunken p-4 font-mono text-xs text-text-primary\">\n {environmentJson}\n </pre>\n </div>\n ) : null}\n </section>\n\n {/* Discussion */}\n <section className=\"space-y-4\">\n <div className=\"flex items-center gap-2\">\n <MessageSquare className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">Discussion</h2>\n </div>\n <DiscussionPanel\n entityType=\"cosmicintersection-notebook\"\n entityId={notebook.id}\n title={notebook.title}\n widgetKey=\"notebook-discussion\"\n />\n </section>\n\n {/* Analyses */}\n <section className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Analyses</h2>\n <p className=\"text-sm text-text-secondary\">\n Analysis runs executed against this notebook.\n </p>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n </div>\n\n {analysesLoading ? (\n <LoadingState variant=\"rows\" rows={3} label=\"Loading analyses…\" />\n ) : analysesError ? (\n <ErrorState\n message=\"We could not load analyses for this notebook.\"\n onRetry={() => void refetchAnalyses()}\n />\n ) : analyses.length === 0 ? (\n <EmptyState\n icon={Beaker}\n title=\"No analyses yet.\"\n description=\"Run an analysis to generate results and artifacts from this notebook.\"\n action={\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n }\n />\n ) : (\n <>\n {/* Desktop table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Method</TableHead>\n <TableHead>AI</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Output</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {analyses.map((analysis: CosAnalysis) => (\n <TableRow key={analysis.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {analysis.title}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {analysis.method ?? '—'}\n </TableCell>\n <TableCell>\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell>\n <StatusBadge status={analysis.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(analysis.startedAt)}\n </TableCell>\n <TableCell>\n {analysis.outputFile?.id ? (\n <FileDownloadLink fileId={analysis.outputFile.id} label=\"Download\" />\n ) : analysis.results && Object.keys(analysis.results).length > 0 ? (\n <span className=\"text-xs text-text-secondary\">Results recorded</span>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile card list */}\n <ul className=\"space-y-3 md:hidden\">\n {analyses.map((analysis: CosAnalysis) => (\n <li\n key={analysis.id}\n className=\"space-y-2 rounded-card border border-border-default bg-bg-elevated p-4\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{analysis.title}</p>\n {analysis.method ? (\n <p className=\"truncate text-xs text-text-secondary\">{analysis.method}</p>\n ) : null}\n </div>\n <StatusBadge status={analysis.status} />\n </div>\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : null}\n <span>Started {formatDateTime(analysis.startedAt)}</span>\n </div>\n <div>\n {analysis.outputFile?.id ? (\n <FileDownloadLink fileId={analysis.outputFile.id} label=\"Download\" />\n ) : analysis.results && Object.keys(analysis.results).length > 0 ? (\n <span className=\"text-xs text-text-secondary\">Results recorded</span>\n ) : (\n <span className=\"text-xs text-text-secondary\">No output</span>\n )}\n </div>\n </li>\n ))}\n </ul>\n </>\n )}\n </section>\n </div>\n\n <RunAnalysisDialog\n notebookId={notebook.id}\n open={runOpen}\n onOpenChange={setRunOpen}\n onCompleted={() => void refetchAnalyses()}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NotebookDetailPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/notebooks/NotebookDetailPage.tsx"],"sourcesContent":["/**\n * NotebookDetailPage — full record for one notebook.\n *\n * Header renders the notebook's metadata (status, runtime, environment JSON,\n * description, sharing, attached file download). Below it an Analyses section\n * lists `cosListAnalyses(notebookId)` and a \"Run analysis\" dialog kicks off a\n * new `cosRunAnalysis` bound to this notebook. Archive is available from the\n * header action menu. No fabricated data — everything is read from the backend.\n */\nimport { useMemo, useState, type FormEvent } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Beaker,\n Database,\n Download,\n FileCode2,\n MessageSquare,\n Pencil,\n Play,\n Share2,\n Sparkles,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Label,\n Switch,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosAnalysesQuery,\n useCosArchiveNotebookMutation,\n useCosDatasetsQuery,\n useCosNotebookQuery,\n useCosRunAnalysisMutation,\n type CosAnalysis,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n DiscussionPanel,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n useFileDownloadUrl,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nfunction formatDateTime(value?: string | null): string {\n if (!value) {\n return '—';\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return '—';\n }\n return date.toLocaleString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\n/** Renders a presigned download link for an attached wspace-files artifact. */\nfunction FileDownloadLink({ fileId, label }: { fileId: string; label: string }) {\n const { data, loading } = useFileDownloadUrl(fileId);\n const url = data?.getFileDownloadUrl ?? null;\n if (loading) {\n return <span className=\"text-xs text-text-secondary\">Resolving link…</span>;\n }\n if (!url) {\n return <span className=\"text-xs text-text-secondary\">File {fileId}</span>;\n }\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n {label}\n </a>\n );\n}\n\ninterface RunAnalysisDialogProps {\n notebookId: string;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCompleted: () => void;\n}\n\n/** Dialog to launch a new analysis on this notebook. */\nfunction RunAnalysisDialog({\n notebookId,\n open,\n onOpenChange,\n onCompleted,\n}: RunAnalysisDialogProps) {\n const [title, setTitle] = useState('');\n const [method, setMethod] = useState('');\n const [aiAssisted, setAiAssisted] = useState(false);\n const [runAnalysis, { loading }] = useCosRunAnalysisMutation();\n\n const reset = () => {\n setTitle('');\n setMethod('');\n setAiAssisted(false);\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const trimmed = title.trim();\n if (!trimmed) {\n notify.error('Title is required', 'Give the analysis a descriptive title.');\n return;\n }\n const response = await runAnalysis({\n variables: {\n input: {\n notebookId,\n title: trimmed,\n method: method.trim() || undefined,\n aiAssisted,\n },\n },\n });\n if (response.error || !response.data?.cosRunAnalysis?.id) {\n return;\n }\n notify.success('Analysis started', `“${trimmed}” is now running.`);\n reset();\n onOpenChange(false);\n onCompleted();\n };\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!loading) {\n onOpenChange(next);\n }\n }}\n >\n <DialogContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <DialogHeader>\n <DialogTitle>Run analysis</DialogTitle>\n <DialogDescription>\n Launch a new analysis run against this notebook. It starts in the running state and\n can be completed later with results.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-analysis-title\">Title</Label>\n <Input\n id=\"run-analysis-title\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder=\"e.g. Period-folding search\"\n required\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"run-analysis-method\">Method</Label>\n <Input\n id=\"run-analysis-method\"\n value={method}\n onChange={(event) => setMethod(event.target.value)}\n placeholder=\"e.g. box-least-squares\"\n />\n </div>\n <div className=\"flex items-center gap-3\">\n <Switch\n id=\"run-analysis-ai\"\n checked={aiAssisted}\n onCheckedChange={(checked) => setAiAssisted(checked)}\n />\n <Label htmlFor=\"run-analysis-ai\" className=\"cursor-pointer\">\n AI-assisted analysis\n </Label>\n </div>\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={loading}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={loading}>\n <Play className=\"size-4\" />\n {loading ? 'Starting…' : 'Run analysis'}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport default function NotebookDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useCosmicIntersection();\n const [runOpen, setRunOpen] = useState(false);\n\n const { data, loading, error, refetch } = useCosNotebookQuery(id);\n const {\n data: analysesData,\n loading: analysesLoading,\n error: analysesError,\n refetch: refetchAnalyses,\n } = useCosAnalysesQuery({ notebookId: id });\n // Resolve the notebook's linked dataset UUIDs to human names for chips.\n const { data: datasetsData } = useCosDatasetsQuery({ pagination: { limit: 100, offset: 0 } });\n const [archiveNotebook] = useCosArchiveNotebookMutation();\n\n const notebook = data?.cosNotebook ?? null;\n const analyses = analysesData?.cosListAnalyses ?? [];\n\n const datasetNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const dataset of datasetsData?.cosListDatasets.items ?? []) {\n map.set(dataset.id, dataset.name);\n }\n return map;\n }, [datasetsData]);\n\n const handleArchive = async () => {\n if (!notebook) {\n return;\n }\n const response = await archiveNotebook({ variables: { id: notebook.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveNotebook) {\n notify.error('Could not archive notebook', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n await refetch();\n notify.success('Notebook archived', `“${notebook.title}” was archived.`);\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Notebook\" backPath=\"notebooks\">\n <LoadingState label=\"Loading notebook…\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Notebook\" backPath=\"notebooks\">\n <ErrorState\n message=\"We could not load this notebook. Please try again.\"\n onRetry={() => void refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!notebook) {\n return (\n <PageLayout title=\"Notebook\" backPath=\"notebooks\">\n <EmptyState\n icon={FileCode2}\n title=\"Notebook not found.\"\n description=\"This notebook may have been removed or is outside your current scope.\"\n action={\n <Button type=\"button\" onClick={() => navigate('notebooks')}>\n Back to notebooks\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const environmentJson =\n notebook.environment && Object.keys(notebook.environment).length > 0\n ? JSON.stringify(notebook.environment, null, 2)\n : null;\n\n const actions = (\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`notebooks/${notebook.id}/edit`)}\n >\n <Pencil className=\"size-4\" />\n Edit\n </Button>\n <ActionMenu\n ariaLabel={`Actions for ${notebook.title}`}\n entityName={notebook.title}\n onDelete={notebook.status === 'ARCHIVED' ? undefined : handleArchive}\n deleteLabel=\"Archive notebook\"\n />\n </div>\n );\n\n return (\n <PageLayout\n title={notebook.title}\n description=\"Notebook detail\"\n backPath=\"notebooks\"\n actions={actions}\n >\n <div className=\"space-y-6\">\n {/* Overview */}\n <section className=\"rounded-card border border-border-default bg-bg-elevated p-6\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <StatusBadge status={notebook.status} />\n {notebook.runtime ? <Badge variant=\"outline\">Runtime: {notebook.runtime}</Badge> : null}\n {notebook.isShared ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Share2 className=\"size-3\" aria-hidden=\"true\" />\n Shared{notebook.shareSlug ? `: ${notebook.shareSlug}` : ''}\n </Badge>\n ) : (\n <Badge variant=\"outline\">Private</Badge>\n )}\n </div>\n\n {notebook.description ? (\n <p className=\"mt-4 text-sm text-text-secondary\">{notebook.description}</p>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No description provided.</p>\n )}\n\n <div className=\"mt-6\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Linked datasets</p>\n {notebook.datasetIds.length === 0 ? (\n <p className=\"mt-2 text-sm text-text-secondary\">No datasets linked.</p>\n ) : (\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {notebook.datasetIds.map((datasetId) => {\n const name = datasetNameById.get(datasetId);\n return (\n <Button\n key={datasetId}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto gap-1.5 px-2.5 py-1\"\n onClick={() => navigate(`datasets/${datasetId}`)}\n >\n <Database className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className={name ? 'max-w-[16rem] truncate' : 'font-mono text-xs'}>\n {name ?? datasetId}\n </span>\n </Button>\n );\n })}\n </div>\n )}\n </div>\n\n <dl className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <div>\n <dt className=\"text-xs uppercase tracking-wide text-text-secondary\">Created</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">\n {formatDateTime(notebook.createdAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs uppercase tracking-wide text-text-secondary\">Updated</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">\n {formatDateTime(notebook.updatedAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs uppercase tracking-wide text-text-secondary\">Notebook file</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">\n {notebook.file?.id ? (\n <FileDownloadLink fileId={notebook.file.id} label=\"Download notebook\" />\n ) : (\n <span className=\"text-text-secondary\">No file attached</span>\n )}\n </dd>\n </div>\n </dl>\n\n {notebook.tags.length > 0 ? (\n <div className=\"mt-6\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Tags</p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {notebook.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\">\n {tag}\n </Badge>\n ))}\n </div>\n </div>\n ) : null}\n\n {environmentJson ? (\n <div className=\"mt-6\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Environment</p>\n <pre className=\"mt-2 max-h-64 overflow-auto rounded-card border border-border-default bg-bg-sunken p-4 font-mono text-xs text-text-primary\">\n {environmentJson}\n </pre>\n </div>\n ) : null}\n </section>\n\n {/* Discussion */}\n <section className=\"space-y-4\">\n <div className=\"flex items-center gap-2\">\n <MessageSquare className=\"size-5 text-text-secondary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">Discussion</h2>\n </div>\n <DiscussionPanel\n entityType=\"cosmicintersection-notebook\"\n entityId={notebook.id}\n title={notebook.title}\n widgetKey=\"notebook-discussion\"\n />\n </section>\n\n {/* Analyses */}\n <section className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Analyses</h2>\n <p className=\"text-sm text-text-secondary\">\n Analysis runs executed against this notebook.\n </p>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n </div>\n\n {analysesLoading ? (\n <LoadingState variant=\"rows\" rows={3} label=\"Loading analyses…\" />\n ) : analysesError ? (\n <ErrorState\n message=\"We could not load analyses for this notebook.\"\n onRetry={() => void refetchAnalyses()}\n />\n ) : analyses.length === 0 ? (\n <EmptyState\n icon={Beaker}\n title=\"No analyses yet.\"\n description=\"Run an analysis to generate results and artifacts from this notebook.\"\n action={\n <Button type=\"button\" onClick={() => setRunOpen(true)}>\n <Play className=\"size-4\" />\n Run analysis\n </Button>\n }\n />\n ) : (\n <>\n {/* Desktop table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Method</TableHead>\n <TableHead>AI</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Started</TableHead>\n <TableHead>Output</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {analyses.map((analysis: CosAnalysis) => (\n <TableRow key={analysis.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {analysis.title}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {analysis.method ?? '—'}\n </TableCell>\n <TableCell>\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n <TableCell>\n <StatusBadge status={analysis.status} />\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(analysis.startedAt)}\n </TableCell>\n <TableCell>\n {analysis.outputFile?.id ? (\n <FileDownloadLink fileId={analysis.outputFile.id} label=\"Download\" />\n ) : analysis.results && Object.keys(analysis.results).length > 0 ? (\n <span className=\"text-xs text-text-secondary\">Results recorded</span>\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile card list */}\n <ul className=\"space-y-3 md:hidden\">\n {analyses.map((analysis: CosAnalysis) => (\n <li\n key={analysis.id}\n className=\"space-y-2 rounded-card border border-border-default bg-bg-elevated p-4\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{analysis.title}</p>\n {analysis.method ? (\n <p className=\"truncate text-xs text-text-secondary\">{analysis.method}</p>\n ) : null}\n </div>\n <StatusBadge status={analysis.status} />\n </div>\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n {analysis.aiAssisted ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Sparkles className=\"size-3\" aria-hidden=\"true\" />\n AI\n </Badge>\n ) : null}\n <span>Started {formatDateTime(analysis.startedAt)}</span>\n </div>\n <div>\n {analysis.outputFile?.id ? (\n <FileDownloadLink fileId={analysis.outputFile.id} label=\"Download\" />\n ) : analysis.results && Object.keys(analysis.results).length > 0 ? (\n <span className=\"text-xs text-text-secondary\">Results recorded</span>\n ) : (\n <span className=\"text-xs text-text-secondary\">No output</span>\n )}\n </div>\n </li>\n ))}\n </ul>\n </>\n )}\n </section>\n </div>\n\n <RunAnalysisDialog\n notebookId={notebook.id}\n open={runOpen}\n onOpenChange={setRunOpen}\n onCompleted={() => void refetchAnalyses()}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|