mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Lens/SCSS] Replace scss to css-in-js for Lens codebase (#209768)
Replace SCSS in css-in-js for Lens codebase
This commit is contained in:
parent
231507bf28
commit
de52f41a5c
127 changed files with 1672 additions and 1979 deletions
File diff suppressed because one or more lines are too long
|
@ -2,16 +2,11 @@
|
|||
"extends": "../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react",
|
||||
"@emotion/css/types"
|
||||
],
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"../../../../../typings/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/ui-theme"
|
||||
|
|
|
@ -75,9 +75,12 @@ function DimensionButtonImpl({
|
|||
<EuiFlexItem>
|
||||
<EuiToolTip content={message?.content} position="left">
|
||||
<EuiLink
|
||||
className="lnsLayerPanel__dimensionLink"
|
||||
css={css`
|
||||
width: 100%;
|
||||
&:focus {
|
||||
background-color: transparent;
|
||||
text-decoration-thickness: ${euiTheme.border.thin} !important;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,9 @@ export const DimensionTrigger = ({
|
|||
<span
|
||||
className="dimensionTrigger__textLabel"
|
||||
css={css`
|
||||
.domDroppable--replacing & {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
transition: background-color ${euiThemeVars.euiAnimSpeedFast} ease-in-out;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -256,7 +256,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
'10'
|
||||
);
|
||||
|
||||
await testSubjects.click('unifiedHistogramEditFlyoutVisualization');
|
||||
expect(await getCurrentVisTitle()).to.be('Line');
|
||||
expect(await discover.getVisContextSuggestionType()).to.be('histogramForESQL');
|
||||
});
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
// sass-lint:disable-block indentation, no-color-keywords
|
||||
|
||||
// SASSTODO: Create this in EUI
|
||||
@mixin lnsOverflowShadowHorizontal {
|
||||
$hideHeight: $euiScrollBarCorner * 1.25;
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
transparentize($euiColorDanger, .9) 0%,
|
||||
transparentize($euiColorDanger, 0) $hideHeight,
|
||||
transparentize($euiColorDanger, 0) calc(100% - #{$hideHeight}),
|
||||
transparentize($euiColorDanger, .9) 100%
|
||||
);
|
||||
}
|
||||
|
||||
// Removes EUI focus ring
|
||||
@mixin removeEuiFocusRing {
|
||||
outline: none;
|
||||
|
||||
&:focus-visible {
|
||||
outline-style: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Passes focus ring styles down to a child of a focused element
|
||||
@mixin passDownFocusRing($target) {
|
||||
@include removeEuiFocusRing;
|
||||
|
||||
#{$target} {
|
||||
@include euiFocusBackground;
|
||||
outline: $euiFocusRingSize solid currentColor; // Safari & Firefox
|
||||
}
|
||||
|
||||
&:focus-visible #{$target} {
|
||||
outline-style: auto; // Chrome
|
||||
}
|
||||
|
||||
&:not(:focus-visible) #{$target} {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin euiFlyout {
|
||||
border-left: $euiBorderThin;
|
||||
// The mixin augments the above
|
||||
// sass-lint:disable mixins-before-declarations
|
||||
@include euiBottomShadowLarge;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
z-index: $euiZFlyout;
|
||||
background: $euiColorEmptyShade;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@keyframes euiFlyoutAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
75% {
|
||||
opacity: 1;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
$lnsPanelMinWidth: $euiSize * 18;
|
||||
|
||||
// These sizes also match canvas' page thumbnails for consistency
|
||||
$lnsSuggestionHeight: 100px;
|
||||
$lnsSuggestionWidth: 150px;
|
||||
|
||||
$lnsZLevel0: 0;
|
||||
$lnsZLevel1: 1;
|
||||
$lnsZLevel2: 2;
|
||||
$lnsZLevel3: 3;
|
|
@ -1,40 +0,0 @@
|
|||
.lnsAppWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.lnsApp {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.lnsApp__frame {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
// Less-than-ideal styles to add a vertical divider after this button. Consider restructuring markup for better semantics and styling options in the future.
|
||||
.lnsNavItem__withDivider {
|
||||
@include euiBreakpoint('m', 'l', 'xl') {
|
||||
margin-right: $euiSizeM;
|
||||
position: relative;
|
||||
}
|
||||
&::after {
|
||||
@include euiBreakpoint('m', 'l', 'xl') {
|
||||
border-right: $euiBorderThin;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -$euiSizeS;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './app.scss';
|
||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
|
@ -13,6 +12,7 @@ import { EuiConfirmModal } from '@elastic/eui';
|
|||
import { useExecutionContext, useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { OnSaveProps } from '@kbn/saved-objects-plugin/public';
|
||||
import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { LensAppProps, LensAppServices } from './types';
|
||||
import { LensTopNavMenu } from './lens_top_nav';
|
||||
import { AddUserMessages, EditorFrameInstance, Simplify, UserMessagesGetter } from '../types';
|
||||
|
@ -437,7 +437,18 @@ export function App({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="lnsApp" data-test-subj="lnsApp" role="main">
|
||||
<div
|
||||
data-test-subj="lnsApp"
|
||||
className="lnsApp"
|
||||
role="main"
|
||||
css={css`
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<LensTopNavMenu
|
||||
initialInput={initialInput}
|
||||
redirectToOrigin={redirectToOrigin}
|
||||
|
|
|
@ -17,8 +17,9 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
|
|||
import { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
|
||||
import { getManagedContentBadge } from '@kbn/managed-content-badge';
|
||||
import moment from 'moment';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { EuiCallOut, UseEuiTheme, euiBreakpoint } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { SerializedStyles, css } from '@emotion/react';
|
||||
import { LENS_APP_LOCATOR } from '../../common/locator/locator';
|
||||
import { LENS_APP_NAME } from '../../common/constants';
|
||||
import { LensAppServices, LensTopNavActions, LensTopNavMenuProps } from './types';
|
||||
|
@ -102,6 +103,23 @@ function getSaveButtonMeta({
|
|||
}
|
||||
}
|
||||
|
||||
const navItemWithDividerStyles = (euiThemeContext: UseEuiTheme) => css`
|
||||
${euiBreakpoint(euiThemeContext, ['m', 'l', 'xl'])} {
|
||||
margin-right: ${euiThemeContext.euiTheme.size.m};
|
||||
position: relative;
|
||||
&:after {
|
||||
border-right: ${euiThemeContext.euiTheme.border.thin};
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -${euiThemeContext.euiTheme.size.s};
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function getLensTopNavConfig(options: {
|
||||
isByValueMode: boolean;
|
||||
actions: LensTopNavActions;
|
||||
|
@ -123,7 +141,10 @@ function getLensTopNavConfig(options: {
|
|||
contextFromEmbeddable,
|
||||
isByValueMode,
|
||||
} = options;
|
||||
const topNavMenu: TopNavMenuData[] = [];
|
||||
|
||||
const topNavMenu: Array<
|
||||
TopNavMenuData | ({ css: ({ euiTheme }: UseEuiTheme) => SerializedStyles } & TopNavMenuData)
|
||||
> = [];
|
||||
|
||||
const showSaveAndReturn = actions.saveAndReturn.visible;
|
||||
|
||||
|
@ -150,13 +171,13 @@ function getLensTopNavConfig(options: {
|
|||
values: { contextOriginatingApp },
|
||||
}),
|
||||
run: actions.goBack.execute,
|
||||
className: 'lnsNavItem__withDivider',
|
||||
testId: 'lnsApp_goBackToAppButton',
|
||||
description: i18n.translate('xpack.lens.app.goBackLabel', {
|
||||
defaultMessage: `Go back to {contextOriginatingApp}`,
|
||||
values: { contextOriginatingApp },
|
||||
}),
|
||||
disableButton: !actions.goBack.enabled,
|
||||
css: navItemWithDividerStyles,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -169,12 +190,12 @@ function getLensTopNavConfig(options: {
|
|||
label: exploreDataInDiscoverLabel,
|
||||
run: actions.getUnderlyingDataUrl.execute,
|
||||
testId: 'lnsApp_openInDiscover',
|
||||
className: 'lnsNavItem__withDivider',
|
||||
description: exploreDataInDiscoverLabel,
|
||||
disableButton: !actions.getUnderlyingDataUrl.enabled,
|
||||
tooltip: actions.getUnderlyingDataUrl.tooltip,
|
||||
target: '_blank',
|
||||
href: actions.getUnderlyingDataUrl.getLink?.(),
|
||||
css: navItemWithDividerStyles,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -210,11 +231,11 @@ function getLensTopNavConfig(options: {
|
|||
defaultMessage: 'Settings',
|
||||
}),
|
||||
run: actions.openSettings.execute,
|
||||
className: 'lnsNavItem__withDivider',
|
||||
testId: 'lnsApp_settingsButton',
|
||||
description: i18n.translate('xpack.lens.app.settingsAriaLabel', {
|
||||
defaultMessage: 'Open the Lens settings menu',
|
||||
}),
|
||||
css: navItemWithDividerStyles,
|
||||
});
|
||||
|
||||
if (actions.cancel.visible) {
|
||||
|
|
|
@ -398,8 +398,6 @@ export async function mountApp(
|
|||
window.dispatchEvent(new HashChangeEvent('hashchange'));
|
||||
});
|
||||
|
||||
params.element.classList.add('lnsAppWrapper');
|
||||
|
||||
render(
|
||||
<KibanaRenderContextProvider {...coreStart}>
|
||||
<KibanaContextProvider services={lensServices}>
|
||||
|
|
|
@ -122,6 +122,7 @@ export const FlyoutWrapper = ({
|
|||
margin-left: -${euiThemeVars.euiFormMaxWidth};
|
||||
pointer-events: none;
|
||||
.euiFlyoutBody__overflow {
|
||||
transform: initial;
|
||||
-webkit-mask-image: none;
|
||||
padding-left: inherit;
|
||||
margin-left: inherit;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { UseEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const dataPanelStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
padding: ${euiTheme.size.base} ${euiTheme.size.base} 0;
|
||||
.unifiedFieldListItemButton.kbnFieldButton {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
margin-bottom: calc(${euiTheme.size.xs} / 2);
|
||||
}
|
||||
.unifiedFieldListItemButton__dragging {
|
||||
background: ${euiTheme.colors.backgroundBasePlain};
|
||||
}
|
||||
`;
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
.lnsFieldItem__fieldPanel {
|
||||
min-width: 260px;
|
||||
max-width: 300px;
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './field_item.scss';
|
||||
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import { EuiText, EuiButton, EuiPopoverFooter } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -251,6 +249,10 @@ export function InnerFieldItem(props: FieldItemProps) {
|
|||
isOpen={infoIsOpen}
|
||||
closePopover={closePopover}
|
||||
panelClassName="lnsFieldItem__fieldPanel"
|
||||
panelStyle={{
|
||||
minWidth: '260px',
|
||||
maxWidth: '300px',
|
||||
}}
|
||||
initialFocus=".lnsFieldItem__fieldPanel"
|
||||
data-test-subj="lnsFieldListPanelField"
|
||||
panelProps={{
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
.lnsInnerIndexPatternDataPanel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: $euiSize $euiSize 0;
|
||||
.unifiedFieldListItemButton.kbnFieldButton {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
margin-bottom: calc($euiSizeXS / 2);
|
||||
}
|
||||
.unifiedFieldListItemButton__dragging {
|
||||
background: $euiColorBackgroundBasePlain;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsInnerIndexPatternDataPanel__switcher {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.lnsInnerIndexPatternDataPanel__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $euiSizeS;
|
||||
}
|
||||
|
||||
.lnsChangeIndexPatternPopover__trigger {
|
||||
padding: 0 $euiSize;
|
||||
}
|
|
@ -10,8 +10,7 @@ import { DataView } from '@kbn/data-views-plugin/public';
|
|||
import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { FormBasedDataPanel, FormBasedDataPanelProps } from './datapanel';
|
||||
import * as UseExistingFieldsApi from '@kbn/unified-field-list/src/hooks/use_existing_fields';
|
||||
|
@ -28,6 +27,7 @@ import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
|
|||
import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mock';
|
||||
import { createMockFramePublicAPI } from '../../mocks';
|
||||
import { DataViewsState } from '../../state_management';
|
||||
import { renderWithProviders } from '../../test_utils/test_utils';
|
||||
|
||||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
|
||||
|
@ -241,11 +241,8 @@ const waitToLoad = async () =>
|
|||
await act(async () => new Promise((resolve) => setTimeout(resolve, 0)));
|
||||
|
||||
const renderFormBasedDataPanel = async (propsOverrides?: Partial<FormBasedDataPanelProps>) => {
|
||||
const { rerender, ...rest } = render(
|
||||
<FormBasedDataPanel {...defaultProps} {...propsOverrides} />,
|
||||
{
|
||||
wrapper: ({ children }) => <I18nProvider>{children}</I18nProvider>,
|
||||
}
|
||||
const { rerender, ...rest } = renderWithProviders(
|
||||
<FormBasedDataPanel {...defaultProps} {...propsOverrides} />
|
||||
);
|
||||
await waitToLoad();
|
||||
return {
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './datapanel.scss';
|
||||
import { uniq } from 'lodash';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiCallOut, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
|
@ -41,6 +40,7 @@ import type {
|
|||
import type { FormBasedPrivateState } from './types';
|
||||
import { IndexPatternServiceAPI } from '../../data_views_service/service';
|
||||
import { FieldItem } from '../common/field_item';
|
||||
import { dataPanelStyles } from '../common/datapanel.styles';
|
||||
|
||||
export type FormBasedDataPanelProps = Omit<
|
||||
DatasourceDataPanelProps<FormBasedPrivateState, Query>,
|
||||
|
@ -102,6 +102,7 @@ export function FormBasedDataPanel({
|
|||
const { indexPatterns, indexPatternRefs } = frame.dataViews;
|
||||
const { currentIndexPatternId } = state;
|
||||
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const activeIndexPatterns = useMemo(() => {
|
||||
return uniq(
|
||||
(
|
||||
|
@ -118,7 +119,7 @@ export function FormBasedDataPanel({
|
|||
{Object.keys(indexPatterns).length === 0 && indexPatternRefs.length === 0 ? (
|
||||
<EuiFlexGroup
|
||||
gutterSize="m"
|
||||
className="lnsInnerIndexPatternDataPanel"
|
||||
css={dataPanelStyles(euiThemeContext)}
|
||||
direction="column"
|
||||
responsive={false}
|
||||
>
|
||||
|
@ -201,6 +202,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
|
|||
layerFields?: string[];
|
||||
activeIndexPatterns: IndexPattern[];
|
||||
}) {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { indexPatterns } = frame.dataViews;
|
||||
const currentIndexPattern = indexPatterns[currentIndexPatternId];
|
||||
|
||||
|
@ -400,7 +402,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
|
|||
|
||||
return (
|
||||
<FieldList
|
||||
className="lnsInnerIndexPatternDataPanel"
|
||||
css={dataPanelStyles(euiThemeContext)}
|
||||
isProcessing={isProcessing}
|
||||
prepend={<FieldListFilters {...fieldListFiltersProps} data-test-subj="lnsIndexPattern" />}
|
||||
>
|
||||
|
|
|
@ -32,6 +32,7 @@ export function AdvancedOptions(props: { options: AdvancedOption[] }) {
|
|||
}
|
||||
css={css`
|
||||
padding: 0 ${euiTheme.size.base} ${euiTheme.size.base};
|
||||
color: ${euiTheme.colors.primary};
|
||||
`}
|
||||
>
|
||||
{props.options.map(({ dataTestSubj, inlineElement }) => (
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
.lnsIndexPatternDimensionEditor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor__header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: $euiColorEmptyShade;
|
||||
z-index: $euiZLevel1; // Raise it above the elements that are after it in DOM order
|
||||
padding: 0 $euiSize;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor-isFullscreen {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor--padded {
|
||||
padding: $euiSize;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor--collapseNext {
|
||||
margin-bottom: -$euiSizeL;
|
||||
border-top: $euiBorderThin;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor__columns {
|
||||
display: block;
|
||||
column-count: 2;
|
||||
column-gap: $euiSizeM;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor__operation .euiListGroupItem__label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor__operation > button {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
min-block-size: $euiSizeL;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor__warning {
|
||||
margin-bottom: $euiSize;
|
||||
margin-top: $euiSizeS;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor__droppable {
|
||||
padding: $euiSizeXS;
|
||||
border-radius: $euiBorderRadius;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor__droppableItem {
|
||||
margin-right: $euiSizeS;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor-advancedOptions button {
|
||||
&:hover, &:focus {
|
||||
text-decoration-color: $euiColorPrimary;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './dimension_editor.scss';
|
||||
import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
|
@ -25,6 +24,7 @@ import {
|
|||
EuiPanel,
|
||||
EuiBasicTable,
|
||||
EuiButtonIcon,
|
||||
type UseEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { NameInput } from '@kbn/visualization-ui-components';
|
||||
|
@ -75,6 +75,7 @@ import { ParamEditorProps } from '../operations/definitions';
|
|||
import { WrappingHelpPopover } from '../help_popover';
|
||||
import { isColumn } from '../operations/definitions/helpers';
|
||||
import type { FieldChoiceWithOperationType } from './field_select';
|
||||
import { operationsButtonStyles } from './shared_styles';
|
||||
import type { IndexPattern, IndexPatternField } from '../../../types';
|
||||
import { documentField } from '../document_field';
|
||||
|
||||
|
@ -141,7 +142,9 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
|
||||
const temporaryQuickFunction = Boolean(temporaryState === quickFunctionsName);
|
||||
const temporaryStaticValue = Boolean(temporaryState === staticValueOperationName);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
|
||||
const updateLayer = useCallback(
|
||||
(newLayer: Partial<FormBasedLayer>) =>
|
||||
|
@ -534,7 +537,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
isActive,
|
||||
size: 's',
|
||||
isDisabled: !!disabledStatus,
|
||||
className: 'lnsIndexPatternDimensionEditor__operation',
|
||||
css: operationsButtonStyles(euiThemeContext),
|
||||
'data-test-subj': `lns-indexPatternDimension-${operationType}${
|
||||
compatibleWithCurrentField ? '' : ' incompatible'
|
||||
}`,
|
||||
|
@ -811,7 +814,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
fullWidth
|
||||
>
|
||||
<EuiListGroup
|
||||
className={sideNavItems.length > 3 ? 'lnsIndexPatternDimensionEditor__columns' : ''}
|
||||
css={sideNavItems.length > 3 ? operationsTwoColumnsStyles(euiThemeContext) : undefined}
|
||||
gutterSize="none"
|
||||
color="primary"
|
||||
listItems={
|
||||
|
@ -1290,3 +1293,11 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const operationsTwoColumnsStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
display: block;
|
||||
column-count: 2;
|
||||
column-gap: ${euiTheme.size.m};
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ReactWrapper, ShallowWrapper, ComponentType, mount } from 'enzyme';
|
||||
import { ReactWrapper, ShallowWrapper } from 'enzyme';
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { screen, act, render, within } from '@testing-library/react';
|
||||
import { screen, act, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import {
|
||||
|
@ -17,7 +17,6 @@ import {
|
|||
EuiRange,
|
||||
EuiSelect,
|
||||
EuiComboBoxProps,
|
||||
EuiThemeProvider,
|
||||
} from '@elastic/eui';
|
||||
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
|
@ -48,9 +47,7 @@ import { TimeShift } from './time_shift';
|
|||
import { ReducedTimeRange } from './reduced_time_range';
|
||||
import { DimensionEditor } from './dimension_editor';
|
||||
import { AdvancedOptions } from './advanced_options';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { LensAppServices } from '../../../app_plugin/types';
|
||||
import { mountWithProviders, renderWithProviders } from '../../../test_utils/test_utils';
|
||||
|
||||
jest.mock('./reference_editor', () => ({
|
||||
ReferenceEditor: () => null,
|
||||
|
@ -167,25 +164,6 @@ const bytesColumn: GenericIndexPatternColumn = {
|
|||
params: { format: { id: 'bytes' } },
|
||||
};
|
||||
|
||||
const wrappingComponent: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
return (
|
||||
<KibanaContextProvider services={coreMock.createStart() as unknown as LensAppServices}>
|
||||
<EuiThemeProvider>{children}</EuiThemeProvider>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
function mountWithServices(component: React.ReactElement): ReactWrapper {
|
||||
return mount(component, {
|
||||
// This is an elegant way to wrap a component in Enzyme
|
||||
// preserving the root at the component level rather than
|
||||
// at the wrapper one
|
||||
wrappingComponent: wrappingComponent as ComponentType<{}>,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The datasource exposes four main pieces of code which are tested at
|
||||
* an integration test level. The main reason for this fairly high level
|
||||
|
@ -292,21 +270,8 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
const renderDimensionPanel = (propsOverrides = {}) => {
|
||||
const Wrapper: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
return (
|
||||
<KibanaContextProvider services={coreMock.createStart() as unknown as LensAppServices}>
|
||||
{children}
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const rtlRender = render(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} {...propsOverrides} />,
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
const rtlRender = renderWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} {...propsOverrides} />
|
||||
);
|
||||
|
||||
const getVisibleFieldSelectOptions = () => {
|
||||
|
@ -396,14 +361,14 @@ describe('FormBasedDimensionEditor', () => {
|
|||
};
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
const options = getFieldSelectComboBox(wrapper).prop('options');
|
||||
expect(options![1].options!.map(({ label }) => label)).toEqual(['timestampLabel', 'source']);
|
||||
});
|
||||
|
||||
it('should indicate fields which are incompatible for the operation of the current column', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({ col1: bytesColumn })}
|
||||
|
@ -423,7 +388,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate operations which are incompatible for the field of the current column', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({ col1: bytesColumn })}
|
||||
|
@ -447,7 +412,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate when a transition is invalid due to filterOperations', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -472,7 +437,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should not display hidden operation types', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
const items: EuiListGroupItemProps[] = wrapper.find(EuiListGroup).prop('listItems') || [];
|
||||
|
||||
|
@ -482,7 +447,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate that reference-based operations are not compatible when they are incomplete', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -519,7 +484,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate that reference-based operations are compatible sometimes', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -566,7 +531,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
it('should keep the operation when switching to another field compatible with this operation', async () => {
|
||||
const initialState: FormBasedPrivateState = getStateWithColumns({ col1: bytesColumn });
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={initialState} />
|
||||
);
|
||||
|
||||
|
@ -601,7 +566,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should switch operations when selecting a field that requires another operation', async () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
const comboBox = getFieldSelectComboBox(wrapper);
|
||||
const option = comboBox.prop('options')![1].options!.find(({ label }) => label === 'source')!;
|
||||
|
@ -633,7 +598,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should keep the field when switching to another operation compatible for this field', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({ col1: bytesColumn })}
|
||||
|
@ -665,7 +630,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should not set the state if selecting the currently active operation', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
|
@ -677,7 +642,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should update label and custom label flag on label input changes', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
|
@ -705,7 +670,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should not keep the label as long as it is the default label', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({ col1: bytesColumn })}
|
||||
|
@ -734,7 +699,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should keep the label on operation change if it is custom', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -770,7 +735,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should remove customLabel flag if label is set to default', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -810,7 +775,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
|
||||
describe('transient invalid state', () => {
|
||||
it('should set the state if selecting an operation incompatible with the current field', async () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
|
@ -836,7 +801,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show error message in invalid state', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
|
@ -850,7 +815,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should leave error state if a compatible operation is selected', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
|
@ -868,7 +833,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should leave error state if the original operation is re-selected', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
|
@ -886,7 +851,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should leave error state when switching from incomplete state to fieldless operation', async () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
|
@ -901,7 +866,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should leave error state when re-selecting the original fieldless function', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -933,7 +898,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate fields compatible with selected operation', async () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
|
@ -954,7 +919,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should select compatible operation if field not compatible with selected operation', async () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} columnId={'col2'} />
|
||||
);
|
||||
|
||||
|
@ -1022,7 +987,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
references: ['ref'],
|
||||
},
|
||||
});
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={baseState} columnId={'col2'} />
|
||||
);
|
||||
|
||||
|
@ -1049,7 +1014,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should select the Records field when count is selected on non-existing column', async () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({})}
|
||||
|
@ -1069,7 +1034,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate document and field compatibility with selected document operation', async () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -1101,7 +1066,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should set datasource state if compatible field is selected for operation', async () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
.find('button[data-test-subj="lns-indexPatternDimension-terms incompatible"]')
|
||||
|
@ -1165,7 +1130,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
}
|
||||
|
||||
it('should default to None if time scaling is not set', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...getProps({})} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...getProps({})} />);
|
||||
act(() => {
|
||||
findTestSubject(wrapper, 'indexPattern-advanced-accordion').simulate('click');
|
||||
});
|
||||
|
@ -1179,7 +1144,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show current time scaling if set', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...getProps({ timeScale: 'd' })} />
|
||||
);
|
||||
act(() => {
|
||||
|
@ -1195,7 +1160,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
|
||||
it('should allow to set time scaling initially', () => {
|
||||
const props = getProps({});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
findTestSubject(wrapper, 'indexPattern-advanced-accordion').simulate('click');
|
||||
});
|
||||
|
@ -1232,7 +1197,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
operationType: 'sum',
|
||||
label: 'Sum of bytes per hour',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper.find('button[data-test-subj="lns-indexPatternDimension-count"]').simulate('click');
|
||||
});
|
||||
|
@ -1261,7 +1226,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
operationType: 'sum',
|
||||
label: 'Sum of bytes per hour',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="lns-indexPatternDimension-average"]')
|
||||
|
@ -1287,7 +1252,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
|
||||
it('should allow to change time scaling', () => {
|
||||
const props = getProps({ timeScale: 's', label: 'Count of records per second' });
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
findTestSubject(wrapper, 'indexPattern-advanced-accordion').simulate('click');
|
||||
});
|
||||
|
@ -1320,7 +1285,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
|
||||
it('should not adjust label if it is custom', () => {
|
||||
const props = getProps({ timeScale: 's', customLabel: true, label: 'My label' });
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('[data-test-subj="indexPattern-time-scaling-unit"] select')
|
||||
|
@ -1390,7 +1355,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
}),
|
||||
columnId: 'col2',
|
||||
};
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
findTestSubject(wrapper, 'indexPattern-advanced-accordion').simulate('click');
|
||||
});
|
||||
|
@ -1400,7 +1365,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show current reduced time range if set', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...getProps({ reducedTimeRange: '5m' })} />
|
||||
);
|
||||
expect(
|
||||
|
@ -1410,7 +1375,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
|
||||
it('should allow to set reduced time range initially', () => {
|
||||
const props = getProps({});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
findTestSubject(wrapper, 'indexPattern-advanced-accordion').simulate('click');
|
||||
});
|
||||
|
@ -1442,7 +1407,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
operationType: 'sum',
|
||||
label: 'Sum of bytes per hour',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper.find('button[data-test-subj="lns-indexPatternDimension-count"]').simulate('click');
|
||||
});
|
||||
|
@ -1466,7 +1431,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
const props = getProps({
|
||||
timeShift: '1d',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper.find(ReducedTimeRange).find(EuiComboBox).prop('onCreateOption')!('7m', []);
|
||||
});
|
||||
|
@ -1490,7 +1455,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
const props = getProps({
|
||||
reducedTimeRange: '5 months',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
|
||||
expect(wrapper.find(ReducedTimeRange).find(EuiComboBox).prop('isInvalid')).toBeTruthy();
|
||||
|
||||
|
@ -1547,7 +1512,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
}),
|
||||
columnId: 'col2',
|
||||
};
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...props}
|
||||
indexPatterns={{
|
||||
|
@ -1566,7 +1531,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show custom options if time shift is available', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...getProps({})} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...getProps({})} />);
|
||||
expect(
|
||||
wrapper
|
||||
.find(DimensionEditor)
|
||||
|
@ -1576,7 +1541,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show current time shift if set', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...getProps({ timeShift: '1d' })} />
|
||||
);
|
||||
expect(wrapper.find(TimeShift).find(EuiComboBox).prop('selectedOptions')[0].value).toEqual(
|
||||
|
@ -1586,7 +1551,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
|
||||
it('should allow to set time shift initially', () => {
|
||||
const props = getProps({});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
findTestSubject(wrapper, 'indexPattern-advanced-accordion').simulate('click');
|
||||
});
|
||||
|
@ -1616,7 +1581,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
operationType: 'sum',
|
||||
label: 'Sum of bytes per hour',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper.find('button[data-test-subj="lns-indexPatternDimension-count"]').simulate('click');
|
||||
});
|
||||
|
@ -1640,7 +1605,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
const props = getProps({
|
||||
timeShift: '1d',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper.find(TimeShift).find(EuiComboBox).prop('onCreateOption')!('1h', []);
|
||||
});
|
||||
|
@ -1664,7 +1629,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
const props = getProps({
|
||||
timeShift: '5 months',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
|
||||
expect(wrapper.find(TimeShift).find(EuiComboBox).prop('isInvalid')).toBeTruthy();
|
||||
|
||||
|
@ -1681,7 +1646,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
const props = getProps({
|
||||
timeShift: 'startAt(2022-11-02T00:00:00.000Z)',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
|
||||
expect(wrapper.find(TimeShift).find(EuiComboBox).prop('isInvalid')).toBeTruthy();
|
||||
|
||||
|
@ -1725,7 +1690,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
}
|
||||
|
||||
it('should not show custom options if time scaling is not available', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...getProps({
|
||||
operationType: 'terms',
|
||||
|
@ -1744,7 +1709,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show custom options if filtering is available', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...getProps({})} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...getProps({})} />);
|
||||
act(() => {
|
||||
findTestSubject(wrapper, 'indexPattern-advanced-accordion').simulate('click');
|
||||
});
|
||||
|
@ -1754,7 +1719,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show current filter if set', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...getProps({ filter: { language: 'kuery', query: 'a: b' } })}
|
||||
/>
|
||||
|
@ -1775,7 +1740,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
operationType: 'sum',
|
||||
label: 'Sum of bytes per hour',
|
||||
});
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
act(() => {
|
||||
wrapper.find('button[data-test-subj="lns-indexPatternDimension-count"]').simulate('click');
|
||||
});
|
||||
|
@ -1801,7 +1766,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
filter: { language: 'kuery', query: 'a: b' },
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...props} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...props} />);
|
||||
|
||||
act(() => {
|
||||
const { updateLayer, columnId, layer } = wrapper.find(Filtering).props();
|
||||
|
@ -1832,7 +1797,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should render invalid field if field reference is broken', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={{
|
||||
|
@ -1861,7 +1826,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should support selecting the operation before the field', async () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} columnId={'col2'} />
|
||||
);
|
||||
await act(async () => {
|
||||
|
@ -1915,7 +1880,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should select operation directly if only one field is possible', async () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
columnId={'col2'}
|
||||
|
@ -1959,7 +1924,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should select operation directly if only document is possible', async () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} columnId={'col2'} />
|
||||
);
|
||||
await act(async () => {
|
||||
|
@ -1991,7 +1956,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate compatible fields when selecting the operation first', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} columnId={'col2'} />
|
||||
);
|
||||
|
||||
|
@ -2015,7 +1980,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should indicate document compatibility when document operation is selected', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={getStateWithColumns({
|
||||
|
@ -2037,7 +2002,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should not update when selecting the current field again', async () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
const comboBox = getFieldSelectComboBox(wrapper);
|
||||
|
||||
|
@ -2053,7 +2018,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should show all operations that are not filtered out', () => {
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
filterOperations={(op: OperationMetadata) => !op.isBucketed && op.dataType === 'number'}
|
||||
|
@ -2086,7 +2051,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
// Prevents field format from being loaded
|
||||
setState.mockImplementation(() => {});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} columnId={'col2'} />
|
||||
);
|
||||
|
||||
|
@ -2129,7 +2094,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
const initialState: FormBasedPrivateState = getStateWithColumns({
|
||||
col1: bytesColumn,
|
||||
});
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={initialState} />
|
||||
);
|
||||
act(() => {
|
||||
|
@ -2149,7 +2114,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
});
|
||||
|
||||
it('should keep the latest valid dimension when removing the selection in field combobox', () => {
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
act(() => {
|
||||
getFieldSelectComboBox(wrapper as ReactWrapper).prop('onChange')!([]);
|
||||
});
|
||||
|
@ -2169,7 +2134,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithNumberCol} />
|
||||
);
|
||||
|
||||
|
@ -2213,7 +2178,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithNumberCol} />
|
||||
);
|
||||
|
||||
|
@ -2254,7 +2219,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithNumberCol} />
|
||||
);
|
||||
|
||||
|
@ -2286,7 +2251,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
|
||||
it('should hide the top level field selector when switching from non-reference to reference', async () => {
|
||||
(generateId as jest.Mock).mockReturnValue(`second`);
|
||||
wrapper = mountWithServices(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
wrapper = mountWithProviders(<FormBasedDimensionEditorComponent {...defaultProps} />);
|
||||
|
||||
expect(wrapper.find('ReferenceEditor')).toHaveLength(0);
|
||||
|
||||
|
@ -2311,7 +2276,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithReferences} />
|
||||
);
|
||||
|
||||
|
@ -2337,7 +2302,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithInvalidCol} />
|
||||
);
|
||||
|
||||
|
@ -2362,7 +2327,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
}),
|
||||
};
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={stateWithoutTime}
|
||||
|
@ -2425,7 +2390,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
}),
|
||||
};
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...props} state={stateWithInvalidCol} />
|
||||
);
|
||||
|
||||
|
@ -2444,7 +2409,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithFormulaColumn} />
|
||||
);
|
||||
|
||||
|
@ -2465,7 +2430,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithFormulaColumn} />
|
||||
);
|
||||
|
||||
|
@ -2484,7 +2449,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
supportStaticValue
|
||||
|
@ -2500,7 +2465,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
it('should select the quick function tab by default', () => {
|
||||
const stateWithNoColumn: FormBasedPrivateState = getStateWithColumns({});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent {...defaultProps} state={stateWithNoColumn} />
|
||||
);
|
||||
|
||||
|
@ -2515,7 +2480,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
it('should select the static value tab when supported by default', () => {
|
||||
const stateWithNoColumn: FormBasedPrivateState = getStateWithColumns({});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
supportStaticValue
|
||||
|
@ -2540,7 +2505,7 @@ describe('FormBasedDimensionEditor', () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper = mountWithServices(
|
||||
wrapper = mountWithProviders(
|
||||
<FormBasedDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={stateWithFormulaColumn}
|
||||
|
|
|
@ -12,10 +12,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './dimension_editor.scss';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCallOut, EuiButtonGroup, EuiFormRow } from '@elastic/eui';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiButtonGroup,
|
||||
EuiFormRow,
|
||||
type UseEuiTheme,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { nonNullable } from '../../../utils';
|
||||
import {
|
||||
operationDefinitionMap,
|
||||
|
@ -141,6 +147,7 @@ export const CalloutWarning = ({
|
|||
currentOperationType: keyof typeof operationDefinitionMap | undefined;
|
||||
temporaryStateType: TemporaryState;
|
||||
}) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
if (
|
||||
temporaryStateType === 'none' ||
|
||||
(currentOperationType != null && isQuickFunction(currentOperationType))
|
||||
|
@ -154,7 +161,7 @@ export const CalloutWarning = ({
|
|||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
className="lnsIndexPatternDimensionEditor__warning"
|
||||
css={dimensionEditorWarningStyles(euiThemeContext)}
|
||||
size="s"
|
||||
title={i18n.translate('xpack.lens.indexPattern.staticValueWarning', {
|
||||
defaultMessage: 'Static value currently applied',
|
||||
|
@ -174,7 +181,7 @@ export const CalloutWarning = ({
|
|||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
className="lnsIndexPatternDimensionEditor__warning"
|
||||
css={dimensionEditorWarningStyles(euiThemeContext)}
|
||||
size="s"
|
||||
title={i18n.translate('xpack.lens.indexPattern.formulaWarning', {
|
||||
defaultMessage: 'Formula currently applied',
|
||||
|
@ -252,3 +259,10 @@ export const DimensionEditorButtonGroups = ({
|
|||
</EuiFormRow>
|
||||
);
|
||||
};
|
||||
|
||||
const dimensionEditorWarningStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
margin-bottom: ${euiTheme.size.base};
|
||||
margin-top: ${euiTheme.size.s};
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.lnFieldSelect__option--incompatible {
|
||||
color: $euiColorLightShade;
|
||||
}
|
||||
|
||||
.lnFieldSelect__option--nonExistant {
|
||||
background-color: $euiColorLightestShade;
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './field_select.scss';
|
||||
import { partition } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
|
|
@ -8,11 +8,9 @@
|
|||
import React from 'react';
|
||||
import { FormatSelector, FormatSelectorProps } from './format_selector';
|
||||
import { GenericIndexPatternColumn } from '../../..';
|
||||
import { LensAppServices } from '../../../app_plugin/types';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { coreMock, docLinksServiceMock } from '@kbn/core/public/mocks';
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { renderWithProviders } from '../../../test_utils/test_utils';
|
||||
import { docLinksServiceMock } from '@kbn/core/public/mocks';
|
||||
import { fireEvent, screen, within } from '@testing-library/react';
|
||||
import userEvent, { type UserEvent } from '@testing-library/user-event';
|
||||
|
||||
const props = {
|
||||
|
@ -30,39 +28,10 @@ const props = {
|
|||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
function createMockServices(): LensAppServices {
|
||||
const services = coreMock.createStart();
|
||||
services.uiSettings.get.mockImplementation(() => '0.0');
|
||||
return {
|
||||
...services,
|
||||
docLinks: {
|
||||
links: {
|
||||
indexPatterns: { fieldFormattersNumber: '' },
|
||||
},
|
||||
},
|
||||
} as unknown as LensAppServices;
|
||||
}
|
||||
|
||||
const renderFormatSelector = (propsOverrides?: Partial<FormatSelectorProps>) => {
|
||||
const WrappingComponent: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
return (
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={createMockServices()}>{children}</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
};
|
||||
return render(<FormatSelector {...props} {...propsOverrides} />, {
|
||||
wrapper: WrappingComponent,
|
||||
});
|
||||
return renderWithProviders(<FormatSelector {...props} {...propsOverrides} />);
|
||||
};
|
||||
|
||||
// Skipped for update of userEvent v14: https://github.com/elastic/kibana/pull/189949
|
||||
// It looks like the individual tests within each it block are not really pure,
|
||||
// see for example the first two tests, they run the same code but expect
|
||||
// different results. With the updated userEvent code the tests no longer work
|
||||
// with this setup and should be refactored.
|
||||
describe('FormatSelector', () => {
|
||||
let user: UserEvent;
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './dimension_editor.scss';
|
||||
import React, { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFormRowProps, EuiSpacer, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
@ -32,6 +31,7 @@ import type { FormBasedLayer } from '../types';
|
|||
import type { IndexPattern, IndexPatternField, ParamEditorCustomProps } from '../../../types';
|
||||
import type { FormBasedDimensionEditorProps } from './dimension_panel';
|
||||
import { FormRow } from '../operations/definitions/shared_components';
|
||||
import { operationsButtonStyles } from './shared_styles';
|
||||
|
||||
const operationDisplay = getOperationDisplay();
|
||||
|
||||
|
@ -56,7 +56,7 @@ const getFunctionOptions = (
|
|||
return {
|
||||
label,
|
||||
value: operationType,
|
||||
className: 'lnsIndexPatternDimensionEditor__operation',
|
||||
css: operationsButtonStyles,
|
||||
'data-test-subj': `lns-indexPatternDimension-${operationType}${
|
||||
isCompatible ? '' : ' incompatible'
|
||||
}`,
|
||||
|
@ -204,7 +204,7 @@ export const ReferenceEditor = (props: ReferenceEditorProps) => {
|
|||
const brokenFunctionOption = {
|
||||
label: selectedOperationType && operationDisplay[selectedOperationType].displayName,
|
||||
value: selectedOperationType,
|
||||
className: 'lnsIndexPatternDimensionEditor__operation',
|
||||
css: operationsButtonStyles,
|
||||
'data-test-subj': `lns-indexPatternDimension-${selectedOperationType} incompatible`,
|
||||
} as EuiComboBoxOptionOption<string>;
|
||||
functionOptions?.push(brokenFunctionOption);
|
||||
|
|
|
@ -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 { UseEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const operationsButtonStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
> button {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
min-block-size: ${euiTheme.size.l};
|
||||
}
|
||||
`;
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
.lnsHelpPopover__panel {
|
||||
max-inline-size: $euiSize * 30 !important;
|
||||
}
|
||||
|
||||
.lnsHelpPopover__content {
|
||||
max-height: 40vh;
|
||||
padding: $euiSizeM;
|
||||
@include euiYScrollWithShadows;
|
||||
}
|
||||
|
||||
.lnsHelpPopover__buttonIcon {
|
||||
margin-right: $euiSizeXS;
|
||||
}
|
|
@ -16,10 +16,12 @@ import {
|
|||
EuiWrappingPopoverProps,
|
||||
EuiPopoverTitle,
|
||||
EuiText,
|
||||
type UseEuiTheme,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { css } from '@emotion/react';
|
||||
import { StartServices } from '../../types';
|
||||
import './help_popover.scss';
|
||||
|
||||
export const HelpPopoverButton = ({
|
||||
children,
|
||||
|
@ -28,17 +30,39 @@ export const HelpPopoverButton = ({
|
|||
children: string;
|
||||
onClick: EuiLinkButtonProps['onClick'];
|
||||
}) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
return (
|
||||
<EuiText size="xs">
|
||||
<EuiLink onClick={onClick}>
|
||||
<EuiIcon className="lnsHelpPopover__buttonIcon" size="s" type="help" />
|
||||
|
||||
<EuiIcon size="s" type="help" css={helpPopoverStyles.button(euiThemeContext)} />
|
||||
{children}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
|
||||
const HelpPopoverContent = ({ title, children }: { title?: string; children: ReactNode }) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
return (
|
||||
<>
|
||||
{title && <EuiPopoverTitle paddingSize="m">{title}</EuiPopoverTitle>}
|
||||
<EuiText className="eui-yScroll" size="s" css={helpPopoverStyles.content(euiThemeContext)}>
|
||||
{children}
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const helpPopoverStyles = {
|
||||
button: ({ euiTheme }: UseEuiTheme) => css`
|
||||
margin-right: ${euiTheme.size.xs};
|
||||
`,
|
||||
content: ({ euiTheme }: UseEuiTheme) => css`
|
||||
max-height: 40vh;
|
||||
padding: ${euiTheme.size.m};
|
||||
`,
|
||||
};
|
||||
|
||||
export const HelpPopover = ({
|
||||
anchorPosition,
|
||||
button,
|
||||
|
@ -58,18 +82,13 @@ export const HelpPopover = ({
|
|||
<EuiPopover
|
||||
anchorPosition={anchorPosition}
|
||||
button={button}
|
||||
className="lnsHelpPopover"
|
||||
closePopover={closePopover}
|
||||
isOpen={isOpen}
|
||||
ownFocus
|
||||
panelClassName="lnsHelpPopover__panel"
|
||||
panelStyle={{ maxInlineSize: '480px' }}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
{title && <EuiPopoverTitle paddingSize="m">{title}</EuiPopoverTitle>}
|
||||
|
||||
<EuiText className="lnsHelpPopover__content" size="s">
|
||||
{children}
|
||||
</EuiText>
|
||||
<HelpPopoverContent title={title}>{children}</HelpPopoverContent>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
@ -96,18 +115,13 @@ export const WrappingHelpPopover = ({
|
|||
<EuiWrappingPopover
|
||||
anchorPosition={anchorPosition}
|
||||
button={button}
|
||||
className="lnsHelpPopover"
|
||||
closePopover={closePopover}
|
||||
isOpen={isOpen}
|
||||
ownFocus
|
||||
panelClassName="lnsHelpPopover__panel"
|
||||
panelStyle={{ maxInlineSize: '480px' }}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
{title && <EuiPopoverTitle paddingSize="m">{title}</EuiPopoverTitle>}
|
||||
|
||||
<EuiText className="lnsHelpPopover__content" size="s">
|
||||
{children}
|
||||
</EuiText>
|
||||
<HelpPopoverContent title={title}>{children}</HelpPopoverContent>
|
||||
</EuiWrappingPopover>
|
||||
</KibanaRenderContextProvider>
|
||||
);
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
import React from 'react';
|
||||
import { FormBasedPrivateState } from './types';
|
||||
import { FormBasedLayerPanelProps, LayerPanel } from './layerpanel';
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { fireEvent, screen, within } from '@testing-library/react';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
import { TermsIndexPatternColumn } from './operations';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders } from '../../test_utils/test_utils';
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', { value: 400 });
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { value: 200 });
|
||||
|
@ -225,7 +226,7 @@ describe('Layer Data Panel', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const renderLayerPanel = () => render(<LayerPanel {...defaultProps} />);
|
||||
const renderLayerPanel = () => renderWithProviders(<LayerPanel {...defaultProps} />);
|
||||
|
||||
it('should list all index patterns', async () => {
|
||||
renderLayerPanel();
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.lnsIndexPatternDimensionEditor__filtersEditor {
|
||||
width: $euiSize * 60;
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './filter_popover.scss';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { EuiPopover, EuiSpacer } from '@elastic/eui';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
|
@ -66,7 +64,9 @@ export const FilterPopover = ({
|
|||
return (
|
||||
<EuiPopover
|
||||
data-test-subj="indexPattern-filters-existingFilterContainer"
|
||||
panelClassName="lnsIndexPatternDimensionEditor__filtersEditor"
|
||||
panelStyle={{
|
||||
width: '960px',
|
||||
}}
|
||||
isOpen={isOpen}
|
||||
ownFocus
|
||||
closePopover={closePopover}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
.lnsFiltersOperation__popoverButton {
|
||||
@include euiTextBreakWord;
|
||||
@include euiFontSizeS;
|
||||
min-height: $euiSizeXL;
|
||||
width: 100%;
|
||||
}
|
|
@ -13,12 +13,13 @@ import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public';
|
|||
import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import type { FiltersIndexPatternColumn } from '.';
|
||||
import { filtersOperation } from '..';
|
||||
import type { FormBasedLayer } from '../../../types';
|
||||
import { createMockedIndexPattern } from '../../../mocks';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders } from '../../../../../test_utils/test_utils';
|
||||
|
||||
const uiSettingsMock = {} as IUiSettingsClient;
|
||||
|
||||
|
@ -304,7 +305,7 @@ describe('filters', () => {
|
|||
// Workaround for timeout via https://github.com/testing-library/user-event/issues/833#issuecomment-1171452841
|
||||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
const updateLayerSpy = jest.fn();
|
||||
render(
|
||||
renderWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultProps}
|
||||
layer={layer}
|
||||
|
@ -346,7 +347,7 @@ describe('filters', () => {
|
|||
describe('Modify filters', () => {
|
||||
it('should correctly show existing filters ', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
render(
|
||||
renderWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultProps}
|
||||
layer={layer}
|
||||
|
@ -366,7 +367,7 @@ describe('filters', () => {
|
|||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
jest.useFakeTimers();
|
||||
const updateLayerSpy = jest.fn();
|
||||
render(
|
||||
renderWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultProps}
|
||||
layer={layer}
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './filters.scss';
|
||||
import React, { useState } from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFormRow, EuiLink, htmlIdGenerator } from '@elastic/eui';
|
||||
import { EuiFormRow, EuiLink, htmlIdGenerator, useEuiTheme } from '@elastic/eui';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
import type { AggFunctionsMapping } from '@kbn/data-plugin/public';
|
||||
import { queryFilterToAst } from '@kbn/data-plugin/common';
|
||||
|
@ -27,6 +26,7 @@ import type { BaseIndexPatternColumn } from '../column_types';
|
|||
import { FilterPopover } from './filter_popover';
|
||||
import { TermsIndexPatternColumn } from '../terms';
|
||||
import { isColumnOfType } from '../helpers';
|
||||
import { draggablePopoverButtonStyles } from '../styles';
|
||||
|
||||
const generateId = htmlIdGenerator();
|
||||
const OPERATION_NAME = 'filters';
|
||||
|
@ -181,6 +181,7 @@ export const FilterList = ({
|
|||
indexPattern: IndexPattern;
|
||||
defaultQuery: Filter;
|
||||
}) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const [activeFilterId, setActiveFilterId] = useState('');
|
||||
const [localFilters, setLocalFilters] = useState(() =>
|
||||
filters.map((filter) => ({ ...filter, id: generateId() }))
|
||||
|
@ -275,6 +276,7 @@ export const FilterList = ({
|
|||
title={i18n.translate('xpack.lens.indexPattern.filters.clickToEdit', {
|
||||
defaultMessage: 'Click to edit',
|
||||
})}
|
||||
css={draggablePopoverButtonStyles(euiThemeContext)}
|
||||
>
|
||||
{filter.label || (filter.input.query as string) || defaultLabel}
|
||||
</EuiLink>
|
||||
|
@ -285,9 +287,7 @@ export const FilterList = ({
|
|||
})}
|
||||
</DragDropBuckets>
|
||||
<NewBucketButton
|
||||
onClick={() => {
|
||||
onAddFilter();
|
||||
}}
|
||||
onClick={onAddFilter}
|
||||
label={i18n.translate('xpack.lens.indexPattern.filters.addaFilter', {
|
||||
defaultMessage: 'Add a filter',
|
||||
})}
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
.lnsFormula {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.lnsIndexPatternDimensionEditor-isFullscreen & {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
& > * {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
& > * + * {
|
||||
border-top: $euiBorderThin;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFormula__editor {
|
||||
.lnsIndexPatternDimensionEditor-isFullscreen & {
|
||||
border-bottom: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
& > * + * {
|
||||
border-top: $euiBorderThin;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFormula__editorHeader,
|
||||
.lnsFormula__editorFooter {
|
||||
padding: $euiSizeS;
|
||||
}
|
||||
|
||||
.lnsFormula__editorFooter {
|
||||
// make sure docs are rendered in front of monaco
|
||||
z-index: 1;
|
||||
border-bottom-right-radius: $euiBorderRadius;
|
||||
border-bottom-left-radius: $euiBorderRadius;
|
||||
}
|
||||
|
||||
.lnsFormula__editorHeaderGroup,
|
||||
.lnsFormula__editorFooterGroup {
|
||||
display: block; // Overrides EUI's styling of `display: flex` on `EuiFlexItem` components
|
||||
}
|
||||
|
||||
.lnsFormula__editorContent {
|
||||
background-color: $euiColorBackgroundBasePlain;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
|
||||
.lnsIndexPatternDimensionEditor:not(.lnsIndexPatternDimensionEditor-isFullscreen) & {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.lnsIndexPatternDimensionEditor-isFullscreen & {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFormula__editorPlaceholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: $euiSize;
|
||||
right: 0;
|
||||
color: $euiTextSubduedColor;
|
||||
// Matches monaco editor
|
||||
font-family: Menlo, Monaco, 'Courier New', monospace;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lnsFormula__warningText + .lnsFormula__warningText {
|
||||
margin-top: $euiSizeS;
|
||||
border-top: $euiBorderThin;
|
||||
padding-top: $euiSizeS;
|
||||
}
|
||||
|
||||
.lnsFormula__editorHelp--inline {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: $euiSizeXS;
|
||||
|
||||
& > * + * {
|
||||
margin-left: $euiSizeXS;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFormula__editorError {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lnsFormula__docs {
|
||||
background: $euiColorEmptyShade;
|
||||
}
|
||||
|
||||
.lnsFormulaOverflow {
|
||||
// Needs to be higher than the modal and all flyouts
|
||||
z-index: $euiZLevel9 + 1;
|
||||
}
|
|
@ -25,10 +25,10 @@ import {
|
|||
EuiToolTip,
|
||||
EuiSpacer,
|
||||
useEuiTheme,
|
||||
type UseEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import useUnmount from 'react-use/lib/useUnmount';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import classNames from 'classnames';
|
||||
import { CodeEditor, CodeEditorProps } from '@kbn/code-editor';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { useDebounceWithOptions } from '../../../../../../shared_components';
|
||||
|
@ -50,7 +50,6 @@ import {
|
|||
} from './math_completion';
|
||||
import { LANGUAGE_ID } from './math_tokenization';
|
||||
|
||||
import './formula.scss';
|
||||
import { FormulaIndexPatternColumn } from '../formula';
|
||||
import { insertOrReplaceFormulaColumn } from '../parse';
|
||||
import { filterByVisibleOperation } from '../util';
|
||||
|
@ -126,7 +125,8 @@ export function FormulaEditor({
|
|||
const disposables = React.useRef<monaco.IDisposable[]>([]);
|
||||
const editor1 = React.useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
|
||||
const visibleOperationsMap = useMemo(
|
||||
() => filterByVisibleOperation(operationDefinitionMap),
|
||||
|
@ -685,10 +685,12 @@ export function FormulaEditor({
|
|||
// in the behavior of Monaco when it's first loaded and then reloaded.
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
lnsIndexPatternDimensionEditor: true,
|
||||
'lnsIndexPatternDimensionEditor-isFullscreen': isFullscreen,
|
||||
})}
|
||||
css={[
|
||||
sharedEditorStyles.self(euiThemeContext),
|
||||
isFullscreen
|
||||
? fullscreenEditorStyles(euiThemeContext)
|
||||
: defaultEditorStyles(euiThemeContext),
|
||||
]}
|
||||
>
|
||||
{!isFullscreen && (
|
||||
<EuiFormLabel
|
||||
|
@ -705,17 +707,21 @@ export function FormulaEditor({
|
|||
|
||||
<div
|
||||
className="lnsFormula"
|
||||
css={{
|
||||
css={css({
|
||||
backgroundColor: euiTheme.colors.backgroundBaseSubdued,
|
||||
border: isFullscreen ? 'none' : euiTheme.border.thin,
|
||||
borderRadius: isFullscreen ? 0 : euiTheme.border.radius.medium,
|
||||
height: isFullscreen ? '100%' : 'auto',
|
||||
}}
|
||||
})}
|
||||
>
|
||||
<div className="lnsFormula__editor">
|
||||
<div className="lnsFormula__editorHeader">
|
||||
<div css={sharedEditorStyles.editorHeader(euiThemeContext)}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
|
||||
<EuiFlexItem className="lnsFormula__editorHeaderGroup">
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
display: block;
|
||||
`}
|
||||
>
|
||||
<EuiToolTip
|
||||
content={
|
||||
isWordWrapped
|
||||
|
@ -752,7 +758,12 @@ export function FormulaEditor({
|
|||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem className="lnsFormula__editorHeaderGroup" grow={false}>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
display: block;
|
||||
`}
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
onClick={() => {
|
||||
toggleFullscreen();
|
||||
|
@ -803,7 +814,7 @@ export function FormulaEditor({
|
|||
/>
|
||||
|
||||
{!text ? (
|
||||
<div className="lnsFormula__editorPlaceholder">
|
||||
<div css={sharedEditorStyles.editorPlaceholder(euiThemeContext)}>
|
||||
<EuiText color="subdued" size="s">
|
||||
{i18n.translate('xpack.lens.formulaPlaceholderText', {
|
||||
defaultMessage: 'Type a formula by combining functions with math, like:',
|
||||
|
@ -815,9 +826,9 @@ export function FormulaEditor({
|
|||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="lnsFormula__editorFooter">
|
||||
<div css={sharedEditorStyles.editorFooter(euiThemeContext)}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
|
||||
<EuiFlexItem className="lnsFormula__editorFooterGroup">
|
||||
<EuiFlexItem grow={false}>
|
||||
{isFullscreen ? (
|
||||
<EuiToolTip
|
||||
content={
|
||||
|
@ -837,6 +848,7 @@ export function FormulaEditor({
|
|||
defaultMessage: 'Hide function reference',
|
||||
})}
|
||||
className="lnsFormula__editorHelp lnsFormula__editorHelp--inline"
|
||||
css={sharedEditorStyles.editorHelpLink(euiThemeContext)}
|
||||
color="text"
|
||||
onClick={() => setIsHelpOpen(!isHelpOpen)}
|
||||
>
|
||||
|
@ -866,7 +878,7 @@ export function FormulaEditor({
|
|||
</EuiFlexItem>
|
||||
|
||||
{errorCount || warningCount ? (
|
||||
<EuiFlexItem className="lnsFormula__editorFooterGroup" grow={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
ownFocus={false}
|
||||
isOpen={isWarningOpen}
|
||||
|
@ -874,6 +886,9 @@ export function FormulaEditor({
|
|||
button={
|
||||
<EuiButtonEmpty
|
||||
color={errorCount ? 'danger' : 'warning'}
|
||||
css={css`
|
||||
white-space: nowrap;
|
||||
`}
|
||||
className="lnsFormula__editorError"
|
||||
iconType="warning"
|
||||
size="xs"
|
||||
|
@ -904,7 +919,10 @@ export function FormulaEditor({
|
|||
`}
|
||||
>
|
||||
{warnings.map(({ message, severity }, index) => (
|
||||
<div key={index} className="lnsFormula__warningText">
|
||||
<div
|
||||
key={index}
|
||||
css={index !== 0 && sharedEditorStyles.warningText(euiThemeContext)}
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
color={
|
||||
|
@ -926,13 +944,8 @@ export function FormulaEditor({
|
|||
{/* fix the css here */}
|
||||
{isFullscreen && isHelpOpen ? (
|
||||
<div
|
||||
className="lnsFormula__docs documentation__docs--inline"
|
||||
css={css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// make sure docs are rendered in front of monaco
|
||||
z-index: 1;
|
||||
`}
|
||||
className="documentation__docs--inline"
|
||||
css={sharedEditorStyles.formulaDocs(euiThemeContext)}
|
||||
>
|
||||
<LanguageDocumentationPopoverContent
|
||||
language="Formula"
|
||||
|
@ -944,3 +957,101 @@ export function FormulaEditor({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const sharedEditorStyles = {
|
||||
self: ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
.lnsFormula {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > * {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
& > * + * {
|
||||
border-top: ${euiTheme.border.thin};
|
||||
}
|
||||
}
|
||||
.lnsFormulaOverflow {
|
||||
// Needs to be higher than the modal and all flyouts
|
||||
z-index: ${euiTheme.levels.toast} + 1;
|
||||
}
|
||||
.lnsFormula__editorContent {
|
||||
background-color: ${euiTheme.colors.backgroundBasePlain};
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
},
|
||||
formulaDocs: ({ euiTheme }: UseEuiTheme) => css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// make sure docs are rendered in front of monaco
|
||||
z-index: 1;
|
||||
background: ${euiTheme.colors.backgroundBasePlain};
|
||||
`,
|
||||
editorHeader: ({ euiTheme }: UseEuiTheme) => css`
|
||||
padding: ${euiTheme.size.s};
|
||||
`,
|
||||
editorFooter: ({ euiTheme }: UseEuiTheme) => css`
|
||||
padding: ${euiTheme.size.s};
|
||||
// make sure docs are rendered in front of monaco
|
||||
z-index: 1;
|
||||
border-bottom-right-radius: ${euiTheme.border.radius.medium};
|
||||
border-bottom-left-radius: ${euiTheme.border.radius.medium};
|
||||
`,
|
||||
editorPlaceholder: ({ euiTheme }: UseEuiTheme) => css`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: ${euiTheme.size.base};
|
||||
right: 0;
|
||||
color: ${euiTheme.colors.textSubdued}
|
||||
// Matches monaco editor
|
||||
font-family: Menlo, Monaco, 'Courier New', monospace;
|
||||
pointer-events: none;
|
||||
`,
|
||||
warningText: ({ euiTheme }: UseEuiTheme) => css`
|
||||
margin-top: ${euiTheme.size.s};
|
||||
border-top: ${euiTheme.border.thin};
|
||||
padding-top: ${euiTheme.size.s};
|
||||
`,
|
||||
editorHelpLink: ({ euiTheme }: UseEuiTheme) => css`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: ${euiTheme.size.xs};
|
||||
& > * + * {
|
||||
margin-left: ${euiTheme.size.xs};
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
const defaultEditorStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
.lnsFormula__editorContent {
|
||||
height: 200px;
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
const fullscreenEditorStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
.lnsFormula__editor {
|
||||
border-bottom: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
& > * + * {
|
||||
border-top: ${euiTheme.border.thin};
|
||||
}
|
||||
}
|
||||
.lnsFormula__editorContent {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
.lnsRangesOperation__popoverButton {
|
||||
@include euiTextBreakWord;
|
||||
@include euiFontSizeS;
|
||||
min-height: $euiSizeXL;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lnsRangesOperation__popoverNumberField {
|
||||
width: 14ch; // Roughly 10 characters plus extra for the padding
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './advanced_editor.scss';
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
|
@ -21,6 +19,7 @@ import {
|
|||
EuiToolTip,
|
||||
htmlIdGenerator,
|
||||
keys,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { IFieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import {
|
||||
|
@ -28,11 +27,13 @@ import {
|
|||
DraggableBucketContainer,
|
||||
NewBucketButton,
|
||||
} from '@kbn/visualization-ui-components';
|
||||
import { css } from '@emotion/react';
|
||||
import { useDebounceWithOptions } from '../../../../../shared_components';
|
||||
import { RangeTypeLens, isValidRange } from './ranges';
|
||||
import { FROM_PLACEHOLDER, TO_PLACEHOLDER, TYPING_DEBOUNCE_TIME } from './constants';
|
||||
import { LabelInput } from '../shared_components';
|
||||
import { isValidNumber } from '../helpers';
|
||||
import { draggablePopoverButtonStyles } from '../styles';
|
||||
|
||||
const generateId = htmlIdGenerator();
|
||||
|
||||
|
@ -101,7 +102,9 @@ export const RangePopover = ({
|
|||
<EuiFlexGroup gutterSize="s" responsive={false} alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
className="lnsRangesOperation__popoverNumberField"
|
||||
css={css`
|
||||
width: 14ch; // Roughly 10 characters plus extra for the padding
|
||||
`}
|
||||
value={isValidNumber(from) ? Number(from) : ''}
|
||||
onChange={({ target }) => {
|
||||
const newRange = {
|
||||
|
@ -132,7 +135,9 @@ export const RangePopover = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
className="lnsRangesOperation__popoverNumberField"
|
||||
css={css`
|
||||
width: 14ch; // Roughly 10 characters plus extra for the padding
|
||||
`}
|
||||
value={isValidNumber(to) ? Number(to) : ''}
|
||||
inputRef={(node) => {
|
||||
if (toRef && node) {
|
||||
|
@ -203,6 +208,7 @@ export const AdvancedRangeEditor = ({
|
|||
onToggleEditor: () => void;
|
||||
formatter: IFieldFormat;
|
||||
}) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const [activeRangeId, setActiveRangeId] = useState('');
|
||||
// use a local state to store ids with range objects
|
||||
const [localRanges, setLocalRanges] = useState<LocalRangeType[]>(() =>
|
||||
|
@ -303,8 +309,8 @@ export const AdvancedRangeEditor = ({
|
|||
<EuiLink
|
||||
color="text"
|
||||
onClick={() => changeActiveRange(range.id)}
|
||||
className="lnsRangesOperation__popoverButton"
|
||||
data-test-subj="indexPattern-ranges-popover-trigger"
|
||||
data-test-subj="dataView-ranges-popover-trigger"
|
||||
css={draggablePopoverButtonStyles(euiThemeContext)}
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { EuiFieldNumber, EuiRange, EuiButtonEmpty, EuiLink, EuiText } from '@elastic/eui';
|
||||
import { IUiSettingsClient, HttpSetup } from '@kbn/core/public';
|
||||
|
@ -15,6 +14,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
|||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
|
||||
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
||||
import { mountWithProviders } from '../../../../../test_utils/test_utils';
|
||||
import type { FormBasedLayer } from '../../../types';
|
||||
import { rangeOperation } from '..';
|
||||
import { RangeIndexPatternColumn } from './ranges';
|
||||
|
@ -376,7 +376,7 @@ describe('ranges', () => {
|
|||
|
||||
it('should start update the state with the default maxBars value', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -392,7 +392,7 @@ describe('ranges', () => {
|
|||
it('should update state when changing Max bars number', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -435,7 +435,7 @@ describe('ranges', () => {
|
|||
it('should update the state using the plus or minus buttons by the step amount', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -505,7 +505,7 @@ describe('ranges', () => {
|
|||
it('should show one range interval to start with', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -521,7 +521,7 @@ describe('ranges', () => {
|
|||
it('should use the parentFormat to create the trigger label', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -532,7 +532,7 @@ describe('ranges', () => {
|
|||
);
|
||||
|
||||
expect(
|
||||
instance.find('[data-test-subj="indexPattern-ranges-popover-trigger"]').first().text()
|
||||
instance.find('[data-test-subj="dataView-ranges-popover-trigger"]').first().text()
|
||||
).toBe('0 - 1000');
|
||||
});
|
||||
|
||||
|
@ -541,7 +541,7 @@ describe('ranges', () => {
|
|||
// we intercept the formatter without an id assigned an print "Error"
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -560,14 +560,14 @@ describe('ranges', () => {
|
|||
);
|
||||
|
||||
expect(
|
||||
instance.find('[data-test-subj="indexPattern-ranges-popover-trigger"]').first().text()
|
||||
instance.find('[data-test-subj="dataView-ranges-popover-trigger"]').first().text()
|
||||
).not.toBe('Error');
|
||||
});
|
||||
|
||||
it('should add a new range', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -621,7 +621,7 @@ describe('ranges', () => {
|
|||
it('should add a new range with custom label', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -674,7 +674,7 @@ describe('ranges', () => {
|
|||
it('should open a popover to edit an existing range', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -721,7 +721,7 @@ describe('ranges', () => {
|
|||
it('should not accept invalid ranges', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -771,7 +771,7 @@ describe('ranges', () => {
|
|||
label: '',
|
||||
});
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -809,7 +809,7 @@ describe('ranges', () => {
|
|||
label: '',
|
||||
});
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -841,7 +841,7 @@ describe('ranges', () => {
|
|||
it('should correctly handle the default formatter for the field', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -871,7 +871,7 @@ describe('ranges', () => {
|
|||
params: { decimals: 0 },
|
||||
};
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -895,7 +895,7 @@ describe('ranges', () => {
|
|||
it('should not update the state on mount', () => {
|
||||
const updateLayerSpy = jest.fn();
|
||||
|
||||
mount(
|
||||
mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
@ -915,7 +915,7 @@ describe('ranges', () => {
|
|||
params: { decimals: 3 },
|
||||
};
|
||||
|
||||
const instance = mount(
|
||||
const instance = mountWithProviders(
|
||||
<InlineOptions
|
||||
{...defaultOptions}
|
||||
layer={layer}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.lnsIndexPatternDimensionEditor__labelCustomRank {
|
||||
min-width: 96px;
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiFormLabel, EuiFormRow, EuiFormRowProps } from '@elastic/eui';
|
||||
import './form_row.scss';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
type FormRowProps = EuiFormRowProps & { isInline?: boolean };
|
||||
|
||||
|
@ -20,7 +20,11 @@ export const FormRow = ({ children, label, isInline, ...props }: FormRowProps) =
|
|||
<div data-test-subj={props['data-test-subj']}>
|
||||
{React.cloneElement(children, {
|
||||
prepend: (
|
||||
<EuiFormLabel className="lnsIndexPatternDimensionEditor__labelCustomRank">
|
||||
<EuiFormLabel
|
||||
css={css`
|
||||
min-width: 96px;
|
||||
`}
|
||||
>
|
||||
{label}
|
||||
</EuiFormLabel>
|
||||
),
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { euiFontSize, euiTextBreakWord, UseEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const draggablePopoverButtonStyles = (euiThemeContext: UseEuiTheme) => {
|
||||
return css`
|
||||
${euiTextBreakWord()};
|
||||
${euiFontSize(euiThemeContext, 's')};
|
||||
min-height: ${euiThemeContext.euiTheme.size.xl};
|
||||
width: 100%;
|
||||
`;
|
||||
};
|
|
@ -25,6 +25,7 @@ import {
|
|||
import { uniq } from 'lodash';
|
||||
import { AggFunctionsMapping } from '@kbn/data-plugin/public';
|
||||
import { buildExpressionFunction } from '@kbn/expressions-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { DOCUMENT_FIELD_NAME } from '../../../../../../common/constants';
|
||||
import { insertOrReplaceColumn, updateColumnParam, updateDefaultLabels } from '../../layer_helpers';
|
||||
import type { DataType, OperationMetadata } from '../../../../../types';
|
||||
|
@ -1008,6 +1009,9 @@ The top values of a specified field ranked by the chosen metric.
|
|||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiAccordion
|
||||
css={css`
|
||||
color: ${euiTheme.colors.primary};
|
||||
`}
|
||||
id="lnsTermsAdvanced"
|
||||
arrowProps={{ color: 'primary' }}
|
||||
buttonContent={
|
||||
|
|
|
@ -25,11 +25,13 @@ import {
|
|||
useGroupedFields,
|
||||
} from '@kbn/unified-field-list';
|
||||
import { OverrideFieldGroupDetails } from '@kbn/unified-field-list/src/types';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import type { DatasourceDataPanelProps } from '../../../types';
|
||||
import type { TextBasedPrivateState } from '../types';
|
||||
import { getStateFromAggregateQuery } from '../utils';
|
||||
import { FieldItem } from '../../common/field_item';
|
||||
import { getColumnsFromCache } from '../fieldlist_cache';
|
||||
import { dataPanelStyles } from '../../common/datapanel.styles';
|
||||
|
||||
const getCustomFieldType: GetCustomFieldType<DatatableColumn> = (field) => field?.meta.type;
|
||||
|
||||
|
@ -129,7 +131,7 @@ export function TextBasedDataPanel({
|
|||
},
|
||||
[hasSuggestionForField, dropOntoWorkspace]
|
||||
);
|
||||
|
||||
const euiThemeContext = useEuiTheme();
|
||||
return (
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
|
@ -137,7 +139,7 @@ export function TextBasedDataPanel({
|
|||
}}
|
||||
>
|
||||
<FieldList
|
||||
className="lnsInnerIndexPatternDataPanel"
|
||||
css={dataPanelStyles(euiThemeContext)}
|
||||
isProcessing={!dataHasLoaded}
|
||||
prepend={
|
||||
<FieldListFilters {...fieldListFiltersProps} data-test-subj="lnsTextBasedLanguages" />
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
Droppable,
|
||||
DroppableProps,
|
||||
} from '@kbn/dom-drag-drop';
|
||||
import { css } from '@emotion/react';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import { isDraggedField } from '../../../../utils';
|
||||
import {
|
||||
Datasource,
|
||||
|
@ -64,6 +66,7 @@ export function DraggableDimensionButton({
|
|||
indexPatterns: IndexPatternMap;
|
||||
}) {
|
||||
const [{ dragging }] = useDragDropContext();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
let getDropProps;
|
||||
|
||||
|
@ -139,6 +142,12 @@ export function DraggableDimensionButton({
|
|||
ref={registerNewButtonRefMemoized}
|
||||
className="lnsLayerPanel__dimensionContainer"
|
||||
data-test-subj={group.dataTestSubj}
|
||||
css={css`
|
||||
position: relative;
|
||||
& + & {
|
||||
margin-top: ${euiTheme.size.s};
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Draggable
|
||||
dragType={isOperation(dragging) ? 'move' : 'copy'}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './chart_switch.scss';
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiFlexItem,
|
||||
|
@ -36,7 +35,7 @@ export const ChartOption = ({
|
|||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon className="lnsChartSwitch__chartIcon" type={option.icon || 'empty'} />
|
||||
<EuiIcon type={option.icon || 'empty'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" data-test-subj="lnsChartSwitch-option-label">
|
||||
|
@ -88,7 +87,6 @@ const DataLossWarning = ({ content, id }: { content?: string; id: string }) => {
|
|||
color={euiTheme.colors.warning}
|
||||
content={content}
|
||||
iconProps={{
|
||||
className: 'lnsChartSwitch__chartIcon',
|
||||
'data-test-subj': `lnsChartSwitchPopoverAlert_${id}`,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
.lnsChartSwitch__header {
|
||||
> * {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsChartSwitch__options {
|
||||
width: 384px;
|
||||
}
|
||||
|
||||
.lnsChartSwitch__summaryIcon {
|
||||
transform: translateY(-1px);
|
||||
color: $euiTextSubduedColor;
|
||||
|
||||
@include euiBreakpoint('xl') {
|
||||
margin-right: $euiSizeS;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsChartSwitch__summaryText {
|
||||
@include euiBreakpoint('xs', 's', 'm', 'l') {
|
||||
@include euiScreenReaderOnly;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsChartSwitch__append {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
// Targeting img as this won't target normal EuiIcon's only the custom svgs's
|
||||
img.lnsChartSwitch__chartIcon { // stylelint-disable-line selector-no-qualifying-type
|
||||
// The large icons aren't square so max out the width to fill the height
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lnsChartSwitch__search {
|
||||
width: 10 * $euiSizeXXL;
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './chart_switch.scss';
|
||||
import React, { useState, useMemo, memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ExperimentalBadge } from '../../../../shared_components';
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './chart_switch.scss';
|
||||
import React, { useState, memo } from 'react';
|
||||
import { EuiPopover } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ChartSwitchTrigger } from '@kbn/visualization-ui-components';
|
||||
import { css } from '@emotion/react';
|
||||
import { useLensSelector, selectVisualization } from '../../../../state_management';
|
||||
import { ChartSwitch, ChartSwitchProps } from './chart_switch';
|
||||
|
||||
|
@ -31,28 +31,29 @@ export const ChartSwitchPopover = memo(function ChartSwitchPopover(
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="lnsChartSwitch__header">
|
||||
<EuiPopover
|
||||
id="lnsChartSwitchPopover"
|
||||
ownFocus
|
||||
initialFocus=".lnsChartSwitch__popoverPanel"
|
||||
panelClassName="lnsChartSwitch__popoverPanel"
|
||||
panelPaddingSize="none"
|
||||
repositionOnScroll
|
||||
button={
|
||||
<ChartSwitchTrigger
|
||||
icon={icon}
|
||||
label={label}
|
||||
dataTestSubj="lnsChartSwitchPopover"
|
||||
onClick={() => setFlyoutOpen(!flyoutOpen)}
|
||||
/>
|
||||
}
|
||||
isOpen={flyoutOpen}
|
||||
closePopover={() => setFlyoutOpen(false)}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
{flyoutOpen ? <ChartSwitch {...props} onChartSelect={() => setFlyoutOpen(false)} /> : null}
|
||||
</EuiPopover>
|
||||
</div>
|
||||
<EuiPopover
|
||||
css={css`
|
||||
display: flex;
|
||||
`}
|
||||
id="lnsChartSwitchPopover"
|
||||
ownFocus
|
||||
initialFocus=".lnsChartSwitch__popoverPanel"
|
||||
panelClassName="lnsChartSwitch__popoverPanel"
|
||||
panelPaddingSize="none"
|
||||
repositionOnScroll
|
||||
button={
|
||||
<ChartSwitchTrigger
|
||||
icon={icon}
|
||||
label={label}
|
||||
dataTestSubj="lnsChartSwitchPopover"
|
||||
onClick={() => setFlyoutOpen(!flyoutOpen)}
|
||||
/>
|
||||
}
|
||||
isOpen={flyoutOpen}
|
||||
closePopover={() => setFlyoutOpen(false)}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
{flyoutOpen ? <ChartSwitch {...props} onChartSelect={() => setFlyoutOpen(false)} /> : null}
|
||||
</EuiPopover>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -44,6 +44,9 @@ export const ChartSwitchSelectable = ({
|
|||
isPreFiltered
|
||||
data-test-subj="lnsChartSwitchList"
|
||||
className="lnsChartSwitch__options"
|
||||
css={css`
|
||||
width: 384px;
|
||||
`}
|
||||
height={computeListHeight(props.options as SelectableEntry[])}
|
||||
searchProps={{
|
||||
compressed: true,
|
||||
|
@ -51,6 +54,9 @@ export const ChartSwitchSelectable = ({
|
|||
inputRef: (ref) => {
|
||||
ref?.focus({ preventScroll: true });
|
||||
},
|
||||
css: css`
|
||||
width: 400px;
|
||||
`,
|
||||
className: 'lnsChartSwitch__search',
|
||||
'data-test-subj': 'lnsChartSwitchSearch',
|
||||
onChange: setSearchTerm,
|
||||
|
|
|
@ -24,7 +24,7 @@ import { coreMock } from '@kbn/core/public/mocks';
|
|||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
|
||||
import { generateId } from '../../../id_generator';
|
||||
import { mountWithProvider } from '../../../mocks';
|
||||
import { mountWithReduxStore } from '../../../mocks';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { createIndexPatternServiceMock } from '../../../mocks/data_views_service_mock';
|
||||
|
@ -82,7 +82,7 @@ describe('ConfigPanel', () => {
|
|||
query?: Query | AggregateQuery
|
||||
) {
|
||||
(generateId as jest.Mock).mockReturnValue(`newId`);
|
||||
return mountWithProvider(
|
||||
return mountWithReduxStore(
|
||||
<LayerPanels {...props} />,
|
||||
{
|
||||
preloadedState: {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo, memo, useCallback } from 'react';
|
||||
import { EuiForm } from '@elastic/eui';
|
||||
import { EuiForm, euiBreakpoint, useEuiTheme } from '@elastic/eui';
|
||||
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import {
|
||||
|
@ -15,6 +15,7 @@ import {
|
|||
} from '@kbn/unified-search-plugin/public';
|
||||
|
||||
import { DragDropIdentifier, DropType } from '@kbn/dom-drag-drop';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
changeIndexPattern,
|
||||
onDropToDimension,
|
||||
|
@ -62,6 +63,9 @@ export function LayerPanels(
|
|||
(state) => state.lens
|
||||
);
|
||||
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
|
||||
const dispatchLens = useLensDispatch();
|
||||
|
||||
const layerIds = activeVisualization.getLayerIds(visualization.state);
|
||||
|
@ -252,7 +256,20 @@ export function LayerPanels(
|
|||
const hideAddLayerButton = query && isOfAggregateQueryType(query);
|
||||
|
||||
return (
|
||||
<EuiForm className="lnsConfigPanel">
|
||||
<EuiForm
|
||||
className="eui-yScroll"
|
||||
css={css`
|
||||
.lnsApp & {
|
||||
padding: ${euiTheme.size.base} ${euiTheme.size.base} ${euiTheme.size.xl}
|
||||
calc(400px + ${euiTheme.size.base});
|
||||
margin-left: -400px;
|
||||
${euiBreakpoint(euiThemeContext, ['xs', 's', 'm'])} {
|
||||
padding-left: ${euiTheme.size.base};
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
{layerIds.map((layerId, layerIndex) => {
|
||||
const { hidden, groups } = activeVisualization.getConfiguration({
|
||||
layerId,
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
.lnsLayerAddButton {
|
||||
&:last-child {
|
||||
align-self: unset;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
.lnsLayerAddButton__label {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.lnsLayerAddButton__techBadge,
|
||||
.lnsLayerAddButton__techBadge * {
|
||||
cursor: pointer;
|
||||
}}
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './dimension_container.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { FlyoutContainer } from '../../../shared_components/flyout_container';
|
||||
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
@import '../../../mixins';
|
||||
|
||||
.lnsLayerPanel {
|
||||
margin-bottom: $euiSize;
|
||||
|
||||
// disable focus ring for mouse clicks, leave it for keyboard users
|
||||
&:focus:not(:focus-visible) {
|
||||
animation: none !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
}
|
||||
|
||||
.lnsLayerPanel__layerHeader {
|
||||
padding: $euiSize;
|
||||
border-bottom: $euiBorderThin;
|
||||
}
|
||||
|
||||
// fixes truncation for too long chart switcher labels
|
||||
.lnsLayerPanel__layerSettingsWrapper {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.lnsLayerPanel__settingsStaticHeader {
|
||||
padding-left: $euiSizeXS;
|
||||
}
|
||||
|
||||
.lnsLayerPanel__settingsStaticHeaderIcon {
|
||||
margin-right: $euiSizeS;
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
.lnsLayerPanel__settingsStaticHeaderTitle {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.lnsLayerPanel__row {
|
||||
padding: $euiSize;
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 $euiBorderRadius $euiBorderRadius;
|
||||
}
|
||||
|
||||
// Add border to the top of the next same panel
|
||||
&+& {
|
||||
border-top: $euiBorderThin;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&>* {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// Targeting EUI class as we are unable to apply a class to this element in component
|
||||
&,
|
||||
.euiFormRow__fieldWrapper {
|
||||
&>*+* {
|
||||
margin-top: $euiSizeS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lnsLayerPanel__group {
|
||||
margin: (-$euiSizeXS) (-$euiSize);
|
||||
padding: $euiSizeXS $euiSize;
|
||||
}
|
||||
|
||||
.lnsLayerPanel__styleEditor {
|
||||
padding: $euiSize;
|
||||
}
|
||||
|
||||
// Start dimension style overrides
|
||||
|
||||
.lnsLayerPanel__dimensionContainer {
|
||||
position: relative;
|
||||
&+& {
|
||||
margin-top: $euiSizeS;
|
||||
}
|
||||
}
|
||||
|
||||
.domDroppable--replacing {
|
||||
.dimensionTrigger__textLabel {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
// Added .lnsLayerPanel__dimension specificity required for animation style override
|
||||
.lnsLayerPanel__dimension .lnsLayerPanel__dimensionLink {
|
||||
&:focus {
|
||||
background-color: transparent;
|
||||
text-decoration-thickness: $euiBorderWidthThin !important;
|
||||
@include passDownFocusRing('.dimensionTrigger__textLabel');
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import {
|
|||
createMockVisualization,
|
||||
createMockFramePublicAPI,
|
||||
createMockDatasource,
|
||||
mountWithProvider,
|
||||
mountWithReduxStore,
|
||||
createMockedDragDropContext,
|
||||
renderWithReduxStore,
|
||||
} from '../../../mocks';
|
||||
|
@ -703,7 +703,7 @@ describe('LayerPanel', () => {
|
|||
target.columnId !== 'a' ? { dropTypes: ['field_replace'], nextLabel: '' } : undefined
|
||||
);
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingField })}>
|
||||
<LayerPanel {...getDefaultProps()} />
|
||||
</ChildDragDropProvider>
|
||||
|
@ -762,7 +762,7 @@ describe('LayerPanel', () => {
|
|||
nextLabel: '',
|
||||
});
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||
<LayerPanel {...getDefaultProps()} />
|
||||
</ChildDragDropProvider>
|
||||
|
@ -822,7 +822,7 @@ describe('LayerPanel', () => {
|
|||
const holder = document.createElement('div');
|
||||
document.body.appendChild(holder);
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||
<LayerPanel {...getDefaultProps()} />
|
||||
</ChildDragDropProvider>,
|
||||
|
@ -860,7 +860,7 @@ describe('LayerPanel', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||
<LayerPanel {...getDefaultProps()} />
|
||||
</ChildDragDropProvider>
|
||||
|
@ -897,7 +897,7 @@ describe('LayerPanel', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||
<LayerPanel {...getDefaultProps()} activeVisualization={mockVis} />
|
||||
</ChildDragDropProvider>
|
||||
|
@ -938,7 +938,7 @@ describe('LayerPanel', () => {
|
|||
mockDatasource.onDrop.mockReturnValue(true);
|
||||
const updateVisualization = jest.fn();
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||
<LayerPanel
|
||||
{...getDefaultProps()}
|
||||
|
@ -989,7 +989,7 @@ describe('LayerPanel', () => {
|
|||
mockDatasource.onDrop.mockReturnValue(false);
|
||||
const updateVisualization = jest.fn();
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||
<LayerPanel
|
||||
{...getDefaultProps()}
|
||||
|
@ -1029,7 +1029,7 @@ describe('LayerPanel', () => {
|
|||
|
||||
mockDatasource.onDrop.mockReturnValue(true);
|
||||
|
||||
const { instance } = await mountWithProvider(
|
||||
const { instance } = mountWithReduxStore(
|
||||
<ChildDragDropProvider value={createMockedDragDropContext({ dragging: draggingOperation })}>
|
||||
<LayerPanel {...getDefaultProps()} />
|
||||
</ChildDragDropProvider>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './layer_panel.scss';
|
||||
|
||||
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import {
|
||||
EuiPanel,
|
||||
|
@ -16,6 +14,7 @@ import {
|
|||
EuiFormRow,
|
||||
EuiText,
|
||||
EuiIconTip,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
|
@ -51,6 +50,7 @@ export function LayerPanel(props: LayerPanelProps) {
|
|||
}>({});
|
||||
|
||||
const [isPanelSettingsOpen, setPanelSettingsOpen] = useState(false);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const {
|
||||
framePublicAPI,
|
||||
|
@ -366,13 +366,30 @@ export function LayerPanel(props: LayerPanelProps) {
|
|||
<section
|
||||
tabIndex={-1}
|
||||
ref={registerLayerRef}
|
||||
className="lnsLayerPanel"
|
||||
css={css`
|
||||
margin-bottom: ${euiTheme.size.base};
|
||||
// disable focus ring for mouse clicks, leave it for keyboard users
|
||||
&:focus:not(:focus-visible) {
|
||||
animation: none !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
`}
|
||||
data-test-subj={`lns-layerPanel-${layerIndex}`}
|
||||
>
|
||||
<EuiPanel paddingSize="none" hasShadow={false} hasBorder>
|
||||
<header className="lnsLayerPanel__layerHeader">
|
||||
<header
|
||||
className="lnsLayerPanel__layerHeader"
|
||||
css={css`
|
||||
padding: ${euiTheme.size.base};
|
||||
border-bottom: ${euiTheme.border.thin};
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" responsive={false} alignItems="center">
|
||||
<EuiFlexItem grow className="lnsLayerPanel__layerSettingsWrapper">
|
||||
<EuiFlexItem
|
||||
grow
|
||||
css={css`
|
||||
min-width: 0; // fixes truncation for too long chart switcher labels
|
||||
`}
|
||||
>
|
||||
<LayerHeader
|
||||
layerConfigProps={{
|
||||
...layerVisualizationConfigProps,
|
||||
|
@ -485,6 +502,31 @@ export function LayerPanel(props: LayerPanelProps) {
|
|||
const isOptional = !group.requiredMinDimensionCount && !group.suggestedValue;
|
||||
return (
|
||||
<EuiFormRow
|
||||
css={css`
|
||||
padding: ${euiTheme.size.base};
|
||||
&:last-child {
|
||||
border-radius: 0 0 ${euiTheme.border.radius.medium}
|
||||
${euiTheme.border.radius.medium};
|
||||
}
|
||||
|
||||
// Add border to the top of the next same panel
|
||||
& + & {
|
||||
border-top: ${euiTheme.border.thin};
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// Targeting EUI class as we are unable to apply a class to this element in component
|
||||
&,
|
||||
.euiFormRow__fieldWrapper {
|
||||
& > * + * {
|
||||
margin-top: ${euiTheme.size.s};
|
||||
}
|
||||
}
|
||||
`}
|
||||
className="lnsLayerPanel__row"
|
||||
fullWidth
|
||||
label={
|
||||
|
@ -523,8 +565,11 @@ export function LayerPanel(props: LayerPanelProps) {
|
|||
<>
|
||||
{group.accessors.length ? (
|
||||
<ReorderProvider
|
||||
className={'lnsLayerPanel__group'}
|
||||
dataTestSubj="lnsDragDrop"
|
||||
css={css`
|
||||
margin: -${euiTheme.size.xs} -${euiTheme.size.base};
|
||||
padding: ${euiTheme.size.xs} ${euiTheme.size.base};
|
||||
`}
|
||||
>
|
||||
{group.accessors.map((accessorConfig, accessorIndex) => {
|
||||
const { columnId } = accessorConfig;
|
||||
|
@ -744,7 +789,7 @@ export function LayerPanel(props: LayerPanelProps) {
|
|||
isInlineEditing={isInlineEditing}
|
||||
handleClose={closeDimensionEditor}
|
||||
panel={
|
||||
<>
|
||||
<div>
|
||||
{openColumnGroup &&
|
||||
openColumnId &&
|
||||
layerDatasource &&
|
||||
|
@ -790,7 +835,11 @@ export function LayerPanel(props: LayerPanelProps) {
|
|||
activeVisualization.DimensionEditorComponent &&
|
||||
openColumnGroup?.enableDimensionEditor && (
|
||||
<>
|
||||
<div className="lnsLayerPanel__styleEditor">
|
||||
<div
|
||||
css={css`
|
||||
padding: ${euiTheme.size.base};
|
||||
`}
|
||||
>
|
||||
<activeVisualization.DimensionEditorComponent
|
||||
{...{
|
||||
...layerVisualizationConfigProps,
|
||||
|
@ -821,7 +870,7 @@ export function LayerPanel(props: LayerPanelProps) {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
.lnsDataPanelWrapper {
|
||||
flex: 1 0 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.lnsDataPanelWrapper__switchSource {
|
||||
position: absolute;
|
||||
right: $euiSize + $euiSizeXS;
|
||||
top: $euiSize + $euiSizeXS;
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './data_panel_wrapper.scss';
|
||||
|
||||
import React, { useMemo, memo, useEffect, useCallback } from 'react';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -15,6 +13,7 @@ import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'
|
|||
import { DragDropIdentifier } from '@kbn/dom-drag-drop';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { isEqual } from 'lodash';
|
||||
import { css } from '@emotion/react';
|
||||
import { Easteregg } from './easteregg';
|
||||
import {
|
||||
StateSetter,
|
||||
|
@ -194,7 +193,14 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
|
|||
<>
|
||||
<Easteregg query={externalContext?.query} />
|
||||
{DataPanelComponent && (
|
||||
<div className="lnsDataPanelWrapper" data-test-subj="lnsDataPanelWrapper">
|
||||
<div
|
||||
className="lnsDataPanelWrapper"
|
||||
data-test-subj="lnsDataPanelWrapper"
|
||||
css={css`
|
||||
flex: 1 0 100%;
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
{DataPanelComponent(datasourceProps)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
@import '../../variables';
|
||||
|
||||
.lnsFrameLayout {
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFrameLayout__wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.lnsFrameLayout__pageContent {
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
flex-direction: row;
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
flex-wrap: wrap;
|
||||
overflow: auto;
|
||||
> * {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
> .lnsFrameLayout__sidebar {
|
||||
min-height: $euiSizeL * 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.visEditor {
|
||||
height: 100%;
|
||||
@include flexParent();
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
.visualization {
|
||||
// While we are on a small screen the visualization is below the
|
||||
// editor. In this cases it needs a minimum height, since it would otherwise
|
||||
// maybe end up with 0 height since it just gets the flexbox rest of the screen.
|
||||
min-height: $euiSizeL * 15;
|
||||
}
|
||||
}
|
||||
|
||||
/* 1. Without setting this to 0 you will run into a bug where the filter bar modal is hidden under
|
||||
a tilemap in an iframe: https://github.com/elastic/kibana/issues/16457 */
|
||||
> .visualize {
|
||||
height: 100%;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
z-index: 0; /* 1 */
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFrameLayout__pageBody {
|
||||
min-width: $lnsPanelMinWidth + $euiSizeXL;
|
||||
overflow: hidden auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 100%;
|
||||
// Leave out bottom padding so the suggestions scrollbar stays flush to window edge
|
||||
// Leave out left padding so the left sidebar's focus states are visible outside of content bounds
|
||||
// This also means needing to add same amount of margin to page content and suggestion items
|
||||
padding: $euiSize $euiSize 0;
|
||||
position: relative;
|
||||
z-index: $lnsZLevel1;
|
||||
border-left: $euiBorderThin;
|
||||
border-right: $euiBorderThin;
|
||||
@include euiScrollBar;
|
||||
&:first-child {
|
||||
padding-left: $euiSize;
|
||||
}
|
||||
|
||||
&.lnsFrameLayout__pageBody-isFullscreen {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFrameLayout__sidebar {
|
||||
margin: 0;
|
||||
flex: 1 0 18%;
|
||||
min-width: $lnsPanelMinWidth + $euiSize;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.lnsFrameLayout-isFullscreen .lnsFrameLayout__sidebar--left {
|
||||
// Hide the datapanel in fullscreen mode. Using display: none does trigger
|
||||
// a rerender when the container becomes visible again, maybe pushing offscreen is better
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lnsFrameLayout__sidebar--right {
|
||||
flex-basis: 25%;
|
||||
min-width: $lnsPanelMinWidth + 70;
|
||||
max-width: $euiFormMaxWidth + $euiSizeXXL;
|
||||
max-height: 100%;
|
||||
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.lnsConfigPanel {
|
||||
padding: $euiSize $euiSize $euiSizeXL ($euiFormMaxWidth + $euiSize);
|
||||
margin-left: -$euiFormMaxWidth;
|
||||
@include euiYScroll;
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
padding-left: $euiSize;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lnsFrameLayout__sidebar-isFullscreen {
|
||||
flex: 1;
|
||||
max-width: none;
|
||||
}
|
|
@ -5,12 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './frame_layout.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { EuiScreenReaderOnly, EuiFlexGroup, EuiFlexItem, EuiPage, EuiPageBody } from '@elastic/eui';
|
||||
import {
|
||||
EuiScreenReaderOnly,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
useEuiTheme,
|
||||
euiBreakpoint,
|
||||
type UseEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import classNames from 'classnames';
|
||||
import { css } from '@emotion/react';
|
||||
import { useLensSelector, selectIsFullscreenDatasource } from '../../state_management';
|
||||
|
||||
export interface FrameLayoutProps {
|
||||
|
@ -23,6 +30,8 @@ export interface FrameLayoutProps {
|
|||
|
||||
export function FrameLayout(props: FrameLayoutProps) {
|
||||
const isFullscreen = useLensSelector(selectIsFullscreenDatasource);
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" responsive={false} gutterSize="none" alignItems="stretch">
|
||||
|
@ -40,21 +49,57 @@ export function FrameLayout(props: FrameLayoutProps) {
|
|||
</aside>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem grow={true} className="lnsFrameLayout__wrapper">
|
||||
<EuiFlexItem
|
||||
grow={true}
|
||||
css={css`
|
||||
position: relative;
|
||||
`}
|
||||
>
|
||||
<EuiPage
|
||||
paddingSize="none"
|
||||
className={classNames('lnsFrameLayout', {
|
||||
'lnsFrameLayout-isFullscreen': isFullscreen,
|
||||
})}
|
||||
css={css`
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
${euiBreakpoint(euiThemeContext, ['xs', 's', 'm'])} {
|
||||
position: static;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<EuiPageBody
|
||||
restrictWidth={false}
|
||||
className="lnsFrameLayout__pageContent"
|
||||
aria-labelledby="lns_ChartTitle"
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
flex-direction: row;
|
||||
${euiBreakpoint(euiThemeContext, ['xs', 's', 'm'])} {
|
||||
flex-wrap: wrap;
|
||||
overflow: auto;
|
||||
> * {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
<section
|
||||
className={'lnsFrameLayout__sidebar lnsFrameLayout__sidebar--left hide-for-sharing'}
|
||||
className="hide-for-sharing"
|
||||
aria-labelledby="dataPanelId"
|
||||
css={[
|
||||
sidebarStyles(euiThemeContext),
|
||||
isFullscreen &&
|
||||
css`
|
||||
// Hide the datapanel in fullscreen mode. Using display: none does trigger
|
||||
// a rerender when the container becomes visible again, maybe pushing offscreen is better
|
||||
display: none;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
<EuiScreenReaderOnly>
|
||||
<h2 id="dataPanelId">
|
||||
|
@ -66,9 +111,29 @@ export function FrameLayout(props: FrameLayoutProps) {
|
|||
{props.dataPanel}
|
||||
</section>
|
||||
<section
|
||||
className={classNames('lnsFrameLayout__pageBody', {
|
||||
'lnsFrameLayout__pageBody-isFullscreen': isFullscreen,
|
||||
})}
|
||||
className="eui-scrollBar"
|
||||
css={css`
|
||||
min-width: 432px;
|
||||
overflow: hidden auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 100%;
|
||||
// Leave out bottom padding so the suggestions scrollbar stays flush to window edge
|
||||
// Leave out left padding so the left sidebar's focus states are visible outside of content bounds
|
||||
// This also means needing to add same amount of margin to page content and suggestion items
|
||||
padding: ${euiTheme.size.base} ${euiTheme.size.base} 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-left: ${euiTheme.border.thin};
|
||||
border-right: ${euiTheme.border.thin};
|
||||
&:first-child {
|
||||
padding-left: ${euiTheme.size.base};
|
||||
}
|
||||
${isFullscreen &&
|
||||
`
|
||||
flex: 1;
|
||||
padding: 0;`}
|
||||
`}
|
||||
aria-labelledby="workspaceId"
|
||||
>
|
||||
<EuiScreenReaderOnly>
|
||||
|
@ -79,18 +144,23 @@ export function FrameLayout(props: FrameLayoutProps) {
|
|||
</h2>
|
||||
</EuiScreenReaderOnly>
|
||||
{props.workspacePanel}
|
||||
<div className="lnsFrameLayout__suggestionPanel hide-for-sharing">
|
||||
{props.suggestionsPanel}
|
||||
</div>
|
||||
<div className="hide-for-sharing">{props.suggestionsPanel}</div>
|
||||
</section>
|
||||
<section
|
||||
className={classNames(
|
||||
'lnsFrameLayout__sidebar lnsFrameLayout__sidebar--right',
|
||||
'hide-for-sharing',
|
||||
{
|
||||
'lnsFrameLayout__sidebar-isFullscreen': isFullscreen,
|
||||
}
|
||||
)}
|
||||
css={[
|
||||
sidebarStyles(euiThemeContext),
|
||||
css`
|
||||
flex-basis: 25%;
|
||||
min-width: 358px;
|
||||
max-width: 440px;
|
||||
max-height: 100%;
|
||||
${euiBreakpoint(euiThemeContext, ['xs', 's', 'm'])} {
|
||||
max-width: 100%;
|
||||
}
|
||||
${isFullscreen && `flex: 1; max-width: none;`}
|
||||
`,
|
||||
]}
|
||||
className="hide-for-sharing"
|
||||
aria-labelledby="configPanel"
|
||||
>
|
||||
<EuiScreenReaderOnly>
|
||||
|
@ -108,3 +178,15 @@ export function FrameLayout(props: FrameLayoutProps) {
|
|||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const sidebarStyles = (euiThemeContext: UseEuiTheme) => css`
|
||||
margin: 0;
|
||||
flex: 1 0 18%;
|
||||
min-width: 304px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
${euiBreakpoint(euiThemeContext, ['xs', 's', 'm'])} {
|
||||
min-height: 360px;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
@import '../../mixins';
|
||||
@import '../../variables';
|
||||
|
||||
.lnsSuggestionPanel .euiAccordion__buttonContent {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__suggestions {
|
||||
@include lnsOverflowShadowHorizontal;
|
||||
padding-top: $euiSizeXS;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
|
||||
// Padding / negative margins to make room for overflow shadow
|
||||
padding-left: $euiSizeXS;
|
||||
margin-left: -$euiSizeXS;
|
||||
padding-right: $euiSizeXS;
|
||||
margin-right: -$euiSizeXS;
|
||||
@include euiScrollBar;
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__button {
|
||||
position: relative; // Let the expression progress indicator position itself against the button
|
||||
flex: 0 0 auto;
|
||||
height: $lnsSuggestionHeight;
|
||||
margin-right: $euiSizeS;
|
||||
margin-left: calc($euiSizeXS / 2);
|
||||
margin-bottom: calc($euiSizeXS / 2);
|
||||
padding: 0 $euiSizeS;
|
||||
box-shadow: none !important; // sass-lint:disable-line no-important
|
||||
|
||||
&:focus {
|
||||
transform: none !important; // sass-lint:disable-line no-important
|
||||
@include euiFocusRing;
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__expressionRenderer {
|
||||
position: static; // Let the progress indicator position itself against the button
|
||||
}
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__button-isSelected {
|
||||
background-color: $euiColorLightestShade !important; // sass-lint:disable-line no-important
|
||||
border-color: $euiColorMediumShade !important; // sass-lint:disable-line no-important
|
||||
|
||||
&:not(:focus) {
|
||||
box-shadow: none !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include euiFocusRing;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: none !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__button-fixedWidth {
|
||||
width: $lnsSuggestionWidth !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__suggestionIcon {
|
||||
color: $euiColorDarkShade;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $euiSizeS;
|
||||
|
||||
&:not(:only-child) {
|
||||
height: calc(100% - #{$euiSizeL});
|
||||
}
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__chartWrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__chartWrapper--withLabel {
|
||||
height: calc(100% - #{$euiSizeL});
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__buttonLabel {
|
||||
@include euiTextTruncate;
|
||||
@include euiFontSizeXS;
|
||||
display: block;
|
||||
font-weight: $euiFontWeightBold;
|
||||
text-align: center;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel__applyChangesPrompt {
|
||||
height: $lnsSuggestionHeight;
|
||||
background-color: $euiColorLightestShade !important;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
|
@ -13,14 +13,16 @@ import {
|
|||
createExpressionRendererMock,
|
||||
DatasourceMock,
|
||||
createMockFramePublicAPI,
|
||||
renderWithReduxStore,
|
||||
} from '../../mocks';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public';
|
||||
import { SuggestionPanel, SuggestionPanelProps, SuggestionPanelWrapper } from './suggestion_panel';
|
||||
import { getSuggestions } from './suggestion_helpers';
|
||||
import { EuiIcon, EuiPanel, EuiToolTip, EuiAccordion } from '@elastic/eui';
|
||||
import { IconChartDatatable } from '@kbn/chart-icons';
|
||||
import { mountWithProvider } from '../../mocks';
|
||||
import { mountWithReduxStore } from '../../mocks';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
|
||||
import {
|
||||
|
@ -32,6 +34,7 @@ import {
|
|||
VisualizationState,
|
||||
} from '../../state_management';
|
||||
import { setChangesApplied } from '../../state_management/lens_slice';
|
||||
import { userEvent } from '@testing-library/user-event';
|
||||
|
||||
const SELECTORS = {
|
||||
APPLY_CHANGES_BUTTON: 'button[data-test-subj="lnsApplyChanges__suggestions"]',
|
||||
|
@ -113,7 +116,7 @@ describe('suggestion_panel', () => {
|
|||
});
|
||||
|
||||
it('should avoid completely to render SuggestionPanel when in fullscreen mode', async () => {
|
||||
const { instance, lensStore } = await mountWithProvider(
|
||||
const { instance, lensStore } = mountWithReduxStore(
|
||||
<SuggestionPanelWrapper {...defaultProps} />
|
||||
);
|
||||
expect(instance.find(SuggestionPanel).exists()).toBe(true);
|
||||
|
@ -128,7 +131,7 @@ describe('suggestion_panel', () => {
|
|||
});
|
||||
|
||||
it('should display apply-changes prompt when changes not applied', async () => {
|
||||
const { instance, lensStore } = await mountWithProvider(<SuggestionPanel {...defaultProps} />, {
|
||||
const { instance, lensStore } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />, {
|
||||
preloadedState: {
|
||||
...preloadedState,
|
||||
visualization: {
|
||||
|
@ -160,7 +163,7 @@ describe('suggestion_panel', () => {
|
|||
});
|
||||
|
||||
it('should list passed in suggestions', async () => {
|
||||
const { instance } = await mountWithProvider(<SuggestionPanel {...defaultProps} />, {
|
||||
const { instance } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />, {
|
||||
preloadedState,
|
||||
});
|
||||
|
||||
|
@ -196,10 +199,9 @@ describe('suggestion_panel', () => {
|
|||
});
|
||||
|
||||
it('should not update suggestions if current state is moved to staged preview', async () => {
|
||||
const { instance, lensStore } = await mountWithProvider(
|
||||
<SuggestionPanel {...defaultProps} />,
|
||||
{ preloadedState }
|
||||
);
|
||||
const { instance, lensStore } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />, {
|
||||
preloadedState,
|
||||
});
|
||||
getSuggestionsMock.mockClear();
|
||||
lensStore.dispatch(setState({ stagedPreview }));
|
||||
instance.update();
|
||||
|
@ -207,10 +209,9 @@ describe('suggestion_panel', () => {
|
|||
});
|
||||
|
||||
it('should update suggestions if staged preview is removed', async () => {
|
||||
const { instance, lensStore } = await mountWithProvider(
|
||||
<SuggestionPanel {...defaultProps} />,
|
||||
{ preloadedState }
|
||||
);
|
||||
const { instance, lensStore } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />, {
|
||||
preloadedState,
|
||||
});
|
||||
getSuggestionsMock.mockClear();
|
||||
lensStore.dispatch(setState({ stagedPreview, ...suggestionState }));
|
||||
instance.update();
|
||||
|
@ -219,25 +220,19 @@ describe('suggestion_panel', () => {
|
|||
expect(getSuggestionsMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should highlight currently active suggestion', async () => {
|
||||
const { instance } = await mountWithProvider(<SuggestionPanel {...defaultProps} />, {
|
||||
it('should select currently active suggestion', async () => {
|
||||
const getSuggestionByName = (name: string) => screen.getByRole('listitem', { name });
|
||||
|
||||
renderWithReduxStore(<SuggestionPanel {...defaultProps} />, undefined, {
|
||||
preloadedState,
|
||||
});
|
||||
act(() => {
|
||||
instance.find(SELECTORS.SUGGESTION_TILE_BUTTON).at(2).simulate('click');
|
||||
});
|
||||
|
||||
instance.update();
|
||||
|
||||
expect(instance.find(SELECTORS.SUGGESTION_TILE_BUTTON).at(2).prop('className')).toContain(
|
||||
'lnsSuggestionPanel__button-isSelected'
|
||||
);
|
||||
expect(getSuggestionByName('Current visualization')).toHaveAttribute('aria-current', 'true');
|
||||
await userEvent.click(getSuggestionByName('Suggestion1'));
|
||||
expect(getSuggestionByName('Suggestion1')).toHaveAttribute('aria-current', 'true');
|
||||
});
|
||||
|
||||
it('should rollback suggestion if current panel is clicked', async () => {
|
||||
const { instance, lensStore } = await mountWithProvider(
|
||||
<SuggestionPanel {...defaultProps} />
|
||||
);
|
||||
const { instance, lensStore } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />);
|
||||
|
||||
act(() => {
|
||||
instance.find(SELECTORS.SUGGESTION_TILE_BUTTON).at(2).simulate('click');
|
||||
|
@ -262,7 +257,7 @@ describe('suggestion_panel', () => {
|
|||
});
|
||||
|
||||
it('should dispatch visualization switch action if suggestion is clicked', async () => {
|
||||
const { instance, lensStore } = await mountWithProvider(<SuggestionPanel {...defaultProps} />, {
|
||||
const { instance, lensStore } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />, {
|
||||
preloadedState,
|
||||
});
|
||||
|
||||
|
@ -316,7 +311,7 @@ describe('suggestion_panel', () => {
|
|||
|
||||
mockDatasource.toExpression.mockReturnValue('datasource_expression');
|
||||
|
||||
const { instance } = await mountWithProvider(<SuggestionPanel {...defaultProps} />, {
|
||||
const { instance } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />, {
|
||||
preloadedState,
|
||||
});
|
||||
|
||||
|
@ -344,14 +339,14 @@ describe('suggestion_panel', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const { instance } = await mountWithProvider(<SuggestionPanel {...defaultProps} />, {
|
||||
const { instance } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />, {
|
||||
preloadedState: newPreloadedState,
|
||||
});
|
||||
expect(instance.html()).toEqual(null);
|
||||
});
|
||||
|
||||
it('should hide the selections when the accordion is hidden', async () => {
|
||||
const { instance } = await mountWithProvider(<SuggestionPanel {...defaultProps} />);
|
||||
const { instance } = mountWithReduxStore(<SuggestionPanel {...defaultProps} />);
|
||||
expect(instance.find(EuiAccordion)).toHaveLength(1);
|
||||
act(() => {
|
||||
instance.find(EuiAccordion).at(0).simulate('change');
|
||||
|
@ -386,7 +381,7 @@ describe('suggestion_panel', () => {
|
|||
.mockReturnValueOnce('test | expression');
|
||||
mockDatasource.toExpression.mockReturnValue('datasource_expression');
|
||||
|
||||
mountWithProvider(<SuggestionPanel {...defaultProps} frame={createMockFramePublicAPI()} />);
|
||||
mountWithReduxStore(<SuggestionPanel {...defaultProps} frame={createMockFramePublicAPI()} />);
|
||||
|
||||
expect(expressionRendererMock).toHaveBeenCalledTimes(1);
|
||||
const passedExpression = (expressionRendererMock as jest.Mock).mock.calls[0][0].expression;
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './suggestion_panel.scss';
|
||||
|
||||
import { camelCase, pick } from 'lodash';
|
||||
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
@ -22,12 +20,17 @@ import {
|
|||
EuiAccordion,
|
||||
EuiText,
|
||||
EuiNotificationBadge,
|
||||
type UseEuiTheme,
|
||||
useEuiTheme,
|
||||
euiFocusRing,
|
||||
useEuiFontSize,
|
||||
euiTextTruncate,
|
||||
transparentize,
|
||||
} from '@elastic/eui';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { IconType } from '@elastic/eui/src/components/icon/icon';
|
||||
import { Ast, fromExpression, toExpression } from '@kbn/interpreter';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import classNames from 'classnames';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { ExecutionContextSearch } from '@kbn/es-query';
|
||||
import {
|
||||
|
@ -126,8 +129,10 @@ const PreviewRenderer = ({
|
|||
hasError: boolean;
|
||||
onRender: () => void;
|
||||
}) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
const onErrorMessage = (
|
||||
<div className="lnsSuggestionPanel__suggestionIcon">
|
||||
<div css={suggestionStyles.icon(euiThemeContext)}>
|
||||
<EuiIconTip
|
||||
size="xl"
|
||||
color="danger"
|
||||
|
@ -143,9 +148,13 @@ const PreviewRenderer = ({
|
|||
);
|
||||
return (
|
||||
<div
|
||||
className={classNames('lnsSuggestionPanel__chartWrapper', {
|
||||
'lnsSuggestionPanel__chartWrapper--withLabel': withLabel,
|
||||
})}
|
||||
css={css`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
${withLabel ? `height: calc(100% - ${euiTheme.size.l});` : ''}
|
||||
`}
|
||||
>
|
||||
{!expression || hasError ? (
|
||||
onErrorMessage
|
||||
|
@ -188,6 +197,9 @@ const SuggestionPreview = ({
|
|||
onRender: () => void;
|
||||
wrapSuggestions?: boolean;
|
||||
}) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
const xsFontSize = useEuiFontSize('xs');
|
||||
return (
|
||||
<EuiToolTip
|
||||
content={preview.title}
|
||||
|
@ -207,10 +219,48 @@ const SuggestionPreview = ({
|
|||
<EuiPanel
|
||||
hasBorder={true}
|
||||
hasShadow={false}
|
||||
className={classNames('lnsSuggestionPanel__button', {
|
||||
'lnsSuggestionPanel__button-isSelected': selected,
|
||||
'lnsSuggestionPanel__button-fixedWidth': !wrapSuggestions,
|
||||
})}
|
||||
css={css`
|
||||
position: relative; // Let the expression progress indicator position itself against the button
|
||||
flex: 0 0 auto;
|
||||
height: 100px;
|
||||
margin-right: ${euiTheme.size.s};
|
||||
margin-left: ${euiTheme.size.xxs};
|
||||
margin-bottom: ${euiTheme.size.xxs};
|
||||
padding: 0 ${euiTheme.size.s};
|
||||
box-shadow: none !important; // sass-lint:disable-line no-important
|
||||
|
||||
&:focus {
|
||||
transform: none !important; // sass-lint:disable-line no-important
|
||||
${euiFocusRing(euiThemeContext)};
|
||||
}
|
||||
${selected
|
||||
? `
|
||||
background-color: ${
|
||||
euiTheme.colors.lightestShade
|
||||
} !important; // sass-lint:disable-line no-important
|
||||
border-color: ${
|
||||
euiTheme.colors.mediumShade
|
||||
} !important; // sass-lint:disable-line no-important
|
||||
|
||||
&:not(:focus) {
|
||||
box-shadow: none !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
|
||||
&:focus {
|
||||
${euiFocusRing(euiThemeContext)};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: none !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
`
|
||||
: ''}
|
||||
${!wrapSuggestions
|
||||
? `
|
||||
width: 150px !important; // sass-lint:disable-line no-important
|
||||
`
|
||||
: ''}
|
||||
`}
|
||||
paddingSize="none"
|
||||
data-test-subj="lnsSuggestion"
|
||||
onClick={onSelect}
|
||||
|
@ -228,12 +278,23 @@ const SuggestionPreview = ({
|
|||
onRender={onRender}
|
||||
/>
|
||||
) : (
|
||||
<span className="lnsSuggestionPanel__suggestionIcon">
|
||||
<span css={suggestionStyles.icon(euiThemeContext)}>
|
||||
<EuiIcon size="xxl" type={preview.icon} />
|
||||
</span>
|
||||
)}
|
||||
{showTitleAsLabel && (
|
||||
<span className="lnsSuggestionPanel__buttonLabel">{preview.title}</span>
|
||||
<span
|
||||
css={css`
|
||||
${euiTextTruncate()}
|
||||
${xsFontSize};
|
||||
font-weight: ${euiTheme.font.weight.bold};
|
||||
display: block;
|
||||
text-align: center;
|
||||
flex-grow: 0;
|
||||
`}
|
||||
>
|
||||
{preview.title}
|
||||
</span>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</div>
|
||||
|
@ -266,6 +327,8 @@ export function SuggestionPanel({
|
|||
const existsStagedPreview = useLensSelector((state) => Boolean(state.lens.stagedPreview));
|
||||
const currentVisualization = useLensSelector(selectCurrentVisualization);
|
||||
const currentDatasourceStates = useLensSelector(selectCurrentDatasourceStates);
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
|
||||
const framePublicAPI = useLensSelector((state) => selectFramePublicAPI(state, datasourceMap));
|
||||
const changesApplied = useLensSelector(selectChangesApplied);
|
||||
|
@ -438,7 +501,19 @@ export function SuggestionPanel({
|
|||
}
|
||||
|
||||
const renderApplyChangesPrompt = () => (
|
||||
<EuiPanel hasShadow={false} className="lnsSuggestionPanel__applyChangesPrompt" paddingSize="m">
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
className="lnsSuggestionPanel__applyChangesPrompt"
|
||||
paddingSize="m"
|
||||
css={css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
background-color: ${euiTheme.colors.lightestShade} !important;
|
||||
`}
|
||||
>
|
||||
<EuiText size="s" color="subdued" className="lnsSuggestionPanel__applyChangesMessage">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
|
@ -540,9 +615,11 @@ export function SuggestionPanel({
|
|||
'data-test-subj': 'lensSuggestionsPanelToggleButton',
|
||||
paddingSize: wrapSuggestions ? 'm' : 's',
|
||||
}}
|
||||
className="lnsSuggestionPanel"
|
||||
css={css`
|
||||
padding-bottom: ${wrapSuggestions ? 0 : euiThemeVars.euiSizeS};
|
||||
.euiAccordion__buttonContent {
|
||||
width: 100%;
|
||||
}
|
||||
`}
|
||||
buttonContent={title}
|
||||
forceState={hideSuggestions ? 'closed' : 'open'}
|
||||
|
@ -582,13 +659,24 @@ export function SuggestionPanel({
|
|||
}
|
||||
>
|
||||
<div
|
||||
className="lnsSuggestionPanel__suggestions"
|
||||
className="eui-scrollBar"
|
||||
data-test-subj="lnsSuggestionsPanel"
|
||||
role="list"
|
||||
tabIndex={0}
|
||||
css={css`
|
||||
flex-wrap: ${wrapSuggestions ? 'wrap' : 'nowrap'};
|
||||
gap: ${wrapSuggestions ? euiThemeVars.euiSize : 0};
|
||||
gap: ${wrapSuggestions ? euiTheme.size.base : 0};
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
padding-top: ${euiTheme.size.xs};
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
${transparentize(euiTheme.colors.danger, 0.1)} 0%,
|
||||
${euiTheme.colors.danger} 5px,
|
||||
${euiTheme.colors.danger} calc(100% - 5px),
|
||||
${transparentize(euiTheme.colors.danger, 0.1)} 100%
|
||||
);
|
||||
`}
|
||||
>
|
||||
{changesApplied ? renderSuggestionsUI() : renderApplyChangesPrompt()}
|
||||
|
@ -699,3 +787,18 @@ function preparePreviewExpression(
|
|||
|
||||
return typeof expression === 'string' ? fromExpression(expression) : expression;
|
||||
}
|
||||
|
||||
const suggestionStyles = {
|
||||
icon: ({ euiTheme }: UseEuiTheme) => css`
|
||||
color: ${euiTheme.colors.darkShade};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: ${euiTheme.size.s};
|
||||
&:not(:only-child) {
|
||||
height: calc(100% - ${euiTheme.size.l});
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
.lnsVisualizeGeoFieldWorkspacePanel__dragDrop {
|
||||
padding: $euiSizeXXL ($euiSizeXL * 2);
|
||||
border: $euiBorderThin;
|
||||
border-radius: $euiBorderRadius;
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { EuiText, UseEuiTheme } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { UiActionsStart, VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -15,7 +15,7 @@ import { Droppable } from '@kbn/dom-drag-drop';
|
|||
import { IndexPattern } from '../../../types';
|
||||
import { getVisualizeGeoFieldMessage } from '../../../utils';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import './geo_field_workspace_panel.scss';
|
||||
import { pageContentBodyStyles, promptIllustrationStyle } from './workspace_panel';
|
||||
|
||||
interface Props {
|
||||
fieldType: string;
|
||||
|
@ -45,15 +45,15 @@ export function GeoFieldWorkspacePanel(props: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="lnsWorkspacePanelWrapper__pageContentBody">
|
||||
<EuiText className="lnsWorkspacePanel__emptyContent" textAlign="center" size="s">
|
||||
<div className="eui-scrollBar" css={pageContentBodyStyles}>
|
||||
<EuiText textAlign="center" size="s">
|
||||
<div>
|
||||
<h2>
|
||||
<strong>{getVisualizeGeoFieldMessage(props.fieldType)}</strong>
|
||||
</h2>
|
||||
<GlobeIllustration aria-hidden={true} className="lnsWorkspacePanel__promptIllustration" />
|
||||
<GlobeIllustration aria-hidden={true} css={promptIllustrationStyle} />
|
||||
<Droppable
|
||||
className="lnsVisualizeGeoFieldWorkspacePanel__dragDrop"
|
||||
css={droppableStyles}
|
||||
dataTestSubj="lnsGeoFieldWorkspace"
|
||||
dropTypes={['field_add']}
|
||||
order={dragDropOrder}
|
||||
|
@ -74,3 +74,11 @@ export function GeoFieldWorkspacePanel(props: Props) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const droppableStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return `
|
||||
padding: ${euiTheme.size.xxl} ${euiTheme.size.xxxl};
|
||||
border: ${euiTheme.border.thin};
|
||||
border-radius: ${euiTheme.border.radius};
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
.lnsWorkspaceWarning__buttonText {
|
||||
@include euiBreakpoint('xs', 's', 'm', 'l') {
|
||||
@include euiScreenReaderOnly;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsWorkspaceWarningList {
|
||||
max-height: $euiSize * 20;
|
||||
width: $euiSize * 16;
|
||||
@include euiYScroll;
|
||||
}
|
||||
|
||||
.lnsWorkspaceWarningList__item {
|
||||
& + & {
|
||||
border-top: $euiBorderThin;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsWorkspaceWarningList__textItem {
|
||||
padding: $euiSize;
|
||||
}
|
||||
|
||||
.lnsWorkspaceWarningList__description {
|
||||
overflow-wrap: break-word;
|
||||
min-width: 0;
|
||||
}
|
|
@ -5,9 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './workspace_panel_wrapper.scss';
|
||||
import './message_list.scss';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
EuiPopover,
|
||||
|
@ -17,6 +14,7 @@ import {
|
|||
EuiToolTip,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
type UseEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css, SerializedStyles } from '@emotion/react';
|
||||
|
@ -78,7 +76,6 @@ export const MessageList = ({
|
|||
minWidth={0}
|
||||
color={errorCount ? 'danger' : 'warning'}
|
||||
onClick={onButtonClick}
|
||||
className="lnsWorkspaceWarning__button"
|
||||
data-test-subj="lens-message-list-trigger"
|
||||
title={buttonLabel}
|
||||
css={customButtonStyles}
|
||||
|
@ -106,11 +103,11 @@ export const MessageList = ({
|
|||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<ul className="lnsWorkspaceWarningList">
|
||||
<ul css={workspaceWarningListStyles.self} className="eui-yScroll">
|
||||
{messages.map(({ hidePopoverIcon = false, ...message }, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="lnsWorkspaceWarningList__item"
|
||||
css={workspaceWarningListStyles.item}
|
||||
data-test-subj={`lens-message-list-${message.severity}`}
|
||||
>
|
||||
{typeof message.longMessage === 'function' ? (
|
||||
|
@ -119,7 +116,7 @@ export const MessageList = ({
|
|||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
className="lnsWorkspaceWarningList__textItem"
|
||||
css={workspaceWarningListStyles.textItem}
|
||||
>
|
||||
{!hidePopoverIcon && (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -130,7 +127,7 @@ export const MessageList = ({
|
|||
)}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={1} className="lnsWorkspaceWarningList__description">
|
||||
<EuiFlexItem grow={1} css={workspaceWarningListStyles.description}>
|
||||
<EuiText size="s">{message.longMessage}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -141,3 +138,22 @@ export const MessageList = ({
|
|||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
const workspaceWarningListStyles = {
|
||||
self: css`
|
||||
max-height: 320px;
|
||||
width: 256px;
|
||||
`,
|
||||
item: ({ euiTheme }: UseEuiTheme) => `
|
||||
& + & {
|
||||
border-top: 1px solid ${euiTheme.colors.lightShade};
|
||||
}
|
||||
`,
|
||||
textItem: ({ euiTheme }: UseEuiTheme) => `
|
||||
padding: ${euiTheme.size.base}
|
||||
`,
|
||||
description: css`
|
||||
overflow-wrap: break-word;
|
||||
min-width: 0;
|
||||
`,
|
||||
};
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './workspace_panel_wrapper.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiScreenReaderOnly } from '@elastic/eui';
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
renderWithReduxStore,
|
||||
} from '../../../mocks';
|
||||
|
||||
import { mockDataPlugin, mountWithProvider } from '../../../mocks';
|
||||
import { mockDataPlugin, mountWithReduxStore } from '../../../mocks';
|
||||
|
||||
import { WorkspacePanel } from './workspace_panel';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
|
@ -367,7 +367,7 @@ describe('workspace_panel', () => {
|
|||
|
||||
const visualizationShowing = () => instance.exists(expressionRendererMock);
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
datasourceMap={{
|
||||
|
@ -431,7 +431,7 @@ describe('workspace_panel', () => {
|
|||
mockDatasource.getLayers.mockReturnValue(['first']);
|
||||
const props = defaultProps;
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...props}
|
||||
datasourceMap={{
|
||||
|
@ -467,7 +467,7 @@ describe('workspace_panel', () => {
|
|||
mockDatasource.getLayers.mockReturnValue(['first']);
|
||||
const props = defaultProps;
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...props}
|
||||
datasourceMap={{
|
||||
|
@ -505,7 +505,7 @@ describe('workspace_panel', () => {
|
|||
mockDatasource.getLayers.mockReturnValue(['first']);
|
||||
const props = defaultProps;
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...props}
|
||||
datasourceMap={{
|
||||
|
@ -540,7 +540,7 @@ describe('workspace_panel', () => {
|
|||
mockDatasource.toExpression.mockReturnValue('datasource');
|
||||
mockDatasource.getLayers.mockReturnValue(['table1']);
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
datasourceMap={{
|
||||
|
@ -585,7 +585,7 @@ describe('workspace_panel', () => {
|
|||
|
||||
expressionRendererMock = jest.fn((_arg) => <span />);
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
datasourceMap={{
|
||||
|
@ -629,7 +629,7 @@ describe('workspace_panel', () => {
|
|||
.mockReturnValueOnce('datasource second');
|
||||
|
||||
expressionRendererMock = jest.fn((_arg) => <span />);
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
datasourceMap={{
|
||||
|
@ -687,7 +687,7 @@ describe('workspace_panel', () => {
|
|||
|
||||
const getUserMessages = jest.fn(() => messages);
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
getUserMessages={getUserMessages}
|
||||
|
@ -717,7 +717,7 @@ describe('workspace_panel', () => {
|
|||
let userMessages = [] as UserMessage[];
|
||||
const getUserMessageFn = jest.fn(() => userMessages);
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
getUserMessages={getUserMessageFn}
|
||||
|
@ -786,7 +786,7 @@ describe('workspace_panel', () => {
|
|||
const mockAddUserMessages = jest.fn(() => mockRemoveUserMessages);
|
||||
const mockGetUserMessages = jest.fn<UserMessage[], unknown[]>(() => []);
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
datasourceMap={{
|
||||
|
@ -814,7 +814,7 @@ describe('workspace_panel', () => {
|
|||
first: mockDatasource.publicAPIMock,
|
||||
};
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
datasourceMap={{
|
||||
|
@ -844,7 +844,7 @@ describe('workspace_panel', () => {
|
|||
framePublicAPI.datasourceLayers = {
|
||||
first: mockDatasource.publicAPIMock,
|
||||
};
|
||||
const mounted = await mountWithProvider(
|
||||
const mounted = mountWithReduxStore(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
datasourceMap={{
|
||||
|
|
|
@ -12,7 +12,16 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { toExpression } from '@kbn/interpreter';
|
||||
import type { KibanaExecutionContext } from '@kbn/core-execution-context-common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiText, EuiButtonEmpty, EuiLink, EuiTextColor } from '@elastic/eui';
|
||||
import {
|
||||
EuiText,
|
||||
EuiButtonEmpty,
|
||||
EuiLink,
|
||||
EuiTextColor,
|
||||
transparentize,
|
||||
useEuiTheme,
|
||||
EuiSpacer,
|
||||
type UseEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type {
|
||||
|
@ -27,6 +36,7 @@ import { DropIllustration } from '@kbn/chart-icons';
|
|||
import { useDragDropContext, DragDropIdentifier, Droppable } from '@kbn/dom-drag-drop';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { ChartSizeSpec, isChartSizeEvent } from '@kbn/chart-expressions-common';
|
||||
import { css } from '@emotion/react';
|
||||
import { getSuccessfulRequestTimings } from '../../../report_performance_metric_util';
|
||||
import { trackUiCounterEvents } from '../../../lens_ui_telemetry';
|
||||
import { getSearchWarningMessages } from '../../../utils';
|
||||
|
@ -51,6 +61,7 @@ import { WorkspacePanelWrapper } from './workspace_panel_wrapper';
|
|||
import applyChangesIllustrationDark from '../../../assets/render_dark@2x.png';
|
||||
import applyChangesIllustrationLight from '../../../assets/render_light@2x.png';
|
||||
import { getOriginalRequestErrorMessages } from '../../error_helper';
|
||||
import { lnsExpressionRendererStyle } from '../../../expression_renderer_styles';
|
||||
import {
|
||||
onActiveDataChange,
|
||||
useLensDispatch,
|
||||
|
@ -195,6 +206,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
const dataReceivedTime = useRef<number>(NaN);
|
||||
const esTookTime = useRef<number>(0);
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const onRender$ = useCallback(() => {
|
||||
if (renderDeps.current) {
|
||||
if (!initialVisualizationRenderComplete.current) {
|
||||
|
@ -495,19 +508,18 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiText
|
||||
className={classNames('lnsWorkspacePanel__emptyContent')}
|
||||
textAlign="center"
|
||||
data-test-subj="workspace-drag-drop-prompt"
|
||||
size="s"
|
||||
>
|
||||
<EuiText textAlign="center" data-test-subj="workspace-drag-drop-prompt" size="s">
|
||||
<div>
|
||||
<DropIllustration
|
||||
aria-hidden={true}
|
||||
className={classNames(
|
||||
'lnsWorkspacePanel__promptIllustration',
|
||||
'lnsWorkspacePanel__dropIllustration'
|
||||
)}
|
||||
css={[
|
||||
css`
|
||||
filter: drop-shadow(0 6px 12px ${transparentize(euiTheme.colors.shadow, 0.2)})
|
||||
drop-shadow(0 4px 4px ${transparentize(euiTheme.colors.shadow, 0.2)})
|
||||
drop-shadow(0 2px 2px ${transparentize(euiTheme.colors.shadow, 0.2)});
|
||||
`,
|
||||
promptIllustrationStyle,
|
||||
]}
|
||||
/>
|
||||
<h2>
|
||||
<strong>
|
||||
|
@ -521,15 +533,21 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
</strong>
|
||||
</h2>
|
||||
{!expressionExists && (
|
||||
<>
|
||||
<EuiTextColor color="subdued" component="div">
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.editorFrame.emptyWorkspaceHeading', {
|
||||
defaultMessage: 'Lens is the recommended editor for creating visualizations',
|
||||
})}
|
||||
</p>
|
||||
<div
|
||||
css={css`
|
||||
.domDroppable--active & {
|
||||
filter: blur(5px);
|
||||
transition: filter ${euiTheme.animation.fast} ease-in-out;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<EuiTextColor color="subdued" component="p">
|
||||
{i18n.translate('xpack.lens.editorFrame.emptyWorkspaceHeading', {
|
||||
defaultMessage: 'Lens is the recommended editor for creating visualizations',
|
||||
})}
|
||||
</EuiTextColor>
|
||||
<p className="lnsWorkspacePanel__actions">
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/products/kibana/feedback"
|
||||
target="_blank"
|
||||
|
@ -540,7 +558,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
})}
|
||||
</EuiLink>
|
||||
</p>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</EuiText>
|
||||
|
@ -557,18 +575,13 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
});
|
||||
|
||||
return (
|
||||
<EuiText
|
||||
className={classNames('lnsWorkspacePanel__emptyContent')}
|
||||
textAlign="center"
|
||||
data-test-subj="workspace-apply-changes-prompt"
|
||||
size="s"
|
||||
>
|
||||
<EuiText textAlign="center" data-test-subj="workspace-apply-changes-prompt" size="s">
|
||||
<div>
|
||||
<img
|
||||
aria-hidden={true}
|
||||
css={promptIllustrationStyle}
|
||||
src={IS_DARK_THEME ? applyChangesIllustrationDark : applyChangesIllustrationLight}
|
||||
alt={applyChangesString}
|
||||
className="lnsWorkspacePanel__promptIllustration"
|
||||
/>
|
||||
<h2>
|
||||
<strong>
|
||||
|
@ -577,7 +590,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
})}
|
||||
</strong>
|
||||
</h2>
|
||||
<p className="lnsWorkspacePanel__actions">
|
||||
<EuiSpacer size="s" />
|
||||
<p>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
className={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
|
||||
|
@ -645,13 +659,28 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
className={classNames('lnsWorkspacePanel__dragDrop', {
|
||||
'lnsWorkspacePanel__dragDrop--fullscreen': isFullscreen,
|
||||
})}
|
||||
css={css`
|
||||
${isFullscreen && `border: none !important;`}
|
||||
`}
|
||||
dataTestSubj="lnsWorkspace"
|
||||
dropTypes={suggestionForDraggedField ? ['field_add'] : undefined}
|
||||
onDrop={onDrop}
|
||||
value={dropProps.value}
|
||||
order={dropProps.order}
|
||||
>
|
||||
<div className="lnsWorkspacePanelWrapper__pageContentBody">{renderWorkspaceContents()}</div>
|
||||
<div
|
||||
className="eui-scrollBar"
|
||||
css={[
|
||||
pageContentBodyStyles,
|
||||
isFullscreen &&
|
||||
`
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
{renderWorkspaceContents()}
|
||||
</div>
|
||||
</Droppable>
|
||||
);
|
||||
};
|
||||
|
@ -730,6 +759,7 @@ export const VisualizationWrapper = ({
|
|||
onComponentRendered();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const searchContext = useLensSelector(selectExecutionContextSearch);
|
||||
// Used for reporting
|
||||
|
@ -765,7 +795,16 @@ export const VisualizationWrapper = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className="lnsExpressionRenderer"
|
||||
className="lnsExpressionRenderer eui-scrollBar"
|
||||
css={[
|
||||
lnsExpressionRendererStyle,
|
||||
`
|
||||
.domDroppable--active & {
|
||||
filter: blur(${euiTheme.size.xs}) !important;
|
||||
opacity: .25 !important;
|
||||
transition: filter ${euiTheme.animation.normal} ease-in-out, opacity ${euiTheme.animation.normal} ease-in-out;
|
||||
}`,
|
||||
]}
|
||||
data-shared-items-container
|
||||
data-render-complete={isRenderComplete}
|
||||
data-shared-item=""
|
||||
|
@ -773,7 +812,6 @@ export const VisualizationWrapper = ({
|
|||
ref={nodeRef}
|
||||
>
|
||||
<ExpressionRendererComponent
|
||||
className="lnsExpressionRenderer__component"
|
||||
padding={displayOptions?.noPadding ? undefined : 'm'}
|
||||
expression={expression!}
|
||||
allowCache={true}
|
||||
|
@ -807,3 +845,36 @@ export const VisualizationWrapper = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const promptIllustrationStyle = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
overflow: visible; // Shows arrow animation when it gets out of bounds
|
||||
margin-top: 0;
|
||||
margin-bottom: -${euiTheme.size.base};
|
||||
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
max-width: 176px;
|
||||
max-height: 176px;
|
||||
`;
|
||||
};
|
||||
|
||||
export const pageContentBodyStyles = ({ euiTheme }: UseEuiTheme) => {
|
||||
return css`
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
border: ${euiTheme.border.thin};
|
||||
border-radius: ${euiTheme.border.radius.medium};
|
||||
background: ${euiTheme.colors.emptyShade};
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
& > * {
|
||||
flex: 1 1 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
@import '../../../mixins';
|
||||
|
||||
.lnsWorkspacePanelWrapper {
|
||||
margin-bottom: $euiSize;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative; // For positioning the dnd overlay
|
||||
min-height: $euiSizeXXL * 10;
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
|
||||
.lnsWorkspacePanelWrapper__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.lnsWorkspacePanelWrapper__pageContentBody {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
border: $euiBorderThin;
|
||||
border-radius: $euiBorderRadius;
|
||||
background: $euiColorEmptyShade;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@include euiScrollBar;
|
||||
&>* {
|
||||
flex: 1 1 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.lnsWorkspacePanelWrapper--fullscreen {
|
||||
margin-bottom: 0;
|
||||
|
||||
.lnsWorkspacePanelWrapper__pageContentBody {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.lnsWorkspacePanel__dragDrop {
|
||||
&.domDroppable--active {
|
||||
p {
|
||||
transition: filter $euiAnimSpeedFast ease-in-out;
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.lnsExpressionRenderer {
|
||||
transition: filter $euiAnimSpeedNormal ease-in-out, opacity $euiAnimSpeedNormal ease-in-out;
|
||||
filter: blur($euiSizeXS);
|
||||
opacity: .25;
|
||||
}
|
||||
}
|
||||
|
||||
&.domDroppable--hover {
|
||||
.lnsDropIllustration__hand {
|
||||
animation: lnsWorkspacePanel__illustrationPulseContinuous 1.5s ease-in-out 0s infinite normal forwards;
|
||||
}
|
||||
}
|
||||
|
||||
&.lnsWorkspacePanel__dragDrop--fullscreen {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsWorkspacePanel__emptyContent {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color $euiAnimSpeedFast ease-in-out;
|
||||
|
||||
.lnsWorkspacePanel__actions {
|
||||
margin-top: $euiSizeL;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsWorkspacePanelWrapper__toolbar {
|
||||
margin-bottom: $euiSizeXS;
|
||||
}
|
||||
|
||||
.lnsWorkspacePanelWrapper__toolbar--fullscreen {
|
||||
background-color: $euiColorEmptyShade;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 0;
|
||||
padding: $euiSizeS $euiSizeS 0;
|
||||
}
|
||||
|
||||
.lnsWorkspacePanelWrapper__applyButton .euiButton__text {
|
||||
@include euiBreakpoint('xs', 's', 'm', 'l') {
|
||||
@include euiScreenReaderOnly;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsWorkspacePanel__promptIllustration {
|
||||
overflow: visible; // Shows arrow animation when it gets out of bounds
|
||||
margin-top: 0;
|
||||
margin-bottom: -$euiSize;
|
||||
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
max-width: 176px;
|
||||
max-height: 176px;
|
||||
}
|
||||
|
||||
.lnsWorkspacePanel__dropIllustration {
|
||||
// Drop shadow values is a dupe of @euiBottomShadowMedium but used as a filter
|
||||
// Hard-coded px values OK (@cchaos)
|
||||
// sass-lint:disable-block indentation
|
||||
filter:
|
||||
drop-shadow(0 6px 12px transparentize($euiShadowColor, .8)) drop-shadow(0 4px 4px transparentize($euiShadowColor, .8)) drop-shadow(0 2px 2px transparentize($euiShadowColor, .8));
|
||||
}
|
||||
|
||||
.lnsDropIllustration__adjustFill {
|
||||
fill: $euiColorFullShade;
|
||||
}
|
||||
|
||||
.lnsDropIllustration__hand {
|
||||
animation: lnsWorkspacePanel__illustrationPulseArrow 5s ease-in-out 0s infinite normal forwards;
|
||||
}
|
||||
|
||||
@keyframes lnsWorkspacePanel__illustrationPulseArrow {
|
||||
0% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
65% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
72% {
|
||||
transform: translateY(10%);
|
||||
}
|
||||
|
||||
79% {
|
||||
transform: translateY(7%);
|
||||
}
|
||||
|
||||
86% {
|
||||
transform: translateY(10%);
|
||||
}
|
||||
|
||||
95% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes lnsWorkspacePanel__illustrationPulseContinuous {
|
||||
0% {
|
||||
transform: translateY(10%);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateY(15%);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(10%);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateY(15%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(10%);
|
||||
}
|
||||
}
|
||||
|
||||
.lnsVisualizationToolbar--fixed {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
background-color: $euiColorLightestShade;
|
||||
}
|
|
@ -5,10 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './workspace_panel_wrapper.scss';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiPageTemplate, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
import { EuiPageTemplate, EuiFlexGroup, EuiFlexItem, EuiButton, useEuiTheme } from '@elastic/eui';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ChartSizeSpec } from '@kbn/chart-expressions-common';
|
||||
|
@ -74,11 +72,10 @@ const getAspectRatioStyles = ({ x, y }: { x: number; y: number }) => {
|
|||
export function VisualizationToolbar(props: {
|
||||
activeVisualization: Visualization | null;
|
||||
framePublicAPI: FramePublicAPI;
|
||||
isFixedPosition?: boolean;
|
||||
}) {
|
||||
const dispatchLens = useLensDispatch();
|
||||
const visualization = useLensSelector(selectVisualizationState);
|
||||
const { activeVisualization, isFixedPosition } = props;
|
||||
const { activeVisualization } = props;
|
||||
const setVisualizationState = useCallback(
|
||||
(newState: unknown) => {
|
||||
if (!activeVisualization) {
|
||||
|
@ -99,12 +96,7 @@ export function VisualizationToolbar(props: {
|
|||
return (
|
||||
<>
|
||||
{ToolbarComponent && (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
className={classNames({
|
||||
'lnsVisualizationToolbar--fixed': isFixedPosition,
|
||||
})}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
{ToolbarComponent({
|
||||
frame: props.framePublicAPI,
|
||||
state: visualization.state,
|
||||
|
@ -128,6 +120,9 @@ export function WorkspacePanelWrapper({
|
|||
}: WorkspacePanelWrapperProps) {
|
||||
const dispatchLens = useLensDispatch();
|
||||
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const { euiTheme } = euiThemeContext;
|
||||
|
||||
const changesApplied = useLensSelector(selectChangesApplied);
|
||||
const autoApplyEnabled = useLensSelector(selectAutoApplyEnabled);
|
||||
|
||||
|
@ -180,9 +175,16 @@ export function WorkspacePanelWrapper({
|
|||
alignItems="flexEnd"
|
||||
gutterSize="s"
|
||||
direction="row"
|
||||
className={classNames('lnsWorkspacePanelWrapper__toolbar', {
|
||||
'lnsWorkspacePanelWrapper__toolbar--fullscreen': isFullscreen,
|
||||
})}
|
||||
css={css`
|
||||
margin-bottom: ${euiTheme.size.xs};
|
||||
${isFullscreen &&
|
||||
`
|
||||
background-color: ${euiTheme.colors.emptyShade};
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 0;
|
||||
padding: ${euiTheme.size.s} ${euiTheme.size.s} 0;
|
||||
`}
|
||||
`}
|
||||
responsive={false}
|
||||
>
|
||||
{!isFullscreen && (
|
||||
|
@ -238,10 +240,30 @@ export function WorkspacePanelWrapper({
|
|||
contentProps={{
|
||||
className: 'lnsWorkspacePanelWrapper__content',
|
||||
}}
|
||||
className={classNames('lnsWorkspacePanelWrapper stretch-for-sharing', {
|
||||
'lnsWorkspacePanelWrapper--fullscreen': isFullscreen,
|
||||
})}
|
||||
css={{ height: '100%' }}
|
||||
className={classNames('lnsWorkspacePanelWrapper stretch-for-sharing')}
|
||||
css={css`
|
||||
height: 100%;
|
||||
margin-bottom: ${euiTheme.size.base};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative; // For positioning the dnd overlay
|
||||
min-height: 400px;
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
|
||||
.lnsWorkspacePanelWrapper__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
${isFullscreen &&
|
||||
`
|
||||
margin-bottom: 0;
|
||||
.lnsWorkspacePanelWrapper__content {
|
||||
padding: ${euiTheme.size.s}
|
||||
}
|
||||
`}
|
||||
`}
|
||||
color="transparent"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
DataViewsPublicPluginStart,
|
||||
} from '@kbn/data-views-plugin/public';
|
||||
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { LensDocument } from '../persistence/saved_object_store';
|
||||
import {
|
||||
Datasource,
|
||||
|
@ -136,7 +137,14 @@ export class EditorFrameService {
|
|||
addUserMessages,
|
||||
}) => {
|
||||
return (
|
||||
<div className="lnsApp__frame">
|
||||
<div
|
||||
css={css`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
`}
|
||||
>
|
||||
<EditorFrame
|
||||
data-test-subj="lnsEditorFrame"
|
||||
core={core}
|
||||
|
|
|
@ -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 { UseEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const lnsExpressionRendererStyle = (euiThemeContext: UseEuiTheme) => {
|
||||
return css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
`;
|
||||
};
|
|
@ -25,7 +25,7 @@ export {
|
|||
mockDatasourceStates,
|
||||
defaultState,
|
||||
makeLensStore,
|
||||
mountWithProvider,
|
||||
mountWithReduxStore,
|
||||
renderWithReduxStore,
|
||||
} from './store_mocks';
|
||||
export { lensPluginMock } from './lens_plugin_mock';
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import React, { PropsWithChildren, ReactElement } from 'react';
|
||||
import { ReactWrapper, mount } from 'enzyme';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PreloadedState } from '@reduxjs/toolkit';
|
||||
import { RenderOptions, render } from '@testing-library/react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { RenderOptions } from '@testing-library/react';
|
||||
import { LensAppServices } from '../app_plugin/types';
|
||||
import { mountWithProviders, renderWithProviders } from '../test_utils/test_utils';
|
||||
import { makeConfigureStore, LensAppState, LensState, LensStoreDeps } from '../state_management';
|
||||
import { getResolvedDateRange } from '../utils';
|
||||
import { DatasourceMap, VisualizationMap } from '../types';
|
||||
|
@ -19,21 +19,15 @@ import { mockVisualizationMap } from './visualization_mock';
|
|||
import { mockDatasourceMap } from './datasource_mock';
|
||||
import { makeDefaultServices } from './services_mock';
|
||||
|
||||
export const mockStoreDeps = (
|
||||
{
|
||||
lensServices = makeDefaultServices(),
|
||||
datasourceMap = mockDatasourceMap(),
|
||||
visualizationMap = mockVisualizationMap(),
|
||||
}: {
|
||||
lensServices?: LensAppServices;
|
||||
datasourceMap?: DatasourceMap;
|
||||
visualizationMap?: VisualizationMap;
|
||||
} = {
|
||||
lensServices: makeDefaultServices(),
|
||||
datasourceMap: mockDatasourceMap(),
|
||||
visualizationMap: mockVisualizationMap(),
|
||||
}
|
||||
) => ({
|
||||
export const mockStoreDeps = ({
|
||||
lensServices = makeDefaultServices(),
|
||||
datasourceMap = mockDatasourceMap(),
|
||||
visualizationMap = mockVisualizationMap(),
|
||||
}: {
|
||||
lensServices?: LensAppServices;
|
||||
datasourceMap?: DatasourceMap;
|
||||
visualizationMap?: VisualizationMap;
|
||||
} = {}) => ({
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
lensServices,
|
||||
|
@ -86,17 +80,13 @@ export const renderWithReduxStore = (
|
|||
|
||||
const CustomWrapper = wrapper as React.ComponentType<React.PropsWithChildren<{}>>;
|
||||
|
||||
const Wrapper: React.FC<PropsWithChildren<{}>> = ({ children }) => {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<I18nProvider>
|
||||
{wrapper ? <CustomWrapper>{children}</CustomWrapper> : children}
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
const Wrapper: React.FC<PropsWithChildren<{}>> = ({ children }) => (
|
||||
<Provider store={store}>
|
||||
{wrapper ? <CustomWrapper>{children}</CustomWrapper> : children}
|
||||
</Provider>
|
||||
);
|
||||
|
||||
const rtlRender = render(ui, { wrapper: Wrapper, ...options });
|
||||
const rtlRender = renderWithProviders(ui, { wrapper: Wrapper, ...options });
|
||||
|
||||
return {
|
||||
store,
|
||||
|
@ -106,13 +96,11 @@ export const renderWithReduxStore = (
|
|||
|
||||
export function makeLensStore({
|
||||
preloadedState,
|
||||
dispatch,
|
||||
storeDeps = mockStoreDeps(),
|
||||
}: {
|
||||
storeDeps?: LensStoreDeps;
|
||||
preloadedState?: Partial<LensAppState>;
|
||||
dispatch?: jest.Mock;
|
||||
}) {
|
||||
} = {}) {
|
||||
const data = storeDeps.lensServices.data;
|
||||
const store = makeConfigureStore(storeDeps, {
|
||||
lens: {
|
||||
|
@ -124,18 +112,17 @@ export function makeLensStore({
|
|||
},
|
||||
} as unknown as PreloadedState<LensState>);
|
||||
|
||||
const origDispatch = store.dispatch;
|
||||
store.dispatch = jest.fn(dispatch || origDispatch);
|
||||
store.dispatch = jest.spyOn(store, 'dispatch') as jest.Mock;
|
||||
return { store, deps: storeDeps };
|
||||
}
|
||||
|
||||
export interface MountStoreProps {
|
||||
storeDeps?: LensStoreDeps;
|
||||
preloadedState?: Partial<LensAppState>;
|
||||
dispatch?: jest.Mock;
|
||||
}
|
||||
|
||||
export const mountWithProvider = async (
|
||||
// legacy enzyme usage: remove when all tests are migrated to @testing-library/react
|
||||
export const mountWithReduxStore = (
|
||||
component: React.ReactElement,
|
||||
store?: MountStoreProps,
|
||||
options?: {
|
||||
|
@ -144,52 +131,24 @@ export const mountWithProvider = async (
|
|||
attachTo?: HTMLElement;
|
||||
}
|
||||
) => {
|
||||
const { mountArgs, lensStore, deps } = getMountWithProviderParams(component, store, options);
|
||||
const instance = mount(mountArgs.component, mountArgs.options);
|
||||
return { instance, lensStore, deps };
|
||||
};
|
||||
|
||||
const getMountWithProviderParams = (
|
||||
component: React.ReactElement,
|
||||
store?: MountStoreProps,
|
||||
options?: {
|
||||
wrappingComponent?: React.FC<PropsWithChildren<{}>>;
|
||||
wrappingComponentProps?: Record<string, unknown>;
|
||||
attachTo?: HTMLElement;
|
||||
}
|
||||
) => {
|
||||
const { store: lensStore, deps } = makeLensStore(store || {});
|
||||
const { store: lensStore, deps } = makeLensStore(store);
|
||||
|
||||
let wrappingComponent: React.FC<PropsWithChildren<{}>> = ({ children }) => (
|
||||
<I18nProvider>
|
||||
<Provider store={lensStore}>{children}</Provider>
|
||||
</I18nProvider>
|
||||
<Provider store={lensStore}>{children}</Provider>
|
||||
);
|
||||
|
||||
let restOptions: {
|
||||
attachTo?: HTMLElement | undefined;
|
||||
} = {};
|
||||
if (options) {
|
||||
const { wrappingComponent: _wrappingComponent, wrappingComponentProps, ...rest } = options;
|
||||
restOptions = rest;
|
||||
|
||||
if (_wrappingComponent) {
|
||||
wrappingComponent = ({ children }) => {
|
||||
return _wrappingComponent({
|
||||
...wrappingComponentProps,
|
||||
children: <Provider store={lensStore}>{children}</Provider>,
|
||||
});
|
||||
};
|
||||
}
|
||||
if (options?.wrappingComponent) {
|
||||
wrappingComponent = ({ children }) => {
|
||||
return options?.wrappingComponent?.({
|
||||
...options?.wrappingComponentProps,
|
||||
children: wrappingComponent({ children }),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const mountArgs = {
|
||||
component,
|
||||
options: {
|
||||
wrappingComponent,
|
||||
...restOptions,
|
||||
} as unknown as ReactWrapper,
|
||||
};
|
||||
const instance = mountWithProviders(component, {
|
||||
...options,
|
||||
wrappingComponent,
|
||||
} as unknown as ReactWrapper);
|
||||
|
||||
return { mountArgs, lensStore, deps };
|
||||
return { instance, lensStore, deps };
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ import classNames from 'classnames';
|
|||
import { getOriginalRequestErrorMessages } from '../editor_frame_service/error_helper';
|
||||
import { LensInspector } from '../lens_inspector_service';
|
||||
import { UserMessage } from '../types';
|
||||
import { lnsExpressionRendererStyle } from '../expression_renderer_styles';
|
||||
|
||||
export interface ExpressionWrapperProps {
|
||||
ExpressionRenderer: ReactExpressionRendererType;
|
||||
|
@ -76,12 +77,12 @@ export function ExpressionWrapper({
|
|||
if (!expression) return null;
|
||||
return (
|
||||
<div
|
||||
className={classNames('lnsExpressionRenderer', className)}
|
||||
className={classNames('lnsExpressionRenderer', 'eui-scrollBar', className)}
|
||||
css={lnsExpressionRendererStyle}
|
||||
style={style}
|
||||
data-test-subj="lens-embeddable"
|
||||
>
|
||||
<ExpressionRendererComponent
|
||||
className="lnsExpressionRenderer__component"
|
||||
padding={noPadding ? undefined : 's'}
|
||||
variables={variables}
|
||||
allowCache={true}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { TracksOverlays } from '@kbn/presentation-containers';
|
|||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { type UseEuiTheme } from '@elastic/eui';
|
||||
|
||||
/**
|
||||
* Shared logic to mount the inline config panel
|
||||
|
@ -41,6 +42,7 @@ export function mountInlineEditPanel(
|
|||
),
|
||||
{
|
||||
className: 'lnsConfigPanel__overlay',
|
||||
css: inlineFlyoutStyles,
|
||||
size: 's',
|
||||
'data-test-subj': 'customizeLens',
|
||||
type: 'push',
|
||||
|
@ -60,3 +62,22 @@ export function mountInlineEditPanel(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// styles needed to display extra drop targets that are outside of the config panel main area while also allowing to scroll vertically
|
||||
const inlineFlyoutStyles = ({ euiTheme }: UseEuiTheme) => `
|
||||
clip-path: polygon(-100% 0, 100% 0, 100% 100%, -100% 100%);
|
||||
max-inline-size: 640px;
|
||||
min-inline-size: 256px;
|
||||
background:${euiTheme.colors.backgroundBaseSubdued};
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
clip-path: none;
|
||||
}
|
||||
.kbnOverlayMountWrapper {
|
||||
padding-left: 400px;
|
||||
margin-left: -400px;
|
||||
pointer-events: none;
|
||||
.euiFlyoutFooter {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import { UserMessage } from '../../types';
|
||||
import { getLongMessage } from '../../user_messages_utils';
|
||||
|
||||
|
@ -24,7 +25,16 @@ export function VisualizationErrorPanel({
|
|||
const showMore = errors.length > 1;
|
||||
const canFixInLens = canEdit && errors.some(({ fixableInEditor }) => fixableInEditor);
|
||||
return (
|
||||
<div className="lnsEmbeddedError">
|
||||
<div
|
||||
className="lnsEmbeddedError"
|
||||
css={css`
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: auto;
|
||||
`}
|
||||
>
|
||||
<EuiEmptyPrompt
|
||||
iconType="warning"
|
||||
iconColor="danger"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
.lnsPanelFeatureList {
|
||||
max-height: $euiSize * 20;
|
||||
@include euiYScroll;
|
||||
}
|
|
@ -19,7 +19,6 @@ import { i18n } from '@kbn/i18n';
|
|||
import React, { Fragment } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type { UserMessage } from '../../types';
|
||||
import './info_badges.scss';
|
||||
import { getLongMessage } from '../../user_messages_utils';
|
||||
|
||||
export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }) => {
|
||||
|
@ -65,7 +64,13 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }
|
|||
gap: ${euiTheme.size.xs};
|
||||
}
|
||||
&:hover {
|
||||
color: ${euiTheme.colors.text};
|
||||
color: ${euiTheme.colors.textParagraph};
|
||||
}
|
||||
// Make the visualization modifiers icon appear only on panel hover
|
||||
.embPanel__content:hover & {
|
||||
background: ${euiTheme.colors.backgroundBasePlain};
|
||||
transition: color ${euiTheme.animation.slow}, background ${euiTheme.animation.slow};
|
||||
color: ${euiTheme.colors.textParagraph};
|
||||
}
|
||||
`}
|
||||
iconType="wrench"
|
||||
|
@ -101,7 +106,12 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }
|
|||
<EuiTitle size="xxs" css={css`color=${euiTheme.colors.title}`}>
|
||||
<h3>{shortMessage}</h3>
|
||||
</EuiTitle>
|
||||
<ul className="lnsPanelFeatureList">
|
||||
<ul
|
||||
className="eui-yScroll"
|
||||
css={css`
|
||||
max-height: 320px;
|
||||
`}
|
||||
>
|
||||
{messageGroup.map((message, i) => (
|
||||
<Fragment key={`${uniqueId}-${i}`}>{getLongMessage(message)}</Fragment>
|
||||
))}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
.kbnToolbarButton {
|
||||
line-height: $euiButtonHeight; // Keeps alignment of text and chart icon
|
||||
|
||||
// todo: once issue https://github.com/elastic/eui/issues/4730 is merged, this code might be safe to remove
|
||||
// Some toolbar buttons are just icons, but EuiButton comes with margin and min-width that need to be removed
|
||||
min-width: 0;
|
||||
border-width: $euiBorderWidthThin;
|
||||
border-style: solid;
|
||||
border-color: $euiBorderColor; // Lighten the border color for all states
|
||||
|
||||
// Override background color for non-disabled buttons
|
||||
&:not(:disabled) {
|
||||
background-color: $euiColorEmptyShade;
|
||||
}
|
||||
|
||||
.kbnToolbarButton__text > svg {
|
||||
margin-top: -1px; // Just some weird alignment issue when icon is the child not the `iconType`
|
||||
}
|
||||
|
||||
.kbnToolbarButton__text:empty {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// Toolbar buttons don't look good with centered text when fullWidth
|
||||
&[class*='fullWidth'] {
|
||||
text-align: left;
|
||||
|
||||
.kbnToolbarButton__content {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kbnToolbarButton--groupLeft {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.kbnToolbarButton--groupCenter {
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.kbnToolbarButton--groupRight {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.kbnToolbarButton--bold {
|
||||
font-weight: $euiFontWeightBold;
|
||||
}
|
||||
|
||||
.kbnToolbarButton--normal {
|
||||
font-weight: $euiFontWeightRegular;
|
||||
}
|
||||
|
||||
.kbnToolbarButton--s {
|
||||
box-shadow: none !important; // sass-lint:disable-line no-important
|
||||
font-size: $euiFontSizeS;
|
||||
}
|
|
@ -5,10 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './toolbar_button.scss';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { EuiButton, PropsOf, EuiButtonProps } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
PropsOf,
|
||||
EuiButtonProps,
|
||||
type UseEuiTheme,
|
||||
euiFontSize,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
const groupPositionToClassMap = {
|
||||
none: null,
|
||||
|
@ -57,6 +64,7 @@ export const ToolbarButton: React.FunctionComponent<ToolbarButtonProps> = ({
|
|||
textProps,
|
||||
...rest
|
||||
}) => {
|
||||
const euiThemeContext = useEuiTheme();
|
||||
const classes = classNames(
|
||||
'kbnToolbarButton',
|
||||
groupPositionToClassMap[groupPosition],
|
||||
|
@ -69,6 +77,7 @@ export const ToolbarButton: React.FunctionComponent<ToolbarButtonProps> = ({
|
|||
data-test-subj={dataTestSubj}
|
||||
className={classes}
|
||||
iconSide="right"
|
||||
css={toolbarButtonStyles(euiThemeContext)}
|
||||
iconType={hasArrow ? 'arrowDown' : ''}
|
||||
color="text"
|
||||
contentProps={{
|
||||
|
@ -85,3 +94,70 @@ export const ToolbarButton: React.FunctionComponent<ToolbarButtonProps> = ({
|
|||
</EuiButton>
|
||||
);
|
||||
};
|
||||
|
||||
const toolbarButtonStyles = (euiThemeContext: UseEuiTheme) => {
|
||||
const { euiTheme } = euiThemeContext;
|
||||
return css`
|
||||
&.kbnToolbarButton {
|
||||
line-height: ${euiTheme.size.xxl}; // Keeps alignment of text and chart icon
|
||||
|
||||
// todo: once issue https://github.com/elastic/eui/issues/4730 is merged, this code might be safe to remove
|
||||
// Some toolbar buttons are just icons, but EuiButton comes with margin and min-width that need to be removed
|
||||
min-width: 0;
|
||||
border-width: ${euiTheme.border.width.thin};
|
||||
border-style: solid;
|
||||
border-color: ${euiTheme.border.color}; // Lighten the border color for all states
|
||||
|
||||
// Override background color for non-disabled buttons
|
||||
&:not(:disabled) {
|
||||
background-color: ${euiTheme.colors.backgroundBasePlain};
|
||||
}
|
||||
|
||||
&.kbnToolbarButton__text > svg {
|
||||
margin-top: -1px; // Just some weird alignment issue when icon is the child not the iconType
|
||||
}
|
||||
|
||||
&.kbnToolbarButton__text:empty {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// Toolbar buttons don't look good with centered text when fullWidth
|
||||
&[class*='fullWidth'] {
|
||||
text-align: left;
|
||||
|
||||
.kbnToolbarButton__content {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.kbnToolbarButton--groupLeft {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&.kbnToolbarButton--groupCenter {
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
&.kbnToolbarButton--groupRight {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
&.kbnToolbarButton--bold {
|
||||
font-weight: ${euiTheme.font.weight.bold};
|
||||
}
|
||||
|
||||
&.kbnToolbarButton--normal {
|
||||
font-weight: ${euiTheme.font.weight.regular};
|
||||
}
|
||||
|
||||
&.kbnToolbarButton--s {
|
||||
box-shadow: none !important; // sass-lint:disable-line no-important
|
||||
font-size: ${euiFontSize(euiThemeContext, 's').fontSize};
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -6,21 +6,23 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TriggerButton } from './trigger';
|
||||
import { renderWithProviders } from '../../test_utils/test_utils';
|
||||
import * as ToolbarButtonFile from './toolbar_button';
|
||||
|
||||
describe('TriggerButton', () => {
|
||||
describe('base version (no icons)', () => {
|
||||
it('should render the basic button', () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TriggerButton togglePopover={jest.fn()} label={'Trigger label'} dataTestSubj="test-id" />
|
||||
);
|
||||
expect(screen.getByText('Trigger label')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the title if provided', () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TriggerButton
|
||||
togglePopover={jest.fn()}
|
||||
label={'Trigger'}
|
||||
|
@ -33,7 +35,7 @@ describe('TriggerButton', () => {
|
|||
|
||||
it('should call the toggle callback on click', async () => {
|
||||
const toggleFn = jest.fn();
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TriggerButton
|
||||
togglePopover={toggleFn}
|
||||
label={'Trigger'}
|
||||
|
@ -47,7 +49,8 @@ describe('TriggerButton', () => {
|
|||
});
|
||||
|
||||
it('should render the main label as red if missing', () => {
|
||||
render(
|
||||
const ToolbarButtonSpy = jest.spyOn(ToolbarButtonFile, 'ToolbarButton');
|
||||
renderWithProviders(
|
||||
<TriggerButton
|
||||
togglePopover={jest.fn()}
|
||||
label={'Trigger'}
|
||||
|
@ -56,14 +59,16 @@ describe('TriggerButton', () => {
|
|||
isMissingCurrent
|
||||
/>
|
||||
);
|
||||
// EUI danger red: rgb(167, 22, 39)
|
||||
expect(screen.getByTestId('test-id')).toHaveStyle({ color: 'rgb(167, 22, 39)' });
|
||||
expect(ToolbarButtonSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ color: 'danger' }),
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with icons', () => {
|
||||
it('should render one icon', () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TriggerButton
|
||||
togglePopover={jest.fn()}
|
||||
label={'Trigger label'}
|
||||
|
@ -83,7 +88,7 @@ describe('TriggerButton', () => {
|
|||
|
||||
it('should render multiple icons', () => {
|
||||
const indexes = [1, 2, 3];
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TriggerButton
|
||||
togglePopover={jest.fn()}
|
||||
label={'Trigger label'}
|
||||
|
@ -102,7 +107,7 @@ describe('TriggerButton', () => {
|
|||
});
|
||||
|
||||
it('should render the value together with the provided component', () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TriggerButton
|
||||
togglePopover={jest.fn()}
|
||||
label={'Trigger label'}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { UseEuiTheme, euiShadow } from '@elastic/eui';
|
||||
import { css, keyframes } from '@emotion/react';
|
||||
|
||||
const flyoutOpenCloseAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
75% {
|
||||
opacity: 1;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
`;
|
||||
|
||||
export const flyoutContainerStyles = (euiThemeContext: UseEuiTheme) => css`
|
||||
border-left: ${euiThemeContext.euiTheme.border.thin};
|
||||
${euiShadow(euiThemeContext, 'xl')};
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
z-index: ${euiThemeContext.euiTheme.levels.flyout};
|
||||
background: ${euiThemeContext.euiTheme.colors.backgroundBasePlain};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
animation: ${flyoutOpenCloseAnimation} ${euiThemeContext.euiTheme.animation.normal}
|
||||
${euiThemeContext.euiTheme.animation.resistance};
|
||||
.lnsIndexPatternDimensionEditor--padded {
|
||||
padding: ${euiThemeContext.euiTheme.size.base};
|
||||
}
|
||||
.lnsIndexPatternDimensionEditor--collapseNext {
|
||||
margin-bottom: -${euiThemeContext.euiTheme.size.l};
|
||||
border-top: ${euiThemeContext.euiTheme.border.thin};
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
`;
|
|
@ -1,47 +0,0 @@
|
|||
@import '../mixins';
|
||||
|
||||
.lnsDimensionContainer {
|
||||
// Use the EuiFlyout style
|
||||
@include euiFlyout;
|
||||
// But with custom positioning to keep it within the sidebar contents
|
||||
animation: euiFlyoutAnimation $euiAnimSpeedNormal $euiAnimSlightResistance;
|
||||
max-width: none !important;
|
||||
left: 0;
|
||||
z-index: $euiZContentMenu;
|
||||
|
||||
@include euiBreakpoint('m', 'l', 'xl') {
|
||||
height: 100% !important;
|
||||
position: absolute;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
.lnsFrameLayout__sidebar-isFullscreen & {
|
||||
border-left: $euiBorderThin; // Force border regardless of theme in fullscreen
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsDimensionContainer__header {
|
||||
padding: $euiSize;
|
||||
|
||||
.lnsFrameLayout__sidebar-isFullscreen & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsDimensionContainer__content {
|
||||
flex: 1;
|
||||
@include euiYScroll;
|
||||
}
|
||||
|
||||
.lnsDimensionContainer__footer {
|
||||
padding: $euiSize;
|
||||
|
||||
.lnsFrameLayout__sidebar-isFullscreen & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsBody--overflowHidden {
|
||||
overflow: hidden;
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './flyout_container.scss';
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
|
@ -18,9 +16,13 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFocusTrap,
|
||||
type UseEuiTheme,
|
||||
euiBreakpoint,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../utils';
|
||||
import { flyoutContainerStyles } from './flyout.styles';
|
||||
|
||||
function fromExcludedClickTarget(event: Event) {
|
||||
for (
|
||||
|
@ -61,6 +63,7 @@ export function FlyoutContainer({
|
|||
isInlineEditing?: boolean;
|
||||
}) {
|
||||
const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false);
|
||||
const euiThemeContext = useEuiTheme();
|
||||
|
||||
const closeFlyout = useCallback(() => {
|
||||
setFocusTrapIsEnabled(false);
|
||||
|
@ -69,12 +72,14 @@ export function FlyoutContainer({
|
|||
|
||||
useEffect(() => {
|
||||
if (!isInlineEditing) {
|
||||
document.body.classList.toggle('lnsBody--overflowHidden', isOpen);
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = isOpen ? 'hidden' : '';
|
||||
}
|
||||
return () => {
|
||||
if (isOpen) {
|
||||
setFocusTrapIsEnabled(false);
|
||||
}
|
||||
document.body.classList.remove('lnsBody--overflowHidden');
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
}
|
||||
}, [isInlineEditing, isOpen]);
|
||||
|
@ -100,10 +105,13 @@ export function FlyoutContainer({
|
|||
ref={panelContainerRef}
|
||||
role="dialog"
|
||||
aria-labelledby="lnsDimensionContainerTitle"
|
||||
className="lnsDimensionContainer"
|
||||
css={css`
|
||||
box-shadow: ${isInlineEditing ? 'none !important' : 'inherit'};
|
||||
`}
|
||||
css={[
|
||||
css`
|
||||
box-shadow: ${isInlineEditing || isFullscreen ? 'none !important' : 'inherit'};
|
||||
`,
|
||||
flyoutContainerStyles(euiThemeContext),
|
||||
dimensionContainerStyles.self(euiThemeContext),
|
||||
]}
|
||||
onAnimationEnd={() => {
|
||||
if (isOpen) {
|
||||
// EuiFocusTrap interferes with animating elements with absolute position:
|
||||
|
@ -113,7 +121,7 @@ export function FlyoutContainer({
|
|||
}
|
||||
}}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder className="lnsDimensionContainer__header">
|
||||
<EuiFlyoutHeader hasBorder css={dimensionContainerStyles.header(euiThemeContext)}>
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}>
|
||||
{isInlineEditing && (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -131,12 +139,7 @@ export function FlyoutContainer({
|
|||
)}
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiTitle size="xs">
|
||||
<h2
|
||||
id="lnsDimensionContainerTitle"
|
||||
className="lnsDimensionContainer__headerTitle"
|
||||
>
|
||||
{label}
|
||||
</h2>
|
||||
<h2 id="lnsDimensionContainerTitle">{label}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
@ -157,10 +160,18 @@ export function FlyoutContainer({
|
|||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<div className="lnsDimensionContainer__content">{children}</div>
|
||||
<div
|
||||
className="eui-yScroll"
|
||||
css={css`
|
||||
flex: 1;
|
||||
z-index: 1;
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{customFooter || (
|
||||
<EuiFlyoutFooter className="lnsDimensionContainer__footer">
|
||||
<EuiFlyoutFooter css={dimensionContainerStyles.footer(euiThemeContext)}>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="s"
|
||||
|
@ -183,3 +194,24 @@ export function FlyoutContainer({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const dimensionContainerStyles = {
|
||||
self: (euiThemeContext: UseEuiTheme) => {
|
||||
return css`
|
||||
// But with custom positioning to keep it within the sidebar contents
|
||||
max-width: none !important;
|
||||
left: 0;
|
||||
${euiBreakpoint(euiThemeContext, ['m', 'l', 'xl'])} {
|
||||
height: 100% !important;
|
||||
position: absolute;
|
||||
top: 0 !important;
|
||||
}
|
||||
`;
|
||||
},
|
||||
header: ({ euiTheme }: UseEuiTheme) => css`
|
||||
padding: ${euiTheme.size.base};
|
||||
`,
|
||||
footer: ({ euiTheme }: UseEuiTheme) => css`
|
||||
padding: ${euiTheme.size.base};
|
||||
`,
|
||||
};
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
@import '../mixins';
|
||||
|
||||
.lnsSettingWithSiblingFlyout {
|
||||
// Use the EuiFlyout style
|
||||
@include euiFlyout;
|
||||
// But with custom positioning to keep it within the sidebar contents
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
animation: euiFlyoutAnimation $euiAnimSpeedNormal $euiAnimSlightResistance;
|
||||
// making just a bit higher than the dimension flyout to stack on top of it
|
||||
z-index: $euiZLevel3 + 1
|
||||
}
|
||||
|
||||
.lnsSettingWithSiblingFlyout__header {
|
||||
padding: $euiSize;
|
||||
}
|
||||
|
||||
.lnsSettingWithSiblingFlyout__content {
|
||||
flex: 1;
|
||||
@include euiYScroll;
|
||||
}
|
||||
|
||||
.lnsSettingWithSiblingFlyout__footer {
|
||||
padding: $euiSize;
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './setting_with_sibling_flyout.scss';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState, useEffect, MutableRefObject } from 'react';
|
||||
import {
|
||||
|
@ -20,7 +18,11 @@ import {
|
|||
EuiFocusTrap,
|
||||
EuiOutsideClickDetector,
|
||||
EuiPortal,
|
||||
type UseEuiTheme,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { flyoutContainerStyles } from './flyout.styles';
|
||||
|
||||
const DEFAULT_TITLE = i18n.translate('xpack.lens.colorSiblingFlyoutTitle', {
|
||||
defaultMessage: 'Color',
|
||||
|
@ -43,6 +45,7 @@ export function SettingWithSiblingFlyout({
|
|||
}) {
|
||||
const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false);
|
||||
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
|
||||
const euiThemeContext = useEuiTheme();
|
||||
|
||||
const toggleFlyout = () => {
|
||||
setIsFlyoutOpen(!isFlyoutOpen);
|
||||
|
@ -74,9 +77,15 @@ export function SettingWithSiblingFlyout({
|
|||
role="dialog"
|
||||
aria-labelledby="lnsSettingWithSiblingFlyoutTitle"
|
||||
data-test-subj={dataTestSubj}
|
||||
className="lnsSettingWithSiblingFlyout"
|
||||
css={[
|
||||
flyoutContainerStyles(euiThemeContext),
|
||||
siblingflyoutContainerStyles.self(euiThemeContext),
|
||||
]}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder className="lnsSettingWithSiblingFlyout__header">
|
||||
<EuiFlyoutHeader
|
||||
hasBorder
|
||||
css={siblingflyoutContainerStyles.header(euiThemeContext)}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
|
@ -92,20 +101,24 @@ export function SettingWithSiblingFlyout({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h3
|
||||
id="lnsSettingWithSiblingFlyoutTitle"
|
||||
className="lnsSettingWithSiblingFlyout__headerTitle"
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<h3 id="lnsSettingWithSiblingFlyoutTitle">{title}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
{children && <div className="lnsSettingWithSiblingFlyout__content">{children}</div>}
|
||||
{children && (
|
||||
<div
|
||||
className="eui-yScroll"
|
||||
css={css`
|
||||
flex: 1;
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EuiFlyoutFooter className="lnsSettingWithSiblingFlyout__footer">
|
||||
<EuiFlyoutFooter css={siblingflyoutContainerStyles.footer(euiThemeContext)}>
|
||||
<EuiButtonEmpty flush="left" size="s" iconType="sortLeft" onClick={closeFlyout}>
|
||||
{i18n.translate('xpack.lens.settingWithSiblingFlyout.back', {
|
||||
defaultMessage: 'Back',
|
||||
|
@ -120,3 +133,21 @@ export function SettingWithSiblingFlyout({
|
|||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const siblingflyoutContainerStyles = {
|
||||
self: ({ euiTheme }: UseEuiTheme) => css`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
// making just a bit higher than the dimension flyout to stack on top of it
|
||||
z-index: ${euiTheme.levels.menu};
|
||||
`,
|
||||
header: ({ euiTheme }: UseEuiTheme) => css`
|
||||
padding: ${euiTheme.size.base};
|
||||
`,
|
||||
footer: ({ euiTheme }: UseEuiTheme) => css`
|
||||
padding: ${euiTheme.size.base};
|
||||
`,
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, IconType } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, IconType, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const StaticHeader = ({
|
||||
|
@ -18,12 +18,15 @@ export const StaticHeader = ({
|
|||
icon?: IconType;
|
||||
indicator?: React.ReactNode;
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
responsive={false}
|
||||
className={'lnsLayerPanel__settingsStaticHeader'}
|
||||
css={css`
|
||||
padding-left: ${euiTheme.size.xs};
|
||||
`}
|
||||
>
|
||||
{icon && (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.lnsVisToolbar__popover {
|
||||
width: 410px;
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import './toolbar_popover.scss';
|
||||
import React, { PropsWithChildren, useState } from 'react';
|
||||
import { EuiFlexItem, EuiPopover, EuiPopoverProps, EuiPopoverTitle, IconType } from '@elastic/eui';
|
||||
import { ToolbarButton, ToolbarButtonProps } from '@kbn/shared-ux-button-toolbar';
|
||||
|
@ -41,6 +40,8 @@ export type ToolbarPopoverProps = Partial<EuiPopoverProps> & {
|
|||
handleClose?: () => void;
|
||||
};
|
||||
|
||||
const defaultPanelStyles = { width: '410px' };
|
||||
|
||||
export const ToolbarPopover: React.FC<PropsWithChildren<ToolbarPopoverProps>> = ({
|
||||
children,
|
||||
title,
|
||||
|
@ -49,7 +50,7 @@ export const ToolbarPopover: React.FC<PropsWithChildren<ToolbarPopoverProps>> =
|
|||
groupPosition,
|
||||
buttonDataTestSubj,
|
||||
handleClose,
|
||||
panelClassName = 'lnsVisToolbar__popover',
|
||||
panelStyle = defaultPanelStyles,
|
||||
...euiPopoverProps
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
@ -59,7 +60,7 @@ export const ToolbarPopover: React.FC<PropsWithChildren<ToolbarPopoverProps>> =
|
|||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
panelClassName={panelClassName}
|
||||
panelStyle={panelStyle}
|
||||
ownFocus
|
||||
aria-label={title}
|
||||
button={
|
||||
|
|
|
@ -37,7 +37,7 @@ import { layerTypes } from '../../common/layer_types';
|
|||
describe('lensSlice', () => {
|
||||
let store: EnhancedStore<{ lens: LensAppState }>;
|
||||
beforeEach(() => {
|
||||
store = makeLensStore({}).store;
|
||||
store = makeLensStore().store;
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
const customQuery = { query: 'custom' } as Query;
|
||||
|
|
|
@ -118,13 +118,13 @@ describe('Initializing the store', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const { store, deps } = makeLensStore({
|
||||
const { store } = makeLensStore({
|
||||
storeDeps,
|
||||
preloadedState,
|
||||
});
|
||||
|
||||
await loadInitialAppState(store, defaultProps);
|
||||
const { datasourceMap } = deps;
|
||||
const { datasourceMap } = storeDeps;
|
||||
|
||||
expect(datasourceMap.testDatasource.initialize).toHaveBeenCalledWith(
|
||||
datasource1State,
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 { EuiThemeProvider } from '@elastic/eui';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { RenderOptions, render } from '@testing-library/react';
|
||||
import { ComponentType, MountRendererProps, mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { PropsWithChildren, ReactElement } from 'react';
|
||||
import { LensAppServices } from '../app_plugin/types';
|
||||
|
||||
export const renderWithProviders = (
|
||||
ui: ReactElement,
|
||||
renderOptions?: RenderOptions
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): any => {
|
||||
const { wrapper, ...options } = renderOptions || {};
|
||||
|
||||
const CustomWrapper = wrapper as React.ComponentType<React.PropsWithChildren<{}>>;
|
||||
|
||||
const Wrapper: React.FC<PropsWithChildren<{}>> = ({ children }) => {
|
||||
return (
|
||||
<KibanaContextProvider services={coreMock.createStart() as unknown as LensAppServices}>
|
||||
<I18nProvider>
|
||||
<EuiThemeProvider>
|
||||
{wrapper ? <CustomWrapper>{children}</CustomWrapper> : children}
|
||||
</EuiThemeProvider>
|
||||
</I18nProvider>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const rtlRender = render(ui, { wrapper: Wrapper, ...options });
|
||||
|
||||
return rtlRender;
|
||||
};
|
||||
|
||||
// legacy enzyme usage: remove when all tests are migrated to @testing-library/react
|
||||
export const mountWithProviders = (component: React.ReactElement, options?: MountRendererProps) => {
|
||||
const { wrappingComponent, wrappingComponentProps } = options || {};
|
||||
|
||||
const WrappingComponent = wrappingComponent as React.ComponentType<React.PropsWithChildren<{}>>;
|
||||
|
||||
const wrapper: React.FC<PropsWithChildren<{}>> = ({ children }) => (
|
||||
<KibanaContextProvider services={coreMock.createStart() as unknown as LensAppServices}>
|
||||
<I18nProvider>
|
||||
<EuiThemeProvider>
|
||||
{WrappingComponent ? (
|
||||
<WrappingComponent {...wrappingComponentProps}>{children}</WrappingComponent>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</EuiThemeProvider>
|
||||
</I18nProvider>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
||||
const instance = mount(component, {
|
||||
...options,
|
||||
wrappingComponent: wrapper as ComponentType<PropsWithChildren<{}>>,
|
||||
});
|
||||
return instance;
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
// styles needed to display extra drop targets that are outside of the config panel main area while also allowing to scroll vertically
|
||||
.lnsConfigPanel__overlay {
|
||||
clip-path: polygon(-100% 0, 100% 0, 100% 100%, -100% 100%);
|
||||
max-inline-size: $euiSizeXXL * 20;
|
||||
min-inline-size: $euiSizeXXL * 8;
|
||||
background: $euiColorBackgroundBaseSubdued;
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
clip-path: none;
|
||||
}
|
||||
.kbnOverlayMountWrapper {
|
||||
padding-left: $euiFormMaxWidth;
|
||||
margin-left: -$euiFormMaxWidth;
|
||||
pointer-events: none;
|
||||
.euiFlyoutFooter {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lnsEditFlyoutBody {
|
||||
.euiFlyoutBody__overflow {
|
||||
transform: initial;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import { isOfAggregateQueryType } from '@kbn/es-query';
|
|||
import { ENABLE_ESQL } from '@kbn/esql-utils';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import '../helpers.scss';
|
||||
import { PublishingSubject } from '@kbn/presentation-publishing';
|
||||
import { LensPluginStartDependencies } from '../../../plugin';
|
||||
import { DatasourceMap, VisualizationMap } from '../../../types';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue