From c2a564707ca802b494b36fd12addc230e28fe726 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Wed, 15 Sep 2021 13:40:19 -0700 Subject: [PATCH 01/20] removed live button Signed-off-by: Eric Wei --- public/components/explorer/explorer.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index 45ed230fb..c6ebbf2dc 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -410,15 +410,6 @@ export const Explorer = ({ } } }, - { - text: 'Live', - iconType: 'play', - handlers: { - onClick: () => { - console.log('refresh clicked'); - } - } - }, { text: 'Save', iconType: 'heart', From 53f20577f2d46c40c29c6f03ebcc014cb31698a5 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Wed, 15 Sep 2021 22:45:09 -0700 Subject: [PATCH 02/20] refresh button Signed-off-by: Eric Wei --- public/components/common/search/autocomplete.tsx | 1 + public/components/common/search/autocomplete_plugin.ts | 4 ++-- public/components/explorer/explorer.tsx | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/components/common/search/autocomplete.tsx b/public/components/common/search/autocomplete.tsx index 19d881120..55f68f19f 100644 --- a/public/components/common/search/autocomplete.tsx +++ b/public/components/common/search/autocomplete.tsx @@ -20,6 +20,7 @@ export function Autocomplete(props: IQueryBarProps) { const { query, handleQueryChange, handleQuerySearch, dslService } = props; const PPLSuggestionPlugin = createPPLSuggestionsPlugin({ + query, handleQueryChange: props.handleQueryChange, handleQuerySearch: props.handleQuerySearch, dslService: props.dslService, diff --git a/public/components/common/search/autocomplete_plugin.ts b/public/components/common/search/autocomplete_plugin.ts index 0c4d5f0bf..657c9204e 100644 --- a/public/components/common/search/autocomplete_plugin.ts +++ b/public/components/common/search/autocomplete_plugin.ts @@ -21,6 +21,7 @@ interface PPLSuggestion { } interface CreatePPLSuggestionsPluginProps { + query: any; handleQueryChange: (query: string, index: string) => void; handleQuerySearch: () => void; dslService: DSLService; @@ -267,9 +268,8 @@ export function createPPLSuggestionsPlugin( ): AutocompletePlugin { return { onStateChange: ({ state }) => { - if (state.query.length > queryLength) { + if (options.query.rawQuery !== state.query) { options.handleQueryChange(state.query, currIndex); - queryLength++; } }, onSubmit: () => { diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index c6ebbf2dc..03b93fbd3 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -405,8 +405,8 @@ export const Explorer = ({ text: 'Refresh', iconType: 'refresh', handlers: { - onClick: () => { - console.log('refresh clicked'); + onMouseDown: () => { + handleQuerySearch(); } } }, @@ -424,6 +424,7 @@ export const Explorer = ({ const handleContentTabClick = (selectedTab: IQueryTab) => setSelectedContentTab(selectedTab.id); const handleQuerySearch = () => { + console.log('handle query change'); fetchData(); } From bf6daf6aee2fd1b5df330a2708eb0db23943be10 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Thu, 16 Sep 2021 10:09:41 -0700 Subject: [PATCH 03/20] removed live button from home Signed-off-by: Eric Wei --- public/components/explorer/home.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/public/components/explorer/home.tsx b/public/components/explorer/home.tsx index 84498e398..5f2700f38 100644 --- a/public/components/explorer/home.tsx +++ b/public/components/explorer/home.tsx @@ -64,13 +64,6 @@ export const Home = (props: any) => { history.push('/explorer/events'); } } - }, - { - text: 'Live', - iconType: 'play', - handlers: { - onClick: () => {} - } } ]; From 493a334afb6c686fb80f319244e18b127675348a Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Fri, 17 Sep 2021 11:20:27 -0700 Subject: [PATCH 04/20] xaxis range selectable Signed-off-by: Eric Wei --- common/constants/explorer.ts | 39 +++++++++++++++++ public/components/explorer/explorer.tsx | 42 ++----------------- .../timechart_header/timechart_header.tsx | 4 +- .../count_distribution/count_distribution.tsx | 11 +++++ .../components/visualizations/charts/bar.tsx | 12 ++++-- .../components/visualizations/plotly/plot.tsx | 12 +++--- 6 files changed, 70 insertions(+), 50 deletions(-) diff --git a/common/constants/explorer.ts b/common/constants/explorer.ts index b82d86aee..64e37260d 100644 --- a/common/constants/explorer.ts +++ b/common/constants/explorer.ts @@ -21,6 +21,45 @@ export const TAB_EVENT_TITLE = 'Events'; export const TAB_EVENT_ID_TXT_PFX = 'main-content-events-'; export const TAB_CHART_ID_TXT_PFX = 'main-content-charts-'; +export const TIME_INTERVAL_OPTIONS = [ + { + display: 'Auto', + val: 'h' // same as value of Hour for now + }, + { + display: 'Millisecond', + val: 'ms' + }, + { + display: 'Second', + val: 's' + }, + { + display: 'Minute', + val: 'm' + }, + { + display: 'Hour', + val: 'h' + }, + { + display: 'Day', + val: 'd' + }, + { + display: 'Week', + val: 'w' + }, + { + display: 'Month', + val: 'M' + }, + { + display: 'Year', + val: 'y' + }, +] + // redux export const SELECTED_QUERY_TAB = 'selectedQueryTab'; export const QUERY_TAB_IDS = 'queryTabIds'; diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index 03b93fbd3..5962e96a0 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -48,7 +48,8 @@ import { RAW_QUERY, SELECTED_FIELDS, UNSELECTED_FIELDS, - INDEX + INDEX, + TIME_INTERVAL_OPTIONS } from '../../../common/constants/explorer'; import { getIndexPatternFromRawQuery } from '../../../common/utils'; import { @@ -251,44 +252,7 @@ export const Explorer = ({ > { getCountVisualizations(intrv); }} diff --git a/public/components/explorer/timechart_header/timechart_header.tsx b/public/components/explorer/timechart_header/timechart_header.tsx index a21ac0bbd..cf34ceb0f 100644 --- a/public/components/explorer/timechart_header/timechart_header.tsx +++ b/public/components/explorer/timechart_header/timechart_header.tsx @@ -117,8 +117,8 @@ export function TimechartHeader({ label: display, }; })} - value={interval} - onChange={handleIntervalChange} + value={ interval } + onChange={ handleIntervalChange } append={ undefined } /> diff --git a/public/components/explorer/visualizations/count_distribution/count_distribution.tsx b/public/components/explorer/visualizations/count_distribution/count_distribution.tsx index 43f935df9..09446b62a 100644 --- a/public/components/explorer/visualizations/count_distribution/count_distribution.tsx +++ b/public/components/explorer/visualizations/count_distribution/count_distribution.tsx @@ -33,6 +33,13 @@ export const CountDistribution = ({ }, height: 220 }; + const config = {}; + const xaxis = { + autorange: true + }; + const yaxis = { + fixedrange: true + }; if (!xkey || !ykey) { return null; @@ -44,6 +51,10 @@ export const CountDistribution = ({ yvalues={ data[ykey] || [] } name="Event counts" layoutConfig={ layout } + config={ config } + xaxis={ xaxis } + yaxis={ yaxis } + showlegend={ true } /> ); }; \ No newline at end of file diff --git a/public/components/visualizations/charts/bar.tsx b/public/components/visualizations/charts/bar.tsx index cf5b0b557..48bf5d36a 100644 --- a/public/components/visualizations/charts/bar.tsx +++ b/public/components/visualizations/charts/bar.tsx @@ -15,8 +15,11 @@ import { Plt } from '../plotly/plot'; export const Bar = ({ xvalues, yvalues, + xaxisConf, + yaxisConf, name, layoutConfig, + ...rest }: any) => { return ( ); }; \ No newline at end of file diff --git a/public/components/visualizations/plotly/plot.tsx b/public/components/visualizations/plotly/plot.tsx index 545a03772..1faeac0c2 100644 --- a/public/components/visualizations/plotly/plot.tsx +++ b/public/components/visualizations/plotly/plot.tsx @@ -16,9 +16,9 @@ import Plotly from 'plotly.js-dist'; interface PltProps { data: Plotly.Data[]; layout?: Partial; + config?: Partial; onHoverHandler?: (event: Readonly) => void; onUnhoverHandler?: (event: Readonly) => void; - onClickHandler?: (event: Readonly) => void; height?: string; } @@ -31,9 +31,11 @@ export function Plt(props: PltProps) { style={{ width: '100%', height: props.height || '100%' }} onHover={props.onHoverHandler} onUnhover={props.onUnhoverHandler} - onClick={props.onClickHandler} useResizeHandler - config={{ displayModeBar: false }} + config={{ + displayModeBar: false, + ...props.config + }} layout={{ autosize: true, margin: { @@ -53,12 +55,12 @@ export function Plt(props: PltProps) { xaxis: { showgrid: true, zeroline: false, - rangemode: 'normal', + rangemode: 'normal' }, yaxis: { showgrid: true, zeroline: false, - rangemode: 'normal', + rangemode: 'normal' }, ...props.layout, }} From 4cf83bb9226c0e05408c9a7d1fdeef9a778c3a04 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Thu, 23 Sep 2021 15:13:53 -0700 Subject: [PATCH 05/20] added new charts Signed-off-by: Eric Wei --- .../assets/chart_bar_horizontal.tsx | 39 ++++++++++ .../workspace_panel/workspace_panel.tsx | 73 +++++++++++++------ .../workspace_panel_wrapper.scss | 4 - .../components/visualizations/charts/bar.tsx | 51 +++++++------ .../visualizations/charts/horizontal_bar.tsx | 52 +++++++++++++ .../components/visualizations/charts/line.tsx | 42 +++++++---- .../components/visualizations/plotly/plot.tsx | 4 +- 7 files changed, 202 insertions(+), 63 deletions(-) create mode 100644 public/components/explorer/visualizations/assets/chart_bar_horizontal.tsx create mode 100644 public/components/visualizations/charts/horizontal_bar.tsx diff --git a/public/components/explorer/visualizations/assets/chart_bar_horizontal.tsx b/public/components/explorer/visualizations/assets/chart_bar_horizontal.tsx new file mode 100644 index 000000000..489a20e0d --- /dev/null +++ b/public/components/explorer/visualizations/assets/chart_bar_horizontal.tsx @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarHorizontal = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx b/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx index a5bc53449..866cd6401 100644 --- a/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx +++ b/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx @@ -15,32 +15,39 @@ import { DragDrop } from '../drag_drop'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { Bar } from '../../../visualizations/charts/bar'; import { Line } from '../../../visualizations/charts/line'; +import { HorizontalBar } from '../../../visualizations/charts/horizontal_bar'; import { LensIconChartBar } from '../assets/chart_bar'; import { LensIconChartLine } from '../assets/chart_line'; +import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; +import { EmptyPlaceholder } from '../shared_components/empty_placeholder'; -const layout = { +const plotlySharedlayout = { showlegend: true, margin: { - l: 60, + l: 50, r: 10, - b: 15, + b: 30, t: 30, pad: 0, }, - height: 300 + height: 500, + legend: { + orientation: 'v', + traceorder: 'normal', + } +}; + +const plotlySharedConfig = { + displayModeBar: true, + displaylogo: false, + responsive: true, + editable: true }; export function WorkspacePanel({ visualizations }: any) { - if (!visualizations || !visualizations.data) return null; - - const data = visualizations.data; - const meta = visualizations.metadata; - const xkey = meta?.xfield?.name; - const ykey = meta?.yfield?.name; - const memorizedVisualizationTypes = useMemo(() => { return ([ { @@ -52,11 +59,31 @@ export function WorkspacePanel({ selection: { dataLoss: 'nothing' }, - chart: : + }, + { + id: 'horizontal_bar', + label: 'H. Bar', + fullLabel: 'H. Bar', + icon: LensIconChartBarHorizontal, + visualizationId: uniqueId('vis-horizontal-bar-'), + selection: { + dataLoss: 'nothing' + }, + chart: (!visualizations || !visualizations.data) ? + : }, { @@ -68,11 +95,13 @@ export function WorkspacePanel({ selection: { dataLoss: 'nothing' }, - chart: : } ]); @@ -104,7 +133,7 @@ export function WorkspacePanel({ className="lnsWorkspacePanel__dragDrop" data-test-subj="lnsWorkspace" draggable={false} - droppable={true} + droppable={false} onDrop={onDrop} >
diff --git a/public/components/explorer/visualizations/workspace_panel/workspace_panel_wrapper.scss b/public/components/explorer/visualizations/workspace_panel/workspace_panel_wrapper.scss index 689e4fdf1..8a6bb7f8b 100644 --- a/public/components/explorer/visualizations/workspace_panel/workspace_panel_wrapper.scss +++ b/public/components/explorer/visualizations/workspace_panel/workspace_panel_wrapper.scss @@ -14,8 +14,6 @@ .lnsWorkspacePanelWrapper { @include euiScrollBar; overflow: hidden; - // Override panel size padding - padding: 0 !important; // sass-lint:disable-line no-important margin-bottom: $euiSize; display: flex; flex-direction: column; @@ -25,8 +23,6 @@ .lnsWorkspacePanelWrapper__pageContentHeader { @include euiTitle('xs'); padding: $euiSizeM; - // override EuiPage - margin-bottom: 0 !important; // sass-lint:disable-line no-important } .lnsWorkspacePanelWrapper__pageContentHeader--unsaved { diff --git a/public/components/visualizations/charts/bar.tsx b/public/components/visualizations/charts/bar.tsx index 48bf5d36a..a63f6fcc3 100644 --- a/public/components/visualizations/charts/bar.tsx +++ b/public/components/visualizations/charts/bar.tsx @@ -10,44 +10,51 @@ */ import React from 'react'; +import { + take, + merge +} from 'lodash'; import { Plt } from '../plotly/plot'; export const Bar = ({ - xvalues, - yvalues, - xaxisConf, - yaxisConf, - name, - layoutConfig, - ...rest + visualizations, + barConfig = {}, + layoutConfig = {}, }: any) => { + + const { data, metadata: { fields, } } = visualizations; + const stackLength = fields.length - 1; + const barValues = take(fields, stackLength).map((field: any) => { + return { + x: barConfig.orientation !== 'h' ? data[fields[stackLength].name] : data[field.name], + y: barConfig.orientation !== 'h' ? data[field.name] : data[fields[stackLength].name], + type: 'bar', + name: field.name, + ...barConfig + }; + }); + + const barLayoutConfig = merge({ + xaxis: { + automargin: true + }, + }, layoutConfig); + return ( ); }; \ No newline at end of file diff --git a/public/components/visualizations/charts/horizontal_bar.tsx b/public/components/visualizations/charts/horizontal_bar.tsx new file mode 100644 index 000000000..0e33f39fc --- /dev/null +++ b/public/components/visualizations/charts/horizontal_bar.tsx @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React from 'react'; +import { merge } from 'lodash'; +import { Bar } from './bar'; + +interface IBarTrace { + xvalues: Array; + yvalues: Array; + mode: string; + name: string +} + +export interface IStackedBarProps { + name: string; + barValues: Array; + layoutConfig?: any +}; + +export const HorizontalBar = ({ + visualizations, + horizontalConfig, + layoutConfig = {} +}: any) => { + + const horizontalBarConfig = merge({ + orientation: 'h' + }, horizontalConfig); + + const horizontalBarLayoutConfig = merge({ + yaxis: { + automargin: true + }, + }, layoutConfig); + + return ( + + ); +}; \ No newline at end of file diff --git a/public/components/visualizations/charts/line.tsx b/public/components/visualizations/charts/line.tsx index a7b879db7..7a66eab21 100644 --- a/public/components/visualizations/charts/line.tsx +++ b/public/components/visualizations/charts/line.tsx @@ -10,25 +10,40 @@ */ import React from 'react'; +import { + take, + merge +} from 'lodash'; import { Plt } from '../plotly/plot'; export const Line = ({ - xvalues, - yvalues, - name, - layoutConfig, + visualizations, + lineConfig = {}, + layoutConfig = {}, }: any) => { + + const { data, metadata: { fields, } } = visualizations; + const lineLength = fields.length - 1; + const lineValues = take(fields, lineLength).map((field: any) => { + return { + x: data[fields[lineLength].name], + y: data[field.name], + type: 'line', + name: field.name + }; + }); + + const config = { + barmode: 'line', + xaxis: { + automargin: true + } + }; + const lineLayoutConfig = merge(config, layoutConfig); return ( ); }; \ No newline at end of file diff --git a/public/components/visualizations/plotly/plot.tsx b/public/components/visualizations/plotly/plot.tsx index 1faeac0c2..04563eba5 100644 --- a/public/components/visualizations/plotly/plot.tsx +++ b/public/components/visualizations/plotly/plot.tsx @@ -24,7 +24,6 @@ interface PltProps { export function Plt(props: PltProps) { const PlotComponent = plotComponentFactory(Plotly); - return ( Date: Sun, 26 Sep 2021 19:31:54 -0700 Subject: [PATCH 06/20] added sidebar to vis, hided vis setting panel Signed-off-by: Eric Wei --- public/components/explorer/explorer.tsx | 2 ++ .../explorer/hooks/use_fetch_events.ts | 9 +++++---- public/components/explorer/sidebar/sidebar.tsx | 4 +++- .../count_distribution/count_distribution.tsx | 13 ++++--------- .../explorer/visualizations/frame_layout.scss | 2 +- .../explorer/visualizations/frame_layout.tsx | 4 ++-- .../components/explorer/visualizations/index.tsx | 16 +++++++++++++--- 7 files changed, 30 insertions(+), 20 deletions(-) diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index 5962e96a0..a30a8fbbf 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -329,6 +329,8 @@ export const Explorer = ({ ); }; diff --git a/public/components/explorer/hooks/use_fetch_events.ts b/public/components/explorer/hooks/use_fetch_events.ts index 81e52a036..68498b95e 100644 --- a/public/components/explorer/hooks/use_fetch_events.ts +++ b/public/components/explorer/hooks/use_fetch_events.ts @@ -80,12 +80,13 @@ export const useFetchEvents = ({ tabId: requestParams.tabId, data: { [SELECTED_FIELDS]: [], - [UNSELECTED_FIELDS]: res.schema ? [ ...res.schema ] : [] + [UNSELECTED_FIELDS]: res.schema ? [ ...res.schema ] : [], + [AVAILABLE_FIELDS]: res?.schema ? [...res.schema] : [] } })); dispatch(sortFields({ tabId: requestParams.tabId, - data: [UNSELECTED_FIELDS] + data: [AVAILABLE_FIELDS, UNSELECTED_FIELDS] })); }); }); @@ -101,13 +102,13 @@ export const useFetchEvents = ({ tabId: requestParams.tabId, data: { [SELECTED_FIELDS]: [], - [UNSELECTED_FIELDS]: [], + [UNSELECTED_FIELDS]: res?.schema ? [...res.schema] : [], [AVAILABLE_FIELDS]: res?.schema ? [...res.schema] : [] } })); dispatch(sortFields({ tabId: requestParams.tabId, - data: [AVAILABLE_FIELDS] + data: [AVAILABLE_FIELDS, UNSELECTED_FIELDS] })); }); }); diff --git a/public/components/explorer/sidebar/sidebar.tsx b/public/components/explorer/sidebar/sidebar.tsx index 65ae6c6ca..eaaca2fcb 100644 --- a/public/components/explorer/sidebar/sidebar.tsx +++ b/public/components/explorer/sidebar/sidebar.tsx @@ -41,6 +41,8 @@ export const Sidebar = (props: ISidebarProps) => { const [showFields, setShowFields] = useState(false); const [searchTerm, setSearchTerm] = useState(''); + console.log('sidebar explorerFields: ', explorerFields); + return (
{

diff --git a/public/components/explorer/visualizations/count_distribution/count_distribution.tsx b/public/components/explorer/visualizations/count_distribution/count_distribution.tsx index 09446b62a..a31df4287 100644 --- a/public/components/explorer/visualizations/count_distribution/count_distribution.tsx +++ b/public/components/explorer/visualizations/count_distribution/count_distribution.tsx @@ -16,9 +16,8 @@ export const CountDistribution = ({ countDistribution }: any) => { - if (!countDistribution) return null; - - const data = countDistribution.data; + if (!countDistribution || !countDistribution.data) return null; + const meta = countDistribution.metadata; const xkey = meta?.xfield?.name; const ykey = meta?.yfield?.name; @@ -46,15 +45,11 @@ export const CountDistribution = ({ } return ( - ); }; \ No newline at end of file diff --git a/public/components/explorer/visualizations/frame_layout.scss b/public/components/explorer/visualizations/frame_layout.scss index 6f68babbd..fbc2e4ff6 100644 --- a/public/components/explorer/visualizations/frame_layout.scss +++ b/public/components/explorer/visualizations/frame_layout.scss @@ -48,7 +48,7 @@ $lnsSuggestionWidth: 150px; } .lnsFrameLayout__sidebar { - margin: 0; + margin: 0 16px 0; flex: 1 0 18%; min-width: $lnsPanelMinWidth + $euiSize; display: flex; diff --git a/public/components/explorer/visualizations/frame_layout.tsx b/public/components/explorer/visualizations/frame_layout.tsx index b568d869b..1870178df 100644 --- a/public/components/explorer/visualizations/frame_layout.tsx +++ b/public/components/explorer/visualizations/frame_layout.tsx @@ -31,9 +31,9 @@ export function FrameLayout(props: FrameLayoutProps) { {props.workspacePanel} - + {/* {props.configPanel} - + */}
); diff --git a/public/components/explorer/visualizations/index.tsx b/public/components/explorer/visualizations/index.tsx index 984e6321d..748e0f153 100644 --- a/public/components/explorer/visualizations/index.tsx +++ b/public/components/explorer/visualizations/index.tsx @@ -16,19 +16,29 @@ import _ from 'lodash'; import React from 'react'; import { FrameLayout } from './frame_layout'; import { DataPanel } from './datapanel'; +import { Sidebar } from '../sidebar/sidebar'; import { WorkspacePanel } from './workspace_panel'; import { ConfigPanelWrapper } from './config_panel'; export const ExplorerVisualizations = ({ explorerVis, - explorerFields + explorerFields, + handleAddField, + handleRemoveField }: any) => { return ( + // } + dataPanel={ + } workspacePanel={ From 2e6a1e869f107f0c17928caaaf51f75331405d88 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Mon, 27 Sep 2021 14:45:32 -0700 Subject: [PATCH 07/20] fixed a bar issue Signed-off-by: Eric Wei --- public/components/visualizations/charts/bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/components/visualizations/charts/bar.tsx b/public/components/visualizations/charts/bar.tsx index a63f6fcc3..0d6ae6a00 100644 --- a/public/components/visualizations/charts/bar.tsx +++ b/public/components/visualizations/charts/bar.tsx @@ -24,7 +24,7 @@ export const Bar = ({ const { data, metadata: { fields, } } = visualizations; const stackLength = fields.length - 1; - const barValues = take(fields, stackLength).map((field: any) => { + const barValues = take(fields, stackLength > 0 ? stackLength : 1).map((field: any) => { return { x: barConfig.orientation !== 'h' ? data[fields[stackLength].name] : data[field.name], y: barConfig.orientation !== 'h' ? data[field.name] : data[fields[stackLength].name], From 4cb8b73d674737d282b87a4e3ab12e38b54dc922 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Mon, 27 Sep 2021 16:13:47 -0700 Subject: [PATCH 08/20] modified run/refresh button Signed-off-by: Eric Wei --- public/components/explorer/explorer.tsx | 7 +++++-- public/components/explorer/home.tsx | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index a30a8fbbf..cc64f6057 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -368,8 +368,11 @@ export const Explorer = ({ const actionItems = [ { - text: 'Refresh', - iconType: 'refresh', + text: isEmpty(explorerData) ? 'Run' : 'Refresh', + iconType: isEmpty(explorerData) ? 'play': 'refresh', + attributes: { + fill: isEmpty(explorerData) ? true : false + }, handlers: { onMouseDown: () => { handleQuerySearch(); diff --git a/public/components/explorer/home.tsx b/public/components/explorer/home.tsx index 5f2700f38..aae8f0ab3 100644 --- a/public/components/explorer/home.tsx +++ b/public/components/explorer/home.tsx @@ -55,7 +55,7 @@ export const Home = (props: any) => { const actionItems = [ { text: 'Run', - iconType: '', + iconType: 'play', attributes: { fill: true }, From 99fd853b13d47722408ec886c9f4c61fe774f554 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Mon, 27 Sep 2021 16:31:04 -0700 Subject: [PATCH 09/20] hided surrounding docs Signed-off-by: Eric Wei --- public/components/explorer/docTable/docViewRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/components/explorer/docTable/docViewRow.tsx b/public/components/explorer/docTable/docViewRow.tsx index bd24e6719..7224cf98b 100644 --- a/public/components/explorer/docTable/docViewRow.tsx +++ b/public/components/explorer/docTable/docViewRow.tsx @@ -188,7 +188,7 @@ export const DocViewRow = (props: IDocViewRowProps) => { key={ uniqueId('grid-td-detail-') } colSpan={ selectedCols.length ? selectedCols.length + 2 : 3 } > - + {/* */} From 5c2eb10ca0a2a047e7009d2b1b4edfa3ae047f53 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Mon, 27 Sep 2021 16:40:49 -0700 Subject: [PATCH 10/20] disabled insights Signed-off-by: Eric Wei --- public/components/explorer/sidebar/field.tsx | 4 ++-- public/components/explorer/sidebar/sidebar.tsx | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/public/components/explorer/sidebar/field.tsx b/public/components/explorer/sidebar/field.tsx index 6eefe466e..01a3380d5 100644 --- a/public/components/explorer/sidebar/field.tsx +++ b/public/components/explorer/sidebar/field.tsx @@ -92,7 +92,7 @@ export const Field = (props: IFieldProps) => { ownFocus display="block" isOpen={ isFieldDetailsOpen } - closePopover={ () => setIsFieldDetailsOpen(false) } + closePopover={ () => {} } anchorPosition="rightUp" panelClassName="dscSidebarItem__fieldPopoverPanel" button={ @@ -113,7 +113,7 @@ export const Field = (props: IFieldProps) => { } fieldAction={ getFieldActionDOM() } - onClick={() => togglePopover()} + onClick={() => {}} /> } > diff --git a/public/components/explorer/sidebar/sidebar.tsx b/public/components/explorer/sidebar/sidebar.tsx index eaaca2fcb..e1d668c5e 100644 --- a/public/components/explorer/sidebar/sidebar.tsx +++ b/public/components/explorer/sidebar/sidebar.tsx @@ -41,8 +41,6 @@ export const Sidebar = (props: ISidebarProps) => { const [showFields, setShowFields] = useState(false); const [searchTerm, setSearchTerm] = useState(''); - console.log('sidebar explorerFields: ', explorerFields); - return (
Date: Mon, 27 Sep 2021 17:27:02 -0700 Subject: [PATCH 11/20] disable reset button and remove s/ms options Signed-off-by: Eric Wei --- common/constants/explorer.ts | 8 -------- public/components/explorer/explorer.tsx | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/common/constants/explorer.ts b/common/constants/explorer.ts index 64e37260d..3421211bb 100644 --- a/common/constants/explorer.ts +++ b/common/constants/explorer.ts @@ -26,14 +26,6 @@ export const TIME_INTERVAL_OPTIONS = [ display: 'Auto', val: 'h' // same as value of Hour for now }, - { - display: 'Millisecond', - val: 'ms' - }, - { - display: 'Second', - val: 's' - }, { display: 'Minute', val: 'm' diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index cc64f6057..a632e4b7e 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -243,7 +243,7 @@ export const Explorer = ({ > {} } /> From 28096096c22bdce5339994058f7b7d624bff6936 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Thu, 30 Sep 2021 12:02:26 -0700 Subject: [PATCH 12/20] breadcrumb/route changes Signed-off-by: Eric Wei --- public/components/app.tsx | 31 +++----- public/components/common/side_nav.tsx | 2 +- .../components/explorer/event_analytics.tsx | 70 +++++++++++++++++++ public/components/explorer/home.tsx | 6 +- 4 files changed, 85 insertions(+), 24 deletions(-) create mode 100644 public/components/explorer/event_analytics.tsx diff --git a/public/components/app.tsx b/public/components/app.tsx index a18d02bdb..f6611733f 100644 --- a/public/components/app.tsx +++ b/public/components/app.tsx @@ -20,8 +20,7 @@ import { AppPluginStartDependencies } from '../types'; import { Home as ApplicationAnalyticsHome } from './application_analytics/home'; import { renderPageWithSidebar } from './common/side_nav'; import { Home as CustomPanelsHome } from './custom_panels/home'; -import { Home as EventExplorerHome } from './explorer/home'; -import { LogExplorer } from './explorer/log_explorer'; +import { EventAnalytics } from './explorer/event_analytics'; import { Main as NotebooksHome } from './notebooks/components/main'; import { Home as TraceAnalyticsHome } from './trace_analytics/home'; @@ -97,17 +96,17 @@ export const App = ({ )} /> { - chrome.setBreadcrumbs([ - parentBreadcrumb, - { - text: 'Event analytics', - href: '/explorer/events', - }, - ]); - return renderPageWithSidebar(); + return ( + + ); }} /> - } - /> diff --git a/public/components/common/side_nav.tsx b/public/components/common/side_nav.tsx index 8299bf193..5e3a728fc 100644 --- a/public/components/common/side_nav.tsx +++ b/public/components/common/side_nav.tsx @@ -63,7 +63,7 @@ export const renderPageWithSidebar = (BodyComponent: React.ReactNode) => { { name: 'Event analytics', id: 3, - href: '#/explorer/home', + href: '#/event_analytics', }, { name: 'Operational panels', diff --git a/public/components/explorer/event_analytics.tsx b/public/components/explorer/event_analytics.tsx new file mode 100644 index 000000000..902ed4646 --- /dev/null +++ b/public/components/explorer/event_analytics.tsx @@ -0,0 +1,70 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React from 'react'; +import { HashRouter, Route, Switch } from 'react-router-dom'; +import { LogExplorer } from './log_explorer'; +import { Home as EventExplorerHome } from './home'; +import { renderPageWithSidebar } from '../common/side_nav'; + +export const EventAnalytics = ({ + chrome, + parentBreadcrumb, + pplService, + dslService, + ...props +}: any) => { + + const eventAnalyticsBreadcrumb = { + text: 'Event analytics', + href: '#/event_analytics', + }; + + return ( + + + { + chrome.setBreadcrumbs([ + parentBreadcrumb, + eventAnalyticsBreadcrumb, + { + text: 'Explorer', + href: '#/event_analytics/explorer', + }, + ]); + return ( + + ); + }} + /> + { + chrome.setBreadcrumbs([ + parentBreadcrumb, + eventAnalyticsBreadcrumb, + { + text: 'Home', + href: '#/event_analytics', + } + ]); + return renderPageWithSidebar(); + }} + /> + + + ); +} \ No newline at end of file diff --git a/public/components/explorer/home.tsx b/public/components/explorer/home.tsx index aae8f0ab3..ca8d642a3 100644 --- a/public/components/explorer/home.tsx +++ b/public/components/explorer/home.tsx @@ -93,7 +93,7 @@ export const Home = (props: any) => { })); } } handleQuerySearch={ () => { - history.push('/explorer/events'); + history.push('/event_analytics/explorer'); } } pplService={ pplService } dslService={ dslService } @@ -133,7 +133,7 @@ export const Home = (props: any) => { [RAW_QUERY]: item.target.outerText } })); - history.push('/explorer/events'); + history.push('/event_analytics/explorer'); }} label={ h.query } color="primary" @@ -168,7 +168,7 @@ export const Home = (props: any) => { [RAW_QUERY]: item.target.outerText } })); - history.push('/explorer/events'); + history.push('/event_analytics/explorer'); }} label={ h.query } color="primary" From 460411a4f3b5117c77d46ef84e309b43ca2247c1 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Sat, 2 Oct 2021 13:16:02 -0700 Subject: [PATCH 13/20] add date range to query & added runtime fields Signed-off-by: Eric Wei --- common/constants/explorer.ts | 6 +- common/constants/shared.ts | 2 + common/types/explorer.ts | 6 +- common/utils/index.ts | 2 +- common/utils/query_utils.ts | 35 +++++++- .../search/{Filter.tsx => date_picker.tsx} | 14 +-- public/components/common/search/search.tsx | 9 +- public/components/explorer/data_grid.tsx | 82 ++++++++++++++---- .../explorer/docTable/docViewRow.tsx | 16 ++-- public/components/explorer/explorer.tsx | 86 ++++++++++++++----- .../explorer/hooks/use_fetch_events.ts | 23 +++-- .../hooks/use_fetch_visualizations.ts | 41 +++++++-- .../explorer/reducers/fetch_reducers.ts | 5 +- .../components/explorer/sidebar/sidebar.tsx | 46 ++++++++-- .../components/explorer/slices/field_slice.ts | 7 +- .../components/explorer/slices/query_slice.ts | 23 ++++- server/adaptors/ppl_datasource.ts | 36 ++++++++ server/common/types/index.ts | 1 + 18 files changed, 351 insertions(+), 89 deletions(-) rename public/components/common/search/{Filter.tsx => date_picker.tsx} (83%) diff --git a/common/constants/explorer.ts b/common/constants/explorer.ts index 3421211bb..f3df84ca4 100644 --- a/common/constants/explorer.ts +++ b/common/constants/explorer.ts @@ -10,10 +10,13 @@ */ export const RAW_QUERY = 'rawQuery'; -export const INDEX = 'index'; +export const FINAL_QUERY = 'finalQuery'; +export const SELECTED_DATE_RANGE = 'selectedDateRange'; +export const INDEX = 'indexPattern'; export const SELECTED_FIELDS = 'selectedFields'; export const UNSELECTED_FIELDS = 'unselectedFields'; export const AVAILABLE_FIELDS = 'availableFields'; +export const QUERIED_FIELDS = 'queriedFields'; export const TAB_ID_TXT_PFX = 'query-panel-'; export const TAB_TITLE = 'New query'; export const TAB_CHART_TITLE = 'Visualizations'; @@ -21,6 +24,7 @@ export const TAB_EVENT_TITLE = 'Events'; export const TAB_EVENT_ID_TXT_PFX = 'main-content-events-'; export const TAB_CHART_ID_TXT_PFX = 'main-content-charts-'; +export const DATE_PICKER_FORMAT = 'YYYY-MM-DD HH:mm:ss'; export const TIME_INTERVAL_OPTIONS = [ { display: 'Auto', diff --git a/common/constants/shared.ts b/common/constants/shared.ts index e39904a6d..b92d39c3f 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -29,6 +29,8 @@ export const observabilityPluginOrder = 6000; // Shared Constants export const UI_DATE_FORMAT = 'MM/DD/YYYY hh:mm A'; export const PPL_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'; +export const PPL_STATS_REGEX = /\|\s*stats/i; +export const PPL_INDEX_INSERT_POINT_REGEX = /search (source|index)\s*=\s*([^\s]+)(.*)/i; export const PPL_INDEX_REGEX = /(search source|source|index)\s*=\s*([^|\s]+)/i; // Observability plugin URI diff --git a/common/types/explorer.ts b/common/types/explorer.ts index 40a76dbb0..fc6df89c6 100644 --- a/common/types/explorer.ts +++ b/common/types/explorer.ts @@ -12,7 +12,9 @@ import { RAW_QUERY, SELECTED_FIELDS, - UNSELECTED_FIELDS + UNSELECTED_FIELDS, + AVAILABLE_FIELDS, + QUERIED_FIELDS } from '../constants/explorer'; export interface IQueryTab { @@ -45,6 +47,8 @@ export interface IExplorerTabFields { export interface IExplorerFields { [SELECTED_FIELDS]: Array; [UNSELECTED_FIELDS]: Array; + [AVAILABLE_FIELDS]: Array; + [QUERIED_FIELDS]: Array; } export interface ILogExplorerProps { diff --git a/common/utils/index.ts b/common/utils/index.ts index a0d10243d..01b1afb4f 100644 --- a/common/utils/index.ts +++ b/common/utils/index.ts @@ -9,4 +9,4 @@ * GitHub history for details. */ -export { getIndexPatternFromRawQuery } from './query_utils'; \ No newline at end of file +export { getIndexPatternFromRawQuery, insertFieldsToQuery, insertDateRangeToQuery } from './query_utils'; \ No newline at end of file diff --git a/common/utils/query_utils.ts b/common/utils/query_utils.ts index 8eefe7c85..5a3e72067 100644 --- a/common/utils/query_utils.ts +++ b/common/utils/query_utils.ts @@ -9,7 +9,13 @@ * GitHub history for details. */ -import { PPL_INDEX_REGEX } from "../constants/shared"; +import { isEmpty } from 'lodash'; +import datemath from '@elastic/datemath'; +import { DATE_PICKER_FORMAT } from '../../common/constants/explorer'; +import { + PPL_INDEX_REGEX, + PPL_INDEX_INSERT_POINT_REGEX +} from '../../common/constants/shared'; export const getIndexPatternFromRawQuery = (query: string) : string => { const matches = query.match(PPL_INDEX_REGEX); @@ -17,4 +23,31 @@ export const getIndexPatternFromRawQuery = (query: string) : string => { return matches[2]; } return ''; +}; + +export const insertDateRangeToQuery = ({ + rawQuery, + startTime, + endTime, + timeField = 'utc_time', +}: { + rawQuery: string; + startTime: string; + endTime: string; + timeField?: string; +}) => { + + let finalQuery = ''; + + if (isEmpty(rawQuery)) return finalQuery; + + // convert to moment + const start = datemath.parse(startTime)?.format(DATE_PICKER_FORMAT); + const end = datemath.parse(endTime)?.format(DATE_PICKER_FORMAT); + const tokens = rawQuery.match(PPL_INDEX_INSERT_POINT_REGEX); + + if (isEmpty(tokens)) return finalQuery; + finalQuery = `search ${tokens![1]}=${tokens![2]} | where ${timeField} >= timestamp('${start}') and ${timeField} <= timestamp('${end}')${tokens![3]}`; + + return finalQuery; }; \ No newline at end of file diff --git a/public/components/common/search/Filter.tsx b/public/components/common/search/date_picker.tsx similarity index 83% rename from public/components/common/search/Filter.tsx rename to public/components/common/search/date_picker.tsx index d6dae8aae..79700b23d 100644 --- a/public/components/common/search/Filter.tsx +++ b/public/components/common/search/date_picker.tsx @@ -12,21 +12,20 @@ import React from 'react'; import { EuiFlexItem, - EuiSwitch, - EuiSuperDatePicker, + EuiSuperDatePicker } from '@elastic/eui'; import { - IFilterProps + IDatePickerProps } from './search'; -export function Filter(props: IFilterProps) { +export function DatePicker(props: IDatePickerProps) { const { startTime, endTime, setStartTime, setEndTime, - setIsOutputStale + handleTimePickerChange } = props; function handleTimeChange({ @@ -69,8 +68,9 @@ export function Filter(props: IFilterProps) { showUpdateButton={ false } dateFormat="MM/DD/YYYY hh:mm:ss A" onTimeChange={(e) => { - setStartTime(e.start); - setEndTime(e.end); + const start = e.start; + const end = e.start === e.end ? 'now' : e.end; + handleTimePickerChange([start, end]); }} onRefresh={ handleRefresh } className="osdQueryBar__datePicker" diff --git a/public/components/common/search/search.tsx b/public/components/common/search/search.tsx index 336a89a51..9ab46e3db 100644 --- a/public/components/common/search/search.tsx +++ b/public/components/common/search/search.tsx @@ -15,7 +15,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiButton, EuiFlexItem } from '@elastic/eui'; import _ from 'lodash'; -import { Filter } from './Filter'; +import { DatePicker } from './date_picker'; import '@algolia/autocomplete-theme-classic'; import { Autocomplete } from './autocomplete'; @@ -26,13 +26,14 @@ export interface IQueryBarProps { dslService: any; } -export interface IFilterProps { +export interface IDatePickerProps { startTime: string; endTime: string; setStartTime: () => void; setEndTime: () => void; setTimeRange: () => void; setIsOutputStale: () => void; + handleTimePickerChange: (timeRange: Array) => any; } export const Search = (props: any) => { @@ -40,6 +41,7 @@ export const Search = (props: any) => { query, handleQueryChange, handleQuerySearch, + handleTimePickerChange, dslService, startTime, endTime, @@ -71,7 +73,7 @@ export const Search = (props: any) => {
{renderAutocomplete({ query, handleQueryChange, handleQuerySearch, dslService })}
- { setIsOutputStale={setIsOutputStale} liveStreamChecked={props.liveStreamChecked} onLiveStreamChange={props.onLiveStreamChange} + handleTimePickerChange={ (timeRange: Array) => handleTimePickerChange(timeRange) } /> {actionItems.length > 0 && actionItems.map((item: any) => { diff --git a/public/components/explorer/data_grid.tsx b/public/components/explorer/data_grid.tsx index 84c7eaa00..ad29cbb01 100644 --- a/public/components/explorer/data_grid.tsx +++ b/public/components/explorer/data_grid.tsx @@ -14,29 +14,31 @@ import './data_grid.scss'; import React, { useMemo } from 'react'; import { uniqueId } from 'lodash'; import { DocViewRow } from './docTable/index'; -import { IExplorerFields } from '../../../common/types/explorer'; +import { IExplorerFields, IField } from '../../../common/types/explorer'; interface DataGridProps { rows: Array; + rowsAll: Array; explorerFields: IExplorerFields; } export function DataGrid(props: DataGridProps) { const { rows, + rowsAll, explorerFields } = props; const getTrs = ( docs: Array = [], - explorerFields: IExplorerFields + explorerFields: Array ) => { return docs.map((doc) => { return ( ); }); @@ -48,9 +50,9 @@ export function DataGrid(props: DataGridProps) { '_source' ]; - const getHeaders = (fields: IExplorerFields) => { + const getHeaders = (fields: any) => { let tableHeadContent = null; - if (!fields.selectedFields || fields.selectedFields.length === 0) { + if (!fields || fields.length === 0) { tableHeadContent = ( <> { defaultCols.map((colName: string) => { @@ -63,12 +65,12 @@ export function DataGrid(props: DataGridProps) { ); } else { - tableHeadContent = fields.selectedFields.map(selField => { + tableHeadContent = fields.map(selField => { return ( { selField.name } ); }); - tableHeadContent.unshift(Time); + // tableHeadContent.unshift(Time); tableHeadContent.unshift(); } @@ -80,21 +82,63 @@ export function DataGrid(props: DataGridProps) { }; - const headers = useMemo(() => getHeaders(explorerFields), [ explorerFields ]); - const tableRows = useMemo(() => getTrs(rows, explorerFields), [ rows, explorerFields ]); + const Queriedheaders = useMemo( + () => getHeaders(explorerFields.queriedFields), + [ explorerFields.queriedFields ] + ); + const QueriedtableRows = useMemo( + () => getTrs(rows, explorerFields.queriedFields), + [ rows, explorerFields.queriedFields ] + ); + const headers = useMemo( + () => getHeaders(explorerFields.selectedFields), + [ explorerFields.selectedFields ] + ); + const tableRows = useMemo( + () => { + const dataToRender = explorerFields?.queriedFields && explorerFields.queriedFields.length > 0 ? rowsAll : rows + return getTrs(dataToRender, explorerFields.selectedFields); + }, + [ rows, explorerFields.selectedFields ] + ); + return ( <> - - - { headers } - - - { tableRows } - -
+ { + explorerFields?.queriedFields && + explorerFields.queriedFields.length > 0 && + ( + + + { Queriedheaders } + + + { QueriedtableRows } + +
+ ) + } + { + ( + explorerFields?.queriedFields && + explorerFields?.queriedFields?.length > 0 && + explorerFields.selectedFields?.length === 0 + ) ? null : ( + + + { headers } + + + { tableRows } + +
+ ) + } ) } diff --git a/public/components/explorer/docTable/docViewRow.tsx b/public/components/explorer/docTable/docViewRow.tsx index 7224cf98b..50fe8d3ab 100644 --- a/public/components/explorer/docTable/docViewRow.tsx +++ b/public/components/explorer/docTable/docViewRow.tsx @@ -149,14 +149,14 @@ export const DocViewRow = (props: IDocViewRowProps) => { ); }); - if (has(doc, 'timestamp')) { - cols.unshift( - getTdTmpl({ - clsName: timestampClsName, - content: doc['timestamp'] - }) - ); - } + // if (has(doc, 'timestamp')) { + // cols.unshift( + // getTdTmpl({ + // clsName: timestampClsName, + // content: doc['timestamp'] + // }) + // ); + // } } // Add detail toggling column diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index a632e4b7e..33f06c9c5 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -14,7 +14,8 @@ import { batch, useDispatch, useSelector } from 'react-redux'; import { uniqueId, isEmpty, - cloneDeep + cloneDeep, + concat } from 'lodash'; import { FormattedMessage @@ -46,18 +47,25 @@ import { TAB_EVENT_ID_TXT_PFX, TAB_CHART_ID_TXT_PFX, RAW_QUERY, + SELECTED_DATE_RANGE, SELECTED_FIELDS, UNSELECTED_FIELDS, + AVAILABLE_FIELDS, INDEX, TIME_INTERVAL_OPTIONS } from '../../../common/constants/explorer'; -import { getIndexPatternFromRawQuery } from '../../../common/utils'; +import { PPL_STATS_REGEX } from '../../../common/constants/shared'; +import { + getIndexPatternFromRawQuery, + insertDateRangeToQuery +} from '../../../common/utils'; import { useFetchEvents, useFetchVisualizations } from './hooks'; import { changeQuery, + changeDateRange, selectQueries } from './slices/query_slice'; import { selectQueryResult } from './slices/query_result_slice'; @@ -111,8 +119,6 @@ export const Explorer = ({ const countDistribution = useSelector(selectCountDistribution)[tabId]; const explorerVisualizations = useSelector(selectExplorerVisualization)[tabId]; const [selectedContentTabId, setSelectedContentTab] = useState(TAB_EVENT_ID); - const [startTime, setStartTime] = useState('now-15m'); - const [endTime, setEndTime] = useState('now'); const [liveStreamChecked, setLiveStreamChecked] = useState(false); const [isSidebarClosed, setIsSidebarClosed] = useState(false); const [fixedScrollEl, setFixedScrollEl] = useState(); @@ -128,13 +134,38 @@ export const Explorer = ({ [setFixedScrollEl] ); + const composeFinalQuery = (curQuery: any) => { + if (isEmpty(curQuery![RAW_QUERY])) return ''; + return insertDateRangeToQuery({ + rawQuery: curQuery![RAW_QUERY], + startTime: curQuery!['selectedDateRange'][0], + endTime: curQuery!['selectedDateRange'][1] + }); + }; + const fetchData = () => { - const searchQuery = queryRef.current[RAW_QUERY]; - if (!searchQuery) return; - if (searchQuery.match(/\|\s*stats/i)) { - const index = getIndexPatternFromRawQuery(searchQuery); - if (!index) return; - getAvailableFields(`search source=${index}`); + const curQuery = queryRef.current; + const rawQueryStr = curQuery![RAW_QUERY]; + if (isEmpty(rawQueryStr)) return; + /** + * get index pattern -> compose final query -> + * get all available fields for an index - > + * get + */ + + const index = getIndexPatternFromRawQuery(rawQueryStr); + if (!isEmpty(index)) getAvailableFields(`search source=${index}`); + + const finalQuery = composeFinalQuery(curQuery); + + dispatch(changeQuery({ + tabId, + query: { + finalQuery, + } + })); + + if (rawQueryStr.match(PPL_STATS_REGEX)) { getVisualizations(); } else { getEvents(); @@ -146,9 +177,19 @@ export const Explorer = ({ fetchData(); }, []); - const handleAddField = (field: IField) => toggleFields(field, UNSELECTED_FIELDS, SELECTED_FIELDS); + const handleAddField = (field: IField) => toggleFields(field, AVAILABLE_FIELDS, SELECTED_FIELDS); - const handleRemoveField = (field: IField) => toggleFields(field, SELECTED_FIELDS, UNSELECTED_FIELDS); + const handleRemoveField = (field: IField) => toggleFields(field, SELECTED_FIELDS, AVAILABLE_FIELDS); + + const handleTimePickerChange = async (timeRange: Array) => { + await dispatch(changeDateRange({ + tabId: requestParams.tabId, + data: { + [SELECTED_DATE_RANGE]: timeRange + } + })); + fetchData(); + } /** * Toggle fields between selected and unselected sets @@ -281,6 +322,7 @@ export const Explorer = ({
@@ -392,10 +434,7 @@ export const Explorer = ({ const handleContentTabClick = (selectedTab: IQueryTab) => setSelectedContentTab(selectedTab.id); - const handleQuerySearch = () => { - console.log('handle query change'); - fetchData(); - } + const handleQuerySearch = () => fetchData(); const handleQueryChange = (query: string, index: string) => { dispatch(changeQuery({ @@ -406,19 +445,20 @@ export const Explorer = ({ }, })); } - + + const dateRange = isEmpty(query['selectedDateRange']) ? ['now/15m', 'now'] : + [query['selectedDateRange'][0], query['selectedDateRange'][1]]; + return (
-

testing

{ handleQueryChange(query, index) } } + handleQueryChange={ (query: string, index: string = '') => { handleQueryChange(query, index) } } handleQuerySearch={ () => { handleQuerySearch() } } dslService = { dslService } - startTime={ startTime } - endTime={ endTime } - setStartTime={ setStartTime } - setEndTime={ setEndTime } + startTime={ dateRange[0] } + endTime={ dateRange[1] } + handleTimePickerChange={ (timeRange: Array) => handleTimePickerChange(timeRange) } setIsOutputStale={ () => {} } liveStreamChecked={ liveStreamChecked } onLiveStreamChange={ handleLiveStreamChecked } diff --git a/public/components/explorer/hooks/use_fetch_events.ts b/public/components/explorer/hooks/use_fetch_events.ts index 68498b95e..44b8b884e 100644 --- a/public/components/explorer/hooks/use_fetch_events.ts +++ b/public/components/explorer/hooks/use_fetch_events.ts @@ -11,15 +11,18 @@ import { useState, useRef } from 'react'; import { batch } from 'react-redux'; +import { isEmpty } from 'lodash'; import { useDispatch, useSelector } from 'react-redux'; import { RAW_QUERY, + FINAL_QUERY, SELECTED_FIELDS, UNSELECTED_FIELDS, - AVAILABLE_FIELDS + AVAILABLE_FIELDS, + QUERIED_FIELDS } from '../../../../common/constants/explorer'; import { fetchSuccess, reset as queryResultReset } from '../slices/query_result_slice'; import { selectQueries } from '../slices/query_slice'; @@ -66,10 +69,14 @@ export const useFetchEvents = ({ }); }; - const getEvents = () => { + const getEvents = (query: string = '') => { const cur = queriesRef.current; - fetchEvents({ query: cur[requestParams.tabId][RAW_QUERY] }, 'jdbc', (res: any) => { + const searchQuery = isEmpty(query) ? cur![requestParams.tabId][FINAL_QUERY] : query; + fetchEvents({ query: searchQuery }, 'jdbc', (res: any) => { batch(() => { + dispatch(queryResultReset({ + tabId: requestParams.tabId + })); dispatch(fetchSuccess({ tabId: requestParams.tabId, data: { @@ -81,6 +88,7 @@ export const useFetchEvents = ({ data: { [SELECTED_FIELDS]: [], [UNSELECTED_FIELDS]: res.schema ? [ ...res.schema ] : [], + [QUERIED_FIELDS]: [], [AVAILABLE_FIELDS]: res?.schema ? [...res.schema] : [] } })); @@ -95,14 +103,15 @@ export const useFetchEvents = ({ const getAvailableFields = (query: string) => { fetchEvents({ query, }, 'jdbc', (res: any) => { batch(() => { - dispatch(queryResultReset({ - tabId: requestParams.tabId + dispatch(fetchSuccess({ + tabId: requestParams.tabId, + data: { + jsonDataAll: res['jsonData'] + } })); dispatch(updateFields({ tabId: requestParams.tabId, data: { - [SELECTED_FIELDS]: [], - [UNSELECTED_FIELDS]: res?.schema ? [...res.schema] : [], [AVAILABLE_FIELDS]: res?.schema ? [...res.schema] : [] } })); diff --git a/public/components/explorer/hooks/use_fetch_visualizations.ts b/public/components/explorer/hooks/use_fetch_visualizations.ts index d2e622af7..c21e5aec4 100644 --- a/public/components/explorer/hooks/use_fetch_visualizations.ts +++ b/public/components/explorer/hooks/use_fetch_visualizations.ts @@ -11,16 +11,25 @@ import { useState, useRef } from 'react'; import { + batch, useDispatch, useSelector } from 'react-redux'; import { - RAW_QUERY + FINAL_QUERY, + QUERIED_FIELDS, + RAW_QUERY, + SELECTED_FIELDS } from '../../../../common/constants/explorer'; import { render as renderCountDis } from '../slices/count_distribution_slice'; import { selectQueries } from '../slices/query_slice'; import { render as renderExplorerVis } from '../slices/visualization_slice'; +import { + updateFields, + sortFields +} from '../slices/field_slice'; import PPLService from '../../../services/requests/ppl'; +import { fetchSuccess } from '../slices/query_result_slice'; interface IFetchVisualizationsParams { pplService: PPLService; @@ -62,7 +71,7 @@ export const useFetchVisualizations = ({ const getCountVisualizations = (interval: string) => { const cur = queriesRef.current; - const rawQuery = cur[requestParams.tabId][RAW_QUERY]; + const rawQuery = cur![requestParams.tabId][FINAL_QUERY]; fetchVisualizations({ query: `${rawQuery} | stats count() by span(timestamp, '1${interval = interval ? interval: 'm' }')` }, 'viz', @@ -76,15 +85,33 @@ export const useFetchVisualizations = ({ const getVisualizations = () => { const cur = queriesRef.current; - const rawQuery = cur[requestParams.tabId][RAW_QUERY]; + const rawQuery = cur![requestParams.tabId][FINAL_QUERY]; fetchVisualizations({ query: rawQuery }, 'viz', (res: any) => { - dispatch(renderExplorerVis({ - tabId: requestParams.tabId, - data: res - })); + batch(() => { + dispatch(renderExplorerVis({ + tabId: requestParams.tabId, + data: res + })); + dispatch(fetchSuccess({ + tabId: requestParams.tabId, + data: { + jsonData: res.jsonData + } + })); + dispatch(updateFields({ + tabId: requestParams.tabId, + data: { + [QUERIED_FIELDS]: res?.metadata.fields + } + })); + dispatch(sortFields({ + tabId: requestParams.tabId, + data: [QUERIED_FIELDS] + })); + }); }); } diff --git a/public/components/explorer/reducers/fetch_reducers.ts b/public/components/explorer/reducers/fetch_reducers.ts index 3b50693ef..3cbf49b17 100644 --- a/public/components/explorer/reducers/fetch_reducers.ts +++ b/public/components/explorer/reducers/fetch_reducers.ts @@ -10,5 +10,8 @@ */ export const fetchSuccess = (state, { payload }) => { - state[payload.tabId] = payload.data; + state[payload.tabId] = { + ...state[payload.tabId], + ...payload.data + }; }; \ No newline at end of file diff --git a/public/components/explorer/sidebar/sidebar.tsx b/public/components/explorer/sidebar/sidebar.tsx index e1d668c5e..6dacc7bf5 100644 --- a/public/components/explorer/sidebar/sidebar.tsx +++ b/public/components/explorer/sidebar/sidebar.tsx @@ -60,13 +60,49 @@ export const Sidebar = (props: ISidebarProps) => {
- { explorerFields && !isEmpty(explorerFields) && ( + { !isEmpty(explorerFields) && ( <> + { + explorerFields?.queriedFields && explorerFields.queriedFields?.length > 0 && ( + <> + +

+ +

+
+ +
    + { explorerFields.queriedFields && explorerFields.queriedFields.map(field => { + return ( +
  • + +
  • + )}) + } +
+ + ) + }

@@ -97,7 +133,7 @@ export const Sidebar = (props: ISidebarProps) => {

@@ -132,8 +168,8 @@ export const Sidebar = (props: ISidebarProps) => { data-test-subj={`fieldList-unpopular`} > { - explorerFields.unselectedFields && - explorerFields.unselectedFields.filter( + explorerFields.availableFields && + explorerFields.availableFields.filter( (field) => searchTerm === '' || field.name.indexOf(searchTerm) !== -1) .map((field) => { return ( diff --git a/public/components/explorer/slices/field_slice.ts b/public/components/explorer/slices/field_slice.ts index 1320c4622..e58e90042 100644 --- a/public/components/explorer/slices/field_slice.ts +++ b/public/components/explorer/slices/field_slice.ts @@ -17,13 +17,17 @@ import { initialTabId } from '../../../framework/redux/store/shared_state'; import { SELECTED_FIELDS, UNSELECTED_FIELDS, + AVAILABLE_FIELDS, + QUERIED_FIELDS, REDUX_EXPL_SLICE_FIELDS } from '../../../../common/constants/explorer'; import { IField } from '../../../../common/types/explorer'; const initialFields = { [SELECTED_FIELDS]: [], - [UNSELECTED_FIELDS]: [] + [UNSELECTED_FIELDS]: [], + [AVAILABLE_FIELDS]: [], + [QUERIED_FIELDS]: [], }; const initialState = { @@ -43,6 +47,7 @@ export const fieldSlice = createSlice({ }, updateFields: (state, { payload }) => { state[payload.tabId] = { + ...state[payload.tabId], ...payload.data }; }, diff --git a/public/components/explorer/slices/query_slice.ts b/public/components/explorer/slices/query_slice.ts index 838210e20..e09b3e8d9 100644 --- a/public/components/explorer/slices/query_slice.ts +++ b/public/components/explorer/slices/query_slice.ts @@ -15,14 +15,22 @@ import { import { initialTabId } from '../../../framework/redux/store/shared_state'; import { RAW_QUERY, + FINAL_QUERY, + SELECTED_DATE_RANGE, REDUX_EXPL_SLICE_QUERIES, INDEX } from '../../../../common/constants/explorer'; +const initialQueryState = { + [RAW_QUERY]: '', + [FINAL_QUERY]: '', + [INDEX]: '', + [SELECTED_DATE_RANGE]: ['now-15m', 'now'] +}; + const initialState = { [initialTabId]: { - [RAW_QUERY]: '', - [INDEX]: '' + ...initialQueryState } }; @@ -32,13 +40,19 @@ export const queriesSlice = createSlice({ reducers: { changeQuery: (state, { payload }) => { state[payload.tabId] = { + ...state[payload.tabId], ...payload.query } }, + changeDateRange: (state, { payload }) => { + state[payload.tabId] = { + ...state[payload.tabId], + ...payload.data + } + }, init: (state, { payload }) => { state[payload.tabId] = { - [RAW_QUERY]: '', - [INDEX]: '' + ...initialQueryState }; }, remove: (state, { payload }) => { @@ -50,6 +64,7 @@ export const queriesSlice = createSlice({ export const { changeQuery, + changeDateRange, remove, init } = queriesSlice.actions; diff --git a/server/adaptors/ppl_datasource.ts b/server/adaptors/ppl_datasource.ts index b5103c20a..1d846e6c6 100644 --- a/server/adaptors/ppl_datasource.ts +++ b/server/adaptors/ppl_datasource.ts @@ -25,6 +25,42 @@ export class PPLDataSource { ) { if (this.dataType === 'jdbc') { this.addSchemaRowMapping(); + } else if (this.dataType === 'viz') { + this.addStatsMapping(); + } + } + + private addStatsMapping = () => { + const visData = this.pplDataSource; + + /** + * Add vis mapping for runtime fields + * [{ + * agent: "mozilla", + * avg(bytes): 5756 + * ... + * }, { + * agent: "MSIE", + * avg(bytes): 5605 + * ... + * }, { + * agent: "chrome", + * avg(bytes): 5648 + * ... + * }] + */ + let res = []; + if (visData?.metadata?.fields) { + const queriedFields = visData.metadata.fields; + for (let i = 0; i < visData.size; i++) { + const entry: any = {}; + queriedFields.map((field: any) => { + const statsDataSet = visData?.data; + entry[field.name] = statsDataSet[field.name][i]; + }); + res.push(entry); + } + visData['jsonData'] = res; } } diff --git a/server/common/types/index.ts b/server/common/types/index.ts index 42cc4c25f..43380bac3 100644 --- a/server/common/types/index.ts +++ b/server/common/types/index.ts @@ -17,6 +17,7 @@ export interface ISchema { export interface IPPLVisualizationDataSource { data: any; metadata: any; + jsonData?: Array; size: Number; status: Number; } From 56555702ac73ffd68d120ab28c5062b1d0d395b8 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Sun, 3 Oct 2021 21:49:49 -0700 Subject: [PATCH 14/20] disabled toggle button for queried fields Signed-off-by: Eric Wei --- public/components/explorer/sidebar/field.tsx | 36 +++++++++++-------- .../components/explorer/sidebar/sidebar.tsx | 1 + 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/public/components/explorer/sidebar/field.tsx b/public/components/explorer/sidebar/field.tsx index 01a3380d5..7ca05d888 100644 --- a/public/components/explorer/sidebar/field.tsx +++ b/public/components/explorer/sidebar/field.tsx @@ -23,6 +23,7 @@ import { IField } from '../../../../common/types/explorer'; interface IFieldProps { field: IField; selected: boolean; + showToggleButton: boolean; onToggleField: (field: IField) => void; } @@ -31,6 +32,7 @@ export const Field = (props: IFieldProps) => { const { field, selected, + showToggleButton = true, onToggleField } = props; @@ -68,21 +70,25 @@ export const Field = (props: IFieldProps) => { ) } > - ) => { - if (e.type === 'click') { - e.currentTarget.focus(); - } - e.preventDefault(); - e.stopPropagation(); - toggleField(field); - }} - data-test-subj={`fieldToggle-${field.name}`} - aria-label={ selected ? removeLabelAria : addLabelAria } - /> + { + showToggleButton ? ( + ) => { + if (e.type === 'click') { + e.currentTarget.focus(); + } + e.preventDefault(); + e.stopPropagation(); + toggleField(field); + }} + data-test-subj={`fieldToggle-${field.name}`} + aria-label={ selected ? removeLabelAria : addLabelAria } + /> + ) : <> + } ); }; diff --git a/public/components/explorer/sidebar/sidebar.tsx b/public/components/explorer/sidebar/sidebar.tsx index 6dacc7bf5..120228b0a 100644 --- a/public/components/explorer/sidebar/sidebar.tsx +++ b/public/components/explorer/sidebar/sidebar.tsx @@ -89,6 +89,7 @@ export const Sidebar = (props: ISidebarProps) => { From 482129aa041259a85122e92393cca150f17097a2 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Mon, 4 Oct 2021 08:48:22 -0700 Subject: [PATCH 15/20] modified text Signed-off-by: Eric Wei --- public/components/explorer/no_results.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/public/components/explorer/no_results.tsx b/public/components/explorer/no_results.tsx index e9d460b4c..490f70ed1 100644 --- a/public/components/explorer/no_results.tsx +++ b/public/components/explorer/no_results.tsx @@ -44,16 +44,14 @@ export const NoResults = () => {

-

From 5088c9e34f4a37c122507b97b6ab8192b44b60cc Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Mon, 4 Oct 2021 10:53:41 -0700 Subject: [PATCH 16/20] added couple of configs to autocomplete --- .../components/common/search/autocomplete.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/public/components/common/search/autocomplete.tsx b/public/components/common/search/autocomplete.tsx index 55f68f19f..9075107ee 100644 --- a/public/components/common/search/autocomplete.tsx +++ b/public/components/common/search/autocomplete.tsx @@ -10,13 +10,20 @@ */ import './search.scss'; -import React, { useEffect } from 'react'; +import React, { + createElement, + Fragment, + useEffect, + useRef +} from 'react'; +import { render } from 'react-dom'; import { autocomplete } from '@algolia/autocomplete-js'; import { IQueryBarProps } from './search'; import { RAW_QUERY } from '../../../../common/constants/explorer'; import { createPPLSuggestionsPlugin } from './autocomplete_plugin'; export function Autocomplete(props: IQueryBarProps) { + const containerRef = useRef(null); const { query, handleQueryChange, handleQuerySearch, dslService } = props; const PPLSuggestionPlugin = createPPLSuggestionsPlugin({ @@ -27,9 +34,18 @@ export function Autocomplete(props: IQueryBarProps) { }); useEffect(() => { + + if (!containerRef.current) { + return undefined; + } + const search = autocomplete({ - container: '#autocomplete', - initialState: { query: props.query[RAW_QUERY] }, + container: containerRef.current, + renderer: { createElement, Fragment }, + render({ children }, root) { + render(children, root); + }, + initialState: { query: query[RAW_QUERY] }, openOnFocus: true, placeholder: 'Enter PPL query to retrieve log, traces, and metrics', plugins: [PPLSuggestionPlugin], @@ -41,5 +57,5 @@ export function Autocomplete(props: IQueryBarProps) { }; }, []); - return
; + return
; } From c43aacd7430b5c3e5d8f0b855ae5494a52daab0c Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Wed, 13 Oct 2021 15:33:30 -0700 Subject: [PATCH 17/20] added saved objects and few bug fixes Signed-off-by: Eric Wei --- common/constants/explorer.ts | 2 +- common/constants/shared.ts | 10 +- common/utils/index.ts | 2 +- public/components/app.tsx | 6 +- .../common/search/autocomplete_plugin.ts | 2 +- public/components/common/search/search.tsx | 134 ++++++++-- .../explorer/docTable/docViewRow.tsx | 4 + .../components/explorer/event_analytics.tsx | 6 +- public/components/explorer/explorer.tsx | 140 ++++++---- public/components/explorer/home.tsx | 122 +++------ .../explorer/hooks/use_fetch_events.ts | 6 +- .../hooks/use_fetch_visualizations.ts | 3 +- public/components/explorer/log_explorer.tsx | 6 +- .../save_panel/index.ts | 0 .../explorer/save_panel/savePanel.tsx | 105 ++++++++ .../components/explorer/sidebar/sidebar.tsx | 11 +- .../explorer/slices/visualization_slice.ts | 10 +- .../explorer/visualizations/index.tsx | 12 +- .../save_panel/savePanel.tsx | 48 ---- .../workspace_panel/workspace_panel.tsx | 26 +- .../workspace_panel_wrapper.tsx | 55 +--- public/components/index.tsx | 4 +- public/plugin.ts | 5 +- .../event_analytics/saved_objects.ts | 246 ++++++++++++++++++ server/adaptors/ppl_datasource.ts | 3 +- server/adaptors/saved_objects_plugin.ts | 148 +++++++++++ server/plugin.ts | 7 +- .../event_analytics/event_analytics_router.ts | 212 +++++++++++++++ server/routes/index.ts | 10 +- server/routes/ppl.ts | 18 +- server/services/facets/ppl_facet.ts | 6 +- server/services/facets/saved_objects.ts | 154 +++++++++++ 32 files changed, 1236 insertions(+), 287 deletions(-) rename public/components/explorer/{visualizations/shared_components => }/save_panel/index.ts (100%) create mode 100644 public/components/explorer/save_panel/savePanel.tsx delete mode 100644 public/components/explorer/visualizations/shared_components/save_panel/savePanel.tsx create mode 100644 public/services/saved_objects/event_analytics/saved_objects.ts create mode 100644 server/adaptors/saved_objects_plugin.ts create mode 100644 server/routes/event_analytics/event_analytics_router.ts create mode 100644 server/services/facets/saved_objects.ts diff --git a/common/constants/explorer.ts b/common/constants/explorer.ts index f3df84ca4..3f4999327 100644 --- a/common/constants/explorer.ts +++ b/common/constants/explorer.ts @@ -22,7 +22,7 @@ export const TAB_TITLE = 'New query'; export const TAB_CHART_TITLE = 'Visualizations'; export const TAB_EVENT_TITLE = 'Events'; export const TAB_EVENT_ID_TXT_PFX = 'main-content-events-'; -export const TAB_CHART_ID_TXT_PFX = 'main-content-charts-'; +export const TAB_CHART_ID_TXT_PFX = 'main-content-vis-'; export const DATE_PICKER_FORMAT = 'YYYY-MM-DD HH:mm:ss'; export const TIME_INTERVAL_OPTIONS = [ diff --git a/common/constants/shared.ts b/common/constants/shared.ts index 45c490c3b..298ebb2a2 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -16,6 +16,11 @@ export const DSL_BASE = '/api/dsl'; export const DSL_SEARCH = '/search'; export const DSL_CAT = '/cat.indices'; export const DSL_MAPPING = '/indices.getFieldMapping'; +export const OBSERVABILITY_BASE = '/api/observability'; +export const EVENT_ANALYTICS = '/event_analytics'; +export const SAVED_OBJECTS = '/saved_objects'; +export const SAVED_QUERY = '/query'; +export const SAVED_VISUALIZATION = '/vis'; // Server route export const PPL_ENDPOINT = '/_plugins/_ppl'; @@ -38,4 +43,7 @@ export const PPL_CONTAINS_TIMESTAMP_REGEX = /\|\s*.*\s*[<|<=|=|>=|>]\s*timestamp const BASE_OBSERVABILITY_URI = '/_plugins/_observability'; export const OPENSEARCH_PANELS_API = { OBJECT: `${BASE_OBSERVABILITY_URI}/object`, -}; \ No newline at end of file +}; + +// Saved Objects +export const SAVED_OBJECT = '/object'; diff --git a/common/utils/index.ts b/common/utils/index.ts index 01b1afb4f..62211e2c6 100644 --- a/common/utils/index.ts +++ b/common/utils/index.ts @@ -9,4 +9,4 @@ * GitHub history for details. */ -export { getIndexPatternFromRawQuery, insertFieldsToQuery, insertDateRangeToQuery } from './query_utils'; \ No newline at end of file +export { getIndexPatternFromRawQuery, insertDateRangeToQuery } from './query_utils'; diff --git a/public/components/app.tsx b/public/components/app.tsx index f6611733f..4b5cdf2fe 100644 --- a/public/components/app.tsx +++ b/public/components/app.tsx @@ -29,13 +29,15 @@ interface ObservabilityAppDeps { DepsStart: AppPluginStartDependencies; pplService: any; dslService: any; + savedObjects: any; } export const App = ({ CoreStart, DepsStart, pplService, - dslService + dslService, + savedObjects }: ObservabilityAppDeps) => { const { chrome, http, notifications } = CoreStart; @@ -104,6 +106,8 @@ export const App = ({ parentBreadcrumb={ parentBreadcrumb } pplService={ pplService } dslService={ dslService } + savedObjects={ savedObjects } + http={ http } { ...props } /> ); diff --git a/public/components/common/search/autocomplete_plugin.ts b/public/components/common/search/autocomplete_plugin.ts index 657c9204e..319e509e7 100644 --- a/public/components/common/search/autocomplete_plugin.ts +++ b/public/components/common/search/autocomplete_plugin.ts @@ -174,7 +174,7 @@ const getSuggestions = async (str: string, dslService: DSLService) => { item: '=', }); currField = splittedModel[splittedModel.length - 2]; - currFieldType = fieldsFromBackend.find((field) => field.label === currField).type; + currFieldType = fieldsFromBackend.find((field) => field.label === currField)?.type; return fullSuggestions.filter(({ label }) => label.startsWith(prefix) && prefix !== label); } else if (nextWhere === splittedModel.length - 2) { return fillSuggestions( diff --git a/public/components/common/search/search.tsx b/public/components/common/search/search.tsx index 9ab46e3db..b033df127 100644 --- a/public/components/common/search/search.tsx +++ b/public/components/common/search/search.tsx @@ -11,13 +11,35 @@ import './search.scss'; -import React from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; -import { EuiFlexGroup, EuiButton, EuiFlexItem } from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import { + EuiFlexGroup, + EuiButton, + EuiFlexItem, + EuiTitle, + EuiComboBox, + EuiFormRow, + EuiSpacer, + EuiFieldText, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPopover, + EuiButtonEmpty, + EuiPopoverFooter, + EuiPopoverTitle, + EuiTextArea +} from '@elastic/eui'; import _ from 'lodash'; import { DatePicker } from './date_picker'; import '@algolia/autocomplete-theme-classic'; import { Autocomplete } from './autocomplete'; +import { SavePanel } from '../../explorer/save_panel'; +import { PPL_STATS_REGEX } from '../../../../common/constants/shared'; +import { SaveQueryForm } from '../../../../../../src/plugins/data/public/ui/saved_query_form'; +import { useCallback } from 'react'; export interface IQueryBarProps { query: any; @@ -48,9 +70,28 @@ export const Search = (props: any) => { setStartTime, setEndTime, setIsOutputStale, - actionItems, + explorerData, + selectedPanelName, + selectedCustomPanelOptions, + setSelectedPanelName, + setSelectedCustomPanelOptions, + handleSavingObject, + isPanelTextFieldInvalid, + savedObjects, + showSavePanelOptionsList, + showSaveButton = true } = props; + const [isSavePanelOpen, setIsSavePanelOpen] = useState(false); + + const memorizedHandleQuerySearch = useCallback(() => { + handleQuerySearch(); + }, [ + query, + startTime, + endTime + ]); + function renderAutocomplete({ query, handleQueryChange, @@ -67,6 +108,21 @@ export const Search = (props: any) => { ); } + const button = ( + { + setIsSavePanelOpen((staleState) => { + return !staleState; + }) + } + } + > + { "Save" } + + ); + return (
@@ -83,19 +139,67 @@ export const Search = (props: any) => { onLiveStreamChange={props.onLiveStreamChange} handleTimePickerChange={ (timeRange: Array) => handleTimePickerChange(timeRange) } /> - {actionItems.length > 0 && - actionItems.map((item: any) => { - return ( - + { + memorizedHandleQuerySearch(); + }} + > + { isEmpty(explorerData) ? 'Run' : 'Refresh' } + + + { showSaveButton && ( + <> + + setIsSavePanelOpen(false)} > - - {item.text} - - - ); - })} + {/* {"Save to..."} */} + + + + + setIsSavePanelOpen(false)}> + { "Cancel" } + + + + handleSaveVisualization()}> + onClick={() => handleSavingObject()}> + { "Save" } + + + + + + + + )}
); diff --git a/public/components/explorer/docTable/docViewRow.tsx b/public/components/explorer/docTable/docViewRow.tsx index 50fe8d3ab..aec3a87ac 100644 --- a/public/components/explorer/docTable/docViewRow.tsx +++ b/public/components/explorer/docTable/docViewRow.tsx @@ -37,6 +37,9 @@ export const DocViewRow = (props: IDocViewRowProps) => { selectedCols } = props; + console.log('doc view row doc: ', doc); + console.log('doc view selectedCols: ', selectedCols); + const [detailsOpen, setDetailsOpen] = useState(false); const getTdTmpl = (conf: { clsName: string, content: React.ReactDOM | string }) => { @@ -140,6 +143,7 @@ export const DocViewRow = (props: IDocViewRowProps) => { filteredDoc[selCol.name] = doc[selCol.name]; } }) + console.log('filteredDoc: ', filteredDoc); forEach(filteredDoc, (val, key) => { cols.push( getTdTmpl({ diff --git a/public/components/explorer/event_analytics.tsx b/public/components/explorer/event_analytics.tsx index 902ed4646..38d4c88fe 100644 --- a/public/components/explorer/event_analytics.tsx +++ b/public/components/explorer/event_analytics.tsx @@ -20,6 +20,8 @@ export const EventAnalytics = ({ parentBreadcrumb, pplService, dslService, + savedObjects, + http, ...props }: any) => { @@ -46,6 +48,8 @@ export const EventAnalytics = ({ ); }} @@ -61,7 +65,7 @@ export const EventAnalytics = ({ href: '#/event_analytics', } ]); - return renderPageWithSidebar(); + return renderPageWithSidebar(); }} /> diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index 9dbf8f686..953497c26 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -15,6 +15,7 @@ import { uniqueId, isEmpty, cloneDeep, + isEqual, concat } from 'lodash'; import { @@ -22,11 +23,17 @@ import { } from '@osd/i18n/react'; import { EuiText, + EuiButton, EuiButtonIcon, EuiTabbedContent, EuiTabbedContentTab, EuiFlexGroup, - EuiFlexItem + EuiFlexItem, + EuiPopover, + EuiPopoverTitle, + EuiPopoverFooter, + EuiButtonEmpty, + htmlIdGenerator } from '@elastic/eui'; import classNames from 'classnames'; import { Search } from '../common/search/search'; @@ -62,7 +69,7 @@ import { } from '../../../common/utils'; import { useFetchEvents, - useFetchVisualizations + useFetchVisualizations, } from './hooks'; import { changeQuery, @@ -79,6 +86,7 @@ import { selectCountDistribution } from './slices/count_distribution_slice'; import { selectExplorerVisualization } from './slices/visualization_slice'; import PPLService from '../../services/requests/ppl'; import DSLService from '../../services/requests/dsl'; +import SavedObjects from '../../services/saved_objects/event_analytics/saved_objects'; const TAB_EVENT_ID = uniqueId(TAB_EVENT_ID_TXT_PFX); const TAB_CHART_ID = uniqueId(TAB_CHART_ID_TXT_PFX); @@ -86,13 +94,16 @@ const TAB_CHART_ID = uniqueId(TAB_CHART_ID_TXT_PFX); interface IExplorerProps { pplService: PPLService; dslService: DSLService; - tabId: string + tabId: string; + savedObjects: SavedObjects; } export const Explorer = ({ pplService, dslService, - tabId + http, + tabId, + savedObjects }: IExplorerProps) => { const dispatch = useDispatch(); @@ -120,11 +131,19 @@ export const Explorer = ({ const countDistribution = useSelector(selectCountDistribution)[tabId]; const explorerVisualizations = useSelector(selectExplorerVisualization)[tabId]; const [selectedContentTabId, setSelectedContentTab] = useState(TAB_EVENT_ID); + const [selectedCustomPanelOptions, setSelectedCustomPanelOptions] = useState([]); + const [selectedPanelName, setSelectedPanelName] = useState(''); + const [curVisId, setCurVisId] = useState('bar'); + const [isPanelTextFieldInvalid, setIsPanelTextFieldInvalid ] = useState(false); const [liveStreamChecked, setLiveStreamChecked] = useState(false); const [isSidebarClosed, setIsSidebarClosed] = useState(false); const [fixedScrollEl, setFixedScrollEl] = useState(); const queryRef = useRef(); + const selectedPanelNameRef = useRef(); + const explorerFieldsRef = useRef(); queryRef.current = query; + selectedPanelNameRef.current = selectedPanelName; + explorerFieldsRef.current = explorerFields; const fixedScrollRef = useCallback( (node: HTMLElement) => { @@ -144,22 +163,16 @@ export const Explorer = ({ }); }; - const fetchData = () => { + const fetchData = async () => { const curQuery = queryRef.current; const rawQueryStr = curQuery![RAW_QUERY]; if (isEmpty(rawQueryStr)) return; - /** - * get index pattern -> compose final query -> - * get all available fields for an index - > - * get - */ - const index = getIndexPatternFromRawQuery(rawQueryStr); if (!isEmpty(index)) getAvailableFields(`search source=${index}`); const finalQuery = composeFinalQuery(curQuery); - dispatch(changeQuery({ + await dispatch(changeQuery({ tabId, query: { finalQuery, @@ -223,8 +236,6 @@ export const Explorer = ({ }); }; - const handleLiveStreamChecked = () => setLiveStreamChecked(!liveStreamChecked); - const sidebarClassName = classNames({ closed: isSidebarClosed, }); @@ -248,6 +259,7 @@ export const Explorer = ({
handleAddField(field) } handleRemoveField={ (field: IField) => handleRemoveField(field) } /> @@ -270,7 +282,7 @@ export const Explorer = ({ />
- { (explorerData && !isEmpty(explorerData)) ? ( + { (explorerData && !isEmpty(explorerData.jsonData)) ? (
{ @@ -370,6 +382,8 @@ export const Explorer = ({ const getExplorerVis = () => { return ( { - handleQuerySearch(); - } - } - }, - { - text: 'Save', - iconType: 'heart', - handlers: { - onClick: () => { - console.log('refresh clicked'); - } - } - } - ]; - const handleContentTabClick = (selectedTab: IQueryTab) => setSelectedContentTab(selectedTab.id); const handleQuerySearch = () => fetchData(); @@ -447,12 +439,66 @@ export const Explorer = ({ })); } + const handleSavingObject = () => { + + const currQuery = queryRef.current; + const currFields = explorerFieldsRef.current; + if (isEmpty(currQuery![RAW_QUERY])) return; + + if (isEmpty(selectedPanelNameRef.current)) { + setIsPanelTextFieldInvalid(true); + return; + } else { + setIsPanelTextFieldInvalid(false); + } + + if (isEqual(selectedContentTabId, TAB_EVENT_ID)) { + + // create new saved query + savedObjects.createSavedQuery({ + query: currQuery![RAW_QUERY], + fields: currFields![SELECTED_FIELDS], + dateRange: currQuery![SELECTED_DATE_RANGE], + name: selectedPanelNameRef.current + }); + + // to-dos - update selected custom panel + if (!isEmpty(selectedCustomPanelOptions)) { + // update custom panel - query + } + + } else if (isEqual(selectedContentTabId, TAB_CHART_ID)) { + + // create new saved visualization + savedObjects.createSavedVisualization({ + query: currQuery![RAW_QUERY], + fields: currFields![SELECTED_FIELDS], + dateRange: currQuery![SELECTED_DATE_RANGE], + type: curVisId, + name: selectedPanelNameRef.current + }); + + // update custom panel - visualization + if (!isEmpty(selectedCustomPanelOptions)) { + + savedObjects.bulkUpdateCustomPanel({ + selectedCustomPanels: selectedCustomPanelOptions, + query: currQuery![RAW_QUERY], + type: curVisId, + timeField: !isEmpty(currQuery!['selectedTimestamp']) ? currQuery!['selectedTimestamp'] : 'utc_time', // temprary + name: selectedPanelNameRef.current + }); + } + } + }; + const dateRange = isEmpty(query['selectedDateRange']) ? ['now/15m', 'now'] : [query['selectedDateRange'][0], query['selectedDateRange'][1]]; return (
{ handleQueryChange(query, index) } } handleQuerySearch={ () => { handleQuerySearch() } } @@ -460,16 +506,20 @@ export const Explorer = ({ startTime={ dateRange[0] } endTime={ dateRange[1] } handleTimePickerChange={ (timeRange: Array) => handleTimePickerChange(timeRange) } - setIsOutputStale={ () => {} } - liveStreamChecked={ liveStreamChecked } - onLiveStreamChange={ handleLiveStreamChecked } - actionItems={ actionItems } + selectedPanelName={ selectedPanelNameRef.current } + selectedCustomPanelOptions={ selectedCustomPanelOptions } + setSelectedPanelName={ setSelectedPanelName } + setSelectedCustomPanelOptions={ setSelectedCustomPanelOptions } + handleSavingObject={ handleSavingObject } + isPanelTextFieldInvalid={ isPanelTextFieldInvalid } + savedObjects={ savedObjects } + showSavePanelOptionsList={ isEqual(selectedContentTabId, TAB_CHART_ID) } /> - { handleQueryChange(query, index) } } - /> + /> */} { - const {pplService, dslService} = props; +interface IHomeProps { + pplService: any; + dslService: any; + savedObjects: SavedObjects; + http: any; +} + +export const Home = (props: IHomeProps) => { + const { + pplService, + dslService, + savedObjects, + http + } = props; const history = useHistory(); const dispatch = useDispatch(); const query = useSelector(selectQueries)[initialTabId][RAW_QUERY]; + const [savedHistories, setSavedHistories] = useState([]); - const queryHistories = [ - { - query: "search source=opensearch_dashboards_sample_data_logs | where utc_time > timestamp('2021-07-01 00:00:00') and utc_time < timestamp('2021-07-02 00:00:00')", - iconType: "tokenEnum" - } - ]; - - const visHistories = [ - { - query: "search source=opensearch_dashboards_sample_data_logs | where utc_time > timestamp('2021-07-01 00:00:00') and utc_time < timestamp('2021-07-02 00:00:00') | stats count() by span(utc_time, '15m')", - iconType: "tokenHistogram" - } - ]; + const fetchHistories = async () => { + const res = await savedObjects.fetchSavedObjects({ + objectType: ['savedQuery', 'savedVisualization'], + sortOrder: 'desc', + fromIndex: 0, + maxItems: 10 + }); + setSavedHistories(res['observabilityObjectList'] || []); + }; - const actionItems = [ - { - text: 'Run', - iconType: 'play', - attributes: { - fill: true - }, - handlers: { - onClick: () => { - history.push('/explorer/events'); - } - } - } - ]; + useEffect(() => { + fetchHistories(); + }, []); return (
@@ -104,7 +103,7 @@ export const Home = (props: any) => { setIsOutputStale={ () => {} } liveStreamChecked={ false } onLiveStreamChange={ () => {} } - actionItems={ actionItems } + showSaveButton={ false } /> { wrapText={ true } > -

Query History

-
- - { - queryHistories.map((h) => { - return ( - { - dispatch(changeQuery({ - tabId: initialTabId, - query: { - [RAW_QUERY]: item.target.outerText - } - })); - history.push('/event_analytics/explorer'); - }} - label={ h.query } - color="primary" - size="s" - iconType={ h.iconType } - /> - ); - }) - } - - - - - -

Visualization History

+

{ "Histories" }

- { - visHistories.map((h) => { - return ( - { - dispatch(changeQuery({ - tabId: initialTabId, - query: { - [RAW_QUERY]: item.target.outerText - } - })); - history.push('/event_analytics/explorer'); - }} - label={ h.query } - color="primary" - size="s" - iconType={ h.iconType } - /> - ); - }) - }
diff --git a/public/components/explorer/hooks/use_fetch_events.ts b/public/components/explorer/hooks/use_fetch_events.ts index 44b8b884e..ce0865545 100644 --- a/public/components/explorer/hooks/use_fetch_events.ts +++ b/public/components/explorer/hooks/use_fetch_events.ts @@ -26,6 +26,7 @@ import { } from '../../../../common/constants/explorer'; import { fetchSuccess, reset as queryResultReset } from '../slices/query_result_slice'; import { selectQueries } from '../slices/query_slice'; +import { reset as visualizationReset } from '../slices/visualization_slice'; import { updateFields, sortFields @@ -87,7 +88,7 @@ export const useFetchEvents = ({ tabId: requestParams.tabId, data: { [SELECTED_FIELDS]: [], - [UNSELECTED_FIELDS]: res.schema ? [ ...res.schema ] : [], + [UNSELECTED_FIELDS]: res?.schema ? [ ...res.schema ] : [], [QUERIED_FIELDS]: [], [AVAILABLE_FIELDS]: res?.schema ? [...res.schema] : [] } @@ -96,6 +97,9 @@ export const useFetchEvents = ({ tabId: requestParams.tabId, data: [AVAILABLE_FIELDS, UNSELECTED_FIELDS] })); + dispatch(visualizationReset({ + tabId: requestParams.tabId, + })); }); }); }; diff --git a/public/components/explorer/hooks/use_fetch_visualizations.ts b/public/components/explorer/hooks/use_fetch_visualizations.ts index c21e5aec4..79e3e3c32 100644 --- a/public/components/explorer/hooks/use_fetch_visualizations.ts +++ b/public/components/explorer/hooks/use_fetch_visualizations.ts @@ -104,7 +104,8 @@ export const useFetchVisualizations = ({ dispatch(updateFields({ tabId: requestParams.tabId, data: { - [QUERIED_FIELDS]: res?.metadata.fields + [QUERIED_FIELDS]: res?.metadata.fields, + [SELECTED_FIELDS]: [] } })); dispatch(sortFields({ diff --git a/public/components/explorer/log_explorer.tsx b/public/components/explorer/log_explorer.tsx index 49f6609af..fedde6f06 100644 --- a/public/components/explorer/log_explorer.tsx +++ b/public/components/explorer/log_explorer.tsx @@ -50,7 +50,9 @@ import { export const LogExplorer = ({ pplService, - dslService + dslService, + savedObjects, + http }: ILogExplorerProps) => { const dispatch = useDispatch(); @@ -143,7 +145,9 @@ export const LogExplorer = ({ key={`explorer_${tabId}`} pplService={ pplService } dslService={ dslService } + http={ http } tabId={ tabId } + savedObjects={ savedObjects } /> ) }; diff --git a/public/components/explorer/visualizations/shared_components/save_panel/index.ts b/public/components/explorer/save_panel/index.ts similarity index 100% rename from public/components/explorer/visualizations/shared_components/save_panel/index.ts rename to public/components/explorer/save_panel/index.ts diff --git a/public/components/explorer/save_panel/savePanel.tsx b/public/components/explorer/save_panel/savePanel.tsx new file mode 100644 index 000000000..ce909a336 --- /dev/null +++ b/public/components/explorer/save_panel/savePanel.tsx @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { + EuiTitle, + EuiComboBox, + EuiFormRow, + EuiSpacer, + EuiFieldText +} from '@elastic/eui'; +import { useEffect } from 'react'; +import SavedObjects from '../../../services/saved_objects/event_analytics/saved_objects'; +import { isEmpty } from 'lodash'; + +interface ISavedPanelProps { + selectedOptions: any; + handleNameChange: any; + handleOptionChange: any; + savedObjects: SavedObjects; + isTextFieldInvalid: boolean; + savePanelName: string; + showOptionList: boolean; +} + +type CustomPanelOptions = { + id: string; + name: string; + dateCreated: string; + dateModified: string; +} + +export const SavePanel = ({ + selectedOptions, + handleNameChange, + handleOptionChange, + savedObjects, + isTextFieldInvalid, + savePanelName, + showOptionList, +}: ISavedPanelProps) => { + + const [options, setOptions] = useState([]); + + const getCustomPabnelList = async (savedObjects: SavedObjects) => { + const optionRes = await savedObjects.fetchCustomPanels(); + setOptions(optionRes['panels']); + }; + + useEffect(() => { + getCustomPabnelList(savedObjects); + }, []); + + return ( + <> + { showOptionList && ( + <> + +

{ 'Custom operational dashboards/application' }

+
+ + { handleOptionChange(options) } } + selectedOptions={ selectedOptions } + options={ options.map((option: CustomPanelOptions) => { + return { + panel: option, + label: option['name'], + } + }) } + isClearable={true} + data-test-subj="demoComboBox" + /> + + + )} + +

{ 'Name' }

+
+ + { + handleNameChange(e.target.value); + }} + /> + + + ); +}; \ No newline at end of file diff --git a/public/components/explorer/sidebar/sidebar.tsx b/public/components/explorer/sidebar/sidebar.tsx index 120228b0a..317f23a54 100644 --- a/public/components/explorer/sidebar/sidebar.tsx +++ b/public/components/explorer/sidebar/sidebar.tsx @@ -26,6 +26,7 @@ import { IExplorerFields, IField } from '../../../../common/types/explorer'; interface ISidebarProps { explorerFields: IExplorerFields; + explorerData: any; handleAddField: (field: IField) => void; handleRemoveField: (field: IField) => void; } @@ -34,6 +35,7 @@ export const Sidebar = (props: ISidebarProps) => { const { explorerFields, + explorerData, handleAddField, handleRemoveField } = props; @@ -60,7 +62,7 @@ export const Sidebar = (props: ISidebarProps) => {
- { !isEmpty(explorerFields) && ( + { explorerData && !isEmpty(explorerData.jsonData) && !isEmpty(explorerFields) && ( <> { explorerFields?.queriedFields && explorerFields.queriedFields?.length > 0 && ( @@ -113,7 +115,10 @@ export const Sidebar = (props: ISidebarProps) => { aria-labelledby="selected_fields" data-test-subj={`fieldList-selected`} > - { explorerFields.selectedFields && explorerFields.selectedFields.map(field => { + { explorerData && + !isEmpty(explorerData.jsonData) && + explorerFields.selectedFields && + explorerFields.selectedFields.map(field => { return (
  • { data-test-subj={`fieldList-unpopular`} > { + explorerData && + !isEmpty(explorerData.jsonData) && explorerFields.availableFields && explorerFields.availableFields.filter( (field) => searchTerm === '' || field.name.indexOf(searchTerm) !== -1) diff --git a/public/components/explorer/slices/visualization_slice.ts b/public/components/explorer/slices/visualization_slice.ts index 3860e5efc..3ddd6b8a4 100644 --- a/public/components/explorer/slices/visualization_slice.ts +++ b/public/components/explorer/slices/visualization_slice.ts @@ -25,13 +25,19 @@ export const explorerVisualizationSlice = createSlice({ reducers: { render: (state, { payload }) => { state[payload.tabId] = payload.data; - } + }, + reset: (state, { payload }) => { + state[payload.tabId] = { + ...initialState + } + }, }, extraReducers: (builder) => {} }); export const { - render + render, + reset } = explorerVisualizationSlice.actions; export const selectExplorerVisualization = (state) => state.explorerVisualization; diff --git a/public/components/explorer/visualizations/index.tsx b/public/components/explorer/visualizations/index.tsx index 748e0f153..2bb041f6d 100644 --- a/public/components/explorer/visualizations/index.tsx +++ b/public/components/explorer/visualizations/index.tsx @@ -21,10 +21,15 @@ import { WorkspacePanel } from './workspace_panel'; import { ConfigPanelWrapper } from './config_panel'; export const ExplorerVisualizations = ({ + curVisId, + setCurVisId, explorerVis, explorerFields, handleAddField, - handleRemoveField + handleRemoveField, + savedObjects, + onSaveVisualization, + getSavedVisualization }: any) => { return ( @@ -43,7 +48,12 @@ export const ExplorerVisualizations = ({ } workspacePanel={ } configPanel={ diff --git a/public/components/explorer/visualizations/shared_components/save_panel/savePanel.tsx b/public/components/explorer/visualizations/shared_components/save_panel/savePanel.tsx deleted file mode 100644 index e574e71fa..000000000 --- a/public/components/explorer/visualizations/shared_components/save_panel/savePanel.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -import React, { useState } from 'react'; -import { - EuiTitle, - EuiComboBox, - EuiFormRow, - EuiSpacer, - EuiFieldText -} from '@elastic/eui'; - -export const SavePanel = () => { - return ( - <> - -

    { 'Custom operational dashboards/application' }

    -
    - - - - - -

    { 'Visualization name' }

    -
    - - {}} - aria-label="Use aria labels when no actual label is in use" - /> - - - ); -}; \ No newline at end of file diff --git a/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx b/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx index 866cd6401..dae5982c4 100644 --- a/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx +++ b/public/components/explorer/visualizations/workspace_panel/workspace_panel.tsx @@ -20,6 +20,7 @@ import { LensIconChartBar } from '../assets/chart_bar'; import { LensIconChartLine } from '../assets/chart_line'; import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; import { EmptyPlaceholder } from '../shared_components/empty_placeholder'; +import SavedObjects from '../../../../services/saved_objects/event_analytics/saved_objects'; const plotlySharedlayout = { showlegend: true, @@ -44,9 +45,20 @@ const plotlySharedConfig = { editable: true }; +interface IWorkSpacePanel { + curVisId: string; + setCurVisId: any; + visualizations: any; + savedObjects: SavedObjects; + onSaveVisualization: any; + getSavedObjects: any; +} + export function WorkspacePanel({ + curVisId, + setCurVisId, visualizations -}: any) { +}: IWorkSpacePanel) { const memorizedVisualizationTypes = useMemo(() => { return ([ @@ -105,9 +117,12 @@ export function WorkspacePanel({ /> } ]); - }, [visualizations]); + }, [ + curVisId, + visualizations + ]); - const [curVisId, setCurVisId] = useState(memorizedVisualizationTypes[0]['id']); + const [savePanelName, setSavePanelName] = useState(''); function onDrop() {} @@ -128,6 +143,11 @@ export function WorkspacePanel({ setVis={ setCurVisId } vis={ getCurChart() } visualizationTypes={ memorizedVisualizationTypes } + handleSavePanelNameChange={ (name: string) => { + console.log('vis updating state name: ', name); + setSavePanelName(name) + } } + savePanelName={ savePanelName } > setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); - const closePopover = () => setIsPopoverOpen(false); - const button = ( - - Save - - ); - return ( <>
    @@ -72,36 +51,6 @@ export function WorkspacePanelWrapper({ visualizationTypes={ visualizationTypes } /> - - - {"Save to..."} - - - - - {}}> - Cancel - - - - {}}> - Save - - - - - -
    diff --git a/public/components/index.tsx b/public/components/index.tsx index 5fc1f746b..8aa74880a 100644 --- a/public/components/index.tsx +++ b/public/components/index.tsx @@ -20,7 +20,8 @@ export const Observability = ( DepsStart: AppPluginStartDependencies, AppMountParameters: AppMountParameters, pplService: any, - dslService: any + dslService: any, + savedObjects: any ) => { ReactDOM.render( , AppMountParameters.element ); diff --git a/public/plugin.ts b/public/plugin.ts index 05fabbc27..aea8f4660 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -17,6 +17,7 @@ import { } from '../common/constants/shared'; import PPLService from './services/requests/ppl'; import DSLService from './services/requests/dsl'; +import SavedObjects from './services/saved_objects/event_analytics/saved_objects'; import { AppPluginStartDependencies, ObservabilitySetup, ObservabilityStart } from './types'; export class ObservabilityPlugin implements Plugin { @@ -37,12 +38,14 @@ export class ObservabilityPlugin implements Plugin | string; + objectType?: Array | string; + sortField?: string; + sortOrder?: "asc" | "desc"; + fromIndex?: number; + maxItems?: number; + name?: string; + lastUpdatedTimeMs?: string; + createdTimeMs?: string; +} + +interface ISelectedPanelsParams { + selectedCustomPanels: Array + name: string; + query: string; + type: string; + timeField: string; +} + +interface IBulkUpdateSavedVisualizationRquest { + query: string; + fields: Array; + dateRange: Array; + type: string; + name: string; + savedObjectList: Array; +} + +export default class SavedObjects { + + constructor(private readonly http: any) {} + + buildRequestBody ({ + query, + fields, + dateRange, + name = '', + chartType = '', + description = '' + }: any) { + + const objRequest = { + object: { + query, + selected_date_range: { + start: dateRange[0] || 'now/15m', + end: dateRange[1] || 'now', + text: '' + }, + selected_fields: { + tokens: fields, + text: '' + }, + name: name || '', + description: description || '' + } + }; + + if (!isEmpty(chartType)) { + objRequest['object']['type'] = chartType; + } + + return objRequest; + } + + private stringifyList( + targetObj: any, + key: string, + joinBy: string + ) { + if (has(targetObj, key) && isArray(targetObj[key])) { + targetObj[key] = targetObj[key].join(joinBy); + } + return targetObj; + } + + async fetchSavedObjects(savedObjectRequestParams: ISavedObjectRequestParams) { + + // turn array into string. exmaple objectType ['savedQuery', 'savedVisualization'] => + // 'savedQuery,savedVisualization' + CONCAT_FIELDS.map((arrayField) => { + this.stringifyList( + savedObjectRequestParams, + arrayField, + ',' + ); + }); + + const res = await this.http.get( + `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}`, + { + query: { + ...savedObjectRequestParams + }, + } + ).catch((error: any) => console.log(error)); + + return res; + } + + async fetchCustomPanels() { + return await this.http.get(`${CUSTOM_PANELS_API_PREFIX}/panels`).catch((error: any) => console.log(error)); + } + + async bulkUpdateCustomPanel (selectedPanelsParams: ISelectedPanelsParams) { + const finalParams = { + panelId: '', + newVisualization: { + id: `panelViz_'${htmlIdGenerator()()}`, + title: selectedPanelsParams['name'], + query: selectedPanelsParams['query'], + type: selectedPanelsParams['type'], + timeField: selectedPanelsParams['timeField'] + } + }; + + const responses = await Promise.all( + selectedPanelsParams['selectedCustomPanels'].map((panel) => { + finalParams['panelId'] = panel['panel']['id']; + return this.http.post(`${CUSTOM_PANELS_API_PREFIX}/visualizations/event_explorer`, { + body: JSON.stringify(finalParams) + }); + }) + ).catch((error) => {}); + + }; + + async bulkUpdateSavedVisualization(bulkUpdateSavedVisualizationRquest: IBulkUpdateSavedVisualizationRquest) { + + const finalParams = this.buildRequestBody({ + query: bulkUpdateSavedVisualizationRquest['query'], + fields: bulkUpdateSavedVisualizationRquest['fields'], + dateRange: bulkUpdateSavedVisualizationRquest['dateRange'], + chartType: bulkUpdateSavedVisualizationRquest['type'], + name: bulkUpdateSavedVisualizationRquest['name'] + }); + + const responses = await Promise.all( + bulkUpdateSavedVisualizationRquest.savedObjectList.map((objectToUpdate) => { + finalParams['object_id'] = objectToUpdate['saved_object']['objectId']; + return this.http.put(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_VISUALIZATION}`, { + body: JSON.stringify(finalParams) + }); + }) + ).catch((error) => {}); + } + + async updateSavedVisualizationById(updateVisualizationRequest: any) { + const finalParams = this.buildRequestBody({ + query: updateVisualizationRequest['query'], + fields: updateVisualizationRequest['fields'], + dateRange: updateVisualizationRequest['dateRange'], + }); + + finalParams['object_id'] = updateVisualizationRequest['objectId']; + + return await this.http.post(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, { + body: JSON.stringify(finalParams) + }).catch((error: any) => console.log(error)); + + } + + async updateSavedQueryById(updateQueryRequest: any) { + const finalParams = this.buildRequestBody({ + query: updateQueryRequest['query'], + fields: updateQueryRequest['fields'], + dateRange: updateQueryRequest['dateRange'], + name: "get all" + }); + + finalParams['object_id'] = updateQueryRequest['objectId']; + + return await this.http.put(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, { + body: JSON.stringify(finalParams) + }).catch((error: any) => console.log(error)); + } + + async createSavedQuery(createQueryRequest: any) { + + console.log('createQueryRequest from create query: ', createQueryRequest); + + const finalParams = this.buildRequestBody({ + query: createQueryRequest['query'], + fields: createQueryRequest['fields'], + dateRange: createQueryRequest['dateRange'], + name: createQueryRequest['name'] + }); + + return await this.http.post(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, { + body: JSON.stringify(finalParams) + }).catch((error: any) => console.log(error)); + } + + async createSavedVisualization(createVisualizationRequest: any) { + + console.log('createVisualizationRequest: ', createVisualizationRequest); + + const finalParams = this.buildRequestBody({ + query: createVisualizationRequest['query'], + fields: createVisualizationRequest['fields'], + dateRange: createVisualizationRequest['dateRange'], + chartType: createVisualizationRequest['type'], + name: createVisualizationRequest['name'] + }); + + return await this.http.post(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_VISUALIZATION}`, { + body: JSON.stringify(finalParams) + }).catch((error: any) => console.log(error)); + } + + deleteSavedObjectsById(deleteObjectRequest: any) {} + + deleteSavedObjectsByIdList(deleteObjectRequesList: any) {} + +} \ No newline at end of file diff --git a/server/adaptors/ppl_datasource.ts b/server/adaptors/ppl_datasource.ts index 1d846e6c6..993021780 100644 --- a/server/adaptors/ppl_datasource.ts +++ b/server/adaptors/ppl_datasource.ts @@ -35,6 +35,7 @@ export class PPLDataSource { /** * Add vis mapping for runtime fields + * json data structure added to response will be * [{ * agent: "mozilla", * avg(bytes): 5756 @@ -76,7 +77,7 @@ export class PPLDataSource { _.forEach(pplRes.datarows, (row) => { const record: any = {}; - for (let i = 0; i < pplRes.schema.length; i++) { + for (let i = 0; i < pplRes.schema.length; i++) { const cur = pplRes.schema[i]; diff --git a/server/adaptors/saved_objects_plugin.ts b/server/adaptors/saved_objects_plugin.ts new file mode 100644 index 000000000..a7be0849e --- /dev/null +++ b/server/adaptors/saved_objects_plugin.ts @@ -0,0 +1,148 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { + BASE_OBSERVABILITY_URI, + SAVED_OBJECT +} from '../../common/constants/shared'; + +export function SavedObjectsPlugin(Client: any, config: any, components: any) { + const clientAction = components.clientAction.factory; + + Client.prototype.observability = components.clientAction.namespaceFactory(); + const observability = Client.prototype.observability.prototype; + +// observability.getPanels = clientAction({ +// url: { +// fmt: OPENSEARCH_PANELS_API.GET_PANELS, +// params: { +// fromIndex: { +// type: 'number', +// }, +// maxItems: { +// type: 'number', +// }, +// }, +// }, +// method: 'GET', +// }); + + observability.saveSearch = clientAction({ + url: { + fmt: `${BASE_OBSERVABILITY_URI}${SAVED_OBJECT}`, + }, + method: 'POST', + needBody: true, + }); + +// observability.getPanelById = clientAction({ +// url: { +// fmt: `${OPENSEARCH_PANELS_API.PANEL}/<%=panelId%>`, +// req: { +// panelId: { +// type: 'string', +// required: true, +// }, +// }, +// }, +// method: 'GET', +// }); + +// observability.updatePanelById = clientAction({ +// url: { +// fmt: `${OPENSEARCH_PANELS_API.PANEL}/<%=panelId%>`, +// req: { +// panelId: { +// type: 'string', +// required: true, +// }, +// }, +// }, +// method: 'PUT', +// needBody: true, +// }); + +// observability.deletePanelById = clientAction({ +// url: { +// fmt: `${OPENSEARCH_PANELS_API.PANEL}/<%=panelId%>`, +// req: { +// panelId: { +// type: 'string', +// required: true, +// }, +// }, +// }, +// method: 'DELETE', +// }); + +// observability.getNotebooks = clientAction({ +// url: { +// fmt: OPENSEARCH_NOTEBOOKS_API.GET_NOTEBOOKS, +// params: { +// fromIndex: { +// type: 'number', +// }, +// maxItems: { +// type: 'number', +// }, +// }, +// }, +// method: 'GET', +// }); + +// observability.createNotebook = clientAction({ +// url: { +// fmt: OPENSEARCH_NOTEBOOKS_API.NOTEBOOK, +// }, +// method: 'POST', +// needBody: true, +// }); + +// observability.getNotebookById = clientAction({ +// url: { +// fmt: `${OPENSEARCH_NOTEBOOKS_API.NOTEBOOK}/<%=notebookId%>`, +// req: { +// notebookId: { +// type: 'string', +// required: true, +// }, +// }, +// }, +// method: 'GET', +// }); + +// observability.updateNotebookById = clientAction({ +// url: { +// fmt: `${OPENSEARCH_NOTEBOOKS_API.NOTEBOOK}/<%=notebookId%>`, +// req: { +// notebookId: { +// type: 'string', +// required: true, +// }, +// }, +// }, +// method: 'PUT', +// needBody: true, +// }); + +// observability.deleteNotebookById = clientAction({ +// url: { +// fmt: `${OPENSEARCH_NOTEBOOKS_API.NOTEBOOK}/<%=notebookId%>`, +// req: { +// notebookId: { +// type: 'string', +// required: true, +// }, +// }, +// }, +// method: 'DELETE', +// }); +} \ No newline at end of file diff --git a/server/plugin.ts b/server/plugin.ts index 7a217e79a..297946c8f 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -19,6 +19,7 @@ import { } from '../../../src/core/server'; import { OpenSearchObservabilityPlugin } from './adaptors/opensearch_observability_plugin'; import { PPLPlugin } from './adaptors/ppl_plugin'; +import { SavedObjectsPlugin } from './adaptors/saved_objects_plugin'; import { setupRoutes } from './routes/index'; import { ObservabilityPluginSetup, ObservabilityPluginStart } from './types'; @@ -36,7 +37,11 @@ export class ObservabilityPlugin const openSearchObservabilityClient: ILegacyClusterClient = core.opensearch.legacy.createClient( 'opensearch_observability', { - plugins: [PPLPlugin, OpenSearchObservabilityPlugin], + plugins: [ + PPLPlugin, + OpenSearchObservabilityPlugin, + // SavedObjectsPlugin + ], } ); diff --git a/server/routes/event_analytics/event_analytics_router.ts b/server/routes/event_analytics/event_analytics_router.ts new file mode 100644 index 000000000..54cbaf5c4 --- /dev/null +++ b/server/routes/event_analytics/event_analytics_router.ts @@ -0,0 +1,212 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { schema } from '@osd/config-schema'; +import { + IRouter, + IOpenSearchDashboardsResponse, + ResponseError, +} from '../../../../../src/core/server'; +import { + OBSERVABILITY_BASE, + EVENT_ANALYTICS, + SAVED_OBJECTS, + SAVED_QUERY, + SAVED_VISUALIZATION +} from '../../../common/constants/shared'; +import SavedObjectFacet from '../../services/facets/saved_objects'; + +export const registerEventAnalyticsRouter = ({ + router, savedObjectFacet +}: { + router: IRouter, + savedObjectFacet: SavedObjectFacet +}) => { + + router.get({ + path: `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}`, + validate: false + }, + async ( + context, + req, + res + ) : Promise> => { + const savedRes = await savedObjectFacet.getSavedQuery(req); + const result: any = { + body: { + ...savedRes['data'] + } + }; + + if (savedRes['success']) return res.ok(result); + + result['statusCode'] = 500; + result['message'] = savedRes['data']; + return res.custom(result); + }); + + router.post({ + path: `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, + validate: { + body: schema.object({ + object: schema.object({ + query: schema.string(), + selected_date_range: schema.object({ + start: schema.string(), + end: schema.string(), + text: schema.string(), + }), + selected_fields: schema.object({ + tokens: schema.arrayOf(schema.object({}, { unknowns: 'allow' })), + text: schema.string(), + }), + name: schema.string(), + description: schema.string(), + }) + })} + }, + async ( + context, + req, + res + ) : Promise> => { + const savedRes = await savedObjectFacet.createSavedQuery(req); + const result: any = { + body: { + ...savedRes['data'] + } + }; + + if (savedRes['success']) return res.ok(result); + + result['statusCode'] = 500; + result['message'] = savedRes['data']; + return res.custom(result); + }); + + router.post({ + path: `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_VISUALIZATION}`, + validate: { + body: schema.object({ + object: schema.object({ + query: schema.string(), + selected_date_range: schema.object({ + start: schema.string(), + end: schema.string(), + text: schema.string(), + }), + selected_fields: schema.object({ + tokens: schema.arrayOf(schema.object({}, { unknowns: 'allow' })), + text: schema.string(), + }), + type: schema.string(), + name: schema.string(), + description: schema.string(), + }) + })} + }, + async ( + context, + req, + res + ) : Promise> => { + const savedRes = await savedObjectFacet.createSavedVisualization(req); + const result: any = { + body: { + ...savedRes['data'] + } + }; + + if (savedRes['success']) return res.ok(result); + + result['statusCode'] = 500; + result['message'] = savedRes['data']; + return res.custom(result); + }); + + router.put({ + path: `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, + validate: { + body: schema.object({ + object_id: schema.string(), + object: schema.object({ + query: schema.string(), + selected_date_range: schema.object({ + start: schema.string(), + end: schema.string(), + text: schema.string(), + }), + selected_fields: schema.object({ + tokens: schema.arrayOf(schema.object({}, { unknowns: 'allow' })), + text: schema.string(), + }), + name: schema.string(), + description: schema.string(), + }) + })} + }, + async ( + context, + req, + res + ) : Promise> => { + const savedRes = await savedObjectFacet.updateSavedQuery(req); + const result: any = { + body: { + ...savedRes['data'] + } + }; + if (savedRes['success']) return res.ok(result); + result['statusCode'] = 500; + result['message'] = savedRes['data']; + return res.custom(result); + }); + + router.put({ + path: `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_VISUALIZATION}`, + validate: { + body: schema.object({ + object_id: schema.string(), + object: schema.object({ + query: schema.string(), + selected_date_range: schema.object({ + start: schema.string(), + end: schema.string(), + text: schema.string(), + }), + selected_fields: schema.object({ + tokens: schema.arrayOf(schema.object({}, { unknowns: 'allow' })), + text: schema.string(), + }), + type: schema.string(), + name: schema.string(), + description: schema.string(), + }) + })} + }, + async ( + context, + req, + res + ) : Promise> => { + const savedRes = await savedObjectFacet.updateSavedVisualization(req); + const result: any = { + body: { + ...savedRes['data'] + } + }; + if (savedRes['success']) return res.ok(result); + result['statusCode'] = 500; + result['message'] = savedRes['data']; + return res.custom(result); + }); +} \ No newline at end of file diff --git a/server/routes/index.ts b/server/routes/index.ts index 82291c15f..a385588fa 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -14,6 +14,7 @@ import { registerPplRoute } from './ppl'; import PPLFacet from '../services/facets/ppl_facet'; import { registerDslRoute } from './dsl'; import DSLFacet from '../services/facets/dsl_facet'; +import SavedObjectFacet from '../services/facets/saved_objects'; import { PanelsRouter } from './custom_panels/panels_router'; import { VisualizationsRouter } from './custom_panels/visualizations_router'; import { registerTraceAnalyticsDslRouter } from './trace_analytics_dsl_router'; @@ -22,15 +23,16 @@ import { registerNoteRoute } from './notebooks/noteRouter'; import { registerVizRoute } from './notebooks/vizRouter'; import QueryService from '../services/queryService'; import { registerSqlRoute } from './notebooks/sqlRouter'; +import { registerEventAnalyticsRouter } from './event_analytics/event_analytics_router'; export function setupRoutes({ router, client }: { router: IRouter; client: ILegacyClusterClient }) { PanelsRouter(router); VisualizationsRouter(router); - const pplFacet = new PPLFacet(client); - registerPplRoute({ router, facet: pplFacet }); - const dslFacet = new DSLFacet(client); - registerDslRoute({ router, facet: dslFacet}) + registerPplRoute({ router, facet: new PPLFacet(client) }); + registerDslRoute({ router, facet: new DSLFacet(client)}); + registerEventAnalyticsRouter({ router, savedObjectFacet: new SavedObjectFacet(client) }); + // TODO remove trace analytics route when DSL route for autocomplete is added registerTraceAnalyticsDslRouter(router); diff --git a/server/routes/ppl.ts b/server/routes/ppl.ts index 85b9c001f..9993fb003 100644 --- a/server/routes/ppl.ts +++ b/server/routes/ppl.ts @@ -41,16 +41,18 @@ export function registerPplRoute({ req, res ) : Promise> => { - const queryRes = await facet.describeQuery(req); - const result: any = { - body: { - ...queryRes['data'] - } - }; + const queryRes: any = await facet.describeQuery(req); if (queryRes['success']) { + const result: any = { + body: { + ...queryRes['data'] + } + }; return res.ok(result); } - result['statusCode'] = 500; - return res.custom(result); + return res.custom({ + statusCode: queryRes.data.statusCode || queryRes.data.status || 500, + body: queryRes.data.body || queryRes.data.message || '', + }); }); } diff --git a/server/services/facets/ppl_facet.ts b/server/services/facets/ppl_facet.ts index 6c21c97ee..f7cc906cf 100644 --- a/server/services/facets/ppl_facet.ts +++ b/server/services/facets/ppl_facet.ts @@ -34,15 +34,15 @@ export default class PPLFacet { } }; if (request.body.format !== 'jdbc') { - params['format'] = request.body.format; + params['format'] = request.body.format; } const queryRes = await this.client.asScoped(request).callAsCurrentUser(format, params); const pplDataSource = new PPLDataSource(queryRes, request.body.format); res['success'] = true; res['data'] = pplDataSource.getDataSource(); } catch (err: any) { - console.log('pplfacet err: ', err); - res['data'] = err.body; + console.error('PPL query fetch err: ', err); + res['data'] = err; } return res }; diff --git a/server/services/facets/saved_objects.ts b/server/services/facets/saved_objects.ts new file mode 100644 index 000000000..2144e0bc1 --- /dev/null +++ b/server/services/facets/saved_objects.ts @@ -0,0 +1,154 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +export default class SavedObjectFacet { + constructor(private client: any) { + this.client = client; + } + + fetch = async ( + request: any, + format: string, + responseFormat: string, + objectType: string + ) => { + const res = { + success: false, + data: {} + }; + try { + const params = { + ...request.url.query + }; + const savedQueryRes = await this.client.asScoped(request).callAsCurrentUser(format, params); + res['success'] = true; + res['data'] = savedQueryRes; + } catch (err: any) { + console.error('Event analytics fetch error: ', err); + res['data'] = err; + } + return res; + }; + + create = async ( + request: any, + format: string, + responseFormat: string, + objectType: string + ) => { + const res = { + success: false, + data: {} + }; + try { + const params = { + body: { + [objectType]: { + ...request.body.object + } + } + }; + const savedQueryRes = await this.client.asScoped(request).callAsCurrentUser(format, params); + res['success'] = true; + res['data'] = savedQueryRes; + } catch (err: any) { + console.error('Event analytics create error: ', err); + res['data'] = err; + } + return res; + }; + + update = async ( + request: any, + format: string, + responseFormat: string, + objectType: string + ) => { + const res = { + success: false, + data: {} + }; + try { + const params = { + objectId: request.body.object_id, + body: { + [objectType]: { + ...request.body.object + } + } + }; + const savedQueryRes = await this.client.asScoped(request).callAsCurrentUser(format, params); + res['success'] = true; + res['data'] = savedQueryRes; + } catch (err: any) { + console.log('Event analytics update error: ', err); + res['data'] = err; + } + return res; + }; + + delete = async ( + request: any, + format: string, + responseFormat: string, + objectType: string + ) => { + const res = { + success: false, + data: {} + }; + try { + const params = { + objectId: request.body.object_id, + body: { + [objectType]: { + ...request.body.object + } + } + }; + const savedQueryRes = await this.client.asScoped(request).callAsCurrentUser(format, params); + res['success'] = true; + res['data'] = savedQueryRes; + } catch (err: any) { + console.log('Event analytics delete error: ', err); + res['data'] = err; + } + return res; + }; + + getSavedQuery = async (request: any) => { + return this.fetch(request, 'observability.getObject', 'json', 'savedQuery'); + }; + + getSavedVisualization = async (request: any) => { + return this.fetch(request, 'observability.getObject', 'json', 'savedVisualization'); + }; + + createSavedQuery = async (request: any) => { + return this.create(request, 'observability.createObject', 'json', 'savedQuery'); + }; + + createSavedVisualization = (request: any) => { + return this.create(request, 'observability.createObject', 'json', 'savedVisualization'); + }; + + updateSavedQuery = (request: any) => { + return this.update(request, 'observability.updateObjectById', 'json', 'savedQuery'); + }; + + updateSavedVisualization = (request: any) => { + return this.update(request, 'observability.updateObjectById', 'json', 'savedVisualization'); + }; + + deleteSavedQuery = async (request: any) => { + return this.delete(request, 'observability.deleteObjectByIdList', 'json', 'savedQuery'); + }; +} \ No newline at end of file From 5e8e37717e6df6ff07367dd817ce980137774ff0 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Wed, 13 Oct 2021 15:40:37 -0700 Subject: [PATCH 18/20] removed unused reference/files, modified few loggings Signed-off-by: Eric Wei --- .../event_analytics/saved_objects.ts | 28 ++-- server/adaptors/saved_objects_plugin.ts | 148 ------------------ server/plugin.ts | 2 - server/services/facets/saved_objects.ts | 4 +- 4 files changed, 14 insertions(+), 168 deletions(-) delete mode 100644 server/adaptors/saved_objects_plugin.ts diff --git a/public/services/saved_objects/event_analytics/saved_objects.ts b/public/services/saved_objects/event_analytics/saved_objects.ts index 583eb8ffb..95df000c2 100644 --- a/public/services/saved_objects/event_analytics/saved_objects.ts +++ b/public/services/saved_objects/event_analytics/saved_objects.ts @@ -191,24 +191,22 @@ export default class SavedObjects { } - async updateSavedQueryById(updateQueryRequest: any) { - const finalParams = this.buildRequestBody({ - query: updateQueryRequest['query'], - fields: updateQueryRequest['fields'], - dateRange: updateQueryRequest['dateRange'], - name: "get all" - }); + // async updateSavedQueryById(updateQueryRequest: any) { + // const finalParams = this.buildRequestBody({ + // query: updateQueryRequest['query'], + // fields: updateQueryRequest['fields'], + // dateRange: updateQueryRequest['dateRange'], + // name: "get all" + // }); - finalParams['object_id'] = updateQueryRequest['objectId']; + // finalParams['object_id'] = updateQueryRequest['objectId']; - return await this.http.put(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, { - body: JSON.stringify(finalParams) - }).catch((error: any) => console.log(error)); - } + // return await this.http.put(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, { + // body: JSON.stringify(finalParams) + // }).catch((error: any) => console.log(error)); + // } async createSavedQuery(createQueryRequest: any) { - - console.log('createQueryRequest from create query: ', createQueryRequest); const finalParams = this.buildRequestBody({ query: createQueryRequest['query'], @@ -224,8 +222,6 @@ export default class SavedObjects { async createSavedVisualization(createVisualizationRequest: any) { - console.log('createVisualizationRequest: ', createVisualizationRequest); - const finalParams = this.buildRequestBody({ query: createVisualizationRequest['query'], fields: createVisualizationRequest['fields'], diff --git a/server/adaptors/saved_objects_plugin.ts b/server/adaptors/saved_objects_plugin.ts deleted file mode 100644 index a7be0849e..000000000 --- a/server/adaptors/saved_objects_plugin.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -import { - BASE_OBSERVABILITY_URI, - SAVED_OBJECT -} from '../../common/constants/shared'; - -export function SavedObjectsPlugin(Client: any, config: any, components: any) { - const clientAction = components.clientAction.factory; - - Client.prototype.observability = components.clientAction.namespaceFactory(); - const observability = Client.prototype.observability.prototype; - -// observability.getPanels = clientAction({ -// url: { -// fmt: OPENSEARCH_PANELS_API.GET_PANELS, -// params: { -// fromIndex: { -// type: 'number', -// }, -// maxItems: { -// type: 'number', -// }, -// }, -// }, -// method: 'GET', -// }); - - observability.saveSearch = clientAction({ - url: { - fmt: `${BASE_OBSERVABILITY_URI}${SAVED_OBJECT}`, - }, - method: 'POST', - needBody: true, - }); - -// observability.getPanelById = clientAction({ -// url: { -// fmt: `${OPENSEARCH_PANELS_API.PANEL}/<%=panelId%>`, -// req: { -// panelId: { -// type: 'string', -// required: true, -// }, -// }, -// }, -// method: 'GET', -// }); - -// observability.updatePanelById = clientAction({ -// url: { -// fmt: `${OPENSEARCH_PANELS_API.PANEL}/<%=panelId%>`, -// req: { -// panelId: { -// type: 'string', -// required: true, -// }, -// }, -// }, -// method: 'PUT', -// needBody: true, -// }); - -// observability.deletePanelById = clientAction({ -// url: { -// fmt: `${OPENSEARCH_PANELS_API.PANEL}/<%=panelId%>`, -// req: { -// panelId: { -// type: 'string', -// required: true, -// }, -// }, -// }, -// method: 'DELETE', -// }); - -// observability.getNotebooks = clientAction({ -// url: { -// fmt: OPENSEARCH_NOTEBOOKS_API.GET_NOTEBOOKS, -// params: { -// fromIndex: { -// type: 'number', -// }, -// maxItems: { -// type: 'number', -// }, -// }, -// }, -// method: 'GET', -// }); - -// observability.createNotebook = clientAction({ -// url: { -// fmt: OPENSEARCH_NOTEBOOKS_API.NOTEBOOK, -// }, -// method: 'POST', -// needBody: true, -// }); - -// observability.getNotebookById = clientAction({ -// url: { -// fmt: `${OPENSEARCH_NOTEBOOKS_API.NOTEBOOK}/<%=notebookId%>`, -// req: { -// notebookId: { -// type: 'string', -// required: true, -// }, -// }, -// }, -// method: 'GET', -// }); - -// observability.updateNotebookById = clientAction({ -// url: { -// fmt: `${OPENSEARCH_NOTEBOOKS_API.NOTEBOOK}/<%=notebookId%>`, -// req: { -// notebookId: { -// type: 'string', -// required: true, -// }, -// }, -// }, -// method: 'PUT', -// needBody: true, -// }); - -// observability.deleteNotebookById = clientAction({ -// url: { -// fmt: `${OPENSEARCH_NOTEBOOKS_API.NOTEBOOK}/<%=notebookId%>`, -// req: { -// notebookId: { -// type: 'string', -// required: true, -// }, -// }, -// }, -// method: 'DELETE', -// }); -} \ No newline at end of file diff --git a/server/plugin.ts b/server/plugin.ts index 297946c8f..cdacc2459 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -19,7 +19,6 @@ import { } from '../../../src/core/server'; import { OpenSearchObservabilityPlugin } from './adaptors/opensearch_observability_plugin'; import { PPLPlugin } from './adaptors/ppl_plugin'; -import { SavedObjectsPlugin } from './adaptors/saved_objects_plugin'; import { setupRoutes } from './routes/index'; import { ObservabilityPluginSetup, ObservabilityPluginStart } from './types'; @@ -40,7 +39,6 @@ export class ObservabilityPlugin plugins: [ PPLPlugin, OpenSearchObservabilityPlugin, - // SavedObjectsPlugin ], } ); diff --git a/server/services/facets/saved_objects.ts b/server/services/facets/saved_objects.ts index 2144e0bc1..55a720c9a 100644 --- a/server/services/facets/saved_objects.ts +++ b/server/services/facets/saved_objects.ts @@ -89,7 +89,7 @@ export default class SavedObjectFacet { res['success'] = true; res['data'] = savedQueryRes; } catch (err: any) { - console.log('Event analytics update error: ', err); + console.error('Event analytics update error: ', err); res['data'] = err; } return res; @@ -118,7 +118,7 @@ export default class SavedObjectFacet { res['success'] = true; res['data'] = savedQueryRes; } catch (err: any) { - console.log('Event analytics delete error: ', err); + console.error('Event analytics delete error: ', err); res['data'] = err; } return res; From b94d2cd58d374cc5fb0949c669a4bb283083e4ed Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Thu, 14 Oct 2021 10:30:38 -0700 Subject: [PATCH 19/20] removed missed comments/usused code, fixed one sidebar issue Signed-off-by: Eric Wei --- common/constants/shared.ts | 1 - public/components/explorer/data_grid.tsx | 1 - public/components/explorer/docTable/docViewRow.tsx | 14 -------------- public/components/explorer/explorer.tsx | 1 + public/components/explorer/sidebar/field.tsx | 2 +- .../explorer/visualizations/frame_layout.tsx | 3 --- .../components/explorer/visualizations/index.tsx | 7 ++----- public/components/visualizations/charts/bar.tsx | 3 +++ 8 files changed, 7 insertions(+), 25 deletions(-) diff --git a/common/constants/shared.ts b/common/constants/shared.ts index 298ebb2a2..e82bbd3d5 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -37,7 +37,6 @@ export const PPL_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'; export const PPL_STATS_REGEX = /\|\s*stats/i; export const PPL_INDEX_INSERT_POINT_REGEX = /search (source|index)\s*=\s*([^\s]+)(.*)/i; export const PPL_INDEX_REGEX = /(search source|source|index)\s*=\s*([^|\s]+)/i; -export const PPL_CONTAINS_TIMESTAMP_REGEX = /\|\s*.*\s*[<|<=|=|>=|>]\s*timestamp\([^\)]+\)/i; // Observability plugin URI const BASE_OBSERVABILITY_URI = '/_plugins/_observability'; diff --git a/public/components/explorer/data_grid.tsx b/public/components/explorer/data_grid.tsx index ad29cbb01..c159f2508 100644 --- a/public/components/explorer/data_grid.tsx +++ b/public/components/explorer/data_grid.tsx @@ -70,7 +70,6 @@ export function DataGrid(props: DataGridProps) { { selField.name } ); }); - // tableHeadContent.unshift(Time); tableHeadContent.unshift(); } diff --git a/public/components/explorer/docTable/docViewRow.tsx b/public/components/explorer/docTable/docViewRow.tsx index aec3a87ac..3e4d62389 100644 --- a/public/components/explorer/docTable/docViewRow.tsx +++ b/public/components/explorer/docTable/docViewRow.tsx @@ -37,9 +37,6 @@ export const DocViewRow = (props: IDocViewRowProps) => { selectedCols } = props; - console.log('doc view row doc: ', doc); - console.log('doc view selectedCols: ', selectedCols); - const [detailsOpen, setDetailsOpen] = useState(false); const getTdTmpl = (conf: { clsName: string, content: React.ReactDOM | string }) => { @@ -143,7 +140,6 @@ export const DocViewRow = (props: IDocViewRowProps) => { filteredDoc[selCol.name] = doc[selCol.name]; } }) - console.log('filteredDoc: ', filteredDoc); forEach(filteredDoc, (val, key) => { cols.push( getTdTmpl({ @@ -152,15 +148,6 @@ export const DocViewRow = (props: IDocViewRowProps) => { }) ); }); - - // if (has(doc, 'timestamp')) { - // cols.unshift( - // getTdTmpl({ - // clsName: timestampClsName, - // content: doc['timestamp'] - // }) - // ); - // } } // Add detail toggling column @@ -192,7 +179,6 @@ export const DocViewRow = (props: IDocViewRowProps) => { key={ uniqueId('grid-td-detail-') } colSpan={ selectedCols.length ? selectedCols.length + 2 : 3 } > - {/* */} diff --git a/public/components/explorer/explorer.tsx b/public/components/explorer/explorer.tsx index 953497c26..992f29ec2 100644 --- a/public/components/explorer/explorer.tsx +++ b/public/components/explorer/explorer.tsx @@ -386,6 +386,7 @@ export const Explorer = ({ setCurVisId={ setCurVisId } explorerFields={ explorerFields } explorerVis={ explorerVisualizations } + explorerData={ explorerData } handleAddField={ handleAddField } handleRemoveField={ handleRemoveField } /> diff --git a/public/components/explorer/sidebar/field.tsx b/public/components/explorer/sidebar/field.tsx index 7ca05d888..4c39d416c 100644 --- a/public/components/explorer/sidebar/field.tsx +++ b/public/components/explorer/sidebar/field.tsx @@ -98,7 +98,7 @@ export const Field = (props: IFieldProps) => { ownFocus display="block" isOpen={ isFieldDetailsOpen } - closePopover={ () => {} } + closePopover={ () => togglePopover } anchorPosition="rightUp" panelClassName="dscSidebarItem__fieldPopoverPanel" button={ diff --git a/public/components/explorer/visualizations/frame_layout.tsx b/public/components/explorer/visualizations/frame_layout.tsx index 1870178df..a06e49f37 100644 --- a/public/components/explorer/visualizations/frame_layout.tsx +++ b/public/components/explorer/visualizations/frame_layout.tsx @@ -31,9 +31,6 @@ export function FrameLayout(props: FrameLayoutProps) { {props.workspacePanel} - {/* - {props.configPanel} - */}
  • ); diff --git a/public/components/explorer/visualizations/index.tsx b/public/components/explorer/visualizations/index.tsx index 2bb041f6d..34b77b6e1 100644 --- a/public/components/explorer/visualizations/index.tsx +++ b/public/components/explorer/visualizations/index.tsx @@ -25,6 +25,7 @@ export const ExplorerVisualizations = ({ setCurVisId, explorerVis, explorerFields, + explorerData, handleAddField, handleRemoveField, savedObjects, @@ -34,14 +35,10 @@ export const ExplorerVisualizations = ({ return ( - // } dataPanel={ diff --git a/public/components/visualizations/charts/bar.tsx b/public/components/visualizations/charts/bar.tsx index 0d6ae6a00..c5b1662d5 100644 --- a/public/components/visualizations/charts/bar.tsx +++ b/public/components/visualizations/charts/bar.tsx @@ -38,6 +38,9 @@ export const Bar = ({ xaxis: { automargin: true }, + yaxis: { + automargin: true + }, }, layoutConfig); return ( From 3bee70af73bd0b755ec9185c78182cfbe4f79e27 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Thu, 14 Oct 2021 11:25:53 -0700 Subject: [PATCH 20/20] modified files for code review Signed-off-by: Eric Wei --- common/types/explorer.ts | 2 ++ public/components/common/search/search.tsx | 16 +------------- .../event_analytics/saved_objects.ts | 15 ------------- server/services/facets/saved_objects.ts | 21 +++++++------------ 4 files changed, 11 insertions(+), 43 deletions(-) diff --git a/common/types/explorer.ts b/common/types/explorer.ts index fc6df89c6..e4cf65af9 100644 --- a/common/types/explorer.ts +++ b/common/types/explorer.ts @@ -16,6 +16,7 @@ import { AVAILABLE_FIELDS, QUERIED_FIELDS } from '../constants/explorer'; + import SavedObjects from '../../public/services/saved_objects/event_analytics/saved_objects'; export interface IQueryTab { id: string; @@ -54,4 +55,5 @@ export interface IExplorerFields { export interface ILogExplorerProps { pplService: any; dslService: any; + savedObjects: SavedObjects; } \ No newline at end of file diff --git a/public/components/common/search/search.tsx b/public/components/common/search/search.tsx index b033df127..eca65b7b5 100644 --- a/public/components/common/search/search.tsx +++ b/public/components/common/search/search.tsx @@ -11,34 +11,22 @@ import './search.scss'; -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { isEmpty } from 'lodash'; import { EuiFlexGroup, EuiButton, EuiFlexItem, - EuiTitle, - EuiComboBox, - EuiFormRow, - EuiSpacer, - EuiFieldText, - EuiPageContent, - EuiPageContentBody, - EuiPageContentHeader, EuiPopover, EuiButtonEmpty, EuiPopoverFooter, - EuiPopoverTitle, - EuiTextArea } from '@elastic/eui'; import _ from 'lodash'; import { DatePicker } from './date_picker'; import '@algolia/autocomplete-theme-classic'; import { Autocomplete } from './autocomplete'; import { SavePanel } from '../../explorer/save_panel'; -import { PPL_STATS_REGEX } from '../../../../common/constants/shared'; -import { SaveQueryForm } from '../../../../../../src/plugins/data/public/ui/saved_query_form'; import { useCallback } from 'react'; export interface IQueryBarProps { @@ -164,7 +152,6 @@ export const Search = (props: any) => { isOpen={isSavePanelOpen} closePopover={() => setIsSavePanelOpen(false)} > - {/* {"Save to..."} */} { handleSaveVisualization()}> onClick={() => handleSavingObject()}> { "Save" } diff --git a/public/services/saved_objects/event_analytics/saved_objects.ts b/public/services/saved_objects/event_analytics/saved_objects.ts index 95df000c2..a23a380c1 100644 --- a/public/services/saved_objects/event_analytics/saved_objects.ts +++ b/public/services/saved_objects/event_analytics/saved_objects.ts @@ -191,21 +191,6 @@ export default class SavedObjects { } - // async updateSavedQueryById(updateQueryRequest: any) { - // const finalParams = this.buildRequestBody({ - // query: updateQueryRequest['query'], - // fields: updateQueryRequest['fields'], - // dateRange: updateQueryRequest['dateRange'], - // name: "get all" - // }); - - // finalParams['object_id'] = updateQueryRequest['objectId']; - - // return await this.http.put(`${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, { - // body: JSON.stringify(finalParams) - // }).catch((error: any) => console.log(error)); - // } - async createSavedQuery(createQueryRequest: any) { const finalParams = this.buildRequestBody({ diff --git a/server/services/facets/saved_objects.ts b/server/services/facets/saved_objects.ts index 55a720c9a..cd1976008 100644 --- a/server/services/facets/saved_objects.ts +++ b/server/services/facets/saved_objects.ts @@ -16,9 +16,7 @@ export default class SavedObjectFacet { fetch = async ( request: any, - format: string, - responseFormat: string, - objectType: string + format: string ) => { const res = { success: false, @@ -41,7 +39,6 @@ export default class SavedObjectFacet { create = async ( request: any, format: string, - responseFormat: string, objectType: string ) => { const res = { @@ -69,7 +66,6 @@ export default class SavedObjectFacet { update = async ( request: any, format: string, - responseFormat: string, objectType: string ) => { const res = { @@ -98,7 +94,6 @@ export default class SavedObjectFacet { delete = async ( request: any, format: string, - responseFormat: string, objectType: string ) => { const res = { @@ -125,30 +120,30 @@ export default class SavedObjectFacet { }; getSavedQuery = async (request: any) => { - return this.fetch(request, 'observability.getObject', 'json', 'savedQuery'); + return this.fetch(request, 'observability.getObject'); }; getSavedVisualization = async (request: any) => { - return this.fetch(request, 'observability.getObject', 'json', 'savedVisualization'); + return this.fetch(request, 'observability.getObject'); }; createSavedQuery = async (request: any) => { - return this.create(request, 'observability.createObject', 'json', 'savedQuery'); + return this.create(request, 'observability.createObject', 'savedQuery'); }; createSavedVisualization = (request: any) => { - return this.create(request, 'observability.createObject', 'json', 'savedVisualization'); + return this.create(request, 'observability.createObject', 'savedVisualization'); }; updateSavedQuery = (request: any) => { - return this.update(request, 'observability.updateObjectById', 'json', 'savedQuery'); + return this.update(request, 'observability.updateObjectById', 'savedQuery'); }; updateSavedVisualization = (request: any) => { - return this.update(request, 'observability.updateObjectById', 'json', 'savedVisualization'); + return this.update(request, 'observability.updateObjectById', 'savedVisualization'); }; deleteSavedQuery = async (request: any) => { - return this.delete(request, 'observability.deleteObjectByIdList', 'json', 'savedQuery'); + return this.delete(request, 'observability.deleteObjectByIdList', 'savedQuery'); }; } \ No newline at end of file