Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Kibana management jobs list #42570

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/ml/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const ml = (kibana: any) => {
publicDir: resolve(__dirname, 'public'),

uiExports: {
managementSections: ['plugins/ml/management'],
app: {
title: i18n.translate('xpack.ml.mlNavTitle', {
defaultMessage: 'Machine Learning',
Expand Down
5 changes: 5 additions & 0 deletions x-pack/legacy/plugins/ml/public/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
// ML has it's own variables for coloring
@import 'variables';

// Kibana management page ML section
#kibanaManagementMLSection {
@import 'management/index';
walterra marked this conversation as resolved.
Show resolved Hide resolved
}

// Protect the rest of Kibana from ML generic namespacing
// SASSTODO: Prefix ml selectors instead
#ml-app {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class JobDetailsUI extends Component {
datafeedTimingStats
} = extractJobDetails(job);

const { intl } = this.props;
const { intl, fullDetails } = this.props;

const tabs = [{
id: 'job-settings',
Expand All @@ -88,13 +88,6 @@ class JobDetailsUI extends Component {
defaultMessage: 'Job config'
}),
content: <JobDetailsPane sections={[detectors, influencers, analysisConfig, analysisLimits, dataDescription]} />,
}, {
id: 'datafeed',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.tabs.datafeedLabel',
defaultMessage: 'Datafeed'
}),
content: <JobDetailsPane sections={[datafeed, datafeedTimingStats]} />,
}, {
id: 'counts',
name: intl.formatMessage({
Expand All @@ -116,24 +109,38 @@ class JobDetailsUI extends Component {
defaultMessage: 'Job messages'
}),
content: <JobMessagesPane job={job} />,
}, {
id: 'datafeed-preview',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.tabs.datafeedPreviewLabel',
defaultMessage: 'Datafeed preview'
}),
content: <DatafeedPreviewPane job={job} />,
}, {
id: 'forecasts',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.tabs.forecastsLabel',
defaultMessage: 'Forecasts'
}),
content: <ForecastsTable job={job} />,
}
},
];

if (mlAnnotationsEnabled) {
if (fullDetails) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, it's not immediately clear what fullDetails is, especially when used in a truthy check like this.
perhaps showFullDetails would be clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isManagementTable is used later on, perhaps it should also be used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to showFullDetails - 9d707f9

// Datafeed should be at index 2 in tabs array for full details
tabs.splice(2, 0, {
id: 'datafeed',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.tabs.datafeedLabel',
defaultMessage: 'Datafeed'
}),
content: <JobDetailsPane sections={[datafeed, datafeedTimingStats]} />,
});

tabs.push({
id: 'datafeed-preview',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.tabs.datafeedPreviewLabel',
defaultMessage: 'Datafeed preview'
}),
content: <DatafeedPreviewPane job={job} />,
}, {
id: 'forecasts',
name: intl.formatMessage({
id: 'xpack.ml.jobsList.jobDetails.tabs.forecastsLabel',
defaultMessage: 'Forecasts'
}),
content: <ForecastsTable job={job} />,
});
}

if (mlAnnotationsEnabled && fullDetails) {
tabs.push({
id: 'annotations',
name: intl.formatMessage({
Expand Down Expand Up @@ -166,6 +173,7 @@ JobDetailsUI.propTypes = {
job: PropTypes.object,
addYourself: PropTypes.func.isRequired,
removeYourself: PropTypes.func.isRequired,
fullDetails: PropTypes.bool
};

export const JobDetails = injectI18n(JobDetailsUI);
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import React, {

import { ml } from 'plugins/ml/services/ml_api_service';
import { JobGroup } from '../job_group';
import { getSelectedJobIdFromUrl } from '../utils';

import {
EuiSearchBar,
Expand Down Expand Up @@ -56,6 +57,20 @@ class JobFilterBarUI extends Component {
this.setFilters = props.setFilters;
}

componentDidMount() {
// If job id is selected in url, filter table to that id
const selectedId = getSelectedJobIdFromUrl(window.location.href);
alvarezmelissa87 marked this conversation as resolved.
Show resolved Hide resolved
if (selectedId !== undefined) {
this.setState({
selectedId
}, () => {
// trigger onChange with query for job id to trigger table filter
const query = EuiSearchBar.Query.parse(selectedId);
this.onChange({ query });
});
}
}

onChange = ({ query, error }) => {
if (error) {
this.setState({ error });
Expand All @@ -71,7 +86,7 @@ class JobFilterBarUI extends Component {

render() {
const { intl } = this.props;
const { error } = this.state;
const { error, selectedId } = this.state;
const filters = [
{
type: 'field_value_toggle_group',
Expand Down Expand Up @@ -133,10 +148,12 @@ class JobFilterBarUI extends Component {
}

];

// if prop flag for default filter set to true
// set defaultQuery to job id and force trigger filter with onChange - pass it the query object for the job id
return (
<EuiFlexGroup direction="column">
<EuiFlexItem data-test-subj="mlJobListSearchBar" grow={false}>
{selectedId === undefined &&
<EuiSearchBar
box={{
incremental: true,
Expand All @@ -145,6 +162,17 @@ class JobFilterBarUI extends Component {
onChange={this.onChange}
className="mlJobFilterBar"
/>
}
{selectedId !== undefined &&
<EuiSearchBar
box={{
incremental: true,
}}
defaultQuery={selectedId}
alvarezmelissa87 marked this conversation as resolved.
Show resolved Hide resolved
filters={filters}
onChange={this.onChange}
className="mlJobFilterBar"
/>}
<EuiFormRow
fullWidth
isInvalid={(error !== null)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ import { tabColor } from '../../../../../common/util/group_color_utils';

import PropTypes from 'prop-types';
import React from 'react';
import theme from '@elastic/eui/dist/eui_theme_light.json';


export function JobGroup({ name }) {
return (
<div
className="inline-group"
style={{ backgroundColor: tabColor(name) }}
style={{
backgroundColor: tabColor(name),
display: 'inline-block',
walterra marked this conversation as resolved.
Show resolved Hide resolved
padding: '2px 5px',
borderRadius: '2px',
fontSize: '12px',
margin: '0px 3px',
color: theme.euiColorEmptyShade
}}
>
{name}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ import { toLocaleString } from '../../../../util/string_utils';
import { ResultLinks, actionsMenuContent } from '../job_actions';
import { JobDescription } from './job_description';
import { JobIcon } from '../../../../components/job_message_icon';
import { getJobIdUrl } from '../utils';

import {
EuiBadge,
EuiBasicTable,
EuiButtonIcon,
EuiLink
} from '@elastic/eui';
import { injectI18n } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
Expand All @@ -29,6 +32,7 @@ const PAGE_SIZE = 10;
const PAGE_SIZE_OPTIONS = [10, 25, 50];
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';

// 'isManagementTable' bool prop to determine when to configure table for use in Kibana management page
class JobsListUI extends Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -75,6 +79,14 @@ class JobsListUI extends Component {
this.props.toggleRow(item.id);
};

getJobIdLink(id) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to disable the links to the Jobs list (inside ML) and the results views if ML is not enabled in the space? Currently in that situation you just get the page with the {"statusCode":404,"error":"Not Found","message":"Not Found"} error message

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added as TODO in a follow-up 👍

return (
<EuiLink href={getJobIdUrl(id)}>
{id}
</EuiLink>
);
}

getPageOfJobs(index, size, sortField, sortDirection) {
let list = this.state.jobsSummaryList;
list = sortBy(this.state.jobsSummaryList, (item) => item[sortField]);
Expand All @@ -100,7 +112,7 @@ class JobsListUI extends Component {
}

render() {
const { intl, loading } = this.props;
const { intl, loading, isManagementTable } = this.props;
const selectionControls = {
selectable: job => (job.deleting !== true),
selectableMessage: (selectable, rowItem) => (
Expand All @@ -126,7 +138,10 @@ class JobsListUI extends Component {
),
onSelectionChange: this.props.selectJobChange
};

// Adding 'width' props to columns for use in the Kibana management jobs list table
// The version of the table used in ML > Job Managment depends on many EUI class overrides that set the width explicitly.
// The ML > Job Managment table won't change as the overwritten class styles take precedence, though these values may need to
// be updated if we move to always using props for width.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

css overrides where originally used because it wasn't possible to add widths to the column objects.
it would be good to remove the css overrides and have all widths here.
This could be done in a follow up PR

const columns = [
{
name: '',
Expand All @@ -148,7 +163,8 @@ class JobsListUI extends Component {
)}
data-row-id={item.id}
/>
)
),
width: '3%'
alvarezmelissa87 marked this conversation as resolved.
Show resolved Hide resolved
}, {
field: 'id',
name: intl.formatMessage({
Expand All @@ -157,7 +173,8 @@ class JobsListUI extends Component {
}),
sortable: true,
truncateText: false,

width: '20%',
render: isManagementTable ? (id) => this.getJobIdLink(id) : undefined,
}, {
field: 'auditMessage',
name: '',
Expand All @@ -175,6 +192,7 @@ class JobsListUI extends Component {
<JobDescription job={item} />
),
textOnly: true,
width: '20%'
}, {
field: 'processed_record_count',
name: intl.formatMessage({
Expand All @@ -184,7 +202,8 @@ class JobsListUI extends Component {
sortable: true,
truncateText: false,
dataType: 'number',
render: count => toLocaleString(count)
render: count => toLocaleString(count),
width: '10%'
}, {
field: 'memory_status',
name: intl.formatMessage({
Expand All @@ -193,6 +212,7 @@ class JobsListUI extends Component {
}),
sortable: true,
truncateText: false,
width: '5%'
}, {
field: 'jobState',
name: intl.formatMessage({
Expand All @@ -201,6 +221,7 @@ class JobsListUI extends Component {
}),
sortable: true,
truncateText: false,
width: '8%'
}, {
field: 'datafeedState',
name: intl.formatMessage({
Expand All @@ -209,7 +230,32 @@ class JobsListUI extends Component {
}),
sortable: true,
truncateText: false,
width: '8%'
}, {
name: intl.formatMessage({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, is it possible to hide this column if ML is not enabled in the space?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added as a TODO in a follow-up 👍

id: 'xpack.ml.jobsList.actionsLabel',
defaultMessage: 'Actions'
}),
render: (item) => (
<ResultLinks jobs={[item]} />
)
}
];

if (isManagementTable === true) {
// insert before last column
columns.splice(columns.length - 1, 0, {
name: intl.formatMessage({
id: 'xpack.ml.jobsList.spacesLabel',
defaultMessage: 'Spaces'
}),
render: () => (
<EuiBadge color={'hollow'}>{'all'}</EuiBadge>
)
});
} else {
// insert before last column
columns.splice(columns.length - 1, 0, {
name: intl.formatMessage({
id: 'xpack.ml.jobsList.latestTimestampLabel',
defaultMessage: 'Latest timestamp'
Expand All @@ -225,24 +271,18 @@ class JobsListUI extends Component {
</span>
),
textOnly: true,
}, {
name: intl.formatMessage({
id: 'xpack.ml.jobsList.actionsLabel',
defaultMessage: 'Actions'
}),
render: (item) => (
<ResultLinks jobs={[item]} />
)
}, {
width: '15%'
});
columns.push({
name: '',
actions: actionsMenuContent(
this.props.showEditJobFlyout,
this.props.showDeleteJobModal,
this.props.showStartDatafeedModal,
this.props.refreshJobs,
)
}
];
});
}

const {
pageIndex,
Expand Down Expand Up @@ -292,7 +332,7 @@ class JobsListUI extends Component {
columns={columns}
pagination={pagination}
onChange={this.onTableChange}
selection={selectionControls}
selection={isManagementTable ? undefined : selectionControls}
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
isExpandable={true}
sorting={sorting}
Expand All @@ -307,10 +347,10 @@ JobsListUI.propTypes = {
itemIdToExpandedRowMap: PropTypes.object.isRequired,
toggleRow: PropTypes.func.isRequired,
selectJobChange: PropTypes.func.isRequired,
showEditJobFlyout: PropTypes.func.isRequired,
showDeleteJobModal: PropTypes.func.isRequired,
showStartDatafeedModal: PropTypes.func.isRequired,
refreshJobs: PropTypes.func.isRequired,
showEditJobFlyout: PropTypes.func,
showDeleteJobModal: PropTypes.func,
showStartDatafeedModal: PropTypes.func,
refreshJobs: PropTypes.func,
selectedJobsCount: PropTypes.number.isRequired,
loading: PropTypes.bool,
};
Expand Down
Loading