diff --git a/common/constants/data_sources.ts b/common/constants/data_sources.ts new file mode 100644 index 000000000..aa596ad10 --- /dev/null +++ b/common/constants/data_sources.ts @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const DATA_SOURCE_NAME_URL_PARAM_KEY = 'datasourceName'; +export const DATA_SOURCE_TYPE_URL_PARAM_KEY = 'datasourceType'; +export const DEFAULT_DATA_SOURCE_TYPE = 'DEFAULT_INDEX_PATTERNS'; +export const DEFAULT_DATA_SOURCE_NAME = 'Default cluster'; +export const DEFAULT_DATA_SOURCE_OBSERVABILITY_DISPLAY_NAME = 'OpenSearch'; +export const DEFAULT_DATA_SOURCE_TYPE_NAME = 'Default Group'; +export const enum QUERY_LANGUAGE { + PPL = 'PPL', + SQL = 'SQL', + DQL = 'DQL', +} diff --git a/common/constants/shared.ts b/common/constants/shared.ts index 8175f3380..198ce05a3 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -242,6 +242,7 @@ export const WAITING_TIME_ON_USER_ACTIONS = 300; export const VISUALIZATION_ERROR = { NO_DATA: 'No data found.', INVALID_DATA: 'Invalid visualization data', + NO_SERIES: 'Add a field to start', }; export const S3_DATASOURCE_TYPE = 'S3_DATASOURCE'; diff --git a/common/types/explorer.ts b/common/types/explorer.ts index af94da802..a36dd81a8 100644 --- a/common/types/explorer.ts +++ b/common/types/explorer.ts @@ -38,6 +38,7 @@ import { SavedObjectsStart, } from '../../../../src/core/public/saved_objects'; import { ChromeBreadcrumb } from '../../../../src/core/public/chrome'; +import { DataSourceType } from '../../../../src/plugins/data/public'; export interface IQueryTab { id: string; @@ -145,13 +146,23 @@ export interface IExplorerProps { queryManager?: QueryManager; } -export interface SavedQuery { +export interface SelectedDataSource { + label: string; + name: string; + value: string; + type: string; + ds?: DataSourceType; +} + +export interface SavedQuery extends SavedObjectAttributes { description: string; name: string; query: string; selected_date_range: { start: string; end: string; text: string }; selected_fields: { text: string; tokens: IField[] }; selected_timestamp: IField; + dataSources: string; // list of type SelectedDataSources that is stringified + queryLang: string; } export interface SavedVisualization extends SavedObjectAttributes { @@ -166,6 +177,8 @@ export interface SavedVisualization extends SavedObjectAttributes { user_configs?: string; units_of_measure?: string; application_id?: string; + dataSources: string; // list of type SelectedDataSources that is stringified + queryLang: string; } export interface ExplorerDataType { @@ -406,3 +419,11 @@ export interface GridSortingColumn { id: string; direction: 'asc' | 'desc'; } + +export enum DirectQueryLoadingStatus { + SUCCESS = 'SUCCESS', + FAILED = 'FAILED', + RUNNING = 'RUNNING', + SCHEDULED = 'SCHEDULED', + CANCELED = 'CANCELED', +} diff --git a/common/types/observability_saved_object_attributes.ts b/common/types/observability_saved_object_attributes.ts index 520f922bc..a98f0b4b2 100644 --- a/common/types/observability_saved_object_attributes.ts +++ b/common/types/observability_saved_object_attributes.ts @@ -4,10 +4,14 @@ */ import { SavedObjectAttributes } from '../../../../src/core/types'; -import { SavedVisualization } from './explorer'; +import { SavedQuery, SavedVisualization } from './explorer'; export const VISUALIZATION_SAVED_OBJECT = 'observability-visualization'; -export const OBSERVABILTY_SAVED_OBJECTS = [VISUALIZATION_SAVED_OBJECT] as const; +export const SEARCH_SAVED_OBJECT = 'observability-search'; +export const OBSERVABILTY_SAVED_OBJECTS = [ + VISUALIZATION_SAVED_OBJECT, + SEARCH_SAVED_OBJECT, +] as const; export const SAVED_OBJECT_VERSION = 1; export interface VisualizationSavedObjectAttributes extends SavedObjectAttributes { @@ -17,3 +21,11 @@ export interface VisualizationSavedObjectAttributes extends SavedObjectAttribute createdTimeMs: number; savedVisualization: SavedVisualization; } + +export interface SearchSavedObjectAttributes extends SavedObjectAttributes { + title: string; + description: string; + version: number; + createdTimeMs: number; + savedQuery: SavedQuery; +} diff --git a/common/utils/index.ts b/common/utils/index.ts index 284c1f4b0..0becefc0a 100644 --- a/common/utils/index.ts +++ b/common/utils/index.ts @@ -10,6 +10,7 @@ export { buildRawQuery, composeFinalQuery, removeBacktick, + getSavingCommonParams, } from '../../public/components/common/query_utils'; export * from './core_services'; diff --git a/public/components/application_analytics/components/application.tsx b/public/components/application_analytics/components/application.tsx index d5ba1b8fe..bdfea879a 100644 --- a/public/components/application_analytics/components/application.tsx +++ b/public/components/application_analytics/components/application.tsx @@ -116,6 +116,7 @@ export function Application(props: AppDetailProps) { callback, queryManager, mode, + dataSourcePluggables, } = props; const [application, setApplication] = useState({ id: '', @@ -371,6 +372,7 @@ export function Application(props: AppDetailProps) { callbackInApp={callbackInApp} queryManager={queryManager} curSelectedTabId={selectedTabId} + dataSourcePluggables={dataSourcePluggables} /> ); }; diff --git a/public/components/application_analytics/home.tsx b/public/components/application_analytics/home.tsx index a97f56f34..c360e50d9 100644 --- a/public/components/application_analytics/home.tsx +++ b/public/components/application_analytics/home.tsx @@ -73,6 +73,7 @@ export const Home = (props: HomeProps) => { chrome, notifications, queryManager, + dataSourcePluggables, } = props; const [triggerSwitchToEvent, setTriggerSwitchToEvent] = useState(0); const dispatch = useDispatch(); @@ -140,6 +141,7 @@ export const Home = (props: HomeProps) => { setEndTime, mode: 'data_prepper', dataPrepperIndicesExist: indicesExist, + dataSourcePluggables, }; const setToast = (title: string, color = 'success', text?: ReactChild) => { diff --git a/public/components/common/field_icon/field_icon.tsx b/public/components/common/field_icon/field_icon.tsx index 467c2c7c0..e5b2d0117 100644 --- a/public/components/common/field_icon/field_icon.tsx +++ b/public/components/common/field_icon/field_icon.tsx @@ -50,7 +50,7 @@ export const typeToEuiIconMap: Partial> = { export function FieldIcon({ type, label, - size = 'l', + size = 's', scripted, className, ...rest diff --git a/public/components/common/query_utils/index.ts b/public/components/common/query_utils/index.ts index 98d0b54ff..bed3432db 100644 --- a/public/components/common/query_utils/index.ts +++ b/public/components/common/query_utils/index.ts @@ -6,10 +6,12 @@ import dateMath from '@elastic/datemath'; import { Moment } from 'moment-timezone'; import { isEmpty } from 'lodash'; -import moment from 'moment'; +import { SearchMetaData } from 'public/components/event_analytics/redux/slices/search_meta_data_slice'; import { - DATE_PICKER_FORMAT, PPL_DEFAULT_PATTERN_REGEX_FILETER, + SELECTED_DATE_RANGE, + SELECTED_FIELDS, + SELECTED_TIMESTAMP, } from '../../../../common/constants/explorer'; import { PPL_DATE_FORMAT, @@ -17,6 +19,7 @@ import { PPL_INDEX_REGEX, PPL_NEWLINE_REGEX, } from '../../../../common/constants/shared'; +import { IExplorerFields, IQuery } from '../../../../common/types/explorer'; /* * "Query Utils" This file contains different reused functions in operational panels @@ -186,9 +189,6 @@ export const preprocessQuery = ({ if (!start || !end) return finalQuery; - const formattedStart = moment(start).utc().format(DATE_PICKER_FORMAT); - const formattedEnd = moment(end).utc().format(DATE_PICKER_FORMAT); - const promQLTokens = parsePromQLIntoKeywords(rawQuery); if (promQLTokens?.connection) { @@ -245,24 +245,12 @@ export const buildPatternsQuery = ( return finalQuery; }; -export const buildQuery = (baseQuery: string, currQuery: string) => { - let fullQuery: string; - if (baseQuery) { - fullQuery = baseQuery; - if (currQuery) { - fullQuery += '| ' + currQuery; - } - } else { - fullQuery = currQuery; - } - return fullQuery; -}; +export const buildQuery = (baseQuery: string, currQuery: string) => baseQuery + '| ' + currQuery; -export const buildRawQuery = (query: any, appBaseQuery: string) => { - const rawQueryStr = (query.rawQuery as string).includes(appBaseQuery) - ? query.rawQuery - : buildQuery(appBaseQuery, query.rawQuery); - return rawQueryStr; +export const buildRawQuery = (query: IQuery, appBaseQuery: string) => { + if (appBaseQuery && !query.rawQuery.includes(appBaseQuery)) + return buildQuery(appBaseQuery, query.rawQuery); + return query.rawQuery; }; export const composeFinalQuery = ( @@ -294,3 +282,28 @@ export const removeBacktick = (stringContainsBacktick: string) => { if (!stringContainsBacktick) return ''; return stringContainsBacktick.replace(/`/g, ''); }; + +export const getSavingCommonParams = ( + queryState: IQuery, + appBaseQuery: string, + fields: IExplorerFields, + savingTitle: string, + explorerSearchMeta: SearchMetaData +) => { + return { + dataSources: JSON.stringify([ + { + name: explorerSearchMeta.datasources?.[0]?.name || '', + type: explorerSearchMeta.datasources?.[0]?.type || '', + label: explorerSearchMeta.datasources?.[0]?.label || '', + value: explorerSearchMeta.datasources?.[0]?.value || '', + }, + ]), + queryLang: explorerSearchMeta.lang, + query: buildRawQuery(queryState, appBaseQuery), + fields: fields[SELECTED_FIELDS], + dateRange: queryState[SELECTED_DATE_RANGE], + name: savingTitle, + timestamp: queryState[SELECTED_TIMESTAMP], + }; +}; diff --git a/public/components/common/search/search.tsx b/public/components/common/search/search.tsx index 604575850..de450d09c 100644 --- a/public/components/common/search/search.tsx +++ b/public/components/common/search/search.tsx @@ -20,24 +20,21 @@ import { } from '@elastic/eui'; import { isEqual } from 'lodash'; import React, { useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { APP_ANALYTICS_TAB_ID_REGEX, RAW_QUERY } from '../../../../common/constants/explorer'; +import { useDispatch } from 'react-redux'; +import { APP_ANALYTICS_TAB_ID_REGEX } from '../../../../common/constants/explorer'; import { PPL_SPAN_REGEX } from '../../../../common/constants/shared'; import { uiSettingsService } from '../../../../common/utils'; import { useFetchEvents } from '../../../components/event_analytics/hooks'; -import { changeQuery } from '../../../components/event_analytics/redux/slices/query_slice'; import { usePolling } from '../../../components/hooks/use_polling'; import { coreRefs } from '../../../framework/core_refs'; import { SQLService } from '../../../services/requests/sql'; import { SavePanel } from '../../event_analytics/explorer/save_panel'; -import { - selectSearchMetaData, - update as updateSearchMetaData, -} from '../../event_analytics/redux/slices/search_meta_data_slice'; +import { update as updateSearchMetaData } from '../../event_analytics/redux/slices/search_meta_data_slice'; import { PPLReferenceFlyout } from '../helpers'; import { LiveTailButton, StopLiveButton } from '../live_tail/live_tail_button'; import { Autocomplete } from './autocomplete'; import { DatePicker } from './date_picker'; +import { QUERY_LANGUAGE } from '../../../../common/constants/data_sources'; export interface IQueryBarProps { query: string; tempQuery: string; @@ -97,14 +94,12 @@ export const Search = (props: any) => { setIsQueryRunning, } = props; - const explorerSearchMetadata = useSelector(selectSearchMetaData)[tabId]; const dispatch = useDispatch(); const appLogEvents = tabId.match(APP_ANALYTICS_TAB_ID_REGEX); const [isSavePanelOpen, setIsSavePanelOpen] = useState(false); const [isLanguagePopoverOpen, setLanguagePopoverOpen] = useState(false); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const [queryLang, setQueryLang] = useState('PPL'); - const [jobId, setJobId] = useState(''); + const [queryLang, setQueryLang] = useState(QUERY_LANGUAGE.PPL); const sqlService = new SQLService(coreRefs.http); const { application } = coreRefs; @@ -119,7 +114,7 @@ export const Search = (props: any) => { }, 5000); const requestParams = { tabId }; - const { getLiveTail, getEvents, getAvailableFields, dispatchOnGettingHis } = useFetchEvents({ + const { dispatchOnGettingHis } = useFetchEvents({ pplService: new SQLService(coreRefs.http), requestParams, }); @@ -163,10 +158,9 @@ export const Search = (props: any) => { ); const handleQueryLanguageChange = (lang: string) => { - if (lang === 'DQL') { - return application!.navigateToUrl( - `../app/data-explorer/discover#?_a=(discover:(columns:!(_source),isDirty:!f,sort:!()),metadata:(indexPattern:'${explorerSearchMetadata.datasources[0].value}',view:discover))&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_q=(filters:!(),query:(language:kuery,query:''))` - ); + if (lang === QUERY_LANGUAGE.DQL) { + application!.navigateToUrl('../app/data-explorer/discover'); + return; } dispatch( updateSearchMetaData({ @@ -187,10 +181,16 @@ export const Search = (props: any) => { }; const languagePopOverItems = [ - handleQueryLanguageChange('PPL')}> + handleQueryLanguageChange(QUERY_LANGUAGE.PPL)} + > PPL , - handleQueryLanguageChange('DQL')}> + handleQueryLanguageChange(QUERY_LANGUAGE.DQL)} + > DQL , ]; @@ -215,24 +215,9 @@ export const Search = (props: any) => { } }, [pollingResult, pollingError]); - useEffect(() => { - if (explorerSearchMetadata.datasources?.[0]?.type === 'DEFAULT_INDEX_PATTERNS') { - const queryWithSelectedSource = `source = ${explorerSearchMetadata.datasources[0].label}`; - handleQueryChange(queryWithSelectedSource); - dispatch( - changeQuery({ - tabId, - query: { - [RAW_QUERY]: queryWithSelectedSource, - }, - }) - ); - } - }, [explorerSearchMetadata.datasources]); - return (
- + {appLogEvents && ( @@ -242,19 +227,21 @@ export const Search = (props: any) => { )} - - - - - - + {!appLogEvents && ( + + + + + + )} + { query, tempQuery, handleQueryChange, - handleTimePickerChange, dslService, - startTime, - endTime, - setStartTime, - setEndTime, - setIsOutputStale, selectedPanelName, selectedCustomPanelOptions, setSelectedPanelName, @@ -75,33 +72,24 @@ export const DirectSearch = (props: any) => { savedObjects, showSavePanelOptionsList, showSaveButton = true, - handleTimeRangePickerRefresh, - isLiveTailPopoverOpen, - closeLiveTailPopover, - popoverItems, - isLiveTailOn, selectedSubTabId, searchBarConfigs = {}, getSuggestions, onItemSelect, tabId = '', baseQuery = '', - stopLive, - setIsLiveTailPopoverOpen, - liveTailName, curVisId, setSubType, setIsQueryRunning, } = props; - const explorerSearchMetadata = useSelector(selectSearchMetaData)[tabId]; + const explorerSearchMetadata = useSelector(selectSearchMetaData)[tabId] || {}; const dispatch = useDispatch(); const appLogEvents = tabId.match(APP_ANALYTICS_TAB_ID_REGEX); const [isSavePanelOpen, setIsSavePanelOpen] = useState(false); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const [isLanguagePopoverOpen, setLanguagePopoverOpen] = useState(false); - const [queryLang, setQueryLang] = useState('SQL'); - const [jobId, setJobId] = useState(''); + const [queryLang, setQueryLang] = useState(explorerSearchMetadata.lang || QUERY_LANGUAGE.SQL); const sqlService = new SQLService(coreRefs.http); const { application } = coreRefs; @@ -116,7 +104,7 @@ export const DirectSearch = (props: any) => { }, 5000); const requestParams = { tabId }; - const { getLiveTail, getEvents, getAvailableFields, dispatchOnGettingHis } = useFetchEvents({ + const { dispatchOnGettingHis } = useFetchEvents({ pplService: new SQLService(coreRefs.http), requestParams, }); @@ -151,16 +139,10 @@ export const DirectSearch = (props: any) => { const handleQueryLanguageChange = (lang: string) => { if (lang === 'DQL') { - return application!.navigateToUrl( - `../app/data-explorer/discover#?_a=(discover:(columns:!(_source),isDirty:!f,sort:!()),metadata:(indexPattern:'${explorerSearchMetadata.datasources[0].value}',view:discover))&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_q=(filters:!(),query:(language:kuery,query:''))` - ); + application!.navigateToUrl('../app/data-explorer/discover'); + return; } - dispatch( - updateSearchMetaData({ - tabId, - data: { lang }, - }) - ); + dispatch(updateSearchMetaData({ tabId, data: { lang } })); setQueryLang(lang); closeLanguagePopover(); }; @@ -174,10 +156,16 @@ export const DirectSearch = (props: any) => { }; const languagePopOverItems = [ - handleQueryLanguageChange('SQL')}> + handleQueryLanguageChange(QUERY_LANGUAGE.SQL)} + > SQL , - handleQueryLanguageChange('PPL')}> + handleQueryLanguageChange(QUERY_LANGUAGE.PPL)} + > PPL , ]; @@ -194,16 +182,14 @@ export const DirectSearch = (props: any) => { ); - const onQuerySearch = (lang) => { + const onQuerySearch = (lang: string) => { setIsQueryRunning(true); - dispatch( - updateSearchMetaData({ - tabId, - data: { - isPolling: true, - }, - }) - ); + batch(() => { + dispatch( + changeQuery({ tabId, query: { [RAW_QUERY]: tempQuery.replaceAll(PPL_NEWLINE_REGEX, '') } }) + ); + }); + dispatch(updateSearchMetaData({ tabId, data: { isPolling: true, lang } })); sqlService .fetch({ lang: lowerCase(lang), @@ -212,7 +198,6 @@ export const DirectSearch = (props: any) => { }) .then((result) => { if (result.queryId) { - setJobId(result.queryId); startPolling({ queryId: result.queryId, }); @@ -229,7 +214,10 @@ export const DirectSearch = (props: any) => { useEffect(() => { // cancel direct query - if (pollingResult && (pollingResult.status === 'SUCCESS' || pollingResult.datarows)) { + if (!pollingResult) return; + const { status, datarows } = pollingResult; + + if (status === DirectQueryLoadingStatus.SUCCESS || datarows) { // stop polling stopPolling(); setIsQueryRunning(false); @@ -238,16 +226,30 @@ export const DirectSearch = (props: any) => { tabId, data: { isPolling: false, + status: undefined, }, }) ); // update page with data dispatchOnGettingHis(pollingResult, ''); + return; } + dispatch( + updateSearchMetaData({ + tabId, + data: { status }, + }) + ); }, [pollingResult, pollingError]); useEffect(() => { - if (explorerSearchMetadata.isPolling === false) { + return () => { + stopPolling(); + }; + }, []); + + useEffect(() => { + if (!explorerSearchMetadata.isPolling) { stopPolling(); setIsQueryRunning(false); } @@ -255,7 +257,7 @@ export const DirectSearch = (props: any) => { return (
- + {appLogEvents && ( @@ -265,19 +267,21 @@ export const DirectSearch = (props: any) => { )} - - - - - - + {!appLogEvents && ( + + + + + + )} + { isSuggestionDisabled={queryLang === 'SQL'} isDisabled={explorerSearchMetadata.isPolling} /> - {queryLang === 'PPL' && ( + {queryLang === QUERY_LANGUAGE.PPL && ( - - +
- - +
-
- - -
- -
+ } > - - } +
- + + +
- - + +
+ + +
+ +
+ + +
-
- - -
-

- + Expand your time range or modify your query - -

-

- - Your query may not match anything in the current time range, or there may not be any data at all in the currently selected time range. Try change time range, query filters or choose different time fields - -

-
-
-
- -
- - - - + + + +

+ + + Your query may not match anything in the current time range, + or there may not be any data at all in the currently selected time range. + Try change time range, query filters or choose different time fields. + + +

+
+ +
+
+
+ +
+ `; diff --git a/public/components/event_analytics/explorer/__tests__/__snapshots__/data_grid.test.tsx.snap b/public/components/event_analytics/explorer/__tests__/__snapshots__/data_grid.test.tsx.snap index a0e63ed20..e21e9494f 100644 --- a/public/components/event_analytics/explorer/__tests__/__snapshots__/data_grid.test.tsx.snap +++ b/public/components/event_analytics/explorer/__tests__/__snapshots__/data_grid.test.tsx.snap @@ -220,217 +220,225 @@ exports[`Datagrid component Renders data grid component 1`] = ` timeStampField="timestamp" totalHits={1390} > -
- - - - - -
-
+ +
+
+
+ +
-
+ -
- - - - - -
+ + + +
+
+ `; diff --git a/public/components/event_analytics/explorer/datasources/datasources_selection.tsx b/public/components/event_analytics/explorer/datasources/datasources_selection.tsx index 5d4fe5f9a..bbea0a628 100644 --- a/public/components/event_analytics/explorer/datasources/datasources_selection.tsx +++ b/public/components/event_analytics/explorer/datasources/datasources_selection.tsx @@ -3,10 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useState, useMemo } from 'react'; import { batch, useDispatch, useSelector } from 'react-redux'; import { LogExplorerRouterContext } from '../..'; -import { DataSourceSelectable } from '../../../../../../../src/plugins/data/public'; +import { + DataSourceGroup, + DataSourceSelectable, + DataSourceType, +} from '../../../../../../../src/plugins/data/public'; import { coreRefs } from '../../../../framework/core_refs'; import { selectSearchMetaData, @@ -18,109 +22,168 @@ import { reset as resetPatterns } from '../../redux/slices/patterns_slice'; import { reset as resetQueryResults } from '../../redux/slices/query_result_slice'; import { reset as resetVisualization } from '../../redux/slices/visualization_slice'; import { reset as resetVisConfig } from '../../redux/slices/viualization_config_slice'; +import { reset as resetQuery } from '../../redux/slices/query_slice'; +import { SelectedDataSource } from '../../../../../common/types/explorer'; +import { ObservabilityDefaultDataSource } from '../../../../framework/datasources/obs_opensearch_datasource'; +import { + DATA_SOURCE_TYPE_URL_PARAM_KEY, + DATA_SOURCE_NAME_URL_PARAM_KEY, + DEFAULT_DATA_SOURCE_NAME, + DEFAULT_DATA_SOURCE_TYPE, + DEFAULT_DATA_SOURCE_TYPE_NAME, + DEFAULT_DATA_SOURCE_OBSERVABILITY_DISPLAY_NAME, +} from '../../../../../common/constants/data_sources'; + +const getDataSourceState = (selectedSourceState: SelectedDataSource[]) => { + if (selectedSourceState.length === 0) return []; + return [ + { + label: selectedSourceState[0].label, + value: selectedSourceState[0].value, + type: selectedSourceState[0].type, + name: selectedSourceState[0].name, + }, + ]; +}; + +const removeDataSourceFromURLParams = (currURL: string) => { + // Parse the current URL + const currentURL = new URL(currURL); + + // Split the hash into its base and query parts + const [hashBase, hashQuery] = currentURL.hash.split('?'); -export const DataSourceSelection = ({ tabId }) => { + if (hashQuery) { + // Convert the hash query into a URLSearchParams object for easier manipulation + const hashParams = new URLSearchParams(hashQuery); + + // Remove the data source redirection parameters + hashParams.delete(DATA_SOURCE_NAME_URL_PARAM_KEY); + hashParams.delete(DATA_SOURCE_TYPE_URL_PARAM_KEY); + + // Reconstruct the hash + currentURL.hash = hashParams.toString() ? `${hashBase}?${hashParams.toString()}` : hashBase; + + // Update the browser's address bar + history.replaceState({}, '', currentURL.toString()); + } +}; + +export const DataSourceSelection = ({ tabId }: { tabId: string }) => { const { dataSources } = coreRefs; const dispatch = useDispatch(); const routerContext = useContext(LogExplorerRouterContext); const explorerSearchMetadata = useSelector(selectSearchMetaData)[tabId]; - const [activeDataSources, setActiveDataSources] = useState([]); - const [dataSourceOptionList, setDataSourceOptionList] = useState([]); - const [selectedSources, setSelectedSources] = useState([...explorerSearchMetadata.datasources]); - - const resetStateOnDatasourceChange = () => { - dispatch( - resetFields({ - tabId, - }) - ); - dispatch( - resetPatterns({ - tabId, - }) - ); - dispatch( - resetQueryResults({ - tabId, - }) - ); - dispatch( - resetVisConfig({ - tabId, - }) - ); - dispatch( - resetVisualization({ - tabId, - }) - ); - dispatch( - resetCountDistribution({ - tabId, - }) - ); + const [activeDataSources, setActiveDataSources] = useState([]); + const [dataSourceOptionList, setDataSourceOptionList] = useState([]); + const [selectedSources, setSelectedSources] = useState( + getDataSourceState(explorerSearchMetadata.datasources) + ); + + /** + * Resets various states associated with data source changes. + */ + const resetStateOnDataSourceChange = () => { + dispatch(resetQuery({ tabId })); + dispatch(resetFields({ tabId })); + dispatch(resetFields({ tabId })); + dispatch(resetPatterns({ tabId })); + dispatch(resetQueryResults({ tabId })); + dispatch(resetVisConfig({ tabId })); + dispatch(resetVisualization({ tabId })); + dispatch(resetCountDistribution({ tabId })); }; - const handleSourceChange = (selectedSource) => { + /** + * Handle the changes in the data source selection. + * + * @param {SelectedDataSource[]} selectedSource - The newly selected data source(s). + */ + const handleSourceChange = (selectedSource: SelectedDataSource[]) => { batch(() => { - resetStateOnDatasourceChange(); + resetStateOnDataSourceChange(); dispatch( - updateSearchMetaData({ - tabId, - data: { - datasources: selectedSource, - }, - }) + updateSearchMetaData({ tabId, data: { datasources: getDataSourceState(selectedSource) } }) ); }); setSelectedSources(selectedSource); }; useEffect(() => { - setSelectedSources([...(explorerSearchMetadata.datasources || [])]); - return () => {}; + setSelectedSources(getDataSourceState(explorerSearchMetadata.datasources)); }, [explorerSearchMetadata.datasources]); const handleDataSetFetchError = useCallback(() => { - return (error) => {}; + return (error: Error) => { + console.error('Error fetching dataset:', error); + }; }, []); + /** + * Subscribe to data source updates and manage the active data sources state. + */ useEffect(() => { const subscription = dataSources.dataSourceService.dataSources$.subscribe( (currentDataSources) => { - setActiveDataSources([...Object.values(currentDataSources)]); + // temporary solution for 2.11 to render OpenSearch / default cluster for observability + // local indices and index patterns, while keep listing all index patterns for data explorer + // it filters the registered index pattern data sources in data plugin, and attach default cluster + // for all indices + setActiveDataSources([ + new ObservabilityDefaultDataSource({ + name: DEFAULT_DATA_SOURCE_NAME, + type: DEFAULT_DATA_SOURCE_TYPE, + metadata: null, + }), + ...Object.values(currentDataSources).filter((ds) => ds.type !== DEFAULT_DATA_SOURCE_TYPE), + ]); } ); return () => subscription.unsubscribe(); }, []); + /** + * Check for URL parameters to update the data source if redirected from discover. + * Removes data source name and type from URL after processing. This is temporary solution for 2.11 + * as observability log explorer will adopt view service. + */ useEffect(() => { - // update datasource if url contains - const datasourceName = routerContext?.searchParams.get('datasourceName'); - const datasourceType = routerContext?.searchParams.get('datasourceType'); + const datasourceName = routerContext?.searchParams.get(DATA_SOURCE_NAME_URL_PARAM_KEY); + const datasourceType = routerContext?.searchParams.get(DATA_SOURCE_TYPE_URL_PARAM_KEY); if (datasourceName && datasourceType) { - dispatch( - updateSearchMetaData({ - tabId, - data: { - datasources: [ - { - label: datasourceName, - type: datasourceType, - }, - ], - }, - }) - ); + // remove datasourceName and datasourceType from URL for a clean search state + removeDataSourceFromURLParams(window.location.href); + batch(() => { + resetStateOnDataSourceChange(); + dispatch( + updateSearchMetaData({ + tabId, + data: { datasources: [{ label: datasourceName, type: datasourceType }] }, + }) + ); + }); } }, []); + /** + * Process the data source options to display different than discover's group names. + * Temporary solution for version 2.11. + */ + const memorizedDataSourceOptionList = useMemo(() => { + return dataSourceOptionList.map((dsOption) => { + if (dsOption.label === DEFAULT_DATA_SOURCE_TYPE_NAME) { + dsOption.label = DEFAULT_DATA_SOURCE_OBSERVABILITY_DISPLAY_NAME; + } + return dsOption; + }); + }, [dataSourceOptionList]); + return ( { + const explorerSearchMeta = useSelector(selectSearchMetaData)[tabId] || {}; const dispatch = useDispatch(); return ( } title={

Query Processing

} body={ - { - dispatch( - updateSearchMetaData({ - tabId, - data: { - isPolling: false, - }, - }) - ); - }} - > - Cancel - + <> + + Status: {explorerSearchMeta.status ?? DirectQueryLoadingStatus.SCHEDULED} + + + { + dispatch( + updateSearchMetaData({ + tabId, + data: { + isPolling: false, + }, + }) + ); + }} + > + Cancel + + } /> ); diff --git a/public/components/event_analytics/explorer/events_views/data_grid.scss b/public/components/event_analytics/explorer/events_views/data_grid.scss index bf5392d3b..af387c2cd 100644 --- a/public/components/event_analytics/explorer/events_views/data_grid.scss +++ b/public/components/event_analytics/explorer/events_views/data_grid.scss @@ -225,9 +225,6 @@ // SASSTODO: replace the z-index value with a variable .dscWrapper { - padding-left: $euiSizeXL; - padding-right: $euiSizeS; - margin-top: $euiSizeM; z-index: 1; @include euiBreakpoint('xs', 's', 'm') { padding-left: $euiSizeS; diff --git a/public/components/event_analytics/explorer/events_views/data_grid.tsx b/public/components/event_analytics/explorer/events_views/data_grid.tsx index f6019d42b..56cac6abb 100644 --- a/public/components/event_analytics/explorer/events_views/data_grid.tsx +++ b/public/components/event_analytics/explorer/events_views/data_grid.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo, useState, useRef, RefObject, Fragment, useCallback } from 'react'; +import React, { useMemo, useState, useRef, Fragment, useCallback } from 'react'; import { EuiDataGrid, EuiDescriptionList, @@ -11,26 +11,21 @@ import { EuiDescriptionListTitle, EuiDataGridColumn, EuiDataGridSorting, + EuiPanel, } from '@elastic/eui'; import moment from 'moment'; -import dompurify from 'dompurify'; -import datemath from '@elastic/datemath'; import { MutableRefObject } from 'react'; -import { GridSortingColumn, IExplorerFields, IField } from '../../../../../common/types/explorer'; +import { IExplorerFields, IField } from '../../../../../common/types/explorer'; import { DATE_DISPLAY_FORMAT, - DATE_PICKER_FORMAT, + DEFAULT_EMPTY_EXPLORER_FIELDS, DEFAULT_SOURCE_COLUMN, DEFAULT_TIMESTAMP_COLUMN, } from '../../../../../common/constants/explorer'; import { HttpSetup } from '../../../../../../../src/core/public'; import PPLService from '../../../../services/requests/ppl'; -import { FlyoutButton, IDocType } from './docViewRow'; +import { FlyoutButton } from './docViewRow'; import { useFetchEvents } from '../../hooks'; -import { - PPL_INDEX_INSERT_POINT_REGEX, - PPL_NEWLINE_REGEX, -} from '../../../../../common/constants/shared'; import { redoQuery } from '../../utils/utils'; interface DataGridProps { @@ -61,55 +56,76 @@ export function DataGrid(props: DataGridProps) { requestParams, startTime, endTime, - storedSelectedColumns, } = props; - const { getEvents } = useFetchEvents({ + const { fetchEvents } = useFetchEvents({ pplService, requestParams, }); + const selectedColumns = + explorerFields.selectedFields.length > 0 + ? explorerFields.selectedFields + : DEFAULT_EMPTY_EXPLORER_FIELDS; // useRef instead of useState somehow solves the issue of user triggered sorting not // having any delays const sortingFields: MutableRefObject = useRef([]); const pageFields = useRef([0, 100]); + const [data, setData] = useState(rows); + // setSort and setPage are used to change the query and send a direct request to get data const setSort = (sort: EuiDataGridSorting['columns']) => { sortingFields.current = sort; - redoQuery(startTime, endTime, rawQuery, timeStampField, sortingFields, pageFields, getEvents); + + redoQuery( + startTime, + endTime, + rawQuery, + timeStampField, + sortingFields, + pageFields, + fetchEvents, + setData + ); }; const setPage = (page: number[]) => { pageFields.current = page; - redoQuery(startTime, endTime, rawQuery, timeStampField, sortingFields, pageFields, getEvents); + redoQuery( + startTime, + endTime, + rawQuery, + timeStampField, + sortingFields, + pageFields, + fetchEvents, + setData + ); }; // creates the header for each column listing what that column is const dataGridColumns = useMemo(() => { - if (storedSelectedColumns.length > 0) { - const columns: EuiDataGridColumn[] = []; - storedSelectedColumns.map(({ name, type }) => { - if (name === 'timestamp') { - columns.push(DEFAULT_TIMESTAMP_COLUMN); - } else if (name === '_source') { - columns.push(DEFAULT_SOURCE_COLUMN); - } else { - columns.push({ - id: name, - display: name, - isSortable: true, // TODO: add functionality here based on type - }); - } - }); - return columns; - } - return []; - }, [storedSelectedColumns]); + const columns: EuiDataGridColumn[] = []; + selectedColumns.map(({ name, type }) => { + if (name === 'timestamp') { + columns.push(DEFAULT_TIMESTAMP_COLUMN); + } else if (name === '_source') { + columns.push(DEFAULT_SOURCE_COLUMN); + } else { + columns.push({ + id: name, + display: name, + isSortable: true, // TODO: add functionality here based on type + }); + } + }); + return columns; + }, [explorerFields, totalHits]); // used for which columns are visible and their order const dataGridColumnVisibility = useMemo(() => { - if (storedSelectedColumns.length > 0) { + if (selectedColumns.length > 0) { const columns: string[] = []; - storedSelectedColumns.map(({ name }) => { + selectedColumns.map(({ name }) => { columns.push(name); }); return { @@ -121,7 +137,7 @@ export function DataGrid(props: DataGridProps) { } // default shown fields throw new Error('explorer data grid stored columns empty'); - }, [storedSelectedColumns]); + }, [explorerFields, totalHits]); // sets the very first column, which is the button used for the flyout of each row const dataGridLeadingColumns = useMemo(() => { @@ -155,23 +171,23 @@ export function DataGrid(props: DataGridProps) { width: 40, }, ]; - }, [rows, http, explorerFields, pplService, rawQuery, timeStampField]); + }, [rows, http, explorerFields, pplService, rawQuery, timeStampField, totalHits]); // renders what is shown in each cell, i.e. the content of each row const dataGridCellRender = useCallback( ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => { const trueIndex = rowIndex % pageFields.current[1]; - if (trueIndex < rows.length) { + if (trueIndex < data.length) { if (columnId === '_source') { return ( - {Object.keys(rows[trueIndex]).map((key) => ( + {Object.keys(data[trueIndex]).map((key) => ( {key} - {rows[trueIndex][key]} + {data[trueIndex][key]} ))} @@ -179,13 +195,13 @@ export function DataGrid(props: DataGridProps) { ); } if (columnId === 'timestamp') { - return `${moment(rows[trueIndex][columnId]).format(DATE_DISPLAY_FORMAT)}`; + return `${moment(data[trueIndex][columnId]).format(DATE_DISPLAY_FORMAT)}`; } - return `${rows[trueIndex][columnId]}`; + return `${data[trueIndex][columnId]}`; } return null; }, - [rows, pageFields, explorerFields] + [data, rows, pageFields, explorerFields, totalHits] ); // ** Pagination config @@ -197,7 +213,7 @@ export function DataGrid(props: DataGridProps) { setPage([0, pageSize]); return { pageIndex: 0, pageSize }; }), - [setPagination, setPage] + [setPagination, setPage, totalHits] ); // changing the page index, keep page size constant const onChangePage = useCallback( @@ -207,23 +223,23 @@ export function DataGrid(props: DataGridProps) { return { pageSize, pageIndex }; }); }, - [setPagination, setPage] + [setPagination, setPage, totalHits] ); const rowHeightsOptions = useMemo( () => ({ defaultHeight: { // if source is listed as a column, add extra space - lineCount: storedSelectedColumns.some((obj) => obj.name === '_source') ? 3 : 1, + lineCount: selectedColumns.some((obj) => obj.name === '_source') ? 3 : 1, }, }), - [storedSelectedColumns] + [explorerFields, totalHits] ); // TODO: memoize the expensive table below return ( - <> +
- +
); } diff --git a/public/components/event_analytics/explorer/events_views/docView.scss b/public/components/event_analytics/explorer/events_views/docView.scss index c395ed4da..f05023e21 100644 --- a/public/components/event_analytics/explorer/events_views/docView.scss +++ b/public/components/event_analytics/explorer/events_views/docView.scss @@ -37,8 +37,8 @@ .events-flyout-resize { position: absolute; - right: 30px; - top: 0px; + right: 38px; + top: 8px; z-index: 3; } diff --git a/public/components/event_analytics/explorer/events_views/doc_flyout.tsx b/public/components/event_analytics/explorer/events_views/doc_flyout.tsx index dc1629b71..feb0a57e9 100644 --- a/public/components/event_analytics/explorer/events_views/doc_flyout.tsx +++ b/public/components/event_analytics/explorer/events_views/doc_flyout.tsx @@ -85,7 +85,7 @@ export const DocFlyout = ({ { - return ( - dataSourcePluggables[explorerSearchMeta.datasources[0]?.type] || - dataSourcePluggables.DEFAULT_INDEX_PATTERNS - ); + return explorerSearchMeta.datasources?.[0]?.type + ? dataSourcePluggables[explorerSearchMeta?.datasources[0]?.type] + : dataSourcePluggables.DEFAULT_INDEX_PATTERNS; }, [explorerSearchMeta.datasources]); const { ui } = - currentPluggable?.getComponentSetForVariation('languages', explorerSearchMeta.lang || 'SQL') || - {}; + currentPluggable?.getComponentSetForVariation( + 'languages', + explorerSearchMeta.lang || QUERY_LANGUAGE.SQL + ) || {}; const SearchBar = ui?.SearchBar || Search; - + const isDefaultDataSourceType = + explorerSearchMeta.datasources?.[0]?.type === DEFAULT_DATA_SOURCE_TYPE; const selectedIntervalRef = useRef<{ text: string; value: string; @@ -219,6 +223,8 @@ export const Explorer = ({ const isLiveTailOnRef = useRef(false); const liveTailTabIdRef = useRef(''); const liveTailNameRef = useRef('Live'); + const savedObjectLoader = useRef(undefined); + const isObjectIdUpdatedFromSave = useRef(false); // Flag to prevent reload when the current search's objectId changes due to a save operation. queryRef.current = query; selectedPanelNameRef.current = selectedPanelName; explorerFieldsRef.current = explorerFields; @@ -311,7 +317,7 @@ export const Explorer = ({ !isEqual(getIndexPatternFromRawQuery(currentQuery), getIndexPatternFromRawQuery(prevTabQuery)); const updateTabData = async (objectId: string) => { - await new PPLSavedObjectLoader( + savedObjectLoader.current = new ExplorerSavedObjectLoader( getSavedObjectsClient({ objectId, objectType: 'savedQuery' }), notifications, { @@ -338,10 +344,26 @@ export const Explorer = ({ setSubType, setSelectedContentTab, fetchData, + dispatchOnGettingHis, } - ).load(); + ); + savedObjectLoader.current.load(); }; + // stop polling when cancel or unmounts + useEffect(() => { + const sol: ExplorerSavedObjectLoader | undefined = savedObjectLoader.current; + if (!explorerSearchMeta.isPolling && sol !== undefined && sol.getPollingInstance) { + sol?.getPollingInstance()!.stopPolling(); + savedObjectLoader.current = undefined; + } + return () => { + if (sol && sol.getPollingInstance) { + sol?.getPollingInstance()!.stopPolling(); + } + }; + }, [explorerSearchMeta.isPolling]); + const prepareAvailability = async () => { setSelectedContentTab(TAB_CHART_ID); setTriggerAvailability(true); @@ -371,8 +393,9 @@ export const Explorer = ({ }, []); useEffect(() => { - if (savedObjectId) { + if (savedObjectId && !isObjectIdUpdatedFromSave.current) { updateTabData(savedObjectId); + isObjectIdUpdatedFromSave.current = false; } }, [savedObjectId]); @@ -384,10 +407,7 @@ export const Explorer = ({ await dispatch( changeDateRange({ tabId: requestParams.tabId, - data: { - [RAW_QUERY]: queryRef.current![RAW_QUERY], - [SELECTED_DATE_RANGE]: timeRange, - }, + data: { [RAW_QUERY]: queryRef.current![RAW_QUERY], [SELECTED_DATE_RANGE]: timeRange }, }) ); }; @@ -423,48 +443,13 @@ export const Explorer = ({ } }; - useEffect(() => { - if (explorerSearchMeta.datasources?.[0]?.type !== 'DEFAULT_INDEX_PATTERNS') { - dispatch( - changeQuery({ - tabId, - query: { - [RAW_QUERY]: '', - [FINAL_QUERY]: '', - }, - }) - ); - } - }, [explorerSearchMeta.datasources]); - const handleOverrideTimestamp = async (timestamp: IField) => { setIsOverridingTimestamp(true); - await dispatch( - changeQuery({ - tabId, - query: { - [SELECTED_TIMESTAMP]: timestamp?.name || '', - }, - }) - ); + await dispatch(changeQuery({ tabId, query: { [SELECTED_TIMESTAMP]: timestamp?.name || '' } })); setIsOverridingTimestamp(false); handleQuerySearch(); }; - const handleOverridePattern = async (pattern: IField) => { - setIsOverridingPattern(true); - await setDefaultPatternsField( - '', - pattern.name, - getErrorHandler('Error overriding default pattern') - ); - setIsOverridingPattern(false); - await getPatterns( - selectedIntervalRef.current?.value.replace(/^auto_/, '') || 'y', - getErrorHandler('Error fetching patterns') - ); - }; - const totalHits: number = useMemo(() => { if (isLiveTailOn && countDistribution?.data) { const hits = reduce( @@ -481,20 +466,16 @@ export const Explorer = ({ }, [countDistribution?.data]); const dateRange = getDateRange(startTime, endTime, query); - - const [storedExplorerFields, setStoredExplorerFields] = useState(explorerFields); - const mainContent = useMemo(() => { return ( -
+
{explorerData && !isEmpty(explorerData.jsonData) ? ( - {explorerSearchMeta.datasources?.[0]?.type === 'DEFAULT_INDEX_PATTERNS' && ( + {(isDefaultDataSourceType || appLogEvents) && ( - {/* */} {countDistribution?.data && !isLiveTailOnRef.current && ( - <> + item.value === selectedIntrv ); const intrv = selectedIntrv.replace(/^auto_/, ''); + dispatch( + updateCountDistribution({ tabId, data: { selectedInterval: intrv } }) + ); getCountVisualizations(intrv); selectedIntervalRef.current = timeIntervalOptions[intervalOptionsIndex]; getPatterns(intrv, getErrorHandler('Error fetching patterns')); @@ -521,12 +505,12 @@ export const Explorer = ({ startTime={appLogEvents ? startTime : dateRange[0]} endTime={appLogEvents ? endTime : dateRange[1]} /> - + )} )} - {explorerSearchMeta.datasources?.[0]?.type === 'DEFAULT_INDEX_PATTERNS' && ( + {(isDefaultDataSourceType || appLogEvents) && ( @@ -568,25 +552,26 @@ export const Explorer = ({ )} - - 0 - ? storedExplorerFields.selectedFields - : DEFAULT_EMPTY_EXPLORER_FIELDS - } - /> + {(countDistribution.data?.['count()'] || explorerData?.datarows?.length) && ( + + )} @@ -604,13 +589,11 @@ export const Explorer = ({ isPanelTextFieldInvalid, explorerData, explorerFields, - isSidebarClosed, countDistribution, explorerVisualizations, isOverridingTimestamp, query, isLiveTailOnRef.current, - isOverridingPattern, isQueryRunning, ]); @@ -638,7 +621,7 @@ export const Explorer = ({ }; const explorerVis = useMemo(() => { - return explorerSearchMeta.datasources?.[0]?.type === 'DEFAULT_INDEX_PATTERNS' ? ( + return isDefaultDataSourceType || appLogEvents ? ( { await dispatch( - changeQuery({ - tabId, - query: { - [RAW_QUERY]: updateQuery.replaceAll(PPL_NEWLINE_REGEX, ''), - }, - }) + changeQuery({ tabId, query: { [RAW_QUERY]: updateQuery.replaceAll(PPL_NEWLINE_REGEX, '') } }) ); }; @@ -698,14 +676,7 @@ export const Explorer = ({ async (availability?: boolean) => { // clear previous selected timestamp when index pattern changes if (isIndexPatternChanged(tempQuery, query[RAW_QUERY])) { - await dispatch( - changeQuery({ - tabId, - query: { - [SELECTED_TIMESTAMP]: '', - }, - }) - ); + await dispatch(changeQuery({ tabId, query: { [SELECTED_TIMESTAMP]: '' } })); await setDefaultPatternsField('', ''); } if (availability !== true) { @@ -718,26 +689,21 @@ export const Explorer = ({ const handleQueryChange = async (newQuery: string) => setTempQuery(newQuery); - const getSavingCommonParams = ( - queryState: IQuery, - fields: IExplorerFields, - savingTitle: string - ) => { - return { - query: buildRawQuery(query, appBaseQuery), - fields: fields[SELECTED_FIELDS], - dateRange: queryState[SELECTED_DATE_RANGE], - name: savingTitle, - timestamp: queryState[SELECTED_TIMESTAMP], - }; - }; - const handleSavingObject = useCallback(() => { const isOnEventPage = isEqual(selectedContentTabId, TAB_EVENT_ID); const isObjTypeMatchQuery = isEqual(query[SAVED_OBJECT_TYPE], SAVED_QUERY); const isObjTypeMatchVis = isEqual(query[SAVED_OBJECT_TYPE], SAVED_VISUALIZATION); const isTabHasObjID = !isEmpty(query[SAVED_OBJECT_ID]); - const commonParams = getSavingCommonParams(query, explorerFields, selectedPanelNameRef.current); + const commonParams = getSavingCommonParams( + query, + appBaseQuery, + explorerFields, + selectedPanelNameRef.current, + explorerSearchMeta + ); + + // Set the flag to differentiate between an object save action and a load action + isObjectIdUpdatedFromSave.current = true; let soClient; if (isOnEventPage) { @@ -745,7 +711,10 @@ export const Explorer = ({ soClient = new SaveAsCurrentQuery( { tabId, notifications }, { dispatch, updateTabName }, - PPLSavedQueryClient.getInstance(), + getSavedObjectsClient({ + objectId: query[SAVED_OBJECT_ID], + objectType: 'savedQuery', + }), { ...commonParams, objectId: query[SAVED_OBJECT_ID], @@ -755,7 +724,7 @@ export const Explorer = ({ soClient = new SaveAsNewQuery( { tabId, history, notifications, showPermissionErrorToast }, { batch, dispatch, changeQuery, updateTabName }, - new PPLSavedQueryClient(http), + OSDSavedSearchClient.getInstance(), { ...commonParams } ); } @@ -813,10 +782,9 @@ export const Explorer = ({ explorerFields, subType, selectedCustomPanelOptions, + explorerSearchMeta, ]); - // live tail - const liveTailLoop = async ( name: string, startingTime: string, @@ -913,104 +881,89 @@ export const Explorer = ({ handleQueryChange, }} > -
- - - - -
- -
-
+ + + + {!appLogEvents && ( + + + + )} + + + + + + +
+ -
- + handleTimePickerChange(timeRange) + } + selectedPanelName={selectedPanelNameRef.current} + selectedCustomPanelOptions={selectedCustomPanelOptions} + setSelectedPanelName={setSelectedPanelName} + setSelectedCustomPanelOptions={setSelectedCustomPanelOptions} + handleSavingObject={handleSavingObject} + isPanelTextFieldInvalid={isPanelTextFieldInvalid} + savedObjects={savedObjects} + showSavePanelOptionsList={isEqual(selectedContentTabId, TAB_CHART_ID)} + handleTimeRangePickerRefresh={handleTimeRangePickerRefresh} + isLiveTailPopoverOpen={isLiveTailPopoverOpen} + closeLiveTailPopover={() => setIsLiveTailPopoverOpen(false)} + popoverItems={popoverItems} + isLiveTailOn={isLiveTailOnRef.current} + selectedSubTabId={selectedContentTabId} + searchBarConfigs={searchBarConfigs} + getSuggestions={parseGetSuggestions} + onItemSelect={onItemSelect} + tabId={tabId} + baseQuery={appBaseQuery} + stopLive={stopLive} + setIsLiveTailPopoverOpen={setIsLiveTailPopoverOpen} + liveTailName={liveTailNameRef.current} + curVisId={curVisId} + setSubType={setSubType} + http={http} + setIsQueryRunning={setIsQueryRunning} + /> + {explorerSearchMeta.isPolling ? ( + + ) : ( + tab.id === selectedContentTabId)} + onTabClick={(selectedTab: EuiTabbedContentTab) => + handleContentTabClick(selectedTab) } - storedExplorerFields={ - storedExplorerFields.availableFields.length > 0 - ? storedExplorerFields - : explorerFields - } - setStoredExplorerFields={setStoredExplorerFields} + tabs={contentTabs} + size="s" /> -
+ )}
- - - handleTimePickerChange(timeRange)} - selectedPanelName={selectedPanelNameRef.current} - selectedCustomPanelOptions={selectedCustomPanelOptions} - setSelectedPanelName={setSelectedPanelName} - setSelectedCustomPanelOptions={setSelectedCustomPanelOptions} - handleSavingObject={handleSavingObject} - isPanelTextFieldInvalid={isPanelTextFieldInvalid} - savedObjects={savedObjects} - showSavePanelOptionsList={isEqual(selectedContentTabId, TAB_CHART_ID)} - handleTimeRangePickerRefresh={handleTimeRangePickerRefresh} - isLiveTailPopoverOpen={isLiveTailPopoverOpen} - closeLiveTailPopover={() => setIsLiveTailPopoverOpen(false)} - popoverItems={popoverItems} - isLiveTailOn={isLiveTailOnRef.current} - selectedSubTabId={selectedContentTabId} - searchBarConfigs={searchBarConfigs} - getSuggestions={parseGetSuggestions} - onItemSelect={onItemSelect} - tabId={tabId} - baseQuery={appBaseQuery} - stopLive={stopLive} - setIsLiveTailPopoverOpen={setIsLiveTailPopoverOpen} - liveTailName={liveTailNameRef.current} - curVisId={curVisId} - setSubType={setSubType} - http={http} - setIsQueryRunning={setIsQueryRunning} - /> - {explorerSearchMeta.isPolling ? ( - - ) : ( - tab.id === selectedContentTabId)} - onTabClick={(selectedTab: EuiTabbedContentTab) => - handleContentTabClick(selectedTab) - } - tabs={contentTabs} - size="s" - /> - )} - - -
+
+ + ); }; diff --git a/public/components/event_analytics/explorer/log_explorer.tsx b/public/components/event_analytics/explorer/log_explorer.tsx index 05bd47544..37296e266 100644 --- a/public/components/event_analytics/explorer/log_explorer.tsx +++ b/public/components/event_analytics/explorer/log_explorer.tsx @@ -4,6 +4,7 @@ */ /* eslint-disable react-hooks/exhaustive-deps */ import { isEmpty } from 'lodash'; +import { EuiPage } from '@elastic/eui'; import React, { useContext, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; diff --git a/public/components/event_analytics/explorer/no_results.tsx b/public/components/event_analytics/explorer/no_results.tsx index 91cc8ab62..14ee3a573 100644 --- a/public/components/event_analytics/explorer/no_results.tsx +++ b/public/components/event_analytics/explorer/no_results.tsx @@ -4,48 +4,46 @@ */ import React from 'react'; -import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; -import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@osd/i18n/react'; +import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiPage, EuiSpacer, EuiText } from '@elastic/eui'; export const NoResults = () => { return ( - - <> - - - - - - } - color="warning" - iconType="help" - data-test-subj="discoverNoResults" - /> - <> - - -

- -

-

- -

-
- -
-
- -
+ + + + + } + color="warning" + iconType="help" + data-test-subj="observabilityNoResultsCallout" + /> + + + + +

+ +

+

+ +

+
+
+
+
); }; diff --git a/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/field.test.tsx.snap b/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/field.test.tsx.snap index 0abf095fc..0b906bf4e 100644 --- a/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/field.test.tsx.snap +++ b/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/field.test.tsx.snap @@ -17,137 +17,20 @@ exports[`Field component Renders a sidebar field 1`] = ` showTimestampOverrideButton={true} showToggleButton={true} > - - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- AGENT -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - agent - - - } - isActive={false} - onClick={[Function]} - size="s" +
- -
+ +
+ + - - - - - - - - - - - + + + + + +
+
+ + + + + + +
- - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" + -
+ } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - - - + + +
+ + +
- - - - - - -
+ > + + + + + + + +
+
- + `; diff --git a/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap b/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap index afc799306..fe7ab15c3 100644 --- a/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap +++ b/public/components/event_analytics/explorer/sidebar/__tests__/__snapshots__/sidebar.test.tsx.snap @@ -163,107 +163,149 @@ exports[`Siderbar component Renders empty sidebar component 1`] = ` } } > -
-
- - -
- - + - - -
- - - - - -
-
+ + +
+ + + + + +
+
+
+
+
+
+
-
- - -
- -
- -
- + + + <_EuiSplitPanelInner + className="eui-yScroll" + paddingSize="none" + > + +
+ + +
+
+ @@ -796,343 +838,307 @@ exports[`Siderbar component Renders sidebar component 1`] = ` } } > -
-
- - -
- - + - - -
- - - - - -
-
+ + + + + +
+ +
+
+ + +
-
- - -
- -
- -
- - - Query fields - - - } - id="fieldSelector__queriedFields" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - paddingSize="xs" - > -
+ + <_EuiSplitPanelInner + className="eui-yScroll" + paddingSize="none" > -
- -
-
- -
-
+ + + + - -
-
- - - -
- - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- DOUBLE_PER_IP_BYTES -

-
-
- - Long - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - double_per_ip_bytes - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ double_per_ip_bytes +
+
+
+
+ - - -
- double_per_ip_bytes -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+
- +
- - - - - - +
+ + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- HOST -

-
-
- - Text - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - host - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ host +
+
+
+
+ - - -
- host -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+
- +
- - - - - - +
+ + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- IP_COUNT -

-
-
- - Integer - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - ip_count - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ ip_count +
+
+
+
+ - - -
- ip_count -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+
- +
- - - - - - +
+ + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- PER_IP_BYTES -

-
-
- - Long - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - per_ip_bytes - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ per_ip_bytes +
+
+
+
+ - - -
- per_ip_bytes -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- RESP_CODE -

-
-
- - Text - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - resp_code - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ resp_code +
+
+
+
+ - - -
- resp_code -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- SUM_BYTES -

-
-
- - Long - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - sum_bytes - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ sum_bytes +
+
+
+
+ - - -
- sum_bytes -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
-
- -
- - - - - - - - - - - -
- - - - Selected Fields - - - } - id="fieldSelector__selectedFields" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - paddingSize="xs" - > -
-
-
+ + + + + +
+ +
+
+ + + + - - - - - - - + + + - - - Selected Fields - - - - -
-
- -
-
- -
-
- - - -
-
- -
-
-
-
-
-
-
-
-
- - - -
- - - - Available Fields - - - } - id="fieldSelector__availableFields" - initialIsOpen={true} - isLoading={false} - isLoadingMessage={false} - paddingSize="xs" - > -
-
-
+
+ + + + - - - - - - - + + + - - - Available Fields - - - - -
-
- -
-
- -
-
- - - -
- - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- AGENT -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - agent - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ agent +
+
+
+
+ - - -
- agent -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + +
+
- +
- - - - - - +
+ + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- BYTES -

-
-
- - Long - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - bytes - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ bytes +
+
+
+
+ - - -
- bytes -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- CLIENTIP -

-
-
- - Ip - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - clientip - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ clientip +
+
+
+
+ - - -
- clientip -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- EVENT -

-
-
- - Struct - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - event - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ event +
+
+
+
+ - - -
- event -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- EXTENSION -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - extension - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ extension +
+
+
+
+ - - -
- extension -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- GEO -

-
-
- - Struct - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - geo - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ geo +
+
+
+
+ - - -
- geo -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- HOST -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - host - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ host +
+
+
+
+ - - -
- host -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- INDEX -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - index - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ index +
+
+
+
+ - - -
- index -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- IP -

-
-
- - Ip - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - ip - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ ip +
+
+
+
+ - - -
- ip -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- MACHINE -

-
-
- - Struct - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - machine - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ machine +
+
+
+
+ - - -
- machine -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - +
+ +
- - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- MEMORY -

-
-
- - Double - -
- -
- - - - - - - - } - fieldIcon={ - - } - fieldName={ - - - memory - - - } - isActive={false} - onClick={[Function]} - size="s" - > -
-
+ + +
+ +
+ memory +
+
+
+
+ - - -
- memory -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+
-
+
-
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- MESSAGE -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - message - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ message +
+
+
+
+ - - -
- message -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- PHPMEMORY -

-
-
- - Long - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - phpmemory - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ phpmemory +
+
+
+
+ - - -
- phpmemory -
-
-
-
- -
- - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
-
-
-
- + + +
+ + +
- - - - - - -
+ > + + + + + + +
+
+ - + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- REFERER -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - referer - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ referer +
+
+
+
+ - - -
- referer -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- REQUEST -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - request - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ request +
+
+
+
+ - - -
- request -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- RESPONSE -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - response - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ response +
+
+
+
+ - - -
- response -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- TAGS -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - tags - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ tags +
+
+
+
+ - - -
- tags -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - - Default Timestamp - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- TIMESTAMP -

-
-
- - Timestamp - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - timestamp - - + className="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow dscSidebar__item euiDraggable__item" + data-attr-field="timestamp" + data-test-subj="fieldList-field" + > + +
-
+ + +
+ +
+ timestamp +
+
+
+
+ - - -
- timestamp -
-
-
-
- -
- - - - - - - - Default Timestamp - - - - - + + + - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" +
-
+ + + + + + + Default Timestamp + + + + + +
+ +
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} + onBlur={[Function]} + onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" + > +
+
- - + > + + + + + +
-
- - - - + + +
+ + +
- - - - - - -
+ > + + + + + +
+
+
+
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - Override - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- URL -

-
-
- - String - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - url - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ url +
+
+
+
+ - - -
- url -
-
-
-
- -
- - - - - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ + + + +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
- + + + + + + + + + - - - -
- - - - - - - - Override - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > - - - -

- UTC_TIME -

-
-
- - Timestamp - -
- -
-
- - - - - - - } - fieldIcon={ - - } - fieldName={ - - - utc_time - - - } - isActive={false} - onClick={[Function]} - size="s" +
-
+ + +
+ +
+ utc_time +
+
+
+
+ - - -
- utc_time -
-
-
-
- -
- - - - + + + - - - - - - - - - - } - closePopover={[Function]} - display="block" - hasArrow={true} - isOpen={false} - onBlur={[Function]} - onFocus={[Function]} - ownFocus={true} - panelClassName="explorerSidebarItem__fieldPopoverPanel" - panelPaddingSize="m" - > -
+ + + + + +
+ +
+
+ +
+ + + + } + closePopover={[Function]} + display="block" + hasArrow={true} + isOpen={false} onBlur={[Function]} onFocus={[Function]} + ownFocus={true} + panelClassName="explorerSidebarItem__fieldPopoverPanel" + panelPaddingSize="m" >
- - - + > + + + + + +
-
- - -
- + + +
+ + +
- - - - - - -
+ > + + + + + + + + +
- + -
-
-
-
-
-
- -
- - - - - - - - - - - - + + + + + + + +
+ +
+ + + + + + + + + + diff --git a/public/components/event_analytics/explorer/sidebar/field.tsx b/public/components/event_analytics/explorer/sidebar/field.tsx index b55f5ff2c..69e7fba1b 100644 --- a/public/components/event_analytics/explorer/sidebar/field.tsx +++ b/public/components/event_analytics/explorer/sidebar/field.tsx @@ -16,6 +16,7 @@ import { EuiFlexItem, EuiTitle, EuiText, + EuiBadge, } from '@elastic/eui'; import { FieldButton } from '../../../common/field_button'; import { FieldIcon } from '../../../common/field_icon'; @@ -73,57 +74,91 @@ export const Field = (props: IFieldProps) => { onToggleField(fields); }; - const getFieldActionDOM = () => { - return ( - <> - - <> - {isEqual(field.type, 'string') ? ( - isEqual(selectedPattern, field.name) ? ( - - Default Pattern - - ) : isOverridingPattern ? ( + return ( + + + + + + {field.name} + + + <> + {isEqual(field.type, 'string') ? ( + isEqual(selectedPattern, field.name) ? ( + + {' '} + + + Default Pattern + + + + ) : isOverridingPattern ? ( + - ) : ( + + ) : ( + handleOverridePattern(field)} data-test-subj="eventExplorer__overrideDefaultPattern" + className="dscSidebarField__actionButton" > Override - ) - ) : null} - - - - <> - {showTimestampOverrideButton && isEqual(field.type, 'timestamp') ? ( - isEqual(selectedTimestamp, field.name) ? ( - - Default Timestamp - - ) : isOverridingTimestamp ? ( + + ) + ) : null} + + + + <> + {showTimestampOverrideButton && isEqual(field.type, 'timestamp') ? ( + isEqual(selectedTimestamp, field.name) ? ( + + + {' '} + + Default Timestamp + + + + ) : isOverridingTimestamp ? ( + - ) : ( + + ) : ( + handleOverrideTimestamp(field)} data-test-subj="eventExplorer__overrideDefaultTimestamp" + className="dscSidebarField__actionButton" > Override - ) - ) : null} - - + + ) + ) : null} + + + { closePopover={() => setIsFieldDetailsOpen(false)} anchorPosition="rightUp" panelClassName="explorerSidebarItem__fieldPopoverPanel" - button={} + button={ + + } > @@ -146,6 +189,8 @@ export const Field = (props: IFieldProps) => { + + { isDisabled data-test-subj={`fieldToggle-${field.name}`} aria-label={selected ? removeLabelAria : addLabelAria} + className="dscSidebarField__actionButton" /> ) : ( { }} data-test-subj={`fieldToggle-${field.name}`} aria-label={selected ? removeLabelAria : addLabelAria} + className="dscSidebarField__actionButton" /> )} - - ); - }; - - return ( - } - fieldName={ - - {field.name} - - } - fieldAction={getFieldActionDOM()} - onClick={togglePopover} - /> + + ); }; diff --git a/public/components/event_analytics/explorer/sidebar/field_insights.tsx b/public/components/event_analytics/explorer/sidebar/field_insights.tsx index 2b82bfa0e..a8a60a58f 100644 --- a/public/components/event_analytics/explorer/sidebar/field_insights.tsx +++ b/public/components/event_analytics/explorer/sidebar/field_insights.tsx @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo, useState, useContext, useEffect } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; import { indexOf, last } from 'lodash'; import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiBasicTable } from '@elastic/eui'; import { getIndexPatternFromRawQuery } from '../../../common/query_utils'; -import { TabContext } from '../../hooks/use_tab_context'; +import { coreRefs } from '../../../../framework/core_refs'; interface IInsightsReq { id: string; @@ -19,7 +19,7 @@ interface IInsightsReq { type IInsightsReqParams = Pick; export const FieldInsights = ({ field, query }: any) => { - const { pplService } = useContext(TabContext); + const { pplService } = coreRefs; const { rawQuery } = query; const index = getIndexPatternFromRawQuery(rawQuery); const generalReports = [ @@ -115,11 +115,11 @@ export const FieldInsights = ({ field, query }: any) => { .catch((error) => { console.error(error); }); - }, []); + }, [query]); const getInsights = async (insightParams: IInsightsReqParams) => { try { - return await pplService.fetch(insightParams); + return await pplService?.fetch(insightParams); } catch (error) { console.error(error); } diff --git a/public/components/event_analytics/explorer/sidebar/observability_sidebar.tsx b/public/components/event_analytics/explorer/sidebar/observability_sidebar.tsx new file mode 100644 index 000000000..89cb4e5e6 --- /dev/null +++ b/public/components/event_analytics/explorer/sidebar/observability_sidebar.tsx @@ -0,0 +1,95 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import _, { isEmpty } from 'lodash'; +import { changeQuery, selectQueries } from '../../redux/slices/query_slice'; +import { selectQueryResult } from '../../redux/slices/query_result_slice'; +import { selectFields } from '../../redux/slices/field_slice'; +import { + RAW_QUERY, + SELECTED_PATTERN_FIELD, + SELECTED_TIMESTAMP, +} from '../../../../../common/constants/explorer'; +import { PPL_STATS_REGEX } from '../../../../../common/constants/shared'; +import { Sidebar } from './sidebar'; +import { useFetchPatterns } from '../../hooks'; +import { formatError } from '../../utils'; +import { IField } from '../../../../../common/types/explorer'; +import { selectCountDistribution } from '../../redux/slices/count_distribution_slice'; + +export const ObservabilitySideBar = ({ tabId, pplService, notifications }) => { + const dispatch = useDispatch(); + const query = useSelector(selectQueries)[tabId]; + const explorerData = useSelector(selectQueryResult)[tabId]; + const explorerFields = useSelector(selectFields)[tabId]; + const countDistribution = useSelector(selectCountDistribution)[tabId]; + const requestParams = { tabId }; + const { + isEventsLoading: isPatternLoading, + getPatterns, + setDefaultPatternsField, + } = useFetchPatterns({ + pplService, + requestParams, + }); + const [isOverridingPattern, setIsOverridingPattern] = useState(false); + const [isOverridingTimestamp, setIsOverridingTimestamp] = useState(false); + + const getErrorHandler = (title: string) => { + return (error: any) => { + const formattedError = formatError(error.name, error.message, error.body.message); + notifications.toasts.addError(formattedError, { + title, + }); + }; + }; + + const handleOverridePattern = async (pattern: IField) => { + setIsOverridingPattern(true); + await setDefaultPatternsField( + '', + pattern.name, + getErrorHandler('Error overriding default pattern') + ); + setIsOverridingPattern(false); + await getPatterns( + countDistribution.selectedInterval || 'y', + getErrorHandler('Error fetching patterns') + ); + }; + + const handleOverrideTimestamp = async (timestamp: IField) => { + setIsOverridingTimestamp(true); + await dispatch( + changeQuery({ + tabId, + query: { + [SELECTED_TIMESTAMP]: timestamp?.name || '', + }, + }) + ); + setIsOverridingTimestamp(false); + }; + + return ( + + ); +}; diff --git a/public/components/event_analytics/explorer/sidebar/sidebar.scss b/public/components/event_analytics/explorer/sidebar/sidebar.scss index 8f96c7764..b9eeb45ff 100644 --- a/public/components/event_analytics/explorer/sidebar/sidebar.scss +++ b/public/components/event_analytics/explorer/sidebar/sidebar.scss @@ -3,6 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ + .dscSidebarField { + &__actionButton { + opacity: 0; + transition: opacity $euiAnimSpeedFast; + + @include ouiBreakpoint("xs", "s", "m") { + opacity: 1; + } + } + + &:hover &__actionButton, + &:focus &__actionButton { + opacity: 1; + } +} + .explorerIndexPattern__container { display: flex; align-items: center; @@ -128,4 +144,16 @@ .sidebar_content{ white-space: nowrap; padding: 0.1px; +} + +// align with discover 2.0 sidebar styling + +.dscSideBarFieldListHeader { + padding-left: 8px; +} + +.deSidebar { + height: calc(100vh - 98px); + max-width: 462px; + min-width: 400px; } \ No newline at end of file diff --git a/public/components/event_analytics/explorer/sidebar/sidebar.tsx b/public/components/event_analytics/explorer/sidebar/sidebar.tsx index ae6a55a31..21d21b905 100644 --- a/public/components/event_analytics/explorer/sidebar/sidebar.tsx +++ b/public/components/event_analytics/explorer/sidebar/sidebar.tsx @@ -4,23 +4,20 @@ */ import { - EuiAccordion, EuiDragDropContext, EuiDraggable, EuiDroppable, EuiFieldSearch, - EuiHorizontalRule, - EuiPanel, - EuiSpacer, EuiTitle, + EuiSplitPanel, + EuiPanel, } from '@elastic/eui'; -import { I18nProvider } from '@osd/i18n/react'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; import { isEmpty } from 'lodash'; -import React, { useCallback, useContext, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { batch, useDispatch } from 'react-redux'; import { AVAILABLE_FIELDS, SELECTED_FIELDS } from '../../../../../common/constants/explorer'; import { ExplorerFields, IExplorerFields, IField } from '../../../../../common/types/explorer'; -import { TabContext } from '../../hooks/use_tab_context'; import { sortFields, updateFields } from '../../redux/slices/field_slice'; import { Field } from './field'; @@ -51,14 +48,24 @@ export const Sidebar = (props: ISidebarProps) => { isFieldToggleButtonDisabled, handleOverridePattern, handleOverrideTimestamp, - storedExplorerFields, - setStoredExplorerFields, + tabId, } = props; const dispatch = useDispatch(); - const { tabId } = useContext(TabContext); const [showFields, setShowFields] = useState(false); const [searchTerm, setSearchTerm] = useState(''); + // method to return the type of a field from its name + const getFieldTypes = (newFieldName: string) => { + let fieldType: string = ''; + explorerFields.availableFields.map((field) => { + if (field.name === newFieldName) fieldType = field.type; + }); + explorerFields.selectedFields.map((field) => { + if (field.name === newFieldName) fieldType = field.type; + }); + return fieldType; + }; + /** * Toggle fields between selected and unselected sets * @param fieldState all fields in store @@ -100,81 +107,88 @@ export const Sidebar = (props: ISidebarProps) => { }); }; - const checkWithStoredFields = () => { - if ( - explorerFields.selectedFields.length === 0 && - storedExplorerFields.selectedFields.length !== 0 - ) { - return storedExplorerFields; - } - return explorerFields; - }; - const handleAddField = useCallback( (field: IField) => { - const nextFields = toggleFields( - checkWithStoredFields(), - field, - AVAILABLE_FIELDS, + updateStoreFields( + toggleFields(explorerFields, field, AVAILABLE_FIELDS, SELECTED_FIELDS), + tabId, SELECTED_FIELDS ); - updateStoreFields(nextFields, tabId, SELECTED_FIELDS); - setStoredExplorerFields(nextFields); }, [explorerFields, tabId] ); const handleRemoveField = useCallback( (field: IField) => { - const nextFields = toggleFields( - checkWithStoredFields(), - field, - SELECTED_FIELDS, + updateStoreFields( + toggleFields(explorerFields, field, SELECTED_FIELDS, AVAILABLE_FIELDS), + tabId, AVAILABLE_FIELDS ); - updateStoreFields(nextFields, tabId, AVAILABLE_FIELDS); - setStoredExplorerFields(nextFields); }, [explorerFields, tabId] ); - const onDragEnd = ({}) => { - console.log('source, destination'); + const onDragEnd = ({ + destination, + source, + draggableId, + }: { + destination: any; + source: any; + draggableId: string; + }) => { + // check if the destination and source are the same area + if (destination.droppableId !== source.droppableId) { + // if dropped into the selected fields: add, if dropped into available: remove + if (destination.droppableId === 'SELECTED FIELDS') { + handleAddField({ name: draggableId, type: getFieldTypes(draggableId) }); + } else if (destination.droppableId === 'AVAILABLE FIELDS') { + handleRemoveField({ name: draggableId, type: getFieldTypes(draggableId) }); + } + } }; return ( -
-
- { - setSearchTerm(e.target.value); - }} - placeholder="Search field names" - value={searchTerm} - data-test-subj="eventExplorer__sidebarSearch" - /> -
- -
+ + +
+ { + setSearchTerm(e.target.value); + }} + placeholder="Search field names" + value={searchTerm} + data-test-subj="eventExplorer__sidebarSearch" + /> +
+
+ {((explorerData && !isEmpty(explorerData.jsonData) && !isEmpty(explorerFields)) || !isEmpty(explorerFields.availableFields)) && ( <> {explorerFields?.queriedFields && explorerFields.queriedFields?.length > 0 && ( - - Query fields - - } - paddingSize="xs" - > - + <> + +

+ +

+
{ - + + + ); })} -
+ )} - - - Selected Fields - - } - paddingSize="xs" + - - - {explorerData && - !isEmpty(explorerData?.jsonData) && - storedExplorerFields?.selectedFields && - storedExplorerFields?.selectedFields.map((field, index) => { - return ( - + + + + + {explorerData && + !isEmpty(explorerData?.jsonData) && + explorerFields?.selectedFields && + explorerFields?.selectedFields.map((field, index) => { + return ( + + { showTimestampOverrideButton={true} onToggleField={handleRemoveField} /> - - ); - })} - - - - - Available Fields - - } - paddingSize="xs" + + + ); + })} + + - - - {storedExplorerFields?.availableFields && - storedExplorerFields?.availableFields - .filter( - (field) => searchTerm === '' || field.name.indexOf(searchTerm) !== -1 - ) - .map((field, index) => { - return ( - + + + + + {explorerFields?.availableFields && + explorerFields?.availableFields + .filter((field) => searchTerm === '' || field.name.indexOf(searchTerm) !== -1) + .map((field, index) => { + return ( + + { isFieldToggleButtonDisabled={isFieldToggleButtonDisabled} showTimestampOverrideButton={true} /> - - ); - })} - - + + + ); + })} + )} -
-
+ +
); diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss b/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss index b6737a338..23028f83a 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss @@ -158,6 +158,7 @@ $vis-editor-sidebar-min-width: 350px; #vis__mainContent .vis__leftPanel { overflow-y: unset; // unset default setting + margin-right: 8px; } .panelItem_button { @@ -168,6 +169,15 @@ $vis-editor-sidebar-min-width: 350px; align-items: center; } +.panelItem_box { + color: #5A6875; + display: grid; + grid-gap: 4px; + padding: 8px 8px 8px 8px; + background-color: #D6D9DD; + border-radius: 4px; +} + .field_text { text-overflow: ellipsis; overflow: hidden; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx index 4c7f666c5..669a314c8 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx @@ -12,6 +12,8 @@ import { EuiText, EuiTitle, EuiToolTip, + EuiFormRow, + EuiFormLabel, } from '@elastic/eui'; import { isArray, isEmpty, lowerCase } from 'lodash'; import { @@ -60,7 +62,7 @@ export const DataConfigPanelFields = ({ const { time_field: timeField, unit, interval } = dimensionSpan; - const tooltipIcon = ; + const tooltipIcon = ; const crossIcon = (index: number, configName: string) => ( -
- -

{sectionName}

-
- {infoToolTip(tooltipIcon, DATA_CONFIG_HINTS_INFO[`${sectionName}`])} -
- - {sectionName === GROUPBY && dimensionSpan && !isEmpty(timeField) && ( - - - handleServiceEdit(list.length - 1, GROUPBY, true)} - data-test-subj="viz-config-add-btn" - > - {`${SPAN}(${timeField[0]?.name}, ${interval} ${unit[0]?.value})`} - - - {crossIcon(-1, SPAN)} - - )} - - {isArray(list) && - list.map((obj: ConfigListEntry, index: number) => ( - + + <> +
+ {sectionName} + {infoToolTip(tooltipIcon, DATA_CONFIG_HINTS_INFO[`${sectionName}`])} +
+ +
+ {sectionName === GROUPBY && dimensionSpan && !isEmpty(timeField) && ( handleServiceEdit(index, sectionName, false)} + onClick={() => handleServiceEdit(list.length - 1, GROUPBY, true)} data-test-subj="viz-config-add-btn" > - {removeBacktick( - obj[CUSTOM_LABEL] || `${isAggregation ? obj.aggregation : ''} ${obj.label}` - )} + {`${SPAN}(${timeField[0]?.name}, ${interval} ${unit[0]?.value})`} - {isAggregation - ? infoToolTip(crossIcon(index, sectionName), DATA_CONFIG_HINTS_INFO[AGGREGATIONS]) - : crossIcon(index, sectionName)} + {crossIcon(-1, SPAN)} + + )} + {isArray(list) && + list.map((obj: ConfigListEntry, index: number) => ( + + + + handleServiceEdit(index, sectionName, false)} + data-test-subj="viz-config-add-btn" + > + {removeBacktick( + obj[CUSTOM_LABEL] || `${isAggregation ? obj.aggregation : ''} ${obj.label}` + )} + + + {isAggregation + ? infoToolTip( + crossIcon(index, sectionName), + DATA_CONFIG_HINTS_INFO[AGGREGATIONS] + ) + : crossIcon(index, sectionName)} + + + ))} + {!hideClickToAddButton(sectionName) && ( + + {addButtonText} + handleServiceAdd(sectionName)} + data-test-subj="viz-config-add-btn" + /> - - - ))} - {!hideClickToAddButton(sectionName) && ( - - {addButtonText} - handleServiceAdd(sectionName)} - data-test-subj="viz-config-add-btn" - /> - - )} - -
+ )} + + +
); }; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss index cac373f90..6ca8a2e84 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss @@ -39,4 +39,50 @@ &.showSecondary > .wizConfig__section { transform: translateX(-100%); } +} + +.vbConfig { + @include euiYScrollWithShadows; + + background: $euiColorLightestShade; + border-left: $euiBorderThin; + position: relative; + overflow-x: hidden; + + &__section { + width: 100%; + transition: transform $euiAnimSpeedNormal 0s $euiAnimSlightResistance; + } + + &__title { + padding: $euiSizeS; + padding-bottom: 0; + + &.showDivider { + border-bottom: 1px solid $euiColorLightShade; + } + } + + &__content { + padding: $euiSizeS; + } + + &__aggEditor { + padding: 0 $euiSizeM; + } + + &--secondary { + position: absolute; + top: 0; + left: 0; + padding: $euiSizeS; + + .visEditorAggParam--half { + margin: $euiSize 0; + } + } + + &.showSecondary > .vbConfig__section { + transform: translateX(-100%); + } } \ No newline at end of file diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx index e5374811e..d55fedc85 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx @@ -16,6 +16,9 @@ import { EuiSpacer, EuiTitle, htmlIdGenerator, + EuiForm, + EuiFlexGroup, + EuiHorizontalRule, } from '@elastic/eui'; import { filter, isEmpty, isEqual } from 'lodash'; import { @@ -340,7 +343,7 @@ export const DataConfigPanelItem = ({ const selectedObj = isTimeStampSelected ? configList[SPAN] : configList[name][index]; const isAggregations = name === AGGREGATIONS; return ( - <> +
- +
); }; @@ -535,49 +538,59 @@ export const DataConfigPanelItem = ({ return isAddConfigClicked ? ( getCommonUI(selectedConfigItem.name) ) : ( - <> - -

Configuration

-
- - {visualizations.vis.name !== VIS_CHART_TYPES.Histogram ? ( - <> - {DataConfigPanelFields(getRenderFieldsObj(AGGREGATIONS))} - - {DataConfigPanelFields(getRenderFieldsObj(GROUPBY))} - - {(visualizations.vis.name === VIS_CHART_TYPES.Bar || - visualizations.vis.name === VIS_CHART_TYPES.HorizontalBar || - visualizations.vis.name === VIS_CHART_TYPES.Line) && ( - <>{DataConfigPanelFields(getRenderFieldsObj(BREAKDOWNS))} - )} - - ) : ( - <> - -

Bucket Size

-
- {getNumberField('bucketSize')} + +
+
+ + + +

Configuration

+
+
+
+
+ {visualizations.vis.name !== VIS_CHART_TYPES.Histogram ? ( +
+ + {DataConfigPanelFields(getRenderFieldsObj(AGGREGATIONS))} + + {DataConfigPanelFields(getRenderFieldsObj(GROUPBY))} + + {(visualizations.vis.name === VIS_CHART_TYPES.Bar || + visualizations.vis.name === VIS_CHART_TYPES.HorizontalBar || + visualizations.vis.name === VIS_CHART_TYPES.Line) && ( + <>{DataConfigPanelFields(getRenderFieldsObj(BREAKDOWNS))} + )} + +
+ ) : ( + <> + +

Bucket Size

+
+ {getNumberField('bucketSize')} - - -

Bucket Offset

-
- {getNumberField('bucketOffset')} - - )} - - - updateChart()} - size="s" - isDisabled={isEmpty(configList[AGGREGATIONS])} - > - Update chart - - - + + +

Bucket Offset

+
+ {getNumberField('bucketOffset')} + + )} +
+ + updateChart()} + size="s" + isDisabled={isEmpty(configList[AGGREGATIONS])} + > + Update chart + + +
+
+
); }; diff --git a/public/components/event_analytics/explorer/visualizations/count_distribution/count_distribution.tsx b/public/components/event_analytics/explorer/visualizations/count_distribution/count_distribution.tsx index 8e5f10f31..6fa677881 100644 --- a/public/components/event_analytics/explorer/visualizations/count_distribution/count_distribution.tsx +++ b/public/components/event_analytics/explorer/visualizations/count_distribution/count_distribution.tsx @@ -4,6 +4,7 @@ */ import React from 'react'; +import { EuiPanel } from '@elastic/eui'; import { BarOrientation, LONG_CHART_COLOR } from '../../../../../../common/constants/shared'; import { Plt } from '../../../../visualizations/plotly/plot'; import { fillTimeDataWithEmpty } from '../../../utils/utils'; diff --git a/public/components/event_analytics/explorer/visualizations/direct_query_vis.tsx b/public/components/event_analytics/explorer/visualizations/direct_query_vis.tsx index 73d55f0a5..a1268c62d 100644 --- a/public/components/event_analytics/explorer/visualizations/direct_query_vis.tsx +++ b/public/components/event_analytics/explorer/visualizations/direct_query_vis.tsx @@ -3,8 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiLink, EuiTitle } from '@elastic/eui'; +import { + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPage, + EuiText, + EuiSpacer, +} from '@elastic/eui'; import React from 'react'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; import { queryWorkbenchPluginID } from '../../../../../common/constants/shared'; import { coreRefs } from '../../../../framework/core_refs'; @@ -14,31 +23,53 @@ interface DirectQueryVisualizationProps { export const DirectQueryVisualization = ({ currentDataSource }: DirectQueryVisualizationProps) => { return ( - - - -

- - coreRefs?.application!.navigateToApp(queryWorkbenchPluginID, { - path: `#/${currentDataSource}`, - }) + + + + + } + color="danger" + iconType="alert" > - Index data to visualize - -

-
-
- - -

Index data to visualize or select indexed data.

-
-

- For external data only materialized views or covering indexes can be visualized. Ask your - administrator to create these indexes to visualize them. -

-
-
+

+ + coreRefs?.application!.navigateToApp(queryWorkbenchPluginID, { + path: `#/${currentDataSource}`, + }) + } + > + + +

+ + + + +

+ +

+ +
+
+ + + ); }; diff --git a/public/components/event_analytics/explorer/visualizations/index.tsx b/public/components/event_analytics/explorer/visualizations/index.tsx index f97db37f3..0b3a6bea5 100644 --- a/public/components/event_analytics/explorer/visualizations/index.tsx +++ b/public/components/event_analytics/explorer/visualizations/index.tsx @@ -93,16 +93,7 @@ export const ExplorerVisualizations = ({ paddingSize="none" className="vis__leftPanel" > -
- {!isMarkDown && ( -
- {renderDataConfigContainer()} -
- )} -
+ {!isMarkDown && <>{renderDataConfigContainer()}} {}, }); -export const { changeQuery, changeDateRange, remove, init } = queriesSlice.actions; +export const { changeQuery, changeDateRange, remove, init, reset } = queriesSlice.actions; export const selectQueries = createSelector( (state) => state.queries, diff --git a/public/components/event_analytics/redux/slices/search_meta_data_slice.ts b/public/components/event_analytics/redux/slices/search_meta_data_slice.ts index eee7083fa..524e13bd8 100644 --- a/public/components/event_analytics/redux/slices/search_meta_data_slice.ts +++ b/public/components/event_analytics/redux/slices/search_meta_data_slice.ts @@ -3,36 +3,61 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { createSlice, createSelector } from '@reduxjs/toolkit'; +import { createSlice, createSelector, PayloadAction } from '@reduxjs/toolkit'; import { initialTabId } from '../../../../framework/redux/store/shared_state'; import { REDUX_EXPL_SLICE_SEARCH_META_DATA } from '../../../../../common/constants/explorer'; +import { DirectQueryLoadingStatus, SelectedDataSource } from '../../../../../common/types/explorer'; + +const searchMetaInitialState = { + lang: 'PPL', + datasources: [], + isPolling: false, +}; const initialState = { [initialTabId]: { - lang: 'PPL', - datasources: [], - isPolling: false, - }, + ...searchMetaInitialState, + } as SearchMetaData, }; +interface SearchMetaData { + lang: string; + datasources: SelectedDataSource[]; + isPolling: boolean; + status: DirectQueryLoadingStatus; +} + +interface SearchMetaDataState { + [key: string]: SearchMetaData; +} + +interface UpdatePayload { + tabId: string; + data: Partial; +} + export const searchMetaDataSlice = createSlice({ name: REDUX_EXPL_SLICE_SEARCH_META_DATA, initialState, reducers: { - update: (state, { payload }) => { - state[payload.tabId] = { - ...state[payload.tabId], - ...payload.data, + update: (state, action: PayloadAction) => { + const { tabId, data } = action.payload; + state[tabId] = { + ...state[tabId], + ...data, }; }, - reset: (state, { payload }) => { - state[payload.tabId] = {}; + reset: (state, action: PayloadAction<{ tabId: string }>) => { + const { tabId } = action.payload; + state[tabId] = { ...searchMetaInitialState }; }, - init: (state, { payload }) => { - state[payload.tabId] = {}; + init: (state, action: PayloadAction<{ tabId: string }>) => { + const { tabId } = action.payload; + state[tabId] = { ...searchMetaInitialState }; }, - remove: (state, { payload }) => { - delete state[payload.tabId]; + remove: (state, action: PayloadAction<{ tabId: string }>) => { + const { tabId } = action.payload; + delete state[tabId]; }, }, }); @@ -45,3 +70,5 @@ export const selectSearchMetaData = createSelector( ); export const searchMetaDataSliceReducer = searchMetaDataSlice.reducer; + +export type { SearchMetaData, SearchMetaDataState, UpdatePayload }; diff --git a/public/components/event_analytics/utils/__tests__/utils.test.tsx b/public/components/event_analytics/utils/__tests__/utils.test.tsx index f6b5138d2..85a216b48 100644 --- a/public/components/event_analytics/utils/__tests__/utils.test.tsx +++ b/public/components/event_analytics/utils/__tests__/utils.test.tsx @@ -118,7 +118,9 @@ describe('Utils event analytics helper functions', () => { }); it('validates redoQuery function', () => { - const getEvents = jest.fn(); + const fetchEvents = jest.fn(); + const setData = jest.fn(); + redoQuery( '2023-01-01 00:00:00', '2023-09-28 23:19:10', @@ -135,10 +137,14 @@ describe('Utils event analytics helper functions', () => { { current: [0, 100], }, - getEvents + fetchEvents, + setData ); - const expectedFinalQuery = - "source=opensearch_dashboards_sample_data_logs | where timestamp >= '2023-01-01 00:00:00' and timestamp <= '2023-09-28 23:19:10' | where match(request,'filebeat') | sort + timestamp | head 100 from 0"; - expect(getEvents).toBeCalledWith(expectedFinalQuery); + const expectedFinalQuery = { + query: + "source=opensearch_dashboards_sample_data_logs | where timestamp >= '2023-01-01 00:00:00' and timestamp <= '2023-09-28 23:19:10' | where match(request,'filebeat') | sort + timestamp | head 100 from 0", + }; + // final query is the only thing being tested here + expect(fetchEvents).toBeCalledWith(expectedFinalQuery, 'jdbc', expect.anything()); }); }); diff --git a/public/components/event_analytics/utils/utils.tsx b/public/components/event_analytics/utils/utils.tsx index d680860e1..996a656b6 100644 --- a/public/components/event_analytics/utils/utils.tsx +++ b/public/components/event_analytics/utils/utils.tsx @@ -413,7 +413,8 @@ export const redoQuery = ( timeStampField: string, sortingFields: MutableRefObject, pageFields: MutableRefObject, - getEvents: any + fetchEvents: any, + setData: React.Dispatch> ) => { let finalQuery = ''; @@ -436,5 +437,8 @@ export const redoQuery = ( finalQuery = finalQuery + ` | head ${pageFields.current[1]} from ${pageFields.current[0] * pageFields.current[1]}`; - getEvents(finalQuery); + + fetchEvents({ query: finalQuery }, 'jdbc', (res: any) => { + setData(res.jsonData); + }); }; diff --git a/public/components/hooks/index.ts b/public/components/hooks/index.ts index d3c599c18..3296182d4 100644 --- a/public/components/hooks/index.ts +++ b/public/components/hooks/index.ts @@ -3,4 +3,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { usePolling } from './use_polling'; +export { usePolling, PollingConfigurations } from './use_polling'; diff --git a/public/components/hooks/use_direct_query_search.ts b/public/components/hooks/use_direct_query_search.ts deleted file mode 100644 index a850c1690..000000000 --- a/public/components/hooks/use_direct_query_search.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ diff --git a/public/components/hooks/use_polling.ts b/public/components/hooks/use_polling.ts index 42fe5b9d3..c1f79ada1 100644 --- a/public/components/hooks/use_polling.ts +++ b/public/components/hooks/use_polling.ts @@ -7,6 +7,65 @@ import { useState, useRef } from 'react'; type FetchFunction = (params?: P) => Promise; +export interface PollingConfigurations { + tabId: string; +} + +export class UsePolling { + public data: T | null = null; + public error: Error | null = null; + public loading: boolean = true; + private shouldPoll: boolean = false; + private intervalRef?: NodeJS.Timeout; + + constructor( + private fetchFunction: FetchFunction, + private interval: number = 5000, + private onPollingSuccess?: (data: T, configurations: PollingConfigurations) => boolean, + private onPollingError?: (error: Error) => boolean, + private configurations?: PollingConfigurations + ) {} + + async fetchData(params?: P) { + this.loading = true; + try { + const result = await this.fetchFunction(params); + this.data = result; + this.loading = false; + + if (this.onPollingSuccess && this.onPollingSuccess(result, this.configurations!)) { + this.stopPolling(); + } + } catch (err) { + this.error = err as Error; + this.loading = false; + + if (this.onPollingError && this.onPollingError(this.error)) { + this.stopPolling(); + } + } + } + + startPolling(params?: P) { + this.shouldPoll = true; + if (!this.intervalRef) { + this.intervalRef = setInterval(() => { + if (this.shouldPoll) { + this.fetchData(params); + } + }, this.interval); + } + } + + stopPolling() { + this.shouldPoll = false; + if (this.intervalRef) { + clearInterval(this.intervalRef); + this.intervalRef = undefined; + } + } +} + interface UsePollingReturn { data: T | null; loading: boolean; @@ -17,7 +76,10 @@ interface UsePollingReturn { export function usePolling( fetchFunction: FetchFunction, - interval: number = 5000 + interval: number = 5000, + onPollingSuccess?: (data: T, configurations: PollingConfigurations) => boolean, + onPollingError?: (error: Error) => boolean, + configurations?: PollingConfigurations ): UsePollingReturn { const [data, setData] = useState(null); const [error, setError] = useState(null); @@ -45,8 +107,18 @@ export function usePolling( try { const result = await fetchFunction(params); setData(result); - } catch (err) { - setError(err); + + // Check the success condition and stop polling if it's met + if (onPollingSuccess && onPollingSuccess(result, configurations)) { + stopPolling(); + } + } catch (err: unknown) { + setError(err as Error); + + // Check the error condition and stop polling if it's met + if (onPollingError && onPollingError(err as Error)) { + stopPolling(); + } } finally { setLoading(false); } diff --git a/public/components/metrics/index.scss b/public/components/metrics/index.scss index 9f2d60198..abfeba374 100644 --- a/public/components/metrics/index.scss +++ b/public/components/metrics/index.scss @@ -15,6 +15,6 @@ } .mainContentTabs .euiResizableContainer { - height: calc(100vh - 298px); + height: calc(100vh - 194px); } \ No newline at end of file diff --git a/public/components/visualizations/visualization.tsx b/public/components/visualizations/visualization.tsx index 6de22585e..313f43cf7 100644 --- a/public/components/visualizations/visualization.tsx +++ b/public/components/visualizations/visualization.tsx @@ -29,7 +29,7 @@ export const Visualization = ({ // Markdown, it does not depend on if there is data if (vis.id === VIS_CHART_TYPES.Text) return [true, '']; - if (isEmpty(series)) return [false, VISUALIZATION_ERROR.INVALID_DATA]; // series is required to any visualization type + if (isEmpty(series)) return [false, VISUALIZATION_ERROR.NO_SERIES]; // series is required to any visualization type // bars, pie if (dimensions.length < 1 && isEmpty(span)) return [false, VISUALIZATION_ERROR.INVALID_DATA]; diff --git a/public/framework/datasources/obs_opensearch_datasource.ts b/public/framework/datasources/obs_opensearch_datasource.ts new file mode 100644 index 000000000..07a30dd02 --- /dev/null +++ b/public/framework/datasources/obs_opensearch_datasource.ts @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DataSource } from '../../../../../src/plugins/data/public'; + +interface DataSourceConfig { + name: string; + type: string; + metadata: any; +} + +export class ObservabilityDefaultDataSource extends DataSource { + constructor({ name, type, metadata }: DataSourceConfig) { + super(name, type, metadata); + } + + async getDataSet(dataSetParams?: any) { + return ['Default data source']; + } + + async testConnection(): Promise { + return true; + } + + async runQuery(queryParams: any) { + return null; + } +} diff --git a/public/services/saved_objects/saved_object_client/client_factory.ts b/public/services/saved_objects/saved_object_client/client_factory.ts index 4af366a83..7f9fb965a 100644 --- a/public/services/saved_objects/saved_object_client/client_factory.ts +++ b/public/services/saved_objects/saved_object_client/client_factory.ts @@ -3,10 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { VISUALIZATION_SAVED_OBJECT } from '../../../../common/types/observability_saved_object_attributes'; +import { + SEARCH_SAVED_OBJECT, + VISUALIZATION_SAVED_OBJECT, +} from '../../../../common/types/observability_saved_object_attributes'; import { ISavedObjectsClient } from './client_interface'; import { OSDSavedObjectClient } from './osd_saved_objects/osd_saved_object_client'; import { OSDSavedVisualizationClient } from './osd_saved_objects/saved_visualization'; +import { OSDSavedSearchClient } from './osd_saved_objects/saved_searches'; import { PPLSavedQueryClient, PPLSavedVisualizationClient } from './ppl'; interface GetSavedObjectsClientOptions { @@ -22,6 +26,8 @@ export const getSavedObjectsClient = ( switch (type) { case VISUALIZATION_SAVED_OBJECT: return OSDSavedVisualizationClient.getInstance(); + case SEARCH_SAVED_OBJECT: + return OSDSavedSearchClient.getInstance(); default: break; diff --git a/public/services/saved_objects/saved_object_client/osd_saved_objects/osd_saved_object_client.ts b/public/services/saved_objects/saved_object_client/osd_saved_objects/osd_saved_object_client.ts index f0d0baa04..ac3204b7a 100644 --- a/public/services/saved_objects/saved_object_client/osd_saved_objects/osd_saved_object_client.ts +++ b/public/services/saved_objects/saved_object_client/osd_saved_objects/osd_saved_object_client.ts @@ -66,6 +66,8 @@ export abstract class OSDSavedObjectClient extends SavedObjectClientBase { fields, dateRange, timestamp, + dataSources, + queryLang, name = '', chartType = '', description = '', @@ -94,6 +96,8 @@ export abstract class OSDSavedObjectClient extends SavedObjectClientBase { }, name: name || '', description: description || '', + data_sources: dataSources, + query_lang: queryLang, }, }; diff --git a/public/services/saved_objects/saved_object_client/osd_saved_objects/saved_searches.ts b/public/services/saved_objects/saved_object_client/osd_saved_objects/saved_searches.ts new file mode 100644 index 000000000..45b5e3de4 --- /dev/null +++ b/public/services/saved_objects/saved_object_client/osd_saved_objects/saved_searches.ts @@ -0,0 +1,179 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectsFindOptions } from '../../../../../../../src/core/public'; +import { IField } from '../../../../../common/types/explorer'; +import { + SAVED_OBJECT_VERSION, + SearchSavedObjectAttributes, + SEARCH_SAVED_OBJECT, +} from '../../../../../common/types/observability_saved_object_attributes'; +import { getOSDSavedObjectsClient } from '../../../../../common/utils'; +import { + SavedObjectsDeleteBulkParams, + SavedObjectsDeleteParams, + SavedObjectsDeleteResponse, + SavedObjectsGetParams, + SavedObjectsGetResponse, +} from '../types'; +import { OSDSavedObjectClient } from './osd_saved_object_client'; +import { OSDSavedObjectCreateResponse, OSDSavedObjectUpdateResponse } from './types'; + +interface CommonParams { + query: string; + fields: IField[]; + dateRange: [string, string]; + type: string; + name: string; + timestamp: string; + applicationId: string; + userConfigs: any; + description: string; + subType: string; + unitsOfMeasure: string; + selectedLabels: string; + dataSources: string; // list of type SelectedDataSources that is stringified + queryLang: string; +} + +type CreateParams = CommonParams & { applicationId: string }; +type UpdateParams = Partial & { objectId: string }; + +export class OSDSavedSearchClient extends OSDSavedObjectClient { + private static instance: OSDSavedSearchClient; + + protected prependTypeToId(objectId: string) { + return `${SEARCH_SAVED_OBJECT}:${objectId}`; + } + + async create( + params: CreateParams + ): Promise> { + const body = this.buildRequestBody({ + query: params.query, + fields: params.fields, + dateRange: params.dateRange, + name: params.name, + timestamp: params.timestamp, + description: params.description, + dataSources: params.dataSources, + queryLang: params.queryLang, + }); + + const response = await this.client.create(SEARCH_SAVED_OBJECT, { + title: params.name, + description: params.description, + version: SAVED_OBJECT_VERSION, + createdTimeMs: new Date().getTime(), + savedQuery: { + ...body.object, + }, + }); + + return { + objectId: this.prependTypeToId(response.id), + object: response, + }; + } + + async update( + params: UpdateParams + ): Promise> { + const body = this.buildRequestBody({ + query: params.query, + fields: params.fields, + dateRange: params.dateRange, + name: params.name, + timestamp: params.timestamp, + description: params.description, + dataSources: params.dataSources, + queryLang: params.queryLang, + }); + + const response = await this.client.update>( + SEARCH_SAVED_OBJECT, + OSDSavedObjectClient.extractTypeAndUUID(params.objectId).uuid, + { + title: params.name, + description: params.description, + version: SAVED_OBJECT_VERSION, + savedQuery: body.object, + } + ); + + return { + objectId: this.prependTypeToId(response.id), + object: response, + }; + } + + updateBulk(params: unknown): Promise>> { + throw new Error('Method not implemented.'); + } + + async get(params: SavedObjectsGetParams): Promise { + const response = await this.client.get( + SEARCH_SAVED_OBJECT, + OSDSavedObjectClient.extractTypeAndUUID(params.objectId).uuid + ); + return { + observabilityObjectList: [ + { + objectId: this.prependTypeToId(response.id), + createdTimeMs: response.attributes.createdTimeMs, + lastUpdatedTimeMs: OSDSavedObjectClient.convertToLastUpdatedMs(response.updated_at), + savedQuery: response.attributes.savedQuery, + }, + ], + }; + } + + async getBulk(params: Partial = {}): Promise { + const observabilityObjectList = await this.client + .find({ + ...params, + type: SEARCH_SAVED_OBJECT, + }) + .then((findRes) => + findRes.savedObjects.map((o) => ({ + objectId: this.prependTypeToId(o.id), + createdTimeMs: o.attributes.createdTimeMs, + lastUpdatedTimeMs: OSDSavedObjectClient.convertToLastUpdatedMs(o.updated_at), + savedQuery: o.attributes.savedQuery, + })) + ); + return { totalHits: observabilityObjectList.length, observabilityObjectList }; + } + + async delete(params: SavedObjectsDeleteParams): Promise { + const uuid = OSDSavedObjectClient.extractTypeAndUUID(params.objectId).uuid; + return this.client + .delete(SEARCH_SAVED_OBJECT, uuid) + .then(() => ({ deleteResponseList: { [params.objectId]: 'OK' } })) + .catch((res) => ({ deleteResponseList: { [params.objectId]: res } })); + } + + async deleteBulk(params: SavedObjectsDeleteBulkParams): Promise { + const deleteResponseList: SavedObjectsDeleteResponse['deleteResponseList'] = {}; + await Promise.allSettled(params.objectIdList.map((objectId) => this.delete({ objectId }))).then( + (res) => { + res.forEach((r, i) => { + deleteResponseList[params.objectIdList[i]] = + r.status === 'fulfilled' + ? r.value.deleteResponseList[params.objectIdList[i]] + : r.reason; + }); + } + ); + return { deleteResponseList }; + } + + static getInstance() { + if (!this.instance) { + this.instance = new this(getOSDSavedObjectsClient()); + } + return this.instance; + } +} diff --git a/public/services/saved_objects/saved_object_client/osd_saved_objects/saved_visualization.ts b/public/services/saved_objects/saved_object_client/osd_saved_objects/saved_visualization.ts index 635199c54..f0adf13f8 100644 --- a/public/services/saved_objects/saved_object_client/osd_saved_objects/saved_visualization.ts +++ b/public/services/saved_objects/saved_object_client/osd_saved_objects/saved_visualization.ts @@ -34,6 +34,8 @@ interface CommonParams { subType: string; unitsOfMeasure: string; selectedLabels: string; + dataSources: string; // list of type SelectedDataSources that is stringified + queryLang: string; } type CreateParams = CommonParams & { applicationId: string }; @@ -62,6 +64,8 @@ export class OSDSavedVisualizationClient extends OSDSavedObjectClient { subType: params.subType, unitsOfMeasure: params.unitsOfMeasure, selectedLabels: params.selectedLabels, + dataSources: params.dataSources, + queryLang: params.queryLang, }); const response = await this.client.create( @@ -99,6 +103,8 @@ export class OSDSavedVisualizationClient extends OSDSavedObjectClient { subType: params.subType, unitsOfMeasure: params.unitsOfMeasure, selectedLabels: params.selectedLabels, + dataSources: params.dataSources, + queryLang: params.queryLang, }); const response = await this.client.update>( diff --git a/public/services/saved_objects/saved_object_client/saved_objects_actions.ts b/public/services/saved_objects/saved_object_client/saved_objects_actions.ts index 5d8ae29d3..3a1fc5a66 100644 --- a/public/services/saved_objects/saved_object_client/saved_objects_actions.ts +++ b/public/services/saved_objects/saved_object_client/saved_objects_actions.ts @@ -3,10 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { VISUALIZATION_SAVED_OBJECT } from '../../../../common/types/observability_saved_object_attributes'; +import { + SEARCH_SAVED_OBJECT, + VISUALIZATION_SAVED_OBJECT, +} from '../../../../common/types/observability_saved_object_attributes'; import { ISavedObjectRequestParams } from '../event_analytics/saved_objects'; import { OSDSavedObjectClient } from './osd_saved_objects/osd_saved_object_client'; import { OSDSavedVisualizationClient } from './osd_saved_objects/saved_visualization'; +import { OSDSavedSearchClient } from './osd_saved_objects/saved_searches'; import { ObservabilitySavedObjectsType } from './osd_saved_objects/types'; import { PPLSavedQueryClient } from './ppl'; import { @@ -29,6 +33,8 @@ export class SavedObjectsActions { switch (type) { case VISUALIZATION_SAVED_OBJECT: return OSDSavedVisualizationClient.getInstance().get(params); + case SEARCH_SAVED_OBJECT: + return OSDSavedSearchClient.getInstance().get(params); default: // for non-osd objects it does not matter which client implementation @@ -52,6 +58,17 @@ export class SavedObjectsActions { ]; } + if (params.objectType?.includes('savedQuery')) { + const osdSearchObjects = await OSDSavedSearchClient.getInstance().getBulk(); + if (objects.totalHits && osdSearchObjects.totalHits) { + objects.totalHits += osdSearchObjects.totalHits; + } + objects.observabilityObjectList = [ + ...objects.observabilityObjectList, + ...osdSearchObjects.observabilityObjectList, + ]; + } + if (params.sortOrder === 'asc') { objects.observabilityObjectList.sort((a, b) => a.lastUpdatedTimeMs - b.lastUpdatedTimeMs); } else { @@ -65,6 +82,8 @@ export class SavedObjectsActions { switch (type) { case VISUALIZATION_SAVED_OBJECT: return OSDSavedVisualizationClient.getInstance().delete(params); + case SEARCH_SAVED_OBJECT: + return OSDSavedSearchClient.getInstance().delete(params); default: return PPLSavedQueryClient.getInstance().delete(params); @@ -101,6 +120,16 @@ export class SavedObjectsActions { }; } + if (idMap[SEARCH_SAVED_OBJECT]?.length) { + const searchDeleteResponses = await OSDSavedSearchClient.getInstance().deleteBulk({ + objectIdList: idMap[SEARCH_SAVED_OBJECT], + }); + responses.deleteResponseList = { + ...responses.deleteResponseList, + ...searchDeleteResponses.deleteResponseList, + }; + } + const remainingObjectIds = [ ...new Set( idMap.non_osd?.concat( diff --git a/public/services/saved_objects/saved_object_loaders/explorer_saved_object_loader.ts b/public/services/saved_objects/saved_object_loaders/explorer_saved_object_loader.ts new file mode 100644 index 000000000..6e207f7ab --- /dev/null +++ b/public/services/saved_objects/saved_object_loaders/explorer_saved_object_loader.ts @@ -0,0 +1,387 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { isEmpty } from 'lodash'; +import { i18n } from '@osd/i18n'; +import { batch as Batch } from 'react-redux'; +import { updateFields as updateFieldsAction } from '../../../components/event_analytics/redux/slices/field_slice'; +import { changeQuery as changeQueryAction } from '../../../components/event_analytics/redux/slices/query_slice'; +import { updateTabName as updateTabNameAction } from '../../../components/event_analytics/redux/slices/query_tab_slice'; +import { change as updateVizConfigAction } from '../../../components/event_analytics/redux/slices/viualization_config_slice'; +import { update as updateSearchMetaData } from '../../../components/event_analytics/redux/slices/search_meta_data_slice'; +import { NotificationsStart } from '../../../../../../src/core/public'; +import { + AGGREGATIONS, + BREAKDOWNS, + GROUPBY, + RAW_QUERY, + SAVED_OBJECT_ID, + SAVED_OBJECT_TYPE, + SAVED_QUERY, + SAVED_VISUALIZATION, + SELECTED_DATE_RANGE, + SELECTED_FIELDS, + SELECTED_TIMESTAMP, + TYPE_TAB_MAPPING, +} from '../../../../common/constants/explorer'; +import { QueryManager } from '../../../../common/query_manager'; +import { statsChunk } from '../../../../common/query_manager/ast/types/stats'; +import { + IField, + SavedQuery, + SavedVisualization, + SelectedDataSource, +} from '../../../../common/types/explorer'; +import { AppDispatch } from '../../../framework/redux/store'; +import { ISavedObjectsClient } from '../saved_object_client/client_interface'; +import { ObservabilitySavedObject, ObservabilitySavedQuery } from '../saved_object_client/types'; +import { SavedObjectLoaderBase } from './loader_base'; +import { ISavedObjectLoader } from './loader_interface'; +import { PollingConfigurations } from '../../../components/hooks'; +import { SQLService } from '../../requests/sql'; +import { coreRefs } from '../../../framework/core_refs'; +import { UsePolling } from '../../../components/hooks/use_polling'; + +enum DIRECT_DATA_SOURCE_TYPES { + DEFAULT_INDEX_PATTERNS = 'DEFAULT_INDEX_PATTERNS', + SPARK = 'spark', + S3GLUE = 's3glue', +} + +interface LoadParams { + objectId: string; +} + +interface LoadContext { + tabId: string; + appLogEvents: boolean; + setStartTime: (startTime: string) => void; + setEndTime: (endTime: string) => void; + queryManager: QueryManager; + getDefaultVisConfig: ( + statsToken: statsChunk + ) => { + [AGGREGATIONS]: IField[]; + [GROUPBY]: IField[]; + [BREAKDOWNS]?: IField[]; + span?: any; + }; + setSelectedPanelName: (savedObjectName: string) => void; + setCurVisId: (visId: string) => void; + setTempQuery: (tmpQuery: string) => void; + setMetricChecked: (metricChecked: boolean) => void; + setMetricMeasure: (metricMeasure: string) => void; + setSubType: (type: string) => void; + setSelectedContentTab: (curTab: string) => void; + fetchData: () => void; + dispatchOnGettingHis: (res: unknown, query: string) => void; +} + +interface Dispatchers { + batch: typeof Batch; + dispatch: AppDispatch; + changeQuery: typeof changeQueryAction; + updateFields: typeof updateFieldsAction; + updateTabName: typeof updateTabNameAction; + updateVizConfig: typeof updateVizConfigAction; +} + +type SavedObjectData = ObservabilitySavedObject; + +function isObjectSavedQuery( + savedObjectData: SavedObjectData +): savedObjectData is ObservabilitySavedQuery { + return SAVED_QUERY in savedObjectData; +} + +function isInnerObjectSavedVisualization( + objectData: SavedQuery | SavedVisualization +): objectData is SavedVisualization { + return 'type' in objectData; +} + +const parseStringDataSource = ( + dsInSavedObject: string, + notifications: NotificationsStart +): SelectedDataSource[] => { + let selectedDataSources: SelectedDataSource[]; + try { + selectedDataSources = JSON.parse(dsInSavedObject); + } catch (err: unknown) { + console.error(err); + notifications.toasts.addError(err as Error, { + title: i18n.translate('observability.notification.error.savedDataSourceParsingError', { + defaultMessage: 'Cannot parse datasources from saved object', + }), + }); + return [] as SelectedDataSource[]; + } + return selectedDataSources; +}; + +export class ExplorerSavedObjectLoader extends SavedObjectLoaderBase implements ISavedObjectLoader { + private pollingInstance: UsePolling | undefined; + constructor( + protected readonly savedObjectClient: ISavedObjectsClient, + protected readonly notifications: NotificationsStart, + protected readonly dispatchers: Dispatchers, + protected readonly loadParams: LoadParams, + protected readonly loadContext: LoadContext + ) { + super(); + } + + async load() { + await this.getSavedObjectById(this.loadParams.objectId); + } + + async getSavedObjectById(objectId: string) { + try { + const res = await this.savedObjectClient.get({ + objectId, + }); + await this.processSavedData(res.observabilityObjectList[0]); + } catch (error) { + this.notifications.toasts.addError(error, { + title: `Cannot get saved data for object id: ${objectId}`, + }); + } + } + + updateAppAnalyticSelectedDateRange(selectedDateRange: { start: string; end: string }) { + const { setStartTime, setEndTime } = this.loadContext; + setStartTime(selectedDateRange.start); + setEndTime(selectedDateRange.end); + } + + async processSavedData(savedObjectData: SavedObjectData) { + const savedType = isObjectSavedQuery(savedObjectData) ? SAVED_QUERY : SAVED_VISUALIZATION; + const objectData = isObjectSavedQuery(savedObjectData) + ? savedObjectData.savedQuery + : savedObjectData.savedVisualization; + const currQuery = objectData?.query || ''; + const { appLogEvents } = this.loadContext; + + // app analytics specific + if (appLogEvents && objectData.selected_date_range) { + this.updateAppAnalyticSelectedDateRange(objectData.selected_date_range); + } + + // update redux store with this saved object data + await this.updateReduxState(savedType, objectData, currQuery); + + // update UI state with this saved object data + await this.updateUIState(objectData); + + // fetch data based on saved object data + const { tabId } = this.loadContext; + await this.loadDataFromSavedObject(objectData, tabId); + } + + async updateReduxState( + savedType: typeof SAVED_QUERY | typeof SAVED_VISUALIZATION, + objectData: SavedQuery | SavedVisualization, + currQuery: string + ) { + const { batch, dispatch, changeQuery, updateFields, updateTabName } = this.dispatchers; + const { tabId } = this.loadContext; + const { objectId } = this.loadParams; + batch(async () => { + await dispatch( + changeQuery({ + tabId, + query: { + [RAW_QUERY]: currQuery, + [SELECTED_TIMESTAMP]: objectData?.selected_timestamp?.name || 'timestamp', + [SAVED_OBJECT_ID]: objectId, + [SAVED_OBJECT_TYPE]: savedType, + [SELECTED_DATE_RANGE]: + objectData?.selected_date_range?.start && objectData?.selected_date_range?.end + ? [objectData.selected_date_range.start, objectData.selected_date_range.end] + : ['now-15m', 'now'], + }, + }) + ); + await dispatch( + updateFields({ + tabId, + data: { + [SELECTED_FIELDS]: [...objectData?.selected_fields?.tokens], + }, + }) + ); + await dispatch( + updateTabName({ + tabId, + tabName: objectData.name, + }) + ); + await dispatch( + updateSearchMetaData({ + tabId, + data: { + datasources: JSON.parse(objectData.data_sources), + lang: objectData.query_lang, + }, + }) + ); + if (isInnerObjectSavedVisualization(objectData)) { + await this.updateVisualizationConfig(objectData); + } + }); + } + + async updateVisualizationConfig(objectData: SavedVisualization) { + const { dispatch, updateVizConfig } = this.dispatchers; + const { tabId, queryManager, getDefaultVisConfig } = this.loadContext; + // fill saved user configs + let visConfig = {}; + const customConfig = objectData.user_configs ? JSON.parse(objectData.user_configs) : {}; + if (!isEmpty(customConfig.dataConfig) && !isEmpty(customConfig.dataConfig?.series)) { + visConfig = { ...customConfig }; + } else { + const statsTokens = queryManager.queryParser().parse(objectData.query).getStats(); + visConfig = { dataConfig: { ...getDefaultVisConfig(statsTokens) } }; + } + await dispatch( + updateVizConfig({ + tabId, + vizId: objectData?.type, + data: visConfig, + }) + ); + } + + async updateUIState(objectData: SavedQuery | SavedVisualization) { + const { + setSelectedPanelName, + setCurVisId, + setTempQuery, + setMetricChecked, + setMetricMeasure, + setSubType, + setSelectedContentTab, + } = this.loadContext; + // update UI state with saved data + setSelectedPanelName(objectData?.name || ''); + setCurVisId(objectData?.type || 'bar'); + setTempQuery((staleTempQuery) => { + return objectData?.query || staleTempQuery; + }); + if (isInnerObjectSavedVisualization(objectData)) { + if (objectData.sub_type === 'metric') { + setMetricChecked(true); + setMetricMeasure(objectData.units_of_measure || ''); + } + setSubType(objectData.sub_type); + } + const tabToBeFocused = isInnerObjectSavedVisualization(objectData) + ? TYPE_TAB_MAPPING[SAVED_VISUALIZATION] + : TYPE_TAB_MAPPING[SAVED_QUERY]; + setSelectedContentTab(tabToBeFocused); + } + + handleDirectQuerySuccess = (pollingResult, configurations: PollingConfigurations) => { + const { tabId, dispatchOnGettingHis } = this.loadContext; + const { dispatch } = this.dispatchers; + if (pollingResult && pollingResult.status === 'SUCCESS') { + // stop polling + dispatch( + updateSearchMetaData({ + tabId, + data: { + isPolling: false, + }, + }) + ); + // update page with data + dispatchOnGettingHis(pollingResult, ''); + return true; + } + return false; + }; + + handleDirectQueryError = (error: Error) => { + console.error(error); + return true; + }; + + loadDefaultIndexPattern = () => { + const { fetchData } = this.loadContext; + fetchData(); + }; + + loadSparkGlue = ({ objectData, dataSources, tabId }) => { + const { dispatch } = this.dispatchers; + const sqlService = new SQLService(coreRefs.http); + + // Create an instance of UsePolling + const polling = new UsePolling( + (params) => { + return sqlService.fetchWithJobId(params); + }, + 5000, + this.handleDirectQuerySuccess, + this.handleDirectQueryError, + { tabId } + ); + + // Update your references from the destructured hook to direct properties of the polling instance + const startPolling = polling.startPolling.bind(polling); // bind to ensure correct 'this' context + + this.pollingInstance = polling; + + dispatch( + updateSearchMetaData({ + tabId, + data: { + isPolling: true, + }, + }) + ); + + sqlService + .fetch({ + lang: objectData.query_lang.toLowerCase(), + query: objectData.query, + datasource: dataSources[0].label, + }) + .then((result) => { + if (result.queryId) { + startPolling({ queryId: result.queryId }); + } else { + console.log('no query id found in response'); + } + }) + .catch((e) => { + console.error(e); + }); + }; + + async loadDataFromSavedObject(objectData, tabId: string) { + const dataSources = parseStringDataSource(objectData.data_sources, this.notifications); + if (dataSources.length > 0 && dataSources[0].type) { + switch (dataSources[0].type) { + case DIRECT_DATA_SOURCE_TYPES.DEFAULT_INDEX_PATTERNS: + this.loadDefaultIndexPattern(); + return; + case DIRECT_DATA_SOURCE_TYPES.SPARK: + case DIRECT_DATA_SOURCE_TYPES.S3GLUE: + this.loadSparkGlue({ + objectData, + dataSources, + tabId, + }); + return; + default: + return; + } + } + } + + getPollingInstance() { + return this.pollingInstance; + } +} diff --git a/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts b/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts index 21fc4b27a..d10a78086 100644 --- a/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts +++ b/public/services/saved_objects/saved_object_loaders/ppl/ppl_loader.ts @@ -3,12 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { has, isEmpty } from 'lodash'; -import { updateFields as updateFieldsAction } from 'public/components/event_analytics/redux/slices/field_slice'; -import { changeQuery as changeQueryAction } from 'public/components/event_analytics/redux/slices/query_slice'; -import { updateTabName as updateTabNameAction } from 'public/components/event_analytics/redux/slices/query_tab_slice'; -import { change as updateVizConfigAction } from 'public/components/event_analytics/redux/slices/viualization_config_slice'; +import { isEmpty } from 'lodash'; import { batch as Batch } from 'react-redux'; +import { update } from 'public/components/event_analytics/redux/slices/search_meta_data_slice'; +import { updateFields as updateFieldsAction } from '../../../../components/event_analytics/redux/slices/field_slice'; +import { changeQuery as changeQueryAction } from '../../../../components/event_analytics/redux/slices/query_slice'; +import { updateTabName as updateTabNameAction } from '../../../../components/event_analytics/redux/slices/query_tab_slice'; +import { change as updateVizConfigAction } from '../../../../components/event_analytics/redux/slices/viualization_config_slice'; +import { update as updateSearchMetaData } from '../../../../components/event_analytics/redux/slices/search_meta_data_slice'; import { NotificationsStart } from '../../../../../../../src/core/public'; import { AGGREGATIONS, @@ -59,6 +61,7 @@ interface LoadContext { setSubType: (type: string) => void; setSelectedContentTab: (curTab: string) => void; fetchData: () => void; + dataSources: SelectedDataSource[]; } interface Dispatchers { @@ -179,6 +182,15 @@ export class PPLSavedObjectLoader extends SavedObjectLoaderBase implements ISave tabName: objectData.name, }) ); + await dispatch( + updateSearchMetaData({ + tabId, + data: { + datasources: [JSON.parse(objectData.data_sources)], + lang: objectData.query_lang, + }, + }) + ); if (isInnerObjectSavedVisualization(objectData)) { await this.updateVisualizationConfig(objectData); } diff --git a/server/plugin.ts b/server/plugin.ts index 31db6b3ef..59e3e4124 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -15,7 +15,10 @@ import { import { OpenSearchObservabilityPlugin } from './adaptors/opensearch_observability_plugin'; import { PPLPlugin } from './adaptors/ppl_plugin'; import { setupRoutes } from './routes/index'; -import { visualizationSavedObject } from './saved_objects/observability_saved_object'; +import { + searchSavedObject, + visualizationSavedObject, +} from './saved_objects/observability_saved_object'; import { ObservabilityPluginSetup, ObservabilityPluginStart } from './types'; export class ObservabilityPlugin @@ -111,6 +114,7 @@ export class ObservabilityPlugin setupRoutes({ router, client: openSearchObservabilityClient }); core.savedObjects.registerType(visualizationSavedObject); + core.savedObjects.registerType(searchSavedObject); core.capabilities.registerProvider(() => ({ observability: { show: true, diff --git a/server/saved_objects/observability_saved_object.ts b/server/saved_objects/observability_saved_object.ts index d8f8ca32d..2054a4329 100644 --- a/server/saved_objects/observability_saved_object.ts +++ b/server/saved_objects/observability_saved_object.ts @@ -5,7 +5,10 @@ import { SavedObjectsType } from '../../../../src/core/server'; import { observabilityID, observabilityLogsID } from '../../common/constants/shared'; -import { VISUALIZATION_SAVED_OBJECT } from '../../common/types/observability_saved_object_attributes'; +import { + SEARCH_SAVED_OBJECT, + VISUALIZATION_SAVED_OBJECT, +} from '../../common/types/observability_saved_object_attributes'; export const visualizationSavedObject: SavedObjectsType = { name: VISUALIZATION_SAVED_OBJECT, @@ -41,3 +44,38 @@ export const visualizationSavedObject: SavedObjectsType = { }, migrations: {}, }; + +export const searchSavedObject: SavedObjectsType = { + name: SEARCH_SAVED_OBJECT, + icon: 'editorCodeBlock', + hidden: false, + namespaceType: 'single', + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle(obj) { + return obj.attributes.title; + }, + getInAppUrl(obj) { + const editPath = `#/explorer/${SEARCH_SAVED_OBJECT}:${obj.id}`; + const editUrl = `/app/${observabilityLogsID}${editPath}`; + return { + path: editUrl, + uiCapabilitiesPath: 'observability.show', + }; + }, + }, + mappings: { + dynamic: false, + properties: { + title: { + type: 'text', + }, + description: { + type: 'text', + }, + version: { type: 'integer' }, + }, + }, + migrations: {}, +};