diff --git a/dashboards-observability/common/types/explorer.ts b/dashboards-observability/common/types/explorer.ts index 8838a9a35..9f5c15fb0 100644 --- a/dashboards-observability/common/types/explorer.ts +++ b/dashboards-observability/common/types/explorer.ts @@ -104,13 +104,12 @@ export interface IExplorerProps { tabCreatedTypes?: any; searchBarConfigs?: any; appId?: string; - baseQuery?: string; addVisualizationToPanel?: any; startTime?: string; endTime?: string; setStartTime?: any; setEndTime?: any; - appBaseQuery: string; + appBaseQuery?: string; } export interface SavedQuery { diff --git a/dashboards-observability/public/components/app.tsx b/dashboards-observability/public/components/app.tsx index fbdd65a32..36594b890 100644 --- a/dashboards-observability/public/components/app.tsx +++ b/dashboards-observability/public/components/app.tsx @@ -18,7 +18,7 @@ import { Main as NotebooksHome } from './notebooks/components/main'; import { Home as TraceAnalyticsHome } from './trace_analytics/home'; interface ObservabilityAppDeps { - CoreStart: CoreStart; + CoreStartProp: CoreStart; DepsStart: AppPluginStartDependencies; pplService: any; dslService: any; @@ -27,14 +27,14 @@ interface ObservabilityAppDeps { } export const App = ({ - CoreStart, + CoreStartProp, DepsStart, pplService, dslService, savedObjects, timestampUtils, }: ObservabilityAppDeps) => { - const { chrome, http, notifications } = CoreStart; + const { chrome, http, notifications } = CoreStartProp; const parentBreadcrumb = { text: observabilityTitle, href: `${observabilityID}#/`, @@ -60,7 +60,7 @@ export const App = ({ chrome={chrome} http={http} notifications={notifications} - parentBreadcrumb={parentBreadcrumb} + parentBreadcrumbs={[parentBreadcrumb]} pplService={pplService} dslService={dslService} savedObjects={savedObjects} @@ -93,7 +93,7 @@ export const App = ({ )} /> @@ -118,7 +118,7 @@ export const App = ({ return ( { @@ -37,11 +38,16 @@ describe('Create Page', () => { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const utils = render( { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const utils = render( { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const utils = render( { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const utils = render( { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const serviceFilters = [ { field: 'serviceName', @@ -292,7 +317,8 @@ describe('Create Page', () => { { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const serviceFilters = [ { field: 'serviceName', @@ -363,7 +393,8 @@ describe('Create Page', () => { { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const traceFilters = [ { field: 'traceGroup', @@ -434,7 +469,8 @@ describe('Create Page', () => { { fetchIndices: jest.fn(), fetchFields: jest.fn(), } as unknown) as DSLService; + const pplService = ({ + http: jest.fn(), + fetch: jest.fn(), + } as unknown) as PPLService; const traceFilters = [ { field: 'traceGroup', @@ -505,7 +545,8 @@ describe('Create Page', () => { { chrome.setBreadcrumbs([ - parentBreadcrumb, + ...parentBreadcrumbs, { text: 'Application analytics', href: '#/application_analytics', diff --git a/dashboards-observability/public/components/application_analytics/components/application.tsx b/dashboards-observability/public/components/application_analytics/components/application.tsx index 269cc7c04..ecde5e1a0 100644 --- a/dashboards-observability/public/components/application_analytics/components/application.tsx +++ b/dashboards-observability/public/components/application_analytics/components/application.tsx @@ -27,13 +27,14 @@ import React, { ReactChild, useEffect, useState } from 'react'; import { uniqueId } from 'lodash'; import { useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; +import { last } from 'lodash'; +import { DashboardContent } from '../../../components/trace_analytics/components/dashboard/dashboard_content'; import { filtersToDsl, PanelTitle, } 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 { Dashboard } from '../../trace_analytics/components/dashboard'; import { Services } from '../../trace_analytics/components/services'; import { Traces } from '../../trace_analytics/components/traces'; import { Configuration } from './configuration'; @@ -110,7 +111,7 @@ export function Application(props: AppDetailProps) { notifications, appId, chrome, - parentBreadcrumb, + parentBreadcrumbs, startTime, endTime, query, @@ -211,14 +212,14 @@ export function Application(props: AppDetailProps) { useEffect(() => { chrome.setBreadcrumbs([ - parentBreadcrumb, + ...parentBreadcrumbs, { text: 'Application analytics', href: '#/application_analytics', }, { text: application.name, - href: `${parentBreadcrumb.href}${appId}`, + href: `${last(parentBreadcrumbs)!.href}application_analytics/${appId}`, }, ]); setStartTimeForApp(sessionStorage.getItem(`${application.name}StartTime`) || 'now-24h'); @@ -230,6 +231,12 @@ export function Application(props: AppDetailProps) { setSpanDSL(DSL); }, [filters, appConfigs, query, startTime, endTime]); + useEffect(() => { + if (selectedTabId !== TAB_LOG_ID) { + switchToEditViz(''); + } + }, [selectedTabId]); + const openServiceFlyout = (serviceName: string) => { setSpanFlyoutId(''); setTraceFlyoutId(''); @@ -260,17 +267,29 @@ export function Application(props: AppDetailProps) { setTraceFlyoutId(''); }; + const childBreadcrumbs = [ + { + text: 'Application analytics', + href: '#/application_analytics', + }, + { + text: `${application.name}`, + href: `#/application_analytics/${appId}`, + }, + ]; + const getOverview = () => { return ( - + <> + + + ); }; @@ -339,7 +358,6 @@ export function Application(props: AppDetailProps) { http={http} searchBarConfigs={searchBarConfigs} appId={appId} - baseQuery={application.baseQuery} addVisualizationToPanel={addVisualizationToPanel} startTime={startTime} endTime={endTime} @@ -357,12 +375,13 @@ export function Application(props: AppDetailProps) { panelId={application.panelId} http={http} pplService={pplService} + dslService={dslService} chrome={chrome} - parentBreadcrumb={[parentBreadcrumb]} + parentBreadcrumbs={parentBreadcrumbs} // App analytics will not be renaming/cloning/deleting panels - renameCustomPanel={() => undefined} - cloneCustomPanel={(): Promise => Promise.reject()} - deleteCustomPanel={(): Promise => Promise.reject()} + renameCustomPanel={async () => undefined} + cloneCustomPanel={async () => Promise.reject()} + deleteCustomPanel={async () => Promise.reject()} setToast={setToasts} page="app" appName={application.name} @@ -394,7 +413,7 @@ export function Application(props: AppDetailProps) { return ( void; updateApp: (appId: string, updateAppData: Partial, type: string) => void; @@ -39,7 +40,7 @@ export const Configuration = (props: ConfigProps) => { const { appId, application, - parentBreadcrumb, + parentBreadcrumbs, visWithAvailability, updateApp, switchToEditViz, @@ -72,7 +73,7 @@ export const Configuration = (props: ConfigProps) => { fill onClick={() => { window.location.assign( - `${parentBreadcrumb.href}application_analytics/edit/${appId}` + `${last(parentBreadcrumbs)!.href}application_analytics/edit/${appId}` ); }} > diff --git a/dashboards-observability/public/components/application_analytics/components/create.tsx b/dashboards-observability/public/components/application_analytics/components/create.tsx index fba8b5525..7a209ac1f 100644 --- a/dashboards-observability/public/components/application_analytics/components/create.tsx +++ b/dashboards-observability/public/components/application_analytics/components/create.tsx @@ -26,6 +26,7 @@ import { import DSLService from 'public/services/requests/dsl'; import React, { ReactChild, useEffect, useState } from 'react'; import PPLService from 'public/services/requests/ppl'; +import { last } from 'lodash'; import { AppAnalyticsComponentDeps } from '../home'; import { TraceConfig } from './config_components/trace_config'; import { ServiceConfig } from './config_components/service_config'; @@ -46,7 +47,7 @@ interface CreateAppProps extends AppAnalyticsComponentDeps { export const CreateApp = (props: CreateAppProps) => { const { - parentBreadcrumb, + parentBreadcrumbs, chrome, http, query, @@ -80,7 +81,7 @@ export const CreateApp = (props: CreateAppProps) => { useEffect(() => { chrome.setBreadcrumbs([ - parentBreadcrumb, + ...parentBreadcrumbs, { text: 'Application analytics', href: '#/application_analytics', @@ -162,7 +163,7 @@ export const CreateApp = (props: CreateAppProps) => { const onCancel = () => { clearStorage(); - window.location.assign(`${parentBreadcrumb.href}application_analytics`); + window.location.assign(`${last(parentBreadcrumbs)!.href}application_analytics`); }; return ( diff --git a/dashboards-observability/public/components/application_analytics/home.tsx b/dashboards-observability/public/components/application_analytics/home.tsx index f73c1c6ae..644a23bd2 100644 --- a/dashboards-observability/public/components/application_analytics/home.tsx +++ b/dashboards-observability/public/components/application_analytics/home.tsx @@ -13,7 +13,7 @@ import SavedObjects from 'public/services/saved_objects/event_analytics/saved_ob import TimestampUtils from 'public/services/timestamp/timestamp'; import { EuiGlobalToastList, EuiLink } from '@elastic/eui'; import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; -import _, { isEmpty } from 'lodash'; +import { isEmpty, last } from 'lodash'; import { useDispatch } from 'react-redux'; import { AppTable } from './components/app_table'; import { Application } from './components/application'; @@ -64,7 +64,7 @@ export const Home = (props: HomeProps) => { dslService, timestampUtils, savedObjects, - parentBreadcrumb, + parentBreadcrumbs, http, chrome, notifications, @@ -124,7 +124,7 @@ export const Home = (props: HomeProps) => { }, []); const commonProps: AppAnalyticsComponentDeps = { - parentBreadcrumb, + parentBreadcrumbs, http, chrome, name, @@ -321,7 +321,7 @@ export const Home = (props: HomeProps) => { } if (type !== 'editAvailability') { window.location.assign( - `${parentBreadcrumb.href}application_analytics/${res.updatedAppId}` + `${last(parentBreadcrumbs)!.href}application_analytics/${res.updatedAppId}` ); } }) diff --git a/dashboards-observability/public/components/custom_panels/__tests__/__snapshots__/custom_panel_table.test.tsx.snap b/dashboards-observability/public/components/custom_panels/__tests__/__snapshots__/custom_panel_table.test.tsx.snap index c82b136c5..4b900b8e7 100644 --- a/dashboards-observability/public/components/custom_panels/__tests__/__snapshots__/custom_panel_table.test.tsx.snap +++ b/dashboards-observability/public/components/custom_panels/__tests__/__snapshots__/custom_panel_table.test.tsx.snap @@ -21,7 +21,7 @@ exports[`Panels Table Component renders empty panel table container 1`] = ` } } loading={false} - parentBreadcrumb={ + parentBreadcrumbs={ Array [ Object { "href": "#/operational_panels/", @@ -624,7 +624,7 @@ exports[`Panels Table Component renders panel table container 1`] = ` } } loading={false} - parentBreadcrumb={ + parentBreadcrumbs={ Array [ Object { "href": "#/operational_panels/", diff --git a/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_table.test.tsx b/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_table.test.tsx index 6ff11f14e..fb754bba3 100644 --- a/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_table.test.tsx +++ b/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_table.test.tsx @@ -33,7 +33,7 @@ describe('Panels Table Component', () => { customPanels={customPanelData} createCustomPanel={createCustomPanel} setBreadcrumbs={setBreadcrumbs} - parentBreadcrumb={parentBreadcrumb} + parentBreadcrumbs={parentBreadcrumb} renameCustomPanel={renameCustomPanel} cloneCustomPanel={cloneCustomPanel} deleteCustomPanelList={deleteCustomPanelList} @@ -66,7 +66,7 @@ describe('Panels Table Component', () => { customPanels={customPanelData} createCustomPanel={createCustomPanel} setBreadcrumbs={setBreadcrumbs} - parentBreadcrumb={parentBreadcrumb} + parentBreadcrumbs={parentBreadcrumb} renameCustomPanel={renameCustomPanel} cloneCustomPanel={cloneCustomPanel} deleteCustomPanelList={deleteCustomPanelList} diff --git a/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_view.test.tsx b/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_view.test.tsx index 01628d4fa..c5c3d3df0 100644 --- a/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_view.test.tsx +++ b/dashboards-observability/public/components/custom_panels/__tests__/custom_panel_view.test.tsx @@ -17,6 +17,7 @@ import { } from '../../../../test/panels_constants'; import httpClientMock from '../../../../test/__mocks__/httpClientMock'; import PPLService from '../../../../public/services/requests/ppl'; +import DSLService from '../../../../public/services/requests/dsl'; import { coreStartMock } from '../../../../test/__mocks__/coreMocks'; import { HttpResponse } from '../../../../../../src/core/public'; @@ -30,8 +31,9 @@ describe.skip('Panels View Component', () => { const panelId = 'L8Sx53wBDp0rvEg3yoLb'; const http = httpClientMock; const pplService = new PPLService(httpClientMock); + const dslService = new DSLService(httpClientMock); const core = coreStartMock; - const parentBreadcrumb = panelBreadCrumbs; + const parentBreadcrumbs = panelBreadCrumbs; const start = 'now-30m'; const end = 'now'; const setStart = jest.fn(); @@ -46,8 +48,9 @@ describe.skip('Panels View Component', () => { panelId={panelId} http={http} pplService={pplService} + dslService={dslService} chrome={core.chrome} - parentBreadcrumb={parentBreadcrumb} + parentBreadcrumbs={parentBreadcrumbs} renameCustomPanel={renameCustomPanel} cloneCustomPanel={cloneCustomPanel} deleteCustomPanel={deleteCustomPanel} @@ -83,8 +86,9 @@ describe.skip('Panels View Component', () => { const panelId = 'L8Sx53wBDp0rvEg3yoLb'; const http = httpClientMock; const pplService = new PPLService(httpClientMock); + const dslService = new DSLService(httpClientMock); const core = coreStartMock; - const parentBreadcrumb = panelBreadCrumbs; + const parentBreadcrumbs = panelBreadCrumbs; const start = 'now-30m'; const end = 'now'; const setStart = jest.fn(); @@ -99,8 +103,9 @@ describe.skip('Panels View Component', () => { panelId={panelId} http={http} pplService={pplService} + dslService={dslService} chrome={core.chrome} - parentBreadcrumb={parentBreadcrumb} + parentBreadcrumbs={parentBreadcrumbs} renameCustomPanel={renameCustomPanel} cloneCustomPanel={cloneCustomPanel} deleteCustomPanel={deleteCustomPanel} diff --git a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx index e92334e79..831834a00 100644 --- a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx +++ b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx @@ -2,6 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +/* eslint-disable react-hooks/exhaustive-deps */ import { EuiBreadcrumb, @@ -63,7 +64,7 @@ interface Props { customPanels: CustomPanelListType[]; createCustomPanel: (newCustomPanelName: string) => void; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; - parentBreadcrumb: EuiBreadcrumb[]; + parentBreadcrumbs: EuiBreadcrumb[]; renameCustomPanel: (newCustomPanelName: string, customPanelId: string) => void; cloneCustomPanel: (newCustomPanelName: string, customPanelId: string) => void; deleteCustomPanelList: (customPanelIdList: string[], toastMessage: string) => any; @@ -76,7 +77,7 @@ export const CustomPanelTable = ({ customPanels, createCustomPanel, setBreadcrumbs, - parentBreadcrumb, + parentBreadcrumbs, renameCustomPanel, cloneCustomPanel, deleteCustomPanelList, @@ -89,7 +90,7 @@ export const CustomPanelTable = ({ const [searchQuery, setSearchQuery] = useState(''); useEffect(() => { - setBreadcrumbs(parentBreadcrumb); + setBreadcrumbs(parentBreadcrumbs); fetchCustomPanels(); }, []); @@ -255,7 +256,7 @@ export const CustomPanelTable = ({ sortable: true, truncateText: true, render: (value, record) => ( - + {_.truncate(value, { length: 100 })} ), diff --git a/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx b/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx index 28d4b249c..6865ddb5b 100644 --- a/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx +++ b/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx @@ -6,7 +6,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { - EuiBadge, EuiBreadcrumb, EuiButton, EuiContextMenu, @@ -27,7 +26,7 @@ import { OnTimeChangeProps, ShortDate, } from '@elastic/eui'; -import _ from 'lodash'; +import { last } from 'lodash'; import React, { useEffect, useState } from 'react'; import { DurationRange } from '@elastic/eui/src/components/date_picker/types'; import moment from 'moment'; @@ -84,7 +83,7 @@ interface Props { pplService: PPLService; dslService: DSLService; chrome: CoreStart['chrome']; - parentBreadcrumb: EuiBreadcrumb[]; + parentBreadcrumbs: EuiBreadcrumb[]; renameCustomPanel: (editedCustomPanelName: string, editedCustomPanelId: string) => Promise; deleteCustomPanel: (customPanelId: string, customPanelName: string) => Promise; cloneCustomPanel: (clonedCustomPanelName: string, clonedCustomPanelId: string) => Promise; @@ -111,7 +110,7 @@ export const CustomPanelView = ({ pplService, dslService, chrome, - parentBreadcrumb, + parentBreadcrumbs, startTime, endTime, setStartTime, @@ -243,7 +242,7 @@ export const CustomPanelView = ({ const onDelete = async () => { deleteCustomPanel(panelId, openPanelName).then((res) => { setTimeout(() => { - window.location.assign(`${_.last(parentBreadcrumb).href}`); + window.location.assign(`${last(parentBreadcrumbs)!.href}`); }, 1000); }); closeModal(); @@ -286,7 +285,7 @@ export const CustomPanelView = ({ const onClone = async (newCustomPanelName: string) => { cloneCustomPanel(newCustomPanelName, panelId).then((id: string) => { - window.location.assign(`${_.last(parentBreadcrumb).href}${id}`); + window.location.assign(`${last(parentBreadcrumbs)!.href}${id}`); }); closeModal(); }; @@ -539,22 +538,22 @@ export const CustomPanelView = ({ let newBreadcrumb; if (appPanel) { newBreadcrumb = [ - ...parentBreadcrumb, + ...parentBreadcrumbs, { text: 'Application analytics', href: '#/application_analytics', }, { text: appName, - href: `${_.last(parentBreadcrumb).href}${appId}`, + href: `${last(parentBreadcrumbs)!.href}${appId}`, }, ]; } else { newBreadcrumb = [ - ...parentBreadcrumb, + ...parentBreadcrumbs, { text: openPanelName, - href: `${_.last(parentBreadcrumb).href}${panelId}`, + href: `${last(parentBreadcrumbs)!.href}${panelId}`, }, ]; } diff --git a/dashboards-observability/public/components/custom_panels/home.tsx b/dashboards-observability/public/components/custom_panels/home.tsx index 0d47f88d5..112015b0b 100644 --- a/dashboards-observability/public/components/custom_panels/home.tsx +++ b/dashboards-observability/public/components/custom_panels/home.tsx @@ -8,6 +8,7 @@ import { EuiBreadcrumb, EuiGlobalToastList, EuiLink, ShortDate } from '@elastic/ import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; import _ from 'lodash'; import React, { ReactChild, useState } from 'react'; +// eslint-disable-next-line @osd/eslint/module_migration import { StaticContext } from 'react-router'; import { Route, RouteComponentProps } from 'react-router-dom'; import PPLService from '../../services/requests/ppl'; @@ -42,7 +43,7 @@ import { isNameValid } from './helpers/utils'; interface Props { http: CoreStart['http']; chrome: CoreStart['chrome']; - parentBreadcrumb: EuiBreadcrumb[]; + parentBreadcrumbs: EuiBreadcrumb[]; pplService: PPLService; dslService: DSLService; renderProps: RouteComponentProps; @@ -51,7 +52,7 @@ interface Props { export const Home = ({ http, chrome, - parentBreadcrumb, + parentBreadcrumbs, pplService, dslService, renderProps, @@ -98,7 +99,7 @@ export const Home = ({ }) .then(async (res) => { setToast(`Operational Panel "${newCustomPanelName}" successfully created!`); - window.location.assign(`${_.last(parentBreadcrumb).href}${res.newPanelId}`); + window.location.assign(`${_.last(parentBreadcrumbs)!.href}${res.newPanelId}`); }) .catch((err) => { setToast( @@ -116,7 +117,7 @@ export const Home = ({ const renameCustomPanel = (editedCustomPanelName: string, editedCustomPanelId: string) => { if (!isNameValid(editedCustomPanelName)) { setToast('Invalid Custom Panel name', 'danger'); - return; + return Promise.reject(); } const renamePanelObject = { panelId: editedCustomPanelId, @@ -304,7 +305,7 @@ export const Home = ({ customPanels={customPanelData} createCustomPanel={createCustomPanel} setBreadcrumbs={chrome.setBreadcrumbs} - parentBreadcrumb={parentBreadcrumb} + parentBreadcrumbs={parentBreadcrumbs} renameCustomPanel={renameCustomPanel} cloneCustomPanel={cloneCustomPanel} deleteCustomPanelList={deleteCustomPanelList} @@ -324,7 +325,7 @@ export const Home = ({ pplService={pplService} dslService={dslService} chrome={chrome} - parentBreadcrumb={parentBreadcrumb} + parentBreadcrumbs={parentBreadcrumbs} renameCustomPanel={renameCustomPanel} cloneCustomPanel={cloneCustomPanel} deleteCustomPanel={deleteCustomPanel} diff --git a/dashboards-observability/public/components/explorer/event_analytics.tsx b/dashboards-observability/public/components/explorer/event_analytics.tsx index 8187d97d3..7690e474d 100644 --- a/dashboards-observability/public/components/explorer/event_analytics.tsx +++ b/dashboards-observability/public/components/explorer/event_analytics.tsx @@ -16,7 +16,7 @@ import { LogExplorer } from './log_explorer'; export const EventAnalytics = ({ chrome, - parentBreadcrumb, + parentBreadcrumbs, pplService, dslService, savedObjects, @@ -63,9 +63,9 @@ export const EventAnalytics = ({ { + render={(prop) => { chrome.setBreadcrumbs([ - parentBreadcrumb, + ...parentBreadcrumbs, eventAnalyticsBreadcrumb, { text: 'Explorer', @@ -74,7 +74,7 @@ export const EventAnalytics = ({ ]); return ( { + render={() => { chrome.setBreadcrumbs([ - parentBreadcrumb, + ...parentBreadcrumbs, eventAnalyticsBreadcrumb, { text: 'Home', diff --git a/dashboards-observability/public/components/explorer/explorer.tsx b/dashboards-observability/public/components/explorer/explorer.tsx index 957c22415..5b3f3244f 100644 --- a/dashboards-observability/public/components/explorer/explorer.tsx +++ b/dashboards-observability/public/components/explorer/explorer.tsx @@ -42,7 +42,6 @@ import { SELECTED_TIMESTAMP, AVAILABLE_FIELDS, TIME_INTERVAL_OPTIONS, - HAS_SAVED_TIMESTAMP, SAVED_QUERY, SAVED_VISUALIZATION, SAVED_OBJECT_ID, diff --git a/dashboards-observability/public/components/explorer/log_explorer.tsx b/dashboards-observability/public/components/explorer/log_explorer.tsx index f16931fdf..dd10d70a7 100644 --- a/dashboards-observability/public/components/explorer/log_explorer.tsx +++ b/dashboards-observability/public/components/explorer/log_explorer.tsx @@ -2,6 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +/* eslint-disable react-hooks/exhaustive-deps */ import './log_explorer.scss'; import React, { useEffect, useMemo, useRef, useState } from 'react'; @@ -51,7 +52,7 @@ export const LogExplorer = ({ }: ILogExplorerProps) => { const dispatch = useDispatch(); const tabIds = useSelector(selectQueryTabs).queryTabIds.filter( - (tabid: string) => !tabid.startsWith('application-analytics-tab') + (tabid: string) => !tabid.match(APP_ANALYTICS_TAB_ID_REGEX) ); const tabNames = useSelector(selectQueryTabs).tabNames; const queries = useSelector(selectQueries); @@ -143,11 +144,11 @@ export const LogExplorer = ({ function getQueryTab({ tabTitle, tabId, - handleTabClose, + handlesTabClose, }: { tabTitle: string; tabId: string; - handleTabClose: (TabIdToBeClosed: string) => void; + handlesTabClose: (TabIdToBeClosed: string) => void; }) { return { id: tabId, @@ -159,7 +160,7 @@ export const LogExplorer = ({ type="cross" onClick={(e) => { e.stopPropagation(); - handleTabClose(tabId); + handlesTabClose(tabId); }} data-test-subj="eventExplorer__tabClose" /> @@ -191,13 +192,11 @@ export const LogExplorer = ({ const memorizedTabs = useMemo(() => { const res = map(tabIds, (tabId) => { - if (!tabId.match(APP_ANALYTICS_TAB_ID_REGEX)) { - return getQueryTab({ - tabTitle: tabNames[tabId] || TAB_TITLE, - tabId, - handleTabClose, - }); - } + return getQueryTab({ + tabTitle: tabNames[tabId] || TAB_TITLE, + tabId, + handlesTabClose: handleTabClose, + }); }); return res; diff --git a/dashboards-observability/public/components/index.tsx b/dashboards-observability/public/components/index.tsx index f9c9300a8..267cb6b5d 100644 --- a/dashboards-observability/public/components/index.tsx +++ b/dashboards-observability/public/components/index.tsx @@ -10,9 +10,9 @@ import { AppPluginStartDependencies } from '../types'; import { App } from './app'; export const Observability = ( - CoreStart: CoreStart, + CoreStartProp: CoreStart, DepsStart: AppPluginStartDependencies, - AppMountParameters: AppMountParameters, + AppMountParametersProp: AppMountParameters, pplService: any, dslService: any, savedObjects: any, @@ -20,15 +20,15 @@ export const Observability = ( ) => { ReactDOM.render( , - AppMountParameters.element + AppMountParametersProp.element ); - return () => ReactDOM.unmountComponentAtNode(AppMountParameters.element); + return () => ReactDOM.unmountComponentAtNode(AppMountParametersProp.element); }; diff --git a/dashboards-observability/public/components/notebooks/components/main.tsx b/dashboards-observability/public/components/notebooks/components/main.tsx index 971bcc666..9ba18c8e7 100644 --- a/dashboards-observability/public/components/notebooks/components/main.tsx +++ b/dashboards-observability/public/components/notebooks/components/main.tsx @@ -2,13 +2,15 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +/* eslint-disable no-console */ import { EuiGlobalToastList, EuiLink } from '@elastic/eui'; import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; -import PPLService from '../../../services/requests/ppl'; import React, { ReactChild } from 'react'; +// eslint-disable-next-line @osd/eslint/module_migration import { Route, Switch } from 'react-router'; import { HashRouter, RouteComponentProps } from 'react-router-dom'; +import PPLService from '../../../services/requests/ppl'; import { ChromeBreadcrumb, CoreStart } from '../../../../../../src/core/public'; import { DashboardStart } from '../../../../../../src/plugins/dashboard/public'; import { @@ -39,19 +41,19 @@ type MainProps = RouteComponentProps & { setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; }; -type MainState = { - data: Array; +interface MainState { + data: NotebookType[]; openedNotebook: NotebookType | undefined; toasts: Toast[]; loading: boolean; -}; +} -export type NotebookType = { +export interface NotebookType { path: string; id: string; dateCreated: string; dateModified: string; -}; +} export class Main extends React.Component { constructor(props: Readonly) { @@ -272,7 +274,7 @@ export class Main extends React.Component { body: JSON.stringify({ visIds }), }) .then((res) => { - const newData = res.body.map((notebook) => ({ + const newData = res.body.map((notebook: any) => ({ path: notebook.name, id: notebook.id, dateCreated: notebook.dateCreated, @@ -283,7 +285,7 @@ export class Main extends React.Component { })); }); this.setToast(`Sample notebooks successfully added.`); - } catch (err) { + } catch (err: any) { this.setToast('Error adding sample notebooks.', 'danger'); console.error(err.body.message); } finally { diff --git a/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/dashboard.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/dashboard.test.tsx.snap index 962c11fa0..b28ee37d9 100644 --- a/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/dashboard.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/__snapshots__/dashboard.test.tsx.snap @@ -3,6 +3,18 @@ exports[`Dashboard component renders dashboard 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} > -
- - - + + + +
-
- - - - - - - - -
- -
- - - + + + +
+ + + + + +
- + + + +
-
- -
- - -
- - Latency by trace group - - - (0) - -
-
-
-
-
- -
+ Latency by trace group + + + (0) + +
+ + +
+ + - - + +
+ + +
+ + +
+ + - -
-
- + â–  + + >= 95 percentile +
+ + + +
+ + +
+ +
+ + +
+ + +
+
+ +
- - + + No data matches the selected filter. Clear the filter and/or increase the time range to see more results. + + } + title={ +

+ No matches +

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

- No matches -

- } + className="euiFlexItem euiFlexItem--flexGrow4" > -
- - +
- -

- No matches -

-
+
+ + Service map + +
+ + @@ -1128,620 +1302,530 @@ exports[`Dashboard component renders dashboard 1`] = ` className="euiSpacer euiSpacer--m" /> - -
- -
- No data matches the selected filter. Clear the filter and/or increase the time range to see more results. -
-
-
-
- - -
- - -
- - -
- - - -
- - -
- -
- - -
- - -
- - 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. +
+
+
+
+ + +
+ + +
+ + +
-
- - -
- - -
- + +
+
+ +
-
- -
- + +
- -
- - -
- - Trace error rate over time - -
-
-
- -
-
- - + Trace error rate over time + +
+ + + -
- - - No data matches the selected filter. Clear the filter and/or increase the time range to see more results. - - } - title={ -

- No matches -

- } + + -
+
+ + + No 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. +
+
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
- -
- - -
- - Traces over time - -
-
-
- -
-
- - + Traces over time + +
+ + + -
- - - No data matches the selected filter. Clear the filter and/or increase the time range to see more results. - - } - title={ -

- No matches -

- } + + -
+
+ + + No 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[`Dashboard component renders empty dashboard 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} > -
- - - + + + +
-
- - - -
- -
- - - - -
- - - - 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 -

- } + + + +
+ +
+ + + +
+ + + + 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. +
+
+
+
+ + + +
+ + +
+ + - - - -
- - + + + + +
+ + + `; diff --git a/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/dashboard.test.tsx b/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/dashboard.test.tsx index 982af9f2b..14b67f290 100644 --- a/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/dashboard.test.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/dashboard/__tests__/dashboard.test.tsx @@ -23,10 +23,22 @@ describe('Dashboard component', () => { { ({}); - const [serviceMapIdSelected, setServiceMapIdSelected] = useState< - 'latency' | 'error_rate' | 'throughput' - >('latency'); - const [percentileMap, setPercentileMap] = useState<{ [traceGroup: string]: number[] }>({}); - const [filteredService, setFilteredService] = useState(''); - const [redirect, setRedirect] = useState(true); - const [loading, setLoading] = useState(false); - const appOverview = page === 'app'; - - const breadCrumbs = appOverview - ? [ - { - text: 'Application analytics', - href: '#/application_analytics', - }, - { - text: `${appName}`, - href: `#/application_analytics/${appId}`, - }, - ] - : [ - { - text: 'Trace analytics', - href: '#/trace_analytics/home', - }, - { - text: 'Dashboards', - href: '#/trace_analytics/home', - }, - ]; - - useEffect(() => { - props.chrome.setBreadcrumbs([parentBreadcrumb, ...breadCrumbs]); - const validFilters = getValidFilterFields(page); - props.setFilters([ - ...props.filters.map((filter) => ({ - ...filter, - locked: validFilters.indexOf(filter.field) === -1, - })), - ]); - setRedirect(false); - if (appOverview) { - 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.startTime, props.endTime, props.appConfigs]); - - const refresh = async (currService?: string) => { - setLoading(true); - const DSL = filtersToDsl( - props.filters, - props.query, - props.startTime, - props.endTime, - page, - appOverview ? props.appConfigs : [] - ); - const timeFilterDSL = filtersToDsl([], '', props.startTime, props.endTime, page); - const latencyTrendStartTime = dateMath - .parse(props.endTime) - ?.subtract(24, 'hours') - .toISOString()!; - const latencyTrendDSL = filtersToDsl( - appOverview ? props.appConfigs : props.filters, - props.query, - latencyTrendStartTime, - props.endTime, - page - ); - const fixedInterval = minFixedInterval(props.startTime, props.endTime); - - handleDashboardRequest( - props.http, - DSL, - timeFilterDSL, - latencyTrendDSL, - tableItems, - setTableItems, - setPercentileMap - ).then(() => setLoading(false)); - handleDashboardThroughputPltRequest( - props.http, - DSL, - fixedInterval, - throughputPltItems, - setThroughputPltItems - ); - handleDashboardErrorRatePltRequest( - props.http, - DSL, - fixedInterval, - errorRatePltItems, - setErrorRatePltItems - ); - // service map should not be filtered by service name (https://github.com/opensearch-project/observability/issues/442) - const serviceMapDSL = _.cloneDeep(DSL); - serviceMapDSL.query.bool.must = serviceMapDSL.query.bool.must.filter( - (must: any) => must?.term?.serviceName == null - ); - handleServiceMapRequest( - props.http, - serviceMapDSL, - serviceMap, - setServiceMap, - currService || filteredService - ); - }; - - 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 addPercentileFilter = (condition = 'gte', additionalFilters = [] as FilterType[]) => { - if (tableItems.length === 0 || Object.keys(percentileMap).length === 0) return; - for (let i = 0; i < props.filters.length; i++) { - if (props.filters[i].custom) { - const newFilter = JSON.parse(JSON.stringify(props.filters[i])); - newFilter.custom.query.bool.should.forEach((should: any) => - should.bool.must.forEach((must: any) => { - const range = must?.range?.['traceGroupFields.durationInNanos']; - if (range) { - const duration = range.lt || range.lte || range.gt || range.gte; - if (duration || duration === 0) { - must.range['traceGroupFields.durationInNanos'] = { - [condition]: duration, - }; - } - } - }) - ); - newFilter.value = condition === 'gte' ? '>= 95th' : '< 95th'; - const newFilters = [...props.filters, ...additionalFilters]; - newFilters.splice(i, 1, newFilter); - props.setFilters(newFilters); - return; - } - } - - const percentileMaps = Object.keys(percentileMap).map((traceGroup) => ({ - traceGroupName: traceGroup, - durationFilter: { [condition]: milliToNanoSec(percentileMap[traceGroup][1]) }, - })); - const percentileFilter = getPercentileFilter( - percentileMaps, - condition === 'gte' ? '>= 95th' : '< 95th' - ); - const newFilters = [...props.filters, percentileFilter, ...additionalFilters]; - props.setFilters(newFilters); - }; - return ( <> - {appOverview ? ( - - ) : ( - -

Dashboard

-
- )} - - - {props.indicesExist ? ( - <> - - - - - - - - - - - - - - - - - - - ) : ( - - )} + +

Dashboard

+
+ ); } 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 new file mode 100644 index 000000000..50f2aa042 --- /dev/null +++ b/dashboards-observability/public/components/trace_analytics/components/dashboard/dashboard_content.tsx @@ -0,0 +1,256 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable react-hooks/exhaustive-deps */ + +import dateMath from '@elastic/datemath'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { + handleDashboardErrorRatePltRequest, + handleDashboardRequest, + handleDashboardThroughputPltRequest, +} from '../../requests/dashboard_request_handler'; +import { handleServiceMapRequest } from '../../requests/services_request_handler'; +import { FilterType } from '../common/filters/filters'; +import { getValidFilterFields } from '../common/filters/filter_helpers'; +import { + filtersToDsl, + getPercentileFilter, + milliToNanoSec, + minFixedInterval, + MissingConfigurationMessage, +} from '../common/helper_functions'; +import { ErrorRatePlt } from '../common/plots/error_rate_plt'; +import { ServiceMap, ServiceObject } from '../common/plots/service_map'; +import { ThroughputPlt } from '../common/plots/throughput_plt'; +import { SearchBar } from '../common/search_bar'; +import { DashboardProps } from './dashboard'; +import { DashboardTable } from './dashboard_table'; + +export function DashboardContent(props: DashboardProps) { + const { + http, + chrome, + page, + query, + appConfigs, + startTime, + endTime, + childBreadcrumbs, + parentBreadcrumbs, + filters, + indicesExist, + setStartTime, + setEndTime, + setQuery, + setFilters, + } = props; + const [tableItems, setTableItems] = useState([]); + const [throughputPltItems, setThroughputPltItems] = useState({ items: [], fixedInterval: '1h' }); + const [errorRatePltItems, setErrorRatePltItems] = useState({ items: [], fixedInterval: '1h' }); + const [serviceMap, setServiceMap] = useState({}); + const [serviceMapIdSelected, setServiceMapIdSelected] = useState< + 'latency' | 'error_rate' | 'throughput' + >('latency'); + const [percentileMap, setPercentileMap] = useState<{ [traceGroup: string]: number[] }>({}); + const [filteredService, setFilteredService] = useState(''); + const [redirect, setRedirect] = useState(true); + const [loading, setLoading] = useState(false); + + useEffect(() => { + chrome.setBreadcrumbs([...parentBreadcrumbs, ...childBreadcrumbs]); + const validFilters = getValidFilterFields(page); + 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, startTime, endTime, appConfigs]); + + const refresh = async (currService?: string) => { + setLoading(true); + const DSL = filtersToDsl(filters, query, startTime, endTime, page, appConfigs); + const timeFilterDSL = filtersToDsl([], '', startTime, endTime, page); + const latencyTrendStartTime = dateMath.parse(endTime)?.subtract(24, 'hours').toISOString()!; + const latencyTrendDSL = filtersToDsl( + filters, + query, + latencyTrendStartTime, + endTime, + page, + appConfigs + ); + const fixedInterval = minFixedInterval(startTime, endTime); + + handleDashboardRequest( + http, + DSL, + timeFilterDSL, + latencyTrendDSL, + tableItems, + setTableItems, + setPercentileMap + ).then(() => setLoading(false)); + handleDashboardThroughputPltRequest( + http, + DSL, + fixedInterval, + throughputPltItems, + setThroughputPltItems + ); + handleDashboardErrorRatePltRequest( + http, + DSL, + fixedInterval, + errorRatePltItems, + setErrorRatePltItems + ); + // service map should not be filtered by service name (https://github.com/opensearch-project/observability/issues/442) + const serviceMapDSL = _.cloneDeep(DSL); + serviceMapDSL.query.bool.must = serviceMapDSL.query.bool.must.filter( + (must: any) => must?.term?.serviceName == null + ); + handleServiceMapRequest( + http, + serviceMapDSL, + serviceMap, + setServiceMap, + currService || filteredService + ); + }; + + 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 addPercentileFilter = (condition = 'gte', additionalFilters = [] as FilterType[]) => { + if (tableItems.length === 0 || Object.keys(percentileMap).length === 0) return; + for (let i = 0; i < filters.length; i++) { + if (filters[i].custom) { + const newFilter = JSON.parse(JSON.stringify(filters[i])); + newFilter.custom.query.bool.should.forEach((should: any) => + should.bool.must.forEach((must: any) => { + const range = must?.range?.['traceGroupFields.durationInNanos']; + if (range) { + const duration = range.lt || range.lte || range.gt || range.gte; + if (duration || duration === 0) { + must.range['traceGroupFields.durationInNanos'] = { + [condition]: duration, + }; + } + } + }) + ); + newFilter.value = condition === 'gte' ? '>= 95th' : '< 95th'; + const newFilters = [...filters, ...additionalFilters]; + newFilters.splice(i, 1, newFilter); + setFilters(newFilters); + return; + } + } + + const percentileMaps = Object.keys(percentileMap).map((traceGroup) => ({ + traceGroupName: traceGroup, + durationFilter: { [condition]: milliToNanoSec(percentileMap[traceGroup][1]) }, + })); + const percentileFilter = getPercentileFilter( + percentileMaps, + condition === 'gte' ? '>= 95th' : '< 95th' + ); + const newFilters = [...filters, percentileFilter, ...additionalFilters]; + setFilters(newFilters); + }; + + return ( + <> + + + {indicesExist ? ( + <> + + + + + + + + + + + + + + + + + + + ) : ( + + )} + + ); +} 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 25445b00a..f18ff21d9 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 @@ -39,11 +39,13 @@ exports[`Services component renders empty services page 1`] = ` http={[MockFunction]} indicesExist={false} page="services" - parentBreadcrumb={ - Object { - "href": "test#/", - "text": "test", - } + parentBreadcrumbs={ + Array [ + Object { + "href": "test#/", + "text": "test", + }, + ] } query="" setEndTime={[MockFunction]} @@ -1699,11 +1701,13 @@ exports[`Services component renders services page 1`] = ` http={[MockFunction]} indicesExist={true} page="services" - parentBreadcrumb={ - Object { - "href": "test#/", - "text": "test", - } + parentBreadcrumbs={ + Array [ + Object { + "href": "test#/", + "text": "test", + }, + ] } query="" setEndTime={[MockFunction]} diff --git a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/service_view.test.tsx b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/service_view.test.tsx index f98e6bb7b..b48055548 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/__tests__/service_view.test.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/services/__tests__/service_view.test.tsx @@ -23,7 +23,8 @@ describe('Service view component', () => { { { { props.chrome.setBreadcrumbs([ - props.parentBreadcrumb, + ...props.parentBreadcrumbs, { text: 'Trace analytics', href: '#/trace_analytics/home', 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 178c279b5..da22c7064 100644 --- a/dashboards-observability/public/components/trace_analytics/components/services/services.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/services/services.tsx @@ -2,6 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +/* eslint-disable react-hooks/exhaustive-deps */ import { EuiSpacer, EuiTitle } from '@elastic/eui'; import _ from 'lodash'; @@ -28,7 +29,7 @@ interface ServicesProps extends TraceAnalyticsComponentDeps { } export function Services(props: ServicesProps) { - const { appId, appName, parentBreadcrumb, page, switchToEditViz } = props; + const { appId, appName, parentBreadcrumbs, page, switchToEditViz } = props; const [tableItems, setTableItems] = useState([]); const [serviceMap, setServiceMap] = useState({}); const [serviceMapIdSelected, setServiceMapIdSelected] = useState< @@ -62,7 +63,7 @@ export function Services(props: ServicesProps) { ]; useEffect(() => { - props.chrome.setBreadcrumbs([parentBreadcrumb, ...breadCrumbs]); + props.chrome.setBreadcrumbs([...parentBreadcrumbs, ...breadCrumbs]); const validFilters = getValidFilterFields('services'); props.setFilters([ ...props.filters.map((filter) => ({ @@ -119,9 +120,8 @@ export function Services(props: ServicesProps) { 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; + 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); diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces.test.tsx.snap index 2d06eee82..cab39bbf2 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/__snapshots__/traces.test.tsx.snap @@ -39,11 +39,13 @@ exports[`Traces component renders empty traces page 1`] = ` http={[MockFunction]} indicesExist={false} page="traces" - parentBreadcrumb={ - Object { - "href": "test#/", - "text": "test", - } + parentBreadcrumbs={ + Array [ + Object { + "href": "test#/", + "text": "test", + }, + ] } query="" setEndTime={[MockFunction]} @@ -1159,11 +1161,13 @@ exports[`Traces component renders traces page 1`] = ` http={[MockFunction]} indicesExist={true} page="traces" - parentBreadcrumb={ - Object { - "href": "test#/", - "text": "test", - } + parentBreadcrumbs={ + Array [ + Object { + "href": "test#/", + "text": "test", + }, + ] } query="" setEndTime={[MockFunction]} diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/trace_view.test.tsx b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/trace_view.test.tsx index d3e514ce8..3d334e25c 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/trace_view.test.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/trace_view.test.tsx @@ -18,7 +18,7 @@ describe('Trace view component', () => { ); diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx index 6ada90e0d..363d7851e 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/traces/__tests__/traces.test.tsx @@ -22,7 +22,7 @@ describe('Traces component', () => { { { expect(wrapper).toMatchSnapshot(); }); }); - 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 68b0ef8ef..67cf5f4e1 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 @@ -186,7 +186,7 @@ export function TraceView(props: TraceViewProps) { useEffect(() => { props.chrome.setBreadcrumbs([ - props.parentBreadcrumb, + ...props.parentBreadcrumbs, { text: 'Trace analytics', href: '#/trace_analytics/home', diff --git a/dashboards-observability/public/components/trace_analytics/components/traces/traces.tsx b/dashboards-observability/public/components/trace_analytics/components/traces/traces.tsx index 5b7a77692..6841d068e 100644 --- a/dashboards-observability/public/components/trace_analytics/components/traces/traces.tsx +++ b/dashboards-observability/public/components/trace_analytics/components/traces/traces.tsx @@ -22,7 +22,7 @@ interface TracesProps extends TraceAnalyticsComponentDeps { } export function Traces(props: TracesProps) { - const { appId, appName, parentBreadcrumb, page, openTraceFlyout, switchToEditViz } = props; + const { appId, appName, parentBreadcrumbs, page, openTraceFlyout, switchToEditViz } = props; const [tableItems, setTableItems] = useState([]); const [redirect, setRedirect] = useState(true); const [loading, setLoading] = useState(false); @@ -51,7 +51,7 @@ export function Traces(props: TracesProps) { ]; useEffect(() => { - props.chrome.setBreadcrumbs([parentBreadcrumb, ...breadCrumbs]); + props.chrome.setBreadcrumbs([...parentBreadcrumbs, ...breadCrumbs]); const validFilters = getValidFilterFields('traces'); props.setFilters([ ...props.filters.map((filter) => ({ diff --git a/dashboards-observability/public/components/trace_analytics/home.tsx b/dashboards-observability/public/components/trace_analytics/home.tsx index b1399392d..af94f16a8 100644 --- a/dashboards-observability/public/components/trace_analytics/home.tsx +++ b/dashboards-observability/public/components/trace_analytics/home.tsx @@ -20,7 +20,7 @@ import { Traces, TraceView } from './components/traces'; import { handleIndicesExistRequest } from './requests/request_handler'; export interface TraceAnalyticsCoreDeps { - parentBreadcrumb: ChromeBreadcrumb; + parentBreadcrumbs: ChromeBreadcrumb[]; http: HttpStart; chrome: ChromeStart; } @@ -66,8 +66,19 @@ export const Home = (props: HomeProps) => { handleIndicesExistRequest(props.http, setIndicesExist); }, []); + const childBreadcrumbs = [ + { + text: 'Trace analytics', + href: '#/trace_analytics/home', + }, + { + text: 'Dashboards', + href: '#/trace_analytics/home', + }, + ]; + const commonProps: TraceAnalyticsComponentDeps = { - parentBreadcrumb: props.parentBreadcrumb, + parentBreadcrumbs: props.parentBreadcrumbs, http: props.http, chrome: props.chrome, query, @@ -89,7 +100,7 @@ export const Home = (props: HomeProps) => { path={['/trace_analytics', '/trace_analytics/home']} render={(routerProps) => ( - + )} /> @@ -106,7 +117,7 @@ export const Home = (props: HomeProps) => { path="/trace_analytics/traces/:id+" render={(routerProps) => ( console.error(error)); }