diff --git a/CHANGELOG.md b/CHANGELOG.md index 0873a250d74..2b8c8d732ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [I18n] Register ru, ru-RU locale ([#2817](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2817)) - Add yarn opensearch arg to setup plugin dependencies ([#2544](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2544)) - [Multi DataSource] Test the connection to an external data source when creating or updating ([#2973](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2973)) +- Add Dashboards-list integrations for Plugins ([#3090](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3090) ) - [Table Visualization] Refactor table visualization using React and DataGrid component ([#2863](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2863)) - [Vis Builder] Add redux store persistence ([#3088](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3088)) - [Multi DataSource] Improve test connection ([#3110](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3110)) diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts index 6ac626ac7eb..e527565b3c4 100644 --- a/src/plugins/dashboard/public/application/application.ts +++ b/src/plugins/dashboard/public/application/application.ts @@ -45,6 +45,7 @@ import { AppMountParameters, } from 'opensearch-dashboards/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { DashboardProvider } from 'src/plugins/dashboard/public/types'; import { Storage } from '../../../opensearch_dashboards_utils/public'; // @ts-ignore import { initDashboardApp } from './legacy_app'; @@ -71,6 +72,7 @@ export interface RenderDeps { navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; savedDashboards: SavedObjectLoader; + dashboardProviders: () => { [key: string]: DashboardProvider }; dashboardConfig: OpenSearchDashboardsLegacyStart['dashboardConfig']; dashboardCapabilities: any; embeddableCapabilities: { @@ -79,7 +81,6 @@ export interface RenderDeps { }; uiSettings: IUiSettingsClient; chrome: ChromeStart; - addBasePath: (path: string) => string; savedQueryService: DataPublicPluginStart['query']['savedQueries']; embeddable: EmbeddableStart; localStorage: Storage; @@ -141,12 +142,11 @@ function createLocalAngularModule() { createLocalI18nModule(); createLocalIconModule(); - const dashboardAngularModule = angular.module(moduleName, [ + return angular.module(moduleName, [ ...thirdPartyAngularDependencies, 'app/dashboard/I18n', 'app/dashboard/icon', ]); - return dashboardAngularModule; } function createLocalIconModule() { diff --git a/src/plugins/dashboard/public/application/legacy_app.js b/src/plugins/dashboard/public/application/legacy_app.js index 14656799946..b6895814327 100644 --- a/src/plugins/dashboard/public/application/legacy_app.js +++ b/src/plugins/dashboard/public/application/legacy_app.js @@ -53,9 +53,10 @@ export function initDashboardApp(app, deps) { app.directive('dashboardListing', function (reactDirective) { return reactDirective(DashboardListing, [ ['core', { watchDepth: 'reference' }], + ['dashboardProviders', { watchDepth: 'reference' }], ['createItem', { watchDepth: 'reference' }], - ['getViewUrl', { watchDepth: 'reference' }], ['editItem', { watchDepth: 'reference' }], + ['viewItem', { watchDepth: 'reference' }], ['findItems', { watchDepth: 'reference' }], ['deleteItems', { watchDepth: 'reference' }], ['listingLimit', { watchDepth: 'reference' }], @@ -127,14 +128,47 @@ export function initDashboardApp(app, deps) { $scope.create = () => { history.push(DashboardConstants.CREATE_NEW_DASHBOARD_URL); }; - $scope.find = (search) => { - return service.find(search, $scope.listingLimit); + $scope.dashboardProviders = deps.dashboardProviders() || []; + $scope.dashboardListTypes = Object.keys($scope.dashboardProviders); + + const mapListAttributesToDashboardProvider = (obj) => { + const provider = $scope.dashboardProviders[obj.type]; + return { + id: obj.id, + appId: provider.appId, + type: provider.savedObjectsName, + ...obj.attributes, + updated_at: obj.updated_at, + viewUrl: provider.viewUrlPathFn(obj), + editUrl: provider.editUrlPathFn(obj), + }; }; - $scope.editItem = ({ id }) => { - history.push(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`); + + $scope.find = async (search) => { + const savedObjectsClient = deps.savedObjectsClient; + + const res = await savedObjectsClient.find({ + type: $scope.dashboardListTypes, + search: search ? `${search}*` : undefined, + fields: ['title', 'type', 'description', 'updated_at'], + perPage: $scope.initialPageSize, + page: 1, + searchFields: ['title^3', 'type', 'description'], + defaultSearchOperator: 'AND', + }); + const list = res.savedObjects?.map(mapListAttributesToDashboardProvider) || []; + + return { + total: list.length, + hits: list, + }; + }; + + $scope.editItem = ({ editUrl }) => { + history.push(deps.addBasePath(editUrl)); }; - $scope.getViewUrl = ({ id }) => { - return deps.addBasePath(`#${createDashboardEditUrl(id)}`); + $scope.viewItem = ({ viewUrl }) => { + history.push(deps.addBasePath(viewUrl)); }; $scope.delete = (dashboards) => { return service.delete(dashboards.map((d) => d.id)); diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/create_button.test.tsx.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/create_button.test.tsx.snap new file mode 100644 index 00000000000..1a803b90a3c --- /dev/null +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/create_button.test.tsx.snap @@ -0,0 +1,558 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`create button no props renders empty when no providers given 1`] = ` + +`; + +exports[`create button with props renders button dropdown menu when two providers given 1`] = ` + + +
+ + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="createMenuPopover" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > +
+
+ + + + + +
+
+
+
+
+
+`; + +exports[`create button with props renders single button when one provider given 1`] = ` + + +
+ + + + + + + + + + + Create + +   + TestModule + + + + + + +
+
+
+`; diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap index fd45b2291f9..8ad036fc120 100644 --- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap @@ -3,6 +3,7 @@ exports[`after fetch hideWriteControls 1`] = ` `; @@ -82,6 +90,7 @@ exports[`after fetch hideWriteControls 1`] = ` exports[`after fetch initialFilter 1`] = ` } createItem={[Function]} deleteItems={[Function]} editItem={[Function]} @@ -162,6 +171,12 @@ exports[`after fetch initialFilter 1`] = ` "render": [Function], "sortable": true, }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, Object { "dataType": "string", "field": "description", @@ -198,6 +213,7 @@ exports[`after fetch initialFilter 1`] = ` }, } } + viewItem={[Function]} /> `; @@ -205,6 +221,7 @@ exports[`after fetch initialFilter 1`] = ` exports[`after fetch renders call to action when no dashboards exist 1`] = ` } createItem={[Function]} deleteItems={[Function]} editItem={[Function]} @@ -285,6 +302,12 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` "render": [Function], "sortable": true, }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, Object { "dataType": "string", "field": "description", @@ -321,6 +344,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` }, } } + viewItem={[Function]} /> `; @@ -328,6 +352,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` exports[`after fetch renders table rows 1`] = ` } createItem={[Function]} deleteItems={[Function]} editItem={[Function]} @@ -408,6 +433,12 @@ exports[`after fetch renders table rows 1`] = ` "render": [Function], "sortable": true, }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, Object { "dataType": "string", "field": "description", @@ -444,6 +475,7 @@ exports[`after fetch renders table rows 1`] = ` }, } } + viewItem={[Function]} /> `; @@ -451,6 +483,7 @@ exports[`after fetch renders table rows 1`] = ` exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` } createItem={[Function]} deleteItems={[Function]} editItem={[Function]} @@ -531,6 +564,12 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` "render": [Function], "sortable": true, }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, Object { "dataType": "string", "field": "description", @@ -567,6 +606,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` }, } } + viewItem={[Function]} /> `; @@ -574,6 +614,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` } createItem={[Function]} deleteItems={[Function]} editItem={[Function]} @@ -582,6 +623,7 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` findItems={[Function]} headingId="dashboardListingHeading" initialFilter="" + initialPageSize={10} listingLimit={1000} noItemsFragment={
@@ -653,6 +695,12 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` "render": [Function], "sortable": true, }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, Object { "dataType": "string", "field": "description", @@ -689,6 +737,7 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` }, } } + viewItem={[Function]} /> `; diff --git a/src/plugins/dashboard/public/application/listing/create_button.test.tsx b/src/plugins/dashboard/public/application/listing/create_button.test.tsx new file mode 100644 index 00000000000..5d2a200f55d --- /dev/null +++ b/src/plugins/dashboard/public/application/listing/create_button.test.tsx @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { findTestSubject } from '@elastic/eui/lib/test'; + +import React from 'react'; + +import { CreateButton } from './create_button'; +import { DashboardProvider } from '../../types'; + +const provider = (type?: string, url?: string, text?: string): DashboardProvider => { + return { + appId: 'test', + savedObjectsType: type || 'test', + savedObjectsName: type || 'Test', + createUrl: url || 'createUrl', + createLinkText: text || 'TestModule', + createSortText: text || 'TestModule', + viewUrlPathFn: (id) => `/${type || 'test'}_plugin/${id}`, + editUrlPathFn: (id) => `/${type || 'test'}_plugin/${id}/edit`, + }; +}; + +function mountComponent(props?: any) { + return mountWithIntl(); +} + +describe('create button no props', () => { + test('renders empty when no providers given', () => { + const component = mountComponent(); + + expect(component).toMatchSnapshot(); + }); +}); +describe('create button with props', () => { + test('renders single button when one provider given', () => { + const component = mountComponent({ dashboardProviders: [provider()] }); + expect(component).toMatchSnapshot(); + const links = findTestSubject(component, 'newItemButton'); + expect(links.length).toBe(1); + }); + test('renders button dropdown menu when two providers given', () => { + const provider1 = provider('test1', 'test1', 'test1'); + const provider2 = provider('test2', 'test2', 'test2'); + const component = mountComponent({ dashboardProviders: [provider2, provider1] }); + expect(component).toMatchSnapshot(); + const createButtons = findTestSubject(component, 'newItemButton'); + expect(createButtons.length).toBe(0); + const createDropdown = findTestSubject(component, 'createMenuDropdown'); + createDropdown.simulate('click'); + const contextMenus = findTestSubject(component, 'contextMenuItem'); + expect(contextMenus.length).toBe(2); + expect(contextMenus.at(0).prop('href')).toBe('test1'); + }); +}); diff --git a/src/plugins/dashboard/public/application/listing/create_button.tsx b/src/plugins/dashboard/public/application/listing/create_button.tsx new file mode 100644 index 00000000000..04e6df88377 --- /dev/null +++ b/src/plugins/dashboard/public/application/listing/create_button.tsx @@ -0,0 +1,107 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import React, { useState } from 'react'; +import { FormattedMessage } from '@osd/i18n/react'; +import { + EuiButton, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexItem, + EuiPopover, +} from '@elastic/eui'; +import type { DashboardProvider } from '../../types'; + +interface CreateButtonProps { + dashboardProviders?: DashboardProvider[]; +} + +const CreateButton = (props: CreateButtonProps) => { + const [isPopoverOpen, setPopover] = useState(false); + + const onMenuButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const getPopupMenuItems = () => { + const providers = Object.values(props.dashboardProviders || {}); + return providers + .sort((a: DashboardProvider, b: DashboardProvider) => + a.createSortText.localeCompare(b.createSortText) + ) + .map((provider: DashboardProvider) => ( + + {provider.createLinkText} + + )); + }; + + const renderCreateMenuDropDown = () => { + const button = ( + + + + ); + + return ( + + + + + + ); + }; + + const renderCreateSingleButton = () => { + const provider: DashboardProvider = Object.values(props.dashboardProviders!)[0]; + return ( + + + +  {provider.createLinkText} + + + ); + }; + + const renderMenu = () => { + if (!props.dashboardProviders || Object.keys(props.dashboardProviders!).length === 0) { + return null; + } else if (Object.keys(props.dashboardProviders!).length === 1) { + return renderCreateSingleButton(); + } else { + return renderCreateMenuDropDown(); + } + }; + + return renderMenu(); +}; + +export { CreateButton }; diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.js b/src/plugins/dashboard/public/application/listing/dashboard_listing.js index 1864c2852ae..7e43bc96faf 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.js +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.js @@ -37,6 +37,7 @@ import { i18n } from '@osd/i18n'; import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { TableListView } from '../../../../opensearch_dashboards_react/public'; +import { CreateButton } from './create_button'; export const EMPTY_FILTER = ''; @@ -56,9 +57,15 @@ export class DashboardListing extends React.Component { + ) + } findItems={this.props.findItems} deleteItems={this.props.hideWriteControls ? null : this.props.deleteItems} editItem={this.props.hideWriteControls ? null : this.props.editItem} + viewItem={this.props.hideWriteControls ? null : this.props.viewItem} tableColumns={this.getTableColumns()} listingLimit={this.props.listingLimit} initialFilter={this.props.initialFilter} @@ -163,7 +170,8 @@ export class DashboardListing extends React.Component { getTableColumns() { const dateFormat = this.props.core.uiSettings.get('dateFormat'); - const tableColumns = [ + + return [ { field: 'title', name: i18n.translate('dashboard.listing.table.titleColumnName', { @@ -172,13 +180,21 @@ export class DashboardListing extends React.Component { sortable: true, render: (field, record) => ( {field} ), }, + { + field: 'type', + name: i18n.translate('dashboard.listing.table.typeColumnName', { + defaultMessage: 'Type', + }), + dataType: 'string', + sortable: true, + }, { field: 'description', name: i18n.translate('dashboard.listing.table.descriptionColumnName', { @@ -201,16 +217,18 @@ export class DashboardListing extends React.Component { render: (updatedAt) => updatedAt && moment(updatedAt).format(dateFormat), }, ]; - return tableColumns; } } DashboardListing.propTypes = { - createItem: PropTypes.func.isRequired, + createItem: PropTypes.func, + dashboardProviders: PropTypes.object, findItems: PropTypes.func.isRequired, deleteItems: PropTypes.func.isRequired, editItem: PropTypes.func.isRequired, - getViewUrl: PropTypes.func.isRequired, + getViewUrl: PropTypes.func, + editItemAvailable: PropTypes.func, + viewItem: PropTypes.func, listingLimit: PropTypes.number.isRequired, hideWriteControls: PropTypes.bool.isRequired, initialFilter: PropTypes.string, diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js index bb469003da0..7bce8de4208 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js @@ -70,7 +70,10 @@ test('renders empty page in before initial fetch to avoid flickering', () => { deleteItems={() => {}} createItem={() => {}} editItem={() => {}} - getViewUrl={() => {}} + viewItem={() => {}} + dashboardItemCreatorClickHandler={() => {}} + dashboardItemCreators={() => []} + initialPageSize={10} listingLimit={1000} hideWriteControls={false} core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} @@ -87,7 +90,9 @@ describe('after fetch', () => { deleteItems={() => {}} createItem={() => {}} editItem={() => {}} - getViewUrl={() => {}} + viewItem={() => {}} + dashboardItemCreatorClickHandler={() => {}} + dashboardItemCreators={() => []} listingLimit={1000} hideWriteControls={false} initialPageSize={10} @@ -111,7 +116,9 @@ describe('after fetch', () => { deleteItems={() => {}} createItem={() => {}} editItem={() => {}} - getViewUrl={() => {}} + viewItem={() => {}} + dashboardItemCreatorClickHandler={() => {}} + dashboardItemCreators={() => []} listingLimit={1000} initialPageSize={10} hideWriteControls={false} @@ -134,7 +141,9 @@ describe('after fetch', () => { deleteItems={() => {}} createItem={() => {}} editItem={() => {}} - getViewUrl={() => {}} + viewItem={() => {}} + dashboardItemCreatorClickHandler={() => {}} + dashboardItemCreators={() => []} listingLimit={1} initialPageSize={10} hideWriteControls={false} @@ -157,7 +166,9 @@ describe('after fetch', () => { deleteItems={() => {}} createItem={() => {}} editItem={() => {}} - getViewUrl={() => {}} + viewItem={() => {}} + dashboardItemCreatorClickHandler={() => {}} + dashboardItemCreators={() => []} listingLimit={1} initialPageSize={10} hideWriteControls={true} @@ -180,7 +191,9 @@ describe('after fetch', () => { deleteItems={() => {}} createItem={() => {}} editItem={() => {}} - getViewUrl={() => {}} + viewItem={() => {}} + dashboardItemCreatorClickHandler={() => {}} + dashboardItemCreators={() => []} listingLimit={1} initialPageSize={10} hideWriteControls={false} diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html b/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html index ba05c138a0c..5e19fbfe678 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html @@ -1,8 +1,10 @@ void; + +export interface DashboardSetup { + registerDashboardProvider: RegisterDashboardProviderFn; +} export interface DashboardStart { getSavedDashboardLoader: () => SavedObjectLoader; @@ -200,6 +207,7 @@ export class DashboardPlugin private currentHistory: ScopedHistory | undefined = undefined; private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig; + private dashboardProviders: { [key: string]: DashboardProvider } = {}; private dashboardUrlGenerator?: DashboardUrlGenerator; public setup( @@ -308,6 +316,48 @@ export class DashboardPlugin stopUrlTracker(); }; + const registerDashboardProvider: RegisterDashboardProviderFn = ( + provider: DashboardProvider + ) => { + const found = this.dashboardProviders[provider.savedObjectsType]; + if (found) { + throw new Error(`DashboardProvider ${provider.savedObjectsType} is registered twice`); + } + if ( + isEmpty(provider.createSortText) || + isEmpty(provider.createUrl) || + isEmpty(provider.createLinkText) + ) { + throw new Error( + `DashboardProvider ${provider.savedObjectsType} requires 'createSortText', 'createLinkText', and 'createUrl'` + ); + } + if (isEmpty(provider.savedObjectsType || isEmpty(provider.savedObjectsName))) { + throw new Error( + `DashboardProvider ${provider.savedObjectsType} requires 'savedObjectsId', and 'savedObjectsType'` + ); + } + + this.dashboardProviders[provider.savedObjectsType] = provider; + }; + + registerDashboardProvider({ + savedObjectsType: 'dashboard', + savedObjectsName: 'Dashboard', + appId: 'dashboards', + viewUrlPathFn: (obj) => `#/view/${obj.id}`, + editUrlPathFn: (obj) => `/view/${obj.id}?_a=(viewMode:edit)`, + createUrl: core.http.basePath.prepend('/app/dashboards#/create'), + createSortText: 'Dashboard', + createLinkText: ( + + ), + }); + const app: App = { id: DashboardConstants.DASHBOARDS_ID, title: 'Dashboard', @@ -341,6 +391,7 @@ export class DashboardPlugin data: dataStart, savedObjectsClient: coreStart.savedObjects.client, savedDashboards: dashboardStart.getSavedDashboardLoader(), + dashboardProviders: () => this.dashboardProviders, chrome: coreStart.chrome, addBasePath: coreStart.http.basePath.prepend, uiSettings: coreStart.uiSettings, @@ -420,6 +471,10 @@ export class DashboardPlugin order: 100, }); } + + return { + registerDashboardProvider, + }; } private addEmbeddableToDashboard( diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index 49705c8614d..42a07b40ef1 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -141,3 +141,74 @@ export interface StagedFilter { operator: string; index: string; } + +export interface DashboardProvider { + // appId : + // The appId used to register this Plugin application. + // This value needs to be repeated here as the 'app' of this plugin + // is not directly referenced in the details below, and the 'app' object + // is not linked in the Dashboards List surrounding code. + appId: string; + + // savedObjectstype : + // This string should be the SavedObjects 'type' that you + // have registered for your objects. This must match the value + // used by your Plugin's Server setup with `savedObjects.registerType()` call. + savedObjectsType: string; + + // savedObjectsName : + // This string should be the display-name that will be used on the + // Dashboads / Dashboards table in a column named "Type". + savedObjectsName: string; + + // savedObjectsId : Optional + // If provided, this string will override the use of the `savedObjectsType` + // for use with querying the SavedObjects index for your objects. + // The default value for this string is implicitly set to the `savedObjectsType` + savedObjectsId?: string; + + // createLinkText : + // this is the string or Element that will be used to construct the + // OUI MenuPopup of Create options. + createLinkText: string | JSX.Element; + + // createSortText : + // This string will be used in sorting the Create options. Use + // the verbatim string here, not any interpolation or function. + createSortText: string; + + // createUrl : + // This string should be the url-path for your plugin's Create + // feature. + createUrl: string; + + // viewUrlPathFn : + // This function will be called on every iteratee of your objects + // while querying the SavedObjects for Dashboards / Dashboards + // This function should return the url-path to the View page + // for your Plugin's objects, within the "app" basepath. + // For instance : + // appId = "myplugin" + // app.basepath is then "/app/myplugin" + // then + // viewUrlPathFn: (obj) => `#/view/${obj.id}` + // + // At onClick of rendered table "view" link for item {id: 'abc123', ...}, the navigated path will be: + // "http://../app/myplugin#/view/abc123" + viewUrlPathFn: (obj: SavedObjectType) => string; + + // editUrlPathFn : + // This function will be called on every iteratee of your objects + // while querying the SavedObjects for Dashboards / Dashboards + // This function should return the url-path to the Edit page + // for your Plugin's objects, within the "app" basepath. + // For instance : + // appId = "myplugin" + // app.basepath is then "/app/myplugin" + // then + // editUrlPathFn: (obj) => `#/edit/${obj.id}` + // + // At onClick of rendered table "edit" link for item {id: 'abc123', ...}, the navigated path will be: + // "http://../app/myplugin#/edit/abc123" + editUrlPathFn: (obj: SavedObjectType) => string; +} diff --git a/src/plugins/opensearch_dashboards_react/public/table_list_view/README.md b/src/plugins/opensearch_dashboards_react/public/table_list_view/README.md new file mode 100644 index 00000000000..ecbd04a4944 --- /dev/null +++ b/src/plugins/opensearch_dashboards_react/public/table_list_view/README.md @@ -0,0 +1,44 @@ +# TableListView +An OpenDashboardsReact component + +## Overview +TableListView is a component composed of several OUI modules, wrapped in a convenient way for flexibility of options and input data. + +The TableListView contains : +- OUI InMemoryTable, including pagination and sortable columns +- OUI SearchBar +- Create button child component or callback handler. + +## Props +- createButton - JSX.Element (optinoal) + + - if provided, this element will be rendered at Right of SearchBox. Element component and callback-handling is expected to be controlled by the component wrapping this TableListView. + +- createItem - Function () => void (optional) + + - if provided, and no `createButton`, a default "Create" button will be rendered, using this prop function as callback to the default