schema: Use generated dashboard model in frontend (#55769)

Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
Co-authored-by: polinaboneva <polina.boneva@grafana.com>
pull/60594/head
sam boyer 3 years ago committed by GitHub
parent a553040441
commit f86abf096d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      .betterer.results
  2. 40
      devenv/dev-dashboards/alerting/testdata_alerts.json
  3. 89
      kinds/structured/dashboard/dashboard_kind.cue
  4. 3
      packages/grafana-data/src/types/templateVars.ts
  5. 32
      packages/grafana-schema/src/index.gen.ts
  6. 149
      packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts
  7. 47
      packages/grafana-schema/src/veneer/dashboard.types.ts
  8. 18
      pkg/codegen/jenny_tsveneerindex.go
  9. 147
      pkg/kinds/dashboard/dashboard_types_gen.go
  10. 7
      public/app/features/alerting/TestRuleResult.test.tsx
  11. 5
      public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx
  12. 10
      public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx
  13. 5
      public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx
  14. 4
      public/app/features/dashboard/components/DashNav/DashNav.test.tsx
  15. 71
      public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx
  16. 14
      public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx
  17. 4
      public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx
  18. 4
      public/app/features/dashboard/components/DashboardSettings/DashboardSettings.test.tsx
  19. 7
      public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx
  20. 5
      public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx
  21. 8
      public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx
  22. 5
      public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx
  23. 12
      public/app/features/dashboard/components/PanelEditor/PanelEditorTableView.test.tsx
  24. 17
      public/app/features/dashboard/components/PanelEditor/state/actions.test.ts
  25. 3
      public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.test.tsx
  26. 1
      public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx
  27. 5
      public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx
  28. 11
      public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx
  29. 14
      public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx
  30. 8
      public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx
  31. 5
      public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts
  32. 7
      public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.test.tsx
  33. 78
      public/app/features/dashboard/state/DashboardMigrator.test.ts
  34. 4
      public/app/features/dashboard/state/DashboardModel.refresh.test.ts
  35. 148
      public/app/features/dashboard/state/DashboardModel.test.ts
  36. 15
      public/app/features/dashboard/state/DashboardModel.ts
  37. 68
      public/app/features/dashboard/state/__fixtures__/dashboardFixtures.ts
  38. 6
      public/app/features/dashboard/state/reducers.test.ts
  39. 8
      public/app/features/dashboard/state/reducers.ts
  40. 9
      public/app/features/dashboard/utils/getPanelMenu.test.ts
  41. 20
      public/app/features/dashboard/utils/panelMerge.test.ts
  42. 2
      public/app/features/explore/AddToDashboard/addToDashboard.test.ts
  43. 5
      public/app/features/explore/AddToDashboard/index.test.tsx
  44. 2
      public/app/features/library-panels/state/api.ts
  45. 4
      public/app/features/query/state/PanelQueryRunner.test.ts
  46. 4
      public/app/features/query/state/queryAnalytics.test.ts
  47. 4
      public/app/features/query/state/runRequest.test.ts
  48. 2
      public/app/features/scenes/variables/interpolation/sceneInterpolator.ts
  49. 3
      public/app/features/variables/state/onTimeRangeUpdated.test.ts
  50. 5
      public/app/features/variables/state/templateVarsChangedInUrl.test.ts
  51. 19
      public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx
  52. 4
      public/app/plugins/panel/graph/specs/graph.test.ts
  53. 8
      public/app/types/dashboard.ts

@ -991,6 +991,11 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"]
],
"packages/grafana-schema/src/veneer/dashboard.types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"]
],
"packages/grafana-toolkit/src/cli/tasks/component.create.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
@ -3110,17 +3115,18 @@ exports[`better eslint`] = {
],
"public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
[0, 0, 0, "Do not use any type assertions.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
],
"public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@ -3469,15 +3475,14 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "26"],
[0, 0, 0, "Unexpected any. Specify a different type.", "27"],
[0, 0, 0, "Unexpected any. Specify a different type.", "28"],
[0, 0, 0, "Unexpected any. Specify a different type.", "29"],
[0, 0, 0, "Do not use any type assertions.", "30"],
[0, 0, 0, "Do not use any type assertions.", "29"],
[0, 0, 0, "Unexpected any. Specify a different type.", "30"],
[0, 0, 0, "Unexpected any. Specify a different type.", "31"],
[0, 0, 0, "Unexpected any. Specify a different type.", "32"],
[0, 0, 0, "Unexpected any. Specify a different type.", "33"],
[0, 0, 0, "Unexpected any. Specify a different type.", "34"],
[0, 0, 0, "Unexpected any. Specify a different type.", "35"],
[0, 0, 0, "Unexpected any. Specify a different type.", "36"],
[0, 0, 0, "Unexpected any. Specify a different type.", "37"]
[0, 0, 0, "Unexpected any. Specify a different type.", "36"]
],
"public/app/features/dashboard/state/PanelModel.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@ -3560,7 +3565,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard/state/reducers.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/dashboard/utils/getPanelMenu.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],

@ -90,13 +90,7 @@
"id": 4,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
"show": true
},
"lines": true,
"linewidth": 2,
@ -233,13 +227,7 @@
"id": 7,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
"show": true
},
"lines": true,
"linewidth": 2,
@ -399,13 +387,7 @@
"id": 6,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
"show": true
},
"lines": true,
"linewidth": 2,
@ -537,13 +519,7 @@
"id": 3,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
"show": true
},
"lines": true,
"linewidth": 2,
@ -679,13 +655,7 @@
"id": 5,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
"show": true
},
"lines": true,
"linewidth": 2,

@ -21,6 +21,9 @@ lineage: seqs: [
// Description of dashboard.
description?: string
// Version of the current dashboard data
revision: int64 | *-1 @grafanamaturity(NeedsExpertReview)
gnetId?: string @grafanamaturity(NeedsExpertReview)
// Tags associated with dashboard.
tags?: [...string] @grafanamaturity(NeedsExpertReview)
@ -69,15 +72,17 @@ lineage: seqs: [
panels?: [...(#Panel | #RowPanel | #GraphPanel | #HeatmapPanel)] @grafanamaturity(NeedsExpertReview)
// TODO docs
templating?: {
list: [...#VariableModel] @grafanamaturity(NeedsExpertReview)
list?: [...#VariableModel] @grafanamaturity(NeedsExpertReview)
}
// TODO docs
annotations?: {
list: [...#AnnotationQuery] @grafanamaturity(NeedsExpertReview)
list?: [...#AnnotationQuery] @grafanamaturity(NeedsExpertReview)
}
// TODO docs
links?: [...#DashboardLink] @grafanamaturity(NeedsExpertReview)
snapshot?: #Snapshot @grafanamaturity(NeedsExpertReview)
///////////////////////////////////////
// Definitions (referenced above) are declared below
@ -119,20 +124,42 @@ lineage: seqs: [
// TODO what about what's in public/app/features/types.ts?
// TODO there appear to be a lot of different kinds of [template] vars here? if so need a disjunction
#VariableModel: {
id: string | *"00000000-0000-0000-0000-000000000000"
type: #VariableType
name: string
label?: string
rootStateKey?: string
global: bool | *false
hide: #VariableHide
skipUrlSync: bool | *false
index: int32 | *-1
state: #LoadingState
error?: {...}
description?: string
// TODO: Move this into a separated QueryVariableModel type
query?: string | {...}
datasource?: #DataSourceRef
...
} @cuetsy(kind="interface") @grafanamaturity(NeedsExpertReview)
} @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview)
// TODO: There is a bug generating the names, they are always title case
#VariableHide: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview)
#LoadingState: "NotStarted" | "Loading" | "Streaming" | "Done" | "Error" @cuetsy(kind="enum") @grafanamaturity(NeedsExpertReview)
// Ref to a DataSource instance
#DataSourceRef: {
// The plugin type-id
type?: string @grafanamaturity(NeedsExpertReview)
// Specific datasource instance
uid?: string @grafanamaturity(NeedsExpertReview)
} @cuetsy(kind="interface") @grafanamaturity(NeedsExpertReview)
// FROM public/app/features/dashboard/state/DashboardModels.ts - ish
// TODO docs
#DashboardLink: {
title: string @grafanamaturity(NeedsExpertReview)
type: #DashboardLinkType @grafanamaturity(NeedsExpertReview)
icon?: string @grafanamaturity(NeedsExpertReview)
tooltip?: string @grafanamaturity(NeedsExpertReview)
url?: string @grafanamaturity(NeedsExpertReview)
icon: string @grafanamaturity(NeedsExpertReview)
tooltip: string @grafanamaturity(NeedsExpertReview)
url: string @grafanamaturity(NeedsExpertReview)
tags: [...string] @grafanamaturity(NeedsExpertReview)
asDropdown: bool | *false @grafanamaturity(NeedsExpertReview)
targetBlank: bool | *false @grafanamaturity(NeedsExpertReview)
@ -273,6 +300,43 @@ lineage: seqs: [
// type directly to achieve the same effect.
#Target: {...} @grafanamaturity(NeedsExpertReview)
// TODO docs
#Snapshot: {
// TODO docs
created: string @grafanamaturity(NeedsExpertReview)
// TODO docs
expires: string @grafanamaturity(NeedsExpertReview)
// TODO docs
external: bool @grafanamaturity(NeedsExpertReview)
// TODO docs
externalUrl: string @grafanamaturity(NeedsExpertReview)
// TODO docs
id: uint32 @grafanamaturity(NeedsExpertReview)
// TODO docs
key: string @grafanamaturity(NeedsExpertReview)
// TODO docs
name: string @grafanamaturity(NeedsExpertReview)
// TODO docs
orgId: uint32 @grafanamaturity(NeedsExpertReview)
// TODO docs
updated: string @grafanamaturity(NeedsExpertReview)
// TODO docs
url?: string @grafanamaturity(NeedsExpertReview)
// TODO docs
userId: uint32 @grafanamaturity(NeedsExpertReview)
} @grafanamaturity(NeedsExpertReview)
// Dashboard panels. Panels are canonically defined inline
// because they share a version timeline with the dashboard
// schema; they do not evolve independently.
@ -313,7 +377,10 @@ lineage: seqs: [
repeat?: string @grafanamaturity(NeedsExpertReview)
// Direction to repeat in if 'repeat' is set.
// "h" for horizontal, "v" for vertical.
// TODO this is probably optional
repeatDirection: *"h" | "v" @grafanamaturity(NeedsExpertReview)
// Id of the repeating panel.
repeatPanelId?: int64 @grafanamaturity(NeedsExpertReview)
// TODO docs
maxDataPoints?: number @grafanamaturity(NeedsExpertReview)
@ -441,12 +508,18 @@ lineage: seqs: [
// Support for legacy graph and heatmap panels.
#GraphPanel: {
type: "graph" @grafanamaturity(NeedsExpertReview)
// @deprecated this is part of deprecated graph panel
legend?: {
show: bool | *true
sort?: string
sortDesc?: bool
}
...
} @grafanamaturity(NeedsExpertReview)
} @cuetsy(kind="interface") @grafanamaturity(NeedsExpertReview)
#HeatmapPanel: {
type: "heatmap" @grafanamaturity(NeedsExpertReview)
...
} @grafanamaturity(NeedsExpertReview)
} @cuetsy(kind="interface") @grafanamaturity(NeedsExpertReview)
},
]
},

@ -144,10 +144,11 @@ export interface SystemVariable<TProps extends { toString: () => string }> exten
current: { value: TProps };
}
export interface BaseVariableModel extends VariableModel {
export interface BaseVariableModel {
name: string;
label?: string;
id: string;
type: VariableType;
rootStateKey: string | null;
global: boolean;
hide: VariableHide;

@ -11,38 +11,41 @@
export type {
AnnotationTarget,
AnnotationQuery,
VariableModel,
DataSourceRef,
DashboardLink,
DashboardLinkType,
VariableType,
FieldColorModeId,
FieldColorSeriesByMode,
FieldColor,
GridPos,
Threshold,
ThresholdsMode,
ThresholdsConfig,
ValueMapping,
MappingType,
ValueMap,
RangeMap,
RegexMap,
SpecialValueMap,
SpecialValueMatch,
ValueMappingResult,
Transformation,
DashboardCursorSync,
MatcherConfig,
RowPanel
RowPanel,
GraphPanel,
HeatmapPanel
} from './raw/dashboard/x/dashboard_types.gen';
// Raw generated default consts from dashboard kind.
// Raw generated enums and default consts from dashboard kind.
export {
defaultAnnotationTarget,
defaultAnnotationQuery,
LoadingState,
defaultDashboardLink,
FieldColorModeId,
defaultGridPos,
ThresholdsMode,
defaultThresholdsConfig,
MappingType,
SpecialValueMatch,
DashboardCursorSync,
defaultDashboardCursorSync,
defaultMatcherConfig,
defaultRowPanel
@ -59,6 +62,7 @@ export {
// TODO generate code such that tsc enforces type compatibility between raw and veneer decls
export type {
Dashboard,
VariableModel,
Panel,
FieldConfigSource,
FieldConfig
@ -75,6 +79,8 @@ export type {
// TODO generate code such that tsc enforces type compatibility between raw and veneer decls
export {
defaultDashboard,
defaultVariableModel,
VariableHide,
defaultPanel,
defaultFieldConfigSource,
defaultFieldConfig
@ -86,11 +92,11 @@ export type {
PlaylistItem
} from './raw/playlist/x/playlist_types.gen';
// Raw generated default consts from playlist kind.
// Raw generated enums and default consts from playlist kind.
export { defaultPlaylist } from './raw/playlist/x/playlist_types.gen';
// Raw generated types from Team kind.
export type {
Team,
Permission
} from './raw/team/x/team_types.gen';
export type { Team } from './raw/team/x/team_types.gen';
// Raw generated enums and default consts from team kind.
export { Permission } from './raw/team/x/team_types.gen';

@ -75,26 +75,78 @@ export const defaultAnnotationQuery: Partial<AnnotationQuery> = {
* TODO there appear to be a lot of different kinds of [template] vars here? if so need a disjunction
*/
export interface VariableModel {
datasource?: DataSourceRef;
description?: string;
error?: Record<string, unknown>;
global: boolean;
hide: VariableHide;
id: string;
index: number;
label?: string;
name: string;
/**
* TODO: Move this into a separated QueryVariableModel type
*/
query?: (string | Record<string, unknown>);
rootStateKey?: string;
skipUrlSync: boolean;
state: LoadingState;
type: VariableType;
}
export const defaultVariableModel: Partial<VariableModel> = {
global: false,
id: '00000000-0000-0000-0000-000000000000',
index: -1,
skipUrlSync: false,
};
/**
* TODO: There is a bug generating the names, they are always title case
*/
export enum VariableHide {
DontHide = 0,
HideLabel = 1,
HideVariable = 2,
}
export enum LoadingState {
Done = 'Done',
Error = 'Error',
Loading = 'Loading',
NotStarted = 'NotStarted',
Streaming = 'Streaming',
}
/**
* Ref to a DataSource instance
*/
export interface DataSourceRef {
/**
* The plugin type-id
*/
type?: string;
/**
* Specific datasource instance
*/
uid?: string;
}
/**
* FROM public/app/features/dashboard/state/DashboardModels.ts - ish
* TODO docs
*/
export interface DashboardLink {
asDropdown: boolean;
icon?: string;
icon: string;
includeVars: boolean;
keepTime: boolean;
tags: Array<string>;
targetBlank: boolean;
title: string;
tooltip?: string;
tooltip: string;
type: DashboardLinkType;
url?: string;
url: string;
}
export const defaultDashboardLink: Partial<DashboardLink> = {
@ -380,8 +432,13 @@ export interface Panel {
/**
* Direction to repeat in if 'repeat' is set.
* "h" for horizontal, "v" for vertical.
* TODO this is probably optional
*/
repeatDirection: ('h' | 'v');
/**
* Id of the repeating panel.
*/
repeatPanelId?: number;
/**
* TODO docs
*/
@ -544,11 +601,7 @@ export interface RowPanel {
};
gridPos?: GridPos;
id: number;
panels: Array<(Panel | {
type: 'graph';
} | {
type: 'heatmap';
})>;
panels: Array<(Panel | GraphPanel | HeatmapPanel)>;
/**
* Name of template variable to repeat for.
*/
@ -562,12 +615,31 @@ export const defaultRowPanel: Partial<RowPanel> = {
panels: [],
};
/**
* Support for legacy graph and heatmap panels.
*/
export interface GraphPanel {
/**
* @deprecated this is part of deprecated graph panel
*/
legend?: {
show: boolean;
sort?: string;
sortDesc?: boolean;
};
type: 'graph';
}
export interface HeatmapPanel {
type: 'heatmap';
}
export interface Dashboard {
/**
* TODO docs
*/
annotations?: {
list: Array<AnnotationQuery>;
list?: Array<AnnotationQuery>;
};
/**
* Description of dashboard.
@ -596,21 +668,67 @@ export interface Dashboard {
* TODO docs
*/
liveNow?: boolean;
panels?: Array<(Panel | RowPanel | {
type: 'graph';
} | {
type: 'heatmap';
})>;
panels?: Array<(Panel | RowPanel | GraphPanel | HeatmapPanel)>;
/**
* TODO docs
*/
refresh?: (string | false);
/**
* Version of the current dashboard data
*/
revision: number;
/**
* Version of the JSON schema, incremented each time a Grafana update brings
* changes to said schema.
* TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion
*/
schemaVersion: number;
snapshot?: {
/**
* TODO docs
*/
created: string;
/**
* TODO docs
*/
expires: string;
/**
* TODO docs
*/
external: boolean;
/**
* TODO docs
*/
externalUrl: string;
/**
* TODO docs
*/
id: number;
/**
* TODO docs
*/
key: string;
/**
* TODO docs
*/
name: string;
/**
* TODO docs
*/
orgId: number;
/**
* TODO docs
*/
updated: string;
/**
* TODO docs
*/
url?: string;
/**
* TODO docs
*/
userId: number;
};
/**
* Theme of dashboard.
*/
@ -623,7 +741,7 @@ export interface Dashboard {
* TODO docs
*/
templating?: {
list: Array<VariableModel>;
list?: Array<VariableModel>;
};
/**
* Time range for dashboard, e.g. last 6 hours, last 7 days, etc
@ -685,6 +803,7 @@ export const defaultDashboard: Partial<Dashboard> = {
graphTooltip: DashboardCursorSync.Off,
links: [],
panels: [],
revision: -1,
schemaVersion: 36,
style: 'dark',
tags: [],

@ -1,23 +1,33 @@
import * as raw from '../raw/dashboard/x/dashboard_types.gen';
export interface Dashboard extends raw.Dashboard {
panels?: Array<
| Panel
| raw.RowPanel
| {
type: 'graph';
}
| {
type: 'heatmap';
}
>;
}
export interface Panel<TOptions = Record<string, unknown>, TCustomFieldConfig = Record<string, unknown>>
extends raw.Panel {
fieldConfig: FieldConfigSource<TCustomFieldConfig>;
}
export enum VariableHide {
dontHide,
hideLabel,
hideVariable,
}
export interface VariableModel
extends Omit<raw.VariableModel, 'rootStateKey' | 'error' | 'description' | 'hide' | 'datasource'> {
// Overrides nullable properties because CUE doesn't support null values
rootStateKey: string | null;
error: any | null;
description: string | null;
hide: VariableHide;
datasource: raw.DataSourceRef | null;
}
export interface Dashboard extends Omit<raw.Dashboard, 'templating'> {
panels?: Array<Panel | raw.RowPanel | raw.GraphPanel | raw.HeatmapPanel>;
templating?: {
list?: VariableModel[];
};
}
export interface FieldConfig<TOptions = Record<string, unknown>> extends raw.FieldConfig {
custom?: TOptions & Record<string, unknown>;
}
@ -26,7 +36,16 @@ export interface FieldConfigSource<TOptions = Record<string, unknown>> extends r
defaults: FieldConfig<TOptions>;
}
export const defaultDashboard: Partial<Dashboard> = raw.defaultDashboard;
export const defaultDashboard = raw.defaultDashboard as Dashboard;
export const defaultVariableModel = {
...raw.defaultVariableModel,
rootStateKey: null,
error: null,
description: null,
hide: VariableHide.dontHide,
state: raw.LoadingState.NotStarted,
datasource: null,
} as VariableModel;
export const defaultPanel: Partial<Panel> = raw.defaultPanel;
export const defaultFieldConfig: Partial<FieldConfig> = raw.defaultFieldConfig;
export const defaultFieldConfigSource: Partial<FieldConfigSource> = raw.defaultFieldConfigSource;

@ -79,7 +79,7 @@ func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(decl *DeclForGen, tf *
sels := p.Selectors()
switch len(sels) {
case 0:
name = strings.Title(lin.Name())
name = comm.Name
fallthrough
case 1:
// Only deal with subpaths that are definitions, for now
@ -110,12 +110,24 @@ func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(decl *DeclForGen, tf *
has = has || tgt.target == "type"
}
if has {
// enums can't use 'export type'
if pair.isEnum {
customD = append(customD, *pair.T)
} else {
custom = append(custom, *pair.T)
}
if pair.D != nil {
customD = append(customD, *pair.D)
}
} else {
// enums can't use 'export type'
if pair.isEnum {
rawD = append(rawD, *pair.T)
} else {
raw = append(raw, *pair.T)
}
if pair.D != nil {
rawD = append(rawD, *pair.D)
}
@ -146,7 +158,7 @@ func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(decl *DeclForGen, tf *
}
if len(rawD) > 0 {
ret = append(ret, ast.ExportSet{
CommentList: []ast.Comment{ts.CommentFromString(fmt.Sprintf("Raw generated default consts from %s kind.", lin.Name()), 80, false)},
CommentList: []ast.Comment{ts.CommentFromString(fmt.Sprintf("Raw generated enums and default consts from %s kind.", lin.Name()), 80, false)},
TypeOnly: false,
Exports: rawD,
From: ast.Str{Value: fmt.Sprintf("./raw/%s/%s/%s_types.gen", comm.MachineName, vpath, comm.MachineName)},
@ -188,6 +200,7 @@ func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(decl *DeclForGen, tf *
type declPair struct {
T, D *ast.Ident
isEnum bool
}
type tsVeneerAttr struct {
@ -206,6 +219,7 @@ func findDeclNode(name string, tf *ast.File) declPair {
case ast.TypeDecl:
if x.Name.Name == name {
p.T = &x.Name
_, p.isEnum = x.Type.(ast.EnumType)
}
case ast.VarDecl:
if x.Names.Idents[0].Name == "default"+name {

@ -83,6 +83,19 @@ const (
HeatmapPanelTypeHeatmap HeatmapPanelType = "heatmap"
)
// Defines values for LoadingState.
const (
LoadingStateDone LoadingState = "Done"
LoadingStateError LoadingState = "Error"
LoadingStateLoading LoadingState = "Loading"
LoadingStateNotStarted LoadingState = "NotStarted"
LoadingStateStreaming LoadingState = "Streaming"
)
// Defines values for MappingType.
const (
MappingTypeRange MappingType = "range"
@ -162,6 +175,37 @@ const (
ValueMapTypeValue ValueMapType = "value"
)
// Defines values for VariableHide.
const (
VariableHideN0 VariableHide = 0
VariableHideN1 VariableHide = 1
VariableHideN2 VariableHide = 2
)
// Defines values for VariableModelHide.
const (
VariableModelHideN0 VariableModelHide = 0
VariableModelHideN1 VariableModelHide = 1
VariableModelHideN2 VariableModelHide = 2
)
// Defines values for VariableModelState.
const (
VariableModelStateDone VariableModelState = "Done"
VariableModelStateError VariableModelState = "Error"
VariableModelStateLoading VariableModelState = "Loading"
VariableModelStateNotStarted VariableModelState = "NotStarted"
VariableModelStateStreaming VariableModelState = "Streaming"
)
// Defines values for VariableModelType.
const (
VariableModelTypeAdhoc VariableModelType = "adhoc"
@ -204,7 +248,7 @@ const (
type Dashboard struct {
Annotations *struct {
// TODO docs
List []AnnotationQuery `json:"list"`
List *[]AnnotationQuery `json:"list,omitempty"`
} `json:"annotations,omitempty"`
// Description of dashboard.
@ -232,11 +276,17 @@ type Dashboard struct {
// TODO docs
Refresh *interface{} `json:"refresh,omitempty"`
// Version of the current dashboard data
Revision int `json:"revision"`
// Version of the JSON schema, incremented each time a Grafana update brings
// changes to said schema.
// TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion
SchemaVersion int `json:"schemaVersion"`
// TODO docs
Snapshot *Snapshot `json:"snapshot,omitempty"`
// Theme of dashboard.
Style Style `json:"style"`
@ -244,7 +294,7 @@ type Dashboard struct {
Tags *[]string `json:"tags,omitempty"`
Templating *struct {
// TODO docs
List []VariableModel `json:"list"`
List *[]VariableModel `json:"list,omitempty"`
} `json:"templating,omitempty"`
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc
@ -346,20 +396,29 @@ type DashboardCursorSync int
// TODO docs
type DashboardLink struct {
AsDropdown bool `json:"asDropdown"`
Icon *string `json:"icon,omitempty"`
Icon string `json:"icon"`
IncludeVars bool `json:"includeVars"`
KeepTime bool `json:"keepTime"`
Tags []string `json:"tags"`
TargetBlank bool `json:"targetBlank"`
Title string `json:"title"`
Tooltip *string `json:"tooltip,omitempty"`
Tooltip string `json:"tooltip"`
Type DashboardLinkType `json:"type"`
Url *string `json:"url,omitempty"`
Url string `json:"url"`
}
// DashboardLinkType defines model for DashboardLink.Type.
type DashboardLinkType string
// Ref to a DataSource instance
type DataSourceRef struct {
// The plugin type-id
Type *string `json:"type,omitempty"`
// Specific datasource instance
Uid *string `json:"uid,omitempty"`
}
// DynamicConfigValue defines model for dashboard.DynamicConfigValue.
type DynamicConfigValue struct {
Id string `json:"id"`
@ -498,13 +557,18 @@ type FieldConfigSource struct {
} `json:"overrides"`
}
// GraphPanel defines model for dashboard.GraphPanel.
// Support for legacy graph and heatmap panels.
type GraphPanel struct {
// Support for legacy graph and heatmap panels.
// @deprecated this is part of deprecated graph panel
Legend *struct {
Show bool `json:"show"`
Sort *string `json:"sort,omitempty"`
SortDesc *bool `json:"sortDesc,omitempty"`
} `json:"legend,omitempty"`
Type GraphPanelType `json:"type"`
}
// Support for legacy graph and heatmap panels.
// GraphPanelType defines model for GraphPanel.Type.
type GraphPanelType string
// GridPos defines model for dashboard.GridPos.
@ -533,6 +597,9 @@ type HeatmapPanel struct {
// HeatmapPanelType defines model for HeatmapPanel.Type.
type HeatmapPanelType string
// LoadingState defines model for dashboard.LoadingState.
type LoadingState string
// TODO docs
type MappingType string
@ -643,8 +710,12 @@ type Panel struct {
// Direction to repeat in if 'repeat' is set.
// "h" for horizontal, "v" for vertical.
// TODO this is probably optional
RepeatDirection PanelRepeatDirection `json:"repeatDirection"`
// Id of the repeating panel.
RepeatPanelId *int64 `json:"repeatPanelId,omitempty"`
// TODO docs
Tags *[]string `json:"tags,omitempty"`
@ -681,6 +752,7 @@ type Panel struct {
// Direction to repeat in if 'repeat' is set.
// "h" for horizontal, "v" for vertical.
// TODO this is probably optional
type PanelRepeatDirection string
// TODO docs
@ -741,6 +813,42 @@ type RowPanel struct {
// RowPanelType defines model for RowPanel.Type.
type RowPanelType string
// TODO docs
type Snapshot struct {
// TODO docs
Created string `json:"created"`
// TODO docs
Expires string `json:"expires"`
// TODO docs
External bool `json:"external"`
// TODO docs
ExternalUrl string `json:"externalUrl"`
// TODO docs
Id int `json:"id"`
// TODO docs
Key string `json:"key"`
// TODO docs
Name string `json:"name"`
// TODO docs
OrgId int `json:"orgId"`
// TODO docs
Updated string `json:"updated"`
// TODO docs
Url *string `json:"url,omitempty"`
// TODO docs
UserId int `json:"userId"`
}
// TODO docs
type SpecialValueMap struct {
Options struct {
@ -842,16 +950,39 @@ type ValueMappingResult struct {
Text *string `json:"text,omitempty"`
}
// TODO: There is a bug generating the names, they are always title case
type VariableHide int
// FROM: packages/grafana-data/src/types/templateVars.ts
// TODO docs
// TODO what about what's in public/app/features/types.ts?
// TODO there appear to be a lot of different kinds of [template] vars here? if so need a disjunction
type VariableModel struct {
// Ref to a DataSource instance
Datasource *DataSourceRef `json:"datasource,omitempty"`
Description *string `json:"description,omitempty"`
Error *map[string]interface{} `json:"error,omitempty"`
Global bool `json:"global"`
Hide VariableModelHide `json:"hide"`
Id string `json:"id"`
Index int `json:"index"`
Label *string `json:"label,omitempty"`
Name string `json:"name"`
// TODO: Move this into a separated QueryVariableModel type
Query *interface{} `json:"query,omitempty"`
RootStateKey *string `json:"rootStateKey,omitempty"`
SkipUrlSync bool `json:"skipUrlSync"`
State VariableModelState `json:"state"`
Type VariableModelType `json:"type"`
}
// VariableModelHide defines model for VariableModel.Hide.
type VariableModelHide int
// VariableModelState defines model for VariableModel.State.
type VariableModelState string
// VariableModelType defines model for VariableModel.Type.
type VariableModelType string

@ -1,7 +1,8 @@
import { render } from '@testing-library/react';
import React from 'react';
import { DashboardModel, PanelModel } from '../dashboard/state';
import { PanelModel } from '../dashboard/state';
import { createDashboardModelFixture, createPanelJSONFixture } from '../dashboard/state/__fixtures__/dashboardFixtures';
import { TestRuleResult, Props } from './TestRuleResult';
@ -18,7 +19,9 @@ jest.mock('@grafana/runtime', () => {
const props: Props = {
panel: new PanelModel({ id: 1 }),
dashboard: new DashboardModel({ panels: [{ id: 1 }] }),
dashboard: createDashboardModelFixture({
panels: [createPanelJSONFixture({ id: 1 })],
}),
};
describe('TestRuleResult', () => {

@ -3,7 +3,8 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import { logInfo } from '@grafana/runtime';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { PanelModel } from 'app/features/dashboard/state';
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { LogMessages } from '../../Analytics';
@ -40,7 +41,7 @@ describe('Analytics', () => {
const panel = new PanelModel({
id: 123,
});
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
id: 1,
});
render(<NewRuleFromPanelButton panel={panel} dashboard={dashboard} />);

@ -8,6 +8,7 @@ import { Provider } from 'react-redux';
import { byRole, byTestId } from 'testing-library-selector';
import { setBackendSrv } from '@grafana/runtime';
import { defaultDashboard } from '@grafana/schema';
import { backendSrv } from 'app/core/services/backend_srv';
import { DashboardDTO } from '../../../../../types';
@ -259,15 +260,12 @@ function mockDashboardSearchItem(searchItem: Partial<DashboardSearchItem>) {
};
}
function mockDashboardDto(dashboard: Partial<DashboardDTO['dashboard']>) {
function mockDashboardDto(dashboard: Partial<DashboardDTO['dashboard']>): DashboardDTO {
return {
dashboard: {
title: '',
uid: '',
templating: { list: [] },
panels: [],
...defaultDashboard,
...dashboard,
},
} as DashboardDTO['dashboard'],
meta: {},
};
}

@ -1,13 +1,14 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { DashboardModel, PanelModel } from '../../state';
import { PanelModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { AddPanelWidgetUnconnected as AddPanelWidget, Props } from './AddPanelWidget';
const getTestContext = (propOverrides?: object) => {
const props: Props = {
dashboard: new DashboardModel({}),
dashboard: createDashboardModelFixture(),
panel: new PanelModel({}),
addPanel: jest.fn() as any,
};

@ -10,13 +10,13 @@ import { getGrafanaContextMock } from '../../../../../test/mocks/getGrafanaConte
import { setStarred } from '../../../../core/reducers/navBarTree';
import { configureStore } from '../../../../store/configureStore';
import { updateTimeZoneForSession } from '../../../profile/state/reducers';
import { DashboardModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { DashNav } from './DashNav';
describe('Public dashboard title tag', () => {
it('will be rendered when publicDashboardEnabled set to true in dashboard meta', async () => {
let dashboard = new DashboardModel({}, { publicDashboardEnabled: false });
let dashboard = createDashboardModelFixture({}, { publicDashboardEnabled: false });
const store = configureStore();
const context = getGrafanaContextMock();

@ -1,21 +1,22 @@
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
import { setContextSrv } from '../../../../core/services/context_srv';
import { DashboardModel } from '../../state/DashboardModel';
import { PanelModel } from '../../state/PanelModel';
import { createDashboardModelFixture, createPanelJSONFixture } from '../../state/__fixtures__/dashboardFixtures';
import { hasChanges, ignoreChanges } from './DashboardPrompt';
function getDefaultDashboardModel(): DashboardModel {
return new DashboardModel({
function getDefaultDashboardModel() {
return createDashboardModelFixture({
refresh: false,
panels: [
{
createPanelJSONFixture({
id: 1,
type: 'graph',
gridPos: { x: 0, y: 0, w: 24, h: 6 },
legend: { sortDesc: false },
},
legend: { show: true, sortDesc: false }, // TODO legend is marked as a non-persisted field
}),
{
id: 2,
type: 'row',
@ -26,7 +27,7 @@ function getDefaultDashboardModel(): DashboardModel {
{ id: 4, type: 'graph', gridPos: { x: 12, y: 6, w: 12, h: 2 } },
],
},
{ id: 5, type: 'row', gridPos: { x: 0, y: 6, w: 1, h: 1 } },
{ id: 5, type: 'row', gridPos: { x: 0, y: 6, w: 1, h: 1 }, collapsed: false, panels: [] },
],
});
}
@ -34,8 +35,8 @@ function getDefaultDashboardModel(): DashboardModel {
function getTestContext() {
const contextSrv: any = { isSignedIn: true, isEditor: true };
setContextSrv(contextSrv);
const dash: any = getDefaultDashboardModel();
const original: any = dash.getSaveModelClone();
const dash = getDefaultDashboardModel();
const original = dash.getSaveModelClone();
return { dash, original, contextSrv };
}
@ -68,8 +69,8 @@ describe('DashboardPrompt', () => {
it('Should ignore panel legend changes', () => {
const { original, dash } = getTestContext();
dash.panels[0].legend.sortDesc = true;
dash.panels[0].legend.sort = 'avg';
dash.panels[0]!.legend!.sortDesc = true;
dash.panels[0]!.legend!.sort = 'avg';
expect(hasChanges(dash, original)).toBe(false);
});
@ -90,47 +91,45 @@ describe('DashboardPrompt', () => {
describe('when called without current dashboard', () => {
it('then it should return true', () => {
const { original } = getTestContext();
expect(ignoreChanges(null as unknown as DashboardModel, original)).toBe(true);
});
});
describe('when called without meta in current dashboard', () => {
it('then it should return true', () => {
const { original, dash } = getTestContext();
expect(ignoreChanges({ ...dash, meta: undefined }, original)).toBe(true);
expect(ignoreChanges(null, original)).toBe(true);
});
});
describe('when called for a viewer without save permissions', () => {
it('then it should return true', () => {
const { original, dash, contextSrv } = getTestContext();
const { contextSrv } = getTestContext();
const dash = createDashboardModelFixture({}, { canSave: false });
const original = dash.getSaveModelClone();
contextSrv.isEditor = false;
expect(ignoreChanges({ ...dash, meta: { canSave: false } }, original)).toBe(true);
expect(ignoreChanges(dash, original)).toBe(true);
});
});
describe('when called for a viewer with save permissions', () => {
it('then it should return undefined', () => {
const { original, dash, contextSrv } = getTestContext();
const { contextSrv } = getTestContext();
const dash = createDashboardModelFixture({}, { canSave: true });
const original = dash.getSaveModelClone();
contextSrv.isEditor = false;
expect(ignoreChanges({ ...dash, meta: { canSave: true } }, original)).toBe(undefined);
expect(ignoreChanges(dash, original)).toBe(undefined);
});
});
describe('when called for an user that is not signed in', () => {
it('then it should return true', () => {
const { original, dash, contextSrv } = getTestContext();
const { contextSrv } = getTestContext();
const dash = createDashboardModelFixture({}, { canSave: true });
const original = dash.getSaveModelClone();
contextSrv.isSignedIn = false;
expect(ignoreChanges({ ...dash, meta: { canSave: true } }, original)).toBe(true);
expect(ignoreChanges(dash, original)).toBe(true);
});
});
describe('when called with fromScript', () => {
it('then it should return true', () => {
const { original, dash } = getTestContext();
expect(
ignoreChanges({ ...dash, meta: { canSave: true, fromScript: true, fromFile: undefined } }, original)
).toBe(true);
const dash = createDashboardModelFixture({}, { canSave: true, fromScript: true, fromFile: undefined });
const original = dash.getSaveModelClone();
expect(ignoreChanges(dash, original)).toBe(true);
});
});
@ -147,19 +146,17 @@ describe('DashboardPrompt', () => {
describe('when called with fromFile', () => {
it('then it should return true', () => {
const { original, dash } = getTestContext();
expect(
ignoreChanges({ ...dash, meta: { canSave: true, fromScript: undefined, fromFile: true } }, original)
).toBe(true);
const dash = createDashboardModelFixture({}, { canSave: true, fromScript: undefined, fromFile: true });
const original = dash.getSaveModelClone();
expect(ignoreChanges(dash, original)).toBe(true);
});
});
describe('when called with canSave but without fromScript and fromFile', () => {
it('then it should return false', () => {
const { original, dash } = getTestContext();
expect(
ignoreChanges({ ...dash, meta: { canSave: true, fromScript: undefined, fromFile: undefined } }, original)
).toBe(undefined);
const dash = createDashboardModelFixture({}, { canSave: true, fromScript: undefined, fromFile: undefined });
const original = dash.getSaveModelClone();
expect(ignoreChanges(dash, original)).toBe(undefined);
});
});
});

@ -4,6 +4,7 @@ import React, { useContext, useEffect, useState } from 'react';
import { Prompt } from 'react-router-dom';
import { locationService } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { ModalsContext } from '@grafana/ui';
import { appEvents } from 'app/core/app_events';
import { contextSrv } from 'app/core/services/context_srv';
@ -140,7 +141,7 @@ function moveToBlockedLocationAfterReactStateUpdate(location?: H.Location | null
/**
* For some dashboards and users changes should be ignored *
*/
export function ignoreChanges(current: DashboardModel, original: object | null) {
export function ignoreChanges(current: DashboardModel | null, original: object | null) {
if (!original) {
return true;
}
@ -150,7 +151,7 @@ export function ignoreChanges(current: DashboardModel, original: object | null)
return true;
}
if (!current || !current.meta) {
if (!current) {
return true;
}
@ -165,7 +166,7 @@ export function ignoreChanges(current: DashboardModel, original: object | null)
/**
* Remove stuff that should not count in diff
*/
function cleanDashboardFromIgnoredChanges(dashData: unknown) {
function cleanDashboardFromIgnoredChanges(dashData: Dashboard) {
// need to new up the domain model class to get access to expand / collapse row logic
const model = new DashboardModel(dashData);
@ -193,13 +194,14 @@ function cleanDashboardFromIgnoredChanges(dashData: unknown) {
return dash;
}
// TODO: Adapt original to be Dashboard type instead
export function hasChanges(current: DashboardModel, original: unknown) {
if (current.hasUnsavedChanges()) {
return true;
}
const currentClean = cleanDashboardFromIgnoredChanges(current.getSaveModelClone());
const originalClean = cleanDashboardFromIgnoredChanges(original);
// TODO: Make getSaveModelClone return Dashboard type instead
const currentClean = cleanDashboardFromIgnoredChanges(current.getSaveModelClone() as unknown as Dashboard);
const originalClean = cleanDashboardFromIgnoredChanges(original as Dashboard);
const currentTimepicker = find((currentClean as any).nav, { type: 'timepicker' });
const originalTimepicker = find((originalClean as any).nav, { type: 'timepicker' });

@ -13,6 +13,7 @@ import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state/DashboardModel';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { AnnotationsSettings } from './AnnotationsSettings';
@ -83,7 +84,7 @@ describe('AnnotationsSettings', () => {
});
beforeEach(() => {
dashboard = new DashboardModel({
dashboard = createDashboardModelFixture({
id: 74,
version: 7,
annotations: {
@ -96,6 +97,7 @@ describe('AnnotationsSettings', () => {
iconColor: 'rgba(0, 211, 255, 1)',
name: 'Annotations & Alerts',
type: 'dashboard',
showIn: 1,
},
],
},

@ -9,7 +9,7 @@ import { BackendSrv, setBackendSrv } from '@grafana/runtime';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from 'app/store/configureStore';
import { DashboardModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { DashboardSettings } from './DashboardSettings';
@ -27,7 +27,7 @@ setBackendSrv({
describe('DashboardSettings', () => {
it('pressing escape navigates away correctly', async () => {
const dashboard = new DashboardModel(
const dashboard = createDashboardModelFixture(
{
title: 'Foo',
},

@ -12,7 +12,7 @@ import { BackendSrv, setBackendSrv } from '@grafana/runtime';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { GeneralSettingsUnconnected as GeneralSettings, Props } from './GeneralSettings';
@ -23,13 +23,16 @@ setBackendSrv({
const setupTestContext = (options: Partial<Props>) => {
const store = configureStore();
const defaults: Props = {
dashboard: new DashboardModel(
dashboard: createDashboardModelFixture(
{
title: 'test dashboard title',
description: 'test dashboard description',
timepicker: {
refresh_intervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d', '2d'],
time_options: ['5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d'],
collapse: true,
enable: true,
hidden: false,
},
timezone: 'utc',
},

@ -12,6 +12,7 @@ import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { DashboardSettings } from './DashboardSettings';
@ -37,7 +38,7 @@ function setup(dashboard: DashboardModel) {
}
function buildTestDashboard() {
return new DashboardModel({
return createDashboardModelFixture({
links: [
{
asDropdown: false,
@ -87,7 +88,7 @@ describe('LinksSettings', () => {
};
test('it renders a header and cta if no links', () => {
const linklessDashboard = new DashboardModel({ links: [] });
const linklessDashboard = createDashboardModelFixture({ links: [] });
setup(linklessDashboard);
expect(screen.getByRole('heading', { name: 'Links' })).toBeInTheDocument();

@ -9,7 +9,7 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state/DashboardModel';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { historySrv } from '../VersionHistory/HistorySrv';
import { VersionsSettings, VERSIONS_FETCH_LIMIT } from './VersionsSettings';
@ -30,11 +30,11 @@ const queryByFullText = (text: string) =>
function setup() {
const store = configureStore();
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
id: 74,
version: 11,
formatDate: jest.fn(() => 'date'),
getRelativeTime: jest.fn(() => 'time ago'),
// formatDate: jest.fn(() => 'date'),
// getRelativeTime: jest.fn(() => 'time ago'),
});
const sectionNav = {

@ -16,7 +16,8 @@ import { selectors } from '@grafana/e2e-selectors';
import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/OptionsUI/registry';
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
import { DashboardModel, PanelModel } from '../../state';
import { PanelModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { OptionsPaneOptions } from './OptionsPaneOptions';
import { dataOverrideTooltipDescription, overrideRuleTooltipDescription } from './state/getOptionOverrides';
@ -88,7 +89,7 @@ class OptionsPaneOptionsTestScenario {
options: {},
});
dashboard = new DashboardModel({});
dashboard = createDashboardModelFixture();
store = mockStore({
dashboard: { panels: [] },
templating: {

@ -18,7 +18,8 @@ import {
import { getTimeSrv, TimeSrv, setTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { PanelQueryRunner } from '../../../query/state/PanelQueryRunner';
import { DashboardModel, PanelModel } from '../../state';
import { PanelModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { PanelEditorTableView, Props } from './PanelEditorTableView';
@ -53,14 +54,11 @@ function setupTestContext(options: Partial<Props> = {}) {
getDisplayTitle: jest.fn(),
runAllPanelQueries: jest.fn(),
}),
dashboard: new DashboardModel({
dashboard: createDashboardModelFixture({
id: 1,
uid: 'super-unique-id',
panelInitialized: jest.fn(),
events: new EventBusSrv(),
meta: {
isPublic: false,
},
// panelInitialized: jest.fn(),
// events: new EventBusSrv(),
panels: [],
}),
plugin: {

@ -1,8 +1,9 @@
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { panelModelAndPluginReady, removePanel } from 'app/features/panel/state/reducers';
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
import { thunkTester } from '../../../../../../test/core/thunk/thunkTester';
import { DashboardModel, PanelModel } from '../../../state';
import { PanelModel } from '../../../state';
import { exitPanelEditor, initPanelEditor, skipPanelUpdate } from './actions';
import { closeEditor, initialState, PanelEditorState } from './reducers';
@ -10,7 +11,7 @@ import { closeEditor, initialState, PanelEditorState } from './reducers';
describe('panelEditor actions', () => {
describe('initPanelEditor', () => {
it('initPanelEditor should create edit panel model as clone', async () => {
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [{ id: 12, type: 'graph' }],
});
const sourcePanel = new PanelModel({ id: 12, type: 'graph' });
@ -34,7 +35,7 @@ describe('panelEditor actions', () => {
describe('panelEditorCleanUp', () => {
it('should update source panel', async () => {
const sourcePanel = new PanelModel({ id: 12, type: 'graph' });
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [{ id: 12, type: 'graph' }],
});
@ -66,7 +67,7 @@ describe('panelEditor actions', () => {
it('should dispatch panelModelAndPluginReady if type changed', async () => {
const sourcePanel = new PanelModel({ id: 12, type: 'graph' });
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [{ id: 12, type: 'graph' }],
});
@ -105,7 +106,7 @@ describe('panelEditor actions', () => {
customFieldConfigs: {},
} as any;
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [{ id: 12, type: 'graph' }],
});
@ -138,7 +139,7 @@ describe('panelEditor actions', () => {
sourcePanel.plugin = getPanelPlugin({});
sourcePanel.plugin.angularPanelCtrl = undefined;
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [{ id: 12, type: 'graph' }],
});
@ -168,7 +169,7 @@ describe('panelEditor actions', () => {
sourcePanel.plugin = getPanelPlugin({});
sourcePanel.plugin.angularPanelCtrl = undefined;
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [{ id: 12, type: 'graph' }],
});
@ -207,7 +208,7 @@ describe('panelEditor actions', () => {
sourcePanel.plugin = getPanelPlugin({});
sourcePanel.plugin.angularPanelCtrl = {};
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [{ id: 12, type: 'graph' }],
});

@ -6,6 +6,7 @@ import { Provider } from 'react-redux';
import { configureStore } from 'app/store/configureStore';
import { DashboardModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { SaveDashboardDrawer } from './SaveDashboardDrawer';
@ -30,7 +31,7 @@ jest.mock('app/core/services/backend_srv', () => ({
const store = configureStore();
const mockPost = jest.fn();
const buildMocks = () => ({
dashboard: new DashboardModel({
dashboard: createDashboardModelFixture({
uid: 'mockDashboardUid',
version: 1,
}),

@ -31,7 +31,6 @@ const getSaveAsDashboardClone = (dashboard: DashboardModel) => {
});
}
delete clone.autoUpdate;
return clone;
};

@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import { DashboardModel } from 'app/features/dashboard/state';
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { SaveDashboardOptions } from '../types';
@ -123,14 +124,14 @@ describe('SaveDashboardAsForm', () => {
it('renders saved message draft if it was filled before', () => {
render(
<SaveDashboardForm
dashboard={new DashboardModel({})}
dashboard={createDashboardModelFixture()}
onCancel={() => {}}
onSuccess={() => {}}
onSubmit={async () => {
return {};
}}
saveModel={{
clone: new DashboardModel({}),
clone: createDashboardModelFixture(),
diff: {},
diffCount: 0,
hasChanges: true,

@ -6,7 +6,8 @@ import { setEchoSrv } from '@grafana/runtime/src';
import config from 'app/core/config';
import { Echo } from '../../../../core/services/echo/Echo';
import { DashboardModel, PanelModel } from '../../state';
import { PanelModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { ShareEmbed } from './ShareEmbed';
@ -68,7 +69,7 @@ describe('ShareEmbed', () => {
});
it('generates the correct embed url for a dashboard', () => {
const mockDashboard = new DashboardModel({
const mockDashboard = createDashboardModelFixture({
uid: 'mockDashboardUid',
});
const mockPanel = new PanelModel({
@ -86,7 +87,7 @@ describe('ShareEmbed', () => {
it('generates the correct embed url for a dashboard set to the homepage in the grafana config', () => {
mockLocationHref('http://dashboards.grafana.com/?orgId=1');
const mockDashboard = new DashboardModel({
const mockDashboard = createDashboardModelFixture({
uid: 'mockDashboardUid',
});
const mockPanel = new PanelModel({
@ -104,7 +105,7 @@ describe('ShareEmbed', () => {
it('generates the correct embed url for a snapshot', () => {
const mockSlug = 'mockSlug';
mockLocationHref(`http://dashboards.grafana.com/dashboard/snapshot/${mockSlug}?orgId=1`);
const mockDashboard = new DashboardModel({
const mockDashboard = createDashboardModelFixture({
uid: 'mockDashboardUid',
});
const mockPanel = new PanelModel({
@ -122,7 +123,7 @@ describe('ShareEmbed', () => {
it('generates the correct embed url for a scripted dashboard', () => {
const mockSlug = 'scripted.js';
mockLocationHref(`http://dashboards.grafana.com/dashboard/script/${mockSlug}?orgId=1`);
const mockDashboard = new DashboardModel({
const mockDashboard = createDashboardModelFixture({
uid: 'mockDashboardUid',
});
const mockPanel = new PanelModel({

@ -11,7 +11,8 @@ import { initTemplateSrv } from '../../../../../test/helpers/initTemplateSrv';
import { Echo } from '../../../../core/services/echo/Echo';
import { variableAdapters } from '../../../variables/adapters';
import { createQueryVariableAdapter } from '../../../variables/query/adapter';
import { DashboardModel, PanelModel } from '../../state';
import { PanelModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { Props, ShareLink } from './ShareLink';
@ -78,13 +79,20 @@ describe('ShareModal', () => {
});
beforeEach(() => {
const defaultTimeRange = getDefaultTimeRange();
setUTCTimeZone();
mockLocationHref('http://server/#!/test');
config.rendererAvailable = true;
config.bootData.user.orgId = 1;
props = {
panel: new PanelModel({ id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }),
dashboard: new DashboardModel({ time: getDefaultTimeRange(), id: 1 }),
dashboard: createDashboardModelFixture({
time: {
from: defaultTimeRange.from.toISOString(),
to: defaultTimeRange.to.toISOString(),
},
id: 1,
}),
};
});
@ -175,7 +183,7 @@ describe('when appUrl is set in the grafana config', () => {
});
it('should render the correct link', async () => {
const mockDashboard = new DashboardModel({
const mockDashboard = createDashboardModelFixture({
uid: 'mockDashboardUid',
id: 1,
});

@ -8,11 +8,13 @@ import 'whatwg-fetch';
import { BootData, DataQuery } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { setEchoSrv } from '@grafana/runtime/src';
import { Panel } from '@grafana/schema';
import config from 'app/core/config';
import { backendSrv } from 'app/core/services/backend_srv';
import { contextSrv } from 'app/core/services/context_srv';
import { Echo } from 'app/core/services/echo/Echo';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { configureStore } from 'app/store/configureStore';
import { ShareModal } from '../ShareModal';
@ -71,7 +73,7 @@ beforeAll(() => {
beforeEach(() => {
config.featureToggles.publicDashboards = true;
mockDashboard = new DashboardModel({
mockDashboard = createDashboardModelFixture({
uid: 'mockDashboardUid',
});
@ -203,8 +205,8 @@ describe('SharePublic - New config setup', () => {
datasource: { type: 'notSupportedDatasource', uid: 'abc123' },
} as DataQuery,
] as DataQuery[],
} as PanelModel;
const dashboard = new DashboardModel({
} as unknown as Panel;
const dashboard = createDashboardModelFixture({
id: 1,
panels: [panelModel],
});

@ -1,4 +1,5 @@
import { DashboardModel } from '../../state/DashboardModel';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { HistorySrv } from './HistorySrv';
import { restore, versions } from './__mocks__/dashboardHistoryMocks';
@ -25,8 +26,8 @@ describe('historySrv', () => {
let historySrv = new HistorySrv();
const dash = new DashboardModel({ uid: '_U4zObQMz' });
const emptyDash = new DashboardModel({});
const dash = createDashboardModelFixture({ uid: '_U4zObQMz' });
const emptyDash = createDashboardModelFixture();
const historyListOpts = { limit: 10, start: 0 };
beforeEach(() => {

@ -2,7 +2,8 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { createEmptyQueryResponse } from '../../../explore/state/utils';
import { DashboardModel, PanelModel } from '../../state';
import { PanelModel } from '../../state';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import { PanelHeader } from './PanelHeader';
@ -16,7 +17,7 @@ let panelModel = new PanelModel({
let panelData = createEmptyQueryResponse();
describe('Panel Header', () => {
const dashboardModel = new DashboardModel({}, { publicDashboardAccessToken: 'abc123' });
const dashboardModel = createDashboardModelFixture({}, { publicDashboardAccessToken: 'abc123' });
it('will render header title but not render dropdown icon when dashboard is being viewed publicly', () => {
window.history.pushState({}, 'Test Title', '/public-dashboards/abc123');
@ -29,7 +30,7 @@ describe('Panel Header', () => {
});
it('will render header title and dropdown icon when dashboard is not being viewed publicly', () => {
const dashboardModel = new DashboardModel({}, { publicDashboardAccessToken: '' });
const dashboardModel = createDashboardModelFixture({}, { publicDashboardAccessToken: '' });
window.history.pushState({}, 'Test Title', '/d/abc/123');
render(

@ -64,6 +64,7 @@ describe('DashboardModel', () => {
{ type: 'annotations', enable: true, annotations: [{ name: 'old' }] },
],
panels: [
// @ts-expect-error
{
type: 'graph',
legend: true,
@ -87,6 +88,7 @@ describe('DashboardModel', () => {
{
type: 'singlestat',
legend: true,
// @ts-expect-error
thresholds: '10,20,30',
colors: ['#FF0000', 'green', 'orange'],
aliasYAxis: { test: 2 },
@ -95,6 +97,7 @@ describe('DashboardModel', () => {
},
{
type: 'singlestat',
// @ts-expect-error
thresholds: '10,20,30',
colors: ['#FF0000', 'green', 'orange'],
gauge: {
@ -106,6 +109,7 @@ describe('DashboardModel', () => {
},
{
type: 'table',
// @ts-expect-error
legend: true,
styles: [{ thresholds: ['10', '20', '30'] }, { thresholds: ['100', '200', '300'] }],
targets: [{ refId: 'A' }, {}],
@ -210,6 +214,7 @@ describe('DashboardModel', () => {
panels: [
{
type: 'graph',
// @ts-expect-error
y_formats: ['kbyte', 'ms'],
grid: {
threshold1: 200,
@ -468,6 +473,7 @@ describe('DashboardModel', () => {
const model = {
panels: [{ minSpan: 8 }],
};
// @ts-expect-error
const dashboard = new DashboardModel(model);
expect(dashboard.panels[0].maxPerRow).toBe(3);
});
@ -481,6 +487,7 @@ describe('DashboardModel', () => {
panels: [
{
links: [
// @ts-expect-error
{
url: 'http://mylink.com',
keepTime: true,
@ -488,23 +495,28 @@ describe('DashboardModel', () => {
},
{
url: 'http://mylink.com?existingParam',
// @ts-expect-error
params: 'customParam',
title: 'test',
},
// @ts-expect-error
{
url: 'http://mylink.com?existingParam',
includeVars: true,
title: 'test',
},
{
// @ts-expect-error
dashboard: 'my other dashboard',
title: 'test',
},
{
// @ts-expect-error
dashUri: '',
title: 'test',
},
{
// @ts-expect-error
type: 'dashboard',
keepTime: true,
},
@ -536,6 +548,7 @@ describe('DashboardModel', () => {
beforeEach(() => {
model = new DashboardModel({
panels: [
// @ts-expect-error
{
//graph panel
options: {
@ -549,6 +562,7 @@ describe('DashboardModel', () => {
],
},
},
// @ts-expect-error
{
// panel with field options
options: {
@ -601,6 +615,7 @@ describe('DashboardModel', () => {
beforeEach(() => {
model = new DashboardModel({
panels: [
// @ts-expect-error
{
//graph panel
options: {
@ -611,6 +626,7 @@ describe('DashboardModel', () => {
],
},
},
// @ts-expect-error
{
// panel with field options
options: {
@ -649,6 +665,7 @@ describe('DashboardModel', () => {
templating: {
list: [
{
// @ts-expect-error
multi: false,
current: {
value: ['value'],
@ -656,6 +673,7 @@ describe('DashboardModel', () => {
},
},
{
// @ts-expect-error
multi: true,
current: {
value: ['value'],
@ -697,6 +715,7 @@ describe('DashboardModel', () => {
list: [
{
type: 'query',
// @ts-expect-error
tags: ['Africa', 'America', 'Asia', 'Europe'],
tagsQuery: 'select datacenter from x',
tagValuesQuery: 'select value from x where datacenter = xyz',
@ -704,6 +723,7 @@ describe('DashboardModel', () => {
},
{
type: 'query',
// @ts-expect-error
current: {
tags: [
{
@ -729,6 +749,7 @@ describe('DashboardModel', () => {
},
{
type: 'query',
// @ts-expect-error
tags: [
{ text: 'Africa', selected: false },
{ text: 'America', selected: true },
@ -783,10 +804,12 @@ describe('DashboardModel', () => {
id: 2,
type: 'text',
title: 'Angular Text Panel',
// @ts-expect-error
content:
'# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n',
mode: 'markdown',
},
// @ts-expect-error
{
id: 3,
type: 'text2',
@ -797,6 +820,7 @@ describe('DashboardModel', () => {
'# React Text Panel from scratch\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text',
},
},
// @ts-expect-error
{
id: 4,
type: 'text2',
@ -867,24 +891,28 @@ describe('DashboardModel', () => {
type: 'query',
hide: VariableHide.dontHide,
datasource: null,
// @ts-expect-error
allFormat: '',
},
{
type: 'query',
hide: VariableHide.hideLabel,
datasource: null,
// @ts-expect-error
allFormat: '',
},
{
type: 'query',
hide: VariableHide.hideVariable,
datasource: null,
// @ts-expect-error
allFormat: '',
},
{
type: 'constant',
hide: VariableHide.dontHide,
query: 'default value',
// @ts-expect-error
current: { selected: true, text: 'A', value: 'B' },
options: [{ selected: true, text: 'A', value: 'B' }],
datasource: null,
@ -894,6 +922,7 @@ describe('DashboardModel', () => {
type: 'constant',
hide: VariableHide.hideLabel,
query: 'default value',
// @ts-expect-error
current: { selected: true, text: 'A', value: 'B' },
options: [{ selected: true, text: 'A', value: 'B' }],
datasource: null,
@ -903,6 +932,7 @@ describe('DashboardModel', () => {
type: 'constant',
hide: VariableHide.hideVariable,
query: 'default value',
// @ts-expect-error
current: { selected: true, text: 'A', value: 'B' },
options: [{ selected: true, text: 'A', value: 'B' }],
datasource: null,
@ -967,79 +997,93 @@ describe('DashboardModel', () => {
{
type: 'query',
name: 'variable_with_never_refresh_with_options',
// @ts-expect-error
options: [{ text: 'A', value: 'A' }],
refresh: 0,
},
{
type: 'query',
name: 'variable_with_never_refresh_without_options',
// @ts-expect-error
options: [],
refresh: 0,
},
{
type: 'query',
name: 'variable_with_dashboard_refresh_with_options',
// @ts-expect-error
options: [{ text: 'A', value: 'A' }],
refresh: 1,
},
{
type: 'query',
name: 'variable_with_dashboard_refresh_without_options',
// @ts-expect-error
options: [],
refresh: 1,
},
{
type: 'query',
name: 'variable_with_timerange_refresh_with_options',
// @ts-expect-error
options: [{ text: 'A', value: 'A' }],
refresh: 2,
},
{
type: 'query',
name: 'variable_with_timerange_refresh_without_options',
// @ts-expect-error
options: [],
refresh: 2,
},
{
type: 'query',
name: 'variable_with_no_refresh_with_options',
// @ts-expect-error
options: [{ text: 'A', value: 'A' }],
},
{
type: 'query',
name: 'variable_with_no_refresh_without_options',
// @ts-expect-error
options: [],
},
{
type: 'query',
name: 'variable_with_unknown_refresh_with_options',
// @ts-expect-error
options: [{ text: 'A', value: 'A' }],
refresh: 2001,
},
{
type: 'query',
name: 'variable_with_unknown_refresh_without_options',
// @ts-expect-error
options: [],
refresh: 2001,
},
{
type: 'custom',
name: 'custom',
// @ts-expect-error
options: [{ text: 'custom', value: 'custom' }],
},
{
type: 'textbox',
name: 'textbox',
// @ts-expect-error
options: [{ text: 'Hello', value: 'World' }],
},
{
type: 'datasource',
name: 'datasource',
// @ts-expect-error
options: [{ text: 'ds', value: 'ds' }], // fake example doesn't exist
},
{
type: 'interval',
name: 'interval',
// @ts-expect-error
options: [{ text: '1m', value: '1m' }],
},
],
@ -1112,10 +1156,12 @@ describe('DashboardModel', () => {
fieldConfig: {
defaults: {
thresholds: {
// @ts-expect-error
mode: 'absolute',
steps: [
{
color: 'green',
// @ts-expect-error
value: null,
},
{
@ -1128,12 +1174,14 @@ describe('DashboardModel', () => {
{
id: 0,
text: '1',
// @ts-expect-error
type: 1,
value: 'up',
},
{
id: 1,
text: 'BAD',
// @ts-expect-error
type: 1,
value: 'down',
},
@ -1142,6 +1190,7 @@ describe('DashboardModel', () => {
id: 2,
text: 'below 30',
to: '30',
// @ts-expect-error
type: 2,
},
{
@ -1149,9 +1198,11 @@ describe('DashboardModel', () => {
id: 3,
text: '100',
to: '100',
// @ts-expect-error
type: 2,
},
{
// @ts-expect-error
type: 1,
value: 'null',
text: 'it is null',
@ -1243,6 +1294,7 @@ describe('DashboardModel', () => {
panels: [
{
type: 'timeseries',
// @ts-expect-error
legend: true,
options: {
tooltipOptions: { mode: 'multi' },
@ -1250,6 +1302,7 @@ describe('DashboardModel', () => {
},
{
type: 'xychart',
// @ts-expect-error
legend: true,
options: {
tooltipOptions: { mode: 'single' },
@ -1281,6 +1334,7 @@ describe('DashboardModel', () => {
{
type: 'singlestat',
legend: true,
// @ts-expect-error
thresholds: '10,20,30',
colors: ['#FF0000', 'green', 'orange'],
aliasYAxis: { test: 2 },
@ -1342,6 +1396,7 @@ describe('DashboardModel', () => {
{
type: 'singlestat',
legend: true,
// @ts-expect-error
thresholds: '10,20,30',
colors: ['#FF0000', 'green', 'orange'],
aliasYAxis: { test: 2 },
@ -1424,6 +1479,7 @@ describe('DashboardModel', () => {
{
id: 1,
type: 'timeseries',
// @ts-expect-error
panels: [
{
id: 2,
@ -1462,6 +1518,7 @@ describe('DashboardModel', () => {
{
id: 1,
type: 'timeseries',
// @ts-expect-error
transformations: [{ id: 'labelsToFields' }],
},
],
@ -1493,6 +1550,7 @@ describe('DashboardModel', () => {
annotations: {
list: [
{
// @ts-expect-error
actionPrefix: '',
alarmNamePrefix: '',
alias: '',
@ -1515,6 +1573,7 @@ describe('DashboardModel', () => {
],
},
panels: [
// @ts-expect-error
{
gridPos: {
h: 8,
@ -1602,6 +1661,7 @@ describe('DashboardModel', () => {
annotations: {
list: [
{
// @ts-expect-error
actionPrefix: '',
alarmNamePrefix: '',
alias: '',
@ -1636,6 +1696,7 @@ describe('DashboardModel', () => {
title: 'DynamoDB',
type: 'row',
panels: [
// @ts-expect-error
{
gridPos: {
h: 8,
@ -1690,6 +1751,7 @@ describe('DashboardModel', () => {
title: 'Panel Title',
type: 'timeseries',
},
// @ts-expect-error
{
gridPos: {
h: 8,
@ -1793,6 +1855,7 @@ describe('DashboardModel', () => {
name: 'var',
options: [{ text: 'A', value: 'A' }],
refresh: 0,
// @ts-expect-error
datasource: 'prom',
},
],
@ -1800,14 +1863,17 @@ describe('DashboardModel', () => {
panels: [
{
id: 1,
// @ts-expect-error
datasource: 'prom',
},
{
id: 2,
// @ts-expect-error
datasource: null,
},
{
id: 3,
// @ts-expect-error
datasource: MIXED_DATASOURCE_NAME,
targets: [
{
@ -1827,6 +1893,7 @@ describe('DashboardModel', () => {
panels: [
{
id: 6,
// @ts-expect-error
datasource: 'prom',
},
],
@ -1873,6 +1940,7 @@ describe('DashboardModel', () => {
panels: [
{
id: 2,
// @ts-expect-error
datasource: null,
targets: [
{
@ -1894,6 +1962,7 @@ describe('DashboardModel', () => {
test('preserves x axis visibility', () => {
const model = new DashboardModel({
panels: [
// @ts-expect-error
{
type: 'timeseries',
fieldConfig: {
@ -1937,6 +2006,7 @@ describe('DashboardModel', () => {
{
type: 'query',
name: 'var',
// @ts-expect-error
options: [{ text: 'A', value: 'A' }],
refresh: 0,
datasource: null,
@ -1946,9 +2016,11 @@ describe('DashboardModel', () => {
annotations: {
list: [
{
// @ts-expect-error
datasource: null,
},
{
// @ts-expect-error
datasource: 'prom',
},
],
@ -1956,6 +2028,7 @@ describe('DashboardModel', () => {
panels: [
{
id: 2,
// @ts-expect-error
datasource: null,
targets: [
{
@ -1963,6 +2036,7 @@ describe('DashboardModel', () => {
},
],
},
// @ts-expect-error
{
id: 3,
targets: [
@ -2007,6 +2081,7 @@ describe('DashboardModel', () => {
beforeEach(() => {
model = new DashboardModel({
panels: [
// @ts-expect-error
{
id: 2,
targets: [
@ -2040,6 +2115,7 @@ describe('when generating the legend for a panel', () => {
beforeEach(() => {
model = new DashboardModel({
panels: [
// @ts-expect-error
{
id: 0,
options: {
@ -2052,6 +2128,7 @@ describe('when generating the legend for a panel', () => {
},
},
},
// @ts-expect-error
{
id: 1,
options: {
@ -2064,6 +2141,7 @@ describe('when generating the legend for a panel', () => {
},
},
},
// @ts-expect-error
{
id: 2,
options: {

@ -2,8 +2,8 @@ import { appEvents } from '../../../core/core';
import { VariablesChanged } from '../../variables/types';
import { getTimeSrv, setTimeSrv } from '../services/TimeSrv';
import { DashboardModel } from './DashboardModel';
import { PanelModel } from './PanelModel';
import { createDashboardModelFixture } from './__fixtures__/dashboardFixtures';
function getTestContext({
usePanelInEdit,
@ -12,7 +12,7 @@ function getTestContext({
}: { usePanelInEdit?: boolean; usePanelInView?: boolean; refreshAll?: boolean } = {}) {
jest.clearAllMocks();
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture();
const startRefreshMock = jest.fn();
dashboard.startRefresh = startRefreshMock;
const panelInView = new PanelModel({ id: 99 });

@ -1,5 +1,7 @@
import { keys as _keys } from 'lodash';
import { VariableHide } from '@grafana/data';
import { defaultVariableModel } from '@grafana/schema';
import { contextSrv } from 'app/core/services/context_srv';
import { getDashboardModel } from '../../../../test/helpers/getDashboardModel';
@ -11,6 +13,13 @@ import { setTimeSrv, TimeSrv } from '../services/TimeSrv';
import { DashboardModel } from '../state/DashboardModel';
import { PanelModel } from '../state/PanelModel';
import {
createAnnotationJSONFixture,
createDashboardModelFixture,
createPanelJSONFixture,
createVariableJSONFixture,
} from './__fixtures__/dashboardFixtures';
jest.mock('app/core/services/context_srv');
const mockContextSrv = jest.mocked(contextSrv);
@ -26,7 +35,7 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel({}, {});
model = createDashboardModelFixture();
});
it('should have title', () => {
@ -47,8 +56,8 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel({
panels: [{ id: 5 }],
model = createDashboardModelFixture({
panels: [createPanelJSONFixture({ id: 5 })],
});
});
@ -59,8 +68,7 @@ describe('DashboardModel', () => {
describe('getSaveModelClone', () => {
it('should sort keys', () => {
const model = new DashboardModel({});
model.autoUpdate = null;
const model = createDashboardModelFixture();
const saveModel = model.getSaveModelClone();
const keys = _keys(saveModel);
@ -70,7 +78,7 @@ describe('DashboardModel', () => {
});
it('should remove add panel panels', () => {
const model = new DashboardModel({});
const model = createDashboardModelFixture();
model.addPanel({
type: 'add-panel',
});
@ -87,7 +95,7 @@ describe('DashboardModel', () => {
});
it('should save model in edit mode', () => {
const model = new DashboardModel({});
const model = createDashboardModelFixture();
model.addPanel({ type: 'graph' });
const panel = model.initEditPanel(model.panels[0]);
@ -105,7 +113,7 @@ describe('DashboardModel', () => {
let dashboard: DashboardModel;
beforeEach(() => {
dashboard = new DashboardModel({});
dashboard = createDashboardModelFixture();
});
it('adding panel should new up panel model', () => {
@ -148,7 +156,7 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel({ editable: false });
model = createDashboardModelFixture({ editable: false });
});
it('Should set meta canEdit and canSave to false', () => {
@ -167,12 +175,11 @@ describe('DashboardModel', () => {
let target: any;
beforeEach(() => {
model = new DashboardModel({
model = createDashboardModelFixture({
schemaVersion: 1,
panels: [
{
createPanelJSONFixture({
type: 'graph',
grid: {},
yaxes: [{}, {}],
targets: [
{
alias: '$tag_datacenter $tag_source $col',
@ -211,7 +218,7 @@ describe('DashboardModel', () => {
],
},
],
},
}),
],
});
@ -233,13 +240,9 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel({
annotations: {
enable: true,
},
templating: {
enable: true,
},
model = createDashboardModelFixture({
annotations: {},
templating: {},
});
});
@ -258,7 +261,7 @@ describe('DashboardModel', () => {
let dashboard: DashboardModel;
beforeEach(() => {
dashboard = new DashboardModel({ timezone: 'utc' });
dashboard = createDashboardModelFixture({ timezone: 'utc' });
});
it('Should format timestamp with second resolution by default', () => {
@ -278,7 +281,7 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel({});
model = createDashboardModelFixture();
});
it('should not show submenu', () => {
@ -290,9 +293,21 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel({
model = createDashboardModelFixture({
schemaVersion: 30,
annotations: {
list: [{}],
list: [
{
datasource: { uid: 'fake-uid', type: 'prometheus' },
showIn: 0,
name: 'Fake annotation',
type: 'dashboard',
iconColor: 'rgba(0, 211, 255, 1)',
enable: true,
hide: false,
builtIn: 0,
},
],
},
});
});
@ -306,10 +321,10 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel(
model = createDashboardModelFixture(
{
templating: {
list: [{}],
list: [createVariableJSONFixture({})],
},
},
{},
@ -327,9 +342,14 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
model = new DashboardModel({
model = createDashboardModelFixture({
templating: {
list: [{ hide: 2 }],
list: [
{
...defaultVariableModel,
hide: VariableHide.hideVariable,
},
],
},
});
});
@ -343,9 +363,9 @@ describe('DashboardModel', () => {
let dashboard: DashboardModel;
beforeEach(() => {
dashboard = new DashboardModel({
dashboard = createDashboardModelFixture({
annotations: {
list: [{ hide: true }],
list: [createAnnotationJSONFixture({ hide: true })],
},
});
});
@ -359,13 +379,13 @@ describe('DashboardModel', () => {
let dashboard: DashboardModel;
beforeEach(() => {
dashboard = new DashboardModel({
dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'graph', gridPos: { x: 0, y: 0, w: 24, h: 2 } },
{ id: 2, type: 'row', gridPos: { x: 0, y: 2, w: 24, h: 2 } },
{ id: 3, type: 'graph', gridPos: { x: 0, y: 4, w: 12, h: 2 } },
{ id: 4, type: 'graph', gridPos: { x: 12, y: 4, w: 12, h: 2 } },
{ id: 5, type: 'row', gridPos: { x: 0, y: 6, w: 24, h: 2 } },
createPanelJSONFixture({ id: 1, type: 'graph', gridPos: { x: 0, y: 0, w: 24, h: 2 } }),
createPanelJSONFixture({ id: 2, type: 'row', gridPos: { x: 0, y: 2, w: 24, h: 2 } }),
createPanelJSONFixture({ id: 3, type: 'graph', gridPos: { x: 0, y: 4, w: 12, h: 2 } }),
createPanelJSONFixture({ id: 4, type: 'graph', gridPos: { x: 12, y: 4, w: 12, h: 2 } }),
createPanelJSONFixture({ id: 5, type: 'row', gridPos: { x: 0, y: 6, w: 24, h: 2 } }),
],
});
dashboard.toggleRow(dashboard.panels[1]);
@ -410,7 +430,7 @@ describe('DashboardModel', () => {
let dashboard: DashboardModel;
beforeEach(() => {
dashboard = new DashboardModel({
dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'graph', gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{
@ -423,7 +443,7 @@ describe('DashboardModel', () => {
{ id: 4, type: 'graph', gridPos: { x: 12, y: 7, w: 12, h: 2 } },
],
},
{ id: 5, type: 'row', gridPos: { x: 0, y: 7, w: 1, h: 1 } },
{ id: 5, type: 'row', collapsed: false, panels: [], gridPos: { x: 0, y: 7, w: 1, h: 1 } },
],
});
dashboard.toggleRow(dashboard.panels[1]);
@ -481,7 +501,7 @@ describe('DashboardModel', () => {
let dashboard: DashboardModel;
beforeEach(() => {
dashboard = new DashboardModel({
dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'graph', gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{
@ -490,11 +510,14 @@ describe('DashboardModel', () => {
gridPos: { x: 0, y: 6, w: 24, h: 1 },
collapsed: true,
panels: [
// this whole test is about dealing with out-of-spec (or at least ambigious) data...
//@ts-expect-error
{ id: 3, type: 'graph', gridPos: { w: 12, h: 2 } },
//@ts-expect-error
{ id: 4, type: 'graph', gridPos: { w: 12, h: 2 } },
],
},
{ id: 5, type: 'row', gridPos: { x: 0, y: 7, w: 1, h: 1 } },
{ id: 5, type: 'row', collapsed: false, panels: [], gridPos: { x: 0, y: 7, w: 1, h: 1 } },
],
});
dashboard.toggleRow(dashboard.panels[1]);
@ -523,7 +546,7 @@ describe('DashboardModel', () => {
beforeEach(() => {
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
model = new DashboardModel({
model = createDashboardModelFixture({
time: {
from: 'now-6h',
to: 'now',
@ -838,14 +861,13 @@ describe('DashboardModel', () => {
let model: DashboardModel;
beforeEach(() => {
const data = {
model = createDashboardModelFixture({
panels: [
{ id: 1, type: 'graph', gridPos: { x: 0, y: 0, w: 24, h: 2 }, legend: { show: true } },
{ id: 3, type: 'graph', gridPos: { x: 0, y: 4, w: 12, h: 2 }, legend: { show: false } },
{ id: 4, type: 'graph', gridPos: { x: 12, y: 4, w: 12, h: 2 }, legend: { show: false } },
],
};
model = new DashboardModel(data);
});
});
it('toggleLegendsForAll should toggle all legends on on first execution', () => {
@ -876,7 +898,7 @@ describe('DashboardModel', () => {
`(
'when called with canEdit:{$canEdit}, canMakeEditable:{$canMakeEditable}, canAdd:{$canAdd} and expected:{$expected}',
({ canEdit, canMakeEditable, canAdd, expected }) => {
const dashboard = new DashboardModel(
const dashboard = createDashboardModelFixture(
{},
{
annotationsPermissions: {
@ -909,7 +931,7 @@ describe('DashboardModel', () => {
`(
'when called with canEdit:{$canEdit}, canMakeEditable:{$canMakeEditable}, canEditWithOrgPermission:{$canEditWithOrgPermission} and expected:{$expected}',
({ canEdit, canMakeEditable, canEditWithOrgPermission, expected }) => {
const dashboard = new DashboardModel(
const dashboard = createDashboardModelFixture(
{},
{
annotationsPermissions: {
@ -940,7 +962,7 @@ describe('DashboardModel', () => {
`(
'when called with canEdit:{$canEdit}, canMakeEditable:{$canMakeEditable}, canEditWithDashboardPermission:{$canEditWithDashboardPermission} and expected:{$expected}',
({ canEdit, canMakeEditable, canEditWithDashboardPermission, expected }) => {
const dashboard = new DashboardModel(
const dashboard = createDashboardModelFixture(
{},
{
annotationsPermissions: {
@ -973,7 +995,7 @@ describe('DashboardModel', () => {
`(
'when called with canEdit:{$canEdit}, canMakeEditable:{$canMakeEditable}, canDeleteWithOrgPermission:{$canDeleteWithOrgPermission} and expected:{$expected}',
({ canEdit, canMakeEditable, canDeleteWithOrgPermission, expected }) => {
const dashboard = new DashboardModel(
const dashboard = createDashboardModelFixture(
{},
{
annotationsPermissions: {
@ -1004,7 +1026,7 @@ describe('DashboardModel', () => {
`(
'when called with canEdit:{$canEdit}, canMakeEditable:{$canMakeEditable}, canDeleteWithDashboardPermission:{$canDeleteWithDashboardPermission} and expected:{$expected}',
({ canEdit, canMakeEditable, canDeleteWithDashboardPermission, expected }) => {
const dashboard = new DashboardModel(
const dashboard = createDashboardModelFixture(
{},
{
annotationsPermissions: {
@ -1025,9 +1047,9 @@ describe('DashboardModel', () => {
describe('canEditPanel', () => {
it('returns false if the dashboard cannot be edited', () => {
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 1, type: 'row', collapsed: false, panels: [], gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 2, type: 'graph', gridPos: { x: 0, y: 7, w: 12, h: 2 } },
],
});
@ -1037,9 +1059,9 @@ describe('DashboardModel', () => {
});
it('returns false if no panel is passed in', () => {
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 1, type: 'row', collapsed: false, panels: [], gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 2, type: 'graph', gridPos: { x: 0, y: 7, w: 12, h: 2 } },
],
});
@ -1047,9 +1069,9 @@ describe('DashboardModel', () => {
});
it('returns false if the panel is a repeat', () => {
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 1, type: 'row', collapsed: false, panels: [], gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 2, type: 'graph', gridPos: { x: 0, y: 7, w: 12, h: 2 } },
{ id: 3, type: 'graph', gridPos: { x: 0, y: 7, w: 12, h: 2 }, repeatPanelId: 2 },
],
@ -1059,9 +1081,9 @@ describe('DashboardModel', () => {
});
it('returns false if the panel is a row', () => {
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 1, type: 'row', collapsed: false, panels: [], gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 2, type: 'graph', gridPos: { x: 0, y: 7, w: 12, h: 2 } },
],
});
@ -1070,9 +1092,9 @@ describe('DashboardModel', () => {
});
it('returns true otherwise', () => {
const dashboard = new DashboardModel({
const dashboard = createDashboardModelFixture({
panels: [
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 1, type: 'row', collapsed: false, panels: [], gridPos: { x: 0, y: 0, w: 24, h: 6 } },
{ id: 2, type: 'graph', gridPos: { x: 0, y: 7, w: 12, h: 2 } },
],
});
@ -1085,7 +1107,7 @@ describe('DashboardModel', () => {
describe('exitViewPanel', () => {
function getTestContext() {
const panel: any = { setIsViewing: jest.fn() };
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture();
dashboard.startRefresh = jest.fn();
dashboard.panelInView = panel;
@ -1122,7 +1144,7 @@ describe('exitViewPanel', () => {
describe('exitPanelEditor', () => {
function getTestContext(pauseAutoRefresh = false) {
const panel: any = { destroy: jest.fn() };
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture();
const timeSrvMock = {
pauseAutoRefresh: jest.fn(),
resumeAutoRefresh: jest.fn(),
@ -1172,7 +1194,7 @@ describe('exitPanelEditor', () => {
describe('initEditPanel', () => {
function getTestContext() {
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture();
const timeSrvMock = {
pauseAutoRefresh: jest.fn(),
resumeAutoRefresh: jest.fn(),

@ -17,6 +17,7 @@ import {
UrlQueryValue,
} from '@grafana/data';
import { RefreshEvent, TimeRangeUpdatedEvent } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL } from 'app/core/constants';
import { contextSrv } from 'app/core/services/context_srv';
@ -68,9 +69,9 @@ export interface DashboardLink {
export class DashboardModel implements TimeModel {
id: any;
uid: string;
// TODO: use propert type and fix all the places where uid is set to null
uid: any;
title: string;
autoUpdate: any;
description: any;
tags: any;
style: any;
@ -125,17 +126,13 @@ export class DashboardModel implements TimeModel {
lastRefresh: true,
};
constructor(data: any, meta?: DashboardMeta, private getVariablesFromState: GetVariables = getVariablesByKey) {
if (!data) {
data = {};
}
constructor(data: Dashboard, meta?: DashboardMeta, private getVariablesFromState: GetVariables = getVariablesByKey) {
this.events = new EventBusSrv();
this.id = data.id || null;
// UID is not there for newly created dashboards
this.uid = data.uid || null;
this.revision = data.revision;
this.revision = data.revision || 1;
this.title = data.title ?? 'No Title';
this.autoUpdate = data.autoUpdate;
this.description = data.description;
this.tags = data.tags ?? [];
this.style = data.style ?? 'dark';

@ -0,0 +1,68 @@
import {
AnnotationQuery,
Dashboard,
defaultDashboardCursorSync,
defaultVariableModel,
GraphPanel,
Panel,
VariableModel,
} from '@grafana/schema';
import { GetVariables } from 'app/features/variables/state/selectors';
import { DashboardMeta } from 'app/types';
import { DashboardModel } from '../DashboardModel';
export function createDashboardModelFixture(
dashboardInput: Partial<Dashboard> = {},
meta?: DashboardMeta,
getVariablesFromState?: GetVariables
): DashboardModel {
const dashboardJson: Dashboard = {
editable: true,
graphTooltip: defaultDashboardCursorSync,
schemaVersion: 1,
revision: 1,
style: 'dark',
...dashboardInput,
};
return new DashboardModel(dashboardJson, meta, getVariablesFromState);
}
export function createPanelJSONFixture(panelInput: Partial<Panel | GraphPanel> = {}): Panel {
return {
fieldConfig: {
defaults: {},
overrides: [],
},
options: {},
repeatDirection: 'h',
transformations: [],
transparent: false,
type: 'timeseries',
...panelInput,
};
}
export function createAnnotationJSONFixture(annotationInput: Partial<AnnotationQuery>): AnnotationQuery {
return {
builtIn: 0, // ??
datasource: {
type: 'foo',
uid: 'bar',
},
showIn: 2,
enable: true,
type: 'anno',
...annotationInput,
};
}
export function createVariableJSONFixture(annotationInput: Partial<VariableModel>): VariableModel {
return {
...defaultVariableModel,
name: 'foo.variable',
type: 'constant',
...annotationInput,
};
}

@ -1,6 +1,6 @@
import { DashboardInitPhase, DashboardState, OrgRole, PermissionLevel } from 'app/types';
import { DashboardModel } from './DashboardModel';
import { createDashboardModelFixture, createPanelJSONFixture } from './__fixtures__/dashboardFixtures';
import {
dashboardInitCompleted,
dashboardInitFailed,
@ -35,9 +35,9 @@ describe('dashboard reducer', () => {
state = dashboardReducer(
state,
dashboardInitCompleted(
new DashboardModel({
createDashboardModelFixture({
title: 'My dashboard',
panels: [{ id: 1 }, { id: 2 }],
panels: [createPanelJSONFixture({ id: 1 }), createPanelJSONFixture({ id: 2 })],
})
)
);

@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { PanelPlugin } from '@grafana/data';
import { AngularComponent } from '@grafana/runtime';
import { Dashboard, defaultDashboard } from '@grafana/schema';
import { processAclItems } from 'app/core/utils/acl';
import { DashboardAclDTO, DashboardInitError, DashboardInitPhase, DashboardState } from 'app/types';
@ -36,7 +37,12 @@ const dashboardSlice = createSlice({
state.initPhase = DashboardInitPhase.Failed;
state.initError = action.payload;
state.getModel = () => {
return new DashboardModel({ title: 'Dashboard init failed' }, { canSave: false, canEdit: false });
// TODO this is a type conflict,
// we need to fix the defaultDashboard init type when generated by cue
return new DashboardModel(
{ ...(defaultDashboard as Dashboard), title: 'Dashboard init failed' },
{ canSave: false, canEdit: false }
);
};
},
cleanUpDashboard: (state) => {

@ -3,7 +3,8 @@ import config from 'app/core/config';
import * as actions from 'app/features/explore/state/main';
import { setStore } from 'app/store/store';
import { DashboardModel, PanelModel } from '../state';
import { PanelModel } from '../state';
import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures';
import { getPanelMenu } from './getPanelMenu';
@ -16,7 +17,7 @@ jest.mock('app/core/services/context_srv', () => ({
describe('getPanelMenu', () => {
it('should return the correct panel menu items', () => {
const panel = new PanelModel({});
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture({});
const menuItems = getPanelMenu(dashboard, panel);
expect(menuItems).toMatchInlineSnapshot(`
@ -100,7 +101,7 @@ describe('getPanelMenu', () => {
const scope: any = { $$childHead: { ctrl } };
const angularComponent: any = { getScope: () => scope };
const panel = new PanelModel({ isViewing: true });
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture({});
const menuItems = getPanelMenu(dashboard, panel, angularComponent);
expect(menuItems).toMatchInlineSnapshot(`
@ -171,7 +172,7 @@ describe('getPanelMenu', () => {
beforeAll(() => {
const panel = new PanelModel({});
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture({});
const menuItems = getPanelMenu(dashboard, panel);
explore = menuItems.find((item) => item.text === 'Explore') as PanelMenuItem;
navigateSpy = jest.spyOn(actions, 'navigateToExplore');

@ -1,6 +1,8 @@
import { PanelModel } from '@grafana/data';
import { FieldColorModeId, ThresholdsMode } from '@grafana/schema/src';
import { DashboardModel } from '../state/DashboardModel';
import { createDashboardModelFixture, createPanelJSONFixture } from '../state/__fixtures__/dashboardFixtures';
describe('Merge dashbaord panels', () => {
describe('simple changes', () => {
@ -8,35 +10,35 @@ describe('Merge dashbaord panels', () => {
let rawPanels: PanelModel[];
beforeEach(() => {
dashboard = new DashboardModel({
dashboard = createDashboardModelFixture({
title: 'simple title',
panels: [
{
createPanelJSONFixture({
id: 1,
type: 'timeseries',
},
{
}),
createPanelJSONFixture({
id: 2,
type: 'timeseries',
},
{
}),
createPanelJSONFixture({
id: 3,
type: 'table',
fieldConfig: {
defaults: {
thresholds: {
mode: 'absolute',
mode: ThresholdsMode.Absolute,
steps: [
{ color: 'green', value: -Infinity }, // save model has this as null
{ color: 'red', value: 80 },
],
},
mappings: [],
color: { mode: 'thresholds' },
color: { mode: FieldColorModeId.Thresholds },
},
overrides: [],
},
},
}),
],
});
rawPanels = dashboard.getSaveModelClone().panels;

@ -1,4 +1,5 @@
import { DataQuery, MutableDataFrame } from '@grafana/data';
import { defaultDashboard } from '@grafana/schema';
import { backendSrv } from 'app/core/services/backend_srv';
import * as api from 'app/features/dashboard/state/initDashboard';
import { ExplorePanelData } from 'app/types';
@ -53,6 +54,7 @@ describe('addPanelToDashboard', () => {
const existingPanel = { prop: 'this should be kept' };
jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
dashboard: {
...defaultDashboard,
templating: { list: [] },
title: 'Previous panels should not be removed',
uid: 'someUid',

@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
import { DataQuery } from '@grafana/data';
import { locationService, setEchoSrv } from '@grafana/runtime';
import { defaultDashboard } from '@grafana/schema';
import { backendSrv } from 'app/core/services/backend_srv';
import { contextSrv } from 'app/core/services/context_srv';
import { Echo } from 'app/core/services/echo/Echo';
@ -194,7 +195,7 @@ describe('AddToDashboardButton', () => {
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
dashboard: { templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
dashboard: { ...defaultDashboard, templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
meta: {},
});
jest.spyOn(backendSrv, 'search').mockResolvedValue([
@ -236,7 +237,7 @@ describe('AddToDashboardButton', () => {
const pushSpy = jest.spyOn(locationService, 'push');
jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
dashboard: { templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
dashboard: { ...defaultDashboard, templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
meta: {},
});
jest.spyOn(backendSrv, 'search').mockResolvedValue([

@ -1,5 +1,6 @@
import { lastValueFrom } from 'rxjs';
import { defaultDashboard } from '@grafana/schema';
import { DashboardModel } from 'app/features/dashboard/state';
import { getBackendSrv } from '../../../core/services/backend_srv';
@ -59,6 +60,7 @@ export async function getLibraryPanel(uid: string, isHandled = false): Promise<L
// kinda heavy weight migration process!!!
const { result } = response.data;
const dash = new DashboardModel({
...defaultDashboard,
schemaVersion: 35, // should be saved in the library panel
panels: [result.model],
});

@ -7,7 +7,7 @@ import * as grafanaData from '@grafana/data';
import { setDataSourceSrv, setEchoSrv } from '@grafana/runtime';
import { Echo } from '../../../core/services/echo/Echo';
import { DashboardModel } from '../../dashboard/state/index';
import { createDashboardModelFixture } from '../../dashboard/state/__fixtures__/dashboardFixtures';
import {
createDashboardQueryRunner,
@ -30,7 +30,7 @@ jest.mock('app/core/config', () => ({
}),
}));
const dashboardModel = new DashboardModel({
const dashboardModel = createDashboardModelFixture({
panels: [{ id: 1, type: 'graph' }],
});

@ -10,7 +10,7 @@ import {
} from '@grafana/data';
import { MetaAnalyticsEventName, reportMetaAnalytics } from '@grafana/runtime';
import { DashboardModel } from '../../dashboard/state';
import { createDashboardModelFixture } from '../../dashboard/state/__fixtures__/dashboardFixtures';
import { emitDataRequestEvent } from './queryAnalytics';
@ -24,7 +24,7 @@ const datasource = {
uid: 'test',
} as DataSourceApi;
const dashboardModel = new DashboardModel(
const dashboardModel = createDashboardModelFixture(
{ id: 1, title: 'Test Dashboard', uid: 'test' },
{ folderTitle: 'Test Folder' }
);

@ -14,13 +14,13 @@ import { setEchoSrv } from '@grafana/runtime';
import { deepFreeze } from '../../../../test/core/redux/reducerTester';
import { Echo } from '../../../core/services/echo/Echo';
import { DashboardModel } from '../../dashboard/state/DashboardModel';
import { createDashboardModelFixture } from '../../dashboard/state/__fixtures__/dashboardFixtures';
import { runRequest } from './runRequest';
jest.mock('app/core/services/backend_srv');
const dashboardModel = new DashboardModel({
const dashboardModel = createDashboardModelFixture({
panels: [{ id: 1, type: 'graph' }],
});

@ -11,7 +11,7 @@ import { formatRegistry, FormatRegistryID, FormatVariable } from './formatRegist
export type CustomFormatterFn = (
value: unknown,
legacyVariableModel: VariableModel,
legacyVariableModel: Partial<VariableModel>,
legacyDefaultFormatter?: CustomFormatterFn
) => string;

@ -6,6 +6,7 @@ import { appEvents } from '../../../core/core';
import { notifyApp } from '../../../core/reducers/appNotification';
import { DashboardState } from '../../../types';
import { DashboardModel } from '../../dashboard/state';
import { createDashboardModelFixture } from '../../dashboard/state/__fixtures__/dashboardFixtures';
import { TemplateSrv } from '../../templating/template_srv';
import { variableAdapters } from '../adapters';
import { createConstantVariableAdapter } from '../constant/adapter';
@ -226,5 +227,5 @@ describe('when onTimeRangeUpdated is dispatched', () => {
});
function getDashboardModel(): DashboardModel {
return new DashboardModel({ schemaVersion: 9999 }); // ignore any schema migrations
return createDashboardModelFixture({ schemaVersion: 9999 }); // ignore any schema migrations
}

@ -1,7 +1,8 @@
import { TypedVariableModel } from '@grafana/data';
import { DashboardState, StoreState } from '../../../types';
import { DashboardModel, PanelModel } from '../../dashboard/state';
import { PanelModel } from '../../dashboard/state';
import { createDashboardModelFixture } from '../../dashboard/state/__fixtures__/dashboardFixtures';
import { initialState } from '../../dashboard/state/reducers';
import { variableAdapters } from '../adapters';
import { createConstantVariableAdapter } from '../constant/adapter';
@ -13,7 +14,7 @@ import { templateVarsChangedInUrl } from './actions';
import { getPreloadedState } from './helpers';
import { VariablesState } from './types';
const dashboardModel = new DashboardModel({});
const dashboardModel = createDashboardModelFixture({});
variableAdapters.setInit(() => [createCustomVariableAdapter(), createConstantVariableAdapter()]);

@ -8,6 +8,11 @@ import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardModel } from 'app/features/dashboard/state';
import {
createDashboardModelFixture,
createPanelJSONFixture,
} from '../../../features/dashboard/state/__fixtures__/dashboardFixtures';
import { DashboardQueryEditor } from './DashboardQueryEditor';
import { SHARED_DASHBOARD_QUERY } from './types';
@ -42,21 +47,21 @@ describe('DashboardQueryEditor', () => {
let mockDashboard: DashboardModel;
beforeEach(() => {
mockDashboard = new DashboardModel({
mockDashboard = createDashboardModelFixture({
panels: [
{
createPanelJSONFixture({
targets: [],
type: 'timeseries',
id: 1,
title: 'My first panel',
},
{
}),
createPanelJSONFixture({
targets: [],
id: 2,
type: 'timeseries',
title: 'Another panel',
},
{
}),
createPanelJSONFixture({
datasource: {
uid: SHARED_DASHBOARD_QUERY,
},
@ -64,7 +69,7 @@ describe('DashboardQueryEditor', () => {
id: 3,
type: 'timeseries',
title: 'A dashboard query panel',
},
}),
],
});
jest.spyOn(getDashboardSrv(), 'getCurrent').mockImplementation(() => mockDashboard);

@ -6,7 +6,7 @@ import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2';
import { DashboardModel } from '../../../../features/dashboard/state';
import { createDashboardModelFixture } from '../../../../features/dashboard/state/__fixtures__/dashboardFixtures';
import { graphDirective, GraphElement } from '../graph';
import { GraphCtrl } from '../module';
@ -1337,7 +1337,7 @@ describe('grafanaGraph', () => {
});
function getGraphElement({ canEdit, canMakeEditable }: { canEdit?: boolean; canMakeEditable?: boolean } = {}) {
const dashboard = new DashboardModel({});
const dashboard = createDashboardModelFixture({});
dashboard.events.on = jest.fn();
dashboard.meta.canEdit = canEdit;
dashboard.meta.canMakeEditable = canMakeEditable;

@ -1,6 +1,6 @@
import { DataQuery } from '@grafana/data';
import { Dashboard } from '@grafana/schema';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { VariableModel } from 'app/features/variables/types';
import { DashboardAcl } from './acl';
@ -60,12 +60,10 @@ export interface AnnotationsPermissions {
organization: AnnotationActions;
}
export interface DashboardDataDTO {
// FIXME: This should not override Dashboard types
export interface DashboardDataDTO extends Dashboard {
title: string;
uid: string;
templating: {
list: VariableModel[];
};
panels?: any[];
}

Loading…
Cancel
Save