mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ML] Update the Overview page (#159609)
## Summary
Resolves https://github.com/elastic/kibana/issues/154294 and updates the
UI of the Overview page
- Updates panels layout
- Stores expand/collapsed state of the panels in the local storage
- Update empty states text and layout
<img width="1341" alt="image"
src="8833fa2a
-b574-44ee-bacb-e974186dd35f">
### Checklist
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
This commit is contained in:
parent
12a2203d10
commit
6ac52fb9ec
18 changed files with 457 additions and 319 deletions
|
@ -14,6 +14,7 @@ export const ML_GETTING_STARTED_CALLOUT_DISMISSED = 'ml.gettingStarted.isDismiss
|
|||
export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference';
|
||||
export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels';
|
||||
export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt';
|
||||
export const ML_OVERVIEW_PANELS = 'ml.overviewPanels';
|
||||
|
||||
export type PartitionFieldConfig =
|
||||
| {
|
||||
|
@ -52,6 +53,12 @@ export interface AnomalyExplorerPanelsState {
|
|||
mainPage: { size: number };
|
||||
}
|
||||
|
||||
export interface OverviewPanelsState {
|
||||
nodes: boolean;
|
||||
adJobs: boolean;
|
||||
dfaJobs: boolean;
|
||||
}
|
||||
|
||||
export interface MlStorageRecord {
|
||||
[key: string]: unknown;
|
||||
[ML_ENTITY_FIELDS_CONFIG]: PartitionFieldsConfig;
|
||||
|
@ -60,6 +67,7 @@ export interface MlStorageRecord {
|
|||
[ML_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
|
||||
[ML_ANOMALY_EXPLORER_PANELS]: AnomalyExplorerPanelsState | undefined;
|
||||
[ML_NOTIFICATIONS_LAST_CHECKED_AT]: number | undefined;
|
||||
[ML_OVERVIEW_PANELS]: OverviewPanelsState;
|
||||
}
|
||||
|
||||
export type MlStorage = Partial<MlStorageRecord> | null;
|
||||
|
@ -78,6 +86,8 @@ export type TMlStorageMapped<T extends MlStorageKey> = T extends typeof ML_ENTIT
|
|||
? AnomalyExplorerPanelsState | undefined
|
||||
: T extends typeof ML_NOTIFICATIONS_LAST_CHECKED_AT
|
||||
? number | undefined
|
||||
: T extends typeof ML_OVERVIEW_PANELS
|
||||
? OverviewPanelsState | undefined
|
||||
: null;
|
||||
|
||||
export const ML_STORAGE_KEYS = [
|
||||
|
@ -87,4 +97,5 @@ export const ML_STORAGE_KEYS = [
|
|||
ML_FROZEN_TIER_PREFERENCE,
|
||||
ML_ANOMALY_EXPLORER_PANELS,
|
||||
ML_NOTIFICATIONS_LAST_CHECKED_AT,
|
||||
ML_OVERVIEW_PANELS,
|
||||
] as const;
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiBadge,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSplitPanel,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import React, { type FC } from 'react';
|
||||
import { css } from '@emotion/react/dist/emotion-react.cjs';
|
||||
import { useCurrentThemeVars } from '../../contexts/kibana';
|
||||
|
||||
export interface CollapsiblePanelProps {
|
||||
isOpen: boolean;
|
||||
onToggle: (isOpen: boolean) => void;
|
||||
|
||||
header: React.ReactElement;
|
||||
headerItems?: React.ReactElement[];
|
||||
}
|
||||
|
||||
export const CollapsiblePanel: FC<CollapsiblePanelProps> = ({
|
||||
isOpen,
|
||||
onToggle,
|
||||
children,
|
||||
header,
|
||||
headerItems,
|
||||
}) => {
|
||||
const { euiTheme } = useCurrentThemeVars();
|
||||
|
||||
return (
|
||||
<EuiSplitPanel.Outer
|
||||
grow
|
||||
hasShadow={false}
|
||||
css={{
|
||||
border: `${euiTheme.euiBorderWidthThin} solid ${
|
||||
isOpen ? euiTheme.euiBorderColor : 'transparent'
|
||||
}`,
|
||||
}}
|
||||
>
|
||||
<EuiSplitPanel.Inner color={isOpen ? 'plain' : 'subdued'}>
|
||||
<EuiFlexGroup justifyContent={'spaceBetween'} alignItems={'center'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize={'s'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
color={'text'}
|
||||
iconType={isOpen ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => {
|
||||
onToggle(!isOpen);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xxs">
|
||||
<h2>{header}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{headerItems ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize={'l'} alignItems={'center'}>
|
||||
{headerItems.map((item, i) => {
|
||||
return (
|
||||
<EuiFlexItem key={i} grow={false}>
|
||||
<div
|
||||
css={
|
||||
i < headerItems?.length - 1
|
||||
? css`
|
||||
border-right: ${euiTheme.euiBorderWidthThin} solid
|
||||
${euiTheme.euiBorderColor};
|
||||
padding-right: ${euiTheme.euiPanelPaddingModifiers.paddingLarge};
|
||||
`
|
||||
: null
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiSplitPanel.Inner>
|
||||
{isOpen ? (
|
||||
<EuiSplitPanel.Inner
|
||||
css={{ borderTop: `${euiTheme.euiBorderWidthThin} solid ${euiTheme.euiBorderColor}` }}
|
||||
grow={false}
|
||||
>
|
||||
{children}
|
||||
</EuiSplitPanel.Inner>
|
||||
) : null}
|
||||
</EuiSplitPanel.Outer>
|
||||
);
|
||||
};
|
||||
|
||||
export interface StatEntry {
|
||||
label: string;
|
||||
value: number;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export interface OverviewStatsBarProps {
|
||||
inputStats: StatEntry[];
|
||||
dataTestSub?: string;
|
||||
}
|
||||
|
||||
export const OverviewStatsBar: FC<OverviewStatsBarProps> = ({ inputStats, dataTestSub }) => {
|
||||
return (
|
||||
<EuiFlexGroup data-test-subj={dataTestSub} alignItems={'center'} gutterSize={'m'}>
|
||||
{inputStats.map(({ value, label, 'data-test-subj': dataTestSubjValue }) => {
|
||||
return (
|
||||
<EuiFlexItem grow={false} key={label}>
|
||||
<EuiFlexGroup alignItems={'center'} gutterSize={'s'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size={'s'}>{label}:</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge data-test-subj={dataTestSubjValue}>{value}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { CollapsiblePanel } from './collapsible_panel';
|
|
@ -6,16 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiImage,
|
||||
EuiLink,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import dfaImage from './data_frame_analytics_kibana.png';
|
||||
|
@ -26,10 +17,7 @@ import { usePermissionCheck } from '../../../../../capabilities/check_capabiliti
|
|||
|
||||
export const AnalyticsEmptyPrompt: FC = () => {
|
||||
const {
|
||||
services: {
|
||||
docLinks,
|
||||
http: { basePath },
|
||||
},
|
||||
services: { docLinks },
|
||||
} = useMlKibana();
|
||||
|
||||
const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics] = usePermissionCheck([
|
||||
|
@ -40,7 +28,6 @@ export const AnalyticsEmptyPrompt: FC = () => {
|
|||
const disabled =
|
||||
!mlNodesAvailable() || !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics;
|
||||
|
||||
const transformsLink = `${basePath.get()}/app/management/data/transform`;
|
||||
const navigateToPath = useNavigateToPath();
|
||||
|
||||
const navigateToSourceSelection = async () => {
|
||||
|
@ -57,16 +44,15 @@ export const AnalyticsEmptyPrompt: FC = () => {
|
|||
size="fullWidth"
|
||||
src={dfaImage}
|
||||
alt={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', {
|
||||
defaultMessage: 'Create your first data frame analytics job',
|
||||
defaultMessage: 'Analyze your data with data frame analytics',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
color="subdued"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataFrame.analyticsList.emptyPromptTitle"
|
||||
defaultMessage="Create your first data frame analytics job"
|
||||
defaultMessage="Analyze your data with data frame analytics"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
|
@ -78,39 +64,6 @@ export const AnalyticsEmptyPrompt: FC = () => {
|
|||
defaultMessage="Train outlier detection, regression, or classification machine learning models using data frame analytics."
|
||||
/>
|
||||
</p>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.overview.analyticsList.emptyPromptHelperText"
|
||||
defaultMessage="Before building a data frame analytics job, use {transforms} to construct an {sourcedata}."
|
||||
values={{
|
||||
transforms: (
|
||||
<EuiLink href={transformsLink} target="blank" color={'accent'}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.overview.gettingStartedSectionTransforms"
|
||||
defaultMessage="transforms"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
sourcedata: (
|
||||
<EuiLink
|
||||
href={docLinks.links.ml.dFAPrepareData}
|
||||
target="blank"
|
||||
color={'accent'}
|
||||
external
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.overview.gettingStartedSectionSourceData"
|
||||
defaultMessage="entity-centric source data set"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
iconType="iInCircle"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
actions={[
|
||||
|
@ -118,37 +71,19 @@ export const AnalyticsEmptyPrompt: FC = () => {
|
|||
onClick={navigateToSourceSelection}
|
||||
isDisabled={disabled}
|
||||
color="primary"
|
||||
iconType="plusInCircle"
|
||||
fill
|
||||
data-test-subj="mlAnalyticsCreateFirstButton"
|
||||
>
|
||||
{i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', {
|
||||
defaultMessage: 'Create job',
|
||||
defaultMessage: 'Create data frame analytics job',
|
||||
})}
|
||||
</EuiButton>,
|
||||
<EuiLink href={docLinks.links.ml.dataFrameAnalytics} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.common.readDocumentationLink"
|
||||
defaultMessage="Read documentation"
|
||||
/>
|
||||
</EuiLink>,
|
||||
]}
|
||||
footer={
|
||||
<EuiFlexGroup gutterSize={'xs'} alignItems={'center'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xxs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.common.learnMoreQuestion"
|
||||
defaultMessage="Want to learn more?"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href={docLinks.links.ml.dataFrameAnalytics} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.common.readDocumentationLink"
|
||||
defaultMessage="Read documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
data-test-subj="mlNoDataFrameAnalyticsFound"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('get_analytics', () => {
|
|||
// act and assert
|
||||
expect(getAnalyticsJobsStats(mockResponse)).toEqual({
|
||||
total: {
|
||||
label: 'Total analytics jobs',
|
||||
label: 'Total',
|
||||
value: 2,
|
||||
show: true,
|
||||
},
|
||||
|
|
|
@ -47,7 +47,7 @@ export function getInitialAnalyticsStats(): AnalyticStatsBarStats {
|
|||
return {
|
||||
total: {
|
||||
label: i18n.translate('xpack.ml.overview.statsBar.totalAnalyticsLabel', {
|
||||
defaultMessage: 'Total analytics jobs',
|
||||
defaultMessage: 'Total',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
|
@ -97,12 +97,18 @@ export function getAnalyticsJobsStats(
|
|||
);
|
||||
resultStats.failed.show = resultStats.failed.value > 0;
|
||||
resultStats.total.value = analyticsStats.count;
|
||||
|
||||
if (resultStats.total.value === 0) {
|
||||
resultStats.started.show = false;
|
||||
resultStats.stopped.show = false;
|
||||
}
|
||||
|
||||
return resultStats;
|
||||
}
|
||||
|
||||
export const getAnalyticsFactory = (
|
||||
setAnalytics: React.Dispatch<React.SetStateAction<DataFrameAnalyticsListRow[]>>,
|
||||
setAnalyticsStats: React.Dispatch<React.SetStateAction<AnalyticStatsBarStats | undefined>>,
|
||||
setAnalyticsStats: (update: AnalyticStatsBarStats | undefined) => void,
|
||||
setErrorMessage: React.Dispatch<
|
||||
React.SetStateAction<GetDataFrameAnalyticsStatsResponseError | undefined>
|
||||
>,
|
||||
|
|
|
@ -7,15 +7,7 @@
|
|||
|
||||
import React, { FC } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiImage,
|
||||
EuiLink,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui';
|
||||
import adImage from './anomaly_detection_kibana.png';
|
||||
import { ML_PAGES } from '../../../../../../common/constants/locator';
|
||||
import { useMlKibana, useMlLocator, useNavigateToPath } from '../../../../contexts/kibana';
|
||||
|
@ -47,12 +39,11 @@ export const AnomalyDetectionEmptyState: FC = () => {
|
|||
hasBorder={false}
|
||||
hasShadow={false}
|
||||
icon={<EuiImage size="fullWidth" src={adImage} alt="anomaly_detection" />}
|
||||
color="subdued"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.overview.anomalyDetection.createFirstJobMessage"
|
||||
defaultMessage="Create your first anomaly detection job"
|
||||
defaultMessage="Start detecting anomalies"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
|
@ -66,43 +57,25 @@ export const AnomalyDetectionEmptyState: FC = () => {
|
|||
</p>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
actions={[
|
||||
<EuiButton
|
||||
color="primary"
|
||||
onClick={redirectToCreateJobSelectIndexPage}
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
isDisabled={disableCreateAnomalyDetectionJob}
|
||||
data-test-subj="mlCreateNewJobButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.overview.anomalyDetection.createJobButtonText"
|
||||
defaultMessage="Create job"
|
||||
defaultMessage="Create anomaly detection job"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
footer={
|
||||
<EuiFlexGroup gutterSize={'xs'} alignItems={'center'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xxs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.common.learnMoreQuestion"
|
||||
defaultMessage="Want to learn more?"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href={docLinks.links.ml.anomalyDetection} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.common.readDocumentationLink"
|
||||
defaultMessage="Read documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
</EuiButton>,
|
||||
<EuiLink href={docLinks.links.ml.anomalyDetection} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.common.readDocumentationLink"
|
||||
defaultMessage="Read documentation"
|
||||
/>
|
||||
</EuiLink>,
|
||||
]}
|
||||
data-test-subj="mlAnomalyDetectionEmptyState"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -200,15 +200,20 @@ export const NodesList: FC<NodesListProps> = ({ compactView = false }) => {
|
|||
|
||||
return (
|
||||
<div data-test-subj={'mlNodesOverviewPanel'}>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{nodesStats && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatsBar stats={nodesStats} dataTestSub={'mlTrainedModelsNodesStatsBar'} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
{nodesStats && !compactView ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{nodesStats && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatsBar stats={nodesStats} dataTestSub={'mlTrainedModelsNodesStatsBar'} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<div data-test-subj="mlNodesTableContainer">
|
||||
<EuiInMemoryTable<NodeItem>
|
||||
allowNeutralSort={false}
|
||||
|
|
|
@ -43,3 +43,7 @@ export function lazyMlNodesAvailable() {
|
|||
export function permissionToViewMlNodeCount() {
|
||||
return userHasPermissionToViewMlNodeCount;
|
||||
}
|
||||
|
||||
export function getMlNodesCount(): number {
|
||||
return mlNodeCount;
|
||||
}
|
||||
|
|
|
@ -5,28 +5,32 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { EuiCallOut, EuiLink, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useStorage } from '@kbn/ml-local-storage';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { type AnalyticStatsBarStats } from '../../../components/stats_bar';
|
||||
import {
|
||||
OverviewStatsBar,
|
||||
type StatEntry,
|
||||
} from '../../../components/collapsible_panel/collapsible_panel';
|
||||
import {
|
||||
ML_OVERVIEW_PANELS,
|
||||
MlStorageKey,
|
||||
TMlStorageMapped,
|
||||
} from '../../../../../common/types/storage';
|
||||
import { AnalyticsTable } from './table';
|
||||
import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service';
|
||||
import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
|
||||
import { AnalyticStatsBarStats, StatsBar } from '../../../components/stats_bar';
|
||||
import { useMlLink } from '../../../contexts/kibana';
|
||||
import { ML_PAGES } from '../../../../../common/constants/locator';
|
||||
import { useRefresh } from '../../../routing/use_refresh';
|
||||
import type { GetDataFrameAnalyticsStatsResponseError } from '../../../services/ml_api_service/data_frame_analytics';
|
||||
import { AnalyticsEmptyPrompt } from '../../../data_frame_analytics/pages/analytics_management/components/empty_prompt';
|
||||
import { overviewPanelDefaultState } from '../../overview_page';
|
||||
import { CollapsiblePanel } from '../../../components/collapsible_panel';
|
||||
|
||||
interface Props {
|
||||
setLazyJobCount: React.Dispatch<React.SetStateAction<number>>;
|
||||
|
@ -35,9 +39,7 @@ export const AnalyticsPanel: FC<Props> = ({ setLazyJobCount }) => {
|
|||
const refresh = useRefresh();
|
||||
|
||||
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
|
||||
const [analyticsStats, setAnalyticsStats] = useState<AnalyticStatsBarStats | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [analyticsStats, setAnalyticsStats] = useState<StatEntry[] | undefined>(undefined);
|
||||
const [errorMessage, setErrorMessage] = useState<GetDataFrameAnalyticsStatsResponseError>();
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
|
@ -45,9 +47,24 @@ export const AnalyticsPanel: FC<Props> = ({ setLazyJobCount }) => {
|
|||
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
|
||||
});
|
||||
|
||||
const [panelsState, setPanelsState] = useStorage<
|
||||
MlStorageKey,
|
||||
TMlStorageMapped<typeof ML_OVERVIEW_PANELS>
|
||||
>(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
|
||||
|
||||
const setAnalyticsStatsCustom = useCallback((stats: AnalyticStatsBarStats | undefined) => {
|
||||
if (!stats) return;
|
||||
|
||||
const result = Object.entries(stats)
|
||||
.filter(([k, v]) => v.show)
|
||||
.map(([k, v]) => v);
|
||||
|
||||
setAnalyticsStats(result);
|
||||
}, []);
|
||||
|
||||
const getAnalytics = getAnalyticsFactory(
|
||||
setAnalytics,
|
||||
setAnalyticsStats,
|
||||
setAnalyticsStatsCustom,
|
||||
setErrorMessage,
|
||||
setIsInitialized,
|
||||
setLazyJobCount,
|
||||
|
@ -78,58 +95,40 @@ export const AnalyticsPanel: FC<Props> = ({ setLazyJobCount }) => {
|
|||
const noDFAJobs = errorMessage === undefined && isInitialized === true && analytics.length === 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{noDFAJobs ? (
|
||||
<AnalyticsEmptyPrompt />
|
||||
) : (
|
||||
<EuiPanel
|
||||
css={isInitialized ? {} : { textAlign: 'center', padding: '10%' }}
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
>
|
||||
{typeof errorMessage !== 'undefined' ? errorDisplay : null}
|
||||
{isInitialized === false && (
|
||||
<EuiLoadingSpinner css={{ display: 'inline-block' }} size="xl" />
|
||||
)}
|
||||
<CollapsiblePanel
|
||||
isOpen={panelsState.dfaJobs}
|
||||
onToggle={(update) => {
|
||||
setPanelsState({ ...panelsState, dfaJobs: update });
|
||||
}}
|
||||
header={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.overview.analyticsList.PanelTitle"
|
||||
defaultMessage="Data Frame Analytics Jobs"
|
||||
/>
|
||||
}
|
||||
headerItems={[
|
||||
...(analyticsStats
|
||||
? [
|
||||
<OverviewStatsBar
|
||||
inputStats={analyticsStats}
|
||||
dataTestSub={'mlOverviewAnalyticsStatsBar'}
|
||||
/>,
|
||||
]
|
||||
: []),
|
||||
<EuiLink href={manageJobsLink}>
|
||||
{i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', {
|
||||
defaultMessage: 'Manage jobs',
|
||||
})}
|
||||
</EuiLink>,
|
||||
]}
|
||||
>
|
||||
{noDFAJobs ? <AnalyticsEmptyPrompt /> : null}
|
||||
|
||||
{isInitialized === true && analytics.length > 0 && (
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>
|
||||
{i18n.translate('xpack.ml.overview.analyticsList.PanelTitle', {
|
||||
defaultMessage: 'Analytics',
|
||||
})}
|
||||
</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize={'s'} alignItems="center">
|
||||
{analyticsStats !== undefined ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatsBar
|
||||
stats={analyticsStats}
|
||||
dataTestSub={'mlOverviewAnalyticsStatsBar'}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="m" fill href={manageJobsLink}>
|
||||
{i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', {
|
||||
defaultMessage: 'Manage jobs',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<AnalyticsTable items={analytics} />
|
||||
</>
|
||||
)}
|
||||
</EuiPanel>
|
||||
)}
|
||||
</>
|
||||
{typeof errorMessage !== 'undefined' ? errorDisplay : null}
|
||||
|
||||
{isInitialized === false && <EuiLoadingSpinner css={{ display: 'inline-block' }} size="xl" />}
|
||||
|
||||
{isInitialized === true && analytics.length > 0 ? <AnalyticsTable items={analytics} /> : null}
|
||||
</CollapsiblePanel>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,10 +6,20 @@
|
|||
*/
|
||||
|
||||
import React, { FC, Fragment, useEffect, useState } from 'react';
|
||||
import { EuiCallOut, EuiLoadingSpinner, EuiPanel } from '@elastic/eui';
|
||||
import { EuiCallOut, EuiLink, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { zipObject } from 'lodash';
|
||||
import { useMlKibana } from '../../../contexts/kibana';
|
||||
import { zipObject, groupBy } from 'lodash';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useStorage } from '@kbn/ml-local-storage';
|
||||
import {
|
||||
ML_OVERVIEW_PANELS,
|
||||
MlStorageKey,
|
||||
TMlStorageMapped,
|
||||
} from '../../../../../common/types/storage';
|
||||
import { ML_PAGES } from '../../../../../common/constants/locator';
|
||||
import { OverviewStatsBar } from '../../../components/collapsible_panel/collapsible_panel';
|
||||
import { CollapsiblePanel } from '../../../components/collapsible_panel';
|
||||
import { useMlKibana, useMlLink } from '../../../contexts/kibana';
|
||||
import { AnomalyDetectionTable } from './table';
|
||||
import { ml } from '../../../services/ml_api_service';
|
||||
import { getGroupsFromJobs, getStatsBarData } from './utils';
|
||||
|
@ -19,8 +29,8 @@ import { useRefresh } from '../../../routing/use_refresh';
|
|||
import { useToastNotificationService } from '../../../services/toast_notification_service';
|
||||
import { AnomalyTimelineService } from '../../../services/anomaly_timeline_service';
|
||||
import type { OverallSwimlaneData } from '../../../explorer/explorer_utils';
|
||||
import { JobStatsBarStats } from '../../../components/stats_bar';
|
||||
import { AnomalyDetectionEmptyState } from '../../../jobs/jobs_list/components/anomaly_detection_empty_state';
|
||||
import { overviewPanelDefaultState } from '../../overview_page';
|
||||
|
||||
export type GroupsDictionary = Dictionary<Group>;
|
||||
|
||||
|
@ -50,10 +60,21 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
|
|||
|
||||
const refresh = useRefresh();
|
||||
|
||||
const [panelsState, setPanelsState] = useStorage<
|
||||
MlStorageKey,
|
||||
TMlStorageMapped<typeof ML_OVERVIEW_PANELS>
|
||||
>(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
|
||||
|
||||
const manageJobsLink = useMlLink({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
|
||||
});
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [groups, setGroups] = useState<GroupsDictionary>({});
|
||||
const [groupsCount, setGroupsCount] = useState<number>(0);
|
||||
const [statsBarData, setStatsBarData] = useState<JobStatsBarStats>();
|
||||
const [statsBarData, setStatsBarData] = useState<Array<{ label: string; value: number }>>();
|
||||
const [restStatsBarData, setRestStatsBarData] =
|
||||
useState<Array<{ label: string; value: number }>>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const loadJobs = async () => {
|
||||
|
@ -71,9 +92,20 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
|
|||
});
|
||||
const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList);
|
||||
const stats = getStatsBarData(jobsSummaryList);
|
||||
|
||||
const statGroups = groupBy(
|
||||
Object.entries(stats)
|
||||
.filter(([k, v]) => v.show)
|
||||
.map(([k, v]) => v),
|
||||
'group'
|
||||
);
|
||||
|
||||
setIsLoading(false);
|
||||
setErrorMessage(undefined);
|
||||
setStatsBarData(stats);
|
||||
|
||||
setStatsBarData(statGroups[0]);
|
||||
setRestStatsBarData(statGroups[1]);
|
||||
|
||||
setGroupsCount(count);
|
||||
setGroups(jobsGroups);
|
||||
loadOverallSwimLanes(jobsGroups);
|
||||
|
@ -138,30 +170,52 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
|
|||
</Fragment>
|
||||
);
|
||||
|
||||
const panelClass = isLoading ? 'mlOverviewPanel__isLoading' : 'mlOverviewPanel';
|
||||
|
||||
const noAdJobs =
|
||||
!errorMessage &&
|
||||
isLoading === false &&
|
||||
typeof errorMessage === 'undefined' &&
|
||||
groupsCount === 0;
|
||||
|
||||
if (noAdJobs) {
|
||||
return <AnomalyDetectionEmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPanel className={panelClass} hasShadow={false} hasBorder>
|
||||
<CollapsiblePanel
|
||||
isOpen={panelsState.adJobs}
|
||||
onToggle={(update) => {
|
||||
setPanelsState({ ...panelsState, adJobs: update });
|
||||
}}
|
||||
header={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.overview.adJobsPanel.header"
|
||||
defaultMessage="Anomaly Detection Jobs"
|
||||
/>
|
||||
}
|
||||
headerItems={[
|
||||
...(statsBarData
|
||||
? [<OverviewStatsBar inputStats={statsBarData} dataTestSub={'mlOverviewJobStatsBar'} />]
|
||||
: []),
|
||||
...(restStatsBarData
|
||||
? [
|
||||
<OverviewStatsBar
|
||||
inputStats={restStatsBarData}
|
||||
dataTestSub={'mlOverviewJobStatsBarExtra'}
|
||||
/>,
|
||||
]
|
||||
: []),
|
||||
<EuiLink href={manageJobsLink}>
|
||||
{i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', {
|
||||
defaultMessage: 'Manage jobs',
|
||||
})}
|
||||
</EuiLink>,
|
||||
]}
|
||||
>
|
||||
{noAdJobs ? <AnomalyDetectionEmptyState /> : null}
|
||||
|
||||
{typeof errorMessage !== 'undefined' && errorDisplay}
|
||||
{isLoading && <EuiLoadingSpinner className="mlOverviewPanel__spinner" size="xl" />}
|
||||
|
||||
{isLoading ? <EuiLoadingSpinner className="mlOverviewPanel__spinner" size="xl" /> : null}
|
||||
|
||||
{isLoading === false && typeof errorMessage === 'undefined' && groupsCount > 0 ? (
|
||||
<AnomalyDetectionTable
|
||||
items={groups}
|
||||
statsBarData={statsBarData!}
|
||||
chartsService={chartsService}
|
||||
/>
|
||||
<AnomalyDetectionTable items={groups} chartsService={chartsService} />
|
||||
) : null}
|
||||
</EuiPanel>
|
||||
</CollapsiblePanel>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,13 +9,8 @@ import React, { FC, useState } from 'react';
|
|||
import {
|
||||
Direction,
|
||||
EuiBasicTableColumn,
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiInMemoryTable,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -24,13 +19,10 @@ import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
|||
import { formatHumanReadableDateTime } from '@kbn/ml-date-utils';
|
||||
import { useGroupActions } from './actions';
|
||||
import { Group, GroupsDictionary } from './anomaly_detection_panel';
|
||||
import { JobStatsBarStats, StatsBar } from '../../../components/stats_bar';
|
||||
import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge';
|
||||
import { toLocaleString } from '../../../util/string_utils';
|
||||
import { SwimlaneContainer } from '../../../explorer/swimlane_container';
|
||||
import { useTimeBuckets } from '../../../components/custom_hooks/use_time_buckets';
|
||||
import { ML_PAGES } from '../../../../../common/constants/locator';
|
||||
import { useMlLink } from '../../../contexts/kibana';
|
||||
|
||||
export enum AnomalyDetectionListColumns {
|
||||
id = 'id',
|
||||
|
@ -44,11 +36,10 @@ export enum AnomalyDetectionListColumns {
|
|||
|
||||
interface Props {
|
||||
items: GroupsDictionary;
|
||||
statsBarData: JobStatsBarStats;
|
||||
chartsService: ChartsPluginStart;
|
||||
}
|
||||
|
||||
export const AnomalyDetectionTable: FC<Props> = ({ items, statsBarData, chartsService }) => {
|
||||
export const AnomalyDetectionTable: FC<Props> = ({ items, chartsService }) => {
|
||||
const groupsList = Object.values(items);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
@ -58,10 +49,6 @@ export const AnomalyDetectionTable: FC<Props> = ({ items, statsBarData, chartsSe
|
|||
|
||||
const timeBuckets = useTimeBuckets();
|
||||
|
||||
const manageJobsLink = useMlLink({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
|
||||
});
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<Group>> = [
|
||||
{
|
||||
field: AnomalyDetectionListColumns.id,
|
||||
|
@ -195,47 +182,19 @@ export const AnomalyDetectionTable: FC<Props> = ({ items, statsBarData, chartsSe
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize={'s'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>
|
||||
{i18n.translate('xpack.ml.overview.anomalyDetection.panelTitle', {
|
||||
defaultMessage: 'Anomaly Detection',
|
||||
})}
|
||||
</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize={'s'} alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatsBar stats={statsBarData} dataTestSub={'mlOverviewJobStatsBar'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="m" fill href={manageJobsLink}>
|
||||
{i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', {
|
||||
defaultMessage: 'Manage jobs',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiInMemoryTable<Group>
|
||||
allowNeutralSort={false}
|
||||
className="mlAnomalyDetectionTable"
|
||||
columns={columns}
|
||||
hasActions={true}
|
||||
isExpandable={false}
|
||||
isSelectable={false}
|
||||
items={groupsList}
|
||||
itemId={AnomalyDetectionListColumns.id}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
data-test-subj="mlOverviewTableAnomalyDetection"
|
||||
/>
|
||||
</>
|
||||
<EuiInMemoryTable<Group>
|
||||
allowNeutralSort={false}
|
||||
className="mlAnomalyDetectionTable"
|
||||
columns={columns}
|
||||
hasActions={true}
|
||||
isExpandable={false}
|
||||
isSelectable={false}
|
||||
items={groupsList}
|
||||
itemId={AnomalyDetectionListColumns.id}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
data-test-subj="mlOverviewTableAnomalyDetection"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -76,40 +76,45 @@ export function getGroupsFromJobs(jobs: MlSummaryJobs): {
|
|||
|
||||
export function getStatsBarData(jobsList: any) {
|
||||
const jobStats = {
|
||||
total: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.totalJobsLabel', {
|
||||
defaultMessage: 'Total',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
group: 0,
|
||||
},
|
||||
open: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.openJobsLabel', {
|
||||
defaultMessage: 'Open',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
group: 0,
|
||||
},
|
||||
closed: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.closedJobsLabel', {
|
||||
defaultMessage: 'Closed',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
group: 0,
|
||||
},
|
||||
failed: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.failedJobsLabel', {
|
||||
defaultMessage: 'Failed',
|
||||
}),
|
||||
value: 0,
|
||||
show: false,
|
||||
group: 0,
|
||||
},
|
||||
activeNodes: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', {
|
||||
defaultMessage: 'Active ML nodes',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
},
|
||||
total: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.totalJobsLabel', {
|
||||
defaultMessage: 'Total jobs',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
},
|
||||
open: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.openJobsLabel', {
|
||||
defaultMessage: 'Open jobs',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
},
|
||||
closed: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.closedJobsLabel', {
|
||||
defaultMessage: 'Closed jobs',
|
||||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
},
|
||||
failed: {
|
||||
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.failedJobsLabel', {
|
||||
defaultMessage: 'Failed jobs',
|
||||
}),
|
||||
value: 0,
|
||||
show: false,
|
||||
group: 1,
|
||||
},
|
||||
activeDatafeeds: {
|
||||
label: i18n.translate('xpack.ml.jobsList.statsBar.activeDatafeedsLabel', {
|
||||
|
@ -117,6 +122,7 @@ export function getStatsBarData(jobsList: any) {
|
|||
}),
|
||||
value: 0,
|
||||
show: true,
|
||||
group: 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -158,5 +164,13 @@ export function getStatsBarData(jobsList: any) {
|
|||
|
||||
jobStats.activeNodes.value = Object.keys(mlNodes).length;
|
||||
|
||||
if (jobStats.total.value === 0) {
|
||||
for (const [statKey, val] of Object.entries(jobStats)) {
|
||||
if (statKey !== 'total') {
|
||||
val.show = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jobStats;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,15 @@
|
|||
*/
|
||||
|
||||
import React, { FC, useState } from 'react';
|
||||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiLink, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { useStorage } from '@kbn/ml-local-storage';
|
||||
import { OverviewStatsBar } from '../components/collapsible_panel/collapsible_panel';
|
||||
import { ML_PAGES } from '../../../common/constants/locator';
|
||||
import { ML_OVERVIEW_PANELS, MlStorageKey, TMlStorageMapped } from '../../../common/types/storage';
|
||||
import { CollapsiblePanel } from '../components/collapsible_panel';
|
||||
import { usePermissionCheck } from '../capabilities/check_capabilities';
|
||||
import { mlNodesAvailable } from '../ml_nodes_check';
|
||||
import { OverviewContent } from './components/content';
|
||||
|
@ -17,11 +23,18 @@ import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warnin
|
|||
import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
||||
import { UpgradeWarning } from '../components/upgrade';
|
||||
import { HelpMenu } from '../components/help_menu';
|
||||
import { useMlKibana } from '../contexts/kibana';
|
||||
import { useMlKibana, useMlLink } from '../contexts/kibana';
|
||||
import { NodesList } from '../memory_usage/nodes_overview';
|
||||
import { MlPageHeader } from '../components/page_header';
|
||||
import { PageTitle } from '../components/page_title';
|
||||
import { useIsServerless } from '../contexts/kibana/use_is_serverless';
|
||||
import { getMlNodesCount } from '../ml_nodes_check/check_ml_nodes';
|
||||
|
||||
export const overviewPanelDefaultState = Object.freeze({
|
||||
nodes: true,
|
||||
adJobs: true,
|
||||
dfaJobs: true,
|
||||
});
|
||||
|
||||
export const OverviewPage: FC = () => {
|
||||
const serverless = useIsServerless();
|
||||
|
@ -33,11 +46,20 @@ export const OverviewPage: FC = () => {
|
|||
} = useMlKibana();
|
||||
const helpLink = docLinks.links.ml.guide;
|
||||
|
||||
const viewNodesLink = useMlLink({
|
||||
page: ML_PAGES.MEMORY_USAGE,
|
||||
});
|
||||
|
||||
const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true });
|
||||
|
||||
const [adLazyJobCount, setAdLazyJobCount] = useState(0);
|
||||
const [dfaLazyJobCount, setDfaLazyJobCount] = useState(0);
|
||||
|
||||
const [panelsState, setPanelsState] = useStorage<
|
||||
MlStorageKey,
|
||||
TMlStorageMapped<typeof ML_OVERVIEW_PANELS>
|
||||
>(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MlPageHeader>
|
||||
|
@ -63,9 +85,36 @@ export const OverviewPage: FC = () => {
|
|||
|
||||
{canViewMlNodes && serverless === false ? (
|
||||
<>
|
||||
<EuiPanel hasShadow={false} hasBorder>
|
||||
<CollapsiblePanel
|
||||
isOpen={panelsState.nodes}
|
||||
onToggle={(update) => {
|
||||
setPanelsState({ ...panelsState, nodes: update });
|
||||
}}
|
||||
header={
|
||||
<FormattedMessage id="xpack.ml.overview.nodesPanel.header" defaultMessage="Nodes" />
|
||||
}
|
||||
headerItems={[
|
||||
<OverviewStatsBar
|
||||
inputStats={[
|
||||
{
|
||||
label: i18n.translate('xpack.ml.overview.nodesPanel.totalNodesLabel', {
|
||||
defaultMessage: 'Total',
|
||||
}),
|
||||
value: getMlNodesCount(),
|
||||
'data-test-subj': 'mlTotalNodesCount',
|
||||
},
|
||||
]}
|
||||
dataTestSub={'mlOverviewAnalyticsStatsBar'}
|
||||
/>,
|
||||
<EuiLink href={viewNodesLink}>
|
||||
{i18n.translate('xpack.ml.overview.nodesPanel.viewNodeLink', {
|
||||
defaultMessage: 'View nodes',
|
||||
})}
|
||||
</EuiLink>,
|
||||
]}
|
||||
>
|
||||
<NodesList compactView />
|
||||
</EuiPanel>
|
||||
</CollapsiblePanel>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
|
|
|
@ -35,7 +35,6 @@ export const useRouteResolver = (
|
|||
): {
|
||||
context: RouteResolverContext;
|
||||
results: ResolverResults;
|
||||
component?: React.Component;
|
||||
} => {
|
||||
const requiredCapabilitiesRef = useRef(requiredCapabilities);
|
||||
const customResolversRef = useRef(customResolvers);
|
||||
|
|
|
@ -22521,7 +22521,6 @@
|
|||
"xpack.ml.notifications.newNotificationsMessage": "Il y a eu {newNotificationsCount, plural, one {# notification} many {# notifications} other {# notifications}} depuis {sinceDate}. Actualisez la page pour afficher les mises à jour.",
|
||||
"xpack.ml.notificationsIndicator.errorsAndWarningLabel": "Il y a eu {count, plural, one {# notification} many {# notifications} other {# notifications}} avec un niveau d'avertissement ou d'erreur depuis {lastCheckedAt}",
|
||||
"xpack.ml.notificationsIndicator.unreadLabel": "Vous avez des notifications non lues depuis {lastCheckedAt}",
|
||||
"xpack.ml.overview.analyticsList.emptyPromptHelperText": "Avant de créer une tâche d'analyse du cadre de données, utilisez des {transforms} pour créer une {sourcedata}.",
|
||||
"xpack.ml.previewAlert.otherValuesLabel": "et {count, plural, one {# autre} many {# autres} other {# autres}}",
|
||||
"xpack.ml.previewAlert.previewMessage": "{alertsCount, plural, one {# anomalie a été trouvée} many {# anomalies ont été trouvées} other {# anomalies ont été trouvées}} au cours des dernières {interval}.",
|
||||
"xpack.ml.privilege.pleaseContactAdministratorTooltip": "{message} Veuillez contacter votre administrateur.",
|
||||
|
@ -22898,7 +22897,6 @@
|
|||
"xpack.ml.cases.anomalySwimLane.embeddableAddedEvent": "couloir d'anomalie ajouté",
|
||||
"xpack.ml.changePointDetection.pageHeader": "Modifier la détection du point",
|
||||
"xpack.ml.chrome.help.appName": "Machine Learning",
|
||||
"xpack.ml.common.learnMoreQuestion": "Envie d'en savoir plus ?",
|
||||
"xpack.ml.common.readDocumentationLink": "Lire la documentation",
|
||||
"xpack.ml.components.colorRangeLegend.blueColorRangeLabel": "Bleu",
|
||||
"xpack.ml.components.colorRangeLegend.greenRedColorRangeLabel": "Vert – Rouge",
|
||||
|
@ -24525,7 +24523,6 @@
|
|||
"xpack.ml.overview.anomalyDetection.noAnomaliesFoundMessage": "Aucune anomalie n'a été trouvée",
|
||||
"xpack.ml.overview.anomalyDetection.noResultsFoundMessage": "Résultat introuvable",
|
||||
"xpack.ml.overview.anomalyDetection.overallScore": "Score général",
|
||||
"xpack.ml.overview.anomalyDetection.panelTitle": "Détection des anomalies",
|
||||
"xpack.ml.overview.anomalyDetection.resultActions.openInJobManagementText": "Afficher les tâches",
|
||||
"xpack.ml.overview.anomalyDetection.resultActions.openJobsInAnomalyExplorerText": "Afficher dans l’Explorateur d'anomalies",
|
||||
"xpack.ml.overview.anomalyDetection.tableActionLabel": "Actions",
|
||||
|
@ -24539,8 +24536,6 @@
|
|||
"xpack.ml.overview.anomalyDetection.tableTypicalTooltip": "Valeurs typiques dans les résultats d'enregistrement des anomalies.",
|
||||
"xpack.ml.overview.anomalyDetection.viewJobsActionName": "Afficher les tâches",
|
||||
"xpack.ml.overview.anomalyDetection.viewResultsActionName": "Afficher dans l’Explorateur d'anomalies",
|
||||
"xpack.ml.overview.gettingStartedSectionSourceData": "ensemble de données source centré sur les entités",
|
||||
"xpack.ml.overview.gettingStartedSectionTransforms": "transformations",
|
||||
"xpack.ml.overview.notificationsLabel": "Notifications",
|
||||
"xpack.ml.overview.overviewLabel": "Aperçu",
|
||||
"xpack.ml.overview.statsBar.failedAnalyticsLabel": "Échoué",
|
||||
|
|
|
@ -22512,7 +22512,6 @@
|
|||
"xpack.ml.notifications.newNotificationsMessage": "{sinceDate}以降に{newNotificationsCount, plural, other {#件の通知があります}}。更新を表示するには、ページを更新してください。",
|
||||
"xpack.ml.notificationsIndicator.errorsAndWarningLabel": "{lastCheckedAt}以降にエラーまたは警告レベルの{count, plural, other {#件の通知があります}}",
|
||||
"xpack.ml.notificationsIndicator.unreadLabel": "{lastCheckedAt}以降に未読の通知があります",
|
||||
"xpack.ml.overview.analyticsList.emptyPromptHelperText": "データフレーム分析ジョブを構築する前に、{transforms}を使用して{sourcedata}を作成してください。",
|
||||
"xpack.ml.previewAlert.otherValuesLabel": "および{count, plural, other {#個のその他}}",
|
||||
"xpack.ml.previewAlert.previewMessage": "過去{interval}に{alertsCount, plural, other {#個の異常}}が見つかりました。",
|
||||
"xpack.ml.privilege.pleaseContactAdministratorTooltip": "{message} 管理者にお問い合わせください。",
|
||||
|
@ -22884,7 +22883,6 @@
|
|||
"xpack.ml.cases.anomalySwimLane.embeddableAddedEvent": "追加された異常スイムレーン",
|
||||
"xpack.ml.changePointDetection.pageHeader": "変化点検出",
|
||||
"xpack.ml.chrome.help.appName": "機械学習",
|
||||
"xpack.ml.common.learnMoreQuestion": "詳細について",
|
||||
"xpack.ml.common.readDocumentationLink": "ドキュメンテーションを表示",
|
||||
"xpack.ml.components.colorRangeLegend.blueColorRangeLabel": "青",
|
||||
"xpack.ml.components.colorRangeLegend.greenRedColorRangeLabel": "緑 - 赤",
|
||||
|
@ -24511,7 +24509,6 @@
|
|||
"xpack.ml.overview.anomalyDetection.noAnomaliesFoundMessage": "異常値が見つかりませんでした",
|
||||
"xpack.ml.overview.anomalyDetection.noResultsFoundMessage": "結果が見つかりませんでした",
|
||||
"xpack.ml.overview.anomalyDetection.overallScore": "全体スコア",
|
||||
"xpack.ml.overview.anomalyDetection.panelTitle": "異常検知",
|
||||
"xpack.ml.overview.anomalyDetection.resultActions.openInJobManagementText": "ジョブを表示",
|
||||
"xpack.ml.overview.anomalyDetection.resultActions.openJobsInAnomalyExplorerText": "異常エクスプローラーで表示",
|
||||
"xpack.ml.overview.anomalyDetection.tableActionLabel": "アクション",
|
||||
|
@ -24525,8 +24522,6 @@
|
|||
"xpack.ml.overview.anomalyDetection.tableTypicalTooltip": "異常レコード結果の標準的な値。",
|
||||
"xpack.ml.overview.anomalyDetection.viewJobsActionName": "ジョブを表示",
|
||||
"xpack.ml.overview.anomalyDetection.viewResultsActionName": "異常エクスプローラーで表示",
|
||||
"xpack.ml.overview.gettingStartedSectionSourceData": "エンティティ中心のソースデータセット",
|
||||
"xpack.ml.overview.gettingStartedSectionTransforms": "トランスフォーム",
|
||||
"xpack.ml.overview.notificationsLabel": "通知",
|
||||
"xpack.ml.overview.overviewLabel": "概要",
|
||||
"xpack.ml.overview.statsBar.failedAnalyticsLabel": "失敗",
|
||||
|
|
|
@ -22511,7 +22511,6 @@
|
|||
"xpack.ml.notifications.newNotificationsMessage": "自 {sinceDate}以来有 {newNotificationsCount, plural, other {# 个通知}}。刷新页面以查看更新。",
|
||||
"xpack.ml.notificationsIndicator.errorsAndWarningLabel": "自 {lastCheckedAt}以来有 {count, plural, other {# 个通知}}包含错误或警告级别",
|
||||
"xpack.ml.notificationsIndicator.unreadLabel": "自 {lastCheckedAt}以来您有未计通知",
|
||||
"xpack.ml.overview.analyticsList.emptyPromptHelperText": "构建数据帧分析作业之前,请使用 {transforms} 构造一个 {sourcedata}。",
|
||||
"xpack.ml.previewAlert.otherValuesLabel": "和{count, plural, other {另外 # 个}}",
|
||||
"xpack.ml.previewAlert.previewMessage": "在过去 {interval}找到 {alertsCount, plural, other {# 个异常}}。",
|
||||
"xpack.ml.privilege.pleaseContactAdministratorTooltip": "{message}请联系您的管理员。",
|
||||
|
@ -22883,7 +22882,6 @@
|
|||
"xpack.ml.cases.anomalySwimLane.embeddableAddedEvent": "已添加异常泳道",
|
||||
"xpack.ml.changePointDetection.pageHeader": "更改点检测",
|
||||
"xpack.ml.chrome.help.appName": "Machine Learning",
|
||||
"xpack.ml.common.learnMoreQuestion": "希望了解详情?",
|
||||
"xpack.ml.common.readDocumentationLink": "阅读文档",
|
||||
"xpack.ml.components.colorRangeLegend.blueColorRangeLabel": "蓝",
|
||||
"xpack.ml.components.colorRangeLegend.greenRedColorRangeLabel": "绿 - 红",
|
||||
|
@ -24510,7 +24508,6 @@
|
|||
"xpack.ml.overview.anomalyDetection.noAnomaliesFoundMessage": "找不到异常",
|
||||
"xpack.ml.overview.anomalyDetection.noResultsFoundMessage": "找不到结果",
|
||||
"xpack.ml.overview.anomalyDetection.overallScore": "总分",
|
||||
"xpack.ml.overview.anomalyDetection.panelTitle": "异常检测",
|
||||
"xpack.ml.overview.anomalyDetection.resultActions.openInJobManagementText": "查看作业",
|
||||
"xpack.ml.overview.anomalyDetection.resultActions.openJobsInAnomalyExplorerText": "在 Anomaly Explorer 中查看",
|
||||
"xpack.ml.overview.anomalyDetection.tableActionLabel": "操作",
|
||||
|
@ -24524,8 +24521,6 @@
|
|||
"xpack.ml.overview.anomalyDetection.tableTypicalTooltip": "异常记录结果中的典型值。",
|
||||
"xpack.ml.overview.anomalyDetection.viewJobsActionName": "查看作业",
|
||||
"xpack.ml.overview.anomalyDetection.viewResultsActionName": "在 Anomaly Explorer 中查看",
|
||||
"xpack.ml.overview.gettingStartedSectionSourceData": "实体中心型源数据集",
|
||||
"xpack.ml.overview.gettingStartedSectionTransforms": "转换",
|
||||
"xpack.ml.overview.notificationsLabel": "通知",
|
||||
"xpack.ml.overview.overviewLabel": "概览",
|
||||
"xpack.ml.overview.statsBar.failedAnalyticsLabel": "失败",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue