diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index 9a7dbf5717e..cc68092fc2b 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -551,7 +551,9 @@ RowsLayoutRowKind: { RowsLayoutRowSpec: { title?: string - collapsed: bool + collapse?: bool + hideHeader?: bool + fillScreen?: bool conditionalRendering?: ConditionalRenderingGroupKind repeat?: RowRepeatOptions layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index 95cf7dac3a4..6a4950bfbb0 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -837,7 +837,9 @@ func NewDashboardRowsLayoutRowKind() *DashboardRowsLayoutRowKind { // +k8s:openapi-gen=true type DashboardRowsLayoutRowSpec struct { Title *string `json:"title,omitempty"` - Collapsed bool `json:"collapsed"` + Collapse *bool `json:"collapse,omitempty"` + HideHeader *bool `json:"hideHeader,omitempty"` + FillScreen *bool `json:"fillScreen,omitempty"` ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"` Repeat *DashboardRowRepeatOptions `json:"repeat,omitempty"` Layout DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind `json:"layout"` diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index bb10dc9f5e4..40863c013f1 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -3577,11 +3577,22 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardRowsLayoutRowSpec(ref common.Re Format: "", }, }, - "collapsed": { + "collapse": { SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", + Type: []string{"boolean"}, + Format: "", + }, + }, + "hideHeader": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "fillScreen": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", }, }, "conditionalRendering": { @@ -3600,7 +3611,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardRowsLayoutRowSpec(ref common.Re }, }, }, - Required: []string{"collapsed", "layout"}, + Required: []string{"layout"}, }, }, Dependencies: []string{ diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index bce9256027d..aab1c43860a 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -551,7 +551,9 @@ RowsLayoutRowKind: { RowsLayoutRowSpec: { title?: string - collapsed: bool + collapse?: bool + hideHeader?: bool + fillScreen?: bool repeat?: RowRepeatOptions conditionalRendering?: ConditionalRenderingGroupKind layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts index 3612df2dfb6..3d85d4a8cca 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts @@ -733,14 +733,15 @@ export const defaultRowsLayoutRowKind = (): RowsLayoutRowKind => ({ export interface RowsLayoutRowSpec { title?: string; - collapsed: boolean; + collapse?: boolean; + hideHeader?: boolean; + fillScreen?: boolean; repeat?: RowRepeatOptions; conditionalRendering?: ConditionalRenderingGroupKind; layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind; } export const defaultRowsLayoutRowSpec = (): RowsLayoutRowSpec => ({ - collapsed: false, layout: defaultGridLayoutKind(), }); diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts index b9420ff737b..2fa322ff296 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts @@ -688,14 +688,15 @@ export const defaultRowsLayoutRowKind = (): RowsLayoutRowKind => ({ export interface RowsLayoutRowSpec { title?: string; - collapsed: boolean; + collapse?: boolean; + hideHeader?: boolean; + fillScreen?: boolean; conditionalRendering?: ConditionalRenderingGroupKind; repeat?: RowRepeatOptions; layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind; } export const defaultRowsLayoutRowSpec = (): RowsLayoutRowSpec => ({ - collapsed: false, layout: defaultGridLayoutKind(), }); diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx index 12b916670c8..94b9b0e4f42 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx @@ -21,8 +21,8 @@ import { RowsLayoutManager } from './RowsLayoutManager'; export interface RowItemState extends SceneObjectState { layout: DashboardLayoutManager; title?: string; - isCollapsed?: boolean; - isHeaderHidden?: boolean; + collapse?: boolean; + hideHeader?: boolean; fillScreen?: boolean; conditionalRendering?: ConditionalRendering; } @@ -137,8 +137,8 @@ export class RowItem this.setState({ title }); } - public onHeaderHiddenToggle(isHeaderHidden = !this.state.isHeaderHidden) { - this.setState({ isHeaderHidden }); + public onHeaderHiddenToggle(hideHeader = !this.state.hideHeader) { + this.setState({ hideHeader }); } public onChangeFillScreen(fillScreen: boolean) { @@ -163,7 +163,7 @@ export class RowItem } public onCollapseToggle() { - this.setState({ isCollapsed: !this.state.isCollapsed }); + this.setState({ collapse: !this.state.collapse }); } private _getParentLayout(): RowsLayoutManager { diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx index 1abade8a396..394da98c40e 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx @@ -93,7 +93,7 @@ function RowTitleInput({ row }: { row: RowItem }) { } function RowHeaderSwitch({ row }: { row: RowItem }) { - const { isHeaderHidden = false } = row.useState(); + const { hideHeader: isHeaderHidden = false } = row.useState(); return row.onHeaderHiddenToggle()} />; } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx index 4e6eb2df022..7ddd553d072 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx @@ -19,7 +19,7 @@ import { RowItem } from './RowItem'; import { RowItemMenu } from './RowItemMenu'; export function RowItemRenderer({ model }: SceneComponentProps) { - const { layout, isCollapsed, fillScreen, isHeaderHidden } = model.useState(); + const { layout, collapse: isCollapsed, fillScreen, hideHeader: isHeaderHidden } = model.useState(); const isClone = useIsClone(model); const { isEditing } = useDashboardState(model); const isConditionallyHidden = useIsConditionallyHidden(model); diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemsEditor.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemsEditor.tsx index 641c4284ea0..7647694490f 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemsEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemsEditor.tsx @@ -23,12 +23,12 @@ function RowHeaderCheckboxMulti({ model }: { model: RowItems }) { let indeterminate = false; for (let i = 0; i < rows.length; i++) { - const { isHeaderHidden } = rows[i].useState(); + const { hideHeader } = rows[i].useState(); const prevElement = rows[i - 1]; - indeterminate = indeterminate || (prevElement && !!prevElement.state.isHeaderHidden !== !!isHeaderHidden); + indeterminate = indeterminate || (prevElement && !!prevElement.state.hideHeader !== !!hideHeader); - value = value || !!isHeaderHidden; + value = value || !!hideHeader; } return ( diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 15e3efbcd31..41977255db0 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -261,7 +261,7 @@ export class RowsLayoutManager extends SceneObjectBase i (rowConfig) => new RowItem({ title: rowConfig.title, - isCollapsed: !!rowConfig.isCollapsed, + collapse: !!rowConfig.isCollapsed, layout: DefaultGridLayoutManager.fromGridItems( rowConfig.children, rowConfig.isDraggable, diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts index 426a277a7a2..77bd8f4c60e 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts @@ -20,7 +20,7 @@ describe('deserialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Row 1', - collapsed: false, + collapse: false, layout: { kind: 'GridLayout', spec: { items: [] } }, }, }, @@ -33,6 +33,33 @@ describe('deserialization', () => { expect(deserialized.state.rows[0].state.layout).toBeInstanceOf(DefaultGridLayoutManager); }); + it('should deserialize rows layout with collapse and hideHeader properly set', () => { + const layout: DashboardV2Spec['layout'] = { + kind: 'RowsLayout', + spec: { + rows: [ + { + kind: 'RowsLayoutRow', + spec: { + title: 'Row 1', + collapse: true, + hideHeader: true, + fillScreen: true, + layout: { kind: 'GridLayout', spec: { items: [] } }, + }, + }, + ], + }, + }; + const serializer = new RowsLayoutSerializer(); + const deserialized = serializer.deserialize(layout, {}, false); + expect(deserialized).toBeInstanceOf(RowsLayoutManager); + expect(deserialized.state.rows[0].state.layout).toBeInstanceOf(DefaultGridLayoutManager); + expect(deserialized.state.rows[0].state.collapse).toBe(true); + expect(deserialized.state.rows[0].state.hideHeader).toBe(true); + expect(deserialized.state.rows[0].state.fillScreen).toBe(true); + }); + it('should deserialize rows layout with responsive grid child', () => { const layout: DashboardV2Spec['layout'] = { kind: 'RowsLayout', @@ -42,7 +69,7 @@ describe('deserialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Row 1', - collapsed: false, + collapse: false, layout: { kind: 'ResponsiveGridLayout', spec: { @@ -72,7 +99,9 @@ describe('deserialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Row 1', - collapsed: false, + collapse: false, + hideHeader: undefined, + fillScreen: undefined, layout: { kind: 'ResponsiveGridLayout', spec: { @@ -88,7 +117,9 @@ describe('deserialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Row 2', - collapsed: true, + collapse: true, + hideHeader: undefined, + fillScreen: undefined, layout: { kind: 'GridLayout', spec: { items: [] } }, }, }, @@ -101,8 +132,8 @@ describe('deserialization', () => { expect(deserialized.state.rows).toHaveLength(2); expect(deserialized.state.rows[0].state.layout).toBeInstanceOf(ResponsiveGridLayoutManager); expect(deserialized.state.rows[1].state.layout).toBeInstanceOf(DefaultGridLayoutManager); - expect(deserialized.state.rows[0].state.isCollapsed).toBe(false); - expect(deserialized.state.rows[1].state.isCollapsed).toBe(true); + expect(deserialized.state.rows[0].state.collapse).toBe(false); + expect(deserialized.state.rows[1].state.collapse).toBe(true); }); it('should handle 0 rows', () => { @@ -127,7 +158,9 @@ describe('deserialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Repeated Row', - collapsed: false, + collapse: false, + hideHeader: undefined, + fillScreen: undefined, layout: { kind: 'GridLayout', spec: { items: [] } }, repeat: { value: 'foo', mode: 'variable' }, }, @@ -157,7 +190,7 @@ describe('serialization', () => { rows: [ new RowItem({ title: 'Row 1', - isCollapsed: false, + collapse: false, layout: new DefaultGridLayoutManager({ grid: new SceneGridLayout({ children: [], @@ -180,7 +213,50 @@ describe('serialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Row 1', - collapsed: false, + collapse: false, + hideHeader: undefined, + fillScreen: undefined, + layout: { kind: 'GridLayout', spec: { items: [] } }, + }, + }, + ], + }, + }); + }); + + it('should serialize basic row layout with collapse and hideHeader', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [ + new RowItem({ + title: 'Row 1', + collapse: true, + hideHeader: true, + fillScreen: true, + layout: new DefaultGridLayoutManager({ + grid: new SceneGridLayout({ + children: [], + isDraggable: true, + isResizable: true, + }), + }), + }), + ], + }); + + const serializer = new RowsLayoutSerializer(); + const serialized = serializer.serialize(rowsLayout); + + expect(serialized).toEqual({ + kind: 'RowsLayout', + spec: { + rows: [ + { + kind: 'RowsLayoutRow', + spec: { + title: 'Row 1', + collapse: true, + hideHeader: true, + fillScreen: true, layout: { kind: 'GridLayout', spec: { items: [] } }, }, }, @@ -194,7 +270,7 @@ describe('serialization', () => { rows: [ new RowItem({ title: 'Repeated Row', - isCollapsed: false, + collapse: false, layout: new DefaultGridLayoutManager({ grid: new SceneGridLayout({ children: [], @@ -218,7 +294,9 @@ describe('serialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Repeated Row', - collapsed: false, + collapse: false, + hideHeader: undefined, + fillScreen: undefined, layout: { kind: 'GridLayout', spec: { items: [] } }, repeat: { value: 'foo', mode: 'variable' }, }, @@ -233,7 +311,9 @@ describe('serialization', () => { rows: [ new RowItem({ title: 'Row 1', - isCollapsed: false, + collapse: false, + hideHeader: undefined, + fillScreen: undefined, layout: new ResponsiveGridLayoutManager({ columnWidth: 'standard', rowHeight: 'standard', @@ -243,7 +323,7 @@ describe('serialization', () => { }), new RowItem({ title: 'Row 2', - isCollapsed: true, + collapse: true, layout: new DefaultGridLayoutManager({ grid: new SceneGridLayout({ children: [], @@ -266,7 +346,9 @@ describe('serialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Row 1', - collapsed: false, + collapse: false, + hideHeader: undefined, + fillScreen: undefined, layout: { kind: 'ResponsiveGridLayout', spec: { @@ -285,7 +367,9 @@ describe('serialization', () => { kind: 'RowsLayoutRow', spec: { title: 'Row 2', - collapsed: true, + collapse: true, + hideHeader: undefined, + fillScreen: undefined, layout: { kind: 'GridLayout', spec: { items: [] } }, }, }, diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts index c403b5a3d7a..0f06109d0e1 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts @@ -20,8 +20,10 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer { kind: 'RowsLayoutRow', spec: { title: row.state.title, - collapsed: row.state.isCollapsed ?? false, + collapse: row.state.collapse, layout: layout, + fillScreen: row.state.fillScreen, + hideHeader: row.state.hideHeader, }, }; @@ -64,7 +66,9 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer { return new RowItem({ title: row.spec.title, - isCollapsed: row.spec.collapsed, + collapse: row.spec.collapse, + hideHeader: row.spec.hideHeader, + fillScreen: row.spec.fillScreen, $behaviors: behaviors, layout: layoutSerializerRegistry.get(layout.kind).serializer.deserialize(layout, elements, preload), conditionalRendering: getConditionalRendering(row), diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts index fd16165b8bd..c4bf583c66a 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts @@ -593,7 +593,7 @@ describe('transformSaveModelSchemaV2ToScene', () => { kind: 'RowsLayoutRow', spec: { title: 'row1', - collapsed: false, + collapse: false, layout: { kind: 'ResponsiveGridLayout', spec: { @@ -619,7 +619,7 @@ describe('transformSaveModelSchemaV2ToScene', () => { kind: 'RowsLayoutRow', spec: { title: 'row2', - collapsed: true, + collapse: true, layout: { kind: 'GridLayout', spec: {