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

[Observability AI Assistant] Create first Starter prompts #178907

Merged
merged 18 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-templat
import React, { useContext, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
import { i18n } from '@kbn/i18n';
import { KibanaEnvironmentContext } from '../../../context/kibana_environment_context/kibana_environment_context';
import { getPathForFeedback } from '../../../utils/get_path_for_feedback';
import { EnvironmentsContextProvider } from '../../../context/environments_context/environments_context';
Expand Down Expand Up @@ -118,21 +119,52 @@ export function ApmMainTemplate({
isServerless: config?.serverlessOnboarding,
});

let screenDescription = '';
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we move the AI assistant code to its own file?

Copy link
Contributor

Choose a reason for hiding this comment

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

Same comments for the other files. I think we could clean up the components.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really. This functionality is intended for other plugins to give contextual information to the assistant about what the user is looking at right now.

Copy link
Member

Choose a reason for hiding this comment

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

I think what @cauemarcondes means is that it could be a separate hook or something similar. I've also done that in other places in APM (e.g. getThroughputScreenContext).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw those and didn't think it was super readable. But if its important for you, I will oblige

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO it keeps the component clean and small making it easy to maintain.

Copy link
Contributor Author

@CoenWarmer CoenWarmer Apr 9, 2024

Choose a reason for hiding this comment

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

IMO jumping between files hinders readability and leads engineers to just leave it alone because they don't see the contents anymore. Many, many examples of this in the Kibana codebase abound.

As stated though I will update.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO jumping between files hinders readability and leads engineers to just leave it alone because they don't see the contents anymore

I tend to agree and disagree with that 😄. The screen context/description is very specific for the AI assistant to know about this page/component. Me as a developer who is actively working on these components/pages I don't care what the assistant is doing. I know they are there though. What I want is to see a clean component, not having a bunch of static text on my way 😆.

For you, I think it'll be even easier as you'll have a clean file with only AI stuff.


if (!hasApmData && !hasApmIntegrations) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made this change as the LLM was giving a suboptimal answer previously.

When hasApmIntegrations would be false it would report that there might not be data because of the time range that was selected or the APM integration was not installed. With this adjustment it gives a better answer.

Copy link
Member

Choose a reason for hiding this comment

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

good one, saw this as well

screenDescription =
'The user does not have the APM integration installed and does not have APM data.';
} else {
screenDescription = hasApmData
? 'The user has APM data.'
: 'The user does not have APM data.';
screenDescription = hasApmIntegrations
? `${screenDescription} The user has the APM integration installed.`
: `${screenDescription} The user does not have the APM integration installed.`;
}

if (noDataConfig !== undefined) {
screenDescription = `${screenDescription} The user is looking at a screen that tells them they do not have any data.`;
}

useEffect(() => {
return aiAssistant?.service.setScreenContext({
screenDescription: [
hasApmData
? 'The user has APM data.'
: 'The user does not have APM data.',
hasApmIntegrations
? 'The user has the APM integration installed. '
: 'The user does not have the APM integration installed',
noDataConfig !== undefined
? 'The user is looking at a screen that tells them they do not have any data.'
: '',
].join('\n'),
screenDescription,
starterPrompts: [
...(!hasApmData
CoenWarmer marked this conversation as resolved.
Show resolved Hide resolved
? [
{
title: i18n.translate(
'xpack.apm.aiAssistant.starterPrompts.explainNoData.title',
{ defaultMessage: 'Explain' }
),
prompt: i18n.translate(
'xpack.apm.aiAssistant.starterPrompts.explainNoData.prompt',
{ defaultMessage: "Why don't I see any data?" }
),
icon: 'sparkles',
},
]
: []),
],
});
}, [hasApmData, hasApmIntegrations, noDataConfig, aiAssistant]);
}, [
hasApmData,
hasApmIntegrations,
noDataConfig,
aiAssistant,
screenDescription,
]);

const rightSideItems = [
...(showServiceGroupSaveButton ? [<ServiceGroupSaveButton />] : []),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React from 'react';
import React, { useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
Expand All @@ -25,6 +25,7 @@ export const LogsPageTemplate: React.FC<LogsPageTemplateProps> = ({
}) => {
const {
services: {
observabilityAIAssistant,
observabilityShared: {
navigation: { PageTemplate },
},
Expand All @@ -35,6 +36,34 @@ export const LogsPageTemplate: React.FC<LogsPageTemplateProps> = ({
const { http } = useKibana().services;
const basePath = http!.basePath.get();

const { setScreenContext } = observabilityAIAssistant?.service || {};

useEffect(() => {
return setScreenContext?.({
starterPrompts: [
...(!isDataLoading && !hasData
? [
{
title: i18n.translate(
'xpack.infra.aiAssistant.starterPrompts.explainNoData.title',
{
defaultMessage: 'Explain',
}
),
prompt: i18n.translate(
'xpack.infra.aiAssistant.starterPrompts.explainNoData.prompt',
{
defaultMessage: "Why don't I see any data?",
}
),
icon: 'sparkles',
},
]
: []),
],
});
}, [hasData, isDataLoading, setScreenContext]);

const noDataConfig: NoDataConfig | undefined = hasData
? undefined
: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import { i18n } from '@kbn/i18n';
import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public';
import type { NoDataConfig } from '@kbn/shared-ux-page-kibana-template';
import React from 'react';
import React, { useEffect } from 'react';
import {
noMetricIndicesPromptDescription,
noMetricIndicesPromptPrimaryActionTitle,
} from '../../components/empty_states';
import { useSourceContext } from '../../containers/metrics_source';
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';

interface MetricsPageTemplateProps extends LazyObservabilityPageTemplateProps {
Expand All @@ -26,13 +27,16 @@ export const MetricsPageTemplate: React.FC<MetricsPageTemplateProps> = ({
}) => {
const {
services: {
observabilityAIAssistant,
observabilityShared: {
navigation: { PageTemplate },
},
docLinks,
},
} = useKibanaContextForPlugin();

const { source } = useSourceContext();

const noDataConfig: NoDataConfig | undefined = hasData
? undefined
: {
Expand All @@ -48,6 +52,41 @@ export const MetricsPageTemplate: React.FC<MetricsPageTemplateProps> = ({
docsLink: docLinks.links.observability.guide,
};

const { setScreenContext } = observabilityAIAssistant?.service || {};

useEffect(() => {
return setScreenContext?.({
data: [
{
name: 'Metrics configuration',
value: source,
description: 'The configuration of the Metrics app',
},
],
starterPrompts: [
...(!hasData
? [
{
title: i18n.translate(
'xpack.infra.metrics.aiAssistant.starterPrompts.explainNoData.title',
{
defaultMessage: 'Explain',
}
),
prompt: i18n.translate(
'xpack.infra.metrics.aiAssistant.starterPrompts.explainNoData.prompt',
{
defaultMessage: "Why don't I see any data?",
}
),
icon: 'sparkles',
},
]
: []),
],
});
}, [hasData, setScreenContext, source]);

return (
<PageTemplate
data-test-subj={hasData ? _dataTestSubj : 'noDataPage'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { isEmpty, uniqueId } from 'lodash';
import React, { createContext, useEffect, useState } from 'react';
import { asyncForEach } from '@kbn/std';
import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../utils/kibana_react';
import {
ALERT_APP,
Expand All @@ -25,7 +26,7 @@ import { getObservabilityAlerts } from './get_observability_alerts';
import { ObservabilityFetchDataPlugins } from '../../typings/fetch_overview_data';
import { ApmIndicesConfig } from '../../../common/typings';

type DataContextApps = ObservabilityFetchDataPlugins | 'alert';
export type DataContextApps = ObservabilityFetchDataPlugins | 'alert';

export type HasDataMap = Record<
DataContextApps,
Expand Down Expand Up @@ -57,6 +58,30 @@ const apps: DataContextApps[] = [
UNIVERSAL_PROFILING_APP,
];

export const appLabels: Record<DataContextApps, string> = {
alert: i18n.translate('xpack.observability.overview.alertsLabel', {
defaultMessage: 'Alerts',
}),
apm: i18n.translate('xpack.observability.overview.apmLabel', {
defaultMessage: 'APM',
}),
infra_logs: i18n.translate('xpack.observability.overview.logsLabel', {
defaultMessage: 'Logs',
}),
infra_metrics: i18n.translate('xpack.observability.overview.metricsLabel', {
defaultMessage: 'Metrics',
}),
universal_profiling: i18n.translate('xpack.observability.overview.profilingLabel', {
defaultMessage: 'Profiling',
}),
uptime: i18n.translate('xpack.observability.overview.uptimeLabel', {
defaultMessage: 'Uptime',
}),
ux: i18n.translate('xpack.observability.overview.uxLabel', {
defaultMessage: 'UX',
}),
};

export function HasDataContextProvider({ children }: { children: React.ReactNode }) {
const { http } = useKibana().services;
const [forceUpdate, setForceUpdate] = useState('');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 { useLoadRuleTypesQuery } from '@kbn/triggers-actions-ui-plugin/public';
import { useMemo } from 'react';
import { useKibana } from '../utils/kibana_react';
import { useGetFilteredRuleTypes } from './use_get_filtered_rule_types';

export function useGetAvailableRulesWithDescriptions() {
const filteredRuleTypes = useGetFilteredRuleTypes();
Copy link
Contributor Author

@CoenWarmer CoenWarmer Apr 5, 2024

Choose a reason for hiding this comment

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

useGetFilteredRuleTypes(); only returns ids, no name or description


const {
triggersActionsUi: { ruleTypeRegistry },
} = useKibana().services;

const {
ruleTypesState: { data: ruleTypes },
} = useLoadRuleTypesQuery({
Copy link
Contributor Author

@CoenWarmer CoenWarmer Apr 5, 2024

Choose a reason for hiding this comment

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

useLoadRuleTypesQuery() returns ids and a name, but no description

filteredRuleTypes,
});

return useMemo(() => {
const ruleTypesFromRuleTypeRegistry = ruleTypeRegistry.list();
Copy link
Contributor Author

@CoenWarmer CoenWarmer Apr 5, 2024

Choose a reason for hiding this comment

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

ruleTypeRegistry.list() returns ids and descriptions, but no name 😅


return Array.from(ruleTypes).map(([id, rule]) => {
return {
id,
name: rule.name,
description: ruleTypesFromRuleTypeRegistry.find((f) => f.id === id)?.description || '',
};
});
}, [ruleTypeRegistry, ruleTypes]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getAlertSummaryTimeRange } from '../../utils/alert_summary_widget';
import { observabilityAlertFeatureIds } from '../../../common/constants';
import { ALERTS_URL_STORAGE_KEY } from '../../../common/constants';
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
import { useGetAvailableRulesWithDescriptions } from '../../hooks/use_get_available_rules_with_descriptions';

const ALERTS_SEARCH_BAR_ID = 'alerts-search-bar-o11y';
const ALERTS_PER_PAGE = 50;
Expand All @@ -54,6 +55,7 @@ function InternalAlertsPage() {
},
http,
notifications: { toasts },
observabilityAIAssistant,
share: {
url: { locators },
},
Expand All @@ -72,6 +74,29 @@ function InternalAlertsPage() {

const filteredRuleTypes = useGetFilteredRuleTypes();

const { setScreenContext } = observabilityAIAssistant?.service || {};

const ruleTypesWithDescriptions = useGetAvailableRulesWithDescriptions();

useEffect(() => {
return setScreenContext?.({
screenDescription: `The rule types that are available are: ${JSON.stringify(
CoenWarmer marked this conversation as resolved.
Show resolved Hide resolved
ruleTypesWithDescriptions
)}`,
starterPrompts: [
{
title: i18n.translate('xpack.observability.app.starterPrompts.explainRules.title', {
defaultMessage: 'Explain',
}),
prompt: i18n.translate('xpack.observability.app.starterPrompts.explainRules.prompt', {
defaultMessage: `Can you explain the rule types that are available?`,
}),
icon: 'sparkles',
},
],
});
}, [filteredRuleTypes, ruleTypesWithDescriptions, setScreenContext]);

const onBrushEnd: BrushEndListener = (brushEvent) => {
const { x } = brushEvent as XYBrushEvent;
if (x) {
Expand Down
Loading