Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Feature anywhere] Add annotation click handler #3777

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/plugins/vis_augmenter/public/test_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { OpenSearchDashboardsDatatable } from '../../expressions/public';
import { VIS_LAYER_COLUMN_TYPE, VisLayerTypes, HOVER_PARAM } from './';
import { VisInteraction, VisInteractionEventHandlerName } from './vega/constants';

const TEST_X_AXIS_ID = 'test-x-axis-id';
const TEST_VALUE_AXIS_ID = 'test-value-axis-id';
Expand Down Expand Up @@ -457,7 +458,10 @@ const TEST_EVENTS_LAYER_SINGLE_VIS_LAYER = {
filled: true,
opacity: 1,
},
transform: [{ filter: `datum['${TEST_PLUGIN_RESOURCE_ID}'] > 0` }],
transform: [
{ filter: `datum['${TEST_PLUGIN_RESOURCE_ID}'] > 0` },
{ calculate: `'${VisInteraction.POINT_IN_TIME_ANNOTATION}'`, as: 'annotationType' },
],
params: [{ name: HOVER_PARAM, select: { type: 'point', on: 'mouseover' } }],
encoding: {
x: {
Expand Down Expand Up @@ -503,6 +507,7 @@ const TEST_EVENTS_LAYER_MULTIPLE_VIS_LAYERS = {
{
filter: `datum['${TEST_PLUGIN_RESOURCE_ID}'] > 0 || datum['${TEST_PLUGIN_RESOURCE_ID_2}'] > 0`,
},
{ calculate: `'${VisInteraction.POINT_IN_TIME_ANNOTATION}'`, as: 'annotationType' },
],
};

Expand Down Expand Up @@ -572,3 +577,23 @@ export const TEST_RESULT_SPEC_MULTIPLE_VIS_LAYERS = {
TEST_EVENTS_LAYER_MULTIPLE_VIS_LAYERS,
],
};

export const TEST_RESULT_SPEC_WITH_VIS_INTERACTION_CONFIG = {
...TEST_SPEC_NO_VIS_LAYERS,
config: {
...TEST_SPEC_NO_VIS_LAYERS.config,
kibana: {
...(TEST_SPEC_NO_VIS_LAYERS.config.kibana || {}),
visInteractions: [
{
event: 'click',
handlerName: VisInteractionEventHandlerName.POINT_IN_TIME_CLICK_EVENT_HANDLER,
},
{
event: 'mouseover',
handlerName: VisInteractionEventHandlerName.POINT_IN_TIME_HOVER_IN_EVENT_HANDLER,
},
],
},
},
};
13 changes: 13 additions & 0 deletions src/plugins/vis_augmenter/public/vega/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export enum VisInteraction {
POINT_IN_TIME_ANNOTATION = 'POINT_IN_TIME_DATA_POINT',
}

export enum VisInteractionEventHandlerName {
POINT_IN_TIME_CLICK_EVENT_HANDLER = 'visAugmenter.pointInTimeClickEventHandler',
POINT_IN_TIME_HOVER_IN_EVENT_HANDLER = 'visAugmenter.pointInTimeHoverInEventHandler',
}
11 changes: 11 additions & 0 deletions src/plugins/vis_augmenter/public/vega/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
addMissingRowsToTableBounds,
addPointInTimeEventsLayersToTable,
addPointInTimeEventsLayersToSpec,
addPointInTimeInteractionsConfig,
} from './helpers';
import { VIS_LAYER_COLUMN_TYPE, VisLayerTypes, PointInTimeEventsVisLayer, VisLayer } from '../';
import {
Expand All @@ -32,6 +33,7 @@ import {
TEST_RESULT_SPEC_MULTIPLE_VIS_LAYERS,
TEST_RESULT_SPEC_SINGLE_VIS_LAYER,
TEST_RESULT_SPEC_SINGLE_VIS_LAYER_EMPTY,
TEST_RESULT_SPEC_WITH_VIS_INTERACTION_CONFIG,
amsiglan marked this conversation as resolved.
Show resolved Hide resolved
TEST_SPEC_MULTIPLE_VIS_LAYERS,
TEST_SPEC_NO_VIS_LAYERS,
TEST_SPEC_SINGLE_VIS_LAYER,
Expand Down Expand Up @@ -455,4 +457,13 @@ describe('helpers', function () {
expect(returnSpec).toEqual(expectedSpec);
});
});

describe('addPointInTimeInteractionsConfig()', function () {
it('appends interactions config to the provided config from visualization spec', function () {
const expectedSpec = TEST_RESULT_SPEC_WITH_VIS_INTERACTION_CONFIG;
const testSpec = { ...TEST_SPEC_NO_VIS_LAYERS };
testSpec.config = addPointInTimeInteractionsConfig(TEST_SPEC_NO_VIS_LAYERS.config) as any;
expect(testSpec).toEqual(expectedSpec);
});
});
});
62 changes: 61 additions & 1 deletion src/plugins/vis_augmenter/public/vega/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import moment from 'moment';
import { cloneDeep, isEmpty, get } from 'lodash';
import { Item, ScenegraphEvent } from 'vega';
import {
OpenSearchDashboardsDatatable,
OpenSearchDashboardsDatatableColumn,
Expand All @@ -24,6 +25,7 @@ import {
VisLayers,
VisLayerTypes,
} from '../';
import { VisInteractionEventHandlerName, VisInteraction as VisAnnotationType } from './constants';

// Given any visLayers, create a map to indicate which VisLayer types are present.
// Convert to an array since ES6 Maps cannot be stringified.
Expand Down Expand Up @@ -314,7 +316,10 @@ export const addPointInTimeEventsLayersToSpec = (
filled: true,
opacity: 1,
},
transform: [{ filter: generateVisLayerFilterString(visLayerColumnIds) }],
transform: [
{ filter: generateVisLayerFilterString(visLayerColumnIds) },
{ calculate: `'${VisAnnotationType.POINT_IN_TIME_ANNOTATION}'`, as: 'annotationType' },
],
params: [{ name: HOVER_PARAM, select: { type: 'point', on: 'mouseover' } }],
encoding: {
x: {
Expand All @@ -340,3 +345,58 @@ export const addPointInTimeEventsLayersToSpec = (

return newSpec;
};

/**
* Interaction handling functions mapped by their action names.
*/
export const interactionHandlersByAction = {
[VisInteractionEventHandlerName.POINT_IN_TIME_CLICK_EVENT_HANDLER]: (
_event: ScenegraphEvent,
amsiglan marked this conversation as resolved.
Show resolved Hide resolved
item?: Item | null
) => {
if (isPointInTimeAnnotation(item)) {
// TODO: Show the events flyout
}
},
[VisInteractionEventHandlerName.POINT_IN_TIME_HOVER_IN_EVENT_HANDLER]: (
_event: ScenegraphEvent,
item?: Item | null
) => {
if (isPointInTimeAnnotation(item)) {
// TODO: Show the custom tooltip
}
},
};

/**
* Updating the vega-lite spec to add interaction config that is used to add event handlers on the visualization view.
* Note: Since, we cannot have handler functions added directly to the spec object as it is stringified,
* we pass the handler names as part of the config and use the @see interactionHandlersByAction to get the corresponding
* handler when required.
*/
export const addPointInTimeInteractionsConfig = (config: object) => {
const kibana = get(config, 'kibana', {});
const withInteractionsConfig = {
...kibana,
visInteractions: [
...(kibana.visInteractions || []),
{
event: 'click',
handlerName: VisInteractionEventHandlerName.POINT_IN_TIME_CLICK_EVENT_HANDLER,
},
{
event: 'mouseover',
handlerName: VisInteractionEventHandlerName.POINT_IN_TIME_HOVER_IN_EVENT_HANDLER,
},
],
};

return {
...config,
kibana: withInteractionsConfig,
};
};

export const isPointInTimeAnnotation = (item?: Item | null) => {
return item?.datum?.annotationType === VisAnnotationType.POINT_IN_TIME_ANNOTATION;
};
6 changes: 6 additions & 0 deletions src/plugins/vis_type_vega/public/data_model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,19 @@ type ContextVarsObjectProps =

type ToolTipPositions = 'top' | 'right' | 'bottom' | 'left';

export interface VisInteractionDescriptor {
event: string;
handlerName: string;
}

export interface OpenSearchDashboards {
controlsLocation: ControlsLocation;
controlsDirection: ControlsDirection;
hideWarnings: boolean;
type: string;
renderer: Renderer;
visibleVisLayers?: Map<VisLayerTypes, boolean>;
visInteractions?: VisInteractionDescriptor[];
}

export interface VegaSpec {
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/vis_type_vega/public/data_model/vega_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ControlsLocation,
ControlsDirection,
OpenSearchDashboards,
VisInteractionDescriptor,
} from './types';

// Set default single color to match other OpenSearch Dashboards visualizations
Expand Down Expand Up @@ -94,6 +95,7 @@ export class VegaParser {
filters: Bool;
timeCache: TimeCache;
visibleVisLayers: Map<VisLayerTypes, boolean>;
visInteractions?: VisInteractionDescriptor[];

constructor(
spec: VegaSpec | string,
Expand Down Expand Up @@ -409,6 +411,9 @@ The URL is an identifier only. OpenSearch Dashboards and your browser will never
if (result.visibleVisLayers !== undefined && Array.isArray(result.visibleVisLayers)) {
result.visibleVisLayers = new Map<VisLayerTypes, boolean>(result.visibleVisLayers);
}
if (result.visInteractions) {
this.visInteractions = result.visInteractions;
}
}
}
return result || {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
addPointInTimeEventsLayersToTable,
addPointInTimeEventsLayersToSpec,
enableVisLayersInSpecConfig,
addPointInTimeInteractionsConfig,
} from '../../../vis_augmenter/public';
import { formatDatatable, createSpecFromDatatable } from './helpers';
import { VegaVisualizationDependencies } from '../plugin';
Expand Down Expand Up @@ -85,6 +86,7 @@ export const createLineVegaSpecFn = (
if (!isEmpty(pointInTimeEventsVisLayers) && dimensions.x !== null) {
spec = addPointInTimeEventsLayersToSpec(table, dimensions, spec);
spec.config = enableVisLayersInSpecConfig(spec, pointInTimeEventsVisLayers);
spec.config = addPointInTimeInteractionsConfig(spec.config);
}
return JSON.stringify(spec);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { opensearchFilters } from '../../../data/public';

import { getEnableExternalUrls, getData } from '../services';
import { extractIndexPatternsFromSpec } from '../lib/extract_index_pattern';
import { interactionHandlersByAction } from '../../../vis_augmenter/public';

vega.scheme('euiPaletteColorBlind', euiPaletteColorBlind());

Expand Down Expand Up @@ -297,6 +298,14 @@ export class VegaBaseView {
this._addDestroyHandler(() => tthandler.hideTooltip());
}

if (this._parser.visInteractions) {
this._parser.visInteractions.forEach(({ handlerName, event }) => {
if (interactionHandlersByAction[handlerName]) {
view.addEventListener(event, interactionHandlersByAction[handlerName]);
}
});
}

return view.runAsync(); // Allows callers to await rendering
}
}
Expand Down