mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Lens] Embeddable overrides feature (#153204)
## Summary Fixes #149220 Fixes https://github.com/elastic/kibana/issues/148845 This PR introduces the concept of `Embeddable` overrides within the Lens Embeddable: overrides are a small subset of configuration options that can overwrite Lens original Elastic Chart rendering configuration. This feature will only apply at the Embeddable level and is ignored within the Lens editor context. The readme has been updated with some documentation on the topic. ### Playground changes This PR also contains a refactor/enhancement of the playground example app to showcase the `attributes` and `overrides` for most Lens charts. The UI has been redesigned with 3 main dropdowns with some explanation of the different switch controls: <img width="730" alt="Screenshot 2023-03-16 at 16 01 22" src="https://user-images.githubusercontent.com/924948/225660824-f40446d7-923b-4b0c-af5a-aacf1c3b3b33.png"> <img width="661" alt="Screenshot 2023-03-16 at 16 01 28" src="https://user-images.githubusercontent.com/924948/225660841-82ae8428-d003-4379-beea-5eac70662955.png"> <img width="658" alt="Screenshot 2023-03-16 at 16 01 33" src="https://user-images.githubusercontent.com/924948/225660930-87fce81b-55b2-46fd-bc8b-f4767abc2848.png"> For each override setting an example code snippet is shown on hover: <img width="387" alt="Screenshot 2023-03-20 at 11 16 52" src="https://user-images.githubusercontent.com/924948/226362615-46920754-e519-4584-adfd-b7b9c2b5bde6.png"> <img width="388" alt="Screenshot 2023-03-20 at 11 17 00" src="https://user-images.githubusercontent.com/924948/226362620-8e77cf38-9f2f-48d5-a5eb-f65ad089bc40.png"> The second menu badge shows when overrides are enabled: <img width="659" alt="Screenshot 2023-03-16 at 16 01 44" src="https://user-images.githubusercontent.com/924948/225661114-2aaf0c46-2320-41dc-aad1-8e4a26da0635.png"> Different chart types have different options available: <img width="724" alt="Screenshot 2023-03-16 at 16 04 58" src="https://user-images.githubusercontent.com/924948/225661241-61899a19-64d3-4326-9645-f908c9b35b65.png"> <img width="653" alt="Screenshot 2023-03-16 at 16 07 42" src="https://user-images.githubusercontent.com/924948/225661248-3f6e489e-6a0e-4ac6-a0b9-994ed1572750.png"> <img width="635" alt="Screenshot 2023-03-20 at 14 58 00" src="https://user-images.githubusercontent.com/924948/226362432-76cd8d7d-06de-4a55-a809-9e64e243cd8a.png"> The datatable and metric visualization have no overrides for now. ### Difference with #152842 The two feature work in a similar space, but they are substantially different from their use cases. The `overrides` feature is something to use in 2 scenarios: * small styling/tuning configuration of the final chart via Lens-unsupported Elastic Chart props * For instance having `integersOnly` ticks on a XY axis, or value labels outside only for a pie chart * Selectively turning off specific event handlers on the component * For instance to completely remove any complex logic from a legend item (i.e. filter popup) The `preventDefault` feature is useful instead when the user wants to keep all the handlers at chart level, but selectively disabled some Kibana-wide event from bubble. For instance clicking on a bar or pie slice should trigger the `edit` event but the consumer's custom handler should be the only one to be executed, without bubbling up to the `unifiedSearch` registered triggers. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli <stratoula1@gmail.com> Co-authored-by: Drew Tate <drewctate@gmail.com>
This commit is contained in:
parent
95bc7c0e1c
commit
bd48d13e17
73 changed files with 1793 additions and 302 deletions
|
@ -6,4 +6,5 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { extractContainerType, extractVisualizationType } from './utils';
|
||||
export { extractContainerType, extractVisualizationType, getOverridesFor } from './utils';
|
||||
export type { Simplify, MakeOverridesSerializable } from './types';
|
||||
|
|
28
src/plugins/chart_expressions/common/types.ts
Normal file
28
src/plugins/chart_expressions/common/types.ts
Normal file
|
@ -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 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';
|
||||
|
||||
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
|
||||
|
||||
// Overrides should not expose Functions, React nodes and children props
|
||||
// So filter out any type which is not serializable
|
||||
export type MakeOverridesSerializable<T> = {
|
||||
[KeyType in keyof T]: NonNullable<T[KeyType]> extends Function
|
||||
? // cannot use boolean here as it would be challenging to distinguish
|
||||
// between a "native" boolean props and a disabled callback
|
||||
// so use a specific keyword
|
||||
'ignore'
|
||||
: // be careful here to not filter out string/number types
|
||||
NonNullable<T[KeyType]> extends React.ReactChildren | React.ReactElement
|
||||
? never
|
||||
: // make it recursive
|
||||
NonNullable<T[KeyType]> extends object
|
||||
? MakeOverridesSerializable<T[KeyType]>
|
||||
: NonNullable<T[KeyType]>;
|
||||
};
|
33
src/plugins/chart_expressions/common/utils.test.ts
Normal file
33
src/plugins/chart_expressions/common/utils.test.ts
Normal 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 { getOverridesFor } from './utils';
|
||||
|
||||
describe('Overrides utilities', () => {
|
||||
describe('getOverridesFor', () => {
|
||||
it('should return an empty object for undefined values', () => {
|
||||
expect(getOverridesFor(undefined, 'settings')).toEqual({});
|
||||
// @ts-expect-error
|
||||
expect(getOverridesFor({}, 'settings')).toEqual({});
|
||||
// @ts-expect-error
|
||||
expect(getOverridesFor({ otherOverride: {} }, 'settings')).toEqual({});
|
||||
});
|
||||
|
||||
it('should return only the component specific overrides', () => {
|
||||
expect(
|
||||
getOverridesFor({ otherOverride: { a: 15 }, settings: { b: 10 } }, 'settings')
|
||||
).toEqual({ b: 10 });
|
||||
});
|
||||
|
||||
it('should swap any "ignore" value into undefined value', () => {
|
||||
expect(
|
||||
getOverridesFor({ otherOverride: { a: 15 }, settings: { b: 10, c: 'ignore' } }, 'settings')
|
||||
).toEqual({ b: 10, c: undefined });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,6 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { KibanaExecutionContext } from '@kbn/core-execution-context-common';
|
||||
|
||||
export const extractContainerType = (context?: KibanaExecutionContext): string | undefined => {
|
||||
|
@ -33,3 +32,30 @@ export const extractVisualizationType = (context?: KibanaExecutionContext): stri
|
|||
return recursiveGet(context)?.type;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an override specification and returns a props object to use directly with the Component
|
||||
* @param overrides Overrides object
|
||||
* @param componentName name of the Component to look for (i.e. "settings", "axisX")
|
||||
* @returns an props object to use directly with the component
|
||||
*/
|
||||
export function getOverridesFor<
|
||||
// Component props
|
||||
P extends Record<string, unknown>,
|
||||
// Overrides
|
||||
O extends Record<string, P>,
|
||||
// Overrides Component names
|
||||
K extends keyof O
|
||||
>(overrides: O | undefined, componentName: K) {
|
||||
if (!overrides || !overrides[componentName]) {
|
||||
return {};
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(overrides[componentName]).map(([key, value]) => {
|
||||
if (value === 'ignore') {
|
||||
return [key, undefined];
|
||||
}
|
||||
return [key, value];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -131,6 +132,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -182,6 +184,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -233,6 +236,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -284,6 +288,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -337,6 +342,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -390,6 +396,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -441,6 +448,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -492,6 +500,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -543,6 +552,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -594,6 +604,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -645,6 +656,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -696,6 +708,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -747,6 +760,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -11,6 +11,7 @@ import { GaugeArguments, GaugeShapes } from '..';
|
|||
import { functionWrapper } from '@kbn/expressions-plugin/common/expression_functions/specs/tests/utils';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs';
|
||||
import {
|
||||
EXPRESSION_GAUGE_NAME,
|
||||
GaugeCentralMajorModes,
|
||||
GaugeColorModes,
|
||||
GaugeLabelMajorModes,
|
||||
|
@ -110,4 +111,23 @@ describe('interpreter/functions#gauge', () => {
|
|||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
};
|
||||
const handlers = {
|
||||
variables: { overrides },
|
||||
getExecutionContext: jest.fn(),
|
||||
} as unknown as ExecutionContext;
|
||||
const result = await fn(context, args, handlers);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: EXPRESSION_GAUGE_NAME,
|
||||
value: expect.objectContaining({ overrides }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { prepareLogTable, validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { GaugeExpressionFunctionDefinition } from '../types';
|
||||
import { GaugeExpressionFunctionDefinition, GaugeRenderProps } from '../types';
|
||||
import {
|
||||
EXPRESSION_GAUGE_NAME,
|
||||
GaugeCentralMajorModes,
|
||||
|
@ -232,6 +232,7 @@ export const gaugeFunction = (): GaugeExpressionFunctionDefinition => ({
|
|||
handlers.getExecutionContext?.()?.description,
|
||||
},
|
||||
canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens),
|
||||
overrides: handlers.variables?.overrides as GaugeRenderProps['overrides'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@ export const PLUGIN_ID = 'expressionGauge';
|
|||
export const PLUGIN_NAME = 'expressionGauge';
|
||||
|
||||
export type {
|
||||
AllowedGaugeOverrides,
|
||||
GaugeExpressionFunctionDefinition,
|
||||
GaugeExpressionProps,
|
||||
FormatFactory,
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
} from '@kbn/expressions-plugin/common';
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import { CustomPaletteState } from '@kbn/charts-plugin/common';
|
||||
import type { MakeOverridesSerializable, Simplify } from '@kbn/chart-expressions-common/types';
|
||||
import type { GoalProps } from '@elastic/charts';
|
||||
import {
|
||||
EXPRESSION_GAUGE_NAME,
|
||||
GAUGE_FUNCTION_RENDERER_NAME,
|
||||
|
@ -84,3 +86,7 @@ export interface Accessors {
|
|||
metric?: string;
|
||||
goal?: string;
|
||||
}
|
||||
|
||||
export type AllowedGaugeOverrides = Partial<
|
||||
Record<'gauge', Simplify<MakeOverridesSerializable<GoalProps>>>
|
||||
>;
|
||||
|
|
|
@ -10,7 +10,8 @@ import type { PaletteRegistry } from '@kbn/coloring';
|
|||
import type { PersistedState } from '@kbn/visualizations-plugin/public';
|
||||
import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||
import type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import type { GaugeExpressionProps } from './expression_functions';
|
||||
import type { AllowedSettingsOverrides } from '@kbn/charts-plugin/common';
|
||||
import type { AllowedGaugeOverrides, GaugeExpressionProps } from './expression_functions';
|
||||
|
||||
export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
|
||||
|
||||
|
@ -20,4 +21,5 @@ export type GaugeRenderProps = GaugeExpressionProps & {
|
|||
paletteService: PaletteRegistry;
|
||||
renderComplete: () => void;
|
||||
uiState: PersistedState;
|
||||
overrides?: AllowedGaugeOverrides & AllowedSettingsOverrides;
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
GaugeColorModes,
|
||||
} from '../../common';
|
||||
import GaugeComponent from './gauge_component';
|
||||
import { Chart, Goal } from '@elastic/charts';
|
||||
import { Chart, Goal, Settings } from '@elastic/charts';
|
||||
|
||||
jest.mock('@elastic/charts', () => {
|
||||
const original = jest.requireActual('@elastic/charts');
|
||||
|
@ -405,4 +405,19 @@ describe('GaugeComponent', function () {
|
|||
expect(goal.prop('bands')).toEqual([0, 2, 6, 8, 10]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrides', () => {
|
||||
it('should apply overrides to the settings component', () => {
|
||||
const component = shallowWithIntl(
|
||||
<GaugeComponent
|
||||
{...wrapperProps}
|
||||
overrides={{ settings: { onBrushEnd: 'ignore', ariaUseDefaultSummary: true } }}
|
||||
/>
|
||||
);
|
||||
|
||||
const settingsComponent = component.find(Settings);
|
||||
expect(settingsComponent.prop('onBrushEnd')).toBeUndefined();
|
||||
expect(settingsComponent.prop('ariaUseDefaultSummary')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ import type { PaletteOutput } from '@kbn/coloring';
|
|||
import { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import type { CustomPaletteState } from '@kbn/charts-plugin/public';
|
||||
import { EmptyPlaceholder } from '@kbn/charts-plugin/public';
|
||||
import { getOverridesFor } from '@kbn/chart-expressions-common';
|
||||
import { isVisDimension } from '@kbn/visualizations-plugin/common/utils';
|
||||
import {
|
||||
GaugeRenderProps,
|
||||
|
@ -167,7 +168,16 @@ function getTicks(
|
|||
}
|
||||
|
||||
export const GaugeComponent: FC<GaugeRenderProps> = memo(
|
||||
({ data, args, uiState, formatFactory, paletteService, chartsThemeService, renderComplete }) => {
|
||||
({
|
||||
data,
|
||||
args,
|
||||
uiState,
|
||||
formatFactory,
|
||||
paletteService,
|
||||
chartsThemeService,
|
||||
renderComplete,
|
||||
overrides,
|
||||
}) => {
|
||||
const {
|
||||
shape: gaugeType,
|
||||
palette,
|
||||
|
@ -360,6 +370,7 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
|
|||
ariaLabel={args.ariaLabel}
|
||||
ariaUseDefaultSummary={!args.ariaLabel}
|
||||
onRenderChange={onRenderChange}
|
||||
{...getOverridesFor(overrides, 'settings')}
|
||||
/>
|
||||
<Goal
|
||||
id="goal"
|
||||
|
@ -396,6 +407,7 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
|
|||
labelMinor={labelMinor ? `${labelMinor}${minorExtraSpaces}` : ''}
|
||||
{...extraTitles}
|
||||
{...goalConfig}
|
||||
{...getOverridesFor(overrides, 'gauge')}
|
||||
/>
|
||||
</Chart>
|
||||
{commonLabel && <div className="gauge__label">{commonLabel}</div>}
|
||||
|
|
|
@ -102,6 +102,7 @@ Object {
|
|||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"overrides": undefined,
|
||||
"syncCursor": true,
|
||||
"syncTooltips": false,
|
||||
},
|
||||
|
|
|
@ -10,7 +10,11 @@ import { heatmapFunction } from './heatmap_function';
|
|||
import type { HeatmapArguments } from '..';
|
||||
import { functionWrapper } from '@kbn/expressions-plugin/common/expression_functions/specs/tests/utils';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs';
|
||||
import { EXPRESSION_HEATMAP_GRID_NAME, EXPRESSION_HEATMAP_LEGEND_NAME } from '../constants';
|
||||
import {
|
||||
EXPRESSION_HEATMAP_GRID_NAME,
|
||||
EXPRESSION_HEATMAP_LEGEND_NAME,
|
||||
EXPRESSION_HEATMAP_NAME,
|
||||
} from '../constants';
|
||||
import { ExecutionContext } from '@kbn/expressions-plugin/common';
|
||||
|
||||
describe('interpreter/functions#heatmap', () => {
|
||||
|
@ -80,4 +84,23 @@ describe('interpreter/functions#heatmap', () => {
|
|||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
};
|
||||
const handlers = {
|
||||
variables: { overrides },
|
||||
getExecutionContext: jest.fn(),
|
||||
} as unknown as ExecutionContext;
|
||||
const result = await fn(context, args, handlers);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: EXPRESSION_HEATMAP_NAME,
|
||||
value: expect.objectContaining({ overrides }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
Dimension,
|
||||
validateAccessor,
|
||||
} from '@kbn/visualizations-plugin/common/utils';
|
||||
import { HeatmapExpressionFunctionDefinition } from '../types';
|
||||
import type { HeatmapExpressionFunctionDefinition, HeatmapExpressionProps } from '../types';
|
||||
import {
|
||||
EXPRESSION_HEATMAP_NAME,
|
||||
EXPRESSION_HEATMAP_GRID_NAME,
|
||||
|
@ -230,9 +230,10 @@ export const heatmapFunction = (): HeatmapExpressionFunctionDefinition => ({
|
|||
(handlers.variables?.embeddableTitle as string) ??
|
||||
handlers.getExecutionContext?.()?.description,
|
||||
},
|
||||
syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false,
|
||||
syncCursor: handlers?.isSyncCursorEnabled?.() ?? true,
|
||||
canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens),
|
||||
syncTooltips: handlers.isSyncTooltipsEnabled?.() ?? false,
|
||||
syncCursor: handlers.isSyncCursorEnabled?.() ?? true,
|
||||
canNavigateToLens: Boolean(handlers.variables?.canNavigateToLens),
|
||||
overrides: handlers.variables?.overrides as HeatmapExpressionProps['overrides'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from '@kbn/expressions-plugin/common';
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
|
||||
import { CustomPaletteState } from '@kbn/charts-plugin/common';
|
||||
import { AllowedSettingsOverrides, CustomPaletteState } from '@kbn/charts-plugin/common';
|
||||
import type { LegendSize } from '@kbn/visualizations-plugin/public';
|
||||
import {
|
||||
EXPRESSION_HEATMAP_NAME,
|
||||
|
@ -95,6 +95,7 @@ export interface HeatmapExpressionProps {
|
|||
syncTooltips: boolean;
|
||||
syncCursor: boolean;
|
||||
canNavigateToLens?: boolean;
|
||||
overrides?: AllowedSettingsOverrides;
|
||||
}
|
||||
|
||||
export interface HeatmapRender {
|
||||
|
|
|
@ -428,4 +428,19 @@ describe('HeatmapComponent', function () {
|
|||
expect(component.find(Settings).first().prop('onElementClick')).toBeUndefined();
|
||||
expect(component.find(Settings).first().prop('onBrushEnd')).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('overrides', () => {
|
||||
it('should apply overrides to the settings component', () => {
|
||||
const component = shallowWithIntl(
|
||||
<HeatmapComponent
|
||||
{...wrapperProps}
|
||||
overrides={{ settings: { onBrushEnd: 'ignore', ariaUseDefaultSummary: true } }}
|
||||
/>
|
||||
);
|
||||
|
||||
const settingsComponent = component.find(Settings);
|
||||
expect(settingsComponent.prop('onBrushEnd')).toBeUndefined();
|
||||
expect(settingsComponent.prop('ariaUseDefaultSummary')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
ESFixedIntervalUnit,
|
||||
ESCalendarIntervalUnit,
|
||||
PartialTheme,
|
||||
SettingsProps,
|
||||
} from '@elastic/charts';
|
||||
import type { CustomPaletteState } from '@kbn/charts-plugin/public';
|
||||
import { search } from '@kbn/data-plugin/public';
|
||||
|
@ -36,6 +37,7 @@ import {
|
|||
} from '@kbn/visualizations-plugin/common/constants';
|
||||
import { DatatableColumn } from '@kbn/expressions-plugin/public';
|
||||
import { IconChartHeatmap } from '@kbn/chart-icons';
|
||||
import { getOverridesFor } from '@kbn/chart-expressions-common';
|
||||
import type { HeatmapRenderProps, FilterEvent, BrushEvent } from '../../common';
|
||||
import {
|
||||
applyPaletteParams,
|
||||
|
@ -148,6 +150,7 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
|
|||
syncTooltips,
|
||||
syncCursor,
|
||||
renderComplete,
|
||||
overrides,
|
||||
}) => {
|
||||
const chartRef = useRef<Chart>(null);
|
||||
const chartTheme = chartsThemeService.useChartsTheme();
|
||||
|
@ -498,6 +501,11 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
|
|||
};
|
||||
});
|
||||
|
||||
const { theme: settingsThemeOverrides = {}, ...settingsOverrides } = getOverridesFor(
|
||||
overrides,
|
||||
'settings'
|
||||
) as Partial<SettingsProps>;
|
||||
|
||||
const themeOverrides: PartialTheme = {
|
||||
legend: {
|
||||
labelOptions: {
|
||||
|
@ -591,7 +599,13 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
|
|||
legendColorPicker={uiState ? LegendColorPickerWrapper : undefined}
|
||||
debugState={window._echDebugStateFlag ?? false}
|
||||
tooltip={tooltip}
|
||||
theme={[themeOverrides, chartTheme]}
|
||||
theme={[
|
||||
themeOverrides,
|
||||
chartTheme,
|
||||
...(Array.isArray(settingsThemeOverrides)
|
||||
? settingsThemeOverrides
|
||||
: [settingsThemeOverrides]),
|
||||
]}
|
||||
baseTheme={chartBaseTheme}
|
||||
xDomain={{
|
||||
min:
|
||||
|
@ -606,6 +620,7 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
|
|||
onBrushEnd={interactive ? (onBrushEnd as BrushEndListener) : undefined}
|
||||
ariaLabel={args.ariaLabel}
|
||||
ariaUseDefaultSummary={!args.ariaLabel}
|
||||
{...settingsOverrides}
|
||||
/>
|
||||
<Heatmap
|
||||
id="heatmap"
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { metricVisFunction } from './metric_vis_function';
|
||||
import type { MetricArguments } from '..';
|
||||
import { functionWrapper } from '@kbn/expressions-plugin/common/expression_functions/specs/tests/utils';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs';
|
||||
import { EXPRESSION_METRIC_NAME } from '../constants';
|
||||
import { ExecutionContext } from '@kbn/expressions-plugin/common';
|
||||
|
||||
describe('interpreter/functions#metricVis', () => {
|
||||
const fn = functionWrapper(metricVisFunction());
|
||||
const context: Datatable = {
|
||||
type: 'datatable',
|
||||
rows: [{ 'col-0-1': 0 }],
|
||||
columns: [{ id: 'col-0-1', name: 'Count', meta: { type: 'number' } }],
|
||||
};
|
||||
const args: MetricArguments = {
|
||||
metric: 'col-0-1',
|
||||
progressDirection: 'horizontal',
|
||||
maxCols: 1,
|
||||
inspectorTableId: 'random-id',
|
||||
};
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
};
|
||||
const handlers = {
|
||||
variables: { overrides },
|
||||
getExecutionContext: jest.fn(),
|
||||
} as unknown as ExecutionContext;
|
||||
const result = await fn(context, args, handlers);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: EXPRESSION_METRIC_NAME,
|
||||
value: expect.objectContaining({ overrides }),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -14,7 +14,7 @@ import {
|
|||
validateAccessor,
|
||||
} from '@kbn/visualizations-plugin/common/utils';
|
||||
import { LayoutDirection } from '@elastic/charts';
|
||||
import { visType } from '../types';
|
||||
import { MetricVisRenderConfig, visType } from '../types';
|
||||
import { MetricVisExpressionFunctionDefinition } from '../types';
|
||||
import { EXPRESSION_METRIC_NAME, EXPRESSION_METRIC_TRENDLINE_NAME } from '../constants';
|
||||
|
||||
|
@ -194,6 +194,7 @@ export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({
|
|||
breakdownBy: args.breakdownBy,
|
||||
},
|
||||
},
|
||||
overrides: handlers.variables?.overrides as MetricVisRenderConfig['overrides'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
ExpressionValueRender,
|
||||
} from '@kbn/expressions-plugin/common';
|
||||
import { ExpressionValueVisDimension, prepareLogTable } from '@kbn/visualizations-plugin/common';
|
||||
import { CustomPaletteState } from '@kbn/charts-plugin/common';
|
||||
import type { AllowedSettingsOverrides, CustomPaletteState } from '@kbn/charts-plugin/common';
|
||||
import { VisParams, visType } from './expression_renderers';
|
||||
import { EXPRESSION_METRIC_NAME, EXPRESSION_METRIC_TRENDLINE_NAME } from '../constants';
|
||||
|
||||
|
@ -40,6 +40,7 @@ export interface MetricVisRenderConfig {
|
|||
visType: typeof visType;
|
||||
visData: Datatable;
|
||||
visConfig: Pick<VisParams, 'metric' | 'dimensions'>;
|
||||
overrides?: AllowedSettingsOverrides;
|
||||
}
|
||||
|
||||
export type MetricVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
|
|
|
@ -1471,4 +1471,29 @@ describe('MetricVisComponent', function () {
|
|||
expect(secondary).toBe('1.12K%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrides', () => {
|
||||
it('should apply overrides to the settings component', () => {
|
||||
const component = shallow(
|
||||
<MetricVis
|
||||
config={{
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
},
|
||||
dimensions: {
|
||||
metric: basePriceColumnId,
|
||||
},
|
||||
}}
|
||||
data={table}
|
||||
{...defaultProps}
|
||||
overrides={{ settings: { onBrushEnd: 'ignore', ariaUseDefaultSummary: true } }}
|
||||
/>
|
||||
);
|
||||
|
||||
const settingsComponent = component.find(Settings);
|
||||
expect(settingsComponent.prop('onBrushEnd')).toBeUndefined();
|
||||
expect(settingsComponent.prop('ariaUseDefaultSummary')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
Settings,
|
||||
MetricWTrend,
|
||||
MetricWNumber,
|
||||
SettingsProps,
|
||||
} from '@elastic/charts';
|
||||
import { getColumnByAccessor, getFormatByAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
|
@ -36,6 +37,8 @@ import { CUSTOM_PALETTE } from '@kbn/coloring';
|
|||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { useResizeObserver, useEuiScrollBar } from '@elastic/eui';
|
||||
import { AllowedSettingsOverrides } from '@kbn/charts-plugin/common';
|
||||
import { getOverridesFor } from '@kbn/chart-expressions-common';
|
||||
import { DEFAULT_TRENDLINE_NAME } from '../../common/constants';
|
||||
import { VisParams } from '../../common';
|
||||
import {
|
||||
|
@ -177,6 +180,7 @@ export interface MetricVisComponentProps {
|
|||
fireEvent: IInterpreterRenderHandlers['event'];
|
||||
renderMode: RenderMode;
|
||||
filterable: boolean;
|
||||
overrides?: AllowedSettingsOverrides;
|
||||
}
|
||||
|
||||
export const MetricVis = ({
|
||||
|
@ -186,6 +190,7 @@ export const MetricVis = ({
|
|||
fireEvent,
|
||||
renderMode,
|
||||
filterable,
|
||||
overrides,
|
||||
}: MetricVisComponentProps) => {
|
||||
const primaryMetricColumn = getColumnByAccessor(config.dimensions.metric, data.columns)!;
|
||||
const formatPrimaryMetric = getMetricFormatter(config.dimensions.metric, data.columns);
|
||||
|
@ -331,6 +336,11 @@ export const MetricVis = ({
|
|||
);
|
||||
}, [grid.length, minHeight, scrollDimensions.height]);
|
||||
|
||||
const { theme: settingsThemeOverrides = {}, ...settingsOverrides } = getOverridesFor(
|
||||
overrides,
|
||||
'settings'
|
||||
) as Partial<SettingsProps>;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
|
@ -357,8 +367,11 @@ export const MetricVis = ({
|
|||
background: defaultColor,
|
||||
barBackground: euiThemeVars.euiColorLightShade,
|
||||
},
|
||||
...chartTheme,
|
||||
},
|
||||
chartTheme,
|
||||
...(Array.isArray(settingsThemeOverrides)
|
||||
? settingsThemeOverrides
|
||||
: [settingsThemeOverrides]),
|
||||
]}
|
||||
baseTheme={baseTheme}
|
||||
onRenderChange={onRenderChange}
|
||||
|
@ -383,6 +396,7 @@ export const MetricVis = ({
|
|||
}
|
||||
: undefined
|
||||
}
|
||||
{...settingsOverrides}
|
||||
/>
|
||||
<Metric id="metric" data={grid} />
|
||||
</Chart>
|
||||
|
|
|
@ -55,7 +55,7 @@ export const getMetricVisRenderer = (
|
|||
name: EXPRESSION_METRIC_NAME,
|
||||
displayName: 'metric visualization',
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, { visData, visConfig }, handlers) => {
|
||||
render: async (domNode, { visData, visConfig, overrides }, handlers) => {
|
||||
const { core, plugins } = deps.getStartDeps();
|
||||
|
||||
handlers.onDestroy(() => {
|
||||
|
@ -103,6 +103,7 @@ export const getMetricVisRenderer = (
|
|||
fireEvent={handlers.event}
|
||||
renderMode={handlers.getRenderMode()}
|
||||
filterable={filterable}
|
||||
overrides={overrides}
|
||||
/>
|
||||
</div>
|
||||
</KibanaThemeProvider>,
|
||||
|
|
|
@ -45,6 +45,7 @@ Object {
|
|||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"overrides": undefined,
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
|
|
|
@ -27,6 +27,7 @@ Object {
|
|||
"type": "render",
|
||||
"value": Object {
|
||||
"canNavigateToLens": false,
|
||||
"overrides": undefined,
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
|
@ -168,6 +169,7 @@ Object {
|
|||
"type": "render",
|
||||
"value": Object {
|
||||
"canNavigateToLens": false,
|
||||
"overrides": undefined,
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
|
|
|
@ -45,6 +45,7 @@ Object {
|
|||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"overrides": undefined,
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
|
|
|
@ -37,6 +37,7 @@ Object {
|
|||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"overrides": undefined,
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs';
|
||||
import { mosaicVisFunction } from './mosaic_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
import { PARTITION_LABELS_VALUE, PARTITION_VIS_RENDERER_NAME } from '../constants';
|
||||
import { ExecutionContext } from '@kbn/expressions-plugin/common';
|
||||
|
||||
describe('interpreter/functions#mosaicVis', () => {
|
||||
|
@ -147,4 +147,23 @@ describe('interpreter/functions#mosaicVis', () => {
|
|||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
};
|
||||
const handlers = {
|
||||
variables: { overrides },
|
||||
getExecutionContext: jest.fn(),
|
||||
} as unknown as ExecutionContext;
|
||||
const result = await fn(context, visConfig, handlers);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: expect.objectContaining({ overrides }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,11 @@
|
|||
import { Position } from '@elastic/charts';
|
||||
import { prepareLogTable, validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { DEFAULT_LEGEND_SIZE, LegendSize } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import {
|
||||
LegendDisplay,
|
||||
type PartitionChartProps,
|
||||
type PartitionVisParams,
|
||||
} from '../types/expression_renderers';
|
||||
import { ChartTypes, MosaicVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
|
@ -172,6 +176,7 @@ export const mosaicVisFunction = (): MosaicVisExpressionFunctionDefinition => ({
|
|||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
overrides: handlers.variables?.overrides as PartitionChartProps['overrides'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { ExpressionValueVisDimension, LegendSize } from '@kbn/visualizations-plugin/common';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs';
|
||||
import { pieVisFunction } from './pie_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
import { PARTITION_LABELS_VALUE, PARTITION_VIS_RENDERER_NAME } from '../constants';
|
||||
import { ExecutionContext } from '@kbn/expressions-plugin/common';
|
||||
|
||||
describe('interpreter/functions#pieVis', () => {
|
||||
|
@ -144,4 +144,23 @@ describe('interpreter/functions#pieVis', () => {
|
|||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
};
|
||||
const handlers = {
|
||||
variables: { overrides },
|
||||
getExecutionContext: jest.fn(),
|
||||
} as unknown as ExecutionContext;
|
||||
const result = await fn(context, { ...visConfig, isDonut: false }, handlers);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: expect.objectContaining({ overrides }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,12 @@
|
|||
import { Position } from '@elastic/charts';
|
||||
import { prepareLogTable, validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { DEFAULT_LEGEND_SIZE, LegendSize } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { EmptySizeRatios, LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import {
|
||||
EmptySizeRatios,
|
||||
LegendDisplay,
|
||||
type PartitionChartProps,
|
||||
type PartitionVisParams,
|
||||
} from '../types/expression_renderers';
|
||||
import { ChartTypes, PieVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
|
@ -199,6 +204,7 @@ export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({
|
|||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
overrides: handlers.variables?.overrides as PartitionChartProps['overrides'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs';
|
||||
import { treemapVisFunction } from './treemap_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
import { PARTITION_LABELS_VALUE, PARTITION_VIS_RENDERER_NAME } from '../constants';
|
||||
import { ExecutionContext } from '@kbn/expressions-plugin/common';
|
||||
|
||||
describe('interpreter/functions#treemapVis', () => {
|
||||
|
@ -150,4 +150,23 @@ describe('interpreter/functions#treemapVis', () => {
|
|||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
};
|
||||
const handlers = {
|
||||
variables: { overrides },
|
||||
getExecutionContext: jest.fn(),
|
||||
} as unknown as ExecutionContext;
|
||||
const result = await fn(context, visConfig, handlers);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: expect.objectContaining({ overrides }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,11 @@
|
|||
import { Position } from '@elastic/charts';
|
||||
import { prepareLogTable, validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { DEFAULT_LEGEND_SIZE, LegendSize } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import {
|
||||
LegendDisplay,
|
||||
type PartitionChartProps,
|
||||
type PartitionVisParams,
|
||||
} from '../types/expression_renderers';
|
||||
import { ChartTypes, TreemapVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
|
@ -178,6 +182,7 @@ export const treemapVisFunction = (): TreemapVisExpressionFunctionDefinition =>
|
|||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
overrides: handlers.variables?.overrides as PartitionChartProps['overrides'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs';
|
||||
import { waffleVisFunction } from './waffle_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
import { PARTITION_LABELS_VALUE, PARTITION_VIS_RENDERER_NAME } from '../constants';
|
||||
import { ExecutionContext } from '@kbn/expressions-plugin/common';
|
||||
|
||||
describe('interpreter/functions#waffleVis', () => {
|
||||
|
@ -121,4 +121,23 @@ describe('interpreter/functions#waffleVis', () => {
|
|||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
};
|
||||
const handlers = {
|
||||
variables: { overrides },
|
||||
getExecutionContext: jest.fn(),
|
||||
} as unknown as ExecutionContext;
|
||||
const result = await fn(context, visConfig, handlers);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: expect.objectContaining({ overrides }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,11 @@
|
|||
import { Position } from '@elastic/charts';
|
||||
import { prepareLogTable, validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { DEFAULT_LEGEND_SIZE, LegendSize } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import {
|
||||
LegendDisplay,
|
||||
type PartitionChartProps,
|
||||
type PartitionVisParams,
|
||||
} from '../types/expression_renderers';
|
||||
import { ChartTypes, WaffleVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
|
@ -173,6 +177,7 @@ export const waffleVisFunction = (): WaffleVisExpressionFunctionDefinition => ({
|
|||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
overrides: handlers.variables?.overrides as PartitionChartProps['overrides'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -26,6 +26,7 @@ export {
|
|||
} from './expression_functions';
|
||||
|
||||
export type {
|
||||
AllowedPartitionOverrides,
|
||||
ExpressionValuePartitionLabels,
|
||||
PieVisExpressionFunctionDefinition,
|
||||
TreemapVisExpressionFunctionDefinition,
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { PartitionProps } from '@elastic/charts';
|
||||
import type { MakeOverridesSerializable, Simplify } from '@kbn/chart-expressions-common/types';
|
||||
import {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
|
@ -21,13 +23,13 @@ import {
|
|||
PARTITION_LABELS_FUNCTION,
|
||||
} from '../constants';
|
||||
import {
|
||||
RenderValue,
|
||||
PieVisConfig,
|
||||
type PartitionChartProps,
|
||||
type PieVisConfig,
|
||||
LabelPositions,
|
||||
ValueFormats,
|
||||
TreemapVisConfig,
|
||||
MosaicVisConfig,
|
||||
WaffleVisConfig,
|
||||
type TreemapVisConfig,
|
||||
type MosaicVisConfig,
|
||||
type WaffleVisConfig,
|
||||
} from './expression_renderers';
|
||||
|
||||
export interface PartitionLabelsArguments {
|
||||
|
@ -63,28 +65,28 @@ export type PieVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
|||
typeof PIE_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
PieVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
ExpressionValueRender<PartitionChartProps>
|
||||
>;
|
||||
|
||||
export type TreemapVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof TREEMAP_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
TreemapVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
ExpressionValueRender<PartitionChartProps>
|
||||
>;
|
||||
|
||||
export type MosaicVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof MOSAIC_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
MosaicVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
ExpressionValueRender<PartitionChartProps>
|
||||
>;
|
||||
|
||||
export type WaffleVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof WAFFLE_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
WaffleVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
ExpressionValueRender<PartitionChartProps>
|
||||
>;
|
||||
|
||||
export enum ChartTypes {
|
||||
|
@ -101,3 +103,15 @@ export type PartitionLabelsExpressionFunctionDefinition = ExpressionFunctionDefi
|
|||
PartitionLabelsArguments,
|
||||
ExpressionValuePartitionLabels
|
||||
>;
|
||||
|
||||
export type AllowedPartitionOverrides = Partial<
|
||||
Record<
|
||||
'partition',
|
||||
Simplify<
|
||||
Omit<
|
||||
MakeOverridesSerializable<PartitionProps>,
|
||||
'id' | 'data' | 'valueAccessor' | 'valueFormatter' | 'layers' | 'layout'
|
||||
>
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
|
|
@ -7,12 +7,17 @@
|
|||
*/
|
||||
|
||||
import { Position } from '@elastic/charts';
|
||||
import type { AllowedSettingsOverrides } from '@kbn/charts-plugin/common';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||
import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||
import type { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import type { LegendSize } from '@kbn/visualizations-plugin/public';
|
||||
import { ChartTypes, ExpressionValuePartitionLabels } from './expression_functions';
|
||||
import {
|
||||
type AllowedPartitionOverrides,
|
||||
ChartTypes,
|
||||
type ExpressionValuePartitionLabels,
|
||||
} from './expression_functions';
|
||||
|
||||
export enum EmptySizeRatios {
|
||||
SMALL = 0.3,
|
||||
|
@ -107,12 +112,13 @@ export interface WaffleVisConfig extends Omit<VisCommonConfig, 'buckets'> {
|
|||
showValuesInLegend: boolean;
|
||||
}
|
||||
|
||||
export interface RenderValue {
|
||||
export interface PartitionChartProps {
|
||||
visData: Datatable;
|
||||
visType: ChartTypes;
|
||||
visConfig: PartitionVisParams;
|
||||
syncColors: boolean;
|
||||
canNavigateToLens?: boolean;
|
||||
overrides?: AllowedPartitionOverrides & AllowedSettingsOverrides;
|
||||
}
|
||||
|
||||
export enum LabelPositions {
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { FC } from 'react';
|
|||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '@kbn/presentation-util-plugin/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { ChartTypes, PartitionChartProps } from '../../common/types';
|
||||
import { getStartDeps } from '../__mocks__';
|
||||
import { mosaicArgTypes, treemapMosaicConfig, data } from './shared';
|
||||
|
||||
|
@ -22,9 +22,9 @@ const containerSize = {
|
|||
const PartitionVisRenderer = () => getPartitionVisRenderer({ getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
visType: PartitionChartProps['visType'];
|
||||
syncColors: PartitionChartProps['syncColors'];
|
||||
} & PartitionChartProps['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { FC } from 'react';
|
|||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '@kbn/presentation-util-plugin/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { ChartTypes, PartitionChartProps } from '../../common/types';
|
||||
import { getStartDeps } from '../__mocks__';
|
||||
import { pieDonutArgTypes, pieConfig, data } from './shared';
|
||||
|
||||
|
@ -22,9 +22,9 @@ const containerSize = {
|
|||
const PartitionVisRenderer = () => getPartitionVisRenderer({ getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
visType: PartitionChartProps['visType'];
|
||||
syncColors: PartitionChartProps['syncColors'];
|
||||
} & PartitionChartProps['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
|
|
|
@ -9,12 +9,12 @@ import { Position } from '@elastic/charts';
|
|||
import {
|
||||
LabelPositions,
|
||||
LegendDisplay,
|
||||
RenderValue,
|
||||
PartitionChartProps,
|
||||
PartitionVisParams,
|
||||
ValueFormats,
|
||||
} from '../../../common/types';
|
||||
|
||||
export const config: RenderValue['visConfig'] = {
|
||||
export const config: PartitionChartProps['visConfig'] = {
|
||||
addTooltip: true,
|
||||
legendDisplay: LegendDisplay.HIDE,
|
||||
metricsToLabels: { percent_uptime: 'percent_uptime' },
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { RenderValue } from '../../../common/types';
|
||||
import { PartitionChartProps } from '../../../common/types';
|
||||
|
||||
export const data: RenderValue['visData'] = {
|
||||
export const data: PartitionChartProps['visData'] = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { FC } from 'react';
|
|||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '@kbn/presentation-util-plugin/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { ChartTypes, PartitionChartProps } from '../../common/types';
|
||||
import { getStartDeps } from '../__mocks__';
|
||||
import { treemapArgTypes, treemapMosaicConfig, data } from './shared';
|
||||
|
||||
|
@ -22,9 +22,9 @@ const containerSize = {
|
|||
const PartitionVisRenderer = () => getPartitionVisRenderer({ getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
visType: PartitionChartProps['visType'];
|
||||
syncColors: PartitionChartProps['syncColors'];
|
||||
} & PartitionChartProps['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { FC } from 'react';
|
|||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '@kbn/presentation-util-plugin/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { ChartTypes, PartitionChartProps } from '../../common/types';
|
||||
import { getStartDeps } from '../__mocks__';
|
||||
import { waffleArgTypes, waffleConfig, data } from './shared';
|
||||
|
||||
|
@ -22,9 +22,9 @@ const containerSize = {
|
|||
const PartitionVisRenderer = () => getPartitionVisRenderer({ getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
visType: PartitionChartProps['visType'];
|
||||
syncColors: PartitionChartProps['syncColors'];
|
||||
} & PartitionChartProps['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
|
|
|
@ -280,6 +280,7 @@ exports[`PartitionVisComponent should render correct structure for donut 1`] = `
|
|||
},
|
||||
},
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
tooltip={
|
||||
|
@ -666,6 +667,7 @@ exports[`PartitionVisComponent should render correct structure for mosaic 1`] =
|
|||
},
|
||||
},
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
tooltip={
|
||||
|
@ -1115,6 +1117,7 @@ exports[`PartitionVisComponent should render correct structure for multi-metric
|
|||
},
|
||||
},
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
tooltip={
|
||||
|
@ -1564,6 +1567,7 @@ exports[`PartitionVisComponent should render correct structure for pie 1`] = `
|
|||
},
|
||||
},
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
tooltip={
|
||||
|
@ -1950,6 +1954,7 @@ exports[`PartitionVisComponent should render correct structure for treemap 1`] =
|
|||
},
|
||||
},
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
tooltip={
|
||||
|
@ -2295,6 +2300,7 @@ exports[`PartitionVisComponent should render correct structure for waffle 1`] =
|
|||
},
|
||||
},
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
tooltip={
|
||||
|
|
|
@ -329,4 +329,19 @@ describe('PartitionVisComponent', function () {
|
|||
"Pie chart can't render with negative values."
|
||||
);
|
||||
});
|
||||
|
||||
describe('overrides', () => {
|
||||
it('should apply overrides to the settings component', () => {
|
||||
const component = shallow(
|
||||
<PartitionVisComponent
|
||||
{...wrapperProps}
|
||||
overrides={{ settings: { onBrushEnd: 'ignore', ariaUseDefaultSummary: true } }}
|
||||
/>
|
||||
);
|
||||
|
||||
const settingsComponent = component.find(Settings);
|
||||
expect(settingsComponent.prop('onBrushEnd')).toBeUndefined();
|
||||
expect(settingsComponent.prop('ariaUseDefaultSummary')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
TooltipType,
|
||||
SeriesIdentifier,
|
||||
PartitionElementEvent,
|
||||
SettingsProps,
|
||||
} from '@elastic/charts';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
|
@ -34,13 +35,15 @@ import {
|
|||
IInterpreterRenderHandlers,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { getOverridesFor } from '@kbn/chart-expressions-common';
|
||||
import { consolidateMetricColumns } from '../../common/utils';
|
||||
import { DEFAULT_PERCENT_DECIMALS } from '../../common/constants';
|
||||
import {
|
||||
PartitionVisParams,
|
||||
BucketColumns,
|
||||
type BucketColumns,
|
||||
ValueFormats,
|
||||
PieContainerDimensions,
|
||||
type PieContainerDimensions,
|
||||
type PartitionChartProps,
|
||||
type PartitionVisParams,
|
||||
} from '../../common/types/expression_renderers';
|
||||
import {
|
||||
LegendColorPickerWrapper,
|
||||
|
@ -66,7 +69,6 @@ import {
|
|||
partitionVisContainerStyle,
|
||||
partitionVisContainerWithToggleStyleFactory,
|
||||
} from './partition_vis_component.styles';
|
||||
import { ChartTypes } from '../../common/types';
|
||||
import { filterOutConfig } from '../utils/filter_out_config';
|
||||
import { ColumnCellValueActions, FilterEvent, StartDeps } from '../types';
|
||||
|
||||
|
@ -78,10 +80,11 @@ declare global {
|
|||
_echDebugStateFlag?: boolean;
|
||||
}
|
||||
}
|
||||
export interface PartitionVisComponentProps {
|
||||
export type PartitionVisComponentProps = Omit<
|
||||
PartitionChartProps,
|
||||
'navigateToLens' | 'visConfig'
|
||||
> & {
|
||||
visParams: PartitionVisParams;
|
||||
visData: Datatable;
|
||||
visType: ChartTypes;
|
||||
uiState: PersistedState;
|
||||
fireEvent: IInterpreterRenderHandlers['event'];
|
||||
renderComplete: IInterpreterRenderHandlers['done'];
|
||||
|
@ -89,9 +92,8 @@ export interface PartitionVisComponentProps {
|
|||
chartsThemeService: ChartsPluginSetup['theme'];
|
||||
palettesRegistry: PaletteRegistry;
|
||||
services: Pick<StartDeps, 'data' | 'fieldFormats'>;
|
||||
syncColors: boolean;
|
||||
columnCellValueActions: ColumnCellValueActions;
|
||||
}
|
||||
};
|
||||
|
||||
const PartitionVisComponent = (props: PartitionVisComponentProps) => {
|
||||
const {
|
||||
|
@ -102,6 +104,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
|
|||
services,
|
||||
syncColors,
|
||||
interactive,
|
||||
overrides,
|
||||
} = props;
|
||||
const visParams = useMemo(() => filterOutConfig(visType, preVisParams), [preVisParams, visType]);
|
||||
const chartTheme = props.chartsThemeService.useChartsTheme();
|
||||
|
@ -354,6 +357,11 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
|
|||
return 1;
|
||||
}, [visData.rows, metricColumn]);
|
||||
|
||||
const { theme: settingsThemeOverrides = {}, ...settingsOverrides } = getOverridesFor(
|
||||
overrides,
|
||||
'settings'
|
||||
) as Partial<SettingsProps>;
|
||||
|
||||
const themeOverrides = useMemo(
|
||||
() => getPartitionTheme(visType, visParams, chartTheme, containerDimensions, rescaleFactor),
|
||||
[visType, visParams, chartTheme, containerDimensions, rescaleFactor]
|
||||
|
@ -489,11 +497,16 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
...(Array.isArray(settingsThemeOverrides)
|
||||
? settingsThemeOverrides
|
||||
: [settingsThemeOverrides]),
|
||||
]}
|
||||
baseTheme={chartBaseTheme}
|
||||
onRenderChange={onRenderChange}
|
||||
ariaLabel={props.visParams.ariaLabel}
|
||||
ariaUseDefaultSummary={!props.visParams.ariaLabel}
|
||||
{...settingsOverrides}
|
||||
/>
|
||||
<Partition
|
||||
id={visType}
|
||||
|
@ -517,6 +530,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
|
|||
}
|
||||
layers={layers}
|
||||
topGroove={!visParams.labels.show ? 0 : undefined}
|
||||
{...getOverridesFor(overrides, 'partition')}
|
||||
/>
|
||||
</Chart>
|
||||
</LegendColorPickerWrapperContext.Provider>
|
||||
|
|
|
@ -25,7 +25,7 @@ import { extractContainerType, extractVisualizationType } from '@kbn/chart-expre
|
|||
import { VisTypePieDependencies } from '../plugin';
|
||||
import { PARTITION_VIS_RENDERER_NAME } from '../../common/constants';
|
||||
import { CellValueAction, GetCompatibleCellValueActions } from '../types';
|
||||
import { ChartTypes, PartitionVisParams, RenderValue } from '../../common/types';
|
||||
import { ChartTypes, type PartitionVisParams, type PartitionChartProps } from '../../common/types';
|
||||
|
||||
export const strings = {
|
||||
getDisplayName: () =>
|
||||
|
@ -73,14 +73,14 @@ export const getColumnCellValueActions = async (
|
|||
|
||||
export const getPartitionVisRenderer: (
|
||||
deps: VisTypePieDependencies
|
||||
) => ExpressionRenderDefinition<RenderValue> = ({ getStartDeps }) => ({
|
||||
) => ExpressionRenderDefinition<PartitionChartProps> = ({ getStartDeps }) => ({
|
||||
name: PARTITION_VIS_RENDERER_NAME,
|
||||
displayName: strings.getDisplayName(),
|
||||
help: strings.getHelpDescription(),
|
||||
reuseDomNode: true,
|
||||
render: async (
|
||||
domNode,
|
||||
{ visConfig, visData, visType, syncColors, canNavigateToLens },
|
||||
{ visConfig, visData, visType, syncColors, canNavigateToLens, overrides },
|
||||
handlers
|
||||
) => {
|
||||
const { core, plugins } = getStartDeps();
|
||||
|
@ -127,6 +127,7 @@ export const getPartitionVisRenderer: (
|
|||
services={{ data: plugins.data, fieldFormats: plugins.fieldFormats }}
|
||||
syncColors={syncColors}
|
||||
columnCellValueActions={columnCellValueActions}
|
||||
overrides={overrides}
|
||||
/>
|
||||
</div>
|
||||
</KibanaThemeProvider>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { XY_VIS_RENDERER } from '../constants';
|
||||
import { LayeredXyVisFn } from '../types';
|
||||
import { LayeredXyVisFn, type XYRender } from '../types';
|
||||
import { logDatatables, logDatatable } from '../utils';
|
||||
import {
|
||||
validateMarkSizeRatioLimits,
|
||||
|
@ -65,6 +65,7 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers)
|
|||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false,
|
||||
syncCursor: handlers?.isSyncCursorEnabled?.() ?? true,
|
||||
overrides: handlers.variables?.overrides as XYRender['value']['overrides'],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -355,4 +355,53 @@ describe('xyVis', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should pass over overrides from variables', async () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const { layers, ...rest } = args;
|
||||
const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer;
|
||||
const overrides = {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
axisX: {
|
||||
showOverlappingTicks: true,
|
||||
},
|
||||
};
|
||||
const context = {
|
||||
...createMockExecutionContext(),
|
||||
variables: {
|
||||
overrides,
|
||||
},
|
||||
};
|
||||
const result = await xyVisFunction.fn(
|
||||
data,
|
||||
{ ...rest, ...restLayerArgs, referenceLines: [] },
|
||||
context
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: XY_VIS,
|
||||
value: {
|
||||
args: {
|
||||
...rest,
|
||||
layers: [
|
||||
{
|
||||
layerType,
|
||||
table: data,
|
||||
layerId: 'dataLayers-0',
|
||||
type,
|
||||
...restLayerArgs,
|
||||
},
|
||||
],
|
||||
},
|
||||
canNavigateToLens: false,
|
||||
syncColors: false,
|
||||
syncTooltips: false,
|
||||
syncCursor: true,
|
||||
overrides,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { Datatable } from '@kbn/expressions-plugin/common';
|
|||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
|
||||
import { LayerTypes, XY_VIS_RENDERER, DATA_LAYER } from '../constants';
|
||||
import { appendLayerIds, getAccessors, getShowLines, normalizeTable } from '../helpers';
|
||||
import { DataLayerConfigResult, XYLayerConfig, XyVisFn, XYArgs } from '../types';
|
||||
import type { DataLayerConfigResult, XYLayerConfig, XyVisFn, XYArgs, XYRender } from '../types';
|
||||
import {
|
||||
hasAreaLayer,
|
||||
hasBarLayer,
|
||||
|
@ -137,6 +137,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false,
|
||||
syncCursor: handlers?.isSyncCursorEnabled?.() ?? true,
|
||||
overrides: handlers.variables?.overrides as XYRender['value']['overrides'],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ export const PLUGIN_NAME = 'expressionXy';
|
|||
export { LayerTypes } from './constants';
|
||||
|
||||
export type {
|
||||
AllowedXYOverrides,
|
||||
XYArgs,
|
||||
EndValue,
|
||||
XYRender,
|
||||
|
|
|
@ -6,14 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts';
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import { type AxisProps, HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts';
|
||||
import type { $Values } from '@kbn/utility-types';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { Datatable, ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
|
||||
import type { Datatable, ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
|
||||
import { LegendSize } from '@kbn/visualizations-plugin/common';
|
||||
import { EventAnnotationOutput } from '@kbn/event-annotation-plugin/common';
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
|
||||
import { MakeOverridesSerializable, Simplify } from '@kbn/chart-expressions-common/types';
|
||||
import {
|
||||
AxisExtentModes,
|
||||
FillStyles,
|
||||
|
@ -497,3 +498,11 @@ export type ExtendedAnnotationLayerFn = ExpressionFunctionDefinition<
|
|||
ExtendedAnnotationLayerArgs,
|
||||
ExtendedAnnotationLayerConfigResult
|
||||
>;
|
||||
|
||||
export type AllowedXYOverrides = Partial<
|
||||
Record<
|
||||
'axisX' | 'axisLeft' | 'axisRight',
|
||||
// id and groupId should not be overridden
|
||||
Simplify<Omit<MakeOverridesSerializable<AxisProps>, 'id' | 'groupId'>>
|
||||
>
|
||||
>;
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
*/
|
||||
|
||||
import { CustomAnnotationTooltip } from '@elastic/charts';
|
||||
import { AllowedSettingsOverrides } from '@kbn/charts-plugin/common';
|
||||
import {
|
||||
AvailableAnnotationIcon,
|
||||
ManualPointEventAnnotationArgs,
|
||||
} from '@kbn/event-annotation-plugin/common';
|
||||
import { XY_VIS_RENDERER } from '../constants';
|
||||
import { XYProps } from './expression_functions';
|
||||
import type { AllowedXYOverrides, XYProps } from './expression_functions';
|
||||
|
||||
export interface XYChartProps {
|
||||
args: XYProps;
|
||||
|
@ -20,6 +21,7 @@ export interface XYChartProps {
|
|||
syncCursor: boolean;
|
||||
syncColors: boolean;
|
||||
canNavigateToLens?: boolean;
|
||||
overrides?: AllowedXYOverrides & AllowedSettingsOverrides;
|
||||
}
|
||||
|
||||
export interface XYRender {
|
||||
|
|
|
@ -607,19 +607,22 @@ exports[`XYChart component it renders area 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -1607,19 +1610,22 @@ exports[`XYChart component it renders bar 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -2607,19 +2613,22 @@ exports[`XYChart component it renders horizontal bar 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -3607,19 +3616,22 @@ exports[`XYChart component it renders line 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -4607,19 +4619,22 @@ exports[`XYChart component it renders stacked area 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -5607,19 +5622,22 @@ exports[`XYChart component it renders stacked bar 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -6607,19 +6625,22 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -7637,19 +7658,22 @@ exports[`XYChart component split chart should render split chart if both, splitR
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -8875,19 +8899,22 @@ exports[`XYChart component split chart should render split chart if splitColumnA
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
@ -10106,19 +10133,22 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
Array [
|
||||
Object {
|
||||
"background": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
"barSeriesStyle": Object {},
|
||||
"chartMargins": Object {},
|
||||
"legend": Object {
|
||||
"labelOptions": Object {
|
||||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
|
|
|
@ -765,7 +765,7 @@ describe('XYChart component', () => {
|
|||
<XYChart {...defaultProps} args={{ ...args, ...markSizeRatioArg }} />
|
||||
);
|
||||
expect(component.find(Settings).at(0).prop('theme')).toEqual(
|
||||
expect.objectContaining(markSizeRatioArg)
|
||||
expect.arrayContaining([expect.objectContaining(markSizeRatioArg)])
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -3468,4 +3468,84 @@ describe('XYChart component', () => {
|
|||
expect(headerFormatter).not.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrides', () => {
|
||||
it('should work for settings component', () => {
|
||||
const { args } = sampleArgs();
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
{...defaultProps}
|
||||
args={{
|
||||
...args,
|
||||
layers: [{ ...(args.layers[0] as DataLayerConfig), seriesType: 'line' }],
|
||||
}}
|
||||
overrides={{ settings: { onBrushEnd: 'ignore', ariaUseDefaultSummary: true } }}
|
||||
/>
|
||||
);
|
||||
|
||||
const settingsComponent = component.find(Settings);
|
||||
expect(settingsComponent.prop('onBrushEnd')).toBeUndefined();
|
||||
expect(settingsComponent.prop('ariaUseDefaultSummary')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should work for all axes components', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const layer = args.layers[0] as DataLayerConfig;
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
{...defaultProps}
|
||||
args={{
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...layer,
|
||||
accessors: ['a', 'b'],
|
||||
decorations: [
|
||||
{
|
||||
type: 'dataDecorationConfig',
|
||||
forAccessor: 'a',
|
||||
axisId: '1',
|
||||
},
|
||||
{
|
||||
type: 'dataDecorationConfig',
|
||||
forAccessor: 'b',
|
||||
axisId: '2',
|
||||
},
|
||||
],
|
||||
table: dataWithoutFormats,
|
||||
},
|
||||
],
|
||||
yAxisConfigs: [
|
||||
{
|
||||
type: 'yAxisConfig',
|
||||
id: '1',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
type: 'yAxisConfig',
|
||||
id: '2',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
}}
|
||||
overrides={{
|
||||
settings: { onBrushEnd: 'ignore', ariaUseDefaultSummary: true },
|
||||
axisX: { showOverlappingTicks: true },
|
||||
axisLeft: { showOverlappingTicks: true },
|
||||
axisRight: { showOverlappingTicks: true },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const axes = component.find(Axis);
|
||||
expect(axes).toHaveLength(3);
|
||||
if (Array.isArray(axes)) {
|
||||
for (const axis of axes) {
|
||||
expect(axis.prop('showOverlappingTicks').toEqual(true));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
Tooltip,
|
||||
XYChartSeriesIdentifier,
|
||||
TooltipValue,
|
||||
SettingsProps,
|
||||
} from '@elastic/charts';
|
||||
import { partition } from 'lodash';
|
||||
import { IconType } from '@elastic/eui';
|
||||
|
@ -51,6 +52,7 @@ import {
|
|||
LegendSizeToPixels,
|
||||
} from '@kbn/visualizations-plugin/common/constants';
|
||||
import { PersistedState } from '@kbn/visualizations-plugin/public';
|
||||
import { getOverridesFor } from '@kbn/chart-expressions-common';
|
||||
import type {
|
||||
FilterEvent,
|
||||
BrushEvent,
|
||||
|
@ -226,6 +228,7 @@ export function XYChart({
|
|||
renderComplete,
|
||||
uiState,
|
||||
timeFormat,
|
||||
overrides,
|
||||
}: XYChartRenderProps) {
|
||||
const {
|
||||
legend,
|
||||
|
@ -792,6 +795,11 @@ export function XYChart({
|
|||
// enable the tooltip actions only if there is at least one splitAccessor to the dataLayer
|
||||
const hasTooltipActions = dataLayers.some((dataLayer) => dataLayer.splitAccessors) && interactive;
|
||||
|
||||
const { theme: settingsThemeOverrides = {}, ...settingsOverrides } = getOverridesFor(
|
||||
overrides,
|
||||
'settings'
|
||||
) as Partial<SettingsProps>;
|
||||
|
||||
return (
|
||||
<div css={chartContainerStyle}>
|
||||
{showLegend !== undefined && uiState && (
|
||||
|
@ -886,31 +894,36 @@ export function XYChart({
|
|||
showLegend={showLegend}
|
||||
legendPosition={legend?.isInside ? legendInsideParams : legend.position}
|
||||
legendSize={LegendSizeToPixels[legend.legendSize ?? DEFAULT_LEGEND_SIZE]}
|
||||
theme={{
|
||||
...chartTheme,
|
||||
barSeriesStyle: {
|
||||
...chartTheme.barSeriesStyle,
|
||||
...valueLabelsStyling,
|
||||
theme={[
|
||||
{
|
||||
...chartTheme,
|
||||
barSeriesStyle: {
|
||||
...chartTheme.barSeriesStyle,
|
||||
...valueLabelsStyling,
|
||||
},
|
||||
background: {
|
||||
color: undefined, // removes background for embeddables
|
||||
},
|
||||
legend: {
|
||||
labelOptions: { maxLines: legend.shouldTruncate ? legend?.maxLines ?? 1 : 0 },
|
||||
},
|
||||
// if not title or labels are shown for axes, add some padding if required by reference line markers
|
||||
chartMargins: {
|
||||
...chartTheme.chartPaddings,
|
||||
...computeChartMargins(
|
||||
linesPaddings,
|
||||
{ ...tickLabelsVisibilitySettings, x: xAxisConfig?.showLabels },
|
||||
{ ...axisTitlesVisibilitySettings, x: xAxisConfig?.showTitle },
|
||||
yAxesMap,
|
||||
shouldRotate
|
||||
),
|
||||
},
|
||||
markSizeRatio: args.markSizeRatio,
|
||||
},
|
||||
background: {
|
||||
color: undefined, // removes background for embeddables
|
||||
},
|
||||
legend: {
|
||||
labelOptions: { maxLines: legend.shouldTruncate ? legend?.maxLines ?? 1 : 0 },
|
||||
},
|
||||
// if not title or labels are shown for axes, add some padding if required by reference line markers
|
||||
chartMargins: {
|
||||
...chartTheme.chartPaddings,
|
||||
...computeChartMargins(
|
||||
linesPaddings,
|
||||
{ ...tickLabelsVisibilitySettings, x: xAxisConfig?.showLabels },
|
||||
{ ...axisTitlesVisibilitySettings, x: xAxisConfig?.showTitle },
|
||||
yAxesMap,
|
||||
shouldRotate
|
||||
),
|
||||
},
|
||||
markSizeRatio: args.markSizeRatio,
|
||||
}}
|
||||
...(Array.isArray(settingsThemeOverrides)
|
||||
? settingsThemeOverrides
|
||||
: [settingsThemeOverrides]),
|
||||
]}
|
||||
baseTheme={chartBaseTheme}
|
||||
allowBrushingLastHistogramBin={isTimeViz}
|
||||
rotation={shouldRotate ? 90 : 0}
|
||||
|
@ -940,6 +953,7 @@ export function XYChart({
|
|||
}
|
||||
: undefined
|
||||
}
|
||||
{...settingsOverrides}
|
||||
/>
|
||||
<XYCurrentTime
|
||||
enabled={Boolean(args.addTimeMarker && isTimeViz)}
|
||||
|
@ -968,6 +982,7 @@ export function XYChart({
|
|||
showOverlappingLabels={xAxisConfig?.showOverlappingLabels}
|
||||
showDuplicatedTicks={xAxisConfig?.showDuplicates}
|
||||
timeAxisLayerCount={shouldUseNewTimeAxis ? 2 : 0}
|
||||
{...getOverridesFor(overrides, 'axisX')}
|
||||
/>
|
||||
{isSplitChart && splitTable && (
|
||||
<SplitChart
|
||||
|
@ -999,6 +1014,10 @@ export function XYChart({
|
|||
domain={getYAxisDomain(axis)}
|
||||
showOverlappingLabels={axis.showOverlappingLabels}
|
||||
showDuplicatedTicks={axis.showDuplicates}
|
||||
{...getOverridesFor(
|
||||
overrides,
|
||||
/left/i.test(axis.groupId) ? 'axisLeft' : 'axisRight'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -18,7 +18,7 @@ export type {
|
|||
export { palette, systemPalette } from './expressions/palette';
|
||||
|
||||
export { paletteIds, defaultCustomColors } from './constants';
|
||||
export type { ColorSchema, RawColorSchema, ColorMap } from './static';
|
||||
export type { AllowedSettingsOverrides, ColorSchema, RawColorSchema, ColorMap } from './static';
|
||||
export {
|
||||
ColorSchemas,
|
||||
vislibColorMaps,
|
||||
|
|
|
@ -18,3 +18,4 @@ export {
|
|||
|
||||
export { ColorMode, LabelRotation, defaultCountLabel } from './components';
|
||||
export * from './styles';
|
||||
export type { AllowedSettingsOverrides } from './overrides';
|
||||
|
|
9
src/plugins/charts/common/static/overrides/index.ts
Normal file
9
src/plugins/charts/common/static/overrides/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 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 './settings';
|
45
src/plugins/charts/common/static/overrides/settings.ts
Normal file
45
src/plugins/charts/common/static/overrides/settings.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { SettingsProps } from '@elastic/charts';
|
||||
|
||||
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
|
||||
|
||||
// Overrides should not expose Functions, React nodes and children props
|
||||
// So filter out any type which is not serializable
|
||||
export type MakeOverridesSerializable<T> = {
|
||||
[KeyType in keyof T]: NonNullable<T[KeyType]> extends Function
|
||||
? // cannot use boolean here as it would be challenging to distinguish
|
||||
// between a "native" boolean props and a disabled callback
|
||||
// so use a specific keyword
|
||||
'ignore'
|
||||
: // be careful here to not filter out string/number types
|
||||
NonNullable<T[KeyType]> extends React.ReactChildren | React.ReactElement
|
||||
? never
|
||||
: // make it recursive
|
||||
NonNullable<T[KeyType]> extends object
|
||||
? MakeOverridesSerializable<T[KeyType]>
|
||||
: NonNullable<T[KeyType]>;
|
||||
};
|
||||
|
||||
export type AllowedSettingsOverrides = Partial<
|
||||
Record<
|
||||
'settings',
|
||||
Simplify<
|
||||
MakeOverridesSerializable<
|
||||
Omit<
|
||||
SettingsProps,
|
||||
| 'onRenderChange'
|
||||
| 'onPointerUpdate'
|
||||
| 'orderOrdinalBinsBy'
|
||||
| 'baseTheme'
|
||||
| 'legendColorPicker'
|
||||
>
|
||||
>
|
||||
>
|
||||
>
|
||||
>;
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Position, RecursivePartial, AxisStyle } from '@elastic/charts';
|
||||
import { Position, type RecursivePartial, type AxisStyle } from '@elastic/charts';
|
||||
|
||||
export const MULTILAYER_TIME_AXIS_STYLE: RecursivePartial<AxisStyle> = {
|
||||
tickLabel: {
|
||||
|
|
|
@ -36,10 +36,18 @@ import type {
|
|||
RangeIndexPatternColumn,
|
||||
PieVisualizationState,
|
||||
MedianIndexPatternColumn,
|
||||
MetricVisualizationState,
|
||||
} from '@kbn/lens-plugin/public';
|
||||
import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { CodeEditor, HJsonLang, KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import type { StartDependencies } from './plugin';
|
||||
import {
|
||||
AllOverrides,
|
||||
AttributesMenu,
|
||||
LensAttributesByType,
|
||||
OverridesMenu,
|
||||
PanelMenu,
|
||||
} from './controls';
|
||||
|
||||
type RequiredType = 'date' | 'string' | 'number';
|
||||
type FieldsMap = Record<RequiredType, string>;
|
||||
|
@ -78,6 +86,13 @@ function getColumnFor(type: RequiredType, fieldName: string, isBucketed: boolean
|
|||
maxBars: 'auto',
|
||||
format: undefined,
|
||||
parentFormat: undefined,
|
||||
ranges: [
|
||||
{
|
||||
from: 0,
|
||||
to: 1000,
|
||||
label: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
} as RangeIndexPatternColumn;
|
||||
}
|
||||
|
@ -162,12 +177,12 @@ function getBaseAttributes(
|
|||
|
||||
// Generate a Lens state based on some app-specific input parameters.
|
||||
// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code.
|
||||
function getLensAttributes(
|
||||
function getLensAttributesXY(
|
||||
defaultIndexPattern: DataView,
|
||||
fields: FieldsMap,
|
||||
chartType: 'bar_stacked' | 'line' | 'area',
|
||||
chartType: XYState['preferredSeriesType'],
|
||||
color: string
|
||||
): TypedLensByValueInput['attributes'] {
|
||||
): LensAttributesByType<'lnsXY'> {
|
||||
const baseAttributes = getBaseAttributes(defaultIndexPattern, fields);
|
||||
|
||||
const xyConfig: XYState = {
|
||||
|
@ -203,7 +218,7 @@ function getLensAttributes(
|
|||
function getLensAttributesHeatmap(
|
||||
defaultIndexPattern: DataView,
|
||||
fields: FieldsMap
|
||||
): TypedLensByValueInput['attributes'] {
|
||||
): LensAttributesByType<'lnsHeatmap'> {
|
||||
const initialType = getInitialType(defaultIndexPattern);
|
||||
const dataLayer = getDataLayer(initialType, fields[initialType]);
|
||||
const heatmapDataLayer = {
|
||||
|
@ -252,7 +267,7 @@ function getLensAttributesHeatmap(
|
|||
function getLensAttributesDatatable(
|
||||
defaultIndexPattern: DataView,
|
||||
fields: FieldsMap
|
||||
): TypedLensByValueInput['attributes'] {
|
||||
): LensAttributesByType<'lnsDatatable'> {
|
||||
const initialType = getInitialType(defaultIndexPattern);
|
||||
const baseAttributes = getBaseAttributes(defaultIndexPattern, fields, initialType);
|
||||
|
||||
|
@ -274,8 +289,9 @@ function getLensAttributesDatatable(
|
|||
|
||||
function getLensAttributesGauge(
|
||||
defaultIndexPattern: DataView,
|
||||
fields: FieldsMap
|
||||
): TypedLensByValueInput['attributes'] {
|
||||
fields: FieldsMap,
|
||||
shape: GaugeVisualizationState['shape'] = 'horizontalBullet'
|
||||
): LensAttributesByType<'lnsGauge'> {
|
||||
const dataLayer = getDataLayer('number', fields.number, false);
|
||||
const gaugeDataLayer = {
|
||||
columnOrder: ['col1'],
|
||||
|
@ -288,7 +304,7 @@ function getLensAttributesGauge(
|
|||
const gaugeConfig: GaugeVisualizationState = {
|
||||
layerId: 'layer1',
|
||||
layerType: 'data',
|
||||
shape: 'horizontalBullet',
|
||||
shape,
|
||||
ticksPosition: 'auto',
|
||||
labelMajorMode: 'auto',
|
||||
metricAccessor: 'col1',
|
||||
|
@ -306,7 +322,7 @@ function getLensAttributesGauge(
|
|||
function getLensAttributesPartition(
|
||||
defaultIndexPattern: DataView,
|
||||
fields: FieldsMap
|
||||
): TypedLensByValueInput['attributes'] {
|
||||
): LensAttributesByType<'lnsPie'> {
|
||||
const baseAttributes = getBaseAttributes(defaultIndexPattern, fields, 'number');
|
||||
const pieConfig: PieVisualizationState = {
|
||||
layers: [
|
||||
|
@ -317,7 +333,7 @@ function getLensAttributesPartition(
|
|||
layerType: 'data',
|
||||
numberDisplay: 'percent',
|
||||
categoryDisplay: 'default',
|
||||
legendDisplay: 'default',
|
||||
legendDisplay: 'show',
|
||||
},
|
||||
],
|
||||
shape: 'pie',
|
||||
|
@ -332,6 +348,30 @@ function getLensAttributesPartition(
|
|||
};
|
||||
}
|
||||
|
||||
function getLensAttributesMetric(
|
||||
defaultIndexPattern: DataView,
|
||||
fields: FieldsMap,
|
||||
color: string
|
||||
): LensAttributesByType<'lnsMetric'> {
|
||||
const dataLayer = getDataLayer('string', fields.number, true);
|
||||
const baseAttributes = getBaseAttributes(defaultIndexPattern, fields, 'number', dataLayer);
|
||||
const metricConfig: MetricVisualizationState = {
|
||||
layerId: 'layer1',
|
||||
layerType: 'data',
|
||||
metricAccessor: 'col2',
|
||||
color,
|
||||
breakdownByAccessor: 'col1',
|
||||
};
|
||||
return {
|
||||
...baseAttributes,
|
||||
visualizationType: 'lnsMetric',
|
||||
state: {
|
||||
...baseAttributes.state,
|
||||
visualization: metricConfig,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getFieldsByType(dataView: DataView) {
|
||||
const aggregatableFields = dataView.fields.filter((f) => f.aggregatable);
|
||||
const fields: Partial<FieldsMap> = {
|
||||
|
@ -350,10 +390,6 @@ function getFieldsByType(dataView: DataView) {
|
|||
return fields as FieldsMap;
|
||||
}
|
||||
|
||||
function isXYChart(attributes: TypedLensByValueInput['attributes']) {
|
||||
return attributes.visualizationType === 'lnsXY';
|
||||
}
|
||||
|
||||
function checkAndParseSO(newSO: string) {
|
||||
try {
|
||||
return JSON.parse(newSO) as TypedLensByValueInput['attributes'];
|
||||
|
@ -394,23 +430,29 @@ export const App = (props: {
|
|||
to: 'now',
|
||||
});
|
||||
|
||||
const initialColor = '#D6BF57';
|
||||
|
||||
const defaultCharts = [
|
||||
{
|
||||
id: 'bar_stacked',
|
||||
attributes: getLensAttributes(props.defaultDataView, fields, 'bar_stacked', 'green'),
|
||||
attributes: getLensAttributesXY(props.defaultDataView, fields, 'bar_stacked', initialColor),
|
||||
},
|
||||
{
|
||||
id: 'line',
|
||||
attributes: getLensAttributes(props.defaultDataView, fields, 'line', 'green'),
|
||||
attributes: getLensAttributesXY(props.defaultDataView, fields, 'line', initialColor),
|
||||
},
|
||||
{
|
||||
id: 'area',
|
||||
attributes: getLensAttributes(props.defaultDataView, fields, 'area', 'green'),
|
||||
attributes: getLensAttributesXY(props.defaultDataView, fields, 'area', initialColor),
|
||||
},
|
||||
{ id: 'pie', attributes: getLensAttributesPartition(props.defaultDataView, fields) },
|
||||
{ id: 'table', attributes: getLensAttributesDatatable(props.defaultDataView, fields) },
|
||||
{ id: 'heatmap', attributes: getLensAttributesHeatmap(props.defaultDataView, fields) },
|
||||
{ id: 'gauge', attributes: getLensAttributesGauge(props.defaultDataView, fields) },
|
||||
{
|
||||
id: 'metric',
|
||||
attributes: getLensAttributesMetric(props.defaultDataView, fields, initialColor),
|
||||
},
|
||||
];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const charts = useMemo(() => [...defaultCharts, ...loadedCharts], [loadedCharts]);
|
||||
|
@ -429,11 +471,13 @@ export const App = (props: {
|
|||
const newAttributes = JSON.stringify(newChart.attributes, null, 2);
|
||||
currentSO.current = newAttributes;
|
||||
saveValidSO(newAttributes);
|
||||
// clear the overrides
|
||||
setOverrides(undefined);
|
||||
},
|
||||
[charts]
|
||||
);
|
||||
|
||||
const currentAttributes = useMemo(() => {
|
||||
const currentAttributes: TypedLensByValueInput['attributes'] = useMemo(() => {
|
||||
try {
|
||||
return JSON.parse(currentSO.current);
|
||||
} catch (e) {
|
||||
|
@ -442,10 +486,11 @@ export const App = (props: {
|
|||
}, [currentValid, currentSO]);
|
||||
|
||||
const isDisabled = !currentAttributes;
|
||||
const isColorDisabled = isDisabled || !isXYChart(currentAttributes);
|
||||
|
||||
useDebounce(() => setErrorDebounced(hasParsingError), 500, [hasParsingError]);
|
||||
|
||||
const [overrides, setOverrides] = useState<AllOverrides | undefined>();
|
||||
|
||||
return (
|
||||
<KibanaContextProvider services={{ uiSettings: props.core.uiSettings }}>
|
||||
<EuiPageTemplate fullHeight template="empty">
|
||||
|
@ -475,29 +520,28 @@ export const App = (props: {
|
|||
<EuiSpacer />
|
||||
<EuiFlexGroup wrap>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="lns-example-change-color"
|
||||
onClick={() => {
|
||||
const newColor = `rgb(${[1, 2, 3].map(() =>
|
||||
Math.floor(Math.random() * 256)
|
||||
)})`;
|
||||
const newAttributes = JSON.stringify(
|
||||
getLensAttributes(
|
||||
props.defaultDataView,
|
||||
fields,
|
||||
currentAttributes.state.visualization.preferredSeriesType,
|
||||
newColor
|
||||
),
|
||||
null,
|
||||
2
|
||||
);
|
||||
currentSO.current = newAttributes;
|
||||
saveValidSO(newAttributes);
|
||||
}}
|
||||
isDisabled={isColorDisabled}
|
||||
>
|
||||
Change color
|
||||
</EuiButton>
|
||||
<AttributesMenu
|
||||
currentSO={currentSO}
|
||||
currentAttributes={currentAttributes}
|
||||
saveValidSO={saveValidSO}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<OverridesMenu
|
||||
currentAttributes={currentAttributes}
|
||||
overrides={overrides}
|
||||
setOverrides={setOverrides}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<PanelMenu
|
||||
enableTriggers={enableTriggers}
|
||||
toggleTriggers={toggleTriggers}
|
||||
enableDefaultAction={enableDefaultAction}
|
||||
setEnableDefaultAction={setEnableDefaultAction}
|
||||
enableExtraAction={enableExtraAction}
|
||||
setEnableExtraAction={setEnableExtraAction}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
|
@ -552,43 +596,7 @@ export const App = (props: {
|
|||
);
|
||||
}}
|
||||
>
|
||||
Edit in Lens (new tab)
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
aria-label="Enable triggers"
|
||||
data-test-subj="lns-example-triggers"
|
||||
isDisabled={isDisabled}
|
||||
onClick={() => {
|
||||
toggleTriggers((prevState) => !prevState);
|
||||
}}
|
||||
>
|
||||
{enableTriggers ? 'Disable triggers' : 'Enable triggers'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
aria-label="Enable extra action"
|
||||
data-test-subj="lns-example-extra-action"
|
||||
isDisabled={isDisabled}
|
||||
onClick={() => {
|
||||
setEnableExtraAction((prevState) => !prevState);
|
||||
}}
|
||||
>
|
||||
{enableExtraAction ? 'Disable extra action' : 'Enable extra action'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
aria-label="Enable default actions"
|
||||
data-test-subj="lns-example-default-action"
|
||||
isDisabled={isDisabled}
|
||||
onClick={() => {
|
||||
setEnableDefaultAction((prevState) => !prevState);
|
||||
}}
|
||||
>
|
||||
{enableDefaultAction ? 'Disable default action' : 'Enable default action'}
|
||||
Open in Lens (new tab)
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -602,6 +610,7 @@ export const App = (props: {
|
|||
style={{ height: 500 }}
|
||||
timeRange={time}
|
||||
attributes={currentAttributes}
|
||||
overrides={overrides}
|
||||
onLoad={(val) => {
|
||||
setIsLoading(val);
|
||||
}}
|
||||
|
|
597
x-pack/examples/testing_embedded_lens/public/controls.tsx
Normal file
597
x-pack/examples/testing_embedded_lens/public/controls.tsx
Normal file
|
@ -0,0 +1,597 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiColorPicker,
|
||||
EuiFormRow,
|
||||
EuiPopover,
|
||||
useColorPickerState,
|
||||
EuiSwitch,
|
||||
EuiNotificationBadge,
|
||||
EuiCodeBlock,
|
||||
EuiIcon,
|
||||
EuiToolTip,
|
||||
EuiPopoverTitle,
|
||||
} from '@elastic/eui';
|
||||
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||
|
||||
export type LensAttributesByType<VizType> = Extract<
|
||||
TypedLensByValueInput['attributes'],
|
||||
{ visualizationType: VizType }
|
||||
>;
|
||||
|
||||
function isXYChart(
|
||||
attributes: TypedLensByValueInput['attributes']
|
||||
): attributes is LensAttributesByType<'lnsXY'> {
|
||||
return attributes.visualizationType === 'lnsXY';
|
||||
}
|
||||
|
||||
function isPieChart(
|
||||
attributes: TypedLensByValueInput['attributes']
|
||||
): attributes is LensAttributesByType<'lnsPie'> {
|
||||
return attributes.visualizationType === 'lnsPie';
|
||||
}
|
||||
|
||||
function isHeatmapChart(
|
||||
attributes: TypedLensByValueInput['attributes']
|
||||
): attributes is LensAttributesByType<'lnsHeatmap'> {
|
||||
return attributes.visualizationType === 'lnsHeatmap';
|
||||
}
|
||||
|
||||
function isDatatable(
|
||||
attributes: TypedLensByValueInput['attributes']
|
||||
): attributes is LensAttributesByType<'lnsDatatable'> {
|
||||
return attributes.visualizationType === 'lnsDatatable';
|
||||
}
|
||||
|
||||
function isGaugeChart(
|
||||
attributes: TypedLensByValueInput['attributes']
|
||||
): attributes is LensAttributesByType<'lnsGauge'> {
|
||||
return attributes.visualizationType === 'lnsGauge';
|
||||
}
|
||||
|
||||
function isMetricChart(
|
||||
attributes: TypedLensByValueInput['attributes']
|
||||
): attributes is LensAttributesByType<'lnsMetric'> {
|
||||
return attributes.visualizationType === 'lnsMetric';
|
||||
}
|
||||
|
||||
function isSupportedChart(attributes: TypedLensByValueInput['attributes']) {
|
||||
return (
|
||||
isXYChart(attributes) ||
|
||||
isPieChart(attributes) ||
|
||||
isHeatmapChart(attributes) ||
|
||||
isGaugeChart(attributes) ||
|
||||
isMetricChart(attributes)
|
||||
);
|
||||
}
|
||||
|
||||
function mergeOverrides(
|
||||
currentOverrides: AllOverrides | undefined,
|
||||
newOverrides: AllOverrides | undefined,
|
||||
defaultOverrides: AllOverrides
|
||||
): AllOverrides | undefined {
|
||||
if (currentOverrides == null || isEqual(currentOverrides, defaultOverrides)) {
|
||||
return newOverrides;
|
||||
}
|
||||
if (newOverrides == null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(currentOverrides)
|
||||
.map(([key, value]) => {
|
||||
if (!(key in defaultOverrides)) {
|
||||
return [key, value];
|
||||
}
|
||||
// @ts-expect-error
|
||||
if (isEqual(currentOverrides[key], defaultOverrides[key])) {
|
||||
return [];
|
||||
}
|
||||
const newObject: Partial<AllOverrides[keyof AllOverrides]> = {};
|
||||
// @ts-expect-error
|
||||
for (const [innerKey, innerValue] of Object.entries(currentOverrides[key])) {
|
||||
// @ts-expect-error
|
||||
if (!(innerKey in defaultOverrides[key])) {
|
||||
// @ts-expect-error
|
||||
newObject[innerKey] = innerValue;
|
||||
}
|
||||
}
|
||||
return [key, newObject];
|
||||
})
|
||||
.filter((arr) => arr.length)
|
||||
);
|
||||
}
|
||||
return {
|
||||
...currentOverrides,
|
||||
...newOverrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function OverrideSwitch({
|
||||
rowLabel,
|
||||
controlLabel,
|
||||
value,
|
||||
override,
|
||||
setOverrideValue,
|
||||
helpText,
|
||||
}: {
|
||||
rowLabel: string;
|
||||
controlLabel: string;
|
||||
helpText?: string;
|
||||
value: AllOverrides | undefined;
|
||||
override: AllOverrides;
|
||||
setOverrideValue: (v: AllOverrides | undefined) => void;
|
||||
}) {
|
||||
// check if value contains an object with the same structure as the default override
|
||||
const rootKey = Object.keys(override)[0] as keyof AllOverrides;
|
||||
const overridePath = [
|
||||
rootKey,
|
||||
Object.keys(override[rootKey] || {})[0] as keyof AllOverrides[keyof AllOverrides],
|
||||
];
|
||||
const hasOverrideEnabled = Boolean(
|
||||
value && overridePath[0] in value && overridePath[1] in value[overridePath[0]]!
|
||||
);
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<EuiToolTip
|
||||
content={<CodeExample propName="overrides" code={JSON.stringify(override, null, 2)} />}
|
||||
position="right"
|
||||
>
|
||||
<span>
|
||||
{rowLabel} <EuiIcon type="questionInCircle" color="subdued" />
|
||||
</span>
|
||||
</EuiToolTip>
|
||||
}
|
||||
helpText={helpText}
|
||||
display="columnCompressedSwitch"
|
||||
hasChildLabel={false}
|
||||
>
|
||||
<EuiSwitch
|
||||
label={controlLabel}
|
||||
name="switch"
|
||||
checked={hasOverrideEnabled}
|
||||
onChange={() => {
|
||||
const finalOverrides = mergeOverrides(
|
||||
value,
|
||||
hasOverrideEnabled ? undefined : override,
|
||||
override
|
||||
);
|
||||
setOverrideValue(finalOverrides);
|
||||
}}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeExample({ propName, code }: { propName: string; code: string }) {
|
||||
return (
|
||||
<EuiCodeBlock language="jsx" paddingSize="none">
|
||||
{`
|
||||
<LensEmbeddable ${propName}={${code}} />
|
||||
`}
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
}
|
||||
|
||||
export function AttributesMenu({
|
||||
currentAttributes,
|
||||
currentSO,
|
||||
saveValidSO,
|
||||
}: {
|
||||
currentAttributes: TypedLensByValueInput['attributes'];
|
||||
currentSO: React.MutableRefObject<string>;
|
||||
saveValidSO: (attr: string) => void;
|
||||
}) {
|
||||
const [attributesPopoverOpen, setAttributesPopoverOpen] = useState(false);
|
||||
const [color, setColor, errors] = useColorPickerState('#D6BF57');
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton
|
||||
data-test-subj="lns-example-change-attributes"
|
||||
onClick={() => setAttributesPopoverOpen(!attributesPopoverOpen)}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
color="primary"
|
||||
isDisabled={!isSupportedChart(currentAttributes)}
|
||||
>
|
||||
Lens Attributes
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={attributesPopoverOpen}
|
||||
closePopover={() => setAttributesPopoverOpen(false)}
|
||||
>
|
||||
<div style={{ width: 300 }}>
|
||||
{isXYChart(currentAttributes) ? (
|
||||
<EuiFormRow label="Pick color" display="columnCompressed">
|
||||
<EuiColorPicker
|
||||
onChange={(newColor, output) => {
|
||||
setColor(newColor, output);
|
||||
// for sake of semplicity of this example change it locally and then shallow copy it
|
||||
const dataLayer = currentAttributes.state.visualization.layers[0];
|
||||
if ('yConfig' in dataLayer && dataLayer.yConfig) {
|
||||
dataLayer.yConfig[0].color = newColor;
|
||||
// this will make a string copy of it
|
||||
const newAttributes = JSON.stringify(currentAttributes, null, 2);
|
||||
currentSO.current = newAttributes;
|
||||
saveValidSO(newAttributes);
|
||||
}
|
||||
}}
|
||||
color={color}
|
||||
isInvalid={!!errors}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
{isMetricChart(currentAttributes) ? (
|
||||
<EuiFormRow label="Pick color" display="columnCompressed">
|
||||
<EuiColorPicker
|
||||
onChange={(newColor, output) => {
|
||||
setColor(newColor, output);
|
||||
// for sake of semplicity of this example change it locally and then shallow copy it
|
||||
currentAttributes.state.visualization.color = newColor;
|
||||
// this will make a string copy of it
|
||||
const newAttributes = JSON.stringify(currentAttributes, null, 2);
|
||||
currentSO.current = newAttributes;
|
||||
saveValidSO(newAttributes);
|
||||
}}
|
||||
color={color}
|
||||
isInvalid={!!errors}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
{isPieChart(currentAttributes) ? (
|
||||
<EuiFormRow label="Show values" display="columnCompressedSwitch">
|
||||
<EuiSwitch
|
||||
label="As percentage"
|
||||
name="switch"
|
||||
checked={currentAttributes.state.visualization.layers[0].numberDisplay === 'percent'}
|
||||
onChange={() => {
|
||||
currentAttributes.state.visualization.layers[0].numberDisplay =
|
||||
currentAttributes.state.visualization.layers[0].numberDisplay === 'percent'
|
||||
? 'value'
|
||||
: 'percent';
|
||||
// this will make a string copy of it
|
||||
const newAttributes = JSON.stringify(currentAttributes, null, 2);
|
||||
currentSO.current = newAttributes;
|
||||
saveValidSO(newAttributes);
|
||||
}}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
{isHeatmapChart(currentAttributes) ? (
|
||||
<EuiFormRow label="Show values" display="columnCompressedSwitch">
|
||||
<EuiSwitch
|
||||
label="As percentage"
|
||||
name="switch"
|
||||
checked={Boolean(currentAttributes.state.visualization.percentageMode)}
|
||||
onChange={() => {
|
||||
currentAttributes.state.visualization.percentageMode =
|
||||
!currentAttributes.state.visualization.percentageMode;
|
||||
// this will make a string copy of it
|
||||
const newAttributes = JSON.stringify(currentAttributes, null, 2);
|
||||
currentSO.current = newAttributes;
|
||||
saveValidSO(newAttributes);
|
||||
}}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
{isGaugeChart(currentAttributes) ? (
|
||||
<EuiFormRow label="Ticks visibility" display="columnCompressedSwitch">
|
||||
<EuiSwitch
|
||||
label="Show ticks"
|
||||
name="switch"
|
||||
checked={Boolean(currentAttributes.state.visualization.ticksPosition !== 'hidden')}
|
||||
onChange={() => {
|
||||
currentAttributes.state.visualization.ticksPosition =
|
||||
currentAttributes.state.visualization.ticksPosition === 'hidden'
|
||||
? 'auto'
|
||||
: 'hidden';
|
||||
// this will make a string copy of it
|
||||
const newAttributes = JSON.stringify(currentAttributes, null, 2);
|
||||
currentSO.current = newAttributes;
|
||||
saveValidSO(newAttributes);
|
||||
}}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
type XYOverride = Record<'axisX' | 'axisLeft' | 'axisRight', { hide: boolean }>;
|
||||
type PieOverride = Record<'partition', { fillOutside: boolean }>;
|
||||
type GaugeOverride = Record<'gauge', { subtype: 'goal'; angleStart: number; angleEnd: number }>;
|
||||
type SettingsOverride = Record<
|
||||
'settings',
|
||||
| { onBrushEnd: 'ignore' }
|
||||
| {
|
||||
theme: {
|
||||
heatmap: { xAxisLabel: { visible: boolean }; yAxisLabel: { visible: boolean } };
|
||||
};
|
||||
}
|
||||
| {
|
||||
theme: {
|
||||
metric: { border: string };
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
export type AllOverrides = Partial<XYOverride & PieOverride & SettingsOverride & GaugeOverride>;
|
||||
|
||||
export function OverridesMenu({
|
||||
currentAttributes,
|
||||
overrides,
|
||||
setOverrides,
|
||||
}: {
|
||||
currentAttributes: TypedLensByValueInput['attributes'];
|
||||
overrides: AllOverrides | undefined;
|
||||
setOverrides: (overrides: AllOverrides | undefined) => void;
|
||||
}) {
|
||||
const [overridesPopoverOpen, setOverridesPopoverOpen] = useState(false);
|
||||
const hasOverridesEnabled = Boolean(overrides) && !isDatatable(currentAttributes);
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton
|
||||
data-test-subj="lns-example-change-overrides"
|
||||
onClick={() => setOverridesPopoverOpen(!overridesPopoverOpen)}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
isDisabled={!isSupportedChart(currentAttributes)}
|
||||
>
|
||||
Overrides{' '}
|
||||
<EuiNotificationBadge color={hasOverridesEnabled ? 'accent' : 'subdued'}>
|
||||
{hasOverridesEnabled ? 'ON' : 'OFF'}
|
||||
</EuiNotificationBadge>
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={overridesPopoverOpen}
|
||||
closePopover={() => setOverridesPopoverOpen(false)}
|
||||
>
|
||||
<div style={{ width: 400 }}>
|
||||
<EuiPopoverTitle>Overrides</EuiPopoverTitle>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
Overrides are local to the Embeddable and forgotten when the visualization is open in
|
||||
the Editor. They should be used carefully for specific tweaks within the integration.
|
||||
</p>
|
||||
<p>
|
||||
There are mainly 2 use cases for overrides:
|
||||
<ul>
|
||||
<li>Specific styling/tuning feature missing in Lens</li>
|
||||
<li>Disable specific chart behaviour</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>Here's some examples:</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
{isXYChart(currentAttributes) ? (
|
||||
<OverrideSwitch
|
||||
override={{
|
||||
settings: { onBrushEnd: 'ignore' },
|
||||
}}
|
||||
value={overrides}
|
||||
setOverrideValue={setOverrides}
|
||||
rowLabel="Brush override"
|
||||
controlLabel="Disable brush action"
|
||||
helpText={`This override disables the brushing locally, via the special "ignore" value.`}
|
||||
/>
|
||||
) : null}
|
||||
{isHeatmapChart(currentAttributes) ? (
|
||||
<OverrideSwitch
|
||||
override={{
|
||||
settings: {
|
||||
theme: {
|
||||
heatmap: { xAxisLabel: { visible: false }, yAxisLabel: { visible: false } },
|
||||
},
|
||||
},
|
||||
}}
|
||||
value={overrides}
|
||||
setOverrideValue={setOverrides}
|
||||
rowLabel="Axis override"
|
||||
controlLabel="Hide all axes"
|
||||
helpText={`Heatmap axis override is set via the settings component.`}
|
||||
/>
|
||||
) : null}
|
||||
{isPieChart(currentAttributes) ? (
|
||||
<OverrideSwitch
|
||||
override={{
|
||||
partition: { fillOutside: true },
|
||||
}}
|
||||
value={overrides}
|
||||
setOverrideValue={setOverrides}
|
||||
rowLabel="Partition override"
|
||||
controlLabel="Label outsides"
|
||||
/>
|
||||
) : null}
|
||||
{isXYChart(currentAttributes) ? (
|
||||
<OverrideSwitch
|
||||
override={{
|
||||
axisX: { hide: true },
|
||||
axisLeft: { hide: true },
|
||||
axisRight: { hide: true },
|
||||
}}
|
||||
value={overrides}
|
||||
setOverrideValue={setOverrides}
|
||||
rowLabel="Axis override"
|
||||
controlLabel="Hide all axes"
|
||||
/>
|
||||
) : null}
|
||||
{isGaugeChart(currentAttributes) ? (
|
||||
<OverrideSwitch
|
||||
override={{
|
||||
gauge: {
|
||||
subtype: 'goal',
|
||||
angleStart: Math.PI + (Math.PI - (2 * Math.PI) / 2.5) / 2,
|
||||
angleEnd: -(Math.PI - (2 * Math.PI) / 2.5) / 2,
|
||||
},
|
||||
}}
|
||||
value={overrides}
|
||||
setOverrideValue={setOverrides}
|
||||
rowLabel="Shape override"
|
||||
controlLabel="Enable Arc shape"
|
||||
helpText="Note that this is used only for example purposes, the arc configuration has some conflicts with some Lens attributes."
|
||||
/>
|
||||
) : null}
|
||||
{isMetricChart(currentAttributes) ? (
|
||||
<OverrideSwitch
|
||||
override={{
|
||||
settings: {
|
||||
theme: {
|
||||
metric: {
|
||||
border: '#D65757',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
value={overrides}
|
||||
setOverrideValue={setOverrides}
|
||||
rowLabel="Metric override"
|
||||
controlLabel="Enable border color"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
export function PanelMenu({
|
||||
enableTriggers,
|
||||
toggleTriggers,
|
||||
enableDefaultAction,
|
||||
setEnableDefaultAction,
|
||||
enableExtraAction,
|
||||
setEnableExtraAction,
|
||||
}: {
|
||||
enableTriggers: boolean;
|
||||
enableDefaultAction: boolean;
|
||||
enableExtraAction: boolean;
|
||||
toggleTriggers: (v: boolean) => void;
|
||||
setEnableDefaultAction: (v: boolean) => void;
|
||||
setEnableExtraAction: (v: boolean) => void;
|
||||
}) {
|
||||
const [panelPopoverOpen, setPanelPopoverOpen] = useState(false);
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton
|
||||
data-test-subj="lns-example-change-overrides"
|
||||
onClick={() => setPanelPopoverOpen(!panelPopoverOpen)}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
>
|
||||
Embeddable settings
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={panelPopoverOpen}
|
||||
closePopover={() => setPanelPopoverOpen(false)}
|
||||
>
|
||||
<div style={{ width: 400 }}>
|
||||
<EuiPopoverTitle>Embeddable settings</EuiPopoverTitle>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
It is possible to control and customize how the Embeddables is shown, disabling the
|
||||
interactivity of the chart or filtering out default actions.
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiFormRow
|
||||
label="Enable triggers"
|
||||
display="columnCompressedSwitch"
|
||||
helpText="This setting controls the interactivity of the chart: when disabled the chart won't bubble any event on user action."
|
||||
>
|
||||
<EuiSwitch
|
||||
showLabel={false}
|
||||
label="Enable triggers"
|
||||
name="switch"
|
||||
checked={enableTriggers}
|
||||
onChange={() => {
|
||||
toggleTriggers(!enableTriggers);
|
||||
}}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label="Enable default action"
|
||||
display="columnCompressedSwitch"
|
||||
helpText="When disabled the default panel actions (i.e. CSV download)"
|
||||
>
|
||||
<EuiSwitch
|
||||
showLabel={false}
|
||||
label="Enable default action"
|
||||
name="switch"
|
||||
checked={enableDefaultAction}
|
||||
onChange={() => {
|
||||
setEnableDefaultAction(!enableDefaultAction);
|
||||
}}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer />
|
||||
<p>It is also possible to pass custom actions to the panel:</p>
|
||||
<EuiSpacer />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<EuiToolTip
|
||||
display="block"
|
||||
content={
|
||||
<CodeExample
|
||||
propName="extraActions"
|
||||
code={`[
|
||||
{
|
||||
id: 'testAction',
|
||||
type: 'link',
|
||||
getIconType: () => 'save',
|
||||
async isCompatible(
|
||||
context: ActionExecutionContext<object>
|
||||
): Promise<boolean> {
|
||||
return true;
|
||||
},
|
||||
execute: async (context: ActionExecutionContext<object>) => {
|
||||
alert('I am an extra action');
|
||||
return;
|
||||
},
|
||||
getDisplayName: () =>
|
||||
'Extra action',
|
||||
}
|
||||
]`}
|
||||
/>
|
||||
}
|
||||
position="right"
|
||||
>
|
||||
<span>
|
||||
Show custom action <EuiIcon type="questionInCircle" color="subdued" />
|
||||
</span>
|
||||
</EuiToolTip>
|
||||
}
|
||||
display="columnCompressedSwitch"
|
||||
helpText="Pass a consumer defined action to show in the panel context menu."
|
||||
>
|
||||
<EuiSwitch
|
||||
showLabel={false}
|
||||
label="Show custom action"
|
||||
name="switch"
|
||||
checked={enableExtraAction}
|
||||
onChange={() => {
|
||||
setEnableExtraAction(!enableExtraAction);
|
||||
}}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
|
@ -17,6 +17,10 @@ import { layerTypes } from './layer_types';
|
|||
import { CollapseFunction } from './expressions';
|
||||
|
||||
export type { OriginalColumn } from './expressions/map_to_columns';
|
||||
export type { AllowedPartitionOverrides } from '@kbn/expression-partition-vis-plugin/common';
|
||||
export type { AllowedSettingsOverrides } from '@kbn/charts-plugin/common';
|
||||
export type { AllowedGaugeOverrides } from '@kbn/expression-gauge-plugin/common';
|
||||
export type { AllowedXYOverrides } from '@kbn/expression-xy-plugin/common';
|
||||
|
||||
export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
|
||||
|
||||
|
|
|
@ -1677,4 +1677,69 @@ describe('embeddable', () => {
|
|||
expect(test.initializeSavedVis).toHaveBeenCalledTimes(2);
|
||||
expect(test.expressionRenderer).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should pass over the overrides as variables', async () => {
|
||||
const embeddable = new Embeddable(
|
||||
{
|
||||
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
|
||||
attributeService,
|
||||
data: dataMock,
|
||||
expressionRenderer,
|
||||
coreStart: {} as CoreStart,
|
||||
basePath,
|
||||
dataViews: {} as DataViewsContract,
|
||||
capabilities: {
|
||||
canSaveDashboards: true,
|
||||
canSaveVisualizations: true,
|
||||
discover: {},
|
||||
navLinks: {},
|
||||
},
|
||||
inspector: inspectorPluginMock.createStartContract(),
|
||||
getTrigger,
|
||||
theme: themeServiceMock.createStartContract(),
|
||||
visualizationMap: defaultVisualizationMap,
|
||||
datasourceMap: defaultDatasourceMap,
|
||||
injectFilterReferences: jest.fn(mockInjectFilterReferences),
|
||||
documentToExpression: () =>
|
||||
Promise.resolve({
|
||||
ast: {
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{ type: 'function', function: 'my', arguments: {} },
|
||||
{ type: 'function', function: 'expression', arguments: {} },
|
||||
],
|
||||
},
|
||||
indexPatterns: {},
|
||||
indexPatternRefs: [],
|
||||
}),
|
||||
uiSettings: { get: () => undefined } as unknown as IUiSettingsClient,
|
||||
},
|
||||
{
|
||||
timeRange: {
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
},
|
||||
overrides: {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
},
|
||||
} as LensEmbeddableInput
|
||||
);
|
||||
embeddable.render(mountpoint);
|
||||
|
||||
// wait one tick to give embeddable time to initialize
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(expressionRenderer).toHaveBeenCalledTimes(1);
|
||||
expect(expressionRenderer.mock.calls[0][0]!.variables).toEqual(
|
||||
expect.objectContaining({
|
||||
overrides: {
|
||||
settings: {
|
||||
onBrushEnd: 'ignore',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -102,6 +102,12 @@ import {
|
|||
UserMessagesDisplayLocationId,
|
||||
} from '../types';
|
||||
|
||||
import type {
|
||||
AllowedPartitionOverrides,
|
||||
AllowedSettingsOverrides,
|
||||
AllowedGaugeOverrides,
|
||||
AllowedXYOverrides,
|
||||
} from '../../common/types';
|
||||
import { getEditPath, DOC_TYPE } from '../../common/constants';
|
||||
import { LensAttributeService } from '../lens_attribute_service';
|
||||
import type { TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
|
@ -150,6 +156,18 @@ interface LensBaseEmbeddableInput extends EmbeddableInput {
|
|||
|
||||
export type LensByValueInput = {
|
||||
attributes: LensSavedObjectAttributes;
|
||||
/**
|
||||
* Overrides can tweak the style of the final embeddable and are executed at the end of the Lens rendering pipeline.
|
||||
* Each visualization type offers various type of overrides, per component (i.e. 'setting', 'axisX', 'partition', etc...)
|
||||
*
|
||||
* While it is not possible to pass function/callback/handlers to the renderer, it is possible to overwrite
|
||||
* the current behaviour by passing the "ignore" string to the override prop (i.e. onBrushEnd: "ignore" to stop brushing)
|
||||
*/
|
||||
overrides?:
|
||||
| AllowedSettingsOverrides
|
||||
| AllowedXYOverrides
|
||||
| AllowedPartitionOverrides
|
||||
| AllowedGaugeOverrides;
|
||||
} & LensBaseEmbeddableInput;
|
||||
|
||||
export type LensByReferenceInput = SavedObjectEmbeddableInput & LensBaseEmbeddableInput;
|
||||
|
@ -469,8 +487,18 @@ export class Embeddable
|
|||
const attributesOrSavedObjectId$ = input$.pipe(
|
||||
distinctUntilChanged((a, b) =>
|
||||
fastIsEqual(
|
||||
['attributes' in a && a.attributes, 'savedObjectId' in a && a.savedObjectId],
|
||||
['attributes' in b && b.attributes, 'savedObjectId' in b && b.savedObjectId]
|
||||
[
|
||||
'attributes' in a && a.attributes,
|
||||
'savedObjectId' in a && a.savedObjectId,
|
||||
'overrides' in a && a.overrides,
|
||||
'disableTriggers' in a && a.disableTriggers,
|
||||
],
|
||||
[
|
||||
'attributes' in b && b.attributes,
|
||||
'savedObjectId' in b && b.savedObjectId,
|
||||
'overrides' in b && b.overrides,
|
||||
'disableTriggers' in b && b.disableTriggers,
|
||||
]
|
||||
)
|
||||
),
|
||||
skip(1),
|
||||
|
@ -875,6 +903,7 @@ export class Embeddable
|
|||
variables={{
|
||||
embeddableTitle: this.getTitle(),
|
||||
...(input.palette ? { theme: { palette: input.palette } } : {}),
|
||||
...('overrides' in input ? { overrides: input.overrides } : {}),
|
||||
}}
|
||||
searchSessionId={this.getInput().searchSessionId}
|
||||
handleEvent={this.handleEvent}
|
||||
|
|
|
@ -24,9 +24,16 @@ import type { LensByReferenceInput, LensByValueInput } from './embeddable';
|
|||
import type { Document } from '../persistence';
|
||||
import type { FormBasedPersistedState } from '../datasources/form_based/types';
|
||||
import type { XYState } from '../visualizations/xy/types';
|
||||
import type { PieVisualizationState, LegacyMetricState } from '../../common/types';
|
||||
import type {
|
||||
PieVisualizationState,
|
||||
LegacyMetricState,
|
||||
AllowedGaugeOverrides,
|
||||
AllowedPartitionOverrides,
|
||||
AllowedSettingsOverrides,
|
||||
AllowedXYOverrides,
|
||||
} from '../../common/types';
|
||||
import type { DatatableVisualizationState } from '../visualizations/datatable/visualization';
|
||||
import type { MetricVisualizationState } from '../visualizations/metric/visualization';
|
||||
import type { MetricVisualizationState } from '../visualizations/metric/types';
|
||||
import type { HeatmapVisualizationState } from '../visualizations/heatmap/types';
|
||||
import type { GaugeVisualizationState } from '../visualizations/gauge/constants';
|
||||
|
||||
|
@ -47,16 +54,28 @@ type LensAttributes<TVisType, TVisState> = Omit<
|
|||
* Type-safe variant of by value embeddable input for Lens.
|
||||
* This can be used to hardcode certain Lens chart configurations within another app.
|
||||
*/
|
||||
export type TypedLensByValueInput = Omit<LensByValueInput, 'attributes'> & {
|
||||
export type TypedLensByValueInput = Omit<LensByValueInput, 'attributes' | 'overrides'> & {
|
||||
attributes:
|
||||
| LensAttributes<'lnsXY', XYState>
|
||||
| LensAttributes<'lnsPie', PieVisualizationState>
|
||||
| LensAttributes<'lnsHeatmap', HeatmapVisualizationState>
|
||||
| LensAttributes<'lnsGauge', GaugeVisualizationState>
|
||||
| LensAttributes<'lnsDatatable', DatatableVisualizationState>
|
||||
| LensAttributes<'lnsLegacyMetric', LegacyMetricState>
|
||||
| LensAttributes<'lnsMetric', MetricVisualizationState>
|
||||
| LensAttributes<'lnsHeatmap', HeatmapVisualizationState>
|
||||
| LensAttributes<'lnsGauge', GaugeVisualizationState>
|
||||
| LensAttributes<string, unknown>;
|
||||
|
||||
/**
|
||||
* Overrides can tweak the style of the final embeddable and are executed at the end of the Lens rendering pipeline.
|
||||
* XY charts offer an override of the Settings ('settings') and Axis ('axisX', 'axisLeft', 'axisRight') components.
|
||||
* While it is not possible to pass function/callback/handlers to the renderer, it is possible to stop them by passing the
|
||||
* "ignore" string as override value (i.e. onBrushEnd: "ignore")
|
||||
*/
|
||||
overrides?:
|
||||
| AllowedSettingsOverrides
|
||||
| AllowedXYOverrides
|
||||
| AllowedPartitionOverrides
|
||||
| AllowedGaugeOverrides;
|
||||
};
|
||||
|
||||
export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceInput) & {
|
||||
|
|
|
@ -44,6 +44,7 @@ export type {
|
|||
export type { DatatableVisualizationState } from './visualizations/datatable/visualization';
|
||||
export type { HeatmapVisualizationState } from './visualizations/heatmap/types';
|
||||
export type { GaugeVisualizationState } from './visualizations/gauge/constants';
|
||||
export type { MetricVisualizationState } from './visualizations/metric/types';
|
||||
export type {
|
||||
FormBasedPersistedState,
|
||||
PersistedIndexPatternLayer,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { GaugeState as GaugeStateOriginal } from '@kbn/expression-gauge-plugin/common';
|
||||
import { LayerType } from '../../../common/types';
|
||||
import type { LayerType } from '../../../common/types';
|
||||
|
||||
export const LENS_GAUGE_ID = 'lnsGauge';
|
||||
|
||||
|
|
37
x-pack/plugins/lens/public/visualizations/metric/types.ts
Normal file
37
x-pack/plugins/lens/public/visualizations/metric/types.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 type { LayoutDirection } from '@elastic/charts';
|
||||
import type { PaletteOutput, CustomPaletteParams } from '@kbn/coloring';
|
||||
import type { CollapseFunction } from '@kbn/visualizations-plugin/common';
|
||||
import type { LayerType } from '../../../common/types';
|
||||
|
||||
export interface MetricVisualizationState {
|
||||
layerId: string;
|
||||
layerType: LayerType;
|
||||
metricAccessor?: string;
|
||||
secondaryMetricAccessor?: string;
|
||||
maxAccessor?: string;
|
||||
breakdownByAccessor?: string;
|
||||
// the dimensions can optionally be single numbers
|
||||
// computed by collapsing all rows
|
||||
collapseFn?: CollapseFunction;
|
||||
subtitle?: string;
|
||||
secondaryPrefix?: string;
|
||||
progressDirection?: LayoutDirection;
|
||||
showBar?: boolean;
|
||||
color?: string;
|
||||
palette?: PaletteOutput<CustomPaletteParams>;
|
||||
maxCols?: number;
|
||||
|
||||
trendlineLayerId?: string;
|
||||
trendlineLayerType?: LayerType;
|
||||
trendlineTimeAccessor?: string;
|
||||
trendlineMetricAccessor?: string;
|
||||
trendlineSecondaryMetricAccessor?: string;
|
||||
trendlineBreakdownByAccessor?: string;
|
||||
}
|
|
@ -25,11 +25,13 @@ When adding visualizations to a solution page, there are multiple ways to approa
|
|||
Pros:
|
||||
* No need to manage searches and rendering logic on your own
|
||||
* "Open in Lens" comes for free
|
||||
* Simple extended visualization options - if Lens can't do it, there's also a limited set of overrides to customize the final result
|
||||
|
||||
Cons:
|
||||
* Each panel does its own data fetching and rendering (can lead to performance problems for high number of embeddables on a single page, e.g. more than 20)
|
||||
* Limited data processing options - if the Lens UI doesn't support it, it can't be used
|
||||
* Limited visualization options - if Lens can't do it, it's not possible
|
||||
|
||||
|
||||
* #### **Using custom data fetching and rendering**
|
||||
In case the disadvantages of using the Lens embeddable heavily affect your use case, it sometimes makes sense to roll your own data fetching and rendering by using the underlying APIs of search service and `elastic-charts` directly. This allows a high degree of flexibility when it comes to data processing, efficiently querying data for multiple charts in a single query and adjusting small details in how charts are rendered. However, do not choose these option lightly as maintenance as well as initial development effort will most likely be much higher than by using the Lens embeddable directly. In this case, almost always an "Open in Lens" button can still be offered to the user to drill down and further explore the data by generating a Lens configuration which is similar to the displayed visualization given the possibilities of Lens. Keep in mind that for the "Open in Lens" flow, the most important property isn't perfect fidelity of the chart but retaining the mental context of the user when switching so they don't have to start over. It's also possible to mix this approach with Lens embeddables on a single page. **Note**: In this situation, please let the Visualizations team know what features you are missing / why you chose not to use Lens.
|
||||
|
||||
|
@ -182,6 +184,23 @@ The Lens embeddable is handling both data fetching and rendering - all the user
|
|||
/>
|
||||
```
|
||||
|
||||
## Overrides
|
||||
|
||||
The Lens embeddable offers a way to extends the current set of visualization feature provided within the Lens editor, via the `overrides` property, which enables the consumer to override some visualization configurations in the embeddable instance.
|
||||
|
||||
```tsx
|
||||
<EmbeddableComponent
|
||||
// ...
|
||||
overrides={{
|
||||
settings: {legendAction: 'ignore'},
|
||||
axisX: {hide: true}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
The each override is component-specific and it inherits the prop from its `elastic-charts` definition directly. Callback/handlers are not supported as functions, but the special value `"ignore"` can be provided in order to disable them in the embeddable rendering.
|
||||
**Note**: overrides are only applied to the local embeddable instance and will disappear when the visualization is open in the Lens editor.
|
||||
|
||||
# Lens Development
|
||||
|
||||
The following sections are concerned with developing the Lens plugin itself.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue