mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* Add basic help text to ML Job dropdown on Rule form * Use EUI's preferred layout for form fields * Add a link to ML in the Job select help text * Restrict timeline picker to EUI guidelines Don't display the row as fullwidth, lest the help text wrap across the entire page. It only looks okay now because it was a short sentence; adding the ML Job select with its wrapped text caused some visual weirdness, so this at least makes it consistent. * Add placeholder option to ML Job dropdown * Humanize rule type on Rule Description component This is displayed both on the readonly form view, and the Rule Details page. * Add useMlCapabilities hook This is a base hook that we can combine with our permissions helpers. * Restrict ML Rule creation to ML Admins If we're auto-activating jobs on their behalf, they'll need to be an admin. * Extract ML Job status helpers to separate file * WIP: Enrich Rule Description with ML Job Data This adds the auditMessage as well as a link to ML; actual status is next * Display job status as a badge on Rule Details Also simplifies the layout of these job details. * Port helper tests to new location * Fix DescriptionStep tests now that they use useSiemJobs UseSiemJobs uses uiSettings, so we need to use our kibana mocks here. * Fix responsiveness of ML Rule Details The long job names were causing the panel to overflow.
This commit is contained in:
parent
76295f3772
commit
99678091af
29 changed files with 386 additions and 137 deletions
|
@ -4,12 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useContext } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { anomaliesTableData } from '../api/anomalies_table_data';
|
||||
import { InfluencerInput, Anomalies, CriteriaFields } from '../types';
|
||||
import { hasMlUserPermissions } from '../permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../permissions/ml_capabilities_provider';
|
||||
import { useSiemJobs } from '../../ml_popover/hooks/use_siem_jobs';
|
||||
import { useMlCapabilities } from '../../ml_popover/hooks/use_ml_capabilities';
|
||||
import { useStateToaster, errorToToaster } from '../../toasters';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
@ -59,7 +59,7 @@ export const useAnomaliesTableData = ({
|
|||
const [tableData, setTableData] = useState<Anomalies | null>(null);
|
||||
const [, siemJobs] = useSiemJobs(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
const timeZone = useTimeZone();
|
||||
|
|
|
@ -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 { isJobStarted, isJobLoading, isJobFailed } from './';
|
||||
|
||||
describe('isJobStarted', () => {
|
||||
test('returns false if only jobState is enabled', () => {
|
||||
expect(isJobStarted('started', 'closing')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false if only datafeedState is enabled', () => {
|
||||
expect(isJobStarted('stopping', 'opened')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true if both enabled states are provided', () => {
|
||||
expect(isJobStarted('started', 'opened')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isJobLoading', () => {
|
||||
test('returns true if both loading states are not provided', () => {
|
||||
expect(isJobLoading('started', 'closing')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if only jobState is loading', () => {
|
||||
expect(isJobLoading('starting', 'opened')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if only datafeedState is loading', () => {
|
||||
expect(isJobLoading('started', 'opening')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false if both disabling states are provided', () => {
|
||||
expect(isJobLoading('stopping', 'closing')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isJobFailed', () => {
|
||||
test('returns true if only jobState is failure/deleted', () => {
|
||||
expect(isJobFailed('failed', 'stopping')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if only dataFeed is failure/deleted', () => {
|
||||
expect(isJobFailed('started', 'deleted')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if both enabled states are failure/deleted', () => {
|
||||
expect(isJobFailed('failed', 'deleted')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false only if both states are not failure/deleted', () => {
|
||||
expect(isJobFailed('opened', 'stopping')).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Based on ML Job/Datafeed States from x-pack/legacy/plugins/ml/common/constants/states.js
|
||||
const enabledStates = ['started', 'opened'];
|
||||
const loadingStates = ['starting', 'stopping', 'opening', 'closing'];
|
||||
const failureStates = ['deleted', 'failed'];
|
||||
|
||||
export const isJobStarted = (jobState: string, datafeedState: string): boolean => {
|
||||
return enabledStates.includes(jobState) && enabledStates.includes(datafeedState);
|
||||
};
|
||||
|
||||
export const isJobLoading = (jobState: string, datafeedState: string): boolean => {
|
||||
return loadingStates.includes(jobState) || loadingStates.includes(datafeedState);
|
||||
};
|
||||
|
||||
export const isJobFailed = (jobState: string, datafeedState: string): boolean => {
|
||||
return failureStates.includes(jobState) || failureStates.includes(datafeedState);
|
||||
};
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
|
@ -16,7 +16,7 @@ import { Loader } from '../../loader';
|
|||
import { getIntervalFromAnomalies } from '../anomaly/get_interval_from_anomalies';
|
||||
import { AnomaliesHostTableProps } from '../types';
|
||||
import { hasMlUserPermissions } from '../permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../permissions/ml_capabilities_provider';
|
||||
import { useMlCapabilities } from '../../ml_popover/hooks/use_ml_capabilities';
|
||||
import { BasicTable } from './basic_table';
|
||||
import { hostEquality } from './host_equality';
|
||||
import { getCriteriaFromHostType } from '../criteria/get_criteria_from_host_type';
|
||||
|
@ -37,7 +37,7 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
|
|||
skip,
|
||||
type,
|
||||
}) => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const [loading, tableData] = useAnomaliesTableData({
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
|
||||
|
@ -13,8 +13,8 @@ import { convertAnomaliesToNetwork } from './convert_anomalies_to_network';
|
|||
import { Loader } from '../../loader';
|
||||
import { AnomaliesNetworkTableProps } from '../types';
|
||||
import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns';
|
||||
import { useMlCapabilities } from '../../ml_popover/hooks/use_ml_capabilities';
|
||||
import { hasMlUserPermissions } from '../permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../permissions/ml_capabilities_provider';
|
||||
import { BasicTable } from './basic_table';
|
||||
import { networkEquality } from './network_equality';
|
||||
import { getCriteriaFromNetworkType } from '../criteria/get_criteria_from_network_type';
|
||||
|
@ -35,7 +35,7 @@ const AnomaliesNetworkTableComponent: React.FC<AnomaliesNetworkTableProps> = ({
|
|||
type,
|
||||
flowTarget,
|
||||
}) => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const [loading, tableData] = useAnomaliesTableData({
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { useContext } from 'react';
|
||||
|
||||
import { MlCapabilitiesContext } from '../../ml/permissions/ml_capabilities_provider';
|
||||
|
||||
export const useMlCapabilities = () => useContext(MlCapabilitiesContext);
|
|
@ -4,18 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { checkRecognizer, getJobsSummary, getModules } from '../api';
|
||||
import { SiemJob } from '../types';
|
||||
import { hasMlUserPermissions } from '../../ml/permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../../ml/permissions/ml_capabilities_provider';
|
||||
import { errorToToaster, useStateToaster } from '../../toasters';
|
||||
import { useUiSetting$ } from '../../../lib/kibana';
|
||||
import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { createSiemJobs } from './use_siem_jobs_helpers';
|
||||
import { useMlCapabilities } from './use_ml_capabilities';
|
||||
|
||||
type Return = [boolean, SiemJob[]];
|
||||
|
||||
|
@ -30,8 +30,8 @@ type Return = [boolean, SiemJob[]];
|
|||
export const useSiemJobs = (refetchData: boolean): Return => {
|
||||
const [siemJobs, setSiemJobs] = useState<SiemJob[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
const mlCapabilities = useMlCapabilities();
|
||||
const userPermissions = hasMlUserPermissions(mlCapabilities);
|
||||
const [siemDefaultIndex] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY);
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { shallow, mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { isChecked, isFailure, isJobLoading, JobSwitchComponent } from './job_switch';
|
||||
import { JobSwitchComponent } from './job_switch';
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
import { mockSiemJobs } from '../__mocks__/api';
|
||||
import { SiemJob } from '../types';
|
||||
|
@ -75,54 +75,4 @@ describe('JobSwitch', () => {
|
|||
);
|
||||
expect(wrapper.find('[data-test-subj="job-switch"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('isChecked', () => {
|
||||
test('returns false if only jobState is enabled', () => {
|
||||
expect(isChecked('started', 'closing')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false if only datafeedState is enabled', () => {
|
||||
expect(isChecked('stopping', 'opened')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true if both enabled states are provided', () => {
|
||||
expect(isChecked('started', 'opened')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isJobLoading', () => {
|
||||
test('returns true if both loading states are not provided', () => {
|
||||
expect(isJobLoading('started', 'closing')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if only jobState is loading', () => {
|
||||
expect(isJobLoading('starting', 'opened')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if only datafeedState is loading', () => {
|
||||
expect(isJobLoading('started', 'opening')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false if both disabling states are provided', () => {
|
||||
expect(isJobLoading('stopping', 'closing')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFailure', () => {
|
||||
test('returns true if only jobState is failure/deleted', () => {
|
||||
expect(isFailure('failed', 'stopping')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if only dataFeed is failure/deleted', () => {
|
||||
expect(isFailure('started', 'deleted')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if both enabled states are failure/deleted', () => {
|
||||
expect(isFailure('failed', 'deleted')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false only if both states are not failure/deleted', () => {
|
||||
expect(isFailure('opened', 'stopping')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import styled from 'styled-components';
|
|||
import React, { useState, useCallback } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSwitch } from '@elastic/eui';
|
||||
import { SiemJob } from '../types';
|
||||
import { isJobLoading, isJobStarted, isJobFailed } from '../../ml/helpers';
|
||||
|
||||
const StaticSwitch = styled(EuiSwitch)`
|
||||
.euiSwitch__thumb,
|
||||
|
@ -24,23 +25,6 @@ export interface JobSwitchProps {
|
|||
onJobStateChange: (job: SiemJob, latestTimestampMs: number, enable: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
// Based on ML Job/Datafeed States from x-pack/legacy/plugins/ml/common/constants/states.js
|
||||
const enabledStates = ['started', 'opened'];
|
||||
const loadingStates = ['starting', 'stopping', 'opening', 'closing'];
|
||||
const failureStates = ['deleted', 'failed'];
|
||||
|
||||
export const isChecked = (jobState: string, datafeedState: string): boolean => {
|
||||
return enabledStates.includes(jobState) && enabledStates.includes(datafeedState);
|
||||
};
|
||||
|
||||
export const isJobLoading = (jobState: string, datafeedState: string): boolean => {
|
||||
return loadingStates.includes(jobState) || loadingStates.includes(datafeedState);
|
||||
};
|
||||
|
||||
export const isFailure = (jobState: string, datafeedState: string): boolean => {
|
||||
return failureStates.includes(jobState) || failureStates.includes(datafeedState);
|
||||
};
|
||||
|
||||
export const JobSwitchComponent = ({
|
||||
job,
|
||||
isSiemJobsLoading,
|
||||
|
@ -64,8 +48,8 @@ export const JobSwitchComponent = ({
|
|||
) : (
|
||||
<StaticSwitch
|
||||
data-test-subj="job-switch"
|
||||
disabled={isFailure(job.jobState, job.datafeedState)}
|
||||
checked={isChecked(job.jobState, job.datafeedState)}
|
||||
disabled={isJobFailed(job.jobState, job.datafeedState)}
|
||||
checked={isJobStarted(job.jobState, job.datafeedState)}
|
||||
onChange={handleChange}
|
||||
showLabel={false}
|
||||
label=""
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
import { EuiButtonEmpty, EuiCallOut, EuiPopover, EuiPopoverTitle, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import moment from 'moment';
|
||||
import React, { useContext, useReducer, useState } from 'react';
|
||||
import React, { useReducer, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../lib/telemetry';
|
||||
import { hasMlAdminPermissions } from '../ml/permissions/has_ml_admin_permissions';
|
||||
import { MlCapabilitiesContext } from '../ml/permissions/ml_capabilities_provider';
|
||||
import { errorToToaster, useStateToaster } from '../toasters';
|
||||
import { setupMlJob, startDatafeeds, stopDatafeeds } from './api';
|
||||
import { filterJobs } from './helpers';
|
||||
|
@ -25,6 +24,7 @@ import { PopoverDescription } from './popover_description';
|
|||
import * as i18n from './translations';
|
||||
import { JobsFilters, JobSummary, SiemJob } from './types';
|
||||
import { UpgradeContents } from './upgrade_contents';
|
||||
import { useMlCapabilities } from './hooks/use_ml_capabilities';
|
||||
|
||||
const PopoverContentsDiv = styled.div`
|
||||
max-width: 684px;
|
||||
|
@ -97,7 +97,7 @@ export const MlPopover = React.memo(() => {
|
|||
const [filterProperties, setFilterProperties] = useState(defaultFilterProps);
|
||||
const [isLoadingSiemJobs, siemJobs] = useSiemJobs(refreshToggle);
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const docLinks = useKibana().services.docLinks;
|
||||
|
||||
// Enable/Disable Job & Datafeed -- passed to JobsTable for use as callback on JobSwitch
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { MlError } from '../ml/types';
|
||||
import { AuditMessageBase } from '../../../../../../plugins/ml/common/types/audit_message';
|
||||
|
||||
export interface Group {
|
||||
id: string;
|
||||
|
@ -101,6 +102,7 @@ export interface MlSetupArgs {
|
|||
* Representation of an ML Job as returned from the `ml/jobs/jobs_summary` API
|
||||
*/
|
||||
export interface JobSummary {
|
||||
auditMessage?: AuditMessageBase;
|
||||
datafeedId: string;
|
||||
datafeedIndices: string[];
|
||||
datafeedState: string;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { EuiFlexItem } from '@elastic/eui';
|
|||
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { DEFAULT_DARK_MODE } from '../../../../../common/constants';
|
||||
import { DescriptionList } from '../../../../../common/utility_types';
|
||||
|
@ -19,8 +19,8 @@ import { InspectButton, InspectButtonContainer } from '../../../inspect';
|
|||
import { HostItem } from '../../../../graphql/types';
|
||||
import { Loader } from '../../../loader';
|
||||
import { IPDetailsLink } from '../../../links';
|
||||
import { MlCapabilitiesContext } from '../../../ml/permissions/ml_capabilities_provider';
|
||||
import { hasMlUserPermissions } from '../../../ml/permissions/has_ml_user_permissions';
|
||||
import { useMlCapabilities } from '../../../ml_popover/hooks/use_ml_capabilities';
|
||||
import { AnomalyScores } from '../../../ml/score/anomaly_scores';
|
||||
import { Anomalies, NarrowDateRange } from '../../../ml/types';
|
||||
import { DescriptionListStyled, OverviewWrapper } from '../../index';
|
||||
|
@ -56,7 +56,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
anomaliesData,
|
||||
narrowDateRange,
|
||||
}) => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { DEFAULT_DARK_MODE } from '../../../../../common/constants';
|
||||
import { DescriptionList } from '../../../../../common/utility_types';
|
||||
|
@ -30,7 +30,7 @@ import { DescriptionListStyled, OverviewWrapper } from '../../index';
|
|||
import { Loader } from '../../../loader';
|
||||
import { Anomalies, NarrowDateRange } from '../../../ml/types';
|
||||
import { AnomalyScores } from '../../../ml/score/anomaly_scores';
|
||||
import { MlCapabilitiesContext } from '../../../ml/permissions/ml_capabilities_provider';
|
||||
import { useMlCapabilities } from '../../../ml_popover/hooks/use_ml_capabilities';
|
||||
import { hasMlUserPermissions } from '../../../ml/permissions/has_ml_user_permissions';
|
||||
import { InspectButton, InspectButtonContainer } from '../../../inspect';
|
||||
|
||||
|
@ -71,7 +71,7 @@ export const IpOverview = React.memo<IpOverviewProps>(
|
|||
anomaliesData,
|
||||
narrowDateRange,
|
||||
}) => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||
const typeData: Overview = data[flowTarget]!;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiFlexGrid, EuiFlexItem, EuiRange, EuiFormRow } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiRange, EuiFormRow } from '@elastic/eui';
|
||||
|
||||
import { FieldHook } from '../../../../../shared_imports';
|
||||
|
||||
|
@ -31,12 +31,11 @@ export const AnomalyThresholdSlider: React.FC<AnomalyThresholdSliderProps> = ({
|
|||
|
||||
return (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={field.label}
|
||||
data-test-subj="anomalyThresholdSlider"
|
||||
describedByIds={describedByIds}
|
||||
>
|
||||
<EuiFlexGrid columns={2}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiRange
|
||||
value={threshold}
|
||||
|
@ -48,7 +47,7 @@ export const AnomalyThresholdSlider: React.FC<AnomalyThresholdSliderProps> = ({
|
|||
tickInterval={25}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
buildSeverityDescription,
|
||||
buildUrlsDescription,
|
||||
buildNoteDescription,
|
||||
buildRuleTypeDescription,
|
||||
} from './helpers';
|
||||
import { ListItems } from './types';
|
||||
|
||||
|
@ -385,4 +386,30 @@ describe('helpers', () => {
|
|||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildRuleTypeDescription', () => {
|
||||
it('returns the label for a machine_learning type', () => {
|
||||
const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'machine_learning');
|
||||
|
||||
expect(result.title).toEqual('Test label');
|
||||
});
|
||||
|
||||
it('returns a humanized description for a machine_learning type', () => {
|
||||
const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'machine_learning');
|
||||
|
||||
expect(result.description).toEqual('Machine Learning');
|
||||
});
|
||||
|
||||
it('returns the label for a query type', () => {
|
||||
const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'query');
|
||||
|
||||
expect(result.title).toEqual('Test label');
|
||||
});
|
||||
|
||||
it('returns a humanized description for a query type', () => {
|
||||
const [result]: ListItems[] = buildRuleTypeDescription('Test label', 'query');
|
||||
|
||||
expect(result.description).toEqual('Query');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,6 +27,8 @@ import * as i18n from './translations';
|
|||
import { BuildQueryBarDescription, BuildThreatDescription, ListItems } from './types';
|
||||
import { SeverityBadge } from '../severity_badge';
|
||||
import ListTreeIcon from './assets/list_tree_icon.svg';
|
||||
import { RuleType } from '../../../../../containers/detection_engine/rules';
|
||||
import { assertUnreachable } from '../../../../../lib/helpers';
|
||||
|
||||
const NoteDescriptionContainer = styled(EuiFlexItem)`
|
||||
height: 105px;
|
||||
|
@ -266,3 +268,27 @@ export const buildNoteDescription = (label: string, note: string): ListItems[] =
|
|||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const buildRuleTypeDescription = (label: string, ruleType: RuleType): ListItems[] => {
|
||||
switch (ruleType) {
|
||||
case 'machine_learning': {
|
||||
return [
|
||||
{
|
||||
title: label,
|
||||
description: i18n.ML_TYPE_DESCRIPTION,
|
||||
},
|
||||
];
|
||||
}
|
||||
case 'query':
|
||||
case 'saved_query': {
|
||||
return [
|
||||
{
|
||||
title: label,
|
||||
description: i18n.QUERY_TYPE_DESCRIPTION,
|
||||
},
|
||||
];
|
||||
}
|
||||
default:
|
||||
return assertUnreachable(ruleType);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -27,6 +27,8 @@ import { schema } from '../step_about_rule/schema';
|
|||
import { ListItems } from './types';
|
||||
import { AboutStepRule } from '../../types';
|
||||
|
||||
jest.mock('../../../../../lib/kibana');
|
||||
|
||||
describe('description_step', () => {
|
||||
const setupMock = coreMock.createSetup();
|
||||
const uiSettingsMock = (pinnedByDefault: boolean) => (key: string) => {
|
||||
|
@ -41,13 +43,6 @@ describe('description_step', () => {
|
|||
let mockAboutStep: AboutStepRule;
|
||||
|
||||
beforeEach(() => {
|
||||
// jest carries state between mocked implementations when using
|
||||
// spyOn. So now we're doing all three of these.
|
||||
// https://github.com/facebook/jest/issues/7136#issuecomment-565976599
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
|
||||
setupMock.uiSettings.get.mockImplementation(uiSettingsMock(true));
|
||||
mockFilterManager = new FilterManager(setupMock.uiSettings);
|
||||
mockAboutStep = mockAboutStepRule();
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
esFilters,
|
||||
FilterManager,
|
||||
} from '../../../../../../../../../../src/plugins/data/public';
|
||||
import { RuleType } from '../../../../../containers/detection_engine/rules';
|
||||
import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/translations';
|
||||
import { useKibana } from '../../../../../lib/kibana';
|
||||
import { IMitreEnterpriseAttack } from '../../types';
|
||||
|
@ -29,7 +30,10 @@ import {
|
|||
buildUnorderedListArrayDescription,
|
||||
buildUrlsDescription,
|
||||
buildNoteDescription,
|
||||
buildRuleTypeDescription,
|
||||
} from './helpers';
|
||||
import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs';
|
||||
import { buildMlJobDescription } from './ml_job_description';
|
||||
|
||||
const DescriptionListContainer = styled(EuiDescriptionList)`
|
||||
&.euiDescriptionList--column .euiDescriptionList__title {
|
||||
|
@ -55,15 +59,22 @@ export const StepRuleDescriptionComponent: React.FC<StepRuleDescriptionProps> =
|
|||
}) => {
|
||||
const kibana = useKibana();
|
||||
const [filterManager] = useState<FilterManager>(new FilterManager(kibana.services.uiSettings));
|
||||
const [, siemJobs] = useSiemJobs(true);
|
||||
|
||||
const keys = Object.keys(schema);
|
||||
const listItems = keys.reduce(
|
||||
(acc: ListItems[], key: string) => [
|
||||
...acc,
|
||||
...buildListItems(data, pick(key, schema), filterManager, indexPatterns),
|
||||
],
|
||||
[]
|
||||
);
|
||||
const listItems = keys.reduce((acc: ListItems[], key: string) => {
|
||||
if (key === 'machineLearningJobId') {
|
||||
return [
|
||||
...acc,
|
||||
buildMlJobDescription(
|
||||
get(key, data) as string,
|
||||
(get(key, schema) as { label: string }).label,
|
||||
siemJobs
|
||||
),
|
||||
];
|
||||
}
|
||||
return [...acc, ...buildListItems(data, pick(key, schema), filterManager, indexPatterns)];
|
||||
}, []);
|
||||
|
||||
if (columns === 'multi') {
|
||||
return (
|
||||
|
@ -176,6 +187,9 @@ export const getDescriptionItem = (
|
|||
} else if (field === 'note') {
|
||||
const val: string = get(field, data);
|
||||
return buildNoteDescription(label, val);
|
||||
} else if (field === 'ruleType') {
|
||||
const ruleType: RuleType = get(field, data);
|
||||
return buildRuleTypeDescription(label, ruleType);
|
||||
}
|
||||
|
||||
const description: string = get(field, data);
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 styled from 'styled-components';
|
||||
import { EuiBadge, EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { useKibana } from '../../../../../lib/kibana';
|
||||
import { SiemJob } from '../../../../../components/ml_popover/types';
|
||||
import { ListItems } from './types';
|
||||
import { isJobStarted } from '../../../../../components/ml/helpers';
|
||||
import { ML_JOB_STARTED, ML_JOB_STOPPED } from './translations';
|
||||
|
||||
enum MessageLevels {
|
||||
info = 'info',
|
||||
warning = 'warning',
|
||||
error = 'error',
|
||||
}
|
||||
|
||||
const AuditIcon: React.FC<{
|
||||
message: SiemJob['auditMessage'];
|
||||
}> = ({ message }) => {
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let color = 'primary';
|
||||
let icon = 'alert';
|
||||
|
||||
if (message.level === MessageLevels.info) {
|
||||
icon = 'iInCircle';
|
||||
} else if (message.level === MessageLevels.warning) {
|
||||
color = 'warning';
|
||||
} else if (message.level === MessageLevels.error) {
|
||||
color = 'danger';
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip content={message.text}>
|
||||
<EuiIcon type={icon} color={color} />
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
export const JobStatusBadge: React.FC<{ job: SiemJob }> = ({ job }) => {
|
||||
const isStarted = isJobStarted(job.jobState, job.datafeedState);
|
||||
|
||||
return isStarted ? (
|
||||
<EuiBadge color="secondary">{ML_JOB_STARTED}</EuiBadge>
|
||||
) : (
|
||||
<EuiBadge color="danger">{ML_JOB_STOPPED}</EuiBadge>
|
||||
);
|
||||
};
|
||||
|
||||
const JobLink = styled(EuiLink)`
|
||||
margin-right: ${({ theme }) => theme.eui.euiSizeS};
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => {
|
||||
const jobUrl = useKibana().services.application.getUrlForApp('ml#/jobs');
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<div>
|
||||
<JobLink href={jobUrl} target="_blank">
|
||||
{job.id}
|
||||
</JobLink>
|
||||
<AuditIcon message={job.auditMessage} />
|
||||
</div>
|
||||
<JobStatusBadge job={job} />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const buildMlJobDescription = (
|
||||
jobId: string,
|
||||
label: string,
|
||||
siemJobs: SiemJob[]
|
||||
): ListItems => {
|
||||
const siemJob = siemJobs.find(job => job.id === jobId);
|
||||
|
||||
return {
|
||||
title: label,
|
||||
description: siemJob ? <MlJobDescription job={siemJob} /> : jobId,
|
||||
};
|
||||
};
|
|
@ -17,3 +17,31 @@ export const QUERY_LABEL = i18n.translate('xpack.siem.detectionEngine.createRule
|
|||
export const SAVED_ID_LABEL = i18n.translate('xpack.siem.detectionEngine.createRule.savedIdLabel', {
|
||||
defaultMessage: 'Saved query name',
|
||||
});
|
||||
|
||||
export const ML_TYPE_DESCRIPTION = i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.mlRuleTypeDescription',
|
||||
{
|
||||
defaultMessage: 'Machine Learning',
|
||||
}
|
||||
);
|
||||
|
||||
export const QUERY_TYPE_DESCRIPTION = i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.queryRuleTypeDescription',
|
||||
{
|
||||
defaultMessage: 'Query',
|
||||
}
|
||||
);
|
||||
|
||||
export const ML_JOB_STARTED = i18n.translate(
|
||||
'xpack.siem.detectionEngine.ruleDescription.mlJobStartedDescription',
|
||||
{
|
||||
defaultMessage: 'Started',
|
||||
}
|
||||
);
|
||||
|
||||
export const ML_JOB_STOPPED = i18n.translate(
|
||||
'xpack.siem.detectionEngine.ruleDescription.mlJobStoppedDescription',
|
||||
{
|
||||
defaultMessage: 'Stopped',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,12 +5,39 @@
|
|||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSuperSelect, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiSuperSelect,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports';
|
||||
import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs';
|
||||
import { useKibana } from '../../../../../lib/kibana';
|
||||
import { ML_JOB_SELECT_PLACEHOLDER_TEXT } from '../step_define_rule/translations';
|
||||
|
||||
const JobDisplay = ({ title, description }: { title: string; description: string }) => (
|
||||
const HelpText: React.FC<{ href: string }> = ({ href }) => (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.detectionEngine.createRule.stepDefineRule.machineLearningJobIdHelpText"
|
||||
defaultMessage="We've provided a few common jobs to get you started. To add your own custom jobs, assign a group of “siem” to those jobs in the {machineLearning} application to make them appear here."
|
||||
values={{
|
||||
machineLearning: (
|
||||
<EuiLink href={href} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.siem.components.mlJobSelect.machineLearningLink"
|
||||
defaultMessage="Machine Learning"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const JobDisplay: React.FC<{ title: string; description: string }> = ({ title, description }) => (
|
||||
<>
|
||||
<strong>{title}</strong>
|
||||
<EuiText size="xs" color="subdued">
|
||||
|
@ -28,23 +55,32 @@ export const MlJobSelect: React.FC<MlJobSelectProps> = ({ describedByIds = [], f
|
|||
const jobId = field.value as string;
|
||||
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
|
||||
const [isLoading, siemJobs] = useSiemJobs(false);
|
||||
const mlUrl = useKibana().services.application.getUrlForApp('ml');
|
||||
const handleJobChange = useCallback(
|
||||
(machineLearningJobId: string) => {
|
||||
field.setValue(machineLearningJobId);
|
||||
},
|
||||
[field]
|
||||
);
|
||||
const placeholderOption = {
|
||||
value: 'placeholder',
|
||||
inputDisplay: ML_JOB_SELECT_PLACEHOLDER_TEXT,
|
||||
dropdownDisplay: ML_JOB_SELECT_PLACEHOLDER_TEXT,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const options = siemJobs.map(job => ({
|
||||
const jobOptions = siemJobs.map(job => ({
|
||||
value: job.id,
|
||||
inputDisplay: job.id,
|
||||
dropdownDisplay: <JobDisplay title={job.id} description={job.description} />,
|
||||
}));
|
||||
|
||||
const options = [placeholderOption, ...jobOptions];
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={field.label}
|
||||
helpText={<HelpText href={mlUrl} />}
|
||||
isInvalid={isInvalid}
|
||||
error={errorMessage}
|
||||
data-test-subj="mlJobSelect"
|
||||
|
@ -57,7 +93,7 @@ export const MlJobSelect: React.FC<MlJobSelectProps> = ({ describedByIds = [], f
|
|||
isLoading={isLoading}
|
||||
onChange={handleJobChange}
|
||||
options={options}
|
||||
valueOfSelected={jobId}
|
||||
valueOfSelected={jobId || 'placeholder'}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -59,7 +59,6 @@ export const PickTimeline = ({
|
|||
helpText={field.helpText}
|
||||
error={errorMessage}
|
||||
isInvalid={isInvalid}
|
||||
fullWidth
|
||||
data-test-subj={dataTestSubj}
|
||||
describedByIds={idAria ? [idAria] : undefined}
|
||||
>
|
||||
|
|
|
@ -48,14 +48,16 @@ interface SelectRuleTypeProps {
|
|||
describedByIds?: string[];
|
||||
field: FieldHook;
|
||||
hasValidLicense?: boolean;
|
||||
isMlAdmin?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({
|
||||
describedByIds = [],
|
||||
field,
|
||||
hasValidLicense = false,
|
||||
isReadOnly = false,
|
||||
hasValidLicense = false,
|
||||
isMlAdmin = false,
|
||||
}) => {
|
||||
const ruleType = field.value as RuleType;
|
||||
const setType = useCallback(
|
||||
|
@ -66,7 +68,7 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({
|
|||
);
|
||||
const setMl = useCallback(() => setType('machine_learning'), [setType]);
|
||||
const setQuery = useCallback(() => setType('query'), [setType]);
|
||||
const mlCardDisabled = isReadOnly || !hasValidLicense;
|
||||
const mlCardDisabled = isReadOnly || !hasValidLicense || !isMlAdmin;
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
|
|
|
@ -15,19 +15,14 @@ import { HeaderSection } from '../../../../../components/header_section';
|
|||
import { StepAboutRule } from '../step_about_rule/';
|
||||
import { AboutStepRule } from '../../types';
|
||||
|
||||
jest.mock('../../../../../lib/kibana');
|
||||
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
|
||||
describe('StepAboutRuleToggleDetails', () => {
|
||||
let mockRule: AboutStepRule;
|
||||
|
||||
beforeEach(() => {
|
||||
// jest carries state between mocked implementations when using
|
||||
// spyOn. So now we're doing all three of these.
|
||||
// https://github.com/facebook/jest/issues/7136#issuecomment-565976599
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockRule = mockAboutStepRule();
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiFormRow } from '@elastic/eui';
|
||||
import React, { FC, memo, useCallback, useState, useEffect, useContext } from 'react';
|
||||
import React, { FC, memo, useCallback, useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/pu
|
|||
import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules';
|
||||
import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants';
|
||||
import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/translations';
|
||||
import { MlCapabilitiesContext } from '../../../../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { useMlCapabilities } from '../../../../../components/ml_popover/hooks/use_ml_capabilities';
|
||||
import { useUiSetting$ } from '../../../../../lib/kibana';
|
||||
import { setFieldValue, isMlRule } from '../../helpers';
|
||||
import { DefineStepRule, RuleStep, RuleStepProps } from '../../types';
|
||||
|
@ -37,6 +37,7 @@ import {
|
|||
import { schema } from './schema';
|
||||
import * as i18n from './translations';
|
||||
import { filterRuleFieldsForType, RuleFields } from '../../create/helpers';
|
||||
import { hasMlAdminPermissions } from '../../../../../components/ml/permissions/has_ml_admin_permissions';
|
||||
|
||||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
|
@ -85,7 +86,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
setForm,
|
||||
setStepData,
|
||||
}) => {
|
||||
const mlCapabilities = useContext(MlCapabilitiesContext);
|
||||
const mlCapabilities = useMlCapabilities();
|
||||
const [openTimelineSearch, setOpenTimelineSearch] = useState(false);
|
||||
const [indexModified, setIndexModified] = useState(false);
|
||||
const [localIsMlRule, setIsMlRule] = useState(false);
|
||||
|
@ -162,8 +163,9 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
component={SelectRuleType}
|
||||
componentProps={{
|
||||
describedByIds: ['detectionEngineStepDefineRuleType'],
|
||||
hasValidLicense: mlCapabilities.isPlatinumOrTrialLicense,
|
||||
isReadOnly: isUpdateView,
|
||||
hasValidLicense: mlCapabilities.isPlatinumOrTrialLicense,
|
||||
isMlAdmin: hasMlAdminPermissions(mlCapabilities),
|
||||
}}
|
||||
/>
|
||||
<EuiFormRow fullWidth style={{ display: localIsMlRule ? 'none' : 'flex' }}>
|
||||
|
|
|
@ -55,3 +55,10 @@ export const IMPORT_TIMELINE_QUERY = i18n.translate(
|
|||
defaultMessage: 'Import query from saved timeline',
|
||||
}
|
||||
);
|
||||
|
||||
export const ML_JOB_SELECT_PLACEHOLDER_TEXT = i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.stepDefineRule.mlJobSelectPlaceholderText',
|
||||
{
|
||||
defaultMessage: 'Select a job',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useContext, useEffect, useCallback, useMemo } from 'react';
|
||||
import React, { useEffect, useCallback, useMemo } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
|
||||
|
@ -15,7 +15,7 @@ import { LastEventTime } from '../../../components/last_event_time';
|
|||
import { AnomalyTableProvider } from '../../../components/ml/anomaly/anomaly_table_provider';
|
||||
import { hostToCriteria } from '../../../components/ml/criteria/host_to_criteria';
|
||||
import { hasMlUserPermissions } from '../../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { useMlCapabilities } from '../../../components/ml_popover/hooks/use_ml_capabilities';
|
||||
import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime';
|
||||
import { SiemNavigation } from '../../../components/navigation';
|
||||
import { KpiHostsComponent } from '../../../components/page/hosts';
|
||||
|
@ -62,7 +62,7 @@ const HostDetailsComponent = React.memo<HostDetailsProps & PropsFromRedux>(
|
|||
useEffect(() => {
|
||||
setHostDetailsTablesActivePageToZero();
|
||||
}, [setHostDetailsTablesActivePageToZero, detailName]);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const kibana = useKibana();
|
||||
const hostDetailsPageFilters: Filter[] = useMemo(() => getHostDetailsPageFilters(detailName), [
|
||||
detailName,
|
||||
|
|
|
@ -14,7 +14,6 @@ import { FiltersGlobal } from '../../components/filters_global';
|
|||
import { HeaderPage } from '../../components/header_page';
|
||||
import { LastEventTime } from '../../components/last_event_time';
|
||||
import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { SiemNavigation } from '../../components/navigation';
|
||||
import { KpiHostsComponent } from '../../components/page/hosts';
|
||||
import { manageQuery } from '../../components/page/manage_query';
|
||||
|
@ -30,6 +29,7 @@ import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from
|
|||
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
import { esQuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { useMlCapabilities } from '../../components/ml_popover/hooks/use_ml_capabilities';
|
||||
import { HostsEmptyPage } from './hosts_empty_page';
|
||||
import { HostsTabs } from './hosts_tabs';
|
||||
import { navTabsHosts } from './nav_tabs';
|
||||
|
@ -52,7 +52,7 @@ export const HostsComponent = React.memo<HostsComponentProps & PropsFromRedux>(
|
|||
to,
|
||||
hostsPagePath,
|
||||
}) => {
|
||||
const capabilities = React.useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const kibana = useKibana();
|
||||
const { tabName } = useParams();
|
||||
const tabsFilters = React.useMemo(() => {
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { useMlCapabilities } from '../../components/ml_popover/hooks/use_ml_capabilities';
|
||||
import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { FlowTarget } from '../../graphql/types';
|
||||
|
||||
|
@ -24,7 +24,7 @@ const networkPagePath = `/:pageName(${SiemPageName.network})`;
|
|||
const ipDetailsPageBasePath = `${networkPagePath}/ip/:detailName`;
|
||||
|
||||
const NetworkContainerComponent: React.FC<Props> = () => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const capabilities = useMlCapabilities();
|
||||
const capabilitiesFetched = capabilities.capabilitiesFetched;
|
||||
const userHasMlUserPermissions = useMemo(() => hasMlUserPermissions(capabilities), [
|
||||
capabilities,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue