From ba3a65ba2a61e01e1feb371d815bcfe8f99a6114 Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Wed, 12 Nov 2025 15:36:23 +0100 Subject: [PATCH 1/3] TRACING-5497: Add Trace AI summary on Trace detail page Signed-off-by: Andreas Gerstmayr --- ...n__distributed-tracing-console-plugin.json | 1 + web/package-lock.json | 10 ++++ web/package.json | 3 ++ web/src/components/PersesWrapper.css | 4 ++ web/src/hooks/ols_actions.ts | 36 +++++++++++++ web/src/hooks/useOLSEnabled.ts | 15 ++++++ .../pages/TraceDetailPage/TraceDetailPage.tsx | 50 ++++++++++++++++++- 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 web/src/hooks/ols_actions.ts create mode 100644 web/src/hooks/useOLSEnabled.ts diff --git a/web/locales/en/plugin__distributed-tracing-console-plugin.json b/web/locales/en/plugin__distributed-tracing-console-plugin.json index 8498ea1..21b7d8d 100644 --- a/web/locales/en/plugin__distributed-tracing-console-plugin.json +++ b/web/locales/en/plugin__distributed-tracing-console-plugin.json @@ -11,6 +11,7 @@ "Trace details": "Trace details", "Trace": "Trace", "Tracing": "Tracing", + "Ask OpenShift Lightspeed": "Ask OpenShift Lightspeed", "Limit traces": "Limit traces", "Hide graph": "Hide graph", "Show graph": "Show graph", diff --git a/web/package-lock.json b/web/package-lock.json index 8fdb517..ae3c2e8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -39,6 +39,7 @@ "@openshift-console/dynamic-plugin-sdk": "^4.19.0", "@openshift-console/dynamic-plugin-sdk-webpack": "^4.19.0", "@types/jest": "^28.1.4", + "@types/js-yaml": "^4.0.9", "@types/node": "^18.0.0", "@types/react": "^17.0.37", "@types/react-helmet": "^6.1.4", @@ -54,6 +55,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.29.1", "eslint-plugin-react-hooks": "^4.6.2", + "js-yaml": "^4.1.0", "mocha-junit-reporter": "^2.2.0", "mochawesome": "^7.1.3", "mochawesome-merge": "^4.3.0", @@ -63,6 +65,7 @@ "react-dom": "^17.0.1", "react-helmet": "^6.1.0", "react-i18next": "^11.8.11", + "react-redux": "7.2.9", "react-router-dom-v5-compat": "^6.30.0", "style-loader": "^3.3.1", "stylelint": "^15.3.0", @@ -6862,6 +6865,13 @@ "pretty-format": "^28.0.0" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", diff --git a/web/package.json b/web/package.json index 7bc1cf6..98710ce 100644 --- a/web/package.json +++ b/web/package.json @@ -25,6 +25,7 @@ "@openshift-console/dynamic-plugin-sdk": "^4.19.0", "@openshift-console/dynamic-plugin-sdk-webpack": "^4.19.0", "@types/jest": "^28.1.4", + "@types/js-yaml": "^4.0.9", "@types/node": "^18.0.0", "@types/react": "^17.0.37", "@types/react-helmet": "^6.1.4", @@ -40,6 +41,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.29.1", "eslint-plugin-react-hooks": "^4.6.2", + "js-yaml": "^4.1.0", "mocha-junit-reporter": "^2.2.0", "mochawesome": "^7.1.3", "mochawesome-merge": "^4.3.0", @@ -49,6 +51,7 @@ "react-dom": "^17.0.1", "react-helmet": "^6.1.0", "react-i18next": "^11.8.11", + "react-redux": "7.2.9", "react-router-dom-v5-compat": "^6.30.0", "style-loader": "^3.3.1", "stylelint": "^15.3.0", diff --git a/web/src/components/PersesWrapper.css b/web/src/components/PersesWrapper.css index 7fe19d7..2a1ade1 100644 --- a/web/src/components/PersesWrapper.css +++ b/web/src/components/PersesWrapper.css @@ -31,3 +31,7 @@ .MuiMenu-root .MuiMenuItem-root:hover { background-color: var(--pf-t--global--background--color--action--plain--hover); } + +.MuiSvgIcon-root { + color: var(--pf-t--global--text--color--regular); +} diff --git a/web/src/hooks/ols_actions.ts b/web/src/hooks/ols_actions.ts new file mode 100644 index 0000000..71eb848 --- /dev/null +++ b/web/src/hooks/ols_actions.ts @@ -0,0 +1,36 @@ +import { action } from 'typesafe-actions'; + +// The redux actions must be kept in sync with https://github.com/openshift/lightspeed-console/blob/main/src/redux-actions.ts + +export enum ActionType { + AttachmentSet = 'attachmentSet', + OpenOLS = 'openOLS', + SetQuery = 'setQuery', +} + +export enum AttachmentTypes { + YAML = 'YAML', +} + +export const openOLS = () => action(ActionType.OpenOLS); + +export const setQuery = (query: string) => action(ActionType.SetQuery, { query }); + +export const attachmentSet = ( + attachmentType: string, + kind: string, + name: string, + ownerName: string, + namespace: string, + value: string, + originalValue?: string, +) => + action(ActionType.AttachmentSet, { + attachmentType, + kind, + name, + namespace, + originalValue, + ownerName, + value, + }); diff --git a/web/src/hooks/useOLSEnabled.ts b/web/src/hooks/useOLSEnabled.ts new file mode 100644 index 0000000..6126f0d --- /dev/null +++ b/web/src/hooks/useOLSEnabled.ts @@ -0,0 +1,15 @@ +import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; + +export function useOLSEnabled(): boolean { + const [, loaded, err] = useK8sWatchResource({ + groupVersionKind: { + group: 'ols.openshift.io', + version: 'v1alpha1', + kind: 'OLSConfig', + }, + name: 'cluster', + isList: false, + }); + + return loaded && !err; +} diff --git a/web/src/pages/TraceDetailPage/TraceDetailPage.tsx b/web/src/pages/TraceDetailPage/TraceDetailPage.tsx index 73fa927..bb9a2c6 100644 --- a/web/src/pages/TraceDetailPage/TraceDetailPage.tsx +++ b/web/src/pages/TraceDetailPage/TraceDetailPage.tsx @@ -1,5 +1,13 @@ import * as React from 'react'; -import { Breadcrumb, BreadcrumbItem, Divider, PageSection, Title } from '@patternfly/react-core'; +import { + Breadcrumb, + BreadcrumbItem, + Button, + Divider, + PageSection, + Title, + Tooltip, +} from '@patternfly/react-core'; import { Helmet } from 'react-helmet'; import { useTranslation } from 'react-i18next'; import { Link, useLocation, useParams } from 'react-router-dom-v5-compat'; @@ -16,6 +24,11 @@ import { memo } from 'react'; import { linkToSpan, linkToTrace, spanAttributeLinks } from '../../links'; import { StringParam, useQueryParam } from 'use-query-params'; import './TraceDetailPage.css'; +import { MagicIcon } from '@patternfly/react-icons'; +import { useDispatch } from 'react-redux'; +import { attachmentSet, AttachmentTypes, openOLS, setQuery } from '../../hooks/ols_actions'; +import { dump as dumpYAML } from 'js-yaml'; +import { useOLSEnabled } from '../../hooks/useOLSEnabled'; function TraceDetailPage() { return ( @@ -37,6 +50,7 @@ function TraceDetailPageBody() { const [tempo] = useTempoInstance(); const location = useLocation(); const [selectedSpanId] = useQueryParam('selectSpan', StringParam); + const olsEnabled = useOLSEnabled(); return (
: undefined, + }} definition={{ kind: 'Panel', spec: { @@ -130,3 +147,32 @@ function useTraceName(): string { // return traceId if span is not loaded or root span is not found return traceId ?? ''; } + +function LightspeedButton() { + const { t } = useTranslation('plugin__distributed-tracing-console-plugin'); + const dispatch = useDispatch(); + const { queryResults } = useDataQueries('TraceQuery'); + const traceName = useTraceName(); + const trace = queryResults[0]?.data?.trace; + + const handleTraceAISummaryClick = () => { + const traceYaml = dumpYAML(trace, { lineWidth: -1 }).trim(); + + dispatch(openOLS()); + dispatch(attachmentSet(AttachmentTypes.YAML, 'Trace', traceName, '', '', traceYaml)); + dispatch( + setQuery('Analyze this trace in my OpenShift cluster and highlight any errors and outliers.'), + ); + }; + + return ( + +