diff --git a/.cypress/integration/3_panels.spec.ts b/.cypress/integration/3_panels.spec.ts index 4fb9cf69c6..6125671dcc 100644 --- a/.cypress/integration/3_panels.spec.ts +++ b/.cypress/integration/3_panels.spec.ts @@ -110,8 +110,8 @@ describe.only('Testing panels table', () => { expectUuid(duplicate) }); - it.only('Duplicates a legacy panel', () => { - createLegacyPanel() + it.only('Duplicates a saved object panel', () => { + createSavedObjectPanel() selectThePanel(); openActionsDropdown(); cy.get('button[data-test-subj="duplicateContextMenuItem"]').click(); @@ -516,13 +516,13 @@ describe('Clean up all test data', () => { const moveToEventsHome = () => { - cy.visit(`${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/event_analytics/`); + cy.visit(`${Cypress.env('opensearchDashboards')}/app/observability-events#/`); cy.wait(delay * 3); }; const moveToPanelHome = () => { cy.visit( - `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels/` + `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/` , { timeout: 3000 }); cy.wait(delay * 3); }; diff --git a/common/constants/shared.ts b/common/constants/shared.ts index f1e936e123..39d84cae74 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -27,6 +27,30 @@ export const observabilityID = 'observability-dashboards'; export const observabilityTitle = 'Observability'; export const observabilityPluginOrder = 6000; +export const observabilityApplicationsID = 'observability-applications'; +export const observabilityApplicationsTitle = 'Applications'; +export const observabilityApplicationsPluginOrder = 5090; + +export const observabilityLogsID = 'observability-logs'; +export const observabilityLogsTitle = 'Logs'; +export const observabilityLogsPluginOrder = 5091; + +export const observabilityMetricsID = 'observability-metrics'; +export const observabilityMetricsTitle = 'Metrics'; +export const observabilityMetricsPluginOrder = 5092; + +export const observabilityTracesID = 'observability-traces'; +export const observabilityTracesTitle = 'Traces'; +export const observabilityTracesPluginOrder = 5093; + +export const observabilityNotebookID = 'observability-notebooks'; +export const observabilityNotebookTitle = 'Notebooks'; +export const observabilityNotebookPluginOrder = 5094; + +export const observabilityPanelsID = 'observability-dashboards'; +export const observabilityPanelsTitle = 'Dashboards'; +export const observabilityPanelsPluginOrder = 5095; + // Shared Constants export const SQL_DOCUMENTATION_URL = 'https://opensearch.org/docs/latest/search-plugins/sql/index/'; export const PPL_DOCUMENTATION_URL = diff --git a/common/types/explorer.ts b/common/types/explorer.ts index 53dfe2ca20..6a5ef9d037 100644 --- a/common/types/explorer.ts +++ b/common/types/explorer.ts @@ -37,6 +37,7 @@ import { SavedObjectAttributes, SavedObjectsStart, } from '../../../../src/core/public/saved_objects'; +import { ChromeBreadcrumb } from '../../../../src/core/public/chrome'; export interface IQueryTab { id: string; @@ -318,7 +319,7 @@ export interface Breadcrumbs { export interface EventAnalyticsProps { chrome: CoreSetup; - parentBreadcrumbs: Breadcrumbs[]; + parentBreadcrumbs: ChromeBreadcrumb; pplService: any; dslService: any; savedObjects: SavedObjectsStart; @@ -326,6 +327,7 @@ export interface EventAnalyticsProps { http: HttpStart; notifications: NotificationsStart; queryManager: QueryManager; + setBreadcrumbs:(newBreadcrumbs: ChromeBreadcrumb[]) => void; } export interface DataConfigPanelProps { diff --git a/public/components/app.tsx b/public/components/app.tsx index cadbdb8e36..519c06c6ce 100644 --- a/public/components/app.tsx +++ b/public/components/app.tsx @@ -6,9 +6,10 @@ import { I18nProvider } from '@osd/i18n/react'; import { QueryManager } from 'common/query_manager'; import React from 'react'; +import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { HashRouter, Route, Switch } from 'react-router-dom'; -import { CoreStart } from '../../../../src/core/public'; +import { AppMountParameters, CoreStart } from '../../../../src/core/public'; import { observabilityID, observabilityTitle } from '../../common/constants/shared'; import { store } from '../framework/redux/store'; import { AppPluginStartDependencies } from '../types'; @@ -28,6 +29,8 @@ interface ObservabilityAppDeps { savedObjects: any; timestampUtils: any; queryManager: QueryManager; + startPage: string; + // mountParams: AppMountParameters; } // for cypress to test redux store @@ -35,6 +38,15 @@ if (window.Cypress) { window.store = store; } +const pages = { + applications: ApplicationAnalyticsHome, + logs: EventAnalytics, + metrics: MetricsHome, + traces: TraceAnalyticsHome, + notebooks: NotebooksHome, + dashboards: CustomPanelsHome, +}; + export const App = ({ CoreStartProp, DepsStart, @@ -43,6 +55,8 @@ export const App = ({ savedObjects, timestampUtils, queryManager, + // mountParams, + startPage, }: ObservabilityAppDeps) => { const { chrome, http, notifications, savedObjects: coreSavedObjects } = CoreStartProp; const parentBreadcrumb = { @@ -55,118 +69,151 @@ export const App = ({ href: '#/operational_panels/', }; + const ModuleComponent = pages[startPage]; + return ( - - - - - { - chrome.setBreadcrumbs([ - parentBreadcrumb, - { text: 'Metrics analytics', href: '#/metrics_analytics/' }, - ]); - return ( - - ); - }} - /> - { - return ( - - ); - }} - /> - ( - - )} - /> - { - chrome.setBreadcrumbs([parentBreadcrumb, customPanelBreadcrumb]); - return ( - - ); - }} - /> - ( - - )} - /> - { - return ( - - ); - }} - /> - - - - + + + + + ); }; + +// // redirect legacy notebooks URL to current URL under observability +// if (window.location.pathname.includes('application_analytics')) { +// window.location.assign(convertLegacyAppAnalyticsUrl(window.location)); +// } + +// return ( +// +// +// +// +// +// { +// chrome.setBreadcrumbs([ +// parentBreadcrumb, +// { text: 'Metrics analytics', href: '#/metrics_analytics/' }, +// ]); +// return ( +// +// ); +// }} +// /> +// { +// return ( +// +// ); +// }} +// /> +// ( +// +// )} +// /> +// { +// chrome.setBreadcrumbs([parentBreadcrumb, customPanelBreadcrumb]); +// return ( +// +// ); +// }} +// /> +// ( +// +// )} +// /> +// { +// return ( +// +// ); +// }} +// /> +// +// +// +// +// +// ); +// }; diff --git a/public/components/application_analytics/components/app_table.tsx b/public/components/application_analytics/components/app_table.tsx index 75fe228af8..19d19a3c8b 100644 --- a/public/components/application_analytics/components/app_table.tsx +++ b/public/components/application_analytics/components/app_table.tsx @@ -37,7 +37,12 @@ import moment from 'moment'; import { DeleteModal } from '../../common/helpers/delete_modal'; import { AppAnalyticsComponentDeps } from '../home'; import { getCustomModal } from '../../custom_panels/helpers/modal_containers'; -import { pageStyles, UI_DATE_FORMAT } from '../../../../common/constants/shared'; +import { + observabilityID, + observabilityTitle, + pageStyles, + UI_DATE_FORMAT, +} from '../../../../common/constants/shared'; import { ApplicationType, AvailabilityType } from '../../../../common/types/application_analytics'; interface AppTableProps extends AppAnalyticsComponentDeps { @@ -70,10 +75,13 @@ export function AppTable(props: AppTableProps) { useEffect(() => { chrome.setBreadcrumbs([ - ...parentBreadcrumbs, { - text: 'Application analytics', - href: '#/application_analytics', + text: observabilityTitle, + href: `${observabilityID}#/`, + }, + { + text: 'Applications', + href: '#/', }, ]); clear(); @@ -216,10 +224,7 @@ export function AppTable(props: AppTableProps) { sortable: true, truncateText: true, render: (value, record) => ( - + {_.truncate(record.name, { length: 100 })} ), @@ -284,7 +289,7 @@ export function AppTable(props: AppTableProps) { - + {createButtonText} @@ -324,7 +329,7 @@ export function AppTable(props: AppTableProps) { - + {createButtonText} diff --git a/public/components/application_analytics/components/application.tsx b/public/components/application_analytics/components/application.tsx index b758942583..0f86072250 100644 --- a/public/components/application_analytics/components/application.tsx +++ b/public/components/application_analytics/components/application.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ -/* eslint-disable no-console */ import { EuiHorizontalRule, @@ -214,7 +213,7 @@ export function Application(props: AppDetailProps) { callback(switchToEvent); }, [appId]); - useEffect(() => { + /* useEffect(() => { chrome.setBreadcrumbs([ ...parentBreadcrumbs, { @@ -228,7 +227,7 @@ export function Application(props: AppDetailProps) { ]); setStartTimeForApp(sessionStorage.getItem(`${application.name}StartTime`) || 'now-24h'); setEndTimeForApp(sessionStorage.getItem(`${application.name}EndTime`) || 'now'); - }, [appId, application.name]); + }, [appId, application.name]); */ useEffect(() => { const DSL = filtersToDsl(mode, filters, query, appStartTime, appEndTime, 'app', appConfigs); @@ -274,11 +273,11 @@ export function Application(props: AppDetailProps) { const childBreadcrumbs = [ { text: 'Application analytics', - href: '#/application_analytics', + href: '#/', }, { text: `${application.name}`, - href: `#/application_analytics/${appId}`, + href: `#/${appId}`, }, ]; @@ -351,7 +350,7 @@ export function Application(props: AppDetailProps) { openFlyout={setSpanFlyoutId} DSL={spanDSL} setTotal={setTotalSpans} - mode='data_prepper' + mode="data_prepper" /> diff --git a/public/components/application_analytics/components/configuration.tsx b/public/components/application_analytics/components/configuration.tsx index 8cb8080ffc..a4346656da 100644 --- a/public/components/application_analytics/components/configuration.tsx +++ b/public/components/application_analytics/components/configuration.tsx @@ -72,9 +72,7 @@ export const Configuration = (props: ConfigProps) => { fill data-test-subj="editApplicationButton" onClick={() => { - window.location.assign( - `${last(parentBreadcrumbs)!.href}application_analytics/edit/${appId}` - ); + window.location.assign(`${last(parentBreadcrumbs)!.href}/edit/${appId}`); }} > Edit diff --git a/public/components/application_analytics/components/create.tsx b/public/components/application_analytics/components/create.tsx index 084d53b17d..9eddacfa09 100644 --- a/public/components/application_analytics/components/create.tsx +++ b/public/components/application_analytics/components/create.tsx @@ -38,6 +38,7 @@ import { OptionType, } from '../../../../common/types/application_analytics'; import { fetchAppById } from '../helpers/utils'; +import { observabilityID, observabilityTitle } from '../../../../common/constants/shared'; interface CreateAppProps extends AppAnalyticsComponentDeps { dslService: DSLService; @@ -88,14 +89,17 @@ export const CreateApp = (props: CreateAppProps) => { useEffect(() => { chrome.setBreadcrumbs([ - ...parentBreadcrumbs, { - text: 'Application analytics', - href: '#/application_analytics', + text: observabilityTitle, + href: `${observabilityID}#/`, + }, + { + text: 'Applications', + href: '#/', }, { text: editMode ? 'Edit' : 'Create', - href: `#/application_analytics/${editMode ? 'edit' : 'create'}`, + href: `#/${editMode ? 'edit' : 'create'}`, }, ]); }, []); diff --git a/public/components/application_analytics/home.tsx b/public/components/application_analytics/home.tsx index 89dec51cfa..3a6260bb34 100644 --- a/public/components/application_analytics/home.tsx +++ b/public/components/application_analytics/home.tsx @@ -3,10 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ -/* eslint-disable no-console */ import React, { ReactChild, useEffect, useState } from 'react'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; import DSLService from 'public/services/requests/dsl'; import PPLService from 'public/services/requests/ppl'; import SavedObjects from 'public/services/saved_objects/event_analytics/saved_objects'; @@ -22,7 +21,7 @@ import { TraceAnalyticsComponentDeps, TraceAnalyticsCoreDeps } from '../trace_an import { FilterType } from '../trace_analytics/components/common/filters/filters'; import { handleDataPrepperIndicesExistRequest } from '../trace_analytics/requests/request_handler'; import { ObservabilitySideBar } from '../common/side_nav'; -import { NotificationsStart } from '../../../../../src/core/public'; +import { ChromeBreadcrumb, NotificationsStart } from '../../../../../src/core/public'; import { APP_ANALYTICS_API_PREFIX } from '../../../common/constants/application_analytics'; import { ApplicationRequestType, @@ -138,7 +137,7 @@ export const Home = (props: HomeProps) => { endTime, setEndTime, mode: 'data_prepper', - dataPrepperIndicesExist: indicesExist + dataPrepperIndicesExist: indicesExist, }; const setToast = (title: string, color = 'success', text?: ReactChild) => { @@ -154,7 +153,7 @@ export const Home = (props: HomeProps) => { }; const moveToApp = (id: string, type: string) => { - window.location.assign(`${last(parentBreadcrumbs)!.href}application_analytics/${id}`); + window.location.assign(`${last(parentBreadcrumbs)!.href}/${id}`); if (type === 'createSetAvailability') { setTriggerSwitchToEvent(2); } @@ -389,12 +388,12 @@ export const Home = (props: HomeProps) => { }} toastLifeTimeMs={6000} /> - - ( - + + + ( { moveToApp={moveToApp} {...commonProps} /> - - )} - /> - ( - - )} - /> - ( - - )} - /> - + )} + /> + ( + + )} + /> + ( + + )} + /> + + ); }; diff --git a/public/components/common/side_nav.tsx b/public/components/common/side_nav.tsx index cf6f467146..687e8b2601 100644 --- a/public/components/common/side_nav.tsx +++ b/public/components/common/side_nav.tsx @@ -24,7 +24,7 @@ export function ObservabilitySideBar(props: { children: React.ReactNode }) { // tries to find an item where href is a prefix of the hash // if none will try to find an item where the hash is a prefix of href function setIsSelected( - items: EuiSideNavItemType[], + items: Array>, hash: string, initial = true, reverse = false @@ -51,48 +51,23 @@ export function ObservabilitySideBar(props: { children: React.ReactNode }) { name: 'Observability', id: 0, items: [ - { - name: 'Application analytics', - id: 1, - href: '#/application_analytics', - }, { name: 'Trace analytics', - id: 2, + id: 1, href: '#/trace_analytics/home', items: [ { name: 'Traces', - id: 2.1, + id: 1.1, href: '#/trace_analytics/traces', }, { name: 'Services', - id: 2.2, + id: 1.2, href: '#/trace_analytics/services', }, ], }, - { - name: 'Event analytics', - id: 3, - href: '#/event_analytics', - }, - { - name: 'Metrics analytics', - id: 4, - href: '#/metrics_analytics/', - }, - { - name: 'Operational panels', - id: 5, - href: '#/operational_panels/', - }, - { - name: 'Notebooks', - id: 6, - href: '#/notebooks', - }, ], }, ]; diff --git a/public/components/custom_panels/home.tsx b/public/components/custom_panels/home.tsx index 2d31001837..8e11096380 100644 --- a/public/components/custom_panels/home.tsx +++ b/public/components/custom_panels/home.tsx @@ -6,12 +6,13 @@ import { EuiBreadcrumb, EuiGlobalToastList, EuiLink, ShortDate } from '@elastic/eui'; import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; import _ from 'lodash'; -import React, { ReactChild, useState } from 'react'; +import React, { ReactChild, useEffect, useState } from 'react'; // eslint-disable-next-line @osd/eslint/module_migration import { StaticContext } from 'react-router'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { map, mergeMap, tap, toArray } from 'rxjs/operators'; import { concat, from, Observable, of } from 'rxjs'; +import { useDispatch } from 'react-redux'; import PPLService from '../../services/requests/ppl'; import DSLService from '../../services/requests/dsl'; import { CoreStart, SavedObjectsStart } from '../../../../../src/core/public'; @@ -35,12 +36,9 @@ import { ObservabilitySideBar } from '../common/side_nav'; import { CustomPanelTable } from './custom_panel_table'; import { CustomPanelView } from './custom_panel_view'; import { isNameValid } from './helpers/utils'; -import { SavedObject } from '../../../../../src/core/types'; import { CustomPanelViewSO } from './custom_panel_view_so'; import { coreRefs } from '../../framework/core_refs'; -import { CustomPanelType } from '../../../common/types/custom_panels'; import { fetchPanels } from './redux/panel_slice'; -import { useDispatch } from 'react-redux'; // import { ObjectFetcher } from '../common/objectFetcher'; @@ -52,13 +50,15 @@ import { useDispatch } from 'react-redux'; * chrome: chrome core service; * parentBreadcrumb: parent breadcrumb name and link * pplService: ppl requestor service - * renderProps: Props from router + * renderProps: Props from router of parent component + * - Used to calculate path when this component embedded into another (WHY?!) */ interface PanelHomeProps { http: CoreStart['http']; chrome: CoreStart['chrome']; parentBreadcrumbs: EuiBreadcrumb[]; + setBreadcrumbs: (newBreadcrumbs: EuiBreadcrumb[]) => void; pplService: PPLService; dslService: DSLService; renderProps: RouteComponentProps; @@ -73,6 +73,7 @@ export const Home = ({ dslService, renderProps, coreSavedObjects, + setBreadcrumbs, }: PanelHomeProps) => { const [toasts, setToasts] = useState([]); const [loading, setLoading] = useState(false); @@ -80,7 +81,15 @@ export const Home = ({ const [start, setStart] = useState(''); const [end, setEnd] = useState(''); - const dispatch = useDispatch() + const dispatch = useDispatch(); + + const customPanelBreadCrumbs = [ + ...parentBreadcrumbs, + { + text: 'Dashboards', + href: `${_.last(parentBreadcrumbs)!.href}`, + }, + ]; const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => { if (!text) text = ''; @@ -138,7 +147,6 @@ export const Home = ({ const isUuid = (id) => !!id.match(uuidRx); - const fetchSavedObjectPanel = async (id: string) => { const soPanel = await coreRefs.savedObjectsClient?.get(CUSTOM_PANELS_SAVED_OBJECT_TYPE, id); return savedObjectToCustomPanel(soPanel); @@ -167,19 +175,15 @@ export const Home = ({ try { // const panelToClone = await fetchPanelfn(clonedCustomPanelId) - // const newPanel: PanelType = { // ...panelToClone, // title: clonedCustomPanelName, // dateCreated: new Date().getTime(), // dateModified: new Date().getTime() // } - // const clonedPanel: CustomPanelType = await coreRefs.savedObjectsClient!.create( // CUSTOM_PANELS_SAVED_OBJECT_TYPE, newPanel, { id: panelToClone.id } // ) - - // setcustomPanelData((prevCustomPanelData) => { // const newPanelData = [ // ...prevCustomPanelData, @@ -301,7 +305,7 @@ export const Home = ({ }), }) .then((res) => { - dispatch(fetchPanels()) + dispatch(fetchPanels()); // setcustomPanelData([...customPanelData, ...res.demoPanelsData]); }); setToast(`Sample panels successfully added.`); @@ -313,8 +317,10 @@ export const Home = ({ } }; + const parentPath = renderProps ? renderProps.match.path : ''; + return ( -
+ { @@ -326,25 +332,23 @@ export const Home = ({ { return ( - - - + ); }} /> { const isSavedObject = !!props.match.params.id.match(uuidRx); @@ -352,7 +356,7 @@ export const Home = ({ -
+ ); }; diff --git a/public/components/event_analytics/explorer/log_explorer.tsx b/public/components/event_analytics/explorer/log_explorer.tsx index ac9aa69bd9..d4edc56b8f 100644 --- a/public/components/event_analytics/explorer/log_explorer.tsx +++ b/public/components/event_analytics/explorer/log_explorer.tsx @@ -93,7 +93,7 @@ export const LogExplorer = ({ const handleTabClick = (selectedTab: EuiTabbedContentTab) => { history.replace( - `/event_analytics/explorer/${queryRef.current![selectedTab.id][SAVED_OBJECT_ID] || ''}` + `/${queryRef.current![selectedTab.id][SAVED_OBJECT_ID] || ''}` ); dispatch(setSelectedQueryTab({ tabId: selectedTab.id })); }; diff --git a/public/components/event_analytics/home/home.tsx b/public/components/event_analytics/home/home.tsx index 69df9f9441..4ffd0f8883 100644 --- a/public/components/event_analytics/home/home.tsx +++ b/public/components/event_analytics/home/home.tsx @@ -197,7 +197,7 @@ const EventAnalyticsHome = (props: IHomeProps) => { await dispatchInitialData(newTabId); // redirect to explorer - history.push('/event_analytics/explorer'); + history.push('/'); }; const handleQueryChange = async (query: string) => setSearchQuery(query); @@ -223,7 +223,7 @@ const EventAnalyticsHome = (props: IHomeProps) => { dispatch(setSelectedQueryTab({ tabId: newTabId })); }); // redirect to explorer - history.push(`/event_analytics/explorer/${objectId}`); + history.push(`/${objectId}`); }; const addSampledata = async () => { @@ -334,7 +334,7 @@ const EventAnalyticsHome = (props: IHomeProps) => { key="redirect" onClick={() => { setIsActionsPopoverOpen(false); - history.push(`/event_analytics/explorer`); + history.push(`/`); }} data-test-subj="eventHomeAction__explorer" > @@ -451,7 +451,7 @@ const EventAnalyticsHome = (props: IHomeProps) => { history.push(`/event_analytics/explorer`)} + onClick={() => history.push(`/`)} data-test-subj="actionEventExplorer" > Event Explorer diff --git a/public/components/event_analytics/index.tsx b/public/components/event_analytics/index.tsx index 711b891d12..934ba609d6 100644 --- a/public/components/event_analytics/index.tsx +++ b/public/components/event_analytics/index.tsx @@ -3,12 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ +import '../../variables.scss'; + import { EuiGlobalToastList } from '@elastic/eui'; import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; import { EmptyTabParams, EventAnalyticsProps } from 'common/types/explorer'; import { isEmpty } from 'lodash'; import React, { createContext, ReactChild, useState } from 'react'; import { HashRouter, Route, RouteComponentProps, Switch, useHistory } from 'react-router-dom'; +import { useEffect } from 'react'; import { RAW_QUERY } from '../../../common/constants/explorer'; import { ObservabilitySideBar } from '../common/side_nav'; import { LogExplorer } from './explorer/log_explorer'; @@ -29,6 +32,7 @@ export const EventAnalytics = ({ http, notifications, queryManager, + setBreadcrumbs, ...props }: EventAnalyticsProps) => { const history = useHistory(); @@ -36,7 +40,7 @@ export const EventAnalytics = ({ const eventAnalyticsBreadcrumb = { text: 'Event analytics', - href: '#/event_analytics', + href: '#/', }; const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => { @@ -56,6 +60,16 @@ export const EventAnalytics = ({ return emptyTabId; }; + useEffect(() => { + setBreadcrumbs([ + ...parentBreadcrumbs, + { + text: 'Logs', + href: `#/`, + }, + ]); + }, [setBreadcrumbs, parentBreadcrumbs]); + return ( <> { chrome.setBreadcrumbs([ ...parentBreadcrumbs, @@ -104,27 +118,32 @@ export const EventAnalytics = ({ /> { - chrome.setBreadcrumbs([ - ...parentBreadcrumbs, - eventAnalyticsBreadcrumb, - { - text: 'Home', - href: '#/event_analytics', - }, - ]); + // chrome.setBreadcrumbs([ + // ...parentBreadcrumbs, + // eventAnalyticsBreadcrumb, + // { + // text: 'Home', + // href: '#/', + // }, + // ]); + // setBreadcrumbs([ + // parentBreadcrumbs, + // { + // text: 'Home', + // href: '#/', + // }, + // ]); return ( - - - + ); }} /> @@ -133,3 +152,5 @@ export const EventAnalytics = ({ ); }; + +export default EventAnalytics; diff --git a/public/components/index.tsx b/public/components/index.tsx index eec21a18b2..851524bdf3 100644 --- a/public/components/index.tsx +++ b/public/components/index.tsx @@ -18,7 +18,8 @@ export const Observability = ( dslService: any, savedObjects: any, timestampUtils: any, - queryManager: QueryManager + queryManager: QueryManager, + startPage: string ) => { ReactDOM.render( , + startPage={startPage} + // mountParams={undefined} + />, AppMountParametersProp.element ); diff --git a/public/components/metrics/index.tsx b/public/components/metrics/index.tsx index 0647cdaba3..5197cbd341 100644 --- a/public/components/metrics/index.tsx +++ b/public/components/metrics/index.tsx @@ -15,9 +15,10 @@ import { } from '@elastic/eui'; import { DurationRange } from '@elastic/eui/src/components/date_picker/types'; import React, { useEffect, useState } from 'react'; -import { Route, RouteComponentProps } from 'react-router-dom'; +import { HashRouter, Route, RouteComponentProps } from 'react-router-dom'; import classNames from 'classnames'; import { StaticContext } from 'react-router-dom'; +import { useSelector } from 'react-redux'; import { ChromeBreadcrumb, CoreStart, Toast } from '../../../../../src/core/public'; import { onTimeChange } from './helpers/utils'; import { Sidebar } from './sidebar/sidebar'; @@ -26,7 +27,6 @@ import PPLService from '../../services/requests/ppl'; import { TopMenu } from './top_menu/top_menu'; import { MetricType } from '../../../common/types/metrics'; import { MetricsGrid } from './view/metrics_grid'; -import { useSelector } from 'react-redux'; import { metricsLayoutSelector, selectedMetricsSelector } from './redux/slices/metrics_slice'; import { resolutionOptions } from '../../../common/constants/metrics'; import SavedObjects from '../../services/saved_objects/event_analytics/saved_objects'; @@ -38,6 +38,7 @@ interface MetricsProps { renderProps: RouteComponentProps; pplService: PPLService; savedObjects: SavedObjects; + setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; } export const Home = ({ @@ -81,7 +82,7 @@ export const Home = ({ setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]); }; - const onRefreshFilters = (startTime: ShortDate, endTime: ShortDate) => { + const onRefreshFilters = (startTime: ShortDate, endTime: ShortDate) => { // eslint-disable-line if (spanValue < 1) { setToast('Please add a valid span interval', 'danger'); return; @@ -102,7 +103,7 @@ export const Home = ({ }; const onEditClick = (savedVisualizationId: string) => { - window.location.assign(`#/event_analytics/explorer/${savedVisualizationId}`); + window.location.assign(`#/${savedVisualizationId}`); }; const onSideBarClick = () => { @@ -114,9 +115,19 @@ export const Home = ({ }, 300); }; + useEffect(() => { + chrome.setBreadcrumbs([ + parentBreadcrumb, + { + text: 'Metrics', + href: `#/`, + }, + ]); + }, []); + useEffect(() => { if (!editMode) { - selectedMetrics.length > 0 ? setIsTopPanelDisabled(false) : setIsTopPanelDisabled(true); + selectedMetrics.length > 0 ? setIsTopPanelDisabled(false) : setIsTopPanelDisabled(true); // eslint-disable-line } else { setIsTopPanelDisabled(true); } @@ -145,81 +156,83 @@ export const Home = ({ side={toastRightSide ? 'right' : 'left'} toastLifeTimeMs={6000} /> - ( -
- - - -
-
-
- {!isSidebarClosed && ( - + + ( +
+ + + +
+
+
+ {!isSidebarClosed && ( + + )} + onSideBarClick()} + data-test-subj="collapseSideBarButton" + aria-controls="discover-sidebar" + aria-expanded={isSidebarClosed ? 'false' : 'true'} + aria-label="Toggle sidebar" + className="dscCollapsibleSidebar__collapseButton" + /> +
+
+
+ {selectedMetrics.length > 0 ? ( + + ) : ( + )} - onSideBarClick()} - data-test-subj="collapseSideBarButton" - aria-controls="discover-sidebar" - aria-expanded={isSidebarClosed ? 'false' : 'true'} - aria-label="Toggle sidebar" - className="dscCollapsibleSidebar__collapseButton" - />
-
- {selectedMetrics.length > 0 ? ( - - ) : ( - - )} -
-
- - -
- )} - /> + + +
+ )} + /> + ); }; diff --git a/public/components/notebooks/components/main.tsx b/public/components/notebooks/components/main.tsx index cd70332d28..7e19dbdd2a 100644 --- a/public/components/notebooks/components/main.tsx +++ b/public/components/notebooks/components/main.tsx @@ -2,7 +2,6 @@ * 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'; @@ -95,7 +94,7 @@ export class Main extends React.Component { createNotebook = (newNoteName: string) => { if (newNoteName.length >= 50 || newNoteName.length === 0) { this.setToast('Invalid notebook name', 'danger'); - window.location.assign('#/notebooks'); + window.location.assign('#/'); return; } const newNoteObject = { @@ -108,7 +107,7 @@ export class Main extends React.Component { }) .then(async (res) => { this.setToast(`Notebook "${newNoteName}" successfully created!`); - window.location.assign(`#/notebooks/${res}`); + window.location.assign(`#/${res}`); }) .catch((err) => { this.setToast( @@ -310,28 +309,26 @@ export class Main extends React.Component { ( - - - + )} /> ( { cloneNotebook={this.cloneNotebook} deleteNotebook={this.deleteNotebook} setToast={this.setToast} - location={this.props.location} - history={this.props.history} + location={props.location} + history={props.history} /> )} /> diff --git a/public/components/notebooks/components/note_table.tsx b/public/components/notebooks/components/note_table.tsx index ee6a3eef53..5624b4601d 100644 --- a/public/components/notebooks/components/note_table.tsx +++ b/public/components/notebooks/components/note_table.tsx @@ -84,7 +84,7 @@ export function NoteTable({ parentBreadcrumb, { text: 'Notebooks', - href: '#/notebooks', + href: '#/', }, ]); fetchNotebooks(); @@ -120,9 +120,8 @@ export function NoteTable({ }; const onDelete = async () => { - const toastMessage = `Notebook${ - selectedNotebooks.length > 1 ? 's' : ' "' + selectedNotebooks[0].path + '"' - } successfully deleted!`; + const toastMessage = `Notebook${selectedNotebooks.length > 1 ? 's' : ' "' + selectedNotebooks[0].path + '"' + } successfully deleted!`; await deleteNotebook( selectedNotebooks.map((note) => note.id), toastMessage @@ -263,7 +262,7 @@ export function NoteTable({ sortable: true, truncateText: true, render: (value, record) => ( - {_.truncate(value, { length: 100 })} + {_.truncate(value, { length: 100 })} ), }, { @@ -329,7 +328,7 @@ export function NoteTable({ - + Create notebook @@ -351,8 +350,8 @@ export function NoteTable({ items={ searchQuery ? notebooks.filter((notebook) => - notebook.path.toLowerCase().includes(searchQuery.toLowerCase()) - ) + notebook.path.toLowerCase().includes(searchQuery.toLowerCase()) + ) : notebooks } itemId="id" diff --git a/public/components/notebooks/components/notebook.tsx b/public/components/notebooks/components/notebook.tsx index fc124588df..ad9729c0f5 100644 --- a/public/components/notebooks/components/notebook.tsx +++ b/public/components/notebooks/components/notebook.tsx @@ -621,11 +621,11 @@ export class Notebook extends Component { this.props.parentBreadcrumb, { text: 'Notebooks', - href: '#/notebooks', + href: '#/', }, { text: path, - href: `#/notebooks/${this.props.openedNoteId}`, + href: `#/${this.props.openedNoteId}`, }, ]); } diff --git a/public/components/trace_analytics/components/dashboard/dashboard_content.tsx b/public/components/trace_analytics/components/dashboard/dashboard_content.tsx index 2cc0af94bd..a67f0b12c6 100644 --- a/public/components/trace_analytics/components/dashboard/dashboard_content.tsx +++ b/public/components/trace_analytics/components/dashboard/dashboard_content.tsx @@ -73,13 +73,17 @@ export function DashboardContent(props: DashboardProps) { useEffect(() => { if (showTimeoutToast === true && toasts.length === 0) { - setToast!('Query took too long to execute.', 'danger', 'Reduce time range or filter your data. If issue persists, consider increasing your cluster size.') + setToast!( + 'Query took too long to execute.', + 'danger', + 'Reduce time range or filter your data. If issue persists, consider increasing your cluster size.' + ); } setShowTimeoutToast(false); - }, [showTimeoutToast]) + }, [showTimeoutToast]); useEffect(() => { - chrome.setBreadcrumbs([...parentBreadcrumbs, ...childBreadcrumbs]); + // chrome.setBreadcrumbs([...parentBreadcrumbs, ...childBreadcrumbs]); const validFilters = getValidFilterFields(mode, page); setFilters([ ...filters.map((filter) => ({ @@ -93,7 +97,7 @@ export function DashboardContent(props: DashboardProps) { useEffect(() => { let newFilteredService = ''; for (const filter of filters) { - if (mode === 'data_prepper') { + if (mode === 'data_prepper') { if (filter.field === 'serviceName') { newFilteredService = filter.value; break; @@ -106,8 +110,22 @@ export function DashboardContent(props: DashboardProps) { } } setFilteredService(newFilteredService); - if (!redirect && ((mode === 'data_prepper' && dataPrepperIndicesExist) || (mode === 'jaeger' && jaegerIndicesExist))) refresh(newFilteredService); - }, [filters, startTime, endTime, appConfigs, redirect, mode, dataPrepperIndicesExist, jaegerIndicesExist]); + if ( + !redirect && + ((mode === 'data_prepper' && dataPrepperIndicesExist) || + (mode === 'jaeger' && jaegerIndicesExist)) + ) + refresh(newFilteredService); + }, [ + filters, + startTime, + endTime, + appConfigs, + redirect, + mode, + dataPrepperIndicesExist, + jaegerIndicesExist, + ]); const refresh = async (currService?: string) => { setLoading(true); @@ -154,12 +172,12 @@ export function DashboardContent(props: DashboardProps) { mode, () => setShowTimeoutToast(true), // () => { - // if (toasts.length === 0) { + // if (toasts.length === 0) { // setToast!('Query took too long to execute.', 'danger', 'Reduce time range or filter your data. If issue persists, consider increasing your cluster size.'); // } // }, setPercentileMap - ).finally(() => setLoading(false)) + ).finally(() => setLoading(false)); handleJaegerErrorDashboardRequest( http, DSL, @@ -170,7 +188,7 @@ export function DashboardContent(props: DashboardProps) { mode, () => setShowTimeoutToast(true), // () => { - // if (toasts.length === 0) { + // if (toasts.length === 0) { // setToast!('Query took too long to execute.', 'danger', 'Reduce time range or filter your data. If issue persists, consider increasing your cluster size.'); // } // }, @@ -295,7 +313,8 @@ export function DashboardContent(props: DashboardProps) { mode={mode} /> - {((mode === 'data_prepper' && dataPrepperIndicesExist) || mode === 'jaeger' && jaegerIndicesExist) ? ( + {(mode === 'data_prepper' && dataPrepperIndicesExist) || + (mode === 'jaeger' && jaegerIndicesExist) ? (
{mode === 'data_prepper' ? ( <> @@ -359,7 +378,7 @@ export function DashboardContent(props: DashboardProps) { )}
) : ( - + )} ); diff --git a/public/embeddable/observability_embeddable_factory.tsx b/public/embeddable/observability_embeddable_factory.tsx index 53e7b7859f..5392881567 100644 --- a/public/embeddable/observability_embeddable_factory.tsx +++ b/public/embeddable/observability_embeddable_factory.tsx @@ -20,7 +20,7 @@ import { OnSaveProps, SavedObjectMetaData, } from '../../../../src/plugins/saved_objects/public'; -import { observabilityID } from '../../common/constants/shared'; +import { observabilityID, observabilityLogsID } from '../../common/constants/shared'; import { VisualizationSavedObjectAttributes, VISUALIZATION_SAVED_OBJECT, @@ -63,8 +63,8 @@ export class ObservabilityEmbeddableFactoryDefinition input: SavedObjectEmbeddableInput, parent?: IContainer ) { - const editPath = `#/event_analytics/explorer/${VISUALIZATION_SAVED_OBJECT}:${savedObjectId}`; - const editUrl = `/app/${observabilityID}${editPath}`; + const editPath = `#/explorer/${VISUALIZATION_SAVED_OBJECT}:${savedObjectId}`; + const editUrl = `/app/${observabilityLogsID}${editPath}`; return new ObservabilityEmbeddable( { editUrl, diff --git a/public/index.ts b/public/index.ts index 97037a9f7d..a43ed0e205 100644 --- a/public/index.ts +++ b/public/index.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import './variables.scss'; import './components/trace_analytics/index.scss'; import './components/notebooks/index.scss' diff --git a/public/plugin.ts b/public/plugin.ts index 1c8ce67e05..901a4fa393 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -5,12 +5,35 @@ import './index.scss'; -import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '../../../src/core/public'; +import { i18n } from '@osd/i18n'; +import { + AppCategory, + AppMountParameters, + CoreSetup, + CoreStart, + Plugin, +} from '../../../src/core/public'; import { CREATE_TAB_PARAM, CREATE_TAB_PARAM_KEY, TAB_CHART_ID } from '../common/constants/explorer'; + import { - observabilityID, - observabilityPluginOrder, - observabilityTitle, + observabilityApplicationsID, + observabilityApplicationsPluginOrder, + observabilityApplicationsTitle, + observabilityTracesTitle, + observabilityMetricsID, + observabilityMetricsPluginOrder, + observabilityMetricsTitle, + observabilityNotebookID, + observabilityNotebookPluginOrder, + observabilityNotebookTitle, + observabilityTracesID, + observabilityTracesPluginOrder, + observabilityPanelsID, + observabilityPanelsTitle, + observabilityPanelsPluginOrder, + observabilityLogsID, + observabilityLogsTitle, + observabilityLogsPluginOrder, } from '../common/constants/shared'; import { QueryManager } from '../common/query_manager'; import { VISUALIZATION_SAVED_OBJECT } from '../common/types/observability_saved_object_attributes'; @@ -45,6 +68,7 @@ import DSLService from './services/requests/dsl'; import PPLService from './services/requests/ppl'; import SavedObjects from './services/saved_objects/event_analytics/saved_objects'; import TimestampUtils from './services/timestamp/timestamp'; +import { observabilityID } from '../common/constants/shared'; import { AppPluginStartDependencies, ObservabilitySetup, @@ -79,47 +103,101 @@ export class ObservabilityPlugin window.location.assign(convertLegacyTraceAnalyticsUrl(window.location)); } + // // redirect legacy notebooks URL to current URL under observability + // if (window.location.pathname.includes('application_analytics')) { + // window.location.assign(convertLegacyAppAnalyticsUrl(window.location)); + // } + setupDeps.dashboard.registerDashboardProvider({ appId: 'observability-panel', savedObjectsType: 'observability-panel', savedObjectsName: 'Observability Panel', - editUrlPathFn: (obj: SavedObject) => - `/app/observability-dashboards#/operational_panels/${obj.id}/edit`, - viewUrlPathFn: (obj: SavedObject) => - `/app/observability-dashboards#/operational_panels/${obj.id}`, + editUrlPathFn: (obj: SavedObject) => `/app/observability-dashboards#/${obj.id}/edit`, + viewUrlPathFn: (obj: SavedObject) => `/app/observability-dashboards#/${obj.id}`, createLinkText: 'Observability Panel', createSortText: 'Observability Panel', - createUrl: '/app/observability-dashboards#/operational_panels/create', + createUrl: '/app/observability-dashboards#/create', }); - core.application.register({ - id: observabilityID, - title: observabilityTitle, - category: { - id: 'opensearch', - label: 'OpenSearch Plugins', - order: 2000, - }, - order: observabilityPluginOrder, - async mount(params: AppMountParameters) { - const { Observability } = await import('./components/index'); - const [coreStart, depsStart] = await core.getStartServices(); - const dslService = new DSLService(coreStart.http); - const savedObjects = new SavedObjects(coreStart.http); - const timestampUtils = new TimestampUtils(dslService, pplService); - return Observability( - coreStart, - depsStart, - params, - pplService, - dslService, - savedObjects, - timestampUtils, - qm - ); + const OBSERVABILITY_APP_CATEGORIES: Record = Object.freeze({ + observability: { + id: 'observability', + label: i18n.translate('core.ui.observabilityNavList.label', { + defaultMessage: 'Observability', + }), + order: 3000, }, }); + const appMountWithStartPage = (startPage: string) => async (params: AppMountParameters) => { + console.log('start page: ', startPage); + const { Observability } = await import('./components/index'); + const [coreStart, depsStart] = await core.getStartServices(); + const dslService = new DSLService(coreStart.http); + const savedObjects = new SavedObjects(coreStart.http); + const timestampUtils = new TimestampUtils(dslService, pplService); + + return Observability( + coreStart, + depsStart as AppPluginStartDependencies, + params, + pplService, + dslService, + savedObjects, + timestampUtils, + qm, + startPage + ); + }; + + core.application.register({ + id: observabilityApplicationsID, + title: observabilityApplicationsTitle, + category: OBSERVABILITY_APP_CATEGORIES.observability, + order: observabilityApplicationsPluginOrder, + mount: appMountWithStartPage('applications'), + }); + + core.application.register({ + id: observabilityLogsID, + title: observabilityLogsTitle, + category: OBSERVABILITY_APP_CATEGORIES.observability, + order: observabilityLogsPluginOrder, + mount: appMountWithStartPage('logs'), + }); + + core.application.register({ + id: observabilityMetricsID, + title: observabilityMetricsTitle, + category: OBSERVABILITY_APP_CATEGORIES.observability, + order: observabilityMetricsPluginOrder, + mount: appMountWithStartPage('metrics'), + }); + + core.application.register({ + id: observabilityTracesID, + title: observabilityTracesTitle, + category: OBSERVABILITY_APP_CATEGORIES.observability, + order: observabilityTracesPluginOrder, + mount: appMountWithStartPage('traces'), + }); + + core.application.register({ + id: observabilityNotebookID, + title: observabilityNotebookTitle, + category: OBSERVABILITY_APP_CATEGORIES.observability, + order: observabilityNotebookPluginOrder, + mount: appMountWithStartPage('notebooks'), + }); + + core.application.register({ + id: observabilityPanelsID, + title: observabilityPanelsTitle, + category: OBSERVABILITY_APP_CATEGORIES.observability, + order: observabilityPanelsPluginOrder, + mount: appMountWithStartPage('dashboards'), + }); + const embeddableFactory = new ObservabilityEmbeddableFactoryDefinition(async () => ({ getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService, savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, @@ -132,16 +210,16 @@ export class ObservabilityPlugin title: OBSERVABILITY_EMBEDDABLE_DISPLAY_NAME, description: OBSERVABILITY_EMBEDDABLE_DESCRIPTION, icon: OBSERVABILITY_EMBEDDABLE_ICON, - aliasApp: observabilityID, - aliasPath: `#/event_analytics/explorer/?${CREATE_TAB_PARAM_KEY}=${CREATE_TAB_PARAM[TAB_CHART_ID]}`, + aliasApp: observabilityLogsID, + aliasPath: `#/explorer/?${CREATE_TAB_PARAM_KEY}=${CREATE_TAB_PARAM[TAB_CHART_ID]}`, stage: 'production', appExtensions: { visualizations: { docTypes: [VISUALIZATION_SAVED_OBJECT], toListItem: ({ id, attributes, updated_at: updatedAt }) => ({ description: attributes?.description, - editApp: observabilityID, - editUrl: `#/event_analytics/explorer/${id}`, + editApp: observabilityLogsID, + editUrl: `#/explorer/${id}`, icon: OBSERVABILITY_EMBEDDABLE_ICON, id, savedObjectType: VISUALIZATION_SAVED_OBJECT, diff --git a/server/saved_objects/observability_saved_object.ts b/server/saved_objects/observability_saved_object.ts index 5c47a0c223..d8f8ca32d0 100644 --- a/server/saved_objects/observability_saved_object.ts +++ b/server/saved_objects/observability_saved_object.ts @@ -4,7 +4,7 @@ */ import { SavedObjectsType } from '../../../../src/core/server'; -import { observabilityID } from '../../common/constants/shared'; +import { observabilityID, observabilityLogsID } from '../../common/constants/shared'; import { VISUALIZATION_SAVED_OBJECT } from '../../common/types/observability_saved_object_attributes'; export const visualizationSavedObject: SavedObjectsType = { @@ -19,8 +19,8 @@ export const visualizationSavedObject: SavedObjectsType = { return obj.attributes.title; }, getInAppUrl(obj) { - const editPath = `#/event_analytics/explorer/${VISUALIZATION_SAVED_OBJECT}:${obj.id}`; - const editUrl = `/app/${observabilityID}${editPath}`; + const editPath = `#/explorer/${VISUALIZATION_SAVED_OBJECT}:${obj.id}`; + const editUrl = `/app/${observabilityLogsID}${editPath}`; return { path: editUrl, uiCapabilitiesPath: 'observability.show',