Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class SearchAPI {
public readonly inspectorAdapters?: VegaInspectorAdapters,
private readonly searchSessionId?: string,
private readonly executionContext?: KibanaExecutionContext,
private readonly projectRouting?: ProjectRouting
public readonly projectRouting?: ProjectRouting
) {}

search(searchRequests: SearchRequest[]) {
Expand Down Expand Up @@ -160,6 +160,7 @@ export class SearchAPI {
abortSignal: this.abortSignal,
sessionId: this.searchSessionId,
executionContext: this.executionContext,
projectRouting: this.projectRouting,
}
)
.pipe(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { extractProjectRoutingOverrides } from './extract_project_routing_overrides';

describe('extractProjectRoutingOverrides', () => {
it('should extract project routing from ES|QL query with SET instruction', () => {
const spec = {
data: {
name: 'metric',
url: {
'%type%': 'esql',
query: 'SET project_routing = "_alias:_origin"; FROM logs-* | STATS count=COUNT()',
},
},
};

const result = extractProjectRoutingOverrides(spec);

expect(result).toEqual([{ name: 'metric', value: '_alias:_origin' }]);
});
it('should extract project routing from ES|QL query with multiple sources', () => {
const spec = {
data: [
{
name: 'metric',
url: {
'%type%': 'esql',
query:
'SET project_routing = "_alias:_origin"; FROM kibana_sample_data_logs | STATS total=COUNT()',
},
},
{
name: 'metric2',
url: {
'%type%': 'esql',
query:
'SET project_routing = "_alias:*"; FROM kibana_sample_data_logs | STATS total=COUNT()',
},
},
],
};

const result = extractProjectRoutingOverrides(spec);

expect(result).toEqual([
{ name: 'metric', value: '_alias:_origin' },
{ name: 'metric2', value: '_alias:*' },
]);
});

it('should return undefined when no ES|QL queries with project routing', () => {
const spec = {
data: [
{
name: 'regular',
url: {
'%type%': 'elasticsearch',
index: 'logs-*',
},
},
],
};

const result = extractProjectRoutingOverrides(spec);

expect(result).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { getProjectRoutingFromEsqlQuery } from '@kbn/esql-utils';
import type { ProjectRoutingOverrides } from '@kbn/presentation-publishing';

interface DataUrl {
'%type%'?: string;
query?: string;
}

interface DataObject {
name?: string;
url?: string | DataUrl;
}

interface VegaSpec {
data?: DataObject | DataObject[];
[key: string]: unknown;
}

/**
* Extracts project routing overrides from a Vega specification.
* Searches for ES|QL queries in the data.url objects and extracts project routing information.
*/
export function extractProjectRoutingOverrides(spec: VegaSpec): ProjectRoutingOverrides {
const overrides: Array<{ name?: string; value: string }> = [];

const processDataObject = (dataObj: DataObject) => {
if (dataObj.url && typeof dataObj.url === 'object') {
const dataUrl = dataObj.url as DataUrl;
// Check if this is an ES|QL data source
if (dataUrl['%type%'] === 'esql' && typeof dataUrl.query === 'string') {
const projectRoutingValue = getProjectRoutingFromEsqlQuery(dataUrl.query);
if (projectRoutingValue) {
overrides.push({
name: dataObj.name,
value: projectRoutingValue,
});
}
}
}
};

// Process data field
if (spec.data) {
if (Array.isArray(spec.data)) {
spec.data.forEach(processDataObject);
} else if (typeof spec.data === 'object') {
processDataObject(spec.data);
}
}

return overrides.length > 0 ? overrides : undefined;
}
11 changes: 11 additions & 0 deletions src/platform/plugins/private/vis_types/vega/public/vega_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { VIS_EVENT_TO_TRIGGER, VisGroups } from '@kbn/visualizations-plugin/publ

import { getDefaultSpec } from './default_spec';
import { extractIndexPatternsFromSpec } from './lib/extract_index_pattern';
import { extractProjectRoutingOverrides } from './lib/extract_project_routing_overrides';
import { createInspectorAdapters } from './vega_inspector';
import { toExpressionAst } from './to_ast';
import { getInfoMessage } from './components/vega_info_message';
Expand Down Expand Up @@ -60,6 +61,16 @@ export const vegaVisType: VisTypeDefinition<VisParams> = {
}
return [];
},
getProjectRoutingOverrides: async (visParams) => {
try {
const spec = parse(visParams.spec, { legacyRoot: false, keepWsc: true });

return extractProjectRoutingOverrides(spec);
} catch (e) {
// spec is invalid
}
return undefined;
},
inspectorAdapters: createInspectorAdapters,
/**
* This is necessary for showing actions bar in top of vega editor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
HasSupportedTriggers,
PublishesDataLoading,
PublishesDataViews,
PublishesProjectRoutingOverrides,
PublishesRendered,
PublishesTimeRange,
PublishesTitle,
Expand Down Expand Up @@ -59,6 +60,7 @@ export type VisualizeApi = Partial<HasEditCapabilities> &
PublishesDataViews &
PublishesDataLoading &
PublishesRendered &
PublishesProjectRoutingOverrides &
Required<PublishesTitle> &
HasVisualizeConfig &
HasInspectorAdapters &
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
titleComparators,
useBatchedPublishingSubjects,
useStateFromPublishingSubject,
type ProjectRoutingOverrides,
} from '@kbn/presentation-publishing';
import { apiPublishesSearchSession } from '@kbn/presentation-publishing/interfaces/fetch/publishes_search_session';
import { get, isEqual } from 'lodash';
Expand Down Expand Up @@ -88,6 +89,16 @@ export const getVisualizeEmbeddableFactory: (deps: {
const initialVisInstance = await createVisInstance(runtimeState.serializedVis);
const vis$ = new BehaviorSubject<Vis>(initialVisInstance);

let initialProjectRoutingOverrides: ProjectRoutingOverrides;
if (initialVisInstance.type.getProjectRoutingOverrides) {
initialProjectRoutingOverrides = await initialVisInstance.type.getProjectRoutingOverrides(
initialVisInstance.params
);
}
const projectRoutingOverrides$ = new BehaviorSubject<ProjectRoutingOverrides>(
initialProjectRoutingOverrides
);

// Track UI state
const onUiStateChange = () => serializedVis$.next(vis$.getValue().serialize());

Expand All @@ -100,6 +111,15 @@ export const getVisualizeEmbeddableFactory: (deps: {
const vis = await createVisInstance(serializedVis);
vis.uiState.on('change', onUiStateChange);
vis$.next(vis);

// Update project routing overrides when vis changes
if (vis.type.getProjectRoutingOverrides) {
const newOverrides = await vis.type.getProjectRoutingOverrides(vis.params);
if (!isEqual(projectRoutingOverrides$.getValue(), newOverrides)) {
projectRoutingOverrides$.next(newOverrides);
}
}

const { params, abortController } = await getExpressionParams();
return { params, abortController };
})
Expand Down Expand Up @@ -235,6 +255,7 @@ export const getVisualizeEmbeddableFactory: (deps: {
defaultTitle$,
dataLoading$,
dataViews$: new BehaviorSubject<DataView[] | undefined>(initialDataViews),
projectRoutingOverrides$,
rendered$: hasRendered$,
supportedTriggers: () => [ACTION_CONVERT_TO_LENS, APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER],
serializeState: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class BaseVisType<TVisParams extends VisParams = VisParams> {
public readonly hierarchicalData;
public readonly setup;
public readonly getUsedIndexPattern;
public readonly getProjectRoutingOverrides;
public readonly inspectorAdapters;
public readonly fetchDatatable: boolean;
public readonly toExpressionAst;
Expand Down Expand Up @@ -83,6 +84,7 @@ export class BaseVisType<TVisParams extends VisParams = VisParams> {
this.hasPartialRows = opts.hasPartialRows ?? false;
this.hierarchicalData = opts.hierarchicalData ?? false;
this.getUsedIndexPattern = opts.getUsedIndexPattern;
this.getProjectRoutingOverrides = opts.getProjectRoutingOverrides;
this.inspectorAdapters = opts.inspectorAdapters;
this.fetchDatatable = opts.fetchDatatable ?? false;
this.toExpressionAst = opts.toExpressionAst;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ export interface VisTypeDefinition<TVisParams extends VisParams> {
*/
readonly getUsedIndexPattern?: (visParams: VisParams) => DataView[] | Promise<DataView[]>;

/**
* Vega may provide project routing overrides.
* This method should return an array of project routing values extracted from the vega spec.
*/
readonly getProjectRoutingOverrides?: (
visParams: VisParams
) => Promise<Array<{ name?: string; value: string }> | undefined>;

readonly isAccessible?: boolean;
/**
* It is the visualization icon, displayed on the wizard.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import type { VisualizeServices } from '../types';
import { VisualizeEditorCommon } from './visualize_editor_common';
import type { VisualizeAppProps } from '../app';
import { useProjectRouting } from '../utils/use/use_project_routing';

export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {
const [originatingApp, setOriginatingApp] = useState<string>();
Expand Down Expand Up @@ -76,13 +77,16 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {
eventEmitter,
byValueVisInstance
);
// Initialize CPS project routing manager for Vega
const projectRoutingManager = useProjectRouting(services);
const { isEmbeddableRendered, currentAppState } = useEditorUpdates(
services,
eventEmitter,
setHasUnsavedChanges,
appState,
byValueVisInstance,
visEditorController
visEditorController,
projectRoutingManager
);
useLinkedSearchUpdates(services, eventEmitter, appState, byValueVisInstance);
useDataViewUpdates(services, eventEmitter, appState, byValueVisInstance);
Expand Down
Loading