Correlations: Expose correlations logic through correlations service (#98124)

* Correlations: Expose correlations logic through correlations service

* Resolve remaining todos

* Marked new items as @alpha
This commit is contained in:
Edvard Falkskär 2025-01-14 12:27:13 +01:00 committed by GitHub
parent 33a91f22c0
commit e45eb95812
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 187 additions and 81 deletions

View File

@ -57,3 +57,12 @@ export { hasPermission, hasPermissionInMetadata, hasAllPermissions, hasAnyPermis
export { QueryEditorWithMigration } from './components/QueryEditorWithMigration';
export { type MigrationHandler, isMigrationHandler, migrateQuery, migrateRequest } from './utils/migrationHandler';
export { usePluginUserStorage } from './utils/userStorage';
export {
type CorrelationsService,
type CorrelationData,
type CorrelationsData,
type CorrelationExternal,
type CorrelationQuery,
getCorrelationsService,
setCorrelationsService,
} from './services/CorrelationsService';

View File

@ -0,0 +1,126 @@
import {
DataFrame,
DataLinkPostProcessor,
DataLinkTransformationConfig,
DataSourceInstanceSettings,
TimeRange,
} from '@grafana/data';
export type CorrelationConfigQuery = {
field: string;
target: object; // for queries, this contains anything that would go in the query editor, so any extension off DataQuery a datasource would have, and needs to be generic.
transformations?: DataLinkTransformationConfig[];
};
export type CorrelationConfigExternal = {
field: string;
target: {
url: string; // For external, this simply contains a URL
};
transformations?: DataLinkTransformationConfig[];
};
type CorrelationBase = {
uid: string;
sourceUID: string;
label?: string;
description?: string;
provisioned: boolean;
orgId?: number;
};
/**
* @alpha
*/
export type CorrelationExternal = CorrelationBase & {
type: 'external';
config: CorrelationConfigExternal;
};
/**
* @alpha
*/
export type CorrelationQuery = CorrelationBase & {
type: 'query';
config: CorrelationConfigQuery;
targetUID: string;
};
/**
* @alpha
*/
export type CorrelationData =
| (Omit<CorrelationExternal, 'sourceUID'> & {
source: DataSourceInstanceSettings;
})
| (Omit<CorrelationQuery, 'sourceUID' | 'targetUID'> & {
source: DataSourceInstanceSettings;
target: DataSourceInstanceSettings;
});
/**
* @alpha
*/
export interface CorrelationsData {
correlations: CorrelationData[];
page: number;
limit: number;
totalCount: number;
}
/**
* Used to work with user defined correlations.
* Should be accessed via {@link getCorrelationsService} function.
*
* @alpha
*/
export interface CorrelationsService {
/**
* Creates data links in data frames from provided correlations
*
* @param dataFrames list of data frames to be processed
* @param correlations list of of possible correlations that can be applied
* @param dataFrameRefIdToDataSourceUid a map that for provided refId references corresponding data source ui
*/
attachCorrelationsToDataFrames: (
dataFrames: DataFrame[],
correlations: CorrelationData[],
dataFrameRefIdToDataSourceUid: Record<string, string>
) => DataFrame[];
/**
* Creates a link post processor function that handles correlation transformations
*
* @param timeRange The current time range
*/
correlationsDataLinkPostProcessorFactory: (timeRange: TimeRange) => DataLinkPostProcessor;
/**
* Loads all the correlations defined for the given data sources.
*
* @param sourceUIDs Data source UIDs
*/
getCorrelationsBySourceUIDs: (sourceUIDs: string[]) => Promise<CorrelationsData>;
}
let singletonInstance: CorrelationsService;
/**
* Used during startup by Grafana to set the CorrelationsService so it is available
* via {@link getCorrelationsService} to the rest of the application.
*
* @internal
*/
export function setCorrelationsService(instance: CorrelationsService) {
console.log('setCorrelationsService', instance);
singletonInstance = instance;
}
/**
* Used to retrieve the {@link CorrelationsService}.
*
* @alpha
*/
export function getCorrelationsService(): CorrelationsService {
return singletonInstance;
}

View File

@ -40,6 +40,7 @@ import {
setCurrentUser,
setChromeHeaderHeightHook,
setPluginLinksHook,
setCorrelationsService,
} from '@grafana/runtime';
import { setPanelDataErrorView } from '@grafana/runtime/src/components/PanelDataErrorView';
import { setPanelRenderer } from '@grafana/runtime/src/components/PanelRenderer';
@ -60,6 +61,7 @@ import { initializeCrashDetection } from './core/crash';
import { initializeI18n } from './core/internationalization';
import { setMonacoEnv } from './core/monacoEnv';
import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks';
import { CorrelationsService } from './core/services/CorrelationsService';
import { NewFrontendAssetsChecker } from './core/services/NewFrontendAssetsChecker';
import { backendSrv } from './core/services/backend_srv';
import { contextSrv, RedirectToUrlKey } from './core/services/context_srv';
@ -146,6 +148,7 @@ export class GrafanaApp {
setPluginPage(PluginPage);
setPanelDataErrorView(PanelDataErrorView);
setLocationSrv(locationService);
setCorrelationsService(new CorrelationsService());
setEmbeddedDashboard(EmbeddedDashboardLazy);
setTimeZoneResolver(() => config.bootData.user.timezone);
initGrafanaLive();

View File

@ -0,0 +1,22 @@
import { DataFrame, TimeRange } from '@grafana/data';
import type { CorrelationData, CorrelationsService as CorrelationsServiceInterface } from '@grafana/runtime';
import { attachCorrelationsToDataFrames, getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
import { exploreDataLinkPostProcessorFactory } from 'app/features/explore/utils/links';
export class CorrelationsService implements CorrelationsServiceInterface {
attachCorrelationsToDataFrames(
dataFrames: DataFrame[],
correlations: CorrelationData[],
dataFrameRefIdToDataSourceUid: Record<string, string>
) {
return attachCorrelationsToDataFrames(dataFrames, correlations, dataFrameRefIdToDataSourceUid);
}
correlationsDataLinkPostProcessorFactory(timeRange: TimeRange) {
return exploreDataLinkPostProcessorFactory(undefined, timeRange);
}
getCorrelationsBySourceUIDs(sourceUIDs: string[]) {
return getCorrelationsBySourceUIDs(sourceUIDs);
}
}

View File

@ -3,7 +3,7 @@ import { negate } from 'lodash';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
import { isFetchError, reportInteraction } from '@grafana/runtime';
import { CorrelationData, isFetchError, reportInteraction } from '@grafana/runtime';
import {
Badge,
Button,
@ -28,7 +28,7 @@ import { AddCorrelationForm } from './Forms/AddCorrelationForm';
import { EditCorrelationForm } from './Forms/EditCorrelationForm';
import { EmptyCorrelationsCTA } from './components/EmptyCorrelationsCTA';
import type { Correlation, RemoveCorrelationParams } from './types';
import { CorrelationData, useCorrelations } from './useCorrelations';
import { useCorrelations } from './useCorrelations';
const sortDatasource: SortByFn<CorrelationData> = (a, b, column) =>
a.values[column].name.localeCompare(b.values[column].name);

View File

@ -2,11 +2,12 @@ import { css } from '@emotion/css';
import { Controller, FieldError, useFormContext, useWatch } from 'react-hook-form';
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
import { CorrelationExternal } from '@grafana/runtime';
import { Field, FieldSet, Input, Select, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
import { CorrelationType, ExternalTypeTarget } from '../types';
import { CorrelationType } from '../types';
import { QueryEditorField } from './QueryEditorField';
import { useCorrelationsFormContext } from './correlationsFormContext';
@ -146,7 +147,7 @@ export const ConfigureCorrelationTargetForm = () => {
},
}}
render={({ field: { onChange, value } }) => {
const castVal = value as ExternalTypeTarget; // the target under "query" type can contain anything a datasource query contains
const castVal = value as CorrelationExternal['config']['target']; // the target under "query" type can contain anything a datasource query contains
return (
<Field
label={t('correlations.target-form.target-label', 'Target')}

View File

@ -1,16 +1,17 @@
import { DeepMap, FieldError, FieldErrors } from 'react-hook-form';
import { SupportedTransformationType } from '@grafana/data';
import { CorrelationExternal, CorrelationQuery } from '@grafana/runtime';
import { t } from 'app/core/internationalization';
import { CorrelationConfigExternal, CorrelationConfigQuery, OmitUnion } from '../types';
import { OmitUnion } from '../types';
export interface FormExternalDTO {
sourceUID: string;
label: string;
description: string;
type: 'external';
config: CorrelationConfigExternal;
config: CorrelationExternal['config'];
}
export interface FormQueryDTO {
@ -19,7 +20,7 @@ export interface FormQueryDTO {
label: string;
description: string;
type: 'query';
config: CorrelationConfigQuery;
config: CorrelationQuery['config'];
}
export type FormDTO = FormExternalDTO | FormQueryDTO;

View File

@ -1,4 +1,4 @@
import { DataLinkTransformationConfig } from '@grafana/data';
import { CorrelationExternal, CorrelationQuery } from '@grafana/runtime';
export interface AddCorrelationResponse {
correlation: Correlation;
@ -28,40 +28,6 @@ export interface RemoveCorrelationResponse {
export type CorrelationType = 'query' | 'external';
export type ExternalTypeTarget = { url: string };
export type CorrelationConfigQuery = {
field: string;
target: object; // for queries, this contains anything that would go in the query editor, so any extension off DataQuery a datasource would have, and needs to be generic.
transformations?: DataLinkTransformationConfig[];
};
export type CorrelationConfigExternal = {
field: string;
target: ExternalTypeTarget; // For external, this simply contains a URL
transformations?: DataLinkTransformationConfig[];
};
type CorrelationBase = {
uid: string;
sourceUID: string;
label?: string;
description?: string;
provisioned: boolean;
orgId?: number;
};
export type CorrelationExternal = CorrelationBase & {
type: 'external';
config: CorrelationConfigExternal;
};
export type CorrelationQuery = CorrelationBase & {
type: 'query';
config: CorrelationConfigQuery;
targetUID: string;
};
export type Correlation = CorrelationExternal | CorrelationQuery;
export type GetCorrelationsParams = {

View File

@ -1,14 +1,11 @@
import { useAsyncFn } from 'react-use';
import { lastValueFrom } from 'rxjs';
import { DataSourceInstanceSettings } from '@grafana/data';
import { getDataSourceSrv, FetchResponse } from '@grafana/runtime';
import { getDataSourceSrv, FetchResponse, CorrelationData, CorrelationsData } from '@grafana/runtime';
import { useGrafana } from 'app/core/context/GrafanaContext';
import {
Correlation,
CorrelationExternal,
CorrelationQuery,
CreateCorrelationParams,
CreateCorrelationResponse,
GetCorrelationsParams,
@ -26,22 +23,6 @@ export interface CorrelationsResponse {
totalCount: number;
}
export type CorrelationData =
| (Omit<CorrelationExternal, 'sourceUID'> & {
source: DataSourceInstanceSettings;
})
| (Omit<CorrelationQuery, 'sourceUID' | 'targetUID'> & {
source: DataSourceInstanceSettings;
target: DataSourceInstanceSettings;
});
export interface CorrelationsData {
correlations: CorrelationData[];
page: number;
limit: number;
totalCount: number;
}
const toEnrichedCorrelationData = ({ sourceUID, ...correlation }: Correlation): CorrelationData | undefined => {
const sourceDatasource = getDataSourceSrv().getInstanceSettings(sourceUID);
const targetDatasource =

View File

@ -1,7 +1,6 @@
import { DataFrame, DataFrameType, DataSourceInstanceSettings, FieldType, toDataFrame } from '@grafana/data';
import { config } from '@grafana/runtime';
import { config, CorrelationData } from '@grafana/runtime';
import { CorrelationData } from './useCorrelations';
import { attachCorrelationsToDataFrames } from './utils';
describe('correlations utils', () => {

View File

@ -1,20 +1,21 @@
import { lastValueFrom } from 'rxjs';
import { DataFrame, DataLinkConfigOrigin } from '@grafana/data';
import { config, createMonitoringLogger, getBackendSrv, getDataSourceSrv } from '@grafana/runtime';
import {
config,
CorrelationData,
CorrelationsData,
createMonitoringLogger,
getBackendSrv,
getDataSourceSrv,
} from '@grafana/runtime';
import { ExploreItemState } from 'app/types';
import { formatValueName } from '../explore/PrometheusListView/ItemLabels';
import { parseLogsFrame } from '../logs/logsFrame';
import { CreateCorrelationParams, CreateCorrelationResponse } from './types';
import {
CorrelationData,
CorrelationsData,
CorrelationsResponse,
getData,
toEnrichedCorrelationsData,
} from './useCorrelations';
import { CorrelationsResponse, getData, toEnrichedCorrelationsData } from './useCorrelations';
type DataFrameRefIdToDataSourceUid = Record<string, string>;
@ -85,7 +86,7 @@ const decorateDataFrameWithInternalDataLinks = (dataFrame: DataFrame, correlatio
};
/*
If a correlation was made based on the log line field prior to the loki data plane, they would use the field "Line"
If a correlation was made based on the log line field prior to the loki data plane, they would use the field "Line"
Change it to use whatever the body field name is post-loki data plane
*/

View File

@ -1,11 +1,10 @@
import { Observable } from 'rxjs';
import { DataLinkTransformationConfig } from '@grafana/data';
import { getDataSourceSrv, reportInteraction } from '@grafana/runtime';
import { CorrelationData, getDataSourceSrv, reportInteraction } from '@grafana/runtime';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { CreateCorrelationParams } from 'app/features/correlations/types';
import { CorrelationData } from 'app/features/correlations/useCorrelations';
import { getCorrelationsBySourceUIDs, createCorrelation, generateDefaultLabel } from 'app/features/correlations/utils';
import { store } from 'app/store/store';
import { ThunkResult } from 'app/types';

View File

@ -11,9 +11,9 @@ import {
ExploreCorrelationHelperData,
EventBusExtended,
} from '@grafana/data';
import { CorrelationData } from '@grafana/runtime';
import { DataQuery, DataSourceRef } from '@grafana/schema';
import { getQueryKeys } from 'app/core/utils/explore';
import { CorrelationData } from 'app/features/correlations/useCorrelations';
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
import { getTimeZone } from 'app/features/profile/state/selectors';
import { createAsyncThunk, ThunkResult } from 'app/types';

View File

@ -10,9 +10,9 @@ import {
DataSourceApi,
DataSourceInstanceSettings,
} from '@grafana/data';
import { CorrelationData } from '@grafana/runtime';
import { DataSourceJsonData, DataQuery } from '@grafana/schema';
import TableModel from 'app/core/TableModel';
import { CorrelationData } from 'app/features/correlations/useCorrelations';
import { ExplorePanelData } from 'app/types';
import {

View File

@ -14,12 +14,11 @@ import {
getRawDisplayProcessor,
DataSourceApi,
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { config, CorrelationData } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { refreshIntervalToSortOrder } from '../../../core/utils/explore';
import { ExplorePanelData } from '../../../types';
import { CorrelationData } from '../../correlations/useCorrelations';
import { attachCorrelationsToDataFrames } from '../../correlations/utils';
import { dataFrameToLogsModel } from '../../logs/logsModel';
import { sortLogsResult } from '../../logs/utils';

View File

@ -19,10 +19,9 @@ import {
ExploreCorrelationHelperData,
DataLinkTransformationConfig,
} from '@grafana/data';
import { CorrelationData } from '@grafana/runtime';
import { RichHistorySearchFilters, RichHistorySettings } from 'app/core/utils/richHistoryTypes';
import { CorrelationData } from '../features/correlations/useCorrelations';
export type ExploreQueryParams = UrlQueryMap;
export enum CORRELATION_EDITOR_POST_CONFIRM_ACTION {