Skip to content

Commit

Permalink
[CM] Onboard maps to cross-type search (#155148)
Browse files Browse the repository at this point in the history
## Summary

Part of #152224
Follow up to #153256

This PR onboards maps CM integration into the multi-type search
(`msearch`). It isn't actually used anywhere in the user-facing UI yet,
as first other types need to be migrated to CM.

This PR also adds an example app to test the `msearch` end-to-end.
  • Loading branch information
Dosant authored Apr 21, 2023
1 parent cfc01d5 commit 6aa1491
Show file tree
Hide file tree
Showing 21 changed files with 316 additions and 23 deletions.
4 changes: 3 additions & 1 deletion examples/content_management_examples/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"browser": true,
"requiredPlugins": [
"contentManagement",
"developerExamples"
"developerExamples",
"kibanaReact",
"savedObjectsTaggingOss"
]
}
}
63 changes: 54 additions & 9 deletions examples/content_management_examples/public/examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,67 @@

import React from 'react';
import ReactDOM from 'react-dom';
import { EuiPageTemplate } from '@elastic/eui';
// eslint-disable-next-line no-restricted-imports
import { Router, Switch, Route, Redirect } from 'react-router-dom';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { EuiPageTemplate, EuiSideNav } from '@elastic/eui';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { StartDeps } from '../types';
import { TodoApp } from './todos';
import { MSearchApp } from './msearch';

export const renderApp = (
{ notifications }: CoreStart,
{ contentManagement }: StartDeps,
{ element }: AppMountParameters
core: CoreStart,
{ contentManagement, savedObjectsTaggingOss }: StartDeps,
{ element, history }: AppMountParameters
) => {
ReactDOM.render(
<EuiPageTemplate offset={0}>
<EuiPageTemplate.Section>
<TodoApp contentClient={contentManagement.client} />
</EuiPageTemplate.Section>
</EuiPageTemplate>,
<Router history={history}>
<RedirectAppLinks coreStart={core}>
<EuiPageTemplate offset={0}>
<EuiPageTemplate.Sidebar>
<EuiSideNav
items={[
{
id: 'Examples',
name: 'Examples',
items: [
{
id: 'todos',
name: 'Todo app',
'data-test-subj': 'todosExample',
href: '/app/contentManagementExamples/todos',
},
{
id: 'msearch',
name: 'MSearch',
'data-test-subj': 'msearchExample',
href: '/app/contentManagementExamples/msearch',
},
],
},
]}
/>
</EuiPageTemplate.Sidebar>

<EuiPageTemplate.Section>
<Switch>
<Redirect from="/" to="/todos" exact />
<Route path="/todos">
<TodoApp contentClient={contentManagement.client} />
</Route>
<Route path="/msearch">
<MSearchApp
contentClient={contentManagement.client}
core={core}
savedObjectsTagging={savedObjectsTaggingOss}
/>
</Route>
</Switch>
</EuiPageTemplate.Section>
</EuiPageTemplate>
</RedirectAppLinks>
</Router>,
element
);

Expand Down
Original file line number Diff line number Diff line change
@@ -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 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.
*/

export { MSearchApp } from './msearch_app';
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 from 'react';
import { ContentClientProvider, type ContentClient } from '@kbn/content-management-plugin/public';
import { TableListViewKibanaProvider } from '@kbn/content-management-table-list';
import type { CoreStart } from '@kbn/core/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
import { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
import { MSearchTable } from './msearch_table';

export const MSearchApp = (props: {
contentClient: ContentClient;
core: CoreStart;
savedObjectsTagging: SavedObjectTaggingOssPluginStart;
}) => {
return (
<ContentClientProvider contentClient={props.contentClient}>
<I18nProvider>
<TableListViewKibanaProvider
core={{
application: props.core.application,
notifications: props.core.notifications,
overlays: props.core.overlays,
http: props.core.http,
}}
toMountPoint={toMountPoint}
FormattedRelative={FormattedRelative}
savedObjectsTagging={props.savedObjectsTagging.getTaggingApi()}
>
<MSearchTable />
</TableListViewKibanaProvider>
</I18nProvider>
</ContentClientProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list';
import { useContentClient } from '@kbn/content-management-plugin/public';
import React from 'react';
import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-browser';

const LISTING_LIMIT = 1000;

export const MSearchTable = () => {
const contentClient = useContentClient();

const findItems = async (
searchQuery: string,
refs?: {
references?: SavedObjectsFindOptionsReference[];
referencesToExclude?: SavedObjectsFindOptionsReference[];
}
) => {
const { hits, pagination } = await contentClient.mSearch<UserContentCommonSchema>({
query: {
text: searchQuery,
limit: LISTING_LIMIT,
cursor: '1',
tags: {
included: refs?.references?.map((ref) => ref.id),
excluded: refs?.referencesToExclude?.map((ref) => ref.id),
},
},
contentTypes: [{ contentTypeId: 'map' }], // TODO: improve types to not require objects here?
});

// TODO: needs to have logic of extracting common schema from an unknown mSearch hit: hits.map(hit => cm.convertToCommonSchema(hit))
// for now we just assume that mSearch hit satisfies UserContentCommonSchema

return { hits, total: pagination.total };
};

return (
<TableListView
id="cm-msearch-table"
headingId="cm-msearch-table-heading"
findItems={findItems}
listingLimit={LISTING_LIMIT}
initialPageSize={50}
entityName={`ContentItem`}
entityNamePlural={`ContentItems`}
tableListTitle={`MSearch Demo`}
urlStateEnabled={false}
emptyPrompt={<>No data found. Try to install some sample data first.</>}
onClickTitle={(item) => {
alert(`Clicked item ${item.attributes.title} (${item.id})`);
}}
/>
);
};
2 changes: 2 additions & 0 deletions examples/content_management_examples/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ContentManagementPublicStart,
} from '@kbn/content-management-plugin/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';

export interface SetupDeps {
contentManagement: ContentManagementPublicSetup;
Expand All @@ -19,4 +20,5 @@ export interface SetupDeps {

export interface StartDeps {
contentManagement: ContentManagementPublicStart;
savedObjectsTaggingOss: SavedObjectTaggingOssPluginStart;
}
6 changes: 6 additions & 0 deletions examples/content_management_examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@
"@kbn/developer-examples-plugin",
"@kbn/content-management-plugin",
"@kbn/core-application-browser",
"@kbn/shared-ux-link-redirect-app",
"@kbn/content-management-table-list",
"@kbn/kibana-react-plugin",
"@kbn/i18n-react",
"@kbn/saved-objects-tagging-oss-plugin",
"@kbn/core-saved-objects-api-browser",
]
}
4 changes: 1 addition & 3 deletions packages/content-management/table_list/src/services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ export interface TableListViewKibanaDependencies {
core: {
application: {
capabilities: {
advancedSettings?: {
save: boolean;
};
[key: string]: Readonly<Record<string, boolean | Record<string, boolean>>>;
};
getUrlForApp: (app: string, options: { path: string }) => string;
currentAppId$: Observable<string | undefined>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ const searchSchemas = getOptionalInOutSchemas({
),
});

// Schema to validate the "msearch" service objects
const mSearchSchemas = schema.maybe(
schema.object({
out: schema.maybe(
schema.object(
{
result: schema.maybe(versionableObjectSchema),
},
{ unknowns: 'forbid' }
)
),
})
);

export const serviceDefinitionSchema = schema.object(
{
get: getSchemas,
Expand All @@ -111,6 +125,7 @@ export const serviceDefinitionSchema = schema.object(
update: createSchemas,
delete: getSchemas,
search: searchSchemas,
mSearch: mSearchSchemas,
},
{ unknowns: 'forbid' }
);
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ describe('CM services getTransforms()', () => {
'delete.out.result',
'search.in.options',
'search.out.result',
'mSearch.out.result',
].sort()
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const serviceObjectPaths = [
'delete.out.result',
'search.in.options',
'search.out.result',
'mSearch.out.result',
];

const validateServiceDefinitions = (definitions: ServiceDefinitionVersioned) => {
Expand Down Expand Up @@ -175,6 +176,11 @@ const getDefaultServiceTransforms = (): ServiceTransforms => ({
result: getDefaultTransforms(),
},
},
mSearch: {
out: {
result: getDefaultTransforms(),
},
},
});

export const getTransforms = (
Expand Down
10 changes: 10 additions & 0 deletions packages/kbn-object-versioning/lib/content_management_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export interface ServicesDefinition {
result?: VersionableObject<any, any, any, any>;
};
};
mSearch?: {
out?: {
result?: VersionableObject<any, any, any, any>;
};
};
}

export interface ServiceTransforms {
Expand Down Expand Up @@ -112,6 +117,11 @@ export interface ServiceTransforms {
result: ObjectTransforms<any, any, any, any>;
};
};
mSearch: {
out: {
result: ObjectTransforms<any, any, any, any>;
};
};
}

export interface ServiceDefinitionVersioned {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/content_management/server/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type {
ContentTypeDefinition,
StorageContext,
StorageContextGetTransformFn,
MSearchConfig,
} from './types';

export type { ContentRegistry } from './registry';
Expand Down
6 changes: 4 additions & 2 deletions src/plugins/content_management/server/core/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { validateVersion } from '@kbn/object-versioning/lib/utils';
import { ContentType } from './content_type';
import { EventBus } from './event_bus';
import type { ContentStorage, ContentTypeDefinition } from './types';
import type { ContentStorage, ContentTypeDefinition, MSearchConfig } from './types';
import type { ContentCrud } from './crud';

export class ContentRegistry {
Expand All @@ -23,7 +23,9 @@ export class ContentRegistry {
* @param contentType The content type to register
* @param config The content configuration
*/
register<S extends ContentStorage<any> = ContentStorage>(definition: ContentTypeDefinition<S>) {
register<S extends ContentStorage<any, any, MSearchConfig<any, any>> = ContentStorage>(
definition: ContentTypeDefinition<S>
) {
if (this.types.has(definition.id)) {
throw new Error(`Content [${definition.id}] is already registered`);
}
Expand Down
12 changes: 8 additions & 4 deletions src/plugins/content_management/server/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ export interface StorageContext {
};
}

export interface ContentStorage<T = unknown, U = T> {
export interface ContentStorage<
T = unknown,
U = T,
TMSearchConfig extends MSearchConfig<T, any> = MSearchConfig<T, unknown>
> {
/** Get a single item */
get(ctx: StorageContext, id: string, options?: object): Promise<GetResult<T, any>>;

Expand Down Expand Up @@ -69,7 +73,7 @@ export interface ContentStorage<T = unknown, U = T> {
* Opt-in to multi-type search.
* Can only be supported if the content type is backed by a saved object since `mSearch` is using the `savedObjects.find` API.
**/
mSearch?: MSearchConfig<T>;
mSearch?: TMSearchConfig;
}

export interface ContentTypeDefinition<S extends ContentStorage = ContentStorage> {
Expand All @@ -87,7 +91,7 @@ export interface ContentTypeDefinition<S extends ContentStorage = ContentStorage
* By configuring a content type with a `MSearchConfig`, it can be searched in the multi-type search.
* Underneath content management is using the `savedObjects.find` API to search the saved objects.
*/
export interface MSearchConfig<T = unknown, SavedObjectAttributes = unknown> {
export interface MSearchConfig<T = unknown, TSavedObjectAttributes = unknown> {
/**
* The saved object type that corresponds to this content type.
*/
Expand All @@ -98,7 +102,7 @@ export interface MSearchConfig<T = unknown, SavedObjectAttributes = unknown> {
*/
toItemResult: (
ctx: StorageContext,
savedObject: SavedObjectsFindResult<SavedObjectAttributes>
savedObject: SavedObjectsFindResult<TSavedObjectAttributes>
) => T;

/**
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/content_management/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ export function plugin(initializerContext: PluginInitializerContext) {
}

export type { ContentManagementServerSetup, ContentManagementServerStart } from './types';
export type { ContentStorage, StorageContext } from './core';
export type { ContentStorage, StorageContext, MSearchConfig } from './core';
Loading

0 comments on commit 6aa1491

Please sign in to comment.