Skip to content

Commit aa9116c

Browse files
committed
[ML] fix job selection flyout
1 parent 718702c commit aa9116c

File tree

3 files changed

+136
-139
lines changed

3 files changed

+136
-139
lines changed

x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66

77
import React, { useState, useEffect } from 'react';
88

9-
import { EuiButtonEmpty, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
9+
import { EuiButtonEmpty, EuiFlexItem, EuiFlexGroup, EuiFlyout } from '@elastic/eui';
1010
import { i18n } from '@kbn/i18n';
1111

1212
import { Dictionary } from '../../../../common/types/common';
1313
import { useUrlState } from '../../util/url_state';
1414
// @ts-ignore
1515
import { IdBadges } from './id_badges/index';
16-
import { BADGE_LIMIT, JobSelectorFlyout, JobSelectorFlyoutProps } from './job_selector_flyout';
16+
import {
17+
BADGE_LIMIT,
18+
JobSelectorFlyoutContent,
19+
JobSelectorFlyoutProps,
20+
} from './job_selector_flyout';
1721
import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs';
1822

1923
interface GroupObj {
@@ -163,16 +167,18 @@ export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: J
163167
function renderFlyout() {
164168
if (isFlyoutVisible) {
165169
return (
166-
<JobSelectorFlyout
167-
dateFormatTz={dateFormatTz}
168-
timeseriesOnly={timeseriesOnly}
169-
singleSelection={singleSelection}
170-
selectedIds={selectedIds}
171-
onSelectionConfirmed={applySelection}
172-
onJobsFetched={setMaps}
173-
onFlyoutClose={closeFlyout}
174-
maps={maps}
175-
/>
170+
<EuiFlyout onClose={closeFlyout}>
171+
<JobSelectorFlyoutContent
172+
dateFormatTz={dateFormatTz}
173+
timeseriesOnly={timeseriesOnly}
174+
singleSelection={singleSelection}
175+
selectedIds={selectedIds}
176+
onSelectionConfirmed={applySelection}
177+
onJobsFetched={setMaps}
178+
onFlyoutClose={closeFlyout}
179+
maps={maps}
180+
/>
181+
</EuiFlyout>
176182
);
177183
}
178184
}

x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx

Lines changed: 115 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import {
1111
EuiButtonEmpty,
1212
EuiFlexItem,
1313
EuiFlexGroup,
14-
EuiFlyout,
1514
EuiFlyoutBody,
1615
EuiFlyoutFooter,
1716
EuiFlyoutHeader,
1817
EuiSwitch,
1918
EuiTitle,
19+
EuiResizeObserver,
2020
} from '@elastic/eui';
2121
import { NewSelectionIdBadges } from './new_selection_id_badges';
2222
// @ts-ignore
@@ -39,7 +39,6 @@ export interface JobSelectorFlyoutProps {
3939
newSelection?: string[];
4040
onFlyoutClose: () => void;
4141
onJobsFetched?: (maps: JobSelectionMaps) => void;
42-
onSelectionChange?: (newSelection: string[]) => void;
4342
onSelectionConfirmed: (payload: {
4443
newSelection: string[];
4544
jobIds: string[];
@@ -52,13 +51,12 @@ export interface JobSelectorFlyoutProps {
5251
withTimeRangeSelector?: boolean;
5352
}
5453

55-
export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
54+
export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({
5655
dateFormatTz,
5756
selectedIds = [],
5857
singleSelection,
5958
timeseriesOnly,
6059
onJobsFetched,
61-
onSelectionChange,
6260
onSelectionConfirmed,
6361
onFlyoutClose,
6462
maps,
@@ -80,7 +78,7 @@ export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
8078
const [ganttBarWidth, setGanttBarWidth] = useState(DEFAULT_GANTT_BAR_WIDTH);
8179
const [jobGroupsMaps, setJobGroupsMaps] = useState(maps);
8280

83-
const flyoutEl = useRef<{ flyout: HTMLElement }>(null);
81+
const flyoutEl = useRef<HTMLElement | null>(null);
8482

8583
function applySelection() {
8684
// allNewSelection will be a list of all job ids (including those from groups) selected from the table
@@ -131,19 +129,19 @@ export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
131129
// Wrap handleResize in useCallback as it is a dependency for useEffect on line 131 below.
132130
// Not wrapping it would cause this dependency to change on every render
133131
const handleResize = useCallback(() => {
134-
if (jobs.length > 0 && flyoutEl && flyoutEl.current && flyoutEl.current.flyout) {
135-
// get all cols in flyout table
136-
const tableHeaderCols: NodeListOf<HTMLElement> = flyoutEl.current.flyout.querySelectorAll(
137-
'table thead th'
138-
);
139-
// get the width of the last col
140-
const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16;
141-
const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth);
142-
setJobs(normalizedJobs);
143-
const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs);
144-
setGroups(updatedGroups);
145-
setGanttBarWidth(derivedWidth);
146-
}
132+
if (jobs.length === 0 || !flyoutEl.current) return;
133+
134+
// get all cols in flyout table
135+
const tableHeaderCols: NodeListOf<HTMLElement> = flyoutEl.current.querySelectorAll(
136+
'table thead th'
137+
);
138+
// get the width of the last col
139+
const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16;
140+
const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth);
141+
setJobs(normalizedJobs);
142+
const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs);
143+
setGroups(updatedGroups);
144+
setGanttBarWidth(derivedWidth);
147145
}, [dateFormatTz, jobs]);
148146

149147
// Fetch jobs list on flyout open
@@ -174,117 +172,109 @@ export const JobSelectorFlyout: FC<JobSelectorFlyoutProps> = ({
174172
}
175173
}
176174

177-
useEffect(() => {
178-
// Ensure ganttBar width gets calculated on resize
179-
window.addEventListener('resize', handleResize);
180-
181-
return () => {
182-
window.removeEventListener('resize', handleResize);
183-
};
184-
}, [handleResize]);
185-
186-
useEffect(() => {
187-
handleResize();
188-
}, [handleResize, jobs]);
189-
190175
return (
191-
<EuiFlyout
192-
// @ts-ignore
193-
ref={flyoutEl}
194-
onClose={onFlyoutClose}
195-
aria-labelledby="jobSelectorFlyout"
196-
data-test-subj="mlFlyoutJobSelector"
197-
>
198-
<EuiFlyoutHeader hasBorder>
199-
<EuiTitle size="m">
200-
<h2 id="flyoutTitle">
201-
{i18n.translate('xpack.ml.jobSelector.flyoutTitle', {
202-
defaultMessage: 'Job selection',
203-
})}
204-
</h2>
205-
</EuiTitle>
206-
</EuiFlyoutHeader>
207-
<EuiFlyoutBody className="mlJobSelectorFlyoutBody">
208-
<EuiFlexGroup direction="column" responsive={false}>
209-
<EuiFlexItem grow={false}>
210-
<EuiFlexGroup wrap responsive={false} gutterSize="xs" alignItems="center">
211-
<NewSelectionIdBadges
212-
limit={BADGE_LIMIT}
213-
maps={jobGroupsMaps}
214-
newSelection={newSelection}
215-
onDeleteClick={removeId}
216-
onLinkClick={() => setShowAllBadges(!showAllBadges)}
217-
showAllBadges={showAllBadges}
218-
/>
176+
<EuiResizeObserver onResize={handleResize}>
177+
{(resizeRef) => (
178+
<div
179+
ref={(e) => {
180+
flyoutEl.current = e;
181+
resizeRef(e);
182+
}}
183+
aria-labelledby="jobSelectorFlyout"
184+
data-test-subj="mlFlyoutJobSelector"
185+
>
186+
<EuiFlyoutHeader hasBorder>
187+
<EuiTitle size="m">
188+
<h2 id="flyoutTitle">
189+
{i18n.translate('xpack.ml.jobSelector.flyoutTitle', {
190+
defaultMessage: 'Job selection',
191+
})}
192+
</h2>
193+
</EuiTitle>
194+
</EuiFlyoutHeader>
195+
<EuiFlyoutBody className="mlJobSelectorFlyoutBody">
196+
<EuiFlexGroup direction="column" responsive={false}>
197+
<EuiFlexItem grow={false}>
198+
<EuiFlexGroup wrap responsive={false} gutterSize="xs" alignItems="center">
199+
<NewSelectionIdBadges
200+
limit={BADGE_LIMIT}
201+
maps={jobGroupsMaps}
202+
newSelection={newSelection}
203+
onDeleteClick={removeId}
204+
onLinkClick={() => setShowAllBadges(!showAllBadges)}
205+
showAllBadges={showAllBadges}
206+
/>
207+
</EuiFlexGroup>
208+
</EuiFlexItem>
209+
<EuiFlexItem grow={false}>
210+
<EuiFlexGroup direction="row" justifyContent="spaceBetween" responsive={false}>
211+
<EuiFlexItem grow={false}>
212+
{!singleSelection && newSelection.length > 0 && (
213+
<EuiButtonEmpty
214+
onClick={clearSelection}
215+
size="xs"
216+
data-test-subj="mlFlyoutJobSelectorButtonClearSelection"
217+
>
218+
{i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', {
219+
defaultMessage: 'Clear all',
220+
})}
221+
</EuiButtonEmpty>
222+
)}
223+
</EuiFlexItem>
224+
{withTimeRangeSelector && (
225+
<EuiFlexItem grow={false}>
226+
<EuiSwitch
227+
label={i18n.translate('xpack.ml.jobSelector.applyTimerangeSwitchLabel', {
228+
defaultMessage: 'Apply time range',
229+
})}
230+
checked={applyTimeRange}
231+
onChange={toggleTimerangeSwitch}
232+
data-test-subj="mlFlyoutJobSelectorSwitchApplyTimeRange"
233+
/>
234+
</EuiFlexItem>
235+
)}
236+
</EuiFlexGroup>
237+
</EuiFlexItem>
219238
</EuiFlexGroup>
220-
</EuiFlexItem>
221-
<EuiFlexItem grow={false}>
222-
<EuiFlexGroup direction="row" justifyContent="spaceBetween" responsive={false}>
239+
<JobSelectorTable
240+
jobs={jobs}
241+
ganttBarWidth={ganttBarWidth}
242+
groupsList={groups}
243+
onSelection={handleNewSelection}
244+
selectedIds={newSelection}
245+
singleSelection={singleSelection}
246+
timeseriesOnly={timeseriesOnly}
247+
/>
248+
</EuiFlyoutBody>
249+
<EuiFlyoutFooter>
250+
<EuiFlexGroup>
223251
<EuiFlexItem grow={false}>
224-
{!singleSelection && newSelection.length > 0 && (
225-
<EuiButtonEmpty
226-
onClick={clearSelection}
227-
size="xs"
228-
data-test-subj="mlFlyoutJobSelectorButtonClearSelection"
229-
>
230-
{i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', {
231-
defaultMessage: 'Clear all',
232-
})}
233-
</EuiButtonEmpty>
234-
)}
252+
<EuiButton
253+
onClick={applySelection}
254+
fill
255+
isDisabled={newSelection.length === 0}
256+
data-test-subj="mlFlyoutJobSelectorButtonApply"
257+
>
258+
{i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', {
259+
defaultMessage: 'Apply',
260+
})}
261+
</EuiButton>
262+
</EuiFlexItem>
263+
<EuiFlexItem grow={false}>
264+
<EuiButtonEmpty
265+
iconType="cross"
266+
onClick={onFlyoutClose}
267+
data-test-subj="mlFlyoutJobSelectorButtonClose"
268+
>
269+
{i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', {
270+
defaultMessage: 'Close',
271+
})}
272+
</EuiButtonEmpty>
235273
</EuiFlexItem>
236-
{withTimeRangeSelector && (
237-
<EuiFlexItem grow={false}>
238-
<EuiSwitch
239-
label={i18n.translate('xpack.ml.jobSelector.applyTimerangeSwitchLabel', {
240-
defaultMessage: 'Apply time range',
241-
})}
242-
checked={applyTimeRange}
243-
onChange={toggleTimerangeSwitch}
244-
data-test-subj="mlFlyoutJobSelectorSwitchApplyTimeRange"
245-
/>
246-
</EuiFlexItem>
247-
)}
248274
</EuiFlexGroup>
249-
</EuiFlexItem>
250-
</EuiFlexGroup>
251-
<JobSelectorTable
252-
jobs={jobs}
253-
ganttBarWidth={ganttBarWidth}
254-
groupsList={groups}
255-
onSelection={handleNewSelection}
256-
selectedIds={newSelection}
257-
singleSelection={singleSelection}
258-
timeseriesOnly={timeseriesOnly}
259-
/>
260-
</EuiFlyoutBody>
261-
<EuiFlyoutFooter>
262-
<EuiFlexGroup>
263-
<EuiFlexItem grow={false}>
264-
<EuiButton
265-
onClick={applySelection}
266-
fill
267-
isDisabled={newSelection.length === 0}
268-
data-test-subj="mlFlyoutJobSelectorButtonApply"
269-
>
270-
{i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', {
271-
defaultMessage: 'Apply',
272-
})}
273-
</EuiButton>
274-
</EuiFlexItem>
275-
<EuiFlexItem grow={false}>
276-
<EuiButtonEmpty
277-
iconType="cross"
278-
onClick={onFlyoutClose}
279-
data-test-subj="mlFlyoutJobSelectorButtonClose"
280-
>
281-
{i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', {
282-
defaultMessage: 'Close',
283-
})}
284-
</EuiButtonEmpty>
285-
</EuiFlexItem>
286-
</EuiFlexGroup>
287-
</EuiFlyoutFooter>
288-
</EuiFlyout>
275+
</EuiFlyoutFooter>
276+
</div>
277+
)}
278+
</EuiResizeObserver>
289279
);
290280
};

x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
toMountPoint,
1414
} from '../../../../../../src/plugins/kibana_react/public';
1515
import { AnomalySwimlaneInitializer } from './anomaly_swimlane_initializer';
16-
import { JobSelectorFlyout } from '../../application/components/job_selector/job_selector_flyout';
16+
import { JobSelectorFlyoutContent } from '../../application/components/job_selector/job_selector_flyout';
1717
import { AnomalyDetectorService } from '../../application/services/anomaly_detector_service';
1818
import { getInitialGroupsMap } from '../../application/components/job_selector/job_selector';
1919
import { getDefaultPanelTitle } from './anomaly_swimlane_embeddable';
@@ -43,7 +43,7 @@ export async function resolveAnomalySwimlaneUserInput(
4343
const flyoutSession = coreStart.overlays.openFlyout(
4444
toMountPoint(
4545
<KibanaContextProvider services={{ ...coreStart, mlServices: getMlGlobalServices(http) }}>
46-
<JobSelectorFlyout
46+
<JobSelectorFlyoutContent
4747
selectedIds={selectedIds}
4848
withTimeRangeSelector={false}
4949
dateFormatTz={dateFormatTz}
@@ -87,6 +87,7 @@ export async function resolveAnomalySwimlaneUserInput(
8787
),
8888
{
8989
'data-test-subj': 'mlAnomalySwimlaneEmbeddable',
90+
ownFocus: true,
9091
}
9192
);
9293
});

0 commit comments

Comments
 (0)