mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Correlations: add tracking for add, update, delete, and details expanded (#58239)
* Correlations: add tracking for add, update, delete, and details expanded * add tests * change delete event * rename handlers
This commit is contained in:
@@ -8,7 +8,14 @@ import { MockDataSourceApi } from 'test/mocks/datasource_srv';
|
|||||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
import { DataSourcePluginMeta } from '@grafana/data';
|
import { DataSourcePluginMeta } from '@grafana/data';
|
||||||
import { BackendSrv, FetchError, FetchResponse, setDataSourceSrv, BackendSrvRequest } from '@grafana/runtime';
|
import {
|
||||||
|
BackendSrv,
|
||||||
|
FetchError,
|
||||||
|
FetchResponse,
|
||||||
|
setDataSourceSrv,
|
||||||
|
BackendSrvRequest,
|
||||||
|
reportInteraction,
|
||||||
|
} from '@grafana/runtime';
|
||||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { configureStore } from 'app/store/configureStore';
|
import { configureStore } from 'app/store/configureStore';
|
||||||
@@ -47,12 +54,6 @@ function createFetchError(overrides?: DeepPartial<FetchError>): FetchError {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
jest.mock('app/core/services/context_srv');
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
contextSrv: jest.mocked(contextSrv),
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderWithContext = async (
|
const renderWithContext = async (
|
||||||
datasources: ConstructorParameters<typeof MockDataSourceSrv>[0] = {},
|
datasources: ConstructorParameters<typeof MockDataSourceSrv>[0] = {},
|
||||||
correlations: Correlation[] = []
|
correlations: Correlation[] = []
|
||||||
@@ -217,6 +218,20 @@ const renderWithContext = async (
|
|||||||
return renderResult;
|
return renderResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jest.mock('app/core/services/context_srv');
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
contextSrv: jest.mocked(contextSrv),
|
||||||
|
reportInteraction: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
reportInteraction: (...args: Parameters<typeof reportInteraction>) => {
|
||||||
|
mocks.reportInteraction(...args);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mocks.contextSrv.hasPermission.mockImplementation(() => true);
|
mocks.contextSrv.hasPermission.mockImplementation(() => true);
|
||||||
});
|
});
|
||||||
@@ -254,6 +269,10 @@ describe('CorrelationsPage', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mocks.reportInteraction.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
it('shows CTA', async () => {
|
it('shows CTA', async () => {
|
||||||
// insert form should not be present
|
// insert form should not be present
|
||||||
expect(screen.queryByRole('button', { name: /add$/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: /add$/i })).not.toBeInTheDocument();
|
||||||
@@ -305,12 +324,18 @@ describe('CorrelationsPage', () => {
|
|||||||
// Waits for the form to be removed, meaning the correlation got successfully saved
|
// Waits for the form to be removed, meaning the correlation got successfully saved
|
||||||
await waitForElementToBeRemoved(() => screen.queryByRole('button', { name: /add$/i }));
|
await waitForElementToBeRemoved(() => screen.queryByRole('button', { name: /add$/i }));
|
||||||
|
|
||||||
|
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_added');
|
||||||
|
|
||||||
// the table showing correlations should have appeared
|
// the table showing correlations should have appeared
|
||||||
expect(screen.getByRole('table')).toBeInTheDocument();
|
expect(screen.getByRole('table')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('With correlations', () => {
|
describe('With correlations', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
mocks.reportInteraction.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
let queryRowsByCellValue: (columnName: Matcher, textValue: Matcher) => HTMLTableRowElement[];
|
let queryRowsByCellValue: (columnName: Matcher, textValue: Matcher) => HTMLTableRowElement[];
|
||||||
let getHeaderByName: (columnName: Matcher) => HTMLTableCellElement;
|
let getHeaderByName: (columnName: Matcher) => HTMLTableCellElement;
|
||||||
let queryCellsByColumnName: (columnName: Matcher) => HTMLTableCellElement[];
|
let queryCellsByColumnName: (columnName: Matcher) => HTMLTableCellElement[];
|
||||||
@@ -430,6 +455,8 @@ describe('CorrelationsPage', () => {
|
|||||||
|
|
||||||
// the form should get removed after successful submissions
|
// the form should get removed after successful submissions
|
||||||
await waitForElementToBeRemoved(() => screen.queryByRole('button', { name: /add$/i }));
|
await waitForElementToBeRemoved(() => screen.queryByRole('button', { name: /add$/i }));
|
||||||
|
|
||||||
|
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_added');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly closes the form when clicking on the close icon', async () => {
|
it('correctly closes the form when clicking on the close icon', async () => {
|
||||||
@@ -460,6 +487,8 @@ describe('CorrelationsPage', () => {
|
|||||||
fireEvent.click(confirmButton);
|
fireEvent.click(confirmButton);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.queryByRole('cell', { name: /some label$/i }));
|
await waitForElementToBeRemoved(() => screen.queryByRole('cell', { name: /some label$/i }));
|
||||||
|
|
||||||
|
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_deleted');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly edits correlations', async () => {
|
it('correctly edits correlations', async () => {
|
||||||
@@ -468,6 +497,8 @@ describe('CorrelationsPage', () => {
|
|||||||
const rowExpanderButton = within(tableRows[0]).getByRole('button', { name: /toggle row expanded/i });
|
const rowExpanderButton = within(tableRows[0]).getByRole('button', { name: /toggle row expanded/i });
|
||||||
fireEvent.click(rowExpanderButton);
|
fireEvent.click(rowExpanderButton);
|
||||||
|
|
||||||
|
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_details_expanded');
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.queryByText(/loading query editor/i));
|
await waitForElementToBeRemoved(() => screen.queryByText(/loading query editor/i));
|
||||||
|
|
||||||
fireEvent.change(screen.getByRole('textbox', { name: /label/i }), { target: { value: 'edited label' } });
|
fireEvent.change(screen.getByRole('textbox', { name: /label/i }), { target: { value: 'edited label' } });
|
||||||
@@ -482,6 +513,8 @@ describe('CorrelationsPage', () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByRole('cell', { name: /edited label$/i })).toBeInTheDocument();
|
expect(screen.queryByRole('cell', { name: /edited label$/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_edited');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -526,6 +559,8 @@ describe('CorrelationsPage', () => {
|
|||||||
|
|
||||||
fireEvent.click(rowExpanderButton);
|
fireEvent.click(rowExpanderButton);
|
||||||
|
|
||||||
|
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_details_expanded');
|
||||||
|
|
||||||
// wait for the form to be rendered and query editor to be mounted
|
// wait for the form to be rendered and query editor to be mounted
|
||||||
await waitForElementToBeRemoved(() => screen.queryByText(/loading query editor/i));
|
await waitForElementToBeRemoved(() => screen.queryByText(/loading query editor/i));
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { CellProps, SortByFn } from 'react-table';
|
import { CellProps, SortByFn } from 'react-table';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { isFetchError } from '@grafana/runtime';
|
import { isFetchError, reportInteraction } from '@grafana/runtime';
|
||||||
import { Badge, Button, DeleteButton, HorizontalGroup, LoadingPlaceholder, useStyles2, Alert } from '@grafana/ui';
|
import { Badge, Button, DeleteButton, HorizontalGroup, LoadingPlaceholder, useStyles2, Alert } from '@grafana/ui';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
@@ -15,6 +15,7 @@ import { AddCorrelationForm } from './Forms/AddCorrelationForm';
|
|||||||
import { EditCorrelationForm } from './Forms/EditCorrelationForm';
|
import { EditCorrelationForm } from './Forms/EditCorrelationForm';
|
||||||
import { EmptyCorrelationsCTA } from './components/EmptyCorrelationsCTA';
|
import { EmptyCorrelationsCTA } from './components/EmptyCorrelationsCTA';
|
||||||
import { Column, Table } from './components/Table';
|
import { Column, Table } from './components/Table';
|
||||||
|
import type { RemoveCorrelationParams } from './types';
|
||||||
import { CorrelationData, useCorrelations } from './useCorrelations';
|
import { CorrelationData, useCorrelations } from './useCorrelations';
|
||||||
|
|
||||||
const sortDatasource: SortByFn<CorrelationData> = (a, b, column) =>
|
const sortDatasource: SortByFn<CorrelationData> = (a, b, column) =>
|
||||||
@@ -43,11 +44,31 @@ export default function CorrelationsPage() {
|
|||||||
|
|
||||||
const canWriteCorrelations = contextSrv.hasPermission(AccessControlAction.DataSourcesWrite);
|
const canWriteCorrelations = contextSrv.hasPermission(AccessControlAction.DataSourcesWrite);
|
||||||
|
|
||||||
const handleAdd = useCallback(() => {
|
const handleAdded = useCallback(() => {
|
||||||
|
reportInteraction('grafana_correlations_added');
|
||||||
fetchCorrelations();
|
fetchCorrelations();
|
||||||
setIsAdding(false);
|
setIsAdding(false);
|
||||||
}, [fetchCorrelations]);
|
}, [fetchCorrelations]);
|
||||||
|
|
||||||
|
const handleUpdated = useCallback(() => {
|
||||||
|
reportInteraction('grafana_correlations_edited');
|
||||||
|
fetchCorrelations();
|
||||||
|
}, [fetchCorrelations]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
(params: RemoveCorrelationParams) => {
|
||||||
|
remove.execute(params);
|
||||||
|
},
|
||||||
|
[remove]
|
||||||
|
);
|
||||||
|
|
||||||
|
// onDelete - triggers when deleting a correlation
|
||||||
|
useEffect(() => {
|
||||||
|
if (remove.value) {
|
||||||
|
reportInteraction('grafana_correlations_deleted');
|
||||||
|
}
|
||||||
|
}, [remove.value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!remove.error && !remove.loading && remove.value) {
|
if (!remove.error && !remove.loading && remove.value) {
|
||||||
fetchCorrelations();
|
fetchCorrelations();
|
||||||
@@ -66,11 +87,11 @@ export default function CorrelationsPage() {
|
|||||||
!readOnly && (
|
!readOnly && (
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
aria-label="delete correlation"
|
aria-label="delete correlation"
|
||||||
onConfirm={() => remove.execute({ sourceUID, uid })}
|
onConfirm={() => handleDelete({ sourceUID, uid })}
|
||||||
closeOnConfirm
|
closeOnConfirm
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[remove]
|
[handleDelete]
|
||||||
);
|
);
|
||||||
|
|
||||||
const columns = useMemo<Array<Column<CorrelationData>>>(
|
const columns = useMemo<Array<Column<CorrelationData>>>(
|
||||||
@@ -142,15 +163,15 @@ export default function CorrelationsPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
{isAdding && <AddCorrelationForm onClose={() => setIsAdding(false)} onCreated={handleAdd} />}
|
{isAdding && <AddCorrelationForm onClose={() => setIsAdding(false)} onCreated={handleAdded} />}
|
||||||
|
|
||||||
{data && data.length >= 1 && (
|
{data && data.length >= 1 && (
|
||||||
<Table
|
<Table
|
||||||
renderExpandedRow={({ target, source, ...correlation }) => (
|
renderExpandedRow={(correlation) => (
|
||||||
<EditCorrelationForm
|
<ExpendedRow
|
||||||
correlation={{ ...correlation, sourceUID: source.uid, targetUID: target.uid }}
|
correlation={correlation}
|
||||||
onUpdated={fetchCorrelations}
|
onUpdated={handleUpdated}
|
||||||
readOnly={isSourceReadOnly({ source }) || !canWriteCorrelations}
|
readOnly={isSourceReadOnly({ source: correlation.source }) || !canWriteCorrelations}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -164,6 +185,28 @@ export default function CorrelationsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExpandedRowProps {
|
||||||
|
correlation: CorrelationData;
|
||||||
|
readOnly: boolean;
|
||||||
|
onUpdated: () => void;
|
||||||
|
}
|
||||||
|
function ExpendedRow({ correlation: { source, target, ...correlation }, readOnly, onUpdated }: ExpandedRowProps) {
|
||||||
|
useEffect(
|
||||||
|
() => reportInteraction('grafana_correlations_details_expanded'),
|
||||||
|
// we only want to fire this on first render
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditCorrelationForm
|
||||||
|
correlation={{ ...correlation, sourceUID: source.uid, targetUID: target.uid }}
|
||||||
|
onUpdated={onUpdated}
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const getDatasourceCellStyles = (theme: GrafanaTheme2) => ({
|
const getDatasourceCellStyles = (theme: GrafanaTheme2) => ({
|
||||||
root: css`
|
root: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const useCorrelations = () => {
|
|||||||
[backend]
|
[backend]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [removeInfo, remove] = useAsyncFn<(params: RemoveCorrelationParams) => Promise<void>>(
|
const [removeInfo, remove] = useAsyncFn<(params: RemoveCorrelationParams) => Promise<{ message: string }>>(
|
||||||
({ sourceUID, uid }) => backend.delete(`/api/datasources/uid/${sourceUID}/correlations/${uid}`),
|
({ sourceUID, uid }) => backend.delete(`/api/datasources/uid/${sourceUID}/correlations/${uid}`),
|
||||||
[backend]
|
[backend]
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user