From 854991adc2cfd79e8357b1e541e6341c4e8c0624 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 12 Dec 2024 16:40:10 +0000 Subject: [PATCH] --wip-- [skip ci] --- .../alerting/unified/RuleEditor.test.tsx | 106 ++++++++++++++++++ .../rule-editor/CloudRulesSourcePicker.tsx | 4 +- .../CloudDataSourceSelector.tsx | 5 + .../unified/mocks/server/all-handlers.ts | 2 + .../mocks/server/handlers/datasources.ts | 4 + .../mocks/server/handlers/mimirRuler.ts | 22 ++++ .../mocks/server/handlers/prometheus.ts | 12 ++ .../MonacoQueryField.test.tsx | 6 +- 8 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 public/app/features/alerting/unified/RuleEditor.test.tsx create mode 100644 public/app/features/alerting/unified/mocks/server/handlers/prometheus.ts diff --git a/public/app/features/alerting/unified/RuleEditor.test.tsx b/public/app/features/alerting/unified/RuleEditor.test.tsx new file mode 100644 index 00000000000..0d8647979fd --- /dev/null +++ b/public/app/features/alerting/unified/RuleEditor.test.tsx @@ -0,0 +1,106 @@ +import { Route, Routes } from 'react-router-dom-v5-compat'; +import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; +import { render, screen } from 'test/test-utils'; + +import { setAppEvents } from '@grafana/runtime'; +import appEvents from 'app/core/app_events'; +import RuleEditor from 'app/features/alerting/unified/RuleEditor'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks'; +import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; +import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; +import { AccessControlAction } from 'app/types'; + +setupMswServer(); + +// Required to make sure that loading the datasource plugin does not fail +// see public/app/plugins/datasource/loki/module.ts +setAppEvents(appEvents); + +const lokiDatasource = mockDataSource( + { + type: DataSourceType.Loki, + name: 'loki', + uid: 'oKflPJ2GF38UCq', + id: 1, + isDefault: true, + jsonData: { + manageAlerts: true, + }, + }, + { + alerting: true, + // needed to make the correct plugin components load + module: 'core:plugin/loki', + id: 'loki', + } +); + +beforeEach(() => { + grantUserPermissions([ + AccessControlAction.AlertingInstanceCreate, + AccessControlAction.FoldersRead, + AccessControlAction.DataSourcesRead, + AccessControlAction.AlertingRuleCreate, + AccessControlAction.AlertingRuleExternalWrite, + AccessControlAction.AlertingRuleExternalRead, + ]); + setupDataSources(lokiDatasource); +}); + +const renderRecordingRuleEditor = () => { + return render( + + } /> + , + { + historyOptions: { + initialEntries: [`/alerting/new/recording`], + }, + } + ); +}; + +describe('Recording rules', () => { + it('allows user to create loki recording rule', async () => { + const { user } = renderRecordingRuleEditor(); + await screen.findByText('New recording rule'); + + const datasourcePicker = await screen.findByLabelText(/select data source/i); + await user.click(datasourcePicker); + + await selectOptionInTest(datasourcePicker, /loki/i); + + const editor = await screen.findByPlaceholderText(/text to find/i); + await user.type(editor, '1'); + + await user.click(screen.getByText(/run query/i)); + // screen.debug(await screen.findByTestId('loki-editor'), Infinity); + expect(await screen.findByText(/No data/)).toBeInTheDocument(); + }); + + // it('renders ', async () => { + // const { user } = render( + // + // } /> + // , + // { + // historyOptions: { + // initialEntries: [`/alerting/new/recording`], + // }, + // } + // ); + + // await screen.findByText('New recording rule'); + + // const datasourcePicker = await screen.findByLabelText(/select data source/i); + // await user.click(datasourcePicker); + + // await selectOptionInTest(datasourcePicker, /loki/i); + + // const editor = await screen.findByPlaceholderText(/text to find/i); + // await user.type(editor, '1'); + + // await user.click(screen.getByText(/run query/i)); + // }); +}); diff --git a/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx b/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx index 8c18b146926..486f246dcdd 100644 --- a/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx @@ -11,9 +11,10 @@ interface Props { value: string | null; onBlur?: () => void; name?: string; + id?: string; } -export function CloudRulesSourcePicker({ value, disabled, ...props }: Props): JSX.Element { +export function CloudRulesSourcePicker({ value, disabled, id, ...props }: Props): JSX.Element { const { rulesSourcesWithRuler: dataSourcesWithRuler, isLoading } = useRulesSourcesWithRuler(); const dataSourceFilter = useCallback( @@ -30,6 +31,7 @@ export function CloudRulesSourcePicker({ value, disabled, ...props }: Props): JS alerting filter={dataSourceFilter} current={value} + inputId={id} {...props} /> ); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx index 5bcbaa7e620..b83b1ccc77e 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx @@ -22,6 +22,9 @@ export const CloudDataSourceSelector = ({ disabled, onChangeCloudDatasource }: C const styles = useStyles2(getStyles); const ruleFormType = watch('type'); + // Include an ID to make the label + datasource picker linked + properly accessible + const id = 'cloud-data-source-picker'; + return ( <>
@@ -31,11 +34,13 @@ export const CloudDataSourceSelector = ({ disabled, onChangeCloudDatasource }: C label={disabled ? 'Data source' : 'Select data source'} error={errors.dataSourceName?.message} invalid={!!errors.dataSourceName?.message} + htmlFor={id} > ( { // reset expression as they don't need to persist after changing datasources diff --git a/public/app/features/alerting/unified/mocks/server/all-handlers.ts b/public/app/features/alerting/unified/mocks/server/all-handlers.ts index d812997314e..04fe0232b85 100644 --- a/public/app/features/alerting/unified/mocks/server/all-handlers.ts +++ b/public/app/features/alerting/unified/mocks/server/all-handlers.ts @@ -17,6 +17,7 @@ import mimirRulerHandlers from 'app/features/alerting/unified/mocks/server/handl import notificationsHandlers from 'app/features/alerting/unified/mocks/server/handlers/notifications'; import pluginsHandlers from 'app/features/alerting/unified/mocks/server/handlers/plugins'; import allPluginHandlers from 'app/features/alerting/unified/mocks/server/handlers/plugins/all-plugin-handlers'; +import prometheusHandlers from 'app/features/alerting/unified/mocks/server/handlers/prometheus'; import provisioningHandlers from 'app/features/alerting/unified/mocks/server/handlers/provisioning'; import searchHandlers from 'app/features/alerting/unified/mocks/server/handlers/search'; import silenceHandlers from 'app/features/alerting/unified/mocks/server/handlers/silences'; @@ -35,6 +36,7 @@ const allHandlers = [ ...folderHandlers, ...pluginsHandlers, ...provisioningHandlers, + ...prometheusHandlers, ...silenceHandlers, ...searchHandlers, ...notificationsHandlers, diff --git a/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts b/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts index 3ea92548fb5..7c9938de2be 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts @@ -46,10 +46,14 @@ const resourcesMetadataHandler = () => HttpResponse.json({ status: 'success', data: {} }) ); +const resourcesLabelsHandler2 = () => + http.get('/api/datasources/uid/:datasourceUid/resources/labels', () => HttpResponse.json({ status: 'success' })); + const datasourcesHandlers = [ datasourceBuildInfoHandler(), labelValuesHandler(), resourcesLabelsHandler(), resourcesMetadataHandler(), + resourcesLabelsHandler2(), ]; export default datasourcesHandlers; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts b/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts index 6dbdff31031..cbc36b244bc 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts @@ -39,6 +39,26 @@ export const updateRulerRuleNamespaceHandler = (options?: HandlerOptions) => { }); }; +const rulerRulesHandler = () => { + return http.get<{ datasourceUid: string }>(`/api/ruler/:datasourceUid/api/v1/rules`, () => { + return HttpResponse.json({}); + }); +}; + +const rulerRulesTestHandler = () => { + /** + * This particular response is needed to allow tests to handle the case of testing + * whether a ruler supports rules (see `isCortexErrorResponse` method) + */ + const missingGroupResponse = { + message: 'get rule group user="", namespace="", name="": group does not exist\n', + traceId: '', + }; + return http.get<{ datasourceUid: string }>('/api/ruler/:datasourceUid/api/v1/rules/test/test', () => { + return HttpResponse.json(missingGroupResponse, { status: 404 }); + }); +}; + export const rulerRuleGroupHandler = (options?: HandlerOptions) => { return http.get<{ namespaceName: string; groupName: string }>( `/api/ruler/:dataSourceUID/api/v1/rules/:namespaceName/:groupName`, @@ -84,6 +104,8 @@ export const deleteRulerRuleGroupHandler = () => { const handlers = [ getRulerRulesHandler(), prometheusRulesHandler(), + rulerRulesHandler(), + rulerRulesTestHandler(), updateRulerRuleNamespaceHandler(), rulerRuleGroupHandler(), deleteRulerRuleGroupHandler(), diff --git a/public/app/features/alerting/unified/mocks/server/handlers/prometheus.ts b/public/app/features/alerting/unified/mocks/server/handlers/prometheus.ts new file mode 100644 index 00000000000..658ba6d5ba2 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/prometheus.ts @@ -0,0 +1,12 @@ +import { http, HttpResponse } from 'msw'; + +export const getPrometheusRulesHandler = () => { + return http.get(`/api/prometheus/:datasourceUid/api/v1/rules`, () => { + return HttpResponse.json({ + data: { groups: [] }, + }); + }); +}; + +const handlers = [getPrometheusRulesHandler()]; +export default handlers; diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx index 71b59d08bb1..72bb711d865 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { selectors } from '@grafana/e2e-selectors'; @@ -31,8 +32,11 @@ function renderComponent({ describe('MonacoQueryField', () => { test('Renders with no errors', async () => { renderComponent(); - + const user = userEvent.setup(); const monacoEditor = await screen.findByTestId(selectors.components.ReactMonacoEditor.editorLazy); expect(monacoEditor).toBeInTheDocument(); + + const editor = await screen.findByLabelText(/editor content/i); + await user.type(editor, '1'); }); });