[ML] Migrate anomaly explorer components from SCSS to Emotion (#212793)

## Summary

Part of: https://github.com/elastic/kibana/issues/140695
Migrates SCSS to emotion for several of the components used across the
Anomaly Explorer and Single Metric Viewer.

Removes the following SCSS files:

```
- x-pack/platform/plugins/shared/m/public/application/components/annotations/annotation_description_list/_index.scss
- x-pack/platform/plugins/shared/ml/public/application/components/entity_cell/_index.scss
- x-pack/platform/plugins/shared/ml/public/application/components/entity_cell/entity_cell.scss
- x-pack/platform/plugins/shared/ml/public/application/components/help_popover/help_popover.scss
- x-pack/platform/plugins/shared/ml/public/application/components/detector_description_list/_detector_description_list.scss
- x-pack/platform/plugins/shared/ml/public/application/components/rule_editor/components/detector_description_list/_index.scss
- x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/_explorer_chart_label_badge.scss
- x-pack/platform/plugins/shared/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/_entity_filter.scss
```

Components edited:
- Help popover

<img width="759" alt="Screenshot 2025-02-28 at 16 28 27"
src="https://github.com/user-attachments/assets/bc182d45-465b-406c-b122-921576ae8304"
/>

- Annotation description list:

<img width="952" alt="Screenshot 2025-02-28 at 16 29 16"
src="https://github.com/user-attachments/assets/71cae50c-41be-4299-8362-92567b73188e"
/>


- Rule editor detector description list:

<img width="674" alt="Screenshot 2025-02-28 at 16 30 02"
src="https://github.com/user-attachments/assets/f94a6878-3063-488f-85c5-2e6ee77bf1ad"
/>

- Anomalies table entity filters:

<img width="506" alt="Screenshot 2025-02-28 at 16 30 53"
src="https://github.com/user-attachments/assets/f074f175-9310-4a70-97b6-09be44af2ad2"
/>

- Explorer chart label badge and entity filters:

<img width="362" alt="Screenshot 2025-02-28 at 16 31 37"
src="https://github.com/user-attachments/assets/dd46ab81-e115-4e8c-ae00-864c83127c16"
/>

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Pete Harverson 2025-03-05 17:27:03 +00:00 committed by GitHub
parent 827219b82a
commit a1c520c49d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 354 additions and 354 deletions

View file

@ -25870,10 +25870,8 @@
"xpack.ml.anomaliesTable.hideDetailsAriaLabel": "Masquer les détails",
"xpack.ml.anomaliesTable.influencersCell.addFilterAriaLabel": "Ajouter un filtre",
"xpack.ml.anomaliesTable.influencersCell.addFilterTooltip": "Ajouter un filtre",
"xpack.ml.anomaliesTable.influencersCell.moreInfluencersLinkText": "et {othersCount} de plus",
"xpack.ml.anomaliesTable.influencersCell.removeFilterAriaLabel": "Supprimer le filtre",
"xpack.ml.anomaliesTable.influencersCell.removeFilterTooltip": "Supprimer le filtre",
"xpack.ml.anomaliesTable.influencersCell.showLessInfluencersLinkText": "afficher moins",
"xpack.ml.anomaliesTable.influencersColumnName": "Influencé par",
"xpack.ml.anomaliesTable.jobIdColumnName": "ID tâche",
"xpack.ml.anomaliesTable.linksMenu.autoGeneratedDiscoverLinkErrorMessage": "Liaison avec Discover impossible ; aucune vue de données nexiste pour le modèle d'index \"{index}\"",

View file

@ -25736,10 +25736,8 @@
"xpack.ml.anomaliesTable.hideDetailsAriaLabel": "詳細を非表示",
"xpack.ml.anomaliesTable.influencersCell.addFilterAriaLabel": "フィルターを追加します",
"xpack.ml.anomaliesTable.influencersCell.addFilterTooltip": "フィルターを追加します",
"xpack.ml.anomaliesTable.influencersCell.moreInfluencersLinkText": "他 {othersCount} 件",
"xpack.ml.anomaliesTable.influencersCell.removeFilterAriaLabel": "フィルターを削除",
"xpack.ml.anomaliesTable.influencersCell.removeFilterTooltip": "フィルターを削除",
"xpack.ml.anomaliesTable.influencersCell.showLessInfluencersLinkText": "縮小表示",
"xpack.ml.anomaliesTable.influencersColumnName": "影響因子:",
"xpack.ml.anomaliesTable.jobIdColumnName": "ジョブID",
"xpack.ml.anomaliesTable.linksMenu.autoGeneratedDiscoverLinkErrorMessage": "Discoverにリンクできません。インデックスパターン''{index}''のデータビューが存在しません",

View file

@ -25300,10 +25300,8 @@
"xpack.ml.anomaliesTable.hideDetailsAriaLabel": "隐藏详情",
"xpack.ml.anomaliesTable.influencersCell.addFilterAriaLabel": "添加筛选",
"xpack.ml.anomaliesTable.influencersCell.addFilterTooltip": "添加筛选",
"xpack.ml.anomaliesTable.influencersCell.moreInfluencersLinkText": "及另外 {othersCount} 个",
"xpack.ml.anomaliesTable.influencersCell.removeFilterAriaLabel": "移除筛选",
"xpack.ml.anomaliesTable.influencersCell.removeFilterTooltip": "移除筛选",
"xpack.ml.anomaliesTable.influencersCell.showLessInfluencersLinkText": "显示更少",
"xpack.ml.anomaliesTable.influencersColumnName": "影响因素",
"xpack.ml.anomaliesTable.jobIdColumnName": "作业 ID",
"xpack.ml.anomaliesTable.linksMenu.configureRulesLabel": "配置作业规则",

View file

@ -5,9 +5,7 @@
@import 'explorer/index'; // SASSTODO: This file needs to be rewritten
// Components
@import 'components/annotations/annotation_description_list/index'; // SASSTODO: This file overwrites EUI directly
@import 'components/anomalies_table/index'; // SASSTODO: This file overwrites EUI directly
@import 'components/entity_cell/index';
@import 'components/job_selector/index';
@import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly

View file

@ -2,7 +2,6 @@
exports[`AnnotationDescriptionList Initialization with annotation. 1`] = `
<EuiDescriptionList
className="ml-annotation-description-list"
columnWidths={
Array [
3,

View file

@ -1,6 +0,0 @@
// SASSTODO: This is based on the overwrites used in the Filters flyout to match the existing style.
// SASSTODO: Dangerous EUI overwrites
.ml-annotation-description-list {
row-gap: $euiSizeXS;
}

View file

@ -114,7 +114,6 @@ export const AnnotationDescriptionList = ({ annotation, detectorDescription }: P
return (
<EuiDescriptionList
data-test-subj={'mlAnnotationDescriptionList'}
className="ml-annotation-description-list"
type="column"
columnWidths={[3, 7]}
listItems={listItems}

View file

@ -1,174 +0,0 @@
/*
* 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 { each } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { EuiLink, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { blurButtonOnClick } from '../../util/component_utils';
/*
* Component for rendering a list of record influencers inside a cell in the anomalies table.
* Truncates long lists of influencers to the supplied limit, with the full list of influencers
* expanded or collapsed via 'and x more' / 'show less' links.
*/
export class InfluencersCell extends Component {
constructor(props) {
super(props);
this.state = {
showAll: false,
};
}
toggleAllInfluencers() {
this.setState({ showAll: !this.state.showAll });
}
renderInfluencers(influencers) {
const numberToDisplay = this.state.showAll === false ? this.props.limit : influencers.length;
const displayInfluencers = influencers.slice(0, numberToDisplay);
const { influencerFilter } = this.props;
let othersCount = Math.max(influencers.length - numberToDisplay, 0);
if (othersCount === 1) {
// Display the additional influencer.
displayInfluencers.push(influencers[this.props.limit]);
othersCount = 0;
}
const displayRows = displayInfluencers.map((influencer, index) => (
<div key={index}>
{influencer.influencerFieldName}: {influencer.influencerFieldValue}
{influencerFilter !== undefined && (
<React.Fragment>
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.influencersCell.addFilterTooltip"
defaultMessage="Add filter"
/>
}
>
<EuiButtonIcon
size="s"
className="filter-button"
onClick={blurButtonOnClick(() => {
influencerFilter(
influencer.influencerFieldName,
influencer.influencerFieldValue,
'+'
);
})}
iconType="plusInCircle"
aria-label={i18n.translate(
'xpack.ml.anomaliesTable.influencersCell.addFilterAriaLabel',
{
defaultMessage: 'Add filter',
}
)}
/>
</EuiToolTip>
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.influencersCell.removeFilterTooltip"
defaultMessage="Remove filter"
/>
}
>
<EuiButtonIcon
size="s"
className="filter-button"
onClick={blurButtonOnClick(() => {
influencerFilter(
influencer.influencerFieldName,
influencer.influencerFieldValue,
'-'
);
})}
iconType="minusInCircle"
aria-label={i18n.translate(
'xpack.ml.anomaliesTable.influencersCell.removeFilterAriaLabel',
{
defaultMessage: 'Remove filter',
}
)}
/>
</EuiToolTip>
</React.Fragment>
)}
</div>
));
return (
<React.Fragment>
{displayRows}
{this.renderOthers(influencers.length, othersCount)}
</React.Fragment>
);
}
renderOthers(totalCount, othersCount) {
if (othersCount > 0) {
return (
<div>
<EuiLink onClick={() => this.toggleAllInfluencers()}>
<FormattedMessage
id="xpack.ml.anomaliesTable.influencersCell.moreInfluencersLinkText"
defaultMessage="and {othersCount} more"
values={{
othersCount,
}}
/>
</EuiLink>
</div>
);
} else if (totalCount > this.props.limit + 1) {
return (
<div>
<EuiLink onClick={() => this.toggleAllInfluencers()}>
<FormattedMessage
id="xpack.ml.anomaliesTable.influencersCell.showLessInfluencersLinkText"
defaultMessage="show less"
/>
</EuiLink>
</div>
);
}
}
render() {
const recordInfluencers = this.props.influencers || [];
const influencers = [];
recordInfluencers.forEach((influencer) => {
each(influencer, (influencerFieldValue, influencerFieldName) => {
influencers.push({
influencerFieldName,
influencerFieldValue,
});
});
});
return (
<div>
{this.renderInfluencers(influencers)}
{this.renderOthers(influencers)}
</div>
);
}
}
InfluencersCell.propTypes = {
influencerFilter: PropTypes.func,
influencers: PropTypes.array,
limit: PropTypes.number,
};

View file

@ -0,0 +1,157 @@
/*
* 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 type { FC } from 'react';
import React, { useState } from 'react';
import { EuiLink, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { useEntityCellStyles } from '../entity_cell/entity_cell_styles';
import { blurButtonOnClick } from '../../util/component_utils';
type InfluencerCellFilter = (
influencerFieldName: string,
influencerFieldValue: string,
direction: '+' | '-'
) => void;
interface Influencer {
influencerFieldName: string;
influencerFieldValue: string;
}
interface InfluencerCellProps {
influencerFilter: InfluencerCellFilter | undefined;
influencers: Influencer[] | undefined;
limit: number;
}
/*
* Component for rendering a list of record influencers inside a cell in the anomalies table.
* Truncates long lists of influencers to the supplied limit, with the full list of influencers
* expanded or collapsed via 'and x more' / 'show less' links.
*/
export const InfluencersCell: FC<InfluencerCellProps> = ({
influencers = [],
influencerFilter,
limit,
}) => {
const { filterButton } = useEntityCellStyles();
const [showAllInfluencers, setShowAllInfluencers] = useState(false);
const toggleAllInfluencers = () => setShowAllInfluencers((prev) => !prev);
let numberToDisplay = showAllInfluencers === false ? limit : influencers.length;
let othersCount = 0;
if (influencers !== undefined) {
othersCount = Math.max(influencers.length - numberToDisplay, 0);
}
if (othersCount === 1) {
// Display the additional influencer.
numberToDisplay++;
othersCount = 0;
}
const displayInfluencers = influencers
.reduce<Influencer[]>((acc, influencer) => {
const [influencerFieldName, influencerFieldValue] = Object.entries(influencer)[0] ?? [];
if (typeof influencerFieldName === 'string' && typeof influencerFieldValue === 'string') {
acc.push({
influencerFieldName,
influencerFieldValue,
});
}
return acc;
}, [])
.slice(0, numberToDisplay);
return (
<div>
{displayInfluencers.map((influencer, index) => (
<div key={index}>
{influencer.influencerFieldName}: {influencer.influencerFieldValue}
{influencerFilter !== undefined && (
<>
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.influencersCell.addFilterTooltip"
defaultMessage="Add filter"
/>
}
>
<EuiButtonIcon
size="s"
css={filterButton}
onClick={blurButtonOnClick(() => {
influencerFilter(
influencer.influencerFieldName,
influencer.influencerFieldValue,
'+'
);
})}
iconType="plusInCircle"
aria-label={i18n.translate(
'xpack.ml.anomaliesTable.influencersCell.addFilterAriaLabel',
{
defaultMessage: 'Add filter',
}
)}
/>
</EuiToolTip>
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.influencersCell.removeFilterTooltip"
defaultMessage="Remove filter"
/>
}
>
<EuiButtonIcon
size="s"
css={filterButton}
onClick={blurButtonOnClick(() => {
influencerFilter(
influencer.influencerFieldName,
influencer.influencerFieldValue,
'-'
);
})}
iconType="minusInCircle"
aria-label={i18n.translate(
'xpack.ml.anomaliesTable.influencersCell.removeFilterAriaLabel',
{
defaultMessage: 'Remove filter',
}
)}
/>
</EuiToolTip>
</>
)}
</div>
))}
{othersCount > 0 && (
<EuiLink onClick={() => toggleAllInfluencers()}>
<FormattedMessage
id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionListMoreLinkText"
defaultMessage="and {othersCount} more"
values={{ othersCount }}
/>
</EuiLink>
)}
{numberToDisplay > limit + 1 && (
<EuiLink onClick={() => toggleAllInfluencers()}>
<FormattedMessage
id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionShowLessLinkText"
defaultMessage="Show less"
/>
</EuiLink>
)}
</div>
);
};

View file

@ -1,26 +0,0 @@
.field-value-short {
@include euiTextTruncate;
display: inline-block;
vertical-align: bottom;
}
.field-value-long {
overflow-wrap: break-word;
}
.filter-button {
opacity: .5;
width: $euiSize;
height: $euiSize;
-webkit-transform: translateY(-1px);
transform: translateY(-1px);
.euiIcon {
width: $euiFontSizeXS;
height: $euiFontSizeXS;
}
}
.filter-button:hover {
opacity: 1;
}

View file

@ -12,6 +12,7 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { ML_ENTITY_FIELD_OPERATIONS, MLCATEGORY } from '@kbn/ml-anomaly-utils';
import { useEntityCellStyles } from './entity_cell_styles';
import { EMPTY_FIELD_VALUE_LABEL } from '../../timeseriesexplorer/components/entity_control/entity_control';
import { blurButtonOnClick } from '../../util/component_utils';
@ -28,61 +29,69 @@ interface EntityCellProps {
wrapText?: boolean;
}
function getAddFilter({ entityName, entityValue, filter }: EntityCellProps) {
if (filter !== undefined) {
return (
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.entityCell.addFilterTooltip"
defaultMessage="Add filter"
/>
}
>
<EuiButtonIcon
size="s"
data-test-subj={`mlAnomaliesTableEntityCellAddFilterButton-${entityValue}`}
className="filter-button"
onClick={blurButtonOnClick(() => {
filter(entityName, entityValue, ML_ENTITY_FIELD_OPERATIONS.ADD);
})}
iconType="plusInCircle"
aria-label={i18n.translate('xpack.ml.anomaliesTable.entityCell.addFilterAriaLabel', {
defaultMessage: 'Add filter',
})}
/>
</EuiToolTip>
);
}
}
const AddFilter: FC<EntityCellProps> = ({ entityName, entityValue, filter }) => {
const { filterButton } = useEntityCellStyles();
function getRemoveFilter({ entityName, entityValue, filter }: EntityCellProps) {
if (filter !== undefined) {
return (
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.entityCell.removeFilterTooltip"
defaultMessage="Remove filter"
/>
}
>
<EuiButtonIcon
size="s"
data-test-subj={`mlAnomaliesTableEntityCellRemoveFilterButton-${entityValue}`}
className="filter-button"
onClick={blurButtonOnClick(() => {
filter(entityName, entityValue, ML_ENTITY_FIELD_OPERATIONS.REMOVE);
})}
iconType="minusInCircle"
aria-label={i18n.translate('xpack.ml.anomaliesTable.entityCell.removeFilterAriaLabel', {
defaultMessage: 'Remove filter',
})}
/>
</EuiToolTip>
);
if (filter === undefined) {
return null;
}
}
return (
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.entityCell.addFilterTooltip"
defaultMessage="Add filter"
/>
}
>
<EuiButtonIcon
size="s"
data-test-subj={`mlAnomaliesTableEntityCellAddFilterButton-${entityValue}`}
css={filterButton}
onClick={blurButtonOnClick(() => {
filter(entityName, entityValue, ML_ENTITY_FIELD_OPERATIONS.ADD);
})}
iconType="plusInCircle"
aria-label={i18n.translate('xpack.ml.anomaliesTable.entityCell.addFilterAriaLabel', {
defaultMessage: 'Add filter',
})}
/>
</EuiToolTip>
);
};
const RemoveFilter: FC<EntityCellProps> = ({ entityName, entityValue, filter }) => {
const { filterButton } = useEntityCellStyles();
if (filter === undefined) {
return null;
}
return (
<EuiToolTip
content={
<FormattedMessage
id="xpack.ml.anomaliesTable.entityCell.removeFilterTooltip"
defaultMessage="Remove filter"
/>
}
>
<EuiButtonIcon
size="s"
data-test-subj={`mlAnomaliesTableEntityCellRemoveFilterButton-${entityValue}`}
css={filterButton}
onClick={blurButtonOnClick(() => {
filter(entityName, entityValue, ML_ENTITY_FIELD_OPERATIONS.REMOVE);
})}
iconType="minusInCircle"
aria-label={i18n.translate('xpack.ml.anomaliesTable.entityCell.removeFilterAriaLabel', {
defaultMessage: 'Remove filter',
})}
/>
</EuiToolTip>
);
};
/*
* Component for rendering an entity, displaying the value
@ -100,20 +109,21 @@ export const EntityCell: FC<EntityCellProps> = ({
valueText = `${MLCATEGORY} ${valueText}`;
}
const { fieldValueShort, fieldValueLong } = useEntityCellStyles();
const textStyle = { maxWidth: '100%' };
const textWrapperClass = wrapText ? 'field-value-long' : 'field-value-short';
const textWrapperCss = wrapText ? fieldValueLong : fieldValueShort;
const shouldDisplayIcons =
filter !== undefined && entityName !== undefined && entityValue !== undefined;
if (wrapText === true) {
return (
<div>
<span className={textWrapperClass}>{valueText}</span>
<span css={textWrapperCss}>{valueText}</span>
{shouldDisplayIcons && (
<React.Fragment>
{getAddFilter({ entityName, entityValue, filter })}
{getRemoveFilter({ entityName, entityValue, filter })}
</React.Fragment>
<>
<AddFilter entityName={entityName} entityValue={entityValue} filter={filter} />
<RemoveFilter entityName={entityName} entityValue={entityValue} filter={filter} />
</>
)}
</div>
);
@ -121,7 +131,7 @@ export const EntityCell: FC<EntityCellProps> = ({
return (
<EuiFlexGroup direction="row" alignItems="center" gutterSize="none">
<EuiFlexItem grow={false} style={textStyle}>
<EuiText size="xs" className={textWrapperClass}>
<EuiText size="xs" css={textWrapperCss}>
{valueText}
</EuiText>
</EuiFlexItem>
@ -129,10 +139,10 @@ export const EntityCell: FC<EntityCellProps> = ({
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="row" alignItems="center" gutterSize="none">
<EuiFlexItem grow={false}>
{getAddFilter({ entityName, entityValue, filter })}
<AddFilter entityName={entityName} entityValue={entityValue} filter={filter} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
{getRemoveFilter({ entityName, entityValue, filter })}
<RemoveFilter entityName={entityName} entityValue={entityValue} filter={filter} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

View file

@ -0,0 +1,38 @@
/*
* 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 { css } from '@emotion/react';
import { useEuiTheme, euiTextTruncate } from '@elastic/eui';
export const useEntityCellStyles = () => {
const { euiTheme } = useEuiTheme();
return {
fieldValueShort: css`
${euiTextTruncate()};
display: inline-block;
vertical-align: bottom;
`,
fieldValueLong: css({
overflowWrap: 'break-word',
}),
filterButton: css({
opacity: 0.5,
width: euiTheme.size.base,
height: euiTheme.size.base,
transform: 'translateY(-1px)',
'&:hover': {
opacity: 1,
},
'.euiIcon': {
width: euiTheme.size.m,
height: euiTheme.size.m,
},
}),
};
};

View file

@ -1,9 +0,0 @@
.mlHelpPopover__panel {
max-width: $euiSize * 30;
}
.mlHelpPopover__content {
max-height: 40vh;
padding: $euiSizeS;
@include euiYScrollWithShadows;
}

View file

@ -10,12 +10,11 @@ import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import type { EuiLinkButtonProps, EuiPopoverProps } from '@elastic/eui';
import { EuiButtonIcon, EuiPopover, EuiPopoverTitle, EuiText } from '@elastic/eui';
import './help_popover.scss';
import { useHelpPopoverStyles } from './help_popover_styles';
export const HelpPopoverButton: FC<{ onClick: EuiLinkButtonProps['onClick'] }> = ({ onClick }) => {
return (
<EuiButtonIcon
className="mlHelpPopover__buttonIcon"
size="s"
iconType="help"
aria-label={i18n.translate('xpack.ml.helpPopover.ariaLabel', {
@ -37,21 +36,21 @@ export const HelpPopover: FC<PropsWithChildren<HelpPopoverProps>> = ({
title,
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const { helpPopoverPanel, helpPopoverContent } = useHelpPopoverStyles();
return (
<EuiPopover
anchorPosition={anchorPosition}
button={<HelpPopoverButton onClick={setIsPopoverOpen.bind(null, !isPopoverOpen)} />}
className="mlHelpPopover"
closePopover={setIsPopoverOpen.bind(null, false)}
isOpen={isPopoverOpen}
ownFocus
panelClassName="mlHelpPopover__panel"
panelProps={{ css: helpPopoverPanel }}
panelPaddingSize="none"
>
{title && <EuiPopoverTitle paddingSize="s">{title}</EuiPopoverTitle>}
<EuiText className="mlHelpPopover__content eui-scrollBar" size="s" tabIndex={0}>
<EuiText css={helpPopoverContent} size="s" tabIndex={0}>
{children}
</EuiText>
</EuiPopover>

View file

@ -0,0 +1,25 @@
/*
* 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 { css } from '@emotion/react';
import { useEuiTheme, useEuiOverflowScroll } from '@elastic/eui';
export const useHelpPopoverStyles = () => {
const { euiTheme } = useEuiTheme();
return {
helpPopoverPanel: css({
maxWidth: `${euiTheme.base * 30}px`,
}),
helpPopoverContent: css`
${useEuiOverflowScroll('y', true)};
max-height: 40vh;
padding: ${euiTheme.size.s};
`,
};
};

View file

@ -14,7 +14,7 @@ import { ML_SEVERITY_COLORS } from '@kbn/ml-anomaly-utils';
export const useInfluencersListStyles = () => {
const { euiTheme } = useEuiTheme();
const euiFontSizeXS = useEuiFontSize('xs').fontSize;
const euiFontSizeS = useEuiFontSize('s').fontSize;
const euiFontSizeM = useEuiFontSize('m').fontSize;
return {
influencersList: css({
@ -23,7 +23,7 @@ export const useInfluencersListStyles = () => {
fieldLabel: css({
fontSize: euiFontSizeXS,
textAlign: 'left',
maxHeight: euiFontSizeS,
maxHeight: euiFontSizeM,
maxWidth: 'calc(100% - 102px)',
}),
progress: css({

View file

@ -1,3 +1 @@
@import 'rule_editor';
@import 'components/detector_description_list/index';

View file

@ -2,10 +2,6 @@
.ml-rule-editor-flyout {
font-size: $euiFontSizeS;
.select-rule-action .rule-detector-description-list {
padding-left: $euiSize;
}
.select-rule-action-panel {
padding: $euiSizeS 0;

View file

@ -2,7 +2,6 @@
exports[`DetectorDescriptionList render for detector with anomaly values 1`] = `
<EuiDescriptionList
className="rule-detector-description-list"
columnWidths={
Array [
15,
@ -49,7 +48,6 @@ exports[`DetectorDescriptionList render for detector with anomaly values 1`] = `
exports[`DetectorDescriptionList render for population detector with no anomaly values 1`] = `
<EuiDescriptionList
className="rule-detector-description-list"
columnWidths={
Array [
15,

View file

@ -1,4 +0,0 @@
// SASSTODO: Dangerous EUI overwrites
.rule-detector-description-list {
row-gap: $euiSizeXS;
}

View file

@ -65,14 +65,7 @@ export function DetectorDescriptionList({ job, detector, anomaly }) {
});
}
return (
<EuiDescriptionList
className="rule-detector-description-list"
type="column"
columnWidths={[15, 85]}
listItems={listItems}
/>
);
return <EuiDescriptionList type="column" columnWidths={[15, 85]} listItems={listItems} />;
}
DetectorDescriptionList.propTypes = {
job: PropTypes.object.isRequired,

View file

@ -50,7 +50,7 @@ export function SelectRuleAction({
}
return (
<div className="select-rule-action">
<div>
{rules.length > 0 && (
<React.Fragment>
<DetectorDescriptionList job={job} detector={detector} anomaly={anomaly} />

View file

@ -1,12 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ExplorerChartLabelBadge Render entity label badge. 1`] = `
<span
className="ml-explorer-chart-label-badge"
>
<span>
<EuiBadge
className="ml-reset-font-weight"
color="hollow"
css={
Object {
"map": undefined,
"name": "1l7mgse",
"next": undefined,
"styles": "font-weight:normal;",
"toString": [Function],
}
}
>
nginx.access.remote_ip

View file

@ -1,9 +0,0 @@
/*
Resets the badge's default strong font-weight so it's possible
to put custom emphasis inside the badge only on a part of it.
Used in the Explorer Chart label badge to display an entity's
field_name as `normal` and field_value as `strong`.
*/
.ml-reset-font-weight {
font-weight: normal;
}

View file

@ -1,2 +1 @@
@import 'explorer_chart_label';
@import 'explorer_chart_label_badge';
@import 'explorer_chart_label';

View file

@ -1,17 +0,0 @@
.filter-button {
opacity: .5;
width: $euiSize;
height: $euiSize;
-webkit-transform: translateY(-1px);
transform: translateY(-1px);
.euiIcon {
width: $euiFontSizeXS;
height: $euiFontSizeXS;
}
}
.filter-button:hover {
opacity: 1;
}

View file

@ -11,8 +11,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { MlEntityFieldOperation } from '@kbn/ml-anomaly-utils';
import { ML_ENTITY_FIELD_OPERATIONS } from '@kbn/ml-anomaly-utils';
import { useEntityFilterStyles } from './entity_filter_styles';
import { blurButtonOnClick } from '../../../../../util/component_utils';
import './_entity_filter.scss';
interface EntityFilterProps {
onFilter: (params: {
@ -30,6 +30,8 @@ export const EntityFilter: FC<EntityFilterProps> = ({
influencerFieldValue,
isEmbeddable,
}) => {
const { filterButton } = useEntityFilterStyles();
return (
<React.Fragment>
<EuiToolTip
@ -42,7 +44,7 @@ export const EntityFilter: FC<EntityFilterProps> = ({
>
<EuiButtonIcon
size="s"
className="filter-button"
css={filterButton}
onClick={blurButtonOnClick(() => {
onFilter({
influencerFieldName,
@ -74,7 +76,7 @@ export const EntityFilter: FC<EntityFilterProps> = ({
>
<EuiButtonIcon
size="s"
className="filter-button"
css={filterButton}
onClick={blurButtonOnClick(() => {
onFilter({
influencerFieldName,

View file

@ -0,0 +1,30 @@
/*
* 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 { css } from '@emotion/react';
import { useEuiTheme } from '@elastic/eui';
export const useEntityFilterStyles = () => {
const { euiTheme } = useEuiTheme();
return {
filterButton: css({
opacity: 0.5,
width: euiTheme.size.base,
height: euiTheme.size.base,
transform: 'translateY(-1px)',
'&:hover': {
opacity: 1,
},
'.euiIcon': {
width: euiTheme.size.m,
height: euiTheme.size.m,
},
}),
};
};

View file

@ -9,11 +9,17 @@ import PropTypes from 'prop-types';
import React from 'react';
import { EuiBadge } from '@elastic/eui';
import { css } from '@emotion/react';
export function ExplorerChartLabelBadge({ entity }) {
// Resets the badge's default strong font-weight so it's possible
// to put custom emphasis inside the badge only on a part of it.
// The entity's field_name will be styled as `normal` and field_value as `strong`.
const resetFontWeightCss = css({ fontWeight: 'normal' });
return (
<span className="ml-explorer-chart-label-badge">
<EuiBadge color="hollow" className="ml-reset-font-weight">
<span>
<EuiBadge color="hollow" css={resetFontWeightCss}>
{entity.fieldName} <strong>{entity.fieldValue}</strong>
</EuiBadge>
</span>