From f519720db64e5812ffb7caa2b99200f9925fb8b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Oct 2023 03:52:21 +0000 Subject: [PATCH] [Feature] Match discover look and feel (#1135) * remove unused files Signed-off-by: Eric Wei * missing snapshots Signed-off-by: Eric Wei * remove unused files Signed-off-by: Eric Wei * move sidebar out of explorer for match look and feel Signed-off-by: Eric * sidebar panel always with full height Signed-off-by: Eric * adjust padding Signed-off-by: Eric * make search components wrappable Signed-off-by: Eric * adjust noresult/novisualize page Signed-off-by: Eric * add padding to datagrid Signed-off-by: Eric * restructured sidebar fields to use exact same spacing/paneling as discover, so that field type icons look the same and field action buttons disappear if not hovered over Signed-off-by: Paul Sebastian * address datasource persistency issue Signed-off-by: Eric * code changes preparing for integration with app analytics Signed-off-by: Eric * resolve page crash and hide datasource for app analytics Signed-off-by: Eric * saved object supports datasources, query language, and direct query Signed-off-by: Eric * change saved object icon Signed-off-by: Eric * bulk delete for saved query Signed-off-by: Eric * change data grid to separate sort/page query to not interfere with other components Signed-off-by: Paul Sebastian * basic drag and drop added Signed-off-by: Paul Sebastian * viz config changes Signed-off-by: Paul Sebastian * add margin to resizable Signed-off-by: Paul Sebastian * wrap countdistribution with panel Signed-off-by: Eric * pr requested changes Signed-off-by: Paul Sebastian * remove panel wrapper for inner countdistribution Signed-off-by: Eric * change datasource selector to only showopensearch/ default cluste Signed-off-by: Eric * remove unused files Signed-off-by: Eric Wei * missing snapshots Signed-off-by: Eric Wei * remove unused files Signed-off-by: Eric Wei * url datasource data prsing and clean up Signed-off-by: Eric * minor format Signed-off-by: Eric * query bar issue when loading direct query saved object Signed-off-by: Eric * minor cleanup Signed-off-by: Eric * update snapshots Signed-off-by: Eric * fix minor query bar issue and add types to searchMeta slice Signed-off-by: Eric * stop polling when load from saved object and few related minor changes Signed-off-by: Eric * fix polling cleanup issue Signed-off-by: Eric * save will not trigger reload Signed-off-by: Eric * add language enum Signed-off-by: Eric * direct query status indicator Signed-off-by: Paul Sebastian * turn default timestamp/pattern mark to badge and on hover Signed-off-by: Paul Sebastian * flyout resize button margin fix Signed-off-by: Paul Sebastian * direct query data grid fixes Signed-off-by: Paul Sebastian * pr changes and added test Signed-off-by: Paul Sebastian * removed console logs Signed-off-by: Paul Sebastian * searchMetaData type change Signed-off-by: Paul Sebastian * use datasource variable Signed-off-by: Paul Sebastian * fixed types and reduced redundancy Signed-off-by: Paul Sebastian * fixed when data grix shows Signed-off-by: Paul Sebastian * simplify condition checking for datagrid Signed-off-by: Eric * update snapshot Signed-off-by: Eric --------- Signed-off-by: Eric Wei Signed-off-by: Eric Signed-off-by: Paul Sebastian Co-authored-by: Paul Sebastian (cherry picked from commit ea2fb2c1669a229f4a58de145db81247eaec6b18) Signed-off-by: github-actions[bot] --- common/constants/data_sources.ts | 16 + common/constants/shared.ts | 1 + common/types/explorer.ts | 23 +- .../observability_saved_object_attributes.ts | 16 +- common/utils/index.ts | 1 + .../components/application.tsx | 2 + .../components/application_analytics/home.tsx | 2 + .../common/field_icon/field_icon.tsx | 2 +- public/components/common/query_utils/index.ts | 57 +- public/components/common/search/search.tsx | 79 +- .../components/common/search/sql_search.tsx | 122 +- .../__snapshots__/no_results.test.tsx.snap | 298 +- .../__snapshots__/data_grid.test.tsx.snap | 378 +- .../datasources/datasources_selection.tsx | 197 +- .../explorer/direct_query_running.tsx | 47 +- .../explorer/events_views/data_grid.scss | 3 - .../explorer/events_views/data_grid.tsx | 114 +- .../explorer/events_views/docView.scss | 4 +- .../explorer/events_views/doc_flyout.tsx | 2 +- .../event_analytics/explorer/explorer.scss | 2 +- .../event_analytics/explorer/explorer.tsx | 405 +- .../event_analytics/explorer/log_explorer.tsx | 1 + .../event_analytics/explorer/no_results.tsx | 78 +- .../__snapshots__/field.test.tsx.snap | 546 +- .../__snapshots__/sidebar.test.tsx.snap | 18472 +++++++--------- .../explorer/sidebar/field.tsx | 136 +- .../explorer/sidebar/field_insights.tsx | 10 +- .../sidebar/observability_sidebar.tsx | 95 + .../explorer/sidebar/sidebar.scss | 28 + .../explorer/sidebar/sidebar.tsx | 327 +- .../config_panel/config_panel.scss | 10 + .../data_config_panel_fields.tsx | 117 +- .../data_configurations_panel.scss | 46 + .../data_configurations_panel.tsx | 103 +- .../count_distribution/count_distribution.tsx | 1 + .../visualizations/direct_query_vis.tsx | 81 +- .../explorer/visualizations/index.tsx | 11 +- .../redux/slices/query_slice.ts | 2 +- .../redux/slices/search_meta_data_slice.ts | 57 +- .../utils/__tests__/utils.test.tsx | 16 +- .../event_analytics/utils/utils.tsx | 8 +- public/components/hooks/index.ts | 2 +- .../hooks/use_direct_query_search.ts | 4 - public/components/hooks/use_polling.ts | 78 +- public/components/metrics/index.scss | 2 +- .../visualizations/visualization.tsx | 2 +- .../datasources/obs_opensearch_datasource.ts | 30 + .../saved_object_client/client_factory.ts | 8 +- .../osd_saved_object_client.ts | 4 + .../osd_saved_objects/saved_searches.ts | 179 + .../osd_saved_objects/saved_visualization.ts | 6 + .../saved_objects_actions.ts | 31 +- .../explorer_saved_object_loader.ts | 387 + .../saved_object_loaders/ppl/ppl_loader.ts | 22 +- server/plugin.ts | 6 +- .../observability_saved_object.ts | 40 +- 56 files changed, 10687 insertions(+), 12030 deletions(-) create mode 100644 common/constants/data_sources.ts create mode 100644 public/components/event_analytics/explorer/sidebar/observability_sidebar.tsx delete mode 100644 public/components/hooks/use_direct_query_search.ts create mode 100644 public/framework/datasources/obs_opensearch_datasource.ts create mode 100644 public/services/saved_objects/saved_object_client/osd_saved_objects/saved_searches.ts create mode 100644 public/services/saved_objects/saved_object_loaders/explorer_saved_object_loader.ts diff --git a/common/constants/data_sources.ts b/common/constants/data_sources.ts new file mode 100644 index 0000000000..aa596ad108 --- /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 8175f33801..198ce05a33 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 af94da8026..a36dd81a8d 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 520f922bc7..a98f0b4b2d 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 284c1f4b0c..0becefc0ac 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 d5ba1b8fe7..bdfea879a8 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 a97f56f34d..c360e50d9b 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 467c2c7c0b..e5b2d01173 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 98d0b54ff0..bed3432dbb 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 604575850b..de450d09c1 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 a0e63ed20c..e21e9494f0 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 5d4fe5f9a7..bbea0a6282 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 bf5392d3b3..af387c2cd5 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 f6019d42b7..56cac6abb5 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 c395ed4dad..f05023e218 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 dc1629b713..feb0a57e99 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 05bd47544f..37296e2668 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 91cc8ab622..14ee3a5733 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 0abf095fc6..0b906bf4e0 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 afc799306d..fe7ab15c31 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 b55f5ff2c2..69e7fba1ba 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 2b82bfa0ea..a8a60a58f9 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 0000000000..89cb4e5e61 --- /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 8f96c7764d..b9eeb45ffe 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 ae6a55a316..21d21b905e 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 b6737a338b..23028f83a2 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 4c7f666c54..669a314c8e 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 cac373f90a..6ca8a2e848 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 e5374811ed..d55fedc850 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 8e5f10f315..6fa6778819 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 73d55f0a56..a1268c62dc 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 f97db37f3f..0b3a6bea5b 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 eee7083fab..524e13bd8c 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 f6b5138d2f..85a216b481 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 d680860e19..996a656b6b 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 d3c599c18d..3296182d47 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 a850c1690e..0000000000 --- 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 42fe5b9d35..c1f79ada11 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 9f2d601982..abfeba3747 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 6de22585ef..313f43cf7b 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 0000000000..07a30dd02f --- /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 4af366a83e..7f9fb965a9 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 f0d0baa047..ac3204b7ad 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 0000000000..45b5e3de4f --- /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 635199c546..f0adf13f82 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 5d8ae29d39..3a1fc5a664 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 0000000000..6e207f7ab4 --- /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 21fc4b27ae..d10a78086e 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 31db6b3efc..59e3e41247 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 d8f8ca32d0..2054a43292 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: {}, +};