[ML] Add the job message tab to data frame analytics (#50468)

* [ML] get analytics job messages from ml-notifications

* [ML] remove commented import

* [ML] add job_type filter for anomaly_detector

* [ML] job messages component

* [ML] update styles

* [ML] update job messages container for anomaly detection with async await

* [ML] fix passing a prop

* [ML] fix types and error message

* [ML] fix i18n

* [ML] fix text alignment

* [ML] remove duplicated copyright comment
This commit is contained in:
Dima Arnautov 2019-11-15 12:56:24 +01:00 committed by GitHub
parent 1a8bb3a223
commit 0c42aa8a58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 176 additions and 234 deletions

View file

@ -10,4 +10,3 @@ export const ML_ANNOTATIONS_INDEX_PATTERN = '.ml-annotations-6';
export const ML_RESULTS_INDEX_PATTERN = '.ml-anomalies-*';
export const ML_NOTIFICATION_INDEX_PATTERN = '.ml-notifications*';
export const ML_DF_NOTIFICATION_INDEX_PATTERN = '.data-frame-notifications-1';

View file

@ -12,10 +12,6 @@ export interface AuditMessageBase {
text?: string;
}
export interface AnalyticsMessage extends AuditMessageBase {
analytics_id: string;
}
export interface JobMessage extends AuditMessageBase {
job_id: string;
}

View file

@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { JobIcon } from './job_message_icon';

View file

@ -11,7 +11,7 @@ import { AuditMessageBase } from '../../../common/types/audit_message';
interface Props {
message: AuditMessageBase;
showTooltip: boolean;
showTooltip?: boolean;
}
const [INFO, WARNING, ERROR] = ['info', 'warning', 'error'];

View file

@ -0,0 +1,7 @@
/*
* 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.
*/
export { JobMessages } from './job_messages';

View file

@ -0,0 +1,73 @@
/*
* 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 React, { FC } from 'react';
import { EuiSpacer, EuiBasicTable } from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
import { i18n } from '@kbn/i18n';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { JobMessage } from '../../../common/types/audit_message';
import { JobIcon } from '../job_message_icon';
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
interface JobMessagesProps {
messages: JobMessage[];
loading: boolean;
error: string;
}
/**
* Component for rendering job messages for anomaly detection
* and data frame analytics jobs.
*/
export const JobMessages: FC<JobMessagesProps> = ({ messages, loading, error }) => {
const columns = [
{
name: '',
render: (message: JobMessage) => <JobIcon message={message} />,
width: `${theme.euiSizeL}`,
},
{
name: i18n.translate('xpack.ml.jobMessages.timeLabel', {
defaultMessage: 'Time',
}),
render: (message: any) => formatDate(message.timestamp, TIME_FORMAT),
width: '120px',
},
{
field: 'node_name',
name: i18n.translate('xpack.ml.jobMessages.nodeLabel', {
defaultMessage: 'Node',
}),
width: '150px',
},
{
field: 'message',
name: i18n.translate('xpack.ml.jobMessages.messageLabel', {
defaultMessage: 'Message',
}),
width: '50%',
},
];
return (
<>
<EuiSpacer size="s" />
<EuiBasicTable
className="job-messages-table"
items={messages}
columns={columns}
compressed={true}
loading={loading}
error={error}
/>
</>
);
};

View file

@ -27,7 +27,7 @@ import {
} from '../../../../common';
import { isCompletedAnalyticsJob } from './common';
import { isRegressionAnalysis } from '../../../../common/analytics';
// import { ExpandedRowMessagesPane } from './expanded_row_messages_pane';
import { ExpandedRowMessagesPane } from './expanded_row_messages_pane';
function getItemDescription(value: any) {
if (typeof value === 'object') {
@ -235,19 +235,16 @@ export const ExpandedRow: FC<Props> = ({ item }) => {
name: 'JSON',
content: <ExpandedRowJsonPane json={item.config} />,
},
// Audit messages are not yet supported by the analytics API.
/*
{
id: 'ml-analytics-job-messages',
name: i18n.translate(
'xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsMessagesLabel',
{
defaultMessage: 'Messages',
defaultMessage: 'Job messages',
}
),
content: <ExpandedRowMessagesPane analyticsId={item.id} />,
},
*/
];
// Using `expand=false` here so the tabs themselves don't spread

View file

@ -4,27 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment, FC, useState } from 'react';
import { EuiSpacer, EuiBasicTable } from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
import React, { FC, useState } from 'react';
import { i18n } from '@kbn/i18n';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { ml } from '../../../../../services/ml_api_service';
// @ts-ignore
import { JobIcon } from '../../../../../components/job_message_icon';
import { AnalyticsMessage } from '../../../../../../common/types/audit_message';
import { useRefreshAnalyticsList } from '../../../../common';
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
import { JobMessages } from '../../../../../components/job_messages';
import { JobMessage } from '../../../../../../common/types/audit_message';
interface Props {
analyticsId: string;
}
export const ExpandedRowMessagesPane: FC<Props> = ({ analyticsId }) => {
const [messages, setMessages] = useState([]);
const [messages, setMessages] = useState<JobMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
@ -63,43 +55,5 @@ export const ExpandedRowMessagesPane: FC<Props> = ({ analyticsId }) => {
useRefreshAnalyticsList({ onRefresh: getMessagesFactory() });
const columns = [
{
name: '',
render: (message: AnalyticsMessage) => <JobIcon message={message} />,
width: `${theme.euiSizeXL}px`,
},
{
name: i18n.translate('xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.timeLabel', {
defaultMessage: 'Time',
}),
render: (message: any) => formatDate(message.timestamp, TIME_FORMAT),
},
{
field: 'node_name',
name: i18n.translate('xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.nodeLabel', {
defaultMessage: 'Node',
}),
},
{
field: 'message',
name: i18n.translate('xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.messageLabel', {
defaultMessage: 'Message',
}),
width: '50%',
},
];
return (
<Fragment>
<EuiSpacer size="s" />
<EuiBasicTable
items={messages}
columns={columns}
compressed={true}
loading={isLoading}
error={errorMessage}
/>
</Fragment>
);
return <JobMessages messages={messages} loading={isLoading} error={errorMessage} />;
};

View file

@ -1,10 +1,9 @@
.tab-contents {
margin: -$euiSizeS;
padding: $euiSizeS;
background-color: $euiColorEmptyShade;
// SASSTODO: Need to remove bootstrap grid
// SASSTODO: Need to remove bootstrap grid
.col-md-6:nth-child(1) {
// SASSTODO: Why is this 7?
padding-right: 7px;
@ -65,40 +64,24 @@
}
}
// SASSTODO: This needs a proper calc
// SASSTODO: This needs a proper calc
.json-textarea {
height: 500px;
}
}
// SASSTODO: This needs to be rewritten. A lot of this should be done with the JS props
.job-messages-table {
max-height: 500px;
overflow: auto;
.job-messages-table {
max-height: 500px;
overflow: auto;
text-align: left;
.euiTable {
font-size: 12px;
th:nth-child(1) {
width: $euiSizeXL;
}
th:nth-child(2) {
width: 150px;
}
th:nth-child(3) {
width: 120px;
}
th:nth-child(4) {
width: auto;
}
tr:last-child {
td {
border-bottom: none;
}
}
.euiTableRowCell {
background-color: $euiColorEmptyShade;
}
tr:last-child {
td {
border-bottom: none;
}
}
.euiTableRowCell {
background-color: $euiColorEmptyShade;
}
}

View file

@ -118,7 +118,7 @@ class JobDetailsUI extends Component {
id: 'xpack.ml.jobsList.jobDetails.tabs.jobMessagesLabel',
defaultMessage: 'Job messages'
}),
content: <JobMessagesPane job={job} />,
content: <JobMessagesPane jobId={job.job_id} />,
},
];

View file

@ -1,89 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import PropTypes from 'prop-types';
import React, {
Component
} from 'react';
import {
EuiSpacer,
EuiBasicTable,
} from '@elastic/eui';
import { formatDate } from '@elastic/eui/lib/services/format';
import { ml } from 'plugins/ml/services/ml_api_service';
import { JobIcon } from '../../../../components/job_message_icon';
import { injectI18n } from '@kbn/i18n/react';
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
class JobMessagesPaneUI extends Component {
constructor(props) {
super(props);
this.state = {
messages: []
};
this.jobId = props.job.job_id;
}
componentDidMount() {
ml.jobs.jobAuditMessages(this.jobId)
.then((messages) => {
this.setState({ messages });
})
.catch((error) => {
console.log('Job messages could not be loaded', error);
});
}
render() {
const { messages } = this.state;
const { intl } = this.props;
const columns = [{
name: '',
render: item => (<JobIcon message={item} />)
}, {
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.messagesPane.timeLabel',
defaultMessage: 'Time'
}),
render: item => formatDate(item.timestamp, TIME_FORMAT)
}, {
field: 'node_name',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.messagesPane.nodeLabel',
defaultMessage: 'Node'
}),
}, {
field: 'message',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.messagesPane.messageLabel',
defaultMessage: 'Message'
}),
}
];
return (
<React.Fragment>
<EuiSpacer size="s" />
<div className="job-messages-table">
<EuiBasicTable
items={messages}
columns={columns}
/>
</div>
</React.Fragment>
);
}
}
JobMessagesPaneUI.propTypes = {
job: PropTypes.object.isRequired,
};
export const JobMessagesPane = injectI18n(JobMessagesPaneUI);

View file

@ -0,0 +1,40 @@
/*
* 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 React, { FC, useEffect, useState } from 'react';
import { ml } from '../../../../services/ml_api_service';
import { JobMessages } from '../../../../components/job_messages';
import { JobMessage } from '../../../../../common/types/audit_message';
interface JobMessagesPaneProps {
jobId: string;
}
export const JobMessagesPane: FC<JobMessagesPaneProps> = ({ jobId }) => {
const [messages, setMessages] = useState<JobMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const fetchMessages = async () => {
setIsLoading(true);
try {
setMessages(await ml.jobs.jobAuditMessages(jobId));
setIsLoading(false);
} catch (e) {
setIsLoading(false);
setErrorMessage(e);
// eslint-disable-next-line no-console
console.error('Job messages could not be loaded', e);
}
};
useEffect(() => {
fetchMessages();
}, []);
return <JobMessages messages={messages} loading={isLoading} error={errorMessage} />;
};

View file

@ -4,7 +4,7 @@
padding: $euiSizeS;
background-color: $euiColorEmptyShade;
// SASSTODO: Need to remove bootstrap grid
// SASSTODO: Need to remove bootstrap grid
.col-md-6:nth-child(1) {
// SASSTODO: Why is this 7?
padding-right: 7px;
@ -65,40 +65,24 @@
}
}
// SASSTODO: This needs a proper calc
// SASSTODO: This needs a proper calc
.json-textarea {
height: 500px;
}
}
// SASSTODO: This needs to be rewritten. A lot of this should be done with the JS props
.job-messages-table {
max-height: 500px;
overflow: auto;
.job-messages-table {
max-height: 500px;
overflow: auto;
text-align: left;
.euiTable {
font-size: 12px;
th:nth-child(1) {
width: $euiSizeXL;
}
th:nth-child(2) {
width: 150px;
}
th:nth-child(3) {
width: 120px;
}
th:nth-child(4) {
width: auto;
}
tr:last-child {
td {
border-bottom: none;
}
}
.euiTableRowCell {
background-color: $euiColorEmptyShade;
}
tr:last-child {
td {
border-bottom: none;
}
}
.euiTableRowCell {
background-color: $euiColorEmptyShade;
}
}

View file

@ -12,6 +12,7 @@ import { MlSummaryJobs } from '../../../common/types/jobs';
import { MlServerDefaults, MlServerLimits } from '../../services/ml_server_info';
import { ES_AGGREGATION } from '../../../common/constants/aggregation_types';
import { DataFrameAnalyticsStats } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { JobMessage } from '../../../common/types/audit_message';
// TODO This is not a complete representation of all methods of `ml.*`.
// It just satisfies needs for other parts of the code area which use
@ -117,7 +118,7 @@ declare interface Ml {
stopDatafeeds(datafeedIds: string[]): Promise<object>;
deleteJobs(jobIds: string[]): Promise<object>;
closeJobs(jobIds: string[]): Promise<object>;
jobAuditMessages(jobId: string, from: string): Promise<object>;
jobAuditMessages(jobId: string, from?: string): Promise<JobMessage[]>;
deletingJobTasks(): Promise<object>;
newJobCaps(indexPatternTitle: string, isRollup: boolean): Promise<object>;
newJobLineChart(

View file

@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ML_DF_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { callWithRequestType } from '../../../common/types/kibana';
import { AnalyticsMessage } from '../../../common/types/audit_message';
import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { JobMessage } from '../../../common/types/audit_message';
const SIZE = 50;
@ -15,7 +15,7 @@ interface Message {
_type: string;
_id: string;
_score: null | number;
_source: AnalyticsMessage;
_source: JobMessage;
sort?: any;
}
@ -37,6 +37,11 @@ export function analyticsAuditMessagesProvider(callWithRequest: callWithRequestT
level: 'activity',
},
},
must: {
term: {
job_type: 'data_frame_analytics',
},
},
},
},
],
@ -50,12 +55,12 @@ export function analyticsAuditMessagesProvider(callWithRequest: callWithRequestT
should: [
{
term: {
analytics_id: '', // catch system messages
job_id: '', // catch system messages
},
},
{
term: {
analytics_id: analyticsId, // messages for specified analyticsId
job_id: analyticsId, // messages for specified analyticsId
},
},
],
@ -65,12 +70,12 @@ export function analyticsAuditMessagesProvider(callWithRequest: callWithRequestT
try {
const resp = await callWithRequest('search', {
index: ML_DF_NOTIFICATION_INDEX_PATTERN,
index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true,
rest_total_hits_as_int: true,
size: SIZE,
body: {
sort: [{ timestamp: { order: 'desc' } }, { analytics_id: { order: 'asc' } }],
sort: [{ timestamp: { order: 'desc' } }, { job_id: { order: 'asc' } }],
query,
},
});

View file

@ -48,6 +48,11 @@ export function jobAuditMessagesProvider(callWithRequest) {
term: {
level: 'activity'
}
},
must: {
term: {
job_type: 'anomaly_detector'
}
}
}
},

View file

@ -6130,9 +6130,6 @@
"xpack.ml.jobsList.jobDetails.forecastsTable.viewLabel": "表示",
"xpack.ml.jobsList.jobDetails.generalTitle": "一般",
"xpack.ml.jobsList.jobDetails.influencersTitle": "影響",
"xpack.ml.jobsList.jobDetails.messagesPane.messageLabel": "メッセージ",
"xpack.ml.jobsList.jobDetails.messagesPane.nodeLabel": "ノード",
"xpack.ml.jobsList.jobDetails.messagesPane.timeLabel": "時間",
"xpack.ml.jobsList.jobDetails.modelSizeStatsTitle": "モデルサイズ統計",
"xpack.ml.jobsList.jobDetails.nodeTitle": "ノード",
"xpack.ml.jobsList.jobDetails.noPermissionToViewDatafeedPreviewTitle": "データフィードのプレビューを表示するパーミッションがありません",
@ -6610,9 +6607,6 @@
"xpack.ml.datavisualizer.searchPanel.sampleLabel": "サンプル",
"xpack.ml.datavisualizer.searchPanel.sampleSizeAriaLabel": "サンプリングするドキュメント数を選択してください",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.errorMessage": "メッセージを読み込めませんでした",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.messageLabel": "メッセージ",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.nodeLabel": "ノード",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.timeLabel": "時間",
"xpack.ml.fieldDataCard.cardBoolean.documentsCountDescription": "{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)",
"xpack.ml.fieldDataCard.cardDate.documentsCountDescription": "{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)",
"xpack.ml.fieldDataCard.cardDate.earliestDescription": "最も古い {earliestFormatted}",

View file

@ -6131,9 +6131,6 @@
"xpack.ml.jobsList.jobDetails.forecastsTable.viewLabel": "查看",
"xpack.ml.jobsList.jobDetails.generalTitle": "常规",
"xpack.ml.jobsList.jobDetails.influencersTitle": "影响因素",
"xpack.ml.jobsList.jobDetails.messagesPane.messageLabel": "消息",
"xpack.ml.jobsList.jobDetails.messagesPane.nodeLabel": "节点",
"xpack.ml.jobsList.jobDetails.messagesPane.timeLabel": "时间",
"xpack.ml.jobsList.jobDetails.modelSizeStatsTitle": "模型大小统计",
"xpack.ml.jobsList.jobDetails.nodeTitle": "节点",
"xpack.ml.jobsList.jobDetails.noPermissionToViewDatafeedPreviewTitle": "您无权查看数据馈送预览",
@ -6703,9 +6700,6 @@
"xpack.ml.datavisualizer.searchPanel.sampleLabel": "采样",
"xpack.ml.datavisualizer.searchPanel.sampleSizeAriaLabel": "选择要采样的文档数目",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.errorMessage": "无法加载消息",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.messageLabel": "消息",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.nodeLabel": "节点",
"xpack.ml.dfAnalyticsList.analyticsDetails.messagesPane.timeLabel": "时间",
"xpack.ml.fieldDataCard.cardBoolean.documentsCountDescription": "{count, plural, zero {# 个文档} one {# 个文档} other {# 个文档}} ({docsPercent}%)",
"xpack.ml.fieldDataCard.cardDate.documentsCountDescription": "{count, plural, zero {# 个文档} one {# 个文档} other {# 个文档}} ({docsPercent}%)",
"xpack.ml.fieldDataCard.cardDate.earliestDescription": "最早的 {earliestFormatted}",