Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CM] Onboard maps to cross-type search #155148

Merged
merged 14 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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?
Dosant marked this conversation as resolved.
Show resolved Hide resolved
});

// 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