diff --git a/dashboards-observability/public/components/application_analytics/components/application.tsx b/dashboards-observability/public/components/application_analytics/components/application.tsx index ecde5e1a0..3712afd97 100644 --- a/dashboards-observability/public/components/application_analytics/components/application.tsx +++ b/dashboards-observability/public/components/application_analytics/components/application.tsx @@ -7,6 +7,7 @@ import { EuiHorizontalRule, + EuiLink, EuiPage, EuiPageBody, EuiPageHeader, @@ -24,7 +25,7 @@ import PPLService from 'public/services/requests/ppl'; import SavedObjects from 'public/services/saved_objects/event_analytics/saved_objects'; import TimestampUtils from 'public/services/timestamp/timestamp'; import React, { ReactChild, useEffect, useState } from 'react'; -import { uniqueId } from 'lodash'; +import { truncate, uniqueId } from 'lodash'; import { useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { last } from 'lodash'; @@ -35,7 +36,6 @@ import { } from '../../../../public/components/trace_analytics/components/common/helper_functions'; import { SpanDetailTable } from '../../../../public/components/trace_analytics/components/traces/span_detail_table'; import { Explorer } from '../../explorer/explorer'; -import { Services } from '../../trace_analytics/components/services'; import { Traces } from '../../trace_analytics/components/traces'; import { Configuration } from './configuration'; import { @@ -63,6 +63,7 @@ import { ServiceDetailFlyout } from './flyout_components/service_detail_flyout'; import { SpanDetailFlyout } from '../../../../public/components/trace_analytics/components/traces/span_detail_flyout'; import { TraceDetailFlyout } from './flyout_components/trace_detail_flyout'; import { fetchAppById, initializeTabData } from '../helpers/utils'; +import { ServicesContent } from '../../trace_analytics/components/services/services_content'; const TAB_OVERVIEW_ID = uniqueId(TAB_OVERVIEW_ID_TXT_PFX); const TAB_SERVICE_ID = uniqueId(TAB_SERVICE_ID_TXT_PFX); @@ -293,19 +294,23 @@ export function Application(props: AppDetailProps) { ); }; + const nameColumnAction = (item: any) => openServiceFlyout(item); + const traceColumnAction = () => switchToTrace(); + const getService = () => { return ( - + <> + + + ); }; diff --git a/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx b/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx index 44f83a666..918cf94d4 100644 --- a/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx +++ b/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx @@ -49,7 +49,7 @@ export const ServiceConfig = (props: ServiceConfigProps) => { const [modalLayout, setModalLayout] = useState(); useEffect(() => { - handleServiceMapRequest(http, dslService, serviceMap, setServiceMap); + handleServiceMapRequest(http, dslService, setServiceMap); }, []); useEffect(() => { diff --git a/dashboards-observability/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx b/dashboards-observability/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx index 552fdcf7d..4e80c64e4 100644 --- a/dashboards-observability/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx +++ b/dashboards-observability/public/components/application_analytics/components/flyout_components/service_detail_flyout.tsx @@ -117,8 +117,8 @@ export function ServiceDetailFlyout(props: ServiceFlyoutProps) { useEffect(() => { const serviceDSL = filtersToDsl(filters, query, startTime, endTime, 'app', appConfigs); - handleServiceViewRequest(serviceName, http, serviceDSL, fields, setFields); - handleServiceMapRequest(http, serviceDSL, serviceMap, setServiceMap, serviceName); + handleServiceViewRequest(serviceName, http, serviceDSL, setFields); + handleServiceMapRequest(http, serviceDSL, setServiceMap, serviceName); const spanDSL = filtersToDsl(filters, query, startTime, endTime, 'app', appConfigs); spanDSL.query.bool.must.push({ term: { diff --git a/dashboards-observability/public/components/trace_analytics/components/common/filters/filters.tsx b/dashboards-observability/public/components/trace_analytics/components/common/filters/filters.tsx index cd0a55bc8..c3bb752e6 100644 --- a/dashboards-observability/public/components/trace_analytics/components/common/filters/filters.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/common/filters/filters.tsx @@ -32,7 +32,7 @@ export interface FilterType { export interface FiltersProps { filters: FilterType[]; - appConfigs: FilterType[]; + appConfigs?: FilterType[]; setFilters: (filters: FilterType[]) => void; } diff --git a/dashboards-observability/public/components/trace_analytics/components/dashboard/dashboard_content.tsx b/dashboards-observability/public/components/trace_analytics/components/dashboard/dashboard_content.tsx index 50f2aa042..43606eeb6 100644 --- a/dashboards-observability/public/components/trace_analytics/components/dashboard/dashboard_content.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/dashboard/dashboard_content.tsx @@ -127,13 +127,7 @@ export function DashboardContent(props: DashboardProps) { serviceMapDSL.query.bool.must = serviceMapDSL.query.bool.must.filter( (must: any) => must?.term?.serviceName == null ); - handleServiceMapRequest( - http, - serviceMapDSL, - serviceMap, - setServiceMap, - currService || filteredService - ); + handleServiceMapRequest(http, serviceMapDSL, setServiceMap, currService || filteredService); }; const addFilter = (filter: FilterType) => { diff --git a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap index f18ff21d9..0bb2cad16 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services.test.tsx.snap @@ -3,6 +3,18 @@ exports[`Services component renders empty services page 1`] = ` - - -
- -
- + +
- -
- - - - -
- + + + +
- - - - -
-
+ > + + + + +
+
+
+
-
- -
-
-
- -
+ +
+
+ - - -
- -
- - } +
+ +
-
+ } > - - - - + - + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="QuickSelectPopover" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" > -
- - - + + + +
-
- - - -
- } - iconType={false} - isCustom={true} - startDateControl={
} + + + +
-
} + iconType={false} + isCustom={true} + startDateControl={
} > - -
- - + + Show dates + + + +
+ + +
-
- -
-
-
- - -
-
- -
+
+
+
+
+
+
+ + - - - - - - - - -
- -
- - - + + + +
+ + + + +
+ + -
- -
- - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={false} - panelPaddingSize="none" - withTitle={true} - > - + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={false} + panelPaddingSize="none" + withTitle={true} > -
- - - + > + + + + + +
-
- - - -
- - -
+ + +
+
+ - - - + Add filter - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={false} - panelPaddingSize="m" - withTitle={true} - > - + + + + Add filter + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={false} + panelPaddingSize="m" + withTitle={true} > -
- - - + + + +
-
- - - - - - - - -
- -
- - - + + + +
+ + + + + +
- + + + +
-
- -
- - -
- - Services - - - (0) - -
-
-
-
-
-
- - -
- - -
-
- - - Learn more - - } - body={ - - The indices required for trace analytics (otel-v1-apm-span-* and otel-v1-apm-service-map*) do not exist or you do not have permission to access them. - - } - title={ -

- Trace Analytics not set up -

- } + + Services + + + (0) + +
+ + +
+ +
+ +
+ + +
+
+ + + Learn more + + } + body={ + + The indices required for trace analytics (otel-v1-apm-span-* and otel-v1-apm-service-map*) do not exist or you do not have permission to access them. + + } + title={ +

+ Trace Analytics not set up +

+ } > - - - -

- Trace Analytics not set up -

-
- -
- - -
+ Trace Analytics not set up + + + - -
- The indices required for trace analytics (otel-v1-apm-span-* and otel-v1-apm-service-map*) do not exist or you do not have permission to access them. -
-
-
-
- - - -
- - -
- - - + + +
+ +
+ The indices required for trace analytics (otel-v1-apm-span-* and otel-v1-apm-service-map*) do not exist or you do not have permission to access them. +
+
+
+
+ + + +
+ + +
+ + - - - -
- - -
- - - -
- - - + + + + +
+ + +
+ + +
- + + + +
- -
- - Service map - -
-
- - -
- - -
+ Service map + +
+ + +
+ + +
- - -
- - - + - - - -
-
-
- - + + + +
+ + + -
- - - + - - - -
- -
- - + + + +
+ + + -
- - - + - - - -
- -
-
- - - -
-
- -
+ + + +
+ + +
+ + + - + + +
-
- -
- Focus on -
-
-
- - -
- + +
+ Focus on +
+
+
+
+ +
- -
- - - - -
- + + +
- - - - -
-
+ > + + + + +
+
+
+
-
- - -
- -
- - + + +
+ + + + +
+
- -
- - -
- - - No data matches the selected filter. Clear the filter and/or increase the time range to see more results. - - } - title={ -

- No matches -

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

+ No matches +

+ } + > +
- - -

- No matches -

-
- -
- - -
+ No matches + + + - -
- No data matches the selected filter. Clear the filter and/or increase the time range to see more results. -
-
-
-
- - -
- - -
- - +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
-
- - + + + `; exports[`Services component renders services page 1`] = ` - - -
- -
- + +
- -
- - - - -
- + + + +
- - - - -
-
+ + + + + + +
+
+
+
-
- -
-
-
- -
+ +
+
+ - - -
- -
- - } +
+ +
-
+ } > - - - - + - + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="QuickSelectPopover" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" > -
- - - + + + +
-
- - - -
- } - iconType={false} - isCustom={true} - startDateControl={
} + + + +
-
} + iconType={false} + isCustom={true} + startDateControl={
} > - -
- - -
-
- -
- -
- - -
-
- -
+ Show dates + + + +
+ + +
+
+ +
+
+
+
+ +
+ + - - - - - -
- -
- - -
- - + + + +
+ +
+ + +
+ + - -
- -
- - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={false} - panelPaddingSize="none" - withTitle={true} - > - + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={false} + panelPaddingSize="none" + withTitle={true} > -
- - - + + + + + + + +
-
- - - -
- - -
+ + +
+
+ - - - + Add filter - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={false} - panelPaddingSize="m" - withTitle={true} - > - + + + + Add filter + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={false} + panelPaddingSize="m" + withTitle={true} > -
- - - + + + +
-
- - - -
- - - - - - -
- - - + + + +
+ + + + + +
- + + + +
-
- -
- - -
- - Services - - - (0) - -
-
-
-
-
-
- - -
- - -
-
- + + Services + + + (0) + +
+ + +
+ +
+
- - No data matches the selected filter. Clear the filter and/or increase the time range to see more results. - - } - title={ -

- No matches -

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

+ No matches +

+ } + > +
- - -

- No matches -

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

- No matches -

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

+ No matches +

+ } + > +
- - -

- No matches -

-
- -
- - -
+ No matches + + + - -
- No data matches the selected filter. Clear the filter and/or increase the time range to see more results. -
-
-
-
- - -
- - -
- - +
+ + +
+ +
+ No data matches the selected filter. Clear the filter and/or increase the time range to see more results. +
+
+
+
+ + +
+ + +
+ + +
-
- - + + + `; diff --git a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services_table.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services_table.test.tsx.snap index 4550da4f7..3fc805b3e 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services_table.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/__snapshots__/services_table.test.tsx.snap @@ -6,10 +6,9 @@ exports[`Services table component renders empty services table message 1`] = ` indicesExist={true} items={Array []} loading={false} - refresh={[MockFunction]} - serviceQuery="test" + nameColumnAction={[Function]} setRedirect={[MockFunction]} - setServiceQuery={[MockFunction]} + traceColumnAction={[Function]} >
diff --git a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/services.test.tsx b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/services.test.tsx index 807293129..fca7a6c44 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/services.test.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/services.test.tsx @@ -18,11 +18,27 @@ describe('Services component', () => { const setFilters = jest.fn(); const setStartTime = jest.fn(); const setEndTime = jest.fn(); + const serviceBreadcrumbs = [ + { + text: 'Trace analytics', + href: '#/trace_analytics/home', + }, + { + text: 'Services', + href: '#/trace_analytics/services', + }, + ]; + const nameColumnAction = (item: any) => + location.assign(`#/trace_analytics/services/${encodeURIComponent(item)}`); + const traceColumnAction = () => location.assign('#/trace_analytics/traces'); const wrapper = mount( { const setFilters = jest.fn(); const setStartTime = jest.fn(); const setEndTime = jest.fn(); + const serviceBreadcrumbs = [ + { + text: 'Trace analytics', + href: '#/trace_analytics/home', + }, + { + text: 'Services', + href: '#/trace_analytics/services', + }, + ]; + const nameColumnAction = (item: any) => + location.assign(`#/trace_analytics/services/${encodeURIComponent(item)}`); + const traceColumnAction = () => location.assign('#/trace_analytics/traces'); const wrapper = mount( { it('renders empty services table message', () => { const addFilter = jest.fn(); - const setServiceQuery = jest.fn(); const setRedirect = jest.fn(); - const refresh = jest.fn(); + const nameColumnAction = (item: any) => + location.assign(`#/trace_analytics/services/${encodeURIComponent(item)}`); + const traceColumnAction = () => location.assign('#/trace_analytics/traces'); const wrapper = mount( @@ -46,17 +45,17 @@ describe('Services table component', () => { }, ]; const addFilter = jest.fn(); - const setServiceQuery = jest.fn(); const setRedirect = jest.fn(); - const refresh = jest.fn(); + const nameColumnAction = (item: any) => + location.assign(`#/trace_analytics/services/${encodeURIComponent(item)}`); + const traceColumnAction = () => location.assign('#/trace_analytics/traces'); const wrapper = mount( diff --git a/dashboards-observability/public/components/trace_analytics/components/services/service_view.tsx b/dashboards-observability/public/components/trace_analytics/components/services/service_view.tsx index 0cf103048..555446ac2 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/service_view.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/services/service_view.tsx @@ -47,8 +47,8 @@ export function ServiceView(props: ServiceViewProps) { const refresh = () => { const DSL = filtersToDsl(props.filters, props.query, props.startTime, props.endTime); - handleServiceViewRequest(props.serviceName, props.http, DSL, fields, setFields); - handleServiceMapRequest(props.http, DSL, serviceMap, setServiceMap, props.serviceName); + handleServiceViewRequest(props.serviceName, props.http, DSL, setFields); + handleServiceMapRequest(props.http, DSL, setServiceMap, props.serviceName); }; useEffect(() => { diff --git a/dashboards-observability/public/components/trace_analytics/components/services/services.tsx b/dashboards-observability/public/components/trace_analytics/components/services/services.tsx index da22c7064..43e6170b7 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/services.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/services/services.tsx @@ -2,183 +2,27 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable react-hooks/exhaustive-deps */ -import { EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiBreadcrumb, EuiTitle } from '@elastic/eui'; import _ from 'lodash'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { TraceAnalyticsComponentDeps } from '../../home'; -import { - handleServiceMapRequest, - handleServicesRequest, -} from '../../requests/services_request_handler'; -import { FilterType } from '../common/filters/filters'; -import { getValidFilterFields } from '../common/filters/filter_helpers'; -import { filtersToDsl } from '../common/helper_functions'; -import { ServiceMap, ServiceObject } from '../common/plots/service_map'; -import { SearchBar } from '../common/search_bar'; -import { ServicesTable } from './services_table'; +import { ServicesContent } from './services_content'; -interface ServicesProps extends TraceAnalyticsComponentDeps { - appId?: string; - appName?: string; - openServiceFlyout?: (serviceName: string) => void; - switchToTrace?: () => void; - switchToEditViz?: any; +export interface ServicesProps extends TraceAnalyticsComponentDeps { + childBreadcrumbs: EuiBreadcrumb[]; + nameColumnAction: any; + traceColumnAction: any; page: 'dashboard' | 'traces' | 'services' | 'app'; } export function Services(props: ServicesProps) { - const { appId, appName, parentBreadcrumbs, page, switchToEditViz } = props; - const [tableItems, setTableItems] = useState([]); - const [serviceMap, setServiceMap] = useState({}); - const [serviceMapIdSelected, setServiceMapIdSelected] = useState< - 'latency' | 'error_rate' | 'throughput' - >('latency'); - const [redirect, setRedirect] = useState(true); - const [loading, setLoading] = useState(false); - const [filteredService, setFilteredService] = useState(''); - const appServices = page === 'app'; - - const breadCrumbs = appServices - ? [ - { - text: 'Application analytics', - href: '#/application_analytics', - }, - { - text: `${appName}`, - href: `#/application_analytics/${appId}`, - }, - ] - : [ - { - text: 'Trace analytics', - href: '#/trace_analytics/home', - }, - { - text: 'Services', - href: '#/trace_analytics/services', - }, - ]; - - useEffect(() => { - props.chrome.setBreadcrumbs([...parentBreadcrumbs, ...breadCrumbs]); - const validFilters = getValidFilterFields('services'); - props.setFilters([ - ...props.filters.map((filter) => ({ - ...filter, - locked: validFilters.indexOf(filter.field) === -1, - })), - ]); - setRedirect(false); - if (appServices) { - switchToEditViz(''); - } - }, []); - - useEffect(() => { - let newFilteredService = ''; - for (const filter of props.filters) { - if (filter.field === 'serviceName') { - newFilteredService = filter.value; - break; - } - } - setFilteredService(newFilteredService); - if (!redirect && props.indicesExist) refresh(newFilteredService); - }, [props.filters, props.appConfigs]); - - const refresh = async (currService?: string) => { - setLoading(true); - const DSL = filtersToDsl( - props.filters, - props.query, - props.startTime, - props.endTime, - props.page, - appServices ? props.appConfigs : [] - ); - // 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 Promise.all([ - handleServicesRequest(props.http, DSL, tableItems, setTableItems, null, serviceQuery), - handleServiceMapRequest( - props.http, - serviceMapDSL, - serviceMap, - setServiceMap, - currService || filteredService - ), - ]); - setLoading(false); - }; - - const addFilter = (filter: FilterType) => { - for (let i = 0; i < props.filters.length; i++) { - const addedFilter = props.filters[i]; - if (addedFilter.field === filter.field) { - if (addedFilter.operator === filter.operator && addedFilter.value === filter.value) return; - const newFilters = [...props.filters]; - newFilters.splice(i, 1, filter); - props.setFilters(newFilters); - return; - } - } - const newFilters = [...props.filters, filter]; - props.setFilters(newFilters); - }; - - const [serviceQuery, setServiceQuery] = useState(''); - return ( <> - {appServices ? ( - - ) : ( - -

Services

-
- )} - - - - - + +

Services

+
+ ); } diff --git a/dashboards-observability/public/components/trace_analytics/components/services/services_content.tsx b/dashboards-observability/public/components/trace_analytics/components/services/services_content.tsx new file mode 100644 index 000000000..e4fa97e25 --- /dev/null +++ b/dashboards-observability/public/components/trace_analytics/components/services/services_content.tsx @@ -0,0 +1,141 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable react-hooks/exhaustive-deps */ + +import { EuiSpacer } from '@elastic/eui'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { + handleServiceMapRequest, + handleServicesRequest, +} from '../../requests/services_request_handler'; +import { FilterType } from '../common/filters/filters'; +import { getValidFilterFields } from '../common/filters/filter_helpers'; +import { filtersToDsl } from '../common/helper_functions'; +import { ServiceMap, ServiceObject } from '../common/plots/service_map'; +import { SearchBar } from '../common/search_bar'; +import { ServicesProps } from './services'; +import { ServicesTable } from './services_table'; + +export function ServicesContent(props: ServicesProps) { + const { + page, + http, + chrome, + filters, + query, + startTime, + endTime, + indicesExist, + appConfigs = [], + childBreadcrumbs, + parentBreadcrumbs, + nameColumnAction, + traceColumnAction, + setFilters, + setQuery, + setStartTime, + setEndTime, + } = props; + const [tableItems, setTableItems] = useState([]); + const [serviceMap, setServiceMap] = useState({}); + const [serviceMapIdSelected, setServiceMapIdSelected] = useState< + 'latency' | 'error_rate' | 'throughput' + >('latency'); + const [redirect, setRedirect] = useState(true); + const [loading, setLoading] = useState(false); + const [filteredService, setFilteredService] = useState(''); + + useEffect(() => { + chrome.setBreadcrumbs([...parentBreadcrumbs, ...childBreadcrumbs]); + const validFilters = getValidFilterFields('services'); + setFilters([ + ...filters.map((filter) => ({ + ...filter, + locked: validFilters.indexOf(filter.field) === -1, + })), + ]); + setRedirect(false); + }, []); + + useEffect(() => { + let newFilteredService = ''; + for (const filter of filters) { + if (filter.field === 'serviceName') { + newFilteredService = filter.value; + break; + } + } + setFilteredService(newFilteredService); + if (!redirect && indicesExist) refresh(newFilteredService); + }, [filters, appConfigs]); + + const refresh = async (currService?: string) => { + setLoading(true); + const DSL = filtersToDsl(filters, query, startTime, endTime, page, appConfigs); + // 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 Promise.all([ + handleServicesRequest(http, DSL, setTableItems), + handleServiceMapRequest(http, serviceMapDSL, setServiceMap, currService || filteredService), + ]); + setLoading(false); + }; + + 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); + }; + + return ( + <> + + + + + + + ); +} diff --git a/dashboards-observability/public/components/trace_analytics/components/services/services_table.tsx b/dashboards-observability/public/components/trace_analytics/components/services/services_table.tsx index a704375a8..d294f13ef 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/services_table.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/services/services_table.tsx @@ -2,6 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +/* eslint-disable react-hooks/exhaustive-deps */ import { EuiFlexGroup, @@ -24,20 +25,26 @@ import { PanelTitle, } from '../common/helper_functions'; -export function ServicesTable(props: { +interface ServicesTableProps { items: any[]; - addFilter: (filter: FilterType) => void; - setRedirect: (redirect: boolean) => void; - serviceQuery: string; - setServiceQuery: (query: string) => void; - refresh: () => void; indicesExist: boolean; loading: boolean; - page?: string; - openServiceFlyout?: any; - switchToTrace?: any; -}) { - const appServices = props.page === 'app'; + nameColumnAction: (item: any) => any; + traceColumnAction: any; + addFilter: (filter: FilterType) => void; + setRedirect: (redirect: boolean) => void; +} + +export function ServicesTable(props: ServicesTableProps) { + const { + items, + indicesExist, + loading, + nameColumnAction, + traceColumnAction, + addFilter, + setRedirect, + } = props; const renderTitleBar = (totalItems?: number) => { return ( @@ -56,31 +63,18 @@ export function ServicesTable(props: { name: 'Name', align: 'left', sortable: true, - render: (item) => - appServices ? ( - props.openServiceFlyout(item)}> - {item.length < 24 ? ( - item - ) : ( -
{_.truncate(item, { length: 24 })}
- )} -
- ) : ( - - {item.length < 24 ? ( - item - ) : ( -
{_.truncate(item, { length: 24 })}
- )} -
- ), + render: (item: any) => ( + nameColumnAction(item)}> + {item.length < 24 ? item :
{_.truncate(item, { length: 24 })}
} +
+ ), }, { field: 'average_latency', name: 'Average latency (ms)', align: 'right', sortable: true, - render: (item) => (item === 0 || item ? _.round(item, 2) : '-'), + render: (item: any) => (item === 0 || item ? _.round(item, 2) : '-'), }, { field: 'error_rate', @@ -96,7 +90,7 @@ export function ServicesTable(props: { align: 'right', sortable: true, truncateText: true, - render: (item) => (item === 0 || item ? : '-'), + render: (item: any) => (item === 0 || item ? : '-'), }, { field: 'number_of_connected_services', @@ -105,7 +99,7 @@ export function ServicesTable(props: { sortable: true, truncateText: true, width: '80px', - render: (item) => (item === 0 || item ? item : '-'), + render: (item: any) => (item === 0 || item ? item : '-'), }, { field: 'connected_services', @@ -113,12 +107,8 @@ export function ServicesTable(props: { align: 'left', sortable: true, truncateText: true, - render: (items) => - items ? ( - {_.truncate(items.join(', '), { length: 50 })} - ) : ( - '-' - ), + render: (item: any) => + item ? {_.truncate(item.join(', '), { length: 50 })} : '-', }, { field: 'traces', @@ -126,24 +116,20 @@ export function ServicesTable(props: { align: 'right', sortable: true, truncateText: true, - render: (item, row) => ( + render: (item: any, row: any) => ( <> {item === 0 || item ? ( { - props.setRedirect(true); - props.addFilter({ + setRedirect(true); + addFilter({ field: 'serviceName', operator: 'is', value: row.name, inverted: false, disabled: false, }); - if (appServices) { - props.switchToTrace(); - } else { - location.assign('#/trace_analytics/traces'); - } + traceColumnAction(); }} > @@ -155,10 +141,10 @@ export function ServicesTable(props: { ), }, ] as Array>, - [props.items] + [items] ); - const titleBar = useMemo(() => renderTitleBar(props.items?.length), [props.items]); + const titleBar = useMemo(() => renderTitleBar(items?.length), [items]); return ( <> @@ -166,10 +152,10 @@ export function ServicesTable(props: { {titleBar} - {props.items?.length > 0 ? ( + {items?.length > 0 ? ( - ) : props.indicesExist ? ( + ) : indicesExist ? ( ) : ( diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/trace_view.tsx b/dashboards-observability/public/components/trace_analytics/components/traces/trace_view.tsx index 7495f9089..e839a87ec 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/trace_view.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/traces/trace_view.tsx @@ -150,7 +150,7 @@ export function TraceView(props: TraceViewProps) { handleTraceViewRequest(props.traceId, props.http, fields, setFields); handlePayloadRequest(props.traceId, props.http, payloadData, setPayloadData); handleServicesPieChartRequest(props.traceId, props.http, setServiceBreakdownData, setColorMap); - handleServiceMapRequest(props.http, DSL, serviceMap, setServiceMap); + handleServiceMapRequest(props.http, DSL, setServiceMap); }; useEffect(() => { diff --git a/dashboards-observability/public/components/trace_analytics/home.tsx b/dashboards-observability/public/components/trace_analytics/home.tsx index af94f16a8..7ec82aac2 100644 --- a/dashboards-observability/public/components/trace_analytics/home.tsx +++ b/dashboards-observability/public/components/trace_analytics/home.tsx @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { EuiLink } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; import { Route, RouteComponentProps } from 'react-router-dom'; import { @@ -66,7 +67,7 @@ export const Home = (props: HomeProps) => { handleIndicesExistRequest(props.http, setIndicesExist); }, []); - const childBreadcrumbs = [ + const dashboardBreadcrumbs = [ { text: 'Trace analytics', href: '#/trace_analytics/home', @@ -77,6 +78,22 @@ export const Home = (props: HomeProps) => { }, ]; + const serviceBreadcrumbs = [ + { + text: 'Trace analytics', + href: '#/trace_analytics/home', + }, + { + text: 'Services', + href: '#/trace_analytics/services', + }, + ]; + + const nameColumnAction = (item: any) => + location.assign(`#/trace_analytics/services/${encodeURIComponent(item)}`); + + const traceColumnAction = () => location.assign('#/trace_analytics/traces'); + const commonProps: TraceAnalyticsComponentDeps = { parentBreadcrumbs: props.parentBreadcrumbs, http: props.http, @@ -100,7 +117,7 @@ export const Home = (props: HomeProps) => { path={['/trace_analytics', '/trace_analytics/home']} render={(routerProps) => ( - + )} /> @@ -129,7 +146,13 @@ export const Home = (props: HomeProps) => { path="/trace_analytics/services" render={(routerProps) => ( - + )} /> diff --git a/dashboards-observability/public/components/trace_analytics/requests/queries/services_queries.ts b/dashboards-observability/public/components/trace_analytics/requests/queries/services_queries.ts index 51b015d7e..327e029d4 100644 --- a/dashboards-observability/public/components/trace_analytics/requests/queries/services_queries.ts +++ b/dashboards-observability/public/components/trace_analytics/requests/queries/services_queries.ts @@ -11,7 +11,7 @@ import { import { getServiceMapTargetResources } from '../../components/common/helper_functions'; import { ServiceObject } from '../../components/common/plots/service_map'; -export const getServicesQuery = (serviceName = null, DSL?) => { +export const getServicesQuery = (serviceName: string | undefined, DSL?: any) => { const query = { size: 0, query: { @@ -41,18 +41,18 @@ export const getServicesQuery = (serviceName = null, DSL?) => { if (serviceName) { query.query.bool.must.push({ term: { - serviceName: serviceName, + serviceName, }, }); } - DSL?.custom?.serviceNames?.map((service) => { + DSL?.custom?.serviceNames?.map((service: string) => { query.query.bool.must.push({ term: { serviceName: service, }, }); }); - DSL?.custom?.serviceNamesExclude?.map((service) => { + DSL?.custom?.serviceNamesExclude?.map((service: string) => { query.query.bool.must_not.push({ term: { serviceName: service, @@ -62,7 +62,7 @@ export const getServicesQuery = (serviceName = null, DSL?) => { return query; }; -export const getRelatedServicesQuery = (serviceName) => { +export const getRelatedServicesQuery = (serviceName: string) => { const query = { size: 0, query: { @@ -92,7 +92,7 @@ export const getRelatedServicesQuery = (serviceName) => { must: [ { term: { - serviceName: serviceName, + serviceName, }, }, ], @@ -185,11 +185,11 @@ export const getServiceEdgesQuery = (source: 'destination' | 'target') => { }; }; -export const getServiceMetricsQuery = (DSL, serviceNames: string[], map: ServiceObject) => { +export const getServiceMetricsQuery = (DSL: any, serviceNames: string[], map: ServiceObject) => { const traceGroupFilter = new Set( DSL?.query?.bool.must - .filter((must) => must.term?.['traceGroup']) - .map((must) => must.term['traceGroup']) || [] + .filter((must: any) => must.term?.['traceGroup']) + .map((must: any) => must.term.traceGroup) || [] ); const targetResource = diff --git a/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts b/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts index 11e5cbbb5..9c59deb18 100644 --- a/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts +++ b/dashboards-observability/public/components/trace_analytics/requests/services_request_handler.ts @@ -2,8 +2,11 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +/* eslint-disable no-console */ import _ from 'lodash'; +import dateMath from '@elastic/datemath'; +import DSLService from 'public/services/requests/dsl'; import { ServiceObject } from '../components/common/plots/service_map'; import { getRelatedServicesQuery, @@ -13,25 +16,22 @@ import { getServicesQuery, } from './queries/services_queries'; import { handleDslRequest } from './request_handler'; -import dateMath from '@elastic/datemath'; -import moment from 'moment'; +import { HttpSetup } from '../../../../../../src/core/public'; export const handleServicesRequest = async ( - http, - DSL, - items, - setItems, - setServiceMap?, - serviceNameFilter? + http: HttpSetup, + DSL: any, + setItems: any, + setServiceMap?: any, + serviceNameFilter?: string ) => { return handleDslRequest(http, DSL, getServicesQuery(serviceNameFilter, DSL)) .then(async (response) => { - const serviceObject: ServiceObject = await handleServiceMapRequest(http, DSL); - if (setServiceMap) setServiceMap(serviceObject); + const serviceObject: ServiceObject = await handleServiceMapRequest(http, DSL, setServiceMap); return Promise.all( response.aggregations.service.buckets - .filter((bucket) => serviceObject[bucket.key]) - .map((bucket) => { + .filter((bucket: any) => serviceObject[bucket.key]) + .map((bucket: any) => { const connectedServices = [ ...serviceObject[bucket.key].targetServices, ...serviceObject[bucket.key].destServices, @@ -54,9 +54,14 @@ export const handleServicesRequest = async ( .catch((error) => console.error(error)); }; -export const handleServiceMapRequest = async (http, DSL, items?, setItems?, currService?) => { +export const handleServiceMapRequest = async ( + http: HttpSetup, + DSL: DSLService | any, + setItems?: any, + currService?: string +) => { let minutesInDateRange: number; - const startTime = DSL?.custom?.timeFilter?.[0]?.range?.startTime; + const startTime = DSL.custom?.timeFilter?.[0]?.range?.startTime; if (startTime) { const gte = dateMath.parse(startTime.gte)!; const lte = dateMath.parse(startTime.lte)!; @@ -68,13 +73,13 @@ export const handleServiceMapRequest = async (http, DSL, items?, setItems?, curr await handleDslRequest(http, null, getServiceNodesQuery()) .then((response) => response.aggregations.service_name.buckets.map( - (bucket) => + (bucket: any) => (map[bucket.key] = { serviceName: bucket.key, id: id++, - traceGroups: bucket.trace_group.buckets.map((traceGroup) => ({ + traceGroups: bucket.trace_group.buckets.map((traceGroup: any) => ({ traceGroup: traceGroup.key, - targetResource: traceGroup.target_resource.buckets.map((res) => res.key), + targetResource: traceGroup.target_resource.buckets.map((res: any) => res.key), })), targetServices: [], destServices: [], @@ -86,9 +91,9 @@ export const handleServiceMapRequest = async (http, DSL, items?, setItems?, curr const targets = {}; await handleDslRequest(http, null, getServiceEdgesQuery('target')) .then((response) => - response.aggregations.service_name.buckets.map((bucket) => { - bucket.resource.buckets.map((resource) => { - resource.domain.buckets.map((domain) => { + response.aggregations.service_name.buckets.map((bucket: any) => { + bucket.resource.buckets.map((resource: any) => { + resource.domain.buckets.map((domain: any) => { targets[resource.key + ':' + domain.key] = bucket.key; }); }); @@ -98,9 +103,9 @@ export const handleServiceMapRequest = async (http, DSL, items?, setItems?, curr await handleDslRequest(http, null, getServiceEdgesQuery('destination')) .then((response) => Promise.all( - response.aggregations.service_name.buckets.map((bucket) => { - bucket.resource.buckets.map((resource) => { - resource.domain.buckets.map((domain) => { + response.aggregations.service_name.buckets.map((bucket: any) => { + bucket.resource.buckets.map((resource: any) => { + resource.domain.buckets.map((domain: any) => { const targetService = targets[resource.key + ':' + domain.key]; if (targetService) { if (map[bucket.key].targetServices.indexOf(targetService) === -1) @@ -121,7 +126,7 @@ export const handleServiceMapRequest = async (http, DSL, items?, setItems?, curr DSL, getServiceMetricsQuery(DSL, Object.keys(map), map) ); - latencies.aggregations.service_name.buckets.map((bucket) => { + 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; @@ -132,13 +137,13 @@ export const handleServiceMapRequest = async (http, DSL, items?, setItems?, curr if (currService) { await handleDslRequest(http, DSL, getRelatedServicesQuery(currService)) .then((response) => - response.aggregations.traces.buckets.filter((bucket) => bucket.service.doc_count > 0) + response.aggregations.traces.buckets.filter((bucket: any) => bucket.service.doc_count > 0) ) .then((traces) => { const maxNumServices = Object.keys(map).length; const relatedServices = new Set(); for (let i = 0; i < traces.length; i++) { - traces[i].all_services.buckets.map((bucket) => relatedServices.add(bucket.key)); + traces[i].all_services.buckets.map((bucket: any) => relatedServices.add(bucket.key)); if (relatedServices.size === maxNumServices) break; } map[currService].relatedServices = [...relatedServices]; @@ -150,7 +155,12 @@ export const handleServiceMapRequest = async (http, DSL, items?, setItems?, curr return map; }; -export const handleServiceViewRequest = (serviceName, http, DSL, fields, setFields) => { +export const handleServiceViewRequest = ( + serviceName: string, + http: HttpSetup, + DSL: any, + setFields: any +) => { handleDslRequest(http, DSL, getServicesQuery(serviceName)) .then(async (response) => { const bucket = response.aggregations.service.buckets[0];