From 30b8fe634e66d29c60f9ccd4f18afbcf5baca801 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 29 Jul 2021 17:06:53 -0400 Subject: [PATCH 1/4] Initial commit Created the test plugin with basic functionality. Subsequent commits will demonstrate individual steps that need to be taken, as described by the Sharing Saved Objects developer guide. Note: this simple test plugin doesn't use links to other object types, so Step 1 is skipped intentionally. --- x-pack/plugins/notes_test/common/constants.ts | 9 ++ x-pack/plugins/notes_test/common/index.ts | 9 ++ x-pack/plugins/notes_test/common/types.ts | 12 +++ x-pack/plugins/notes_test/kibana.json | 10 ++ x-pack/plugins/notes_test/public/app.tsx | 41 +++++++++ .../notes_test/public/create_note_form.tsx | 68 ++++++++++++++ x-pack/plugins/notes_test/public/index.ts | 11 +++ .../plugins/notes_test/public/notes_list.tsx | 91 +++++++++++++++++++ x-pack/plugins/notes_test/public/plugin.tsx | 32 +++++++ x-pack/plugins/notes_test/public/services.ts | 41 +++++++++ .../plugins/notes_test/public/view_note.tsx | 76 ++++++++++++++++ x-pack/plugins/notes_test/server/index.ts | 12 +++ x-pack/plugins/notes_test/server/plugin.ts | 22 +++++ .../notes_test/server/saved_objects.ts | 26 ++++++ x-pack/plugins/notes_test/tsconfig.json | 14 +++ 15 files changed, 474 insertions(+) create mode 100644 x-pack/plugins/notes_test/common/constants.ts create mode 100644 x-pack/plugins/notes_test/common/index.ts create mode 100644 x-pack/plugins/notes_test/common/types.ts create mode 100644 x-pack/plugins/notes_test/kibana.json create mode 100644 x-pack/plugins/notes_test/public/app.tsx create mode 100644 x-pack/plugins/notes_test/public/create_note_form.tsx create mode 100644 x-pack/plugins/notes_test/public/index.ts create mode 100644 x-pack/plugins/notes_test/public/notes_list.tsx create mode 100644 x-pack/plugins/notes_test/public/plugin.tsx create mode 100644 x-pack/plugins/notes_test/public/services.ts create mode 100644 x-pack/plugins/notes_test/public/view_note.tsx create mode 100644 x-pack/plugins/notes_test/server/index.ts create mode 100644 x-pack/plugins/notes_test/server/plugin.ts create mode 100644 x-pack/plugins/notes_test/server/saved_objects.ts create mode 100644 x-pack/plugins/notes_test/tsconfig.json diff --git a/x-pack/plugins/notes_test/common/constants.ts b/x-pack/plugins/notes_test/common/constants.ts new file mode 100644 index 0000000000000..33b2f846ef533 --- /dev/null +++ b/x-pack/plugins/notes_test/common/constants.ts @@ -0,0 +1,9 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const NOTE_OBJ_TYPE = 'note'; +export const VIEW_NOTE_PATH = 'viewNote'; diff --git a/x-pack/plugins/notes_test/common/index.ts b/x-pack/plugins/notes_test/common/index.ts new file mode 100644 index 0000000000000..f4d74984a7d78 --- /dev/null +++ b/x-pack/plugins/notes_test/common/index.ts @@ -0,0 +1,9 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './constants'; +export * from './types'; diff --git a/x-pack/plugins/notes_test/common/types.ts b/x-pack/plugins/notes_test/common/types.ts new file mode 100644 index 0000000000000..5420b0fd3ab7d --- /dev/null +++ b/x-pack/plugins/notes_test/common/types.ts @@ -0,0 +1,12 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface NoteAttributes { + subject: string; + text: string; + createdAt: Date; +} diff --git a/x-pack/plugins/notes_test/kibana.json b/x-pack/plugins/notes_test/kibana.json new file mode 100644 index 0000000000000..9799a48b35ef3 --- /dev/null +++ b/x-pack/plugins/notes_test/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "notesTest", + "owner": { "name": "Platform Security", "githubTeam": "kibana-security" }, + "version": "0.0.1", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": [], + "optionalPlugins": [] +} diff --git a/x-pack/plugins/notes_test/public/app.tsx b/x-pack/plugins/notes_test/public/app.tsx new file mode 100644 index 0000000000000..0f80dacfecf25 --- /dev/null +++ b/x-pack/plugins/notes_test/public/app.tsx @@ -0,0 +1,41 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Router, Switch, Route } from 'react-router-dom'; + +import type { AppMountParameters } from 'src/core/public'; +import { VIEW_NOTE_PATH } from '../common'; +import { NotesList } from './notes_list'; +import type { Services } from './services'; +import { ViewNote } from './view_note'; + +interface RenderParams { + services: Services; + appMountParams: AppMountParameters; +} + +export const renderApp = ({ services, appMountParams }: RenderParams) => { + const { element, history } = appMountParams; + + ReactDOM.render( + + + + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/notes_test/public/create_note_form.tsx b/x-pack/plugins/notes_test/public/create_note_form.tsx new file mode 100644 index 0000000000000..ea33cc3e56fe8 --- /dev/null +++ b/x-pack/plugins/notes_test/public/create_note_form.tsx @@ -0,0 +1,68 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useState } from 'react'; +import { EuiButton, EuiFieldText, EuiForm, EuiFormRow, EuiText, EuiTextArea } from '@elastic/eui'; + +import type { Services } from './services'; + +interface Props { + services: Services; + onAfterCreate: () => void; +} + +export function CreateNoteForm({ services, onAfterCreate }: Props) { + const [subject, setSubject] = useState(''); + const [text, setText] = useState(''); + const [isSaving, setIsSaving] = useState(false); + const [isInvalid, setIsInvalid] = useState(false); + + const saveNote = useCallback(async () => { + if (isSaving) return; + setIsSaving(true); + + if (!subject || !text) { + setIsInvalid(true); + } else { + setIsInvalid(false); + await services.createNote(subject, text); + setSubject(''); + setText(''); + services.addSuccessToast('Note created!'); + onAfterCreate(); + } + + setIsSaving(false); + }, [subject, text, services, isSaving, onAfterCreate]); + + return ( + <> + + + Create a new note + + + setSubject(event.target.value)} + /> + + + setText(event.target.value)} + /> + + + Save + + + > + ); +} diff --git a/x-pack/plugins/notes_test/public/index.ts b/x-pack/plugins/notes_test/public/index.ts new file mode 100644 index 0000000000000..892c787323ed0 --- /dev/null +++ b/x-pack/plugins/notes_test/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializer } from 'src/core/public'; +import { NotesTestPlugin } from './plugin'; + +export const plugin: PluginInitializer<{}, {}> = () => new NotesTestPlugin(); diff --git a/x-pack/plugins/notes_test/public/notes_list.tsx b/x-pack/plugins/notes_test/public/notes_list.tsx new file mode 100644 index 0000000000000..b59f01f691197 --- /dev/null +++ b/x-pack/plugins/notes_test/public/notes_list.tsx @@ -0,0 +1,91 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useState } from 'react'; +import { + EuiInMemoryTable, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { Link } from 'react-router-dom'; + +import type { SimpleSavedObject } from 'src/core/public'; +import type { NoteAttributes } from '../common'; +import { VIEW_NOTE_PATH } from '../common'; +import { CreateNoteForm } from './create_note_form'; +import type { Services } from './services'; + +interface Props { + services: Services; +} + +type NoteObject = SimpleSavedObject; + +export function NotesList({ services }: Props) { + const { findAllNotes } = services; + const [isFetching, setIsFetching] = useState(false); + const [notes, setNotes] = useState([]); + + const fetchNotes = useCallback(async () => { + if (isFetching) return; + setIsFetching(true); + + const response = await findAllNotes(); + setNotes(response); + + setIsFetching(false); + }, [isFetching, findAllNotes, setNotes]); + + useEffect(() => { + fetchNotes(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + + Notes + + + {notes.length ? ( + <> + + items={notes} + columns={[ + { + field: 'attributes.subject', + name: 'Subject', + render: (value, record) => { + const { id, attributes } = record; + return {attributes.subject}; + }, + }, + { + field: 'attributes.createdAt', + name: 'Created at', + dataType: 'date', + }, + ]} + pagination={false} + sorting={{ sort: { field: 'attributes.createdAt', direction: 'desc' } }} + /> + + > + ) : null} + fetchNotes()} /> + + + + ); +} diff --git a/x-pack/plugins/notes_test/public/plugin.tsx b/x-pack/plugins/notes_test/public/plugin.tsx new file mode 100644 index 0000000000000..0223121d6c50d --- /dev/null +++ b/x-pack/plugins/notes_test/public/plugin.tsx @@ -0,0 +1,32 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart, Plugin, CoreSetup, AppMountParameters } from 'src/core/public'; +import { getServices } from './services'; + +export class NotesTestPlugin implements Plugin<{}, {}, {}, {}> { + public setup(core: CoreSetup) { + core.application.register({ + id: 'notesTest', + title: 'Notes test', + async mount(appMountParams: AppMountParameters) { + const [coreStart] = await core.getStartServices(); + const services = getServices(coreStart); + const { renderApp } = await import('./app'); + return renderApp({ services, appMountParams }); + }, + }); + + return {}; + } + + public start(core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/notes_test/public/services.ts b/x-pack/plugins/notes_test/public/services.ts new file mode 100644 index 0000000000000..db36210ad1816 --- /dev/null +++ b/x-pack/plugins/notes_test/public/services.ts @@ -0,0 +1,41 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart, SimpleSavedObject } from 'src/core/public'; + +import type { NoteAttributes } from '../common'; +import { NOTE_OBJ_TYPE } from '../common'; + +export interface Services { + createNote: (subject: string, text: string) => Promise; + findAllNotes: () => Promise>>; + getNoteById: (id: string) => Promise>; + addSuccessToast: (message: string) => void; +} + +export function getServices(core: CoreStart): Services { + const savedObjectsClient = core.savedObjects.client; + + return { + createNote: async (subject: string, text: string) => { + const attributes = { subject, text, createdAt: new Date() }; + await savedObjectsClient.create(NOTE_OBJ_TYPE, attributes); + }, + findAllNotes: async () => { + const findResult = await savedObjectsClient.find({ + type: NOTE_OBJ_TYPE, + perPage: 100, + }); + return findResult.savedObjects; + }, + getNoteById: async (id: string) => { + const savedObject = await savedObjectsClient.get(NOTE_OBJ_TYPE, id); + return savedObject; + }, + addSuccessToast: (message: string) => core.notifications.toasts.addSuccess(message), + }; +} diff --git a/x-pack/plugins/notes_test/public/view_note.tsx b/x-pack/plugins/notes_test/public/view_note.tsx new file mode 100644 index 0000000000000..732ffb38625b7 --- /dev/null +++ b/x-pack/plugins/notes_test/public/view_note.tsx @@ -0,0 +1,76 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; +import { + EuiLoadingSpinner, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { Link, useParams } from 'react-router-dom'; + +import type { SimpleSavedObject } from 'src/core/public'; +import type { NoteAttributes } from '../common'; +import type { Services } from './services'; + +interface Params { + noteId: string; +} +interface Props { + services: Services; +} + +type NoteObject = SimpleSavedObject; + +export function ViewNote({ services }: Props) { + const { noteId } = useParams(); + const [note, setNote] = useState(null); + + const fetchNote = async () => { + const savedObject = await services.getNoteById(noteId); + + setNote(savedObject); + }; + + useEffect(() => { + fetchNote(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [noteId]); + + return ( + + + + + + View note + + + {note ? ( + <> + + {note.attributes.subject} + {note.attributes.createdAt} + + {note.attributes.text} + + + + > + ) : ( + + )} + Back to notes list + + + + ); +} diff --git a/x-pack/plugins/notes_test/server/index.ts b/x-pack/plugins/notes_test/server/index.ts new file mode 100644 index 0000000000000..8c49faa4ff146 --- /dev/null +++ b/x-pack/plugins/notes_test/server/index.ts @@ -0,0 +1,12 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializer } from 'src/core/server'; + +import { NotesTestPlugin } from './plugin'; + +export const plugin: PluginInitializer<{}, {}> = () => new NotesTestPlugin(); diff --git a/x-pack/plugins/notes_test/server/plugin.ts b/x-pack/plugins/notes_test/server/plugin.ts new file mode 100644 index 0000000000000..9bb81a648c28b --- /dev/null +++ b/x-pack/plugins/notes_test/server/plugin.ts @@ -0,0 +1,22 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Plugin, CoreSetup, CoreStart } from 'src/core/server'; +import { registerSavedObject } from './saved_objects'; + +export class NotesTestPlugin implements Plugin<{}, {}> { + public setup(core: CoreSetup) { + registerSavedObject(core.savedObjects); + return {}; + } + + public start(core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/notes_test/server/saved_objects.ts b/x-pack/plugins/notes_test/server/saved_objects.ts new file mode 100644 index 0000000000000..6e9134b4df2b9 --- /dev/null +++ b/x-pack/plugins/notes_test/server/saved_objects.ts @@ -0,0 +1,26 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsServiceSetup, SavedObjectsType } from 'src/core/server'; +import type { NoteAttributes } from '../common'; +import { NOTE_OBJ_TYPE } from '../common'; + +export function registerSavedObject(savedObjects: SavedObjectsServiceSetup) { + savedObjects.registerType(noteSavedObjectType); +} + +const noteSavedObjectType: SavedObjectsType = { + name: NOTE_OBJ_TYPE, + hidden: false, + management: { + importableAndExportable: true, + icon: 'document', + getTitle: ({ attributes }) => attributes.subject, + }, + mappings: { dynamic: false, properties: {} }, + namespaceType: 'single', +}; diff --git a/x-pack/plugins/notes_test/tsconfig.json b/x-pack/plugins/notes_test/tsconfig.json new file mode 100644 index 0000000000000..2ebf144dc584a --- /dev/null +++ b/x-pack/plugins/notes_test/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*"], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + ] +} From c02076e48c107922d70b6f9319e00602fa8e4d85 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 29 Jul 2021 22:02:43 -0400 Subject: [PATCH 2/4] Sharing Saved Objects developer guide: Step 2 This step demonstrates the changes to update client code to use the new SavedObjectsClient `resolve()` method instead of `get()`. --- x-pack/plugins/notes_test/public/services.ts | 8 ++++---- x-pack/plugins/notes_test/public/view_note.tsx | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/notes_test/public/services.ts b/x-pack/plugins/notes_test/public/services.ts index db36210ad1816..fc4e7ebf89b42 100644 --- a/x-pack/plugins/notes_test/public/services.ts +++ b/x-pack/plugins/notes_test/public/services.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { CoreStart, SimpleSavedObject } from 'src/core/public'; +import type { CoreStart, ResolvedSimpleSavedObject, SimpleSavedObject } from 'src/core/public'; import type { NoteAttributes } from '../common'; import { NOTE_OBJ_TYPE } from '../common'; @@ -13,7 +13,7 @@ import { NOTE_OBJ_TYPE } from '../common'; export interface Services { createNote: (subject: string, text: string) => Promise; findAllNotes: () => Promise>>; - getNoteById: (id: string) => Promise>; + getNoteById: (id: string) => Promise>; addSuccessToast: (message: string) => void; } @@ -33,8 +33,8 @@ export function getServices(core: CoreStart): Services { return findResult.savedObjects; }, getNoteById: async (id: string) => { - const savedObject = await savedObjectsClient.get(NOTE_OBJ_TYPE, id); - return savedObject; + const resolveResult = await savedObjectsClient.resolve(NOTE_OBJ_TYPE, id); + return resolveResult; }, addSuccessToast: (message: string) => core.notifications.toasts.addSuccess(message), }; diff --git a/x-pack/plugins/notes_test/public/view_note.tsx b/x-pack/plugins/notes_test/public/view_note.tsx index 732ffb38625b7..96f10716d9117 100644 --- a/x-pack/plugins/notes_test/public/view_note.tsx +++ b/x-pack/plugins/notes_test/public/view_note.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { Link, useParams } from 'react-router-dom'; -import type { SimpleSavedObject } from 'src/core/public'; +import type { ResolvedSimpleSavedObject } from 'src/core/public'; import type { NoteAttributes } from '../common'; import type { Services } from './services'; @@ -28,16 +28,17 @@ interface Props { services: Services; } -type NoteObject = SimpleSavedObject; +type ResolvedNote = ResolvedSimpleSavedObject; export function ViewNote({ services }: Props) { const { noteId } = useParams(); - const [note, setNote] = useState(null); + const [resolvedNote, setResolvedNote] = useState(null); + const note = resolvedNote?.saved_object; const fetchNote = async () => { - const savedObject = await services.getNoteById(noteId); + const resolveResult = await services.getNoteById(noteId); - setNote(savedObject); + setResolvedNote(resolveResult); }; useEffect(() => { From 5b1a467d016fdd62f2d362d63f06da6fa7df39f1 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 29 Jul 2021 23:53:45 -0400 Subject: [PATCH 3/4] Sharing Saved Objects developer guide: Step 3 This step demonstrates the changes to update client code to correctly handle the three different `resolve()` outcomes. It adds an optional dependency on the Spaces plugin, and uses the SpacesApi to change the UI if necessary. --- x-pack/plugins/notes_test/kibana.json | 2 +- x-pack/plugins/notes_test/public/app.tsx | 9 ++-- x-pack/plugins/notes_test/public/plugin.tsx | 15 +++++-- .../plugins/notes_test/public/view_note.tsx | 44 ++++++++++++++++++- x-pack/plugins/notes_test/tsconfig.json | 1 + 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/notes_test/kibana.json b/x-pack/plugins/notes_test/kibana.json index 9799a48b35ef3..15ac81f703784 100644 --- a/x-pack/plugins/notes_test/kibana.json +++ b/x-pack/plugins/notes_test/kibana.json @@ -6,5 +6,5 @@ "server": true, "ui": true, "requiredPlugins": [], - "optionalPlugins": [] + "optionalPlugins": ["spaces"] } diff --git a/x-pack/plugins/notes_test/public/app.tsx b/x-pack/plugins/notes_test/public/app.tsx index 0f80dacfecf25..f01ab7e0c12f9 100644 --- a/x-pack/plugins/notes_test/public/app.tsx +++ b/x-pack/plugins/notes_test/public/app.tsx @@ -9,7 +9,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Router, Switch, Route } from 'react-router-dom'; -import type { AppMountParameters } from 'src/core/public'; +import type { AppMountParameters, HttpStart } from 'src/core/public'; +import type { SpacesPluginStart } from '../../spaces/public'; import { VIEW_NOTE_PATH } from '../common'; import { NotesList } from './notes_list'; import type { Services } from './services'; @@ -18,16 +19,18 @@ import { ViewNote } from './view_note'; interface RenderParams { services: Services; appMountParams: AppMountParameters; + http: HttpStart; + spacesApi?: SpacesPluginStart; } -export const renderApp = ({ services, appMountParams }: RenderParams) => { +export const renderApp = ({ services, appMountParams, http, spacesApi }: RenderParams) => { const { element, history } = appMountParams; ReactDOM.render( - + diff --git a/x-pack/plugins/notes_test/public/plugin.tsx b/x-pack/plugins/notes_test/public/plugin.tsx index 0223121d6c50d..392edfee6c004 100644 --- a/x-pack/plugins/notes_test/public/plugin.tsx +++ b/x-pack/plugins/notes_test/public/plugin.tsx @@ -6,18 +6,25 @@ */ import type { CoreStart, Plugin, CoreSetup, AppMountParameters } from 'src/core/public'; +import type { SpacesPluginStart } from '../../spaces/public'; import { getServices } from './services'; -export class NotesTestPlugin implements Plugin<{}, {}, {}, {}> { - public setup(core: CoreSetup) { +interface PluginStartDeps { + spaces?: SpacesPluginStart; +} + +export class NotesTestPlugin implements Plugin<{}, {}, {}, PluginStartDeps> { + public setup(core: CoreSetup) { core.application.register({ id: 'notesTest', title: 'Notes test', async mount(appMountParams: AppMountParameters) { - const [coreStart] = await core.getStartServices(); + const [coreStart, pluginStartDeps] = await core.getStartServices(); const services = getServices(coreStart); + const { http } = coreStart; + const { spaces: spacesApi } = pluginStartDeps; const { renderApp } = await import('./app'); - return renderApp({ services, appMountParams }); + return renderApp({ services, appMountParams, http, spacesApi }); }, }); diff --git a/x-pack/plugins/notes_test/public/view_note.tsx b/x-pack/plugins/notes_test/public/view_note.tsx index 96f10716d9117..a1d86c88463bf 100644 --- a/x-pack/plugins/notes_test/public/view_note.tsx +++ b/x-pack/plugins/notes_test/public/view_note.tsx @@ -17,8 +17,10 @@ import { } from '@elastic/eui'; import { Link, useParams } from 'react-router-dom'; -import type { ResolvedSimpleSavedObject } from 'src/core/public'; +import type { HttpStart, ResolvedSimpleSavedObject } from 'src/core/public'; +import type { SpacesPluginStart } from '../../spaces/public'; import type { NoteAttributes } from '../common'; +import { VIEW_NOTE_PATH } from '../common'; import type { Services } from './services'; interface Params { @@ -26,11 +28,14 @@ interface Params { } interface Props { services: Services; + http: HttpStart; + spacesApi?: SpacesPluginStart; } type ResolvedNote = ResolvedSimpleSavedObject; +const OBJECT_NOUN = 'note'; -export function ViewNote({ services }: Props) { +export function ViewNote({ services, http, spacesApi }: Props) { const { noteId } = useParams(); const [resolvedNote, setResolvedNote] = useState(null); const note = resolvedNote?.saved_object; @@ -38,6 +43,14 @@ export function ViewNote({ services }: Props) { const fetchNote = async () => { const resolveResult = await services.getNoteById(noteId); + if (spacesApi && resolveResult.outcome === 'aliasMatch') { + // We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash + const newObjectId = resolveResult.alias_target_id!; // This is always defined if outcome === 'aliasMatch' + const newPath = `/${VIEW_NOTE_PATH}/${newObjectId}${window.location.hash}`; // Use the *local* path within this app (do not include the "/app/appId" prefix) + await spacesApi.ui.redirectLegacyUrl(newPath, OBJECT_NOUN); + return; + } + setResolvedNote(resolveResult); }; @@ -46,10 +59,37 @@ export function ViewNote({ services }: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [noteId]); + const getLegacyUrlConflictCallout = () => { + // This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario + if (spacesApi && resolvedNote) { + if (resolvedNote.outcome === 'conflict') { + // We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a + // callout with a warning for the user, and provide a way for them to navigate to the other object. + const currentObjectId = resolvedNote.saved_object.id; + const otherObjectId = resolvedNote.alias_target_id!; // This is always defined if outcome === 'conflict' + const otherObjectPath = `/${VIEW_NOTE_PATH}/${otherObjectId}${window.location.hash}`; // Use the *local* path within this app (do not include the "/app/appId" prefix) + return ( + <> + {spacesApi.ui.components.getLegacyUrlConflict({ + objectNoun: OBJECT_NOUN, + currentObjectId, + otherObjectId, + otherObjectPath, + })} + + > + ); + } + } + return null; + }; + return ( + {/* If we have a legacy URL conflict callout to display, show it at the top of the page */} + {getLegacyUrlConflictCallout()} View note diff --git a/x-pack/plugins/notes_test/tsconfig.json b/x-pack/plugins/notes_test/tsconfig.json index 2ebf144dc584a..0edd5023c4e3a 100644 --- a/x-pack/plugins/notes_test/tsconfig.json +++ b/x-pack/plugins/notes_test/tsconfig.json @@ -10,5 +10,6 @@ "include": ["common/**/*", "public/**/*", "server/**/*"], "references": [ { "path": "../../../src/core/tsconfig.json" }, + { "path": "../spaces/tsconfig.json" }, ] } From 2fd4c42652158d1aca9dcb0d318a9a8eb1065109 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 29 Jul 2021 23:56:34 -0400 Subject: [PATCH 4/4] Sharing Saved Objects developer guide: Step 4 This step demonstrate the changes to update the saved object type registration, which will cause this isolated object type to be converted to become share-capable during the 8.0 upgrade process. Note: the previous steps can be backported to 7.x, but this step cannot be backported, because the conversion cannot take place before 8.0. --- x-pack/plugins/notes_test/server/saved_objects.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/notes_test/server/saved_objects.ts b/x-pack/plugins/notes_test/server/saved_objects.ts index 6e9134b4df2b9..1f1e166755fb4 100644 --- a/x-pack/plugins/notes_test/server/saved_objects.ts +++ b/x-pack/plugins/notes_test/server/saved_objects.ts @@ -22,5 +22,6 @@ const noteSavedObjectType: SavedObjectsType = { getTitle: ({ attributes }) => attributes.subject, }, mappings: { dynamic: false, properties: {} }, - namespaceType: 'single', + namespaceType: 'multiple-isolated', + convertToMultiNamespaceTypeVersion: '8.0.0', };
{note.attributes.createdAt}
+ {note.attributes.text} +
{note.attributes.text}