Skip to content

Commit

Permalink
[Endpoint] Policy Details integration with Ingest APIs (elastic#61827) (
Browse files Browse the repository at this point in the history
elastic#62073)

* implement Policy Details header
* use Ingest APIs to persist (save) policy data to the Datasource
* implement UI behaviour for Save, Cancel
* implement UI bahaviour for when Policy (datasource) can not be retrieved
  • Loading branch information
paul-tavares authored Mar 31, 2020
1 parent b5c9181 commit a62f8ed
Show file tree
Hide file tree
Showing 17 changed files with 1,076 additions and 222 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,67 @@ describe('PageView component', () => {
mount(ui, { wrappingComponent: EuiThemeProvider });

it('should display only body if not header props used', () => {
expect(render(<PageView>body content</PageView>)).toMatchSnapshot();
expect(render(<PageView viewType="list">body content</PageView>)).toMatchSnapshot();
});
it('should display header left and right', () => {
expect(
render(
<PageView headerLeft="page title" headerRight="right side actions">
<PageView viewType="list" headerLeft="page title" headerRight="right side actions">
body content
</PageView>
)
).toMatchSnapshot();
});
it('should display only header left', () => {
expect(render(<PageView headerLeft="page title">body content</PageView>)).toMatchSnapshot();
expect(
render(
<PageView viewType="list" headerLeft="page title">
body content
</PageView>
)
).toMatchSnapshot();
});
it('should display only header right but include an empty left side', () => {
expect(
render(<PageView headerRight="right side actions">body content</PageView>)
render(
<PageView viewType="list" headerRight="right side actions">
body content
</PageView>
)
).toMatchSnapshot();
});
it(`should use custom element for header left and not wrap in EuiTitle`, () => {
expect(
render(<PageView headerLeft={<p>title here</p>}>body content</PageView>)
render(
<PageView viewType="list" headerLeft={<p>title here</p>}>
body content
</PageView>
)
).toMatchSnapshot();
});
it('should display body header wrapped in EuiTitle', () => {
expect(render(<PageView bodyHeader="body header">body content</PageView>)).toMatchSnapshot();
expect(
render(
<PageView viewType="list" bodyHeader="body header">
body content
</PageView>
)
).toMatchSnapshot();
});
it('should display body header custom element', () => {
expect(
render(<PageView bodyHeader={<p>body header</p>}>body content</PageView>)
render(
<PageView viewType="list" bodyHeader={<p>body header</p>}>
body content
</PageView>
)
).toMatchSnapshot();
});
it('should pass through EuiPage props', () => {
expect(
render(
<PageView
viewType="list"
restrictWidth="1000"
className="test-class-name-here"
aria-label="test-aria-label-here"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,65 @@ import React, { memo, ReactNode } from 'react';
import styled from 'styled-components';

const StyledEuiPage = styled(EuiPage)`
padding: 0;
&.endpoint--isListView {
padding: 0;
.endpoint-header {
padding: ${props => props.theme.eui.euiSizeL};
.endpoint-header {
padding: ${props => props.theme.eui.euiSizeL};
}
.endpoint-page-content {
border-left: none;
border-right: none;
}
}
.endpoint-page-content {
border-left: none;
border-right: none;
&.endpoint--isDetailsView {
.endpoint-page-content {
padding: 0;
border: none;
background: none;
}
}
`;

const isStringOrNumber = /(string|number)/;

/**
* The `PageView` component used to render `headerLeft` when it is set as a `string`
* Can be used when wanting to customize the `headerLeft` value but still use the standard
* title component
*/
export const PageViewHeaderTitle = memo<{ children: ReactNode }>(({ children }) => {
return (
<EuiTitle size="l">
<h1 data-test-subj="pageViewHeaderLeftTitle">{children}</h1>
</EuiTitle>
);
});

/**
* The `PageView` component used to render `bodyHeader` when it is set as a `string`
* Can be used when wanting to customize the `bodyHeader` value but still use the standard
* title component
*/
export const PageViewBodyHeaderTitle = memo<{ children: ReactNode }>(
({ children, ...otherProps }) => {
return (
<EuiTitle {...otherProps}>
<h2 data-test-subj="pageViewBodyTitle">{children}</h2>
</EuiTitle>
);
}
);

/**
* Page View layout for use in Endpoint
*/
export const PageView = memo<
EuiPageProps & {
/**
* The type of view
*/
viewType: 'list' | 'details';
/**
* content to be placed on the left side of the header. If a `string` is used, then it will
* be wrapped with `<EuiTitle><h1></h1></EuiTitle>`, else it will just be used as is.
Expand All @@ -52,17 +93,18 @@ export const PageView = memo<
bodyHeader?: ReactNode;
children?: ReactNode;
}
>(({ children, headerLeft, headerRight, bodyHeader, ...otherProps }) => {
>(({ viewType, children, headerLeft, headerRight, bodyHeader, ...otherProps }) => {
return (
<StyledEuiPage {...otherProps}>
<StyledEuiPage
className={(viewType === 'list' && 'endpoint--isListView') || 'endpoint--isDetailsView'}
{...otherProps}
>
<EuiPageBody>
{(headerLeft || headerRight) && (
<EuiPageHeader className="endpoint-header">
<EuiPageHeaderSection data-test-subj="pageViewHeaderLeft">
{isStringOrNumber.test(typeof headerLeft) ? (
<EuiTitle size="l">
<h1>{headerLeft}</h1>
</EuiTitle>
<PageViewHeaderTitle>{headerLeft}</PageViewHeaderTitle>
) : (
headerLeft
)}
Expand All @@ -77,11 +119,9 @@ export const PageView = memo<
<EuiPageContent className="endpoint-page-content">
{bodyHeader && (
<EuiPageContentHeader>
<EuiPageContentHeaderSection data-test-subj="pageViewBodyTitle">
<EuiPageContentHeaderSection data-test-subj="pageViewBodyTitleArea">
{isStringOrNumber.test(typeof bodyHeader) ? (
<EuiTitle>
<h2>{bodyHeader}</h2>
</EuiTitle>
<PageViewBodyHeaderTitle>{bodyHeader}</PageViewBodyHeaderTitle>
) : (
bodyHeader
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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 { PolicyConfig } from '../types';

/**
* Generate a new Policy model.
* NOTE: in the near future, this will likely be removed and an API call to EPM will be used to retrieve
* the latest from the Endpoint package
*/
export const generatePolicy = (): PolicyConfig => {
return {
windows: {
events: {
process: true,
network: true,
},
malware: {
mode: 'prevent',
},
logging: {
stdout: 'debug',
file: 'info',
},
advanced: {
elasticsearch: {
indices: {
control: 'control-index',
event: 'event-index',
logging: 'logging-index',
},
kernel: {
connect: true,
process: true,
},
},
},
},
mac: {
events: {
process: true,
},
malware: {
mode: 'detect',
},
logging: {
stdout: 'debug',
file: 'info',
},
advanced: {
elasticsearch: {
indices: {
control: 'control-index',
event: 'event-index',
logging: 'logging-index',
},
kernel: {
connect: true,
process: true,
},
},
},
},
linux: {
events: {
process: true,
},
logging: {
stdout: 'debug',
file: 'info',
},
advanced: {
elasticsearch: {
indices: {
control: 'control-index',
event: 'event-index',
logging: 'logging-index',
},
kernel: {
connect: true,
process: true,
},
},
},
},
};
};
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 { PolicyConfig } from '../types';
import { UIPolicyConfig } from '../types';

/**
* A typed Object.entries() function where the keys and values are typed based on the given object
Expand All @@ -14,10 +14,10 @@ const entries = <T extends object>(o: T): Array<[keyof T, T[keyof T]]> =>
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };

/**
* Returns a deep copy of PolicyDetailsConfig
* Returns a deep copy of `UIPolicyConfig` object
*/
export function clone(policyDetailsConfig: PolicyConfig): PolicyConfig {
const clonedConfig: DeepPartial<PolicyConfig> = {};
export function clone(policyDetailsConfig: UIPolicyConfig): UIPolicyConfig {
const clonedConfig: DeepPartial<UIPolicyConfig> = {};
for (const [key, val] of entries(policyDetailsConfig)) {
if (typeof val === 'object') {
const valClone: Partial<typeof val> = {};
Expand All @@ -41,5 +41,5 @@ export function clone(policyDetailsConfig: PolicyConfig): PolicyConfig {
/**
* clonedConfig is typed as DeepPartial so we can construct the copy from an empty object
*/
return clonedConfig as PolicyConfig;
return clonedConfig as UIPolicyConfig;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
*/

import { HttpFetchOptions, HttpStart } from 'kibana/public';
import { GetDatasourcesRequest } from '../../../../../ingest_manager/common/types/rest_spec';
import { PolicyData } from '../types';
import {
CreateDatasourceResponse,
GetAgentStatusResponse,
GetDatasourcesRequest,
} from '../../../../../ingest_manager/common/types/rest_spec';
import { NewPolicyData, PolicyData } from '../types';

const INGEST_API_ROOT = `/api/ingest_manager`;
const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`;
const INGEST_API_FLEET = `${INGEST_API_ROOT}/fleet`;
const INGEST_API_FLEET_AGENT_STATUS = `${INGEST_API_FLEET}/agent-status`;

// FIXME: Import from ingest after - https://github.com/elastic/kibana/issues/60677
export interface GetDatasourcesResponse {
Expand All @@ -26,6 +32,11 @@ export interface GetDatasourceResponse {
success: boolean;
}

// FIXME: Import from Ingest after - https://github.com/elastic/kibana/issues/60677
export type UpdateDatasourceResponse = CreateDatasourceResponse & {
item: PolicyData;
};

/**
* Retrieves a list of endpoint specific datasources (those created with a `package.name` of
* `endpoint`) from Ingest
Expand Down Expand Up @@ -60,3 +71,44 @@ export const sendGetDatasource = (
) => {
return http.get<GetDatasourceResponse>(`${INGEST_API_DATASOURCES}/${datasourceId}`, options);
};

/**
* Updates a datasources
*
* @param http
* @param datasourceId
* @param datasource
* @param options
*/
export const sendPutDatasource = (
http: HttpStart,
datasourceId: string,
datasource: NewPolicyData,
options: Exclude<HttpFetchOptions, 'body'> = {}
): Promise<UpdateDatasourceResponse> => {
return http.put(`${INGEST_API_DATASOURCES}/${datasourceId}`, {
...options,
body: JSON.stringify(datasource),
});
};

/**
* Get a status summary for all Agents that are currently assigned to a given agent configuration
*
* @param http
* @param configId
* @param options
*/
export const sendGetFleetAgentStatusForConfig = (
http: HttpStart,
/** the Agent (fleet) configuration id */
configId: string,
options: Exclude<HttpFetchOptions, 'query'> = {}
): Promise<GetAgentStatusResponse> => {
return http.get(INGEST_API_FLEET_AGENT_STATUS, {
...options,
query: {
configId,
},
});
};
Loading

0 comments on commit a62f8ed

Please sign in to comment.