diff --git a/src/plugins/content_management/public/components/card_container/card_list.tsx b/src/plugins/content_management/public/components/card_container/card_list.tsx index 871c6451a8cb..37cf18adb048 100644 --- a/src/plugins/content_management/public/components/card_container/card_list.tsx +++ b/src/plugins/content_management/public/components/card_container/card_list.tsx @@ -22,6 +22,27 @@ interface Props { } const CardListInner = ({ embeddable, input, embeddableServices }: Props) => { + if (input.columns) { + const width = `${(1 / input.columns) * 100}%`; + const cards = Object.values(input.panels).map((panel) => { + const child = embeddable.getChild(panel.explicitInput.id); + return ( + + + + ); + }); + return ( + + {cards} + + ); + } + const cards = Object.values(input.panels).map((panel) => { const child = embeddable.getChild(panel.explicitInput.id); return ( @@ -31,10 +52,9 @@ const CardListInner = ({ embeddable, input, embeddableServices }: Props) => { ); }); - // TODO: we should perhaps display the cards in multiple rows when the actual number of cards exceed the column size return ( - - {input.columns ? cards.slice(0, input.columns) : cards} + + {cards} ); }; diff --git a/src/plugins/content_management/public/components/card_container/types.ts b/src/plugins/content_management/public/components/card_container/types.ts index 5c6ad129af66..8f4ea7855cab 100644 --- a/src/plugins/content_management/public/components/card_container/types.ts +++ b/src/plugins/content_management/public/components/card_container/types.ts @@ -13,7 +13,10 @@ export interface CardExplicitInput { getFooter?: () => React.ReactElement; } -export type CardContainerInput = ContainerInput & { columns?: number }; +export type CardContainerInput = ContainerInput & { + columns?: number; + wrap?: boolean; +}; /** * The props which allow to be updated after card container was created diff --git a/src/plugins/content_management/public/components/section_input.test.ts b/src/plugins/content_management/public/components/section_input.test.ts index be471c8428a2..80fe5ce0863c 100644 --- a/src/plugins/content_management/public/components/section_input.test.ts +++ b/src/plugins/content_management/public/components/section_input.test.ts @@ -5,7 +5,7 @@ import { coreMock } from '../../../../core/public/mocks'; import { Content, Section } from '../services'; -import { createCardInput, createDashboardInput } from './section_input'; +import { DASHBOARD_PANEL_WIDTH, createCardInput, createDashboardInput } from './section_input'; test('it should create card section input', () => { const section: Section = { id: 'section1', kind: 'card', order: 10 }; @@ -363,3 +363,119 @@ test('it should create section with a dynamic dashboard as content', async () => }, }); }); + +test('it renders content with custom width and height', async () => { + const customWidth = 24; + const customHeight = 20; + const section: Section = { id: 'section1', kind: 'dashboard', order: 10 }; + const staticViz: Content = { + id: 'content1', + kind: 'visualization', + order: 0, + width: customWidth, + height: customHeight, + input: { + kind: 'static', + id: 'viz-id-static', + }, + }; + + const clientMock = coreMock.createStart().savedObjects.client; + const input = await createDashboardInput(section, [staticViz], { + savedObjectsClient: clientMock, + }); + + expect(input.panels).toEqual({ + content1: { + explicitInput: { + disabledActions: ['togglePanel'], + id: 'content1', + savedObjectId: 'viz-id-static', + }, + gridData: { + i: 'content1', + h: customHeight, + w: customWidth, + x: 0, + y: 0, + }, + type: 'visualization', + }, + }); +}); + +test('it uses default width if custom content width is <= 0', async () => { + const customWidthShouldBeBiggerThan0 = 0; + const section: Section = { id: 'section1', kind: 'dashboard', order: 10 }; + const staticViz: Content = { + id: 'content1', + kind: 'visualization', + order: 0, + width: customWidthShouldBeBiggerThan0, + input: { + kind: 'static', + id: 'viz-id-static', + }, + }; + + const clientMock = coreMock.createStart().savedObjects.client; + const input = await createDashboardInput(section, [staticViz], { + savedObjectsClient: clientMock, + }); + + expect(input.panels).toEqual({ + content1: { + explicitInput: { + disabledActions: ['togglePanel'], + id: 'content1', + savedObjectId: 'viz-id-static', + }, + gridData: { + h: 15, + i: 'content1', + w: DASHBOARD_PANEL_WIDTH, + x: 0, + y: 0, + }, + type: 'visualization', + }, + }); +}); + +test('it should use default width if custom content width > DASHBOARD_GRID_COLUMN_COUNT: 48', async () => { + const customWidthShouldNotBeBiggerThan0 = 49; + const section: Section = { id: 'section1', kind: 'dashboard', order: 10 }; + const staticViz: Content = { + id: 'content1', + kind: 'visualization', + order: 0, + width: customWidthShouldNotBeBiggerThan0, + input: { + kind: 'static', + id: 'viz-id-static', + }, + }; + + const clientMock = coreMock.createStart().savedObjects.client; + const input = await createDashboardInput(section, [staticViz], { + savedObjectsClient: clientMock, + }); + + expect(input.panels).toEqual({ + content1: { + explicitInput: { + disabledActions: ['togglePanel'], + id: 'content1', + savedObjectId: 'viz-id-static', + }, + gridData: { + h: 15, + i: 'content1', + w: DASHBOARD_PANEL_WIDTH, + x: 0, + y: 0, + }, + type: 'visualization', + }, + }); +}); diff --git a/src/plugins/content_management/public/components/section_input.ts b/src/plugins/content_management/public/components/section_input.ts index 5a0b9ca370c6..7d49f8a17ef9 100644 --- a/src/plugins/content_management/public/components/section_input.ts +++ b/src/plugins/content_management/public/components/section_input.ts @@ -14,6 +14,8 @@ import { CARD_EMBEDDABLE } from './card_container/card_embeddable'; import { CardContainerInput } from './card_container/types'; const DASHBOARD_GRID_COLUMN_COUNT = 48; +export const DASHBOARD_PANEL_WIDTH = 12; +export const DASHBOARD_PANEL_HEIGHT = 15; export const createCardInput = ( section: Section, @@ -31,6 +33,7 @@ export const createCardInput = ( hidePanelTitles: true, viewMode: ViewMode.VIEW, columns: section.columns, + wrap: section.wrap, panels, ...section.input, }; @@ -71,12 +74,26 @@ export const createDashboardInput = async ( const panels: DashboardContainerInput['panels'] = {}; let x = 0; let y = 0; - const w = 12; - const h = 15; const counter = new BehaviorSubject(0); contents.forEach(async (content) => { counter.next(counter.value + 1); + + let w = DASHBOARD_PANEL_WIDTH; + let h = DASHBOARD_PANEL_HEIGHT; + + if ('width' in content && typeof content.width === 'number') { + if (content.width > 0 && content.width <= DASHBOARD_GRID_COLUMN_COUNT) { + w = content.width; + } + } + + if ('height' in content && typeof content.height === 'number') { + if (content.height > 0) { + h = content.height; + } + } + try { if (content.kind === 'dashboard') { let dashboardId = ''; @@ -120,7 +137,13 @@ export const createDashboardInput = async ( return; } - const config: DashboardContainerInput['panels'][string] = { + // If current panel exceed the max dashboard container width, add the panel to the next row + if (x + w > DASHBOARD_GRID_COLUMN_COUNT) { + x = 0; + y = y + h; + } + + const panelConfig: DashboardContainerInput['panels'][string] = { gridData: { w, h, @@ -135,28 +158,25 @@ export const createDashboardInput = async ( }, }; + // The new x starts from the current panel x + current panel width x = x + w; - if (x >= DASHBOARD_GRID_COLUMN_COUNT) { - x = 0; - y = y + h; - } if (content.kind === 'visualization') { - config.type = 'visualization'; + panelConfig.type = 'visualization'; if (content.input.kind === 'dynamic') { - config.explicitInput.savedObjectId = await content.input.get(); + panelConfig.explicitInput.savedObjectId = await content.input.get(); } if (content.input.kind === 'static') { - config.explicitInput.savedObjectId = content.input.id; + panelConfig.explicitInput.savedObjectId = content.input.id; } } if (content.kind === 'custom') { - config.type = CUSTOM_CONTENT_EMBEDDABLE; - config.explicitInput.render = content.render; + panelConfig.type = CUSTOM_CONTENT_EMBEDDABLE; + panelConfig.explicitInput.render = content.render; } - panels[content.id] = config; + panels[content.id] = panelConfig; } catch (e) { // eslint-disable-next-line console.log(e); diff --git a/src/plugins/content_management/public/services/content_management/types.ts b/src/plugins/content_management/public/services/content_management/types.ts index 5b9b4662d37f..755e6f426a0a 100644 --- a/src/plugins/content_management/public/services/content_management/types.ts +++ b/src/plugins/content_management/public/services/content_management/types.ts @@ -35,8 +35,9 @@ export type Section = id: string; order: number; title?: string; - columns?: number; input?: CardContainerExplicitInput; + columns?: number; + wrap?: boolean; }; export type Content = @@ -45,18 +46,24 @@ export type Content = id: string; order: number; input: SavedObjectInput; + width?: number; + height?: number; } | { kind: 'dashboard'; id: string; order: number; input: SavedObjectInput; + width?: number; + height?: number; } | { kind: 'custom'; id: string; order: number; render: () => JSX.Element; + width?: number; + height?: number; } | { kind: 'card';