[ML] Fix job selection flyout (#79850)
* [ML] fix job selection flyout * [ML] hide time range column * [ML] show callout when no AD jobs presented * [ML] close job selector flyout on navigating away from the dashboard * [ML] add Create job button * [ML] fix mocks * [ML] add unit test for callout
This commit is contained in:
parent
18e8b63430
commit
14b02a3506
|
@ -6,14 +6,18 @@
|
|||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { EuiButtonEmpty, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { EuiButtonEmpty, EuiFlexItem, EuiFlexGroup, EuiFlyout } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Dictionary } from '../../../../common/types/common';
|
||||
import { useUrlState } from '../../util/url_state';
|
||||
// @ts-ignore
|
||||
import { IdBadges } from './id_badges/index';
|
||||
import { BADGE_LIMIT, JobSelectorFlyout, JobSelectorFlyoutProps } from './job_selector_flyout';
|
||||
import {
|
||||
BADGE_LIMIT,
|
||||
JobSelectorFlyoutContent,
|
||||
JobSelectorFlyoutProps,
|
||||
} from './job_selector_flyout';
|
||||
import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs';
|
||||
|
||||
interface GroupObj {
|
||||
|
@ -163,16 +167,18 @@ export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: J
|
|||
function renderFlyout() {
|
||||
if (isFlyoutVisible) {
|
||||
return (
|
||||
<JobSelectorFlyout
|
||||
dateFormatTz={dateFormatTz}
|
||||
timeseriesOnly={timeseriesOnly}
|
||||
singleSelection={singleSelection}
|
||||
selectedIds={selectedIds}
|
||||
onSelectionConfirmed={applySelection}
|
||||
onJobsFetched={setMaps}
|
||||
onFlyoutClose={closeFlyout}
|
||||
maps={maps}
|
||||
/>
|
||||
<EuiFlyout onClose={closeFlyout}>
|
||||
<JobSelectorFlyoutContent
|
||||
dateFormatTz={dateFormatTz}
|
||||
timeseriesOnly={timeseriesOnly}
|
||||
singleSelection={singleSelection}
|
||||
selectedIds={selectedIds}
|
||||
onSelectionConfirmed={applySelection}
|
||||
onJobsFetched={setMaps}
|
||||
onFlyoutClose={closeFlyout}
|
||||
maps={maps}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,13 @@ import {
|
|||
EuiButtonEmpty,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiSwitch,
|
||||
EuiTitle,
|
||||
EuiResizeObserver,
|
||||
EuiProgress,
|
||||
} from '@elastic/eui';
|
||||
import { NewSelectionIdBadges } from './new_selection_id_badges';
|
||||
// @ts-ignore
|
||||
|
@ -39,7 +40,6 @@ export interface JobSelectorFlyoutProps {
|
|||
newSelection?: string[];
|
||||
onFlyoutClose: () => void;
|
||||
onJobsFetched?: (maps: JobSelectionMaps) => void;
|
||||
onSelectionChange?: (newSelection: string[]) => void;
|
||||
onSelectionConfirmed: (payload: {
|
||||
newSelection: string[];
|
||||
jobIds: string[];
|
||||
|
@ -52,13 +52,12 @@ export interface JobSelectorFlyoutProps {
|
|||
withTimeRangeSelector?: boolean;
|
||||
}
|
||||
|
||||
export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
|
||||
export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({
|
||||
dateFormatTz,
|
||||
selectedIds = [],
|
||||
singleSelection,
|
||||
timeseriesOnly,
|
||||
onJobsFetched,
|
||||
onSelectionChange,
|
||||
onSelectionConfirmed,
|
||||
onFlyoutClose,
|
||||
maps,
|
||||
|
@ -73,6 +72,7 @@ export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
|
|||
|
||||
const [newSelection, setNewSelection] = useState(selectedIds);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showAllBadges, setShowAllBadges] = useState(false);
|
||||
const [applyTimeRange, setApplyTimeRange] = useState(true);
|
||||
const [jobs, setJobs] = useState<MlJobWithTimeRange[]>([]);
|
||||
|
@ -80,7 +80,7 @@ export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
|
|||
const [ganttBarWidth, setGanttBarWidth] = useState(DEFAULT_GANTT_BAR_WIDTH);
|
||||
const [jobGroupsMaps, setJobGroupsMaps] = useState(maps);
|
||||
|
||||
const flyoutEl = useRef<{ flyout: HTMLElement }>(null);
|
||||
const flyoutEl = useRef<HTMLElement | null>(null);
|
||||
|
||||
function applySelection() {
|
||||
// allNewSelection will be a list of all job ids (including those from groups) selected from the table
|
||||
|
@ -131,19 +131,19 @@ export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
|
|||
// Wrap handleResize in useCallback as it is a dependency for useEffect on line 131 below.
|
||||
// Not wrapping it would cause this dependency to change on every render
|
||||
const handleResize = useCallback(() => {
|
||||
if (jobs.length > 0 && flyoutEl && flyoutEl.current && flyoutEl.current.flyout) {
|
||||
// get all cols in flyout table
|
||||
const tableHeaderCols: NodeListOf<HTMLElement> = flyoutEl.current.flyout.querySelectorAll(
|
||||
'table thead th'
|
||||
);
|
||||
// get the width of the last col
|
||||
const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16;
|
||||
const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth);
|
||||
setJobs(normalizedJobs);
|
||||
const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs);
|
||||
setGroups(updatedGroups);
|
||||
setGanttBarWidth(derivedWidth);
|
||||
}
|
||||
if (jobs.length === 0 || !flyoutEl.current) return;
|
||||
|
||||
// get all cols in flyout table
|
||||
const tableHeaderCols: NodeListOf<HTMLElement> = flyoutEl.current.querySelectorAll(
|
||||
'table thead th'
|
||||
);
|
||||
// get the width of the last col
|
||||
const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16;
|
||||
const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth);
|
||||
setJobs(normalizedJobs);
|
||||
const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs);
|
||||
setGroups(updatedGroups);
|
||||
setGanttBarWidth(derivedWidth);
|
||||
}, [dateFormatTz, jobs]);
|
||||
|
||||
// Fetch jobs list on flyout open
|
||||
|
@ -172,119 +172,124 @@ export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
|
|||
}),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Ensure ganttBar width gets calculated on resize
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, [handleResize]);
|
||||
|
||||
useEffect(() => {
|
||||
handleResize();
|
||||
}, [handleResize, jobs]);
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
// @ts-ignore
|
||||
ref={flyoutEl}
|
||||
onClose={onFlyoutClose}
|
||||
aria-labelledby="jobSelectorFlyout"
|
||||
data-test-subj="mlFlyoutJobSelector"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id="flyoutTitle">
|
||||
{i18n.translate('xpack.ml.jobSelector.flyoutTitle', {
|
||||
defaultMessage: 'Job selection',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody className="mlJobSelectorFlyoutBody">
|
||||
<EuiFlexGroup direction="column" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup wrap responsive={false} gutterSize="xs" alignItems="center">
|
||||
<NewSelectionIdBadges
|
||||
limit={BADGE_LIMIT}
|
||||
maps={jobGroupsMaps}
|
||||
newSelection={newSelection}
|
||||
onDeleteClick={removeId}
|
||||
onLinkClick={() => setShowAllBadges(!showAllBadges)}
|
||||
showAllBadges={showAllBadges}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="row" justifyContent="spaceBetween" responsive={false}>
|
||||
<EuiResizeObserver onResize={handleResize}>
|
||||
{(resizeRef) => (
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
ref={(e) => {
|
||||
flyoutEl.current = e;
|
||||
resizeRef(e);
|
||||
}}
|
||||
aria-labelledby="jobSelectorFlyout"
|
||||
data-test-subj="mlFlyoutJobSelector"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id="flyoutTitle">
|
||||
{i18n.translate('xpack.ml.jobSelector.flyoutTitle', {
|
||||
defaultMessage: 'Job selection',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody className="mlJobSelectorFlyoutBody">
|
||||
{isLoading ? (
|
||||
<EuiProgress size="xs" color="accent" />
|
||||
) : (
|
||||
<>
|
||||
<EuiFlexGroup direction="column" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup wrap responsive={false} gutterSize="xs" alignItems="center">
|
||||
<NewSelectionIdBadges
|
||||
limit={BADGE_LIMIT}
|
||||
maps={jobGroupsMaps}
|
||||
newSelection={newSelection}
|
||||
onDeleteClick={removeId}
|
||||
onLinkClick={() => setShowAllBadges(!showAllBadges)}
|
||||
showAllBadges={showAllBadges}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="row" justifyContent="spaceBetween" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{!singleSelection && newSelection.length > 0 && (
|
||||
<EuiButtonEmpty
|
||||
onClick={clearSelection}
|
||||
size="xs"
|
||||
data-test-subj="mlFlyoutJobSelectorButtonClearSelection"
|
||||
>
|
||||
{i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', {
|
||||
defaultMessage: 'Clear all',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{withTimeRangeSelector && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'xpack.ml.jobSelector.applyTimerangeSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Apply time range',
|
||||
}
|
||||
)}
|
||||
checked={applyTimeRange}
|
||||
onChange={toggleTimerangeSwitch}
|
||||
data-test-subj="mlFlyoutJobSelectorSwitchApplyTimeRange"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<JobSelectorTable
|
||||
jobs={jobs}
|
||||
ganttBarWidth={ganttBarWidth}
|
||||
groupsList={groups}
|
||||
onSelection={handleNewSelection}
|
||||
selectedIds={newSelection}
|
||||
singleSelection={singleSelection}
|
||||
timeseriesOnly={timeseriesOnly}
|
||||
withTimeRangeSelector={withTimeRangeSelector}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
{!singleSelection && newSelection.length > 0 && (
|
||||
<EuiButtonEmpty
|
||||
onClick={clearSelection}
|
||||
size="xs"
|
||||
data-test-subj="mlFlyoutJobSelectorButtonClearSelection"
|
||||
>
|
||||
{i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', {
|
||||
defaultMessage: 'Clear all',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
<EuiButton
|
||||
onClick={applySelection}
|
||||
fill
|
||||
isDisabled={newSelection.length === 0}
|
||||
data-test-subj="mlFlyoutJobSelectorButtonApply"
|
||||
>
|
||||
{i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', {
|
||||
defaultMessage: 'Apply',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
onClick={onFlyoutClose}
|
||||
data-test-subj="mlFlyoutJobSelectorButtonClose"
|
||||
>
|
||||
{i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', {
|
||||
defaultMessage: 'Close',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{withTimeRangeSelector && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.ml.jobSelector.applyTimerangeSwitchLabel', {
|
||||
defaultMessage: 'Apply time range',
|
||||
})}
|
||||
checked={applyTimeRange}
|
||||
onChange={toggleTimerangeSwitch}
|
||||
data-test-subj="mlFlyoutJobSelectorSwitchApplyTimeRange"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlexGroup>
|
||||
<JobSelectorTable
|
||||
jobs={jobs}
|
||||
ganttBarWidth={ganttBarWidth}
|
||||
groupsList={groups}
|
||||
onSelection={handleNewSelection}
|
||||
selectedIds={newSelection}
|
||||
singleSelection={singleSelection}
|
||||
timeseriesOnly={timeseriesOnly}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={applySelection}
|
||||
fill
|
||||
isDisabled={newSelection.length === 0}
|
||||
data-test-subj="mlFlyoutJobSelectorButtonApply"
|
||||
>
|
||||
{i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', {
|
||||
defaultMessage: 'Apply',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
onClick={onFlyoutClose}
|
||||
data-test-subj="mlFlyoutJobSelectorButtonClose"
|
||||
>
|
||||
{i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', {
|
||||
defaultMessage: 'Close',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</EuiResizeObserver>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,11 +9,22 @@ import { PropTypes } from 'prop-types';
|
|||
import { CustomSelectionTable } from '../../custom_selection_table';
|
||||
import { JobSelectorBadge } from '../job_selector_badge';
|
||||
import { TimeRangeBar } from '../timerange_bar';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiTabbedContent } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTabbedContent,
|
||||
EuiCallOut,
|
||||
EuiButton,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { LEFT_ALIGNMENT, CENTER_ALIGNMENT, SortableProperties } from '@elastic/eui/lib/services';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useMlKibana } from '../../../contexts/kibana';
|
||||
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
|
||||
import { PLUGIN_ID } from '../../../../../common/constants/app';
|
||||
|
||||
const JOB_FILTER_FIELDS = ['job_id', 'groups'];
|
||||
const GROUP_FILTER_FIELDS = ['id'];
|
||||
|
@ -26,10 +37,17 @@ export function JobSelectorTable({
|
|||
selectedIds,
|
||||
singleSelection,
|
||||
timeseriesOnly,
|
||||
withTimeRangeSelector,
|
||||
}) {
|
||||
const [sortableProperties, setSortableProperties] = useState();
|
||||
const [currentTab, setCurrentTab] = useState('Jobs');
|
||||
|
||||
const {
|
||||
services: {
|
||||
application: { navigateToApp },
|
||||
},
|
||||
} = useMlKibana();
|
||||
|
||||
useEffect(() => {
|
||||
let sortablePropertyItems = [];
|
||||
let defaultSortProperty = 'job_id';
|
||||
|
@ -125,15 +143,18 @@ export function JobSelectorTable({
|
|||
<JobSelectorBadge key={`${group}-key`} id={group} isGroup={true} />
|
||||
)),
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (withTimeRangeSelector) {
|
||||
columns.push({
|
||||
label: 'time range',
|
||||
id: 'timerange',
|
||||
alignment: LEFT_ALIGNMENT,
|
||||
render: ({ timeRange = {}, isRunning }) => (
|
||||
<TimeRangeBar timerange={timeRange} isRunning={isRunning} ganttBarWidth={ganttBarWidth} />
|
||||
),
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
const filters = [
|
||||
{
|
||||
|
@ -190,15 +211,18 @@ export function JobSelectorTable({
|
|||
alignment: CENTER_ALIGNMENT,
|
||||
render: ({ jobIds = [] }) => jobIds.length,
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
if (withTimeRangeSelector) {
|
||||
groupColumns.push({
|
||||
label: 'time range',
|
||||
id: 'timerange',
|
||||
alignment: LEFT_ALIGNMENT,
|
||||
render: ({ timeRange = {} }) => (
|
||||
<TimeRangeBar timerange={timeRange} ganttBarWidth={ganttBarWidth} />
|
||||
),
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomSelectionTable
|
||||
|
@ -225,9 +249,32 @@ export function JobSelectorTable({
|
|||
);
|
||||
}
|
||||
|
||||
const navigateToWizard = async () => {
|
||||
await navigateToApp(PLUGIN_ID, { path: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB });
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{jobs.length === 0 && <EuiLoadingSpinner size="l" />}
|
||||
{jobs.length === 0 && (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.jobSelector.noJobsFoundTitle"
|
||||
defaultMessage="No anomaly detection jobs found"
|
||||
/>
|
||||
}
|
||||
iconType="iInCircle"
|
||||
>
|
||||
<EuiText textAlign="center">
|
||||
<EuiButton color="primary" onClick={navigateToWizard}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.jobSelector.createJobButtonLabel"
|
||||
defaultMessage="Create job"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
{jobs.length !== 0 && singleSelection === true && renderJobsTable()}
|
||||
{jobs.length !== 0 && !singleSelection && renderTabs()}
|
||||
</Fragment>
|
||||
|
@ -242,4 +289,5 @@ JobSelectorTable.propTypes = {
|
|||
selectedIds: PropTypes.array.isRequired,
|
||||
singleSelection: PropTypes.bool,
|
||||
timeseriesOnly: PropTypes.bool,
|
||||
withTimeRangeSelector: PropTypes.bool,
|
||||
};
|
||||
|
|
|
@ -5,14 +5,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { fireEvent, render } from '@testing-library/react'; // eslint-disable-line import/no-extraneous-dependencies
|
||||
import { JobSelectorTable } from './job_selector_table';
|
||||
|
||||
jest.mock('../../../services/job_service', () => ({
|
||||
mlJobService: {
|
||||
getJob: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('../../../contexts/kibana');
|
||||
|
||||
const props = {
|
||||
ganttBarWidth: 299,
|
||||
|
@ -124,6 +121,19 @@ describe('JobSelectorTable', () => {
|
|||
});
|
||||
|
||||
describe('Not Single Selection', () => {
|
||||
test('renders callout when no jobs provided', () => {
|
||||
const propsEmptyJobs = { ...props, jobs: [], groupsList: [] };
|
||||
const { getByText } = render(
|
||||
<I18nProvider>
|
||||
<JobSelectorTable {...propsEmptyJobs} />
|
||||
</I18nProvider>
|
||||
);
|
||||
const calloutMessage = getByText('No anomaly detection jobs found');
|
||||
const createJobButton = getByText('Create job');
|
||||
expect(createJobButton).toBeDefined();
|
||||
expect(calloutMessage).toBeDefined();
|
||||
});
|
||||
|
||||
test('renders tabs when not singleSelection', () => {
|
||||
const { getAllByRole } = render(<JobSelectorTable {...props} />);
|
||||
const tabs = getAllByRole('tab');
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { useMlKibana } from './kibana_context';
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 const useMlKibana = jest.fn(() => {
|
||||
return {
|
||||
services: {
|
||||
application: {
|
||||
navigateToApp: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
|
@ -7,25 +7,33 @@
|
|||
import React from 'react';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import moment from 'moment';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { from } from 'rxjs';
|
||||
import { VIEW_BY_JOB_LABEL } from '../../application/explorer/explorer_constants';
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
toMountPoint,
|
||||
} from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { AnomalySwimlaneInitializer } from './anomaly_swimlane_initializer';
|
||||
import { JobSelectorFlyout } from '../../application/components/job_selector/job_selector_flyout';
|
||||
import { JobSelectorFlyoutContent } from '../../application/components/job_selector/job_selector_flyout';
|
||||
import { AnomalyDetectorService } from '../../application/services/anomaly_detector_service';
|
||||
import { getInitialGroupsMap } from '../../application/components/job_selector/job_selector';
|
||||
import { getDefaultPanelTitle } from './anomaly_swimlane_embeddable';
|
||||
import { getMlGlobalServices } from '../../application/app';
|
||||
import { HttpService } from '../../application/services/http_service';
|
||||
import { DashboardConstants } from '../../../../../../src/plugins/dashboard/public';
|
||||
import { AnomalySwimlaneEmbeddableInput } from '..';
|
||||
|
||||
export async function resolveAnomalySwimlaneUserInput(
|
||||
coreStart: CoreStart,
|
||||
input?: AnomalySwimlaneEmbeddableInput
|
||||
): Promise<Partial<AnomalySwimlaneEmbeddableInput>> {
|
||||
const { http, uiSettings, overlays } = coreStart;
|
||||
const {
|
||||
http,
|
||||
uiSettings,
|
||||
overlays,
|
||||
application: { currentAppId$ },
|
||||
} = coreStart;
|
||||
|
||||
const anomalyDetectorService = new AnomalyDetectorService(new HttpService(http));
|
||||
|
||||
|
@ -43,7 +51,7 @@ export async function resolveAnomalySwimlaneUserInput(
|
|||
const flyoutSession = coreStart.overlays.openFlyout(
|
||||
toMountPoint(
|
||||
<KibanaContextProvider services={{ ...coreStart, mlServices: getMlGlobalServices(http) }}>
|
||||
<JobSelectorFlyout
|
||||
<JobSelectorFlyoutContent
|
||||
selectedIds={selectedIds}
|
||||
withTimeRangeSelector={false}
|
||||
dateFormatTz={dateFormatTz}
|
||||
|
@ -87,7 +95,15 @@ export async function resolveAnomalySwimlaneUserInput(
|
|||
),
|
||||
{
|
||||
'data-test-subj': 'mlAnomalySwimlaneEmbeddable',
|
||||
ownFocus: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Close the flyout when user navigates out of the dashboard plugin
|
||||
currentAppId$.pipe(takeUntil(from(flyoutSession.onClose))).subscribe((appId) => {
|
||||
if (appId !== DashboardConstants.DASHBOARDS_ID) {
|
||||
flyoutSession.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue