[Canvas] Expression image (#104318) (#106683)

* Added `expression_image` plugin.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
# Conflicts:
#	x-pack/plugins/translations/translations/ja-JP.json
#	x-pack/plugins/translations/translations/zh-CN.json
This commit is contained in:
Yaroslav Kuznietsov 2021-07-26 16:45:09 +03:00 committed by GitHub
parent 9440dc0ccd
commit daa98ee0c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 522 additions and 262 deletions

View file

@ -18,6 +18,7 @@
"devTools": "src/plugins/dev_tools",
"expressions": "src/plugins/expressions",
"expressionError": "src/plugins/expression_error",
"expressionImage": "src/plugins/expression_image",
"expressionRepeatImage": "src/plugins/expression_repeat_image",
"expressionRevealImage": "src/plugins/expression_reveal_image",
"expressionShape": "src/plugins/expression_shape",

View file

@ -74,6 +74,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a
|Expression Error plugin adds an error renderer to the expression plugin. The renderer will display the error image.
|{kib-repo}blob/{branch}/src/plugins/expression_image/README.md[expressionImage]
|Expression Image plugin adds an image renderer to the expression plugin. The renderer will display the given image.
|{kib-repo}blob/{branch}/src/plugins/expression_repeat_image/README.md[expressionRepeatImage]
|Expression Repeat Image plugin adds a repeatImage function to the expression plugin and an associated renderer. The renderer will display the given image in mutliple instances.

View file

@ -114,4 +114,5 @@ pageLoadAssetSize:
cases: 144442
expressionError: 22127
expressionRepeatImage: 22341
expressionImage: 19288
expressionShape: 30033

View file

@ -18,6 +18,7 @@ export const storybookAliases = {
data_enhanced: 'x-pack/plugins/data_enhanced/.storybook',
embeddable: 'src/plugins/embeddable/.storybook',
expression_error: 'src/plugins/expression_error/.storybook',
expression_image: 'src/plugins/expression_image/.storybook',
expression_repeat_image: 'src/plugins/expression_repeat_image/.storybook',
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
expression_shape: 'src/plugins/expression_shape/.storybook',

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
// eslint-disable-next-line import/no-commonjs
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -0,0 +1,9 @@
# expressionRevealImage
Expression Image plugin adds an `image` renderer to the expression plugin. The renderer will display the given image.
---
## Development
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.

View 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { imageFunction } from '../common/expression_functions';

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const PLUGIN_ID = 'expressionImage';
export const PLUGIN_NAME = 'expressionImage';
export const CONTEXT = '_context_';
export const BASE64 = '`base64`';
export const URL = 'URL';

View file

@ -1,72 +1,78 @@
/*
* 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.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { ExecutionContext } from 'src/plugins/expressions';
import {
functionWrapper,
getElasticLogo,
getElasticOutline,
functionWrapper,
} from '../../../../../../src/plugins/presentation_util/common/lib';
import { image } from './image';
} from '../../../presentation_util/common/lib';
import { imageFunction as image } from './image_function';
// TODO: the test was not running and is not up to date
describe('image', () => {
const fn = functionWrapper(image);
let elasticLogo;
let elasticOutline;
let elasticLogo: string;
let elasticOutline: string;
beforeEach(async () => {
elasticLogo = (await getElasticLogo()).elasticLogo;
elasticOutline = (await getElasticOutline()).elasticOutline;
elasticLogo = (await getElasticLogo())?.elasticLogo;
elasticOutline = (await getElasticOutline())?.elasticOutline;
});
it('returns an image object using a dataUrl', async () => {
const result = await fn(null, { dataurl: elasticOutline, mode: 'cover' });
const result = await fn(
null,
{ dataurl: elasticOutline, mode: 'cover' },
{} as ExecutionContext
);
expect(result).to.have.property('type', 'image');
});
describe('args', () => {
describe('dataurl', () => {
it('sets the source of the image using dataurl', async () => {
const result = await fn(null, { dataurl: elasticOutline });
const result = await fn(null, { dataurl: elasticOutline }, {} as ExecutionContext);
expect(result).to.have.property('dataurl', elasticOutline);
});
it.skip('sets the source of the image using url', async () => {
// This is skipped because functionWrapper doesn't use the actual
// interpreter and doesn't resolve aliases
const result = await fn(null, { url: elasticOutline });
const result = await fn(null, { url: elasticOutline }, {} as ExecutionContext);
expect(result).to.have.property('dataurl', elasticOutline);
});
it('defaults to the elasticLogo if not provided', async () => {
const result = await fn(null);
const result = await fn(null, {}, {} as ExecutionContext);
expect(result).to.have.property('dataurl', elasticLogo);
});
});
describe('sets the mode', () => {
it('to contain', async () => {
const result = await fn(null, { mode: 'contain' });
const result = await fn(null, { mode: 'contain' }, {} as ExecutionContext);
expect(result).to.have.property('mode', 'contain');
});
it('to cover', async () => {
const result = await fn(null, { mode: 'cover' });
const result = await fn(null, { mode: 'cover' }, {} as ExecutionContext);
expect(result).to.have.property('mode', 'cover');
});
it('to stretch', async () => {
const result = await fn(null, { mode: 'stretch' });
const result = await fn(null, { mode: 'stretch' }, {} as ExecutionContext);
expect(result).to.have.property('mode', '100% 100%');
});
it("defaults to 'contain' if not provided", async () => {
const result = await fn(null);
const result = await fn(null, {}, {} as ExecutionContext);
expect(result).to.have.property('mode', 'contain');
});
});

View file

@ -0,0 +1,96 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { getElasticLogo, resolveWithMissingImage } from '../../../presentation_util/common/lib';
import { BASE64, URL } from '../constants';
import { ExpressionImageFunction, ImageMode } from '../types';
export const strings = {
help: i18n.translate('expressionImage.functions.imageHelpText', {
defaultMessage:
'Displays an image. Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
URL,
},
}),
args: {
dataurl: i18n.translate('expressionImage.functions.image.args.dataurlHelpText', {
defaultMessage: 'The {https} {URL} or {BASE64} data {URL} of an image.',
values: {
BASE64,
https: 'HTTP(S)',
URL,
},
}),
mode: i18n.translate('expressionImage.functions.image.args.modeHelpText', {
defaultMessage:
'{contain} shows the entire image, scaled to fit. ' +
'{cover} fills the container with the image, cropping from the sides or bottom as needed. ' +
'{stretch} resizes the height and width of the image to 100% of the container.',
values: {
contain: `\`"${ImageMode.CONTAIN}"\``,
cover: `\`"${ImageMode.COVER}"\``,
stretch: `\`"${ImageMode.STRETCH}"\``,
},
}),
},
};
const errors = {
invalidImageMode: () =>
i18n.translate('expressionImage.functions.image.invalidImageModeErrorMessage', {
defaultMessage: '"mode" must be "{contain}", "{cover}", or "{stretch}"',
values: {
contain: ImageMode.CONTAIN,
cover: ImageMode.COVER,
stretch: ImageMode.STRETCH,
},
}),
};
export const imageFunction: ExpressionImageFunction = () => {
const { help, args: argHelp } = strings;
return {
name: 'image',
aliases: [],
type: 'image',
inputTypes: ['null'],
help,
args: {
dataurl: {
// This was accepting dataurl, but there was no facility in fn for checking type and handling a dataurl type.
types: ['string', 'null'],
help: argHelp.dataurl,
aliases: ['_', 'url'],
default: null,
},
mode: {
types: ['string'],
help: argHelp.mode,
default: 'contain',
options: Object.values(ImageMode),
},
},
fn: async (input, { dataurl, mode }) => {
if (!mode || !Object.values(ImageMode).includes(mode)) {
throw new Error(errors.invalidImageMode());
}
const modeStyle = mode === 'stretch' ? '100% 100%' : mode;
const { elasticLogo } = await getElasticLogo();
return {
type: 'image',
mode: modeStyle,
dataurl: resolveWithMissingImage(dataurl, elasticLogo) as string,
};
},
};
};

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { imageFunction } from './image_function';
export const functions = [imageFunction];
export { imageFunction };

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './constants';
export * from './types';

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExpressionFunctionDefinition } from '../../../expressions';
export enum ImageMode {
CONTAIN = 'contain',
COVER = 'cover',
STRETCH = 'stretch',
}
interface Arguments {
dataurl: string | null;
mode: ImageMode | null;
}
export interface Return {
type: 'image';
mode: string;
dataurl: string;
}
export type ExpressionImageFunction = () => ExpressionFunctionDefinition<
'image',
null,
Arguments,
Promise<Return>
>;

View file

@ -0,0 +1,21 @@
/*
* 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 { ImageMode } from './expression_functions';
export type OriginString = 'bottom' | 'left' | 'top' | 'right';
export interface ImageRendererConfig {
dataurl: string | null;
mode: ImageMode | null;
}
export interface NodeDimensions {
width: number;
height: number;
}

View 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './expression_functions';
export * from './expression_renderers';

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/src/plugins/expression_image'],
};

View file

@ -0,0 +1,9 @@
{
"id": "expressionImage",
"version": "1.0.0",
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["expressions", "presentationUtil"],
"optionalPlugins": []
}

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { storiesOf } from '@storybook/react';
import { Render, waitFor } from '../../../../presentation_util/public/__stories__';
import { imageRenderer } from '../image_renderer';
import { getElasticLogo } from '../../../../../../src/plugins/presentation_util/common/lib';
import { ImageMode } from '../../../common';
const Renderer = ({ elasticLogo }: { elasticLogo: string }) => {
const config = {
dataurl: elasticLogo,
mode: ImageMode.COVER,
};
return <Render renderer={imageRenderer} config={config} width="500px" height="500px" />;
};
storiesOf('renderers/image', module).add(
'default',
(_, props) => {
return <Renderer elasticLogo={props?.elasticLogo} />;
},
{ decorators: [waitFor(getElasticLogo())] }
);

View file

@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { i18n } from '@kbn/i18n';
import { getElasticLogo, isValidUrl } from '../../../presentation_util/public';
import { ImageRendererConfig } from '../../common/types';
const strings = {
getDisplayName: () =>
i18n.translate('expressionImage.renderer.image.displayName', {
defaultMessage: 'Image',
}),
getHelpDescription: () =>
i18n.translate('expressionImage.renderer.image.helpDescription', {
defaultMessage: 'Render an image',
}),
};
export const imageRenderer = (): ExpressionRenderDefinition<ImageRendererConfig> => ({
name: 'image',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render: async (
domNode: HTMLElement,
config: ImageRendererConfig,
handlers: IInterpreterRenderHandlers
) => {
const { elasticLogo } = await getElasticLogo();
const dataurl = isValidUrl(config.dataurl ?? '') ? config.dataurl : elasticLogo;
const style = {
height: '100%',
backgroundImage: `url(${dataurl})`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center center',
backgroundSize: config.mode as string,
};
handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});
render(<div style={style} />, domNode, () => handlers.done());
},
});

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { imageRenderer } from './image_renderer';
export const renderers = [imageRenderer];
export { imageRenderer };

View file

@ -0,0 +1,17 @@
/*
* 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 { ExpressionImagePlugin } from './plugin';
export type { ExpressionImagePluginSetup, ExpressionImagePluginStart } from './plugin';
export function plugin() {
return new ExpressionImagePlugin();
}
export * from './expression_renderers';

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public';
import { imageRenderer } from './expression_renderers';
import { imageFunction } from '../common/expression_functions';
interface SetupDeps {
expressions: ExpressionsSetup;
}
interface StartDeps {
expression: ExpressionsStart;
}
export type ExpressionImagePluginSetup = void;
export type ExpressionImagePluginStart = void;
export class ExpressionImagePlugin
implements Plugin<ExpressionImagePluginSetup, ExpressionImagePluginStart, SetupDeps, StartDeps> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionImagePluginSetup {
expressions.registerFunction(imageFunction);
expressions.registerRenderer(imageRenderer);
}
public start(core: CoreStart): ExpressionImagePluginStart {}
public stop() {}
}

View file

@ -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 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 { ExpressionImagePlugin } from './plugin';
export type { ExpressionImagePluginSetup, ExpressionImagePluginStart } from './plugin';
export function plugin() {
return new ExpressionImagePlugin();
}

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server';
import { imageFunction } from '../common/expression_functions';
interface SetupDeps {
expressions: ExpressionsServerSetup;
}
interface StartDeps {
expression: ExpressionsServerStart;
}
export type ExpressionImagePluginSetup = void;
export type ExpressionImagePluginStart = void;
export class ExpressionImagePlugin
implements Plugin<ExpressionImagePluginSetup, ExpressionImagePluginStart, SetupDeps, StartDeps> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionImagePluginSetup {
expressions.registerFunction(imageFunction);
}
public start(core: CoreStart): ExpressionImagePluginStart {}
public stop() {}
}

View file

@ -0,0 +1,22 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true,
"isolatedModules": true
},
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
"__fixtures__/**/*",
],
"references": [
{ "path": "../../core/tsconfig.json" },
{ "path": "../presentation_util/tsconfig.json" },
{ "path": "../expressions/tsconfig.json" },
]
}

View file

@ -1,75 +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 { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { getFunctionHelp, getFunctionErrors } from '../../../i18n';
import {
getElasticLogo,
resolveWithMissingImage,
} from '../../../../../../src/plugins/presentation_util/common/lib';
export enum ImageMode {
CONTAIN = 'contain',
COVER = 'cover',
STRETCH = 'stretch',
}
interface Arguments {
dataurl: string | null;
mode: ImageMode | null;
}
export interface Return {
type: 'image';
mode: string;
dataurl: string;
}
export function image(): ExpressionFunctionDefinition<'image', null, Arguments, Promise<Return>> {
const { help, args: argHelp } = getFunctionHelp().image;
const errors = getFunctionErrors().image;
return {
name: 'image',
aliases: [],
type: 'image',
inputTypes: ['null'],
help,
args: {
dataurl: {
// This was accepting dataurl, but there was no facility in fn for checking type and handling a dataurl type.
types: ['string', 'null'],
help: argHelp.dataurl,
aliases: ['_', 'url'],
default: null,
},
mode: {
types: ['string'],
help: argHelp.mode,
default: 'contain',
options: Object.values(ImageMode),
},
},
fn: async (input, { dataurl, mode }) => {
if (!mode || !Object.values(ImageMode).includes(mode)) {
throw errors.invalidImageMode();
}
const { elasticLogo } = await getElasticLogo();
if (dataurl === null) {
dataurl = elasticLogo;
}
const modeStyle = mode === 'stretch' ? '100% 100%' : mode;
return {
type: 'image',
mode: modeStyle,
dataurl: resolveWithMissingImage(dataurl, elasticLogo) as string,
};
},
};
}

View file

@ -29,7 +29,6 @@ import { gt } from './gt';
import { gte } from './gte';
import { head } from './head';
import { ifFn } from './if';
import { image } from './image';
import { joinRows } from './join_rows';
import { lt } from './lt';
import { lte } from './lte';
@ -79,7 +78,6 @@ export const functions = [
gte,
head,
ifFn,
image,
lt,
lte,
joinRows,

View file

@ -1,29 +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 React from 'react';
import { storiesOf } from '@storybook/react';
import { image } from '../image';
import { getElasticLogo } from '../../../../../../src/plugins/presentation_util/common/lib';
import { waitFor } from '../../../../../../src/plugins/presentation_util/public/__stories__';
import { Render } from './render';
const Renderer = ({ elasticLogo }: { elasticLogo: string }) => {
const config = {
type: 'image' as 'image',
mode: 'cover',
dataurl: elasticLogo,
};
return <Render renderer={image} config={config} width="400px" />;
};
storiesOf('renderers/image', module).add(
'default',
(_, props) => <Renderer elasticLogo={props?.elasticLogo} />,
{ decorators: [waitFor(getElasticLogo())] }
);

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { image } from './image';
import { markdown } from './markdown';
import { metric } from './metric';
import { pie } from './pie';
@ -14,6 +13,6 @@ import { progress } from './progress';
import { text } from './text';
import { table } from './table';
export const renderFunctions = [image, markdown, metric, pie, plot, progress, table, text];
export const renderFunctions = [markdown, metric, pie, plot, progress, table, text];
export const renderFunctionFactories = [];

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { imageRenderer } from '../../../../../src/plugins/expression_image/public';
import { errorRenderer, debugRenderer } from '../../../../../src/plugins/expression_error/public';
import { repeatImageRenderer } from '../../../../../src/plugins/expression_repeat_image/public';
import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public';
@ -14,6 +15,7 @@ export const renderFunctions = [
revealImageRenderer,
debugRenderer,
errorRenderer,
imageRenderer,
shapeRenderer,
repeatImageRenderer,
];

View file

@ -1,41 +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 ReactDOM from 'react-dom';
import React from 'react';
import {
getElasticLogo,
isValidUrl,
} from '../../../../../src/plugins/presentation_util/common/lib';
import { Return as Arguments } from '../functions/common/image';
import { RendererStrings } from '../../i18n';
import { RendererFactory } from '../../types';
const { image: strings } = RendererStrings;
export const image: RendererFactory<Arguments> = () => ({
name: 'image',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render: async (domNode, config, handlers) => {
const { elasticLogo } = await getElasticLogo();
const dataurl = isValidUrl(config.dataurl) ? config.dataurl : elasticLogo;
const style = {
height: '100%',
backgroundImage: `url(${dataurl})`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center center',
backgroundSize: config.mode,
};
ReactDOM.render(<div style={style} />, domNode, () => handlers.done());
handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode));
},
});

View file

@ -1,64 +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 { i18n } from '@kbn/i18n';
import { image } from '../../../canvas_plugin_src/functions/common/image';
import { FunctionHelp } from '../function_help';
import { FunctionFactory } from '../../../types';
import {
URL,
BASE64,
IMAGE_MODE_CONTAIN,
IMAGE_MODE_COVER,
IMAGE_MODE_STRETCH,
} from '../../constants';
export const help: FunctionHelp<FunctionFactory<typeof image>> = {
help: i18n.translate('xpack.canvas.functions.imageHelpText', {
defaultMessage:
'Displays an image. Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
URL,
},
}),
args: {
dataurl: i18n.translate('xpack.canvas.functions.image.args.dataurlHelpText', {
defaultMessage: 'The {https} {URL} or {BASE64} data {URL} of an image.',
values: {
BASE64,
https: 'HTTP(S)',
URL,
},
}),
mode: i18n.translate('xpack.canvas.functions.image.args.modeHelpText', {
defaultMessage:
'{contain} shows the entire image, scaled to fit. ' +
'{cover} fills the container with the image, cropping from the sides or bottom as needed. ' +
'{stretch} resizes the height and width of the image to 100% of the container.',
values: {
contain: `\`"${IMAGE_MODE_CONTAIN}"\``,
cover: `\`"${IMAGE_MODE_COVER}"\``,
stretch: `\`"${IMAGE_MODE_STRETCH}"\``,
},
}),
},
};
export const errors = {
invalidImageMode: () =>
new Error(
i18n.translate('xpack.canvas.functions.image.invalidImageModeErrorMessage', {
defaultMessage: '"mode" must be "{contain}", "{cover}", or "{stretch}"',
values: {
contain: IMAGE_MODE_CONTAIN,
cover: IMAGE_MODE_COVER,
stretch: IMAGE_MODE_STRETCH,
},
})
),
};

View file

@ -14,7 +14,6 @@ import { errors as csv } from './dict/csv';
import { errors as date } from './dict/date';
import { errors as demodata } from './dict/demodata';
import { errors as getCell } from './dict/get_cell';
import { errors as image } from './dict/image';
import { errors as joinRows } from './dict/join_rows';
import { errors as ply } from './dict/ply';
import { errors as pointseries } from './dict/pointseries';
@ -32,7 +31,6 @@ export const getFunctionErrors = () => ({
date,
demodata,
getCell,
image,
joinRows,
ply,
pointseries,

View file

@ -40,7 +40,6 @@ import { help as gt } from './dict/gt';
import { help as gte } from './dict/gte';
import { help as head } from './dict/head';
import { help as ifFn } from './dict/if';
import { help as image } from './dict/image';
import { help as joinRows } from './dict/join_rows';
import { help as location } from './dict/location';
import { help as lt } from './dict/lt';
@ -199,7 +198,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({
head,
if: ifFn,
joinRows,
image,
location,
lt,
lte,

View file

@ -55,16 +55,6 @@ export const RendererStrings = {
defaultMessage: 'Renders an embeddable Saved Object from other parts of Kibana',
}),
},
image: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.image.displayName', {
defaultMessage: 'Image',
}),
getHelpDescription: () =>
i18n.translate('xpack.canvas.renderer.image.helpDescription', {
defaultMessage: 'Render an image',
}),
},
markdown: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.markdown.displayName', {

View file

@ -11,6 +11,7 @@
"data",
"embeddable",
"expressionError",
"expressionImage",
"expressionRepeatImage",
"expressionRevealImage",
"expressionShape",

View file

@ -9,7 +9,7 @@ import { storiesOf } from '@storybook/react';
import React from 'react';
import { ExampleContext } from '../../test/context_example';
import { image } from '../../../canvas_plugin_src/renderers/image';
import { imageFunction } from '../../../../../../src/plugins/expression_image/__fixtures__';
import { sharedWorkpads } from '../../test';
import { RenderedElement, RenderedElementComponent } from '../rendered_element';
@ -30,7 +30,7 @@ storiesOf('shareables/RenderedElement', module)
<ExampleContext style={{ height: 100, width: 100 }}>
<RenderedElementComponent
index={0}
fn={image()}
fn={imageFunction()}
element={{
id: '123',
position: {

View file

@ -8,6 +8,7 @@
import React, { FC, PureComponent } from 'react';
// @ts-expect-error untyped library
import Style from 'style-it';
import { AnyExpressionFunctionDefinition } from '../../../../../src/plugins/expressions';
import { Positionable } from '../../public/components/positionable/positionable';
// @ts-expect-error untyped local
import { elementToShape } from '../../public/components/workpad_page/utils';
@ -34,7 +35,7 @@ export interface Props {
* The Expression function that evaluates the state of the Element and renders
* it to the Page.
*/
fn: AnyRendererSpec;
fn: AnyRendererSpec | ReturnType<AnyExpressionFunctionDefinition['fn']>;
}
/**
@ -64,7 +65,7 @@ export class RenderedElementComponent extends PureComponent<Props> {
try {
fn.render(this.ref.current, value.value, createHandlers());
} catch (e) {
} catch (e: any) {
// eslint-disable-next-line no-console
console.log(as, e.message);
}

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { image } from '../canvas_plugin_src/renderers/image';
import { markdown } from '../canvas_plugin_src/renderers/markdown';
import { metric } from '../canvas_plugin_src/renderers/metric';
import { pie } from '../canvas_plugin_src/renderers/pie';
@ -13,6 +12,7 @@ import { plot } from '../canvas_plugin_src/renderers/plot';
import { progress } from '../canvas_plugin_src/renderers/progress';
import { table } from '../canvas_plugin_src/renderers/table';
import { text } from '../canvas_plugin_src/renderers/text';
import { imageRenderer as image } from '../../../../src/plugins/expression_image/public';
import {
errorRenderer as error,
debugRenderer as debug,

View file

@ -32,6 +32,7 @@
{ "path": "../../../src/plugins/embeddable/tsconfig.json" },
{ "path": "../../../src/plugins/expressions/tsconfig.json" },
{ "path": "../../../src/plugins/expression_error/tsconfig.json" },
{ "path": "../../../src/plugins/expression_image/tsconfig.json" },
{ "path": "../../../src/plugins/expression_repeat_image/tsconfig.json" },
{ "path": "../../../src/plugins/expression_reveal_image/tsconfig.json" },
{ "path": "../../../src/plugins/expression_shape/tsconfig.json" },

View file

@ -6706,10 +6706,10 @@
"xpack.canvas.functions.if.args.elseHelpText": "条件が {BOOLEAN_FALSE} の場合の戻り値です。指定されておらず、条件が満たされていない場合は、元の {CONTEXT} が戻されます。",
"xpack.canvas.functions.if.args.thenHelpText": "条件が {BOOLEAN_TRUE} の場合の戻り値です。指定されておらず、条件が満たされている場合は、元の {CONTEXT} が戻されます。",
"xpack.canvas.functions.ifHelpText": "条件付きロジックを実行します。",
"xpack.canvas.functions.image.args.dataurlHelpText": "画像の {https} {URL} または {BASE64} データ {URL} です。",
"xpack.canvas.functions.image.args.modeHelpText": "{contain} はサイズに合わせて拡大・縮小して画像全体を表示し、{cover} はコンテナーを画像で埋め、必要に応じて両端や下をクロップします。{stretch} は画像の高さと幅をコンテナーの 100% になるよう変更します。",
"xpack.canvas.functions.image.invalidImageModeErrorMessage": "「mode」は「{contain}」、「{cover}」、または「{stretch}」でなければなりません",
"xpack.canvas.functions.imageHelpText": "画像を表示します。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
"expressionImage.functions.image.args.dataurlHelpText": "画像の {https} {URL} または {BASE64} データ {URL} です。",
"expressionImage.functions.image.args.modeHelpText": "{contain} はサイズに合わせて拡大・縮小して画像全体を表示し、{cover} はコンテナーを画像で埋め、必要に応じて両端や下をクロップします。{stretch} は画像の高さと幅をコンテナーの 100% になるよう変更します。",
"expressionImage.functions.image.invalidImageModeErrorMessage": "「mode」は「{contain}」、「{cover}」、または「{stretch}」でなければなりません",
"expressionImage.functions.imageHelpText": "画像を表示します。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
"xpack.canvas.functions.joinRows.args.columnHelpText": "値を抽出する列またはフィールド。",
"xpack.canvas.functions.joinRows.args.distinctHelpText": "一意の値のみを抽出しますか?",
"xpack.canvas.functions.joinRows.args.quoteHelpText": "各抽出された値を囲む引用符文字。",
@ -6957,8 +6957,10 @@
"xpack.canvas.renderer.dropdownFilter.matchAllOptionLabel": "すべて",
"xpack.canvas.renderer.embeddable.displayName": "埋め込み可能",
"xpack.canvas.renderer.embeddable.helpDescription": "Kibana の他の部分から埋め込み可能な保存済みオブジェクトをレンダリングします",
"xpack.canvas.renderer.image.displayName": "画像",
"xpack.canvas.renderer.image.helpDescription": "画像をレンダリングします",
"expressionError.renderer.error.displayName": "エラー情報",
"expressionError.renderer.error.helpDescription": "エラーデータをユーザーにわかるようにレンダリングします",
"expressionImage.renderer.image.displayName": "画像",
"expressionImage.renderer.image.helpDescription": "画像をレンダリングします",
"xpack.canvas.renderer.markdown.displayName": "マークダウン",
"xpack.canvas.renderer.markdown.helpDescription": "{MARKDOWN} インプットを使用して {HTML} を表示",
"xpack.canvas.renderer.metric.displayName": "メトリック",

View file

@ -6747,10 +6747,10 @@
"xpack.canvas.functions.if.args.elseHelpText": "条件为 {BOOLEAN_FALSE} 时的返回值。未指定且条件未满足时,将返回原始 {CONTEXT}。",
"xpack.canvas.functions.if.args.thenHelpText": "条件为 {BOOLEAN_TRUE} 时的返回值。未指定且条件满足时,将返回原始 {CONTEXT}。",
"xpack.canvas.functions.ifHelpText": "执行条件逻辑。",
"xpack.canvas.functions.image.args.dataurlHelpText": "图像的 {https} {URL} 或 {BASE64} 数据 {URL}。",
"xpack.canvas.functions.image.args.modeHelpText": "{contain} 将显示整个图像,图像缩放至适合大小。{cover} 将使用该图像填充容器,根据需要在两边或底部裁剪图像。{stretch} 将图像的高和宽调整为容器的 100%。",
"xpack.canvas.functions.image.invalidImageModeErrorMessage": "“mode”必须为“{contain}”、“{cover}”或“{stretch}”",
"xpack.canvas.functions.imageHelpText": "显示图像。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。",
"expressionImage.functions.image.args.dataurlHelpText": "图像的 {https} {URL} 或 {BASE64} 数据 {URL}。",
"expressionImage.functions.image.args.modeHelpText": "{contain} 将显示整个图像,图像缩放至适合大小。{cover} 将使用该图像填充容器,根据需要在两边或底部裁剪图像。{stretch} 将图像的高和宽调整为容器的 100%。",
"expressionImage.functions.image.invalidImageModeErrorMessage": "“mode”必须为“{contain}”、“{cover}”或“{stretch}”",
"expressionImage.functions.imageHelpText": "显示图像。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。",
"xpack.canvas.functions.joinRows.args.columnHelpText": "从其中提取值的列或字段。",
"xpack.canvas.functions.joinRows.args.distinctHelpText": "仅提取唯一值?",
"xpack.canvas.functions.joinRows.args.quoteHelpText": "要将每个提取的值引起来的引号字符。",
@ -6998,8 +6998,10 @@
"xpack.canvas.renderer.dropdownFilter.matchAllOptionLabel": "任意",
"xpack.canvas.renderer.embeddable.displayName": "可嵌入",
"xpack.canvas.renderer.embeddable.helpDescription": "从 Kibana 的其他部分呈现可嵌入的已保存对象",
"xpack.canvas.renderer.image.displayName": "图像",
"xpack.canvas.renderer.image.helpDescription": "呈现图像",
"expressionError.renderer.error.displayName": "错误信息",
"expressionError.renderer.error.helpDescription": "以用户友好的方式呈现错误数据",
"expressionImage.renderer.image.displayName": "图像",
"expressionImage.renderer.image.helpDescription": "呈现图像",
"xpack.canvas.renderer.markdown.displayName": "Markdown",
"xpack.canvas.renderer.markdown.helpDescription": "使用 {MARKDOWN} 输入呈现 {HTML}",
"xpack.canvas.renderer.metric.displayName": "指标",