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:
Giordano Ricci 2022-11-08 10:10:09 +00:00 committed by GitHub
parent 96cdf77995
commit e4c394dfcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 18 deletions

View File

@ -8,7 +8,14 @@ import { MockDataSourceApi } from 'test/mocks/datasource_srv';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
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 { contextSrv } from 'app/core/services/context_srv';
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 (
datasources: ConstructorParameters<typeof MockDataSourceSrv>[0] = {},
correlations: Correlation[] = []
@ -217,6 +218,20 @@ const renderWithContext = async (
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(() => {
mocks.contextSrv.hasPermission.mockImplementation(() => true);
});
@ -254,6 +269,10 @@ describe('CorrelationsPage', () => {
});
});
afterEach(() => {
mocks.reportInteraction.mockClear();
});
it('shows CTA', async () => {
// insert form should not be present
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
await waitForElementToBeRemoved(() => screen.queryByRole('button', { name: /add$/i }));
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_added');
// the table showing correlations should have appeared
expect(screen.getByRole('table')).toBeInTheDocument();
});
});
describe('With correlations', () => {
afterEach(() => {
mocks.reportInteraction.mockClear();
});
let queryRowsByCellValue: (columnName: Matcher, textValue: Matcher) => HTMLTableRowElement[];
let getHeaderByName: (columnName: Matcher) => HTMLTableCellElement;
let queryCellsByColumnName: (columnName: Matcher) => HTMLTableCellElement[];
@ -430,6 +455,8 @@ describe('CorrelationsPage', () => {
// the form should get removed after successful submissions
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 () => {
@ -460,6 +487,8 @@ describe('CorrelationsPage', () => {
fireEvent.click(confirmButton);
await waitForElementToBeRemoved(() => screen.queryByRole('cell', { name: /some label$/i }));
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_deleted');
});
it('correctly edits correlations', async () => {
@ -468,6 +497,8 @@ describe('CorrelationsPage', () => {
const rowExpanderButton = within(tableRows[0]).getByRole('button', { name: /toggle row expanded/i });
fireEvent.click(rowExpanderButton);
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_details_expanded');
await waitForElementToBeRemoved(() => screen.queryByText(/loading query editor/i));
fireEvent.change(screen.getByRole('textbox', { name: /label/i }), { target: { value: 'edited label' } });
@ -482,6 +513,8 @@ describe('CorrelationsPage', () => {
await waitFor(() => {
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);
expect(mocks.reportInteraction).toHaveBeenLastCalledWith('grafana_correlations_details_expanded');
// wait for the form to be rendered and query editor to be mounted
await waitForElementToBeRemoved(() => screen.queryByText(/loading query editor/i));

View File

@ -4,7 +4,7 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { CellProps, SortByFn } from 'react-table';
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 { Page } from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core';
@ -15,6 +15,7 @@ import { AddCorrelationForm } from './Forms/AddCorrelationForm';
import { EditCorrelationForm } from './Forms/EditCorrelationForm';
import { EmptyCorrelationsCTA } from './components/EmptyCorrelationsCTA';
import { Column, Table } from './components/Table';
import type { RemoveCorrelationParams } from './types';
import { CorrelationData, useCorrelations } from './useCorrelations';
const sortDatasource: SortByFn<CorrelationData> = (a, b, column) =>
@ -43,11 +44,31 @@ export default function CorrelationsPage() {
const canWriteCorrelations = contextSrv.hasPermission(AccessControlAction.DataSourcesWrite);
const handleAdd = useCallback(() => {
const handleAdded = useCallback(() => {
reportInteraction('grafana_correlations_added');
fetchCorrelations();
setIsAdding(false);
}, [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(() => {
if (!remove.error && !remove.loading && remove.value) {
fetchCorrelations();
@ -66,11 +87,11 @@ export default function CorrelationsPage() {
!readOnly && (
<DeleteButton
aria-label="delete correlation"
onConfirm={() => remove.execute({ sourceUID, uid })}
onConfirm={() => handleDelete({ sourceUID, uid })}
closeOnConfirm
/>
),
[remove]
[handleDelete]
);
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 && (
<Table
renderExpandedRow={({ target, source, ...correlation }) => (
<EditCorrelationForm
correlation={{ ...correlation, sourceUID: source.uid, targetUID: target.uid }}
onUpdated={fetchCorrelations}
readOnly={isSourceReadOnly({ source }) || !canWriteCorrelations}
renderExpandedRow={(correlation) => (
<ExpendedRow
correlation={correlation}
onUpdated={handleUpdated}
readOnly={isSourceReadOnly({ source: correlation.source }) || !canWriteCorrelations}
/>
)}
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) => ({
root: css`
display: flex;

View File

@ -48,7 +48,7 @@ export const useCorrelations = () => {
[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}`),
[backend]
);