diff --git a/.cypress/integration/4_trace_analytics_dashboard.spec.js b/.cypress/integration/4_trace_analytics_dashboard.spec.js new file mode 100644 index 0000000000..38441914db --- /dev/null +++ b/.cypress/integration/4_trace_analytics_dashboard.spec.js @@ -0,0 +1,394 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/// + +import { testDataSet, delay, setTimeFilter, jaegerTestDataSet } from '../utils/constants'; + +describe('Dump test data', () => { + it('Indexes test data', () => { + const dumpDataSet = (mapping_url, data_url, index) => { + cy.request({ + method: 'POST', + failOnStatusCode: false, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `${index}`, + method: 'PUT', + }, + }); + + cy.request(mapping_url).then((response) => { + cy.request({ + method: 'POST', + form: true, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `${index}/_mapping`, + method: 'POST', + }, + body: response.body, + }); + }); + + cy.request(data_url).then((response) => { + cy.request({ + method: 'POST', + form: true, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `${index}/_bulk`, + method: 'POST', + }, + body: response.body, + }); + }); + }; + + testDataSet.forEach(({ mapping_url, data_url, index }) => + dumpDataSet(mapping_url, data_url, index) + ); + }); +}); + +describe('Testing dashboard table empty state', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/home', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + cy.wait(delay * 3); + }); + + it('Renders empty state', () => { + cy.contains(' (0)').should('exist'); + cy.contains('No matches').should('exist'); + }); +}); + +describe('Testing dashboard table', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/home', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Renders the dashboard table', () => { + cy.contains(' (10)').should('exist'); + cy.contains('client_cancel_order').should('exist'); + cy.contains('166.44').should('exist'); + cy.contains('7.14%').should('exist'); + }); + + it('Has working breadcrumbs', () => { + cy.get('.euiBreadcrumb').contains('Dashboard').click(); + cy.get('.euiTitle').contains('Dashboard').should('exist'); + cy.get('.euiBreadcrumb').contains('Trace analytics').click(); + cy.get('.euiTitle').contains('Dashboard').should('exist'); + cy.get('.euiBreadcrumb').contains('Observability').click(); + cy.get('.euiTitle').contains('Event analytics').should('exist'); + }); + + it('Adds the percentile filters', () => { + cy.contains(' >= 95 percentile').click({ force: true }); + cy.wait(delay); + cy.contains(' >= 95 percentile').click({ force: true }); + cy.wait(delay); + + cy.contains('Latency percentile within trace group: >= 95th').should('exist'); + cy.contains(' (7)').should('exist'); + cy.contains('318.69').should('exist'); + + cy.contains(' < 95 percentile').click({ force: true }); + cy.wait(delay); + cy.contains(' < 95 percentile').click({ force: true }); + cy.wait(delay); + + cy.contains('Latency percentile within trace group: < 95th').should('exist'); + cy.contains(' (8)').should('exist'); + cy.contains('383.05').should('exist'); + }); + + it('Opens latency trend popover', () => { + setTimeFilter(true); + cy.get('.euiButtonIcon[aria-label="Open popover"]').first().click(); + cy.get('text.ytitle[data-unformatted="Hourly latency (ms)"]').should('exist'); + }); + + it('Redirects to traces table with filter', () => { + cy.wait(delay); + cy.get('.euiLink').contains('13').click(); + cy.wait(delay); + + cy.get('h2.euiTitle').contains('Traces').should('exist'); + cy.contains(' (13)').should('exist'); + cy.contains('client_create_order').should('exist'); + + cy.get('.euiSideNavItemButton__label').contains('Trace analytics').click(); + cy.wait(delay); + + cy.contains('client_create_order').should('exist'); + }); +}); + +describe('Testing plots', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/home', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Renders service map', () => { + // plotly scale texts are in attribute "data-unformatted" + cy.get('text.ytitle[data-unformatted="Latency (ms)"]').should('exist'); + cy.get('text[data-unformatted="200"]').should('exist'); + cy.get('.vis-network').should('exist'); + + cy.get('.euiButton__text[title="Error rate"]').click(); + cy.get('text.ytitle[data-unformatted="Error rate"]').should('exist'); + cy.get('text[data-unformatted="10%"]').should('exist'); + + cy.get('.euiButton__text[title="Throughput"]').click(); + cy.get('text.ytitle[data-unformatted="Throughput"]').should('exist'); + cy.get('text[data-unformatted="50"]').should('exist'); + + cy.get('input[type="search"]').eq(1).focus().type('payment{enter}'); + }); + + it('Renders plots', () => { + cy.get('text.ytitle[data-unformatted="Error rate (%)"]').should('exist'); + cy.get('text.annotation-text[data-unformatted="Now: 14.81%"]').should('exist'); + cy.get('text.ytitle[data-unformatted="Throughput (n)"]').should('exist'); + cy.get('text.annotation-text[data-unformatted="Now: 108"]').should('exist'); + }); +}); + +describe('Latency by trace group table', () =>{ + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/home', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Verify columns in Latency by trace group table along with pagination functionality', () => { + cy.get('span.panel-title').eq(0).should('exist'); + cy.get('[data-test-subj="tableHeaderCell_dashboard_trace_group_name_0"]').should('exist'); + cy.get('[data-test-subj="tableHeaderCell_dashboard_latency_variance_1"]').should('exist'); + cy.get('[data-test-subj="tableHeaderCell_dashboard_average_latency_2"]').should('exist'); + cy.get('[data-test-subj="tableHeaderCell_24_hour_latency_trend_3"]').should('exist'); + cy.get('[data-test-subj="tableHeaderCell_dashboard_error_rate_4"]').should('exist'); + cy.get('[data-test-subj="tableHeaderCell_dashboard_traces_5"]').should('exist'); + cy.get('[data-test-subj="tablePaginationPopoverButton"]').click(); + cy.get('.euiIcon.euiIcon--medium.euiIcon--inherit.euiContextMenu__icon').eq(0).should('exist').click(); + cy.get('[data-test-subj="pagination-button-next"]').should('exist').click(); + cy.get('button[data-test-subj="dashboard-table-trace-group-name-button"]').contains('mysql').should('exist'); + }); + + it('Sorts the Latency by trace group table', () => { + cy.get('span[title*="Trace group name"]').click(); + cy.get('[data-test-subj="dashboard-table-trace-group-name-button"]').eq(0).contains('/**').should('exist'); + cy.wait(delay); + }); + + it('Verify tooltips in Latency by trace group table', () => { + cy.get('.euiIcon.euiIcon--small.euiIcon--subdued.euiIcon-isLoaded.eui-alignTop').eq(0).trigger('mouseover'); + cy.contains('Traces of all requests that share a common API and operation at the start of distributed tracing instrumentation.').should('be.visible'); + cy.get('.euiIcon.euiIcon--small.euiIcon--subdued.euiIcon-isLoaded.eui-alignTop').eq(1).trigger('mouseover'); + cy.contains('Range of latencies for traces within a trace group in the selected time range.').should('be.visible'); + cy.get('.euiIcon.euiIcon--small.euiIcon--subdued.euiIcon-isLoaded.eui-alignTop').eq(2).trigger('mouseover'); + cy.contains('Average latency of traces within a trace group in the selected time range.').should('be.visible'); + cy.get('.euiIcon.euiIcon--small.euiIcon--subdued.euiIcon-isLoaded.eui-alignTop').eq(3).trigger('mouseover'); + cy.contains('24 hour time series view of hourly average, hourly percentile, and hourly range of latency for traces within a trace group.').should('be.visible'); + cy.get('.euiIcon.euiIcon--small.euiIcon--subdued.euiIcon-isLoaded.eui-alignTop').eq(4).trigger('mouseover'); + cy.contains('Error rate based on count of trace errors within a trace group in the selected time range.').should('be.visible'); + cy.get('.euiIcon.euiIcon--small.euiIcon--subdued.euiIcon-isLoaded.eui-alignTop').eq(5).trigger('mouseover'); + cy.contains('Count of traces with unique trace identifiers in the selected time range.').should('be.visible'); + }); + + it('Verify Search engine on Trace dashboard', () => { + cy.get('.euiFieldSearch.euiFieldSearch--fullWidth').click().type('client_pay_order'); + cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); + cy.wait(delay); + cy.get('.euiTableCellContent.euiTableCellContent--alignRight.euiTableCellContent--overflowingContent').contains('211.04').should('exist'); + cy.get('button[data-test-subj="dashboard-table-trace-group-name-button"]').click(); + cy.get('.euiBadge.euiBadge--hollow.euiBadge--iconRight.globalFilterItem').click(); + cy.get('.euiIcon.euiIcon--medium.euiContextMenu__arrow').click(); + cy.get('.euiContextMenuPanelTitle').contains('Edit filter').should('exist'); + cy.get('.euiButton.euiButton--primary.euiButton--fill').click(); + cy.get('.euiBadge.euiBadge--hollow.euiBadge--iconRight.globalFilterItem').click(); + cy.get('.euiContextMenuItem__text').eq(1).contains('Exclude results').click(); + cy.get('.euiTextColor.euiTextColor--danger').should('exist'); + cy.get('.euiBadge.euiBadge--hollow.euiBadge--iconRight.globalFilterItem').click(); + cy.get('.euiContextMenuItem__text').eq(1).contains('Include results').click(); + cy.get('.euiBadge.euiBadge--hollow.euiBadge--iconRight.globalFilterItem').click(); + cy.get('.euiContextMenuItem__text').eq(2).contains('Temporarily disable').click(); + cy.get('.euiBadge.euiBadge--iconRight.globalFilterItem.globalFilterItem-isDisabled').should('exist').click(); + cy.get('.euiContextMenuItem__text').eq(2).contains('Re-enable').click(); + cy.get('.euiBadge.euiBadge--hollow.euiBadge--iconRight.globalFilterItem').click(); + cy.get('.euiContextMenuItem__text').eq(3).contains('Delete').click(); + }); +}); + +describe('Testing filters on trace analytics page', { scrollBehavior: false }, () =>{ + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/home', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Verify Change all filters', () =>{ + cy.get('[data-test-subj="global-filter-button"]').click(); + cy.get('.euiContextMenuPanelTitle').contains('Change all filters').should('exist'); + cy.get('.euiContextMenuItem__text').eq(0).contains('Enable all'); + cy.get('.euiContextMenuItem__text').eq(1).contains('Disable all'); + cy.get('.euiContextMenuItem__text').eq(2).contains('Invert inclusion'); + cy.get('.euiContextMenuItem__text').eq(3).contains('Invert enabled/disabled'); + cy.get('.euiContextMenuItem__text').eq(4).contains('Remove all'); + }) + + it('Verify Add filter section', () => { + cy.get('[data-test-subj="addfilter"]').contains('+ Add filter').click(); + cy.get('.euiPopoverTitle').contains('Add filter').should('exist'); + cy.get('.euiComboBox__inputWrap.euiComboBox__inputWrap--noWrap').eq(0).trigger('mouseover').click(); + cy.get('.euiComboBoxOption__content').eq(1).click(); + cy.get('.euiComboBox__inputWrap.euiComboBox__inputWrap--noWrap').eq(1).trigger('mouseover').click(); + cy.get('.euiComboBoxOption__content').eq(2).click(); + cy.get('.euiButton.euiButton--primary.euiButton--fill').contains('Save').click(); + cy.get('.euiBadge__content').should('exist').click(); + cy.get('.euiIcon.euiIcon--medium.euiContextMenu__arrow').click(); + cy.get('[data-test-subj="filter-popover-cancel-button"]').contains('Cancel').click(); + cy.get('.euiIcon.euiIcon--small.euiIcon--inherit.euiBadge__icon').click(); + }) +}); + +describe('Dump jaeger test data', () => { + it('Indexes test data', () => { + const dumpDataSet = (mapping_url, data_url, index) => { + cy.request({ + method: 'POST', + failOnStatusCode: false, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `${index}`, + method: 'PUT', + }, + }); + + cy.request(mapping_url).then((response) => { + cy.request({ + method: 'POST', + form: true, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `${index}/_mapping`, + method: 'POST', + }, + body: response.body, + }); + }); + + cy.request(data_url).then((response) => { + cy.request({ + method: 'POST', + form: true, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `${index}/_bulk`, + method: 'POST', + }, + body: response.body, + }); + }); + }; + + jaegerTestDataSet.forEach(({ mapping_url, data_url, index }) => + dumpDataSet(mapping_url, data_url, index) + ); + }); +}); + +describe('Testing switch mode to jaeger', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/home', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + cy.get("[data-test-subj='indexPattern-switch-link']").click(); + cy.get("[data-test-subj='jaeger-mode']").click(); + }); + + it('Verifies errors mode columns and data', () => { + cy.contains('redis,GetDriver').should('exist'); + cy.contains('14.7').should('exist'); + cy.contains('100%').should('exist'); + cy.contains('7').should('exist'); + cy.contains('Service and Operation Name').should('exist'); + cy.contains('Average latency (ms)').should('exist'); + cy.contains('Error rate').should('exist'); + cy.contains('Traces').should('exist'); + }); + + it('Verifies traces links to traces page', () => { + cy.wait(delay); + cy.get('.euiLink').contains('7').click(); + cy.wait(delay); + + cy.get('h2.euiTitle').contains('Traces').should('exist'); + cy.contains(' (7)').should('exist'); + cy.get("[data-test-subj='filterBadge']").eq(0).contains('process.serviceName: redis') + cy.get("[data-test-subj='filterBadge']").eq(1).contains('operationName: GetDriver'); + }) + + it('Switches to throughput mode and verifies columns and data', () => { + cy.get("[data-test-subj='throughput-toggle']").click(); + cy.contains('frontend,HTTP GET /dispatch').should('exist'); + cy.contains('711.38').should('exist'); + cy.contains('0%').should('exist'); + cy.contains('8').should('exist'); + cy.contains('Service and Operation Name').should('exist'); + cy.contains('Average latency (ms)').should('exist'); + cy.contains('Error rate').should('exist'); + cy.contains('Traces').should('exist'); + }); +}); diff --git a/.cypress/integration/5_trace_analytics_services.spec.js b/.cypress/integration/5_trace_analytics_services.spec.js new file mode 100644 index 0000000000..66ca860b13 --- /dev/null +++ b/.cypress/integration/5_trace_analytics_services.spec.js @@ -0,0 +1,324 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/// + +import { delay, SERVICE_NAME, SERVICE_SPAN_ID, setTimeFilter, verify_traces_spans_data_grid_cols_exists, count_table_row } from '../utils/constants'; + +describe('Testing services table empty state', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/services', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + cy.wait(delay * 3); + }); + + it('Renders empty state', () => { + cy.contains(' (0)').should('exist'); + cy.contains('No matches').should('exist'); + }); +}); + +describe('Testing services table', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/services', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Renders the services table', () => { + cy.contains(' (8)').should('exist'); + cy.contains('analytics-service, frontend-client, recommendation').should('exist'); + cy.contains('186.95').should('exist'); + cy.contains('14.29%').should('exist'); + }); + + it('Searches correctly', () => { + cy.get('input[type="search"]').first().focus().type(`${SERVICE_NAME}{enter}`); + cy.get('.euiButton__text').contains('Refresh').click(); + cy.contains(' (1)').should('exist'); + cy.contains('3.57%').should('exist'); + }); + + it('Verify columns in Services table', () => { + cy.get('.euiFlexItem.euiFlexItem--flexGrow10 .panel-title').contains('Services').should('exist'); + cy.get('.euiTableCellContent__text[title="Name"]').should('exist'); + cy.get('.euiTableCellContent__text[title="Average latency (ms)"]').should('exist'); + cy.get('.euiTableCellContent__text[title="Error rate"]').should('exist'); + cy.get('.euiTableCellContent__text[title="Throughput"]').should('exist'); + cy.get('.euiTableCellContent__text[title="No. of connected services"]').should('exist'); + cy.get('.euiTableCellContent__text[title="Connected services"]').should('exist'); + cy.get('.euiTableCellContent__text[title="Traces"]').should('exist'); + cy.get('[data-test-subj="tablePaginationPopoverButton"]').click(); + cy.get('.euiIcon.euiIcon--medium.euiIcon--inherit.euiContextMenu__icon').eq(0).should('exist').click(); + cy.get('[data-test-subj="pagination-button-next"]').should('exist').click(); + cy.get('.euiLink.euiLink--primary').contains('order').should('exist'); + }) + + it('Navigate from Services to Traces', () => { + cy.get('.euiTableCellContent__text[title="Traces"]').should('exist'); + cy.contains('74').should('exist').click(); + cy.get('.euiText.euiText--medium .panel-title').should('exist'); + cy.get('.euiBadge__childButton[data-test-subj="filterBadge"]').should('exist'); + }) +}); + +describe('Testing service view empty state', () => { + beforeEach(() => { + // exception is thrown on loading EuiDataGrid in cypress only, ignore for now + cy.on('uncaught:exception', (err, runnable) => { + if (err.message.includes('ResizeObserver loop')) + return false; + }); + cy.visit(`app/observability-dashboards#/trace_analytics/services/${SERVICE_NAME}`, { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + }); + + it('Renders service view empty state', () => { + cy.contains('frontend-client').should('exist'); + cy.get('.euiText').contains('0').should('exist'); + cy.get('.euiText').contains('-').should('exist'); + }); +}); + +describe('Testing service view', () => { + beforeEach(() => { + // exception is thrown on loading EuiDataGrid in cypress only, ignore for now + cy.on('uncaught:exception', (err, runnable) => { + if (err.message.includes('ResizeObserver loop')) + return false; + }); + cy.visit(`app/observability-dashboards#/trace_analytics/services`, { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + cy.get('input[type="search"]').first().focus().type(`${SERVICE_NAME}`); + cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); + cy.wait(delay); + cy.get('[data-test-subj="service-link"]').eq(0).click(); + }); + + it('Renders service view', () => { + cy.get('h2.euiTitle').contains(SERVICE_NAME).should('exist'); + cy.contains('178.6').should('exist'); + cy.contains('3.57%').should('exist'); + cy.get('div.vis-network').should('exist'); + }); + + it('Has working breadcrumbs', () => { + cy.get('.euiBreadcrumb').contains(SERVICE_NAME).click(); + cy.wait(delay); + cy.get('h2.euiTitle').contains(SERVICE_NAME).should('exist'); + cy.get('.euiBreadcrumb').contains('Services').click(); + cy.wait(delay); + cy.get('.euiTitle').contains('Services').should('exist'); + cy.get('.euiBreadcrumb').contains('Trace analytics').click(); + cy.wait(delay); + cy.get('.euiTitle').contains('Dashboard').should('exist'); + cy.get('.euiBreadcrumb').contains('Observability').click(); + cy.wait(delay); + cy.get('.euiTitle').contains('Event analytics').should('exist'); + }); + + it('Renders spans data grid, flyout, filters', () => { + cy.get("[data-test-subj='spanId-link']").contains(SERVICE_SPAN_ID).trigger('mouseover', { force: true }); + cy.get('button[data-datagrid-interactable="true"]').eq(0).click({ force: true }); + cy.wait(delay); + cy.contains('Span detail').should('exist'); + cy.contains('Span attributes').should('exist'); + cy.get('.euiTextColor').contains('Span ID').trigger('mouseover'); + cy.get('.euiButtonIcon[aria-label="span-flyout-filter-icon"').click({ force: true }); + cy.wait(delay); + cy.get('.euiBadge__text').contains('spanId: ').should('exist'); + cy.get('[data-test-subj="euiFlyoutCloseButton"]').click({ force: true }); + cy.contains('Spans (1)').should('exist'); + }); +}); + +describe('Testing Service map', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/services', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Render Service map', () => { + cy.get('.euiText.euiText--medium .panel-title').contains('Service map'); + cy.get('[data-test-subj="latency"]').should('exist'); + cy.get('.ytitle').contains('Latency (ms)'); + cy.get('[data-text = "Error rate"]').click(); + cy.contains('60%'); + cy.get('[data-text = "Throughput"]').click(); + cy.contains('100'); + cy.get('.euiText.euiText--medium').contains('Focus on').should('exist'); + cy.get('[placeholder="Service name"]').focus().type('database{enter}'); + }) +}); + +describe('Testing traces Spans table verify table headers functionality', () => { + beforeEach(() => { + cy.on('uncaught:exception', (err, runnable) => { + if (err.message.includes('ResizeObserver loop')) + return false; + }); + cy.visit('app/observability-dashboards#/trace_analytics/services', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Renders the spans table and verify columns headers', () => { + cy.contains(' (8)').should('exist'); + cy.contains('analytics-service, frontend-client, recommendation').should('exist'); + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + cy.get('.panel-title').contains('Spans').should('exist'); + cy.get('.panel-title-count').contains('8').should('exist'); + verify_traces_spans_data_grid_cols_exists(); + }); + + it('Toggle columns and verify the columns hidden text verify rows', () => { + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + cy.get('[data-test-subj = "dataGridColumnSelectorButton"]').click(); + cy.get('.euiSwitch.euiSwitch--compressed.euiSwitch--mini .euiSwitch__button').eq(3).click(); + cy.get('[data-test-subj = "dataGridColumnSelectorButton"]').click().should('have.text', '2 columns hidden'); + count_table_row(8); + }); + + it('Show all button Spans table', () => { + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + cy.get('[data-test-subj = "dataGridColumnSelectorButton"]').click(); + cy.get('.euiPopoverFooter .euiFlexItem.euiFlexItem--flexGrowZero').eq(0).should('have.text', 'Show all').click(); + cy.get('.euiDataGrid__focusWrap').click().should('exist'); + verify_traces_spans_data_grid_cols_exists(); + }); + + it('Hide all button Spans table', () => { + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + cy.get('[data-test-subj = "dataGridColumnSelectorButton"]').click(); + cy.get('.euiPopoverFooter .euiFlexItem.euiFlexItem--flexGrowZero').eq(1).should('have.text', 'Hide all').click(); + cy.get('.euiDataGrid__focusWrap').click().should('exist'); + cy.get('[data-test-subj="dataGridColumnSelectorPopover"]').should('have.text', '10 columns hidden'); + }); + + it('Render Spans table and change data table Density', () => { + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + verify_traces_spans_data_grid_cols_exists(); + cy.get('.euiButtonEmpty__text').contains('Density').click(); + cy.get('.euiButtonContent__icon').eq(5).click(); + cy.get('.euiButtonContent__icon').eq(6).click(); + cy.get('.euiButtonContent__icon').eq(7).click(); + }); + + it('Render Spans table and and click on sort', () => { + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + verify_traces_spans_data_grid_cols_exists(); + cy.get('[data-test-subj="dataGridColumnSortingButton"]').contains('Sort fields').should('exist').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection"]').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-spanId').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-parentSpanId"]').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-traceId"]').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-traceGroup').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-durationInNanos"]').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-startTime"]').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-endTime').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection-status.code"]').click(); + cy.get('[data-test-subj="dataGridColumnSortingPopoverColumnSelection"]').click(); + cy.get('[data-test-subj="dataGridColumnSortingButton"]').should('have.text', '8 fields sorted'); + cy.get('[data-test-subj="dataGridColumnSortingButton"]').should('exist').click(); + }); +}); + + +describe('Testing traces Spans table and verify columns functionality', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/services', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Renders the spans table and click on first span to verify details', () => { + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + verify_traces_spans_data_grid_cols_exists(); + cy.get('.euiLink--primary').eq(4).click(); + cy.get('[data-test-subj="spanDetailFlyout"] .euiTitle.euiTitle--medium').contains('Span detail').should('exist'); + cy.get('.euiFlyoutBody .panel-title').contains('Overview').should('exist'); + cy.get('.euiTextColor.euiTextColor--subdued').contains('Span ID').should('exist'); + cy.get('[data-test-subj="parentSpanId"]').contains('d03fecfa0f55b77c').should('exist'); + cy.get('.euiFlyoutBody__overflowContent .panel-title').contains('Span attributes').should('exist'); + cy.get('.euiDescriptionList__description .euiFlexItem').eq(0).trigger('mouseover').click(); + cy.get('[aria-label="span-flyout-filter-icon"]').click(); + cy.get('.euiFlyout__closeButton.euiFlyout__closeButton--inside').click(); + cy.get('.euiBadge__content .euiBadge__text').contains('spanId: 277a5934acf55dcf').should('exist'); + cy.wait(delay); + count_table_row(1); + cy.get('[aria-label="remove current filter"]').click(); + cy.wait(delay); + count_table_row(8); + }); + + it('Render Spans table and verify Column functionality', () => { + cy.get('.euiLink.euiLink--primary').contains('authentication').should('exist').click(); + verify_traces_spans_data_grid_cols_exists(); + cy.get('.euiDataGridHeaderCell__content').contains('Span ID').click(); + cy.get('.euiListGroupItem__label').contains('Hide column').click(); + cy.get('.euiDataGridHeaderCell__content').contains('Trace ID').click(); + cy.get('.euiListGroupItem__label').contains('Sort A-Z').click(); + cy.get('.euiDataGridHeaderCell__content').contains('Trace group').click(); + cy.get('.euiListGroupItem__label').contains('Move left').click(); + }); +}); + + +describe('Testing switch mode to jaeger', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/services', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + cy.get("[data-test-subj='indexPattern-switch-link']").click(); + cy.get("[data-test-subj='jaeger-mode']").click(); + }); + + it('Verifies columns and data', () => { + cy.contains('customer').should('exist'); + cy.contains('310.29').should('exist'); + cy.contains('0%').should('exist'); + cy.contains('Name').should('exist'); + cy.contains('Average latency (ms)').should('exist'); + cy.contains('Error rate').should('exist'); + cy.contains('Throughput').should('exist'); + cy.contains('Traces').should('exist'); + }); + + it('Verifies traces links to traces page with filter applied', () => { + cy.wait(delay); + cy.get('.euiLink').contains('7').click(); + cy.wait(delay); + + cy.get('h2.euiTitle').contains('Traces').should('exist'); + cy.contains(' (7)').should('exist'); + cy.get("[data-test-subj='filterBadge']").eq(0).contains('process.serviceName: customer') + }) +}); \ No newline at end of file diff --git a/.cypress/integration/6_trace_analytics_traces.spec.js b/.cypress/integration/6_trace_analytics_traces.spec.js new file mode 100644 index 0000000000..8943991ff7 --- /dev/null +++ b/.cypress/integration/6_trace_analytics_traces.spec.js @@ -0,0 +1,190 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/// + +import { delay, setTimeFilter, SPAN_ID, TRACE_ID } from '../utils/constants'; + +describe('Testing traces table empty state', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/traces', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + cy.wait(delay * 3); + }); + + it('Renders empty state', () => { + cy.contains(' (0)').should('exist'); + cy.contains('No matches').should('exist'); + }); +}); + +describe('Testing traces table', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/traces', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Renders the traces table', () => { + cy.contains(' (108)').should('exist'); + cy.contains('03/25/2021 10:23:45').should('exist'); + cy.contains('03f9c770db5ee2f1caac0...').should('exist'); + cy.contains('224.99').should('exist'); + + // test data contains output from data-prepper 0.8, which doesn't have fields denormalized + // Trace Analytics should be able to handle the discrepancy if some fields cannot be parsed + cy.contains('Invalid date').should('exist'); + cy.contains('-').should('exist'); + }); + + it('Sorts the traces table', () => { + cy.get('.euiTableRow').first().contains('-').should('exist'); + cy.get('.euiTableCellContent').contains('Trace group').click(); + cy.get('.euiTableRow').first().contains('/%2A%2A').should('exist'); + }); + + it('Searches correctly', () => { + cy.get('input[type="search"]').focus().type(`${TRACE_ID}{enter}`); + cy.get('.euiButton__text').contains('Refresh').click(); + cy.contains(' (1)').should('exist'); + cy.contains('03/25/2021 10:21:22').should('exist'); + }); +}); + +describe('Testing trace view', () => { + beforeEach(() => { + cy.visit(`app/observability-dashboards#/trace_analytics/traces`, { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + cy.get('input[type="search"]').focus().type(`${TRACE_ID}`); + cy.get('.euiButton__text').contains('Refresh').click(); + cy.wait(delay); + cy.get('[data-test-subj="trace-link"]').eq(0).click(); + }); + + it('Renders the trace view', () => { + cy.contains('43.75%').should('exist'); + cy.contains('42.58%').should('exist'); + cy.contains('03/25/2021 10:21:22').should('exist'); + cy.contains(TRACE_ID).should('exist'); + + cy.get('div.js-plotly-plot').should('have.length.gte', 2); + cy.get('text[data-unformatted="database
mysql.APM "]').should('exist'); + cy.contains(`"${SPAN_ID}"`).should('exist'); + }); + + it('Has working breadcrumbs', () => { + cy.get(`.euiBreadcrumb[href="#/trace_analytics/traces/${TRACE_ID}"]`).click(); + cy.wait(delay); + cy.get('h2.euiTitle').contains(TRACE_ID).should('exist'); + cy.get('.euiBreadcrumb[href="#/trace_analytics/traces"]').click(); + cy.wait(delay); + cy.get('.euiTitle').contains('Traces').should('exist'); + cy.get('.euiBreadcrumb[href="#/trace_analytics/home"]').click(); + cy.wait(delay); + cy.get('.euiTitle').contains('Dashboard').should('exist'); + cy.get('.euiBreadcrumb[href="observability-dashboards#/"]').click(); + cy.wait(delay); + cy.get('.euiTitle').contains('Event analytics').should('exist'); + }); + + it('Renders data grid, flyout and filters', () => { + cy.get('.euiButton__text[title="Span list"]').click({ force: true }); + cy.contains('2 columns hidden').should('exist'); + + cy.wait(delay); + cy.get('.euiLink').contains(SPAN_ID).trigger('mouseover', { force: true }); + cy.get('button[data-datagrid-interactable="true"]').eq(0).click({ force: true }); + cy.get('button[data-datagrid-interactable="true"]').eq(0).click({ force: true }); // first click doesn't go through eui data grid + cy.wait(delay); + cy.contains('Span detail').should('exist'); + cy.contains('Span attributes').should('exist'); + cy.get('.euiTextColor').contains('Span ID').trigger('mouseover'); + cy.get('.euiButtonIcon[aria-label="span-flyout-filter-icon"').click({ force: true }); + cy.wait(delay); + + cy.get('.euiBadge__text').contains('spanId: ').should('exist'); + cy.contains('Spans (1)').should('exist'); + }); +}); + +describe('Testing traces table', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/traces', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + }); + + it('Renders the traces table and verify Table Column, Pagination and Rows Data ', () => { + cy.get('.euiTableCellContent__text').contains('Trace ID').should('exist'); + cy.get('.euiTableCellContent__text').contains('Trace group').should('exist'); + cy.get('.euiTableCellContent__text').contains('Latency (ms)').should('exist'); + cy.get('.euiTableCellContent__text').contains('Percentile in trace group').should('exist'); + cy.get('.euiTableCellContent__text').contains('Errors').should('exist'); + cy.get('.euiTableCellContent__text').contains('Last updated').should('exist'); + cy.get('[data-test-subj="pagination-button-next"]').click(); + cy.contains('client_pay_order').should('exist'); + cy.get('[data-test-subj="pagination-button-previous"]').click(); + cy.contains('224.99').should('exist'); + cy.get('.euiButtonEmpty').contains('5').click(); + cy.contains('690d3c7af1a78cf89c43e...').should('exist'); + cy.contains('5be8370207cbb002a165d...').click(); + cy.contains('client_create_order').should('exist'); + cy.get('path[style*="rgb(116, 146, 231)"]').should('exist'); + cy.go('back'); + cy.wait(delay); + cy.get('.euiButtonEmpty__text').contains('Rows per page').click(); + cy.get('.euiContextMenuItem__text').contains('15 rows').click(); + let expected_row_count=15; + cy.get('.euiTable--auto') + .find("tr") + .then((row) => { + let total=row.length-1; + expect(total).to.equal(expected_row_count); + }); + }); +}); + +describe('Testing switch mode to jaeger', () => { + beforeEach(() => { + cy.visit('app/observability-dashboards#/trace_analytics/traces', { + onBeforeLoad: (win) => { + win.sessionStorage.clear(); + }, + }); + setTimeFilter(); + cy.get("[data-test-subj='indexPattern-switch-link']").click(); + cy.get("[data-test-subj='jaeger-mode']").click(); + }); + + it('Verifies columns and data', () => { + cy.contains('08ee9fd9bf964384').should('exist'); + cy.contains('0.012').should('exist'); + cy.contains('No').should('exist'); + cy.contains('01/24/2023 08:33:35').should('exist'); + cy.contains('Latency (ms)').should('exist'); + cy.contains('Trace ID').should('exist'); + cy.contains('Errors').should('exist'); + cy.contains('Last updated').should('exist'); + }); + + it('Verifies Trace View', () => { + cy.contains('08ee9fd9bf964384').click(); + cy.contains("Time spent by service").should('exist'); + cy.get("[data-test-subj='span-gantt-chart-panel']").should('exist'); + }) +}); diff --git a/.cypress/utils/constants.js b/.cypress/utils/constants.js new file mode 100644 index 0000000000..4f9487ceb0 --- /dev/null +++ b/.cypress/utils/constants.js @@ -0,0 +1,137 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const delay = 1500; +export const COMMAND_TIMEOUT_LONG = 10000; + +// trace analytics +export const TRACE_ID = '8832ed6abbb2a83516461960c89af49d'; +export const SPAN_ID = 'a673bc074b438374'; +export const SERVICE_NAME = 'frontend-client'; +export const SERVICE_SPAN_ID = '7df5609a6d104736'; + +export const testDataSet = [ + { + mapping_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/otel-v1-apm-service-map-mappings.json', + data_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/otel-v1-apm-service-map.json', + index: 'otel-v1-apm-service-map', + }, + { + mapping_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/otel-v1-apm-span-000001-mappings.json', + data_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/otel-v1-apm-span-000001.json', + index: 'otel-v1-apm-span-000001', + }, + { + mapping_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/otel-v1-apm-span-000001-mappings.json', + data_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/otel-v1-apm-span-000002.json', + index: 'otel-v1-apm-span-000002', + }, +] + +export const jaegerTestDataSet = [ + { + mapping_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/jaeger-service-2023-01-24-mappings.json', + data_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/jaeger-service-2023-01-24.json', + index: 'jaeger-service-2023-01-24', + }, + { + mapping_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/jaeger-span-2023-01-24-mappings.json', + data_url: 'https://raw.githubusercontent.com/opensearch-project/dashboards-observability/main/.cypress/utils/jaeger-span-2023-01-24.json', + index: 'jaeger-span-2023-01-24', + }, +] + +export const setTimeFilter = (setEndTime = false, refresh = true) => { + const startTime = 'Mar 25, 2021 @ 10:00:00.000'; + const endTime = 'Mar 25, 2021 @ 11:00:00.000'; + cy.get('button.euiButtonEmpty[aria-label="Date quick select"]').click(); + cy.get('.euiQuickSelect__applyButton').click(); + cy.get('.euiSuperDatePicker__prettyFormatLink').click(); + cy.get( + 'button.euiDatePopoverButton--start[data-test-subj="superDatePickerstartDatePopoverButton"]' + ).click(); + cy.get('.euiTab__content').contains('Absolute').click(); + cy.get('input[data-test-subj="superDatePickerAbsoluteDateInput"]') + .focus() + .type('{selectall}' + startTime, { force: true }); + if (setEndTime) { + cy.wait(delay); + cy.get( + 'button.euiDatePopoverButton--end[data-test-subj="superDatePickerendDatePopoverButton"]' + ).click(); + cy.wait(delay); + cy.get('.euiTab__content').contains('Absolute').click(); + cy.get('input[data-test-subj="superDatePickerAbsoluteDateInput"]') + .focus() + .type('{selectall}' + endTime, { force: true }); + } + if (refresh) cy.get('.euiButton__text').contains('Refresh').click(); + cy.wait(delay); +}; + +// notebooks +export const TEST_NOTEBOOK = 'Test Notebook'; +export const SAMPLE_URL = 'https://github.com/opensearch-project/sql/tree/main/sql-jdbc'; +export const NOTEBOOK_TEXT = 'Use Notebooks to interactively and collaboratively develop rich reports backed by live data. Common use cases for notebooks includes creating postmortem reports, designing run books, building live infrastructure reports, or even documentation.'; +export const OPENSEARCH_URL = 'https://opensearch.org/docs/latest/observability-plugin/notebooks/'; +export const MARKDOWN_TEXT = `%md +# Heading 1 + +#### List and links + +* 1 +* 2 +* [SQL JDBC](${SAMPLE_URL}) + +--- +#### Code block +* Explain SQL +\`\`\` +POST _plugins/_sql/_explain +{ + "query": "SELECT * FROM my-index LIMIT 50" +} +\`\`\` + +#### Table +| a1 | b1 | c1 | d1 | +|----|----|----|----| +| a2 | b2 | c2 | d2 | +| a3 | b3 | c3 | d3 | +` + +export const SQL_QUERY_TEXT = `%sql +select * from opensearch_dashboards_sample_data_flights limit 20 +` + +export const PPL_QUERY_TEXT = `%ppl +source=opensearch_dashboards_sample_data_flights +` + +export const supressResizeObserverIssue = () => { + // exception is thrown on loading EuiDataGrid in cypress only, ignore for now + cy.on('uncaught:exception', (err, runnable) => { + if (err.message.includes('ResizeObserver loop')) return false; + }); +}; + +export const verify_traces_spans_data_grid_cols_exists = () => { + cy.get('.euiDataGridHeaderCell__content').contains('Span ID').should('exist'); + cy.get('.euiDataGridHeaderCell__content').contains('Trace ID').should('exist'); + cy.get('.euiDataGridHeaderCell__content').contains('Operation').should('exist'); + cy.get('.euiDataGridHeaderCell__content').contains('Duration').should('exist'); + cy.get('.euiDataGridHeaderCell__content').contains('Start time').should('exist'); + cy.get('.euiDataGridHeaderCell__content').contains('End time').should('exist'); + cy.get('.euiDataGridHeaderCell__content').contains('Errors').should('exist'); +} + +export const count_table_row = (expected_row_count) => { + cy.get('.euiDataGridHeader [role="columnheader"]').then($el => { + let colmun_header_count = Cypress.$($el).length; + let table_grid_cell_count = Cypress.$('[data-test-subj="dataGridRowCell"]').length; + const total_row_count = table_grid_cell_count / colmun_header_count; + expect(total_row_count).to.equal(expected_row_count) + }); +} diff --git a/public/components/trace_analytics/components/common/__tests__/__snapshots__/search_bar.test.tsx.snap b/public/components/trace_analytics/components/common/__tests__/__snapshots__/search_bar.test.tsx.snap new file mode 100644 index 0000000000..8489eec0a0 --- /dev/null +++ b/public/components/trace_analytics/components/common/__tests__/__snapshots__/search_bar.test.tsx.snap @@ -0,0 +1,1159 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Search bar components renders date picker 1`] = ` + + +
+ +
+ + } + > +
+ + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + > +
+
+ + + +
+
+
+
+
+ } + iconType={false} + isCustom={true} + startDateControl={
} + > +
+ +
+ + +
+
+ +
+ +
+ + +`; + +exports[`Search bar components renders search bar 1`] = ` + + +
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+ +
+ + +
+ +
+ + } + > +
+ + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + > +
+
+ + + +
+
+
+
+
+ } + iconType={false} + isCustom={true} + startDateControl={
} + > +
+ +
+ + +
+
+ +
+ +
+ + +
+ + +
+ + + + + +
+
+
+ + +
+ + + +
+ +
+ + + } + closePopover={[Function]} + data-test-subj="global-filter-button" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+ +
+ + + + Add filter + + } + closePopover={[Function]} + data-test-subj="addfilter" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+
+
+
+ +`; diff --git a/public/components/trace_analytics/components/common/filters/__tests__/__snapshots__/filters.test.tsx.snap b/public/components/trace_analytics/components/common/filters/__tests__/__snapshots__/filters.test.tsx.snap new file mode 100644 index 0000000000..3d483d4dd2 --- /dev/null +++ b/public/components/trace_analytics/components/common/filters/__tests__/__snapshots__/filters.test.tsx.snap @@ -0,0 +1,186 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filter component renders filters 1`] = ` + + +
+ +
+ + + } + closePopover={[Function]} + data-test-subj="global-filter-button" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+ +
+ + + + Add filter + + } + closePopover={[Function]} + data-test-subj="addfilter" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+
+
+
+`; diff --git a/public/components/trace_analytics/components/common/filters/filters.tsx b/public/components/trace_analytics/components/common/filters/filters.tsx new file mode 100644 index 0000000000..aa2cc086cb --- /dev/null +++ b/public/components/trace_analytics/components/common/filters/filters.tsx @@ -0,0 +1,324 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiBadge, + EuiButtonEmpty, + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPopover, + EuiPopoverTitle, + EuiTextColor, +} from '@elastic/eui'; +import { TraceAnalyticsMode } from 'public/components/trace_analytics/home'; +import React, { useMemo, useState } from 'react'; +import { FilterEditPopover } from './filter_edit_popover'; +import { getFilterFields, getValidFilterFields } from './filter_helpers'; + +export interface FilterType { + field: string; + operator: string; + value: any; + inverted: boolean; + disabled: boolean; + custom?: any; + locked?: boolean; +} + +export interface FiltersProps { + filters: FilterType[]; + appConfigs?: FilterType[]; + setFilters: (filters: FilterType[]) => void; + mode: TraceAnalyticsMode; +} + +interface FiltersOwnProps extends FiltersProps { + page: 'dashboard' | 'traces' | 'services' | 'app'; +} + +export function Filters(props: FiltersOwnProps) { + // set a filter at an index. if newFilter doesn't exist, remove filter at the index + // if index doesn't exist, append newFilter to the end + const setFilter = (newFilter: FilterType, index: number) => { + const newFilters = [...props.filters]; + if (newFilter) newFilters.splice(index, 1, newFilter); + else newFilters.splice(index, 1); + props.setFilters(newFilters); + }; + + const validFilterFields = useMemo(() => getValidFilterFields(props.mode, props.page), [props.page, props.mode]); + const filterFieldOptions = useMemo( + () => getFilterFields(props.mode, props.page).map((field) => ({ label: field })), + [props.page] + ); + + const globalPopoverPanels = [ + { + id: 0, + title: 'Change all filters', + items: [ + { + name: 'Enable all', + icon: , + onClick: () => { + props.setFilters( + props.filters.map((filter) => ({ + ...filter, + disabled: filter.locked ? filter.disabled : false, + })) + ); + }, + }, + { + name: 'Disable all', + icon: , + onClick: () => { + props.setFilters( + props.filters.map((filter) => ({ + ...filter, + disabled: filter.locked ? filter.disabled : true, + })) + ); + }, + }, + { + name: 'Invert inclusion', + icon: , + onClick: () => { + props.setFilters( + // if filter.custom.query exists, it's a customized filter and "inverted" is alwasy false + props.filters.map((filter) => ({ + ...filter, + inverted: filter.locked + ? filter.inverted + : filter.custom?.query + ? false + : !filter.inverted, + })) + ); + }, + }, + { + name: 'Invert enabled/disabled', + icon: , + onClick: () => { + props.setFilters( + props.filters.map((filter) => ({ + ...filter, + disabled: filter.locked ? filter.disabled : !filter.disabled, + })) + ); + }, + }, + { + name: 'Remove all', + icon: , + onClick: () => { + props.setFilters([]); + }, + }, + ], + }, + ]; + + const getFilterPopoverPanels = ( + filter: FilterType, + index: number, + closePopover: () => void + ): EuiContextMenuPanelDescriptor[] => [ + { + id: 0, + items: [ + { + name: 'Edit filter', + icon: , + disabled: !!filter.custom?.query || validFilterFields.indexOf(filter.field) === -1, + panel: 1, + }, + { + name: `${filter.inverted ? 'Include' : 'Exclude'} results`, + icon: , + disabled: !!filter.custom?.query || validFilterFields.indexOf(filter.field) === -1, + onClick: () => { + filter.inverted = !filter.inverted; + setFilter(filter, index); + }, + }, + { + name: filter.disabled ? 'Re-enable' : 'Temporarily disable', + icon: , + disabled: validFilterFields.indexOf(filter.field) === -1, + onClick: () => { + filter.disabled = !filter.disabled; + setFilter(filter, index); + }, + }, + { + name: 'Delete', + icon: , + onClick: () => setFilter(null, index), + }, + ], + }, + { + id: 1, + width: 430, + title: 'Edit filter', + content: ( +
+ +
+ ), + }, + ]; + + const GlobalFilterButton = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + return ( + setIsPopoverOpen(false)} + button={ + setIsPopoverOpen(true)} + iconType="filter" + title="Change all filters" + aria-label="Change all filters" + /> + } + anchorPosition="rightUp" + panelPaddingSize="none" + withTitle + data-test-subj='global-filter-button' + > + + + ); + }; + + const AddFilterButton = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const button = ( + { + setIsPopoverOpen(true); + }} + > + + Add filter + + ); + + return ( + setIsPopoverOpen(false)} + anchorPosition="downLeft" + data-test-subj="addfilter" + withTitle + > + {'Add filter'} + setIsPopoverOpen(false)} + /> + + ); + }; + + const renderFilters = () => { + const FilterBadge = ({ filter, index }: { filter: FilterType; index: number }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const disabled = filter.locked || filter.disabled; + const className = + 'globalFilterItem' + + (disabled ? ' globalFilterItem-isDisabled' : '') + + (filter.inverted ? ' globalFilterItem-isExcluded' : ''); + const value = + typeof filter.value === 'string' + ? filter.value + : Array.isArray(filter.value) // combo box + ? filter.value[0].label + : `${filter.value.from} to ${filter.value.to}`; // range selector + const filterLabel = filter.inverted ? ( + <> + {'NOT '} + {`${filter.field}: ${value}`} + + ) : ( + `${filter.field}: ${value}` + ); + + const badge = ( + setIsPopoverOpen(true)} + onClickAriaLabel="Open filter settings" + color={disabled ? '#e7e9f0' : 'hollow'} + iconType="cross" + iconSide="right" + iconOnClick={() => { + setFilter(null, index); + }} + iconOnClickAriaLabel="Remove filter" + data-test-subj="filterBadge" + > + {filterLabel} + + ); + return ( + + setIsPopoverOpen(false)} + panelPaddingSize="none" + button={badge} + > + setIsPopoverOpen(false))} + /> + + + ); + }; + + return ( + <> + {props.filters.length > 0 + ? props.filters.map((filter, i) => ) + : null} + + ); + }; + + const filterComponents = useMemo(() => renderFilters(), [props.filters]); + + return ( + + + + + {filterComponents} + + + + + ); +} diff --git a/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/dashboard.test.tsx.snap b/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/dashboard.test.tsx.snap new file mode 100644 index 0000000000..452952c19a --- /dev/null +++ b/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/dashboard.test.tsx.snap @@ -0,0 +1,6228 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Dashboard component renders dashboard 1`] = ` + + +

+ Dashboard +

+
+ + + Data Prepper + + } + className="eui-textTruncate" + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + > +
+
+ + + +
+
+
+
+ + + +
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+ +
+ + +
+ +
+ + } + > +
+ + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + > +
+
+ + + +
+
+
+
+
+ } + iconType={false} + isCustom={true} + startDateControl={
} + > +
+ +
+ + +
+
+ +
+ +
+ + +
+ + +
+ + + + + +
+
+
+ + +
+ + + +
+ +
+ + + } + closePopover={[Function]} + data-test-subj="global-filter-button" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+ +
+ + + + Add filter + + } + closePopover={[Function]} + data-test-subj="addfilter" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+
+
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ + Latency by trace group + + + (0) + +
+
+
+
+
+ +
+ + + +
+
+ +
+ + +
+ + + +
+
+ +
+ +
+ + +
+ + +
+
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + + +
+ + +
+ +
+ + +
+ + +
+ + Service map + +
+
+
+ +
+ + +
+ + + +
+ + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ +
+ +
+ Focus on +
+
+
+
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+
+
+ +
+ +
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+
+ + +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ + Trace error rate over time + +
+
+
+ +
+ + + +
+
+
+
+
+ +
+ + + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ + Traces over time + +
+
+
+ +
+ + + +
+
+
+
+
+ +
+
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + +
+ +
+ +
+ +
+ +
+ + +`; + +exports[`Dashboard component renders empty dashboard 1`] = ` + + +

+ Dashboard +

+
+ + + Data Prepper + + } + className="eui-textTruncate" + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + > +
+
+ + + +
+
+
+
+ + + +
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+ +
+ + +
+ +
+ + } + > +
+ + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + > +
+
+ + + +
+
+
+
+
+ } + iconType={false} + isCustom={true} + startDateControl={
} + > +
+ +
+ + +
+
+ +
+ +
+ + +
+ + +
+ + + + + +
+
+
+ + +
+ + + +
+ +
+ + + } + closePopover={[Function]} + data-test-subj="global-filter-button" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+ +
+ + + + Add filter + + } + closePopover={[Function]} + data-test-subj="addfilter" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+
+
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ + Latency by trace group + + + (0) + +
+
+
+
+
+ +
+ + + +
+
+ +
+ + +
+ + + +
+
+ +
+ +
+ + +
+ + +
+
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + + +
+ + +
+ +
+ + +
+ + +
+ + Service map + +
+
+
+ +
+ + +
+ + + +
+ + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ +
+ +
+ Focus on +
+
+
+
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+
+
+ +
+ +
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+
+ + +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ + Trace error rate over time + +
+
+
+ +
+ + + +
+
+
+
+
+ +
+ + + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ + Traces over time + +
+
+
+ +
+ + + +
+
+
+
+
+ +
+
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + +
+ +
+ +
+ +
+ +
+ + +`; + +exports[`Dashboard component renders empty jaeger dashboard 1`] = ` + + +

+ Dashboard +

+
+ + + Jaeger + + } + className="eui-textTruncate" + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + > +
+
+ + + +
+
+
+
+ + + +
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+ +
+ + +
+ +
+ + } + > +
+ + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + > +
+
+ + + +
+
+
+
+
+ } + iconType={false} + isCustom={true} + startDateControl={
} + > +
+ +
+ + +
+
+ +
+ +
+ + +
+ + +
+ + + + + +
+
+
+ + +
+ + + +
+ +
+ + + } + closePopover={[Function]} + data-test-subj="global-filter-button" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+ +
+ + + + Add filter + + } + closePopover={[Function]} + data-test-subj="addfilter" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + withTitle={true} + > +
+
+ + + +
+
+
+
+
+
+
+
+
+ + +
+ +
+ + +
+ + + +
+ +
+ + +
+ + Trace error rate over time + +
+
+
+ +
+ + + +
+ + + + + + + + + + +
+
+
+
+
+ +
+ + + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + + + +
+ +
+ +
+ + +
+ + Top 5 Service and Operation Errors + + + (0) + +
+
+
+
+
+
+
+ +
+ + +
+
+ + +
+ + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

+ } + > +
+ +

+ No matches +

+
+ + + +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
+ + + +
+ + +`; diff --git a/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/mode_picker.test.tsx.snap b/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/mode_picker.test.tsx.snap new file mode 100644 index 0000000000..469f17277f --- /dev/null +++ b/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/mode_picker.test.tsx.snap @@ -0,0 +1,813 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Mode picker component renders mode picker 1`] = ` + + + Jaeger + + } + className="eui-textTruncate" + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + > +
+
+ + + +
+
+
+
+`; + +exports[`Mode picker component renders mode picker 2`] = ` + + + Jaeger + + } + className="eui-textTruncate" + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={true} + ownFocus={true} + panelPaddingSize="s" + > +
+
+ + + +
+ + +
+
+