Skip to content

Commit

Permalink
Merge branch 'master' into test/mappings-editor
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticmachine authored May 6, 2020
2 parents ab76582 + eccdbdb commit 793bbdc
Show file tree
Hide file tree
Showing 76 changed files with 1,903 additions and 598 deletions.
1 change: 1 addition & 0 deletions .backportrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"targetBranchChoices": [
{ "name": "master", "checked": true },
{ "name": "7.x", "checked": true },
"7.8",
"7.7",
"7.6",
"7.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,18 +185,22 @@ describe('ServerMetricsCollector', () => {
let metrics = await collector.collect();
expect(metrics.concurrent_connections).toEqual(0);

sendGet('/').end(() => null);
// supertest requests are executed when calling `.then` (or awaiting them).
// however in this test we need to send the request now and await for it later in the code.
// also using `.end` is not possible as it would execute the request twice.
// so the only option is this noop `.then`.
const res1 = sendGet('/').then(res => res);
await waitForHits(1);
metrics = await collector.collect();
expect(metrics.concurrent_connections).toEqual(1);

sendGet('/').end(() => null);
const res2 = sendGet('/').then(res => res);
await waitForHits(2);
metrics = await collector.collect();
expect(metrics.concurrent_connections).toEqual(2);

waitSubject.next('go');
await delay(requestWaitDelay);
await Promise.all([res1, res2]);
metrics = await collector.collect();
expect(metrics.concurrent_connections).toEqual(0);
});
Expand Down
1 change: 1 addition & 0 deletions test/functional/apps/visualize/_tsvb_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['visualize', 'visualBuilder', 'timePicker', 'visChart']);

describe('visual builder', function describeIndexTests() {
this.tags('includeFirefox');
beforeEach(async () => {
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
await PageObjects.visualize.navigateToNewVisualization();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { CustomLink } from './CustomLink';
import { CustomLinkPopover } from './CustomLink/CustomLinkPopover';
import { getSections } from './sections';
import { useLicense } from '../../../hooks/useLicense';
import { px } from '../../../style/variables';
import { convertFiltersToQuery } from '../../app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper';

interface Props {
Expand Down Expand Up @@ -124,7 +123,7 @@ export const TransactionActionMenu: FunctionComponent<Props> = ({
<ActionMenuButton onClick={() => setIsActionPopoverOpen(true)} />
}
>
<div style={{ maxHeight: px(600), width: px(335) }}>
<div>
{isCustomLinksPopoverOpen ? (
<CustomLinkPopover
customLinks={customLinks.slice(3, customLinks.length)}
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/endpoint/common/generate_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ export class EndpointDocGenerator {
applied: {
actions: {
configure_elasticsearch_connection: {
message: 'elasticsearch comms configured successfully',
message: 'elasticsearch comes configured successfully',
status: HostPolicyResponseActionStatus.success,
},
configure_kernel: {
Expand Down Expand Up @@ -648,7 +648,7 @@ export class EndpointDocGenerator {
response: {
configurations: {
events: {
concerned_actions: this.randomHostPolicyResponseActions(),
concerned_actions: ['download_model'],
status: this.randomHostPolicyResponseActionStatus(),
},
logging: {
Expand Down
4 changes: 3 additions & 1 deletion x-pack/plugins/endpoint/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type Immutable<T> = T extends undefined | null | boolean | string | numbe
? ImmutableSet<M>
: ImmutableObject<T>;

type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
export type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
Expand Down Expand Up @@ -644,6 +644,8 @@ export interface HostPolicyResponseActions {
read_malware_config: HostPolicyResponseActionDetails;
}

export type HostPolicyResponseConfiguration = HostPolicyResponse['endpoint']['policy']['applied']['response']['configurations'];

interface HostPolicyResponseConfigurationStatus {
status: HostPolicyResponseActionStatus;
concerned_actions: Array<keyof HostPolicyResponseActions>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { HostResultList } from '../../../../../common/types';
import { HostResultList, HostPolicyResponseActionStatus } from '../../../../../common/types';
import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors';
import { HostState } from '../../types';
import { ImmutableMiddlewareFactory } from '../../types';
Expand Down Expand Up @@ -77,7 +77,31 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory<HostState> = core
endpoint: {
policy: {
applied: {
status: 'success',
version: '1.0.0',
status: HostPolicyResponseActionStatus.success,
id: '17d4b81d-9940-4b64-9de5-3e03ef1fb5cf',
actions: {
download_model: {
status: 'success',
message: 'Model downloaded',
},
ingest_events_config: {
status: 'failure',
message: 'No action taken',
},
},
response: {
configurations: {
malware: {
status: 'success',
concerned_actions: ['download_model'],
},
events: {
status: 'failure',
concerned_actions: ['ingest_events_config'],
},
},
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
*/
import querystring from 'querystring';
import { createSelector } from 'reselect';
import { Immutable } from '../../../../../common/types';
import {
Immutable,
HostPolicyResponseActions,
HostPolicyResponseConfiguration,
HostPolicyResponseActionStatus,
} from '../../../../../common/types';
import { HostState, HostIndexUIQueryParams } from '../../types';

const PAGE_SIZES = Object.freeze([10, 20, 50]);
Expand All @@ -28,6 +33,61 @@ export const detailsLoading = (state: Immutable<HostState>): boolean => state.de

export const detailsError = (state: Immutable<HostState>) => state.detailsError;

/**
* Returns the full policy response from the endpoint after a user modifies a policy.
*/
const detailsPolicyAppliedResponse = (state: Immutable<HostState>) =>
state.policyResponse && state.policyResponse.endpoint.policy.applied;

/**
* Returns the response configurations from the endpoint after a user modifies a policy.
*/
export const policyResponseConfigurations: (
state: Immutable<HostState>
) => undefined | Immutable<HostPolicyResponseConfiguration> = createSelector(
detailsPolicyAppliedResponse,
applied => {
return applied?.response?.configurations;
}
);

/**
* Returns a map of the number of failed and warning policy response actions per configuration.
*/
export const policyResponseFailedOrWarningActionCount: (
state: Immutable<HostState>
) => Map<string, number> = createSelector(detailsPolicyAppliedResponse, applied => {
const failureOrWarningByConfigType = new Map<string, number>();
if (applied?.response?.configurations !== undefined && applied?.actions !== undefined) {
Object.entries(applied.response.configurations).map(([key, val]) => {
let count = 0;
for (const action of val.concerned_actions) {
const actionStatus = applied.actions[action]?.status;
if (
actionStatus === HostPolicyResponseActionStatus.failure ||
actionStatus === HostPolicyResponseActionStatus.warning
) {
count += 1;
}
}
return failureOrWarningByConfigType.set(key, count);
});
}
return failureOrWarningByConfigType;
});

/**
* Returns the actions taken by the endpoint for each response configuration after a user modifies a policy.
*/
export const policyResponseActions: (
state: Immutable<HostState>
) => undefined | Partial<HostPolicyResponseActions> = createSelector(
detailsPolicyAppliedResponse,
applied => {
return applied?.actions;
}
);

export const isOnHostPage = (state: Immutable<HostState>) =>
state.location ? state.location.pathname === '/hosts' : false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { EuiFlyoutHeader, CommonProps, EuiButtonEmpty } from '@elastic/eui';
import styled from 'styled-components';

export type FlyoutSubHeaderProps = CommonProps & {
children: React.ReactNode;
children?: React.ReactNode;
backButton?: {
title: string;
onClick: MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
Expand All @@ -25,6 +25,9 @@ const StyledEuiFlyoutHeader = styled(EuiFlyoutHeader)`
padding-bottom: ${props => props.theme.eui.paddingSizes.s};
}
.flyoutSubHeaderBackButton {
font-size: ${props => props.theme.eui.euiFontSizeXS};
}
.back-button-content {
padding-left: 0;
&-text {
Expand All @@ -48,7 +51,7 @@ const BUTTON_TEXT_PROPS = Object.freeze({ className: 'back-button-content-text'
export const FlyoutSubHeader = memo<FlyoutSubHeaderProps>(
({ children, backButton, ...otherProps }) => {
return (
<StyledEuiFlyoutHeader hasBorder {...otherProps} className={backButton && `hasButtons`}>
<StyledEuiFlyoutHeader {...otherProps} className={backButton && `hasButtons`}>
{backButton && (
<div className="buttons">
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
Expand All @@ -60,6 +63,7 @@ export const FlyoutSubHeader = memo<FlyoutSubHeaderProps>(
size="xs"
href={backButton?.href ?? ''}
onClick={backButton?.onClick}
className="flyoutSubHeaderBackButton"
>
{backButton?.title}
</EuiButtonEmpty>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*/

import { HostPolicyResponseActionStatus } from '../../../../../../common/types';

export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
{ [key in keyof typeof HostPolicyResponseActionStatus]: string }
>({
success: 'success',
warning: 'warning',
failure: 'danger',
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import {
import React, { memo, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { HostMetadata, HostPolicyResponseActionStatus } from '../../../../../../common/types';
import { HostMetadata } from '../../../../../../common/types';
import { FormattedDateAndTime } from '../../formatted_date_time';
import { LinkToApp } from '../../components/link_to_app';
import { useHostSelector, useHostLogsUrl } from '../hooks';
import { urlFromQueryParams } from '../url_from_query_params';
import { policyResponseStatus, uiQueryParams } from '../../../store/hosts/selectors';
import { useNavigateByRouterEventHandler } from '../../hooks/use_navigate_by_router_event_handler';
import { POLICY_STATUS_TO_HEALTH_COLOR } from './host_constants';

const HostIds = styled(EuiListGroupItem)`
margin-top: 0;
Expand All @@ -31,14 +32,6 @@ const HostIds = styled(EuiListGroupItem)`
}
`;

const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
{ [key in keyof typeof HostPolicyResponseActionStatus]: string }
>({
success: 'success',
warning: 'warning',
failure: 'danger',
});

export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
const { appId, appPath, url } = useHostLogsUrl(details.host.id);
const queryParams = useHostSelector(uiQueryParams);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiTitle,
EuiLoadingContent,
EuiTitle,
EuiText,
EuiSpacer,
} from '@elastic/eui';
import { useHistory } from 'react-router-dom';
Expand All @@ -25,6 +26,9 @@ import {
detailsError,
showView,
detailsLoading,
policyResponseConfigurations,
policyResponseActions,
policyResponseFailedOrWarningActionCount,
} from '../../../store/hosts/selectors';
import { HostDetails } from './host_details';
import { PolicyResponse } from './policy_response';
Expand Down Expand Up @@ -101,6 +105,9 @@ const PolicyResponseFlyoutPanel = memo<{
hostMeta: HostMetadata;
}>(({ hostMeta }) => {
const { show, ...queryParams } = useHostSelector(uiQueryParams);
const responseConfig = useHostSelector(policyResponseConfigurations);
const responseActionStatus = useHostSelector(policyResponseActions);
const responseAttentionCount = useHostSelector(policyResponseFailedOrWarningActionCount);
const detailsUri = useMemo(
() =>
urlFromQueryParams({
Expand All @@ -125,18 +132,28 @@ const PolicyResponseFlyoutPanel = memo<{
<FlyoutSubHeader
backButton={backButtonProp}
data-test-subj="hostDetailsPolicyResponseFlyoutHeader"
>
<EuiTitle size="xxs" data-test-subj="hostDetailsPolicyResponseFlyoutTitle">
<h3>
/>
<EuiFlyoutBody data-test-subj="hostDetailsPolicyResponseFlyoutBody">
<EuiText data-test-subj="hostDetailsPolicyResponseFlyoutTitle">
<h4>
<FormattedMessage
id="xpack.endpoint.host.policyResponse.title"
defaultMessage="Policy Response"
/>
</h3>
</EuiTitle>
</FlyoutSubHeader>
<EuiFlyoutBody data-test-subj="hostDetailsPolicyResponseFlyoutBody">
<PolicyResponse />
</h4>
</EuiText>
{responseConfig !== undefined && responseActionStatus !== undefined ? (
<PolicyResponse
responseConfig={responseConfig}
responseActionStatus={responseActionStatus}
responseAttentionCount={responseAttentionCount}
/>
) : (
<FormattedMessage
id="xpack.endpoint.hostDetails.noPolicyResponse"
defaultMessage="No Policy Response Available"
/>
)}
</EuiFlyoutBody>
</>
);
Expand Down
Loading

0 comments on commit 793bbdc

Please sign in to comment.