{"record":{"id":"d57edeab8089ead6","repo":"grafana/grafana","slug":"cursorsync-is-not-a-valid-value","errorCode":null,"errorMessage":"CursorSync is not a valid value","messagePattern":"CursorSync is not a valid value","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts","lineNumber":874,"sourceCode":"  if (!('annotations' in dash) || !Array.isArray(dash.annotations)) {\n    throw new Error('Annotations is not an array');\n  }\n  if (!('layout' in dash) || typeof dash.layout !== 'object' || dash.layout === null) {\n    throw new Error('Layout is not an object or is null');\n  }\n\n  // Optional properties - only validate if present\n  if ('description' in dash && dash.description !== undefined && typeof dash.description !== 'string') {\n    throw new Error('Description is not a string');\n  }\n  if ('cursorSync' in dash && dash.cursorSync !== undefined) {\n    const validCursorSyncValues = ((): string[] => {\n      const typeValues: DashboardCursorSync[] = ['Off', 'Crosshair', 'Tooltip'];\n      return typeValues;\n    })();\n\n    if (typeof dash.cursorSync !== 'string' || !validCursorSyncValues.includes(dash.cursorSync)) {\n      throw new Error('CursorSync is not a valid value');\n    }\n  }\n  if ('liveNow' in dash && dash.liveNow !== undefined && typeof dash.liveNow !== 'boolean') {\n    throw new Error('LiveNow is not a boolean');\n  }\n  if ('preload' in dash && dash.preload !== undefined && typeof dash.preload !== 'boolean') {\n    throw new Error('Preload is not a boolean');\n  }\n  if ('editable' in dash && dash.editable !== undefined && typeof dash.editable !== 'boolean') {\n    throw new Error('Editable is not a boolean');\n  }\n  if ('links' in dash && dash.links !== undefined && !Array.isArray(dash.links)) {\n    throw new Error('Links is not an array');\n  }\n  if ('tags' in dash && dash.tags !== undefined && !Array.isArray(dash.tags)) {\n    throw new Error('Tags is not an array');\n  }\n  if ('id' in dash && dash.id !== undefined && typeof dash.id !== 'number') {","sourceCodeStart":856,"sourceCodeEnd":892,"githubUrl":"https://github.com/grafana/grafana/blob/ae3104e3690e28e72c8f0c97efc12647a70f75c3/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts#L856-L892","documentation":"Thrown by validateDashboardSchemaV2 (transformSceneToSaveModelSchemaV2.ts:874) when dash.cursorSync is present but not one of the three DashboardCursorSync enum literals 'Off', 'Crosshair', 'Tooltip'. The guard exists because the V2 save model (CUE schema in kinds/) constrains cursorSync to that union; an out-of-enum value would fail server-side CUE validation on save. transformSceneToSaveModelSchemaV2 wraps this throw and rethrows as 'Error transforming dashboard to schema v2: ...', so the save aborts.","triggerScenarios":"Calling transformSceneToSaveModelSchemaV2(scene) when the scene's CursorSync behavior state.sync flows through transformCursorSynctoEnum and yields a value outside the enum (e.g. undefined leaked through, or a numeric sync mode); OR calling validateDashboardSchemaV2(dash) directly on hand-built/parsed JSON where dash.cursorSync is a string like 'crosshair' (lowercase) or 'None'.","commonSituations":"Importing a V2 dashboard JSON whose cursorSync used different casing or an older enum label; external tools/provisioning systems that emit cursorSync as a free-form string; a refactor of CursorSync sync modes that transformCursorSynctoEnum did not cover.","solutions":["Log dash.cursorSync at the call site to see the actual offending value.","Map the source value through transformCursorSynctoEnum (or the DashboardCursorSync type) so only 'Off'|'Crosshair'|'Tooltip' are emitted; coerce unknown values to the default ('Off').","If consuming external JSON, normalize cursorSync with a lookup table before passing the object to the validator.","Type the producer field as DashboardCursorSync so TypeScript rejects invalid literals at compile time."],"exampleFix":"// before\nconst dash = { ...spec, cursorSync: source.sync }; // source.sync could be undefined/'Crosshair '\n// after\nimport type { DashboardCursorSync } from '@grafana/schema';\nconst CURSOR_SYNC: DashboardCursorSync[] = ['Off', 'Crosshair', 'Tooltip'];\nconst dash = {\n  ...spec,\n  cursorSync: CURSOR_SYNC.includes(source.sync) ? source.sync : 'Off',\n};","handlingStrategy":"type-guard","validationCode":"const CURSOR_SYNC = ['Off', 'Crosshair', 'Tooltip'] as const;\nconst v = (dash as any)?.cursorSync;\nif (v != null && !(CURSOR_SYNC as readonly string[]).includes(v)) {\n  throw new Error(`cursorSync '${v}' is invalid`);\n}","typeGuard":"import type { DashboardCursorSync } from '@grafana/schema';\nconst VALUES = ['Off', 'Crosshair', 'Tooltip'] as readonly DashboardCursorSync[];\nfunction isValidCursorSync(v: unknown): v is DashboardCursorSync {\n  return typeof v === 'string' && (VALUES as readonly string[]).includes(v);\n}","tryCatchPattern":"try {\n  validateDashboardSchemaV2(dash);\n} catch (e) {\n  if (String(e).includes('CursorSync')) { dash.cursorSync = 'Off'; /* retry with default */ }\n  else throw e;\n}","preventionTips":["Type cursorSync as DashboardCursorSync at every producer so invalid literals are compile errors.","Route all cursor-sync values through transformCursorSynctoEnum before building the spec.","Normalize imported JSON: lowercase/trim, then map to the enum or default to 'Off'."],"tags":["validation","dashboard-schema-v2","enums","serialization"],"backgroundTag":null,"analyzedSha":"ae3104e3690e28e72c8f0c97efc12647a70f75c3","analyzedAt":"2026-08-12T12:48:38.752Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}