[ML] Remove sub navigation menu from the Anomaly Detection pages (#68663)

* [ML] Remove sub navigation menu from the Anomaly Detection pages

* [ML] Use data-test-subj in results view selector button tests

* [ML] Fix settings page tests

* [ML] Remove redundant top_nav component

* [ML] Tidy up job selector scss

* [ML] Use EUI condensed tab style for main tabs

* [ML] Address review comments and fix type error

* [ML] Edits following review. Fix translations.

* [ML] Add anomaly_detection_settings_context
This commit is contained in:
Pete Harverson 2020-06-12 10:41:35 +01:00 committed by GitHub
parent 8cdc2a1493
commit c307c8622f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 921 additions and 692 deletions

View file

@ -0,0 +1,57 @@
/*
* 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 from 'react';
import { Router } from 'react-router-dom';
import { render, fireEvent } from '@testing-library/react';
import { createBrowserHistory } from 'history';
import { I18nProvider } from '@kbn/i18n/react';
import { AnomalyResultsViewSelector } from './index';
describe('AnomalyResultsViewSelector', () => {
test('should create selector with correctly selected value', () => {
const history = createBrowserHistory();
const { getByTestId } = render(
<I18nProvider>
<Router history={history}>
<AnomalyResultsViewSelector viewId="timeseriesexplorer" />
</Router>
</I18nProvider>
);
// Check the Single Metric Viewer element exists in the selector, and that it is checked.
expect(getByTestId('mlAnomalyResultsViewSelectorSingleMetricViewer')).toBeInTheDocument();
expect(
getByTestId('mlAnomalyResultsViewSelectorSingleMetricViewer').hasAttribute('checked')
).toBe(true);
});
test('should open window to other results view when clicking on non-checked input', () => {
// Create mock for window.open
const mockedOpen = jest.fn();
const originalOpen = window.open;
window.open = mockedOpen;
const history = createBrowserHistory();
const { getByTestId } = render(
<I18nProvider>
<Router history={history}>
<AnomalyResultsViewSelector viewId="timeseriesexplorer" />
</Router>
</I18nProvider>
);
fireEvent.click(getByTestId('mlAnomalyResultsViewSelectorExplorer'));
expect(mockedOpen).toHaveBeenCalledWith('#/explorer', '_self');
// Clean-up window.open.
window.open = originalOpen;
});
});

View file

@ -0,0 +1,67 @@
/*
* 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, useMemo } from 'react';
import { encode } from 'rison-node';
import { EuiButtonGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useUrlState } from '../../util/url_state';
interface Props {
viewId: 'timeseriesexplorer' | 'explorer';
}
// Component for rendering a set of buttons for switching between the Anomaly Detection results views.
export const AnomalyResultsViewSelector: FC<Props> = ({ viewId }) => {
const toggleButtonsIcons = useMemo(
() => [
{
id: 'timeseriesexplorer',
label: i18n.translate('xpack.ml.anomalyResultsViewSelector.singleMetricViewerLabel', {
defaultMessage: 'View results in the Single Metric Viewer',
}),
iconType: 'stats',
value: 'timeseriesexplorer',
'data-test-subj': 'mlAnomalyResultsViewSelectorSingleMetricViewer',
},
{
id: 'explorer',
label: i18n.translate('xpack.ml.anomalyResultsViewSelector.anomalyExplorerLabel', {
defaultMessage: 'View results in the Anomaly Explorer',
}),
iconType: 'tableOfContents',
value: 'explorer',
'data-test-subj': 'mlAnomalyResultsViewSelectorExplorer',
},
],
[]
);
const [globalState] = useUrlState('_g');
const onChangeView = (newViewId: string) => {
const fullGlobalStateString = globalState !== undefined ? `?_g=${encode(globalState)}` : '';
window.open(`#/${newViewId}${fullGlobalStateString}`, '_self');
};
return (
<EuiButtonGroup
legend={i18n.translate('xpack.ml.anomalyResultsViewSelector.buttonGroupLegend', {
defaultMessage: 'Anomaly results view selector',
})}
name="anomalyResultsViewSelector"
data-test-subj="mlAnomalyResultsViewSelector"
options={toggleButtonsIcons}
idSelected={viewId}
onChange={onChangeView}
isIconOnly
/>
);
};

View file

@ -0,0 +1,6 @@
/*
* 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 { AnomalyResultsViewSelector } from './anomaly_results_view_selector';

View file

@ -1,8 +1,8 @@
.mlJobSelectorBar {
padding: $euiSize;
padding: $euiSizeS;
}
.mlJobSelectorFlyoutBody > .euiFlyoutBody__overflow {
.mlJobSelectorFlyoutBody>.euiFlyoutBody__overflow {
padding-top: $euiSizeS;
}
@ -26,54 +26,14 @@
}
.mlJobSelector__ganttBarRunning {
background-image:-webkit-gradient(linear,
0 100%, 100% 0,
color-stop(0.25, rgba(255, 255, 255, 0.15)),
color-stop(0.25, transparent),
color-stop(0.5, transparent),
color-stop(0.5, rgba(255, 255, 255, 0.15)),
color-stop(0.75, rgba(255, 255, 255, 0.15)),
color-stop(0.75, transparent),
to(transparent));
background-image:-webkit-linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%, transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
background-image:-moz-linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
background-image:-o-linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
background-image:linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
-webkit-background-size:40px 40px;
-moz-background-size:40px 40px;
-o-background-size:40px 40px;
background-size:40px 40px;
-webkit-animation:progress-bar-stripes 2s linear infinite;
-moz-animation:progress-bar-stripes 2s linear infinite;
-ms-animation:progress-bar-stripes 2s linear infinite;
-o-animation:progress-bar-stripes 2s linear infinite;
animation:progress-bar-stripes 2s linear infinite;
background-image: linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
background-size: $euiSizeXXL $euiSizeXXL;
animation: progress-bar-stripes 2s linear infinite;
}

View file

@ -1,25 +1,5 @@
.mlNavigationMenu__tab {
padding-bottom: 0;
padding-left: 0px;
padding-right: 0px;
margin-left: $euiSizeM;
}
.mlNavigationMenu__mainTab {
margin-left: $euiSizeM;
padding-bottom: 0;
font-weight: normal;
}
.mlNavigationMenu__topNav {
padding-top: $euiSizeS;
margin-right: $euiSizeS;
}
.mlNavHorizontalRule {
margin: $euiSizeM 0 0 0;
}
.mlSubTabs {
margin-top: $euiSizeS;
.mlNavigationMenu {
padding: 0 $euiSizeM;
border-bottom: $euiBorderThin;
background-color: $euiColorEmptyShade;
}

View file

@ -12,7 +12,7 @@ import { EuiSuperDatePicker } from '@elastic/eui';
import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service';
import { TopNav } from './top_nav';
import { DatePickerWrapper } from './date_picker_wrapper';
jest.mock('../../../contexts/kibana', () => ({
useMlKibana: () => {
@ -43,7 +43,7 @@ jest.mock('../../../contexts/kibana', () => ({
const noop = () => {};
describe('Navigation Menu: <TopNav />', () => {
describe('Navigation Menu: <DatePickerWrapper />', () => {
beforeEach(() => {
jest.useFakeTimers();
});
@ -58,17 +58,17 @@ describe('Navigation Menu: <TopNav />', () => {
const wrapper = mount(
<MemoryRouter>
<TopNav />
<DatePickerWrapper />
</MemoryRouter>
);
expect(wrapper.find(TopNav)).toHaveLength(1);
expect(wrapper.find(DatePickerWrapper)).toHaveLength(1);
expect(refreshListener).toBeCalledTimes(0);
refreshSubscription.unsubscribe();
});
// The following tests are written against EuiSuperDatePicker
// instead of TopNav. TopNav uses hooks and we cannot write tests
// instead of DatePickerWrapper. DatePickerWrapper uses hooks and we cannot write tests
// with async hook updates yet until React 16.9 is available.
test('Listen for consecutive super date picker refreshs.', async () => {
const onRefresh = jest.fn();

View file

@ -43,7 +43,7 @@ function updateLastRefresh(timeRange: OnRefreshProps) {
mlTimefilterRefresh$.next({ lastRefresh: Date.now(), timeRange });
}
export const TopNav: FC = () => {
export const DatePickerWrapper: FC = () => {
const { services } = useMlKibana();
const config = services.uiSettings;
const { timefilter, history } = services.data.query.timefilter;
@ -124,7 +124,7 @@ export const TopNav: FC = () => {
return (
<Fragment>
{(isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled) && (
<div className="mlNavigationMenu__topNav">
<div className="mlNavigationMenu__datePickerWrapper">
<EuiSuperDatePicker
start={time.from}
end={time.to}

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { TopNav } from './top_nav';
export { DatePickerWrapper } from './date_picker_wrapper';

View file

@ -55,6 +55,13 @@ function getTabs(disableLinks: boolean): Tab[] {
}),
disabled: false,
},
{
id: 'settings',
name: i18n.translate('xpack.ml.navMenu.settingsTabLinkText', {
defaultMessage: 'Settings',
}),
disabled: false,
},
];
}
interface TabData {
@ -63,16 +70,19 @@ interface TabData {
}
const TAB_DATA: Record<TabId, TabData> = {
overview: { testSubject: 'mlMainTab overview', pathId: 'overview' },
overview: { testSubject: 'mlMainTab overview' },
// Note that anomaly detection jobs list is mapped to ml#/jobs.
anomaly_detection: { testSubject: 'mlMainTab anomalyDetection', pathId: 'jobs' },
data_frame_analytics: { testSubject: 'mlMainTab dataFrameAnalytics' },
datavisualizer: { testSubject: 'mlMainTab dataVisualizer' },
settings: { testSubject: 'mlMainTab settings' },
'access-denied': { testSubject: 'mlMainTab overview' },
};
export const MainTabs: FC<Props> = ({ tabId, disableLinks }) => {
const [globalState] = useUrlState('_g');
const [selectedTabId, setSelectedTabId] = useState(tabId);
function onSelectedTabChanged(id: string) {
function onSelectedTabChanged(id: TabId) {
setSelectedTabId(id);
}
@ -93,20 +103,23 @@ export const MainTabs: FC<Props> = ({ tabId, disableLinks }) => {
{tab.name}
</EuiTab>
) : (
<EuiLink
data-test-subj={testSubject + (id === selectedTabId ? ' selected' : '')}
href={`#/${defaultPathId}${fullGlobalStateString}`}
key={`${id}-key`}
color="text"
>
<EuiTab
className={'mlNavigationMenu__mainTab'}
onClick={() => onSelectedTabChanged(id)}
isSelected={id === selectedTabId}
<div className="euiTab" key={`div-${id}-key`}>
<EuiLink
data-test-subj={testSubject + (id === selectedTabId ? ' selected' : '')}
href={`#/${defaultPathId}${fullGlobalStateString}`}
key={`${id}-key`}
color="text"
>
{tab.name}
</EuiTab>
</EuiLink>
<EuiTab
className={'mlNavigationMenu__mainTab'}
onClick={() => onSelectedTabChanged(id)}
isSelected={id === selectedTabId}
key={`tab-${id}-key`}
>
{tab.name}
</EuiTab>
</EuiLink>
</div>
);
})}
</EuiTabs>

View file

@ -5,28 +5,19 @@
*/
import React, { Fragment, FC } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { isFullLicense } from '../../license';
import { TopNav } from './top_nav';
import { MainTabs } from './main_tabs';
import { Tabs } from './tabs';
export type TabId = string;
type TabSupport = Record<TabId, string | null>;
const tabSupport: TabSupport = {
overview: null,
jobs: 'anomaly_detection',
settings: 'anomaly_detection',
data_frame_analytics: null,
datavisualizer: null,
filedatavisualizer: null,
timeseriesexplorer: 'anomaly_detection',
'access-denied': null,
explorer: 'anomaly_detection',
};
export type TabId =
| 'access-denied'
| 'anomaly_detection'
| 'data_frame_analytics'
| 'datavisualizer'
| 'overview'
| 'settings';
interface Props {
tabId: TabId;
@ -34,23 +25,14 @@ interface Props {
export const NavigationMenu: FC<Props> = ({ tabId }) => {
const disableLinks = isFullLicense() === false;
const showTabs = typeof tabSupport[tabId] !== 'undefined';
const mainTabId = tabSupport[tabId] || tabId;
// show horizontal rule if there are no subtabs
const showHorizontalRule = tabSupport[tabId] === null;
return (
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexGroup justifyContent="spaceBetween" className="mlNavigationMenu" gutterSize="none">
<EuiFlexItem grow={false}>
{showTabs && <MainTabs tabId={mainTabId} disableLinks={disableLinks} />}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TopNav />
<MainTabs tabId={tabId} disableLinks={disableLinks} />
</EuiFlexItem>
</EuiFlexGroup>
{showHorizontalRule && <EuiHorizontalRule className="mlNavHorizontalRule" />}
{showTabs && <Tabs tabId={tabId} mainTabId={mainTabId} disableLinks={disableLinks} />}
</Fragment>
);
};

View file

@ -1,23 +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 { getTabs } from './tabs';
describe('Navigation Menu: Tabs', () => {
test('getTabs() always returns an array', () => {
const tabs1 = getTabs('anomaly_detection', false);
expect(Array.isArray(tabs1)).toBeTruthy();
expect(tabs1).toHaveLength(4);
const tabs2 = getTabs('access-denied', false);
expect(Array.isArray(tabs2)).toBeTruthy();
expect(tabs2).toHaveLength(0);
const tabs3 = getTabs('datavisualizer', false);
expect(Array.isArray(tabs3)).toBeTruthy();
expect(tabs3).toHaveLength(0);
});
});

View file

@ -1,116 +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 React, { FC, useState } from 'react';
import { encode } from 'rison-node';
import { EuiTabs, EuiTab, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useUrlState } from '../../util/url_state';
import { Tab } from './main_tabs';
import { TabId } from './navigation_menu';
interface Props {
disableLinks: boolean;
mainTabId: TabId;
tabId: TabId;
}
export function getTabs(tabId: TabId, disableLinks: boolean): Tab[] {
const TAB_MAP: Partial<Record<TabId, Tab[]>> = {
overview: [],
datavisualizer: [],
data_frame_analytics: [],
anomaly_detection: [
{
id: 'jobs',
name: i18n.translate('xpack.ml.navMenu.jobManagementTabLinkText', {
defaultMessage: 'Job Management',
}),
disabled: disableLinks,
},
{
id: 'explorer',
name: i18n.translate('xpack.ml.navMenu.anomalyExplorerTabLinkText', {
defaultMessage: 'Anomaly Explorer',
}),
disabled: disableLinks,
},
{
id: 'timeseriesexplorer',
name: i18n.translate('xpack.ml.navMenu.singleMetricViewerTabLinkText', {
defaultMessage: 'Single Metric Viewer',
}),
disabled: disableLinks,
},
{
id: 'settings',
name: i18n.translate('xpack.ml.navMenu.settingsTabLinkText', {
defaultMessage: 'Settings',
}),
disabled: disableLinks,
},
],
};
return TAB_MAP[tabId] || [];
}
enum TAB_TEST_SUBJECT {
overview = 'mlOverview',
jobs = 'mlSubTab jobManagement',
explorer = 'mlSubTab anomalyExplorer',
timeseriesexplorer = 'mlSubTab singleMetricViewer',
settings = 'mlSubTab settings',
}
type TAB_TEST_SUBJECTS = keyof typeof TAB_TEST_SUBJECT;
export const Tabs: FC<Props> = ({ tabId, mainTabId, disableLinks }) => {
const [globalState] = useUrlState('_g');
const [selectedTabId, setSelectedTabId] = useState(tabId);
function onSelectedTabChanged(id: string) {
setSelectedTabId(id);
}
const tabs = getTabs(mainTabId, disableLinks);
if (tabs.length === 0) return null;
return (
<EuiTabs size="s" className={tabId === 'settings' ? 'mlSubTabs' : ''}>
{tabs.map((tab: Tab) => {
const id = tab.id;
// globalState (e.g. selected jobs and time range) should be retained when changing pages.
// appState will not be considered.
const fullGlobalStateString = globalState !== undefined ? `?_g=${encode(globalState)}` : '';
return (
<EuiLink
data-test-subj={
TAB_TEST_SUBJECT[id as TAB_TEST_SUBJECTS] + (id === selectedTabId ? ' selected' : '')
}
href={`#/${id}${fullGlobalStateString}`}
key={`${id}-key`}
color="text"
>
<EuiTab
className="mlNavigationMenu__tab"
onClick={() => onSelectedTabChanged(id)}
isSelected={id === selectedTabId}
disabled={tab.disabled}
>
{tab.name}
</EuiTab>
</EuiLink>
);
})}
</EuiTabs>
);
};

View file

@ -48,7 +48,6 @@ import {
SORT_DIRECTION,
} from '../../../../../components/ml_in_memory_table';
import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats_bar';
import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button';
import { CreateAnalyticsButton } from '../create_analytics_button';
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';
import { getSelectedJobIdFromUrl } from '../../../../../jobs/jobs_list/components/utils';
@ -398,9 +397,6 @@ export const DataFrameAnalyticsList: FC<Props> = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<RefreshAnalyticsListButton />
</EuiFlexItem>
{!isManagementTable && createAnalyticsForm && (
<EuiFlexItem grow={false}>
<CreateAnalyticsButton
@ -412,7 +408,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiSpacer size="m" />
<div data-test-subj="mlAnalyticsTableContainer">
<MlInMemoryTable
allowNeutralSort={false}

View file

@ -39,6 +39,6 @@ describe('Data Frame Analytics: <CreateAnalyticsButton />', () => {
<CreateAnalyticsButton {...props} setIsSourceIndexModalVisible={jest.fn()} />
);
expect(wrapper.find('EuiButton').text()).toBe('Create analytics job');
expect(wrapper.find('EuiButton').text()).toBe('Create job');
});
});

View file

@ -35,7 +35,7 @@ export const CreateAnalyticsButton: FC<Props> = ({
data-test-subj="mlAnalyticsButtonCreate"
>
{i18n.translate('xpack.ml.dataframe.analyticsList.createDataFrameAnalyticsButton', {
defaultMessage: 'Create analytics job',
defaultMessage: 'Create job',
})}
</EuiButton>
);

View file

@ -11,16 +11,21 @@ import { i18n } from '@kbn/i18n';
import {
EuiBetaBadge,
EuiFlexGroup,
EuiFlexItem,
EuiPage,
EuiPageBody,
EuiTitle,
EuiPageContent,
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
} from '@elastic/eui';
import { NavigationMenu } from '../../../components/navigation_menu';
import { DatePickerWrapper } from '../../../components/navigation_menu/date_picker_wrapper';
import { DataFrameAnalyticsList } from './components/analytics_list';
import { useRefreshInterval } from './components/analytics_list/use_refresh_interval';
import { RefreshAnalyticsListButton } from './components/refresh_analytics_list_button';
import { useCreateAnalyticsForm } from './hooks/use_create_analytics_form';
import { NodeAvailableWarning } from '../../../components/node_available_warning';
import { UpgradeWarning } from '../../../components/upgrade';
@ -63,15 +68,27 @@ export const Page: FC = () => {
</h1>
</EuiTitle>
</EuiPageHeaderSection>
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<RefreshAnalyticsListButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<DatePickerWrapper />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageHeaderSection>
</EuiPageHeader>
<NodeAvailableWarning />
<UpgradeWarning />
<DataFrameAnalyticsList
blockRefresh={blockRefresh}
createAnalyticsForm={createAnalyticsForm}
/>
<EuiPageContent>
<DataFrameAnalyticsList
blockRefresh={blockRefresh}
createAnalyticsForm={createAnalyticsForm}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</Fragment>

View file

@ -42,7 +42,6 @@
}
.mlAnomalyExplorer__filterBar {
margin-bottom: $euiSize;
padding-right: $euiSize;
padding-left: $euiSize;
}

View file

@ -20,10 +20,12 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiHorizontalRule,
EuiIconTip,
EuiPage,
EuiPageBody,
EuiScreenReaderOnly,
EuiPageHeader,
EuiPageHeaderSection,
EuiSelect,
EuiSpacer,
EuiTitle,
@ -38,12 +40,14 @@ import {
} from './components';
import { ExplorerSwimlane } from './explorer_swimlane';
import { getTimeBucketsFromCache } from '../util/time_buckets';
import { DatePickerWrapper } from '../components/navigation_menu/date_picker_wrapper';
import { InfluencersList } from '../components/influencers_list';
import {
ALLOW_CELL_RANGE_SELECTION,
dragSelect$,
explorerService,
} from './explorer_dashboard_service';
import { AnomalyResultsViewSelector } from '../components/anomaly_results_view_selector';
import { LoadingIndicator } from '../components/loading_indicator/loading_indicator';
import { NavigationMenu } from '../components/navigation_menu';
import { CheckboxShowCharts } from '../components/controls/checkbox_showcharts';
@ -89,17 +93,67 @@ function mapSwimlaneOptionsToEuiOptions(options) {
text: option,
}));
}
const ExplorerPage = ({ children, jobSelectorProps, resizeRef }) => (
const ExplorerPage = ({
children,
jobSelectorProps,
noInfluencersConfigured,
influencers,
filterActive,
filterPlaceHolder,
indexPattern,
queryString,
filterIconTriggeredQuery,
updateLanguage,
resizeRef,
}) => (
<div ref={resizeRef} data-test-subj="mlPageAnomalyExplorer">
<NavigationMenu tabId="explorer" />
<EuiPage style={{ padding: '0px', background: 'none' }}>
<NavigationMenu tabId="anomaly_detection" />
<EuiPage style={{ background: 'none' }}>
<EuiPageBody>
<EuiScreenReaderOnly>
<h1>
<FormattedMessage id="xpack.ml.explorer.pageTitle" defaultMessage="Anomaly Explorer" />
</h1>
</EuiScreenReaderOnly>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<AnomalyResultsViewSelector viewId="explorer" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle className="eui-textNoWrap">
<h1>
<FormattedMessage
id="xpack.ml.explorer.pageTitle"
defaultMessage="Anomaly Explorer"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageHeaderSection>
<EuiPageHeaderSection style={{ width: '100%' }}>
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" gutterSize="s">
{noInfluencersConfigured === false && influencers !== undefined && (
<EuiFlexItem>
<div className="mlAnomalyExplorer__filterBar">
<ExplorerQueryBar
filterActive={filterActive}
filterPlaceHolder={filterPlaceHolder}
indexPattern={indexPattern}
queryString={queryString}
filterIconTriggeredQuery={filterIconTriggeredQuery}
updateLanguage={updateLanguage}
/>
</div>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<DatePickerWrapper />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiHorizontalRule margin="none" />
<JobSelector {...jobSelectorProps} />
<EuiHorizontalRule margin="none" />
{children}
</EuiPageBody>
</EuiPage>
@ -314,7 +368,18 @@ export class Explorer extends React.Component {
if (loading === true) {
return (
<ExplorerPage jobSelectorProps={jobSelectorProps} resizeRef={this.resizeRef}>
<ExplorerPage
jobSelectorProps={jobSelectorProps}
noInfluencersConfigured={noInfluencersConfigured}
influencers={influencers}
filterActive={filterActive}
filterPlaceHolder={filterPlaceHolder}
filterIconTriggeredQuery={this.state.filterIconTriggeredQuery}
indexPattern={indexPattern}
queryString={queryString}
updateLanguage={this.updateLanguage}
resizeRef={this.resizeRef}
>
<LoadingIndicator
label={i18n.translate('xpack.ml.explorer.loadingLabel', {
defaultMessage: 'Loading',
@ -356,21 +421,19 @@ export class Explorer extends React.Component {
const bounds = timefilter.getActiveBounds();
return (
<ExplorerPage jobSelectorProps={jobSelectorProps} resizeRef={this.resizeRef}>
<ExplorerPage
jobSelectorProps={jobSelectorProps}
noInfluencersConfigured={noInfluencersConfigured}
influencers={influencers}
filterActive={filterActive}
filterPlaceHolder={filterPlaceHolder}
filterIconTriggeredQuery={this.state.filterIconTriggeredQuery}
indexPattern={indexPattern}
queryString={queryString}
updateLanguage={this.updateLanguage}
resizeRef={this.resizeRef}
>
<div className="results-container">
{noInfluencersConfigured === false && influencers !== undefined && (
<div className="mlAnomalyExplorer__filterBar">
<ExplorerQueryBar
filterActive={filterActive}
filterPlaceHolder={filterPlaceHolder}
indexPattern={indexPattern}
queryString={queryString}
filterIconTriggeredQuery={this.state.filterIconTriggeredQuery}
updateLanguage={this.updateLanguage}
/>
</div>
)}
{noInfluencersConfigured && (
<div className="no-influencers-warning">
<EuiIconTip

View file

@ -11,6 +11,7 @@ import {
EuiFlexItem,
EuiPage,
EuiPageBody,
EuiPageContent,
EuiPageHeader,
EuiPageHeaderSection,
EuiSpacer,
@ -30,6 +31,7 @@ import { MultiJobActions } from '../multi_job_actions';
import { NewJobButton } from '../new_job_button';
import { JobStatsBar } from '../jobs_stats_bar';
import { NodeAvailableWarning } from '../../../../components/node_available_warning';
import { DatePickerWrapper } from '../../../../components/navigation_menu/date_picker_wrapper';
import { UpgradeWarning } from '../../../../components/upgrade';
import { RefreshJobsListButton } from '../refresh_jobs_list_button';
import { isEqual } from 'lodash';
@ -352,6 +354,7 @@ export class JobsListView extends Component {
renderJobsListComponents() {
const { isRefreshing, loading, jobsSummaryList } = this.state;
const jobIds = jobsSummaryList.map((j) => j.id);
return (
<EuiPage data-test-subj="ml-jobs-list">
<EuiPageBody>
@ -366,17 +369,7 @@ export class JobsListView extends Component {
</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<NodeAvailableWarning />
<UpgradeWarning />
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<JobStatsBar jobsSummaryList={jobsSummaryList} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<RefreshJobsListButton
@ -385,60 +378,75 @@ export class JobsListView extends Component {
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<NewJobButton />
<DatePickerWrapper />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiSpacer size="s" />
<NodeAvailableWarning />
<div>
<div className="actions-bar">
<MultiJobActions
selectedJobs={this.state.selectedJobs}
allJobIds={jobIds}
showStartDatafeedModal={this.showStartDatafeedModal}
<UpgradeWarning />
<EuiPageContent>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<JobStatsBar jobsSummaryList={jobsSummaryList} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<NewJobButton />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<div>
<div className="actions-bar">
<MultiJobActions
selectedJobs={this.state.selectedJobs}
allJobIds={jobIds}
showStartDatafeedModal={this.showStartDatafeedModal}
showDeleteJobModal={this.showDeleteJobModal}
refreshJobs={() => this.refreshJobSummaryList(true)}
/>
<JobFilterBar setFilters={this.setFilters} />
</div>
<JobsList
jobsSummaryList={this.state.filteredJobsSummaryList}
fullJobsList={this.state.fullJobsList}
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
toggleRow={this.toggleRow}
selectJobChange={this.selectJobChange}
showEditJobFlyout={this.showEditJobFlyout}
showDeleteJobModal={this.showDeleteJobModal}
showStartDatafeedModal={this.showStartDatafeedModal}
refreshJobs={() => this.refreshJobSummaryList(true)}
selectedJobsCount={this.state.selectedJobs.length}
loading={loading}
/>
<EditJobFlyout
setShowFunction={this.setShowEditJobFlyoutFunction}
unsetShowFunction={this.unsetShowEditJobFlyoutFunction}
refreshJobs={() => this.refreshJobSummaryList(true)}
allJobIds={jobIds}
/>
<DeleteJobModal
setShowFunction={this.setShowDeleteJobModalFunction}
unsetShowFunction={this.unsetShowDeleteJobModalFunction}
refreshJobs={() => this.refreshJobSummaryList(true)}
/>
<JobFilterBar setFilters={this.setFilters} />
<StartDatafeedModal
setShowFunction={this.setShowStartDatafeedModalFunction}
unsetShowFunction={this.unsetShowDeleteJobModalFunction}
getShowCreateWatchFlyoutFunction={this.getShowCreateWatchFlyoutFunction}
refreshJobs={() => this.refreshJobSummaryList(true)}
/>
<CreateWatchFlyout
setShowFunction={this.setShowCreateWatchFlyoutFunction}
unsetShowFunction={this.unsetShowCreateWatchFlyoutFunction}
/>
</div>
<JobsList
jobsSummaryList={this.state.filteredJobsSummaryList}
fullJobsList={this.state.fullJobsList}
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
toggleRow={this.toggleRow}
selectJobChange={this.selectJobChange}
showEditJobFlyout={this.showEditJobFlyout}
showDeleteJobModal={this.showDeleteJobModal}
showStartDatafeedModal={this.showStartDatafeedModal}
refreshJobs={() => this.refreshJobSummaryList(true)}
selectedJobsCount={this.state.selectedJobs.length}
loading={loading}
/>
<EditJobFlyout
setShowFunction={this.setShowEditJobFlyoutFunction}
unsetShowFunction={this.unsetShowEditJobFlyoutFunction}
refreshJobs={() => this.refreshJobSummaryList(true)}
allJobIds={jobIds}
/>
<DeleteJobModal
setShowFunction={this.setShowDeleteJobModalFunction}
unsetShowFunction={this.unsetShowDeleteJobModalFunction}
refreshJobs={() => this.refreshJobSummaryList(true)}
/>
<StartDatafeedModal
setShowFunction={this.setShowStartDatafeedModalFunction}
unsetShowFunction={this.unsetShowDeleteJobModalFunction}
getShowCreateWatchFlyoutFunction={this.getShowCreateWatchFlyoutFunction}
refreshJobs={() => this.refreshJobSummaryList(true)}
/>
<CreateWatchFlyout
setShowFunction={this.setShowCreateWatchFlyoutFunction}
unsetShowFunction={this.unsetShowCreateWatchFlyoutFunction}
/>
</div>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);

View file

@ -21,7 +21,7 @@ interface JobsPageProps {
export const JobsPage: FC<JobsPageProps> = (props) => {
return (
<div data-test-subj="mlPageJobManagement">
<NavigationMenu tabId="jobs" />
<NavigationMenu tabId="anomaly_detection" />
<JobsListView {...props} />
</div>
);

View file

@ -28,6 +28,13 @@ export const ANOMALY_DETECTION_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
href: '#/jobs',
});
export const DATA_FRAME_ANALYTICS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.dataFrameAnalyticsLabel', {
defaultMessage: 'Data Frame Analytics',
}),
href: '#/data_frame_analytics',
});
export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.datavisualizerBreadcrumbLabel', {
defaultMessage: 'Data Visualizer',

View file

@ -13,13 +13,14 @@ import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_exploration';
import { ANALYSIS_CONFIG_TYPE } from '../../../data_frame_analytics/common/analytics';
import { ML_BREADCRUMB } from '../../breadcrumbs';
import { ML_BREADCRUMB, DATA_FRAME_ANALYTICS_BREADCRUMB } from '../../breadcrumbs';
const breadcrumbs = [
ML_BREADCRUMB,
DATA_FRAME_ANALYTICS_BREADCRUMB,
{
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameExplorationLabel', {
defaultMessage: 'Data Frame Analytics',
defaultMessage: 'Exploration',
}),
href: '',
},

View file

@ -11,13 +11,14 @@ import { MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_management';
import { ML_BREADCRUMB } from '../../breadcrumbs';
import { ML_BREADCRUMB, DATA_FRAME_ANALYTICS_BREADCRUMB } from '../../breadcrumbs';
const breadcrumbs = [
ML_BREADCRUMB,
DATA_FRAME_ANALYTICS_BREADCRUMB,
{
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameListLabel', {
defaultMessage: 'Data Frame Analytics',
defaultMessage: 'Job Management',
}),
href: '',
},

View file

@ -21,7 +21,7 @@ import {
checkPermission,
} from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { Settings } from '../../../settings';
import { AnomalyDetectionSettingsContext, Settings } from '../../../settings';
import { ML_BREADCRUMB, SETTINGS } from '../../breadcrumbs';
const breadcrumbs = [ML_BREADCRUMB, SETTINGS];
@ -42,11 +42,17 @@ const PageWrapper: FC<PageProps> = ({ deps }) => {
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const canGetFilters = checkPermission('canGetFilters');
const canCreateFilter = checkPermission('canCreateFilter');
const canGetCalendars = checkPermission('canGetCalendars');
const canCreateCalendar = checkPermission('canCreateCalendar');
return (
<PageLoader context={context}>
<Settings canGetCalendars={canGetCalendars} canGetFilters={canGetFilters} />
<AnomalyDetectionSettingsContext.Provider
value={{ canGetFilters, canCreateFilter, canGetCalendars, canCreateCalendar }}
>
<Settings />
</AnomalyDetectionSettingsContext.Provider>
</PageLoader>
);
};

View file

@ -1,5 +1,11 @@
.mlSettingsPage {
.mlSettingsPage__header {
width: map-get($euiBreakpoints, 'xl');
margin-left: auto;
margin-right: auto;
}
.mlSettingsPage__content {
width: map-get($euiBreakpoints, 'xl');
margin-top: $euiSize;

View file

@ -0,0 +1,226 @@
/*
* 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, Fragment, useContext, useEffect, useState } from 'react';
import {
EuiBadge,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiPageContent,
EuiPageContentHeader,
EuiSpacer,
EuiText,
EuiTextColor,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { AnomalyDetectionSettingsContext } from './anomaly_detection_settings_context';
import { useNotifications } from '../contexts/kibana';
import { ml } from '../services/ml_api_service';
export const AnomalyDetectionSettings: FC = () => {
const [calendarsCount, setCalendarsCount] = useState(0);
const [filterListsCount, setFilterListsCount] = useState(0);
const { canGetFilters, canCreateFilter, canGetCalendars, canCreateCalendar } = useContext(
AnomalyDetectionSettingsContext
);
const { toasts } = useNotifications();
useEffect(() => {
loadSummaryStats();
}, []);
async function loadSummaryStats() {
// Obtain the counts of calendars and filter lists.
if (canGetCalendars === true) {
try {
const calendars = await ml.calendars();
setCalendarsCount(calendars.length);
} catch (e) {
toasts.addDanger(
i18n.translate('xpack.ml.settings.anomalyDetection.loadingCalendarsCountErrorMessage', {
defaultMessage: 'An error occurred obtaining the count of calendars',
})
);
}
}
if (canGetFilters === true) {
try {
const filterLists = await ml.filters.filtersStats();
setFilterListsCount(filterLists.length);
} catch (e) {
toasts.addDanger(
i18n.translate('xpack.ml.settings.anomalyDetection.loadingFilterListCountErrorMessage', {
defaultMessage: 'An error occurred obtaining the count of filter lists',
})
);
}
}
}
return (
<Fragment>
<EuiPageContent className="mlSettingsPage__content" horizontalPosition="center">
<EuiPageContentHeader>
<EuiTitle>
<h2>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.anomalyDetectionTitle"
defaultMessage="Anomaly Detection"
/>
</h2>
</EuiTitle>
</EuiPageContentHeader>
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem grow={5}>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.calendarsTitle"
defaultMessage="Calendars"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">
<EuiTextColor color="subdued">
<p>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.calendarsText"
defaultMessage="Calendars contain a list of scheduled events for which you do not want to generate anomalies, such as planned system outages or public holidays."
/>
</p>
</EuiTextColor>
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup alignItems="center">
{canGetCalendars && (
<EuiFlexItem grow={false} style={{ display: 'block' }}>
<EuiText>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.calendarsSummaryCount"
defaultMessage="You have {calendarsCountBadge} {calendarsCount, plural, one {calendar} other {calendars}}"
values={{
calendarsCountBadge: <EuiBadge>{calendarsCount}</EuiBadge>,
calendarsCount,
}}
/>
</EuiText>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="mlCalendarsMngButton"
flush="left"
size="l"
color="primary"
href="#/settings/calendars_list"
isDisabled={canGetCalendars === false}
>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.manageCalendarsLink"
defaultMessage="Manage"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="mlCalendarsCreateButton"
flush="left"
size="l"
color="primary"
href="#/settings/calendars_list/new_calendar"
isDisabled={canCreateCalendar === false}
>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.createCalendarLink"
defaultMessage="Create"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={5}>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.filterListsTitle"
defaultMessage="Filter Lists"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">
<EuiTextColor color="subdued">
<p>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.filterListsText"
defaultMessage=" Filter lists contain values that you can use to include or exclude events from the machine learning analysis."
/>
</p>
</EuiTextColor>
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup alignItems="center">
{canGetFilters && (
<EuiFlexItem grow={false}>
<EuiText>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.filterListsSummaryCount"
defaultMessage="You have {filterListsCountBadge} {filterListsCount, plural, one {filter list} other {filter lists}}"
values={{
filterListsCountBadge: <EuiBadge>{filterListsCount}</EuiBadge>,
filterListsCount,
}}
/>
</EuiText>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="mlFilterListsMngButton"
flush="left"
size="l"
color="primary"
href="#/settings/filter_lists"
isDisabled={canGetFilters === false}
>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.manageFilterListsLink"
defaultMessage="Manage"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="mlFilterListsCreateButton"
size="l"
color="primary"
href="#/settings/filter_lists/new_filter_list"
isDisabled={canCreateFilter === false}
>
<FormattedMessage
id="xpack.ml.settings.anomalyDetection.createFilterListsLink"
defaultMessage="Create"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
</Fragment>
);
};

View file

@ -0,0 +1,21 @@
/*
* 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 { createContext } from 'react';
export interface AnomalyDetectionSettingsContextValue {
canGetFilters: boolean;
canCreateFilter: boolean;
canGetCalendars: boolean;
canCreateCalendar: boolean;
}
export const AnomalyDetectionSettingsContext = createContext<AnomalyDetectionSettingsContextValue>({
canGetFilters: false,
canCreateFilter: false,
canGetCalendars: false,
canCreateCalendar: false,
});

View file

@ -150,7 +150,7 @@ exports[`CalendarForm Renders calendar form 1`] = `
grow={false}
>
<EuiButton
data-test-subj="ml_save_calendar_button"
data-test-subj="mlSaveCalendarButton"
fill={true}
isDisabled={true}
onClick={[MockFunction]}

View file

@ -221,7 +221,7 @@ export const CalendarForm = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="ml_save_calendar_button"
data-test-subj="mlSaveCalendarButton"
fill
onClick={isEdit ? onEdit : onCreate}
isDisabled={saveButtonDisabled}

View file

@ -133,7 +133,7 @@ exports[`EventsTable Renders events table with search bar 1`] = `
"filters": Array [],
"toolsRight": Array [
<EuiButton
data-test-subj="ml_new_event"
data-test-subj="mlNewEvent"
iconType="plusInCircle"
isDisabled={false}
onClick={[MockFunction]}
@ -146,7 +146,7 @@ exports[`EventsTable Renders events table with search bar 1`] = `
/>
</EuiButton>,
<EuiButton
data-test-subj="ml_import_events"
data-test-subj="mlImportEvents"
iconType="importAction"
isDisabled={false}
onClick={[MockFunction]}

View file

@ -91,7 +91,7 @@ export const EventsTable = ({
name: '',
render: (event) => (
<DeleteButton
data-test-subj="event_delete"
data-test-subj="mlEventDelete"
canDeleteCalendar={canDeleteCalendar}
onClick={() => {
onDeleteClick(event.event_id);
@ -106,7 +106,7 @@ export const EventsTable = ({
<EuiButton
isDisabled={canCreateCalendar === false}
key="ml_new_event"
data-test-subj="ml_new_event"
data-test-subj="mlNewEvent"
size="s"
iconType="plusInCircle"
onClick={showNewEventModal}
@ -119,7 +119,7 @@ export const EventsTable = ({
<EuiButton
isDisabled={canCreateCalendar === false}
key="ml_import_event"
data-test-subj="ml_import_events"
data-test-subj="mlImportEvents"
size="s"
iconType="importAction"
onClick={showImportModal}

View file

@ -51,7 +51,7 @@ describe('ImportModal', () => {
instance.setState(testState);
wrapper.update();
expect(wrapper.state('selectedEvents').length).toBe(2);
const deleteButton = wrapper.find('[data-test-subj="event_delete"]');
const deleteButton = wrapper.find('[data-test-subj="mlEventDelete"]');
const button = deleteButton.find('EuiButtonEmpty').first();
button.simulate('click');

View file

@ -117,7 +117,7 @@ describe('NewCalendar', () => {
test('Import modal shown on Import Events button click', () => {
const wrapper = mountWithIntl(<NewCalendar {...props} />);
const importButton = wrapper.find('[data-test-subj="ml_import_events"]');
const importButton = wrapper.find('[data-test-subj="mlImportEvents"]');
const button = importButton.find('EuiButton');
button.simulate('click');
@ -127,7 +127,7 @@ describe('NewCalendar', () => {
test('New event modal shown on New event button click', () => {
const wrapper = mountWithIntl(<NewCalendar {...props} />);
const importButton = wrapper.find('[data-test-subj="ml_new_event"]');
const importButton = wrapper.find('[data-test-subj="mlNewEvent"]');
const button = importButton.find('EuiButton');
button.simulate('click');
@ -154,7 +154,7 @@ describe('NewCalendar', () => {
const wrapper = mountWithIntl(<NewCalendar {...noCreateProps} />);
const buttons = wrapper.find('[data-test-subj="ml_save_calendar_button"]');
const buttons = wrapper.find('[data-test-subj="mlSaveCalendarButton"]');
const saveButton = buttons.find('EuiButton');
expect(saveButton.prop('isDisabled')).toBe(true);

View file

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

View file

@ -1,58 +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 { mountWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { Settings } from './settings';
jest.mock('../components/navigation_menu', () => ({
NavigationMenu: () => <div id="mockNavigationMenu" />,
}));
describe('Settings', () => {
test('Renders settings page with all buttons enabled.', () => {
const wrapper = mountWithIntl(<Settings canGetFilters={true} canGetCalendars={true} />);
const filterButton = wrapper
.find('[data-test-subj="ml_filter_lists_button"]')
.find('EuiButtonEmpty');
expect(filterButton.prop('isDisabled')).toBe(false);
const calendarButton = wrapper
.find('[data-test-subj="ml_calendar_mng_button"]')
.find('EuiButtonEmpty');
expect(calendarButton.prop('isDisabled')).toBe(false);
});
test('Filter Lists button disabled if canGetFilters is false', () => {
const wrapper = mountWithIntl(<Settings canGetFilters={false} canGetCalendars={true} />);
const filterButton = wrapper
.find('[data-test-subj="ml_filter_lists_button"]')
.find('EuiButtonEmpty');
expect(filterButton.prop('isDisabled')).toBe(true);
const calendarButton = wrapper
.find('[data-test-subj="ml_calendar_mng_button"]')
.find('EuiButtonEmpty');
expect(calendarButton.prop('isDisabled')).toBe(false);
});
test('Calendar management button disabled if canGetCalendars is false', () => {
const wrapper = mountWithIntl(<Settings canGetFilters={true} canGetCalendars={false} />);
const filterButton = wrapper
.find('[data-test-subj="ml_filter_lists_button"]')
.find('EuiButtonEmpty');
expect(filterButton.prop('isDisabled')).toBe(false);
const calendarButton = wrapper
.find('[data-test-subj="ml_calendar_mng_button"]')
.find('EuiButtonEmpty');
expect(calendarButton.prop('isDisabled')).toBe(true);
});
});

View file

@ -0,0 +1,76 @@
/*
* 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 { mountWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { AnomalyDetectionSettingsContext } from './anomaly_detection_settings_context';
import { Settings } from './settings';
jest.mock('../components/navigation_menu', () => ({
NavigationMenu: () => <div id="mockNavigationMenu" />,
}));
jest.mock('../contexts/kibana', () => ({
useNotifications: () => {
return {
toasts: { addDanger: jest.fn() },
};
},
}));
describe('Settings', () => {
function runCheckButtonsDisabledTest(
canGetFilters: boolean,
canCreateFilter: boolean,
canGetCalendars: boolean,
canCreateCalendar: boolean,
isFilterListsMngDisabled: boolean,
isFilterListCreateDisabled: boolean,
isCalendarsMngDisabled: boolean,
isCalendarCreateDisabled: boolean
) {
const wrapper = mountWithIntl(
<AnomalyDetectionSettingsContext.Provider
value={{ canGetFilters, canCreateFilter, canGetCalendars, canCreateCalendar }}
>
<Settings />
</AnomalyDetectionSettingsContext.Provider>
);
const filterMngButton = wrapper
.find('[data-test-subj="mlFilterListsMngButton"]')
.find('EuiButtonEmpty');
expect(filterMngButton.prop('isDisabled')).toBe(isFilterListsMngDisabled);
const filterCreateButton = wrapper
.find('[data-test-subj="mlFilterListsCreateButton"]')
.find('EuiButtonEmpty');
expect(filterCreateButton.prop('isDisabled')).toBe(isFilterListCreateDisabled);
const calendarMngButton = wrapper
.find('[data-test-subj="mlCalendarsMngButton"]')
.find('EuiButtonEmpty');
expect(calendarMngButton.prop('isDisabled')).toBe(isCalendarsMngDisabled);
const calendarCreateButton = wrapper
.find('[data-test-subj="mlCalendarsCreateButton"]')
.find('EuiButtonEmpty');
expect(calendarCreateButton.prop('isDisabled')).toBe(isCalendarCreateDisabled);
}
test('should render settings page with all buttons enabled when full user capabilities', () => {
runCheckButtonsDisabledTest(true, true, true, true, false, false, false, false);
});
test('should disable Filter Lists buttons if filters capabilities are false', () => {
runCheckButtonsDisabledTest(false, false, true, true, true, true, false, false);
});
test('should disable Calendars buttons if calendars capabilities are false', () => {
runCheckButtonsDisabledTest(true, true, false, false, false, false, true, true);
});
});

View file

@ -6,76 +6,30 @@
import React, { FC, Fragment } from 'react';
import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiPage,
EuiPageContentHeader,
EuiPageContent,
EuiPageBody,
EuiTitle,
} from '@elastic/eui';
import { EuiPage, EuiPageBody, EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { AnomalyDetectionSettings } from './anomaly_detection_settings';
import { NavigationMenu } from '../components/navigation_menu';
interface Props {
canGetFilters: boolean;
canGetCalendars: boolean;
}
export const Settings: FC<Props> = ({ canGetFilters, canGetCalendars }) => {
export const Settings: FC = () => {
return (
<Fragment>
<NavigationMenu tabId="settings" />
<EuiPage className="mlSettingsPage" data-test-subj="mlPageSettings">
<EuiPageBody className="mlSettingsPage__body">
<EuiPageContent className="mlSettingsPage__content" horizontalPosition="center">
<EuiPageContentHeader>
<EuiPageBody>
<EuiPageHeader className="mlSettingsPage__header">
<EuiPageHeaderSection>
<EuiTitle>
<h1>
<FormattedMessage
id="xpack.ml.settings.jobManagementTitle"
defaultMessage="Job Management"
/>
<FormattedMessage id="xpack.ml.settings.title" defaultMessage="Settings" />
</h1>
</EuiTitle>
</EuiPageContentHeader>
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="ml_calendar_mng_button"
size="l"
color="primary"
href="#/settings/calendars_list"
isDisabled={canGetCalendars === false}
>
<FormattedMessage
id="xpack.ml.settings.calendarManagementButtonLabel"
defaultMessage="Calendar management"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="ml_filter_lists_button"
size="l"
color="primary"
href="#/settings/filter_lists"
isDisabled={canGetFilters === false}
>
<FormattedMessage
id="xpack.ml.settings.filterListsButtonLabel"
defaultMessage="Filter Lists"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
</EuiPageHeaderSection>
</EuiPageHeader>
<AnomalyDetectionSettings />
</EuiPageBody>
</EuiPage>
</Fragment>

View file

@ -491,7 +491,6 @@ export class ForecastingModalUI extends Component {
<EuiButton
onClick={this.openModal}
isDisabled={isForecastingDisabled}
fill
data-test-subj="mlSingleMetricViewerButtonForecast"
>
<FormattedMessage

View file

@ -1107,11 +1107,7 @@ export class TimeSeriesExplorer extends React.Component {
let hasEmptyFieldValues = false;
return (
<TimeSeriesExplorerPage
dateFormatTz={dateFormatTz}
loading={loading}
resizeRef={this.resizeRef}
>
<TimeSeriesExplorerPage dateFormatTz={dateFormatTz} resizeRef={this.resizeRef}>
{fieldNamesWithEmptyValues.length > 0 && (
<EuiCallOut
className="single-metric-request-callout"

View file

@ -8,42 +8,36 @@ import React, { FC } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPage, EuiPageBody, EuiProgress, EuiScreenReaderOnly } from '@elastic/eui';
import {
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPage,
EuiPageBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { AnomalyResultsViewSelector } from '../components/anomaly_results_view_selector';
import { JobSelector } from '../components/job_selector';
import { NavigationMenu } from '../components/navigation_menu';
import { DatePickerWrapper } from '../components/navigation_menu/date_picker_wrapper';
interface TimeSeriesExplorerPageProps {
dateFormatTz: string;
loading?: boolean;
resizeRef?: any;
}
export const TimeSeriesExplorerPage: FC<TimeSeriesExplorerPageProps> = ({
children,
dateFormatTz,
loading,
resizeRef,
}) => {
return (
<>
<NavigationMenu tabId="timeseriesexplorer" />
{/* Show animated progress bar while loading */}
{loading === true && (
<EuiProgress className="mlTimeSeriesExplorerProgress" color="primary" size="xs" />
)}
{/* Show a progress bar with progress 0% when not loading.
If we'd just show no progress bar when not loading it would result in a flickering height effect. */}
{loading === false && (
<EuiProgress
className="mlTimeSeriesExplorerProgress"
value={0}
max={100}
color="primary"
size="xs"
/>
)}
<JobSelector dateFormatTz={dateFormatTz} singleSelection={true} timeseriesOnly={true} />
<NavigationMenu tabId="anomaly_detection" />
<div
className="ml-time-series-explorer"
ref={resizeRef}
@ -51,14 +45,36 @@ export const TimeSeriesExplorerPage: FC<TimeSeriesExplorerPageProps> = ({
>
<EuiPage style={{ background: 'none' }}>
<EuiPageBody>
<EuiScreenReaderOnly>
<h1>
<FormattedMessage
id="xpack.ml.timeSeriesExplorer.pageTitle"
defaultMessage="Single Metric Viewer"
/>
</h1>
</EuiScreenReaderOnly>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<AnomalyResultsViewSelector viewId="timeseriesexplorer" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle className="eui-textNoWrap">
<h1>
<FormattedMessage
id="xpack.ml.timeSeriesExplorer.pageTitle"
defaultMessage="Single Metric Viewer"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageHeaderSection>
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<DatePickerWrapper />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiHorizontalRule margin="none" />
<JobSelector dateFormatTz={dateFormatTz} singleSelection={true} timeseriesOnly={true} />
<EuiHorizontalRule margin="none" />
<EuiSpacer size="s" />
{children}
</EuiPageBody>
</EuiPage>

View file

@ -398,6 +398,9 @@
"core.ui.securityNavList.label": "セキュリティ",
"core.ui.welcomeErrorMessage": "Elastic Kibana が正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。",
"core.ui.welcomeMessage": "Elastic Kibana の読み込み中",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります",
"dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "最小化",
"dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "全画面",
"dashboard.addExistingVisualizationLinkText": "既存のユーザーを追加",
@ -2116,6 +2119,28 @@
"share.advancedSettings.csv.quoteValuesTitle": "CSV の値を引用",
"share.advancedSettings.csv.separatorText": "エクスポートされた値をこの文字列で区切ります",
"share.advancedSettings.csv.separatorTitle": "CSV セパレーター",
"share.contextMenu.embedCodeLabel": "埋め込みコード",
"share.contextMenu.embedCodePanelTitle": "埋め込みコード",
"share.contextMenu.permalinkPanelTitle": "パーマリンク",
"share.contextMenu.permalinksLabel": "パーマリンク",
"share.contextMenuTitle": "この {objectType} を共有",
"share.urlGenerators.error.createUrlFnProvided": "このジェネレーターは非推奨とマークされています。createUrl fn を付けないでください。",
"share.urlGenerators.error.migrateCalledNotDeprecated": "非推奨以外のジェネレーターで migrate を呼び出すことはできません。",
"share.urlGenerators.error.migrationFnGivenNotDeprecated": "移行機能を提供する場合、このジェネレーターに非推奨マークを付ける必要があります",
"share.urlGenerators.error.noCreateUrlFnProvided": "このジェネレーターには非推奨のマークがありません。createUrl fn を付けてください。",
"share.urlGenerators.error.noMigrationFnProvided": "アクセスリンクジェネレーターに非推奨マークが付いている場合、移行機能を提供する必要があります。",
"share.urlGenerators.errors.noGeneratorWithId": "{id} という ID のジェネレーターはありません",
"share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。",
"share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー",
"share.urlPanel.copyLinkButtonLabel": "リンクをコピー",
"share.urlPanel.generateLinkAsLabel": "名前を付けてリンクを生成",
"share.urlPanel.savedObjectDescription": "この URL を共有することで、他のユーザーがこの {objectType} の最も最近保存されたバージョンを読み込めるようになります。",
"share.urlPanel.savedObjectLabel": "保存されたオブジェクト",
"share.urlPanel.shortUrlHelpText": "互換性が最も高くなるよう、短いスナップショット URL を共有することをお勧めします。Internet Explorer は URL の長さに制限があり、一部の wiki やマークアップパーサーは長い完全なスナップショット URL に対応していませんが、短い URL は正常に動作するはずです。",
"share.urlPanel.shortUrlLabel": "短い URL",
"share.urlPanel.snapshotDescription": "スナップショット URL には、{objectType} の現在の状態がエンコードされています。保存された {objectType} への編集内容はこの URL には反映されません。.",
"share.urlPanel.snapshotLabel": "スナップショット",
"share.urlPanel.unableCreateShortUrlErrorMessage": "短い URL を作成できません。エラー: {errorMessage}",
"kbn.advancedSettings.darkModeText": "Kibana UI のダークモードを有効にします。この設定を適用するにはページの更新が必要です。",
"kbn.advancedSettings.darkModeTitle": "ダークモード",
"kbn.advancedSettings.dateFormat.dayOfWeekText": "週の初めの曜日を設定します",
@ -2167,9 +2192,37 @@
"kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "デフォルトの WMS プロパティ",
"visualizations.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。",
"visualizations.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります",
"visualizations.disabledLabVisualizationMessage": "ラボビジュアライゼーションを表示するには、高度な設定でラボモードをオンにしてください。",
"visualizations.disabledLabVisualizationTitle": "{title} はラボビジュアライゼーションです。",
"visualizations.displayName": "ビジュアライゼーション",
"visualizations.function.range.from.help": "範囲の開始",
"visualizations.function.range.help": "範囲オブジェクトを生成します",
"visualizations.function.range.to.help": "範囲の終了",
"visualizations.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名)",
"visualizations.function.visDimension.error.accessor": "入力された列名は無効です。",
"visualizations.function.visDimension.format.help": "フォーマット",
"visualizations.function.visDimension.formatParams.help": "フォーマットパラメーター",
"visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します",
"visualizations.functions.visualization.help": "シンプルなビジュアライゼーションです",
"visualizations.newVisWizard.betaDescription": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません",
"visualizations.newVisWizard.betaTitle": "ベータ",
"visualizations.newVisWizard.chooseSourceTitle": "ソースの選択",
"visualizations.newVisWizard.experimentalDescription": "このビジュアライゼーションは実験的なものです。デザインと導入は安定したビジュアライゼーションよりも完成度が低く、変更される可能性があります。",
"visualizations.newVisWizard.experimentalTitle": "実験的",
"visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。",
"visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング",
"visualizations.newVisWizard.helpText": "タイプを選択してビジュアライゼーションの作成を始めましょう。",
"visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。",
"visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}",
"visualizations.newVisWizard.resultsFound": "{resultCount} 個の{resultCount, plural, one {タイプ} other {タイプ} } が見つかりました",
"visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。",
"visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン",
"visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索",
"visualizations.newVisWizard.selectVisType": "ビジュアライゼーションのタイプを選択してください",
"visualizations.newVisWizard.title": "新規ビジュアライゼーション",
"visualizations.newVisWizard.visTypeAliasDescription": "Visualize 外で Kibana アプリケーションを開きます。",
"visualizations.newVisWizard.visTypeAliasTitle": "Kibana アプリケーション",
"visualizations.savedObjectName": "ビジュアライゼーション",
"kibana_legacy.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}",
"kibana_legacy.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストで接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。",
"kibana_legacy.notify.toaster.errorMessage": "エラー: {errorMessage}\n {errorStack}",
@ -2421,28 +2474,6 @@
"server.status.redTitle": "赤",
"server.status.uninitializedTitle": "アンインストールしました",
"server.status.yellowTitle": "黄色",
"share.contextMenu.embedCodeLabel": "埋め込みコード",
"share.contextMenu.embedCodePanelTitle": "埋め込みコード",
"share.contextMenu.permalinkPanelTitle": "パーマリンク",
"share.contextMenu.permalinksLabel": "パーマリンク",
"share.contextMenuTitle": "この {objectType} を共有",
"share.urlGenerators.error.createUrlFnProvided": "このジェネレーターは非推奨とマークされています。createUrl fn を付けないでください。",
"share.urlGenerators.error.migrateCalledNotDeprecated": "非推奨以外のジェネレーターで migrate を呼び出すことはできません。",
"share.urlGenerators.error.migrationFnGivenNotDeprecated": "移行機能を提供する場合、このジェネレーターに非推奨マークを付ける必要があります",
"share.urlGenerators.error.noCreateUrlFnProvided": "このジェネレーターには非推奨のマークがありません。createUrl fn を付けてください。",
"share.urlGenerators.error.noMigrationFnProvided": "アクセスリンクジェネレーターに非推奨マークが付いている場合、移行機能を提供する必要があります。",
"share.urlGenerators.errors.noGeneratorWithId": "{id} という ID のジェネレーターはありません",
"share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。",
"share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー",
"share.urlPanel.copyLinkButtonLabel": "リンクをコピー",
"share.urlPanel.generateLinkAsLabel": "名前を付けてリンクを生成",
"share.urlPanel.savedObjectDescription": "この URL を共有することで、他のユーザーがこの {objectType} の最も最近保存されたバージョンを読み込めるようになります。",
"share.urlPanel.savedObjectLabel": "保存されたオブジェクト",
"share.urlPanel.shortUrlHelpText": "互換性が最も高くなるよう、短いスナップショット URL を共有することをお勧めします。Internet Explorer は URL の長さに制限があり、一部の wiki やマークアップパーサーは長い完全なスナップショット URL に対応していませんが、短い URL は正常に動作するはずです。",
"share.urlPanel.shortUrlLabel": "短い URL",
"share.urlPanel.snapshotDescription": "スナップショット URL には、{objectType} の現在の状態がエンコードされています。保存された {objectType} への編集内容はこの URL には反映されません。.",
"share.urlPanel.snapshotLabel": "スナップショット",
"share.urlPanel.unableCreateShortUrlErrorMessage": "短い URL を作成できません。エラー: {errorMessage}",
"statusPage.loadStatus.serverIsDownErrorMessage": "サーバーステータスのリクエストに失敗しました。サーバーがダウンしている可能性があります。",
"statusPage.loadStatus.serverStatusCodeErrorMessage": "サーバーステータスのリクエストに失敗しました。ステータスコード: {responseStatus}",
"statusPage.metricsTiles.columns.heapTotalHeader": "ヒープ合計",
@ -3839,37 +3870,6 @@
"visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション",
"visTypeVislib.vislib.tooltip.fieldLabel": "フィールド",
"visTypeVislib.vislib.tooltip.valueLabel": "値",
"visualizations.disabledLabVisualizationMessage": "ラボビジュアライゼーションを表示するには、高度な設定でラボモードをオンにしてください。",
"visualizations.disabledLabVisualizationTitle": "{title} はラボビジュアライゼーションです。",
"visualizations.displayName": "ビジュアライゼーション",
"visualizations.function.range.from.help": "範囲の開始",
"visualizations.function.range.help": "範囲オブジェクトを生成します",
"visualizations.function.range.to.help": "範囲の終了",
"visualizations.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名)",
"visualizations.function.visDimension.error.accessor": "入力された列名は無効です。",
"visualizations.function.visDimension.format.help": "フォーマット",
"visualizations.function.visDimension.formatParams.help": "フォーマットパラメーター",
"visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します",
"visualizations.functions.visualization.help": "シンプルなビジュアライゼーションです",
"visualizations.newVisWizard.betaDescription": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません",
"visualizations.newVisWizard.betaTitle": "ベータ",
"visualizations.newVisWizard.chooseSourceTitle": "ソースの選択",
"visualizations.newVisWizard.experimentalDescription": "このビジュアライゼーションは実験的なものです。デザインと導入は安定したビジュアライゼーションよりも完成度が低く、変更される可能性があります。",
"visualizations.newVisWizard.experimentalTitle": "実験的",
"visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。",
"visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング",
"visualizations.newVisWizard.helpText": "タイプを選択してビジュアライゼーションの作成を始めましょう。",
"visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。",
"visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}",
"visualizations.newVisWizard.resultsFound": "{resultCount} 個の{resultCount, plural, one {タイプ} other {タイプ} } が見つかりました",
"visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。",
"visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン",
"visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索",
"visualizations.newVisWizard.selectVisType": "ビジュアライゼーションのタイプを選択してください",
"visualizations.newVisWizard.title": "新規ビジュアライゼーション",
"visualizations.newVisWizard.visTypeAliasDescription": "Visualize 外で Kibana アプリケーションを開きます。",
"visualizations.newVisWizard.visTypeAliasTitle": "Kibana アプリケーション",
"visualizations.savedObjectName": "ビジュアライゼーション",
"visualize.badge.readOnly.text": "読み取り専用",
"visualize.badge.readOnly.tooltip": "ビジュアライゼーションを保存できません",
"visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "indexPatternまたはsavedSearchIdが必要です",
@ -3969,6 +3969,8 @@
"xpack.uiActionsEnhanced.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle": "更新",
"xpack.uiActionsEnhanced.customizeTimeRange.modal.headerTitle": "パネルの時間範囲のカスタマイズ",
"xpack.uiActionsEnhanced.customizeTimeRangeMenuItem.displayName": "時間範囲のカスタマイズ",
"xpack.uiActionsEnhanced.components.DiscoverDrilldownConfig.chooseIndexPattern": "対象インデックスパターンを選択",
"xpack.uiActionsEnhanced.drilldown.goToDiscover": "Discoverに移動",
"xpack.alerts.alertNavigationRegistry.get.missingNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは登録されていません。",
"xpack.alerts.alertNavigationRegistry.register.duplicateDefaultError": "「{consumer}」内のデフォルトナビゲーションは既に登録されています。",
"xpack.alerts.alertNavigationRegistry.register.duplicateNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは既に登録されています。",
@ -10450,13 +10452,10 @@
"xpack.ml.models.jobValidation.validateJobObject.jobIsNotObjectErrorMessage": "無効な {invalidParamName}:オブジェクトでなければなりません。",
"xpack.ml.models.jobValidation.validateJobObject.timeFieldIsNotStringErrorMessage": "無効な {invalidParamName}:文字列でなければなりません。",
"xpack.ml.navMenu.anomalyDetectionTabLinkText": "異常検知",
"xpack.ml.navMenu.anomalyExplorerTabLinkText": "異常エクスプローラー",
"xpack.ml.navMenu.dataFrameAnalyticsTabLinkText": "分析",
"xpack.ml.navMenu.dataVisualizerTabLinkText": "データビジュアライザー",
"xpack.ml.navMenu.jobManagementTabLinkText": "ジョブ管理",
"xpack.ml.navMenu.overviewTabLinkText": "概要",
"xpack.ml.navMenu.settingsTabLinkText": "設定",
"xpack.ml.navMenu.singleMetricViewerTabLinkText": "シングルメトリックビューアー",
"xpack.ml.newJi18n(ob.recognize.jobsCreationFailed.resetButtonAriaLabel": "リセット",
"xpack.ml.newJob.page.createJob": "ジョブを作成",
"xpack.ml.newJob.recognize.advancedLabel": "高度な設定",
@ -10885,7 +10884,6 @@
"xpack.ml.settings.breadcrumbs.filterLists.createLabel": "作成",
"xpack.ml.settings.breadcrumbs.filterLists.editLabel": "編集",
"xpack.ml.settings.breadcrumbs.filterListsLabel": "フィルターリスト",
"xpack.ml.settings.calendarManagementButtonLabel": "カレンダー管理",
"xpack.ml.settings.calendars.listHeader.calendarsDescription": "カレンダーにはシステム停止日や祝日などの予定を含むことができ、異常検知から除外します。カレンダーは複数のジョブに割り当てることができます。{br}{learnMoreLink}",
"xpack.ml.settings.calendars.listHeader.calendarsDescription.learnMoreLinkText": "詳細",
"xpack.ml.settings.calendars.listHeader.calendarsListTotalCount": "合計 {totalCount}",
@ -10935,8 +10933,6 @@
"xpack.ml.settings.filterLists.table.noFiltersCreatedTitle": "フィルターが 1 つも作成されていません",
"xpack.ml.settings.filterLists.table.notInUseAriaLabel": "使用されていません",
"xpack.ml.settings.filterLists.toolbar.deleteItemButtonLabel": "アイテムを削除",
"xpack.ml.settings.filterListsButtonLabel": "フィルターリスト",
"xpack.ml.settings.jobManagementTitle": "ジョブ管理",
"xpack.ml.settingsBreadcrumbLabel": "設定",
"xpack.ml.singleMetricViewerPageLabel": "シングルメトリックビューアー",
"xpack.ml.stepDefineForm.invalidQuery": "無効なクエリ",
@ -16377,8 +16373,6 @@
"xpack.triggersActionsUI.timeUnits.secondLabel": "{timeValue, plural, one {秒} other {秒}}",
"xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage": "オブジェクトタイプ「{id}」は登録されていません。",
"xpack.triggersActionsUI.typeRegistry.register.duplicateObjectTypeErrorMessage": "オブジェクトタイプ「{id}」は既に登録されています。",
"xpack.uiActionsEnhanced.components.DiscoverDrilldownConfig.chooseIndexPattern": "対象インデックスパターンを選択",
"xpack.uiActionsEnhanced.drilldown.goToDiscover": "Discoverに移動",
"xpack.upgradeAssistant.appTitle": "{version} アップグレードアシスタント",
"xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "{snapshotRestoreDocsButton} でデータをバックアップします。",
"xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "API のスナップショットと復元",

View file

@ -398,6 +398,9 @@
"core.ui.securityNavList.label": "安全",
"core.ui.welcomeErrorMessage": "Elastic Kibana 未正确加载。检查服务器输出以了解详情。",
"core.ui.welcomeMessage": "正在加载 Elastic Kibana",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationTitle": "URL 过长Kibana 可能无法工作",
"dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "最小化",
"dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "全屏",
"dashboard.addExistingVisualizationLinkText": "将现有",
@ -2119,6 +2122,28 @@
"share.advancedSettings.csv.quoteValuesTitle": "使用引号引起 CSV 值",
"share.advancedSettings.csv.separatorText": "使用此字符串分隔导出的值",
"share.advancedSettings.csv.separatorTitle": "CSV 分隔符",
"share.contextMenu.embedCodeLabel": "嵌入代码",
"share.contextMenu.embedCodePanelTitle": "嵌入代码",
"share.contextMenu.permalinkPanelTitle": "固定链接",
"share.contextMenu.permalinksLabel": "固定链接",
"share.contextMenuTitle": "共享此 {objectType}",
"share.urlGenerators.error.createUrlFnProvided": "此生成器标记为已过时。切勿提供 createUrl 函数。",
"share.urlGenerators.error.migrateCalledNotDeprecated": "无法在非过时的生成器上调用迁移。",
"share.urlGenerators.error.migrationFnGivenNotDeprecated": "如果提供了迁移函数,则必须将此生成器标记为已过时",
"share.urlGenerators.error.noCreateUrlFnProvided": "此生成器未标记为已过时。请提供 createUrl 函数。",
"share.urlGenerators.error.noMigrationFnProvided": "如果访问链接生成器标记为已过时,则必须提供迁移函数。",
"share.urlGenerators.errors.noGeneratorWithId": "未找到 ID 为 {id} 的生成器",
"share.urlPanel.canNotShareAsSavedObjectHelpText": "只有保存 {objectType} 后,才能共享为已保存对象。",
"share.urlPanel.copyIframeCodeButtonLabel": "复制 iFrame 代码",
"share.urlPanel.copyLinkButtonLabel": "复制链接",
"share.urlPanel.generateLinkAsLabel": "将链接生成为",
"share.urlPanel.savedObjectDescription": "您可以将此 URL 共享给相关人员,以便他们可以加载此 {objectType} 最新的已保存版本。",
"share.urlPanel.savedObjectLabel": "已保存对象",
"share.urlPanel.shortUrlHelpText": "建议共享缩短的快照 URL以实现最大的兼容性。Internet Explorer 有 URL 长度限制,某些 wiki 和标记分析器无法很好地处理全长版本的快照 URL但应能很好地处理短 URL。",
"share.urlPanel.shortUrlLabel": "短 URL",
"share.urlPanel.snapshotDescription": "快照 URL 将 {objectType} 的当前状态编入 URL 自身之中。通过此 URL 无法看到对已保存 {objectType} 的编辑。",
"share.urlPanel.snapshotLabel": "快照",
"share.urlPanel.unableCreateShortUrlErrorMessage": "无法创建短 URL。错误{errorMessage}",
"kbn.advancedSettings.darkModeText": "为 Kibana UI 启用深色模式需要刷新页面,才能应用设置。",
"kbn.advancedSettings.darkModeTitle": "深色模式",
"kbn.advancedSettings.dateFormat.dayOfWeekText": "一周从哪一日开始?",
@ -2170,9 +2195,37 @@
"kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "默认 WMS 属性",
"visualizations.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。",
"visualizations.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationTitle": "URL 过长Kibana 可能无法工作",
"visualizations.disabledLabVisualizationMessage": "请在高级设置中打开实验室模式,以查看实验室可视化。",
"visualizations.disabledLabVisualizationTitle": "{title} 为实验室可视化。",
"visualizations.displayName": "可视化",
"visualizations.function.range.from.help": "范围起始",
"visualizations.function.range.help": "生成范围对象",
"visualizations.function.range.to.help": "范围结束",
"visualizations.function.visDimension.accessor.help": "要使用的数据集列(列索引或列名称)",
"visualizations.function.visDimension.error.accessor": "提供的列名称无效",
"visualizations.function.visDimension.format.help": "格式",
"visualizations.function.visDimension.formatParams.help": "格式参数",
"visualizations.function.visDimension.help": "生成 visConfig 维度对象",
"visualizations.functions.visualization.help": "简单可视化",
"visualizations.newVisWizard.betaDescription": "此可视化为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束",
"visualizations.newVisWizard.betaTitle": "公测版",
"visualizations.newVisWizard.chooseSourceTitle": "选择源",
"visualizations.newVisWizard.experimentalDescription": "这是实验性可视化。与稳定的可视化相比,其设计和实现均不够成熟,可能会随时发生更改。",
"visualizations.newVisWizard.experimentalTitle": "实验性",
"visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。",
"visualizations.newVisWizard.filterVisTypeAriaLabel": "筛留可视化类型",
"visualizations.newVisWizard.helpText": "通过为该可视化选择类型,来开始创建您的可视化。",
"visualizations.newVisWizard.helpTextAriaLabel": "通过为该可视化选择类型,来开始创建您的可视化。按 Esc 键关闭此模式。按 Tab 键继续。",
"visualizations.newVisWizard.newVisTypeTitle": "新建{visTypeName}",
"visualizations.newVisWizard.resultsFound": "找到了 {resultCount} 个{resultCount, plural, one {类型} other {类型} }",
"visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。",
"visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "索引模式",
"visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索",
"visualizations.newVisWizard.selectVisType": "选择可视化类型",
"visualizations.newVisWizard.title": "新建可视化",
"visualizations.newVisWizard.visTypeAliasDescription": "打开 Visualize 外部的 Kibana 应用程序。",
"visualizations.newVisWizard.visTypeAliasTitle": "Kibana 应用程序",
"visualizations.savedObjectName": "可视化",
"kibana_legacy.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}{errMessage}",
"kibana_legacy.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。",
"kibana_legacy.notify.toaster.errorMessage": "错误:{errorMessage}\n {errorStack}",
@ -2424,28 +2477,6 @@
"server.status.redTitle": "红",
"server.status.uninitializedTitle": "未初始化",
"server.status.yellowTitle": "黄",
"share.contextMenu.embedCodeLabel": "嵌入代码",
"share.contextMenu.embedCodePanelTitle": "嵌入代码",
"share.contextMenu.permalinkPanelTitle": "固定链接",
"share.contextMenu.permalinksLabel": "固定链接",
"share.contextMenuTitle": "共享此 {objectType}",
"share.urlGenerators.error.createUrlFnProvided": "此生成器标记为已过时。切勿提供 createUrl 函数。",
"share.urlGenerators.error.migrateCalledNotDeprecated": "无法在非过时的生成器上调用迁移。",
"share.urlGenerators.error.migrationFnGivenNotDeprecated": "如果提供了迁移函数,则必须将此生成器标记为已过时",
"share.urlGenerators.error.noCreateUrlFnProvided": "此生成器未标记为已过时。请提供 createUrl 函数。",
"share.urlGenerators.error.noMigrationFnProvided": "如果访问链接生成器标记为已过时,则必须提供迁移函数。",
"share.urlGenerators.errors.noGeneratorWithId": "未找到 ID 为 {id} 的生成器",
"share.urlPanel.canNotShareAsSavedObjectHelpText": "只有保存 {objectType} 后,才能共享为已保存对象。",
"share.urlPanel.copyIframeCodeButtonLabel": "复制 iFrame 代码",
"share.urlPanel.copyLinkButtonLabel": "复制链接",
"share.urlPanel.generateLinkAsLabel": "将链接生成为",
"share.urlPanel.savedObjectDescription": "您可以将此 URL 共享给相关人员,以便他们可以加载此 {objectType} 最新的已保存版本。",
"share.urlPanel.savedObjectLabel": "已保存对象",
"share.urlPanel.shortUrlHelpText": "建议共享缩短的快照 URL以实现最大的兼容性。Internet Explorer 有 URL 长度限制,某些 wiki 和标记分析器无法很好地处理全长版本的快照 URL但应能很好地处理短 URL。",
"share.urlPanel.shortUrlLabel": "短 URL",
"share.urlPanel.snapshotDescription": "快照 URL 将 {objectType} 的当前状态编入 URL 自身之中。通过此 URL 无法看到对已保存 {objectType} 的编辑。",
"share.urlPanel.snapshotLabel": "快照",
"share.urlPanel.unableCreateShortUrlErrorMessage": "无法创建短 URL。错误{errorMessage}",
"statusPage.loadStatus.serverIsDownErrorMessage": "无法请求服务器状态。也许您的服务器已关闭?",
"statusPage.loadStatus.serverStatusCodeErrorMessage": "无法使用状态代码 {responseStatus} 请求服务器状态",
"statusPage.metricsTiles.columns.heapTotalHeader": "堆总计",
@ -3842,37 +3873,6 @@
"visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}, 切换选项",
"visTypeVislib.vislib.tooltip.fieldLabel": "字段",
"visTypeVislib.vislib.tooltip.valueLabel": "值",
"visualizations.disabledLabVisualizationMessage": "请在高级设置中打开实验室模式,以查看实验室可视化。",
"visualizations.disabledLabVisualizationTitle": "{title} 为实验室可视化。",
"visualizations.displayName": "可视化",
"visualizations.function.range.from.help": "范围起始",
"visualizations.function.range.help": "生成范围对象",
"visualizations.function.range.to.help": "范围结束",
"visualizations.function.visDimension.accessor.help": "要使用的数据集列(列索引或列名称)",
"visualizations.function.visDimension.error.accessor": "提供的列名称无效",
"visualizations.function.visDimension.format.help": "格式",
"visualizations.function.visDimension.formatParams.help": "格式参数",
"visualizations.function.visDimension.help": "生成 visConfig 维度对象",
"visualizations.functions.visualization.help": "简单可视化",
"visualizations.newVisWizard.betaDescription": "此可视化为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束",
"visualizations.newVisWizard.betaTitle": "公测版",
"visualizations.newVisWizard.chooseSourceTitle": "选择源",
"visualizations.newVisWizard.experimentalDescription": "这是实验性可视化。与稳定的可视化相比,其设计和实现均不够成熟,可能会随时发生更改。",
"visualizations.newVisWizard.experimentalTitle": "实验性",
"visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。",
"visualizations.newVisWizard.filterVisTypeAriaLabel": "筛留可视化类型",
"visualizations.newVisWizard.helpText": "通过为该可视化选择类型,来开始创建您的可视化。",
"visualizations.newVisWizard.helpTextAriaLabel": "通过为该可视化选择类型,来开始创建您的可视化。按 Esc 键关闭此模式。按 Tab 键继续。",
"visualizations.newVisWizard.newVisTypeTitle": "新建{visTypeName}",
"visualizations.newVisWizard.resultsFound": "找到了 {resultCount} 个{resultCount, plural, one {类型} other {类型} }",
"visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。",
"visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "索引模式",
"visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索",
"visualizations.newVisWizard.selectVisType": "选择可视化类型",
"visualizations.newVisWizard.title": "新建可视化",
"visualizations.newVisWizard.visTypeAliasDescription": "打开 Visualize 外部的 Kibana 应用程序。",
"visualizations.newVisWizard.visTypeAliasTitle": "Kibana 应用程序",
"visualizations.savedObjectName": "可视化",
"visualize.badge.readOnly.text": "只读",
"visualize.badge.readOnly.tooltip": "无法保存可视化",
"visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "必须提供 indexPattern 或 savedSearchId",
@ -3972,6 +3972,8 @@
"xpack.uiActionsEnhanced.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle": "更新",
"xpack.uiActionsEnhanced.customizeTimeRange.modal.headerTitle": "定制面板时间范围",
"xpack.uiActionsEnhanced.customizeTimeRangeMenuItem.displayName": "定制时间范围",
"xpack.uiActionsEnhanced.components.DiscoverDrilldownConfig.chooseIndexPattern": "选择目标索引模式",
"xpack.uiActionsEnhanced.drilldown.goToDiscover": "前往 Discover示例",
"xpack.alerts.alertNavigationRegistry.get.missingNavigationError": "在“{consumer}”内针对告警类型“{alertType}”的导航未注册。",
"xpack.alerts.alertNavigationRegistry.register.duplicateDefaultError": "“{consumer}”内的默认导航已注册。",
"xpack.alerts.alertNavigationRegistry.register.duplicateNavigationError": "在“{consumer}”内针对告警类型“{alertType}”的导航已注册。",
@ -10454,13 +10456,10 @@
"xpack.ml.models.jobValidation.validateJobObject.jobIsNotObjectErrorMessage": "无效的 {invalidParamName}:需要是对象。",
"xpack.ml.models.jobValidation.validateJobObject.timeFieldIsNotStringErrorMessage": "无效的 {invalidParamName}:需要是字符串。",
"xpack.ml.navMenu.anomalyDetectionTabLinkText": "异常检测",
"xpack.ml.navMenu.anomalyExplorerTabLinkText": "Anomaly Explorer",
"xpack.ml.navMenu.dataFrameAnalyticsTabLinkText": "分析",
"xpack.ml.navMenu.dataVisualizerTabLinkText": "数据可视化工具",
"xpack.ml.navMenu.jobManagementTabLinkText": "作业管理",
"xpack.ml.navMenu.overviewTabLinkText": "概览",
"xpack.ml.navMenu.settingsTabLinkText": "设置",
"xpack.ml.navMenu.singleMetricViewerTabLinkText": "Single Metric Viewer",
"xpack.ml.newJi18n(ob.recognize.jobsCreationFailed.resetButtonAriaLabel": "重置",
"xpack.ml.newJob.page.createJob": "创建作业",
"xpack.ml.newJob.recognize.advancedLabel": "高级",
@ -10889,7 +10888,6 @@
"xpack.ml.settings.breadcrumbs.filterLists.createLabel": "创建",
"xpack.ml.settings.breadcrumbs.filterLists.editLabel": "编辑",
"xpack.ml.settings.breadcrumbs.filterListsLabel": "筛选列表",
"xpack.ml.settings.calendarManagementButtonLabel": "日历管理",
"xpack.ml.settings.calendars.listHeader.calendarsDescription": "日志包含不想生成异常的已计划事件列表,例如已计划系统中断或公共假期。同一日历可分配给多个作业。{br}{learnMoreLink}",
"xpack.ml.settings.calendars.listHeader.calendarsDescription.learnMoreLinkText": "了解详情",
"xpack.ml.settings.calendars.listHeader.calendarsListTotalCount": "合计 {totalCount}",
@ -10939,8 +10937,6 @@
"xpack.ml.settings.filterLists.table.noFiltersCreatedTitle": "未创建任何筛选",
"xpack.ml.settings.filterLists.table.notInUseAriaLabel": "未在使用",
"xpack.ml.settings.filterLists.toolbar.deleteItemButtonLabel": "删除项",
"xpack.ml.settings.filterListsButtonLabel": "筛选列表",
"xpack.ml.settings.jobManagementTitle": "作业管理",
"xpack.ml.settingsBreadcrumbLabel": "设置",
"xpack.ml.singleMetricViewerPageLabel": "Single Metric Viewer",
"xpack.ml.stepDefineForm.invalidQuery": "无效查询",
@ -16383,8 +16379,6 @@
"xpack.triggersActionsUI.timeUnits.secondLabel": "{timeValue, plural, one {秒} other {秒}}",
"xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage": "未注册对象类型“{id}”。",
"xpack.triggersActionsUI.typeRegistry.register.duplicateObjectTypeErrorMessage": "已注册对象类型“{id}”。",
"xpack.uiActionsEnhanced.components.DiscoverDrilldownConfig.chooseIndexPattern": "选择目标索引模式",
"xpack.uiActionsEnhanced.drilldown.goToDiscover": "前往 Discover示例",
"xpack.upgradeAssistant.appTitle": "{version} 升级助手",
"xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "使用 {snapshotRestoreDocsButton} 备份您的数据。",
"xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "快照和还原 API",

View file

@ -35,20 +35,12 @@ export default function ({ getService }: FtrProviderContext) {
await ml.jobManagement.assertCreateNewJobButtonExists();
});
it('loads the anomaly explorer page', async () => {
await ml.navigation.navigateToAnomalyExplorer();
await ml.anomalyExplorer.assertAnomalyExplorerEmptyListMessageExists();
});
it('loads the single metric viewer page', async () => {
await ml.navigation.navigateToSingleMetricViewer();
await ml.singleMetricViewer.assertSingleMetricViewerEmptyListMessageExsist();
});
it('loads the settings page', async () => {
await ml.navigation.navigateToSettings();
await ml.settings.assertSettingsCalendarLinkExists();
await ml.settings.assertSettingsFilterlistLinkExists();
await ml.settings.assertSettingsManageCalendarsLinkExists();
await ml.settings.assertSettingsCreateCalendarLinkExists();
await ml.settings.assertSettingsManageFilterListsLinkExists();
await ml.settings.assertSettingsCreateFilterListLinkExists();
});
it('loads the data frame analytics page', async () => {

View file

@ -59,42 +59,22 @@ export function MachineLearningNavigationProvider({
async navigateToAnomalyDetection() {
await this.navigateToArea('~mlMainTab & ~anomalyDetection', 'mlPageJobManagement');
await this.assertTabsExist('mlSubTab', [
'jobManagement',
'anomalyExplorer',
'singleMetricViewer',
'settings',
]);
},
async navigateToDataFrameAnalytics() {
await this.navigateToArea('~mlMainTab & ~dataFrameAnalytics', 'mlPageDataFrameAnalytics');
await this.assertTabsExist('mlSubTab', []);
},
async navigateToDataVisualizer() {
await this.navigateToArea('~mlMainTab & ~dataVisualizer', 'mlPageDataVisualizerSelector');
await this.assertTabsExist('mlSubTab', []);
},
async navigateToJobManagement() {
await this.navigateToAnomalyDetection();
await this.navigateToArea('~mlSubTab & ~jobManagement', 'mlPageJobManagement');
},
async navigateToAnomalyExplorer() {
await this.navigateToAnomalyDetection();
await this.navigateToArea('~mlSubTab & ~anomalyExplorer', 'mlPageAnomalyExplorer');
},
async navigateToSingleMetricViewer() {
await this.navigateToAnomalyDetection();
await this.navigateToArea('~mlSubTab & ~singleMetricViewer', 'mlPageSingleMetricViewer');
},
async navigateToSettings() {
await this.navigateToAnomalyDetection();
await this.navigateToArea('~mlSubTab & ~settings', 'mlPageSettings');
await this.navigateToArea('~mlMainTab & ~settings', 'mlPageSettings');
},
};
}

View file

@ -10,12 +10,20 @@ export function MachineLearningSettingsProvider({ getService }: FtrProviderConte
const testSubjects = getService('testSubjects');
return {
async assertSettingsCalendarLinkExists() {
await testSubjects.existOrFail('ml_calendar_mng_button');
async assertSettingsManageCalendarsLinkExists() {
await testSubjects.existOrFail('mlCalendarsMngButton');
},
async assertSettingsFilterlistLinkExists() {
await testSubjects.existOrFail('ml_filter_lists_button');
async assertSettingsCreateCalendarLinkExists() {
await testSubjects.existOrFail('mlCalendarsCreateButton');
},
async assertSettingsManageFilterListsLinkExists() {
await testSubjects.existOrFail('mlFilterListsMngButton');
},
async assertSettingsCreateFilterListLinkExists() {
await testSubjects.existOrFail('mlFilterListsCreateButton');
},
};
}