mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Canvas] Remove filters function (#119044)
* Added expressions searchContext with filters. * Replaced setFilter with applyFilterAction event. * Added dependency manager for canvas services (WIP). * Refactored code and fixed types. * Added types and injection of dependencies while starting the registry. * Refactored expressions service to use injected services. * Reduced bundle size. * Added filterGroup to the result of filters expressions. * Added type casting from kibana_context to filter. * Added expression and filter migrations to canvas. * Added support of migrating expression to a chain of expressions. * Added filters migrations. * Added comment on the filters function definition on the server. * Fixed functional test: 'filter updates when the dropdown is changed'. * Fixed functional test: 'filter updates when time range is changed' * Added unit test for filters migration. * Added migrations test to the executor. * Changed `filters` to `kibana | selectFilter`. * Replaced direct interaction with the global store by using filtersService. * Added support of removeFilter/selectFilter at WorkpadFilters. * Added support of multiple groups by expression selectFilter. * Added custom logic for groups. * Changed all '@kbn/interpreter/common' to '@kbn/interpreter' * Added expressions migrations to workpad_template. * Added migrations to customElement. * Added migrations on Workpad import. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
9a0f51bc2f
commit
2b1e1f3c59
108 changed files with 1909 additions and 599 deletions
|
@ -39,11 +39,9 @@ export const isQueryStringFilter = (filter: Filter): filter is QueryStringFilter
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export const buildQueryFilter = (query: QueryStringFilter['query'], index: string, alias: string) =>
|
||||
({
|
||||
query,
|
||||
meta: {
|
||||
index,
|
||||
alias,
|
||||
},
|
||||
} as QueryStringFilter);
|
||||
export const buildQueryFilter = (
|
||||
query: QueryStringFilter['query'],
|
||||
index: string,
|
||||
alias?: string,
|
||||
meta: QueryStringFilterMeta = {}
|
||||
) => ({ query, meta: { index, alias, ...meta } });
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('migration v2', () => {
|
|||
es: {
|
||||
license: 'basic',
|
||||
dataArchive: Path.join(__dirname, 'archives', '7.14.0_xpack_sample_saved_objects.zip'),
|
||||
esArgs: ['http.max_content_length=1715275b'],
|
||||
esArgs: ['http.max_content_length=1715329b'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
@ -85,7 +85,7 @@ describe('migration v2', () => {
|
|||
});
|
||||
|
||||
it('completes the migration even when a full batch would exceed ES http.max_content_length', async () => {
|
||||
root = createRoot({ maxBatchSizeBytes: 1715275 });
|
||||
root = createRoot({ maxBatchSizeBytes: 1715329 });
|
||||
esServer = await startES();
|
||||
await root.preboot();
|
||||
await root.setup();
|
||||
|
@ -109,7 +109,7 @@ describe('migration v2', () => {
|
|||
await root.preboot();
|
||||
await root.setup();
|
||||
await expect(root.start()).rejects.toMatchInlineSnapshot(
|
||||
`[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715274 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]`
|
||||
`[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715329 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]`
|
||||
);
|
||||
|
||||
await retryAsync(
|
||||
|
@ -122,7 +122,7 @@ describe('migration v2', () => {
|
|||
expect(
|
||||
records.find((rec) =>
|
||||
rec.message.startsWith(
|
||||
`Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715274 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.`
|
||||
`Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715329 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.`
|
||||
)
|
||||
)
|
||||
).toBeDefined();
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('migration v2', () => {
|
|||
});
|
||||
|
||||
it('fails with a descriptive message when maxBatchSizeBytes exceeds ES http.max_content_length', async () => {
|
||||
root = createRoot({ maxBatchSizeBytes: 1715275 });
|
||||
root = createRoot({ maxBatchSizeBytes: 1715329 });
|
||||
esServer = await startES();
|
||||
await root.preboot();
|
||||
await root.setup();
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { ExpressionValueBoxed } from 'src/plugins/expressions/common';
|
||||
import { Filter } from '../../es_query';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { ExpressionValueBoxed, ExpressionValueFilter } from 'src/plugins/expressions/common';
|
||||
import { Query, TimeRange } from '../../query';
|
||||
import { IndexPatternField } from '../..';
|
||||
import { adaptToExpressionValueFilter, IndexPatternField } from '../..';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type ExecutionContextSearch = {
|
||||
|
@ -45,5 +45,13 @@ export const kibanaContext = {
|
|||
type: 'null',
|
||||
};
|
||||
},
|
||||
filter: (input: KibanaContext): ExpressionValueFilter => {
|
||||
const { filters = [] } = input;
|
||||
return {
|
||||
type: 'filter',
|
||||
filterType: 'filter',
|
||||
and: filters.map(adaptToExpressionValueFilter),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -28,6 +28,12 @@ describe('interpreter/functions#selectFilter', () => {
|
|||
},
|
||||
query: {},
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
group: 'g3',
|
||||
},
|
||||
query: {},
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
group: 'g1',
|
||||
|
@ -68,6 +74,12 @@ describe('interpreter/functions#selectFilter', () => {
|
|||
},
|
||||
"query": Object {},
|
||||
},
|
||||
Object {
|
||||
"meta": Object {
|
||||
"group": "g3",
|
||||
},
|
||||
"query": Object {},
|
||||
},
|
||||
Object {
|
||||
"meta": Object {
|
||||
"controlledBy": "i1",
|
||||
|
@ -94,8 +106,8 @@ describe('interpreter/functions#selectFilter', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
it('selects filters belonging to certain group', () => {
|
||||
const actual = fn(kibanaContext, { group: 'g1' }, createMockContext());
|
||||
it('selects filters belonging to certain groups', () => {
|
||||
const actual = fn(kibanaContext, { group: ['g1', 'g3'] }, createMockContext());
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"filters": Array [
|
||||
|
@ -105,6 +117,12 @@ describe('interpreter/functions#selectFilter', () => {
|
|||
},
|
||||
"query": Object {},
|
||||
},
|
||||
Object {
|
||||
"meta": Object {
|
||||
"group": "g3",
|
||||
},
|
||||
"query": Object {},
|
||||
},
|
||||
Object {
|
||||
"meta": Object {
|
||||
"controlledBy": "i1",
|
||||
|
|
|
@ -11,7 +11,7 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
|
|||
import { KibanaContext } from './kibana_context_type';
|
||||
|
||||
interface Arguments {
|
||||
group?: string;
|
||||
group: string[];
|
||||
from?: string;
|
||||
ungrouped?: boolean;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ export const selectFilterFunction: ExpressionFunctionSelectFilter = {
|
|||
help: i18n.translate('data.search.functions.selectFilter.group.help', {
|
||||
defaultMessage: 'Select only filters belonging to the provided group',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
from: {
|
||||
types: ['string'],
|
||||
|
@ -54,13 +55,15 @@ export const selectFilterFunction: ExpressionFunctionSelectFilter = {
|
|||
},
|
||||
},
|
||||
|
||||
fn(input, { group, ungrouped, from }) {
|
||||
fn(input, { group = [], ungrouped, from }) {
|
||||
return {
|
||||
...input,
|
||||
filters:
|
||||
input.filters?.filter(({ meta }) => {
|
||||
const isGroupMatching =
|
||||
(!group && !ungrouped) || group === meta.group || (ungrouped && !meta.group);
|
||||
(!group.length && !ungrouped) ||
|
||||
(meta.group && group.length && group.includes(meta.group)) ||
|
||||
(ungrouped && !meta.group);
|
||||
const isOriginMatching = !from || from === meta.controlledBy;
|
||||
return isGroupMatching && isOriginMatching;
|
||||
}) || [],
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { Filter } from '@kbn/es-query';
|
||||
import { ExpressionValueFilter } from 'src/plugins/expressions/common';
|
||||
|
||||
function getGroupFromFilter(filter: Filter) {
|
||||
const { meta } = filter;
|
||||
const { group } = meta ?? {};
|
||||
return group;
|
||||
}
|
||||
|
||||
function range(filter: Filter): ExpressionValueFilter {
|
||||
const { query } = filter;
|
||||
const { range: rangeQuery } = query ?? {};
|
||||
const column = Object.keys(rangeQuery)[0];
|
||||
const { gte: from, lte: to } = rangeQuery[column] ?? {};
|
||||
return {
|
||||
filterGroup: getGroupFromFilter(filter),
|
||||
from,
|
||||
to,
|
||||
column,
|
||||
type: 'filter',
|
||||
filterType: 'time',
|
||||
and: [],
|
||||
};
|
||||
}
|
||||
|
||||
function luceneQueryString(filter: Filter): ExpressionValueFilter {
|
||||
const { query } = filter;
|
||||
const { query_string: queryString } = query ?? {};
|
||||
const { query: queryValue } = queryString;
|
||||
|
||||
return {
|
||||
filterGroup: getGroupFromFilter(filter),
|
||||
query: queryValue,
|
||||
type: 'filter',
|
||||
filterType: 'luceneQueryString',
|
||||
and: [],
|
||||
};
|
||||
}
|
||||
|
||||
function term(filter: Filter): ExpressionValueFilter {
|
||||
const { query } = filter;
|
||||
const { term: termQuery } = query ?? {};
|
||||
const column = Object.keys(termQuery)[0];
|
||||
const { value } = termQuery[column] ?? {};
|
||||
|
||||
return {
|
||||
filterGroup: getGroupFromFilter(filter),
|
||||
column,
|
||||
value,
|
||||
type: 'filter',
|
||||
filterType: 'exactly',
|
||||
and: [],
|
||||
};
|
||||
}
|
||||
|
||||
const adapters = { range, term, luceneQueryString };
|
||||
|
||||
export function adaptToExpressionValueFilter(filter: Filter): ExpressionValueFilter {
|
||||
const { query = {} } = filter;
|
||||
const filterType = Object.keys(query)[0] as keyof typeof adapters;
|
||||
const adapt = adapters[filterType];
|
||||
if (!adapt || typeof adapt !== 'function') {
|
||||
throw new Error(`Unknown filter type: ${filterType}`);
|
||||
}
|
||||
return adapt(filter);
|
||||
}
|
|
@ -7,3 +7,4 @@
|
|||
*/
|
||||
|
||||
export * from './function_wrapper';
|
||||
export { adaptToExpressionValueFilter } from './filters_adapter';
|
||||
|
|
|
@ -4,5 +4,6 @@ exports[`Executor .inject .getAllMigrations returns list of all registered migra
|
|||
Object {
|
||||
"7.10.0": [Function],
|
||||
"7.10.1": [Function],
|
||||
"8.1.0": [Function],
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -246,6 +246,40 @@ describe('Executor', () => {
|
|||
});
|
||||
|
||||
describe('.migrateToLatest', () => {
|
||||
const fnMigrateTo = {
|
||||
name: 'fnMigrateTo',
|
||||
help: 'test',
|
||||
args: {
|
||||
bar: {
|
||||
types: ['string'],
|
||||
help: 'test',
|
||||
},
|
||||
},
|
||||
fn: jest.fn(),
|
||||
};
|
||||
|
||||
const fnMigrateFrom = {
|
||||
name: 'fnMigrateFrom',
|
||||
help: 'test',
|
||||
args: {
|
||||
bar: {
|
||||
types: ['string'],
|
||||
help: 'test',
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
'8.1.0': ((state: ExpressionAstFunction, version: string) => {
|
||||
const migrateToAst = parseExpression('fnMigrateTo');
|
||||
const { arguments: args } = state;
|
||||
const ast = { ...migrateToAst.chain[0], arguments: args };
|
||||
return { type: 'expression', chain: [ast, ast] };
|
||||
}) as unknown as MigrateFunction,
|
||||
},
|
||||
fn: jest.fn(),
|
||||
};
|
||||
executor.registerFunction(fnMigrateFrom);
|
||||
executor.registerFunction(fnMigrateTo);
|
||||
|
||||
test('calls migrate function for every expression function in expression', () => {
|
||||
executor.migrateToLatest({
|
||||
state: parseExpression(
|
||||
|
@ -255,6 +289,25 @@ describe('Executor', () => {
|
|||
});
|
||||
expect(migrateFn).toBeCalledTimes(5);
|
||||
});
|
||||
|
||||
test('migrates expression function to expression function or chain of expression functions', () => {
|
||||
const plainExpression = 'foo bar={foo bar="baz" | foo bar={foo bar="baz"}}';
|
||||
const plainExpressionAst = parseExpression(plainExpression);
|
||||
const migratedExpressionAst = executor.migrateToLatest({
|
||||
state: parseExpression(`${plainExpression} | fnMigrateFrom bar="baz" | fnMigrateTo`),
|
||||
version: '8.0.0',
|
||||
});
|
||||
|
||||
expect(migratedExpressionAst).toEqual({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
...plainExpressionAst.chain,
|
||||
{ type: 'function', function: 'fnMigrateTo', arguments: { bar: ['baz'] } },
|
||||
{ type: 'function', function: 'fnMigrateTo', arguments: { bar: ['baz'] } },
|
||||
{ type: 'function', function: 'fnMigrateTo', arguments: {} },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -241,6 +241,61 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
|||
return ast;
|
||||
}
|
||||
|
||||
private walkAstAndTransform(
|
||||
ast: ExpressionAstExpression,
|
||||
transform: (
|
||||
fn: ExpressionFunction,
|
||||
ast: ExpressionAstFunction
|
||||
) => ExpressionAstFunction | ExpressionAstExpression
|
||||
): ExpressionAstExpression {
|
||||
let additionalFunctions = 0;
|
||||
return (
|
||||
ast.chain.reduce<ExpressionAstExpression>(
|
||||
(newAst: ExpressionAstExpression, funcAst: ExpressionAstFunction, index: number) => {
|
||||
const realIndex = index + additionalFunctions;
|
||||
const { function: fnName, arguments: fnArgs } = funcAst;
|
||||
const fn = getByAlias(this.getFunctions(), fnName);
|
||||
if (!fn) {
|
||||
return newAst;
|
||||
}
|
||||
|
||||
// if any of arguments are expressions we should migrate those first
|
||||
funcAst.arguments = mapValues(fnArgs, (asts) =>
|
||||
asts.map((arg) =>
|
||||
arg != null && typeof arg === 'object'
|
||||
? this.walkAstAndTransform(arg, transform)
|
||||
: arg
|
||||
)
|
||||
);
|
||||
|
||||
const transformedFn = transform(fn, funcAst);
|
||||
if (transformedFn.type === 'function') {
|
||||
const prevChain = realIndex > 0 ? newAst.chain.slice(0, realIndex) : [];
|
||||
const nextChain = newAst.chain.slice(realIndex + 1);
|
||||
return {
|
||||
...newAst,
|
||||
chain: [...prevChain, transformedFn, ...nextChain],
|
||||
};
|
||||
}
|
||||
|
||||
if (transformedFn.type === 'expression') {
|
||||
const { chain } = transformedFn;
|
||||
const prevChain = realIndex > 0 ? newAst.chain.slice(0, realIndex) : [];
|
||||
const nextChain = newAst.chain.slice(realIndex + 1);
|
||||
additionalFunctions += chain.length - 1;
|
||||
return {
|
||||
...newAst,
|
||||
chain: [...prevChain, ...chain, ...nextChain],
|
||||
};
|
||||
}
|
||||
|
||||
return newAst;
|
||||
},
|
||||
ast
|
||||
) ?? ast
|
||||
);
|
||||
}
|
||||
|
||||
public inject(ast: ExpressionAstExpression, references: SavedObjectReference[]) {
|
||||
let linkId = 0;
|
||||
return this.walkAst(cloneDeep(ast), (fn, link) => {
|
||||
|
@ -296,14 +351,12 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
|||
}
|
||||
|
||||
private migrate(ast: SerializableRecord, version: string) {
|
||||
return this.walkAst(cloneDeep(ast) as ExpressionAstExpression, (fn, link) => {
|
||||
return this.walkAstAndTransform(cloneDeep(ast) as ExpressionAstExpression, (fn, link) => {
|
||||
if (!fn.migrations[version]) {
|
||||
return;
|
||||
return link;
|
||||
}
|
||||
|
||||
({ arguments: link.arguments, type: link.type } = fn.migrations[version](
|
||||
link
|
||||
) as ExpressionAstFunction);
|
||||
return fn.migrations[version](link) as ExpressionAstExpression;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ export type ExpressionValueFilter = ExpressionValueBoxed<
|
|||
'filter',
|
||||
{
|
||||
filterType?: string;
|
||||
filterGroup?: string;
|
||||
value?: string;
|
||||
column?: string;
|
||||
and: ExpressionValueFilter[];
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { DependencyManager } from './dependency_manager';
|
||||
|
||||
describe('DependencyManager', () => {
|
||||
it('orderDependencies. Should sort topology by dependencies', () => {
|
||||
const graph = {
|
||||
N: [],
|
||||
R: [],
|
||||
A: ['B', 'C'],
|
||||
B: ['D'],
|
||||
C: ['F', 'B'],
|
||||
F: ['E'],
|
||||
E: ['D'],
|
||||
D: ['L'],
|
||||
};
|
||||
const sortedTopology = ['N', 'R', 'L', 'D', 'B', 'E', 'F', 'C', 'A'];
|
||||
expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology);
|
||||
});
|
||||
|
||||
it('orderDependencies. Should return base topology if no depended vertices', () => {
|
||||
const graph = {
|
||||
N: [],
|
||||
R: [],
|
||||
D: undefined,
|
||||
};
|
||||
const sortedTopology = ['N', 'R', 'D'];
|
||||
expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology);
|
||||
});
|
||||
|
||||
it('orderDependencies. Should detect circular dependencies and throw error with path', () => {
|
||||
const graph = {
|
||||
N: ['R'],
|
||||
R: ['A'],
|
||||
A: ['B'],
|
||||
B: ['C'],
|
||||
C: ['D'],
|
||||
D: ['E'],
|
||||
E: ['F'],
|
||||
F: ['L'],
|
||||
L: ['G'],
|
||||
G: ['N'],
|
||||
};
|
||||
const circularPath = ['N', 'R', 'A', 'B', 'C', 'D', 'E', 'F', 'L', 'G', 'N'].join(' -> ');
|
||||
const errorMessage = `Circular dependency detected while setting up services: ${circularPath}`;
|
||||
|
||||
expect(() => DependencyManager.orderDependencies(graph)).toThrowError(errorMessage);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
type GraphVertex = string | number | symbol;
|
||||
type Graph<T extends GraphVertex = GraphVertex> = Record<T, T[] | null | undefined>;
|
||||
type BreadCrumbs = Record<GraphVertex, boolean>;
|
||||
|
||||
interface CycleDetectionResult<T extends GraphVertex = GraphVertex> {
|
||||
hasCycle: boolean;
|
||||
path: T[];
|
||||
}
|
||||
|
||||
export class DependencyManager {
|
||||
static orderDependencies<T extends GraphVertex = GraphVertex>(graph: Graph<T>) {
|
||||
const cycleInfo = DependencyManager.getSortedDependencies(graph);
|
||||
if (cycleInfo.hasCycle) {
|
||||
const error = DependencyManager.getCyclePathError(cycleInfo.path);
|
||||
DependencyManager.throwCyclicPathError(error);
|
||||
}
|
||||
|
||||
return cycleInfo.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* DFS algorithm for checking if graph is a DAG (Directed Acyclic Graph)
|
||||
* and sorting topogy (dependencies) if graph is DAG.
|
||||
* @param {Graph} graph - graph of dependencies.
|
||||
*/
|
||||
private static getSortedDependencies<T extends GraphVertex = GraphVertex>(
|
||||
graph: Graph<T> = {} as Graph<T>
|
||||
): CycleDetectionResult<T> {
|
||||
const sortedVertices: Set<T> = new Set();
|
||||
const vertices = Object.keys(graph) as T[];
|
||||
return vertices.reduce<CycleDetectionResult<T>>((cycleInfo, srcVertex) => {
|
||||
if (cycleInfo.hasCycle) {
|
||||
return cycleInfo;
|
||||
}
|
||||
|
||||
return DependencyManager.sortVerticesFrom(srcVertex, graph, sortedVertices, {}, {});
|
||||
}, DependencyManager.createCycleInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified DFS algorithm for topological sort.
|
||||
* @param {T extends GraphVertex} srcVertex - a source vertex - the start point of dependencies ordering.
|
||||
* @param {Graph<T extends GraphVertex>} graph - graph of dependencies, represented in the adjacency list form.
|
||||
* @param {Set<GraphVertex>} sortedVertices - ordered dependencies path from the free to the dependent vertex.
|
||||
* @param {BreadCrumbs} visited - record of visited vertices.
|
||||
* @param {BreadCrumbs} inpath - record of vertices, which was met in the path. Is used for detecting cycles.
|
||||
*/
|
||||
private static sortVerticesFrom<T extends GraphVertex = GraphVertex>(
|
||||
srcVertex: T,
|
||||
graph: Graph<T>,
|
||||
sortedVertices: Set<T>,
|
||||
visited: BreadCrumbs = {},
|
||||
inpath: BreadCrumbs = {}
|
||||
): CycleDetectionResult<T> {
|
||||
visited[srcVertex] = true;
|
||||
inpath[srcVertex] = true;
|
||||
const cycleInfo = graph[srcVertex]?.reduce<CycleDetectionResult<T> | undefined>(
|
||||
(info, vertex) => {
|
||||
if (inpath[vertex]) {
|
||||
const path = (Object.keys(inpath) as T[]).filter(
|
||||
(visitedVertex) => inpath[visitedVertex]
|
||||
);
|
||||
return DependencyManager.createCycleInfo([...path, vertex], true);
|
||||
} else if (!visited[vertex]) {
|
||||
return DependencyManager.sortVerticesFrom(vertex, graph, sortedVertices, visited, inpath);
|
||||
}
|
||||
return info;
|
||||
},
|
||||
undefined
|
||||
);
|
||||
|
||||
inpath[srcVertex] = false;
|
||||
|
||||
if (!sortedVertices.has(srcVertex)) {
|
||||
sortedVertices.add(srcVertex);
|
||||
}
|
||||
|
||||
return cycleInfo ?? DependencyManager.createCycleInfo<T>([...sortedVertices]);
|
||||
}
|
||||
|
||||
private static createCycleInfo<T extends GraphVertex = GraphVertex>(
|
||||
path: T[] = [],
|
||||
hasCycle: boolean = false
|
||||
): CycleDetectionResult<T> {
|
||||
return { hasCycle, path };
|
||||
}
|
||||
|
||||
private static getCyclePathError<T extends GraphVertex = GraphVertex>(
|
||||
cyclePath: CycleDetectionResult<T>['path']
|
||||
) {
|
||||
const cycleString = cyclePath.join(' -> ');
|
||||
return `Circular dependency detected while setting up services: ${cycleString}`;
|
||||
}
|
||||
|
||||
private static throwCyclicPathError(error: string) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,10 @@ import { CoreStart, AppUpdater, PluginInitializerContext } from 'src/core/public
|
|||
* The `StartParameters` generic determines what parameters are expected to
|
||||
* create the service.
|
||||
*/
|
||||
export type PluginServiceFactory<Service, Parameters = {}> = (params: Parameters) => Service;
|
||||
export type PluginServiceFactory<Service, Parameters = {}, RequiredServices = {}> = (
|
||||
params: Parameters,
|
||||
requiredServices: RequiredServices
|
||||
) => Service;
|
||||
|
||||
/**
|
||||
* Parameters necessary to create a Kibana-based service, (e.g. during Plugin
|
||||
|
@ -38,6 +41,7 @@ export interface KibanaPluginServiceParams<Start extends {}> {
|
|||
* The `Setup` generic refers to the specific Plugin `TPluginsSetup`.
|
||||
* The `Start` generic refers to the specific Plugin `TPluginsStart`.
|
||||
*/
|
||||
export type KibanaPluginServiceFactory<Service, Start extends {}> = (
|
||||
params: KibanaPluginServiceParams<Start>
|
||||
export type KibanaPluginServiceFactory<Service, Start extends {}, RequiredServices = {}> = (
|
||||
params: KibanaPluginServiceParams<Start>,
|
||||
requiredServices: RequiredServices
|
||||
) => Service;
|
||||
|
|
|
@ -17,7 +17,25 @@ import { PluginServiceFactory } from './factory';
|
|||
* start the service.
|
||||
*/
|
||||
export type PluginServiceProviders<Services, StartParameters = {}> = {
|
||||
[K in keyof Services]: PluginServiceProvider<Services[K], StartParameters>;
|
||||
[K in keyof Services]: PluginServiceProvider<
|
||||
Services[K],
|
||||
StartParameters,
|
||||
Services,
|
||||
Array<keyof Services>
|
||||
>;
|
||||
};
|
||||
|
||||
type ElementOfArray<ArrayType extends readonly unknown[]> = ArrayType extends Array<
|
||||
infer ElementType
|
||||
>
|
||||
? ElementType
|
||||
: never;
|
||||
|
||||
export type PluginServiceRequiredServices<
|
||||
RequiredServices extends Array<keyof AvailableServices>,
|
||||
AvailableServices
|
||||
> = {
|
||||
[K in ElementOfArray<RequiredServices>]: AvailableServices[K];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,16 +45,34 @@ export type PluginServiceProviders<Services, StartParameters = {}> = {
|
|||
* The `StartParameters` generic determines what parameters are expected to
|
||||
* start the service.
|
||||
*/
|
||||
export class PluginServiceProvider<Service extends {}, StartParameters = {}> {
|
||||
private factory: PluginServiceFactory<Service, StartParameters>;
|
||||
export class PluginServiceProvider<
|
||||
Service extends {},
|
||||
StartParameters = {},
|
||||
Services = {},
|
||||
RequiredServices extends Array<keyof Services> = []
|
||||
> {
|
||||
private factory: PluginServiceFactory<
|
||||
Service,
|
||||
StartParameters,
|
||||
PluginServiceRequiredServices<RequiredServices, Services>
|
||||
>;
|
||||
private _requiredServices?: RequiredServices;
|
||||
private context = createContext<Service | null>(null);
|
||||
private pluginService: Service | null = null;
|
||||
public readonly Provider: React.FC = ({ children }) => {
|
||||
return <this.context.Provider value={this.getService()}>{children}</this.context.Provider>;
|
||||
};
|
||||
|
||||
constructor(factory: PluginServiceFactory<Service, StartParameters>) {
|
||||
constructor(
|
||||
factory: PluginServiceFactory<
|
||||
Service,
|
||||
StartParameters,
|
||||
PluginServiceRequiredServices<RequiredServices, Services>
|
||||
>,
|
||||
requiredServices?: RequiredServices
|
||||
) {
|
||||
this.factory = factory;
|
||||
this._requiredServices = requiredServices;
|
||||
this.context.displayName = 'PluginServiceContext';
|
||||
}
|
||||
|
||||
|
@ -55,8 +91,11 @@ export class PluginServiceProvider<Service extends {}, StartParameters = {}> {
|
|||
*
|
||||
* @param params Parameters used to start the service.
|
||||
*/
|
||||
start(params: StartParameters) {
|
||||
this.pluginService = this.factory(params);
|
||||
start(
|
||||
params: StartParameters,
|
||||
requiredServices: PluginServiceRequiredServices<RequiredServices, Services>
|
||||
) {
|
||||
this.pluginService = this.factory(params, requiredServices);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,4 +119,8 @@ export class PluginServiceProvider<Service extends {}, StartParameters = {}> {
|
|||
stop() {
|
||||
this.pluginService = null;
|
||||
}
|
||||
|
||||
public get requiredServices() {
|
||||
return this._requiredServices ?? [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { DependencyManager } from './dependency_manager';
|
||||
import { PluginServiceProviders, PluginServiceRequiredServices } from './provider';
|
||||
|
||||
export class PluginServiceProvidersMediator<Services, StartParameters> {
|
||||
constructor(private readonly providers: PluginServiceProviders<Services, StartParameters>) {}
|
||||
|
||||
start(params: StartParameters) {
|
||||
this.getOrderedDependencies().forEach((service) => {
|
||||
this.providers[service].start(params, this.getServiceDependencies(service));
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.getOrderedDependencies().forEach((service) => this.providers[service].stop());
|
||||
}
|
||||
|
||||
private getOrderedDependencies() {
|
||||
const dependenciesGraph = this.getGraphOfDependencies();
|
||||
return DependencyManager.orderDependencies<keyof Services>(dependenciesGraph);
|
||||
}
|
||||
|
||||
private getGraphOfDependencies() {
|
||||
return this.getProvidersNames().reduce<Record<keyof Services, Array<keyof Services>>>(
|
||||
(graph, vertex) => ({ ...graph, [vertex]: this.providers[vertex].requiredServices ?? [] }),
|
||||
{} as Record<keyof Services, Array<keyof Services>>
|
||||
);
|
||||
}
|
||||
|
||||
private getProvidersNames() {
|
||||
return Object.keys(this.providers) as Array<keyof Services>;
|
||||
}
|
||||
|
||||
private getServiceDependencies(service: keyof Services) {
|
||||
const requiredServices = this.providers[service].requiredServices ?? [];
|
||||
return this.getServicesByDeps(requiredServices);
|
||||
}
|
||||
|
||||
private getServicesByDeps(deps: Array<keyof Services>) {
|
||||
return deps.reduce<PluginServiceRequiredServices<Array<keyof Services>, Services>>(
|
||||
(services, dependency) => ({
|
||||
...services,
|
||||
[dependency]: this.providers[dependency].getService(),
|
||||
}),
|
||||
{} as PluginServiceRequiredServices<Array<keyof Services>, Services>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { PluginServiceProvider, PluginServiceProviders } from './provider';
|
||||
import { PluginServiceProvidersMediator } from './providers_mediator';
|
||||
|
||||
/**
|
||||
* A `PluginServiceRegistry` maintains a set of service providers which can be collectively
|
||||
|
@ -19,10 +20,12 @@ import { PluginServiceProvider, PluginServiceProviders } from './provider';
|
|||
*/
|
||||
export class PluginServiceRegistry<Services, StartParameters = {}> {
|
||||
private providers: PluginServiceProviders<Services, StartParameters>;
|
||||
private providersMediator: PluginServiceProvidersMediator<Services, StartParameters>;
|
||||
private _isStarted = false;
|
||||
|
||||
constructor(providers: PluginServiceProviders<Services, StartParameters>) {
|
||||
this.providers = providers;
|
||||
this.providersMediator = new PluginServiceProvidersMediator(providers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,8 +72,7 @@ export class PluginServiceRegistry<Services, StartParameters = {}> {
|
|||
* @param params Parameters used to start the registry.
|
||||
*/
|
||||
start(params: StartParameters) {
|
||||
const providerNames = Object.keys(this.providers) as Array<keyof Services>;
|
||||
providerNames.forEach((providerName) => this.providers[providerName].start(params));
|
||||
this.providersMediator.start(params);
|
||||
this._isStarted = true;
|
||||
return this;
|
||||
}
|
||||
|
@ -79,8 +81,7 @@ export class PluginServiceRegistry<Services, StartParameters = {}> {
|
|||
* Stop the registry.
|
||||
*/
|
||||
stop() {
|
||||
const providerNames = Object.keys(this.providers) as Array<keyof Services>;
|
||||
providerNames.forEach((providerName) => this.providers[providerName].stop());
|
||||
this.providersMediator.stop();
|
||||
this._isStarted = false;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -13,9 +13,10 @@ export const areaChart: ElementFactory = () => ({
|
|||
help: 'A line chart with a filled body',
|
||||
type: 'chart',
|
||||
icon: 'visArea',
|
||||
expression: `filters
|
||||
| demodata
|
||||
| pointseries x="time" y="mean(price)"
|
||||
| plot defaultStyle={seriesStyle lines=1 fill=1}
|
||||
| render`,
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="time" y="mean(price)"
|
||||
| plot defaultStyle={seriesStyle lines=1 fill=1}
|
||||
| render`,
|
||||
});
|
||||
|
|
|
@ -15,7 +15,8 @@ export const bubbleChart: ElementFactory = () => ({
|
|||
width: 700,
|
||||
height: 300,
|
||||
icon: 'heatmap',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="project" y="sum(price)" color="state" size="size(username)"
|
||||
| plot defaultStyle={seriesStyle points=5 fill=1}
|
||||
|
|
|
@ -12,6 +12,7 @@ export const filterDebug: ElementFactory = () => ({
|
|||
displayName: 'Debug filter',
|
||||
help: 'Shows the underlying global filters in a workpad',
|
||||
icon: 'bug',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| render as=debug`,
|
||||
});
|
||||
|
|
|
@ -13,7 +13,8 @@ export const horizontalBarChart: ElementFactory = () => ({
|
|||
type: 'chart',
|
||||
help: 'A customizable horizontal bar chart',
|
||||
icon: 'visBarHorizontal',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="size(cost)" y="project" color="project"
|
||||
| plot defaultStyle={seriesStyle bars=0.75 horizontalBars=true} legend=false
|
||||
|
|
|
@ -15,7 +15,8 @@ export const horizontalProgressBar: ElementFactory = () => ({
|
|||
help: 'Displays progress as a portion of a horizontal bar',
|
||||
width: 400,
|
||||
height: 30,
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="horizontalBar" label={formatnumber 0%} font={font size=24 family="${openSans.value}" color="#000000" align=center}
|
||||
|
|
|
@ -15,7 +15,8 @@ export const horizontalProgressPill: ElementFactory = () => ({
|
|||
help: 'Displays progress as a portion of a horizontal pill',
|
||||
width: 400,
|
||||
height: 30,
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="horizontalPill" label={formatnumber 0%} font={font size=24 family="${openSans.value}" color="#000000" align=center}
|
||||
|
|
|
@ -13,7 +13,8 @@ export const lineChart: ElementFactory = () => ({
|
|||
type: 'chart',
|
||||
help: 'A customizable line chart',
|
||||
icon: 'visLine',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="time" y="mean(price)"
|
||||
| plot defaultStyle={seriesStyle lines=3}
|
||||
|
|
|
@ -12,7 +12,8 @@ export const markdown: ElementFactory = () => ({
|
|||
type: 'text',
|
||||
help: 'Add text using Markdown',
|
||||
icon: 'visText',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| markdown "### Welcome to the Markdown element
|
||||
|
||||
|
|
|
@ -19,13 +19,14 @@ export const metricElementInitializer: SetupInitializer<ElementFactory> = (core,
|
|||
width: 200,
|
||||
height: 100,
|
||||
icon: 'visMetric',
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "unique(country)"
|
||||
| metric "Countries"
|
||||
metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48}
|
||||
labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"}
|
||||
metricFormat="${core.uiSettings.get(FORMATS_UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN)}"
|
||||
| render`,
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "unique(country)"
|
||||
| metric "Countries"
|
||||
metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48}
|
||||
labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"}
|
||||
metricFormat="${core.uiSettings.get(FORMATS_UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN)}"
|
||||
| render`,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -12,9 +12,10 @@ export const metricVis: ElementFactory = () => ({
|
|||
type: 'chart',
|
||||
help: 'Metric visualization',
|
||||
icon: 'visMetric',
|
||||
expression: `filters
|
||||
| demodata
|
||||
| head 1
|
||||
| metricVis metric={visdimension "percent_uptime"} colorMode="Labels"
|
||||
| render`,
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| head 1
|
||||
| metricVis metric={visdimension "percent_uptime"} colorMode="Labels"
|
||||
| render`,
|
||||
});
|
||||
|
|
|
@ -14,7 +14,8 @@ export const pie: ElementFactory = () => ({
|
|||
height: 300,
|
||||
help: 'A simple pie chart',
|
||||
icon: 'visPie',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries color="state" size="max(price)"
|
||||
| pie
|
||||
|
|
|
@ -12,7 +12,8 @@ export const plot: ElementFactory = () => ({
|
|||
displayName: 'Coordinate plot',
|
||||
type: 'chart',
|
||||
help: 'Mixed line, bar or dot charts',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="time" y="sum(price)" color="state"
|
||||
| plot defaultStyle={seriesStyle points=5}
|
||||
|
|
|
@ -16,7 +16,8 @@ export const progressGauge: ElementFactory = () => ({
|
|||
width: 200,
|
||||
height: 200,
|
||||
icon: 'visGoal',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="gauge" label={formatnumber 0%} font={font size=24 family="${openSans.value}" color="#000000" align=center}
|
||||
|
|
|
@ -15,7 +15,8 @@ export const progressSemicircle: ElementFactory = () => ({
|
|||
help: 'Displays progress as a portion of a semicircle',
|
||||
width: 200,
|
||||
height: 100,
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="semicircle" label={formatnumber 0%} font={font size=24 family="${openSans.value}" color="#000000" align=center}
|
||||
|
|
|
@ -15,7 +15,8 @@ export const progressWheel: ElementFactory = () => ({
|
|||
help: 'Displays progress as a portion of a wheel',
|
||||
width: 200,
|
||||
height: 200,
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="wheel" label={formatnumber 0%} font={font size=24 family="${openSans.value}" color="#000000" align=center}
|
||||
|
|
|
@ -12,7 +12,8 @@ export const repeatImage: ElementFactory = () => ({
|
|||
displayName: 'Image repeat',
|
||||
type: 'image',
|
||||
help: 'Repeats an image N times',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(cost)"
|
||||
| repeatImage image=null
|
||||
|
|
|
@ -12,7 +12,8 @@ export const revealImage: ElementFactory = () => ({
|
|||
displayName: 'Image reveal',
|
||||
type: 'image',
|
||||
help: 'Reveals a percentage of an image',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| revealImage origin=bottom image=null
|
||||
|
|
|
@ -13,7 +13,8 @@ export const table: ElementFactory = () => ({
|
|||
type: 'chart',
|
||||
help: 'A scrollable grid for displaying data in a tabular format',
|
||||
icon: 'visTable',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| table
|
||||
| render`,
|
||||
|
|
|
@ -12,10 +12,11 @@ export const tagCloud: ElementFactory = () => ({
|
|||
type: 'chart',
|
||||
help: 'Tagcloud visualization',
|
||||
icon: 'visTagCloud',
|
||||
expression: `filters
|
||||
| demodata
|
||||
| ply by="country" fn={math "count(country)" | as "Count"}
|
||||
| filterrows fn={getCell "Count" | gte 10}
|
||||
| tagcloud metric={visdimension "Count"} bucket={visdimension "country"}
|
||||
| render`,
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| ply by="country" fn={math "count(country)" | as "Count"}
|
||||
| filterrows fn={getCell "Count" | gte 10}
|
||||
| tagcloud metric={visdimension "Count"} bucket={visdimension "country"}
|
||||
| render`,
|
||||
});
|
||||
|
|
|
@ -13,7 +13,8 @@ export const verticalBarChart: ElementFactory = () => ({
|
|||
type: 'chart',
|
||||
help: 'A customizable vertical bar chart',
|
||||
icon: 'visBarVertical',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="project" y="size(cost)" color="project"
|
||||
| plot defaultStyle={seriesStyle bars=0.75} legend=false
|
||||
|
|
|
@ -15,7 +15,8 @@ export const verticalProgressBar: ElementFactory = () => ({
|
|||
help: 'Displays progress as a portion of a vertical bar',
|
||||
width: 80,
|
||||
height: 400,
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="verticalBar" label={formatnumber 0%} font={font size=24 family="${openSans.value}" color="#000000" align=center}
|
||||
|
|
|
@ -15,7 +15,8 @@ export const verticalProgressPill: ElementFactory = () => ({
|
|||
help: 'Displays progress as a portion of a vertical pill',
|
||||
width: 80,
|
||||
height: 400,
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="verticalPill" label={formatnumber 0%} font={font size=24 family="${openSans.value}" color="#000000" align=center}
|
||||
|
|
|
@ -47,13 +47,14 @@ export function exactly(): ExpressionFunctionDefinition<
|
|||
},
|
||||
},
|
||||
fn: (input, args) => {
|
||||
const { value, column } = args;
|
||||
const { value, column, filterGroup } = args;
|
||||
|
||||
const filter: ExpressionValueFilter = {
|
||||
type: 'filter',
|
||||
filterType: 'exactly',
|
||||
value,
|
||||
column,
|
||||
filterGroup,
|
||||
and: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -58,11 +58,12 @@ export function timefilter(): ExpressionFunctionDefinition<
|
|||
return input;
|
||||
}
|
||||
|
||||
const { from, to, column } = args;
|
||||
const { from, to, column, filterGroup } = args;
|
||||
const filter: ExpressionValueFilter = {
|
||||
type: 'filter',
|
||||
filterType: 'time',
|
||||
column,
|
||||
filterGroup,
|
||||
and: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fromExpression } from '@kbn/interpreter';
|
||||
import { filters } from './filters';
|
||||
|
||||
const { migrations } = filters();
|
||||
|
||||
describe('filters migrations', () => {
|
||||
const expression = 'filters group="1" group="3" ungrouped=true';
|
||||
const ast = fromExpression(expression);
|
||||
it('8.1.0. Should migrate `filters` expression to `kibana | selectFilter`', () => {
|
||||
const migratedAst = migrations?.['8.1.0'](ast.chain[0]);
|
||||
expect(migratedAst !== null && typeof migratedAst === 'object').toBeTruthy();
|
||||
expect(migratedAst.type).toBe('expression');
|
||||
expect(Array.isArray(migratedAst.chain)).toBeTruthy();
|
||||
expect(migratedAst.chain[0].function === 'kibana').toBeTruthy();
|
||||
expect(migratedAst.chain[0].arguments).toEqual({});
|
||||
expect(migratedAst.chain[1].function === 'selectFilter').toBeTruthy();
|
||||
expect(migratedAst.chain[1].arguments).toEqual(ast.chain[0].arguments);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
ExpressionValueFilter,
|
||||
ExpressionAstExpression,
|
||||
ExpressionAstFunction,
|
||||
} from 'src/plugins/expressions';
|
||||
import { fromExpression } from '@kbn/interpreter';
|
||||
import { buildFiltersFunction } from '../../../common/functions';
|
||||
import type { FiltersFunction } from '../../../common/functions';
|
||||
|
||||
/*
|
||||
Expression function `filters` can't be used on the server, because it is tightly coupled with the redux store.
|
||||
It is replaced with `kibana | selectFilter`.
|
||||
|
||||
Current filters function definition is used only for the purpose of enabling migrations.
|
||||
The function has to be registered on the server while the plugin's setup, to be able to run its migration.
|
||||
*/
|
||||
const filtersFn = (): ExpressionValueFilter => ({
|
||||
type: 'filter',
|
||||
and: [],
|
||||
});
|
||||
|
||||
const migrations: FiltersFunction['migrations'] = {
|
||||
'8.1.0': (ast: ExpressionAstFunction): ExpressionAstFunction | ExpressionAstExpression => {
|
||||
const SELECT_FILTERS = 'selectFilter';
|
||||
const newExpression = `kibana | ${SELECT_FILTERS}`;
|
||||
const newAst: ExpressionAstExpression = fromExpression(newExpression);
|
||||
const selectFiltersAstIndex = newAst.chain.findIndex(
|
||||
({ function: fnName }) => fnName === SELECT_FILTERS
|
||||
);
|
||||
const selectFilterAst = newAst.chain[selectFiltersAstIndex];
|
||||
newAst.chain.splice(selectFiltersAstIndex, 1, { ...selectFilterAst, arguments: ast.arguments });
|
||||
return newAst;
|
||||
},
|
||||
};
|
||||
|
||||
export const filters = buildFiltersFunction(filtersFn, migrations);
|
|
@ -7,5 +7,6 @@
|
|||
|
||||
import { demodata } from './demodata';
|
||||
import { pointseries } from './pointseries';
|
||||
import { filters } from './filters';
|
||||
|
||||
export const functions = [demodata, pointseries];
|
||||
export const functions = [filters, demodata, pointseries];
|
||||
|
|
|
@ -21,7 +21,6 @@ export const defaultHandlers: RendererHandlers = {
|
|||
onEmbeddableInputChange: action('onEmbeddableInputChange'),
|
||||
onResize: action('onResize'),
|
||||
resize: action('resize'),
|
||||
setFilter: action('setFilter'),
|
||||
done: action('done'),
|
||||
onDestroy: action('onDestroy'),
|
||||
reload: action('reload'),
|
||||
|
|
|
@ -25,7 +25,10 @@ export const advancedFilterFactory: StartInitializer<RendererFactory<{}>> =
|
|||
render(domNode, _, handlers) {
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={core.theme.theme$}>
|
||||
<AdvancedFilter commit={handlers.setFilter} value={handlers.getFilter()} />
|
||||
<AdvancedFilter
|
||||
commit={(filter) => handlers.event({ name: 'applyFilterAction', data: filter })}
|
||||
value={handlers.getFilter()}
|
||||
/>
|
||||
</KibanaThemeProvider>,
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
|
|
|
@ -55,20 +55,19 @@ export const dropdownFilterFactory: StartInitializer<RendererFactory<Config>> =
|
|||
(filterExpression === undefined || !filterExpression.includes('exactly'))
|
||||
) {
|
||||
filterExpression = '';
|
||||
handlers.setFilter(filterExpression);
|
||||
handlers.event({ name: 'applyFilterAction', data: filterExpression });
|
||||
} else if (filterExpression !== '') {
|
||||
// NOTE: setFilter() will cause a data refresh, avoid calling unless required
|
||||
// compare expression and filter, update filter if needed
|
||||
const { changed, newAst } = syncFilterExpression(config, filterExpression, ['filterGroup']);
|
||||
|
||||
if (changed) {
|
||||
handlers.setFilter(toExpression(newAst));
|
||||
handlers.event({ name: 'applyFilterAction', data: toExpression(newAst) });
|
||||
}
|
||||
}
|
||||
|
||||
const commit = (commitValue: string) => {
|
||||
if (commitValue === '%%CANVAS_MATCH_ALL%%') {
|
||||
handlers.setFilter('');
|
||||
handlers.event({ name: 'applyFilterAction', data: '' });
|
||||
} else {
|
||||
const newFilterAST: Ast = {
|
||||
type: 'expression',
|
||||
|
@ -86,18 +85,19 @@ export const dropdownFilterFactory: StartInitializer<RendererFactory<Config>> =
|
|||
};
|
||||
|
||||
const newFilter = toExpression(newFilterAST);
|
||||
handlers.setFilter(newFilter);
|
||||
handlers.event({ name: 'applyFilterAction', data: newFilter });
|
||||
}
|
||||
};
|
||||
const filter = (
|
||||
<DropdownFilter
|
||||
commit={commit}
|
||||
choices={config.choices || []}
|
||||
initialValue={getFilterValue(filterExpression)}
|
||||
/>
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={core.theme.theme$}>
|
||||
<DropdownFilter
|
||||
commit={commit}
|
||||
choices={config.choices || []}
|
||||
initialValue={getFilterValue(filterExpression)}
|
||||
/>
|
||||
</KibanaThemeProvider>,
|
||||
<KibanaThemeProvider theme$={core.theme.theme$}>{filter}</KibanaThemeProvider>,
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
);
|
||||
|
|
|
@ -45,7 +45,7 @@ export const timeFilterFactory: StartInitializer<RendererFactory<Arguments>> = (
|
|||
|
||||
if (filterExpression === undefined || filterExpression.indexOf('timefilter') !== 0) {
|
||||
filterExpression = defaultTimeFilterExpression;
|
||||
handlers.setFilter(filterExpression);
|
||||
handlers.event({ name: 'applyFilterAction', data: filterExpression });
|
||||
} else if (filterExpression !== '') {
|
||||
// NOTE: setFilter() will cause a data refresh, avoid calling unless required
|
||||
// compare expression and filter, update filter if needed
|
||||
|
@ -55,14 +55,14 @@ export const timeFilterFactory: StartInitializer<RendererFactory<Arguments>> = (
|
|||
]);
|
||||
|
||||
if (changed) {
|
||||
handlers.setFilter(toExpression(newAst));
|
||||
handlers.event({ name: 'applyFilterAction', data: toExpression(newAst) });
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={theme.theme$}>
|
||||
<TimeFilter
|
||||
commit={handlers.setFilter}
|
||||
commit={(filter) => handlers.event({ name: 'applyFilterAction', data: filter })}
|
||||
filter={filterExpression}
|
||||
commonlyUsedRanges={customQuickRanges}
|
||||
dateFormat={customDateFormat}
|
||||
|
|
56
x-pack/plugins/canvas/common/functions/filters.ts
Normal file
56
x-pack/plugins/canvas/common/functions/filters.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public';
|
||||
import { ExpressionValueFilter } from '../../types';
|
||||
import { getFunctionHelp } from '../../i18n';
|
||||
|
||||
export interface Arguments {
|
||||
group: string[];
|
||||
ungrouped: boolean;
|
||||
}
|
||||
|
||||
export type FiltersFunction = ExpressionFunctionDefinition<
|
||||
'filters',
|
||||
null,
|
||||
Arguments,
|
||||
ExpressionValueFilter
|
||||
>;
|
||||
|
||||
export function buildFiltersFunction(
|
||||
fn: FiltersFunction['fn'],
|
||||
migrations?: FiltersFunction['migrations']
|
||||
) {
|
||||
return function filters(): FiltersFunction {
|
||||
const { help, args: argHelp } = getFunctionHelp().filters;
|
||||
|
||||
return {
|
||||
name: 'filters',
|
||||
type: 'filter',
|
||||
help,
|
||||
context: {
|
||||
types: ['null'],
|
||||
},
|
||||
args: {
|
||||
group: {
|
||||
aliases: ['_'],
|
||||
types: ['string'],
|
||||
help: argHelp.group,
|
||||
multi: true,
|
||||
},
|
||||
ungrouped: {
|
||||
aliases: ['nogroup', 'nogroups'],
|
||||
types: ['boolean'],
|
||||
help: argHelp.ungrouped,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
fn,
|
||||
migrations,
|
||||
};
|
||||
};
|
||||
}
|
9
x-pack/plugins/canvas/common/functions/index.ts
Normal file
9
x-pack/plugins/canvas/common/functions/index.ts
Normal file
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { FiltersFunction } from './filters';
|
||||
export { buildFiltersFunction } from './filters';
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
|
||||
import { buildQueryFilter, Filter } from '@kbn/es-query';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import { maxBy, minBy } from 'lodash';
|
||||
import { ExpressionValueFilter } from '../../types';
|
||||
// @ts-expect-error untyped local
|
||||
import { buildBoolArray } from './build_bool_array';
|
||||
|
@ -16,24 +18,45 @@ export interface EmbeddableFilterInput {
|
|||
timeRange?: TimeRange;
|
||||
}
|
||||
|
||||
type ESFilter = Record<string, any>;
|
||||
|
||||
const TimeFilterType = 'time';
|
||||
|
||||
const formatTime = (str: string | undefined, roundUp: boolean = false) => {
|
||||
if (!str) {
|
||||
return null;
|
||||
}
|
||||
const moment = dateMath.parse(str, { roundUp });
|
||||
return !moment || !moment.isValid() ? null : moment.valueOf();
|
||||
};
|
||||
|
||||
function getTimeRangeFromFilters(filters: ExpressionValueFilter[]): TimeRange | undefined {
|
||||
const timeFilter = filters.find(
|
||||
(filter) => filter.filterType !== undefined && filter.filterType === TimeFilterType
|
||||
const timeFilters = filters.filter(
|
||||
(filter) =>
|
||||
filter.filterType !== undefined &&
|
||||
filter.filterType === TimeFilterType &&
|
||||
filter.from !== undefined &&
|
||||
filter.to !== undefined
|
||||
);
|
||||
|
||||
return timeFilter !== undefined && timeFilter.from !== undefined && timeFilter.to !== undefined
|
||||
? {
|
||||
from: timeFilter.from,
|
||||
to: timeFilter.to,
|
||||
}
|
||||
const validatedTimeFilters = timeFilters.filter(
|
||||
(filter) => formatTime(filter.from) !== null && formatTime(filter.to, true) !== null
|
||||
);
|
||||
|
||||
const minFromFilter = minBy(validatedTimeFilters, (filter) => formatTime(filter.from));
|
||||
const maxToFilter = maxBy(validatedTimeFilters, (filter) => formatTime(filter.to, true));
|
||||
|
||||
return minFromFilter?.from && maxToFilter?.to
|
||||
? { from: minFromFilter.from, to: maxToFilter.to }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function getQueryFilters(filters: ExpressionValueFilter[]): Filter[] {
|
||||
const dataFilters = filters.map((filter) => ({ ...filter, type: filter.filterType }));
|
||||
return buildBoolArray(dataFilters).map(buildQueryFilter);
|
||||
return buildBoolArray(dataFilters).map((filter: ESFilter, index: number) => {
|
||||
const { group, ...restFilter } = filter;
|
||||
return buildQueryFilter(restFilter, index.toString(), '', { group });
|
||||
});
|
||||
}
|
||||
|
||||
export function buildEmbeddableFilters(filters: ExpressionValueFilter[]): EmbeddableFilterInput {
|
||||
|
|
|
@ -16,6 +16,8 @@ export const APP_ROUTE = '/app/canvas';
|
|||
export const APP_ROUTE_WORKPAD = `${APP_ROUTE}#/workpad`;
|
||||
export const API_ROUTE = '/api/canvas';
|
||||
export const API_ROUTE_WORKPAD = `${API_ROUTE}/workpad`;
|
||||
export const API_ROUTE_WORKPAD_EXPORT = `${API_ROUTE_WORKPAD}/export`;
|
||||
export const API_ROUTE_WORKPAD_IMPORT = `${API_ROUTE_WORKPAD}/import`;
|
||||
export const API_ROUTE_WORKPAD_ASSETS = `${API_ROUTE}/workpad-assets`;
|
||||
export const API_ROUTE_WORKPAD_STRUCTURES = `${API_ROUTE}/workpad-structures`;
|
||||
export const API_ROUTE_CUSTOM_ELEMENT = `${API_ROUTE}/custom-element`;
|
||||
|
|
|
@ -13,15 +13,16 @@ export function time(filter) {
|
|||
if (!filter.column) {
|
||||
throw new Error('column is required for Elasticsearch range filters');
|
||||
}
|
||||
const { from, to, column, filterGroup: group } = filter;
|
||||
return {
|
||||
range: {
|
||||
[filter.column]: { gte: filter.from, lte: filter.to },
|
||||
},
|
||||
group,
|
||||
range: { [column]: { gte: from, lte: to } },
|
||||
};
|
||||
}
|
||||
|
||||
export function luceneQueryString(filter) {
|
||||
return {
|
||||
group: filter.filterGroup,
|
||||
query_string: {
|
||||
query: filter.query || '*',
|
||||
},
|
||||
|
@ -30,6 +31,7 @@ export function luceneQueryString(filter) {
|
|||
|
||||
export function exactly(filter) {
|
||||
return {
|
||||
group: filter.filterGroup,
|
||||
term: {
|
||||
[filter.column]: {
|
||||
value: filter.value,
|
||||
|
|
|
@ -7,16 +7,19 @@
|
|||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import { interpretAst } from '../../../lib/run_interpreter';
|
||||
import { Loading } from '../../loading';
|
||||
import { useExpressionsService } from '../../../services';
|
||||
import { DatasourcePreview as Component } from './datasource_preview';
|
||||
|
||||
export const DatasourcePreview = (props) => {
|
||||
const [datatable, setDatatable] = useState();
|
||||
const expressionsService = useExpressionsService();
|
||||
|
||||
useEffect(() => {
|
||||
interpretAst({ type: 'expression', chain: [props.function] }, {}).then(setDatatable);
|
||||
}, [props.function, setDatatable]);
|
||||
expressionsService
|
||||
.interpretAst({ type: 'expression', chain: [props.function] }, {})
|
||||
.then(setDatatable);
|
||||
}, [expressionsService, props.function, setDatatable]);
|
||||
|
||||
if (!datatable) {
|
||||
return <Loading {...props} />;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { compose, withProps } from 'recompose';
|
||||
import { get } from 'lodash';
|
||||
import { toExpression } from '@kbn/interpreter';
|
||||
import { interpretAst } from '../../lib/run_interpreter';
|
||||
import { pluginServices } from '../../services';
|
||||
import { getArgTypeDef } from '../../lib/args';
|
||||
import { FunctionFormList as Component } from './function_form_list';
|
||||
|
||||
|
@ -77,24 +77,27 @@ const componentFactory = ({
|
|||
path,
|
||||
parentPath,
|
||||
removable,
|
||||
}) => ({
|
||||
args,
|
||||
nestedFunctionsArgs: argsWithExprFunctions,
|
||||
argType: argType.function,
|
||||
argTypeDef: Object.assign(argTypeDef, {
|
||||
args: argumentsView,
|
||||
name: argUiConfig?.name ?? argTypeDef.name,
|
||||
displayName: argUiConfig?.displayName ?? argTypeDef.displayName,
|
||||
help: argUiConfig?.help ?? argTypeDef.name,
|
||||
}),
|
||||
argResolver: (argAst) => interpretAst(argAst, prevContext),
|
||||
contextExpression: getExpression(prevContext),
|
||||
expressionIndex, // preserve the index in the AST
|
||||
nextArgType: nextArg && nextArg.function,
|
||||
path,
|
||||
parentPath,
|
||||
removable,
|
||||
});
|
||||
}) => {
|
||||
const { expressions } = pluginServices.getServices();
|
||||
return {
|
||||
args,
|
||||
nestedFunctionsArgs: argsWithExprFunctions,
|
||||
argType: argType.function,
|
||||
argTypeDef: Object.assign(argTypeDef, {
|
||||
args: argumentsView,
|
||||
name: argUiConfig?.name ?? argTypeDef.name,
|
||||
displayName: argUiConfig?.displayName ?? argTypeDef.displayName,
|
||||
help: argUiConfig?.help ?? argTypeDef.name,
|
||||
}),
|
||||
argResolver: (argAst) => expressions.interpretAst(argAst, prevContext),
|
||||
contextExpression: getExpression(prevContext),
|
||||
expressionIndex, // preserve the index in the AST
|
||||
nextArgType: nextArg && nextArg.function,
|
||||
path,
|
||||
parentPath,
|
||||
removable,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts expression functions at the arguments for the expression, to the array of UI component configurations.
|
||||
|
|
|
@ -22,7 +22,8 @@ export const getFunctionExamples = (): FunctionExampleDict => ({
|
|||
syntax: `all {neq "foo"} {neq "bar"} {neq "fizz"}
|
||||
all condition={gt 10} condition={lt 20}`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| formatnumber "0.0%"
|
||||
|
@ -42,7 +43,8 @@ all condition={gt 10} condition={lt 20}`,
|
|||
syntax: `alterColumn "cost" type="string"
|
||||
alterColumn column="@timestamp" name="foo"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| alterColumn "time" name="time_in_ms" type="number"
|
||||
| table
|
||||
|
@ -54,7 +56,8 @@ alterColumn column="@timestamp" name="foo"`,
|
|||
syntax: `any {eq "foo"} {eq "bar"} {eq "fizz"}
|
||||
any condition={lte 10} condition={gt 30}`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| filterrows {
|
||||
getCell "project" | any {eq "elasticsearch"} {eq "kibana"} {eq "x-pack"}
|
||||
|
@ -70,7 +73,8 @@ any condition={lte 10} condition={gt 30}`,
|
|||
as "foo"
|
||||
as name="bar"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| ply by="project" fn={math "count(username)" | as "num_users"} fn={math "mean(price)" | as "price"}
|
||||
| pointseries x="project" y="num_users" size="price" color="project"
|
||||
|
@ -94,7 +98,8 @@ asset id="asset-498f7429-4d56-42a2-a7e4-8bf08d98d114"`,
|
|||
syntax: `axisConfig show=false
|
||||
axisConfig position="right" min=0 max=10 tickSize=1`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="size(cost)" y="project" color="project"
|
||||
| plot defaultStyle={seriesStyle bars=0.75 horizontalBars=true}
|
||||
|
@ -133,7 +138,8 @@ case if={lte 50} then="green"`,
|
|||
syntax: `columns include="@timestamp, projects, cost"
|
||||
columns exclude="username, country, age"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| columns include="price, cost, state, project"
|
||||
| table
|
||||
|
@ -145,7 +151,8 @@ columns exclude="username, country, age"`,
|
|||
syntax: `compare "neq" to="elasticsearch"
|
||||
compare op="lte" to=100`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| mapColumn project
|
||||
fn={getCell project |
|
||||
|
@ -229,7 +236,8 @@ date "01/31/2019" format="MM/DD/YYYY"`,
|
|||
demodata "ci"
|
||||
demodata type="shirts"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| table
|
||||
| render`,
|
||||
|
@ -252,7 +260,8 @@ eq null
|
|||
eq 10
|
||||
eq "foo"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| mapColumn project
|
||||
fn={getCell project |
|
||||
|
@ -272,7 +281,8 @@ eq "foo"`,
|
|||
escount "currency:\"EUR\"" index="kibana_sample_data_ecommerce"
|
||||
escount query="response:404" index="kibana_sample_data_logs"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| escount "Cancelled:true" index="kibana_sample_data_flights"
|
||||
| math "value"
|
||||
| progress shape="semicircle"
|
||||
|
@ -290,7 +300,8 @@ esdocs query="response:404" index="kibana_sample_data_logs"
|
|||
esdocs index="kibana_sample_data_flights" count=100
|
||||
esdocs index="kibana_sample_data_flights" sort="AvgTicketPrice, asc"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| esdocs index="kibana_sample_data_ecommerce"
|
||||
fields="customer_gender, taxful_total_price, order_date"
|
||||
sort="order_date, asc"
|
||||
|
@ -309,7 +320,8 @@ esdocs index="kibana_sample_data_flights" sort="AvgTicketPrice, asc"`,
|
|||
syntax: `essql query="SELECT * FROM \"logstash*\""
|
||||
essql "SELECT * FROM \"apm*\"" count=10000`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| essql query="SELECT Carrier, FlightDelayMin, AvgTicketPrice FROM \"kibana_sample_data_flights\""
|
||||
| table
|
||||
| render`,
|
||||
|
@ -321,7 +333,8 @@ essql "SELECT * FROM \"apm*\"" count=10000`,
|
|||
exactly "age" value=50 filterGroup="group2"
|
||||
exactly column="project" value="beats"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| exactly column=project value=elasticsearch
|
||||
| demodata
|
||||
| pointseries x=project y="mean(age)"
|
||||
|
@ -334,7 +347,8 @@ exactly column="project" value="beats"`,
|
|||
syntax: `filterrows {getCell "project" | eq "kibana"}
|
||||
filterrows fn={getCell "age" | gt 50}`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| filterrows {getCell "country" | any {eq "IN"} {eq "US"} {eq "CN"}}
|
||||
| mapColumn "@timestamp"
|
||||
|
@ -379,7 +393,8 @@ font underline=true
|
|||
font italic=false
|
||||
font lHeight=32`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="project" y="size(cost)" color="project"
|
||||
| plot defaultStyle={seriesStyle bars=0.75} legend=false
|
||||
|
@ -399,7 +414,8 @@ font lHeight=32`,
|
|||
syntax: `formatdate format="YYYY-MM-DD"
|
||||
formatdate "MM/DD/YYYY"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| mapColumn "time" fn={getCell time | formatdate "MMM 'YY"}
|
||||
| pointseries x="time" y="sum(price)" color="state"
|
||||
|
@ -412,7 +428,8 @@ formatdate "MM/DD/YYYY"`,
|
|||
syntax: `formatnumber format="$0,0.00"
|
||||
formatnumber "0.0a"`,
|
||||
usage: {
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="gauge"
|
||||
|
|
|
@ -29,7 +29,7 @@ export const useCreateWorkpad = () => {
|
|||
history.push(`/workpad/${workpad.id}/page/1`);
|
||||
} catch (err) {
|
||||
notifyService.error(err, {
|
||||
title: errors.getUploadFailureErrorMessage(),
|
||||
title: errors.getCreateFailureErrorMessage(),
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
@ -39,8 +39,8 @@ export const useCreateWorkpad = () => {
|
|||
};
|
||||
|
||||
const errors = {
|
||||
getUploadFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't upload workpad`,
|
||||
getCreateFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useCreateWorkpad.createFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't create workpad`,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
// @ts-expect-error
|
||||
import { getDefaultWorkpad } from '../../../state/defaults';
|
||||
import { useNotifyService, useWorkpadService } from '../../../services';
|
||||
|
||||
import type { CanvasWorkpad } from '../../../../types';
|
||||
|
||||
export const useImportWorkpad = () => {
|
||||
const workpadService = useWorkpadService();
|
||||
const notifyService = useNotifyService();
|
||||
const history = useHistory();
|
||||
|
||||
return useCallback(
|
||||
async (workpad: CanvasWorkpad) => {
|
||||
try {
|
||||
const importedWorkpad = await workpadService.import(workpad);
|
||||
history.push(`/workpad/${importedWorkpad.id}/page/1`);
|
||||
} catch (err) {
|
||||
notifyService.error(err, {
|
||||
title: errors.getUploadFailureErrorMessage(),
|
||||
});
|
||||
}
|
||||
return;
|
||||
},
|
||||
[notifyService, history, workpadService]
|
||||
);
|
||||
};
|
||||
|
||||
const errors = {
|
||||
getUploadFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useUploadWorkpad.uploadFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't upload workpad`,
|
||||
}),
|
||||
};
|
|
@ -9,19 +9,25 @@ import { useCallback } from 'react';
|
|||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SavedObject } from 'kibana/public';
|
||||
import { CANVAS, JSON as JSONString } from '../../../../i18n/constants';
|
||||
import { useNotifyService } from '../../../services';
|
||||
import { getId } from '../../../lib/get_id';
|
||||
|
||||
import { useCreateWorkpad } from './use_create_workpad';
|
||||
import { useImportWorkpad as useImportWorkpadHook } from './use_import_workpad';
|
||||
import type { CanvasWorkpad } from '../../../../types';
|
||||
|
||||
const isInvalidWorkpad = (workpad: CanvasWorkpad) =>
|
||||
!Array.isArray(workpad.pages) || workpad.pages.length === 0 || !workpad.assets;
|
||||
|
||||
export const useImportWorkpad = () => {
|
||||
const notifyService = useNotifyService();
|
||||
const createWorkpad = useCreateWorkpad();
|
||||
const importWorkpad = useImportWorkpadHook();
|
||||
|
||||
return useCallback(
|
||||
(file?: File, onComplete: (workpad?: CanvasWorkpad) => void = () => {}) => {
|
||||
(
|
||||
file?: File,
|
||||
onComplete: (workpad?: CanvasWorkpad | SavedObject<CanvasWorkpad>) => void = () => {}
|
||||
) => {
|
||||
if (!file) {
|
||||
onComplete();
|
||||
return;
|
||||
|
@ -42,16 +48,17 @@ export const useImportWorkpad = () => {
|
|||
// handle reading the uploaded file
|
||||
reader.onload = async () => {
|
||||
try {
|
||||
const workpad = JSON.parse(reader.result as string); // Type-casting because we catch below.
|
||||
const workpad: CanvasWorkpad = JSON.parse(reader.result as string); // Type-casting because we catch below.
|
||||
|
||||
workpad.id = getId('workpad');
|
||||
|
||||
// sanity check for workpad object
|
||||
if (!Array.isArray(workpad.pages) || workpad.pages.length === 0 || !workpad.assets) {
|
||||
if (isInvalidWorkpad(workpad)) {
|
||||
onComplete();
|
||||
throw new Error(errors.getMissingPropertiesErrorMessage());
|
||||
}
|
||||
|
||||
await createWorkpad(workpad);
|
||||
await importWorkpad(workpad);
|
||||
onComplete(workpad);
|
||||
} catch (e) {
|
||||
notifyService.error(e, {
|
||||
|
@ -66,7 +73,7 @@ export const useImportWorkpad = () => {
|
|||
// read the uploaded file
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[notifyService, createWorkpad]
|
||||
[notifyService, importWorkpad]
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,19 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fromExpression } from '@kbn/interpreter';
|
||||
import { ExpressionFunctionAST, fromExpression } from '@kbn/interpreter';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { State } from '../../../../types';
|
||||
import { getFiltersByGroups } from '../../../lib/filter';
|
||||
import { getFiltersByFilterExpressions } from '../../../lib/filter';
|
||||
import { adaptCanvasFilter } from '../../../lib/filter_adapters';
|
||||
import { getGlobalFilters } from '../../../state/selectors/workpad';
|
||||
import { useFiltersService } from '../../../services';
|
||||
|
||||
const extractExpressionAST = (filtersExpressions: string[]) =>
|
||||
fromExpression(filtersExpressions.join(' | '));
|
||||
const extractExpressionAST = (filters: string[]) => fromExpression(filters.join(' | '));
|
||||
|
||||
export function useCanvasFilters(groups: string[] = [], ungrouped: boolean = false) {
|
||||
const filterExpressions = useSelector((state: State) => getGlobalFilters(state), shallowEqual);
|
||||
const filtersByGroups = getFiltersByGroups(filterExpressions, groups, ungrouped);
|
||||
export function useCanvasFilters(filterExprsToGroupBy: ExpressionFunctionAST[] = []) {
|
||||
const filtersService = useFiltersService();
|
||||
const filterExpressions = useSelector(
|
||||
(state: State) => filtersService.getFilters(state),
|
||||
shallowEqual
|
||||
);
|
||||
const filtersByGroups = getFiltersByFilterExpressions(filterExpressions, filterExprsToGroupBy);
|
||||
|
||||
const expression = extractExpressionAST(filtersByGroups);
|
||||
const filters = expression.chain.map(adaptCanvasFilter);
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { State, FilterField, PositionedElement } from '../../../types';
|
||||
import {
|
||||
extractGroupsFromElementsFilters,
|
||||
groupFiltersBy,
|
||||
extractUngroupedFromElementsFilters,
|
||||
} from '../../lib/filter';
|
||||
import { groupFiltersBy, getFiltersExprsFromExpression } from '../../lib/filter';
|
||||
import { setGroupFiltersByOption } from '../../state/actions/sidebar';
|
||||
import { getGroupFiltersByOption } from '../../state/selectors/sidebar';
|
||||
import { useCanvasFilters } from './hooks';
|
||||
|
@ -35,11 +31,8 @@ export const WorkpadFilters: FC<Props> = ({ element }) => {
|
|||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const groups = element ? extractGroupsFromElementsFilters(element.expression) : undefined;
|
||||
const ungrouped = element ? extractUngroupedFromElementsFilters(element.expression) : false;
|
||||
|
||||
const canvasFilters = useCanvasFilters(groups, ungrouped);
|
||||
const filterExprs = element ? getFiltersExprsFromExpression(element.expression) : [];
|
||||
const canvasFilters = useCanvasFilters(filterExprs);
|
||||
|
||||
const filtersGroups = groupFiltersByField
|
||||
? groupFiltersBy(canvasFilters, groupFiltersByField)
|
||||
|
|
|
@ -17,7 +17,8 @@ const testElements: { [key: string]: ElementSpec } = {
|
|||
displayName: 'Area chart',
|
||||
help: 'A line chart with a filled body',
|
||||
type: 'chart',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| pointseries x="time" y="mean(price)"
|
||||
| plot defaultStyle={seriesStyle lines=1 fill=1}
|
||||
|
@ -47,7 +48,8 @@ const testElements: { [key: string]: ElementSpec } = {
|
|||
displayName: 'Debug filter',
|
||||
help: 'Shows the underlying global filters in a workpad',
|
||||
icon: 'bug',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| render as=debug`,
|
||||
},
|
||||
image: {
|
||||
|
@ -64,7 +66,8 @@ const testElements: { [key: string]: ElementSpec } = {
|
|||
type: 'text',
|
||||
help: 'Add text using Markdown',
|
||||
icon: 'visText',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| markdown "### Welcome to the Markdown element
|
||||
|
||||
|
@ -89,7 +92,8 @@ You can use standard Markdown in here, but you can also access your piped-in dat
|
|||
width: 200,
|
||||
height: 200,
|
||||
icon: 'visGoal',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="gauge" label={formatnumber 0%} font={font size=24 family="Helvetica" color="#000000" align=center}
|
||||
|
@ -111,7 +115,8 @@ You can use standard Markdown in here, but you can also access your piped-in dat
|
|||
displayName: 'Data table',
|
||||
type: 'chart',
|
||||
help: 'A scrollable grid for displaying data in a tabular format',
|
||||
expression: `filters
|
||||
expression: `kibana
|
||||
| selectFilter
|
||||
| demodata
|
||||
| table
|
||||
| render`,
|
||||
|
|
|
@ -7,15 +7,10 @@
|
|||
|
||||
import { fromExpression } from '@kbn/interpreter';
|
||||
import { get } from 'lodash';
|
||||
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public';
|
||||
import { interpretAst } from '../lib/run_interpreter';
|
||||
// @ts-expect-error untyped local
|
||||
import { getState } from '../state/store';
|
||||
import { getGlobalFilters, getWorkpadVariablesAsObject } from '../state/selectors/workpad';
|
||||
import { ExpressionValueFilter } from '../../types';
|
||||
import { getFunctionHelp } from '../../i18n';
|
||||
import { pluginServices } from '../services';
|
||||
import type { FiltersFunction } from '../../common/functions';
|
||||
import { buildFiltersFunction } from '../../common/functions';
|
||||
import { InitializeArguments } from '.';
|
||||
import { getFiltersByGroups } from '../lib/filter';
|
||||
|
||||
export interface Arguments {
|
||||
group: string[];
|
||||
|
@ -31,58 +26,34 @@ function getFiltersByGroup(allFilters: string[], groups?: string[], ungrouped =
|
|||
// remove all allFilters that belong to a group
|
||||
return allFilters.filter((filter: string) => {
|
||||
const ast = fromExpression(filter);
|
||||
const expGroups = get(ast, 'chain[0].arguments.filterGroup', []);
|
||||
const expGroups: string[] = get(ast, 'chain[0].arguments.filterGroup', []);
|
||||
return expGroups.length === 0;
|
||||
});
|
||||
}
|
||||
|
||||
return getFiltersByGroups(allFilters, groups);
|
||||
return allFilters.filter((filter: string) => {
|
||||
const ast = fromExpression(filter);
|
||||
const expGroups: string[] = get(ast, 'chain[0].arguments.filterGroup', []);
|
||||
return expGroups.length > 0 && expGroups.every((expGroup) => groups.includes(expGroup));
|
||||
});
|
||||
}
|
||||
|
||||
type FiltersFunction = ExpressionFunctionDefinition<
|
||||
'filters',
|
||||
null,
|
||||
Arguments,
|
||||
ExpressionValueFilter
|
||||
>;
|
||||
|
||||
export function filtersFunctionFactory(initialize: InitializeArguments): () => FiltersFunction {
|
||||
return function filters(): FiltersFunction {
|
||||
const { help, args: argHelp } = getFunctionHelp().filters;
|
||||
const fn: FiltersFunction['fn'] = (input, { group, ungrouped }) => {
|
||||
const { expressions, filters: filtersService } = pluginServices.getServices();
|
||||
|
||||
return {
|
||||
name: 'filters',
|
||||
type: 'filter',
|
||||
help,
|
||||
context: {
|
||||
types: ['null'],
|
||||
},
|
||||
args: {
|
||||
group: {
|
||||
aliases: ['_'],
|
||||
types: ['string'],
|
||||
help: argHelp.group,
|
||||
multi: true,
|
||||
},
|
||||
ungrouped: {
|
||||
aliases: ['nogroup', 'nogroups'],
|
||||
types: ['boolean'],
|
||||
help: argHelp.ungrouped,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
fn: (input, { group, ungrouped }) => {
|
||||
const filterList = getFiltersByGroup(getGlobalFilters(getState()), group, ungrouped);
|
||||
const filterList = getFiltersByGroup(filtersService.getFilters(), group, ungrouped);
|
||||
|
||||
if (filterList && filterList.length) {
|
||||
const filterExpression = filterList.join(' | ');
|
||||
const filterAST = fromExpression(filterExpression);
|
||||
return interpretAst(filterAST, getWorkpadVariablesAsObject(getState()));
|
||||
} else {
|
||||
const filterType = initialize.types.filter;
|
||||
return filterType?.from(null, {});
|
||||
}
|
||||
},
|
||||
};
|
||||
if (filterList && filterList.length) {
|
||||
const filterExpression = filterList.join(' | ');
|
||||
const filterAST = fromExpression(filterExpression);
|
||||
const { variables } = filtersService.getFiltersContext();
|
||||
return expressions.interpretAst(filterAST, variables);
|
||||
} else {
|
||||
const filterType = initialize.types.filter;
|
||||
return filterType?.from(null, {});
|
||||
}
|
||||
};
|
||||
|
||||
return buildFiltersFunction(fn);
|
||||
}
|
||||
|
|
|
@ -10,10 +10,9 @@ import {
|
|||
ExpressionRendererEvent,
|
||||
IInterpreterRenderHandlers,
|
||||
} from 'src/plugins/expressions/public';
|
||||
// @ts-expect-error untyped local
|
||||
import { setFilter } from '../state/actions/elements';
|
||||
import { updateEmbeddableExpression, fetchEmbeddableRenderable } from '../state/actions/embeddable';
|
||||
import { RendererHandlers, CanvasElement } from '../../types';
|
||||
import { pluginServices } from '../services';
|
||||
import { clearValue } from '../state/actions/resolved_args';
|
||||
|
||||
// This class creates stub handlers to ensure every element and renderer fulfills the contract.
|
||||
|
@ -58,7 +57,6 @@ export const createHandlers = (baseHandlers = createBaseHandlers()): RendererHan
|
|||
},
|
||||
|
||||
resize(_size: { height: number; width: number }) {},
|
||||
setFilter() {},
|
||||
});
|
||||
|
||||
export const assignHandlers = (handlers: Partial<RendererHandlers> = {}): RendererHandlers =>
|
||||
|
@ -79,6 +77,8 @@ export const createDispatchedHandlerFactory = (
|
|||
oldElement = element;
|
||||
}
|
||||
|
||||
const { filters } = pluginServices.getServices();
|
||||
|
||||
const handlers: RendererHandlers & {
|
||||
event: IInterpreterRenderHandlers['event'];
|
||||
done: IInterpreterRenderHandlers['done'];
|
||||
|
@ -89,8 +89,8 @@ export const createDispatchedHandlerFactory = (
|
|||
case 'embeddableInputChange':
|
||||
this.onEmbeddableInputChange(event.data);
|
||||
break;
|
||||
case 'setFilter':
|
||||
this.setFilter(event.data);
|
||||
case 'applyFilterAction':
|
||||
filters.updateFilter(element.id, event.data);
|
||||
break;
|
||||
case 'onComplete':
|
||||
this.onComplete(event.data);
|
||||
|
@ -106,10 +106,6 @@ export const createDispatchedHandlerFactory = (
|
|||
break;
|
||||
}
|
||||
},
|
||||
setFilter(text: string) {
|
||||
dispatch(setFilter(text, element.id, true));
|
||||
},
|
||||
|
||||
getFilter() {
|
||||
return element.filter || '';
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fromExpression } from '@kbn/interpreter';
|
||||
import { FC } from 'react';
|
||||
import {
|
||||
Filter as FilterType,
|
||||
|
@ -18,9 +19,8 @@ import {
|
|||
flattenFilterView,
|
||||
createFilledFilterView,
|
||||
groupFiltersBy,
|
||||
getFiltersByGroups,
|
||||
extractGroupsFromElementsFilters,
|
||||
extractUngroupedFromElementsFilters,
|
||||
getFiltersExprsFromExpression,
|
||||
getFiltersByFilterExpressions,
|
||||
isExpressionWithFilters,
|
||||
} from './filter';
|
||||
|
||||
|
@ -285,7 +285,7 @@ describe('groupFiltersBy', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getFiltersByGroups', () => {
|
||||
describe('getFiltersByFilterExpressions', () => {
|
||||
const group1 = 'Group 1';
|
||||
const group2 = 'Group 2';
|
||||
|
||||
|
@ -296,66 +296,106 @@ describe('getFiltersByGroups', () => {
|
|||
`exactly value="kibana" column="project2" filterGroup="${group2}"`,
|
||||
];
|
||||
|
||||
it('returns all filters related to a specified groups', () => {
|
||||
expect(getFiltersByGroups(filters, [group1, group2])).toEqual([
|
||||
filters[0],
|
||||
filters[1],
|
||||
filters[3],
|
||||
const filtersExprWithGroup = `filters group="${group2}"`;
|
||||
|
||||
const kibanaExpr = 'kibana';
|
||||
const selectFilterExprEmpty = 'selectFilter';
|
||||
const selectFilterExprWithGroup = `${selectFilterExprEmpty} group="${group2}"`;
|
||||
const selectFilterExprWithGroups = `${selectFilterExprEmpty} group="${group2}" group="${group1}"`;
|
||||
const selectFilterExprWithUngrouped = `${selectFilterExprEmpty} ungrouped=true`;
|
||||
const selectFilterExprWithGroupAndUngrouped = `${selectFilterExprEmpty} group="${group2}" ungrouped=true`;
|
||||
|
||||
const removeFilterExprEmpty = 'removeFilter';
|
||||
const removeFilterExprWithGroup = `${removeFilterExprEmpty} group="${group2}"`;
|
||||
const removeFilterExprWithUngrouped = `${removeFilterExprEmpty} ungrouped=true`;
|
||||
const removeFilterExprWithGroupAndUngrouped = `${removeFilterExprEmpty} group="${group2}" ungrouped=true`;
|
||||
|
||||
const getFiltersAsts = (filtersExprs: string[]) => {
|
||||
const ast = fromExpression(filtersExprs.join(' | '));
|
||||
return ast.chain;
|
||||
};
|
||||
|
||||
it('returns all filters if no arguments specified to selectFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, selectFilterExprEmpty]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual(filters);
|
||||
});
|
||||
|
||||
it('returns filters with group, specified to selectFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, selectFilterExprWithGroups]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([filters[0], filters[1], filters[3]]);
|
||||
});
|
||||
|
||||
it('returns filters without group if ungrouped is true at selectFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, selectFilterExprWithUngrouped]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([filters[2]]);
|
||||
});
|
||||
|
||||
it('returns filters with group if ungrouped is true and groups are not empty at selectFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, selectFilterExprWithGroupAndUngrouped]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([filters[1], filters[2], filters[3]]);
|
||||
});
|
||||
|
||||
it('returns no filters if no arguments, specified to removeFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, removeFilterExprEmpty]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns filters without group, specified to removeFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, removeFilterExprWithGroup]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([filters[0], filters[2]]);
|
||||
});
|
||||
|
||||
it('returns filters without group if ungrouped is true at removeFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, removeFilterExprWithUngrouped]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([filters[0], filters[1], filters[3]]);
|
||||
});
|
||||
|
||||
it('remove filters without group and with specified group if ungrouped is true and groups are not empty at removeFilter expression', () => {
|
||||
const filtersExprs = getFiltersAsts([kibanaExpr, removeFilterExprWithGroupAndUngrouped]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([filters[0]]);
|
||||
});
|
||||
|
||||
it('should include/exclude filters iteratively', () => {
|
||||
const filtersExprs = getFiltersAsts([
|
||||
kibanaExpr,
|
||||
selectFilterExprWithGroup,
|
||||
removeFilterExprWithGroup,
|
||||
selectFilterExprEmpty,
|
||||
]);
|
||||
|
||||
expect(getFiltersByGroups(filters, [group2])).toEqual([filters[1], filters[3]]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns filters without group if ungrouped is true', () => {
|
||||
expect(getFiltersByGroups(filters, [], true)).toEqual([filters[2]]);
|
||||
});
|
||||
|
||||
it('returns filters with group if ungrouped is true and groups are not empty', () => {
|
||||
expect(getFiltersByGroups(filters, [group1], true)).toEqual([filters[0]]);
|
||||
});
|
||||
|
||||
it('returns empty array if not found any filter with a specified group', () => {
|
||||
expect(getFiltersByGroups(filters, ['absent group'])).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty array if not groups specified', () => {
|
||||
expect(getFiltersByGroups(filters, [])).toEqual(filters);
|
||||
it('should include/exclude filters from global filters if `filters` expression is specified', () => {
|
||||
const filtersExprs = getFiltersAsts([
|
||||
kibanaExpr,
|
||||
selectFilterExprWithGroup,
|
||||
removeFilterExprWithGroup,
|
||||
selectFilterExprEmpty,
|
||||
filtersExprWithGroup,
|
||||
]);
|
||||
const matchedFilters = getFiltersByFilterExpressions(filters, filtersExprs);
|
||||
expect(matchedFilters).toEqual([filters[1], filters[3]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractGroupsFromElementsFilters', () => {
|
||||
const exprFilters = 'filters';
|
||||
const exprRest = 'demodata | plot | render';
|
||||
describe('getFiltersExprsFromExpression', () => {
|
||||
it('returns list of filters expressions asts', () => {
|
||||
const filter1 = 'selectFilter';
|
||||
const filter2 = 'filters group="15" ungrouped=true';
|
||||
const filter3 = 'removeFilter';
|
||||
const expression = `kibana | ${filter1} | ${filter2} | ${filter3} | demodata | plot | render`;
|
||||
const filtersAsts = getFiltersExprsFromExpression(expression);
|
||||
|
||||
it('returns groups which are specified at filters expression', () => {
|
||||
const groups = ['group 1', 'group 2', 'group 3', 'group 4'];
|
||||
const additionalGroups = [...groups, 'group 5'];
|
||||
const groupsExpr = groups.map((group) => `group="${group}"`).join(' ');
|
||||
const additionalGroupsExpr = additionalGroups.map((group) => `group="${group}"`).join(' ');
|
||||
|
||||
expect(
|
||||
extractGroupsFromElementsFilters(
|
||||
`${exprFilters} ${groupsExpr} | ${exprFilters} ${additionalGroupsExpr} | ${exprRest}`
|
||||
)
|
||||
).toEqual(additionalGroups);
|
||||
});
|
||||
|
||||
it('returns empty array if no groups were specified at filters expression', () => {
|
||||
expect(extractGroupsFromElementsFilters(`${exprFilters} | ${exprRest}`)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractUngroupedFromElementsFilters', () => {
|
||||
it('checks if ungrouped filters expression exist at the element', () => {
|
||||
const expression =
|
||||
'filters group="10" group="11" | filters group="15" ungrouped=true | demodata | plot | render';
|
||||
const isUngrouped = extractUngroupedFromElementsFilters(expression);
|
||||
expect(isUngrouped).toBeTruthy();
|
||||
|
||||
const nextExpression =
|
||||
'filters group="10" group="11" | filters group="15" | demodata | plot | render';
|
||||
const nextIsUngrouped = extractUngroupedFromElementsFilters(nextExpression);
|
||||
expect(nextIsUngrouped).toBeFalsy();
|
||||
expect(filtersAsts).toEqual([filter1, filter2, filter3].map((f) => fromExpression(f).chain[0]));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fromExpression } from '@kbn/interpreter';
|
||||
import { Ast, ExpressionFunctionAST, fromExpression, toExpression } from '@kbn/interpreter';
|
||||
import { flowRight, get, groupBy } from 'lodash';
|
||||
import {
|
||||
Filter as FilterType,
|
||||
|
@ -14,6 +14,14 @@ import {
|
|||
FlattenFilterViewInstance,
|
||||
} from '../../types/filters';
|
||||
|
||||
const SELECT_FILTER = 'selectFilter';
|
||||
const FILTERS = 'filters';
|
||||
const REMOVE_FILTER = 'removeFilter';
|
||||
|
||||
const includeFiltersExpressions = [FILTERS, SELECT_FILTER];
|
||||
const excludeFiltersExpressions = [REMOVE_FILTER];
|
||||
const filtersExpressions = [...includeFiltersExpressions, ...excludeFiltersExpressions];
|
||||
|
||||
export const defaultFormatter = (value: unknown) => (value || null ? `${value}` : '-');
|
||||
|
||||
export const formatFilterView =
|
||||
|
@ -55,41 +63,73 @@ export const groupFiltersBy = (filters: FilterType[], groupByField: FilterField)
|
|||
}));
|
||||
};
|
||||
|
||||
export const getFiltersByGroups = (
|
||||
filters: string[],
|
||||
groups: string[],
|
||||
ungrouped: boolean = false
|
||||
) =>
|
||||
filters.filter((filter: string) => {
|
||||
const ast = fromExpression(filter);
|
||||
const expGroups: string[] = get(ast, 'chain[0].arguments.filterGroup', []);
|
||||
if (!groups?.length && ungrouped) {
|
||||
return expGroups.length === 0;
|
||||
}
|
||||
|
||||
return (
|
||||
!groups.length ||
|
||||
(expGroups.length > 0 && expGroups.every((expGroup) => groups.includes(expGroup)))
|
||||
const excludeFiltersByGroups = (filters: Ast[], filterExprAst: ExpressionFunctionAST) => {
|
||||
const groupsToExclude = filterExprAst.arguments.group ?? [];
|
||||
const removeUngrouped = filterExprAst.arguments.ungrouped?.[0] ?? false;
|
||||
return filters.filter((filter) => {
|
||||
const groups: string[] = get(filter, 'chain[0].arguments.filterGroup', []).filter(
|
||||
(group: string) => group !== ''
|
||||
);
|
||||
const noNeedToExcludeByGroup = !(
|
||||
groups.length &&
|
||||
groupsToExclude.length &&
|
||||
groupsToExclude.includes(groups[0])
|
||||
);
|
||||
});
|
||||
|
||||
export const extractGroupsFromElementsFilters = (expr: string) => {
|
||||
const ast = fromExpression(expr);
|
||||
const filtersFns = ast.chain.filter((expression) => expression.function === 'filters');
|
||||
const groups = filtersFns.reduce<string[]>((foundGroups, filterFn) => {
|
||||
const filterGroups = filterFn?.arguments.group?.map((g) => g.toString()) ?? [];
|
||||
return [...foundGroups, ...filterGroups];
|
||||
}, []);
|
||||
return [...new Set(groups)];
|
||||
const noNeedToExcludeByUngrouped = (removeUngrouped && groups.length) || !removeUngrouped;
|
||||
const excludeAllFilters = !groupsToExclude.length && !removeUngrouped;
|
||||
|
||||
return !excludeAllFilters && noNeedToExcludeByUngrouped && noNeedToExcludeByGroup;
|
||||
});
|
||||
};
|
||||
|
||||
export const extractUngroupedFromElementsFilters = (expr: string) => {
|
||||
const includeFiltersByGroups = (
|
||||
filters: Ast[],
|
||||
filterExprAst: ExpressionFunctionAST,
|
||||
ignoreUngroupedIfGroups: boolean = false
|
||||
) => {
|
||||
const groupsToInclude = filterExprAst.arguments.group ?? [];
|
||||
const includeOnlyUngrouped = filterExprAst.arguments.ungrouped?.[0] ?? false;
|
||||
return filters.filter((filter) => {
|
||||
const groups: string[] = get(filter, 'chain[0].arguments.filterGroup', []).filter(
|
||||
(group: string) => group !== ''
|
||||
);
|
||||
const needToIncludeByGroup =
|
||||
groups.length && groupsToInclude.length && groupsToInclude.includes(groups[0]);
|
||||
|
||||
const needToIncludeByUngrouped =
|
||||
includeOnlyUngrouped &&
|
||||
!groups.length &&
|
||||
(ignoreUngroupedIfGroups ? !groupsToInclude.length : true);
|
||||
|
||||
const allowAll = !groupsToInclude.length && !includeOnlyUngrouped;
|
||||
return needToIncludeByUngrouped || needToIncludeByGroup || allowAll;
|
||||
});
|
||||
};
|
||||
|
||||
export const getFiltersByFilterExpressions = (
|
||||
filters: string[],
|
||||
filterExprsAsts: ExpressionFunctionAST[]
|
||||
) => {
|
||||
const filtersAst = filters.map((filter) => fromExpression(filter));
|
||||
const matchedFiltersAst = filterExprsAsts.reduce((includedFilters, filter) => {
|
||||
if (excludeFiltersExpressions.includes(filter.function)) {
|
||||
return excludeFiltersByGroups(includedFilters, filter);
|
||||
}
|
||||
const isFiltersExpr = filter.function === FILTERS;
|
||||
const filtersToInclude = isFiltersExpr ? filtersAst : includedFilters;
|
||||
return includeFiltersByGroups(filtersToInclude, filter, isFiltersExpr);
|
||||
}, filtersAst);
|
||||
|
||||
return matchedFiltersAst.map((ast) => toExpression(ast));
|
||||
};
|
||||
|
||||
export const getFiltersExprsFromExpression = (expr: string) => {
|
||||
const ast = fromExpression(expr);
|
||||
const filtersFns = ast.chain.filter((expression) => expression.function === 'filters');
|
||||
return filtersFns.some((filterFn) => filterFn?.arguments.ungrouped?.[0]);
|
||||
return ast.chain.filter((expression) => filtersExpressions.includes(expression.function));
|
||||
};
|
||||
|
||||
export const isExpressionWithFilters = (expr: string) => {
|
||||
const ast = fromExpression(expr);
|
||||
return ast.chain.some((expression) => expression.function === 'filters');
|
||||
return ast.chain.some((expression) => filtersExpressions.includes(expression.function));
|
||||
};
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fromExpression, getType } from '@kbn/interpreter';
|
||||
import { pluck } from 'rxjs/operators';
|
||||
import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public';
|
||||
import { pluginServices } from '../services';
|
||||
|
||||
interface Options {
|
||||
castToRender?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Meant to be a replacement for plugins/interpreter/interpretAST
|
||||
*/
|
||||
export async function interpretAst(
|
||||
ast: ExpressionAstExpression,
|
||||
variables: Record<string, any>,
|
||||
input: ExpressionValue = null
|
||||
): Promise<ExpressionValue> {
|
||||
const context = { variables };
|
||||
const { execute } = pluginServices.getServices().expressions;
|
||||
|
||||
return await execute(ast, input, context).getData().pipe(pluck('result')).toPromise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs interpreter, usually in the browser
|
||||
*
|
||||
* @param {object} ast - Executable AST
|
||||
* @param {any} input - Initial input for AST execution
|
||||
* @param {object} variables - Variables to pass in to the intrepreter context
|
||||
* @param {object} options
|
||||
* @param {boolean} options.castToRender - try to cast to a type: render object?
|
||||
* @returns {promise}
|
||||
*/
|
||||
export async function runInterpreter(
|
||||
ast: ExpressionAstExpression,
|
||||
input: ExpressionValue,
|
||||
variables: Record<string, any>,
|
||||
options: Options = {}
|
||||
): Promise<ExpressionValue> {
|
||||
const context = { variables };
|
||||
try {
|
||||
const { execute } = pluginServices.getServices().expressions;
|
||||
|
||||
const renderable = await execute(ast, input, context)
|
||||
.getData()
|
||||
.pipe(pluck('result'))
|
||||
.toPromise();
|
||||
|
||||
if (getType(renderable) === 'render') {
|
||||
return renderable;
|
||||
}
|
||||
|
||||
if (options.castToRender) {
|
||||
return runInterpreter(fromExpression('render'), renderable, variables, {
|
||||
castToRender: false,
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Ack! I don't know how to render a '${getType(renderable)}'`);
|
||||
} catch (err) {
|
||||
const { error: displayError } = pluginServices.getServices().notify;
|
||||
displayError(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
|
@ -36,7 +36,6 @@ import { BfetchPublicSetup } from '../../../../src/plugins/bfetch/public';
|
|||
import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
|
||||
import { getPluginApi, CanvasApi } from './plugin_api';
|
||||
import { setupExpressions } from './setup_expressions';
|
||||
import { pluginServiceRegistry } from './services/kibana';
|
||||
|
||||
export type { CoreStart, CoreSetup };
|
||||
|
||||
|
@ -123,6 +122,8 @@ export class CanvasPlugin
|
|||
srcPlugin.start(coreStart, startPlugins);
|
||||
|
||||
const { pluginServices } = await import('./services');
|
||||
const { pluginServiceRegistry } = await import('./services/kibana');
|
||||
|
||||
pluginServices.setRegistry(
|
||||
pluginServiceRegistry.start({
|
||||
coreStart,
|
||||
|
|
|
@ -5,6 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ExpressionsServiceStart } from '../../../../../src/plugins/expressions/public';
|
||||
|
||||
export type CanvasExpressionsService = ExpressionsServiceStart;
|
||||
export type { CanvasExpressionsService } from './kibana/expressions';
|
||||
|
|
8
x-pack/plugins/canvas/public/services/filters.ts
Normal file
8
x-pack/plugins/canvas/public/services/filters.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { CanvasFiltersService } from './kibana/filters';
|
|
@ -12,6 +12,7 @@ import { PluginServices } from '../../../../../src/plugins/presentation_util/pub
|
|||
import { CanvasCustomElementService } from './custom_element';
|
||||
import { CanvasEmbeddablesService } from './embeddables';
|
||||
import { CanvasExpressionsService } from './expressions';
|
||||
import { CanvasFiltersService } from './filters';
|
||||
import { CanvasLabsService } from './labs';
|
||||
import { CanvasNavLinkService } from './nav_link';
|
||||
import { CanvasNotifyService } from './notify';
|
||||
|
@ -24,6 +25,7 @@ export interface CanvasPluginServices {
|
|||
customElement: CanvasCustomElementService;
|
||||
embeddables: CanvasEmbeddablesService;
|
||||
expressions: CanvasExpressionsService;
|
||||
filters: CanvasFiltersService;
|
||||
labs: CanvasLabsService;
|
||||
navLink: CanvasNavLinkService;
|
||||
notify: CanvasNotifyService;
|
||||
|
@ -41,6 +43,7 @@ export const useEmbeddablesService = () =>
|
|||
(() => pluginServices.getHooks().embeddables.useService())();
|
||||
export const useExpressionsService = () =>
|
||||
(() => pluginServices.getHooks().expressions.useService())();
|
||||
export const useFiltersService = () => (() => pluginServices.getHooks().filters.useService())();
|
||||
export const useLabsService = () => (() => pluginServices.getHooks().labs.useService())();
|
||||
export const useNavLinkService = () => (() => pluginServices.getHooks().navLink.useService())();
|
||||
export const useNotifyService = () => (() => pluginServices.getHooks().notify.useService())();
|
||||
|
|
|
@ -4,16 +4,137 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fromExpression, getType } from '@kbn/interpreter';
|
||||
import {
|
||||
ExpressionAstExpression,
|
||||
ExpressionExecutionParams,
|
||||
ExpressionValue,
|
||||
} from 'src/plugins/expressions';
|
||||
import { pluck } from 'rxjs/operators';
|
||||
import { buildEmbeddableFilters } from '../../../common/lib/build_embeddable_filters';
|
||||
import { ExpressionsServiceStart } from '../../../../../../src/plugins/expressions/public';
|
||||
import { KibanaPluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public';
|
||||
|
||||
import { CanvasStartDeps } from '../../plugin';
|
||||
import { CanvasExpressionsService } from '../expressions';
|
||||
import { CanvasFiltersService } from './filters';
|
||||
import { CanvasNotifyService } from '../notify';
|
||||
|
||||
interface Options {
|
||||
castToRender?: boolean;
|
||||
}
|
||||
|
||||
export class ExpressionsService {
|
||||
private filters: CanvasFiltersService;
|
||||
private notify: CanvasNotifyService;
|
||||
|
||||
constructor(
|
||||
private readonly expressions: ExpressionsServiceStart,
|
||||
{ filters, notify }: CanvasExpressionsServiceRequiredServices
|
||||
) {
|
||||
this.filters = filters;
|
||||
this.notify = notify;
|
||||
}
|
||||
|
||||
async interpretAst(
|
||||
ast: ExpressionAstExpression,
|
||||
variables: Record<string, any>,
|
||||
input: ExpressionValue = null
|
||||
) {
|
||||
const context = await this.getGlobalContext();
|
||||
return await this.interpretAstWithContext(ast, input, {
|
||||
...(context ?? {}),
|
||||
variables,
|
||||
});
|
||||
}
|
||||
|
||||
async interpretAstWithContext(
|
||||
ast: ExpressionAstExpression,
|
||||
input: ExpressionValue = null,
|
||||
context?: ExpressionExecutionParams
|
||||
): Promise<ExpressionValue> {
|
||||
return await this.expressions
|
||||
.execute(ast, input, context)
|
||||
.getData()
|
||||
.pipe(pluck('result'))
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs interpreter, usually in the browser
|
||||
*
|
||||
* @param {object} ast - Executable AST
|
||||
* @param {any} input - Initial input for AST execution
|
||||
* @param {object} variables - Variables to pass in to the intrepreter context
|
||||
* @param {object} options
|
||||
* @param {boolean} options.castToRender - try to cast to a type: render object?
|
||||
* @returns {Promise<ExpressionValue>}
|
||||
*/
|
||||
async runInterpreter(
|
||||
ast: ExpressionAstExpression,
|
||||
input: ExpressionValue,
|
||||
variables: Record<string, any>,
|
||||
options: Options = {}
|
||||
): Promise<ExpressionValue> {
|
||||
const context = await this.getGlobalContext();
|
||||
const fullContext = { ...(context ?? {}), variables };
|
||||
|
||||
try {
|
||||
const renderable = await this.interpretAstWithContext(ast, input, fullContext);
|
||||
|
||||
if (getType(renderable) === 'render') {
|
||||
return renderable;
|
||||
}
|
||||
|
||||
if (options.castToRender) {
|
||||
return this.runInterpreter(fromExpression('render'), renderable, fullContext, {
|
||||
castToRender: false,
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Ack! I don't know how to render a '${getType(renderable)}'`);
|
||||
} catch (err) {
|
||||
this.notify.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
getRenderer(name: string) {
|
||||
return this.expressions.getRenderer(name);
|
||||
}
|
||||
|
||||
getFunctions() {
|
||||
return this.expressions.getFunctions();
|
||||
}
|
||||
|
||||
private async getFilters() {
|
||||
const filtersList = this.filters.getFilters();
|
||||
const context = this.filters.getFiltersContext();
|
||||
const filterExpression = filtersList.join(' | ');
|
||||
const filterAST = fromExpression(filterExpression);
|
||||
return await this.interpretAstWithContext(filterAST, null, context);
|
||||
}
|
||||
|
||||
private async getGlobalContext() {
|
||||
const canvasFilters = await this.getFilters();
|
||||
const kibanaFilters = buildEmbeddableFilters(canvasFilters ? canvasFilters.and : []);
|
||||
return {
|
||||
searchContext: { ...kibanaFilters },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type CanvasExpressionsService = ExpressionsService;
|
||||
export interface CanvasExpressionsServiceRequiredServices {
|
||||
notify: CanvasNotifyService;
|
||||
filters: CanvasFiltersService;
|
||||
}
|
||||
|
||||
export type CanvasExpressionsServiceFactory = KibanaPluginServiceFactory<
|
||||
CanvasExpressionsService,
|
||||
CanvasStartDeps
|
||||
CanvasStartDeps,
|
||||
CanvasExpressionsServiceRequiredServices
|
||||
>;
|
||||
|
||||
export const expressionsServiceFactory: CanvasExpressionsServiceFactory = ({ startPlugins }) =>
|
||||
startPlugins.expressions;
|
||||
export const expressionsServiceFactory: CanvasExpressionsServiceFactory = (
|
||||
{ startPlugins },
|
||||
requiredServices
|
||||
) => new ExpressionsService(startPlugins.expressions, requiredServices);
|
||||
|
|
42
x-pack/plugins/canvas/public/services/kibana/filters.ts
Normal file
42
x-pack/plugins/canvas/public/services/kibana/filters.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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { KibanaPluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public';
|
||||
// @ts-expect-error untyped local
|
||||
import { getState, getStore } from '../../state/store';
|
||||
import { State } from '../../../types';
|
||||
import { getGlobalFilters, getWorkpadVariablesAsObject } from '../../state/selectors/workpad';
|
||||
import { CanvasStartDeps } from '../../plugin';
|
||||
// @ts-expect-error untyped local
|
||||
import { setFilter } from '../../state/actions/elements';
|
||||
|
||||
export class FiltersService {
|
||||
constructor() {}
|
||||
|
||||
getFilters(state: State = getState()) {
|
||||
return getGlobalFilters(state);
|
||||
}
|
||||
|
||||
updateFilter(filterId: string, filterExpression: string) {
|
||||
const { dispatch } = getStore();
|
||||
dispatch(setFilter(filterExpression, filterId, true));
|
||||
}
|
||||
|
||||
getFiltersContext(state: State = getState()) {
|
||||
const variables = getWorkpadVariablesAsObject(state);
|
||||
return { variables };
|
||||
}
|
||||
}
|
||||
|
||||
export type CanvasFiltersService = FiltersService;
|
||||
|
||||
export type CanvasFiltersServiceFactory = KibanaPluginServiceFactory<
|
||||
CanvasFiltersService,
|
||||
CanvasStartDeps
|
||||
>;
|
||||
|
||||
export const filtersServiceFactory: CanvasFiltersServiceFactory = () => new FiltersService();
|
|
@ -24,10 +24,12 @@ import { platformServiceFactory } from './platform';
|
|||
import { reportingServiceFactory } from './reporting';
|
||||
import { visualizationsServiceFactory } from './visualizations';
|
||||
import { workpadServiceFactory } from './workpad';
|
||||
import { filtersServiceFactory } from './filters';
|
||||
|
||||
export { customElementServiceFactory } from './custom_element';
|
||||
export { embeddablesServiceFactory } from './embeddables';
|
||||
export { expressionsServiceFactory } from './expressions';
|
||||
export { filtersServiceFactory } from './filters';
|
||||
export { labsServiceFactory } from './labs';
|
||||
export { notifyServiceFactory } from './notify';
|
||||
export { platformServiceFactory } from './platform';
|
||||
|
@ -41,7 +43,8 @@ export const pluginServiceProviders: PluginServiceProviders<
|
|||
> = {
|
||||
customElement: new PluginServiceProvider(customElementServiceFactory),
|
||||
embeddables: new PluginServiceProvider(embeddablesServiceFactory),
|
||||
expressions: new PluginServiceProvider(expressionsServiceFactory),
|
||||
expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']),
|
||||
filters: new PluginServiceProvider(filtersServiceFactory),
|
||||
labs: new PluginServiceProvider(labsServiceFactory),
|
||||
navLink: new PluginServiceProvider(navLinkServiceFactory),
|
||||
notify: new PluginServiceProvider(notifyServiceFactory),
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObject } from 'kibana/public';
|
||||
import { KibanaPluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public';
|
||||
|
||||
import { CanvasStartDeps } from '../../plugin';
|
||||
|
@ -67,6 +68,21 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart,
|
|||
|
||||
return { css: DEFAULT_WORKPAD_CSS, variables: [], ...workpad };
|
||||
},
|
||||
export: async (id: string) => {
|
||||
const workpad = await coreStart.http.get<SavedObject<CanvasWorkpad>>(
|
||||
`${getApiPath()}/export/${id}`
|
||||
);
|
||||
const { attributes } = workpad;
|
||||
|
||||
return {
|
||||
...workpad,
|
||||
attributes: {
|
||||
...attributes,
|
||||
css: attributes.css ?? DEFAULT_WORKPAD_CSS,
|
||||
variables: attributes.variables ?? [],
|
||||
},
|
||||
};
|
||||
},
|
||||
resolve: async (id: string) => {
|
||||
const { workpad, outcome, aliasId } = await coreStart.http.get<ResolveWorkpadResponse>(
|
||||
`${getApiPath()}/resolve/${id}`
|
||||
|
@ -93,6 +109,14 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart,
|
|||
}),
|
||||
});
|
||||
},
|
||||
import: (workpad: CanvasWorkpad) =>
|
||||
coreStart.http.post(`${getApiPath()}/import`, {
|
||||
body: JSON.stringify({
|
||||
...sanitizeWorkpad({ ...workpad }),
|
||||
assets: workpad.assets || {},
|
||||
variables: workpad.variables || [],
|
||||
}),
|
||||
}),
|
||||
createFromTemplate: (templateId: string) => {
|
||||
return coreStart.http.post(getApiPath(), {
|
||||
body: JSON.stringify({ templateId }),
|
||||
|
|
|
@ -31,7 +31,7 @@ type CanvasWorkpadServiceFactory = PluginServiceFactory<CanvasWorkpadService, St
|
|||
const TIMEOUT = 500;
|
||||
const promiseTimeout = (time: number) => () => new Promise((resolve) => setTimeout(resolve, time));
|
||||
|
||||
const { findNoTemplates, findNoWorkpads, findSomeTemplates } = stubs;
|
||||
const { findNoTemplates, findNoWorkpads, findSomeTemplates, importWorkpad } = stubs;
|
||||
|
||||
const getRandomName = () => {
|
||||
const lorem =
|
||||
|
@ -85,6 +85,10 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({
|
|||
action('workpadService.findTemplates')();
|
||||
return (hasTemplates ? findSomeTemplates() : findNoTemplates())();
|
||||
},
|
||||
import: (workpad) => {
|
||||
action('workpadService.import')(workpad);
|
||||
return importWorkpad(workpad);
|
||||
},
|
||||
create: (workpad) => {
|
||||
action('workpadService.create')(workpad);
|
||||
return Promise.resolve(workpad);
|
||||
|
|
|
@ -10,11 +10,22 @@ import { plugin } from '../../../../../../src/plugins/expressions/public';
|
|||
import { functions as functionDefinitions } from '../../../canvas_plugin_src/functions/common';
|
||||
import { renderFunctions } from '../../../canvas_plugin_src/renderers/core';
|
||||
import { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public';
|
||||
import { CanvasExpressionsService } from '../expressions';
|
||||
import {
|
||||
CanvasExpressionsService,
|
||||
CanvasExpressionsServiceRequiredServices,
|
||||
ExpressionsService,
|
||||
} from '../kibana/expressions';
|
||||
|
||||
type CanvasExpressionsServiceFactory = PluginServiceFactory<CanvasExpressionsService>;
|
||||
type CanvasExpressionsServiceFactory = PluginServiceFactory<
|
||||
CanvasExpressionsService,
|
||||
{},
|
||||
CanvasExpressionsServiceRequiredServices
|
||||
>;
|
||||
|
||||
export const expressionsServiceFactory: CanvasExpressionsServiceFactory = () => {
|
||||
export const expressionsServiceFactory: CanvasExpressionsServiceFactory = (
|
||||
params,
|
||||
requiredServices
|
||||
) => {
|
||||
const placeholder = {} as any;
|
||||
const expressionsPlugin = plugin(placeholder);
|
||||
const setup = expressionsPlugin.setup(placeholder);
|
||||
|
@ -25,5 +36,5 @@ export const expressionsServiceFactory: CanvasExpressionsServiceFactory = () =>
|
|||
expressionsService.registerRenderer(fn as unknown as AnyExpressionRenderDefinition);
|
||||
});
|
||||
|
||||
return expressionsService;
|
||||
return new ExpressionsService(expressionsService, requiredServices);
|
||||
};
|
||||
|
|
23
x-pack/plugins/canvas/public/services/stubs/filters.ts
Normal file
23
x-pack/plugins/canvas/public/services/stubs/filters.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public';
|
||||
import { CanvasFiltersService } from '../filters';
|
||||
|
||||
export type CanvasFiltersServiceFactory = PluginServiceFactory<CanvasFiltersService, {}>;
|
||||
|
||||
const noop = (..._args: any[]): any => {};
|
||||
|
||||
export const filtersServiceFactory: CanvasFiltersServiceFactory = () => ({
|
||||
getFilters: () => [
|
||||
'exactly value="machine-learning" column="project1" filterGroup="Group 1"',
|
||||
'exactly value="kibana" column="project2" filterGroup="Group 1"',
|
||||
'time column="@timestamp1" from="2021-11-02 17:13:18" to="2021-11-09 17:13:18" filterGroup="Some group"',
|
||||
],
|
||||
updateFilter: noop,
|
||||
getFiltersContext: () => ({ variables: {} }),
|
||||
});
|
|
@ -24,9 +24,11 @@ import { platformServiceFactory } from './platform';
|
|||
import { reportingServiceFactory } from './reporting';
|
||||
import { visualizationsServiceFactory } from './visualizations';
|
||||
import { workpadServiceFactory } from './workpad';
|
||||
import { filtersServiceFactory } from './filters';
|
||||
|
||||
export { customElementServiceFactory } from './custom_element';
|
||||
export { expressionsServiceFactory } from './expressions';
|
||||
export { filtersServiceFactory } from './filters';
|
||||
export { labsServiceFactory } from './labs';
|
||||
export { navLinkServiceFactory } from './nav_link';
|
||||
export { notifyServiceFactory } from './notify';
|
||||
|
@ -38,7 +40,8 @@ export { workpadServiceFactory } from './workpad';
|
|||
export const pluginServiceProviders: PluginServiceProviders<CanvasPluginServices> = {
|
||||
customElement: new PluginServiceProvider(customElementServiceFactory),
|
||||
embeddables: new PluginServiceProvider(embeddablesServiceFactory),
|
||||
expressions: new PluginServiceProvider(expressionsServiceFactory),
|
||||
expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']),
|
||||
filters: new PluginServiceProvider(filtersServiceFactory),
|
||||
labs: new PluginServiceProvider(labsServiceFactory),
|
||||
navLink: new PluginServiceProvider(navLinkServiceFactory),
|
||||
notify: new PluginServiceProvider(notifyServiceFactory),
|
||||
|
|
|
@ -6,13 +6,12 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public';
|
||||
|
||||
// @ts-expect-error
|
||||
import { getDefaultWorkpad } from '../../state/defaults';
|
||||
import { getDefaultWorkpad, getExportedWorkpad } from '../../state/defaults';
|
||||
import { CanvasWorkpadService } from '../workpad';
|
||||
import { CanvasTemplate } from '../../../types';
|
||||
import { CanvasTemplate, CanvasWorkpad } from '../../../types';
|
||||
|
||||
type CanvasWorkpadServiceFactory = PluginServiceFactory<CanvasWorkpadService>;
|
||||
|
||||
|
@ -94,6 +93,7 @@ export const findNoTemplates =
|
|||
.then(() => getNoTemplates());
|
||||
};
|
||||
|
||||
export const importWorkpad = (workpad: CanvasWorkpad) => Promise.resolve(workpad);
|
||||
export const getNoTemplates = () => ({ templates: [] });
|
||||
export const getSomeTemplates = () => ({ templates });
|
||||
|
||||
|
@ -103,6 +103,7 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = () => ({
|
|||
Promise.resolve({ outcome: 'exactMatch', workpad: { ...getDefaultWorkpad(), id } }),
|
||||
findTemplates: findNoTemplates(),
|
||||
create: (workpad) => Promise.resolve(workpad),
|
||||
import: (workpad) => importWorkpad(workpad),
|
||||
createFromTemplate: (_templateId: string) => Promise.resolve(getDefaultWorkpad()),
|
||||
find: findNoWorkpads(),
|
||||
remove: (_id: string) => Promise.resolve(),
|
||||
|
|
|
@ -25,10 +25,12 @@ export interface ResolveWorkpadResponse {
|
|||
outcome: SavedObjectsResolveResponse['outcome'];
|
||||
aliasId?: SavedObjectsResolveResponse['alias_target_id'];
|
||||
}
|
||||
|
||||
export interface CanvasWorkpadService {
|
||||
get: (id: string) => Promise<CanvasWorkpad>;
|
||||
resolve: (id: string) => Promise<ResolveWorkpadResponse>;
|
||||
create: (workpad: CanvasWorkpad) => Promise<CanvasWorkpad>;
|
||||
import: (workpad: CanvasWorkpad) => Promise<CanvasWorkpad>;
|
||||
createFromTemplate: (templateId: string) => Promise<CanvasWorkpad>;
|
||||
find: (term: string) => Promise<WorkpadFindResponse>;
|
||||
remove: (id: string) => Promise<void>;
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
import { getValue as getResolvedArgsValue } from '../selectors/resolved_args';
|
||||
import { getDefaultElement } from '../defaults';
|
||||
import { ErrorStrings } from '../../../i18n';
|
||||
import { runInterpreter, interpretAst } from '../../lib/run_interpreter';
|
||||
import { subMultitree } from '../../lib/aeroelastic/functional';
|
||||
import { pluginServices } from '../../services';
|
||||
import { selectToplevelNodes } from './transient';
|
||||
|
@ -101,11 +100,16 @@ export const fetchContext = createThunk(
|
|||
});
|
||||
|
||||
const variables = getWorkpadVariablesAsObject(getState());
|
||||
|
||||
const { expressions } = pluginServices.getServices();
|
||||
const elementWithNewAst = set(element, pathToTarget, astChain);
|
||||
|
||||
// get context data from a partial AST
|
||||
return interpretAst(elementWithNewAst.ast, variables, prevContextValue).then((value) => {
|
||||
dispatch(args.setValue({ path: contextPath, value }));
|
||||
});
|
||||
return expressions
|
||||
.interpretAst(elementWithNewAst.ast, variables, prevContextValue)
|
||||
.then((value) => {
|
||||
dispatch(args.setValue({ path: contextPath, value }));
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -124,14 +128,14 @@ const fetchRenderableWithContextFn = ({ dispatch, getState }, element, ast, cont
|
|||
});
|
||||
|
||||
const variables = getWorkpadVariablesAsObject(getState());
|
||||
|
||||
return runInterpreter(ast, context, variables, { castToRender: true })
|
||||
const { expressions, notify } = pluginServices.getServices();
|
||||
return expressions
|
||||
.runInterpreter(ast, context, variables, { castToRender: true })
|
||||
.then((renderable) => {
|
||||
dispatch(getAction(renderable));
|
||||
})
|
||||
.catch((err) => {
|
||||
const notifyService = pluginServices.getServices().notify;
|
||||
notifyService.error(err);
|
||||
notify.error(err);
|
||||
dispatch(getAction(err));
|
||||
});
|
||||
};
|
||||
|
@ -171,12 +175,13 @@ export const fetchAllRenderables = createThunk(
|
|||
const argumentPath = [element.id, 'expressionRenderable'];
|
||||
|
||||
const variables = getWorkpadVariablesAsObject(getState());
|
||||
const { expressions, notify } = pluginServices.getServices();
|
||||
|
||||
return runInterpreter(ast, null, variables, { castToRender: true })
|
||||
return expressions
|
||||
.runInterpreter(ast, null, variables, { castToRender: true })
|
||||
.then((renderable) => ({ path: argumentPath, value: renderable }))
|
||||
.catch((err) => {
|
||||
const notifyService = pluginServices.getServices().notify;
|
||||
notifyService.error(err);
|
||||
notify.error(err);
|
||||
return { path: argumentPath, value: err };
|
||||
});
|
||||
});
|
||||
|
|
|
@ -87,6 +87,14 @@ export const getDefaultWorkpad = () => {
|
|||
};
|
||||
};
|
||||
|
||||
export const getExportedWorkpad = () => {
|
||||
const workpad = getDefaultWorkpad();
|
||||
return {
|
||||
id: workpad.id,
|
||||
attributes: workpad,
|
||||
};
|
||||
};
|
||||
|
||||
export const getDefaultSidebar = () => ({
|
||||
groupFiltersByOption: DEFAULT_GROUP_BY_FIELD,
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
ExpressionAstFunction,
|
||||
ExpressionAstExpression,
|
||||
} from '../../../types';
|
||||
import { isExpressionWithFilters } from '../../lib/filter';
|
||||
|
||||
type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & R;
|
||||
type WorkpadInfo = Modify<CanvasWorkpad, { pages: undefined }>;
|
||||
|
@ -248,7 +249,7 @@ function extractFilterGroups(
|
|||
// TODO: we always get a function here, right?
|
||||
const { function: fn, arguments: args } = item;
|
||||
|
||||
if (fn === 'filters') {
|
||||
if (isExpressionWithFilters(fn)) {
|
||||
// we have a filter function, extract groups from args
|
||||
return groups.concat(
|
||||
buildGroupValues(args, (argValue) => {
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface MockWorkpadRouteContext extends CanvasRouteHandlerContext {
|
|||
workpad: {
|
||||
create: jest.Mock;
|
||||
get: jest.Mock;
|
||||
import: jest.Mock;
|
||||
update: jest.Mock;
|
||||
resolve: jest.Mock;
|
||||
};
|
||||
|
@ -23,6 +24,7 @@ export const workpadRouteContextMock = {
|
|||
workpad: {
|
||||
create: jest.fn(),
|
||||
get: jest.fn(),
|
||||
import: jest.fn(),
|
||||
update: jest.fn(),
|
||||
resolve: jest.fn(),
|
||||
},
|
||||
|
|
|
@ -23,7 +23,8 @@ import { initRoutes } from './routes';
|
|||
import { registerCanvasUsageCollector } from './collectors';
|
||||
import { loadSampleData } from './sample_data';
|
||||
import { setupInterpreter } from './setup_interpreter';
|
||||
import { customElementType, workpadType, workpadTemplateType } from './saved_objects';
|
||||
import { customElementType, workpadTypeFactory, workpadTemplateType } from './saved_objects';
|
||||
import type { CanvasSavedObjectTypeMigrationsDeps } from './saved_objects/migrations';
|
||||
import { initializeTemplates } from './templates';
|
||||
import { essqlSearchStrategyProvider } from './lib/essql_strategy';
|
||||
import { getUISettings } from './ui_settings';
|
||||
|
@ -53,10 +54,18 @@ export class CanvasPlugin implements Plugin {
|
|||
public setup(coreSetup: CoreSetup<PluginsStart>, plugins: PluginsSetup) {
|
||||
const expressionsFork = plugins.expressions.fork();
|
||||
|
||||
setupInterpreter(expressionsFork, {
|
||||
embeddablePersistableStateService: {
|
||||
extract: plugins.embeddable.extract,
|
||||
inject: plugins.embeddable.inject,
|
||||
},
|
||||
});
|
||||
|
||||
const deps: CanvasSavedObjectTypeMigrationsDeps = { expressions: expressionsFork };
|
||||
coreSetup.uiSettings.register(getUISettings());
|
||||
coreSetup.savedObjects.registerType(customElementType);
|
||||
coreSetup.savedObjects.registerType(workpadType);
|
||||
coreSetup.savedObjects.registerType(workpadTemplateType);
|
||||
coreSetup.savedObjects.registerType(customElementType(deps));
|
||||
coreSetup.savedObjects.registerType(workpadTypeFactory(deps));
|
||||
coreSetup.savedObjects.registerType(workpadTemplateType(deps));
|
||||
|
||||
plugins.features.registerKibanaFeature(getCanvasFeature(plugins));
|
||||
|
||||
|
@ -84,13 +93,6 @@ export class CanvasPlugin implements Plugin {
|
|||
const kibanaIndex = coreSetup.savedObjects.getKibanaIndex();
|
||||
registerCanvasUsageCollector(plugins.usageCollection, kibanaIndex);
|
||||
|
||||
setupInterpreter(expressionsFork, {
|
||||
embeddablePersistableStateService: {
|
||||
extract: plugins.embeddable.extract,
|
||||
inject: plugins.embeddable.inject,
|
||||
},
|
||||
});
|
||||
|
||||
coreSetup.getStartServices().then(([_, depsStart]) => {
|
||||
const strategy = essqlSearchStrategyProvider();
|
||||
plugins.data.search.registerSearchStrategy(ESSQL_SEARCH_STRATEGY, strategy);
|
||||
|
|
42
x-pack/plugins/canvas/server/routes/workpad/import.ts
Normal file
42
x-pack/plugins/canvas/server/routes/workpad/import.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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RouteInitializerDeps } from '../';
|
||||
import { API_ROUTE_WORKPAD_IMPORT } from '../../../common/lib/constants';
|
||||
import { ImportedCanvasWorkpad } from '../../../types';
|
||||
import { ImportedWorkpadSchema } from './workpad_schema';
|
||||
import { okResponse } from '../ok_response';
|
||||
import { catchErrorHandler } from '../catch_error_handler';
|
||||
|
||||
const createRequestBodySchema = ImportedWorkpadSchema;
|
||||
|
||||
export function initializeImportWorkpadRoute(deps: RouteInitializerDeps) {
|
||||
const { router } = deps;
|
||||
router.post(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD_IMPORT}`,
|
||||
validate: {
|
||||
body: createRequestBodySchema,
|
||||
},
|
||||
options: {
|
||||
body: {
|
||||
maxBytes: 26214400,
|
||||
accepts: ['application/json'],
|
||||
},
|
||||
},
|
||||
},
|
||||
catchErrorHandler(async (context, request, response) => {
|
||||
const workpad = request.body as ImportedCanvasWorkpad;
|
||||
|
||||
const createdObject = await context.canvas.workpad.import(workpad);
|
||||
|
||||
return response.ok({
|
||||
body: { ...okResponse, id: createdObject.id },
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
|
@ -9,6 +9,7 @@ import { RouteInitializerDeps } from '../';
|
|||
import { initializeFindWorkpadsRoute } from './find';
|
||||
import { initializeGetWorkpadRoute } from './get';
|
||||
import { initializeCreateWorkpadRoute } from './create';
|
||||
import { initializeImportWorkpadRoute } from './import';
|
||||
import { initializeUpdateWorkpadRoute, initializeUpdateWorkpadAssetsRoute } from './update';
|
||||
import { initializeDeleteWorkpadRoute } from './delete';
|
||||
import { initializeResolveWorkpadRoute } from './resolve';
|
||||
|
@ -18,6 +19,7 @@ export function initWorkpadRoutes(deps: RouteInitializerDeps) {
|
|||
initializeResolveWorkpadRoute(deps);
|
||||
initializeGetWorkpadRoute(deps);
|
||||
initializeCreateWorkpadRoute(deps);
|
||||
initializeImportWorkpadRoute(deps);
|
||||
initializeUpdateWorkpadRoute(deps);
|
||||
initializeUpdateWorkpadAssetsRoute(deps);
|
||||
initializeDeleteWorkpadRoute(deps);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { TypeOf, schema } from '@kbn/config-schema';
|
||||
|
||||
export const PositionSchema = schema.object({
|
||||
angle: schema.number(),
|
||||
|
@ -18,30 +18,30 @@ export const PositionSchema = schema.object({
|
|||
|
||||
export const WorkpadElementSchema = schema.object({
|
||||
expression: schema.string(),
|
||||
filter: schema.maybe(schema.nullable(schema.string())),
|
||||
filter: schema.nullable(schema.string({ defaultValue: '' })),
|
||||
id: schema.string(),
|
||||
position: PositionSchema,
|
||||
});
|
||||
|
||||
export const WorkpadPageSchema = schema.object({
|
||||
elements: schema.arrayOf(WorkpadElementSchema),
|
||||
groups: schema.maybe(
|
||||
schema.arrayOf(
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
position: PositionSchema,
|
||||
})
|
||||
)
|
||||
groups: schema.arrayOf(
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
position: PositionSchema,
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
),
|
||||
id: schema.string(),
|
||||
style: schema.recordOf(schema.string(), schema.string()),
|
||||
transition: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.object({}),
|
||||
transition: schema.oneOf(
|
||||
[
|
||||
schema.object({}, { defaultValue: {} }),
|
||||
schema.object({
|
||||
name: schema.string(),
|
||||
}),
|
||||
])
|
||||
],
|
||||
{ defaultValue: {} }
|
||||
),
|
||||
});
|
||||
|
||||
|
@ -55,44 +55,71 @@ export const WorkpadAssetSchema = schema.object({
|
|||
export const WorkpadVariable = schema.object({
|
||||
name: schema.string(),
|
||||
value: schema.oneOf([schema.string(), schema.number(), schema.boolean()]),
|
||||
type: schema.string(),
|
||||
});
|
||||
|
||||
export const WorkpadSchema = schema.object(
|
||||
{
|
||||
'@created': schema.maybe(schema.string()),
|
||||
'@timestamp': schema.maybe(schema.string()),
|
||||
assets: schema.maybe(schema.recordOf(schema.string(), WorkpadAssetSchema)),
|
||||
colors: schema.arrayOf(schema.string()),
|
||||
css: schema.string(),
|
||||
variables: schema.arrayOf(WorkpadVariable),
|
||||
height: schema.number(),
|
||||
id: schema.string(),
|
||||
isWriteable: schema.maybe(schema.boolean()),
|
||||
name: schema.string(),
|
||||
page: schema.number(),
|
||||
pages: schema.arrayOf(WorkpadPageSchema),
|
||||
width: schema.number(),
|
||||
},
|
||||
{
|
||||
validate: (workpad) => {
|
||||
// Validate unique page ids
|
||||
const pageIdsArray = workpad.pages.map((page) => page.id);
|
||||
const pageIdsSet = new Set(pageIdsArray);
|
||||
|
||||
if (pageIdsArray.length !== pageIdsSet.size) {
|
||||
return 'Page Ids are not unique';
|
||||
}
|
||||
|
||||
// Validate unique element ids
|
||||
const elementIdsArray = workpad.pages
|
||||
.map((page) => page.elements.map((element) => element.id))
|
||||
.flat();
|
||||
const elementIdsSet = new Set(elementIdsArray);
|
||||
|
||||
if (elementIdsArray.length !== elementIdsSet.size) {
|
||||
return 'Element Ids are not unique';
|
||||
type: schema.string({
|
||||
validate: (type) => {
|
||||
const validTypes = ['string', 'number', 'boolean'];
|
||||
if (type && !validTypes.includes(type)) {
|
||||
return `${type} is invalid type for a variable. Valid types: ${validTypes.join(', ')}.`;
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const commonWorkpadFields = {
|
||||
'@created': schema.maybe(schema.string()),
|
||||
'@timestamp': schema.maybe(schema.string()),
|
||||
colors: schema.arrayOf(schema.string()),
|
||||
css: schema.string(),
|
||||
variables: schema.arrayOf(WorkpadVariable),
|
||||
height: schema.number(),
|
||||
id: schema.maybe(schema.string()),
|
||||
isWriteable: schema.maybe(schema.boolean()),
|
||||
name: schema.string(),
|
||||
page: schema.number(),
|
||||
pages: schema.arrayOf(WorkpadPageSchema),
|
||||
width: schema.number(),
|
||||
};
|
||||
|
||||
const WorkpadSchemaWithoutValidation = schema.object({
|
||||
assets: schema.maybe(schema.recordOf(schema.string(), WorkpadAssetSchema)),
|
||||
...commonWorkpadFields,
|
||||
});
|
||||
|
||||
const ImportedWorkpadSchemaWithoutValidation = schema.object({
|
||||
assets: schema.recordOf(schema.string(), WorkpadAssetSchema),
|
||||
...commonWorkpadFields,
|
||||
});
|
||||
|
||||
const validate = (workpad: TypeOf<typeof WorkpadSchemaWithoutValidation>) => {
|
||||
// Validate unique page ids
|
||||
const pageIdsArray = workpad.pages.map((page) => page.id);
|
||||
const pageIdsSet = new Set(pageIdsArray);
|
||||
|
||||
if (pageIdsArray.length !== pageIdsSet.size) {
|
||||
return 'Page Ids are not unique';
|
||||
}
|
||||
|
||||
// Validate unique element ids
|
||||
const elementIdsArray = workpad.pages
|
||||
.map((page) => page.elements.map((element) => element.id))
|
||||
.flat();
|
||||
const elementIdsSet = new Set(elementIdsArray);
|
||||
|
||||
if (elementIdsArray.length !== elementIdsSet.size) {
|
||||
return 'Element Ids are not unique';
|
||||
}
|
||||
};
|
||||
|
||||
export const WorkpadSchema = WorkpadSchemaWithoutValidation.extends(
|
||||
{},
|
||||
{
|
||||
validate,
|
||||
}
|
||||
);
|
||||
|
||||
export const ImportedWorkpadSchema = ImportedWorkpadSchemaWithoutValidation.extends(
|
||||
{},
|
||||
{
|
||||
validate,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
import { SavedObjectsType } from 'src/core/server';
|
||||
import { CUSTOM_ELEMENT_TYPE } from '../../common/lib/constants';
|
||||
import { customElementMigrationsFactory, CanvasSavedObjectTypeMigrationsDeps } from './migrations';
|
||||
|
||||
export const customElementType: SavedObjectsType = {
|
||||
export const customElementType = (deps: CanvasSavedObjectTypeMigrationsDeps): SavedObjectsType => ({
|
||||
name: CUSTOM_ELEMENT_TYPE,
|
||||
hidden: false,
|
||||
namespaceType: 'multiple-isolated',
|
||||
|
@ -31,7 +32,7 @@ export const customElementType: SavedObjectsType = {
|
|||
'@created': { type: 'date' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
migrations: customElementMigrationsFactory(deps),
|
||||
management: {
|
||||
icon: 'canvasApp',
|
||||
defaultSearchField: 'name',
|
||||
|
@ -40,4 +41,4 @@ export const customElementType: SavedObjectsType = {
|
|||
return obj.attributes.displayName;
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { workpadType } from './workpad';
|
||||
import { workpadTypeFactory } from './workpad';
|
||||
import { customElementType } from './custom_element';
|
||||
import { workpadTemplateType } from './workpad_template';
|
||||
|
||||
export { customElementType, workpadType, workpadTemplateType };
|
||||
export { customElementType, workpadTypeFactory, workpadTemplateType };
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { Ast, fromExpression, toExpression } from '@kbn/interpreter';
|
||||
import { Serializable } from '@kbn/utility-types';
|
||||
import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server';
|
||||
import { flowRight, mapValues } from 'lodash';
|
||||
import {
|
||||
CanvasElement,
|
||||
CanvasTemplateElement,
|
||||
CanvasTemplate,
|
||||
CustomElement,
|
||||
CustomElementContent,
|
||||
CustomElementNode,
|
||||
} from '../../../types';
|
||||
import {
|
||||
MigrateFunction,
|
||||
MigrateFunctionsObject,
|
||||
} from '../../../../../../src/plugins/kibana_utils/common';
|
||||
import { WorkpadAttributes } from '../../routes/workpad/workpad_attributes';
|
||||
import { CanvasSavedObjectTypeMigrationsDeps } from './types';
|
||||
|
||||
type ToSerializable<Type> = {
|
||||
[K in keyof Type]: Type[K] extends unknown[]
|
||||
? ToSerializable<Type[K]>
|
||||
: Type[K] extends {}
|
||||
? ToSerializable<Type[K]>
|
||||
: Serializable;
|
||||
};
|
||||
|
||||
type ExprAst = ToSerializable<Ast>;
|
||||
|
||||
interface CommonPage<T> {
|
||||
elements?: T[];
|
||||
}
|
||||
interface CommonWorkpad<T extends CommonPage<U>, U> {
|
||||
pages?: T[];
|
||||
}
|
||||
|
||||
type MigrationFn<T> = (
|
||||
migrate: MigrateFunction<ExprAst, ExprAst>,
|
||||
version: string
|
||||
) => SavedObjectMigrationFn<T>;
|
||||
|
||||
const toAst = (expression: string): ExprAst => fromExpression(expression);
|
||||
const fromAst = (ast: Ast): string => toExpression(ast);
|
||||
|
||||
const migrateExpr = (expr: string, migrateFn: MigrateFunction<ExprAst, ExprAst>) =>
|
||||
flowRight<string[], ExprAst, ExprAst, string>(fromAst, migrateFn, toAst)(expr);
|
||||
|
||||
const migrateWorkpadElement =
|
||||
(migrate: MigrateFunction<ExprAst, ExprAst>) =>
|
||||
({ filter, expression, ...element }: CanvasElement | CustomElementNode) => ({
|
||||
...element,
|
||||
filter: filter ? migrateExpr(filter, migrate) : filter,
|
||||
expression: expression ? migrateExpr(expression, migrate) : expression,
|
||||
});
|
||||
|
||||
const migrateTemplateElement =
|
||||
(migrate: MigrateFunction<ExprAst, ExprAst>) =>
|
||||
({ expression, ...element }: CanvasTemplateElement) => ({
|
||||
...element,
|
||||
expression: expression ? migrateExpr(expression, migrate) : expression,
|
||||
});
|
||||
|
||||
const migrateWorkpadElements = <T extends CommonPage<U>, U>(
|
||||
doc: SavedObjectUnsanitizedDoc<CommonWorkpad<T, U> | undefined>,
|
||||
migrateElementFn: any
|
||||
) => {
|
||||
if (
|
||||
typeof doc.attributes !== 'object' ||
|
||||
doc.attributes === null ||
|
||||
doc.attributes === undefined
|
||||
) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
const { pages } = doc.attributes;
|
||||
|
||||
const newPages = pages?.map((page) => {
|
||||
const { elements } = page;
|
||||
const newElements = elements?.map(migrateElementFn);
|
||||
return { ...page, elements: newElements };
|
||||
});
|
||||
|
||||
return { ...doc, attributes: { ...doc.attributes, pages: newPages } };
|
||||
};
|
||||
|
||||
const migrateTemplateWorkpadExpressions: MigrationFn<CanvasTemplate['template']> =
|
||||
(migrate) => (doc) =>
|
||||
migrateWorkpadElements(doc, migrateTemplateElement(migrate));
|
||||
|
||||
const migrateWorkpadExpressionsAndFilters: MigrationFn<WorkpadAttributes> = (migrate) => (doc) =>
|
||||
migrateWorkpadElements(doc, migrateWorkpadElement(migrate));
|
||||
|
||||
const migrateCustomElementExpressionsAndFilters: MigrationFn<CustomElement> =
|
||||
(migrate) => (doc) => {
|
||||
if (
|
||||
typeof doc.attributes !== 'object' ||
|
||||
doc.attributes === null ||
|
||||
doc.attributes === undefined
|
||||
) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
const { content } = doc.attributes;
|
||||
const { selectedNodes = [] }: CustomElementContent = content
|
||||
? JSON.parse(content)
|
||||
: { selectedNodes: [] };
|
||||
|
||||
const newSelectedNodes = selectedNodes.map((element) => {
|
||||
const newElement = migrateWorkpadElement(migrate)(element);
|
||||
return { ...element, ...newElement, ast: toAst(newElement.expression) };
|
||||
});
|
||||
|
||||
const newContent = JSON.stringify({ selectedNodes: newSelectedNodes });
|
||||
return { ...doc, attributes: { ...doc.attributes, content: newContent } };
|
||||
};
|
||||
|
||||
export const workpadExpressionsMigrationsFactory = ({
|
||||
expressions,
|
||||
}: CanvasSavedObjectTypeMigrationsDeps) =>
|
||||
mapValues<MigrateFunctionsObject, SavedObjectMigrationFn<WorkpadAttributes>>(
|
||||
expressions.getAllMigrations(),
|
||||
migrateWorkpadExpressionsAndFilters
|
||||
) as MigrateFunctionsObject;
|
||||
|
||||
export const templateWorkpadExpressionsMigrationsFactory = ({
|
||||
expressions,
|
||||
}: CanvasSavedObjectTypeMigrationsDeps) =>
|
||||
mapValues<MigrateFunctionsObject, SavedObjectMigrationFn<CanvasTemplate['template']>>(
|
||||
expressions.getAllMigrations(),
|
||||
migrateTemplateWorkpadExpressions
|
||||
) as MigrateFunctionsObject;
|
||||
|
||||
export const customElementExpressionsMigrationsFactory = ({
|
||||
expressions,
|
||||
}: CanvasSavedObjectTypeMigrationsDeps) =>
|
||||
mapValues<MigrateFunctionsObject, SavedObjectMigrationFn<CustomElement>>(
|
||||
expressions.getAllMigrations(),
|
||||
migrateCustomElementExpressionsAndFilters
|
||||
) as MigrateFunctionsObject;
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
customElementExpressionsMigrationsFactory,
|
||||
templateWorkpadExpressionsMigrationsFactory,
|
||||
workpadExpressionsMigrationsFactory,
|
||||
} from './expressions';
|
||||
import { CanvasSavedObjectTypeMigrationsDeps } from './types';
|
||||
import { workpadMigrationsFactory as workpadMigrationsFactoryFn } from './workpad';
|
||||
import { mergeMigrationFunctionMaps } from '../../../../../../src/plugins/kibana_utils/common';
|
||||
|
||||
export const workpadMigrationsFactory = (deps: CanvasSavedObjectTypeMigrationsDeps) =>
|
||||
mergeMigrationFunctionMaps(
|
||||
workpadMigrationsFactoryFn(deps),
|
||||
workpadExpressionsMigrationsFactory(deps)
|
||||
);
|
||||
|
||||
export const templateWorkpadMigrationsFactory = (deps: CanvasSavedObjectTypeMigrationsDeps) =>
|
||||
templateWorkpadExpressionsMigrationsFactory(deps);
|
||||
|
||||
export const customElementMigrationsFactory = (deps: CanvasSavedObjectTypeMigrationsDeps) =>
|
||||
customElementExpressionsMigrationsFactory(deps);
|
||||
|
||||
export type { CanvasSavedObjectTypeMigrationsDeps } from './types';
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { SavedObjectMigrationFn } from 'src/core/server';
|
||||
|
||||
export const removeAttributesId: SavedObjectMigrationFn = (doc) => {
|
||||
export const removeAttributesId: SavedObjectMigrationFn<any, any> = (doc) => {
|
||||
if (typeof doc.attributes === 'object' && doc.attributes !== null) {
|
||||
delete (doc.attributes as any).id;
|
||||
}
|
||||
|
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ExpressionsService } from 'src/plugins/expressions/public';
|
||||
|
||||
export interface CanvasSavedObjectTypeMigrationsDeps {
|
||||
expressions: ExpressionsService;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common';
|
||||
import { removeAttributesId } from './remove_attributes_id';
|
||||
import { CanvasSavedObjectTypeMigrationsDeps } from './types';
|
||||
|
||||
export const workpadMigrationsFactory = (deps: CanvasSavedObjectTypeMigrationsDeps) =>
|
||||
({
|
||||
'7.0.0': removeAttributesId,
|
||||
} as unknown as MigrateFunctionsObject);
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
import { SavedObjectsType } from 'src/core/server';
|
||||
import { CANVAS_TYPE } from '../../common/lib/constants';
|
||||
import { removeAttributesId } from './migrations/remove_attributes_id';
|
||||
import { workpadMigrationsFactory } from './migrations';
|
||||
import type { CanvasSavedObjectTypeMigrationsDeps } from './migrations';
|
||||
|
||||
export const workpadType: SavedObjectsType = {
|
||||
export const workpadTypeFactory = (
|
||||
deps: CanvasSavedObjectTypeMigrationsDeps
|
||||
): SavedObjectsType => ({
|
||||
name: CANVAS_TYPE,
|
||||
hidden: false,
|
||||
namespaceType: 'multiple-isolated',
|
||||
|
@ -29,9 +32,7 @@ export const workpadType: SavedObjectsType = {
|
|||
'@created': { type: 'date' },
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
'7.0.0': removeAttributesId,
|
||||
},
|
||||
migrations: workpadMigrationsFactory(deps),
|
||||
management: {
|
||||
importableAndExportable: true,
|
||||
icon: 'canvasApp',
|
||||
|
@ -46,4 +47,4 @@ export const workpadType: SavedObjectsType = {
|
|||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -7,8 +7,14 @@
|
|||
|
||||
import { SavedObjectsType } from 'src/core/server';
|
||||
import { TEMPLATE_TYPE } from '../../common/lib/constants';
|
||||
import {
|
||||
CanvasSavedObjectTypeMigrationsDeps,
|
||||
templateWorkpadMigrationsFactory,
|
||||
} from './migrations';
|
||||
|
||||
export const workpadTemplateType: SavedObjectsType = {
|
||||
export const workpadTemplateType = (
|
||||
deps: CanvasSavedObjectTypeMigrationsDeps
|
||||
): SavedObjectsType => ({
|
||||
name: TEMPLATE_TYPE,
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
|
@ -44,7 +50,7 @@ export const workpadTemplateType: SavedObjectsType = {
|
|||
},
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
migrations: templateWorkpadMigrationsFactory(deps),
|
||||
management: {
|
||||
importableAndExportable: false,
|
||||
icon: 'canvasApp',
|
||||
|
@ -53,4 +59,4 @@ export const workpadTemplateType: SavedObjectsType = {
|
|||
return obj.attributes.name;
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -63,7 +63,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "# Sample."\n| render css=".canvasRenderEl h1 {\ntext-align: center;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "# Sample."\n| render css=".canvasRenderEl h1 {\ntext-align: center;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-33286979-7ea0-41ce-9835-b3bf07f09272',
|
||||
|
@ -76,7 +76,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### This is a subtitle"\n| render css=".canvasRenderEl h3 {\ntext-align: center;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### This is a subtitle"\n| render css=".canvasRenderEl h3 {\ntext-align: center;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-1e3b3ffe-4ed8-4376-aad3-77e06d29cafe',
|
||||
|
@ -89,7 +89,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "Footnote can go here"\n| render \n css=".canvasRenderEl p {\ntext-align: center;\ncolor: #FFFFFF;\nfont-size: 18px;\nopacity: .7;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "Footnote can go here"\n| render \n css=".canvasRenderEl p {\ntext-align: center;\ncolor: #FFFFFF;\nfont-size: 18px;\nopacity: .7;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-5b5035a3-d5b7-4483-a240-2cf80f5e0acf',
|
||||
|
@ -150,7 +150,8 @@ export const pitch: CanvasTemplate = {
|
|||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expression: 'filters\n| demodata\n| markdown "##### CATEGORY 01"\n| render',
|
||||
expression:
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "##### CATEGORY 01"\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-96a390b6-3d0a-4372-89cb-3ff38eec9565',
|
||||
|
@ -162,7 +163,8 @@ export const pitch: CanvasTemplate = {
|
|||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expression: 'filters\n| demodata\n| markdown "## Half text, half _image._"\n| render',
|
||||
expression:
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Half text, half _image._"\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-118b848d-0f89-4d20-868c-21597b7fd5e0',
|
||||
|
@ -188,7 +190,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -223,7 +225,7 @@ export const pitch: CanvasTemplate = {
|
|||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expression: 'filters\n| demodata\n| markdown "##### BIOS"\n| render',
|
||||
expression: 'kibana\n| selectFilter\n| demodata\n| markdown "##### BIOS"\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-e2c658ee-7614-4d92-a46e-2b1a81a24485',
|
||||
|
@ -236,7 +238,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## Jane Doe" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Jane Doe" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-3d16765e-5251-4954-8e2a-6c64ed465b73',
|
||||
|
@ -249,7 +251,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### Developer" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h3 {\ncolor: #444444;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### Developer" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h3 {\ncolor: #444444;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-624675cf-46e9-4545-b86a-5409bbe53ac1',
|
||||
|
@ -262,7 +264,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. " \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. " \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-dc841809-d2a9-491b-b44f-be92927b8034',
|
||||
|
@ -301,7 +303,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. " \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. " \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-62f241ec-71ce-4edb-a27b-0de990522d20',
|
||||
|
@ -314,7 +316,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### Designer" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h3 {\ncolor: #444444;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### Designer" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h3 {\ncolor: #444444;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-aa6c07e0-937f-4362-9d52-f70738faa0c5',
|
||||
|
@ -340,7 +342,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## John Smith" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## John Smith" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -388,7 +390,8 @@ export const pitch: CanvasTemplate = {
|
|||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expression: 'filters\n| demodata\n| markdown "##### CATEGORY 10"\n| render',
|
||||
expression:
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "##### CATEGORY 10"\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-96be0724-0945-4802-8929-1dc456192fb5',
|
||||
|
@ -401,7 +404,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## Another page style."\n| render css=".canvasRenderEl h2 {\nfont-size: 64px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Another page style."\n| render css=".canvasRenderEl h2 {\nfont-size: 64px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-3b4ba0ff-7f95-460e-9fa6-0cbb0f8f3df8',
|
||||
|
@ -427,7 +430,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-0b9aa82b-fb0c-4000-805b-146cc9280bc5',
|
||||
|
@ -440,7 +443,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### Introduction"\n| render css=".canvasRenderEl h3 {\ncolor: #444444;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### Introduction"\n| render css=".canvasRenderEl h3 {\ncolor: #444444;\n}"',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -489,7 +492,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #45bdb0;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #45bdb0;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-1ba728f0-f645-4910-9d32-fa5b5820a94c',
|
||||
|
@ -502,7 +505,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. Donec mauris, ut scelerisque urna. Sed vel neque quis metus luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. Donec mauris, ut scelerisque urna. Sed vel neque quis metus luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-db9051eb-7699-4883-b67f-945979cf5650',
|
||||
|
@ -528,7 +531,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="wheel" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=30 align="center" color="#45bdb0" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="wheel" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=30 align="center" color="#45bdb0" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-fc11525c-2d9c-4a7b-9d96-d54e7bc6479b',
|
||||
|
@ -554,7 +557,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. Donec mauris, ut scelerisque urna. Sed vel neque quis metus luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. Donec mauris, ut scelerisque urna. Sed vel neque quis metus luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-eb9a8883-de47-4a46-9400-b7569f9e69e6',
|
||||
|
@ -567,7 +570,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="wheel" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=30 align="center" color="#45bdb0" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="wheel" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=30 align="center" color="#45bdb0" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-20c1c86a-658b-4bd2-8326-f987ef84e730',
|
||||
|
@ -580,7 +583,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. Donec mauris, ut scelerisque urna. Sed vel neque quis metus luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. Donec mauris, ut scelerisque urna. Sed vel neque quis metus luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-335db0c3-f678-4cb8-8b93-a6494f1787f5',
|
||||
|
@ -593,7 +596,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="wheel" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=30 align="center" color="#45bdb0" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="wheel" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=30 align="center" color="#45bdb0" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-079d3cbf-8b15-4ce2-accb-6ba04481019d',
|
||||
|
@ -667,7 +670,8 @@ export const pitch: CanvasTemplate = {
|
|||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expression: 'filters\n| demodata\n| markdown "##### CATEGORY 01"\n| render',
|
||||
expression:
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "##### CATEGORY 01"\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-0f2b9268-f0bd-41b7-abc8-5593276f26fa',
|
||||
|
@ -680,7 +684,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## Bold title text goes _here_."\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Bold title text goes _here_."\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-4f4b503e-f1ef-4ab7-aa1d-5d95b3e2e605',
|
||||
|
@ -706,7 +710,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-f3f28541-06fe-47ea-89b7-1c5831e28e71',
|
||||
|
@ -719,7 +723,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "Caption text goes here" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="right" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\nfont-size: 18px;\nopacity: .8;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "Caption text goes here" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="right" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\nfont-size: 18px;\nopacity: .8;\n}"',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -768,7 +772,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #45bdb0;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #45bdb0;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-5afa7019-af44-4919-9e11-24e2348cfae9',
|
||||
|
@ -781,7 +785,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## Title for live charts."\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Title for live charts."\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-7b856b52-0d8b-492b-a71f-3508a84388a6',
|
||||
|
@ -820,7 +824,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## _Charts with live data._"\n| render css=".canvasRenderEl h1 {\n\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## _Charts with live data._"\n| render css=".canvasRenderEl h1 {\n\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-317bed0b-f067-4d2d-8cb4-1145f6e0a11c',
|
||||
|
@ -833,7 +837,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-34385617-6eb7-4918-b4db-1a0e8dd6eabe',
|
||||
|
@ -846,7 +850,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-b22a35eb-b177-4664-800e-57b91436a879',
|
||||
|
@ -859,7 +863,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-651f8a4a-6069-49bf-a7b0-484854628a79',
|
||||
|
@ -872,7 +876,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "mean(percent_uptime)"\n| progress shape="horizontalBar" label={formatnumber "0%"} \n font={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=18 align="center" color="#444444" weight="bold" underline=false italic=false} valueColor="#45bdb0" valueWeight=15 barColor="#444444" barWeight=15\n| render css=".canvasRenderEl {\nwidth: 100%;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-0ee8c529-4155-442f-8c7c-1df86be37051',
|
||||
|
@ -885,7 +889,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-3fb61301-3dc2-411f-ac69-ad22bd37c77d',
|
||||
|
@ -898,7 +902,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. \n\nDonec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Cras dapibus urna non feugiat imperdiet. \n\nDonec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -960,7 +964,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #45bdb0;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #45bdb0;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-8b9d3e2b-1d7b-48f4-897c-bf48f0f363d4',
|
||||
|
@ -973,7 +977,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## Title on a _dark_ background."\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Title on a _dark_ background."\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-080c3153-45f7-4efc-8b23-ed7735da426f',
|
||||
|
@ -999,7 +1003,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dapibus urna non feugiat imperdiet. Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus."\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\n}"',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -1021,7 +1025,8 @@ export const pitch: CanvasTemplate = {
|
|||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expression: 'filters\n| demodata\n| markdown "## Bullet point layout style"\n| render',
|
||||
expression:
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Bullet point layout style"\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-37dc903a-1c6d-4452-8fc0-38d4afa4631a',
|
||||
|
@ -1034,7 +1039,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "- Dolor sit amet, consectetur adipiscing elit\n- Cras dapibus urna non feugiat imperdiet\n- Donec vel sollicitudin mauris, ut scelerisque urna\n- Sed vel neque quis metus vulputate luctus\n- Dolor sit amet, consectetur adipiscing elit\n- Cras dapibus urna non feugiat imperdiet\n- Donec vel sollicitudin mauris, ut scelerisque urna\n- Sed vel neque quis metus vulputate luctus"\n| render css=".canvasRenderEl li {\nfont-size: 24px;\nline-height: 30px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "- Dolor sit amet, consectetur adipiscing elit\n- Cras dapibus urna non feugiat imperdiet\n- Donec vel sollicitudin mauris, ut scelerisque urna\n- Sed vel neque quis metus vulputate luctus\n- Dolor sit amet, consectetur adipiscing elit\n- Cras dapibus urna non feugiat imperdiet\n- Donec vel sollicitudin mauris, ut scelerisque urna\n- Sed vel neque quis metus vulputate luctus"\n| render css=".canvasRenderEl li {\nfont-size: 24px;\nline-height: 30px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-e506de9d-bda1-4018-89bf-f8d02ee5738e',
|
||||
|
@ -1047,7 +1052,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Vel sollicitudin mauris, ut scelerisque urna." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="left" color="#000000" weight="normal" underline=false italic=true}\n| render css=".canvasRenderEl p {\nfont-size: 18px;\nopacity: .8;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Vel sollicitudin mauris, ut scelerisque urna." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="left" color="#000000" weight="normal" underline=false italic=true}\n| render css=".canvasRenderEl p {\nfont-size: 18px;\nopacity: .8;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-ea5319f5-d204-48c5-a9a0-0724676869a6',
|
||||
|
@ -1073,7 +1078,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### Subtitle goes here"\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### Subtitle goes here"\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -1095,7 +1100,8 @@ export const pitch: CanvasTemplate = {
|
|||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expression: 'filters\n| demodata\n| markdown "## Paragraph layout style"\n| render',
|
||||
expression:
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Paragraph layout style"\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-92b05ab1-c504-4110-a8ad-73d547136024',
|
||||
|
@ -1108,7 +1114,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Proin ipsum orci, consectetur a lacus vel, varius rutrum neque. Mauris quis gravida tellus. Integer quis tellus non lectus vestibulum fermentum. Quisque tortor justo, vulputate quis mollis eu, molestie eu ex. Nam eu arcu ac dui mattis facilisis aliquam venenatis est. Quisque tempor risus quis arcu viverra, quis consequat dolor molestie. Sed sed arcu dictum, sollicitudin dui id, iaculis elit. Nunc odio ex, placerat sed hendrerit vitae, finibus eu felis. Sed vulputate mi diam, at dictum mi tempus eu.\n\nClass aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus malesuada tortor vel eleifend lobortis. Donec vestibulum neque vel neque vehicula auctor. Proin id felis a leo ultrices maximus."\n| render css=".canvasRenderEl p {\nfont-size: 24px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Proin ipsum orci, consectetur a lacus vel, varius rutrum neque. Mauris quis gravida tellus. Integer quis tellus non lectus vestibulum fermentum. Quisque tortor justo, vulputate quis mollis eu, molestie eu ex. Nam eu arcu ac dui mattis facilisis aliquam venenatis est. Quisque tempor risus quis arcu viverra, quis consequat dolor molestie. Sed sed arcu dictum, sollicitudin dui id, iaculis elit. Nunc odio ex, placerat sed hendrerit vitae, finibus eu felis. Sed vulputate mi diam, at dictum mi tempus eu.\n\nClass aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus malesuada tortor vel eleifend lobortis. Donec vestibulum neque vel neque vehicula auctor. Proin id felis a leo ultrices maximus."\n| render css=".canvasRenderEl p {\nfont-size: 24px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-e49141ec-3034-4bec-88ca-f9606d12a60a',
|
||||
|
@ -1134,7 +1140,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### Subtitle goes here"\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### Subtitle goes here"\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -1170,7 +1176,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## Title text can also go _here_ on multiple lines." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## Title text can also go _here_ on multiple lines." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-a8e0d4b3-864d-4dae-b0dc-64caad06c106',
|
||||
|
@ -1196,7 +1202,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Vivamus malesuada tortor vel eleifend lobortis. Donec vestibulum neque vel neque vehicula auctor. Proin id felis a leo ultrices maximus. Nam est nulla, venenatis at mi et, sodales convallis eros. Aliquam a convallis justo, eu viverra augue. Donec mollis ipsum sed orci posuere, vel posuere neque tempus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Vivamus malesuada tortor vel eleifend lobortis. Donec vestibulum neque vel neque vehicula auctor. Proin id felis a leo ultrices maximus. Nam est nulla, venenatis at mi et, sodales convallis eros. Aliquam a convallis justo, eu viverra augue. Donec mollis ipsum sed orci posuere, vel posuere neque tempus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-b54e2908-6908-4dd6-90f1-3ca489807016',
|
||||
|
@ -1222,7 +1228,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Vivamus malesuada tortor vel eleifend lobortis. Donec vestibulum neque vel neque vehicula auctor. Proin id felis a leo ultrices maximus. Nam est nulla, venenatis at mi et, sodales convallis eros. Aliquam a convallis justo, eu viverra augue. Donec mollis ipsum sed orci posuere, vel posuere neque tempus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Vivamus malesuada tortor vel eleifend lobortis. Donec vestibulum neque vel neque vehicula auctor. Proin id felis a leo ultrices maximus. Nam est nulla, venenatis at mi et, sodales convallis eros. Aliquam a convallis justo, eu viverra augue. Donec mollis ipsum sed orci posuere, vel posuere neque tempus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-aa54f47c-fecf-4bdb-ac1d-b815d4a8d71d',
|
||||
|
@ -1235,7 +1241,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## This title is a _centered_ layout." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## This title is a _centered_ layout." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h2 {\ncolor: #FFFFFF;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-6ae072e7-213c-4de9-af22-7fb3e254cf52',
|
||||
|
@ -1284,7 +1290,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "## \\"Aliquam mollis auctor nisl vitae varius. Donec nunc turpis, condimentum non sagittis tristique, sollicitudin blandit sem.\\"" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=true}\n| render',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "## \\"Aliquam mollis auctor nisl vitae varius. Donec nunc turpis, condimentum non sagittis tristique, sollicitudin blandit sem.\\"" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=true}\n| render',
|
||||
},
|
||||
{
|
||||
id: 'element-989daff8-3571-4e02-b5fc-26657b2d9aaf',
|
||||
|
@ -1310,7 +1316,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### Lorem Ipsum" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### Lorem Ipsum" \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-cf931bd0-e3b6-4ae3-9164-8fe9ba14873d',
|
||||
|
@ -1372,7 +1378,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #FFFFFF;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "##### CATEGORY 01"\n| render css=".canvasRenderEl h5 {\ncolor: #FFFFFF;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-dc4336d5-9752-421f-8196-9f4a6f8150f0',
|
||||
|
@ -1385,7 +1391,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: 'group-1303d0b2-057a-40bf-a0ff-4907b00a285c',
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=18 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\nfont-size: 18px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=18 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\nfont-size: 18px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-b8325cb3-2856-4fd6-8c5a-cba2430dda3e',
|
||||
|
@ -1411,7 +1417,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: 'group-1303d0b2-057a-40bf-a0ff-4907b00a285c',
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "unique(project)"\n| metric "Projects" \n metricFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=72 align="center" color="#45bdb0" weight="bold" underline=false italic=false} \n labelFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=24 align="center" color="#45bdb0" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl .canvasMetric__metric {\nmargin-bottom: 32px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "unique(project)"\n| metric "Projects" \n metricFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=72 align="center" color="#45bdb0" weight="bold" underline=false italic=false} \n labelFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=24 align="center" color="#45bdb0" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl .canvasMetric__metric {\nmargin-bottom: 32px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-07f73884-13e9-4a75-8a23-4eb137e75817',
|
||||
|
@ -1424,7 +1430,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Vel sollicitudin mauris, ut scelerisque urna." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#FFFFFF" weight="normal" underline=false italic=true}\n| render css=".canvasRenderEl p {\nfont-size: 16px;\nopacity: .7;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Vel sollicitudin mauris, ut scelerisque urna." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=14 align="center" color="#FFFFFF" weight="normal" underline=false italic=true}\n| render css=".canvasRenderEl p {\nfont-size: 16px;\nopacity: .7;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-201b8f78-045e-4457-9ada-5166965e64cf',
|
||||
|
@ -1437,7 +1443,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: 'group-1303d0b2-057a-40bf-a0ff-4907b00a285c',
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=18 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\nfont-size: 18px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=18 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\nfont-size: 18px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-9b667060-18ba-4f4d-84a2-48adff57efac',
|
||||
|
@ -1450,7 +1456,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: 'group-1303d0b2-057a-40bf-a0ff-4907b00a285c',
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "unique(country)"\n| metric "Countries" \n metricFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=72 align="center" color="#45bdb0" weight="bold" underline=false italic=false} \n labelFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=24 align="center" color="#45bdb0" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl .canvasMetric__metric {\nmargin-bottom: 32px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "unique(country)"\n| metric "Countries" \n metricFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=72 align="center" color="#45bdb0" weight="bold" underline=false italic=false} \n labelFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=24 align="center" color="#45bdb0" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl .canvasMetric__metric {\nmargin-bottom: 32px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-23fcecca-1f6a-44f6-b441-0f65e03d8210',
|
||||
|
@ -1463,7 +1469,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: 'group-1303d0b2-057a-40bf-a0ff-4907b00a285c',
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| math "unique(username)"\n| metric "Customers" \n metricFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=72 align="center" color="#45bdb0" weight="bold" underline=false italic=false} \n labelFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=24 align="center" color="#45bdb0" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl .canvasMetric__metric {\nmargin-bottom: 32px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| math "unique(username)"\n| metric "Customers" \n metricFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=72 align="center" color="#45bdb0" weight="bold" underline=false italic=false} \n labelFont={font family="Futura, Impact, Helvetica, Arial, sans-serif" size=24 align="center" color="#45bdb0" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl .canvasMetric__metric {\nmargin-bottom: 32px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-19f1db84-7a46-4ccb-a6b9-afd6ddd68523',
|
||||
|
@ -1476,7 +1482,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: 'group-1303d0b2-057a-40bf-a0ff-4907b00a285c',
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=18 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\nfont-size: 18px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown \n "Donec vel sollicitudin mauris, ut scelerisque urna. Sed vel neque quis metus vulputate luctus." \n font={font family="\'Open Sans\', Helvetica, Arial, sans-serif" size=18 align="center" color="#000000" weight="normal" underline=false italic=false}\n| render css=".canvasRenderEl p {\ncolor: #FFFFFF;\nopacity: .8;\nfont-size: 18px;\n}"',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
@ -1499,7 +1505,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "## An alternative opening title slide."\n| render css=".canvasRenderEl h2 {\nfont-size: 64px;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "## An alternative opening title slide."\n| render css=".canvasRenderEl h2 {\nfont-size: 64px;\n}"',
|
||||
},
|
||||
{
|
||||
id: 'element-433586c1-4d44-40cf-988e-cf51871248fb',
|
||||
|
@ -1525,7 +1531,7 @@ export const pitch: CanvasTemplate = {
|
|||
parent: null,
|
||||
},
|
||||
expression:
|
||||
'filters\n| demodata\n| markdown "### Subtitle goes here"\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
'kibana\n| selectFilter\n| demodata\n| markdown "### Subtitle goes here"\n| render css=".canvasRenderEl h3 {\ncolor: #45bdb0;\ntext-transform: none;\n}"',
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue