From 3a39de0a6cc2ec290859139d7b3508aab66364ea Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Wed, 4 Sep 2024 00:36:01 -0700 Subject: [PATCH] Trace Analytics support for custom sources (#2112) * improve traces table Signed-off-by: Joshua Li (cherry picked from commit 2e052f2d70fe42343036703f8f685d1d4577295c) * improve queries Signed-off-by: Joshua Li (cherry picked from commit d832ea1b85086224835713cf311214f31d2007fb) * add support for custom trace indices Signed-off-by: Shenoy Pratik * update mode picker Signed-off-by: Shenoy Pratik * added services view for custom trace source Signed-off-by: Shenoy Pratik * updated snapshots Signed-off-by: Shenoy Pratik * update DSL filter tests Signed-off-by: Shenoy Pratik * update app analytics snapshots Signed-off-by: Shenoy Pratik * remove console.log Signed-off-by: Shenoy Pratik * update service map inputs, remove commented code Signed-off-by: Shenoy Pratik * remove console.log, update copy and tests Signed-off-by: Shenoy Pratik --------- Signed-off-by: Shenoy Pratik Co-authored-by: Joshua Li (cherry picked from commit ec67ad1ce6a6d20c0e316a09488d907161931472) --- common/constants/trace_analytics.ts | 16 +- common/types/trace_analytics.ts | 2 + public/components/.DS_Store | Bin 8196 -> 0 bytes .../__snapshots__/create.test.tsx.snap | 384 +- .../service_config.test.tsx.snap | 82 +- .../components/application.tsx | 4 +- .../service_detail_flyout.tsx | 2 +- .../flyout_components/trace_detail_render.tsx | 19 +- .../__tests__/helper_functions.test.tsx | 13 +- .../components/common/custom_index_flyout.tsx | 167 + .../common/filters/filter_helpers.tsx | 12 +- .../components/common/filters/filters.tsx | 4 +- .../components/common/helper_functions.tsx | 64 +- .../__snapshots__/service_map.test.tsx.snap | 13 +- .../components/common/plots/service_map.tsx | 84 +- .../__snapshots__/dashboard.test.tsx.snap | 18 + .../__snapshots__/mode_picker.test.tsx.snap | 201 +- .../dashboard/dashboard_content.tsx | 12 +- .../components/dashboard/mode_picker.tsx | 46 +- .../__snapshots__/services.test.tsx.snap | 100 +- .../components/services/service_metrics.tsx | 3 +- .../components/services/service_view.tsx | 25 +- .../components/services/services_content.tsx | 12 +- .../components/services/services_table.tsx | 10 +- .../__snapshots__/traces.test.tsx.snap | 3393 +++++++++++++---- .../__snapshots__/traces_table.test.tsx.snap | 101 +- .../traces/__tests__/traces.test.tsx | 15 +- .../traces/__tests__/traces_table.test.tsx | 22 +- .../components/traces/services_list.tsx | 97 + .../components/traces/span_detail_flyout.tsx | 39 +- .../components/traces/span_detail_panel.tsx | 4 +- .../components/traces/span_detail_table.tsx | 2 +- .../components/traces/trace_view.tsx | 11 +- .../components/traces/traces.tsx | 4 +- .../components/traces/traces_content.tsx | 192 +- .../traces/traces_custom_indices_table.tsx | 333 ++ .../components/traces/traces_table.tsx | 65 +- public/components/trace_analytics/home.tsx | 24 +- public/components/trace_analytics/index.scss | 35 + .../requests/queries/dashboard_queries.ts | 8 +- .../requests/queries/services_queries.ts | 25 +- .../requests/queries/traces_queries.ts | 148 +- .../requests/request_handler.ts | 13 +- .../requests/services_request_handler.ts | 38 +- .../requests/traces_request_handler.ts | 155 +- server/plugin.ts | 2 + server/plugin_helper/register_settings.ts | 35 + server/routes/trace_analytics_dsl_router.ts | 1 + 48 files changed, 4671 insertions(+), 1384 deletions(-) delete mode 100644 public/components/.DS_Store create mode 100644 public/components/trace_analytics/components/common/custom_index_flyout.tsx create mode 100644 public/components/trace_analytics/components/traces/services_list.tsx create mode 100644 public/components/trace_analytics/components/traces/traces_custom_indices_table.tsx create mode 100644 server/plugin_helper/register_settings.ts diff --git a/common/constants/trace_analytics.ts b/common/constants/trace_analytics.ts index e1ea188715..bbf9a2c7d7 100644 --- a/common/constants/trace_analytics.ts +++ b/common/constants/trace_analytics.ts @@ -7,14 +7,20 @@ export const JAEGER_INDEX_NAME = '*jaeger-span-*'; export const JAEGER_SERVICE_INDEX_NAME = '*jaeger-service*'; export const DATA_PREPPER_INDEX_NAME = 'otel-v1-apm-span-*'; export const DATA_PREPPER_SERVICE_INDEX_NAME = 'otel-v1-apm-service-map*'; -export const TRACE_ANALYTICS_DATE_FORMAT = 'MM/DD/YYYY HH:mm:ss'; -export const TRACE_ANALYTICS_PLOTS_DATE_FORMAT = 'MMM D, YYYY HH:mm:ss'; +export const TRACE_ANALYTICS_DATE_FORMAT = 'MM/DD/YYYY HH:mm:ss.SSS'; +export const TRACE_ANALYTICS_PLOTS_DATE_FORMAT = 'MMM D, YYYY HH:mm:ss.SSS'; export const SERVICE_MAP_MAX_NODES = 500; // size limit when requesting edge related queries, not necessarily the number of edges export const SERVICE_MAP_MAX_EDGES = 1000; export const TRACES_MAX_NUM = 3000; -export const TRACE_ANALYTICS_DOCUMENTATION_LINK = 'https://opensearch.org/docs/latest/observability-plugin/trace/index/'; +export const TRACE_ANALYTICS_DOCUMENTATION_LINK = + 'https://opensearch.org/docs/latest/observability-plugin/trace/index/'; -export const TRACE_ANALYTICS_JAEGER_INDICES_ROUTE = '/api/observability/trace_analytics/jaeger_indices'; -export const TRACE_ANALYTICS_DATA_PREPPER_INDICES_ROUTE = '/api/observability/trace_analytics/data_prepper_indices'; +export const TRACE_ANALYTICS_JAEGER_INDICES_ROUTE = + '/api/observability/trace_analytics/jaeger_indices'; +export const TRACE_ANALYTICS_DATA_PREPPER_INDICES_ROUTE = + '/api/observability/trace_analytics/data_prepper_indices'; export const TRACE_ANALYTICS_DSL_ROUTE = '/api/observability/trace_analytics/query'; + +export const TRACE_CUSTOM_SPAN_INDEX_SETTING = 'observability:traceAnalyticsSpanIndices'; +export const TRACE_CUSTOM_SERVICE_INDEX_SETTING = 'observability:traceAnalyticsServiceIndices'; diff --git a/common/types/trace_analytics.ts b/common/types/trace_analytics.ts index e92c26ae8d..91fbcac41d 100644 --- a/common/types/trace_analytics.ts +++ b/common/types/trace_analytics.ts @@ -52,3 +52,5 @@ export interface GraphVisEdge { to: number; color: string; } + +export type TraceAnalyticsMode = 'jaeger' | 'data_prepper' | 'custom_data_prepper'; diff --git a/public/components/.DS_Store b/public/components/.DS_Store deleted file mode 100644 index b928c26ac868b8b8cab0473a1f8ee452df7ef10d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHM&2G~`5T0$*CRXW>1eL-8$r9J9H2qT%mk`QX2`F7Xe?5r~m5vf|YSs^MDQ3-|h(j2NW!q>Ts z$W+>M8(INRlu%43&qBTDha=Xu!!lqQunbrRECZH-|A7HKvsp?Lp8Mvitt|tVfg8yH zpARky>uRb8M$6HGN>Tvm0=h-eMjjv-H`Uct4~!Nl#x&W3N}(!EF_eMhy36L!)l?6R zHgHk~PAZ*Qr5TEn*@1J#oK)3lTU!P!1EUOZ?p`F1j__H{=I`3a6%MAyD679j&>~t+ ztwRcE5A6h6Q$!84Bb1QdOWz*F=1su)NMCa!%>u9K5Fg-&ZPmS;?L2I|c)O4YqJh=& zY%R=|n?-%fA?K(mtMq`@7^8!cS|g~RIn*L-j!`1acw zL4R}jUWMT=8HN{uxB%_<;v+v4R=@j4VGt*4Yd_S~bYW(;SagbW#kci+-K!^F(v53g z=QV$Op>^n=wLSl3(CRgo?mW>^;ssHwBY>dUg2|g#LDbZ}n(ju;Sg>_-!Es7XW9e?c zzrC@u;#RgcDyJ)M|Ka-Dio3b7eR}GYmhV4!^t^Ep9!8p-08!~_P4njadHor!OK$VH z-ibmTF||C(4DG{xnhx@=Bi>e7`W$S?2u^R&$@ncTn=6aBB?xv+8!q~@rN?cpzrtl) z^zF)BRPOiJE9{lctqVN#5v)NbdWbzZk(ZoriU?ueupK!P@~m-aWm$9J9W8<2kN2pA zQx;Kz9S36vbpYKP4Is5J6hOQdPXhhY;N|@@gkF$YVjLBGm<9CVf*pZlY6#1eTCogV z3kIgmD$AVzKb`#k|61IOU8H5eGVpH
-
- Focus on -
-
-
+
@@ -1899,28 +1895,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
@@ -3091,28 +3083,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
-
- Focus on -
-
-
+
@@ -4258,28 +4246,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
@@ -5518,28 +5502,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
-
- Focus on -
-
-
+
@@ -6685,28 +6665,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
@@ -7872,28 +7848,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
-
- Focus on -
-
-
+
@@ -9000,28 +8972,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
@@ -10190,28 +10158,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
-
- Focus on -
-
-
+
@@ -11323,28 +11287,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
@@ -12547,28 +12507,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
-
- Focus on -
-
-
+
@@ -13714,28 +13670,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
@@ -14906,28 +14858,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
-
- Focus on -
-
-
+
@@ -16073,28 +16021,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
@@ -17303,28 +17247,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
-
- Focus on -
-
-
+
@@ -18556,28 +18496,24 @@ Object { class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
- Focus on -
-
-
-
+
diff --git a/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap b/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap index 9586b31a9f..4d3120efd2 100644 --- a/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap +++ b/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap @@ -986,30 +986,16 @@ exports[`Service Config component renders empty service config 1`] = ` />
- -
- Focus on -
-
-
-
- -
+ + +
@@ -1039,7 +1036,7 @@ exports[`Service Config component renders empty service config 1`] = ` isInvalid={false} >
@@ -2215,30 +2212,16 @@ exports[`Service Config component renders with one service selected 1`] = ` />
- -
- Focus on -
-
-
-
- -
+ + +
@@ -2268,7 +2262,7 @@ exports[`Service Config component renders with one service selected 1`] = ` isInvalid={false} >
diff --git a/public/components/application_analytics/components/application.tsx b/public/components/application_analytics/components/application.tsx index 6825a4f8e0..464de99873 100644 --- a/public/components/application_analytics/components/application.tsx +++ b/public/components/application_analytics/components/application.tsx @@ -314,8 +314,6 @@ export function Application(props: AppDetailProps) { setSelectedTab(TAB_TRACE_ID); }; - const traceIdColumnAction = (item: any) => openTraceFlyout(item); - const getTrace = () => { return ( <> @@ -325,7 +323,7 @@ export function Application(props: AppDetailProps) { page="app" parentBreadcrumb={parentBreadcrumbs[0]} childBreadcrumbs={childBreadcrumbs} - traceIdColumnAction={traceIdColumnAction} + openTraceFlyout={openTraceFlyout} startTime={appStartTime} endTime={appEndTime} setStartTime={setStartTimeForApp} diff --git a/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx b/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx index 3e5c4c2e07..ea8dc6bc48 100644 --- a/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx +++ b/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx @@ -133,7 +133,7 @@ export function ServiceDetailFlyout(props: ServiceFlyoutProps) { handleServiceViewRequest(serviceName, http, serviceDSL, setFields, mode); handleServiceMapRequest(http, serviceDSL, mode, '', setServiceMap, serviceName); const spanDSL = filtersToDsl(mode, filters, query, startTime, endTime, 'app', appConfigs); - spanDSL.query.bool.must.push({ + spanDSL.query.bool.filter.push({ term: { serviceName, }, diff --git a/public/components/application_analytics/components/flyout_components/trace_detail_render.tsx b/public/components/application_analytics/components/flyout_components/trace_detail_render.tsx index bb55a94d96..c382b8b8a0 100644 --- a/public/components/application_analytics/components/flyout_components/trace_detail_render.tsx +++ b/public/components/application_analytics/components/flyout_components/trace_detail_render.tsx @@ -3,27 +3,32 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiText, EuiSpacer, EuiHorizontalRule, EuiCodeBlock } from '@elastic/eui'; +import { EuiCodeBlock, EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui'; import React, { useEffect, useMemo, useState } from 'react'; +import { HttpStart } from '../../../../../../../src/core/public'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { ServiceBreakdownPanel } from '../../../trace_analytics/components/traces/service_breakdown_panel'; import { SpanDetailPanel } from '../../../trace_analytics/components/traces/span_detail_panel'; import { - handleTraceViewRequest, - handleServicesPieChartRequest, handlePayloadRequest, + handleServicesPieChartRequest, + handleTraceViewRequest, } from '../../../trace_analytics/requests/traces_request_handler'; -import { HttpStart } from '../../../../../../../src/core/public'; import { getListItem } from '../../helpers/utils'; -import { TraceAnalyticsMode } from '../../../../../public/components/trace_analytics/home'; interface TraceDetailRenderProps { traceId: string; http: HttpStart; openSpanFlyout: (spanId: string) => void; - mode : TraceAnalyticsMode + mode: TraceAnalyticsMode; } -export const TraceDetailRender = ({ traceId, http, openSpanFlyout, mode }: TraceDetailRenderProps) => { +export const TraceDetailRender = ({ + traceId, + http, + openSpanFlyout, + mode, +}: TraceDetailRenderProps) => { const [fields, setFields] = useState({}); const [serviceBreakdownData, setServiceBreakdownData] = useState([]); const [payloadData, setPayloadData] = useState(''); diff --git a/public/components/trace_analytics/components/common/__tests__/helper_functions.test.tsx b/public/components/trace_analytics/components/common/__tests__/helper_functions.test.tsx index 335bd6c8bf..36d4c66204 100644 --- a/public/components/trace_analytics/components/common/__tests__/helper_functions.test.tsx +++ b/public/components/trace_analytics/components/common/__tests__/helper_functions.test.tsx @@ -5,7 +5,6 @@ import { configure, mount, shallow } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; -import { TraceAnalyticsMode } from 'public/components/trace_analytics/home'; import React from 'react'; import { TEST_SERVICE_MAP, TEST_SERVICE_MAP_GRAPH } from '../../../../../../test/constants'; import { @@ -38,7 +37,9 @@ describe('Helper functions', () => { it('renders no match and missing configuration messages', () => { const noMatchMessage = shallow(); - const missingConfigurationMessage = shallow() + const missingConfigurationMessage = shallow( + + ); expect(noMatchMessage).toMatchSnapshot(); expect(missingConfigurationMessage).toMatchSnapshot(); }); @@ -137,16 +138,16 @@ describe('Helper functions', () => { ); const existsDSL = getTestDslFromFilters(); expect(JSON.stringify(existsDSL)).toEqual( - '{"query":{"bool":{"must":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}},{"exists":{"field":"traceGroup"}}],"filter":[],"should":[],"must_not":[]}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":[]}}}}}' + '{"query":{"bool":{"must":[{"exists":{"field":"traceGroup"}}],"filter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}}],"should":[],"must_not":[]}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":[]}}}}}' ); const isDSL = getTestDslFromFilters('traceGroup', 'is'); expect(JSON.stringify(isDSL)).toEqual( - '{"query":{"bool":{"must":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}},{"term":{"traceGroup":{"from":"100","to":"∞"}}}],"filter":[],"should":[],"must_not":[]}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":[]}}}}}' + '{"query":{"bool":{"must":[{"term":{"traceGroup":{"from":"100","to":"∞"}}}],"filter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}}],"should":[],"must_not":[]}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":[]}}}}}' ); const isBetweenDSL = getTestDslFromFilters('durationInNanos', 'is between'); expect(JSON.stringify(isBetweenDSL)).toEqual( - `{"query":{"bool":{"must":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}},{"range":{"durationInNanos":{"gte":"100"}}}],"filter":[],"should":[],"must_not":[]}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":[]}}}}}` + '{"query":{"bool":{"must":[{"range":{"durationInNanos":{"gte":"100"}}}],"filter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}}],"should":[],"must_not":[]}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":[]}}}}}' ); const customDSL = filtersToDsl( @@ -166,7 +167,7 @@ describe('Helper functions', () => { 'now' ); expect(JSON.stringify(customDSL)).toEqual( - `{"query":{"bool":{"must":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}}],"filter":[],"should":["test"],"must_not":[],"minimum_should_match":1}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":["test"],"minimum_should_match":1}}}}}` + '{"query":{"bool":{"must":[],"filter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}},{"query_string":{"query":"order"}}],"should":["test"],"must_not":[],"minimum_should_match":1}},"custom":{"timeFilter":[{"range":{"startTime":{"gte":"now-5m","lte":"now"}}}],"serviceNames":[],"serviceNamesExclude":[],"traceGroup":[],"traceGroupExclude":[],"percentiles":{"query":{"bool":{"should":["test"],"minimum_should_match":1}}}}}' ); }); }); diff --git a/public/components/trace_analytics/components/common/custom_index_flyout.tsx b/public/components/trace_analytics/components/common/custom_index_flyout.tsx new file mode 100644 index 0000000000..b616b9db49 --- /dev/null +++ b/public/components/trace_analytics/components/common/custom_index_flyout.tsx @@ -0,0 +1,167 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiCallOut, + EuiCompressedFieldText, + EuiDescribedFormGroup, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiFormRow, + EuiLink, + EuiSmallButton, + EuiSmallButtonEmpty, + EuiTitle, +} from '@elastic/eui'; +import React, { Fragment, useEffect, useState } from 'react'; +import { + TRACE_CUSTOM_SERVICE_INDEX_SETTING, + TRACE_CUSTOM_SPAN_INDEX_SETTING, +} from '../../../../../common/constants/trace_analytics'; +import { uiSettingsService } from '../../../../../common/utils'; +import { useToast } from '../../../common/toast'; + +interface CustomIndexFlyoutProps { + isFlyoutVisible: boolean; + setIsFlyoutVisible: React.Dispatch>; +} + +export const CustomIndexFlyout = ({ + isFlyoutVisible, + setIsFlyoutVisible, +}: CustomIndexFlyoutProps) => { + const { setToast } = useToast(); + const [spanIndices, setSpanIndices] = useState(''); + const [serviceIndices, setServiceIndices] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const onChangeSpanIndices = (e: { target: { value: React.SetStateAction } }) => { + setSpanIndices(e.target.value); + }; + + const onChangeServiceIndices = (e: { target: { value: React.SetStateAction } }) => { + setServiceIndices(e.target.value); + }; + + useEffect(() => { + setSpanIndices(uiSettingsService.get(TRACE_CUSTOM_SPAN_INDEX_SETTING)); + setServiceIndices(uiSettingsService.get(TRACE_CUSTOM_SERVICE_INDEX_SETTING)); + }, [uiSettingsService]); + + const onSaveIndices = async () => { + try { + setIsLoading(true); + await uiSettingsService.set(TRACE_CUSTOM_SPAN_INDEX_SETTING, spanIndices); + await uiSettingsService.set(TRACE_CUSTOM_SERVICE_INDEX_SETTING, serviceIndices); + setIsLoading(false); + setToast('Updated trace analytics sources successfully', 'success'); + } catch (error) { + console.error(error); + setToast('Failed to update trace analytics sources', 'danger'); + } + setIsLoading(false); + }; + + const callout = ( + +

+ This feature is experimental, all indices added here should adhere to data prepper index + mappings. For more information on mappings, visit{' '} + + schema documentation + + . +

+
+ ); + let flyout; + + if (isFlyoutVisible) { + flyout = ( + setIsFlyoutVisible(false)} aria-labelledby="flyoutTitle"> + + +

Manage custom source

+
+
+ + Custom span indices} + description={ + + Configure custom span indices to be used by the trace analytics plugin + + } + > + + + + + Custom service indices} + description={ + + Configure custom service indices to be used by the trace analytics plugin + + } + > + + + + + + + + + setIsFlyoutVisible(false)} + flush="left" + > + Close + + + + { + await onSaveIndices(); + setIsFlyoutVisible(false); + }} + fill + isLoading={isLoading} + > + Save + + + + +
+ ); + } + return
{flyout}
; +}; diff --git a/public/components/trace_analytics/components/common/filters/filter_helpers.tsx b/public/components/trace_analytics/components/common/filters/filter_helpers.tsx index 594e870c4f..6634003025 100644 --- a/public/components/trace_analytics/components/common/filters/filter_helpers.tsx +++ b/public/components/trace_analytics/components/common/filters/filter_helpers.tsx @@ -6,20 +6,20 @@ import { EuiCompressedComboBox, EuiCompressedFieldText, - EuiFormControlLayoutDelimited, EuiCompressedFormRow, + EuiFormControlLayoutDelimited, EuiSpacer, } from '@elastic/eui'; -import _ from 'lodash'; -import { TraceAnalyticsMode } from 'public/components/trace_analytics/home'; +import get from 'lodash/get'; import React from 'react'; +import { TraceAnalyticsMode } from '../../../../../../common/types/trace_analytics'; const getFields = ( mode: TraceAnalyticsMode, page: 'dashboard' | 'traces' | 'services' | 'app', attributesFilterFields: string[] ) => - mode === 'data_prepper' + mode === 'data_prepper' || mode === 'custom_data_prepper' ? { dashboard: ['traceGroup', 'serviceName', 'error', 'status.message', 'latency'], traces: [ @@ -93,7 +93,7 @@ const getType = (field: string): string | null => { endTime: 'date_nanos', startTime: 'date_nanos', }; - const type = _.get(typeMapping, field, 'keyword'); + const type = get(typeMapping, field, 'keyword'); return typeof type === 'string' ? type : null; }; @@ -142,7 +142,7 @@ export const getOperatorOptions = (field: string) => { }; const operators = [ ...operatorMapping.default_first, - ..._.get(operatorMapping, type), + ...get(operatorMapping, type), ...operatorMapping.default_last, ]; return operators; diff --git a/public/components/trace_analytics/components/common/filters/filters.tsx b/public/components/trace_analytics/components/common/filters/filters.tsx index 52122b34f4..cafcf36593 100644 --- a/public/components/trace_analytics/components/common/filters/filters.tsx +++ b/public/components/trace_analytics/components/common/filters/filters.tsx @@ -6,7 +6,6 @@ import { EuiBadge, EuiButtonEmpty, - EuiSmallButtonIcon, EuiContextMenu, EuiContextMenuPanelDescriptor, EuiFlexGroup, @@ -14,10 +13,11 @@ import { EuiIcon, EuiPopover, EuiPopoverTitle, + EuiSmallButtonIcon, EuiTextColor, } from '@elastic/eui'; -import { TraceAnalyticsMode } from 'public/components/trace_analytics/home'; import React, { useMemo, useState } from 'react'; +import { TraceAnalyticsMode } from '../../../../../../common/types/trace_analytics'; import { FilterEditPopover } from './filter_edit_popover'; import { getFilterFields, getValidFilterFields } from './filter_helpers'; diff --git a/public/components/trace_analytics/components/common/helper_functions.tsx b/public/components/trace_analytics/components/common/helper_functions.tsx index 9747eb110e..8f98043b46 100644 --- a/public/components/trace_analytics/components/common/helper_functions.tsx +++ b/public/components/trace_analytics/components/common/helper_functions.tsx @@ -5,7 +5,7 @@ /* eslint-disable radix */ import dateMath from '@elastic/datemath'; -import { EuiSmallButtonEmpty, EuiEmptyPrompt, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiSmallButtonEmpty, EuiSpacer, EuiText } from '@elastic/eui'; import { SpacerSize } from '@elastic/eui/src/components/spacer/spacer'; import { isEmpty, round } from 'lodash'; import React from 'react'; @@ -15,10 +15,15 @@ import { JAEGER_INDEX_NAME, JAEGER_SERVICE_INDEX_NAME, TRACE_ANALYTICS_DOCUMENTATION_LINK, + TRACE_CUSTOM_SERVICE_INDEX_SETTING, + TRACE_CUSTOM_SPAN_INDEX_SETTING, } from '../../../../../common/constants/trace_analytics'; -import { GraphVisEdge, GraphVisNode } from '../../../../../common/types/trace_analytics'; +import { + GraphVisEdge, + GraphVisNode, + TraceAnalyticsMode, +} from '../../../../../common/types/trace_analytics'; import { uiSettingsService } from '../../../../../common/utils'; -import { TraceAnalyticsMode } from '../../home'; import { serviceMapColorPalette } from './color_palette'; import { FilterType } from './filters/filters'; import { ServiceObject } from './plots/service_map'; @@ -189,15 +194,12 @@ export function getServiceMapGraph( }; } else { styleOptions = { - borderWidth: 1.5, + borderWidth: 1.0, chosen: false, color: { border: '#DADADC', background: '#FFFFFF', }, - shapeProperties: { - borderDashes: [2, 2], - }, }; } @@ -403,10 +405,10 @@ export const filtersToDsl = ( }, }, }; - DSL.query.bool.must.push(timeFilter); + DSL.query.bool.filter.push(timeFilter); DSL.custom.timeFilter.push(timeFilter); if (query.length > 0) { - DSL.query.bool.must.push({ + DSL.query.bool.filter.push({ query_string: { query, }, @@ -429,13 +431,13 @@ export const filtersToDsl = ( let filterQuery = {}; let field = filter.field; if (field === 'latency') { - if (mode === 'data_prepper') { + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { field = 'traceGroupFields.durationInNanos'; } else if (mode === 'jaeger') { field = 'duration'; } } else if (field === 'error') { - if (mode === 'data_prepper') { + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { field = 'traceGroupFields.statusCode'; } else if (mode === 'jaeger') { field = 'tag.error'; @@ -569,3 +571,43 @@ export const getAttributes = (jsonMapping: JsonMapping): string[] => { } return []; }; + +export const getTraceCustomSpanIndex = () => { + return uiSettingsService.get(TRACE_CUSTOM_SPAN_INDEX_SETTING); +}; + +export const getTraceCustomServiceIndex = () => { + return uiSettingsService.get(TRACE_CUSTOM_SERVICE_INDEX_SETTING); +}; + +export const setTraceCustomSpanIndex = (value: string) => { + return uiSettingsService.set(TRACE_CUSTOM_SPAN_INDEX_SETTING, value); +}; + +export const setTraceCustomServiceIndex = (value: string) => { + return uiSettingsService.set(TRACE_CUSTOM_SERVICE_INDEX_SETTING, value); +}; + +export const getSpanIndices = (mode: TraceAnalyticsMode) => { + switch (mode) { + case 'custom_data_prepper': + return getTraceCustomSpanIndex(); + case 'data_prepper': + return DATA_PREPPER_INDEX_NAME; + case 'jaeger': + default: + return JAEGER_INDEX_NAME; + } +}; + +export const getServiceIndices = (mode: TraceAnalyticsMode) => { + switch (mode) { + case 'custom_data_prepper': + return getTraceCustomServiceIndex(); + case 'data_prepper': + return DATA_PREPPER_SERVICE_INDEX_NAME; + case 'jaeger': + default: + return JAEGER_SERVICE_INDEX_NAME; + } +}; diff --git a/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap b/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap index a048c67739..9af2c5d1ea 100644 --- a/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap +++ b/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap @@ -35,17 +35,11 @@ exports[`Service map component renders service map 1`] = ` margin="m" /> - - Focus on - - - @@ -366,7 +361,7 @@ exports[`Service map component renders service map 1`] = ` `; diff --git a/public/components/trace_analytics/components/common/plots/service_map.tsx b/public/components/trace_analytics/components/common/plots/service_map.tsx index bfa5f18cdc..af2962b2fb 100644 --- a/public/components/trace_analytics/components/common/plots/service_map.tsx +++ b/public/components/trace_analytics/components/common/plots/service_map.tsx @@ -5,13 +5,14 @@ import { EuiButtonGroup, + EuiCompressedFieldSearch, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiSpacer, - EuiText, - EuiCompressedFieldSearch, + EuiSuperSelect, + EuiSuperSelectOption, } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; // @ts-ignore @@ -53,6 +54,7 @@ export function ServiceMap({ page, setCurrentSelectedService, filterByCurrService, + includeMetricsCallback, }: { serviceMap: ServiceObject; idSelected: 'latency' | 'error_rate' | 'throughput'; @@ -70,6 +72,7 @@ export function ServiceMap({ | 'traceView'; setCurrentSelectedService?: (value: React.SetStateAction) => void; filterByCurrService?: boolean; + includeMetricsCallback?: () => void; }) { const [invalid, setInvalid] = useState(false); const [network, setNetwork] = useState(null); @@ -93,6 +96,34 @@ export function ServiceMap({ const [selectedNodeDetails, setSelectedNodeDetails] = useState(null); + const [selectableValue, setSelectableValue] = useState>>([]); + + const onChangeSelectable = (value: React.SetStateAction>>) => { + // if the change is changing for the first time then callback servicemap with metrics + if (selectableValue.length === 0 && value.length !== 0) { + if (includeMetricsCallback) { + includeMetricsCallback(); + } + } + setIdSelected(value); + setSelectableValue(value); + }; + + const metricOptions: Array> = [ + { + value: 'latency', + inputDisplay: 'Duration', + }, + { + value: 'error_rate', + inputDisplay: 'Errors', + }, + { + value: 'throughput', + inputDisplay: 'Request Rate', + }, + ]; + const options = { layout: { // hierarchical: true, @@ -209,20 +240,22 @@ export function ServiceMap({ )} - setIdSelected(id as 'latency' | 'error_rate' | 'throughput')} - buttonSize="s" - color="text" - /> - - - - Focus on - - + {page !== 'traces' && ( + <> + setIdSelected(id as 'latency' | 'error_rate' | 'throughput')} + buttonSize="s" + color="text" + /> + + + )} + + setQuery(e.target.value)} @@ -230,6 +263,17 @@ export function ServiceMap({ isInvalid={query.length > 0 && invalid} /> + {page === 'traces' && ( + + onChangeSelectable(value)} + /> + + )} @@ -267,9 +311,11 @@ export function ServiceMap({ )}
- - - + {(page !== 'traces' || idSelected) && ( + + + + )} ) : (
@@ -277,7 +323,7 @@ export function ServiceMap({
)} - + {filterByCurrService && items?.graph && ( )} 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 index 13c2c5b1d4..979c56df44 100644 --- 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 @@ -384,6 +384,12 @@ exports[`Dashboard component renders dashboard 1`] = `
+ +
+
+ +
+
+ +
+
+ +
+ `; @@ -310,7 +316,7 @@ exports[`Mode picker component renders mode picker 2`] = `
- Choose data type + Choose data schema
+
+
+
+ +
+
+
+ + + +
+
+
+
@@ -568,7 +626,7 @@ exports[`Mode picker component renders mode picker 2`] = `
- Choose data type + Choose data schema
+ +
+ +
+ +
+ + + + + + + +
+
+ +
+ +
+ + + + + + + + + + + +
+
+
+
+
+
+
+
@@ -851,5 +1042,11 @@ exports[`Mode picker component renders mode picker 2`] = `
+ +
+ `; diff --git a/public/components/trace_analytics/components/dashboard/dashboard_content.tsx b/public/components/trace_analytics/components/dashboard/dashboard_content.tsx index f1390f6788..22bac8ebf1 100644 --- a/public/components/trace_analytics/components/dashboard/dashboard_content.tsx +++ b/public/components/trace_analytics/components/dashboard/dashboard_content.tsx @@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import _ from 'lodash'; import React, { useEffect, useState } from 'react'; import { useToast } from '../../../../../public/components/common/toast'; +import { coreRefs } from '../../../../../public/framework/core_refs'; import { handleDashboardErrorRatePltRequest, handleDashboardRequest, @@ -31,7 +32,6 @@ import { ThroughputPlt } from '../common/plots/throughput_plt'; import { DashboardProps } from './dashboard'; import { DashboardTable } from './dashboard_table'; import { TopGroupsPage } from './top_groups_page'; -import { coreRefs } from '../../../../../public/framework/core_refs'; export function DashboardContent(props: DashboardProps) { const { @@ -100,7 +100,8 @@ export function DashboardContent(props: DashboardProps) { useEffect(() => { if ( !redirect && - ((mode === 'data_prepper' && dataPrepperIndicesExist) || + (mode === 'custom_data_prepper' || + (mode === 'data_prepper' && dataPrepperIndicesExist) || (mode === 'jaeger' && jaegerIndicesExist)) ) refresh(); @@ -184,7 +185,7 @@ export function DashboardContent(props: DashboardProps) { dataSourceMDSId[0].id, setPercentileMap ).finally(() => setLoading(false)); - } else if (mode === 'data_prepper') { + } else if (mode === 'data_prepper' || mode === 'custom_data_prepper') { handleDashboardRequest( http, DSL, @@ -285,10 +286,11 @@ export function DashboardContent(props: DashboardProps) { return ( <> - {(mode === 'data_prepper' && dataPrepperIndicesExist) || + {mode === 'custom_data_prepper' || + (mode === 'data_prepper' && dataPrepperIndicesExist) || (mode === 'jaeger' && jaegerIndicesExist) ? (
- {mode === 'data_prepper' ? ( + {mode === 'data_prepper' || mode === 'custom_data_prepper' ? ( <>
- {'Choose data type'} + {'Choose data schema'} )} + + + + { + setIsFlyoutVisible(true); + setPopoverIsOpen(false); + }} + > + Manage custom source + + + + + + + + + + +
+ ); } 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 index 315ab6f04a..41b191f192 100644 --- 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 @@ -614,6 +614,12 @@ exports[`Services component renders empty services page 1`] = `
+ +
+
@@ -2065,30 +2071,16 @@ exports[`Services component renders empty services page 1`] = ` />
- -
- Focus on -
-
-
-
- -
+ + +
@@ -2118,7 +2121,7 @@ exports[`Services component renders empty services page 1`] = ` isInvalid={false} >
@@ -2905,6 +2908,12 @@ exports[`Services component renders jaeger services page 1`] = `
+ +
+
@@ -4619,6 +4628,12 @@ exports[`Services component renders services page 1`] = `
+ +
+
@@ -6073,30 +6088,16 @@ exports[`Services component renders services page 1`] = ` />
- -
- Focus on -
-
-
-
- -
+ + +
@@ -6126,7 +6138,7 @@ exports[`Services component renders services page 1`] = ` isInvalid={false} >
diff --git a/public/components/trace_analytics/components/services/service_metrics.tsx b/public/components/trace_analytics/components/services/service_metrics.tsx index 1541d69ee2..964604059a 100644 --- a/public/components/trace_analytics/components/services/service_metrics.tsx +++ b/public/components/trace_analytics/components/services/service_metrics.tsx @@ -6,9 +6,8 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; import { DataSourceOption } from '../../../../../../../src/plugins/data_source_management/public'; -import { ServiceTrends } from '../../../../../common/types/trace_analytics'; +import { ServiceTrends, TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { coreRefs } from '../../../../framework/core_refs'; -import { TraceAnalyticsMode } from '../../home'; import { handleServiceTrendsRequest } from '../../requests/services_request_handler'; import { ErrorRatePlt } from '../common/plots/error_rate_plt'; import { LatencyPltPanel } from '../common/plots/latency_trend_plt'; diff --git a/public/components/trace_analytics/components/services/service_view.tsx b/public/components/trace_analytics/components/services/service_view.tsx index 7ee815c8d5..d6193f7970 100644 --- a/public/components/trace_analytics/components/services/service_view.tsx +++ b/public/components/trace_analytics/components/services/service_view.tsx @@ -6,7 +6,6 @@ import { EuiBadge, - EuiSmallButton, EuiContextMenu, EuiContextMenuPanelDescriptor, EuiFlexGroup, @@ -21,6 +20,7 @@ import { EuiPageBody, EuiPanel, EuiPopover, + EuiSmallButton, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -88,7 +88,7 @@ export function ServiceView(props: ServiceViewProps) { mode, props.dataSourceMDSId[0].id ); - if (mode === 'data_prepper') { + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { handleServiceMapRequest( props.http, DSL, @@ -137,7 +137,10 @@ export function ServiceView(props: ServiceViewProps) { const redirectToServiceTraces = () => { if (setCurrentSelectedService) setCurrentSelectedService(''); setRedirect(true); - const filterField = mode === 'data_prepper' ? 'serviceName' : 'process.serviceName'; + const filterField = + mode === 'data_prepper' || mode === 'custom_data_prepper' + ? 'serviceName' + : 'process.serviceName'; props.addFilter({ field: filterField, operator: 'is', @@ -167,7 +170,7 @@ export function ServiceView(props: ServiceViewProps) { { id: 0, items: [ - ...(mode === 'data_prepper' + ...(mode === 'data_prepper' || mode === 'custom_data_prepper' ? [ { name: 'View logs', @@ -278,7 +281,7 @@ export function ServiceView(props: ServiceViewProps) { {props.serviceName || '-'} - {mode === 'data_prepper' ? ( + {mode === 'data_prepper' || mode === 'custom_data_prepper' ? ( Number of connected services @@ -290,7 +293,7 @@ export function ServiceView(props: ServiceViewProps) { ) : ( )} - {mode === 'data_prepper' ? ( + {mode === 'data_prepper' || mode === 'custom_data_prepper' ? ( Connected services @@ -403,14 +406,14 @@ export function ServiceView(props: ServiceViewProps) { processTimeStamp(props.startTime, mode), processTimeStamp(props.endTime, mode) ); - if (mode === 'data_prepper') { - spanDSL.query.bool.must.push({ + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { + spanDSL.query.bool.filter.push({ term: { serviceName: props.serviceName, }, }); } else if (mode === 'jaeger') { - spanDSL.query.bool.must.push({ + spanDSL.query.bool.filter.push({ term: { 'process.serviceName': props.serviceName, }, @@ -418,7 +421,7 @@ export function ServiceView(props: ServiceViewProps) { } spanFilters.map(({ field, value }) => { if (value != null) { - spanDSL.query.bool.must.push({ + spanDSL.query.bool.filter.push({ term: { [field]: value, }, @@ -489,7 +492,7 @@ export function ServiceView(props: ServiceViewProps) { {overview} - {mode === 'data_prepper' ? ( + {mode === 'data_prepper' || mode === 'custom_data_prepper' ? ( <> - {mode === 'data_prepper' && dataPrepperIndicesExist ? ( + {mode === 'custom_data_prepper' || + (mode === 'data_prepper' && dataPrepperIndicesExist) ? ( - {mode === 'data_prepper' && ( + {(mode === 'data_prepper' || mode === 'custom_data_prepper') && ( ), }, - ...(mode === 'data_prepper' + ...(mode === 'data_prepper' || mode === 'custom_data_prepper' ? [ { field: 'number_of_connected_services', @@ -190,7 +189,7 @@ export function ServicesTable(props: ServicesTableProps) { }, ] : []), - ...(mode === 'data_prepper' + ...(mode === 'data_prepper' || mode === 'custom_data_prepper' ? [ { field: 'connected_services', @@ -269,6 +268,7 @@ export function ServicesTable(props: ServicesTableProps) { {!( + mode === 'custom_data_prepper' || (mode === 'data_prepper' && dataPrepperIndicesExist) || (mode === 'jaeger' && jaegerIndicesExist) ) ? ( 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 index 36ea53e2f5..6ccb1c3cb4 100644 --- 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 @@ -195,6 +195,7 @@ exports[`Traces component renders empty traces page 1`] = ` } endTime="now" filters={Array []} + getTraceViewUri={[MockFunction]} http={[MockFunction]} mode="data_prepper" modes={ @@ -236,7 +237,6 @@ exports[`Traces component renders empty traces page 1`] = ` setQuery={[MockFunction]} setStartTime={[MockFunction]} startTime="now-5m" - traceIdColumnAction={[Function]} >
+ +
+
@@ -1492,121 +1498,13 @@ exports[`Traces component renders empty traces page 1`] = ` className="euiSpacer euiSpacer--s" />
- -
- -
-
- -
-
- -
-
- -
- -
-
- -
-
- -
- - -
-
- + 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 []} + loading={true} + onTableChange={[Function]} + pagination={ + Object { + "initialPageSize": 10, + "pageSizeOptions": Array [ + 5, + 10, + 15, + ], + } + } + responsive={true} + sorting={ + Object { + "sort": Object { + "direction": "asc", + "field": "trace_id", + }, + } + } + tableLayout="auto" > - -
- - - No data matches the selected filter. Clear the filter and/or increase the time range to see more results. - + + 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, + }, + ] } - title={ -

- No matches -

+ items={Array []} + loading={true} + noItemsMessage="No items found" + onChange={[Function]} + pagination={ + Object { + "hidePerPageOptions": undefined, + "pageIndex": 0, + "pageSize": 10, + "pageSizeOptions": Array [ + 5, + 10, + 15, + ], + "totalItemCount": 0, + } + } + responsive={true} + sorting={ + Object { + "allowNeutralSort": false, + "sort": Object { + "direction": "asc", + "field": "Trace ID", + }, + } } + tableLayout="auto" >
- -

- 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`] = ` - + + +
+ + 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" + > +
+
+ + + +
+
+
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+ + +
+ + + +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + +`; + +exports[`Traces component renders jaeger traces page 1`] = ` +
+ +
+
@@ -3196,52 +3929,759 @@ exports[`Traces component renders jaeger traces page 1`] = ` className="euiPopover euiPopover--anchorDownLeft" data-test-subj="addfilter" > -
+ + + +
+
+ + +
+ +
+ + + +
+ + + +
+ +
+ +
+ + +
+ + Traces + + + (0) + +
+
+
+
+
+
+
+ +
+ + +
+
+ + +
+
+ +
+ +
+ +
+ + +
+ +
+ + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > +
+
+ + + +
+
+
+
+
+
+
+
+ +
+ + + - - + + + + + - - + Add filter - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - +
+ + + + + +
+
+ + No items found + +
+
+
+
+
+ +
- - + + @@ -3308,164 +4748,33 @@ exports[`Traces component renders jaeger traces page 1`] = ` xmlns="http://www.w3.org/2000/svg" > - - - - - - Service and Operations - - -
-
- -
-
- -
- -
-
- -
-
- -
-
- -
- - - -
- -
- -
- - -
- - Traces - - - (0) - -
-
-
-
-
+ d="m5.157 13.069 4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771Z" + fillRule="nonzero" + /> + + + + + + Service and Operations + +
-
-
- - -
-
- - -
- - - No data matches the selected filter. Clear the filter and/or increase the time range to see more results. - - } - title={ -

- No matches -

- } + aria-labelledby="random_html_id" + className="euiAccordion__childWrapper" + id="accordion1" + role="region" + tabIndex={-1} > -
- -

- No matches -

-
- - +
- -
- -
- No data matches the selected filter. Clear the filter and/or increase the time range to see more results. -
-
-
-
- - -
- - -
- - -
- - +
+
+ +
+
+ +
+
@@ -3704,6 +4991,7 @@ exports[`Traces component renders traces page 1`] = ` } endTime="now" filters={Array []} + getTraceViewUri={[MockFunction]} http={[MockFunction]} mode="data_prepper" modes={ @@ -3747,7 +5035,6 @@ exports[`Traces component renders traces page 1`] = ` setQuery={[MockFunction]} setStartTime={[MockFunction]} startTime="now-5m" - traceIdColumnAction={[Function]} >
+ +
+
@@ -4967,38 +6260,912 @@ exports[`Traces component renders traces page 1`] = ` onClick={[Function]} type="button" > - - + + + + Add filter + + + + + +
+
+ + +
+ +
+ + + +
+ + + +
+ +
+ +
+ + +
+ + Traces + + + (0) + +
+
+
+
+
+
+
+ +
+ + +
+
+ + 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 []} + loading={true} + 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 []} + loading={true} + noItemsMessage="No items found" + onChange={[Function]} + pagination={ + Object { + "hidePerPageOptions": undefined, + "pageIndex": 0, + "pageSize": 10, + "pageSizeOptions": Array [ + 5, + 10, + 15, + ], + "totalItemCount": 0, + } + } + responsive={true} + sorting={ + Object { + "allowNeutralSort": false, + "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" + > +
+
+ + + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + - - + Add filter - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+ +
- - + + @@ -5108,158 +7275,6 @@ exports[`Traces component renders traces page 1`] = `
- -
- - - -
- -
- -
- - -
- - 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 index 2dfbd5bbf1..eb4bdd4127 100644 --- 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 @@ -3,12 +3,12 @@ exports[`Traces table component renders empty traces table message 1`] = `
@@ -1765,6 +1773,7 @@ exports[`Traces table component renders jaeger traces table 1`] = ` exports[`Traces table component renders traces table 1`] = `
-
- Percentile in trace group -
- , + "name":
+ Percentile in trace group +
, "render": [Function], "sortable": true, }, @@ -1940,7 +1947,7 @@ exports[`Traces table component renders traces table 1`] = ` "name": "Trace ID", "render": [Function], "sortable": true, - "truncateText": true, + "truncateText": false, }, Object { "align": "left", @@ -1960,11 +1967,9 @@ exports[`Traces table component renders traces table 1`] = ` Object { "align": "right", "field": "percentile_in_trace_group", - "name": -
- Percentile in trace group -
-
, + "name":
+ Percentile in trace group +
, "render": [Function], "sortable": true, }, @@ -2016,7 +2021,7 @@ exports[`Traces table component renders traces table 1`] = ` responsive={true} sorting={ Object { - "allowNeutralSort": true, + "allowNeutralSort": false, "sort": Object { "direction": "asc", "field": "Trace ID", @@ -2082,11 +2087,9 @@ exports[`Traces table component renders traces table 1`] = ` "isSortAscending": undefined, "isSorted": false, "key": "_data_s_percentile_in_trace_group_3", - "name": -
- Percentile in trace group -
-
, + "name":
+ Percentile in trace group +
, "onSort": [Function], }, Object { @@ -2648,7 +2651,7 @@ exports[`Traces table component renders traces table 1`] = ` } setScopeRow={false} textOnly={false} - truncateText={true} + truncateText={false} >
@@ -2785,13 +2793,6 @@ exports[`Traces table component renders traces table 1`] = `
- -
-
@@ -2882,11 +2883,9 @@ exports[`Traces table component renders traces table 1`] = ` key="_data_column_percentile_in_trace_group_0_3" mobileOptions={ Object { - "header": -
- Percentile in trace group -
-
, + "header":
+ Percentile in trace group +
, "render": undefined, } } diff --git a/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx b/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx index 2cf1b8fb6a..2bfe5b5883 100644 --- a/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx +++ b/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx @@ -22,8 +22,7 @@ describe('Traces component', () => { const setFilters = jest.fn(); const setStartTime = jest.fn(); const setEndTime = jest.fn(); - const traceIdColumnAction = (item: any) => - location.assign(`#/trace_analytics/traces/${encodeURIComponent(item)}`); + const getTraceViewUri = jest.fn(); const childBreadcrumbs = [ { text: 'Trace analytics', @@ -40,7 +39,7 @@ describe('Traces component', () => { chrome={chrome!} parentBreadcrumb={{ text: 'test', href: 'test#/' }} childBreadcrumbs={childBreadcrumbs} - traceIdColumnAction={traceIdColumnAction} + getTraceViewUri={getTraceViewUri} query="" setQuery={setQuery} filters={[]} @@ -68,8 +67,7 @@ describe('Traces component', () => { const setFilters = jest.fn(); const setStartTime = jest.fn(); const setEndTime = jest.fn(); - const traceIdColumnAction = (item: any) => - location.assign(`#/trace_analytics/traces/${encodeURIComponent(item)}`); + const getTraceViewUri = jest.fn(); const childBreadcrumbs = [ { text: 'Trace analytics', @@ -86,7 +84,7 @@ describe('Traces component', () => { chrome={chrome!} parentBreadcrumbs={[{ text: 'test', href: 'test#/' }]} childBreadcrumbs={childBreadcrumbs} - traceIdColumnAction={traceIdColumnAction} + getTraceViewUri={getTraceViewUri} query="" setQuery={setQuery} filters={[]} @@ -114,8 +112,7 @@ describe('Traces component', () => { const setFilters = jest.fn(); const setStartTime = jest.fn(); const setEndTime = jest.fn(); - const traceIdColumnAction = (item: any) => - location.assign(`#/trace_analytics/traces/${encodeURIComponent(item)}`); + const getTraceViewUri = jest.fn(); const childBreadcrumbs = [ { text: 'Trace analytics', @@ -132,7 +129,7 @@ describe('Traces component', () => { chrome={chrome!} parentBreadcrumbs={[{ text: 'test', href: 'test#/' }]} childBreadcrumbs={childBreadcrumbs} - traceIdColumnAction={traceIdColumnAction} + getTraceViewUri={getTraceViewUri} query="" setQuery={setQuery} filters={[]} diff --git a/public/components/trace_analytics/components/traces/__tests__/traces_table.test.tsx b/public/components/trace_analytics/components/traces/__tests__/traces_table.test.tsx index b0dcc6f06c..33f01f1ea6 100644 --- a/public/components/trace_analytics/components/traces/__tests__/traces_table.test.tsx +++ b/public/components/trace_analytics/components/traces/__tests__/traces_table.test.tsx @@ -13,7 +13,7 @@ describe('Traces table component', () => { it('renders empty traces table message', () => { const refresh = jest.fn(); - const traceIdColumnAction = (item: any) => + const getTraceViewUri = (item: any) => location.assign(`#/trace_analytics/traces/${encodeURIComponent(item)}`); const noIndicesTable = mount( { refresh={refresh} dataPrepperIndicesExist={false} jaegerIndicesExist={false} - mode='data_prepper' + mode="data_prepper" loading={false} - traceIdColumnAction={traceIdColumnAction} + getTraceViewUri={getTraceViewUri} /> ); expect(noIndicesTable).toMatchSnapshot(); @@ -34,9 +34,9 @@ describe('Traces table component', () => { refresh={refresh} dataPrepperIndicesExist={true} jaegerIndicesExist={false} - mode='data_prepper' + mode="data_prepper" loading={false} - traceIdColumnAction={traceIdColumnAction} + getTraceViewUri={getTraceViewUri} /> ); expect(emptyTable).toMatchSnapshot(); @@ -55,7 +55,7 @@ describe('Traces table component', () => { actions: '#', }, ]; - const traceIdColumnAction = (item: any) => + const getTraceViewUri = (item: any) => location.assign(`#/trace_analytics/traces/${encodeURIComponent(item)}`); const refresh = jest.fn(); const wrapper = mount( @@ -64,9 +64,9 @@ describe('Traces table component', () => { refresh={refresh} dataPrepperIndicesExist={true} jaegerIndicesExist={false} - mode='data_prepper' + mode="data_prepper" loading={false} - traceIdColumnAction={traceIdColumnAction} + getTraceViewUri={getTraceViewUri} /> ); expect(wrapper).toMatchSnapshot(); @@ -86,7 +86,7 @@ describe('Traces table component', () => { actions: '#', }, ]; - const traceIdColumnAction = (item: any) => + const getTraceViewUri = (item: any) => location.assign(`#/trace_analytics/traces/${encodeURIComponent(item)}`); const refresh = jest.fn(); const wrapper = mount( @@ -95,9 +95,9 @@ describe('Traces table component', () => { refresh={refresh} dataPrepperIndicesExist={false} jaegerIndicesExist={true} - mode='jaeger' + mode="jaeger" loading={false} - traceIdColumnAction={traceIdColumnAction} + getTraceViewUri={getTraceViewUri} /> ); expect(wrapper).toMatchSnapshot(); diff --git a/public/components/trace_analytics/components/traces/services_list.tsx b/public/components/trace_analytics/components/traces/services_list.tsx new file mode 100644 index 0000000000..69404c7956 --- /dev/null +++ b/public/components/trace_analytics/components/traces/services_list.tsx @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPanel, + EuiSelectable, + EuiSpacer, +} from '@elastic/eui'; +import React, { Fragment, useEffect, useMemo, useState } from 'react'; +import { FilterType } from '../common/filters/filters'; +import { PanelTitle } from '../common/helper_functions'; +import { ServiceObject } from '../common/plots/service_map'; + +interface ServicesListProps { + serviceMap: ServiceObject; + addFilter?: (filter: FilterType) => void; + filteredService: string; + setFilteredService: React.Dispatch>; +} + +export const ServicesList = ({ + serviceMap, + addFilter, + filteredService, + setFilteredService, +}: ServicesListProps) => { + const [options, setOptions] = useState>([]); + + const nameColumnAction = (serviceName: string) => { + if (addFilter) { + addFilter({ + field: 'serviceName', + operator: 'is', + value: serviceName, + inverted: false, + disabled: false, + }); + setFilteredService(serviceName); + window.scrollTo({ left: 0, top: 0, behavior: 'smooth' }); + } + }; + + const titleBar = useMemo( + () => ( + + + + + + ), + [serviceMap] + ); + + useEffect(() => { + setOptions( + Object.keys(serviceMap).map((key) => { + return filteredService === key + ? { label: key, checked: 'on', bordered: false } + : { label: key, bordered: false }; + }) + ); + }, [serviceMap]); + + return ( + + {titleBar} + + +
+ { + setOptions(newOptions); + nameColumnAction(newOptions.filter((option) => option.checked === 'on')[0].label); + }} + singleSelection={true} + > + {(list, search) => ( + + {search} + {list} + + )} + +
+
+ ); +}; diff --git a/public/components/trace_analytics/components/traces/span_detail_flyout.tsx b/public/components/trace_analytics/components/traces/span_detail_flyout.tsx index 5f75701aac..eefa4dcef1 100644 --- a/public/components/trace_analytics/components/traces/span_detail_flyout.tsx +++ b/public/components/trace_analytics/components/traces/span_detail_flyout.tsx @@ -5,7 +5,6 @@ import { EuiButtonEmpty, - EuiSmallButtonIcon, EuiCodeBlock, EuiCopy, EuiFlexGroup, @@ -14,6 +13,7 @@ import { EuiFlyoutBody, EuiFlyoutHeader, EuiHorizontalRule, + EuiSmallButtonIcon, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -28,9 +28,8 @@ import { } from '../../../../../common/constants/data_sources'; import { observabilityLogsID } from '../../../../../common/constants/shared'; import { TRACE_ANALYTICS_DATE_FORMAT } from '../../../../../common/constants/trace_analytics'; -import { SpanField } from '../../../../../common/types/trace_analytics'; +import { SpanField, TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { coreRefs } from '../../../../framework/core_refs'; -import { TraceAnalyticsMode } from '../../home'; import { handleSpansFlyoutRequest } from '../../requests/traces_request_handler'; import { microToMilliSec, nanoToMilliSec } from '../common/helper_functions'; import { FlyoutListItem } from './flyout_list_item'; @@ -56,6 +55,16 @@ const MODE_TO_FIELDS: Record MODE_TO_FIELDS[mode][field]; @@ -144,7 +153,11 @@ export function SpanDetailFlyout(props: { {(copy) => ( - {mode === 'data_prepper' ? span.parentSpanId : span.references[0].spanID} + {mode === 'data_prepper' || mode === 'custom_data_prepper' + ? span.parentSpanId + : span.references[0].spanID} ) : ( @@ -177,7 +192,7 @@ export function SpanDetailFlyout(props: { getSpanFieldKey(mode, 'DURATION'), 'Duration', `${ - mode === 'data_prepper' + mode === 'data_prepper' || mode === 'custom_data_prepper' ? round(nanoToMilliSec(Math.max(0, span.durationInNanos)), 2) : round(microToMilliSec(Math.max(0, span.duration)), 2) } ms` @@ -185,7 +200,7 @@ export function SpanDetailFlyout(props: { getListItem( getSpanFieldKey(mode, 'START_TIME'), 'Start time', - mode === 'data_prepper' + mode === 'data_prepper' || mode === 'custom_data_prepper' ? moment(span.startTime).format(TRACE_ANALYTICS_DATE_FORMAT) : moment(round(microToMilliSec(Math.max(0, span.startTime)), 2)).format( TRACE_ANALYTICS_DATE_FORMAT @@ -194,7 +209,7 @@ export function SpanDetailFlyout(props: { getListItem( getSpanFieldKey(mode, 'END_TIME'), 'End time', - mode === 'data_prepper' + mode === 'data_prepper' || mode === 'custom_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 @@ -203,7 +218,11 @@ export function SpanDetailFlyout(props: { getListItem( getSpanFieldKey(mode, 'ERRORS'), 'Errors', - (mode === 'data_prepper' ? span['status.code'] === 2 : span.tag?.error) ? ( + ( + mode === 'data_prepper' || mode === 'custom_data_prepper' + ? span['status.code'] === 2 + : span.tag?.error + ) ? ( Yes @@ -316,7 +335,7 @@ export function SpanDetailFlyout(props: {

Span detail

- {mode === 'data_prepper' && ( + {(mode === 'data_prepper' || mode === 'custom_data_prepper') && ( View associated logs diff --git a/public/components/trace_analytics/components/traces/span_detail_panel.tsx b/public/components/trace_analytics/components/traces/span_detail_panel.tsx index 0d146a6e6c..c0dfdbb762 100644 --- a/public/components/trace_analytics/components/traces/span_detail_panel.tsx +++ b/public/components/trace_analytics/components/traces/span_detail_panel.tsx @@ -17,8 +17,8 @@ import debounce from 'lodash/debounce'; import isEmpty from 'lodash/isEmpty'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { HttpSetup } from '../../../../../../../src/core/public'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; 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'; @@ -131,7 +131,7 @@ export function SpanDetailPanel(props: { }; spanFilters.map(({ field, value }) => { if (value != null) { - spanDSL.query.bool.must.push({ + spanDSL.query.bool.filter.push({ term: { [field]: value, }, diff --git a/public/components/trace_analytics/components/traces/span_detail_table.tsx b/public/components/trace_analytics/components/traces/span_detail_table.tsx index 90a0e0e80f..76d3d0b4ee 100644 --- a/public/components/trace_analytics/components/traces/span_detail_table.tsx +++ b/public/components/trace_analytics/components/traces/span_detail_table.tsx @@ -10,7 +10,7 @@ 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 { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { handleSpansRequest } from '../../requests/traces_request_handler'; import { NoMatchMessage, microToMilliSec, nanoToMilliSec } from '../common/helper_functions'; diff --git a/public/components/trace_analytics/components/traces/trace_view.tsx b/public/components/trace_analytics/components/traces/trace_view.tsx index d885d83aa5..e4f3e246fa 100644 --- a/public/components/trace_analytics/components/traces/trace_view.tsx +++ b/public/components/trace_analytics/components/traces/trace_view.tsx @@ -5,7 +5,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { - EuiSmallButtonIcon, EuiCodeBlock, EuiCopy, EuiFlexGroup, @@ -14,6 +13,7 @@ import { EuiPage, EuiPageBody, EuiPanel, + EuiSmallButtonIcon, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -25,9 +25,11 @@ import { DataSourceViewConfig, } from '../../../../../../../src/plugins/data_source_management/public'; import { DataSourceOption } from '../../../../../../../src/plugins/data_source_management/public/components/data_source_menu/types'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { setNavBreadCrumbs } from '../../../../../common/utils/set_nav_bread_crumbs'; import { dataSourceFilterFn } from '../../../../../common/utils/shared'; -import { TraceAnalyticsCoreDeps, TraceAnalyticsMode } from '../../home'; +import { coreRefs } from '../../../../framework/core_refs'; +import { TraceAnalyticsCoreDeps } from '../../home'; import { handleServiceMapRequest } from '../../requests/services_request_handler'; import { handlePayloadRequest, @@ -38,7 +40,6 @@ import { PanelTitle, filtersToDsl, processTimeStamp } from '../common/helper_fun import { ServiceMap, ServiceObject } from '../common/plots/service_map'; import { ServiceBreakdownPanel } from './service_breakdown_panel'; import { SpanDetailPanel } from './span_detail_panel'; -import { coreRefs } from '../../../../framework/core_refs'; const newNavigation = coreRefs.chrome?.navGroup.getNavGroupEnabled(); @@ -101,7 +102,7 @@ export function TraceView(props: TraceViewProps) { )} - {mode === 'data_prepper' ? ( + {mode === 'data_prepper' || mode === 'custom_data_prepper' ? ( Trace group name @@ -311,7 +312,7 @@ export function TraceView(props: TraceViewProps) { ) : null} - {mode === 'data_prepper' ? ( + {mode === 'data_prepper' || mode === 'custom_data_prepper' ? ( >; + getTraceViewUri?: (traceId: string) => string; + openTraceFlyout?: (traceId: string) => void; toasts: Toast[]; dataSourceMDSId: DataSourceOption[]; } diff --git a/public/components/trace_analytics/components/traces/traces_content.tsx b/public/components/trace_analytics/components/traces/traces_content.tsx index 048a82db2b..c6ae79ce72 100644 --- a/public/components/trace_analytics/components/traces/traces_content.tsx +++ b/public/components/trace_analytics/components/traces/traces_content.tsx @@ -6,25 +6,33 @@ import { EuiAccordion, - EuiPanel, - EuiSpacer, - PropertySort, EuiFlexGroup, EuiFlexItem, EuiPage, EuiPageBody, + EuiPanel, + EuiSpacer, + PropertySort, } from '@elastic/eui'; +import cloneDeep from 'lodash/cloneDeep'; import React, { useEffect, useState } from 'react'; import { coreRefs } from '../../../../framework/core_refs'; -import { handleTracesRequest } from '../../requests/traces_request_handler'; +import { handleServiceMapRequest } from '../../requests/services_request_handler'; +import { + handleCustomIndicesTracesRequest, + handleTracesRequest, +} from '../../requests/traces_request_handler'; import { getValidFilterFields } from '../common/filters/filter_helpers'; +import { Filters, FilterType } from '../common/filters/filters'; import { filtersToDsl, processTimeStamp } from '../common/helper_functions'; +import { ServiceMap, ServiceObject } from '../common/plots/service_map'; import { SearchBar } from '../common/search_bar'; import { DashboardContent } from '../dashboard/dashboard_content'; -import { TracesTable } from './traces_table'; -import { TracesProps } from './traces'; import { DataSourcePicker } from '../dashboard/mode_picker'; -import { Filters } from '../common/filters/filters'; +import { ServicesList } from './services_list'; +import { TracesProps } from './traces'; +import { TracesCustomIndicesTable } from './traces_custom_indices_table'; +import { TracesTable } from './traces_table'; export function TracesContent(props: TracesProps) { const { @@ -37,7 +45,8 @@ export function TracesContent(props: TracesProps) { startTime, endTime, childBreadcrumbs, - traceIdColumnAction, + getTraceViewUri, + openTraceFlyout, setQuery, setFilters, setStartTime, @@ -46,11 +55,19 @@ export function TracesContent(props: TracesProps) { dataPrepperIndicesExist, jaegerIndicesExist, attributesFilterFields, + setCurrentSelectedService, } = props; const [tableItems, setTableItems] = useState([]); + const [columns, setColumns] = useState([]); const [redirect, setRedirect] = useState(true); const [loading, setLoading] = useState(false); const [trigger, setTrigger] = useState<'open' | 'closed'>('closed'); + const [serviceMap, setServiceMap] = useState({}); + const [filteredService, setFilteredService] = useState(''); + const [serviceMapIdSelected, setServiceMapIdSelected] = useState< + 'latency' | 'error_rate' | 'throughput' + >(''); + const [includeMetrics, setIncludeMetrics] = useState(false); const isNavGroupEnabled = coreRefs?.chrome?.navGroup.getNavGroupEnabled(); useEffect(() => { @@ -69,19 +86,51 @@ export function TracesContent(props: TracesProps) { }, []); useEffect(() => { + let newFilteredService = ''; + for (const filter of filters) { + if (filter.field === 'serviceName') { + newFilteredService = filter.value; + break; + } + } + setFilteredService(newFilteredService); if ( !redirect && - ((mode === 'data_prepper' && dataPrepperIndicesExist) || + (mode === 'custom_data_prepper' || + (mode === 'data_prepper' && dataPrepperIndicesExist) || (mode === 'jaeger' && jaegerIndicesExist)) ) refresh(); - }, [filters, appConfigs, redirect, mode, dataPrepperIndicesExist, jaegerIndicesExist]); + }, [ + filters, + appConfigs, + redirect, + mode, + jaegerIndicesExist, + dataPrepperIndicesExist, + includeMetrics, + ]); const onToggle = (isOpen: boolean) => { const newState = isOpen ? 'open' : 'closed'; setTrigger(newState); }; + const addFilter = (filter: FilterType) => { + for (let i = 0; i < filters.length; i++) { + const addedFilter = filters[i]; + if (addedFilter.field === filter.field) { + if (addedFilter.operator === filter.operator && addedFilter.value === filter.value) return; + const newFilters = [...filters]; + newFilters.splice(i, 1, filter); + setFilters(newFilters); + return; + } + } + const newFilters = [...filters, filter]; + setFilters(newFilters); + }; + const refresh = async (sort?: PropertySort) => { setLoading(true); const DSL = filtersToDsl( @@ -101,16 +150,46 @@ export function TracesContent(props: TracesProps) { processTimeStamp(endTime, mode), page ); - await handleTracesRequest( - http, - DSL, - timeFilterDSL, - tableItems, - setTableItems, - mode, - props.dataSourceMDSId[0].id, - sort - ); + + if (mode === 'custom_data_prepper') { + // service map should not be filtered by service name + const serviceMapDSL = cloneDeep(DSL); + serviceMapDSL.query.bool.must = serviceMapDSL.query.bool.must.filter( + (must: any) => must?.term?.serviceName == null + ); + + await handleCustomIndicesTracesRequest( + http, + DSL, + tableItems, + setTableItems, + setColumns, + mode, + props.dataSourceMDSId[0].id, + sort + ); + await handleServiceMapRequest( + http, + serviceMapDSL, + mode, + props.dataSourceMDSId[0].id, + setServiceMap, + filteredService, + includeMetrics + ); + } else { + await handleTracesRequest( + http, + DSL, + timeFilterDSL, + tableItems, + setTableItems, + mode, + props.dataSourceMDSId[0].id, + sort + ); + } + setLoading(false); }; @@ -156,11 +235,72 @@ export function TracesContent(props: TracesProps) { mode={mode} attributesFilterFields={attributesFilterFields} /> + + {mode === 'custom_data_prepper' ? ( + + ) : ( + + )} + + {mode === 'custom_data_prepper' && ( + <> + + + + + + + + { + setIncludeMetrics(true); + }} + /> + + + + )} + - - diff --git a/public/components/trace_analytics/components/traces/traces_custom_indices_table.tsx b/public/components/trace_analytics/components/traces/traces_custom_indices_table.tsx new file mode 100644 index 0000000000..5674b6f903 --- /dev/null +++ b/public/components/trace_analytics/components/traces/traces_custom_indices_table.tsx @@ -0,0 +1,333 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable react-hooks/exhaustive-deps */ + +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiCopy, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiInMemoryTable, + EuiLink, + EuiPanel, + EuiSpacer, + EuiTableFieldDataColumnType, + EuiText, + EuiToolTip, + PropertySort, +} from '@elastic/eui'; +import { CriteriaWithPagination } from '@opensearch-project/oui/src/eui_components/basic_table'; +import round from 'lodash/round'; +import truncate from 'lodash/truncate'; +import moment from 'moment'; +import React, { useMemo, useState } from 'react'; +import { + TRACE_ANALYTICS_DATE_FORMAT, + TRACES_MAX_NUM, +} from '../../../../../common/constants/trace_analytics'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; +import { + MissingConfigurationMessage, + nanoToMilliSec, + NoMatchMessage, + PanelTitle, +} from '../common/helper_functions'; + +interface TracesLandingTableProps { + columnItems: string[]; + items: any[]; + refresh: (sort?: PropertySort) => Promise; + mode: TraceAnalyticsMode; + loading: boolean; + getTraceViewUri?: (traceId: string) => string; + openTraceFlyout?: (traceId: string) => void; + jaegerIndicesExist: boolean; + dataPrepperIndicesExist: boolean; +} + +export function TracesCustomIndicesTable(props: TracesLandingTableProps) { + const { columnItems, items, refresh, mode, loading, getTraceViewUri, openTraceFlyout } = props; + const [showAttributes, setShowAttributes] = useState(false); + + const renderTitleBar = (totalItems?: number) => { + return ( + + + + + + setShowAttributes(!showAttributes)}> + {showAttributes ? 'Hide Attributes' : 'Show Attributes'} + + + + ); + }; + + const dynamicColumns = columnItems + .filter((col) => col.includes('attributes') || col.includes('instrumentation')) + .map((col) => ({ + className: 'attributes-column', + field: col, + name: ( + + +

{col}

+
+
+ ), + align: 'right', + sortable: true, + truncateText: true, + render: (item) => + item ? ( + + + + {item.length < 36 ? item :
{truncate(item, { length: 36 })}
} +
+
+
+ ) : ( + '-' + ), + })); + + const columns = useMemo(() => { + if (mode === 'custom_data_prepper' || mode === 'data_prepper') { + return [ + { + field: 'traceId', + name: 'Trace ID', + align: 'left', + sortable: true, + truncateText: false, + render: (item) => ( + + + openTraceFlyout(item) })} + > + + {item} + + + + + + {(copy) => ( + + Click to copy + + )} + + + + ), + }, + { + field: 'traceGroup', + name: 'Trace group', + align: 'left', + sortable: true, + truncateText: true, + className: 'trace-group-column', + render: (item) => + item ? ( + + {item.length < 36 ? item :
{truncate(item, { length: 36 })}
} +
+ ) : ( + '-' + ), + }, + { + field: 'durationInNanos', + name: 'Duration (ms)', + align: 'right', + sortable: true, + truncateText: true, + render: (item) => + item ? {round(nanoToMilliSec(Math.max(0, item)), 2)} : '-', + }, + { + field: 'status.code', + name: 'Errors', + align: 'right', + sortable: true, + render: (item) => + item == null ? ( + '-' + ) : +item === 2 ? ( + + Yes + + ) : ( + 'No' + ), + }, + { + field: 'endTime', + name: 'Last updated', + align: 'right', + sortable: true, + className: 'trace-group-column', + render: (item) => + item === 0 || item ? moment(item).format(TRACE_ANALYTICS_DATE_FORMAT) : '-', + }, + ...(showAttributes ? dynamicColumns : []), + ] as Array>; + } else { + return [ + { + field: 'trace_id', + name: 'Trace ID', + align: 'left', + sortable: true, + truncateText: true, + render: (item) => ( + + + openTraceFlyout(item) })} + > + + {item} + + + + + + {(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>; + } + }, [showAttributes, items]); + + const titleBar = useMemo(() => renderTitleBar(items?.length), [showAttributes, items]); + + const [sorting, setSorting] = useState<{ sort: PropertySort }>({ + sort: { + field: 'trace_id', + direction: 'asc', + }, + }); + + const onTableChange = async ({ sort }: CriteriaWithPagination) => { + 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, + // user needs to click one time more to change sort back to ascending + if (sort.field === 'trace_id') { + refresh({ ...sort, field }); + setSorting({ sort }); + return; + } + + await refresh({ ...sort, field }); + setSorting({ sort }); + }; + + return ( + <> + + {titleBar} + + + {!( + mode === 'custom_data_prepper' || + (mode === 'data_prepper' && props.dataPrepperIndicesExist) || + (mode === 'jaeger' && props.jaegerIndicesExist) + ) ? ( + + ) : items?.length > 0 || loading ? ( + + ) : ( + + )} + + + ); +} diff --git a/public/components/trace_analytics/components/traces/traces_table.tsx b/public/components/trace_analytics/components/traces/traces_table.tsx index c84deabcf2..ff51823cfa 100644 --- a/public/components/trace_analytics/components/traces/traces_table.tsx +++ b/public/components/trace_analytics/components/traces/traces_table.tsx @@ -5,7 +5,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { - EuiSmallButtonIcon, EuiCopy, EuiFlexGroup, EuiFlexItem, @@ -13,16 +12,18 @@ import { EuiInMemoryTable, EuiLink, EuiPanel, + EuiSmallButtonIcon, EuiSpacer, EuiTableFieldDataColumnType, EuiText, PropertySort, } from '@elastic/eui'; +import { CriteriaWithPagination } from '@opensearch-project/oui/src/eui_components/basic_table'; import round from 'lodash/round'; import truncate from 'lodash/truncate'; import React, { useMemo, useState } from 'react'; import { TRACES_MAX_NUM } from '../../../../../common/constants/trace_analytics'; -import { TraceAnalyticsMode } from '../../home'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { MissingConfigurationMessage, NoMatchMessage, @@ -31,16 +32,17 @@ import { interface TracesTableProps { items: any[]; - refresh: (sort?: PropertySort) => void; + refresh: (sort?: PropertySort) => Promise; mode: TraceAnalyticsMode; loading: boolean; - traceIdColumnAction: any; + getTraceViewUri?: (traceId: string) => string; + openTraceFlyout?: (traceId: string) => void; jaegerIndicesExist: boolean; dataPrepperIndicesExist: boolean; } export function TracesTable(props: TracesTableProps) { - const { items, refresh, mode, loading, traceIdColumnAction } = props; + const { items, refresh, mode, loading, getTraceViewUri, openTraceFlyout } = props; const renderTitleBar = (totalItems?: number) => { return ( @@ -52,23 +54,25 @@ export function TracesTable(props: TracesTableProps) { }; const columns = useMemo(() => { - if (mode === 'data_prepper') { + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { return [ { field: 'trace_id', name: 'Trace ID', align: 'left', sortable: true, - truncateText: true, + truncateText: false, render: (item) => ( - - traceIdColumnAction(item)}> - {item.length < 24 ? ( - item - ) : ( -
{truncate(item, { length: 24 })}
- )} + + openTraceFlyout(item) })} + > + + {item} + @@ -84,7 +88,6 @@ export function TracesTable(props: TracesTableProps) { )} -
), }, @@ -112,12 +115,7 @@ export function TracesTable(props: TracesTableProps) { }, { field: 'percentile_in_trace_group', - name: ( - <> -
Percentile in trace group
- {/*
trace group
*/} - - ), + name:
Percentile in trace group
, align: 'right', sortable: true, render: (item) => @@ -157,13 +155,15 @@ export function TracesTable(props: TracesTableProps) { truncateText: true, render: (item) => ( - - traceIdColumnAction(item)}> - {item.length < 24 ? ( - item - ) : ( -
{truncate(item, { length: 24 })}
- )} + + openTraceFlyout(item) })} + > + + {item} + @@ -226,7 +226,7 @@ export function TracesTable(props: TracesTableProps) { }, }); - const onTableChange = async ({ _currPage, sort }: { currPage: any; sort: any }) => { + const onTableChange = async ({ sort }: CriteriaWithPagination) => { if (typeof sort?.field !== 'string') return; // maps table column key to DSL aggregation name @@ -244,7 +244,8 @@ export function TracesTable(props: TracesTableProps) { return; } - // using await when sorting the default sorted field leads to a bug in UI + // using await when sorting the default sorted field leads to a bug in UI, + // user needs to click one time more to change sort back to ascending if (sort.field === 'trace_id') { refresh({ ...sort, field }); setSorting({ sort }); @@ -262,13 +263,15 @@ export function TracesTable(props: TracesTableProps) { {!( + mode === 'custom_data_prepper' || (mode === 'data_prepper' && props.dataPrepperIndicesExist) || (mode === 'jaeger' && props.jaegerIndicesExist) ) ? ( - ) : items?.length > 0 ? ( + ) : items?.length > 0 || loading ? ( { const modes = [ { id: 'jaeger', title: 'Jaeger', 'data-test-subj': 'jaeger-mode' }, { id: 'data_prepper', title: 'Data Prepper', 'data-test-subj': 'data-prepper-mode' }, + { + id: 'custom_data_prepper', + title: 'Custom source', + 'data-test-subj': 'custom-data-prepper-mode', + }, ]; const fetchAttributesFields = () => { coreRefs.dslService - ?.fetchFields(DATA_PREPPER_INDEX_NAME) + ?.fetchFields(getSpanIndices(mode)) .then((res) => { const attributes = getAttributes(res); setAttributesFilterFields(attributes); @@ -192,7 +195,7 @@ export const Home = (props: HomeProps) => { }, [jaegerIndicesExist, dataPrepperIndicesExist]); useEffect(() => { - if (mode === 'data_prepper') fetchAttributesFields(); + if (mode === 'data_prepper' || mode === 'custom_data_prepper') fetchAttributesFields(); }, [mode]); const serviceBreadcrumbs = [ @@ -227,8 +230,7 @@ export const Home = (props: HomeProps) => { const traceColumnAction = () => location.assign('#/traces'); - const traceIdColumnAction = (item: any) => - location.assign(`#/traces/${encodeURIComponent(item)}`); + const getTraceViewUri = (traceId: string) => `#/traces/${encodeURIComponent(traceId)}`; const [spanFlyoutComponent, setSpanFlyoutComponent] = useState(<>); @@ -321,7 +323,8 @@ export const Home = (props: HomeProps) => { { div:first-of-type { + overflow-x: auto; + overflow-y: hidden; + width: 100%; + } +} + +.attributes-column-header, +.attributes-column { + max-width: 200px; +} + +.trace-group-column { + min-width: 200px; +} + +.attributes-column-header { + display: table-cell; +} diff --git a/public/components/trace_analytics/requests/queries/dashboard_queries.ts b/public/components/trace_analytics/requests/queries/dashboard_queries.ts index e840ca1bd8..7c0f544ed4 100644 --- a/public/components/trace_analytics/requests/queries/dashboard_queries.ts +++ b/public/components/trace_analytics/requests/queries/dashboard_queries.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { TraceAnalyticsMode } from '../../home'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; export const getDashboardQuery = () => { return { @@ -515,7 +515,7 @@ export const getJaegerErrorTrendQuery = () => { }; export const getDashboardTraceGroupPercentiles = (mode: TraceAnalyticsMode, buckets?: any[]) => { - if (mode === 'data_prepper') { + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { return { size: 0, query: { @@ -620,7 +620,7 @@ export const getDashboardTraceGroupPercentiles = (mode: TraceAnalyticsMode, buck }; export const getErrorRatePltQuery = (mode: TraceAnalyticsMode, fixedInterval) => { - if (mode === 'data_prepper') { + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { return { size: 0, query: { @@ -805,7 +805,7 @@ export const getDashboardThroughputPltQuery = (mode: TraceAnalyticsMode, fixedIn }; }; -export const getDashboardErrorTopGroupsQuery = (mode: TraceAnalyticsMode) => { +export const getDashboardErrorTopGroupsQuery = (_mode: TraceAnalyticsMode) => { return { size: 0, query: { diff --git a/public/components/trace_analytics/requests/queries/services_queries.ts b/public/components/trace_analytics/requests/queries/services_queries.ts index ab3cd3ce01..ab118dae74 100644 --- a/public/components/trace_analytics/requests/queries/services_queries.ts +++ b/public/components/trace_analytics/requests/queries/services_queries.ts @@ -4,14 +4,15 @@ */ import { - DATA_PREPPER_SERVICE_INDEX_NAME, - JAEGER_SERVICE_INDEX_NAME, SERVICE_MAP_MAX_EDGES, SERVICE_MAP_MAX_NODES, } from '../../../../../common/constants/trace_analytics'; -import { getServiceMapTargetResources } from '../../components/common/helper_functions'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; +import { + getServiceIndices, + getServiceMapTargetResources, +} from '../../components/common/helper_functions'; import { ServiceObject } from '../../components/common/plots/service_map'; -import { TraceAnalyticsMode } from '../../home'; export const getServicesQuery = ( mode: TraceAnalyticsMode, @@ -46,14 +47,14 @@ export const getServicesQuery = ( }; if (mode === 'jaeger') { if (serviceName) { - query.query.bool.must.push({ + query.query.bool.filter.push({ term: { 'process.serviceName': serviceName, }, }); } DSL?.custom?.serviceNames?.map((service: string) => { - query.query.bool.must.push({ + query.query.bool.filter.push({ term: { 'process.serviceName': service, }, @@ -68,14 +69,14 @@ export const getServicesQuery = ( }); } else { if (serviceName) { - query.query.bool.must.push({ + query.query.bool.filter.push({ term: { serviceName, }, }); } DSL?.custom?.serviceNames?.map((service: string) => { - query.query.bool.must.push({ + query.query.bool.filter.push({ term: { serviceName: service, }, @@ -139,7 +140,7 @@ export const getRelatedServicesQuery = (serviceName: string) => { export const getServiceNodesQuery = (mode: TraceAnalyticsMode) => { return { - index: mode === 'jaeger' ? JAEGER_SERVICE_INDEX_NAME : DATA_PREPPER_SERVICE_INDEX_NAME, + index: getServiceIndices(mode), size: 0, query: { bool: { @@ -181,7 +182,7 @@ export const getServiceEdgesQuery = ( mode: TraceAnalyticsMode ) => { return { - index: mode === 'jaeger' ? JAEGER_SERVICE_INDEX_NAME : DATA_PREPPER_SERVICE_INDEX_NAME, + index: getServiceIndices(mode), size: 0, query: { bool: { @@ -457,8 +458,8 @@ export const getServiceMetricsQuery = ( }, }; if (DSL.custom?.timeFilter.length > 0) { - jaegerQuery.query.bool.must.push(...DSL.custom.timeFilter); - dataPrepperQuery.query.bool.must.push(...DSL.custom.timeFilter); + jaegerQuery.query.bool.filter.push(...DSL.custom.timeFilter); + dataPrepperQuery.query.bool.filter.push(...DSL.custom.timeFilter); } return mode === 'jaeger' ? jaegerQuery : dataPrepperQuery; }; diff --git a/public/components/trace_analytics/requests/queries/traces_queries.ts b/public/components/trace_analytics/requests/queries/traces_queries.ts index fbff7cf69d..9969954418 100644 --- a/public/components/trace_analytics/requests/queries/traces_queries.ts +++ b/public/components/trace_analytics/requests/queries/traces_queries.ts @@ -5,8 +5,8 @@ import { PropertySort } from '@elastic/eui'; import { TRACES_MAX_NUM } from '../../../../../common/constants/trace_analytics'; +import { TraceAnalyticsMode } from '../../../../../common/types/trace_analytics'; import { SpanSearchParams } from '../../components/traces/span_detail_table'; -import { TraceAnalyticsMode } from '../../home'; export const getTraceGroupPercentilesQuery = () => { const query: any = { @@ -47,7 +47,11 @@ export const getTraceGroupPercentilesQuery = () => { return query; }; -export const getTracesQuery = (mode: TraceAnalyticsMode, traceId: string = '', sort?: PropertySort) => { +export const getTracesQuery = ( + mode: TraceAnalyticsMode, + traceId: string = '', + sort?: PropertySort +) => { const field = sort?.field || '_key'; const direction = sort?.direction || 'asc'; const jaegerQuery: any = { @@ -68,6 +72,7 @@ export const getTracesQuery = (mode: TraceAnalyticsMode, traceId: string = '', s order: { [field]: direction, }, + execution_hint: 'map', }, aggs: { latency: { @@ -114,6 +119,7 @@ export const getTracesQuery = (mode: TraceAnalyticsMode, traceId: string = '', s }, }, }, + track_total_hits: false, }; const dataPrepperQuery: any = { size: 0, @@ -133,6 +139,7 @@ export const getTracesQuery = (mode: TraceAnalyticsMode, traceId: string = '', s order: { [field]: direction, }, + execution_hint: 'map', }, aggs: { latency: { @@ -169,14 +176,15 @@ export const getTracesQuery = (mode: TraceAnalyticsMode, traceId: string = '', s }, }, }, + track_total_hits: false, }; if (traceId) { - jaegerQuery.query.bool.must.push({ + jaegerQuery.query.bool.filter.push({ term: { - "traceID": traceId, + traceID: traceId, }, }); - dataPrepperQuery.query.bool.must.push({ + dataPrepperQuery.query.bool.filter.push({ term: { traceId, }, @@ -193,7 +201,7 @@ export const getServiceBreakdownQuery = (traceId: string, mode: TraceAnalyticsMo must: [ { term: { - "traceID": traceId, + traceID: traceId, }, }, ], @@ -276,7 +284,7 @@ export const getServiceBreakdownQuery = (traceId: string, mode: TraceAnalyticsMo }, }, }; - return mode === 'jaeger'? jaegerQuery : dataPrepperQuery; + return mode === 'jaeger' ? jaegerQuery : dataPrepperQuery; }; export const getSpanDetailQuery = (mode: TraceAnalyticsMode, traceId: string, size = 3000) => { @@ -288,7 +296,7 @@ export const getSpanDetailQuery = (mode: TraceAnalyticsMode, traceId: string, si must: [ { term: { - "traceID": traceId, + traceID: traceId, }, }, { @@ -318,11 +326,11 @@ export const getSpanDetailQuery = (mode: TraceAnalyticsMode, traceId: string, si 'spanID', 'tag', 'duration', - 'references' - ] + 'references', + ], }, }; - } + } return { size, query: { @@ -374,7 +382,7 @@ export const getPayloadQuery = (mode: TraceAnalyticsMode, traceId: string, size must: [ { term: { - "traceID": traceId, + traceID: traceId, }, }, ], @@ -413,7 +421,7 @@ export const getSpanFlyoutQuery = (mode: TraceAnalyticsMode, spanId?: string, si must: [ { term: { - "spanID": spanId, + spanID: spanId, }, }, ], @@ -460,8 +468,12 @@ export const getSpansQuery = (spanSearchParams: SpanSearchParams) => { return query; }; -export const getValidTraceIdsQuery = (DSL) => { - const query: any = { +export const getCustomIndicesTracesQuery = ( + mode: TraceAnalyticsMode, + traceId: string = '', + sort?: PropertySort +) => { + const jaegerQuery: any = { size: 0, query: { bool: { @@ -475,29 +487,101 @@ export const getValidTraceIdsQuery = (DSL) => { traces: { terms: { field: 'traceID', - size: 10000, + size: TRACES_MAX_NUM, + order: { + [sort?.field || '_key']: sort?.direction || 'asc', + }, + execution_hint: 'map', + }, + aggs: { + latency: { + max: { + script: { + source: ` + if (doc.containsKey('duration') && !doc['duration'].empty) { + return Math.round(doc['duration'].value) / 1000.0 + } + + return 0 + `, + lang: 'painless', + }, + }, + }, + trace_group: { + terms: { + field: 'traceGroup', + size: 1, + }, + }, + error_count: { + filter: { + term: { + 'tag.error': true, + }, + }, + }, + last_updated: { + max: { + script: { + source: ` + if (doc.containsKey('startTime') && !doc['startTime'].empty && doc.containsKey('duration') && !doc['duration'].empty) { + return (Math.round(doc['duration'].value) + Math.round(doc['startTime'].value)) / 1000.0 + } + + return 0 + `, + lang: 'painless', + }, + }, + }, }, }, }, + track_total_hits: false, }; - if (DSL.custom?.timeFilter.length > 0) query.query.bool.must.push(...DSL.custom.timeFilter); - if (DSL.custom?.traceGroupFields.length > 0) { - query.query.bool.filter.push({ - terms: { - traceGroup: DSL.custom.traceGroup, + + const dataPrepperQuery: any = { + size: TRACES_MAX_NUM, + _source: { + includes: [ + 'traceId', + 'traceGroup', + 'durationInNanos', + 'status.code', + 'endTime', + '*attributes*', + '*instrumentation*', + ], + }, + query: { + bool: { + must: [], + filter: [ + { + term: { + parentSpanId: '', // Data prepper root span doesn't have any parent. + }, + }, + ], + should: [], + must_not: [], + }, + }, + ...(sort && { sort: [{ [sort.field]: { order: sort.direction } }] }), + track_total_hits: false, + }; + if (traceId) { + jaegerQuery.query.bool.filter.push({ + term: { + traceID: traceId, }, }); - } - if (DSL.custom?.percentiles?.query.bool.should.length > 0) { - query.query.bool.should.push(...DSL.custom.percentiles.query.bool.should); - query.query.bool.minimum_should_match = DSL.custom.percentiles.query.bool.minimum_should_match; - } - if (DSL.custom?.serviceNames.length > 0) { - query.query.bool.filter.push({ - terms: { - serviceName: DSL.custom.serviceNames, + dataPrepperQuery.query.bool.filter.push({ + term: { + traceId, }, }); } - return query; -}; \ No newline at end of file + return mode === 'jaeger' ? jaegerQuery : dataPrepperQuery; +}; diff --git a/public/components/trace_analytics/requests/request_handler.ts b/public/components/trace_analytics/requests/request_handler.ts index 5babe0a33a..421c489b65 100644 --- a/public/components/trace_analytics/requests/request_handler.ts +++ b/public/components/trace_analytics/requests/request_handler.ts @@ -5,13 +5,12 @@ import { CoreStart } from '../../../../../../src/core/public'; import { - DATA_PREPPER_INDEX_NAME, - JAEGER_INDEX_NAME, TRACE_ANALYTICS_DATA_PREPPER_INDICES_ROUTE, TRACE_ANALYTICS_DSL_ROUTE, TRACE_ANALYTICS_JAEGER_INDICES_ROUTE, } from '../../../../common/constants/trace_analytics'; -import { TraceAnalyticsMode } from '../home'; +import { TraceAnalyticsMode } from '../../../../common/types/trace_analytics'; +import { getSpanIndices } from '../components/common/helper_functions'; export async function handleDslRequest( http: CoreStart['http'], @@ -31,10 +30,10 @@ export async function handleDslRequest( } let body = bodyQuery; if (!bodyQuery.index) { - body = { ...bodyQuery, index: mode === 'jaeger' ? JAEGER_INDEX_NAME : DATA_PREPPER_INDEX_NAME }; + body = { ...bodyQuery, index: getSpanIndices(mode) }; } const query = { - dataSourceMDSId: dataSourceMDSId, + dataSourceMDSId, }; if (setShowTimeoutToast) { const id = setTimeout(() => setShowTimeoutToast(), 25000); // 25 seconds @@ -67,7 +66,7 @@ export async function handleJaegerIndicesExistRequest( dataSourceMDSId?: string ) { const query = { - dataSourceMDSId: dataSourceMDSId, + dataSourceMDSId, }; http .post(TRACE_ANALYTICS_JAEGER_INDICES_ROUTE, { @@ -83,7 +82,7 @@ export async function handleDataPrepperIndicesExistRequest( dataSourceMDSId?: string ) { const query = { - dataSourceMDSId: dataSourceMDSId, + dataSourceMDSId, }; http .post(TRACE_ANALYTICS_DATA_PREPPER_INDICES_ROUTE, { diff --git a/public/components/trace_analytics/requests/services_request_handler.ts b/public/components/trace_analytics/requests/services_request_handler.ts index b99a7f978d..85d3f612eb 100644 --- a/public/components/trace_analytics/requests/services_request_handler.ts +++ b/public/components/trace_analytics/requests/services_request_handler.ts @@ -10,10 +10,9 @@ import moment from 'moment'; import DSLService from 'public/services/requests/dsl'; import { HttpSetup, HttpStart } from '../../../../../../src/core/public'; import { TRACE_ANALYTICS_PLOTS_DATE_FORMAT } from '../../../../common/constants/trace_analytics'; -import { ServiceTrends } from '../../../../common/types/trace_analytics'; +import { ServiceTrends, TraceAnalyticsMode } from '../../../../common/types/trace_analytics'; import { fixedIntervalToMilli } from '../components/common/helper_functions'; import { ServiceObject } from '../components/common/plots/service_map'; -import { TraceAnalyticsMode } from '../home'; import { getRelatedServicesQuery, getServiceEdgesQuery, @@ -81,7 +80,8 @@ export const handleServiceMapRequest = async ( mode: TraceAnalyticsMode, dataSourceMDSId?: string, setItems?: any, - currService?: string + currService?: string, + includeMetrics = true ) => { let minutesInDateRange: number; const startTime = DSL.custom?.timeFilter?.[0]?.range?.startTime; @@ -148,21 +148,23 @@ export const handleServiceMapRequest = async ( ) .catch((error) => console.error(error)); - // service map handles DSL differently - const latencies = await handleDslRequest( - http, - DSL, - getServiceMetricsQuery(DSL, Object.keys(map), map, mode), - mode, - dataSourceMDSId - ); - latencies.aggregations.service_name.buckets.map((bucket: any) => { - map[bucket.key].latency = bucket.average_latency.value; - map[bucket.key].error_rate = round(bucket.error_rate.value, 2) || 0; - map[bucket.key].throughput = bucket.doc_count; - if (minutesInDateRange != null) - map[bucket.key].throughputPerMinute = _.round(bucket.doc_count / minutesInDateRange, 2); - }); + if (includeMetrics) { + // service map handles DSL differently + const latencies = await handleDslRequest( + http, + DSL, + getServiceMetricsQuery(DSL, Object.keys(map), map, mode), + mode, + dataSourceMDSId + ); + latencies.aggregations.service_name.buckets.map((bucket: any) => { + map[bucket.key].latency = bucket.average_latency.value; + map[bucket.key].error_rate = round(bucket.error_rate.value, 2) || 0; + map[bucket.key].throughput = bucket.doc_count; + if (minutesInDateRange != null) + map[bucket.key].throughputPerMinute = round(bucket.doc_count / minutesInDateRange, 2); + }); + } if (currService) { await handleDslRequest(http, DSL, getRelatedServicesQuery(currService), mode, dataSourceMDSId) diff --git a/public/components/trace_analytics/requests/traces_request_handler.ts b/public/components/trace_analytics/requests/traces_request_handler.ts index cb2358d76c..44af86d587 100644 --- a/public/components/trace_analytics/requests/traces_request_handler.ts +++ b/public/components/trace_analytics/requests/traces_request_handler.ts @@ -3,16 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -import _ from 'lodash'; +import { PropertySort } from '@elastic/eui'; +import { isArray, isObject } from 'lodash'; +import get from 'lodash/get'; +import omitBy from 'lodash/omitBy'; +import round from 'lodash/round'; import moment from 'moment'; import { v1 as uuid } from 'uuid'; import { HttpSetup } from '../../../../../../src/core/public'; import { BarOrientation } from '../../../../common/constants/shared'; import { TRACE_ANALYTICS_DATE_FORMAT } from '../../../../common/constants/trace_analytics'; +import { TraceAnalyticsMode } from '../../../../common/types/trace_analytics'; import { microToMilliSec, nanoToMilliSec } from '../components/common/helper_functions'; import { SpanSearchParams } from '../components/traces/span_detail_table'; -import { TraceAnalyticsMode } from '../home'; import { + getCustomIndicesTracesQuery, getPayloadQuery, getServiceBreakdownQuery, getSpanDetailQuery, @@ -20,13 +25,59 @@ import { getSpansQuery, getTraceGroupPercentilesQuery, getTracesQuery, - getValidTraceIdsQuery, } from './queries/traces_queries'; import { handleDslRequest } from './request_handler'; -export const handleValidTraceIds = (http: HttpSetup, DSL: any, mode: TraceAnalyticsMode) => { - return handleDslRequest(http, {}, getValidTraceIdsQuery(DSL), mode) - .then((response) => response.aggregations.traces.buckets.map((bucket: any) => bucket.key)) +export const handleCustomIndicesTracesRequest = async ( + http: HttpSetup, + DSL: any, + items: any, + setItems: (items: any) => void, + setColumns: (items: any) => void, + mode: TraceAnalyticsMode, + dataSourceMDSId?: string, + sort?: PropertySort +) => { + const responsePromise = handleDslRequest( + http, + DSL, + getCustomIndicesTracesQuery(mode, undefined, sort), + mode, + dataSourceMDSId + ); + + return Promise.allSettled([responsePromise]) + .then(([responseResult]) => { + if (responseResult.status === 'rejected') return Promise.reject(responseResult.reason); + + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { + const keys = new Set(); + const response = responseResult.value.hits.hits.map((val) => { + const source = omitBy(val._source, isArray || isObject); + Object.keys(source).forEach((key) => keys.add(key)); + return { ...source }; + }); + + return [keys, response]; + } else { + return [ + [undefined], + responseResult.value.aggregations.traces.buckets.map((bucket: any) => { + return { + trace_id: bucket.key, + latency: bucket.latency.value, + last_updated: moment(bucket.last_updated.value).format(TRACE_ANALYTICS_DATE_FORMAT), + error_count: bucket.error_count.doc_count, + actions: '#', + }; + }), + ]; + } + }) + .then((newItems) => { + setColumns([...newItems[0]]); + setItems(newItems[1]); + }) .catch((error) => console.error(error)); }; @@ -38,7 +89,7 @@ export const handleTracesRequest = async ( setItems: (items: any) => void, mode: TraceAnalyticsMode, dataSourceMDSId?: string, - sort?: any + sort?: PropertySort ) => { const binarySearch = (arr: number[], target: number) => { if (!arr) return Number.NaN; @@ -53,50 +104,63 @@ export const handleTracesRequest = async ( return Math.max(0, Math.min(100, low)); }; - // percentile should only be affected by timefilter - const percentileRanges = await handleDslRequest( + const responsePromise = handleDslRequest( http, - timeFilterDSL, - getTraceGroupPercentilesQuery(), + DSL, + getTracesQuery(mode, undefined, sort), mode, dataSourceMDSId - ).then((response) => { - const map: any = {}; - response.aggregations.trace_group_name.buckets.forEach((traceGroup: any) => { - map[traceGroup.key] = Object.values(traceGroup.percentiles.values).map((value: any) => - nanoToMilliSec(value) - ); - }); - return map; - }); + ); - return handleDslRequest(http, DSL, getTracesQuery(mode, undefined, sort), mode, dataSourceMDSId) - .then((response) => { - return Promise.all( - response.aggregations.traces.buckets.map((bucket: any) => { - if (mode === 'data_prepper') { - return { - trace_id: bucket.key, - trace_group: bucket.trace_group.buckets[0]?.key, - latency: bucket.latency.value, - last_updated: moment(bucket.last_updated.value).format(TRACE_ANALYTICS_DATE_FORMAT), - error_count: bucket.error_count.doc_count, - percentile_in_trace_group: binarySearch( - percentileRanges[bucket.trace_group.buckets[0]?.key], - bucket.latency.value - ), - actions: '#', - }; - } + // percentile should only be affected by timefilter + const percentileRangesPromise = + mode === 'data_prepper' || mode === 'custom_data_prepper' + ? handleDslRequest( + http, + timeFilterDSL, + getTraceGroupPercentilesQuery(), + mode, + dataSourceMDSId + ).then((response) => { + const map: Record = {}; + response.aggregations.trace_group_name.buckets.forEach((traceGroup: any) => { + map[traceGroup.key] = Object.values(traceGroup.percentiles.values).map((value: any) => + nanoToMilliSec(value) + ); + }); + return map; + }) + : Promise.reject('Only data_prepper mode supports percentile'); + + return Promise.allSettled([responsePromise, percentileRangesPromise]) + .then(([responseResult, percentileRangesResult]) => { + if (responseResult.status === 'rejected') return Promise.reject(responseResult.reason); + const percentileRanges = + percentileRangesResult.status === 'fulfilled' ? percentileRangesResult.value : {}; + const response = responseResult.value; + return response.aggregations.traces.buckets.map((bucket: any) => { + if (mode === 'data_prepper' || mode === 'custom_data_prepper') { return { trace_id: bucket.key, + trace_group: bucket.trace_group.buckets[0]?.key, latency: bucket.latency.value, last_updated: moment(bucket.last_updated.value).format(TRACE_ANALYTICS_DATE_FORMAT), error_count: bucket.error_count.doc_count, + percentile_in_trace_group: binarySearch( + percentileRanges[bucket.trace_group.buckets[0]?.key], + bucket.latency.value + ), actions: '#', }; - }) - ); + } + return { + trace_id: bucket.key, + latency: bucket.latency.value, + last_updated: moment(bucket.last_updated.value).format(TRACE_ANALYTICS_DATE_FORMAT), + error_count: bucket.error_count.doc_count, + actions: '#', + }; + }); }) .then((newItems) => { setItems(newItems); @@ -247,14 +311,13 @@ const hitsToSpanDetailData = async (hits: any, colorMap: any, mode: TraceAnalyti : nanoToMilliSec(hit.sort[0]) - minStartTime; const duration = mode === 'jaeger' - ? _.round(microToMilliSec(hit._source.duration), 2) - : _.round(nanoToMilliSec(hit._source.durationInNanos), 2); + ? round(microToMilliSec(hit._source.duration), 2) + : round(nanoToMilliSec(hit._source.durationInNanos), 2); const serviceName = mode === 'jaeger' - ? _.get(hit, ['_source', 'process']).serviceName - : _.get(hit, ['_source', 'serviceName']); - const name = - mode === 'jaeger' ? _.get(hit, '_source.operationName') : _.get(hit, '_source.name'); + ? get(hit, ['_source', 'process']).serviceName + : get(hit, ['_source', 'serviceName']); + const name = mode === 'jaeger' ? get(hit, '_source.operationName') : get(hit, '_source.name'); const error = mode === 'jaeger' ? hit._source.tag?.['error'] === true diff --git a/server/plugin.ts b/server/plugin.ts index b2cf5f9357..627f97b40a 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -20,6 +20,7 @@ import { migrateV1IntegrationToV2Integration } from './adaptors/integrations/mig import { OpenSearchObservabilityPlugin } from './adaptors/opensearch_observability_plugin'; import { PPLPlugin } from './adaptors/ppl_plugin'; import { PPLParsers } from './parsers/ppl_parser'; +import { registerObservabilityUISettings } from './plugin_helper/register_settings'; import { setupRoutes } from './routes/index'; import { notebookSavedObject, @@ -228,6 +229,7 @@ export class ObservabilityPlugin })); assistantDashboards?.registerMessageParser(PPLParsers); + registerObservabilityUISettings(core.uiSettings); core.uiSettings.register({ 'observability:defaultDashboard': { diff --git a/server/plugin_helper/register_settings.ts b/server/plugin_helper/register_settings.ts new file mode 100644 index 0000000000..771404b659 --- /dev/null +++ b/server/plugin_helper/register_settings.ts @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { schema } from '@osd/config-schema'; +import { UiSettingsServiceSetup } from '../../../../src/core/server/ui_settings'; +import { + TRACE_CUSTOM_SERVICE_INDEX_SETTING, + TRACE_CUSTOM_SPAN_INDEX_SETTING, +} from '../../common/constants/trace_analytics'; + +export const registerObservabilityUISettings = (uiSettings: UiSettingsServiceSetup) => { + uiSettings.register({ + [TRACE_CUSTOM_SPAN_INDEX_SETTING]: { + name: 'Trace analytics custom span indices', + value: '', + category: ['Observability'], + description: + 'Experimental feature: Configure custom span indices that adhere to data prepper schema, to be used by the trace analytics plugin', + schema: schema.string(), + }, + }); + + uiSettings.register({ + [TRACE_CUSTOM_SERVICE_INDEX_SETTING]: { + name: 'Trace analytics custom service indices', + value: '', + category: ['Observability'], + description: + 'Experimental feature: Configure custom service indices that adhere to data prepper schema, to be used by the trace analytics plugin', + schema: schema.string(), + }, + }); +}; diff --git a/server/routes/trace_analytics_dsl_router.ts b/server/routes/trace_analytics_dsl_router.ts index 4fe62ab5dc..4d99ea401a 100644 --- a/server/routes/trace_analytics_dsl_router.ts +++ b/server/routes/trace_analytics_dsl_router.ts @@ -126,6 +126,7 @@ export function registerTraceAnalyticsDslRouter(router: IRouter, dataSourceEnabl }) ), script_fields: schema.maybe(schema.any()), + track_total_hits: schema.maybe(schema.boolean()), }), query: schema.object({ dataSourceMDSId: schema.maybe(schema.string({ defaultValue: '' })),