mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ML] Job selection flyout: UX improvements (#189239)
## Summary Related meta issue: https://github.com/elastic/kibana/issues/182042 Fixes https://github.com/elastic/kibana/issues/186228 This PR makes some small UX improvements to the Job selection flyout: - replaces the callout with the EuiEmptyPrompt - the Primary action (Apply) is now on the right of the footer and the Secondary action (Close) is aligned left <img width="725" alt="image" src="https://github.com/user-attachments/assets/3469106b-33a4-4060-b0a0-cbfe582187aa"> <img width="717" alt="image" src="https://github.com/user-attachments/assets/9aae9bc3-04dd-426d-a5ea-9f059dc64e0e"> In dashboard, shows the empty prompt when no jobs in the panel config flyout: <img width="779" alt="image" src="https://github.com/user-attachments/assets/b6526e28-fbaf-43f2-a0d1-27e60bac5cb0"> ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
fec2318ee3
commit
b6b5a89fa4
8 changed files with 200 additions and 143 deletions
|
@ -197,6 +197,7 @@ const MlAnomalyAlertTrigger: FC<MlAnomalyAlertTriggerProps> = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
onChange={useCallback(onAlertParamChange('jobSelection'), [])}
|
||||
errors={Array.isArray(errors.jobSelection) ? errors.jobSelection : []}
|
||||
shouldUseDropdownJobCreate
|
||||
/>
|
||||
|
||||
<ConfigValidator
|
||||
|
|
|
@ -10,12 +10,13 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui';
|
||||
import { EuiComboBox, EuiFormRow } from '@elastic/eui';
|
||||
import { EuiButton, EuiComboBox, EuiEmptyPrompt, EuiFormRow } from '@elastic/eui';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
import { useMlKibana } from '../application/contexts/kibana';
|
||||
import type { JobId } from '../../common/types/anomaly_detection_jobs';
|
||||
import type { MlApiServices } from '../application/services/ml_api_service';
|
||||
import { ALL_JOBS_SELECTION } from '../../common/constants/alerts';
|
||||
import { LoadingIndicator } from '../application/components/loading_indicator';
|
||||
|
||||
interface JobSelection {
|
||||
jobIds?: JobId[];
|
||||
|
@ -43,6 +44,10 @@ export interface JobSelectorControlProps {
|
|||
* Available options to select. By default suggest all existing jobs.
|
||||
*/
|
||||
options?: Array<EuiComboBoxOptionOption<string>>;
|
||||
/**
|
||||
* Flag to indicate whether to use the job creation button in the empty prompt or the dropdown when no jobs are available.
|
||||
*/
|
||||
shouldUseDropdownJobCreate?: boolean;
|
||||
}
|
||||
|
||||
export const JobSelectorControl: FC<JobSelectorControlProps> = ({
|
||||
|
@ -55,6 +60,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({
|
|||
allowSelectAll = false,
|
||||
createJobUrl,
|
||||
options: defaultOptions,
|
||||
shouldUseDropdownJobCreate = false,
|
||||
}) => {
|
||||
const {
|
||||
services: {
|
||||
|
@ -66,6 +72,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({
|
|||
const isMounted = useMountedState();
|
||||
|
||||
const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<string>>>([]);
|
||||
const [areJobsLoading, setAreJobsLoading] = useState<boolean>(false);
|
||||
const jobIds = useMemo(() => new Set(), []);
|
||||
const groupIds = useMemo(() => new Set(), []);
|
||||
|
||||
|
@ -78,6 +85,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({
|
|||
);
|
||||
|
||||
const fetchOptions = useCallback(async () => {
|
||||
setAreJobsLoading(true);
|
||||
try {
|
||||
const { jobIds: jobIdOptions, groupIds: groupIdOptions } =
|
||||
await adJobsApiService.getAllJobAndGroupIds();
|
||||
|
@ -147,6 +155,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({
|
|||
}),
|
||||
});
|
||||
}
|
||||
setAreJobsLoading(false);
|
||||
}, [
|
||||
adJobsApiService,
|
||||
allowSelectAll,
|
||||
|
@ -200,7 +209,9 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [createJobUrl]);
|
||||
|
||||
return (
|
||||
if (areJobsLoading === true) return <LoadingIndicator />;
|
||||
|
||||
return jobIds.size || shouldUseDropdownJobCreate ? (
|
||||
<EuiFormRow
|
||||
data-test-subj="mlAnomalyJobSelectionControls"
|
||||
fullWidth
|
||||
|
@ -225,5 +236,32 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({
|
|||
isInvalid={!!errors?.length}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="mlAnomalyJobSelectionControls"
|
||||
titleSize="xxs"
|
||||
iconType="warning"
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.embeddables.jobSelector.noJobsFoundTitle"
|
||||
defaultMessage="No anomaly detection jobs found"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
body={
|
||||
<EuiButton
|
||||
fill
|
||||
color="primary"
|
||||
onClick={() => navigateToUrl(createJobUrl!)}
|
||||
disabled={createJobUrl === undefined}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.embeddables.jobSelector.createJobButtonLabel"
|
||||
defaultMessage="Create job"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -123,6 +123,7 @@ const AnomalyDetectionJobsHealthRuleTrigger: FC<MlAnomalyAlertTriggerProps> = ({
|
|||
defaultMessage="Include jobs or groups"
|
||||
/>
|
||||
}
|
||||
shouldUseDropdownJobCreate
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
@ -148,6 +149,7 @@ const AnomalyDetectionJobsHealthRuleTrigger: FC<MlAnomalyAlertTriggerProps> = ({
|
|||
/>
|
||||
}
|
||||
options={excludeJobsOptions}
|
||||
shouldUseDropdownJobCreate
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
|
|
@ -242,7 +242,9 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({
|
|||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{withTimeRangeSelector && applyTimeRangeConfig !== undefined && (
|
||||
{withTimeRangeSelector &&
|
||||
applyTimeRangeConfig !== undefined &&
|
||||
jobs.length !== 0 ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
|
@ -256,7 +258,7 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({
|
|||
data-test-subj="mlFlyoutJobSelectorSwitchApplyTimeRange"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -278,19 +280,7 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({
|
|||
</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>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
|
@ -302,6 +292,20 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({
|
|||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{jobs.length !== 0 ? (
|
||||
<EuiButton
|
||||
onClick={applySelection}
|
||||
fill
|
||||
isDisabled={newSelection.length === 0}
|
||||
data-test-subj="mlFlyoutJobSelectorButtonApply"
|
||||
>
|
||||
{i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', {
|
||||
defaultMessage: 'Apply',
|
||||
})}
|
||||
</EuiButton>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
|
|
|
@ -13,12 +13,11 @@ import { TimeRangeBar } from '../timerange_bar';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import {
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTabbedContent,
|
||||
EuiCallOut,
|
||||
EuiButton,
|
||||
EuiText,
|
||||
LEFT_ALIGNMENT,
|
||||
CENTER_ALIGNMENT,
|
||||
SortableProperties,
|
||||
|
@ -265,17 +264,20 @@ export function JobSelectorTable({
|
|||
<Fragment>
|
||||
<MlNodeAvailableWarningShared nodeAvailableCallback={setMlNodesAvailable} />
|
||||
{jobs.length === 0 && (
|
||||
<EuiCallOut
|
||||
<EuiEmptyPrompt
|
||||
titleSize="xs"
|
||||
iconType="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.jobSelector.noJobsFoundTitle"
|
||||
defaultMessage="No anomaly detection jobs found"
|
||||
/>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.jobSelector.noJobsFoundTitle"
|
||||
defaultMessage="No anomaly detection jobs found"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
iconType="iInCircle"
|
||||
>
|
||||
<EuiText textAlign="center">
|
||||
body={
|
||||
<EuiButton
|
||||
fill
|
||||
color="primary"
|
||||
onClick={navigateToWizard}
|
||||
disabled={mlCapabilities.canCreateJob === false || mlNodesAvailable === false}
|
||||
|
@ -285,8 +287,8 @@ export function JobSelectorTable({
|
|||
defaultMessage="Create job"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{jobs.length !== 0 && singleSelection === true && renderJobsTable()}
|
||||
{jobs.length !== 0 && !singleSelection && renderTabs()}
|
||||
|
|
|
@ -101,57 +101,59 @@ export const AnomalyChartsInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
errors={jobIdsErrors}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiForm>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.anomalyChartsEmbeddable.panelTitleLabel"
|
||||
defaultMessage="Panel title"
|
||||
/>
|
||||
}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="panelTitleInput"
|
||||
id="panelTitle"
|
||||
name="panelTitle"
|
||||
value={panelTitle}
|
||||
onChange={(e) => {
|
||||
titleManuallyChanged.current = true;
|
||||
setPanelTitle(e.target.value);
|
||||
}}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
isInvalid={!isMaxSeriesToPlotValid}
|
||||
error={
|
||||
!isMaxSeriesToPlotValid ? (
|
||||
{jobIds.length > 0 ? (
|
||||
<EuiForm>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotError"
|
||||
defaultMessage="Maximum number of series to plot must be between 1 and 50."
|
||||
id="xpack.ml.anomalyChartsEmbeddable.panelTitleLabel"
|
||||
defaultMessage="Panel title"
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotLabel"
|
||||
defaultMessage="Maximum number of series to plot"
|
||||
}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="panelTitleInput"
|
||||
id="panelTitle"
|
||||
name="panelTitle"
|
||||
value={panelTitle}
|
||||
onChange={(e) => {
|
||||
titleManuallyChanged.current = true;
|
||||
setPanelTitle(e.target.value);
|
||||
}}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
data-test-subj="mlAnomalyChartsInitializerMaxSeries"
|
||||
id="selectMaxSeriesToPlot"
|
||||
name="selectMaxSeriesToPlot"
|
||||
value={maxSeriesToPlot}
|
||||
onChange={(e) => setMaxSeriesToPlot(parseInt(e.target.value, 10))}
|
||||
min={1}
|
||||
max={MAX_ANOMALY_CHARTS_ALLOWED}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
isInvalid={!isMaxSeriesToPlotValid}
|
||||
error={
|
||||
!isMaxSeriesToPlotValid ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotError"
|
||||
defaultMessage="Maximum number of series to plot must be between 1 and 50."
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotLabel"
|
||||
defaultMessage="Maximum number of series to plot"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
data-test-subj="mlAnomalyChartsInitializerMaxSeries"
|
||||
id="selectMaxSeriesToPlot"
|
||||
name="selectMaxSeriesToPlot"
|
||||
value={maxSeriesToPlot}
|
||||
onChange={(e) => setMaxSeriesToPlot(parseInt(e.target.value, 10))}
|
||||
min={1}
|
||||
max={MAX_ANOMALY_CHARTS_ALLOWED}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
) : null}
|
||||
</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -157,52 +157,58 @@ export const AnomalySwimlaneInitializer: FC<AnomalySwimlaneInitializerProps> = (
|
|||
}}
|
||||
errors={jobIdsErrors}
|
||||
/>
|
||||
{jobIds.length > 0 ? (
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.swimlaneEmbeddable.panelTitleLabel"
|
||||
defaultMessage="Panel title"
|
||||
/>
|
||||
}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
id="panelTitle"
|
||||
name="panelTitle"
|
||||
value={panelTitle}
|
||||
onChange={(e) => {
|
||||
titleManuallyChanged.current = true;
|
||||
setPanelTitle(e.target.value);
|
||||
}}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.swimlaneEmbeddable.panelTitleLabel"
|
||||
defaultMessage="Panel title"
|
||||
/>
|
||||
}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
id="panelTitle"
|
||||
name="panelTitle"
|
||||
value={panelTitle}
|
||||
onChange={(e) => {
|
||||
titleManuallyChanged.current = true;
|
||||
setPanelTitle(e.target.value);
|
||||
}}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel"
|
||||
defaultMessage="Swim lane type"
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiButtonGroup
|
||||
id="selectSwimlaneType"
|
||||
name="selectSwimlaneType"
|
||||
color="primary"
|
||||
isFullWidth
|
||||
legend={i18n.translate('xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel', {
|
||||
defaultMessage: 'Swim lane type',
|
||||
})}
|
||||
options={swimlaneTypeOptions}
|
||||
idSelected={swimlaneType}
|
||||
onChange={(id) => setSwimlaneType(id as SwimlaneType)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel"
|
||||
defaultMessage="Swim lane type"
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<EuiButtonGroup
|
||||
id="selectSwimlaneType"
|
||||
name="selectSwimlaneType"
|
||||
color="primary"
|
||||
isFullWidth
|
||||
legend={i18n.translate(
|
||||
'xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Swim lane type',
|
||||
}
|
||||
)}
|
||||
options={swimlaneTypeOptions}
|
||||
idSelected={swimlaneType}
|
||||
onChange={(id) => setSwimlaneType(id as SwimlaneType)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{swimlaneType === SWIMLANE_TYPE.VIEW_BY && (
|
||||
<>
|
||||
|
|
|
@ -150,29 +150,31 @@ export const SingleMetricViewerInitializer: FC<SingleMetricViewerInitializerProp
|
|||
}}
|
||||
{...(errorMessage && { errors: [errorMessage] })}
|
||||
/>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.singleMetricViewerEmbeddable.panelTitleLabel"
|
||||
defaultMessage="Panel title"
|
||||
/>
|
||||
}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="panelTitleInput"
|
||||
id="panelTitle"
|
||||
name="panelTitle"
|
||||
value={panelTitle}
|
||||
onChange={(e) => {
|
||||
titleManuallyChanged.current = true;
|
||||
setPanelTitle(e.target.value);
|
||||
}}
|
||||
{job?.job_id && jobId && jobId === job.job_id ? (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.singleMetricViewerEmbeddable.panelTitleLabel"
|
||||
defaultMessage="Panel title"
|
||||
/>
|
||||
}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="panelTitleInput"
|
||||
id="panelTitle"
|
||||
name="panelTitle"
|
||||
value={panelTitle}
|
||||
onChange={(e) => {
|
||||
titleManuallyChanged.current = true;
|
||||
setPanelTitle(e.target.value);
|
||||
}}
|
||||
isInvalid={!isPanelTitleValid}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
<EuiSpacer />
|
||||
{job?.job_id && jobId && jobId === job.job_id ? (
|
||||
<SeriesControls
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue