mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[CM] Example plugin with server-side registry usage (#151885)
## Summary
Close https://github.com/elastic/kibana/issues/152002
In https://github.com/elastic/kibana/pull/151163 we introduced a simple
demo todo app run in a storybook with a custom client-side content
management client (no server-side cm registry usage).
This is a follow-up PR that re-uses the same demo todo app, but also
runs it in an example plugin with proper server-side content management
registry usage, so now we have a basic end-to-end demonstration of
content management capabilities. The demo app is covered by functional
tests, so now we also have basic end-to-end test coverage.
As this is the first kind of real-world end-to-end usage of the CM APIs,
I'd like to use this and
[previous](https://github.com/elastic/kibana/pull/151163) prs as a base
for the discussion and polishing current APIs. I'll leave a review with
comments where I think some API polishing is needed.
**Notable changes apart from the example plugin itself:**
1. Move `demo/` todo app and its stories introduced in
https://github.com/elastic/kibana/pull/151163 from
`src/plugins/content_management` to
`examples/content_management_examples`. This was mostly needed to not
export `demo/` code on the public plugin export to avoid increasing
bundle size.
2. Add needed exports to the plugin contract
3. Reshuffle `common/` to not import `@kbn/schema` client side
48aa41403b
4. Fix client-side RPC client to work with the latest server-side
changes (shouldn't break from now on because of the end-to-end test
coverage)
This commit is contained in:
parent
a799a85533
commit
2e171759ca
48 changed files with 706 additions and 72 deletions
|
@ -19,7 +19,7 @@ const STORYBOOKS = [
|
|||
'cloud_chat',
|
||||
'coloring',
|
||||
'chart_icons',
|
||||
'content_management_plugin',
|
||||
'content_management_examples',
|
||||
'controls',
|
||||
'custom_integrations',
|
||||
'dashboard_enhanced',
|
||||
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -83,6 +83,7 @@ packages/kbn-config-mocks @elastic/kibana-core
|
|||
packages/kbn-config-schema @elastic/kibana-core
|
||||
src/plugins/console @elastic/platform-deployment-management
|
||||
packages/content-management/content_editor @elastic/appex-sharedux
|
||||
examples/content_management_examples @elastic/appex-sharedux
|
||||
src/plugins/content_management @elastic/appex-sharedux
|
||||
packages/content-management/table_list @elastic/appex-sharedux
|
||||
examples/controls_example @elastic/kibana-presentation
|
||||
|
|
3
examples/content_management_examples/README.md
Normal file
3
examples/content_management_examples/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Content Management Examples
|
||||
|
||||
An example plugin that shows how to integrate with the Kibana "content management" plugin.
|
|
@ -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 * from './todos';
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import {
|
||||
CreateIn,
|
||||
DeleteIn,
|
||||
GetIn,
|
||||
SearchIn,
|
||||
UpdateIn,
|
||||
} from '@kbn/content-management-plugin/common';
|
||||
|
||||
export const TODO_CONTENT_ID = 'todos';
|
||||
export interface Todo {
|
||||
id: string;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
}
|
||||
const todoSchema = schema.object({
|
||||
id: schema.string(),
|
||||
title: schema.string(),
|
||||
completed: schema.boolean(),
|
||||
});
|
||||
|
||||
export type TodoCreateIn = CreateIn<'todos', { title: string }>;
|
||||
export type TodoCreateOut = Todo; // TODO: Is this correct?
|
||||
export const createInSchema = schema.object({ title: schema.string() });
|
||||
export const createOutSchema = todoSchema;
|
||||
|
||||
export type TodoUpdateIn = UpdateIn<'todos', Partial<Omit<Todo, 'id'>>>;
|
||||
export type TodoUpdateOut = Todo;
|
||||
export const updateInSchema = schema.object({
|
||||
title: schema.maybe(schema.string()),
|
||||
completed: schema.maybe(schema.boolean()),
|
||||
});
|
||||
export const updateOutSchema = todoSchema;
|
||||
|
||||
export type TodoDeleteIn = DeleteIn<'todos', { id: string }>;
|
||||
export type TodoDeleteOut = void;
|
||||
|
||||
export type TodoGetIn = GetIn<'todos'>;
|
||||
export type TodoGetOut = Todo;
|
||||
export const getOutSchema = todoSchema;
|
||||
|
||||
export type TodoSearchIn = SearchIn<'todos', { filter?: 'todo' | 'completed' }>;
|
||||
export interface TodoSearchOut {
|
||||
hits: Todo[];
|
||||
}
|
||||
export const searchInSchema = schema.object({
|
||||
filter: schema.maybe(
|
||||
schema.oneOf([schema.literal('todo'), schema.literal('completed')], {
|
||||
defaultValue: undefined,
|
||||
})
|
||||
),
|
||||
});
|
||||
export const searchOutSchema = schema.object({
|
||||
hits: schema.arrayOf(todoSchema),
|
||||
});
|
13
examples/content_management_examples/jest.config.js
Normal file
13
examples/content_management_examples/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/examples/content_management_examples'],
|
||||
};
|
15
examples/content_management_examples/kibana.jsonc
Normal file
15
examples/content_management_examples/kibana.jsonc
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/content-management-examples-plugin",
|
||||
"owner": "@elastic/appex-sharedux",
|
||||
"description": "Example plugin integrating with content management plugin",
|
||||
"plugin": {
|
||||
"id": "contentManagementExamples",
|
||||
"server": true,
|
||||
"browser": true,
|
||||
"requiredPlugins": [
|
||||
"contentManagement",
|
||||
"developerExamples"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 ReactDOM from 'react-dom';
|
||||
import { EuiPageTemplate } from '@elastic/eui';
|
||||
import { AppMountParameters, CoreStart } from '@kbn/core/public';
|
||||
import { StartDeps } from '../types';
|
||||
import { TodoApp } from './todos';
|
||||
|
||||
export const renderApp = (
|
||||
{ notifications }: CoreStart,
|
||||
{ contentManagement }: StartDeps,
|
||||
{ element }: AppMountParameters
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<EuiPageTemplate offset={0}>
|
||||
<EuiPageTemplate.Section>
|
||||
<TodoApp contentClient={contentManagement.client} />
|
||||
</EuiPageTemplate.Section>
|
||||
</EuiPageTemplate>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
|
@ -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 { TodoApp } from './todo_app';
|
|
@ -7,8 +7,9 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Todos } from './todos';
|
||||
import { ContentClientProvider, ContentClient } from '../../public/content_client';
|
||||
import { ContentClientProvider, ContentClient } from '@kbn/content-management-plugin/public';
|
||||
|
||||
import { Todos } from '../todos';
|
||||
import { TodosClient } from './todos_client';
|
||||
|
||||
export default {
|
|
@ -7,28 +7,32 @@
|
|||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { CrudClient } from '../../public/crud_client';
|
||||
import type { CreateIn, DeleteIn, GetIn, SearchIn, UpdateIn } from '../../common';
|
||||
|
||||
export interface Todo {
|
||||
id: string;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
export type TodoCreateIn = CreateIn<'todos', { title: string }>;
|
||||
export type TodoUpdateIn = UpdateIn<'todos', Partial<Omit<Todo, 'id'>>>;
|
||||
export type TodoDeleteIn = DeleteIn<'todos', { id: string }>;
|
||||
export type TodoGetIn = GetIn<'todos'>;
|
||||
export type TodoSearchIn = SearchIn<'todos', { filter?: 'todo' | 'completed' }>;
|
||||
import type { CrudClient } from '@kbn/content-management-plugin/public';
|
||||
import type {
|
||||
TodoCreateIn,
|
||||
TodoUpdateIn,
|
||||
TodoDeleteIn,
|
||||
TodoGetIn,
|
||||
TodoSearchIn,
|
||||
TodoUpdateOut,
|
||||
TodoCreateOut,
|
||||
TodoSearchOut,
|
||||
TodoDeleteOut,
|
||||
Todo,
|
||||
TodoGetOut,
|
||||
} from '../../../../common/examples/todos';
|
||||
|
||||
/**
|
||||
* This client is used in the storybook examples to simulate a server-side registry client
|
||||
* and to show how a content type can have a custom client-side CRUD client without using the server-side registry
|
||||
*/
|
||||
export class TodosClient implements CrudClient {
|
||||
private todos: Todo[] = [
|
||||
{ id: uuidv4(), title: 'Learn Elasticsearch', completed: true },
|
||||
{ id: uuidv4(), title: 'Learn Kibana', completed: false },
|
||||
];
|
||||
|
||||
async create(input: TodoCreateIn): Promise<Todo> {
|
||||
async create(input: TodoCreateIn): Promise<TodoCreateOut> {
|
||||
const todo = {
|
||||
id: uuidv4(),
|
||||
title: input.data.title,
|
||||
|
@ -38,22 +42,22 @@ export class TodosClient implements CrudClient {
|
|||
return todo;
|
||||
}
|
||||
|
||||
async delete(input: TodoDeleteIn): Promise<void> {
|
||||
async delete(input: TodoDeleteIn): Promise<TodoDeleteOut> {
|
||||
this.todos = this.todos.filter((todo) => todo.id !== input.id);
|
||||
}
|
||||
|
||||
async get(input: TodoGetIn): Promise<Todo> {
|
||||
async get(input: TodoGetIn): Promise<TodoGetOut> {
|
||||
return this.todos.find((todo) => todo.id === input.id)!;
|
||||
}
|
||||
|
||||
async search(input: TodoSearchIn): Promise<{ hits: Todo[] }> {
|
||||
async search(input: TodoSearchIn): Promise<TodoSearchOut> {
|
||||
const filter = input.query.filter;
|
||||
if (filter === 'todo') return { hits: this.todos.filter((t) => !t.completed) };
|
||||
if (filter === 'completed') return { hits: this.todos.filter((t) => t.completed) };
|
||||
return { hits: [...this.todos] };
|
||||
}
|
||||
|
||||
async update(input: TodoUpdateIn): Promise<Todo> {
|
||||
async update(input: TodoUpdateIn): Promise<TodoUpdateOut> {
|
||||
const idToUpdate = input.id;
|
||||
const todoToUpdate = this.todos.find((todo) => todo.id === idToUpdate)!;
|
||||
if (todoToUpdate) {
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { Todos } from './todos';
|
||||
|
||||
export const TodoApp = (props: { contentClient: ContentClient }) => {
|
||||
return (
|
||||
<ContentClientProvider contentClient={props.contentClient}>
|
||||
<Todos />
|
||||
</ContentClientProvider>
|
||||
);
|
||||
};
|
|
@ -7,22 +7,32 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { EuiButtonGroup, EuiButtonIcon, EuiCheckbox, EuiFieldText, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
useCreateContentMutation,
|
||||
useDeleteContentMutation,
|
||||
useSearchContentQuery,
|
||||
useUpdateContentMutation,
|
||||
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
|
||||
} from '../../public/content_client';
|
||||
import type { Todo, TodoCreateIn, TodoDeleteIn, TodoSearchIn, TodoUpdateIn } from './todos_client';
|
||||
} from '@kbn/content-management-plugin/public';
|
||||
|
||||
const useCreateTodoMutation = () => useCreateContentMutation<TodoCreateIn, Todo>();
|
||||
const useDeleteTodoMutation = () => useDeleteContentMutation<TodoDeleteIn, void>();
|
||||
const useUpdateTodoMutation = () => useUpdateContentMutation<TodoUpdateIn, Todo>();
|
||||
import {
|
||||
TODO_CONTENT_ID,
|
||||
Todo,
|
||||
TodoCreateIn,
|
||||
TodoDeleteIn,
|
||||
TodoSearchIn,
|
||||
TodoUpdateIn,
|
||||
TodoUpdateOut,
|
||||
TodoCreateOut,
|
||||
TodoSearchOut,
|
||||
TodoDeleteOut,
|
||||
} from '../../../common/examples/todos';
|
||||
|
||||
const useCreateTodoMutation = () => useCreateContentMutation<TodoCreateIn, TodoCreateOut>();
|
||||
const useDeleteTodoMutation = () => useDeleteContentMutation<TodoDeleteIn, TodoDeleteOut>();
|
||||
const useUpdateTodoMutation = () => useUpdateContentMutation<TodoUpdateIn, TodoUpdateOut>();
|
||||
const useSearchTodosQuery = ({ filter }: { filter: TodoSearchIn['query']['filter'] }) =>
|
||||
useSearchContentQuery<TodoSearchIn, { hits: Todo[] }>({
|
||||
contentTypeId: 'todos',
|
||||
useSearchContentQuery<TodoSearchIn, TodoSearchOut>({
|
||||
contentTypeId: TODO_CONTENT_ID,
|
||||
query: { filter },
|
||||
});
|
||||
|
||||
|
@ -70,14 +80,17 @@ export const Todos = () => {
|
|||
<ul>
|
||||
{data.hits.map((todo: Todo) => (
|
||||
<React.Fragment key={todo.id}>
|
||||
<li style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<li
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
data-test-subj={`todoItem todoItem-${todo.id}`}
|
||||
>
|
||||
<EuiCheckbox
|
||||
id={todo.id + ''}
|
||||
key={todo.id}
|
||||
checked={todo.completed}
|
||||
onChange={(e) => {
|
||||
updateTodoMutation.mutate({
|
||||
contentTypeId: 'todos',
|
||||
contentTypeId: TODO_CONTENT_ID,
|
||||
id: todo.id,
|
||||
data: {
|
||||
completed: e.target.checked,
|
||||
|
@ -85,7 +98,7 @@ export const Todos = () => {
|
|||
});
|
||||
}}
|
||||
label={todo.title}
|
||||
data-test-subj={`todoCheckbox-${todo.id}`}
|
||||
data-test-subj={`todoCheckbox todoCheckbox-${todo.id}`}
|
||||
/>
|
||||
|
||||
<EuiButtonIcon
|
||||
|
@ -95,7 +108,7 @@ export const Todos = () => {
|
|||
aria-label="Delete"
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
deleteTodoMutation.mutate({ contentTypeId: 'todos', id: todo.id });
|
||||
deleteTodoMutation.mutate({ contentTypeId: TODO_CONTENT_ID, id: todo.id });
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
|
@ -112,7 +125,7 @@ export const Todos = () => {
|
|||
if (!inputRef || !inputRef.value) return;
|
||||
|
||||
createTodoMutation.mutate({
|
||||
contentTypeId: 'todos',
|
||||
contentTypeId: TODO_CONTENT_ID,
|
||||
data: {
|
||||
title: inputRef.value,
|
||||
},
|
13
examples/content_management_examples/public/index.ts
Normal file
13
examples/content_management_examples/public/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 { ContentManagementExamplesPlugin } from './plugin';
|
||||
|
||||
export function plugin() {
|
||||
return new ContentManagementExamplesPlugin();
|
||||
}
|
42
examples/content_management_examples/public/plugin.ts
Normal file
42
examples/content_management_examples/public/plugin.ts
Normal file
|
@ -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 { AppNavLinkStatus } from '@kbn/core-application-browser';
|
||||
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import { StartDeps, SetupDeps } from './types';
|
||||
|
||||
export class ContentManagementExamplesPlugin
|
||||
implements Plugin<unknown, unknown, SetupDeps, StartDeps>
|
||||
{
|
||||
public setup(core: CoreSetup<StartDeps>, { contentManagement, developerExamples }: SetupDeps) {
|
||||
developerExamples.register({
|
||||
appId: `contentManagementExamples`,
|
||||
title: `Content Management Examples`,
|
||||
description: 'Example plugin for the content management plugin',
|
||||
});
|
||||
|
||||
core.application.register({
|
||||
id: `contentManagementExamples`,
|
||||
title: `Content Management Examples`,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
async mount(params: AppMountParameters) {
|
||||
const { renderApp } = await import('./examples');
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
return renderApp(coreStart, deps, params);
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
22
examples/content_management_examples/public/types.ts
Normal file
22
examples/content_management_examples/public/types.ts
Normal file
|
@ -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 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 {
|
||||
ContentManagementPublicSetup,
|
||||
ContentManagementPublicStart,
|
||||
} from '@kbn/content-management-plugin/public';
|
||||
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
|
||||
|
||||
export interface SetupDeps {
|
||||
contentManagement: ContentManagementPublicSetup;
|
||||
developerExamples: DeveloperExamplesSetup;
|
||||
}
|
||||
|
||||
export interface StartDeps {
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
}
|
|
@ -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 { registerTodoContentType } from './todos';
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 {
|
||||
ContentStorage,
|
||||
StorageContext,
|
||||
ContentManagementServerSetup,
|
||||
} from '@kbn/content-management-plugin/server';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
createInSchema,
|
||||
searchInSchema,
|
||||
Todo,
|
||||
TODO_CONTENT_ID,
|
||||
updateInSchema,
|
||||
TodoSearchOut,
|
||||
TodoCreateOut,
|
||||
TodoUpdateOut,
|
||||
TodoDeleteOut,
|
||||
TodoGetOut,
|
||||
createOutSchema,
|
||||
getOutSchema,
|
||||
updateOutSchema,
|
||||
searchOutSchema,
|
||||
TodoUpdateIn,
|
||||
TodoSearchIn,
|
||||
TodoCreateIn,
|
||||
} from '../../../common/examples/todos';
|
||||
|
||||
export const registerTodoContentType = ({
|
||||
contentManagement,
|
||||
}: {
|
||||
contentManagement: ContentManagementServerSetup;
|
||||
}) => {
|
||||
contentManagement.register({
|
||||
id: TODO_CONTENT_ID,
|
||||
schemas: {
|
||||
content: {
|
||||
create: {
|
||||
in: {
|
||||
data: createInSchema,
|
||||
},
|
||||
out: {
|
||||
result: createOutSchema,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
in: {
|
||||
data: updateInSchema,
|
||||
},
|
||||
out: {
|
||||
result: updateOutSchema,
|
||||
},
|
||||
},
|
||||
search: {
|
||||
in: {
|
||||
query: searchInSchema,
|
||||
},
|
||||
out: {
|
||||
result: searchOutSchema,
|
||||
},
|
||||
},
|
||||
get: {
|
||||
out: {
|
||||
result: getOutSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
storage: new TodosStorage(),
|
||||
});
|
||||
};
|
||||
|
||||
class TodosStorage implements ContentStorage {
|
||||
private db: Map<string, Todo> = new Map();
|
||||
|
||||
constructor() {
|
||||
const id1 = v4();
|
||||
this.db.set(id1, {
|
||||
id: id1,
|
||||
title: 'Learn Elasticsearch',
|
||||
completed: true,
|
||||
});
|
||||
const id2 = v4();
|
||||
this.db.set(id2, {
|
||||
id: id2,
|
||||
title: 'Learn Kibana',
|
||||
completed: false,
|
||||
});
|
||||
}
|
||||
|
||||
async get(ctx: StorageContext, id: string): Promise<TodoGetOut> {
|
||||
return this.db.get(id)!;
|
||||
}
|
||||
|
||||
async bulkGet(ctx: StorageContext, ids: string[]): Promise<TodoGetOut[]> {
|
||||
return ids.map((id) => this.db.get(id)!);
|
||||
}
|
||||
|
||||
async create(ctx: StorageContext, data: TodoCreateIn['data']): Promise<TodoCreateOut> {
|
||||
const todo: Todo = {
|
||||
...data,
|
||||
completed: false,
|
||||
id: v4(),
|
||||
};
|
||||
|
||||
this.db.set(todo.id, todo);
|
||||
|
||||
return todo;
|
||||
}
|
||||
|
||||
async update(
|
||||
ctx: StorageContext,
|
||||
id: string,
|
||||
data: TodoUpdateIn['data']
|
||||
): Promise<TodoUpdateOut> {
|
||||
const content = this.db.get(id);
|
||||
if (!content) {
|
||||
throw new Error(`Content to update not found [${id}].`);
|
||||
}
|
||||
|
||||
const updatedContent = {
|
||||
...content,
|
||||
...data,
|
||||
};
|
||||
|
||||
this.db.set(id, updatedContent);
|
||||
|
||||
return updatedContent;
|
||||
}
|
||||
|
||||
async delete(ctx: StorageContext, id: string): Promise<TodoDeleteOut> {
|
||||
this.db.delete(id);
|
||||
}
|
||||
|
||||
async search(ctx: StorageContext, query: TodoSearchIn['query']): Promise<TodoSearchOut> {
|
||||
const hits = Array.from(this.db.values());
|
||||
if (query.filter === 'todo') return { hits: hits.filter((t) => !t.completed) };
|
||||
if (query.filter === 'completed') return { hits: hits.filter((t) => t.completed) };
|
||||
return { hits };
|
||||
}
|
||||
}
|
14
examples/content_management_examples/server/index.ts
Normal file
14
examples/content_management_examples/server/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { PluginInitializerContext } from '@kbn/core/server';
|
||||
import { ContentManagementExamplesPlugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new ContentManagementExamplesPlugin(initializerContext);
|
||||
}
|
26
examples/content_management_examples/server/plugin.ts
Normal file
26
examples/content_management_examples/server/plugin.ts
Normal file
|
@ -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 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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server';
|
||||
import type { SetupDeps, StartDeps } from './types';
|
||||
import { registerTodoContentType } from './examples/todos';
|
||||
|
||||
export class ContentManagementExamplesPlugin implements Plugin<void, void, SetupDeps, StartDeps> {
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup, { contentManagement }: SetupDeps) {
|
||||
registerTodoContentType({ contentManagement });
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, { contentManagement }: StartDeps) {
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
20
examples/content_management_examples/server/types.ts
Normal file
20
examples/content_management_examples/server/types.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 type {
|
||||
ContentManagementServerSetup,
|
||||
ContentManagementServerStart,
|
||||
} from '@kbn/content-management-plugin/server';
|
||||
|
||||
export interface SetupDeps {
|
||||
contentManagement: ContentManagementServerSetup;
|
||||
}
|
||||
|
||||
export interface StartDeps {
|
||||
contentManagement: ContentManagementServerStart;
|
||||
}
|
24
examples/content_management_examples/tsconfig.json
Normal file
24
examples/content_management_examples/tsconfig.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"common/**/*",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../typings/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/developer-examples-plugin",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/core-application-browser",
|
||||
]
|
||||
}
|
|
@ -182,6 +182,7 @@
|
|||
"@kbn/config-schema": "link:packages/kbn-config-schema",
|
||||
"@kbn/console-plugin": "link:src/plugins/console",
|
||||
"@kbn/content-management-content-editor": "link:packages/content-management/content_editor",
|
||||
"@kbn/content-management-examples-plugin": "link:examples/content_management_examples",
|
||||
"@kbn/content-management-plugin": "link:src/plugins/content_management",
|
||||
"@kbn/content-management-table-list": "link:packages/content-management/table_list",
|
||||
"@kbn/controls-example-plugin": "link:examples/controls_example",
|
||||
|
|
|
@ -18,7 +18,7 @@ export const storybookAliases = {
|
|||
language_documentation_popover: 'packages/kbn-language-documentation-popover/.storybook',
|
||||
chart_icons: 'packages/kbn-chart-icons/.storybook',
|
||||
content_management: 'packages/content-management/.storybook',
|
||||
content_management_plugin: 'src/plugins/content_management/.storybook',
|
||||
content_management_examples: 'examples/content_management_examples/.storybook',
|
||||
controls: 'src/plugins/controls/storybook',
|
||||
custom_integrations: 'src/plugins/custom_integrations/storybook',
|
||||
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook',
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
|
||||
export const PLUGIN_ID = 'contentManagement';
|
||||
|
||||
export const API_ENDPOINT = '/api/content_management';
|
||||
export const API_ENDPOINT = '/api/content_management/rpc';
|
||||
|
|
|
@ -19,4 +19,7 @@ export type {
|
|||
SearchIn,
|
||||
} from './rpc';
|
||||
|
||||
export { procedureNames, schemas as rpcSchemas } from './rpc';
|
||||
export { procedureNames } from './rpc/constants';
|
||||
|
||||
// intentionally not exporting schemas to not include @kbn/schema in the public bundle
|
||||
// export { schemas as rpcSchemas } from './rpc';
|
||||
|
|
11
src/plugins/content_management/common/schemas.ts
Normal file
11
src/plugins/content_management/common/schemas.ts
Normal file
|
@ -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.
|
||||
*/
|
||||
|
||||
// exporting schemas separately from the index.ts file to not include @kbn/schema in the public bundle
|
||||
// should be only used server-side or in jest tests
|
||||
export { schemas as rpcSchemas } from './rpc';
|
|
@ -7,6 +7,18 @@
|
|||
*/
|
||||
|
||||
import { ContentManagementPlugin } from './plugin';
|
||||
export type { CrudClient } from './crud_client';
|
||||
export {
|
||||
ContentClientProvider,
|
||||
ContentClient,
|
||||
useCreateContentMutation,
|
||||
useUpdateContentMutation,
|
||||
useDeleteContentMutation,
|
||||
useSearchContentQuery,
|
||||
useGetContentQuery,
|
||||
useContentClient,
|
||||
type QueryOptions,
|
||||
} from './content_client';
|
||||
|
||||
export function plugin() {
|
||||
return new ContentManagementPlugin();
|
||||
|
|
|
@ -13,8 +13,9 @@ import {
|
|||
SetupDependencies,
|
||||
StartDependencies,
|
||||
} from './types';
|
||||
import type { ContentClient } from './content_client';
|
||||
import type { ContentTypeRegistry } from './registry';
|
||||
import { ContentClient } from './content_client';
|
||||
import { ContentTypeRegistry } from './registry';
|
||||
import { RpcClient } from './rpc_client';
|
||||
|
||||
export class ContentManagementPlugin
|
||||
implements
|
||||
|
@ -26,20 +27,17 @@ export class ContentManagementPlugin
|
|||
>
|
||||
{
|
||||
public setup(core: CoreSetup, deps: SetupDependencies) {
|
||||
// don't actually expose the client and the registry until it is used to avoid increasing bundle size
|
||||
return {
|
||||
registry: {} as ContentTypeRegistry,
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, deps: StartDependencies) {
|
||||
// don't actually expose the client and the registry until it is used to avoid increasing bundle size
|
||||
// const rpcClient = new RpcClient(core.http);
|
||||
// const contentTypeRegistry = new ContentTypeRegistry();
|
||||
// const contentClient = new ContentClient(
|
||||
// (contentType) => contentTypeRegistry.get(contentType)?.crud() ?? rpcClient
|
||||
// );
|
||||
// return { client: contentClient, registry: contentTypeRegistry };
|
||||
return { client: {} as ContentClient, registry: {} as ContentTypeRegistry };
|
||||
const rpcClient = new RpcClient(core.http);
|
||||
const contentTypeRegistry = new ContentTypeRegistry();
|
||||
const contentClient = new ContentClient(
|
||||
(contentType) => contentTypeRegistry.get(contentType)?.crud ?? rpcClient
|
||||
);
|
||||
return { client: contentClient, registry: contentTypeRegistry };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { procedureNames, API_ENDPOINT } from '../../common';
|
||||
import { API_ENDPOINT, procedureNames } from '../../common';
|
||||
|
||||
import { RpcClient } from './rpc_client';
|
||||
|
||||
|
|
|
@ -18,36 +18,44 @@ import type {
|
|||
ProcedureName,
|
||||
} from '../../common';
|
||||
import type { CrudClient } from '../crud_client/crud_client';
|
||||
import type {
|
||||
GetResponse,
|
||||
BulkGetResponse,
|
||||
CreateItemResponse,
|
||||
DeleteItemResponse,
|
||||
UpdateItemResponse,
|
||||
SearchResponse,
|
||||
} from '../../server/core/crud';
|
||||
|
||||
export class RpcClient implements CrudClient {
|
||||
constructor(private http: { post: HttpSetup['post'] }) {}
|
||||
|
||||
public get<I extends GetIn = GetIn, O = unknown>(input: I): Promise<O> {
|
||||
return this.sendMessage('get', input);
|
||||
return this.sendMessage<GetResponse<O>>('get', input).then((r) => r.item);
|
||||
}
|
||||
|
||||
public bulkGet<I extends BulkGetIn = BulkGetIn, O = unknown>(input: I): Promise<O> {
|
||||
return this.sendMessage('bulkGet', input);
|
||||
return this.sendMessage<BulkGetResponse<O>>('bulkGet', input).then((r) => r.items);
|
||||
}
|
||||
|
||||
public create<I extends CreateIn = CreateIn, O = unknown>(input: I): Promise<O> {
|
||||
return this.sendMessage('create', input);
|
||||
return this.sendMessage<CreateItemResponse<O>>('create', input).then((r) => r.result);
|
||||
}
|
||||
|
||||
public update<I extends UpdateIn = UpdateIn, O = unknown>(input: I): Promise<O> {
|
||||
return this.sendMessage('update', input);
|
||||
return this.sendMessage<UpdateItemResponse<O>>('update', input).then((r) => r.result);
|
||||
}
|
||||
|
||||
public delete<I extends DeleteIn = DeleteIn, O = unknown>(input: I): Promise<O> {
|
||||
return this.sendMessage('delete', input);
|
||||
return this.sendMessage<DeleteItemResponse>('delete', input).then((r) => r.result);
|
||||
}
|
||||
|
||||
public search<I extends SearchIn = SearchIn, O = unknown>(input: I): Promise<O> {
|
||||
return this.sendMessage('search', input);
|
||||
return this.sendMessage<SearchResponse>('search', input).then((r) => r.result);
|
||||
}
|
||||
|
||||
private sendMessage = async (name: ProcedureName, input: any): Promise<any> => {
|
||||
const { result } = await this.http.post<{ result: any }>(`${API_ENDPOINT}/${name}`, {
|
||||
private sendMessage = async <O = unknown>(name: ProcedureName, input: any): Promise<O> => {
|
||||
const { result } = await this.http.post<{ result: O }>(`${API_ENDPOINT}/${name}`, {
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
return result;
|
||||
|
|
|
@ -10,7 +10,7 @@ import type { ContentStorage, StorageContext } from './types';
|
|||
|
||||
export interface GetResponse<T = any> {
|
||||
contentTypeId: string;
|
||||
item?: T;
|
||||
item: T;
|
||||
}
|
||||
|
||||
export interface BulkGetResponse<T = any> {
|
||||
|
@ -33,6 +33,11 @@ export interface DeleteItemResponse<T = any> {
|
|||
result: T;
|
||||
}
|
||||
|
||||
export interface SearchResponse<T = any> {
|
||||
contentTypeId: string;
|
||||
result: T;
|
||||
}
|
||||
|
||||
export class ContentCrud implements ContentStorage {
|
||||
private storage: ContentStorage;
|
||||
private eventBus: EventBus;
|
||||
|
@ -245,7 +250,7 @@ export class ContentCrud implements ContentStorage {
|
|||
ctx: StorageContext,
|
||||
query: Query,
|
||||
options?: Options
|
||||
): Promise<CreateItemResponse<O>> {
|
||||
): Promise<SearchResponse<O>> {
|
||||
this.eventBus.emit({
|
||||
type: 'searchItemStart',
|
||||
contentTypeId: this.contentTypeId,
|
||||
|
|
|
@ -14,3 +14,4 @@ export function plugin(initializerContext: PluginInitializerContext) {
|
|||
}
|
||||
|
||||
export type { ContentManagementServerSetup, ContentManagementServerStart } from './types';
|
||||
export type { ContentStorage, StorageContext } from './core';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { rpcSchemas } from '../../../common';
|
||||
import { rpcSchemas } from '../../../common/schemas';
|
||||
import type { BulkGetIn } from '../../../common';
|
||||
import type { StorageContext, ContentCrud } from '../../core';
|
||||
import type { ProcedureDefinition } from '../rpc_service';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { rpcSchemas } from '../../../common';
|
||||
import { rpcSchemas } from '../../../common/schemas';
|
||||
import type { CreateIn } from '../../../common';
|
||||
import type { StorageContext, ContentCrud } from '../../core';
|
||||
import type { ProcedureDefinition } from '../rpc_service';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { rpcSchemas } from '../../../common';
|
||||
import { rpcSchemas } from '../../../common/schemas';
|
||||
import type { DeleteIn } from '../../../common';
|
||||
import type { StorageContext, ContentCrud } from '../../core';
|
||||
import type { ProcedureDefinition } from '../rpc_service';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { rpcSchemas } from '../../../common';
|
||||
import { rpcSchemas } from '../../../common/schemas';
|
||||
import type { GetIn } from '../../../common';
|
||||
import type { ContentCrud, StorageContext } from '../../core';
|
||||
import { validate } from '../../utils';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { rpcSchemas } from '../../../common';
|
||||
import { rpcSchemas } from '../../../common/schemas';
|
||||
import type { SearchIn } from '../../../common';
|
||||
import type { StorageContext, ContentCrud } from '../../core';
|
||||
import type { ProcedureDefinition } from '../rpc_service';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { rpcSchemas } from '../../../common';
|
||||
import { rpcSchemas } from '../../../common/schemas';
|
||||
import type { UpdateIn } from '../../../common';
|
||||
import type { StorageContext, ContentCrud } from '../../core';
|
||||
import type { ProcedureDefinition } from '../rpc_service';
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CoreApi } from './core';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface SetupDependencies {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ContentManagementServerSetup {}
|
||||
export interface ContentManagementServerSetup extends CoreApi {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ContentManagementServerStart {}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
},
|
||||
"include": ["common/**/*", "public/**/*", "server/**/*", "demo/**/*"],
|
||||
"include": ["common/**/*", "public/**/*", "server/**/*"],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/config-schema",
|
||||
|
|
|
@ -28,6 +28,7 @@ export default async function ({ readConfigFile }) {
|
|||
require.resolve('./field_formats'),
|
||||
require.resolve('./partial_results'),
|
||||
require.resolve('./search'),
|
||||
require.resolve('./content_management'),
|
||||
],
|
||||
services: {
|
||||
...functionalConfig.get('services'),
|
||||
|
|
16
test/examples/content_management/index.ts
Normal file
16
test/examples/content_management/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 '../../plugin_functional/services';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
|
||||
describe('content management examples', function () {
|
||||
loadTestFile(require.resolve('./todo_app'));
|
||||
});
|
||||
}
|
72
test/examples/content_management/todo_app.ts
Normal file
72
test/examples/content_management/todo_app.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { Key } from 'selenium-webdriver';
|
||||
|
||||
import { PluginFunctionalProviderContext } from '../../plugin_functional/services';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
|
||||
describe('Todo app', () => {
|
||||
it('Todo app works', async () => {
|
||||
const appId = 'contentManagementExamples';
|
||||
await PageObjects.common.navigateToApp(appId);
|
||||
|
||||
// check that initial state is correct
|
||||
let todos = await testSubjects.findAll(`~todoItem`);
|
||||
expect(todos.length).to.be(2);
|
||||
|
||||
// check that filters work
|
||||
await (await find.byCssSelector('label[title="Completed"]')).click();
|
||||
todos = await testSubjects.findAll(`~todoItem`);
|
||||
expect(todos.length).to.be(1);
|
||||
|
||||
await (await find.byCssSelector('label[title="Todo"]')).click();
|
||||
todos = await testSubjects.findAll(`~todoItem`);
|
||||
expect(todos.length).to.be(1);
|
||||
|
||||
await (await find.byCssSelector('label[title="All"]')).click();
|
||||
todos = await testSubjects.findAll(`~todoItem`);
|
||||
expect(todos.length).to.be(2);
|
||||
|
||||
// check that adding new todo works
|
||||
await testSubjects.setValue('newTodo', 'New todo');
|
||||
await (await testSubjects.find('newTodo')).pressKeys(Key.ENTER);
|
||||
await retry.tryForTime(1000, async () => {
|
||||
todos = await testSubjects.findAll(`~todoItem`);
|
||||
expect(todos.length).to.be(3);
|
||||
});
|
||||
|
||||
// check that updating todo works
|
||||
let newTodo = todos[2];
|
||||
expect(await newTodo.getVisibleText()).to.be('New todo');
|
||||
let newTodoCheckbox = await newTodo.findByTestSubject('~todoCheckbox');
|
||||
expect(await newTodoCheckbox.isSelected()).to.be(false);
|
||||
await (await newTodo.findByTagName('label')).click();
|
||||
|
||||
await (await find.byCssSelector('label[title="Completed"]')).click();
|
||||
todos = await testSubjects.findAll(`~todoItem`);
|
||||
expect(todos.length).to.be(2);
|
||||
newTodo = todos[1];
|
||||
expect(await newTodo.getVisibleText()).to.be('New todo');
|
||||
newTodoCheckbox = await newTodo.findByTestSubject('~todoCheckbox');
|
||||
expect(await newTodoCheckbox.isSelected()).to.be(true);
|
||||
|
||||
// check that deleting todo works
|
||||
await (await newTodo.findByCssSelector('[aria-label="Delete"]')).click();
|
||||
todos = await testSubjects.findAll(`~todoItem`);
|
||||
expect(todos.length).to.be(1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -160,6 +160,8 @@
|
|||
"@kbn/console-plugin/*": ["src/plugins/console/*"],
|
||||
"@kbn/content-management-content-editor": ["packages/content-management/content_editor"],
|
||||
"@kbn/content-management-content-editor/*": ["packages/content-management/content_editor/*"],
|
||||
"@kbn/content-management-examples-plugin": ["examples/content_management_examples"],
|
||||
"@kbn/content-management-examples-plugin/*": ["examples/content_management_examples/*"],
|
||||
"@kbn/content-management-plugin": ["src/plugins/content_management"],
|
||||
"@kbn/content-management-plugin/*": ["src/plugins/content_management/*"],
|
||||
"@kbn/content-management-table-list": ["packages/content-management/table_list"],
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -3057,6 +3057,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/content-management-examples-plugin@link:examples/content_management_examples":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/content-management-plugin@link:src/plugins/content_management":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -17589,9 +17593,9 @@ inquirer@^8.2.3:
|
|||
wrap-ansi "^7.0.0"
|
||||
|
||||
install-artifact-from-github@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.1.tgz#eefaad9af35d632e5d912ad1569c1de38c3c2462"
|
||||
integrity sha512-3l3Bymg2eKDsN5wQuMfgGEj2x6l5MCAv0zPL6rxHESufFVlEAKW/6oY9F1aGgvY/EgWm5+eWGRjINveL4X7Hgg==
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.2.tgz#1a16d9508e40330523a3017ae0d4713ccc64de82"
|
||||
integrity sha512-yCFcLvqk0yQdxx0uJz4t9Z3adDMLAYrcGYv546uRXCSvxE+GqNYhhz/KmrGcUKGI/gVLR9n/e/zM9jX/+ASMJQ==
|
||||
|
||||
internal-slot@^1.0.3:
|
||||
version "1.0.3"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue