* Update mapColumn expression function implementation to support partial results * Add partial results example plugin # Conflicts: # .github/CODEOWNERS
This commit is contained in:
parent
3398a48720
commit
47a4c3de2a
9
examples/partial_results_example/README.md
Executable file
9
examples/partial_results_example/README.md
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
## Partial Results Example
|
||||||
|
|
||||||
|
The partial results is a feature of the expressions plugin allowing to emit intermediate execution results over time.
|
||||||
|
|
||||||
|
This example plugin demonstrates:
|
||||||
|
|
||||||
|
1. An expression function emitting a datatable with intermediate results (`getEvents`).
|
||||||
|
2. An expression function emitting an infinite number of results (`countEvent`).
|
||||||
|
3. A combination of those two functions using the `mapColumn` function that continuously updates the resulting table.
|
12
examples/partial_results_example/kibana.json
Normal file
12
examples/partial_results_example/kibana.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"id": "paertialResultsExample",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"kibanaVersion": "kibana",
|
||||||
|
"ui": true,
|
||||||
|
"owner": {
|
||||||
|
"name": "App Services",
|
||||||
|
"githubTeam": "kibana-app-services"
|
||||||
|
},
|
||||||
|
"description": "A plugin demonstrating partial results in the expressions plugin",
|
||||||
|
"requiredPlugins": ["developerExamples", "expressions"]
|
||||||
|
}
|
90
examples/partial_results_example/public/app/app.tsx
Normal file
90
examples/partial_results_example/public/app/app.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* 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, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { pluck } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
EuiBasicTable,
|
||||||
|
EuiCallOut,
|
||||||
|
EuiCodeBlock,
|
||||||
|
EuiPage,
|
||||||
|
EuiPageBody,
|
||||||
|
EuiPageContent,
|
||||||
|
EuiPageContentBody,
|
||||||
|
EuiPageHeader,
|
||||||
|
EuiPageHeaderSection,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiText,
|
||||||
|
EuiTitle,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import type { Datatable } from 'src/plugins/expressions';
|
||||||
|
import { ExpressionsContext } from './expressions_context';
|
||||||
|
|
||||||
|
const expression = `getEvents
|
||||||
|
| mapColumn name="Count" expression={
|
||||||
|
countEvent {pluck "event"}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
const expressions = useContext(ExpressionsContext);
|
||||||
|
const [datatable, setDatatable] = useState<Datatable>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = expressions
|
||||||
|
?.execute<null, Datatable>(expression, null)
|
||||||
|
.getData()
|
||||||
|
.pipe(pluck('result'))
|
||||||
|
.subscribe((value) => setDatatable(value as Datatable));
|
||||||
|
|
||||||
|
return () => subscription?.unsubscribe();
|
||||||
|
}, [expressions]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiPage>
|
||||||
|
<EuiPageBody style={{ maxWidth: 1200, margin: '0 auto' }}>
|
||||||
|
<EuiPageHeader>
|
||||||
|
<EuiPageHeaderSection>
|
||||||
|
<EuiTitle size="l">
|
||||||
|
<h1>Partial Results Demo</h1>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPageHeaderSection>
|
||||||
|
</EuiPageHeader>
|
||||||
|
<EuiPageContent>
|
||||||
|
<EuiPageContentBody style={{ maxWidth: 800, margin: '0 auto' }}>
|
||||||
|
<EuiText data-test-subj="example-help">
|
||||||
|
<p>
|
||||||
|
This example listens for the window events and adds them to the table along with a
|
||||||
|
trigger counter.
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer size={'m'} />
|
||||||
|
<EuiCodeBlock>{expression}</EuiCodeBlock>
|
||||||
|
<EuiSpacer size={'m'} />
|
||||||
|
{datatable ? (
|
||||||
|
<EuiBasicTable
|
||||||
|
textOnly={true}
|
||||||
|
data-test-subj={'example-table'}
|
||||||
|
columns={datatable.columns?.map(({ id: field, name }) => ({
|
||||||
|
field,
|
||||||
|
name,
|
||||||
|
'data-test-subj': `example-column-${field.toLowerCase()}`,
|
||||||
|
}))}
|
||||||
|
items={datatable.rows ?? []}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EuiCallOut color="success">
|
||||||
|
<p>Click or press any key.</p>
|
||||||
|
</EuiCallOut>
|
||||||
|
)}
|
||||||
|
</EuiPageContentBody>
|
||||||
|
</EuiPageContent>
|
||||||
|
</EuiPageBody>
|
||||||
|
</EuiPage>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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 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 { createContext } from 'react';
|
||||||
|
import type { ExpressionsServiceStart } from 'src/plugins/expressions';
|
||||||
|
|
||||||
|
export const ExpressionsContext = createContext<ExpressionsServiceStart | undefined>(undefined);
|
10
examples/partial_results_example/public/app/index.ts
Normal file
10
examples/partial_results_example/public/app/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* 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 './app';
|
||||||
|
export * from './expressions_context';
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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 { Observable, fromEvent } from 'rxjs';
|
||||||
|
import { scan, startWith } from 'rxjs/operators';
|
||||||
|
import type { ExpressionFunctionDefinition } from 'src/plugins/expressions';
|
||||||
|
|
||||||
|
export interface CountEventArguments {
|
||||||
|
event: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const countEvent: ExpressionFunctionDefinition<
|
||||||
|
'countEvent',
|
||||||
|
null,
|
||||||
|
CountEventArguments,
|
||||||
|
Observable<number>
|
||||||
|
> = {
|
||||||
|
name: 'countEvent',
|
||||||
|
type: 'number',
|
||||||
|
help: 'Subscribes for an event and counts a number of triggers.',
|
||||||
|
args: {
|
||||||
|
event: {
|
||||||
|
aliases: ['_'],
|
||||||
|
types: ['string'],
|
||||||
|
help: 'The event name.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn(input, { event }) {
|
||||||
|
return fromEvent(window, event).pipe(
|
||||||
|
scan((count) => count + 1, 1),
|
||||||
|
startWith(1)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* 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 { Observable, fromEvent, merge } from 'rxjs';
|
||||||
|
import { distinct, map, pluck, scan, take } from 'rxjs/operators';
|
||||||
|
import type { Datatable, ExpressionFunctionDefinition } from 'src/plugins/expressions';
|
||||||
|
|
||||||
|
const EVENTS: Array<keyof WindowEventMap> = [
|
||||||
|
'mousedown',
|
||||||
|
'mouseup',
|
||||||
|
'click',
|
||||||
|
'keydown',
|
||||||
|
'keyup',
|
||||||
|
'keypress',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getEvents: ExpressionFunctionDefinition<
|
||||||
|
'getEvents',
|
||||||
|
null,
|
||||||
|
{},
|
||||||
|
Observable<Datatable>
|
||||||
|
> = {
|
||||||
|
name: 'getEvents',
|
||||||
|
type: 'datatable',
|
||||||
|
help: 'Listens for the window events and returns a table with the triggered ones.',
|
||||||
|
args: {},
|
||||||
|
fn() {
|
||||||
|
return merge(...EVENTS.map((event) => fromEvent(window, event))).pipe(
|
||||||
|
pluck('type'),
|
||||||
|
distinct(),
|
||||||
|
take(EVENTS.length),
|
||||||
|
scan((events, event) => [...events, event], [] as string[]),
|
||||||
|
map((events) => ({
|
||||||
|
type: 'datatable',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
id: 'event',
|
||||||
|
meta: { type: 'string' },
|
||||||
|
name: 'Event',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rows: Array.from(events).map((event) => ({ event })),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
11
examples/partial_results_example/public/functions/index.ts
Normal file
11
examples/partial_results_example/public/functions/index.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './count_event';
|
||||||
|
export * from './get_events';
|
||||||
|
export * from './pluck';
|
32
examples/partial_results_example/public/functions/pluck.ts
Normal file
32
examples/partial_results_example/public/functions/pluck.ts
Normal file
|
@ -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 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 { Datatable, ExpressionFunctionDefinition } from 'src/plugins/expressions';
|
||||||
|
|
||||||
|
export interface PluckArguments {
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pluck: ExpressionFunctionDefinition<'pluck', Datatable, PluckArguments, unknown> = {
|
||||||
|
name: 'pluck',
|
||||||
|
inputTypes: ['datatable'],
|
||||||
|
help: 'Takes a cell from the first table row.',
|
||||||
|
args: {
|
||||||
|
key: {
|
||||||
|
aliases: ['_'],
|
||||||
|
types: ['string'],
|
||||||
|
help: 'The column id.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn({ rows }, { key }) {
|
||||||
|
const [{ [key]: value }] = rows;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
12
examples/partial_results_example/public/index.ts
Executable file
12
examples/partial_results_example/public/index.ts
Executable file
|
@ -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 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 { PartialResultsExamplePlugin } from './plugin';
|
||||||
|
|
||||||
|
export function plugin() {
|
||||||
|
return new PartialResultsExamplePlugin();
|
||||||
|
}
|
57
examples/partial_results_example/public/plugin.tsx
Executable file
57
examples/partial_results_example/public/plugin.tsx
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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 type { ExpressionsService, ExpressionsServiceSetup } from 'src/plugins/expressions';
|
||||||
|
import { AppMountParameters, AppNavLinkStatus, CoreSetup, Plugin } from '../../../src/core/public';
|
||||||
|
import type { DeveloperExamplesSetup } from '../../developer_examples/public';
|
||||||
|
import { App, ExpressionsContext } from './app';
|
||||||
|
import { countEvent, getEvents, pluck } from './functions';
|
||||||
|
|
||||||
|
interface SetupDeps {
|
||||||
|
developerExamples: DeveloperExamplesSetup;
|
||||||
|
expressions: ExpressionsServiceSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PartialResultsExamplePlugin implements Plugin<void, void, SetupDeps> {
|
||||||
|
private expressions?: ExpressionsService;
|
||||||
|
|
||||||
|
setup({ application }: CoreSetup, { expressions, developerExamples }: SetupDeps) {
|
||||||
|
this.expressions = expressions.fork();
|
||||||
|
this.expressions.registerFunction(countEvent);
|
||||||
|
this.expressions.registerFunction(getEvents);
|
||||||
|
this.expressions.registerFunction(pluck);
|
||||||
|
|
||||||
|
application.register({
|
||||||
|
id: 'partialResultsExample',
|
||||||
|
title: 'Partial Results Example',
|
||||||
|
navLinkStatus: AppNavLinkStatus.hidden,
|
||||||
|
mount: async ({ element }: AppMountParameters) => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<ExpressionsContext.Provider value={this.expressions}>
|
||||||
|
<App />
|
||||||
|
</ExpressionsContext.Provider>,
|
||||||
|
element
|
||||||
|
);
|
||||||
|
return () => ReactDOM.unmountComponentAtNode(element);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
developerExamples.register({
|
||||||
|
appId: 'partialResultsExample',
|
||||||
|
title: 'Partial Results Example',
|
||||||
|
description: 'Learn how to use partial results in the expressions plugin.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {}
|
||||||
|
}
|
19
examples/partial_results_example/tsconfig.json
Normal file
19
examples/partial_results_example/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./target",
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"index.ts",
|
||||||
|
"public/**/*.ts",
|
||||||
|
"public/**/*.tsx",
|
||||||
|
"../../typings/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../../src/core/tsconfig.json" },
|
||||||
|
{ "path": "../developer_examples/tsconfig.json" },
|
||||||
|
{ "path": "../../src/plugins/expressions/tsconfig.json" },
|
||||||
|
]
|
||||||
|
}
|
|
@ -6,11 +6,11 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable, defer, of, zip } from 'rxjs';
|
import { Observable, combineLatest, defer } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { defaultIfEmpty, map } from 'rxjs/operators';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { ExpressionFunctionDefinition } from '../types';
|
import { ExpressionFunctionDefinition } from '../types';
|
||||||
import { Datatable, DatatableColumn, DatatableColumnType, getType } from '../../expression_types';
|
import { Datatable, DatatableColumnType, getType } from '../../expression_types';
|
||||||
|
|
||||||
export interface MapColumnArguments {
|
export interface MapColumnArguments {
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
|
@ -81,30 +81,34 @@ export const mapColumn: ExpressionFunctionDefinition<
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn(input, args) {
|
fn(input, args) {
|
||||||
|
const metaColumn = args.copyMetaFrom
|
||||||
|
? input.columns.find(({ id }) => id === args.copyMetaFrom)
|
||||||
|
: undefined;
|
||||||
const existingColumnIndex = input.columns.findIndex(({ id, name }) =>
|
const existingColumnIndex = input.columns.findIndex(({ id, name }) =>
|
||||||
args.id ? id === args.id : name === args.name
|
args.id ? id === args.id : name === args.name
|
||||||
);
|
);
|
||||||
const id = input.columns[existingColumnIndex]?.id ?? args.id ?? args.name;
|
const columnIndex = existingColumnIndex === -1 ? input.columns.length : existingColumnIndex;
|
||||||
|
const id = input.columns[columnIndex]?.id ?? args.id ?? args.name;
|
||||||
|
|
||||||
return defer(() => {
|
return defer(() =>
|
||||||
const rows$ = input.rows.length
|
combineLatest(
|
||||||
? zip(
|
input.rows.map((row) =>
|
||||||
...input.rows.map((row) =>
|
|
||||||
args
|
args
|
||||||
.expression({
|
.expression({
|
||||||
type: 'datatable',
|
type: 'datatable',
|
||||||
columns: [...input.columns],
|
columns: [...input.columns],
|
||||||
rows: [row],
|
rows: [row],
|
||||||
})
|
})
|
||||||
.pipe(map((value) => ({ ...row, [id]: value })))
|
.pipe(
|
||||||
|
map((value) => ({ ...row, [id]: value })),
|
||||||
|
defaultIfEmpty(row)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
: of([]);
|
)
|
||||||
|
).pipe(
|
||||||
return rows$.pipe<Datatable>(
|
defaultIfEmpty([] as Datatable['rows']),
|
||||||
map((rows) => {
|
map((rows) => {
|
||||||
let type: DatatableColumnType = 'null';
|
let type: DatatableColumnType = 'null';
|
||||||
if (rows.length) {
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const rowType = getType(row[id]);
|
const rowType = getType(row[id]);
|
||||||
if (rowType !== 'null') {
|
if (rowType !== 'null') {
|
||||||
|
@ -112,25 +116,17 @@ export const mapColumn: ExpressionFunctionDefinition<
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const newColumn: DatatableColumn = {
|
|
||||||
id,
|
|
||||||
name: args.name,
|
|
||||||
meta: { type, params: { id: type } },
|
|
||||||
};
|
|
||||||
if (args.copyMetaFrom) {
|
|
||||||
const metaSourceFrom = input.columns.find(
|
|
||||||
({ id: columnId }) => columnId === args.copyMetaFrom
|
|
||||||
);
|
|
||||||
newColumn.meta = { ...newColumn.meta, ...(metaSourceFrom?.meta ?? {}) };
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [...input.columns];
|
const columns = [...input.columns];
|
||||||
if (existingColumnIndex === -1) {
|
columns[columnIndex] = {
|
||||||
columns.push(newColumn);
|
id,
|
||||||
} else {
|
name: args.name,
|
||||||
columns[existingColumnIndex] = newColumn;
|
meta: {
|
||||||
}
|
type,
|
||||||
|
params: { id: type },
|
||||||
|
...(metaColumn?.meta ?? {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
|
@ -139,6 +135,5 @@ export const mapColumn: ExpressionFunctionDefinition<
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,8 +12,9 @@ import { Datatable } from '../../../expression_types';
|
||||||
import { mapColumn, MapColumnArguments } from '../map_column';
|
import { mapColumn, MapColumnArguments } from '../map_column';
|
||||||
import { emptyTable, functionWrapper, testTable, tableWithNulls } from './utils';
|
import { emptyTable, functionWrapper, testTable, tableWithNulls } from './utils';
|
||||||
|
|
||||||
const pricePlusTwo = (datatable: Datatable) =>
|
const pricePlusTwo = jest.fn((datatable: Datatable) =>
|
||||||
of(typeof datatable.rows[0].price === 'number' ? datatable.rows[0].price + 2 : null);
|
of(typeof datatable.rows[0].price === 'number' ? datatable.rows[0].price + 2 : null)
|
||||||
|
);
|
||||||
|
|
||||||
describe('mapColumn', () => {
|
describe('mapColumn', () => {
|
||||||
const fn = functionWrapper(mapColumn);
|
const fn = functionWrapper(mapColumn);
|
||||||
|
@ -266,4 +267,33 @@ describe('mapColumn', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('supports partial results', () => {
|
||||||
|
testScheduler.run(({ expectObservable, cold }) => {
|
||||||
|
pricePlusTwo.mockReturnValueOnce(cold('ab|', { a: 1000, b: 2000 }));
|
||||||
|
|
||||||
|
expectObservable(
|
||||||
|
runFn(testTable, {
|
||||||
|
id: 'pricePlusTwo',
|
||||||
|
name: 'pricePlusTwo',
|
||||||
|
expression: pricePlusTwo,
|
||||||
|
})
|
||||||
|
).toBe('01|', [
|
||||||
|
expect.objectContaining({
|
||||||
|
rows: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
pricePlusTwo: 1000,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
rows: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
pricePlusTwo: 2000,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,6 +32,7 @@ export default async function ({ readConfigFile }) {
|
||||||
require.resolve('./expressions_explorer'),
|
require.resolve('./expressions_explorer'),
|
||||||
require.resolve('./index_pattern_field_editor_example'),
|
require.resolve('./index_pattern_field_editor_example'),
|
||||||
require.resolve('./field_formats'),
|
require.resolve('./field_formats'),
|
||||||
|
require.resolve('./partial_results'),
|
||||||
],
|
],
|
||||||
services: {
|
services: {
|
||||||
...functionalConfig.get('services'),
|
...functionalConfig.get('services'),
|
||||||
|
|
47
test/examples/partial_results/index.ts
Normal file
47
test/examples/partial_results/index.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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 { FtrProviderContext } from 'test/functional/ftr_provider_context';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
|
const testSubjects = getService('testSubjects');
|
||||||
|
const PageObjects = getPageObjects(['common']);
|
||||||
|
|
||||||
|
describe('Partial Results Example', function () {
|
||||||
|
before(async () => {
|
||||||
|
this.tags('ciGroup2');
|
||||||
|
await PageObjects.common.navigateToApp('partialResultsExample');
|
||||||
|
|
||||||
|
const element = await testSubjects.find('example-help');
|
||||||
|
|
||||||
|
await element.click();
|
||||||
|
await element.click();
|
||||||
|
await element.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trace mouse events', async () => {
|
||||||
|
const events = await Promise.all(
|
||||||
|
(
|
||||||
|
await testSubjects.findAll('example-column-event')
|
||||||
|
).map((wrapper) => wrapper.getVisibleText())
|
||||||
|
);
|
||||||
|
expect(events).to.eql(['mousedown', 'mouseup', 'click']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep track of the events number', async () => {
|
||||||
|
const counters = await Promise.all(
|
||||||
|
(
|
||||||
|
await testSubjects.findAll('example-column-count')
|
||||||
|
).map((wrapper) => wrapper.getVisibleText())
|
||||||
|
);
|
||||||
|
expect(counters).to.eql(['3', '3', '3']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue