Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Canvas] Expression repeat image #104255

Merged
merged 13 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"devTools": "src/plugins/dev_tools",
"expressions": "src/plugins/expressions",
"expressionError": "src/plugins/expression_error",
"expressionRepeatImage": "src/plugins/expression_repeat_image",
"expressionRevealImage": "src/plugins/expression_reveal_image",
"expressionShape": "src/plugins/expression_shape",
"inputControl": "src/plugins/input_control_vis",
Expand Down
4 changes: 4 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,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_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.
|{kib-repo}blob/{branch}/src/plugins/expression_reveal_image/README.md[expressionRevealImage]
|Expression Reveal Image plugin adds a revealImage function to the expression plugin and an associated renderer. The renderer will display the given percentage of a given image.
Expand Down
3 changes: 2 additions & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,6 @@ pageLoadAssetSize:
expressionRevealImage: 25675
cases: 144442
expressionError: 22127
userSetup: 18532
expressionRepeatImage: 22341
expressionShape: 30033
userSetup: 18532
1 change: 1 addition & 0 deletions src/dev/storybook/aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_repeat_image: 'src/plugins/expression_repeat_image/.storybook',
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
expression_shape: 'src/plugins/expression_shape/.storybook',
infra: 'x-pack/plugins/infra/.storybook',
Expand Down
12 changes: 6 additions & 6 deletions src/plugins/expression_error/public/components/error/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import { ShowDebugging } from './show_debugging';

export interface Props {
payload: {
error: Error;
};
}

const strings = {
getDescription: () =>
i18n.translate('expressionError.errorComponent.description', {
Expand All @@ -23,12 +29,6 @@ const strings = {
}),
};

export interface Props {
payload: {
error: Error;
};
}

export const Error: FC<Props> = ({ payload }) => {
const message = get(payload, 'error.message');

Expand Down
10 changes: 10 additions & 0 deletions src/plugins/expression_repeat_image/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 9 additions & 0 deletions src/plugins/expression_repeat_image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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.

---

## Development

See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.
14 changes: 14 additions & 0 deletions src/plugins/expression_repeat_image/common/constants.ts
Original file line number Diff line number Diff line change
@@ -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 = 'expressionRepeatImage';
export const PLUGIN_NAME = 'expressionRepeatImage';

export const CONTEXT = '_context_';
export const BASE64 = '`base64`';
export const URL = 'URL';
Original file line number Diff line number Diff line change
@@ -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 { repeatImageFunction } from './repeat_image_function';

export const functions = [repeatImageFunction];

export { repeatImageFunction };
Original file line number Diff line number Diff line change
@@ -1,76 +1,79 @@
/*
* 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 { ExecutionContext } from 'src/plugins/expressions';
import {
getElasticLogo,
getElasticOutline,
functionWrapper,
} from '../../../../../../src/plugins/presentation_util/common/lib';
import { repeatImage } from './repeat_image';
} from '../../../presentation_util/common/lib';
import { repeatImageFunction } from './repeat_image_function';

describe('repeatImage', () => {
const fn = functionWrapper(repeatImage);
const fn = functionWrapper(repeatImageFunction);

let elasticLogo;
let elasticOutline;
let elasticLogo: string;
let elasticOutline: string;
beforeEach(async () => {
elasticLogo = await (await getElasticLogo()).elasticLogo;
elasticOutline = await (await getElasticOutline()).elasticOutline;
});

it('returns a render as repeatImage', async () => {
const result = await fn(10);
const result = await fn(10, {}, {} as ExecutionContext);
expect(result).toHaveProperty('type', 'render');
expect(result).toHaveProperty('as', 'repeatImage');
});

describe('args', () => {
describe('image', () => {
it('sets the source of the repeated image', async () => {
const result = (await fn(10, { image: elasticLogo })).value;
const result = (await fn(10, { image: elasticLogo }, {} as ExecutionContext)).value;
expect(result).toHaveProperty('image', elasticLogo);
});

it('defaults to the Elastic outline logo', async () => {
const result = (await fn(100000)).value;
const result = (await fn(100000, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('image', elasticOutline);
});
});

describe('size', () => {
it('sets the size of the image', async () => {
const result = (await fn(-5, { size: 200 })).value;
const result = (await fn(-5, { size: 200 }, {} as ExecutionContext)).value;
expect(result).toHaveProperty('size', 200);
});

it('defaults to 100', async () => {
const result = (await fn(-5)).value;
const result = (await fn(-5, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('size', 100);
});
});

describe('max', () => {
it('sets the maximum number of a times the image is repeated', async () => {
const result = (await fn(100000, { max: 20 })).value;
const result = (await fn(100000, { max: 20 }, {} as ExecutionContext)).value;
expect(result).toHaveProperty('max', 20);
});
it('defaults to 1000', async () => {
const result = (await fn(100000)).value;
const result = (await fn(100000, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('max', 1000);
});
});

describe('emptyImage', () => {
it('returns repeatImage object with emptyImage as undefined', async () => {
const result = (await fn(100000, { emptyImage: elasticLogo })).value;
const result = (await fn(100000, { emptyImage: elasticLogo }, {} as ExecutionContext))
.value;
expect(result).toHaveProperty('emptyImage', elasticLogo);
});
it('sets emptyImage to null', async () => {
const result = (await fn(100000)).value;
const result = (await fn(100000, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('emptyImage', null);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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 {
getElasticOutline,
isValidUrl,
resolveWithMissingImage,
} from '../../../presentation_util/common/lib';
import { CONTEXT, BASE64, URL } from '../constants';
import { ExpressionRepeatImageFunction } from '../types';

export const strings = {
help: i18n.translate('expressionRepeatImage.functions.repeatImageHelpText', {
defaultMessage: 'Configures a repeating image element.',
}),
args: {
emptyImage: i18n.translate(
'expressionRepeatImage.functions.repeatImage.args.emptyImageHelpText',
{
defaultMessage:
'Fills the difference between the {CONTEXT} and {maxArg} parameter for the element with this image. ' +
'Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
CONTEXT,
maxArg: '`max`',
URL,
},
}
),
image: i18n.translate('expressionRepeatImage.functions.repeatImage.args.imageHelpText', {
defaultMessage:
'The image to repeat. Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
URL,
},
}),
max: i18n.translate('expressionRepeatImage.functions.repeatImage.args.maxHelpText', {
defaultMessage: 'The maximum number of times the image can repeat.',
}),
size: i18n.translate('expressionRepeatImage.functions.repeatImage.args.sizeHelpText', {
defaultMessage:
'The maximum height or width of the image, in pixels. ' +
'When the image is taller than it is wide, this function limits the height.',
}),
},
};

const errors = {
getMissingMaxArgumentErrorMessage: () =>
i18n.translate('expressionRepeatImage.error.repeatImage.missingMaxArgument', {
defaultMessage: '{maxArgument} must be set if providing an {emptyImageArgument}',
values: {
maxArgument: '`max`',
emptyImageArgument: '`emptyImage`',
},
}),
};

export const repeatImageFunction: ExpressionRepeatImageFunction = () => {
const { help, args: argHelp } = strings;

return {
name: 'repeatImage',
aliases: [],
type: 'render',
inputTypes: ['number'],
help,
args: {
emptyImage: {
types: ['string', 'null'],
help: argHelp.emptyImage,
default: null,
},
image: {
types: ['string', 'null'],
help: argHelp.image,
default: null,
},
max: {
types: ['number', 'null'],
help: argHelp.max,
default: 1000,
},
size: {
types: ['number'],
default: 100,
help: argHelp.size,
},
},
fn: async (count, args) => {
if (args.emptyImage !== null && isValidUrl(args.emptyImage) && args.max === null) {
throw new Error(errors.getMissingMaxArgumentErrorMessage());
}
const { elasticOutline } = await getElasticOutline();
return {
type: 'render',
as: 'repeatImage',
value: {
count: Math.floor(count),
...args,
image: resolveWithMissingImage(args.image, elasticOutline),
emptyImage: resolveWithMissingImage(args.emptyImage),
},
};
},
};
};
11 changes: 11 additions & 0 deletions src/plugins/expression_repeat_image/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export * from './constants';
export * from './types';
export * from './expression_functions';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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, ExpressionValueRender } from '../../../expressions';

interface Arguments {
image: string | null;
size: number;
max: number | null;
emptyImage: string | null;
}

export interface Return {
count: number;
image: string;
size: number;
max: number;
emptyImage: string | null;
}

export type ExpressionRepeatImageFunction = () => ExpressionFunctionDefinition<
'repeatImage',
number,
Arguments,
Promise<ExpressionValueRender<Arguments>>
>;
Original file line number Diff line number Diff line change
@@ -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.
*/

export type OriginString = 'bottom' | 'left' | 'top' | 'right';
export interface RepeatImageRendererConfig {
max: number;
count: number;
emptyImage: string;
image: string;
size: number;
}

export interface NodeDimensions {
width: number;
height: number;
}
9 changes: 9 additions & 0 deletions src/plugins/expression_repeat_image/common/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './expression_functions';
export * from './expression_renderers';
Loading