-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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] Overview tab for ML #45864
[ML] Overview tab for ML #45864
Conversation
Pinging @elastic/ml-ui |
💔 Build Failed |
We rarely show decimal places for anomaly score. Even though it is not the final graphic, probably worth rounding. |
💚 Build Succeeded |
eeafa38
to
b5f65d4
Compare
💔 Build Failed |
💔 Build Failed |
retest |
💔 Build Failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good, the only thing I'd consider a must for this PR are some missing translations.
I made some suggestions for improving types but that could be done in a follow up since I assume the use of some any
s stems from consuming/reusing code from the anomaly jobs list which isn't TypeScript yet.
@@ -21,31 +21,33 @@ import { stopAnalytics } from '../../services/analytics_service'; | |||
import { StartAction } from './action_start'; | |||
import { DeleteAction } from './action_delete'; | |||
|
|||
export const ANALYTICS_VIEW_ACTION = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could be analyticsViewAction
, it's not an enum or a simple constant.
@@ -58,6 +58,57 @@ export const getTaskStateBadge = ( | |||
); | |||
}; | |||
|
|||
export const PROGRESS_COLUMN = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could be progressColumn
, it's not an enum or simple constant.
{isInitialized === true && analytics.length === 0 && ( | ||
<EuiEmptyPrompt | ||
iconType="createAdvancedJob" | ||
title={<h2>Create your first analytics job</h2>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be translated.
body={ | ||
<Fragment> | ||
<p> | ||
Data frame analytics enable you to perform different analyses of your data and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be translated.
} | ||
actions={ | ||
<EuiButton href="#/data_frame_analytics?" color="primary" fill> | ||
Create job |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be translated.
import { Group, Groups, Jobs, Job } from './anomaly_detection_panel'; | ||
|
||
export function getGroupsFromJobs(jobs: Jobs): Groups { | ||
const groups: any = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be groups: Groups
?
max_anomaly_score: number | null; | ||
} | ||
|
||
export interface Job { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have MlJob
in ml/common/types/jobs.ts
could this be used instead here? This would help to get rid of all the any
s when working with this type/interface. If we use that, I'd suggest that we remove the Jobs
type from this file, using MlJob[]
in the consuming code would be more clear IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not quite the type I need since I'm getting jobs from ml.jobsSummary
but I went ahead and added a MlSummaryJob
and MlSummaryJobs
as we'll need those types when we convert all the job management code to typescript.
import { mlResultsService } from '../../../services/results_service'; | ||
import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils'; | ||
|
||
export interface Groups { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could we name this GroupsDictionary
and reuse Dictionary
from x-pack/legacy/plugins/ml/common/types/common.ts
? It's just that looking at the consuming code I assumed Groups
was an array (because Jobs
was) which was a bit irritating.
|
||
export type Jobs = Job[]; | ||
|
||
interface MaxScoresByGroup { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use Dictionary
from x-pack/legacy/plugins/ml/common/types/common.ts
} | ||
|
||
// object to keep track of nodes being used by jobs | ||
const mlNodes: any = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at how this is used later on, could this just be an array of job.nodeName
s like const mlNodes: string[] = [];
or even just a counter like let activeNodes = 0;
?
defaultMessage: 'Jobs in group', | ||
}), | ||
render: (jobIds: Group['jobIds']) => jobIds.length, | ||
sortable: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to follow-up meta issue 👍
name: i18n.translate('xpack.ml.overview.anomalyDetection.tableActionLabel', { | ||
defaultMessage: 'Actions', | ||
}), | ||
render: (group: Group) => <ExplorerLink jobsList={getJobsFromGroup(group, jobsList)} />, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with 59c9808
<Fragment> | ||
<AnalyticsTable items={analytics} /> | ||
<EuiSpacer size="m" /> | ||
<div className="mlOverviewPanel__buttons"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if the Refresh and Manage buttons could do with a bit more padding between them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to meta issue 👍
}, | ||
{ | ||
name: i18n.translate('xpack.ml.overview.anomalyDetection.tableActionLabel', { | ||
defaultMessage: 'Actions', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to follow-up meta issue
<EuiPanel className="mlOverviewPanel"> | ||
{typeof errorMessage !== 'undefined' && errorDisplay} | ||
{isLoading && <EuiLoadingSpinner />} | ||
{isLoading === false && typeof errorMessage === 'undefined' && groupsCount === 0 && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with 59c9808
(ensures removal of ungrouped
when there are no ungrouped jobs)
const resultsIndex = scores[groupId] && scores[groupId].index; | ||
const promiseResult: { success: boolean; results: { [key: string]: number } } = | ||
resultsIndex !== undefined && results[resultsIndex]; | ||
if (promiseResult.success === true && promiseResult.results !== undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, @peteharverson. This is occurring because we're passing the empty jobIds
array. I've created a fix to remove ungrouped
when there are no ungrouped job ids. This also fixes the issue where we weren't seeing the empty prompt when there were no Anomaly Detection jobs and were seeing the table with the loading anomaly score for ungrouped
.
import { ML_BREADCRUMB } from '../breadcrumbs'; | ||
|
||
export function getOverviewBreadcrumbs() { | ||
// Whilst top level nav menu with tabs remains, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: While*
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whilst
is a real word - means while
// TODO: panels can be smaller when empty | ||
export const AnalyticsPanel: FC = () => { | ||
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]); | ||
const [errorMessage, setErrorMessage] = useState<any>(undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe provide an error interface or just get rid of any?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add as a follow up in the meta issue 👍 😄
color="danger" | ||
iconType="alert" | ||
> | ||
<pre>{JSON.stringify(errorMessage)}</pre> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also can be a dedicated component for rendering error messages which cover this logic
import { AnalyticsStatsBar } from './analytics_stats_bar'; | ||
|
||
interface Props { | ||
items: any[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Item interface might be helpful
const scores = getDefaultAnomalyScores(groupsList); | ||
|
||
try { | ||
const promises: any[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: something like
const promises: any[] = []; | |
const promises = groupsList | |
.filter(group => group.jobIds.length > 0) | |
.map((group) => { | |
return mlResultsService.getOverallBucketScores( | |
group.jobIds, | |
1, | |
group.earliest_timestamp, | |
group.latest_timestamp | |
); | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, great suggestion for preventing the anomaly score fetch error with empty jobIds 👌
}); | ||
|
||
const results = await Promise.all(promises); | ||
const tempGroups = Object.assign({}, { ...groupsObject }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should only Object.assign or just spread
}, | ||
}; | ||
|
||
jobs.forEach((job: any) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is a Job interface at x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/configs/job.ts
}, | ||
}; | ||
|
||
jobs.forEach((job: any) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably deserves a function
return groups; | ||
} | ||
|
||
export function getStatsBarData(jobsList: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would by nice to replace any
} | ||
|
||
export function getJobsWithTimerange(jobsList: any) { | ||
const jobs: any = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
const jobs: any = {}; | |
const jobs = jobsList | |
.filter(job => jobs[job.id] === undefined && job.earliestTimestampMs !== undefined) | |
.reduce((acc, job: Job) => { | |
const { earliestTimestampMs, latestResultsTimestampMs } = job; | |
acc[job.id] = { | |
id: job.id, | |
earliestTimestampMs, | |
latestResultsTimestampMs, | |
}; | |
return acc; | |
}, {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am seeing errors from the Transforms wizard with the tables failing to load.
@@ -33,7 +33,7 @@ import { | |||
MlInMemoryTable, | |||
SortingPropType, | |||
SORT_DIRECTION, | |||
} from '../../../../../../common/types/eui/in_memory_table'; | |||
} from '../../../../../components/ml_in_memory_table'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Fixed with d981ba0
@@ -25,7 +25,7 @@ import { | |||
ColumnType, | |||
MlInMemoryTable, | |||
SORT_DIRECTION, | |||
} from '../../../../../../common/types/eui/in_memory_table'; | |||
} from '../../../../../components/ml_in_memory_table'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, this table fails to load for me, due to the missing pageIndex
prop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in d981ba0
@elasticmachine merge upstream |
💔 Build Failed |
<Fragment> | ||
<p> | ||
Data frame analytics enable you to perform different analyses of your data and | ||
annotate it with the results. As part of its output, data frame analytics appends |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the second sentence should be changed to something like this, since otherwise it seems to imply that the source index is altered: "The analytics job stores the annotated data, as well as a copy of the source data, in a new index."
💚 Build Succeeded |
51974cd
to
ce6b3fa
Compare
💚 Build Succeeded |
@@ -66,7 +66,7 @@ interface TabData { | |||
} | |||
|
|||
const TAB_DATA: Record<TabId, TabData> = { | |||
// overview: { testSubject: 'mlTabOverview', pathId: 'overview' }, | |||
overview: { testSubject: 'mlTabOverview', pathId: 'overview' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test subject naming has slightly changed with #45793. Would be good to have mlMainTab overview
here to match the other tab's attributes.
@@ -50,6 +50,10 @@ export function MachineLearningNavigationProvider({ | |||
]); | |||
}, | |||
|
|||
async navigateToOverview() { | |||
await this.navigateToArea('mlTabOverview', 'mlPageOverview'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you follow my suggestion from above, also change the tab data subject to mlMainTab overview
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clicking the Explore
action for an anomaly detection group, puts all jobs of that group into the job selection of the anomaly explorer. This could be a large number of jobs, maybe it's worth to think about just adding the group to the job selection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The refresh of the analytics table works as expected, but clicking the Refresh
button in the anomaly detection table, removes the whole table for a second and displays it again. Is this intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Text LGTM
💚 Build Succeeded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and LGTM. Happy for remaining enhancements and fixes listed in #46357 to be done in follow-ups.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latest changes LGTM
💚 Build Succeeded |
* show overview tab with main tabs * wip:add overview dir with initial components * convert and move AnalyticsTable and types to MlInMemoryTable components dir * create analytics table for overview * add stats bar to analytics panel * wip: adds anomaly detection table * adds actions column to anomaly detection table * add stats bar to anomaly detection panel * add max anomaly score to table * update score display. * add refresh button to panels * create scss files for styles * update functional nav tests * fix functional test failure * fix anomalyDetection for when there are no jobs * add translations and update jobList types * add maxAnomalyScore endpoint * fix types and update tab testSub * fix transforms use of inMemoryTable
* show overview tab with main tabs * wip:add overview dir with initial components * convert and move AnalyticsTable and types to MlInMemoryTable components dir * create analytics table for overview * add stats bar to analytics panel * wip: adds anomaly detection table * adds actions column to anomaly detection table * add stats bar to anomaly detection panel * add max anomaly score to table * update score display. * add refresh button to panels * create scss files for styles * update functional nav tests * fix functional test failure * fix anomalyDetection for when there are no jobs * add translations and update jobList types * add maxAnomalyScore endpoint * fix types and update tab testSub * fix transforms use of inMemoryTable
Summary
Moved and renamed
MlInMemoryTable
(was AnalyticsTable) to a shared location inpublic/components
. Updated all imports with new name/location.Adds
Overview
main tab withAnomaly Detection
jobs section andAnalytics
job section.This is version one of the overview tab. In place of
max anomaly score
, the anomaly detection section will have a mini graph/swimlane representing the anomalies for the last 24hrs across jobs for that group.Checklist
Use
strikethroughsto remove checklist items you don't feel are applicable to this PR.For maintainers
- [ ] This was checked for breaking API changes and was labeled appropriately- [ ] This includes a feature addition or change that requires a release note and was labeled appropriately