Skip to content

Commit 5d0e7e4

Browse files
fix(dashboards): split diagnostics, switch panel query modes, return create revision
- One diagnostics entry per parse error instead of a newline-joined string. - A panel choosing aggregate or columns drops the other mode's inherited dashboard fields, so shared defaults serve table and aggregate panels. - The v2 create response returns the revision it produced, like the replace and edit responses. - The analytics route test uses the shared createMockRequest helper. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S6aTRnkiu7PxYZPNPXYEMV
1 parent 4cc80af commit 5d0e7e4

9 files changed

Lines changed: 44 additions & 14 deletions

File tree

‎apps/docs/openapi-v2-files-audit.json‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,6 +4116,10 @@
41164116
"format": "date-time",
41174117
"examples": ["2026-01-16T09:00:00Z"]
41184118
},
4119+
"revision": {
4120+
"description": "Opaque token for the content this write produced. Send it back as `expectedRevision` on the next write. Absent for a file with no recorded content version.",
4121+
"type": "string"
4122+
},
41194123
"diagnostics": {
41204124
"description": "For a dashboard file, the YAML parse errors in the written content. Empty when it parses; the file is saved either way. Absent for other file types.",
41214125
"type": "array",
@@ -4139,7 +4143,7 @@
41394143
],
41404144
"additionalProperties": false,
41414145
"title": "Created file",
4142-
"description": "A newly created workspace file, with any dashboard parse errors."
4146+
"description": "A newly created workspace file, with the revision it produced and any dashboard parse errors."
41434147
},
41444148
"V2CreatedFileResponse": {
41454149
"type": "object",
@@ -4152,7 +4156,7 @@
41524156
"required": ["data"],
41534157
"additionalProperties": false,
41544158
"title": "Created file response",
4155-
"description": "A newly created workspace file, with any dashboard parse errors.",
4159+
"description": "A newly created workspace file, with the revision it produced and any dashboard parse errors.",
41564160
"examples": [
41574161
{
41584162
"data": {

‎apps/sim/app/api/table/[tableId]/analytics/route.test.ts‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { authMockFns } from '@sim/testing/mocks/auth.mock'
22
import { rateLimiterMock, rateLimiterMockFns } from '@sim/testing/mocks/rate-limiter.mock'
3-
import { NextRequest } from 'next/server'
3+
import { createMockRequest } from '@sim/testing/mocks/request.mock'
44
import { beforeEach, describe, expect, it, vi } from 'vitest'
55
import { POST } from '@/app/api/table/[tableId]/analytics/route'
66

@@ -24,11 +24,7 @@ const body = {
2424
},
2525
}
2626
const request = (value: unknown) =>
27-
new NextRequest('http://localhost/api/table/tbl_test/analytics', {
28-
method: 'POST',
29-
headers: { 'content-type': 'application/json' },
30-
body: JSON.stringify(value),
31-
})
27+
createMockRequest({ method: 'POST', url: '/api/table/tbl_test/analytics', body: value })
3228
beforeEach(() => {
3329
mocks.session.mockResolvedValue({ user: { id: 'viewer' }, session: { id: 'session' } })
3430
mocks.limit.mockResolvedValue(null)

‎apps/sim/app/api/v2/files/route.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { defineV2JsonRoute, v2ApiKeyAuth, v2RateLimits } from '@/lib/api/server/
99
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'
1010
import { v2FileErrorPolicies } from '@/lib/workspace-files/api'
1111
import { createWorkspaceFile } from '@/lib/workspace-files/application/create-workspace-file'
12+
import { workspaceFileRevisionField } from '@/lib/workspace-files/application/file-revision'
1213
import { queryWorkspaceFilePage } from '@/lib/workspace-files/application/list-workspace-files'
1314
import { fileOperations } from '@/lib/workspace-files/application/operations'
1415
import { MAX_WORKSPACE_FILE_INLINE_BODY_BYTES } from '@/lib/workspace-files/orchestration'
@@ -94,6 +95,10 @@ export const POST = defineV2JsonRoute({
9495
}),
9596
useCase: createWorkspaceFile,
9697
present: async ({ file, diagnostics }) => ({
97-
data: { ...(await toV2File(file)), ...(diagnostics ? { diagnostics } : {}) },
98+
data: {
99+
...(await toV2File(file)),
100+
...workspaceFileRevisionField(file),
101+
...(diagnostics ? { diagnostics } : {}),
102+
},
98103
}),
99104
})

‎apps/sim/lib/api/contracts/v2/files.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -767,11 +767,12 @@ const fileDiagnosticsSchema = z
767767
)
768768

769769
export const v2CreatedFileSchema = v2FileSchema
770-
.extend({ diagnostics: fileDiagnosticsSchema })
770+
.extend({ revision: writtenFileRevisionSchema, diagnostics: fileDiagnosticsSchema })
771771
.meta({
772772
id: 'V2CreatedFile',
773773
title: 'Created file',
774-
description: 'A newly created workspace file, with any dashboard parse errors.',
774+
description:
775+
'A newly created workspace file, with the revision it produced and any dashboard parse errors.',
775776
})
776777

777778
export const v2CreateFileContract = defineRouteContract({

‎apps/sim/lib/api/contracts/v2/openapi/files-audit.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ const declaredRoutes = [
210210
v2CreateFileContract.response.schema,
211211
'V2CreatedFileResponse',
212212
'Created file response',
213-
'A newly created workspace file, with any dashboard parse errors.',
213+
'A newly created workspace file, with the revision it produced and any dashboard parse errors.',
214214
[{ data: FILE_EXAMPLE }]
215215
),
216216
}

‎apps/sim/lib/dashboards/diagnostics.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ export function dashboardDiagnostics(
1212
): string[] | undefined {
1313
if (contentType !== DASHBOARD_CONTENT_TYPE) return undefined
1414
const { error } = parseDashboardSpec(content.toString())
15-
return error ? [error] : []
15+
return error ? error.split('\n') : []
1616
}

‎apps/sim/lib/dashboards/spec.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ describe('UTC ranges', () => {
111111
)
112112
})
113113

114+
describe('panel query modes', () => {
115+
it('lets a panel switch between detail columns and aggregates inherited from the dashboard', () => {
116+
const detailDefault = parseDashboardSpec(
117+
'title: T\nsource: {tableId: tbl_1, columns: [status]}\nblocks:\n - stat: Total\n source: {aggregate: {n: {op: count}}}\n'
118+
)
119+
expect(detailDefault.error).toBeUndefined()
120+
const aggregateDefault = parseDashboardSpec(
121+
'title: T\nsource: {tableId: tbl_1, groupBy: [status], aggregate: {n: {op: count}}}\nblocks:\n - table: Rows\n source: {columns: [status]}\n'
122+
)
123+
expect(aggregateDefault.error).toBeUndefined()
124+
})
125+
})
126+
114127
describe('dashboard parse errors', () => {
115128
it('names the block path, the allowed block kinds and any unknown key', () => {
116129
expect(parseDashboardSpec('title: Probe\nblocks:\n - bogus: 1\n').error).toBe(

‎apps/sim/lib/dashboards/spec.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getErrorMessage } from '@sim/utils/errors'
2+
import { omit } from '@sim/utils/object'
23
import { JSON_SCHEMA, load } from 'js-yaml'
34
import { z } from 'zod'
45
import { parseChartSpec } from '@/lib/charts/spec'
@@ -88,11 +89,20 @@ const dashboardSchema: z.ZodType<DashboardSpec> = z
8889
})
8990
.strict()
9091

92+
/**
93+
* A panel's source shallowly overrides the dashboard's. Choosing a query mode (`aggregate` or
94+
* `columns`) drops the other mode's inherited fields, so shared defaults serve both panel kinds.
95+
*/
9196
export function resolveDashboardSource(
9297
defaults: DashboardSource | undefined,
9398
source: DashboardSource | undefined
9499
): ResolvedDashboardSource {
95-
const merged = { ...defaults, ...source }
100+
const inherited = source?.columns
101+
? omit(defaults ?? {}, ['aggregate', 'groupBy', 'bucket'])
102+
: source?.aggregate
103+
? omit(defaults ?? {}, ['columns'])
104+
: defaults
105+
const merged = { ...inherited, ...source }
96106
if (!merged.tableId)
97107
throw new Error('A data panel requires source.tableId, on the dashboard or on the panel')
98108
return { ...merged, tableId: merged.tableId }

‎packages/sim-cli/src/generated/v2-api.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,7 @@ type CreateFileResponseRef0 = {
17021702
uploadedAt: string
17031703
updatedAt: string
17041704
deletedAt: string | null
1705+
revision?: string
17051706
diagnostics?: Array<string>
17061707
}
17071708

0 commit comments

Comments
 (0)