From 14cdfd2d4259fb0fe9cf3e7583fd48f2c421e7ca Mon Sep 17 00:00:00 2001 From: ananzh Date: Fri, 30 Jun 2023 22:38:18 +0000 Subject: [PATCH] [Data Explorer] Clean out angular contents in Discover Issue Resolve https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4459 Signed-off-by: ananzh --- .../public/application/_discover.scss | 164 -------- .../components/chart/point_series.ts | 122 ++++++ .../create_discover_legacy_directive.ts | 67 ---- .../application/components/discover.tsx | 11 +- .../doc_viewer/doc_viewer_render_tab.tsx | 1 - .../{help_menu_util.js => help_menu_util.ts} | 4 +- .../components/histogram/histogram.tsx | 363 ++++++++++++++++++ .../components/no_results/no_results.tsx | 215 +++++++++++ .../sidebar/discover_field_search.tsx | 6 - .../sidebar/discover_index_pattern.tsx | 2 +- .../skip_bottom_button/skip_bottom_button.tsx | 2 +- ...nel.test.js => open_search_panel.test.tsx} | 0 ..._search_panel.js => open_search_panel.tsx} | 5 - ...ch_panel.js => show_open_search_panel.tsx} | 9 +- .../uninitialized/uninitialized.tsx | 78 ++++ .../doc_views/doc_views_helpers.tsx | 105 ----- .../application/doc_views/doc_views_types.ts | 8 - .../discover/public/application/index.scss | 2 - .../public/opensearch_dashboards_services.ts | 19 - src/plugins/discover/public/plugin.ts | 65 +--- 20 files changed, 798 insertions(+), 450 deletions(-) delete mode 100644 src/plugins/discover/public/application/_discover.scss create mode 100644 src/plugins/discover/public/application/components/chart/point_series.ts delete mode 100644 src/plugins/discover/public/application/components/create_discover_legacy_directive.ts rename src/plugins/discover/public/application/components/help_menu/{help_menu_util.js => help_menu_util.ts} (92%) create mode 100644 src/plugins/discover/public/application/components/histogram/histogram.tsx create mode 100644 src/plugins/discover/public/application/components/no_results/no_results.tsx rename src/plugins/discover/public/application/components/top_nav/{open_search_panel.test.js => open_search_panel.test.tsx} (100%) rename src/plugins/discover/public/application/components/top_nav/{open_search_panel.js => open_search_panel.tsx} (97%) rename src/plugins/discover/public/application/components/top_nav/{show_open_search_panel.js => show_open_search_panel.tsx} (89%) create mode 100644 src/plugins/discover/public/application/components/uninitialized/uninitialized.tsx delete mode 100644 src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx delete mode 100644 src/plugins/discover/public/application/index.scss diff --git a/src/plugins/discover/public/application/_discover.scss b/src/plugins/discover/public/application/_discover.scss deleted file mode 100644 index f574357c5ff4..000000000000 --- a/src/plugins/discover/public/application/_discover.scss +++ /dev/null @@ -1,164 +0,0 @@ -.dscAppWrapper { - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; -} - -.dscAppContainer { - > * { - position: relative; - } -} - -discover-app { - flex-grow: 1; -} - -.dscHistogram { - display: flex; - height: 200px; - padding: $euiSizeS; -} - -// SASSTODO: replace the z-index value with a variable -.dscWrapper { - padding-left: $euiSizeXL; - padding-right: $euiSizeS; - z-index: 1; - - @include euiBreakpoint("xs", "s", "m") { - padding-left: $euiSizeS; - } -} - -@include euiPanel(".dscWrapper__content"); - -.dscWrapper__content { - padding-top: $euiSizeXS; - background-color: $euiColorEmptyShade; - - .osd-table { - margin-bottom: 0; - } -} - -.dscTimechart { - display: block; - position: relative; - - // SASSTODO: the visualizing component should have an option or a modifier - .series > rect { - fill-opacity: 0.5; - stroke-width: 1; - } -} - -.dscResultCount { - padding-top: $euiSizeXS; -} - -.dscTimechart__header { - display: flex; - justify-content: center; - min-height: $euiSizeXXL; - padding: $euiSizeXS 0; -} - -.dscOverlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 20; - padding-top: $euiSizeM; - opacity: 0.75; - text-align: center; - background-color: transparent; -} - -.dscTable { - overflow: auto; - - // SASSTODO: add a monospace modifier to the doc-table component - .osdDocTable__row { - font-family: $euiCodeFontFamily; - font-size: $euiFontSizeXS; - } -} - -// SASSTODO: replace the padding value with a variable -.dscTable__footer { - background-color: $euiColorLightShade; - padding: 5px 10px; - text-align: center; -} - -.dscResults { - h3 { - margin: -20px 0 10px; - text-align: center; - } -} - -.dscResults__interval { - display: inline-block; - width: auto; -} - -.dscSkipButton { - position: absolute; - right: $euiSizeM; - top: $euiSizeXS; -} - -.dscTableFixedScroll { - overflow-x: auto; - padding-bottom: 0; - - + .dscTableFixedScroll__scroller { - position: fixed; - bottom: 0; - overflow-x: auto; - overflow-y: hidden; - } -} - -.dscCollapsibleSidebar { - position: relative; - z-index: $euiZLevel1; - - .dscCollapsibleSidebar__collapseButton { - position: absolute; - top: 0; - right: -$euiSizeXL + 4; - cursor: pointer; - z-index: -1; - min-height: $euiSizeM; - min-width: $euiSizeM; - padding: $euiSizeXS * 0.5; - } - - &.closed { - width: 0 !important; - border-right-width: 0; - border-left-width: 0; - - .dscCollapsibleSidebar__collapseButton { - right: -$euiSizeL + 4; - } - } -} - -@include euiBreakpoint("xs", "s", "m") { - .dscCollapsibleSidebar { - &.closed { - display: none; - } - - .dscCollapsibleSidebar__collapseButton { - display: none; - } - } -} diff --git a/src/plugins/discover/public/application/components/chart/point_series.ts b/src/plugins/discover/public/application/components/chart/point_series.ts new file mode 100644 index 000000000000..34b35453cb55 --- /dev/null +++ b/src/plugins/discover/public/application/components/chart/point_series.ts @@ -0,0 +1,122 @@ +/* + * 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. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { uniq } from 'lodash'; +import { Duration, Moment } from 'moment'; +import { Unit } from '@elastic/datemath'; + +import { SerializedFieldFormat } from '../../../../../expressions/common/types'; + +export interface Column { + id: string; + name: string; +} + +export interface Row { + [key: string]: number | 'NaN'; +} + +export interface Table { + columns: Column[]; + rows: Row[]; +} + +interface HistogramParams { + date: true; + interval: Duration; + intervalOpenSearchValue: number; + intervalOpenSearchUnit: Unit; + format: string; + bounds: { + min: Moment; + max: Moment; + }; +} +export interface Dimension { + accessor: 0 | 1; + format: SerializedFieldFormat<{ pattern: string }>; +} + +export interface Dimensions { + x: Dimension & { params: HistogramParams }; + y: Dimension; +} + +interface Ordered { + date: true; + interval: Duration; + intervalOpenSearchUnit: string; + intervalOpenSearchValue: number; + min: Moment; + max: Moment; +} +export interface Chart { + values: Array<{ + x: number; + y: number; + }>; + xAxisOrderedValues: number[]; + xAxisFormat: Dimension['format']; + xAxisLabel: Column['name']; + yAxisLabel?: Column['name']; + ordered: Ordered; +} + +export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => { + const { x, y } = dimensions; + const xAccessor = table.columns[x.accessor].id; + const yAccessor = table.columns[y.accessor].id; + const chart = {} as Chart; + + chart.xAxisOrderedValues = uniq(table.rows.map((r) => r[xAccessor] as number)); + chart.xAxisFormat = x.format; + chart.xAxisLabel = table.columns[x.accessor].name; + + const { intervalOpenSearchUnit, intervalOpenSearchValue, interval, bounds } = x.params; + chart.ordered = { + date: true, + interval, + intervalOpenSearchUnit, + intervalOpenSearchValue, + min: bounds.min, + max: bounds.max, + }; + + chart.yAxisLabel = table.columns[y.accessor].name; + + chart.values = table.rows + .filter((row) => row && row[yAccessor] !== 'NaN') + .map((row) => ({ + x: row[xAccessor] as number, + y: row[yAccessor] as number, + })); + + return chart; +}; diff --git a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts b/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts deleted file mode 100644 index 09cc33964862..000000000000 --- a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts +++ /dev/null @@ -1,67 +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. - * - * Any modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { DiscoverLegacy } from './discover_legacy'; - -export function createDiscoverLegacyDirective(reactDirective: any) { - return reactDirective(DiscoverLegacy, [ - ['addColumn', { watchDepth: 'reference' }], - ['fetch', { watchDepth: 'reference' }], - ['fetchCounter', { watchDepth: 'reference' }], - ['fetchError', { watchDepth: 'reference' }], - ['fieldCounts', { watchDepth: 'reference' }], - ['histogramData', { watchDepth: 'reference' }], - ['hits', { watchDepth: 'reference' }], - ['indexPattern', { watchDepth: 'reference' }], - ['minimumVisibleRows', { watchDepth: 'reference' }], - ['onAddFilter', { watchDepth: 'reference' }], - ['onChangeInterval', { watchDepth: 'reference' }], - ['onMoveColumn', { watchDepth: 'reference' }], - ['onRemoveColumn', { watchDepth: 'reference' }], - ['onSetColumns', { watchDepth: 'reference' }], - ['onSkipBottomButtonClick', { watchDepth: 'reference' }], - ['onSort', { watchDepth: 'reference' }], - ['opts', { watchDepth: 'reference' }], - ['resetQuery', { watchDepth: 'reference' }], - ['resultState', { watchDepth: 'reference' }], - ['rows', { watchDepth: 'reference' }], - ['savedSearch', { watchDepth: 'reference' }], - ['searchSource', { watchDepth: 'reference' }], - ['setIndexPattern', { watchDepth: 'reference' }], - ['showSaveQuery', { watchDepth: 'reference' }], - ['state', { watchDepth: 'reference' }], - ['timefilterUpdateHandler', { watchDepth: 'reference' }], - ['timeRange', { watchDepth: 'reference' }], - ['topNavMenu', { watchDepth: 'reference' }], - ['updateQuery', { watchDepth: 'reference' }], - ['updateSavedQueryId', { watchDepth: 'reference' }], - ['vis', { watchDepth: 'reference' }], - ]); -} diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index b9cd40f5fc2b..bf6e5886035c 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -24,9 +24,9 @@ import { DiscoverSidebar } from './sidebar'; import { DataGridTable } from './data_grid/data_grid_table'; import { getServices, IndexPattern } from '../../opensearch_dashboards_services'; // @ts-ignore -import { DiscoverNoResults } from '../angular/directives/no_results'; -import { DiscoverUninitialized } from '../angular/directives/uninitialized'; -import { DiscoverHistogram } from '../angular/directives/histogram'; +import { DiscoverNoResults } from './no_results/no_results'; +import { DiscoverUninitialized } from './uninitialized/uninitialized'; +import { DiscoverHistogram } from './histogram/histogram'; import { LoadingSpinner } from './loading_spinner/loading_spinner'; import { SkipBottomButton } from './skip_bottom_button'; import { @@ -37,8 +37,9 @@ import { Query, IndexPatternAttributes, } from '../../../../data/public'; -import { Chart } from '../angular/helpers/point_series'; -import { AppState } from '../angular/discover_state'; +import { Chart } from './chart/point_series'; +// TODO: Move to Data Explorer?? +// import { AppState } from '../angular/discover_state'; import { SavedSearch } from '../../saved_searches'; import { SavedObject } from '../../../../../core/types'; diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_tab.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_tab.tsx index edc7f40c5e43..17736de5de2a 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_tab.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_tab.tsx @@ -37,7 +37,6 @@ interface Props { } /** * Responsible for rendering a tab provided by a render function. - * So any other framework can be used (E.g. legacy Angular 3rd party plugin code) * The provided `render` function is called with a reference to the * component's `HTMLDivElement` as 1st arg and `renderProps` as 2nd arg */ diff --git a/src/plugins/discover/public/application/components/help_menu/help_menu_util.js b/src/plugins/discover/public/application/components/help_menu/help_menu_util.ts similarity index 92% rename from src/plugins/discover/public/application/components/help_menu/help_menu_util.js rename to src/plugins/discover/public/application/components/help_menu/help_menu_util.ts index 39ea94046d7c..47bf0dbdcf90 100644 --- a/src/plugins/discover/public/application/components/help_menu/help_menu_util.js +++ b/src/plugins/discover/public/application/components/help_menu/help_menu_util.ts @@ -29,10 +29,12 @@ */ import { i18n } from '@osd/i18n'; +import { ChromeStart } from 'opensearch-dashboards/public'; import { getServices } from '../../../opensearch_dashboards_services'; + const { docLinks } = getServices(); -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome: ChromeStart) { chrome.setHelpExtension({ appName: i18n.translate('discover.helpMenu.appName', { defaultMessage: 'Discover', diff --git a/src/plugins/discover/public/application/components/histogram/histogram.tsx b/src/plugins/discover/public/application/components/histogram/histogram.tsx new file mode 100644 index 000000000000..5cf9ba88e878 --- /dev/null +++ b/src/plugins/discover/public/application/components/histogram/histogram.tsx @@ -0,0 +1,363 @@ +/* + * 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. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; +import moment from 'moment-timezone'; +import { unitOfTime } from 'moment'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; +import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; + +import { + AnnotationDomainType, + Axis, + Chart, + HistogramBarSeries, + LineAnnotation, + Position, + ScaleType, + Settings, + RectAnnotation, + TooltipValue, + TooltipType, + ElementClickListener, + XYChartElementEvent, + BrushEndListener, + Theme, +} from '@elastic/charts'; + +import { i18n } from '@osd/i18n'; +import { IUiSettingsClient } from 'opensearch-dashboards/public'; +import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme'; +import { Subscription, combineLatest } from 'rxjs'; +import { getServices } from '../../../opensearch_dashboards_services'; +import { Chart as IChart } from '../helpers/point_series'; + +export interface DiscoverHistogramProps { + chartData: IChart; + timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; +} + +interface DiscoverHistogramState { + chartsTheme: EuiChartThemeType['theme']; + chartsBaseTheme: Theme; +} + +function findIntervalFromDuration( + dateValue: number, + opensearchValue: number, + opensearchUnit: unitOfTime.Base, + timeZone: string +) { + const date = moment.tz(dateValue, timeZone); + const startOfDate = moment.tz(date, timeZone).startOf(opensearchUnit); + const endOfDate = moment + .tz(date, timeZone) + .startOf(opensearchUnit) + .add(opensearchValue, opensearchUnit); + return endOfDate.valueOf() - startOfDate.valueOf(); +} + +function getIntervalInMs( + value: number, + opensearchValue: number, + opensearchUnit: unitOfTime.Base, + timeZone: string +): number { + switch (opensearchUnit) { + case 's': + return 1000 * opensearchValue; + case 'ms': + return 1 * opensearchValue; + default: + return findIntervalFromDuration(value, opensearchValue, opensearchUnit, timeZone); + } +} + +function getTimezone(uiSettings: IUiSettingsClient) { + if (uiSettings.isDefault('dateFormat:tz')) { + const detectedTimezone = moment.tz.guess(); + if (detectedTimezone) return detectedTimezone; + else return moment().format('Z'); + } else { + return uiSettings.get('dateFormat:tz', 'Browser'); + } +} + +export function findMinInterval( + xValues: number[], + opensearchValue: number, + opensearchUnit: string, + timeZone: string +): number { + return xValues.reduce((minInterval, currentXvalue, index) => { + let currentDiff = minInterval; + if (index > 0) { + currentDiff = Math.abs(xValues[index - 1] - currentXvalue); + } + const singleUnitInterval = getIntervalInMs( + currentXvalue, + opensearchValue, + opensearchUnit as unitOfTime.Base, + timeZone + ); + return Math.min(minInterval, singleUnitInterval, currentDiff); + }, Number.MAX_SAFE_INTEGER); +} + +export class DiscoverHistogram extends Component { + public static propTypes = { + chartData: PropTypes.object, + timefilterUpdateHandler: PropTypes.func, + }; + + private subscription?: Subscription; + public state = { + chartsTheme: getServices().theme.chartsDefaultTheme, + chartsBaseTheme: getServices().theme.chartsDefaultBaseTheme, + }; + + componentDidMount() { + this.subscription = combineLatest( + getServices().theme.chartsTheme$, + getServices().theme.chartsBaseTheme$ + ).subscribe(([chartsTheme, chartsBaseTheme]) => + this.setState({ chartsTheme, chartsBaseTheme }) + ); + } + + componentWillUnmount() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public onBrushEnd: BrushEndListener = ({ x }) => { + if (!x) { + return; + } + const [from, to] = x; + this.props.timefilterUpdateHandler({ from, to }); + }; + + public onElementClick = (xInterval: number): ElementClickListener => ([elementData]) => { + const startRange = (elementData as XYChartElementEvent)[0].x; + + const range = { + from: startRange, + to: startRange + xInterval, + }; + + this.props.timefilterUpdateHandler(range); + }; + + public formatXValue = (val: string) => { + const xAxisFormat = this.props.chartData.xAxisFormat.params!.pattern; + + return moment(val).format(xAxisFormat); + }; + + public renderBarTooltip = (xInterval: number, domainStart: number, domainEnd: number) => ( + headerData: TooltipValue + ): JSX.Element | string => { + const headerDataValue = headerData.value; + const formattedValue = this.formatXValue(headerDataValue); + + const partialDataText = i18n.translate('discover.histogram.partialData.bucketTooltipText', { + defaultMessage: + 'The selected time range does not include this entire bucket, it may contain partial data.', + }); + + if (headerDataValue < domainStart || headerDataValue + xInterval > domainEnd) { + return ( + + + + + + {partialDataText} + + +

{formattedValue}

+
+ ); + } + + return formattedValue; + }; + + public render() { + const uiSettings = getServices().uiSettings; + const timeZone = getTimezone(uiSettings); + const { chartData } = this.props; + const { chartsTheme, chartsBaseTheme } = this.state; + + if (!chartData) { + return null; + } + + const data = chartData.values; + + /** + * Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval]. + * see https://github.com/elastic/kibana/issues/27410 + * TODO: Once the Discover query has been update, we should change the below to use the new field + */ + const { intervalOpenSearchValue, intervalOpenSearchUnit, interval } = chartData.ordered; + const xInterval = interval.asMilliseconds(); + + const xValues = chartData.xAxisOrderedValues; + const lastXValue = xValues[xValues.length - 1]; + + const domain = chartData.ordered; + const domainStart = domain.min.valueOf(); + const domainEnd = domain.max.valueOf(); + + const domainMin = data[0]?.x > domainStart ? domainStart : data[0]?.x; + const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue; + + const xDomain = { + min: domainMin, + max: domainMax, + minInterval: findMinInterval( + xValues, + intervalOpenSearchValue, + intervalOpenSearchUnit, + timeZone + ), + }; + + // Domain end of 'now' will be milliseconds behind current time, so we extend time by 1 minute and check if + // the annotation is within this range; if so, the line annotation uses the domainEnd as its value + const now = moment(); + const isAnnotationAtEdge = moment(domainEnd).add(60000).isAfter(now) && now.isAfter(domainEnd); + const lineAnnotationValue = isAnnotationAtEdge ? domainEnd : now; + + const lineAnnotationData = [ + { + dataValue: lineAnnotationValue, + }, + ]; + const isDarkMode = uiSettings.get('theme:darkMode'); + + const lineAnnotationStyle = { + line: { + strokeWidth: 2, + stroke: isDarkMode ? darkEuiTheme.euiColorDanger : lightEuiTheme.euiColorDanger, + opacity: 0.7, + }, + }; + + const rectAnnotations = []; + if (domainStart !== domainMin) { + rectAnnotations.push({ + coordinates: { + x1: domainStart, + }, + }); + } + if (domainEnd !== domainMax) { + rectAnnotations.push({ + coordinates: { + x0: domainEnd, + }, + }); + } + + const rectAnnotationStyle = { + stroke: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, + strokeWidth: 0, + opacity: isDarkMode ? 0.6 : 0.2, + fill: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, + }; + + const tooltipProps = { + headerFormatter: this.renderBarTooltip(xInterval, domainStart, domainEnd), + type: TooltipType.VerticalCursor, + }; + + return ( + + + + + + + + + ); + } +} diff --git a/src/plugins/discover/public/application/components/no_results/no_results.tsx b/src/plugins/discover/public/application/components/no_results/no_results.tsx new file mode 100644 index 000000000000..79b3191e5eda --- /dev/null +++ b/src/plugins/discover/public/application/components/no_results/no_results.tsx @@ -0,0 +1,215 @@ +/* + * 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. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Component, Fragment } from 'react'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; +import PropTypes from 'prop-types'; + +import { + EuiCallOut, + EuiCode, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { getServices } from '../../../opensearch_dashboards_services'; + +export const DiscoverNoResults = (timeFieldName: string, queryLanguage: any) => { + let timeFieldMessage; + + if (timeFieldName) { + timeFieldMessage = ( + + + + +

+ +

+ +

+ +

+
+
+ ); + } + + let luceneQueryMessage; + + if (queryLanguage === 'lucene') { + const searchExamples = [ + { + description: 200, + title: ( + + + + + + ), + }, + { + description: status:200, + title: ( + + + + + + ), + }, + { + description: status:[400 TO 499], + title: ( + + + + + + ), + }, + { + description: status:[400 TO 499] AND extension:PHP, + title: ( + + + + + + ), + }, + { + description: status:[400 TO 499] AND (extension:php OR extension:html), + title: ( + + + + + + ), + }, + ]; + + luceneQueryMessage = ( + + + + +

+ +

+ +

+ + + + ), + }} + /> +

+
+ + + + + + +
+ ); + } + + return ( + + + + + + + + } + color="warning" + iconType="help" + data-test-subj="discoverNoResults" + /> + {timeFieldMessage} + {luceneQueryMessage} + + + + + ); +}; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index 4a1390cb1955..4de6f76b792a 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -106,12 +106,6 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { missing: true, }); - if (typeof value !== 'string') { - // at initial rendering value is undefined (angular related), this catches the warning - // should be removed once all is react - return null; - } - const filterBtnAriaLabel = isPopoverOpen ? i18n.translate('discover.fieldChooser.toggleFieldFilterButtonHideAriaLabel', { defaultMessage: 'Hide field filter settings', diff --git a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx index 95154bec1939..c8487732471a 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.tsx @@ -41,7 +41,7 @@ export interface DiscoverIndexPatternProps { */ indexPatternList: Array>; /** - * currently selected index pattern, due to angular issues it's undefined at first rendering + * currently selected index pattern */ selectedIndexPattern: IIndexPattern; /** diff --git a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.tsx b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.tsx index a1e5754cb312..a780752fc54c 100644 --- a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.tsx +++ b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.tsx @@ -49,7 +49,7 @@ export function SkipBottomButton({ onClick }: SkipBottomButtonProps) { // prevent the anchor to reload the page on click event.preventDefault(); // The destinationId prop cannot be leveraged here as the table needs - // to be updated first (angular logic) + // to be updated first onClick(); }} className="dscSkipButton" diff --git a/src/plugins/discover/public/application/components/top_nav/open_search_panel.test.js b/src/plugins/discover/public/application/components/top_nav/open_search_panel.test.tsx similarity index 100% rename from src/plugins/discover/public/application/components/top_nav/open_search_panel.test.js rename to src/plugins/discover/public/application/components/top_nav/open_search_panel.test.tsx diff --git a/src/plugins/discover/public/application/components/top_nav/open_search_panel.js b/src/plugins/discover/public/application/components/top_nav/open_search_panel.tsx similarity index 97% rename from src/plugins/discover/public/application/components/top_nav/open_search_panel.js rename to src/plugins/discover/public/application/components/top_nav/open_search_panel.tsx index f575b8dee625..f2aaba39be0b 100644 --- a/src/plugins/discover/public/application/components/top_nav/open_search_panel.js +++ b/src/plugins/discover/public/application/components/top_nav/open_search_panel.tsx @@ -115,8 +115,3 @@ export function OpenSearchPanel(props) { ); } - -OpenSearchPanel.propTypes = { - onClose: PropTypes.func.isRequired, - makeUrl: PropTypes.func.isRequired, -}; diff --git a/src/plugins/discover/public/application/components/top_nav/show_open_search_panel.js b/src/plugins/discover/public/application/components/top_nav/show_open_search_panel.tsx similarity index 89% rename from src/plugins/discover/public/application/components/top_nav/show_open_search_panel.js rename to src/plugins/discover/public/application/components/top_nav/show_open_search_panel.tsx index 8cb550f49f16..12fa150c99db 100644 --- a/src/plugins/discover/public/application/components/top_nav/show_open_search_panel.js +++ b/src/plugins/discover/public/application/components/top_nav/show_open_search_panel.tsx @@ -31,10 +31,17 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { OpenSearchPanel } from './open_search_panel'; +import { I18nStart } from '../../../../../../core/public'; let isOpen = false; -export function showOpenSearchPanel({ makeUrl, I18nContext }) { +export function showOpenSearchPanel({ + makeUrl, + I18nContext, +}: { + makeUrl: (id: string) => void; + I18nContext: I18nStart['Context']; +}) { if (isOpen) { return; } diff --git a/src/plugins/discover/public/application/components/uninitialized/uninitialized.tsx b/src/plugins/discover/public/application/components/uninitialized/uninitialized.tsx new file mode 100644 index 000000000000..9cc47b034d1e --- /dev/null +++ b/src/plugins/discover/public/application/components/uninitialized/uninitialized.tsx @@ -0,0 +1,78 @@ +/* + * 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. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; + +import { EuiButton, EuiEmptyPrompt, EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; + +interface Props { + onRefresh: () => void; +} + +export const DiscoverUninitialized = ({ onRefresh }: Props) => { + return ( + + + + + + + + } + body={ +

+ +

+ } + actions={ + + + + } + /> +
+
+
+
+ ); +}; diff --git a/src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx b/src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx deleted file mode 100644 index e113f6961fc3..000000000000 --- a/src/plugins/discover/public/application/doc_views/doc_views_helpers.tsx +++ /dev/null @@ -1,105 +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. - * - * Any modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { auto, IController } from 'angular'; -import React from 'react'; -import { render } from 'react-dom'; -import angular, { ICompileService } from 'angular'; -import { DocViewRenderProps, AngularScope, AngularDirective } from './doc_views_types'; -import { DocViewerError } from '../components/doc_viewer/doc_viewer_render_error'; - -/** - * Compiles and injects the give angular template into the given dom node - * returns a function to cleanup the injected angular element - */ -export async function injectAngularElement( - domNode: Element, - template: string, - scopeProps: DocViewRenderProps, - Controller: IController, - getInjector: () => Promise -): Promise<() => void> { - const $injector = await getInjector(); - const rootScope: AngularScope = $injector.get('$rootScope'); - const $compile: ICompileService = $injector.get('$compile'); - const newScope = Object.assign(rootScope.$new(), scopeProps); - - if (typeof Controller === 'function') { - // when a controller is defined, expose the value it produces to the view as `$ctrl` - // see: https://docs.angularjs.org/api/ng/provider/$compileProvider#component - (newScope as any).$ctrl = $injector.instantiate(Controller, { - $scope: newScope, - }); - } - - const $target = angular.element(domNode); - const $element = angular.element(template); - - newScope.$apply(() => { - const linkFn = $compile($element); - $target.empty().append($element); - linkFn(newScope); - }); - - return () => { - newScope.$destroy(); - }; -} -/** - * Converts a given legacy angular directive to a render function - * for usage in a react component. Note that the rendering is async - */ -export function convertDirectiveToRenderFn( - directive: AngularDirective, - getInjector: () => Promise -) { - return (domNode: Element, props: DocViewRenderProps) => { - let rejected = false; - - const cleanupFnPromise = injectAngularElement( - domNode, - directive.template, - props, - directive.controller, - getInjector - ); - cleanupFnPromise.catch((e) => { - rejected = true; - render(, domNode); - }); - - return () => { - if (!rejected) { - // for cleanup - cleanupFnPromise.then((cleanup) => cleanup()); - } - }; - }; -} diff --git a/src/plugins/discover/public/application/doc_views/doc_views_types.ts b/src/plugins/discover/public/application/doc_views/doc_views_types.ts index bf027d32848a..db9757d385b0 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_types.ts +++ b/src/plugins/discover/public/application/doc_views/doc_views_types.ts @@ -29,17 +29,9 @@ */ import { ComponentType } from 'react'; -import { IScope } from 'angular'; import { SearchResponse } from 'elasticsearch'; import { IndexPattern } from '../../../../data/public'; -export interface AngularDirective { - controller: (...injectedServices: any[]) => void; - template: string; -} - -export type AngularScope = IScope; - export type OpenSearchSearchHit = SearchResponse['hits']['hits'][number]; export interface FieldMapping { diff --git a/src/plugins/discover/public/application/index.scss b/src/plugins/discover/public/application/index.scss deleted file mode 100644 index b9f191ac6d69..000000000000 --- a/src/plugins/discover/public/application/index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "angular/index"; -@import "discover"; diff --git a/src/plugins/discover/public/opensearch_dashboards_services.ts b/src/plugins/discover/public/opensearch_dashboards_services.ts index 8531564e0cc7..205a345acc33 100644 --- a/src/plugins/discover/public/opensearch_dashboards_services.ts +++ b/src/plugins/discover/public/opensearch_dashboards_services.ts @@ -36,26 +36,10 @@ import { DiscoverServices } from './build_services'; import { createGetterSetter } from '../../opensearch_dashboards_utils/public'; import { search } from '../../data/public'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; -import { DocViewsLinksRegistry } from './application/doc_views_links/doc_views_links_registry'; -let angularModule: any = null; let services: DiscoverServices | null = null; let uiActions: UiActionsStart; -/** - * set bootstrapped inner angular module - */ -export function setAngularModule(module: any) { - angularModule = module; -} - -/** - * get boostrapped inner angular module - */ -export function getAngularModule() { - return angularModule; -} - export function getServices(): DiscoverServices { if (!services) { throw new Error('Discover services are not yet available'); @@ -83,9 +67,6 @@ export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter('DocViewsLinksRegistry'); /** * Makes sure discover and context are using one instance of history. */ diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 1070461abeb6..41f872322b7d 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -53,22 +53,17 @@ import { import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public'; -import { stringify } from 'query-string'; -import rison from 'rison-node'; import { DataPublicPluginStart, DataPublicPluginSetup, opensearchFilters } from '../../data/public'; import { SavedObjectLoader } from '../../saved_objects/public'; import { createOsdUrlTracker, url } from '../../opensearch_dashboards_utils/public'; import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; -import { DocViewLink } from './application/doc_views_links/doc_views_links_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; -import { DocViewsLinksRegistry } from './application/doc_views_links/doc_views_links_registry'; import { DocViewTable } from './application/components/table/table'; import { JsonCodeBlock } from './application/components/json_code_block/json_code_block'; import { setDocViewsRegistry, - setDocViewsLinksRegistry, setUrlTracker, setServices, setHeaderActionMenuMounter, @@ -105,15 +100,10 @@ export interface DiscoverSetup { docViews: { /** * Add new doc view shown along with table view and json view in the details of each document in Discover. - * Both react and angular doc views are supported. * @param docViewRaw */ addDocView(docViewRaw: DocViewInput | DocViewInputFn): void; }; - - docViewsLinks: { - addDocViewLink(docViewLinkRaw: DocViewLink): void; - }; } export interface DiscoverStart { @@ -169,12 +159,9 @@ export interface DiscoverStartPlugins { visualizations: VisualizationsStart; } -const embeddableAngularName = 'app/discoverEmbeddable'; - /** * Contains Discover, one of the oldest parts of OpenSearch Dashboards - * There are 2 kinds of Angular bootstrapped for rendering, additionally to the main Angular - * Discover provides embeddables, those contain a slimmer Angular + * Discover provides embeddables for Dashboards */ export class DiscoverPlugin implements Plugin { @@ -182,7 +169,6 @@ export class DiscoverPlugin private appStateUpdater = new BehaviorSubject(() => ({})); private docViewsRegistry: DocViewsRegistry | null = null; - private docViewsLinksRegistry: DocViewsLinksRegistry | null = null; private stopUrlTracking: (() => void) | undefined = undefined; private servicesInitialized: boolean = false; private urlGenerator?: DiscoverStart['urlGenerator']; @@ -218,52 +204,6 @@ export class DiscoverPlugin component: JsonCodeBlock, }); - this.docViewsLinksRegistry = new DocViewsLinksRegistry(); - setDocViewsLinksRegistry(this.docViewsLinksRegistry); - - this.docViewsLinksRegistry.addDocViewLink({ - label: i18n.translate('discover.docTable.tableRow.viewSurroundingDocumentsLinkText', { - defaultMessage: 'View surrounding documents', - }), - generateCb: (renderProps: any) => { - const globalFilters: any = getServices().filterManager.getGlobalFilters(); - const appFilters: any = getServices().filterManager.getAppFilters(); - - const hash = stringify( - url.encodeQuery({ - _g: rison.encode({ - filters: globalFilters || [], - }), - _a: rison.encode({ - columns: renderProps.columns, - filters: (appFilters || []).map(opensearchFilters.disableFilter), - }), - }), - { encode: false, sort: false } - ); - - return { - url: `#/context/${encodeURIComponent(renderProps.indexPattern.id)}/${encodeURIComponent( - renderProps.hit._id - )}?${hash}`, - hide: !renderProps.indexPattern.isTimeBased(), - }; - }, - order: 1, - }); - - this.docViewsLinksRegistry.addDocViewLink({ - label: i18n.translate('discover.docTable.tableRow.viewSingleDocumentLinkText', { - defaultMessage: 'View single document', - }), - generateCb: (renderProps) => ({ - url: `#/doc/${renderProps.indexPattern.id}/${ - renderProps.hit._index - }?id=${encodeURIComponent(renderProps.hit._id)}`, - }), - order: 2, - }); - const { appMounted, appUnMounted, @@ -403,9 +343,6 @@ export class DiscoverPlugin docViews: { addDocView: this.docViewsRegistry.addDocView.bind(this.docViewsRegistry), }, - docViewsLinks: { - addDocViewLink: this.docViewsLinksRegistry.addDocViewLink.bind(this.docViewsLinksRegistry), - }, }; }