mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Add responsive layout to Index data visualizer, fix doc count chart margin (#147137)
## Summary This PR addresses https://github.com/elastic/kibana/issues/137257 and better handles the layout when the screen width decreases. Changes include: - The css is now updating based on the body content width (which window's width - side bar navigation's width). - The link cards are shown in the bottom instead of on the right side when the content width is smaller - Refresh button will only show the refresh icon when the size is small - Fix the doc count chart margin too big causing the chart - Fix wide time range display with sparse data For reviewers: - **kibana-design**: 2 `.scss` files were deleted Before After <img width="1223" alt="Screen Shot 2022-12-06 at 14 31 27" src="https://user-images.githubusercontent.com/43350163/206016842-f884ee1f-eb60-4c83-9ad3-2fd0f7c90005.png"> ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
3a5f5626b2
commit
602b6d645a
21 changed files with 303 additions and 282 deletions
|
@ -34,6 +34,7 @@ import { dataVisualizerRefresh$ } from '../../../index_data_visualizer/services/
|
|||
import { useUrlState } from '../../util/url_state';
|
||||
|
||||
const DEFAULT_REFRESH_INTERVAL_MS = 5000;
|
||||
const DATE_PICKER_MAX_WIDTH = 540;
|
||||
|
||||
interface TimePickerQuickRange {
|
||||
from: string;
|
||||
|
@ -69,10 +70,11 @@ function updateLastRefresh(timeRange?: OnRefreshProps) {
|
|||
}
|
||||
|
||||
// FIXME: Consolidate this component with ML and AIOps's component
|
||||
export const DatePickerWrapper: FC<{ isAutoRefreshOnly?: boolean; showRefresh?: boolean }> = ({
|
||||
isAutoRefreshOnly,
|
||||
showRefresh,
|
||||
}) => {
|
||||
export const DatePickerWrapper: FC<{
|
||||
isAutoRefreshOnly?: boolean;
|
||||
showRefresh?: boolean;
|
||||
compact?: boolean;
|
||||
}> = ({ isAutoRefreshOnly, showRefresh, compact = false }) => {
|
||||
const {
|
||||
services,
|
||||
notifications: { toasts },
|
||||
|
@ -242,9 +244,18 @@ export const DatePickerWrapper: FC<{ isAutoRefreshOnly?: boolean; showRefresh?:
|
|||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
className="mlNavigationMenu__datePickerWrapper"
|
||||
data-test-subj="mlNavigationMenuDatePickerWrapper"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={
|
||||
compact
|
||||
? {
|
||||
maxWidth: DATE_PICKER_MAX_WIDTH,
|
||||
}
|
||||
: null
|
||||
}
|
||||
>
|
||||
<EuiSuperDatePicker
|
||||
start={time.from}
|
||||
end={time.to}
|
||||
|
@ -257,6 +268,7 @@ export const DatePickerWrapper: FC<{ isAutoRefreshOnly?: boolean; showRefresh?:
|
|||
recentlyUsedRanges={recentlyUsedRanges}
|
||||
dateFormat={dateFormat}
|
||||
commonlyUsedRanges={commonlyUsedRanges}
|
||||
updateButtonProps={{ iconOnly: compact }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { FC, useCallback, useMemo } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
Axis,
|
||||
BarSeries,
|
||||
HistogramBarSeries,
|
||||
BrushEndListener,
|
||||
Chart,
|
||||
ElementClickListener,
|
||||
|
@ -22,7 +22,7 @@ import {
|
|||
import moment from 'moment';
|
||||
import { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common';
|
||||
import { EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui';
|
||||
import { useDataVisualizerKibana } from '../../../../kibana_context';
|
||||
|
||||
export interface DocumentCountChartPoint {
|
||||
|
@ -137,8 +137,9 @@ export const DocumentCountChart: FC<Props> = ({
|
|||
const timeZone = getTimezone(uiSettings);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ width: width ?? '100%', height: 120, display: 'flex', alignItems: 'center' }}
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
css={{ width: width ?? '100%' }}
|
||||
data-test-subj="dataVisualizerDocumentCountChart"
|
||||
>
|
||||
{loading ? (
|
||||
|
@ -147,6 +148,7 @@ export const DocumentCountChart: FC<Props> = ({
|
|||
<Chart
|
||||
size={{
|
||||
width: '100%',
|
||||
height: 120,
|
||||
}}
|
||||
>
|
||||
<Settings
|
||||
|
@ -161,11 +163,13 @@ export const DocumentCountChart: FC<Props> = ({
|
|||
position={Position.Bottom}
|
||||
showOverlappingTicks={true}
|
||||
tickFormat={(value) => xAxisFormatter.convert(value)}
|
||||
// temporary fix to reduce horizontal chart margin until fixed in Elastic Charts itself
|
||||
labelFormat={useLegacyTimeAxis ? undefined : () => ''}
|
||||
timeAxisLayerCount={useLegacyTimeAxis ? 0 : 2}
|
||||
style={useLegacyTimeAxis ? {} : MULTILAYER_TIME_AXIS_STYLE}
|
||||
/>
|
||||
<Axis id="left" position={Position.Left} />
|
||||
<BarSeries
|
||||
<HistogramBarSeries
|
||||
id={SPEC_ID}
|
||||
name={seriesName}
|
||||
xScaleType={ScaleType.Time}
|
||||
|
@ -174,9 +178,10 @@ export const DocumentCountChart: FC<Props> = ({
|
|||
yAccessors={['value']}
|
||||
data={adjustedChartPoints}
|
||||
timeZone={timeZone}
|
||||
yNice
|
||||
/>
|
||||
</Chart>
|
||||
)}
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -31,6 +31,7 @@ export const FieldCountPanel: FC<Props> = ({
|
|||
data-test-subj="dataVisualizerFieldCountPanel"
|
||||
responsive={false}
|
||||
className="dvFieldCount__panel"
|
||||
wrap
|
||||
>
|
||||
<TotalFieldsCount fieldsCountStats={fieldsCountStats} />
|
||||
<MetricFieldsCount metricsStats={metricsStats} />
|
||||
|
|
|
@ -85,6 +85,7 @@ export const FieldsStatsGrid: FC<Props> = ({ results }) => {
|
|||
gutterSize="xs"
|
||||
style={{ marginLeft: 4 }}
|
||||
data-test-subj="dataVisualizerFieldCountPanel"
|
||||
responsive={true}
|
||||
>
|
||||
<TotalFieldsCount fieldsCountStats={fieldsCountStats} />
|
||||
<MetricFieldsCount metricsStats={metricsStats} />
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
EuiPanel,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { useCurrentEuiTheme } from '../../hooks/use_current_eui_theme';
|
||||
|
||||
export interface LinkCardProps {
|
||||
icon: IconType;
|
||||
|
@ -41,6 +42,8 @@ export const LinkCard: FC<LinkCardProps> = ({
|
|||
isDisabled,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const euiTheme = useCurrentEuiTheme();
|
||||
|
||||
const linkHrefAndOnClickProps = {
|
||||
...(href ? { href } : {}),
|
||||
...(onClick ? { onClick } : {}),
|
||||
|
@ -62,10 +65,10 @@ export const LinkCard: FC<LinkCardProps> = ({
|
|||
color="subdued"
|
||||
{...linkHrefAndOnClickProps}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="l" responsive={true}>
|
||||
<EuiFlexItem grow={false} style={{ paddingTop: '8px' }}>
|
||||
<EuiFlexGroup gutterSize="s" responsive={true}>
|
||||
<EuiFlexItem grow={false} css={{ paddingTop: euiTheme.euiSizeXS }}>
|
||||
{typeof icon === 'string' ? (
|
||||
<EuiIcon size="xl" type={icon} aria-label={iconAreaLabel} />
|
||||
<EuiIcon size="m" type={icon} aria-label={iconAreaLabel} />
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
|
|
|
@ -15,10 +15,11 @@ import {
|
|||
EuiPopoverTitle,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React, { FC, ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import React, { FC, ReactNode, useEffect, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { euiDarkVars as euiThemeDark, euiLightVars as euiThemeLight } from '@kbn/ui-theme';
|
||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
||||
import type { SerializedStyles } from '@emotion/react';
|
||||
import { css } from '@emotion/react';
|
||||
import { useCurrentEuiTheme } from '../../hooks/use_current_eui_theme';
|
||||
|
||||
export interface Option {
|
||||
name?: string | ReactNode;
|
||||
|
@ -27,6 +28,8 @@ export interface Option {
|
|||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const SELECT_PICKER_HEIGHT = '250px';
|
||||
|
||||
const NoFilterItems = () => {
|
||||
return (
|
||||
<div className="euiFilterSelect__note">
|
||||
|
@ -44,15 +47,10 @@ const NoFilterItems = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export function useCurrentEuiTheme() {
|
||||
const { services } = useDataVisualizerKibana();
|
||||
const uiSettings = services.uiSettings;
|
||||
return useMemo(
|
||||
() => (uiSettings.get('theme:darkMode') ? euiThemeDark : euiThemeLight),
|
||||
[uiSettings]
|
||||
);
|
||||
interface MultiSelectPickerStyles {
|
||||
filterGroup?: SerializedStyles;
|
||||
filterItemContainer?: SerializedStyles;
|
||||
}
|
||||
|
||||
export const MultiSelectPicker: FC<{
|
||||
options: Option[];
|
||||
onChange?: (items: string[]) => void;
|
||||
|
@ -60,7 +58,8 @@ export const MultiSelectPicker: FC<{
|
|||
checkedOptions: string[];
|
||||
dataTestSubj: string;
|
||||
postfix?: React.ReactElement;
|
||||
}> = ({ options, onChange, title, checkedOptions, dataTestSubj, postfix }) => {
|
||||
cssStyles?: MultiSelectPickerStyles;
|
||||
}> = ({ options, onChange, title, checkedOptions, dataTestSubj, postfix, cssStyles }) => {
|
||||
const euiTheme = useCurrentEuiTheme();
|
||||
|
||||
const [items, setItems] = useState<Option[]>(options);
|
||||
|
@ -114,7 +113,7 @@ export const MultiSelectPicker: FC<{
|
|||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup data-test-subj={dataTestSubj} style={{ marginLeft: 8 }}>
|
||||
<EuiFilterGroup data-test-subj={dataTestSubj} css={cssStyles?.filterGroup}>
|
||||
<EuiPopover
|
||||
ownFocus
|
||||
data-test-subj={`${dataTestSubj}-popover`}
|
||||
|
@ -130,7 +129,15 @@ export const MultiSelectPicker: FC<{
|
|||
data-test-subj={`${dataTestSubj}-searchInput`}
|
||||
/>
|
||||
</EuiPopoverTitle>
|
||||
<div style={{ maxHeight: 250, overflow: 'auto' }}>
|
||||
<div
|
||||
css={
|
||||
cssStyles?.filterItemContainer ??
|
||||
css`
|
||||
max-height: ${SELECT_PICKER_HEIGHT};
|
||||
overflow: auto;
|
||||
`
|
||||
}
|
||||
>
|
||||
{Array.isArray(items) && items.length > 0 ? (
|
||||
items.map((item, index) => {
|
||||
const checked =
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
|
||||
.dvFieldCount__item {
|
||||
max-width: 300px;
|
||||
min-width: 300px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
|
|
@ -88,7 +88,6 @@ export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
|||
);
|
||||
const [showDistributions, setShowDistributions] = useState<boolean>(showPreviewByDefault ?? true);
|
||||
const [dimensions, setDimensions] = useState(calculateTableColumnsDimensions());
|
||||
const [tableWidth, setTableWidth] = useState<number>(1400);
|
||||
|
||||
const toggleExpandAll = useCallback(
|
||||
(shouldExpandAll: boolean) => {
|
||||
|
@ -109,10 +108,9 @@ export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
|||
throttle((e: { width: number; height: number }) => {
|
||||
// When window or table is resized,
|
||||
// update the column widths and other settings accordingly
|
||||
setTableWidth(e.width);
|
||||
setDimensions(calculateTableColumnsDimensions(e.width));
|
||||
}, 500),
|
||||
[tableWidth]
|
||||
[]
|
||||
);
|
||||
|
||||
const toggleShowDistribution = useCallback(() => {
|
||||
|
@ -138,6 +136,8 @@ export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
|||
const columns = useMemo(() => {
|
||||
const expanderColumn: EuiTableComputedColumnType<DataVisualizerTableItem> = {
|
||||
name:
|
||||
// EUI will automatically show an expander button when table is mobile view (where width <700)
|
||||
// so we need to not render any addition button
|
||||
dimensions.breakPoint !== 'small' ? (
|
||||
<EuiButtonIcon
|
||||
data-test-subj={`dataVisualizerToggleDetailsForAllRowsButton ${
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
*/
|
||||
|
||||
import d3 from 'd3';
|
||||
import { useMemo } from 'react';
|
||||
import { euiLightVars as euiThemeLight, euiDarkVars as euiThemeDark } from '@kbn/ui-theme';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useDataVisualizerKibana } from '../../../../kibana_context';
|
||||
import { useCurrentEuiTheme } from '../../../hooks/use_current_eui_theme';
|
||||
|
||||
/**
|
||||
* Custom color scale factory that takes the amount of feature influencers
|
||||
|
@ -159,7 +158,7 @@ export const useColorRange = (
|
|||
colorRangeScale = COLOR_RANGE_SCALE.LINEAR,
|
||||
featureCount = 1
|
||||
) => {
|
||||
const { euiTheme } = useCurrentEuiTheme();
|
||||
const euiTheme = useCurrentEuiTheme();
|
||||
|
||||
const colorRanges: Record<COLOR_RANGE, string[]> = {
|
||||
[COLOR_RANGE.BLUE]: [
|
||||
|
@ -197,13 +196,3 @@ export const useColorRange = (
|
|||
};
|
||||
|
||||
export type EuiThemeType = typeof euiThemeLight | typeof euiThemeDark;
|
||||
|
||||
export function useCurrentEuiTheme() {
|
||||
const {
|
||||
services: { uiSettings },
|
||||
} = useDataVisualizerKibana();
|
||||
return useMemo(
|
||||
() => ({ euiTheme: uiSettings.get('theme:darkMode') ? euiThemeDark : euiThemeLight }),
|
||||
[uiSettings]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import type { PartialTheme } from '@elastic/charts';
|
||||
import { useMemo } from 'react';
|
||||
import { useCurrentEuiTheme } from './use_color_range';
|
||||
import { useCurrentEuiTheme } from '../../../hooks/use_current_eui_theme';
|
||||
export const useDataVizChartTheme = (): PartialTheme => {
|
||||
const { euiTheme } = useCurrentEuiTheme();
|
||||
const euiTheme = useCurrentEuiTheme();
|
||||
const chartTheme = useMemo<PartialTheme>(() => {
|
||||
const AREA_SERIES_COLOR = euiTheme.euiColorVis0;
|
||||
return {
|
||||
|
|
|
@ -40,7 +40,7 @@ export const getTFPercentage = (config: FileBasedFieldVisConfig) => {
|
|||
// Map of DataVisualizerTable breakpoints specific to the table component
|
||||
// Note that the table width is not always the full width of the browser window
|
||||
const TABLE_BREAKPOINTS = {
|
||||
small: 600,
|
||||
small: 700,
|
||||
medium: 1000,
|
||||
large: Infinity, // default
|
||||
};
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { euiDarkVars as euiThemeDark, euiLightVars as euiThemeLight } from '@kbn/ui-theme';
|
||||
import { useDataVisualizerKibana } from '../../kibana_context';
|
||||
|
||||
export function useCurrentEuiTheme() {
|
||||
const { services } = useDataVisualizerKibana();
|
||||
const uiSettings = services.uiSettings;
|
||||
return useMemo(
|
||||
() => (uiSettings.get('theme:darkMode') ? euiThemeDark : euiThemeLight),
|
||||
[uiSettings]
|
||||
);
|
||||
}
|
|
@ -11,6 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { flatten } from 'lodash';
|
||||
import { LinkCardProps } from '../../../common/components/link_card/link_card';
|
||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
||||
|
@ -24,13 +25,17 @@ interface Props {
|
|||
searchString?: string | { [key: string]: any };
|
||||
searchQueryLanguage?: string;
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const ACTIONS_PANEL_WIDTH = '240px';
|
||||
|
||||
export const ActionsPanel: FC<Props> = ({
|
||||
dataView,
|
||||
searchString,
|
||||
searchQueryLanguage,
|
||||
getAdditionalLinks,
|
||||
compact,
|
||||
}) => {
|
||||
const [globalState] = useUrlState('_g');
|
||||
|
||||
|
@ -112,23 +117,33 @@ export const ActionsPanel: FC<Props> = ({
|
|||
data.query,
|
||||
getAdditionalLinks,
|
||||
]);
|
||||
const showActionsPanel =
|
||||
discoverLink || (Array.isArray(asyncHrefCards) && asyncHrefCards.length > 0);
|
||||
|
||||
// Note we use display:none for the DataRecognizer section as it needs to be
|
||||
// passed the recognizerResults object, and then run the recognizer check which
|
||||
// controls whether the recognizer section is ultimately displayed.
|
||||
return (
|
||||
<div data-test-subj="dataVisualizerActionsPanel">
|
||||
return showActionsPanel ? (
|
||||
<div
|
||||
data-test-subj="dataVisualizerActionsPanel"
|
||||
css={
|
||||
!compact &&
|
||||
css`
|
||||
width: ${ACTIONS_PANEL_WIDTH};
|
||||
`
|
||||
}
|
||||
>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.index.actionsPanel.exploreTitle"
|
||||
defaultMessage="Explore your data"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
{discoverLink && (
|
||||
<>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.index.actionsPanel.exploreTitle"
|
||||
defaultMessage="Explore your data"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<LinkCard
|
||||
href={discoverLink}
|
||||
icon="discoverApp"
|
||||
|
@ -164,5 +179,5 @@ export const ActionsPanel: FC<Props> = ({
|
|||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import 'index_data_visualizer_view';
|
|
@ -1,14 +0,0 @@
|
|||
.dataViewTitleHeader {
|
||||
min-width: 300px;
|
||||
padding: $euiSizeS 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@include euiBreakpoint('xs', 's', 'm', 'l') {
|
||||
.dataVisualizerPageHeader {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment, useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||
import React, { FC, useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -23,6 +23,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { Filter, FilterStateStore, Query } from '@kbn/es-query';
|
||||
import { generateFilters } from '@kbn/data-plugin/public';
|
||||
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { useCurrentEuiTheme } from '../../../common/hooks/use_current_eui_theme';
|
||||
import { DV_RANDOM_SAMPLER_PREFERENCE, useStorage } from '../../hooks/use_storage';
|
||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
||||
import { usePageUrlState, useUrlState } from '../../../common/util/url_state';
|
||||
|
@ -46,14 +47,11 @@ import { kbnTypeToJobType } from '../../../common/util/field_types_utils';
|
|||
import { SearchPanel } from '../search_panel';
|
||||
import { ActionsPanel } from '../actions_panel';
|
||||
import { DatePickerWrapper } from '../../../common/components/date_picker_wrapper';
|
||||
import { HelpMenu } from '../../../common/components/help_menu';
|
||||
import { createMergedEsQuery } from '../../utils/saved_search_utils';
|
||||
import { DataVisualizerDataViewManagement } from '../data_view_management';
|
||||
import { GetAdditionalLinks } from '../../../common/components/results_links';
|
||||
import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data';
|
||||
import { DataVisualizerGridInput } from '../../embeddables/grid_embeddable/grid_embeddable';
|
||||
// TODO port to `@emotion/react` once `useEuiBreakpoint` is available https://github.com/elastic/eui/pull/6057
|
||||
import './_index.scss';
|
||||
import { RANDOM_SAMPLER_OPTION, RandomSamplerOption } from '../../constants/random_sampler';
|
||||
|
||||
interface DataVisualizerPageState {
|
||||
|
@ -116,9 +114,12 @@ export interface IndexDataVisualizerViewProps {
|
|||
currentSavedSearch: SavedSearchSavedObject | null;
|
||||
currentSessionId?: string;
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVisualizerProps) => {
|
||||
const euiTheme = useCurrentEuiTheme();
|
||||
|
||||
const [savedRandomSamplerPreference, saveRandomSamplerPreference] =
|
||||
useStorage<RandomSamplerOption>(
|
||||
DV_RANDOM_SAMPLER_PREFERENCE,
|
||||
|
@ -136,7 +137,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
);
|
||||
|
||||
const { services } = useDataVisualizerKibana();
|
||||
const { docLinks, notifications, uiSettings, data } = services;
|
||||
const { notifications, uiSettings, data } = services;
|
||||
const { toasts } = notifications;
|
||||
|
||||
const [dataVisualizerListState, setDataVisualizerListState] = usePageUrlState(
|
||||
|
@ -149,7 +150,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
dataVisualizerProps.currentSavedSearch
|
||||
);
|
||||
|
||||
const { currentDataView, currentSessionId, getAdditionalLinks } = dataVisualizerProps;
|
||||
const { currentDataView, currentSessionId, getAdditionalLinks, compact } = dataVisualizerProps;
|
||||
|
||||
useEffect(() => {
|
||||
if (dataVisualizerProps?.currentSavedSearch !== undefined) {
|
||||
|
@ -200,7 +201,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
queryLanguage: SearchQueryLanguage;
|
||||
filters: Filter[];
|
||||
}) => {
|
||||
// When the user loads saved search and then clear or modify the query
|
||||
// When the user loads saved search and then clears or modifies the query
|
||||
// we should remove the saved search and replace it with the index pattern id
|
||||
if (currentSavedSearch !== null) {
|
||||
setCurrentSavedSearch(null);
|
||||
|
@ -217,12 +218,6 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
[currentSavedSearch, dataVisualizerListState, setDataVisualizerListState]
|
||||
);
|
||||
|
||||
const samplerShardSize =
|
||||
dataVisualizerListState.samplerShardSize ?? restorableDefaults.samplerShardSize;
|
||||
const setSamplerShardSize = (value: number) => {
|
||||
setDataVisualizerListState({ ...dataVisualizerListState, samplerShardSize: value });
|
||||
};
|
||||
|
||||
const visibleFieldTypes =
|
||||
dataVisualizerListState.visibleFieldTypes ?? restorableDefaults.visibleFieldTypes;
|
||||
const setVisibleFieldTypes = (values: string[]) => {
|
||||
|
@ -386,8 +381,6 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
]
|
||||
);
|
||||
|
||||
const wizardPanelWidth = '280px';
|
||||
|
||||
const fieldsCountStats: TotalFieldsStats | undefined = useMemo(() => {
|
||||
let _visibleFieldsCount = 0;
|
||||
let _totalFieldsCount = 0;
|
||||
|
@ -454,131 +447,136 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
() => currentDataView.timeFieldName !== undefined && currentDataView.timeFieldName !== '',
|
||||
[currentDataView.timeFieldName]
|
||||
);
|
||||
const helpLink = docLinks.links.ml.guide;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiPageBody data-test-subj="dataVisualizerIndexPage" paddingSize="none" panelled={false}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiPageContentHeader className="dataVisualizerPageHeader">
|
||||
<EuiPageContentHeaderSection>
|
||||
<div className="dataViewTitleHeader">
|
||||
<EuiTitle size={'s'}>
|
||||
<h2>{currentDataView.getName()}</h2>
|
||||
</EuiTitle>
|
||||
<DataVisualizerDataViewManagement
|
||||
currentDataView={currentDataView}
|
||||
useNewFieldsApi={true}
|
||||
/>
|
||||
</div>
|
||||
</EuiPageContentHeaderSection>
|
||||
|
||||
<EuiPageBody data-test-subj="dataVisualizerIndexPage" paddingSize="none" panelled={false}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiPageContentHeader
|
||||
data-test-subj="dataVisualizerPageHeader"
|
||||
css={compact ? { flexDirection: 'column', alignItems: 'flex-start' } : null}
|
||||
>
|
||||
<EuiPageContentHeaderSection>
|
||||
<EuiFlexGroup
|
||||
data-test-subj="dataViewTitleHeader"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="s"
|
||||
data-test-subj="dataVisualizerTimeRangeSelectorSection"
|
||||
css={{ padding: `${euiTheme.euiSizeS} 0`, marginRight: `${euiTheme.euiSize}` }}
|
||||
>
|
||||
{hasValidTimeField ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<FullTimeRangeSelector
|
||||
dataView={currentDataView}
|
||||
query={undefined}
|
||||
disabled={false}
|
||||
timefilter={timefilter}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiTitle size={'s'}>
|
||||
<h2>{currentDataView.getName()}</h2>
|
||||
</EuiTitle>
|
||||
<DataVisualizerDataViewManagement
|
||||
currentDataView={currentDataView}
|
||||
useNewFieldsApi={true}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentHeaderSection>
|
||||
|
||||
{compact ? <EuiSpacer size="m" /> : null}
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="s"
|
||||
data-test-subj="dataVisualizerTimeRangeSelectorSection"
|
||||
>
|
||||
{hasValidTimeField ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DatePickerWrapper
|
||||
isAutoRefreshOnly={!hasValidTimeField}
|
||||
showRefresh={!hasValidTimeField}
|
||||
<FullTimeRangeSelector
|
||||
dataView={currentDataView}
|
||||
query={undefined}
|
||||
disabled={false}
|
||||
timefilter={timefilter}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentHeader>
|
||||
) : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<DatePickerWrapper
|
||||
isAutoRefreshOnly={!hasValidTimeField}
|
||||
showRefresh={!hasValidTimeField}
|
||||
compact={compact}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentHeader>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPageContentBody>
|
||||
<EuiFlexGroup gutterSize="m" direction={compact ? 'column' : 'row'}>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder>
|
||||
<SearchPanel
|
||||
dataView={currentDataView}
|
||||
searchString={searchString}
|
||||
searchQuery={searchQuery}
|
||||
searchQueryLanguage={searchQueryLanguage}
|
||||
setSearchParams={setSearchParams}
|
||||
overallStats={overallStats}
|
||||
indexedFieldTypes={fieldTypes}
|
||||
setVisibleFieldTypes={setVisibleFieldTypes}
|
||||
visibleFieldTypes={visibleFieldTypes}
|
||||
visibleFieldNames={visibleFieldNames}
|
||||
setVisibleFieldNames={setVisibleFieldNames}
|
||||
showEmptyFields={showEmptyFields}
|
||||
onAddFilter={onAddFilter}
|
||||
compact={compact}
|
||||
/>
|
||||
|
||||
{overallStats?.totalCount !== undefined && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="s" direction="column">
|
||||
<DocumentCountContent
|
||||
documentCountStats={documentCountStats}
|
||||
totalCount={overallStats.totalCount}
|
||||
setSamplingProbability={setSamplingProbability}
|
||||
samplingProbability={
|
||||
dataVisualizerListState.probability === null
|
||||
? documentCountStats?.probability
|
||||
: dataVisualizerListState.probability
|
||||
}
|
||||
loading={overallStatsProgress.loaded < 100}
|
||||
randomSamplerPreference={savedRandomSamplerPreference}
|
||||
setRandomSamplerPreference={saveRandomSamplerPreference}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<FieldCountPanel
|
||||
showEmptyFields={showEmptyFields}
|
||||
toggleShowEmptyFields={toggleShowEmptyFields}
|
||||
fieldsCountStats={fieldsCountStats}
|
||||
metricsStats={metricsStats}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiProgress value={progress} max={100} size="xs" />
|
||||
<DataVisualizerTable<FieldVisConfig>
|
||||
items={configs}
|
||||
pageState={dataVisualizerListState}
|
||||
updatePageState={setDataVisualizerListState}
|
||||
getItemIdToExpandedRowMap={getItemIdToExpandedRowMap}
|
||||
extendedColumns={extendedColumns}
|
||||
loading={progress < 100}
|
||||
overallStatsRunning={overallStatsProgress.isRunning}
|
||||
showPreviewByDefault={dataVisualizerListState.showDistributions ?? true}
|
||||
onChange={setDataVisualizerListState}
|
||||
totalCount={overallStats.totalCount}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
{compact ? <EuiSpacer size="m" /> : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<ActionsPanel
|
||||
dataView={currentDataView}
|
||||
searchQueryLanguage={searchQueryLanguage}
|
||||
searchString={searchString}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
compact={compact}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPageContentBody>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder>
|
||||
<SearchPanel
|
||||
dataView={currentDataView}
|
||||
searchString={searchString}
|
||||
searchQuery={searchQuery}
|
||||
searchQueryLanguage={searchQueryLanguage}
|
||||
setSearchParams={setSearchParams}
|
||||
samplerShardSize={samplerShardSize}
|
||||
setSamplerShardSize={setSamplerShardSize}
|
||||
overallStats={overallStats}
|
||||
indexedFieldTypes={fieldTypes}
|
||||
setVisibleFieldTypes={setVisibleFieldTypes}
|
||||
visibleFieldTypes={visibleFieldTypes}
|
||||
visibleFieldNames={visibleFieldNames}
|
||||
setVisibleFieldNames={setVisibleFieldNames}
|
||||
showEmptyFields={showEmptyFields}
|
||||
onAddFilter={onAddFilter}
|
||||
/>
|
||||
|
||||
{overallStats?.totalCount !== undefined && (
|
||||
<>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiFlexItem grow={true}>
|
||||
<DocumentCountContent
|
||||
documentCountStats={documentCountStats}
|
||||
totalCount={overallStats.totalCount}
|
||||
setSamplingProbability={setSamplingProbability}
|
||||
samplingProbability={
|
||||
dataVisualizerListState.probability === null
|
||||
? documentCountStats?.probability
|
||||
: dataVisualizerListState.probability
|
||||
}
|
||||
loading={overallStatsProgress.loaded < 100}
|
||||
randomSamplerPreference={savedRandomSamplerPreference}
|
||||
setRandomSamplerPreference={saveRandomSamplerPreference}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
<EuiSpacer size={'m'} />
|
||||
<FieldCountPanel
|
||||
showEmptyFields={showEmptyFields}
|
||||
toggleShowEmptyFields={toggleShowEmptyFields}
|
||||
fieldsCountStats={fieldsCountStats}
|
||||
metricsStats={metricsStats}
|
||||
/>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiProgress value={progress} max={100} size={'xs'} />
|
||||
<DataVisualizerTable<FieldVisConfig>
|
||||
items={configs}
|
||||
pageState={dataVisualizerListState}
|
||||
updatePageState={setDataVisualizerListState}
|
||||
getItemIdToExpandedRowMap={getItemIdToExpandedRowMap}
|
||||
extendedColumns={extendedColumns}
|
||||
loading={progress < 100}
|
||||
overallStatsRunning={overallStatsProgress.isRunning}
|
||||
showPreviewByDefault={dataVisualizerListState.showDistributions ?? true}
|
||||
onChange={setDataVisualizerListState}
|
||||
totalCount={overallStats.totalCount}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ width: wizardPanelWidth }}>
|
||||
<ActionsPanel
|
||||
dataView={currentDataView}
|
||||
searchQueryLanguage={searchQueryLanguage}
|
||||
searchString={searchString}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageBody>
|
||||
|
||||
<HelpMenu docLink={helpLink} />
|
||||
</Fragment>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageBody>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import React, { FC, useMemo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import { useCurrentEuiTheme } from '../../../common/hooks/use_current_eui_theme';
|
||||
import { FieldTypesHelpPopover } from '../../../common/components/field_types_filter/field_types_help_popover';
|
||||
import type { SupportedFieldType } from '../../../../../common/types';
|
||||
import { FieldTypeIcon } from '../../../common/components/field_type_icon';
|
||||
|
@ -19,6 +21,7 @@ export const DataVisualizerFieldTypeFilter: FC<{
|
|||
setVisibleFieldTypes(q: string[]): void;
|
||||
visibleFieldTypes: string[];
|
||||
}> = ({ indexedFieldTypes, setVisibleFieldTypes, visibleFieldTypes }) => {
|
||||
const euiTheme = useCurrentEuiTheme();
|
||||
const options: Option[] = useMemo(() => {
|
||||
return indexedFieldTypes.map((indexedFieldName) => {
|
||||
const label = jobTypeLabels[indexedFieldName] ?? '';
|
||||
|
@ -55,6 +58,11 @@ export const DataVisualizerFieldTypeFilter: FC<{
|
|||
checkedOptions={visibleFieldTypes}
|
||||
dataTestSubj={'dataVisualizerFieldTypeSelect'}
|
||||
postfix={<FieldTypesHelpPopover fieldTypes={indexedFieldTypes} />}
|
||||
cssStyles={{
|
||||
filterGroup: css`
|
||||
margin-left: ${euiTheme.euiSizeS};
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.dvSearchPanel__controls {
|
||||
flex-direction: row;
|
||||
padding: $euiSizeS;
|
||||
}
|
||||
|
||||
.dvSearchPanel__container {
|
||||
|
@ -12,7 +11,7 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
.dvSearchBar {
|
||||
min-width: #{'max(100%, 500px)'};
|
||||
min-width: #{'max(100%, 300px)'};
|
||||
}
|
||||
.dvSearchPanel__controls {
|
||||
padding: 0;
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
*/
|
||||
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Query, Filter } from '@kbn/es-query';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { isDefined } from '../../../common/util/is_defined';
|
||||
import { DataVisualizerFieldNamesFilter } from './field_name_filter';
|
||||
import { DataVisualizerFieldTypeFilter } from './field_type_filter';
|
||||
|
@ -26,8 +25,6 @@ interface Props {
|
|||
searchString: Query['query'];
|
||||
searchQuery: Query['query'];
|
||||
searchQueryLanguage: SearchQueryLanguage;
|
||||
samplerShardSize: number;
|
||||
setSamplerShardSize(s: number): void;
|
||||
overallStats: OverallStats;
|
||||
indexedFieldTypes: SupportedFieldType[];
|
||||
setVisibleFieldTypes(q: string[]): void;
|
||||
|
@ -47,14 +44,13 @@ interface Props {
|
|||
}): void;
|
||||
showEmptyFields: boolean;
|
||||
onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export const SearchPanel: FC<Props> = ({
|
||||
dataView,
|
||||
searchString,
|
||||
searchQueryLanguage,
|
||||
samplerShardSize,
|
||||
setSamplerShardSize,
|
||||
overallStats,
|
||||
indexedFieldTypes,
|
||||
setVisibleFieldTypes,
|
||||
|
@ -63,6 +59,7 @@ export const SearchPanel: FC<Props> = ({
|
|||
visibleFieldNames,
|
||||
setSearchParams,
|
||||
showEmptyFields,
|
||||
compact,
|
||||
}) => {
|
||||
const {
|
||||
services: {
|
||||
|
@ -120,7 +117,7 @@ export const SearchPanel: FC<Props> = ({
|
|||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
gutterSize="none"
|
||||
data-test-subj="dataVisualizerSearchPanel"
|
||||
className={'dvSearchPanel__container'}
|
||||
responsive={false}
|
||||
|
@ -147,14 +144,15 @@ export const SearchPanel: FC<Props> = ({
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{compact ? <EuiSpacer size="s" /> : null}
|
||||
<EuiFlexItem
|
||||
grow={2}
|
||||
className={'dvSearchPanel__controls'}
|
||||
css={css`
|
||||
margin-left: 0px !important;
|
||||
padding-left: 0px !important;
|
||||
padding-right: 0px !important;
|
||||
`}
|
||||
css={{
|
||||
marginLeft: '0px !important',
|
||||
paddingLeft: '0px !important',
|
||||
paddingRight: '0px !important',
|
||||
}}
|
||||
>
|
||||
<DataVisualizerFieldNamesFilter
|
||||
overallStats={overallStats}
|
||||
|
|
|
@ -5,15 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import '../_index.scss';
|
||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { parse, stringify } from 'query-string';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isEqual, throttle } from 'lodash';
|
||||
import { encode } from '@kbn/rison';
|
||||
import { SimpleSavedObject } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { EuiResizeObserver } from '@elastic/eui';
|
||||
import { getCoreStart, getPluginsStart } from '../../kibana_services';
|
||||
import {
|
||||
IndexDataVisualizerViewProps,
|
||||
|
@ -34,7 +35,7 @@ import { DATA_VISUALIZER_APP_LOCATOR, IndexDataVisualizerLocatorParams } from '.
|
|||
import { DATA_VISUALIZER_INDEX_VIEWER } from './constants/index_data_visualizer_viewer';
|
||||
import { INDEX_DATA_VISUALIZER_NAME } from '../common/constants';
|
||||
|
||||
export interface DataVisualizerUrlStateContextProviderProps {
|
||||
export interface DataVisualizerStateContextProviderProps {
|
||||
IndexDataVisualizerComponent: FC<IndexDataVisualizerViewProps>;
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}
|
||||
|
@ -70,9 +71,10 @@ export const getLocatorParams = (params: {
|
|||
return locatorParams;
|
||||
};
|
||||
|
||||
export const DataVisualizerUrlStateContextProvider: FC<
|
||||
DataVisualizerUrlStateContextProviderProps
|
||||
> = ({ IndexDataVisualizerComponent, getAdditionalLinks }) => {
|
||||
export const DataVisualizerStateContextProvider: FC<DataVisualizerStateContextProviderProps> = ({
|
||||
IndexDataVisualizerComponent,
|
||||
getAdditionalLinks,
|
||||
}) => {
|
||||
const { services } = useDataVisualizerKibana();
|
||||
const {
|
||||
data: { dataViews, search },
|
||||
|
@ -240,15 +242,36 @@ export const DataVisualizerUrlStateContextProvider: FC<
|
|||
[history, urlSearchString]
|
||||
);
|
||||
|
||||
const [panelWidth, setPanelWidth] = useState(1600);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const resizeHandler = useCallback(
|
||||
throttle((e: { width: number; height: number }) => {
|
||||
// When window or table is resized,
|
||||
// update the page body width
|
||||
setPanelWidth(e.width);
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
const compact = useMemo(() => panelWidth <= 1024, [panelWidth]);
|
||||
|
||||
return (
|
||||
<UrlStateContextProvider value={{ searchString: urlSearchString, setUrlState }}>
|
||||
{currentDataView ? (
|
||||
<IndexDataVisualizerComponent
|
||||
currentDataView={currentDataView}
|
||||
currentSavedSearch={currentSavedSearch}
|
||||
currentSessionId={currentSessionId}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
// Needs ResizeObserver to measure window width - side bar navigation
|
||||
<EuiResizeObserver onResize={resizeHandler}>
|
||||
{(resizeRef) => (
|
||||
<div ref={resizeRef}>
|
||||
<IndexDataVisualizerComponent
|
||||
currentDataView={currentDataView}
|
||||
currentSavedSearch={currentSavedSearch}
|
||||
currentSessionId={currentSessionId}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
compact={compact}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</EuiResizeObserver>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
@ -293,7 +316,7 @@ export const IndexDataVisualizer: FC<{
|
|||
return (
|
||||
<KibanaThemeProvider theme$={coreStart.theme.theme$}>
|
||||
<KibanaContextProvider services={{ ...services }}>
|
||||
<DataVisualizerUrlStateContextProvider
|
||||
<DataVisualizerStateContextProvider
|
||||
IndexDataVisualizerComponent={IndexDataVisualizerView}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
|
|
|
@ -21,52 +21,6 @@ import type {
|
|||
const MINIMUM_RANDOM_SAMPLER_DOC_COUNT = 100000;
|
||||
const DEFAULT_INITIAL_RANDOM_SAMPLER_PROBABILITY = 0.000001;
|
||||
|
||||
export const getDocumentCountStatsRequest = (params: OverallStatsSearchStrategyParams) => {
|
||||
const {
|
||||
index,
|
||||
timeFieldName,
|
||||
earliest: earliestMs,
|
||||
latest: latestMs,
|
||||
runtimeFieldMap,
|
||||
searchQuery,
|
||||
intervalMs,
|
||||
fieldsToFetch,
|
||||
} = params;
|
||||
|
||||
const size = 0;
|
||||
const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, searchQuery);
|
||||
|
||||
// Don't use the sampler aggregation as this can lead to some potentially
|
||||
// confusing date histogram results depending on the date range of data amongst shards.
|
||||
const aggs = {
|
||||
eventRate: {
|
||||
date_histogram: {
|
||||
field: timeFieldName,
|
||||
fixed_interval: `${intervalMs}ms`,
|
||||
min_doc_count: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const searchBody = {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria,
|
||||
},
|
||||
},
|
||||
...(!fieldsToFetch && timeFieldName !== undefined && intervalMs !== undefined && intervalMs > 0
|
||||
? { aggs }
|
||||
: {}),
|
||||
...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}),
|
||||
track_total_hits: true,
|
||||
size,
|
||||
};
|
||||
return {
|
||||
index,
|
||||
body: searchBody,
|
||||
};
|
||||
};
|
||||
|
||||
export const getDocumentCountStats = async (
|
||||
search: DataPublicPluginStart['search'],
|
||||
params: OverallStatsSearchStrategyParams,
|
||||
|
@ -104,7 +58,11 @@ export const getDocumentCountStats = async (
|
|||
date_histogram: {
|
||||
field: timeFieldName,
|
||||
fixed_interval: `${intervalMs}ms`,
|
||||
min_doc_count: 1,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: {
|
||||
min: earliestMs,
|
||||
max: latestMs,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue