Skip to content

Commit

Permalink
[Security Solution] Security Assistant: useSecurityAssistantQuery hoo…
Browse files Browse the repository at this point in the history
…k and New chat button #5

- adds the useSecurityAssistantQuery hook
- adds a `New chat` button that renders a query in a popover
  • Loading branch information
andrew-goldstein authored May 9, 2023
1 parent 562e5d9 commit 199af3c
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 93 deletions.
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>
);
};

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',
}
);
Loading

0 comments on commit 199af3c

Please sign in to comment.