Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Security Assistant: useSecurityAssistantQuery hook and New chat button #5

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ const AlertSummaryViewComponent: React.FC<{
);

return (
<SummaryView rows={summaryRows} title={title} goToTable={goToTable} isReadOnly={isReadOnly} />
<SummaryView
data={data}
rows={summaryRows}
title={title}
goToTable={goToTable}
isReadOnly={isReadOnly}
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('Summary View', () => {
test('should show an empty table', () => {
render(
<TestProviders>
<SummaryView goToTable={jest.fn()} title="Test Summary View" rows={[]} />
<SummaryView data={[]} goToTable={jest.fn()} title="Test Summary View" rows={[]} />
</TestProviders>
);
expect(screen.getByText('No items found')).toBeInTheDocument();
Expand All @@ -84,7 +84,12 @@ describe('Summary View', () => {

render(
<TestProviders>
<SummaryView goToTable={jest.fn()} title="Test Summary View" rows={sampleRows} />
<SummaryView
data={[]}
goToTable={jest.fn()}
title="Test Summary View"
rows={sampleRows}
/>
</TestProviders>
);
// Shows the field name
Expand Down Expand Up @@ -113,6 +118,7 @@ describe('Summary View', () => {
render(
<TestProviders>
<SummaryView
data={[]}
goToTable={jest.fn()}
title="Test Summary View"
rows={sampleRows}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
} from '@elastic/eui';
import React from 'react';

import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import type { AlertSummaryRow } from './helpers';
import { NewChat } from '../../../security_assistant/new_chat';
import * as i18n from './translations';
import { VIEW_ALL_FIELDS } from './translations';
import { SummaryTable } from './table/summary_table';
Expand Down Expand Up @@ -67,15 +69,17 @@ const rowProps = {
};

const SummaryViewComponent: React.FC<{
data: TimelineEventsDetailsItem[];
goToTable: () => void;
title: string;
rows: AlertSummaryRow[];
isReadOnly?: boolean;
}> = ({ goToTable, rows, title, isReadOnly }) => {
}> = ({ data, goToTable, rows, title, isReadOnly }) => {
const columns = isReadOnly ? baseColumns : allColumns;

return (
<div>
<NewChat data={data} />
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xxxs">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.
*/

export interface SecurityAssistantUiSettings {
virusTotal: {
apiKey: string;
baseUrl: string;
};

openAI: {
apiKey: string;
baseUrl: string;
};
}

export async function fetchVirusTotalReport({
hash,
settings: { virusTotal, openAI },
}: {
hash: string;
settings: SecurityAssistantUiSettings;
}): Promise<unknown> {
const url = `${virusTotal.baseUrl}/files/${hash}`;

const response = await fetch(url, {
headers: {
'x-apikey': virusTotal.apiKey,
},
});

if (!response.ok) {
throw new Error(`VirusTotal API request failed with status ${response.status}`);
}

const data = await response.json();
return data;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { EuiButtonEmpty, EuiPopover } from '@elastic/eui';
import React, { useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';

import { useToasts } from '../../common/lib/kibana';
import type { TimelineEventsDetailsItem } from '../../../common/search_strategy';
import { SecurityAssistant } from '../security_assistant';
import * as i18n from './translations';
import { useSecurityAssistantQuery } from '../use_security_assistant_query';

const SecurityAssistantContainer = styled.div`
max-height: 1020px;
max-width: 600px;
`;

const NewChatComponent: React.FC<{
data: TimelineEventsDetailsItem[];
}> = ({ data }) => {
const toasts = useToasts();
const [query, setQuery] = useState<string>('');

const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const closePopover = () => setIsPopoverOpen(false);

const { getQuery } = useSecurityAssistantQuery({ data });

const onStartConversation = useCallback(async () => {
try {
setQuery(await getQuery());
setIsPopoverOpen((isOpen) => !isOpen);
} catch (error) {
toasts.addError(error, { title: i18n.ERROR_FETCHING_SECURITY_ASSISTANT_QUERY });
}
}, [getQuery, toasts]);

const NewChatButton = useMemo(
() => (
<EuiButtonEmpty onClick={onStartConversation} iconType="discuss">
{i18n.NEW_CHAT}
</EuiButtonEmpty>
),
[onStartConversation]
);

return (
<EuiPopover
button={NewChatButton}
closePopover={closePopover}
isOpen={isPopoverOpen}
panelPaddingSize="none"
>
<SecurityAssistantContainer>
<SecurityAssistant input={query} />
</SecurityAssistantContainer>
</EuiPopover>
);
Comment on lines +52 to +63
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

};

NewChatComponent.displayName = 'NewChatComponent';

export const NewChat = React.memo(NewChatComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const NEW_CHAT = i18n.translate('xpack.securitySolution.securityAssistant.newChatButton', {
defaultMessage: 'New chat',
});

export const ERROR_FETCHING_SECURITY_ASSISTANT_QUERY = i18n.translate(
'xpack.securitySolution.securityAssistant.errorFetchingSecurityAssistantQuery',
{
defaultMessage: 'Error fetching security assistant query',
}
);