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={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+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={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
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"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Data Prepper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency by trace group
+
+
+ (0)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ â–¡
+
+ < 95 percentile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ â–
+
+ >= 95 percentile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Throughput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Data Prepper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency by trace group
+
+
+ (0)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ â–¡
+
+ < 95 percentile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ â–
+
+ >= 95 percentile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Throughput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jaeger
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Jaeger
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Mode picker component renders mode picker 2`] = `
+
+
+ Jaeger
+
+ }
+ className="eui-textTruncate"
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={true}
+ ownFocus={true}
+ panelPaddingSize="s"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Jaeger
+
+
+
+
+
+
+
+
+
+
+
+
+
+ You are in a dialog. To close this dialog, hit escape.
+
+
+
+
+ Choose data type
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ You are in a dialog. To close this dialog, hit escape.
+
+
+
+
+
+
+
+
+ Choose data type
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/public/components/trace_analytics/components/dashboard/mode_picker.tsx b/public/components/trace_analytics/components/dashboard/mode_picker.tsx
new file mode 100644
index 0000000000..14594958ca
--- /dev/null
+++ b/public/components/trace_analytics/components/dashboard/mode_picker.tsx
@@ -0,0 +1,100 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { EuiButtonEmpty, EuiPopover, EuiPopoverTitle, EuiSelectable } from '@elastic/eui';
+import React, { useState } from 'react';
+import { TraceAnalyticsMode } from '../../home';
+
+const labels = new Map([
+ ['jaeger', 'Jaeger'],
+ ['data_prepper', 'Data Prepper'],
+]);
+
+export function DataSourcePicker(props: {
+ modes: {
+ id: string;
+ title: string;
+ }[];
+ selectedMode: TraceAnalyticsMode;
+ setMode: (mode: TraceAnalyticsMode) => void;
+}) {
+ const { modes, selectedMode, setMode } = props;
+ const [isPopoverOpen, setPopoverIsOpen] = useState(false);
+
+ const trigger = {
+ label: labels.get(selectedMode),
+ title: selectedMode,
+ 'data-test-subj': 'indexPattern-switch-link',
+ className: 'dscIndexPattern__triggerButton',
+ };
+
+ const createTrigger = () => {
+ const { label, title, ...rest } = trigger;
+ return (
+ setPopoverIsOpen(!isPopoverOpen)}
+ {...rest}
+ >
+ {label}
+
+ );
+ };
+
+ return (
+ <>
+ setPopoverIsOpen(false)}
+ className="eui-textTruncate"
+ anchorClassName="eui-textTruncate"
+ display="inlineBlock"
+ panelPaddingSize="s"
+ ownFocus
+ >
+
+ {'Choose data type'}
+ ({
+ label: x.title,
+ key: x.id,
+ value: x.id,
+ checked: x.id === selectedMode ? 'on' : undefined,
+ "data-test-subj": x.id + '-mode',
+ }))}
+ onChange={(choices) => {
+ const choice = choices.find(({ checked }) => checked) as unknown as {
+ value: string;
+ label: string;
+ key: TraceAnalyticsMode;
+ };
+ setMode(choice.key);
+ setPopoverIsOpen(false);
+ sessionStorage.setItem('TraceAnalyticsMode', choice.key);
+ }}
+ searchProps={{
+ compressed: true,
+ }}
+ >
+ {(list, search) => (
+ <>
+ {search}
+ {list}
+ >
+ )}
+
+
+
+ >
+ );
+}
diff --git a/public/components/trace_analytics/components/dashboard/top_groups_page.tsx b/public/components/trace_analytics/components/dashboard/top_groups_page.tsx
new file mode 100644
index 0000000000..f086332843
--- /dev/null
+++ b/public/components/trace_analytics/components/dashboard/top_groups_page.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { EuiSpacer } from '@elastic/eui';
+import React, { useState } from 'react';
+import { FilterType } from '../common/filters/filters';
+import { ErrorRatePlt } from '../common/plots/error_rate_plt';
+import { ThroughputPlt } from '../common/plots/throughput_plt';
+import { ErrorRatesTable } from './top_error_rates_table';
+import { LatencyTable } from './top_latency_table';
+
+export function TopGroupsPage(props: {
+ filters: FilterType[];
+ addFilter: (filter: FilterType) => void;
+ addFilters: (filter: FilterType[]) => void;
+ addPercentileFilter: (condition?: 'gte' | 'lte', additionalFilters?: FilterType[]) => void;
+ setRedirect: (redirect: boolean) => void;
+ loading: boolean;
+ page: 'dashboard' | 'traces' | 'services' | 'app';
+ throughPutItems: { items: any[]; fixedInterval: string };
+ jaegerErrorRatePltItems: { items: any[]; fixedInterval: string };
+ jaegerErrorTableItems: any[];
+ jaegerTableItems: any[];
+ setStartTime: (time: string) => void;
+ setEndTime: (time: string) => void;
+}) {
+ const toggleButtons = [
+ {
+ id: 'error_rate',
+ label: 'Error rate',
+ 'data-test-subj': 'errors-toggle'
+ },
+ {
+ id: 'throughput',
+ label: 'Throughput',
+ 'data-test-subj': 'throughput-toggle'
+ },
+ ];
+ const [idSelected, setIdSelected] = useState('error_rate');
+ return (
+ <>
+ {idSelected === 'error_rate' ? (
+ <>
+
+ setIdSelected(mode)}
+ idSelected={idSelected}
+ toggleButtons={toggleButtons}
+ />
+
+ >
+ ) : (
+ <>
+ setIdSelected(mode)}
+ idSelected={idSelected}
+ toggleButtons={toggleButtons}
+ />
+
+ >
+ )}
+ >
+ );
+}
diff --git a/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap b/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap
new file mode 100644
index 0000000000..42291d3fa6
--- /dev/null
+++ b/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap
@@ -0,0 +1,4963 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Services component renders empty services page 1`] = `
+
+
+
+ Services
+
+
+
+
+ Data Prepper
+
+ }
+ className="eui-textTruncate"
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="s"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Data Prepper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Services
+
+
+ (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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Throughput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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[`Services component renders jaeger services page 1`] = `
+
+
+
+ Services
+
+
+
+
+ Jaeger
+
+ }
+ className="eui-textTruncate"
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="s"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jaeger
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Services
+
+
+ (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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Services component renders services page 1`] = `
+
+
+
+ Services
+
+
+
+
+ Data Prepper
+
+ }
+ className="eui-textTruncate"
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="s"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Data Prepper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Services
+
+
+ (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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Throughput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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/services/__tests__/__snapshots__/services_table.test.tsx.snap b/public/components/trace_analytics/components/services/__tests__/__snapshots__/services_table.test.tsx.snap
new file mode 100644
index 0000000000..9d99f82c4f
--- /dev/null
+++ b/public/components/trace_analytics/components/services/__tests__/__snapshots__/services_table.test.tsx.snap
@@ -0,0 +1,3346 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Services table component renders empty jaeger services table message 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Services
+
+
+ (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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Services table component renders empty services table message 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Services
+
+
+ (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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Services table component renders jaeger services table 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Services
+
+
+ (1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sorting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Average latency (ms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Throughput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+ database
+
+
+
+
+
+
+
+
+ Average latency (ms)
+
+
+ 49.54
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+ Throughput
+
+
+
+ 53
+
+
+
+
+
+
+
+ Traces
+
+
+
+
+
+ 31
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+ 10
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rows per page
+
+ :
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Services table component renders services table 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Services
+
+
+ (1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sorting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Average latency (ms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Throughput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No. of connected services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Connected services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+ database
+
+
+
+
+
+
+
+
+ Average latency (ms)
+
+
+ 49.54
+
+
+
+
+
+
+ Error rate
+
+
+
+
+
+
+
+ Throughput
+
+
+
+ 53
+
+
+
+
+
+
+
+ No. of connected services
+
+
+ 2
+
+
+
+
+
+
+ Connected services
+
+
+
+
+ order, inventory
+
+
+
+
+
+
+
+
+ Traces
+
+
+
+
+
+ 31
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+ 10
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rows per page
+
+ :
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/public/components/trace_analytics/components/services/services_table.tsx b/public/components/trace_analytics/components/services/services_table.tsx
new file mode 100644
index 0000000000..4ffe85107c
--- /dev/null
+++ b/public/components/trace_analytics/components/services/services_table.tsx
@@ -0,0 +1,200 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* eslint-disable react-hooks/exhaustive-deps */
+
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHorizontalRule,
+ EuiI18nNumber,
+ EuiInMemoryTable,
+ EuiLink,
+ EuiPanel,
+ EuiSpacer,
+ EuiTableFieldDataColumnType,
+ EuiText,
+} from '@elastic/eui';
+import _ from 'lodash';
+import React, { useMemo } from 'react';
+import { TraceAnalyticsMode } from '../../home';
+import { FilterType } from '../common/filters/filters';
+import {
+ MissingConfigurationMessage,
+ NoMatchMessage,
+ PanelTitle,
+} from '../common/helper_functions';
+
+interface ServicesTableProps {
+ items: any[];
+ loading: boolean;
+ nameColumnAction: (item: any) => any;
+ traceColumnAction: any;
+ addFilter: (filter: FilterType) => void;
+ setRedirect: (redirect: boolean) => void;
+ mode: TraceAnalyticsMode;
+ jaegerIndicesExist: boolean;
+ dataPrepperIndicesExist: boolean;
+}
+
+export function ServicesTable(props: ServicesTableProps) {
+ const {
+ items,
+ mode,
+ loading,
+ nameColumnAction,
+ traceColumnAction,
+ addFilter,
+ setRedirect,
+ jaegerIndicesExist,
+ dataPrepperIndicesExist,
+ } = props;
+ const renderTitleBar = (totalItems?: number) => {
+ return (
+
+
+
+
+
+ );
+ };
+
+ const columns = useMemo(
+ () =>
+ [
+ {
+ field: 'name',
+ name: 'Name',
+ align: 'left',
+ sortable: true,
+ render: (item: any) => (
+ nameColumnAction(item)}>
+ {item.length < 24 ? item : {_.truncate(item, { length: 24 })}
}
+
+ ),
+ },
+ {
+ field: 'average_latency',
+ name: 'Average latency (ms)',
+ align: 'right',
+ sortable: true,
+ render: (item: any) => (item === 0 || item ? _.round(item, 2) : '-'),
+ },
+ {
+ field: 'error_rate',
+ name: 'Error rate',
+ align: 'right',
+ sortable: true,
+ render: (item) =>
+ item === 0 || item ? {`${_.round(item, 2)}%`} : '-',
+ },
+ {
+ field: 'throughput',
+ name: 'Throughput',
+ align: 'right',
+ sortable: true,
+ truncateText: true,
+ render: (item: any) => (item === 0 || item ? : '-'),
+ },
+ ...(mode === 'data_prepper'
+ ? [
+ {
+ field: 'number_of_connected_services',
+ name: 'No. of connected services',
+ align: 'right',
+ sortable: true,
+ truncateText: true,
+ width: '80px',
+ render: (item: any) => (item === 0 || item ? item : '-'),
+ },
+ ]
+ : []),
+ ...(mode === 'data_prepper'
+ ? [
+ {
+ field: 'connected_services',
+ name: 'Connected services',
+ align: 'left',
+ sortable: true,
+ truncateText: true,
+ render: (item: any) =>
+ item ? (
+ {_.truncate(item.join(', '), { length: 50 })}
+ ) : (
+ '-'
+ ),
+ },
+ ]
+ : []),
+ {
+ field: 'traces',
+ name: 'Traces',
+ align: 'right',
+ sortable: true,
+ truncateText: true,
+ render: (item: any, row: any) => (
+ <>
+ {item === 0 || item ? (
+ {
+ setRedirect(true);
+ addFilter({
+ field: mode === 'jaeger' ? 'process.serviceName' : 'serviceName',
+ operator: 'is',
+ value: row.name,
+ inverted: false,
+ disabled: false,
+ });
+ traceColumnAction();
+ }}
+ >
+
+
+ ) : (
+ '-'
+ )}
+ >
+ ),
+ },
+ ] as Array>,
+ [items]
+ );
+
+ const titleBar = useMemo(() => renderTitleBar(items?.length), [items]);
+
+ return (
+ <>
+
+ {titleBar}
+
+
+ {!(
+ (mode === 'data_prepper' && dataPrepperIndicesExist) ||
+ (mode === 'jaeger' && jaegerIndicesExist)
+ ) ? (
+
+ ) : items?.length > 0 ? (
+
+ ) : (
+
+ )}
+
+ >
+ );
+}
diff --git a/public/components/trace_analytics/components/traces/__tests__/__snapshots__/service_breakdown_panel.test.tsx.snap b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/service_breakdown_panel.test.tsx.snap
new file mode 100644
index 0000000000..4b25a19b50
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/service_breakdown_panel.test.tsx.snap
@@ -0,0 +1,434 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Service breakdown panel component renders empty service breakdown panel 1`] = `
+
+
+
+
+
+
+
+ Time spent by service
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Service breakdown panel component renders service breakdown panel 1`] = `
+%{value:.2f}% ",
+ "labels": Array [
+ "inventory",
+ ],
+ "marker": Object {
+ "colors": Array [
+ "#7492e7",
+ ],
+ },
+ "textinfo": "none",
+ "type": "pie",
+ "values": Array [
+ 100,
+ ],
+ },
+ ]
+ }
+>
+
+
+
+
+
+
+ Time spent by service
+
+
+
+
+
+
+
+
+
+
+
+
%{value:.2f}% ",
+ "labels": Array [
+ "inventory",
+ ],
+ "marker": Object {
+ "colors": Array [
+ "#7492e7",
+ ],
+ },
+ "textinfo": "none",
+ "type": "pie",
+ "values": Array [
+ 100,
+ ],
+ },
+ ]
+ }
+ layout={
+ Object {
+ "height": 200,
+ "margin": Object {
+ "b": 5,
+ "l": 5,
+ "r": 5,
+ "t": 5,
+ },
+ "showlegend": false,
+ "width": 200,
+ }
+ }
+ >
+ %{value:.2f}% ",
+ "labels": Array [
+ "inventory",
+ ],
+ "marker": Object {
+ "colors": Array [
+ "#7492e7",
+ ],
+ },
+ "textinfo": "none",
+ "type": "pie",
+ "values": Array [
+ 100,
+ ],
+ },
+ ]
+ }
+ debug={false}
+ divId="explorerPlotComponent"
+ layout={
+ Object {
+ "autosize": true,
+ "barmode": "stack",
+ "height": 200,
+ "hovermode": "closest",
+ "legend": Object {
+ "orientation": "h",
+ "traceorder": "normal",
+ },
+ "margin": Object {
+ "b": 5,
+ "l": 5,
+ "r": 5,
+ "t": 5,
+ },
+ "showlegend": false,
+ "width": 200,
+ "xaxis": Object {
+ "automargin": true,
+ "rangemode": "normal",
+ "showgrid": true,
+ "zeroline": false,
+ },
+ "yaxis": Object {
+ "rangemode": "normal",
+ "showgrid": true,
+ "zeroline": false,
+ },
+ }
+ }
+ style={
+ Object {
+ "height": "100%",
+ "width": "100%",
+ }
+ }
+ useResizeHandler={true}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/public/components/trace_analytics/components/traces/__tests__/__snapshots__/span_detail_panel.test.tsx.snap b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/span_detail_panel.test.tsx.snap
new file mode 100644
index 0000000000..9c05f6040d
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/span_detail_panel.test.tsx.snap
@@ -0,0 +1,522 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Service breakdown panel component renders service breakdown panel 1`] = `
+HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ Object {
+ "hovertemplate": "%{x} ",
+ "marker": Object {
+ "color": "#7492e7",
+ },
+ "orientation": "h",
+ "text": Array [
+ "Error",
+ ],
+ "textfont": Object {
+ "color": Array [
+ "#c14125",
+ ],
+ },
+ "textposition": "outside",
+ "type": "bar",
+ "width": 0.4,
+ "x": Array [
+ 19.91,
+ ],
+ "y": Array [
+ "inventory HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ ],
+ "ganttMaxX": 19.91,
+ "table": Array [
+ Object {
+ "end_time": "2020-11-10T17:55:45.239564396Z",
+ "error": "Error",
+ "latency": 19.91,
+ "service_name": "inventory",
+ "span_id": "32c641131b569afa",
+ "start_time": "2020-11-10T17:55:45.219652629Z",
+ "vs_benchmark": 0,
+ },
+ ],
+ }
+ }
+ mode="data_prepper"
+ setData={[MockFunction]}
+>
+
+
+
+
+
+
+
+
+
+
+ Spans
+
+
+ (1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ Object {
+ "hovertemplate": "%{x} ",
+ "marker": Object {
+ "color": "#7492e7",
+ },
+ "orientation": "h",
+ "text": Array [
+ "Error",
+ ],
+ "textfont": Object {
+ "color": Array [
+ "#c14125",
+ ],
+ },
+ "textposition": "outside",
+ "type": "bar",
+ "width": 0.4,
+ "x": Array [
+ 19.91,
+ ],
+ "y": Array [
+ "inventory HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ ]
+ }
+ layout={
+ Object {
+ "height": 110,
+ "margin": Object {
+ "b": 30,
+ "l": 260,
+ "r": 5,
+ "t": 30,
+ },
+ "width": 800,
+ "xaxis": Object {
+ "color": "#91989c",
+ "range": Array [
+ 0,
+ 23.892,
+ ],
+ "showline": true,
+ "side": "top",
+ "ticksuffix": " ms",
+ },
+ "yaxis": Object {
+ "showgrid": false,
+ "ticktext": Array [
+ "inventory HTTP GET ",
+ ],
+ "tickvals": Array [
+ "inventory HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ }
+ }
+ onClickHandler={[Function]}
+ onHoverHandler={[Function]}
+ onUnhoverHandler={[Function]}
+ >
+ HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ Object {
+ "hovertemplate": "%{x} ",
+ "marker": Object {
+ "color": "#7492e7",
+ },
+ "orientation": "h",
+ "text": Array [
+ "Error",
+ ],
+ "textfont": Object {
+ "color": Array [
+ "#c14125",
+ ],
+ },
+ "textposition": "outside",
+ "type": "bar",
+ "width": 0.4,
+ "x": Array [
+ 19.91,
+ ],
+ "y": Array [
+ "inventory HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ ]
+ }
+ debug={false}
+ divId="explorerPlotComponent"
+ layout={
+ Object {
+ "autosize": true,
+ "barmode": "stack",
+ "height": 110,
+ "hovermode": "closest",
+ "legend": Object {
+ "orientation": "h",
+ "traceorder": "normal",
+ },
+ "margin": Object {
+ "b": 30,
+ "l": 260,
+ "r": 5,
+ "t": 30,
+ },
+ "showlegend": false,
+ "width": 800,
+ "xaxis": Object {
+ "color": "#91989c",
+ "range": Array [
+ 0,
+ 23.892,
+ ],
+ "showline": true,
+ "side": "top",
+ "ticksuffix": " ms",
+ },
+ "yaxis": Object {
+ "showgrid": false,
+ "ticktext": Array [
+ "inventory HTTP GET ",
+ ],
+ "tickvals": Array [
+ "inventory HTTP GET 4dec6080-61af-11eb-aee3-ef2f84ffa4a2",
+ ],
+ },
+ }
+ }
+ onClick={[Function]}
+ onHover={[Function]}
+ onUnhover={[Function]}
+ style={
+ Object {
+ "height": "100%",
+ "width": "100%",
+ }
+ }
+ useResizeHandler={true}
+ >
+
+
+
+
+
+
+
+`;
diff --git a/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces.test.tsx.snap b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces.test.tsx.snap
new file mode 100644
index 0000000000..1aeeab3f1d
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces.test.tsx.snap
@@ -0,0 +1,3879 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Traces component renders empty traces page 1`] = `
+
+
+
+ Traces
+
+
+
+
+ Data Prepper
+
+ }
+ className="eui-textTruncate"
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="s"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Data Prepper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+ (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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Traces component renders jaeger traces page 1`] = `
+
+
+
+ Traces
+
+
+
+
+ Jaeger
+
+ }
+ className="eui-textTruncate"
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="s"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jaeger
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+ (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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Traces component renders traces page 1`] = `
+
+
+
+ Traces
+
+
+
+
+ Data Prepper
+
+ }
+ className="eui-textTruncate"
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="s"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Data Prepper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ iconType={false}
+ isCustom={true}
+ startDateControl={
}
+ >
+
+
+ Last 5 minutes
+
+
+ Show dates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ 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}
+ >
+
+
+
+
+
+
+
+ + Add filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+ (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/traces/__tests__/__snapshots__/traces_table.test.tsx.snap b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces_table.test.tsx.snap
new file mode 100644
index 0000000000..ac654a6f71
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces_table.test.tsx.snap
@@ -0,0 +1,3360 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Traces table component renders empty traces table message 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+ (0)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Learn more
+
+ }
+ body={
+
+ The indices required for trace analytics (otel-v1-apm-span-* and otel-v1-apm-service-map*) do not exist or you do not have permission to access them.
+
+ }
+ title={
+
+ Trace Analytics not set up
+
+ }
+ >
+
+
+
+ Trace Analytics not set up
+
+
+
+
+
+
+
+
+
+
+
+ The indices required for trace analytics (otel-v1-apm-span-* and otel-v1-apm-service-map*) do not exist or you do not have permission to access them.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Learn more
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Traces table component renders empty traces table message 2`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+ (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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Traces table component renders jaeger traces table 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+ (1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sorting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Trace ID
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency (ms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Errors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last updated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Trace ID
+
+
+
+
+
+
+
+
+
+ 00079a615e31e61766fcb...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency (ms)
+
+
+
+ 19.91
+
+
+
+
+
+
+
+ Errors
+
+
+ No
+
+
+
+
+
+
+ Last updated
+
+
+ 11/10/2020 09:55:45
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+ 10
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rows per page
+
+ :
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Traces table component renders traces table 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Traces
+
+
+ (1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Percentile in trace group
+
+ ,
+ "render": [Function],
+ "sortable": true,
+ },
+ Object {
+ "align": "right",
+ "field": "error_count",
+ "name": "Errors",
+ "render": [Function],
+ "sortable": true,
+ },
+ Object {
+ "align": "left",
+ "field": "last_updated",
+ "name": "Last updated",
+ "render": [Function],
+ "sortable": true,
+ },
+ ]
+ }
+ items={
+ Array [
+ Object {
+ "actions": "#",
+ "error_count": "Yes",
+ "last_updated": "11/10/2020 09:55:45",
+ "latency": 19.91,
+ "percentile_in_trace_group": 30,
+ "trace_group": "HTTP GET",
+ "trace_id": "00079a615e31e61766fcb20b557051c1",
+ },
+ ]
+ }
+ loading={false}
+ onTableChange={[Function]}
+ pagination={
+ Object {
+ "initialPageSize": 10,
+ "pageSizeOptions": Array [
+ 5,
+ 10,
+ 15,
+ ],
+ }
+ }
+ responsive={true}
+ sorting={
+ Object {
+ "sort": Object {
+ "direction": "asc",
+ "field": "trace_id",
+ },
+ }
+ }
+ tableLayout="auto"
+ >
+
+
+ Percentile in trace group
+
+ ,
+ "render": [Function],
+ "sortable": true,
+ },
+ Object {
+ "align": "right",
+ "field": "error_count",
+ "name": "Errors",
+ "render": [Function],
+ "sortable": true,
+ },
+ Object {
+ "align": "left",
+ "field": "last_updated",
+ "name": "Last updated",
+ "render": [Function],
+ "sortable": true,
+ },
+ ]
+ }
+ items={
+ Array [
+ Object {
+ "actions": "#",
+ "error_count": "Yes",
+ "last_updated": "11/10/2020 09:55:45",
+ "latency": 19.91,
+ "percentile_in_trace_group": 30,
+ "trace_group": "HTTP GET",
+ "trace_id": "00079a615e31e61766fcb20b557051c1",
+ },
+ ]
+ }
+ loading={false}
+ noItemsMessage="No items found"
+ onChange={[Function]}
+ pagination={
+ Object {
+ "hidePerPageOptions": undefined,
+ "pageIndex": 0,
+ "pageSize": 10,
+ "pageSizeOptions": Array [
+ 5,
+ 10,
+ 15,
+ ],
+ "totalItemCount": 1,
+ }
+ }
+ responsive={true}
+ sorting={
+ Object {
+ "allowNeutralSort": true,
+ "sort": Object {
+ "direction": "asc",
+ "field": "Trace ID",
+ },
+ }
+ }
+ tableLayout="auto"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Percentile in trace group
+
+ ,
+ "onSort": [Function],
+ },
+ Object {
+ "isSortAscending": undefined,
+ "isSorted": false,
+ "key": "_data_s_error_count_4",
+ "name": "Errors",
+ "onSort": [Function],
+ },
+ Object {
+ "isSortAscending": undefined,
+ "isSorted": false,
+ "key": "_data_s_last_updated_5",
+ "name": "Last updated",
+ "onSort": [Function],
+ },
+ ]
+ }
+ >
+
+
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sorting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Trace ID
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Trace group
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Latency (ms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Percentile in trace group
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Errors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Last updated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Trace ID
+
+
+
+
+
+
+
+
+
+ 00079a615e31e61766fcb...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Trace group
+
+
+
+
+
+
+
+ Latency (ms)
+
+
+
+ 19.91
+
+
+
+
+
+
+ Percentile in trace group
+
+ ,
+ "render": undefined,
+ }
+ }
+ setScopeRow={false}
+ textOnly={false}
+ >
+
+
+
+ Percentile in trace group
+
+
+
+
+
+
+
+
+ Errors
+
+
+ No
+
+
+
+
+
+
+ Last updated
+
+
+ 11/10/2020 09:55:45
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+ 10
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rows per page
+
+ :
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/public/components/trace_analytics/components/traces/service_breakdown_panel.tsx b/public/components/trace_analytics/components/traces/service_breakdown_panel.tsx
new file mode 100644
index 0000000000..6f8703a03e
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/service_breakdown_panel.tsx
@@ -0,0 +1,84 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHealth,
+ EuiHorizontalRule,
+ EuiPanel,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+import _ from 'lodash';
+import React, { useMemo } from 'react';
+import { Plt } from '../../../visualizations/plotly/plot';
+import { PanelTitle } from '../common/helper_functions';
+
+export function ServiceBreakdownPanel(props: { data: Plotly.Data[] }) {
+ const layout = useMemo(
+ () =>
+ ({
+ height: 200,
+ width: 200,
+ showlegend: false,
+ margin: {
+ l: 5,
+ r: 5,
+ b: 5,
+ t: 5,
+ },
+ } as Partial),
+ [props.data]
+ );
+
+ const renderStats = () => {
+ return props.data.length > 0 ? (
+
+
+
+ {props.data[0].marker.colors.map((color, i) => (
+
+
+ {props.data[0].labels[i]}
+
+
+ ))}
+
+
+
+
+
+
+ {props.data[0].values.map((value, i) => (
+
+ {_.round(value, 2)}%
+
+ ))}
+
+
+
+ ) : null;
+ };
+
+ const stats = useMemo(() => renderStats(), [props.data]);
+
+ return (
+ <>
+
+
+
+
+
+ {props.data?.length > 0 ? : null}
+
+
+ {stats}
+
+
+
+ >
+ );
+}
diff --git a/public/components/trace_analytics/components/traces/span_detail_flyout.tsx b/public/components/trace_analytics/components/traces/span_detail_flyout.tsx
new file mode 100644
index 0000000000..5a3089bc1a
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/span_detail_flyout.tsx
@@ -0,0 +1,240 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ EuiButtonIcon,
+ EuiCodeBlock,
+ EuiCopy,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiHorizontalRule,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+import _ from 'lodash';
+import moment from 'moment';
+import React, { useEffect, useState } from 'react';
+import { HttpSetup } from '../../../../../../../src/core/public';
+import { TRACE_ANALYTICS_DATE_FORMAT } from '../../../../../common/constants/trace_analytics';
+import { TraceAnalyticsMode } from '../../home';
+import { handleSpansFlyoutRequest } from '../../requests/traces_request_handler';
+import { microToMilliSec, nanoToMilliSec } from '../common/helper_functions';
+import { FlyoutListItem } from './flyout_list_item';
+
+export function SpanDetailFlyout(props: {
+ http: HttpSetup;
+ spanId: string;
+ isFlyoutVisible: boolean;
+ closeFlyout: () => void;
+ addSpanFilter: (field: string, value: any) => void;
+ mode: TraceAnalyticsMode;
+}) {
+ const { mode } = props;
+ const [span, setSpan] = useState({});
+
+ useEffect(() => {
+ handleSpansFlyoutRequest(props.http, props.spanId, setSpan, mode);
+ }, [props.spanId]);
+
+ const getListItem = (field: string, title: React.ReactNode, description: React.ReactNode) => {
+ return (
+ props.addSpanFilter(field, span[field])}
+ />
+ );
+ };
+
+ const isEmpty = (value) => {
+ return (
+ value == null ||
+ (value.hasOwnProperty('length') && value.length === 0) ||
+ (value.constructor === Object && Object.keys(value).length === 0)
+ );
+ };
+
+ const renderContent = () => {
+ if (!span || _.isEmpty(span)) return '-';
+ const overviewList = [
+ getListItem(
+ 'spanId',
+ 'Span ID',
+ (mode === 'data_prepper' ? span.spanId : span.spanID) ? (
+
+
+
+ {(copy) => (
+
+ )}
+
+
+ {mode === 'data_prepper' ? span.spanId : span.spanID}
+
+ ) : (
+ '-'
+ )
+ ),
+ getListItem(
+ 'parentSpanId',
+ 'Parent span ID',
+ (mode === 'data_prepper' ? span.parentSpanId : span.references.length) ? (
+
+
+
+ {(copy) => (
+
+ )}
+
+
+
+ {mode === 'data_prepper' ? span.parentSpanId : span.references[0].spanID}
+
+
+ ) : (
+ '-'
+ )
+ ),
+ getListItem(
+ 'serviceName',
+ 'Service',
+ (mode === 'data_prepper' ? span.serviceName : span.process.serviceName) || '-'
+ ),
+ getListItem(
+ 'name',
+ 'Operation',
+ (mode === 'data_prepper' ? span.name : span.operationName) || '-'
+ ),
+ getListItem(
+ 'durationInNanos',
+ 'Duration',
+ `${
+ mode === 'data_prepper'
+ ? _.round(nanoToMilliSec(Math.max(0, span.durationInNanos)), 2)
+ : _.round(microToMilliSec(Math.max(0, span.duration)), 2)
+ } ms`
+ ),
+ getListItem(
+ 'startTime',
+ 'Start time',
+ mode === 'data_prepper'
+ ? moment(span.startTime).format(TRACE_ANALYTICS_DATE_FORMAT)
+ : moment(_.round(microToMilliSec(Math.max(0, span.startTime)), 2)).format(
+ TRACE_ANALYTICS_DATE_FORMAT
+ )
+ ),
+ getListItem(
+ 'endTime',
+ 'End time',
+ mode === 'data_prepper'
+ ? moment(span.endTime).format(TRACE_ANALYTICS_DATE_FORMAT)
+ : moment(_.round(microToMilliSec(Math.max(0, span.startTime + span.duration)), 2)).format(
+ TRACE_ANALYTICS_DATE_FORMAT
+ )
+ ),
+ getListItem(
+ 'status.code',
+ 'Errors',
+ (mode === 'data_prepper' ? span['status.code'] === 2 : span.tag.error) ? (
+
+ Yes
+
+ ) : (
+ 'No'
+ )
+ ),
+ ];
+ const ignoredKeys = new Set([
+ 'spanId',
+ 'spanID',
+ 'parentSpanId',
+ 'serviceName',
+ 'name',
+ 'operationName',
+ 'durationInNanos',
+ 'duration',
+ 'startTime',
+ 'startTimeMillis',
+ 'endTime',
+ 'status.code',
+ 'events',
+ 'traceId',
+ 'traceID',
+ 'traceGroup',
+ 'traceGroupFields.endTime',
+ 'traceGroupFields.statusCode',
+ 'traceGroupFields.durationInNanos',
+ ]);
+ const attributesList = Object.keys(span)
+ .filter((key) => !ignoredKeys.has(key))
+ .sort((keyA, keyB) => {
+ const isANull = isEmpty(span[keyA]);
+ const isBNull = isEmpty(span[keyB]);
+ if ((isANull && isBNull) || (!isANull && !isBNull)) return keyA < keyB ? -1 : 1;
+ if (isANull) return 1;
+ return -1;
+ })
+ .map((key) => {
+ if (isEmpty(span[key])) return getListItem(key, key, '-');
+ let value = span[key];
+ if (typeof value === 'object') value = JSON.stringify(value);
+ return getListItem(key, key, value);
+ });
+
+ const eventsComponent = _.isEmpty(span.events) ? null : (
+ <>
+
+ Event
+
+
+ {JSON.stringify(span.events, null, 2)}
+
+
+
+ >
+ );
+
+ return (
+ <>
+
+ Overview
+
+
+ {overviewList}
+
+
+ {eventsComponent}
+
+ Span attributes
+ {attributesList.length === 0 || attributesList.length ? (
+ {` (${attributesList.length})`}
+ ) : null}
+
+
+ {attributesList}
+ >
+ );
+ };
+
+ return (
+ <>
+
+
+
+ Span detail
+
+
+ {renderContent()}
+
+ >
+ );
+}
diff --git a/public/components/trace_analytics/components/traces/span_detail_panel.tsx b/public/components/trace_analytics/components/traces/span_detail_panel.tsx
new file mode 100644
index 0000000000..4c2ebedb63
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/span_detail_panel.tsx
@@ -0,0 +1,291 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* eslint-disable react-hooks/exhaustive-deps */
+
+import {
+ EuiBadge,
+ EuiButtonGroup,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHorizontalRule,
+ EuiPanel,
+ EuiSpacer,
+} from '@elastic/eui';
+import _ from 'lodash';
+import React, { useEffect, useMemo, useState } from 'react';
+import { HttpSetup } from '../../../../../../../src/core/public';
+import { Plt } from '../../../visualizations/plotly/plot';
+import { TraceAnalyticsMode } from '../../home';
+import { handleSpansGanttRequest } from '../../requests/traces_request_handler';
+import { PanelTitle } from '../common/helper_functions';
+import { SpanDetailFlyout } from './span_detail_flyout';
+import { SpanDetailTable } from './span_detail_table';
+
+export function SpanDetailPanel(props: {
+ http: HttpSetup;
+ traceId: string;
+ colorMap: any;
+ mode: TraceAnalyticsMode
+ page?: string;
+ openSpanFlyout?: any;
+ data?: { gantt: any[]; table: any[]; ganttMaxX: number };
+ setData?: (data: { gantt: any[]; table: any[]; ganttMaxX: number }) => void;
+}) {
+ const { mode } = props;
+ const storedFilters = sessionStorage.getItem('TraceAnalyticsSpanFilters');
+ const fromApp = props.page === 'app';
+ const [spanFilters, setSpanFilters] = useState>(
+ storedFilters ? JSON.parse(storedFilters) : []
+ );
+ const [DSL, setDSL] = useState({});
+ let data: { gantt: any[]; table: any[]; ganttMaxX: number },
+ setData: (data: { gantt: any[]; table: any[]; ganttMaxX: number }) => void;
+ if (props.data && props.setData) {
+ [data, setData] = [props.data, props.setData];
+ } else {
+ [data, setData] = useState<{ gantt: any[]; table: any[]; ganttMaxX: number }>({
+ gantt: [],
+ table: [],
+ ganttMaxX: 0,
+ });
+ }
+
+ const setSpanFiltersWithStorage = (newFilters: Array<{ field: string; value: any }>) => {
+ setSpanFilters(newFilters);
+ sessionStorage.setItem('TraceAnalyticsSpanFilters', JSON.stringify(newFilters));
+ };
+
+ const addSpanFilter = (field: string, value: any) => {
+ const newFilters = [...spanFilters];
+ const index = newFilters.findIndex(({ field: filterField }) => field === filterField);
+ if (index === -1) {
+ newFilters.push({ field, value });
+ } else {
+ newFilters.splice(index, 1, { field, value });
+ }
+ setSpanFiltersWithStorage(newFilters);
+ };
+
+ const removeSpanFilter = (field: string) => {
+ const newFilters = [...spanFilters];
+ const index = newFilters.findIndex(({ field: filterField }) => field === filterField);
+ if (index !== -1) {
+ newFilters.splice(index, 1);
+ setSpanFiltersWithStorage(newFilters);
+ }
+ };
+
+ const refresh = _.debounce(() => {
+ if (_.isEmpty(props.colorMap)) return;
+ const refreshDSL = spanFiltersToDSL();
+ setDSL(refreshDSL);
+ handleSpansGanttRequest(props.traceId, props.http, setData, props.colorMap, refreshDSL, mode);
+ }, 150);
+
+ const spanFiltersToDSL = () => {
+ const spanDSL: any = mode === 'jaeger' ? {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ traceID: props.traceId,
+ },
+ },
+ ],
+ filter: [],
+ should: [],
+ must_not: [],
+ },
+ },
+ } : {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ traceId: props.traceId,
+ },
+ },
+ ],
+ filter: [],
+ should: [],
+ must_not: [],
+ },
+ },
+ };
+ spanFilters.map(({ field, value }) => {
+ if (value != null) {
+ spanDSL.query.bool.must.push({
+ term: {
+ [field]: value,
+ },
+ });
+ }
+ });
+ return spanDSL;
+ };
+
+ useEffect(() => {
+ refresh();
+ }, [props.colorMap, spanFilters]);
+
+ const getSpanDetailLayout = (plotTraces: Plotly.Data[], maxX: number): Partial => {
+ // get unique labels from traces
+ const yLabels = plotTraces
+ .map((d) => d.y[0])
+ .filter((label, i, self) => self.indexOf(label) === i);
+ // remove uuid when displaying y-ticks
+ const yTexts = yLabels.map((label) => label.substring(0, label.length - 36));
+
+ return {
+ height: 25 * plotTraces.length + 60,
+ width: 800,
+ margin: {
+ l: 260,
+ r: 5,
+ b: 30,
+ t: 30,
+ },
+ xaxis: {
+ ticksuffix: ' ms',
+ side: 'top',
+ color: '#91989c',
+ showline: true,
+ range: [0, maxX * 1.2],
+ },
+ yaxis: {
+ showgrid: false,
+ tickvals: yLabels,
+ ticktext: yTexts,
+ },
+ };
+ };
+
+ const layout = useMemo(
+ () => getSpanDetailLayout(data.gantt, data.ganttMaxX),
+ [data.gantt, data.ganttMaxX]
+ );
+
+ const [currentSpan, setCurrentSpan] = useState('');
+
+ const onClick = (event: any) => {
+ if (!event?.points) return;
+ const point = event.points[0];
+ if (fromApp) {
+ props.openSpanFlyout(point.data.spanId);
+ } else {
+ setCurrentSpan(point.data.spanId);
+ }
+ };
+
+ const renderFilters = useMemo(() => {
+ return spanFilters.map(({ field, value }) => (
+
+ removeSpanFilter(field)}
+ iconOnClickAriaLabel="remove current filter"
+ >
+ {`${field}: ${value}`}
+
+
+ ));
+ }, [spanFilters]);
+
+ const onHover = () => {
+ const dragLayer = document.getElementsByClassName('nsewdrag')?.[0];
+ dragLayer.style.cursor = 'pointer';
+ };
+
+ const onUnhover = () => {
+ const dragLayer = document.getElementsByClassName('nsewdrag')?.[0];
+ dragLayer.style.cursor = '';
+ };
+
+ const toggleOptions = [
+ {
+ id: 'timeline',
+ label: 'Timeline',
+ },
+ {
+ id: 'span_list',
+ label: 'Span list',
+ },
+ ];
+ const [toggleIdSelected, setToggleIdSelected] = useState(toggleOptions[0].id);
+
+ const spanDetailTable = useMemo(
+ () => (
+ {
+ if (fromApp) {
+ props.openSpanFlyout(spanId);
+ } else {
+ setCurrentSpan(spanId);
+ }
+ }}
+ />
+ ),
+ [DSL, setCurrentSpan]
+ );
+
+ return (
+ <>
+
+
+
+
+
+
+ setToggleIdSelected(id)}
+ />
+
+
+ {spanFilters.length > 0 && (
+ <>
+
+
+ {renderFilters}
+
+ >
+ )}
+
+
+ {toggleIdSelected === 'timeline' ? (
+
+ ) : (
+ spanDetailTable
+ )}
+
+
+ {!!currentSpan && (
+ setCurrentSpan('')}
+ addSpanFilter={addSpanFilter}
+ mode={mode}
+ />
+ )}
+ >
+ );
+}
diff --git a/public/components/trace_analytics/components/traces/span_detail_table.tsx b/public/components/trace_analytics/components/traces/span_detail_table.tsx
new file mode 100644
index 0000000000..30f60010f8
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/span_detail_table.tsx
@@ -0,0 +1,288 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* eslint-disable react-hooks/exhaustive-deps */
+
+import { EuiDataGrid, EuiDataGridColumn, EuiLink, EuiText } from '@elastic/eui';
+import _ from 'lodash';
+import moment from 'moment';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { HttpSetup } from '../../../../../../../src/core/public';
+import { TRACE_ANALYTICS_DATE_FORMAT } from '../../../../../common/constants/trace_analytics';
+import { TraceAnalyticsMode } from '../../home';
+import { handleSpansRequest } from '../../requests/traces_request_handler';
+import { microToMilliSec, nanoToMilliSec, NoMatchMessage } from '../common/helper_functions';
+
+interface SpanDetailTableProps {
+ http: HttpSetup;
+ hiddenColumns: string[];
+ openFlyout: (spanId: string) => void;
+ mode: TraceAnalyticsMode;
+ DSL?: any;
+ setTotal?: (total: number) => void;
+}
+
+export interface SpanSearchParams {
+ from: number;
+ size: number;
+ sortingColumns: Array<{
+ [id: string]: 'asc' | 'desc';
+ }>;
+}
+
+export function SpanDetailTable(props: SpanDetailTableProps) {
+ const [tableParams, setTableParams] = useState({
+ size: 10,
+ page: 0,
+ sortingColumns: [] as Array<{
+ id: string;
+ direction: 'asc' | 'desc';
+ }>,
+ });
+ const { mode } = props;
+ const [items, setItems] = useState([]);
+ const [total, setTotal] = useState(0);
+
+ useEffect(() => {
+ const spanSearchParams: SpanSearchParams = {
+ from: tableParams.page * tableParams.size,
+ size: tableParams.size,
+ sortingColumns: tableParams.sortingColumns.map(({ id, direction }) => ({ [id]: direction })),
+ };
+ handleSpansRequest(props.http, setItems, setTotal, spanSearchParams, props.DSL, mode);
+ }, [tableParams, props.DSL]);
+
+ useEffect(() => {
+ if (props.setTotal) props.setTotal(total);
+ }, [total]);
+
+ const columns: EuiDataGridColumn[] = [
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'spanID',
+ display: 'Span ID',
+ },
+ ]
+ : [
+ {
+ id: 'spanId',
+ display: 'Span ID',
+ },
+ ]),
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'references',
+ display: 'Parent span ID',
+ },
+ ]
+ : [
+ {
+ id: 'parentSpanId',
+ display: 'Parent span ID',
+ },
+ ]),
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'traceID',
+ display: 'Trace ID',
+ },
+ ]
+ : [
+ {
+ id: 'traceId',
+ display: 'Trace ID',
+ },
+ ]),
+ ...(mode === 'jaeger'
+ ? []
+ : [
+ {
+ id: 'traceGroup',
+ display: 'Trace group',
+ },
+ ]),
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'process',
+ display: 'Service',
+ },
+ ]
+ : [
+ {
+ id: 'serviceName',
+ display: 'Service',
+ },
+ ]),
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'operationName',
+ display: 'Operation',
+ },
+ ]
+ : [
+ {
+ id: 'name',
+ display: 'Operation',
+ },
+ ]),
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'duration',
+ display: 'Duration',
+ },
+ ]
+ : [
+ {
+ id: 'durationInNanos',
+ display: 'Duration',
+ },
+ ]),
+ {
+ id: 'startTime',
+ display: 'Start time',
+ },
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'jaegerEndTime',
+ display: 'End time',
+ },
+ ]
+ : [
+ {
+ id: 'endTime',
+ display: 'End time',
+ },
+ ]),
+ ...(mode === 'jaeger'
+ ? [
+ {
+ id: 'tag',
+ display: 'Errors',
+ },
+ ]
+ : [
+ {
+ id: 'status.code',
+ display: 'Errors',
+ },
+ ]),
+ ];
+
+ const [visibleColumns, setVisibleColumns] = useState(() =>
+ columns
+ .filter(({ id }) => props.hiddenColumns.findIndex((column) => column === id) === -1)
+ .map(({ id }) => id)
+ );
+
+ const renderCellValue = useMemo(() => {
+ return ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => {
+ const adjustedRowIndex = rowIndex - tableParams.page * tableParams.size;
+ if (!items.hasOwnProperty(adjustedRowIndex)) return '-';
+ const value = items[adjustedRowIndex][columnId];
+ if ((value == null || value === '') && columnId !== 'jaegerEndTime') return '-';
+ switch (columnId) {
+ case 'tag':
+ return value.error === true ? (
+
+ Yes
+
+ ) : (
+ 'No'
+ );
+ case 'references':
+ return value.length > 0 ? value[0].spanID : '';
+ case 'process':
+ return value.serviceName;
+ case 'spanId':
+ return (
+ props.openFlyout(value)}>
+ {value}
+
+ );
+ case 'spanID':
+ return props.openFlyout(value)}>{value} ;
+ case 'durationInNanos':
+ return `${_.round(nanoToMilliSec(Math.max(0, value)), 2)} ms`;
+ case 'duration':
+ return `${_.round(microToMilliSec(Math.max(0, value)), 2)} ms`;
+ case 'startTime':
+ return mode === 'jaeger'
+ ? moment(_.round(microToMilliSec(Math.max(0, value)), 2)).format(
+ TRACE_ANALYTICS_DATE_FORMAT
+ )
+ : moment(value).format(TRACE_ANALYTICS_DATE_FORMAT);
+ case 'jaegerEndTime':
+ return moment(
+ _.round(
+ microToMilliSec(
+ Math.max(0, items[adjustedRowIndex].startTime + items[adjustedRowIndex].duration)
+ ),
+ 2
+ )
+ ).format(TRACE_ANALYTICS_DATE_FORMAT);
+ case 'endTime':
+ return moment(value).format(TRACE_ANALYTICS_DATE_FORMAT);
+ case 'status.code':
+ return value === 2 ? (
+
+ Yes
+
+ ) : (
+ 'No'
+ );
+
+ default:
+ return value;
+ }
+ };
+ }, [items, tableParams.page, tableParams.size]);
+
+ const onSort = useCallback(
+ (sortingColumns) => {
+ setTableParams({
+ ...tableParams,
+ sortingColumns,
+ });
+ },
+ [setTableParams]
+ );
+
+ const onChangeItemsPerPage = useCallback((size) => setTableParams({ ...tableParams, size }), [
+ tableParams,
+ setTableParams,
+ ]);
+ const onChangePage = useCallback((page) => setTableParams({ ...tableParams, page }), [
+ tableParams,
+ setTableParams,
+ ]);
+
+ return (
+ <>
+
+ {total === 0 && }
+ >
+ );
+}
diff --git a/public/components/trace_analytics/components/traces/traces_table.tsx b/public/components/trace_analytics/components/traces/traces_table.tsx
new file mode 100644
index 0000000000..03fa6c68aa
--- /dev/null
+++ b/public/components/trace_analytics/components/traces/traces_table.tsx
@@ -0,0 +1,293 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* eslint-disable react-hooks/exhaustive-deps */
+
+import {
+ EuiButtonIcon,
+ EuiCopy,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHorizontalRule,
+ EuiInMemoryTable,
+ EuiLink,
+ EuiPanel,
+ EuiSpacer,
+ EuiTableFieldDataColumnType,
+ EuiText,
+ PropertySort,
+} from '@elastic/eui';
+import _ from 'lodash';
+import React, { useMemo, useState } from 'react';
+import { TRACES_MAX_NUM } from '../../../../../common/constants/trace_analytics';
+import { TraceAnalyticsMode } from '../../home';
+import {
+ MissingConfigurationMessage,
+ NoMatchMessage,
+ PanelTitle,
+} from '../common/helper_functions';
+
+interface TracesTableProps {
+ items: any[];
+ refresh: (sort?: PropertySort) => void;
+ mode: TraceAnalyticsMode;
+ loading: boolean;
+ traceIdColumnAction: any;
+ jaegerIndicesExist: boolean;
+ dataPrepperIndicesExist: boolean;
+}
+
+export function TracesTable(props: TracesTableProps) {
+ const { items, refresh, mode, loading, traceIdColumnAction } = props;
+ const renderTitleBar = (totalItems?: number) => {
+ return (
+
+
+
+
+
+ );
+ };
+
+ const columns = useMemo(
+ () => {
+ if (mode === 'data_prepper') {
+ return(
+ [
+ {
+ field: 'trace_id',
+ name: 'Trace ID',
+ align: 'left',
+ sortable: true,
+ truncateText: true,
+ render: (item) => (
+
+
+ traceIdColumnAction(item)}>
+ {item.length < 24 ? (
+ item
+ ) : (
+ {_.truncate(item, { length: 24 })}
+ )}
+
+
+
+
+ {(copy) => (
+
+ Click to copy
+
+ )}
+
+
+
+
+ ),
+ },
+ {
+ field: 'trace_group',
+ name: 'Trace group',
+ align: 'left',
+ sortable: true,
+ truncateText: true,
+ render: (item) =>
+ item ? (
+
+ {item.length < 36 ? (
+ item
+ ) : (
+ {_.truncate(item, { length: 36 })}
+ )}
+
+ ) : (
+ '-'
+ ),
+ },
+ {
+ field: 'latency',
+ name: 'Latency (ms)',
+ align: 'right',
+ sortable: true,
+ truncateText: true,
+ },
+ {
+ field: 'percentile_in_trace_group',
+ name: (
+ <>
+ Percentile in trace group
+ {/* trace group
*/}
+ >
+ ),
+ align: 'right',
+ sortable: true,
+ render: (item) =>
+ item === 0 || item ? {`${_.round(item, 2)}th`} : '-',
+ },
+ {
+ field: 'error_count',
+ name: 'Errors',
+ align: 'right',
+ sortable: true,
+ render: (item) =>
+ item == null ? (
+ '-'
+ ) : item > 0 ? (
+
+ Yes
+
+ ) : (
+ 'No'
+ ),
+ },
+ {
+ field: 'last_updated',
+ name: 'Last updated',
+ align: 'left',
+ sortable: true,
+ render: (item) => (item === 0 || item ? item : '-'),
+ },
+ ] as Array>)
+ } else {
+ return (
+ [
+ {
+ field: 'trace_id',
+ name: 'Trace ID',
+ align: 'left',
+ sortable: true,
+ truncateText: true,
+ render: (item) => (
+
+
+ traceIdColumnAction(item)}>
+ {item.length < 24 ? (
+ item
+ ) : (
+ {_.truncate(item, { length: 24 })}
+ )}
+
+
+
+
+ {(copy) => (
+
+ Click to copy
+
+ )}
+
+
+
+
+ ),
+ },
+ {
+ field: 'latency',
+ name: 'Latency (ms)',
+ align: 'right',
+ sortable: true,
+ truncateText: true,
+ },
+ {
+ field: 'error_count',
+ name: 'Errors',
+ align: 'right',
+ sortable: true,
+ render: (item) =>
+ item == null ? (
+ '-'
+ ) : item > 0 ? (
+
+ Yes
+
+ ) : (
+ 'No'
+ ),
+ },
+ {
+ field: 'last_updated',
+ name: 'Last updated',
+ align: 'left',
+ sortable: true,
+ render: (item) => (item === 0 || item ? item : '-'),
+ },
+ ] as Array>)
+ }
+ },
+ [items]
+ );
+
+ const titleBar = useMemo(() => renderTitleBar(items?.length), [items]);
+
+ const [sorting, setSorting] = useState<{ sort: PropertySort }>({
+ sort: {
+ field: 'trace_id',
+ direction: 'asc',
+ },
+ });
+
+ const onTableChange = async ({ currPage, sort }: { currPage: any; sort: any }) => {
+ if (typeof sort?.field !== 'string') return;
+
+ // maps table column key to DSL aggregation name
+ const fieldMappings = {
+ trace_id: '_key',
+ trace_group: null,
+ latency: 'latency',
+ percentile_in_trace_group: null,
+ error_count: 'error_count',
+ last_updated: 'last_updated',
+ };
+ const field = fieldMappings[sort.field as keyof typeof fieldMappings];
+ if (!field || items?.length < TRACES_MAX_NUM) {
+ setSorting({ sort });
+ return;
+ }
+
+ // using await when sorting the default sorted field leads to a bug in UI
+ if (sort.field === 'trace_id') {
+ refresh({ ...sort, field });
+ setSorting({ sort });
+ return;
+ }
+
+ await refresh({ ...sort, field });
+ setSorting({ sort });
+ };
+
+ return (
+ <>
+
+ {titleBar}
+
+
+ {!((mode === 'data_prepper' && props.dataPrepperIndicesExist) || (mode === 'jaeger' && props.jaegerIndicesExist)) ? (
+
+ ) : items?.length > 0 ? (
+
+ ) : (
+
+ )}
+
+ >
+ );
+}
diff --git a/public/components/trace_analytics/home.tsx b/public/components/trace_analytics/home.tsx
new file mode 100644
index 0000000000..d9c443ff15
--- /dev/null
+++ b/public/components/trace_analytics/home.tsx
@@ -0,0 +1,250 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { EuiGlobalToastList } from '@elastic/eui';
+import { Toast } from '@elastic/eui/src/components/toast/global_toast_list';
+import React, { ReactChild, useEffect, useState } from 'react';
+import { Route, RouteComponentProps } from 'react-router-dom';
+import {
+ ChromeBreadcrumb,
+ ChromeStart,
+ HttpStart,
+} from '../../../../../src/core/public';
+import { ObservabilitySideBar } from '../common/side_nav';
+import { FilterType } from './components/common/filters/filters';
+import { SearchBarProps } from './components/common/search_bar';
+import { Dashboard } from './components/dashboard';
+import { Services, ServiceView } from './components/services';
+import { Traces, TraceView } from './components/traces';
+import { handleDataPrepperIndicesExistRequest, handleJaegerIndicesExistRequest } from './requests/request_handler';
+
+export interface TraceAnalyticsCoreDeps {
+ parentBreadcrumbs: ChromeBreadcrumb[];
+ http: HttpStart;
+ chrome: ChromeStart;
+}
+
+interface HomeProps extends RouteComponentProps, TraceAnalyticsCoreDeps {}
+
+export type TraceAnalyticsMode = 'jaeger' | 'data_prepper'
+
+export interface TraceAnalyticsComponentDeps extends TraceAnalyticsCoreDeps, SearchBarProps {
+ mode: TraceAnalyticsMode;
+ modes: {
+ id: string;
+ title: string;
+ }[];
+ setMode: (mode: TraceAnalyticsMode) => void;
+ jaegerIndicesExist: boolean;
+ dataPrepperIndicesExist: boolean;
+}
+
+export const Home = (props: HomeProps) => {
+ const [dataPrepperIndicesExist, setDataPrepperIndicesExist] = useState(false);
+ const [jaegerIndicesExist, setJaegerIndicesExist] = useState(false);
+ const [mode, setMode] = useState(sessionStorage.getItem('TraceAnalyticsMode') as TraceAnalyticsMode || 'jaeger')
+ const storedFilters = sessionStorage.getItem('TraceAnalyticsFilters');
+ const [query, setQuery] = useState(sessionStorage.getItem('TraceAnalyticsQuery') || '');
+ const [filters, setFilters] = useState(
+ storedFilters ? JSON.parse(storedFilters) : []
+ );
+ const [startTime, setStartTime] = useState(
+ sessionStorage.getItem('TraceAnalyticsStartTime') || 'now-5m'
+ );
+ const [endTime, setEndTime] = useState(
+ sessionStorage.getItem('TraceAnalyticsEndTime') || 'now'
+ );
+
+ const setFiltersWithStorage = (newFilters: FilterType[]) => {
+ setFilters(newFilters);
+ sessionStorage.setItem('TraceAnalyticsFilters', JSON.stringify(newFilters));
+ };
+ const setQueryWithStorage = (newQuery: string) => {
+ setQuery(newQuery);
+ sessionStorage.setItem('TraceAnalyticsQuery', newQuery);
+ };
+ const setStartTimeWithStorage = (newStartTime: string) => {
+ setStartTime(newStartTime);
+ sessionStorage.setItem('TraceAnalyticsStartTime', newStartTime);
+ };
+ const setEndTimeWithStorage = (newEndTime: string) => {
+ setEndTime(newEndTime);
+ sessionStorage.setItem('TraceAnalyticsEndTime', newEndTime);
+ };
+ const [toasts, setToasts] = useState([]);
+
+ const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => {
+ if (!text) text = '';
+ setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]);
+ };
+
+ useEffect(() => {
+ handleDataPrepperIndicesExistRequest(props.http, setDataPrepperIndicesExist)
+ handleJaegerIndicesExistRequest(props.http, setJaegerIndicesExist);
+ }, []);
+
+
+ const modes = [
+ { id: 'jaeger', title: 'Jaeger', 'data-test-subj': 'jaeger-mode' },
+ { id: 'data_prepper', title: 'Data Prepper', 'data-test-subj': 'data-prepper-mode' },
+ ];
+
+ useEffect(() => {
+ if (!sessionStorage.getItem('TraceAnalyticsMode')){
+ if (dataPrepperIndicesExist) {
+ setMode('data_prepper');
+ } else if (jaegerIndicesExist) {
+ setMode('jaeger');
+ }
+ }
+ }, [jaegerIndicesExist, dataPrepperIndicesExist]);
+
+ const dashboardBreadcrumbs = [
+ {
+ text: 'Trace analytics',
+ href: '#/trace_analytics/home',
+ },
+ {
+ text: 'Dashboard',
+ href: '#/trace_analytics/home',
+ },
+ ];
+
+ const serviceBreadcrumbs = [
+ {
+ text: 'Trace analytics',
+ href: '#/trace_analytics/home',
+ },
+ {
+ text: 'Services',
+ href: '#/trace_analytics/services',
+ },
+ ];
+
+ const traceBreadcrumbs = [
+ {
+ text: 'Trace analytics',
+ href: '#/trace_analytics/home',
+ },
+ {
+ text: 'Traces',
+ href: '#/trace_analytics/traces',
+ },
+ ];
+
+ const nameColumnAction = (item: any) =>
+ location.assign(`#/trace_analytics/services/${encodeURIComponent(item)}`);
+
+ const traceColumnAction = () => location.assign('#/trace_analytics/traces');
+
+ const traceIdColumnAction = (item: any) =>
+ location.assign(`#/trace_analytics/traces/${encodeURIComponent(item)}`);
+
+ const [appConfigs, _] = useState([]);
+
+ const commonProps: TraceAnalyticsComponentDeps = {
+ parentBreadcrumbs: props.parentBreadcrumbs,
+ http: props.http,
+ chrome: props.chrome,
+ query,
+ setQuery: setQueryWithStorage,
+ filters,
+ appConfigs: appConfigs,
+ setFilters: setFiltersWithStorage,
+ startTime,
+ setStartTime: setStartTimeWithStorage,
+ endTime,
+ setEndTime: setEndTimeWithStorage,
+ mode,
+ modes,
+ setMode: (mode: TraceAnalyticsMode) => {setMode(mode)},
+ jaegerIndicesExist,
+ dataPrepperIndicesExist,
+ };
+
+ return (
+ <>
+ {
+ setToasts(toasts.filter((toast) => toast.id !== removedToast.id));
+ }}
+ toastLifeTimeMs={6000}
+ />
+ (
+
+
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
+ (
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
+ (
+ {
+ for (const addedFilter of filters) {
+ if (
+ addedFilter.field === filter.field &&
+ addedFilter.operator === filter.operator &&
+ addedFilter.value === filter.value
+ ) {
+ return;
+ }
+ }
+ const newFilters = [...filters, filter];
+ setFiltersWithStorage(newFilters);
+ }}
+ />
+ )}
+ />
+ >
+ );
+};