diff --git a/dashboards-observability/common/constants/explorer.ts b/dashboards-observability/common/constants/explorer.ts index 845aebbb2..739d3f689 100644 --- a/dashboards-observability/common/constants/explorer.ts +++ b/dashboards-observability/common/constants/explorer.ts @@ -24,6 +24,7 @@ export const TAB_CHART_TITLE = 'Visualizations'; export const TAB_EVENT_TITLE = 'Events'; export const TAB_EVENT_ID_TXT_PFX = 'main-content-events-'; export const TAB_CHART_ID_TXT_PFX = 'main-content-vis-'; +export const HAS_SAVED_TIMESTAMP = 'hasSavedTimestamp'; export const DATE_PICKER_FORMAT = 'YYYY-MM-DD HH:mm:ss'; export const TIME_INTERVAL_OPTIONS = [ diff --git a/dashboards-observability/public/components/common/search/search.tsx b/dashboards-observability/public/components/common/search/search.tsx index e3616cc77..76fcc59d1 100644 --- a/dashboards-observability/public/components/common/search/search.tsx +++ b/dashboards-observability/public/components/common/search/search.tsx @@ -68,7 +68,8 @@ export const Search = (props: any) => { isPanelTextFieldInvalid, savedObjects, showSavePanelOptionsList, - showSaveButton = true + showSaveButton = true, + setToast } = props; const [isSavePanelOpen, setIsSavePanelOpen] = useState(false); @@ -184,7 +185,10 @@ export const Search = (props: any) => { handleSavingObject()}> + onClick={() => { + handleSavingObject(); + setIsSavePanelOpen(false); + }}> { "Save" } diff --git a/dashboards-observability/public/components/explorer/event_analytics.tsx b/dashboards-observability/public/components/explorer/event_analytics.tsx index 6e8853f03..a873fed53 100644 --- a/dashboards-observability/public/components/explorer/event_analytics.tsx +++ b/dashboards-observability/public/components/explorer/event_analytics.tsx @@ -9,8 +9,10 @@ * GitHub history for details. */ -import React from 'react'; +import React, { useState, ReactChild } from 'react'; import { HashRouter, Route, Switch } from 'react-router-dom'; +import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; +import { EuiGlobalToastList } from '@elastic/eui'; import { LogExplorer } from './log_explorer'; import { Home as EventExplorerHome } from './home'; import { renderPageWithSidebar } from '../common/side_nav'; @@ -26,13 +28,28 @@ export const EventAnalytics = ({ ...props }: any) => { + const [toasts, setToasts] = useState>([]); + const eventAnalyticsBreadcrumb = { text: 'Event analytics', href: '#/event_analytics', }; + const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => { + if (!text) text = ''; + setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]); + }; + return ( - + <> + { + setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); + }} + toastLifeTimeMs={6000} + /> + ); }} @@ -79,5 +97,6 @@ export const EventAnalytics = ({ /> + ); } \ No newline at end of file diff --git a/dashboards-observability/public/components/explorer/explorer.tsx b/dashboards-observability/public/components/explorer/explorer.tsx index 509dace02..188729a2c 100644 --- a/dashboards-observability/public/components/explorer/explorer.tsx +++ b/dashboards-observability/public/components/explorer/explorer.tsx @@ -9,31 +9,25 @@ * GitHub history for details. */ -import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react'; +import React, { useState, useMemo, useEffect, useRef } from 'react'; import { batch, useDispatch, useSelector } from 'react-redux'; import { uniqueId, isEmpty, cloneDeep, isEqual, - concat + has } from 'lodash'; import { FormattedMessage } from '@osd/i18n/react'; import { EuiText, - EuiButton, EuiButtonIcon, EuiTabbedContent, EuiTabbedContentTab, EuiFlexGroup, EuiFlexItem, - EuiPopover, - EuiPopoverTitle, - EuiPopoverFooter, - EuiButtonEmpty, - htmlIdGenerator } from '@elastic/eui'; import classNames from 'classnames'; import { Search } from '../common/search/search'; @@ -44,7 +38,6 @@ import { NoResults } from './no_results'; import { HitsCounter } from './hits_counter/hits_counter'; import { TimechartHeader } from './timechart_header'; import { ExplorerVisualizations } from './visualizations'; -import { IndexPicker } from '../common/search/searchindex'; import { IField, IQueryTab @@ -61,7 +54,8 @@ import { UNSELECTED_FIELDS, AVAILABLE_FIELDS, INDEX, - TIME_INTERVAL_OPTIONS + TIME_INTERVAL_OPTIONS, + HAS_SAVED_TIMESTAMP } from '../../../common/constants/explorer'; import { PPL_STATS_REGEX } from '../../../common/constants/shared'; import { @@ -99,6 +93,12 @@ interface IExplorerProps { tabId: string; savedObjects: SavedObjects; timestampUtils: TimestampUtils; + setToast: ( + title: string, + color?: string, + text?: React.ReactChild | undefined, + side?: string | undefined + ) => void; } export const Explorer = ({ @@ -106,7 +106,8 @@ export const Explorer = ({ dslService, tabId, savedObjects, - timestampUtils + timestampUtils, + setToast }: IExplorerProps) => { const dispatch = useDispatch(); @@ -134,15 +135,13 @@ export const Explorer = ({ const countDistribution = useSelector(selectCountDistribution)[tabId]; const explorerVisualizations = useSelector(selectExplorerVisualization)[tabId]; - const [selectedContentTabId, setSelectedContentTab] = useState(TAB_EVENT_ID); + const [selectedContentTabId, setSelectedContentTab] = useState(TAB_EVENT_ID); const [selectedCustomPanelOptions, setSelectedCustomPanelOptions] = useState([]); const [selectedPanelName, setSelectedPanelName] = useState(''); - const [curVisId, setCurVisId] = useState('bar'); - const [prevIndex, setPrevIndex] = useState(''); - const [isPanelTextFieldInvalid, setIsPanelTextFieldInvalid ] = useState(false); - const [liveStreamChecked, setLiveStreamChecked] = useState(false); - const [isSidebarClosed, setIsSidebarClosed] = useState(false); - const [fixedScrollEl, setFixedScrollEl] = useState(); + const [curVisId, setCurVisId] = useState('bar'); + const [prevIndex, setPrevIndex] = useState(''); + const [isPanelTextFieldInvalid, setIsPanelTextFieldInvalid ] = useState(false); + const [isSidebarClosed, setIsSidebarClosed] = useState(false); const queryRef = useRef(); const selectedPanelNameRef = useRef(); @@ -150,15 +149,6 @@ export const Explorer = ({ queryRef.current = query; selectedPanelNameRef.current = selectedPanelName; explorerFieldsRef.current = explorerFields; - - const fixedScrollRef = useCallback( - (node: HTMLElement) => { - if (node !== null) { - setFixedScrollEl(node); - } - }, - [setFixedScrollEl] - ); const composeFinalQuery = (curQuery: any, timeField: string) => { if (isEmpty(curQuery![RAW_QUERY])) return ''; @@ -176,7 +166,10 @@ export const Explorer = ({ const curIndex = getIndexPatternFromRawQuery(rawQueryStr); if (isEmpty(rawQueryStr)) return; - if (isEmpty(curIndex)) return; + if (isEmpty(curIndex)) { + setToast('Query does not include vaild index.', 'danger'); + return; + } let curTimestamp = ''; let hasSavedTimestamp = false; @@ -185,6 +178,8 @@ export const Explorer = ({ if (isEmpty(curQuery![SELECTED_TIMESTAMP]) || !isEqual(curIndex, prevIndex)) { const savedTimestamps = await savedObjects.fetchSavedObjects({ objectId: curIndex + }).catch((error: any) => { + console.log(`Unable to get saved timestamp for this index: ${error.message}`); }); if (savedTimestamps?.observabilityObjectList[0]?.timestamp?.name) { // from saved objects @@ -198,6 +193,11 @@ export const Explorer = ({ } } + if (isEmpty(curTimestamp)) { + setToast('Index does not contain time field.', 'danger'); + return; + } + // compose final query const finalQuery = composeFinalQuery(curQuery, curTimestamp || curQuery![SELECTED_TIMESTAMP]); @@ -206,11 +206,10 @@ export const Explorer = ({ query: { finalQuery, [SELECTED_TIMESTAMP]: curTimestamp || curQuery![SELECTED_TIMESTAMP], - 'hasSavedTimestamp': hasSavedTimestamp + [HAS_SAVED_TIMESTAMP]: hasSavedTimestamp } })); - // search if (rawQueryStr.match(PPL_STATS_REGEX)) { getVisualizations(); @@ -295,16 +294,38 @@ export const Explorer = ({ type: timestamp.type, dsl_type: 'date' }; - if (isEmpty(rawQueryStr) || isEmpty(curIndex)) return; - if (curQuery!['hasSavedTimestamp']) { - await savedObjects.updateTimestamp({ + if (isEmpty(rawQueryStr) || isEmpty(curIndex)) { + setToast('Cannot override timestamp because there was no valid index found.', 'danger'); + return; + } + + let saveTimestampRes; + if (curQuery![HAS_SAVED_TIMESTAMP]) { + saveTimestampRes = await savedObjects.updateTimestamp({ ...requests + }) + .then((res: any) => { + setToast(`Timestamp has been overridden successfully.`, 'success'); + return res; + }) + .catch((error: any) => { + setToast(`Cannot override timestamp, error: ${error.message}`, 'danger'); }); } else { - await savedObjects.createSavedTimestamp({ + saveTimestampRes = await savedObjects.createSavedTimestamp({ ...requests + }) + .then((res: any) => { + setToast(`Timestamp has been overridden successfully.`, 'success'); + return res; + }) + .catch((error: any) => { + setToast(`Cannot override timestamp, error: ${error.message}`, 'danger'); }); } + + if (!has(saveTimestampRes, 'objectId')) return; + await dispatch(changeQuery({ tabId, query: { @@ -394,7 +415,6 @@ export const Explorer = ({

{ + setToast(`Query '${selectedPanelNameRef.current}' has been successfully saved.`, 'success'); + }) + .catch((error: any) => { + setToast(`Cannot save query '${selectedPanelNameRef.current}', error: ${error.message}`, 'danger'); }); // to-dos - update selected custom panel @@ -548,14 +578,29 @@ export const Explorer = ({ type: curVisId, name: selectedPanelNameRef.current, timestamp: currQuery![SELECTED_TIMESTAMP] + }) + .then((res: any) => { + setToast(`Visualization '${selectedPanelNameRef.current}' has been successfully saved.`, 'success'); + return res; + }) + .catch((error: any) => { + setToast(`Cannot save Visualization '${selectedPanelNameRef.current}', error: ${error.message}`, 'danger'); }); + if (!has(savingVisRes, 'objectId')) return; + // update custom panel - visualization if (!isEmpty(selectedCustomPanelOptions)) { savedObjects.bulkUpdateCustomPanel({ selectedCustomPanels: selectedCustomPanelOptions, savedVisualizationId: savingVisRes?.objectId + }) + .then((res: any) => { + setToast(`Visualization '${selectedPanelNameRef.current}' has been successfully saved to operation panels.`, 'success'); + }) + .catch((error: any) => { + setToast(`Cannot add Visualization '${selectedPanelNameRef.current}' to operation panels, error: ${error.message}`, 'danger'); }); } } @@ -584,11 +629,6 @@ export const Explorer = ({ savedObjects={ savedObjects } showSavePanelOptionsList={ isEqual(selectedContentTabId, TAB_CHART_ID) } /> - {/* { handleQueryChange(query, index) } } - /> */} { const dispatch = useDispatch(); @@ -78,7 +79,7 @@ export const LogExplorer = ({ const handleTabClose = (TabIdToBeClosed: string) => { if (tabIds.length === 1) { - console.log('Have to have at least one tab'); + setToast('Have to have at least one tab', 'danger'); return; } @@ -146,10 +147,10 @@ export const LogExplorer = ({ key={`explorer_${tabId}`} pplService={ pplService } dslService={ dslService } - http={ http } tabId={ tabId } savedObjects={ savedObjects } timestampUtils={ timestampUtils } + setToast={ setToast } /> ) }; diff --git a/dashboards-observability/public/components/explorer/save_panel/savePanel.tsx b/dashboards-observability/public/components/explorer/save_panel/savePanel.tsx index ce909a336..42fd9774f 100644 --- a/dashboards-observability/public/components/explorer/save_panel/savePanel.tsx +++ b/dashboards-observability/public/components/explorer/save_panel/savePanel.tsx @@ -52,8 +52,12 @@ export const SavePanel = ({ const [options, setOptions] = useState([]); const getCustomPabnelList = async (savedObjects: SavedObjects) => { - const optionRes = await savedObjects.fetchCustomPanels(); - setOptions(optionRes['panels']); + const optionRes = await savedObjects.fetchCustomPanels() + .then((res: any) => { + return res; + }) + .catch((error: any) => console.error(error)); + setOptions(optionRes?.panels || []); }; useEffect(() => { diff --git a/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts b/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts index cc117ef81..ed00d1e17 100644 --- a/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts +++ b/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts @@ -14,7 +14,6 @@ import { isEmpty, isArray } from 'lodash'; -import { htmlIdGenerator } from '@elastic/eui'; import { OBSERVABILITY_BASE, EVENT_ANALYTICS, @@ -119,20 +118,18 @@ export default class SavedObjects { ); }); - const res = await this.http.get( + return await this.http.get( `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}`, { query: { ...params }, } - ).catch((error: any) => console.error(error)); - - return res; + ); } async fetchCustomPanels() { - return await this.http.get(`${CUSTOM_PANELS_API_PREFIX}/panels`).catch((error: any) => console.error(error)); + return await this.http.get(`${CUSTOM_PANELS_API_PREFIX}/panels`); } async bulkUpdateCustomPanel (params: ISelectedPanelsParams) { @@ -141,14 +138,14 @@ export default class SavedObjects { savedVisualizationId: params.savedVisualizationId, }; - const responses = await Promise.all( + return await Promise.all( params['selectedCustomPanels'].map((panel) => { finalParams['panelId'] = panel['panel']['id']; return this.http.post(`${CUSTOM_PANELS_API_PREFIX}/visualizations`, { body: JSON.stringify(finalParams) }); }) - ).catch((error) => console.error(error)); + ); }; async bulkUpdateSavedVisualization(params: IBulkUpdateSavedVisualizationRquest) { @@ -161,7 +158,7 @@ export default class SavedObjects { name: params.name }); - const responses = await Promise.all( + return await Promise.all( params.savedObjectList.map((objectToUpdate) => { finalParams['object_id'] = objectToUpdate['saved_object']['objectId']; return this.http.put( @@ -171,7 +168,7 @@ export default class SavedObjects { } ); }) - ).catch((error) => console.error(error)); + ); } async updateSavedVisualizationById(params: any) { @@ -188,7 +185,7 @@ export default class SavedObjects { { body: JSON.stringify(finalParams) } - ).catch((error: any) => console.error(error)); + ); } async createSavedQuery(params: any) { @@ -206,7 +203,7 @@ export default class SavedObjects { { body: JSON.stringify(finalParams) } - ).catch((error: any) => console.log(error)); + ); } async createSavedVisualization(params: any) { @@ -225,7 +222,7 @@ export default class SavedObjects { { body: JSON.stringify(finalParams) } - ).catch((error: any) => console.log(error)); + ); } async createSavedTimestamp(params: any) { @@ -241,7 +238,7 @@ export default class SavedObjects { { body: JSON.stringify(finalParams) } - ).catch((error: any) => console.log(error)); + ); } async updateTimestamp(params: any) { @@ -259,7 +256,7 @@ export default class SavedObjects { { body: JSON.stringify(finalParams) } - ).catch((error: any) => console.log(error)); + ); } deleteSavedObjectsById(deleteObjectRequest: any) {}