diff --git a/examples/index_pattern_field_editor_example/README.md b/examples/index_pattern_field_editor_example/README.md new file mode 100644 index 00000000000000..35ae814fc10e25 --- /dev/null +++ b/examples/index_pattern_field_editor_example/README.md @@ -0,0 +1,7 @@ +## index pattern field editor example + +This example index pattern field editor app shows how to: + - Edit index pattern fields via flyout + - Delete index pattern runtime fields with modal confirm prompt + +To run this example, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/examples/index_pattern_field_editor_example/kibana.json b/examples/index_pattern_field_editor_example/kibana.json new file mode 100644 index 00000000000000..c522e6698ac3d6 --- /dev/null +++ b/examples/index_pattern_field_editor_example/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "indexPatternFieldEditorExample", + "version": "0.0.1", + "kibanaVersion": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["data", "indexPatternFieldEditor", "developerExamples"], + "optionalPlugins": [], + "requiredBundles": [] +} diff --git a/examples/index_pattern_field_editor_example/public/app.tsx b/examples/index_pattern_field_editor_example/public/app.tsx new file mode 100644 index 00000000000000..bd725759380aae --- /dev/null +++ b/examples/index_pattern_field_editor_example/public/app.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; +import ReactDOM from 'react-dom'; +import { + EuiPage, + EuiPageHeader, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiButton, + EuiInMemoryTable, + EuiText, + DefaultItemAction, +} from '@elastic/eui'; +import { AppMountParameters } from '../../../src/core/public'; +import { + DataPublicPluginStart, + IndexPattern, + IndexPatternField, +} from '../../../src/plugins/data/public'; +import { IndexPatternFieldEditorStart } from '../../../src/plugins/index_pattern_field_editor/public'; + +interface Props { + indexPattern?: IndexPattern; + indexPatternFieldEditor: IndexPatternFieldEditorStart; +} + +const IndexPatternFieldEditorExample = ({ indexPattern, indexPatternFieldEditor }: Props) => { + const [fields, setFields] = useState( + indexPattern?.getNonScriptedFields() || [] + ); + const refreshFields = () => setFields(indexPattern?.getNonScriptedFields() || []); + const columns = [ + { + field: 'name', + name: 'Field name', + }, + { + name: 'Actions', + actions: [ + { + name: 'Edit', + description: 'Edit this field', + icon: 'pencil', + type: 'icon', + 'data-test-subj': 'editField', + onClick: (fld: IndexPatternField) => + indexPatternFieldEditor.openEditor({ + ctx: { indexPattern: indexPattern! }, + fieldName: fld.name, + onSave: refreshFields, + }), + }, + { + name: 'Delete', + description: 'Delete this field', + icon: 'trash', + type: 'icon', + 'data-test-subj': 'deleteField', + available: (fld) => !!fld.runtimeField, + onClick: (fld: IndexPatternField) => + indexPatternFieldEditor.openDeleteModal({ + fieldName: fld.name, + ctx: { + indexPattern: indexPattern!, + }, + onDelete: refreshFields, + }), + }, + ] as Array>, + }, + ]; + + const content = indexPattern ? ( + <> + Index pattern: {indexPattern?.title} +
+ + indexPatternFieldEditor.openEditor({ + ctx: { indexPattern: indexPattern! }, + onSave: refreshFields, + }) + } + data-test-subj="addField" + > + Add field + +
+ + items={fields} + columns={columns} + pagination={true} + hasActions={true} + sorting={{ + sort: { + field: 'name', + direction: 'asc', + }, + }} + /> + + ) : ( +

Please create an index pattern

+ ); + + return ( + + + Index pattern field editor demo + + {content} + + + + ); +}; + +interface RenderAppDependencies { + data: DataPublicPluginStart; + indexPatternFieldEditor: IndexPatternFieldEditorStart; +} + +export const renderApp = async ( + { data, indexPatternFieldEditor }: RenderAppDependencies, + { element }: AppMountParameters +) => { + const indexPattern = (await data.indexPatterns.getDefault()) || undefined; + ReactDOM.render( + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/index_pattern_field_editor_example/public/index.ts b/examples/index_pattern_field_editor_example/public/index.ts new file mode 100644 index 00000000000000..cc509da31d25fb --- /dev/null +++ b/examples/index_pattern_field_editor_example/public/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IndexPatternFieldEditorPlugin } from './plugin'; + +export const plugin = () => new IndexPatternFieldEditorPlugin(); diff --git a/examples/index_pattern_field_editor_example/public/plugin.tsx b/examples/index_pattern_field_editor_example/public/plugin.tsx new file mode 100644 index 00000000000000..ccbb93e3acf956 --- /dev/null +++ b/examples/index_pattern_field_editor_example/public/plugin.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public'; +import { DeveloperExamplesSetup } from '../../developer_examples/public'; +import { DataPublicPluginStart } from '../../../src/plugins/data/public'; +import { IndexPatternFieldEditorStart } from '../../../src/plugins/index_pattern_field_editor/public'; + +interface StartDeps { + data: DataPublicPluginStart; + indexPatternFieldEditor: IndexPatternFieldEditorStart; +} + +interface SetupDeps { + developerExamples: DeveloperExamplesSetup; +} + +export class IndexPatternFieldEditorPlugin implements Plugin { + public setup(core: CoreSetup, deps: SetupDeps) { + core.application.register({ + id: 'indexPatternFieldEditorExample', + title: 'Index pattern field editor example', + navLinkStatus: AppNavLinkStatus.hidden, + async mount(params: AppMountParameters) { + const [, depsStart] = await core.getStartServices(); + const { renderApp } = await import('./app'); + return renderApp(depsStart, params); + }, + }); + + deps.developerExamples.register({ + appId: 'indexPatternFieldEditorExample', + title: 'Index pattern field editor', + description: `IndexPatternFieldEditor provides a UI for editing index pattern fields directly from Kibana apps. This example plugin demonstrates integration.`, + links: [ + { + label: 'README', + href: + 'https://github.com/elastic/kibana/blob/master/src/plugins/index_pattern_field_editor/README.md', + iconType: 'logoGithub', + size: 's', + target: '_blank', + }, + ], + }); + } + + public start() {} + + public stop() {} +} diff --git a/examples/index_pattern_field_editor_example/tsconfig.json b/examples/index_pattern_field_editor_example/tsconfig.json new file mode 100644 index 00000000000000..1f6d52ed5260e4 --- /dev/null +++ b/examples/index_pattern_field_editor_example/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../src/core/tsconfig.json" }, + { "path": "../../src/plugins/kibana_react/tsconfig.json" }, + ] +} \ No newline at end of file diff --git a/test/examples/config.js b/test/examples/config.js index cb6c487c564c3b..d47748e5f22a97 100644 --- a/test/examples/config.js +++ b/test/examples/config.js @@ -28,6 +28,7 @@ export default async function ({ readConfigFile }) { require.resolve('./state_sync'), require.resolve('./routing'), require.resolve('./expressions_explorer'), + require.resolve('./index_pattern_field_editor_example'), ], services: { ...functionalConfig.get('services'), diff --git a/test/examples/index_pattern_field_editor_example/index.ts b/test/examples/index_pattern_field_editor_example/index.ts new file mode 100644 index 00000000000000..0cd23a33c84762 --- /dev/null +++ b/test/examples/index_pattern_field_editor_example/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ + getService, + getPageObjects, + loadTestFile, +}: PluginFunctionalProviderContext) { + const browser = getService('browser'); + const es = getService('es'); + const PageObjects = getPageObjects(['common', 'header', 'settings']); + + describe('index pattern field editor example', function () { + this.tags('ciGroup2'); + before(async () => { + await browser.setWindowSize(1300, 900); + await es.transport.request({ + path: '/blogs/_doc', + method: 'POST', + body: { user: 'matt', message: 20 }, + }); + + await PageObjects.settings.navigateTo(); + await PageObjects.settings.createIndexPattern('blogs', null); + await PageObjects.common.navigateToApp('indexPatternFieldEditorExample'); + }); + + loadTestFile(require.resolve('./index_pattern_field_editor_example')); + }); +} diff --git a/test/examples/index_pattern_field_editor_example/index_pattern_field_editor_example.ts b/test/examples/index_pattern_field_editor_example/index_pattern_field_editor_example.ts new file mode 100644 index 00000000000000..5744c8e64f5c11 --- /dev/null +++ b/test/examples/index_pattern_field_editor_example/index_pattern_field_editor_example.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: PluginFunctionalProviderContext) { + const testSubjects = getService('testSubjects'); + + describe('', () => { + it('finds an index pattern', async () => { + await testSubjects.existOrFail('indexPatternTitle'); + }); + it('opens the field editor', async () => { + await testSubjects.click('addField'); + await testSubjects.existOrFail('flyoutTitle'); + }); + }); +}