Skip to content

Commit

Permalink
Merge branch 'master' of github.com:elastic/kibana into issue-104699-…
Browse files Browse the repository at this point in the history
…offset-ms-percission-trin-buckets
  • Loading branch information
simianhacker committed Jul 12, 2021
2 parents b085acf + 41a9307 commit 9624e5a
Show file tree
Hide file tree
Showing 16 changed files with 265 additions and 21 deletions.
12 changes: 10 additions & 2 deletions .github/workflows/project-assigner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ jobs:
name: Assign issue or PR to project based on label
steps:
- name: Assign to project
uses: elastic/github-actions/project-assigner@v2.0.0
uses: elastic/github-actions/project-assigner@v2.1.0
id: project_assigner
with:
issue-mappings: '[{"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"}, {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"}, {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"}]'
issue-mappings: |
[
{"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"},
{"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"},
{"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"},
{"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"},
{"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"},
{"label": "Team:Security", "projectNumber": 320, "columnName": "Awaiting triage", "projectScope": "org"}
]
ghToken: ${{ secrets.PROJECT_ASSIGNER_TOKEN }}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ import routeData from 'react-router';

import { getFormMock, useFormMock, useFormDataMock } from '../__mock__/form';
import { useUpdateComment } from '../../containers/use_update_comment';
import { basicCase, basicPush, getUserAction } from '../../containers/mock';
import {
basicCase,
basicPush,
getUserAction,
getHostIsolationUserAction,
hostIsolationComment,
hostReleaseComment,
} from '../../containers/mock';
import { UserActionTree } from '.';
import { TestProviders } from '../../common/mock';
import { Ecs } from '../../../common';
Expand Down Expand Up @@ -368,4 +375,82 @@ describe(`UserActionTree`, () => {
).toEqual(true);
});
});
describe('Host isolation action', () => {
it('renders in the cases details view', async () => {
const isolateAction = [getHostIsolationUserAction()];
const props = {
...defaultProps,
caseUserActions: isolateAction,
data: { ...defaultProps.data, comments: [...basicCase.comments, hostIsolationComment()] },
};

const wrapper = mount(
<TestProviders>
<UserActionTree {...props} />
</TestProviders>
);
await waitFor(() => {
expect(wrapper.find(`[data-test-subj="endpoint-action"]`).exists()).toBe(true);
});
});

it('shows the correct username', async () => {
const isolateAction = [getHostIsolationUserAction()];
const props = {
...defaultProps,
caseUserActions: isolateAction,
data: { ...defaultProps.data, comments: [hostIsolationComment()] },
};

const wrapper = mount(
<TestProviders>
<UserActionTree {...props} />
</TestProviders>
);
await waitFor(() => {
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().prop('name')).toEqual(
defaultProps.data.createdBy.fullName
);
});
});

it('shows a lock icon if the action is isolate', async () => {
const isolateAction = [getHostIsolationUserAction()];
const props = {
...defaultProps,
caseUserActions: isolateAction,
data: { ...defaultProps.data, comments: [hostIsolationComment()] },
};

const wrapper = mount(
<TestProviders>
<UserActionTree {...props} />
</TestProviders>
);
await waitFor(() => {
expect(
wrapper.find(`[data-test-subj="endpoint-action"]`).first().prop('timelineIcon')
).toBe('lock');
});
});
it('shows a lockOpen icon if the action is unisolate/release', async () => {
const isolateAction = [getHostIsolationUserAction()];
const props = {
...defaultProps,
caseUserActions: isolateAction,
data: { ...defaultProps.data, comments: [hostReleaseComment()] },
};

const wrapper = mount(
<TestProviders>
<UserActionTree {...props} />
</TestProviders>
);
await waitFor(() => {
expect(
wrapper.find(`[data-test-subj="endpoint-action"]`).first().prop('timelineIcon')
).toBe('lockOpen');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { mount } from 'enzyme';
import { HostIsolationCommentEvent } from './user_action_host_isolation_comment_event';

const defaultProps = () => {
return {
type: 'isolate',
endpoints: [{ endpointId: 'e1', hostname: 'host1' }],
href: jest.fn(),
onClick: jest.fn(),
};
};

describe('UserActionHostIsolationCommentEvent', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('renders with the correct action and hostname', async () => {
const wrapper = mount(<HostIsolationCommentEvent {...defaultProps()} />);
expect(wrapper.find(`[data-test-subj="actions-link-e1"]`).first().exists()).toBeTruthy();
expect(wrapper.text()).toBe('isolated host host1');
});

it('navigates to app on link click', async () => {
const onActionsLinkClick = jest.fn();

const wrapper = mount(
<HostIsolationCommentEvent {...defaultProps()} onClick={onActionsLinkClick} />
);

wrapper.find(`[data-test-subj="actions-link-e1"]`).first().simulate('click');
expect(onActionsLinkClick).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const HostIsolationCommentEventComponent: React.FC<Props> = ({
<LinkAnchor
onClick={onLinkClick}
href={endpointDetailsHref}
data-test-subj={`endpointDetails-activity-log-link-${endpoints[0].endpointId}`}
data-test-subj={`actions-link-${endpoints[0].endpointId}`}
>
{endpoints[0].hostname}
</LinkAnchor>
Expand Down
61 changes: 61 additions & 0 deletions x-pack/plugins/cases/public/containers/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,58 @@ export const alertComment: Comment = {
version: 'WzQ3LDFc',
};

export const hostIsolationComment: () => Comment = () => {
return {
type: CommentType.actions,
comment: 'I just isolated the host!',
id: 'isolate-comment-id',
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
associationType: AssociationType.case,
createdAt: basicCreatedAt,
createdBy: elasticUser,
owner: SECURITY_SOLUTION_OWNER,
pushedAt: null,
pushedBy: null,
updatedAt: null,
updatedBy: null,
version: 'WzQ3LDFc',
};
};

export const hostReleaseComment: () => Comment = () => {
return {
type: CommentType.actions,
comment: 'I just released the host!',
id: 'isolate-comment-id',
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'unisolate',
},
associationType: AssociationType.case,
createdAt: basicCreatedAt,
createdBy: elasticUser,
owner: SECURITY_SOLUTION_OWNER,
pushedAt: null,
pushedBy: null,
updatedAt: null,
updatedBy: null,
version: 'WzQ3LDFc',
};
};

export const basicCase: Case = {
type: CaseType.individual,
owner: SECURITY_SOLUTION_OWNER,
Expand Down Expand Up @@ -374,6 +426,15 @@ export const getAlertUserAction = () => ({
newValue: '{"type":"alert","alertId":"alert-id-1","index":"index-id-1"}',
});

export const getHostIsolationUserAction = () => ({
...basicAction,
actionId: 'isolate-action-id',
actionField: ['comment'] as UserActionField,
action: 'create' as UserAction,
commentId: 'isolate-comment-id',
newValue: 'some value',
});

export const caseUserActions: CaseUserActions[] = [
getUserAction(['description'], 'create'),
getUserAction(['comment'], 'create'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ export const exampleResult = {
titleField: 'otherTitle',
subtitleField: 'otherSubtitle',
urlField: 'myLink',
urlFieldIsLinkable: true,
color: '#e3e3e3',
descriptionField: 'about',
typeField: 'otherType',
Expand All @@ -314,14 +315,18 @@ export const exampleResult = {
{ fieldName: 'dogs', label: 'Canines' },
],
},
titleFieldHover: false,
urlFieldHover: false,
exampleDocuments: [
{
myLink: 'http://foo',
otherTitle: 'foo',
content_source_id: '60e85e7ea2564c265a88a4f0',
external_id: 'doc-60e85eb7a2564c937a88a4f3',
last_updated: '2021-07-09T14:35:35+00:00',
updated_at: '2021-07-09T14:35:35+00:00',
source: 'custom',
},
],
schemaFields: {},
};

export const mostRecentIndexJob = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export interface ContentSource {
export interface SourceContentItem {
id: string;
last_updated: string;
[key: string]: string;
[key: string]: string | CustomAPIFieldValue;
}

export interface ContentSourceDetails extends ContentSource {
Expand Down Expand Up @@ -186,8 +186,25 @@ export interface CustomSource {
id: string;
}

// https://www.elastic.co/guide/en/workplace-search/current/workplace-search-custom-sources-api.html#_schema_data_types
type CustomAPIString = string | string[];
type CustomAPINumber = number | number[];
type CustomAPIDate = string | string[];
type CustomAPIGeolocation = string | string[] | number[] | number[][];

export type CustomAPIFieldValue =
| CustomAPIString
| CustomAPINumber
| CustomAPIDate
| CustomAPIGeolocation;

export interface Result {
[key: string]: string | string[];
content_source_id: string;
last_updated: string;
external_id: string;
updated_at: string;
source: string;
[key: string]: CustomAPIFieldValue;
}

export interface OptionValue {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ describe('getAsLocalDateTimeString', () => {
expect(getAsLocalDateTimeString(date)).toEqual(new Date(Date.parse(date)).toLocaleString());
});

it('returns null if passed value is not a string', () => {
const date = ['1', '2'];

expect(getAsLocalDateTimeString(date)).toEqual(null);
});

it('returns null if string cannot be parsed as date', () => {
const date = 'foo';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
* 2.0.
*/

export const getAsLocalDateTimeString = (str: string) => {
const dateValue = Date.parse(str);
import { CustomAPIFieldValue } from '../types';

export const getAsLocalDateTimeString = (maybeDate: CustomAPIFieldValue) => {
if (typeof maybeDate !== 'string') return null;

const dateValue = Date.parse(maybeDate);
return dateValue ? new Date(dateValue).toLocaleString() : null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import { CustomAPIFieldValue } from '../types';

const mimeTypes = {
'application/iwork-keynote-sffkey': 'Keynote',
'application/x-iwork-keynote-sffkey': 'Keynote',
Expand Down Expand Up @@ -51,4 +53,5 @@ const mimeTypes = {
'video/quicktime': 'MOV',
} as { [key: string]: string };

export const mimeType = (type: string) => mimeTypes[type.toLowerCase()] || type;
export const mimeType = (type: CustomAPIFieldValue) =>
mimeTypes[type.toString().toLowerCase()] || type;
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const ExampleResultDetailCard: React.FC = () => {
<div className="example-result-detail-card__content">
{detailFields.length > 0 ? (
detailFields.map(({ fieldName, label }, index) => {
const value = result[fieldName] as string;
const value = result[fieldName];
const dateValue = getAsLocalDateTimeString(value);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const ExampleSearchResultGroup: React.FC = () => {
data-test-subj="MediaTypeField"
>
<span className="example-search-result__tag-content">
{mimeType(result[mediaTypeField] as string)}
{mimeType(result[mediaTypeField])}
</span>
</div>
)}
Expand All @@ -135,8 +135,7 @@ export const ExampleSearchResultGroup: React.FC = () => {
by {result[updatedByField]}&nbsp;
</span>
)}
{getAsLocalDateTimeString(result.last_updated as string) ||
result.last_updated}
{getAsLocalDateTimeString(result.last_updated) || result.last_updated}
</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const ExampleStandoutResult: React.FC = () => {
data-test-subj="MediaTypeField"
>
<span className="example-search-result__tag-content">
{mimeType(result[mediaTypeField] as string)}
{mimeType(result[mediaTypeField])}
</span>
</div>
)}
Expand All @@ -127,7 +127,7 @@ export const ExampleStandoutResult: React.FC = () => {
by {result[updatedByField]}&nbsp;
</span>
)}
{getAsLocalDateTimeString(result.last_updated as string) || result.last_updated}
{getAsLocalDateTimeString(result.last_updated) || result.last_updated}
</span>
</div>
</div>
Expand Down
Loading

0 comments on commit 9624e5a

Please sign in to comment.