Skip to content

Commit

Permalink
[Lens] Provide single-value functions to show the "First" or "Last" v…
Browse files Browse the repository at this point in the history
…alue of some field (#83437)
  • Loading branch information
mbondyra committed Dec 2, 2020
1 parent 7426452 commit f9e7cb0
Show file tree
Hide file tree
Showing 32 changed files with 1,256 additions and 127 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.lnsWorkspaceWarning__button {
color: $euiColorWarningText;
}

.lnsWorkspaceWarningList {
@include euiYScroll;
max-height: $euiSize * 20;
width: $euiSize * 16;
}

.lnsWorkspaceWarningList__item {
padding: $euiSize;

& + & {
border-top: $euiBorderThin;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import './workspace_panel_wrapper.scss';
import './warnings_popover.scss';

import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiPopover, EuiText, EuiButtonEmpty } from '@elastic/eui';

export const WarningsPopover = ({
children,
}: {
children?: React.ReactNode | React.ReactNode[];
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);

if (!children) {
return null;
}

const onButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen);
const closePopover = () => setIsPopoverOpen(false);
const warningsCount = React.Children.count(children);
return (
<EuiPopover
panelPaddingSize="none"
button={
<EuiButtonEmpty
onClick={onButtonClick}
iconType="alert"
className="lnsWorkspaceWarning__button"
>
{i18n.translate('xpack.lens.chartWarnings.number', {
defaultMessage: `{warningsCount} {warningsCount, plural, one {warning} other {warnings}}`,
values: {
warningsCount,
},
})}
</EuiButtonEmpty>
}
isOpen={isPopoverOpen}
closePopover={closePopover}
>
<ul className="lnsWorkspaceWarningList">
{React.Children.map(children, (child, index) => (
<li key={index} className="lnsWorkspaceWarningList__item">
<EuiText size="s">{child}</EuiText>
</li>
))}
</ul>
</EuiPopover>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Datasource, FramePublicAPI, Visualization } from '../../../types';
import { NativeRenderer } from '../../../native_renderer';
import { Action } from '../state_management';
import { ChartSwitch } from './chart_switch';
import { WarningsPopover } from './warnings_popover';

export interface WorkspacePanelWrapperProps {
children: React.ReactNode | React.ReactNode[];
Expand Down Expand Up @@ -64,40 +65,59 @@ export function WorkspacePanelWrapper({
},
[dispatch, activeVisualization]
);
const warningMessages =
activeVisualization?.getWarningMessages &&
activeVisualization.getWarningMessages(visualizationState, framePublicAPI);
return (
<>
<div>
<EuiFlexGroup
alignItems="center"
gutterSize="m"
direction="row"
responsive={false}
wrap={true}
className="lnsWorkspacePanelWrapper__toolbar"
justifyContent="spaceBetween"
>
<EuiFlexItem grow={false}>
<ChartSwitch
data-test-subj="lnsChartSwitcher"
visualizationMap={visualizationMap}
visualizationId={visualizationId}
visualizationState={visualizationState}
datasourceMap={datasourceMap}
datasourceStates={datasourceStates}
dispatch={dispatch}
framePublicAPI={framePublicAPI}
/>
<EuiFlexGroup
gutterSize="m"
direction="row"
responsive={false}
wrap={true}
className="lnsWorkspacePanelWrapper__toolbar"
>
<EuiFlexItem grow={false}>
<ChartSwitch
data-test-subj="lnsChartSwitcher"
visualizationMap={visualizationMap}
visualizationId={visualizationId}
visualizationState={visualizationState}
datasourceMap={datasourceMap}
datasourceStates={datasourceStates}
dispatch={dispatch}
framePublicAPI={framePublicAPI}
/>
</EuiFlexItem>
{activeVisualization && activeVisualization.renderToolbar && (
<EuiFlexItem grow={false}>
<NativeRenderer
render={activeVisualization.renderToolbar}
nativeProps={{
frame: framePublicAPI,
state: visualizationState,
setState: setVisualizationState,
}}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{warningMessages && warningMessages.length ? (
<WarningsPopover>{warningMessages}</WarningsPopover>
) : null}
</EuiFlexItem>
{activeVisualization && activeVisualization.renderToolbar && (
<EuiFlexItem grow={false}>
<NativeRenderer
render={activeVisualization.renderToolbar}
nativeProps={{
frame: framePublicAPI,
state: visualizationState,
setState: setVisualizationState,
}}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
</div>
<EuiPageContent className="lnsWorkspacePanelWrapper">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
column-gap: $euiSizeXL;
}

.lnsIndexPatternDimensionEditor__operation .euiListGroupItem__label {
width: 100%;
}

.lnsIndexPatternDimensionEditor__operation > button {
padding-top: 0;
padding-bottom: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
EuiSpacer,
EuiListGroupItemProps,
EuiFormLabel,
EuiToolTip,
} from '@elastic/eui';
import { IndexPatternDimensionEditorProps } from './dimension_panel';
import { OperationSupportMatrix } from './operation_support';
Expand Down Expand Up @@ -141,20 +142,19 @@ export function DimensionEditor(props: DimensionEditorProps) {
definition.input === 'field' &&
fieldByOperation[operationType]?.has(selectedColumn.sourceField)) ||
(selectedColumn && !hasField(selectedColumn) && definition.input === 'none'),
disabledStatus:
definition.getDisabledStatus &&
definition.getDisabledStatus(state.indexPatterns[state.currentIndexPatternId]),
};
});

const selectedColumnSourceField =
selectedColumn && 'sourceField' in selectedColumn ? selectedColumn.sourceField : undefined;

const currentFieldIsInvalid = useMemo(
() =>
fieldIsInvalid(selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern),
[selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern]
);
const currentFieldIsInvalid = useMemo(() => fieldIsInvalid(selectedColumn, currentIndexPattern), [
selectedColumn,
currentIndexPattern,
]);

const sideNavItems: EuiListGroupItemProps[] = operationsWithCompatibility.map(
({ operationType, compatibleWithCurrentField }) => {
({ operationType, compatibleWithCurrentField, disabledStatus }) => {
const isActive = Boolean(
incompleteOperation === operationType ||
(!incompleteOperation && selectedColumn && selectedColumn.operationType === operationType)
Expand All @@ -168,7 +168,13 @@ export function DimensionEditor(props: DimensionEditorProps) {
}

let label: EuiListGroupItemProps['label'] = operationPanels[operationType].displayName;
if (isActive) {
if (disabledStatus) {
label = (
<EuiToolTip content={disabledStatus} display="block" position="left">
<span>{operationPanels[operationType].displayName}</span>
</EuiToolTip>
);
} else if (isActive) {
label = <strong>{operationPanels[operationType].displayName}</strong>;
}

Expand All @@ -178,6 +184,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
color,
isActive,
size: 's',
isDisabled: !!disabledStatus,
className: 'lnsIndexPatternDimensionEditor__operation',
'data-test-subj': `lns-indexPatternDimension-${operationType}${
compatibleWithCurrentField ? '' : ' incompatible'
Expand Down Expand Up @@ -264,7 +271,6 @@ export function DimensionEditor(props: DimensionEditorProps) {
? currentIndexPattern.getFieldByName(selectedColumn.sourceField)
: undefined,
});

setState(mergeLayer({ state, layerId, newLayer }));
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1242,12 +1242,12 @@ describe('IndexPatternDimensionEditorPanel', () => {
expect(items.map(({ label }: { label: React.ReactNode }) => label)).toEqual([
'Average',
'Count',
'Last value',
'Maximum',
'Median',
'Minimum',
'Sum',
'Unique count',
'\u00a0',
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { DatasourceDimensionTriggerProps, DatasourceDimensionEditorProps } from '../../types';
import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';
import { IndexPatternColumn } from '../indexpattern';
import { fieldIsInvalid } from '../utils';
import { isColumnInvalid } from '../utils';
import { IndexPatternPrivateState } from '../types';
import { DimensionEditor } from './dimension_editor';
import { DateRange } from '../../../common';
Expand Down Expand Up @@ -45,24 +45,22 @@ export const IndexPatternDimensionTriggerComponent = function IndexPatternDimens
) {
const layerId = props.layerId;
const layer = props.state.layers[layerId];
const selectedColumn: IndexPatternColumn | null = layer.columns[props.columnId] || null;
const currentIndexPattern = props.state.indexPatterns[layer.indexPatternId];
const { columnId, uniqueLabel } = props;

const selectedColumnSourceField =
selectedColumn && 'sourceField' in selectedColumn ? selectedColumn.sourceField : undefined;
const currentFieldIsInvalid = useMemo(
() =>
fieldIsInvalid(selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern),
[selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern]
const currentColumnHasErrors = useMemo(
() => isColumnInvalid(layer, columnId, currentIndexPattern),
[layer, columnId, currentIndexPattern]
);

const { columnId, uniqueLabel } = props;
const selectedColumn: IndexPatternColumn | null = layer.columns[props.columnId] || null;

if (!selectedColumn) {
return null;
}
const formattedLabel = wrapOnDot(uniqueLabel);

if (currentFieldIsInvalid) {
if (currentColumnHasErrors) {
return (
<EuiToolTip
content={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import { OperationMetadata } from '../../types';
import { IndexPatternColumn } from '../operations';
import { getFieldByNameFactory } from '../pure_helpers';

jest.mock('../operations');

const fields = [
{
name: 'timestamp',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ describe('IndexPattern Data Source', () => {
expect(messages).toHaveLength(1);
expect(messages![0]).toEqual({
shortMessage: 'Invalid reference.',
longMessage: 'Field "bytes" has an invalid reference.',
longMessage: '"Foo" has an invalid reference.',
});
});

Expand All @@ -844,7 +844,7 @@ describe('IndexPattern Data Source', () => {
col2: {
dataType: 'number',
isBucketed: false,
label: 'Foo',
label: 'Foo2',
operationType: 'count', // <= invalid
sourceField: 'memory',
},
Expand All @@ -857,7 +857,7 @@ describe('IndexPattern Data Source', () => {
expect(messages).toHaveLength(1);
expect(messages![0]).toEqual({
shortMessage: 'Invalid references.',
longMessage: 'Fields "bytes", "memory" have invalid reference.',
longMessage: '"Foo", "Foo2" have invalid reference.',
});
});

Expand All @@ -882,7 +882,7 @@ describe('IndexPattern Data Source', () => {
col2: {
dataType: 'number',
isBucketed: false,
label: 'Foo',
label: 'Foo2',
operationType: 'count', // <= invalid
sourceField: 'memory',
},
Expand All @@ -909,11 +909,11 @@ describe('IndexPattern Data Source', () => {
expect(messages).toEqual([
{
shortMessage: 'Invalid references on Layer 1.',
longMessage: 'Layer 1 has invalid references in fields "bytes", "memory".',
longMessage: 'Layer 1 has invalid references in "Foo", "Foo2".',
},
{
shortMessage: 'Invalid reference on Layer 2.',
longMessage: 'Layer 2 has an invalid reference in field "source".',
longMessage: 'Layer 2 has an invalid reference in "Foo".',
},
]);
});
Expand Down
Loading

0 comments on commit f9e7cb0

Please sign in to comment.