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 17 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 @@ -9,9 +9,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiPageHeaderProps } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public';
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
import React, { useContext, useEffect } from 'react';
import React, { useContext } from 'react';
import { useLocation } from 'react-router-dom';
import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
import { useDefaultAiAssistantStarterPromptsForAPM } from '../../../hooks/use_default_ai_assistant_starter_prompts_for_apm';
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 @@ -66,8 +67,6 @@ export function ApmMainTemplate({
const basePath = http?.basePath.get();
const { config } = useApmPluginContext();

const aiAssistant = services.observabilityAIAssistant;

const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate;

const { data, status } = useFetcher((callApmApi) => {
Expand Down Expand Up @@ -118,21 +117,11 @@ export function ApmMainTemplate({
isServerless: config?.serverlessOnboarding,
});

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'),
});
}, [hasApmData, hasApmIntegrations, noDataConfig, aiAssistant]);
useDefaultAiAssistantStarterPromptsForAPM({
hasApmData,
hasApmIntegrations,
noDataConfig,
});

const rightSideItems = [
...(showServiceGroupSaveButton ? [<ServiceGroupSaveButton />] : []),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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 { useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { NoDataConfig } from '@kbn/shared-ux-page-kibana-template';
import type { ApmPluginStartDeps } from '../plugin';

export function useDefaultAiAssistantStarterPromptsForAPM({
hasApmData,
hasApmIntegrations,
noDataConfig,
}: {
hasApmData: boolean;
hasApmIntegrations: boolean;
noDataConfig?: NoDataConfig;
}) {
const { observabilityAIAssistant } = useKibana<ApmPluginStartDeps>().services;

let screenDescription = '';

if (!hasApmData && !hasApmIntegrations) {
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 observabilityAIAssistant?.service.setScreenContext({
screenDescription,
starterPrompts: [
...(hasApmData
? []
: [
{
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,
screenDescription,
observabilityAIAssistant?.service,
]);
}
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,31 @@ function InternalAlertsPage() {

const filteredRuleTypes = useGetFilteredRuleTypes();

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

const ruleTypesWithDescriptions = useGetAvailableRulesWithDescriptions();

useEffect(() => {
return setScreenContext?.({
data: ruleTypesWithDescriptions.map((rule) => ({
name: rule.id,
value: `${rule.name} ${rule.description}`,
Copy link
Member

Choose a reason for hiding this comment

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

you can use value: rule. In this case it probably is overkill to use data (I wasn't aware of the shape, sorry). But it's also fine to leave it as-is, there's not a lot of tokens so it gets sent over automatically.

description: `An available rule is ${rule.name}.`,
})),
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