diff --git a/e2e/various-suite/helpers/prometheus-helpers.ts b/e2e/various-suite/helpers/prometheus-helpers.ts new file mode 100644 index 00000000000..265095ecd65 --- /dev/null +++ b/e2e/various-suite/helpers/prometheus-helpers.ts @@ -0,0 +1,62 @@ +import { e2e } from '../../utils'; + +/** + * Create a Prom data source + */ +export function createPromDS(dataSourceID: string, name: string): void { + // login + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); + + // select the prometheus DS + e2e.pages.AddDataSource.visit(); + e2e.pages.AddDataSource.dataSourcePluginsV2(dataSourceID) + .scrollIntoView() + .should('be.visible') // prevents flakiness + .click(); + + // add url for DS to save without error + e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090'); + + // name the DS + e2e.pages.DataSource.name().clear(); + e2e.pages.DataSource.name().type(name); + e2e.pages.DataSource.saveAndTest().click(); +} + +export function getResources() { + cy.intercept(/__name__/g, metricResponse); + + cy.intercept(/metadata/g, metadataResponse); + + cy.intercept(/labels/g, labelsResponse); +} + +const metricResponse = { + status: 'success', + data: ['metric1', 'metric2'], +}; + +const metadataResponse = { + status: 'success', + data: { + metric1: [ + { + type: 'counter', + help: 'metric1 help', + unit: '', + }, + ], + metric2: [ + { + type: 'counter', + help: 'metric2 help', + unit: '', + }, + ], + }, +}; + +const labelsResponse = { + status: 'success', + data: ['__name__', 'action', 'active', 'backend'], +}; diff --git a/e2e/various-suite/prometheus-annotations.spec.ts b/e2e/various-suite/prometheus-annotations.spec.ts new file mode 100644 index 00000000000..b32620dda70 --- /dev/null +++ b/e2e/various-suite/prometheus-annotations.spec.ts @@ -0,0 +1,75 @@ +import { selectors } from '@grafana/e2e-selectors'; + +import { e2e } from '../utils'; +import { addDashboard } from '../utils/flows'; + +import { createPromDS, getResources } from './helpers/prometheus-helpers'; + +const DATASOURCE_ID = 'Prometheus'; + +const DATASOURCE_NAME = 'aprometheusAnnotationDS'; + +/** + * Click dashboard settings and then the variables tab + * + */ +function navigateToAnnotations() { + e2e.components.PageToolbar.item('Dashboard settings').click(); + e2e.components.Tab.title('Annotations').click(); +} + +function addPrometheusAnnotation(annotationName: string) { + e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTAV2().click(); + getResources(); + e2e.pages.Dashboard.Settings.Annotations.Settings.name().clear().type(annotationName); + e2e.components.DataSourcePicker.container().should('be.visible').click(); + cy.contains(DATASOURCE_NAME).scrollIntoView().should('be.visible').click(); +} + +describe('Prometheus annotations', () => { + beforeEach(() => { + createPromDS(DATASOURCE_ID, DATASOURCE_NAME); + }); + + it('should navigate to variable query editor', () => { + const annotationName = 'promAnnotation'; + addDashboard(); + navigateToAnnotations(); + addPrometheusAnnotation(annotationName); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser + .openButton() + .contains('Metrics browser') + .click(); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist').type('met'); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser + .metricList() + .should('exist') + .contains('metric1') + .click(); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist').click(); + + e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist').contains('metric1'); + + // check for other parts of the annotations + // min step + cy.get(`#${selectors.components.DataSource.Prometheus.annotations.minStep}`); + + // title + e2e.components.DataSource.Prometheus.annotations.title().scrollIntoView().should('exist'); + // tags + e2e.components.DataSource.Prometheus.annotations.tags().scrollIntoView().should('exist'); + // text + e2e.components.DataSource.Prometheus.annotations.text().scrollIntoView().should('exist'); + // series value as timestamp + e2e.components.DataSource.Prometheus.annotations.seriesValueAsTimestamp().scrollIntoView().should('exist'); + + e2e.pages.Dashboard.Settings.Annotations.NewAnnotation.previewInDashboard().click(); + + // check that annotation exists + cy.get('body').contains(annotationName); + }); +}); diff --git a/e2e/various-suite/prometheus-config.spec.ts b/e2e/various-suite/prometheus-config.spec.ts new file mode 100644 index 00000000000..09b7dfd1a95 --- /dev/null +++ b/e2e/various-suite/prometheus-config.spec.ts @@ -0,0 +1,116 @@ +import { selectors } from '@grafana/e2e-selectors'; + +import { e2e } from '../utils'; + +const DATASOURCE_ID = 'Prometheus'; +const DATASOURCE_TYPED_NAME = 'PrometheusDatasourceInstance'; + +describe('Prometheus config', () => { + beforeEach(() => { + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); + + e2e.pages.AddDataSource.visit(); + e2e.pages.AddDataSource.dataSourcePluginsV2(DATASOURCE_ID) + .scrollIntoView() + .should('be.visible') // prevents flakiness + .click(); + }); + + it('should have a connection settings component', () => { + e2e.components.DataSource.Prometheus.configPage.connectionSettings().should('be.visible'); + }); + + it('should have a managed alerts component', () => { + cy.get(`#${selectors.components.DataSource.Prometheus.configPage.manageAlerts}`).scrollIntoView().should('exist'); + }); + + it('should have a scrape interval component', () => { + e2e.components.DataSource.Prometheus.configPage.scrapeInterval().scrollIntoView().should('exist'); + }); + + it('should have a query timeout component', () => { + e2e.components.DataSource.Prometheus.configPage.queryTimeout().scrollIntoView().should('exist'); + }); + + it('should have a default editor component', () => { + e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist'); + }); + + it('should save the default editor when navigating to explore', () => { + e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist').click(); + + selectOption('Code'); + + e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090'); + + e2e.pages.DataSource.name().clear(); + e2e.pages.DataSource.name().type(DATASOURCE_TYPED_NAME); + e2e.pages.DataSource.saveAndTest().click(); + + e2e.pages.Explore.visit(); + + e2e.components.DataSourcePicker.container().should('be.visible').click(); + cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click(); + + const monacoLoadingText = 'Loading...'; + e2e.components.QueryField.container().should('be.visible').should('have.text', monacoLoadingText); + e2e.components.QueryField.container().should('be.visible').should('not.have.text', monacoLoadingText); + }); + + it('should have a disable metric lookup component', () => { + cy.get(`#${selectors.components.DataSource.Prometheus.configPage.disableMetricLookup}`) + .scrollIntoView() + .should('exist'); + }); + + it('should have a prometheus type component', () => { + e2e.components.DataSource.Prometheus.configPage.prometheusType().scrollIntoView().should('exist'); + }); + + it('should allow a user to add the version when the Prom type is selected', () => { + e2e.components.DataSource.Prometheus.configPage.prometheusType().scrollIntoView().should('exist').click(); + + selectOption('Prometheus'); + + e2e.components.DataSource.Prometheus.configPage.prometheusVersion().scrollIntoView().should('exist'); + }); + + it('should have a cache level component', () => { + e2e.components.DataSource.Prometheus.configPage.cacheLevel().scrollIntoView().should('exist'); + }); + + it('should have an incremental querying component', () => { + cy.get(`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`) + .scrollIntoView() + .should('exist'); + }); + + it('should allow a user to select a query overlap window when incremental querying is selected', () => { + cy.get(`#${selectors.components.DataSource.Prometheus.configPage.incrementalQuerying}`) + .scrollIntoView() + .should('exist') + .check({ force: true }); + + e2e.components.DataSource.Prometheus.configPage.queryOverlapWindow().scrollIntoView().should('exist'); + }); + + it('should have a disable recording rules component', () => { + cy.get(`#${selectors.components.DataSource.Prometheus.configPage.disableRecordingRules}`) + .scrollIntoView() + .should('exist'); + }); + + it('should have a custom query parameters component', () => { + e2e.components.DataSource.Prometheus.configPage.customQueryParameters().scrollIntoView().should('exist'); + }); + + it('should have an http method component', () => { + e2e.components.DataSource.Prometheus.configPage.httpMethod().scrollIntoView().should('exist'); + }); + + // exemplars tested in exemplar.spec +}); + +export function selectOption(option: string) { + cy.get("[aria-label='Select option']").contains(option).should('be.visible').click(); +} diff --git a/e2e/various-suite/prometheus-editor.spec.ts b/e2e/various-suite/prometheus-editor.spec.ts new file mode 100644 index 00000000000..b5b6b7874b8 --- /dev/null +++ b/e2e/various-suite/prometheus-editor.spec.ts @@ -0,0 +1,182 @@ +import { selectors } from '@grafana/e2e-selectors'; + +import { e2e } from '../utils'; + +import { getResources } from './helpers/prometheus-helpers'; + +const DATASOURCE_ID = 'Prometheus'; + +type editorType = 'Code' | 'Builder'; + +/** + * Login, create and save a Prometheus data source, navigate to code or builder + * + * @param editorType 'Code' or 'Builder' + */ +function navigateToEditor(editorType: editorType, name: string): void { + // login + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); + + // select the prometheus DS + e2e.pages.AddDataSource.visit(); + e2e.pages.AddDataSource.dataSourcePluginsV2(DATASOURCE_ID) + .scrollIntoView() + .should('be.visible') // prevents flakiness + .click(); + + // choose default editor + e2e.components.DataSource.Prometheus.configPage.defaultEditor().scrollIntoView().should('exist').click(); + selectOption(editorType); + + // add url for DS to save without error + e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090'); + + // name the DS + e2e.pages.DataSource.name().clear(); + e2e.pages.DataSource.name().type(name); + e2e.pages.DataSource.saveAndTest().click(); + + // visit explore + e2e.pages.Explore.visit(); + + // choose the right DS + e2e.components.DataSourcePicker.container().should('be.visible').click(); + cy.contains(name).scrollIntoView().should('be.visible').click(); +} + +describe('Prometheus query editor', () => { + it('should have a kickstart component', () => { + navigateToEditor('Code', 'prometheus'); + e2e.components.QueryBuilder.queryPatterns().scrollIntoView().should('exist'); + }); + + it('should have an explain component', () => { + navigateToEditor('Code', 'prometheus'); + e2e.components.DataSource.Prometheus.queryEditor.explain().scrollIntoView().should('exist'); + }); + + it('should have an editor toggle component', () => { + navigateToEditor('Code', 'prometheus'); + e2e.components.DataSource.Prometheus.queryEditor.editorToggle().scrollIntoView().should('exist'); + }); + + it('should have an options component with legend, format, step, type and exemplars', () => { + navigateToEditor('Code', 'prometheus'); + // open options + e2e.components.DataSource.Prometheus.queryEditor.options().scrollIntoView().should('exist').click(); + // check options + e2e.components.DataSource.Prometheus.queryEditor.legend().scrollIntoView().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.format().scrollIntoView().should('exist'); + cy.get(`#${selectors.components.DataSource.Prometheus.queryEditor.step}`).scrollIntoView().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.type().scrollIntoView().should('exist'); + cy.get(`#${selectors.components.DataSource.Prometheus.queryEditor.exemplars}`).scrollIntoView().should('exist'); + }); + + describe('Code editor', () => { + it('navigates to the code editor with editor type as code', () => { + navigateToEditor('Code', 'prometheusCode'); + }); + + it('navigates to the code editor and opens the metrics browser with metric search, labels, label values, and all components', () => { + navigateToEditor('Code', 'prometheusCode'); + + getResources(); + + e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist'); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser + .openButton() + .contains('Metrics browser') + .click(); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelNamesFilter().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelValuesFilter().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useAsRateQuery().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.validateSelector().should('exist'); + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.clear().should('exist'); + }); + + it('selects a metric in the metrics browser and uses the query', () => { + navigateToEditor('Code', 'prometheusCode'); + + getResources(); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser + .openButton() + .contains('Metrics browser') + .click(); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist').type('met'); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser + .metricList() + .should('exist') + .contains('metric1') + .click(); + + e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist').click(); + + e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist').contains('metric1'); + }); + }); + + describe('Query builder', () => { + it('navigates to the query builder with editor type as code', () => { + navigateToEditor('Builder', 'prometheusBuilder'); + }); + + it('the query builder contains metric select, label filters and operations', () => { + navigateToEditor('Builder', 'prometheusBuilder'); + + getResources(); + + e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist'); + e2e.components.QueryBuilder.labelSelect().should('exist'); + e2e.components.QueryBuilder.matchOperatorSelect().should('exist'); + e2e.components.QueryBuilder.valueSelect().should('exist'); + }); + + it('can select a metric and provide a hint', () => { + navigateToEditor('Builder', 'prometheusBuilder'); + + getResources(); + + e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click(); + + selectOption('metric1'); + + e2e.components.DataSource.Prometheus.queryEditor.builder.hints().contains('hint: add rate'); + }); + + it('should have the metrics explorer opened via the metric select', () => { + navigateToEditor('Builder', 'prometheusBuilder'); + + getResources(); + + e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click(); + + selectOption('Metrics explorer'); + + e2e.components.DataSource.Prometheus.queryEditor.builder.metricsExplorer().should('exist'); + }); + + // NEED TO COMPLETE QUEY ADVISOR WORK OR FIGURE OUT HOW TO ENABLE EXPERIMENTAL FEATURE TOGGLES + // it('should have a query advisor when enabled with feature toggle', () => { + // cy.window().then((win) => { + // win.localStorage.setItem('grafana.featureToggles', 'prometheusPromQAIL=0'); + + // navigateToEditor('Builder', 'prometheusBuilder'); + + // getResources(); + + // e2e.components.DataSource.Prometheus.queryEditor.builder.queryAdvisor().should('exist'); + // }); + // }); + }); +}); + +function selectOption(option: string) { + cy.get("[aria-label='Select option']").contains(option).should('be.visible').click(); +} diff --git a/e2e/various-suite/prometheus-variable-editor.spec.ts b/e2e/various-suite/prometheus-variable-editor.spec.ts new file mode 100644 index 00000000000..5133e345870 --- /dev/null +++ b/e2e/various-suite/prometheus-variable-editor.spec.ts @@ -0,0 +1,122 @@ +import { e2e } from '../utils'; +import { addDashboard } from '../utils/flows'; + +import { createPromDS, getResources } from './helpers/prometheus-helpers'; + +const DATASOURCE_ID = 'Prometheus'; + +const DATASOURCE_NAME = 'prometheusVariableDS'; + +/** + * Click dashboard settings and then the variables tab + */ +function navigateToVariables() { + e2e.components.PageToolbar.item('Dashboard settings').click(); + e2e.components.Tab.title('Variables').click(); +} + +/** + * Begin the process of adding a query type variable for a Prometheus data source + * + * @param variableName the name of the variable as a label of the variable dropdown + */ +function addPrometheusQueryVariable(variableName: string) { + e2e.pages.Dashboard.Settings.Variables.List.addVariableCTAV2().click(); + + e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(variableName); + e2e.components.DataSourcePicker.container().should('be.visible').click(); + cy.contains(DATASOURCE_NAME).scrollIntoView().should('be.visible').click(); + + getResources(); +} + +/** + * Create a Prometheus variable and navigate to the query editor to check that it is available to use. + * + * @param variableName name the variable + * @param queryType query type of 'Label names', 'Label values', 'Metrics', 'Query result', 'Series query' or 'Classic query'. These types should be imported from the Prometheus library eventually but not now because we are in the process of decoupling the DS from core grafana. + */ +function variableFlowToQueryEditor(variableName: string, queryType: string) { + addDashboard(); + navigateToVariables(); + addPrometheusQueryVariable(variableName); + + // select query type + e2e.components.DataSource.Prometheus.variableQueryEditor.queryType().click(); + selectOption(queryType); + + // apply the variable + e2e.pages.Dashboard.Settings.Variables.Edit.General.applyButton().click(); + + // close to return to dashboard + e2e.pages.Dashboard.Settings.Actions.close().click(); + + // add visualization + e2e.pages.AddDashboard.itemButton('Create new panel button').should('be.visible').click(); + + // close the data source picker modal + cy.get('[aria-label="Close"]').click(); + + // select prom data source from the data source list with the useful data-testid + e2e.components.DataSourcePicker.inputV2().click({ force: true }).type(`${DATASOURCE_NAME}{enter}`); + + // confirm the variable exists in the correct input + // use the variable query type from the library in the future + switch (queryType) { + case 'Label names': + e2e.components.QueryBuilder.labelSelect().should('exist').click({ force: true }); + selectOption(`${variableName}`); + case 'Label values': + e2e.components.QueryBuilder.valueSelect().should('exist').click({ force: true }); + selectOption(`${variableName}`); + case 'Metrics': + e2e.components.DataSource.Prometheus.queryEditor.builder.metricSelect().should('exist').click({ force: true }); + selectOption(`${variableName}`); + default: + // do nothing + } +} + +describe('Prometheus variable query editor', () => { + beforeEach(() => { + createPromDS(DATASOURCE_ID, DATASOURCE_NAME); + }); + + it('should navigate to variable query editor', () => { + addDashboard(); + navigateToVariables(); + }); + + it('should select a query type for a Prometheus variable query', () => { + addDashboard(); + navigateToVariables(); + addPrometheusQueryVariable('labelsVariable'); + + // select query type + e2e.components.DataSource.Prometheus.variableQueryEditor.queryType().click(); + + selectOption('Label names'); + }); + + it('should create a label names variable that is selectable in the label select in query builder', () => { + addDashboard(); + navigateToVariables(); + variableFlowToQueryEditor('labelnames', 'Label names'); + }); + + it('should create a label values variable that is selectable in the label values select in query builder', () => { + addDashboard(); + navigateToVariables(); + variableFlowToQueryEditor('labelvalues', 'Label values'); + }); + + it('should create a metric names variable that is selectable in the metric select in query builder', () => { + addDashboard(); + navigateToVariables(); + variableFlowToQueryEditor('metrics', 'Metrics'); + }); +}); + +function selectOption(option: string) { + cy.get("[aria-label='Select option']").contains(option).should('be.visible').click(); +} diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index c994124ff27..73f975940fd 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -62,11 +62,83 @@ export const Components = { }, Prometheus: { configPage: { - connectionSettings: 'Data source connection URL', + connectionSettings: 'Data source connection URL', // aria-label in grafana experimental + manageAlerts: 'prometheus-alerts-manager', // id for switch component + scrapeInterval: 'data-testid scrape interval', + queryTimeout: 'data-testid query timeout', + defaultEditor: 'data-testid default editor', + disableMetricLookup: 'disable-metric-lookup', // id for switch component + prometheusType: 'data-testid prometheus type', + prometheusVersion: 'data-testid prometheus version', + cacheLevel: 'data-testid cache level', + incrementalQuerying: 'prometheus-incremental-querying', // id for switch component + queryOverlapWindow: 'data-testid query overlap window', + disableRecordingRules: 'disable-recording-rules', // id for switch component + customQueryParameters: 'data-testid custom query parameters', + httpMethod: 'data-testid http method', exemplarsAddButton: 'data-testid Add exemplar config button', internalLinkSwitch: 'data-testid Internal link switch', }, + queryEditor: { + // kickstart: '', see QueryBuilder queryPatterns below + explain: 'data-testid prometheus explain switch wrapper', + editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle + options: 'data-testid prometheus options', // wrapper for options group + legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents + format: 'data-testid prometheus format', + step: 'prometheus-step', // id for autosize component + type: 'data-testid prometheus type', //wrapper for radio button group + exemplars: 'prometheus-exemplars', // id for editor switch component + builder: { + // see QueryBuilder below for commented selectors + // labelSelect: 'data-testid Select label', + // valueSelect: 'data-testid Select value', + // matchOperatorSelect: 'data-testid Select match operator', + metricSelect: 'data-testid metric select', + hints: 'data-testid prometheus hints', // wrapper for hints component + metricsExplorer: 'data-testid metrics explorer', + queryAdvisor: 'data-testid query advisor', + }, + code: { + queryField: 'data-testid prometheus query field', + metricsBrowser: { + openButton: 'data-testid open metrics browser', + selectMetric: 'data-testid select a metric', + metricList: 'data-testid metric list', + labelNamesFilter: 'data-testid label names filter', + labelValuesFilter: 'data-testid label values filter', + useQuery: 'data-testid use query', + useAsRateQuery: 'data-testid use as rate query', + validateSelector: 'data-testid validate selector', + clear: 'data-testid clear', + }, + }, + }, exemplarMarker: 'data-testid Exemplar marker', + variableQueryEditor: { + queryType: 'data-testid query type', + labelnames: { + metricRegex: 'data-testid label names metric regex', + }, + labelValues: { + labelSelect: 'data-testid label values label select', + // metric select see queryEditor: builder for more context + // label select for metric filtering see queryEditor: builder for more context + }, + metricNames: { + metricRegex: 'data-testid metric names metric regex', + }, + varQueryResult: 'data-testid variable query result', + seriesQuery: 'data-testid prometheus series query', + classicQuery: 'data-testid prometheus classic query', + }, + annotations: { + minStep: 'prometheus-annotation-min-step', // id for autosize input + title: 'data-testid prometheus annotation title', + tags: 'data-testid prometheus annotation tags', + text: 'data-testid prometheus annotation text', + seriesValueAsTimestamp: 'data-testid prometheus annotation series value as timestamp', + }, }, }, Menu: { diff --git a/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx index ad2e1e90114..dc89036e1d1 100644 --- a/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/prometheus/components/AnnotationQueryEditor.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { AnnotationQuery } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { EditorField, EditorRow, EditorRows, EditorSwitch, Space } from '@grafana/experimental'; import { AutoSizeInput, Input } from '@grafana/ui'; @@ -56,6 +57,7 @@ export function AnnotationQueryEditor(props: Props) { }); }} defaultValue={query.interval} + id={selectors.components.DataSource.Prometheus.annotations.minStep} /> @@ -78,6 +80,7 @@ export function AnnotationQueryEditor(props: Props) { titleFormat: event.currentTarget.value, }); }} + data-testid={selectors.components.DataSource.Prometheus.annotations.title} /> @@ -91,6 +94,7 @@ export function AnnotationQueryEditor(props: Props) { tagKeys: event.currentTarget.value, }); }} + data-testid={selectors.components.DataSource.Prometheus.annotations.tags} /> diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index 1986901cca3..496322a5a7d 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -2,6 +2,7 @@ import { cx } from '@emotion/css'; import React, { ReactNode } from 'react'; import { isDataFrame, QueryEditorProps, QueryHint, TimeRange, toLegacyResponseData } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; import { Icon, Themeable2, withTheme2, clearButtonStyles } from '@grafana/ui'; import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider'; @@ -230,6 +231,7 @@ class PromQueryField extends React.PureComponent {chooserText} diff --git a/public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.tsx b/public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.tsx index 2ef3b2694ee..b877843cddb 100644 --- a/public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.tsx +++ b/public/app/plugins/datasource/prometheus/components/PrometheusMetricsBrowser.tsx @@ -3,6 +3,7 @@ import React, { ChangeEvent } from 'react'; import { FixedSizeList } from 'react-window'; import { GrafanaTheme2, TimeRange } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Button, HorizontalGroup, @@ -493,9 +494,14 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component - + {/* Using fixed height here to prevent jumpy layout */} @@ -564,6 +573,9 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component @@ -625,10 +637,16 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component {validationStatus && {validationStatus}} - + Use query Validate selector - + Clear diff --git a/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx index 169380f039e..ed44dfb53a9 100644 --- a/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx @@ -1,6 +1,7 @@ import React, { FormEvent, useCallback, useEffect, useState } from 'react'; import { QueryEditorProps, SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { InlineField, InlineFieldRow, Input, Select, TextArea } from '@grafana/ui'; import { PrometheusDatasource } from '../datasource'; @@ -243,6 +244,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: value={qryType} options={variableOptions} width={25} + data-testid={selectors.components.DataSource.Prometheus.variableQueryEditor.queryType} /> @@ -269,6 +271,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: width={25} allowCustomValue isClearable={true} + data-testid={selectors.components.DataSource.Prometheus.variableQueryEditor.labelValues.labelSelect} /> @@ -303,6 +306,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: setLabelNamesMatch(e.currentTarget.value); }} width={25} + data-testid={selectors.components.DataSource.Prometheus.variableQueryEditor.labelnames.metricRegex} /> @@ -329,6 +333,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: onMetricChange(e.currentTarget.value); }} width={25} + data-testid={selectors.components.DataSource.Prometheus.variableQueryEditor.metricNames.metricRegex} /> @@ -358,6 +363,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: } }} cols={100} + data-testid={selectors.components.DataSource.Prometheus.variableQueryEditor.varQueryResult} /> @@ -389,6 +395,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: } }} width={100} + data-testid={selectors.components.DataSource.Prometheus.variableQueryEditor.seriesQuery} /> @@ -418,6 +425,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: } }} width={100} + data-testid={selectors.components.DataSource.Prometheus.variableQueryEditor.classicQuery} /> diff --git a/public/app/plugins/datasource/prometheus/configuration/AlertingSettingsOverhaul.tsx b/public/app/plugins/datasource/prometheus/configuration/AlertingSettingsOverhaul.tsx index dc8243d9163..92e05489913 100644 --- a/public/app/plugins/datasource/prometheus/configuration/AlertingSettingsOverhaul.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/AlertingSettingsOverhaul.tsx @@ -2,6 +2,7 @@ import { cx } from '@emotion/css'; import React from 'react'; import { DataSourceJsonData, DataSourcePluginOptionsEditorProps } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { ConfigSubSection } from '@grafana/experimental'; import { config } from '@grafana/runtime'; import { InlineField, Switch, useTheme2 } from '@grafana/ui'; @@ -53,6 +54,7 @@ export function AlertingSettingsOverhaul({ jsonData: { ...options.jsonData, manageAlerts: event!.currentTarget.checked }, }) } + id={selectors.components.DataSource.Prometheus.configPage.manageAlerts} /> diff --git a/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx b/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx index 2eb17508cfc..7d11f4d9bad 100644 --- a/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx @@ -8,6 +8,7 @@ import { SelectableValue, updateDatasourcePluginJsonDataOption, } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { ConfigSubSection } from '@grafana/experimental'; import { getBackendSrv } from '@grafana/runtime/src'; import { InlineField, Input, Select, Switch, useTheme2 } from '@grafana/ui'; @@ -202,6 +203,7 @@ export const PromSettings = (props: Props) => { timeInterval: e.currentTarget.value, }) } + data-testid={selectors.components.DataSource.Prometheus.configPage.scrapeInterval} /> {validateInput(validDuration.timeInterval, DURATION_REGEX, durationError)} > @@ -231,6 +233,7 @@ export const PromSettings = (props: Props) => { queryTimeout: e.currentTarget.value, }) } + data-testid={selectors.components.DataSource.Prometheus.configPage.queryTimeout} /> {validateInput(validDuration.queryTimeout, DURATION_REGEX, durationError)} > @@ -259,6 +262,7 @@ export const PromSettings = (props: Props) => { } onChange={onChangeHandler('defaultEditor', options, onOptionsChange)} width={40} + data-testid={selectors.components.DataSource.Prometheus.configPage.defaultEditor} /> @@ -280,6 +284,7 @@ export const PromSettings = (props: Props) => { @@ -338,6 +343,7 @@ export const PromSettings = (props: Props) => { } )} width={40} + data-testid={selectors.components.DataSource.Prometheus.configPage.prometheusType} /> @@ -365,6 +371,7 @@ export const PromSettings = (props: Props) => { )} onChange={onChangeHandler('prometheusVersion', options, onOptionsChange)} width={40} + data-testid={selectors.components.DataSource.Prometheus.configPage.prometheusVersion} /> @@ -392,6 +399,7 @@ export const PromSettings = (props: Props) => { value={ cacheValueOptions.find((o) => o.value === options.jsonData.cacheLevel) ?? PrometheusCacheLevel.Low } + data-testid={selectors.components.DataSource.Prometheus.configPage.cacheLevel} /> @@ -416,6 +424,7 @@ export const PromSettings = (props: Props) => { @@ -447,6 +456,7 @@ export const PromSettings = (props: Props) => { value={options.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow} onChange={onChangeHandler('incrementalQueryOverlapWindow', options, onOptionsChange)} spellCheck={false} + data-testid={selectors.components.DataSource.Prometheus.configPage.queryOverlapWindow} /> {validateInput(validDuration.incrementalQueryOverlapWindow, MULTIPLE_DURATION_REGEX, durationError)} > @@ -467,6 +477,7 @@ export const PromSettings = (props: Props) => { @@ -496,6 +507,7 @@ export const PromSettings = (props: Props) => { onChange={onChangeHandler('customQueryParameters', options, onOptionsChange)} spellCheck={false} placeholder="Example: max_source_resolution=5m&timeout=10" + data-testid={selectors.components.DataSource.Prometheus.configPage.customQueryParameters} /> @@ -522,6 +534,7 @@ export const PromSettings = (props: Props) => { options={httpOptions} value={httpOptions.find((o) => o.value === options.jsonData.httpMethod)} onChange={onChangeHandler('httpMethod', options, onOptionsChange)} + data-testid={selectors.components.DataSource.Prometheus.configPage.httpMethod} /> diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx index 8e9067f71f8..76ace108966 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx @@ -4,6 +4,7 @@ import React, { RefCallback, useCallback, useState } from 'react'; import Highlighter from 'react-highlight-words'; import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { EditorField, EditorFieldGroup } from '@grafana/experimental'; import { config } from '@grafana/runtime'; import { @@ -188,6 +189,7 @@ export function MetricSelect({ {...props.innerProps} ref={props.innerRef} className={`${styles.customOptionWidth} metric-encyclopedia-open`} + aria-label="Select option" onKeyDown={(e) => { // if there is no metric and the m.e. is enabled, open the modal if (e.code === 'Enter') { @@ -259,6 +261,7 @@ export function MetricSelect({ const asyncSelect = () => { return ( ((props) => { )} - - datasource={datasource} - query={query} - onChange={onChange} - data={data} - queryModeller={promQueryModeller} - buildVisualQueryFromString={buildVisualQueryFromString} - /> + + + datasource={datasource} + query={query} + onChange={onChange} + data={data} + queryModeller={promQueryModeller} + buildVisualQueryFromString={buildVisualQueryFromString} + /> + {showExplain && ( diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 4ac51127e97..27beaafb4d8 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -1,6 +1,7 @@ import React, { SyntheticEvent } from 'react'; import { CoreApp, SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { EditorField, EditorRow, EditorSwitch } from '@grafana/experimental'; import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui'; @@ -61,56 +62,69 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange return ( - - onChange({ ...query, legendFormat })} - onRunQuery={onRunQuery} - /> - - An additional lower limit for the step parameter of the Prometheus query and for the{' '} - $__interval and $__rate_interval variables. - > - } + + - onChange({ ...query, legendFormat })} + onRunQuery={onRunQuery} /> - - - - - - - - {shouldShowExemplarSwitch(query, app) && ( - - - - )} - {query.intervalFactor && query.intervalFactor > 1 && ( - - option.value === query.intervalFactor)} + + An additional lower limit for the step parameter of the Prometheus query and for the{' '} + $__interval and $__rate_interval variables. + > + } + > + - )} - + + + + + + + {shouldShowExemplarSwitch(query, app) && ( + + + + )} + {query.intervalFactor && query.intervalFactor > 1 && ( + + option.value === query.intervalFactor)} + /> + + )} + + ); }); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx index f9bfe24bf7d..8f7987f8d9d 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryCodeEditor.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { useStyles2 } from '@grafana/ui'; import PromQueryField from '../../components/PromQueryField'; @@ -18,7 +19,10 @@ export function PromQueryCodeEditor(props: Props) { const styles = useStyles2(getStyles); return ( - + ((props) => { > Kick start your query - + + + {app !== CoreApp.Explore && app !== CoreApp.Correlations && ( ((props) => { Run queries )} - + + + diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx index bb1503b0d9d..971eac3d52c 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor.tsx @@ -1,6 +1,7 @@ import React, { useRef } from 'react'; import { SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { EditorField } from '@grafana/experimental'; import { Select, AutoSizeInput } from '@grafana/ui'; @@ -64,6 +65,7 @@ export const PromQueryLegendEditor = React.memo(({ legendFormat, onChange <> {mode === LegendFormatMode.Custom && ( diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/MetricsModal.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/MetricsModal.tsx index dd10667c0dc..c44d2cc06c0 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/MetricsModal.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/MetricsModal.tsx @@ -3,6 +3,7 @@ import debounce from 'debounce-promise'; import React, { useCallback, useEffect, useMemo, useReducer } from 'react'; import { SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Input, Modal, @@ -204,7 +205,10 @@ export const MetricsModal = (props: MetricsModalProps) => { className={styles.modal} > - + {'\u00A0'}Get query suggestions
$__interval
$__rate_interval