[Infrastructure UI] add copilot to hosts process (#159413)

## Summary

Add [Copilot
Prompt](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/co_pilot_prompt)
to Hosts processes

I removed the Autosizer component that wrapped this row component which
set a fixed height in the CSS. It was causing problems with how the
prompt renders a response and is constantly changing the height with
each new sentence. I'm not sure why we needed to have a fixed height, so
I removed it. Ideally we could use [EUI expanding
rows](https://elastic.github.io/eui/#/tabular-content/tables#expanding-rows)
style and functionality if possible.

<img width="2306" alt="Screenshot 2023-06-08 at 4 52 33 PM"
src="c3e67b9c-c542-4844-8c58-90a241c9bb6c">


### How to Test

To test you will need to enable copilot 

- Prompt should only show up in Hosts flyout
- Prompt will not be visible unless Copilot is enabled

Ref: https://github.com/elastic/kibana/pull/158678

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Sandra G 2023-06-14 10:45:35 -04:00 committed by GitHub
parent 02fab4dece
commit 4d794f5a7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 93 deletions

View file

@ -9,6 +9,8 @@ import { AppMountParameters, CoreStart } from '@kbn/core/public';
import React from 'react';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { CoPilotContextProvider } from '@kbn/observability-plugin/public';
import { CoPilotService } from '@kbn/observability-plugin/public/typings/co_pilot';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { NavigationWarningPromptProvider } from '@kbn/observability-shared-plugin/public';
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
@ -22,18 +24,29 @@ export const CommonInfraProviders: React.FC<{
appName: string;
storage: Storage;
triggersActionsUI: TriggersAndActionsUIPublicPluginStart;
observabilityCopilot: CoPilotService;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
theme$: AppMountParameters['theme$'];
}> = ({ children, triggersActionsUI, setHeaderActionMenu, appName, storage, theme$ }) => {
}> = ({
children,
triggersActionsUI,
observabilityCopilot,
setHeaderActionMenu,
appName,
storage,
theme$,
}) => {
const darkMode = useIsDarkMode();
return (
<TriggersActionsProvider triggersActionsUI={triggersActionsUI}>
<EuiThemeProvider darkMode={darkMode}>
<DataUIProviders appName={appName} storage={storage}>
<HeaderActionMenuProvider setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}>
<NavigationWarningPromptProvider>{children}</NavigationWarningPromptProvider>
</HeaderActionMenuProvider>
<CoPilotContextProvider value={observabilityCopilot}>
<HeaderActionMenuProvider setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}>
<NavigationWarningPromptProvider>{children}</NavigationWarningPromptProvider>
</HeaderActionMenuProvider>
</CoPilotContextProvider>
</DataUIProviders>
</EuiThemeProvider>
</TriggersActionsProvider>

View file

@ -14,7 +14,6 @@ import { Route } from '@kbn/shared-ux-router';
import { AppMountParameters } from '@kbn/core/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import '../index.scss';
import { CoPilotContextProvider } from '@kbn/observability-plugin/public';
import { LinkToLogsPage } from '../pages/link_to/link_to_logs';
import { LogsPage } from '../pages/logs';
import { InfraClientStartDeps, InfraClientStartExports } from '../types';
@ -69,20 +68,19 @@ const LogsApp: React.FC<{
storage={storage}
theme$={theme$}
triggersActionsUI={plugins.triggersActionsUi}
observabilityCopilot={plugins.observability.getCoPilotService()}
>
<CoPilotContextProvider value={plugins.observability.getCoPilotService()}>
<Router history={history}>
<KbnUrlStateStorageFromRouterProvider
history={history}
toastsService={core.notifications.toasts}
>
<Switch>
<Route path="/link-to" component={LinkToLogsPage} />
{uiCapabilities?.logs?.show && <Route path="/" component={LogsPage} />}
</Switch>
</KbnUrlStateStorageFromRouterProvider>
</Router>
</CoPilotContextProvider>
<Router history={history}>
<KbnUrlStateStorageFromRouterProvider
history={history}
toastsService={core.notifications.toasts}
>
<Switch>
<Route path="/link-to" component={LinkToLogsPage} />
{uiCapabilities?.logs?.show && <Route path="/" component={LogsPage} />}
</Switch>
</KbnUrlStateStorageFromRouterProvider>
</Router>
</CommonInfraProviders>
</CoreProviders>
);

View file

@ -72,6 +72,7 @@ const MetricsApp: React.FC<{
storage={storage}
theme$={theme$}
triggersActionsUI={plugins.triggersActionsUi}
observabilityCopilot={plugins.observability.getCoPilotService()}
>
<SourceProvider sourceId="default">
<Router history={history}>

View file

@ -213,7 +213,7 @@ const ProcessesTableBody = ({ items, currentTime }: TableBodyProps) => (
{column.render ? column.render(item[column.field], currentTime) : item[column.field]}
</EuiTableRowCell>
));
return <ProcessRow cells={cells} item={item} key={`row-${i}`} />;
return <ProcessRow cells={cells} item={item} key={`row-${i}`} supportCopilot={true} />;
})}
</>
);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiTableRow,
@ -22,17 +22,43 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { useCoPilot, CoPilotPrompt } from '@kbn/observability-plugin/public';
import { CoPilotPromptId } from '@kbn/observability-plugin/common';
import useToggle from 'react-use/lib/useToggle';
import { AutoSizer } from '../../../../../../../components/auto_sizer';
import { Process } from './types';
import { ProcessRowCharts } from './process_row_charts';
interface Props {
cells: React.ReactNode[];
item: Process;
supportCopilot?: boolean;
}
export const CopilotProcessRow = ({ command }: { command: string }) => {
const coPilotService = useCoPilot();
const explainProcessParams = useMemo(() => {
return command ? { command } : undefined;
}, [command]);
return (
<>
{coPilotService?.isEnabled() && explainProcessParams ? (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexItem grow={false}>
<CoPilotPrompt
coPilot={coPilotService}
title={explainProcessMessageTitle}
params={explainProcessParams}
promptId={CoPilotPromptId.InfraExplainProcess}
/>
</EuiFlexItem>
</EuiFlexItem>
</EuiFlexGroup>
) : null}
</>
);
};
export const ProcessRow = ({ cells, item }: Props) => {
export const ProcessRow = ({ cells, item, supportCopilot = false }: Props) => {
const [isExpanded, toggle] = useToggle(false);
return (
@ -50,79 +76,80 @@ export const ProcessRow = ({ cells, item }: Props) => {
</EuiTableRow>
<EuiTableRow isExpandable isExpandedRow={isExpanded}>
{isExpanded && (
<AutoSizer bounds>
{({ measureRef, bounds: { height = 0 } }) => (
<ExpandedRowCell commandHeight={height}>
<EuiSpacer size="s" />
<ExpandedRowDescriptionList>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>
<div ref={measureRef}>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelCommand',
{
defaultMessage: 'Command',
}
)}
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<ExpandedCommandLine>{item.command}</ExpandedCommandLine>
</EuiDescriptionListDescription>
</div>
</EuiFlexItem>
{item.apmTrace && (
<EuiFlexItem grow={false}>
<EuiButton data-test-subj="infraProcessRowViewTraceInApmButton">
{i18n.translate(
'xpack.infra.metrics.nodeDetails.processes.viewTraceInAPM',
{
defaultMessage: 'View trace in APM',
}
)}
</EuiButton>
</EuiFlexItem>
<ExpandedRowCell>
<EuiSpacer size="s" />
<ExpandedRowDescriptionList>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>
<div>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelCommand',
{
defaultMessage: 'Command',
}
)}
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<ExpandedCommandLine>{item.command}</ExpandedCommandLine>
</EuiDescriptionListDescription>
</div>
</EuiFlexItem>
{item.apmTrace && (
<EuiFlexItem grow={false}>
<EuiButton data-test-subj="infraProcessRowViewTraceInApmButton">
{i18n.translate('xpack.infra.metrics.nodeDetails.processes.viewTraceInAPM', {
defaultMessage: 'View trace in APM',
})}
</EuiButton>
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiFlexGrid columns={2} gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelPID',
{
defaultMessage: 'PID',
}
)}
</EuiFlexGroup>
<EuiFlexGrid columns={2} gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelPID',
{
defaultMessage: 'PID',
}
)}
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<CodeListItem>{item.pid}</CodeListItem>
</EuiDescriptionListDescription>
</EuiFlexItem>
<EuiFlexItem>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelUser',
{
defaultMessage: 'User',
}
)}
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<CodeListItem>{item.user}</CodeListItem>
</EuiDescriptionListDescription>
</EuiFlexItem>
<ProcessRowCharts command={item.command} />
</EuiFlexGrid>
</ExpandedRowDescriptionList>
</ExpandedRowCell>
)}
</AutoSizer>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<CodeListItem>{item.pid}</CodeListItem>
</EuiDescriptionListDescription>
</EuiFlexItem>
<EuiFlexItem>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelUser',
{
defaultMessage: 'User',
}
)}
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<CodeListItem>{item.user}</CodeListItem>
</EuiDescriptionListDescription>
</EuiFlexItem>
<ProcessRowCharts command={item.command} />
</EuiFlexGrid>
{supportCopilot && <CopilotProcessRow command={item.command} />}
</ExpandedRowDescriptionList>
</ExpandedRowCell>
)}
</EuiTableRow>
</>
);
};
const explainProcessMessageTitle = i18n.translate(
'xpack.infra.hostFlyout.explainProcessMessageTitle',
{
defaultMessage: "What's this process?",
}
);
const ExpandedRowDescriptionList = euiStyled(EuiDescriptionList).attrs({
compressed: true,
})`
@ -149,8 +176,8 @@ const ExpandedCommandLine = euiStyled(EuiCode).attrs({
const ExpandedRowCell = euiStyled(EuiTableRowCell).attrs({
textOnly: false,
colSpan: 6,
})<{ commandHeight: number }>`
height: ${(props) => props.commandHeight + 240}px;
padding: 0 ${(props) => props.theme.eui.euiSizeM};
})`
padding-top: ${(props) => props.theme.eui.euiSizeM} !important;
padding-bottom: ${(props) => props.theme.eui.euiSizeM} !important;
background-color: ${(props) => props.theme.eui.euiColorLightestShade};
`;

View file

@ -22,6 +22,7 @@ export enum CoPilotPromptId {
ApmExplainError = 'apmExplainError',
LogsExplainMessage = 'logsExplainMessage',
LogsFindSimilar = 'logsFindSimilar',
InfraExplainProcess = 'infraExplainProcess',
ExplainLogSpike = 'explainLogSpike',
}
@ -38,7 +39,13 @@ const APM_GPT_SYSTEM_MESSAGE = {
};
const LOGS_SYSTEM_MESSAGE = {
content: `You logsapm-gpt, a helpful assistant for logs-based observability. Answer as
content: `You are logs-gpt, a helpful assistant for logs-based observability. Answer as
concisely as possible.`,
role: 'system' as const,
};
const INFRA_SYSTEM_MESSAGE = {
content: `You are infra-gpt, a helpful assistant for metrics-based infrastructure observability. Answer as
concisely as possible.`,
role: 'system' as const,
};
@ -133,7 +140,7 @@ export const coPilotPrompts = {
The library is: ${library}
The function is: ${functionName}
Your task is to desribe what the library is and what its use cases are, and to describe what the function
Your task is to describe what the library is and what its use cases are, and to describe what the function
does. The output format should look as follows:
Library description: Provide a concise description of the library
@ -231,6 +238,73 @@ export const coPilotPrompts = {
];
},
}),
[CoPilotPromptId.InfraExplainProcess]: prompt({
params: t.type({
command: t.string,
}),
messages: ({ command }) => {
return [
INFRA_SYSTEM_MESSAGE,
{
content: `I am a software engineer. I am trying to understand what a process running on my
machine does.
Your task is to first describe what the process is and what its general use cases are. If I also provide you
with the arguments to the process you should then explain its arguments and how they influence the behaviour
of the process. If I do not provide any arguments then explain the behaviour of the process when no arguments are
provided.
If you do not recognise the process say "No information available for this process". If I provide an argument
to the process that you do not recognise then say "No information available for this argument" when explaining
that argument.
Here is an example with arguments.
Process: metricbeat -c /etc/metricbeat.yml -d autodiscover,kafka -e -system.hostfs=/hostfs
Explaination: Metricbeat is part of the Elastic Stack. It is a lightweight shipper that you can install on your
servers to periodically collect metrics from the operating system and from services running on the server.
Use cases for Metricbeat generally revolve around infrastructure monitoring. You would typically install
Metricbeat on your servers to collect metrics from your systems and services. These metrics are then
used for performance monitoring, anomaly detection, system status checks, etc.
Here is a breakdown of the arguments used:
* -c /etc/metricbeat.yml: The -c option is used to specify the configuration file for Metricbeat. In
this case, /etc/metricbeat.yml is the configuration file. This file contains configurations for what
metrics to collect and where to send them (e.g., to Elasticsearch or Logstash).
* -d autodiscover,kafka: The -d option is used to enable debug output for selected components. In
this case, debug output is enabled for autodiscover and kafka components. The autodiscover feature
allows Metricbeat to automatically discover services as they get started and stopped in your environment,
and kafka is presumably a monitored service from which Metricbeat collects metrics.
* -e: The -e option is used to log to stderr and disable syslog/file output. This is useful for debugging.
* -system.hostfs=/hostfs: The -system.hostfs option is used to set the mount point of the hosts
filesystem for use in monitoring a host from within a container. In this case, /hostfs is the mount
point. When running Metricbeat inside a container, filesystem metrics would be for the container by
default, but with this option, Metricbeat can get metrics for the host system.
Here is an example without arguments.
Process: metricbeat
Explanation: Metricbeat is part of the Elastic Stack. It is a lightweight shipper that you can install on your
servers to periodically collect metrics from the operating system and from services running on the server.
Use cases for Metricbeat generally revolve around infrastructure monitoring. You would typically install
Metricbeat on your servers to collect metrics from your systems and services. These metrics are then
used for performance monitoring, anomaly detection, system status checks, etc.
Running it without any arguments will start the process with the default configuration file, typically
located at /etc/metricbeat/metricbeat.yml. This file specifies the metrics to be collected and where
to ship them to.
Now explain this process to me.
Process: ${command}
Explanation:
`,
role: 'user',
},
];
},
}),
[CoPilotPromptId.ExplainLogSpike]: prompt({
params: t.type({
significantFieldValues: significantFieldValuesRt,