mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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:
parent
827219b82a
commit
a1c520c49d
31 changed files with 354 additions and 354 deletions
|
@ -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 n’existe pour le modèle d'index \"{index}\"",
|
||||
|
|
|
@ -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}''のデータビューが存在しません",
|
||||
|
|
|
@ -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": "配置作业规则",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
exports[`AnnotationDescriptionList Initialization with annotation. 1`] = `
|
||||
<EuiDescriptionList
|
||||
className="ml-annotation-description-list"
|
||||
columnWidths={
|
||||
Array [
|
||||
3,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
@import 'entity_cell';
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
.mlHelpPopover__panel {
|
||||
max-width: $euiSize * 30;
|
||||
}
|
||||
|
||||
.mlHelpPopover__content {
|
||||
max-height: 40vh;
|
||||
padding: $euiSizeS;
|
||||
@include euiYScrollWithShadows;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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};
|
||||
`,
|
||||
};
|
||||
};
|
|
@ -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({
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
@import 'rule_editor';
|
||||
|
||||
@import 'components/detector_description_list/index';
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
// SASSTODO: Dangerous EUI overwrites
|
||||
.rule-detector-description-list {
|
||||
row-gap: $euiSizeXS;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import 'detector_description_list';
|
|
@ -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,
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,2 +1 @@
|
|||
@import 'explorer_chart_label';
|
||||
@import 'explorer_chart_label_badge';
|
||||
@import 'explorer_chart_label';
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue