Skip to content

Commit

Permalink
Sharing Saved Objects developer guide: Step 3
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jportner committed Sep 28, 2021
1 parent c02076e commit 5b1a467
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 10 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/notes_test/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"server": true,
"ui": true,
"requiredPlugins": [],
"optionalPlugins": []
"optionalPlugins": ["spaces"]
}
9 changes: 6 additions & 3 deletions x-pack/plugins/notes_test/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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(
<Router history={history}>
<Switch>
<Route path={`/${VIEW_NOTE_PATH}/:noteId`}>
<ViewNote services={services} />
<ViewNote services={services} http={http} spacesApi={spacesApi} />
</Route>
<Route path="/" exact>
<NotesList services={services} />
Expand Down
15 changes: 11 additions & 4 deletions x-pack/plugins/notes_test/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<PluginStartDeps>) {
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 });
},
});

Expand Down
44 changes: 42 additions & 2 deletions x-pack/plugins/notes_test/public/view_note.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,40 @@ 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 {
noteId: string;
}
interface Props {
services: Services;
http: HttpStart;
spacesApi?: SpacesPluginStart;
}

type ResolvedNote = ResolvedSimpleSavedObject<NoteAttributes>;
const OBJECT_NOUN = 'note';

export function ViewNote({ services }: Props) {
export function ViewNote({ services, http, spacesApi }: Props) {
const { noteId } = useParams<Params>();
const [resolvedNote, setResolvedNote] = useState<ResolvedNote | null>(null);
const note = resolvedNote?.saved_object;

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);
};

Expand All @@ -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,
})}
<EuiSpacer />
</>
);
}
}
return null;
};

return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
{/* If we have a legacy URL conflict callout to display, show it at the top of the page */}
{getLegacyUrlConflictCallout()}
<EuiPageContentHeader>
<EuiText>
<h1>View note</h1>
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/notes_test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"include": ["common/**/*", "public/**/*", "server/**/*"],
"references": [
{ "path": "../../../src/core/tsconfig.json" },
{ "path": "../spaces/tsconfig.json" },
]
}

0 comments on commit 5b1a467

Please sign in to comment.