-
Notifications
You must be signed in to change notification settings - Fork 893
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New core plugin for dynamic content rendering (#7201)
* feat: add content management plugin Signed-off-by: Yulong Ruan <[email protected]> * fix: add license header Signed-off-by: Yulong Ruan <[email protected]> * add card container for rendering content as cards Signed-off-by: Yulong Ruan <[email protected]> * rename GetStartedCard -> CardList Signed-off-by: Yulong Ruan <[email protected]> * content management supporting embedding dashboards Signed-off-by: Yulong Ruan <[email protected]> * rename interface VisualizationInput -> SavedObjectInput Signed-off-by: Yulong Ruan <[email protected]> * cleanup and refactor Signed-off-by: Yulong Ruan <[email protected]> * fix license header Signed-off-by: Yulong Ruan <[email protected]> * add unit test for content management plugin Signed-off-by: Yulong Ruan <[email protected]> * Add a todo to cleanup demo code Signed-off-by: Yulong Ruan <[email protected]> * refactor: decouple content and page with content provider Signed-off-by: Yulong Ruan <[email protected]> * fix linter Signed-off-by: Yulong Ruan <[email protected]> * fix lint Signed-off-by: Yulong Ruan <[email protected]> * Changeset file for PR #7201 created/updated * pr feedback updates Signed-off-by: Yulong Ruan <[email protected]> * fix tests Signed-off-by: Yulong Ruan <[email protected]> * fix license header Signed-off-by: Yulong Ruan <[email protected]> * cleanup unused variable Signed-off-by: Yulong Ruan <[email protected]> * mark registerContentProvider API as experimental Signed-off-by: Yulong Ruan <[email protected]> --------- Signed-off-by: Yulong Ruan <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> (cherry picked from commit cc6949b) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
acbfcaf
commit 375e82b
Showing
44 changed files
with
1,953 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
feat: | ||
- Introduced an new plugin contentManagement for dynamic content rendering ([#7201](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7201)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"id": "contentManagement", | ||
"version": "opensearchDashboards", | ||
"server": false, | ||
"ui": true, | ||
"requiredPlugins": ["embeddable"], | ||
"optionalPlugins": [], | ||
"requiredBundles": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from 'react'; | ||
|
||
import { SavedObjectsClientContract } from 'opensearch-dashboards/public'; | ||
import { PageRender } from './components/page_render'; | ||
import { Page } from './services'; | ||
import { EmbeddableStart } from '../../embeddable/public'; | ||
|
||
export const renderPage = ({ | ||
page, | ||
embeddable, | ||
savedObjectsClient, | ||
}: { | ||
page: Page; | ||
embeddable: EmbeddableStart; | ||
savedObjectsClient: SavedObjectsClientContract; | ||
}) => { | ||
return <PageRender page={page} embeddable={embeddable} savedObjectsClient={savedObjectsClient} />; | ||
}; |
27 changes: 27 additions & 0 deletions
27
src/plugins/content_management/public/components/card_container/card_container.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from 'react'; | ||
import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; | ||
import { CardContainer } from './card_container'; | ||
|
||
jest.mock('./card_list', () => { | ||
return { | ||
CardList: jest.fn().mockReturnValue(<span id="mockCardList" />), | ||
}; | ||
}); | ||
|
||
test('CardContainer should render CardList', () => { | ||
const container = new CardContainer( | ||
{ id: 'container-id', panels: {} }, | ||
embeddablePluginMock.createStartContract() | ||
); | ||
const node = document.createElement('div'); | ||
container.render(node); | ||
expect(node.querySelector('#mockCardList')).toBeTruthy(); | ||
|
||
container.destroy(); | ||
expect(node.querySelector('#mockCardList')).toBeFalsy(); | ||
}); |
46 changes: 46 additions & 0 deletions
46
src/plugins/content_management/public/components/card_container/card_container.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import { Container, ContainerInput, EmbeddableStart } from '../../../../embeddable/public'; | ||
import { CardList } from './card_list'; | ||
|
||
export const CARD_CONTAINER = 'CARD_CONTAINER'; | ||
|
||
export type CardContainerInput = ContainerInput<{ description: string; onClick?: () => void }>; | ||
|
||
export class CardContainer extends Container<{}, CardContainerInput> { | ||
public readonly type = CARD_CONTAINER; | ||
private node?: HTMLElement; | ||
|
||
constructor(input: CardContainerInput, private embeddableServices: EmbeddableStart) { | ||
super(input, { embeddableLoaded: {} }, embeddableServices.getEmbeddableFactory); | ||
} | ||
|
||
getInheritedInput() { | ||
return { | ||
viewMode: this.input.viewMode, | ||
}; | ||
} | ||
|
||
public render(node: HTMLElement) { | ||
if (this.node) { | ||
ReactDOM.unmountComponentAtNode(this.node); | ||
} | ||
this.node = node; | ||
ReactDOM.render( | ||
<CardList embeddable={this} embeddableServices={this.embeddableServices} />, | ||
node | ||
); | ||
} | ||
|
||
public destroy() { | ||
super.destroy(); | ||
if (this.node) { | ||
ReactDOM.unmountComponentAtNode(this.node); | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...lugins/content_management/public/components/card_container/card_container_factory.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; | ||
import { CARD_CONTAINER, CardContainer } from './card_container'; | ||
import { CardContainerFactoryDefinition } from './card_container_factory'; | ||
|
||
test('CardContainerFactoryDefinition', async () => { | ||
const getStartServices = jest | ||
.fn() | ||
.mockResolvedValue({ embeddableServices: embeddablePluginMock.createStartContract() }); | ||
const factory = new CardContainerFactoryDefinition(getStartServices); | ||
expect(factory.type).toBe(CARD_CONTAINER); | ||
expect(factory.isContainerType).toBe(true); | ||
expect(await factory.isEditable()).toBe(false); | ||
expect(factory.getDisplayName()).toBe('Card container'); | ||
expect(await factory.create({ id: 'card-id', panels: {} })).toBeInstanceOf(CardContainer); | ||
}); |
42 changes: 42 additions & 0 deletions
42
src/plugins/content_management/public/components/card_container/card_container_factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { i18n } from '@osd/i18n'; | ||
|
||
import { | ||
EmbeddableFactoryDefinition, | ||
EmbeddableStart, | ||
EmbeddableFactory, | ||
ContainerOutput, | ||
} from '../../../../embeddable/public'; | ||
import { CARD_CONTAINER, CardContainer, CardContainerInput } from './card_container'; | ||
|
||
interface StartServices { | ||
embeddableServices: EmbeddableStart; | ||
} | ||
|
||
export type CardContainerFactory = EmbeddableFactory<CardContainerInput, ContainerOutput>; | ||
export class CardContainerFactoryDefinition | ||
implements EmbeddableFactoryDefinition<CardContainerInput, ContainerOutput> { | ||
public readonly type = CARD_CONTAINER; | ||
public readonly isContainerType = true; | ||
|
||
constructor(private getStartServices: () => Promise<StartServices>) {} | ||
|
||
public async isEditable() { | ||
return false; | ||
} | ||
|
||
public create = async (initialInput: CardContainerInput) => { | ||
const { embeddableServices } = await this.getStartServices(); | ||
return new CardContainer(initialInput, embeddableServices); | ||
}; | ||
|
||
public getDisplayName() { | ||
return i18n.translate('contentManagement.cardContainer.displayName', { | ||
defaultMessage: 'Card container', | ||
}); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
src/plugins/content_management/public/components/card_container/card_embeddable.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { CardEmbeddable } from './card_embeddable'; | ||
|
||
test('CardEmbeddable should render a card with the title', () => { | ||
const embeddable = new CardEmbeddable({ id: 'card-id', title: 'card title', description: '' }); | ||
|
||
const node = document.createElement('div'); | ||
embeddable.render(node); | ||
|
||
// it should render the card with title specified | ||
expect( | ||
Array.from(node.querySelectorAll('*')).find((ele) => ele.textContent?.trim() === 'card title') | ||
).toBeTruthy(); | ||
|
||
embeddable.destroy(); | ||
expect( | ||
Array.from(node.querySelectorAll('*')).find((ele) => ele.textContent?.trim() === 'card title') | ||
).toBeFalsy(); | ||
}); |
47 changes: 47 additions & 0 deletions
47
src/plugins/content_management/public/components/card_container/card_embeddable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import { EuiCard } from '@elastic/eui'; | ||
|
||
import { Embeddable, EmbeddableInput, IContainer } from '../../../../embeddable/public'; | ||
|
||
export const CARD_EMBEDDABLE = 'card_embeddable'; | ||
export type CardEmbeddableInput = EmbeddableInput & { description: string; onClick?: () => void }; | ||
|
||
export class CardEmbeddable extends Embeddable<CardEmbeddableInput> { | ||
public readonly type = CARD_EMBEDDABLE; | ||
private node: HTMLElement | null = null; | ||
|
||
constructor(initialInput: CardEmbeddableInput, parent?: IContainer) { | ||
super(initialInput, {}, parent); | ||
} | ||
|
||
public render(node: HTMLElement) { | ||
if (this.node) { | ||
ReactDOM.unmountComponentAtNode(this.node); | ||
} | ||
this.node = node; | ||
ReactDOM.render( | ||
<EuiCard | ||
title={this.input.title ?? ''} | ||
description={this.input.description} | ||
display="plain" | ||
onClick={this.input.onClick} | ||
/>, | ||
node | ||
); | ||
} | ||
|
||
public destroy() { | ||
super.destroy(); | ||
if (this.node) { | ||
ReactDOM.unmountComponentAtNode(this.node); | ||
} | ||
} | ||
|
||
public reload() {} | ||
} |
17 changes: 17 additions & 0 deletions
17
...ugins/content_management/public/components/card_container/card_embeddable_factory.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { CARD_EMBEDDABLE, CardEmbeddable } from './card_embeddable'; | ||
import { CardEmbeddableFactoryDefinition } from './card_embeddable_factory'; | ||
|
||
test('create CardEmbeddableFactoryDefinition', async () => { | ||
const factory = new CardEmbeddableFactoryDefinition(); | ||
expect(factory.type).toBe(CARD_EMBEDDABLE); | ||
expect(factory.getDisplayName()).toBe('Card'); | ||
expect(await factory.isEditable()).toBe(false); | ||
expect(await factory.create({ id: 'card-id', title: 'title', description: '' })).toBeInstanceOf( | ||
CardEmbeddable | ||
); | ||
}); |
26 changes: 26 additions & 0 deletions
26
src/plugins/content_management/public/components/card_container/card_embeddable_factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { i18n } from '@osd/i18n'; | ||
import { EmbeddableFactoryDefinition, IContainer } from '../../../../embeddable/public'; | ||
import { CARD_EMBEDDABLE, CardEmbeddable, CardEmbeddableInput } from './card_embeddable'; | ||
|
||
export class CardEmbeddableFactoryDefinition implements EmbeddableFactoryDefinition { | ||
public readonly type = CARD_EMBEDDABLE; | ||
|
||
public async isEditable() { | ||
return false; | ||
} | ||
|
||
public async create(initialInput: CardEmbeddableInput, parent?: IContainer) { | ||
return new CardEmbeddable(initialInput, parent); | ||
} | ||
|
||
public getDisplayName() { | ||
return i18n.translate('contentManagement.embeddable.card', { | ||
defaultMessage: 'Card', | ||
}); | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
src/plugins/content_management/public/components/card_container/card_list.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
|
||
import { CardList } from './card_list'; | ||
import { CardContainer } from './card_container'; | ||
import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; | ||
import { CARD_EMBEDDABLE } from './card_embeddable'; | ||
|
||
beforeEach(() => { | ||
jest.restoreAllMocks(); | ||
}); | ||
|
||
test('render list of cards', () => { | ||
const embeddableStart = embeddablePluginMock.createStartContract(); | ||
jest | ||
.spyOn(embeddableStart, 'EmbeddablePanel') | ||
.mockImplementation(() => <span>CardEmbeddablePanel</span>); | ||
render( | ||
<CardList | ||
embeddableServices={embeddableStart} | ||
embeddable={ | ||
new CardContainer( | ||
{ | ||
id: 'card', | ||
panels: { | ||
'card-id-1': { type: CARD_EMBEDDABLE, explicitInput: { id: 'card-id-1' } }, | ||
'card-id-2': { type: CARD_EMBEDDABLE, explicitInput: { id: 'card-id-2' } }, | ||
}, | ||
}, | ||
embeddableStart | ||
) | ||
} | ||
/> | ||
); | ||
expect(screen.queryAllByText('CardEmbeddablePanel')).toHaveLength(2); | ||
}); |
44 changes: 44 additions & 0 deletions
44
src/plugins/content_management/public/components/card_container/card_list.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from 'react'; | ||
import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; | ||
|
||
import { | ||
IContainer, | ||
withEmbeddableSubscription, | ||
ContainerInput, | ||
ContainerOutput, | ||
EmbeddableStart, | ||
} from '../../../../embeddable/public'; | ||
|
||
interface Props { | ||
embeddable: IContainer; | ||
input: ContainerInput; | ||
embeddableServices: EmbeddableStart; | ||
} | ||
|
||
const CardListInner = ({ embeddable, input, embeddableServices }: Props) => { | ||
const cards = Object.values(input.panels).map((panel) => { | ||
const child = embeddable.getChild(panel.explicitInput.id); | ||
return ( | ||
<EuiFlexItem key={panel.explicitInput.id}> | ||
<embeddableServices.EmbeddablePanel embeddable={child} /> | ||
</EuiFlexItem> | ||
); | ||
}); | ||
return ( | ||
<EuiFlexGrid gutterSize="s" columns={4}> | ||
{cards} | ||
</EuiFlexGrid> | ||
); | ||
}; | ||
|
||
export const CardList = withEmbeddableSubscription< | ||
ContainerInput, | ||
ContainerOutput, | ||
IContainer, | ||
{ embeddableServices: EmbeddableStart } | ||
>(CardListInner); |
Oops, something went wrong.