This commit is contained in:
Kristina Durivage
2025-11-20 18:41:38 -06:00
parent e61912c721
commit 9e9b1c660f
6 changed files with 215 additions and 137 deletions
@@ -33,6 +33,7 @@ import {
MockDataSourceSrv,
} from './mocks/useCorrelations.mocks';
import { Correlation, CreateCorrelationParams, OmitUnion } from './types';
import { useCorrelations } from './useCorrelations';
// Set app events up, otherwise plugin modules will fail to load
setAppEvents(appEvents);
@@ -110,9 +111,17 @@ const renderWithContext = async (
setDataSourceSrv(dsServer);
const { remove, get } = useCorrelations();
const renderResult = render(
<TestProvider store={configureStore({})} grafanaContext={grafanaContext}>
<CorrelationsPage />
<CorrelationsPage
fetchCorrelations={get.execute}
correlations={get.value}
isLoading={get.loading}
error={get.error}
removeFn={remove.execute}
/>
</TestProvider>,
{
queries: {
@@ -4,7 +4,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { CorrelationData, isFetchError, reportInteraction } from '@grafana/runtime';
import { CorrelationData, CorrelationsData, isFetchError, reportInteraction } from '@grafana/runtime';
import {
Badge,
Button,
@@ -27,8 +27,17 @@ import { AccessControlAction } from 'app/types/accessControl';
import { AddCorrelationForm } from './Forms/AddCorrelationForm';
import { EditCorrelationForm } from './Forms/EditCorrelationForm';
import { EmptyCorrelationsCTA } from './components/EmptyCorrelationsCTA';
import type { Correlation, RemoveCorrelationParams } from './types';
import { useCorrelations } from './useCorrelations';
import type { Correlation, GetCorrelationsParams, RemoveCorrelationParams } from './types';
type CorrelationsPageProps = {
fetchCorrelations: (params: GetCorrelationsParams) => Promise<CorrelationsData>;
correlations?: CorrelationsData;
isLoading: boolean;
removeFn?: (params: RemoveCorrelationParams) => Promise<{
message: string;
}>;
error?: Error;
};
const sortDatasource: SortByFn<CorrelationData> = (a, b, column) =>
a.values[column].name.localeCompare(b.values[column].name);
@@ -40,7 +49,8 @@ const loaderWrapper = css({
justifyContent: 'center',
});
export default function CorrelationsPage() {
export default function CorrelationsPage(props: CorrelationsPageProps) {
const { fetchCorrelations, correlations, isLoading, error, removeFn } = props;
const navModel = useNavModel('correlations');
const [isAdding, setIsAddingValue] = useState(false);
const page = useRef(1);
@@ -52,11 +62,6 @@ export default function CorrelationsPage() {
}
};
const {
remove,
get: { execute: fetchCorrelations, ...get },
} = useCorrelations();
const canWriteCorrelations = contextSrv.hasPermission(AccessControlAction.DataSourcesWrite);
const handleAdded = useCallback(() => {
@@ -72,7 +77,7 @@ export default function CorrelationsPage() {
const handleDelete = useCallback(
async (params: RemoveCorrelationParams, isLastRow: boolean) => {
await remove.execute(params);
await removeFn(params);
reportInteraction('grafana_correlations_deleted');
if (isLastRow) {
@@ -80,7 +85,7 @@ export default function CorrelationsPage() {
}
fetchCorrelations({ page: page.current });
},
[remove, fetchCorrelations]
[removeFn, fetchCorrelations]
);
useEffect(() => {
@@ -145,8 +150,8 @@ export default function CorrelationsPage() {
[RowActions, canWriteCorrelations]
);
const data = useMemo(() => get.value, [get.value]);
const showEmptyListCTA = data?.correlations.length === 0 && !isAdding && !get.error;
const data = useMemo(() => correlations, [correlations]);
const showEmptyListCTA = data?.correlations.length === 0 && !isAdding && !error;
const addButton = canWriteCorrelations && data?.correlations?.length !== 0 && data !== undefined && !isAdding && (
<Button icon="plus" onClick={() => setIsAdding(true)}>
<Trans i18nKey="correlations.add-new">Add new</Trans>
@@ -170,7 +175,7 @@ export default function CorrelationsPage() {
>
<Page.Contents>
<div>
{!data && get.loading && (
{!data && isLoading && (
<div className={loaderWrapper}>
<LoadingPlaceholder text={t('correlations.list.loading', 'loading...')} />
</div>
@@ -182,13 +187,13 @@ export default function CorrelationsPage() {
{
// This error is not actionable, it'd be nice to have a recovery button
get.error && (
error && (
<Alert
severity="error"
title={t('correlations.alert.title', 'Error fetching correlation data')}
topSpacing={2}
>
{(isFetchError(get.error) && get.error.data?.message) ||
{(isFetchError(error) && error.data?.message) ||
t(
'correlations.alert.error-message',
'An unknown error occurred while fetching correlation data. Please try again.'
@@ -0,0 +1,58 @@
import {
generatedAPI as correlationAPIv0alpha1,
CorrelationList,
} from '@grafana/api-clients/rtkq/correlations/v0alpha1';
import { config, CorrelationsData } from '@grafana/runtime';
import CorrelationsPage from './CorrelationsPage';
import { GetCorrelationsParams } from './types';
import { useCorrelations } from './useCorrelations';
import { toEnrichedCorrelationDataK8s } from './useCorrelationsK8s';
export default function CorrelationsPageWrapper() {
//const { remove, get } = useCorrelations();
try {
const { data, isLoading, error } = correlationAPIv0alpha1.endpoints.listCorrelation.useQuery({});
//if (config.featureToggles.kubernetesCorrelations) {
const enrichedCorrelations = (correlations?: CorrelationList) => {
return correlations !== undefined
? correlations.items.map((item) => toEnrichedCorrelationDataK8s(item)).filter((i) => i !== undefined)
: [];
};
// we cant do a straight refetch, we have to pass in new pages if necessary
const enhRefetch = (params: GetCorrelationsParams): Promise<CorrelationsData> => {
const { data } = correlationAPIv0alpha1.endpoints.listCorrelation.useQuery({});
return new Promise(() => enrichedCorrelations(data));
};
return (
<CorrelationsPage
fetchCorrelations={enhRefetch}
correlations={{
correlations: enrichedCorrelations(data),
page: 0,
limit: 1000,
totalCount: enrichedCorrelations.length,
}}
isLoading={isLoading}
error={error as Error}
/>
);
} catch (e) {
console.log(e);
}
/*} else {
return (
<CorrelationsPage
fetchCorrelations={get.execute}
correlations={get.value}
isLoading={get.loading}
error={get.error}
removeFn={remove.execute}
/>
);
}*/
}
@@ -1,21 +1,9 @@
import { useAsyncFn } from 'react-use';
import { lastValueFrom } from 'rxjs';
import {
generatedAPI as correlationAPIv0alpha1,
Correlation as CorrelationK8s,
} from '@grafana/api-clients/rtkq/correlations/v0alpha1';
import {
getDataSourceSrv,
FetchResponse,
CorrelationData,
CorrelationsData,
config,
CorrelationExternal,
CorrelationQuery,
} from '@grafana/runtime';
import { getDataSourceSrv, FetchResponse, CorrelationData, CorrelationsData } from '@grafana/runtime';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { dispatch } from 'app/store/store';
//import { dispatch } from 'app/store/store';
import {
Correlation,
@@ -36,7 +24,7 @@ export interface CorrelationsResponse {
totalCount: number;
}
const toEnrichedCorrelationData = ({ sourceUID, ...correlation }: Correlation): CorrelationData | undefined => {
export const toEnrichedCorrelationData = ({ sourceUID, ...correlation }: Correlation): CorrelationData | undefined => {
const sourceDatasource = getDataSourceSrv().getInstanceSettings(sourceUID);
const targetDatasource =
correlation.type === 'query' ? getDataSourceSrv().getInstanceSettings(correlation.targetUID) : undefined;
@@ -80,60 +68,6 @@ const toEnrichedCorrelationData = ({ sourceUID, ...correlation }: Correlation):
return undefined;
};
/*
the various todos in this function relate to some changes that were required for correlations to be added to app platform
we now must resolve those changes with the way correlations currently functions
the main one is around the various source/target references - current this just refers to datasources by UID, but I was
required to do a group/name combination - how do I resolve this?
secondly, I was required to remove the provisioned flag. This provisioned flag locks the correlations record and makes it readonly
so people don't make edits that will be overwritten by provisioned correlations. Maybe this isn't relevant for app platform and hardcoding it to false is fine.
With transformations, I just didn't convert because they should be straightforward, that can be ignored for now and I'll work on it
also, maybe this should be closer to the app platform code, like in the client code? any other best practices?
*/
const toEnrichedCorrelationDataK8s = (item: CorrelationK8s): CorrelationData | undefined => {
if (item.metadata.name !== undefined) {
const baseCor = {
uid: item.metadata.name,
sourceUID: item.spec.source.name, //todo
label: item.spec.label,
description: item.spec.description,
provisioned: false, // todo,
};
if (item.spec.type === 'external') {
const extCorr: CorrelationExternal = {
...baseCor,
type: 'external',
config: {
field: item.spec.config.field,
target: {
url: item.spec.config.target.url || '',
},
transformations: [], // todo fix
},
};
return toEnrichedCorrelationData(extCorr);
} else {
const queryCorr: CorrelationQuery = {
...baseCor,
type: 'query',
targetUID: item.spec.target?.name || '', // todo
config: {
field: item.spec.config.field,
target: item.spec.config.target,
transformations: [], // todo fix
},
};
return toEnrichedCorrelationData(queryCorr);
}
} else {
return undefined;
}
};
const validSourceFilter = (correlation: CorrelationData | undefined): correlation is CorrelationData => !!correlation;
export const toEnrichedCorrelationsData = (correlationsResponse: CorrelationsResponse): CorrelationsData => {
@@ -158,27 +92,16 @@ export const useCorrelations = () => {
const [getInfo, get] = useAsyncFn<(params: GetCorrelationsParams) => Promise<CorrelationsData>>(
async (params) => {
if (config.featureToggles.kubernetesCorrelations) {
// the legacy version has pages , how does one accomplish this when getting a full list back?
const { data } = correlationAPIv0alpha1.endpoints.listCorrelation.useQuery({});
const enrichedCorrelations =
data !== undefined
? data.items.map((item) => toEnrichedCorrelationDataK8s(item)).filter((i) => i !== undefined)
: [];
// todo returning bad response data, how to fix?
return { correlations: enrichedCorrelations, page: 0, limit: 1000, totalCount: enrichedCorrelations.length };
} else {
return lastValueFrom(
backend.fetch<CorrelationsResponse>({
url: '/api/datasources/correlations',
params: { page: params.page },
method: 'GET',
showErrorAlert: false,
})
)
.then(getData)
.then(toEnrichedCorrelationsData);
}
return lastValueFrom(
backend.fetch<CorrelationsResponse>({
url: '/api/datasources/correlations',
params: { page: params.page },
method: 'GET',
showErrorAlert: false,
})
)
.then(getData)
.then(toEnrichedCorrelationsData);
},
[backend]
@@ -186,35 +109,17 @@ export const useCorrelations = () => {
const [createInfo, create] = useAsyncFn<(params: CreateCorrelationParams) => Promise<CorrelationData>>(
async ({ sourceUID, ...correlation }) => {
if (config.featureToggles.kubernetesCorrelations) {
const result = await dispatch(
correlationAPIv0alpha1.endpoints.createCorrelation.initiate({
correlation: {
apiVersion: 'correlations.grafana.app/v0alpha1',
kind: 'Correlations',
metadata: {},
spec: {
...correlation,
label: correlation.label ?? '',
source: { name: sourceUID, group: '' },
config: { ...correlation.config, transformations: [] },
},
},
})
);
return result;
} else {
return backend
.post<CreateCorrelationResponse>(`/api/datasources/uid/${sourceUID}/correlations`, correlation)
.then((response) => {
const enrichedCorrelation = toEnrichedCorrelationData(response.result);
if (enrichedCorrelation !== undefined) {
return enrichedCorrelation;
} else {
throw new Error('invalid sourceUID');
}
});
}
return backend
.post<CreateCorrelationResponse>(`/api/datasources/uid/${sourceUID}/correlations`, correlation)
.then((response) => {
const enrichedCorrelation = toEnrichedCorrelationData(response.result);
if (enrichedCorrelation !== undefined) {
return enrichedCorrelation;
} else {
throw new Error('invalid sourceUID');
}
});
// }
},
[backend]
);
@@ -0,0 +1,100 @@
import {
generatedAPI as correlationAPIv0alpha1,
Correlation as CorrelationK8s,
} from '@grafana/api-clients/rtkq/correlations/v0alpha1';
import { CorrelationData, CorrelationExternal, CorrelationQuery } from '@grafana/runtime';
import { toEnrichedCorrelationData } from './useCorrelations';
/*
the various todos in this function relate to some changes that were required for correlations to be added to app platform
we now must resolve those changes with the way correlations currently functions
the main one is around the various source/target references - current this just refers to datasources by UID, but I was
required to do a group/name combination - how do I resolve this?
secondly, I was required to remove the provisioned flag. This provisioned flag locks the correlations record and makes it readonly
so people don't make edits that will be overwritten by provisioned correlations. Maybe this isn't relevant for app platform and hardcoding it to false is fine.
With transformations, I just didn't convert because they should be straightforward, that can be ignored for now and I'll work on it
also, maybe this should be closer to the app platform code, like in the client code? any other best practices?
*/
export const toEnrichedCorrelationDataK8s = (item: CorrelationK8s): CorrelationData | undefined => {
if (item.metadata.name !== undefined) {
const baseCor = {
uid: item.metadata.name,
sourceUID: item.spec.source.name, //todo
label: item.spec.label,
description: item.spec.description,
provisioned: false, // todo,
};
if (item.spec.type === 'external') {
const extCorr: CorrelationExternal = {
...baseCor,
type: 'external',
config: {
field: item.spec.config.field,
target: {
url: item.spec.config.target.url || '',
},
transformations: [], // todo fix
},
};
return toEnrichedCorrelationData(extCorr);
} else {
const queryCorr: CorrelationQuery = {
...baseCor,
type: 'query',
targetUID: item.spec.target?.name || '', // todo
config: {
field: item.spec.config.field,
target: item.spec.config.target,
transformations: [], // todo fix
},
};
return toEnrichedCorrelationData(queryCorr);
}
} else {
return undefined;
}
};
export const useCorrelationsK8s = () => {
const { data, isLoading, error } = correlationAPIv0alpha1.endpoints.listCorrelation.useQuery({});
const enrichedCorrelations =
data !== undefined
? data.items.map((item) => toEnrichedCorrelationDataK8s(item)).filter((i) => i !== undefined)
: [];
// todo returning bad response data, how to fix?
return {
get: {
execute: () => {},
value: { correlations: enrichedCorrelations, page: 0, limit: 1000, totalCount: enrichedCorrelations.length },
loading: isLoading,
error,
},
};
};
/*
if (config.featureToggles.kubernetesCorrelations) {
const result = await dispatch(
correlationAPIv0alpha1.endpoints.createCorrelation.initiate({
correlation: {
apiVersion: 'correlations.grafana.app/v0alpha1',
kind: 'Correlations',
metadata: {},
spec: {
...correlation,
label: correlation.label ?? '',
source: { name: sourceUID, group: '' },
config: { ...correlation.config, transformations: [] },
},
},
})
);
return result;
} else {
*/
+2 -1
View File
@@ -139,7 +139,8 @@ export function getAppRoutes(): RouteDescriptor[] {
{
path: '/datasources/correlations',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "CorrelationsPage" */ 'app/features/correlations/CorrelationsPage')
() =>
import(/* webpackChunkName: "CorrelationsPageWrapper" */ 'app/features/correlations/CorrelationsPageWrapper')
),
},
{