diff --git a/src/legacy/core_plugins/kibana/public/discover/components/histogram/_index.scss b/src/legacy/core_plugins/kibana/public/discover/components/histogram/_index.scss new file mode 100644 index 000000000000000..8baf8fa44d5bd86 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/components/histogram/_index.scss @@ -0,0 +1,3 @@ +.dscHistogram__header--partial { + font-weight: $euiFontWeightRegular; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/histogram/directive.js b/src/legacy/core_plugins/kibana/public/discover/components/histogram/directive.js new file mode 100644 index 000000000000000..29b7c1d98a78dad --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/components/histogram/directive.js @@ -0,0 +1,28 @@ +/* + * 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 'ngreact'; +import { uiModules } from 'ui/modules'; +import { DiscoverHistogram } from './histogram'; + +import { wrapInI18nContext } from 'ui/i18n'; + +const app = uiModules.get('apps/discover', ['react']); + +app.directive('discoverHistogram', reactDirective => reactDirective(wrapInI18nContext(DiscoverHistogram))); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/histogram/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/components/histogram/histogram.tsx new file mode 100644 index 000000000000000..dd328f1bca23540 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/components/histogram/histogram.tsx @@ -0,0 +1,259 @@ +/* + * 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 React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { + AnnotationDomainTypes, + Axis, + Chart, + HistogramBarSeries, + getAnnotationId, + getAxisId, + getSpecId, + LineAnnotation, + Position, + ScaleType, + Settings, + RectAnnotation, + TooltipValue, +} from '@elastic/charts'; + +import { i18n } from '@kbn/i18n'; + +import { getChartTheme } from 'ui/elastic_charts'; +import chrome from 'ui/chrome'; + +export interface DiscoverHistogramProps { + chartData: any; + visLib: any; +} + +const getTimezone = () => { + const config = chrome.getUiSettingsClient(); + const DATE_FORMAT_CONFIG_KEY = 'dateFormat:tz'; + const isCustomTimezone = !config.isDefault(DATE_FORMAT_CONFIG_KEY); + if (isCustomTimezone) { + return config.get(DATE_FORMAT_CONFIG_KEY); + } + + const detectedTimezone = moment.tz.guess(); + if (detectedTimezone) { + return detectedTimezone; + } + // default to UTC if we can't figure out the timezone + const tzOffset = moment().format('Z'); + return tzOffset; +}; + +export class DiscoverHistogram extends Component { + static propTypes = { + chartData: PropTypes.object, + visLib: PropTypes.object, + }; + + render() { + const { visLib, chartData } = this.props; + + if (!this.props.chartData || !this.props.chartData.series[0]) { + return null; + } + + const appState = visLib.API.getAppState(); + + const data = chartData.series[0].values; + const format = chartData.xAxisFormat.params.pattern; + + /** + * Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval]. + * see https://github.com/elastic/kibana/issues/27410 + */ + const xInterval = chartData.ordered.interval; // We can remove this line only for versions > 8.x + + const xValues = chartData.xAxisOrderedValues; + const lastXValue = xValues[xValues.length - 1]; + + const formatter = (val: string) => { + return moment(val).format(format); + }; + + 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: xInterval, + }; + + // Duplicated from point_series.js + // Domain end of 'now' will be milliseconds behind current time + // Extend toTime by 1 minute to ensure those cases have a TimeMarker + const now = moment(); + const isAnnotationAtEdge = domainEnd + 60000 > now && now > domainEnd; + const lineAnnotationValue = isAnnotationAtEdge ? domainEnd : now; + + const currentTime = { + dataValue: lineAnnotationValue, + }; + + const lineAnnotationStyle = { + line: { + strokeWidth: 2, + stroke: '#c80000', + opacity: 0.3, + }, + }; + + const partialAnnotationText = i18n.translate( + 'kbn.discover.histogram.partialData.annotationText', + { + defaultMessage: + 'This area may contain partial data. The selected time range does not fully cover it.', + } + ); + + const rectAnnotations = [ + { + coordinates: { + x0: domainStart, + }, + details: partialAnnotationText, + }, + { + coordinates: { + x1: domainEnd, + }, + details: partialAnnotationText, + }, + ]; + + const rectAnnotationStyle = { + stroke: 'rgba(0, 0, 0, 0)', + strokeWidth: 1, + opacity: 1, + fill: 'rgba(0, 0, 0, 0.1)', + }; + + const customTooltip = (details?: string) => ( +
+ + + + + {details} + +
+ ); + + const onBrushEnd = (min: number, max: number) => { + const brushData = { + aggConfigs: visLib.aggs, + data: chartData, + range: [min, max], + }; + + visLib.API.events.brush(brushData, appState); + }; + + const partialDataText = i18n.translate('kbn.discover.histogram.partialData.bucketTooltipText', { + defaultMessage: + 'Part of this bucket may contain partial data. The selected time range does not fully cover it.', + }); + + const tooltipHeaderFormater = (headerData: TooltipValue): JSX.Element | string => { + const headerDataValue = headerData.value; + const formattedValue = formatter(headerDataValue); + + if (headerDataValue < domainStart || headerDataValue + xInterval > domainEnd) { + return ( + + + + + + {partialDataText} + + +

{formattedValue}

+
+ ); + } + + return formattedValue; + }; + + const tooltipProps = { + headerFormatter: tooltipHeaderFormater, + }; + + return ( + + + + + + + + + ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/histogram/index.js b/src/legacy/core_plugins/kibana/public/discover/components/histogram/index.js new file mode 100644 index 000000000000000..b8fdf0672964098 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/components/histogram/index.js @@ -0,0 +1,20 @@ +/* + * 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 './directive'; diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index 124729c36ced75c..05127d194b9c6e8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -57,6 +57,7 @@ import { VisualizeLoaderProvider } from 'ui/visualize/loader/visualize_loader'; import { recentlyAccessed } from 'ui/persisted_log'; import { getDocLink } from 'ui/documentation_links'; import '../components/fetch_error'; +import '../components/histogram'; import { getPainlessError } from './get_painless_error'; import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; @@ -747,6 +748,7 @@ function discoverController( .resolve(buildVislibDimensions($scope.vis, { timeRange: $scope.timeRange, searchSource: $scope.searchSource })) .then(resp => responseHandler(tabifiedData, resp)) .then(resp => { + $scope.histogramData = resp; visualizeHandler.render({ as: 'visualization', value: { diff --git a/src/legacy/core_plugins/kibana/public/discover/index.html b/src/legacy/core_plugins/kibana/public/discover/index.html index 3a161c4bb780bd9..781aff5d4e33431 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.html +++ b/src/legacy/core_plugins/kibana/public/discover/index.html @@ -166,6 +166,14 @@

+