Skip to content

Commit

Permalink
[ML] Fix job selection flyout (elastic#79850)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
darnautov authored and Dmitrii Arnautov committed Oct 8, 2020
1 parent 7a9f913 commit 9213891
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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>
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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[];
Expand All @@ -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,
Expand All @@ -73,14 +72,15 @@ 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[]>([]);
const [groups, setGroups] = useState<any[]>([]);
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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>
</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>
</EuiFlyoutFooter>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
)}
</EuiResizeObserver>
);
};
Loading

0 comments on commit 9213891

Please sign in to comment.