diff --git a/static/app/components/events/interfaces/llm-monitoring/llmMonitoringSection.tsx b/static/app/components/events/interfaces/llm-monitoring/llmMonitoringSection.tsx index 69463f104a83b0..78c4f9e49d9f77 100644 --- a/static/app/components/events/interfaces/llm-monitoring/llmMonitoringSection.tsx +++ b/static/app/components/events/interfaces/llm-monitoring/llmMonitoringSection.tsx @@ -2,38 +2,15 @@ import {LinkButton} from 'sentry/components/core/button'; import {ButtonBar} from 'sentry/components/core/button/buttonBar'; import {IconOpen} from 'sentry/icons'; import {t} from 'sentry/locale'; -import type {Event} from 'sentry/types/event'; -import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout'; -import {useSpansIndexed} from 'sentry/views/insights/common/queries/useDiscover'; +import LlmEventNumberOfPipelinesChartWidget from 'sentry/views/insights/common/components/widgets/llmEventNumberOfPipelinesChartWidget'; +import LlmEventTotalTokensUsedChartWidget from 'sentry/views/insights/common/components/widgets/llmEventTotalTokensUsedChartWidget'; import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL'; -import { - NumberOfPipelinesChart, - TotalTokensUsedChart, -} from 'sentry/views/insights/llmMonitoring/components/charts/llmMonitoringCharts'; -import {SpanIndexedField} from 'sentry/views/insights/types'; import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection'; -interface Props { - event: Event; -} - -export default function LLMMonitoringSection({event}: Props) { +export default function LLMMonitoringSection() { const moduleUrl = useModuleURL('ai'); - const trace = event.contexts.trace; - - const {data} = useSpansIndexed( - { - limit: 1, - fields: [SpanIndexedField.SPAN_AI_PIPELINE_GROUP], - search: new MutableSearch(`trace:${trace?.trace_id} id:"${trace?.span_id}"`), - enabled: Boolean(trace?.span_id) && Boolean(trace?.trace_id), - }, - 'api.ai-pipelines.view' - ); - - const aiPipelineGroup = data[0]?.[SpanIndexedField.SPAN_AI_PIPELINE_GROUP]; const actions = ( @@ -50,18 +27,14 @@ export default function LLMMonitoringSection({event}: Props) { help={t('Charts showing how many tokens are being used')} actions={actions} > - {aiPipelineGroup ? ( - - - - - - - - - ) : ( - 'loading' - )} + + + + + + + + ); } diff --git a/static/app/views/insights/common/components/widgets/hooks/useAiPipelineGroup.tsx b/static/app/views/insights/common/components/widgets/hooks/useAiPipelineGroup.tsx new file mode 100644 index 00000000000000..ef0e892c6c2985 --- /dev/null +++ b/static/app/views/insights/common/components/widgets/hooks/useAiPipelineGroup.tsx @@ -0,0 +1,43 @@ +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {useSpansIndexed} from 'sentry/views/insights/common/queries/useDiscover'; +import {SpanIndexedField} from 'sentry/views/insights/types'; +import {useGroupEvent} from 'sentry/views/issueDetails/useGroupEvent'; + +/** + * Given an issue's groupId + eventId, fetch the AI + * Pipeline's group ID via event's trace + */ +export function useAiPipelineGroup({ + groupId, + eventId, +}: { + groupId: string; + eventId?: string; +}) { + const { + data: event, + isPending: isGroupPending, + error: groupError, + } = useGroupEvent({groupId, eventId}); + + const trace = event?.contexts.trace; + const { + data, + isPending: isSpanPending, + error: spanError, + } = useSpansIndexed( + { + limit: 1, + fields: [SpanIndexedField.SPAN_AI_PIPELINE_GROUP], + search: new MutableSearch(`trace:${trace?.trace_id} id:"${trace?.span_id}"`), + enabled: Boolean(trace?.span_id) && Boolean(trace?.trace_id), + }, + 'api.ai-pipelines.view' + ); + + return { + groupId: data[0]?.[SpanIndexedField.SPAN_AI_PIPELINE_GROUP], + isPending: isGroupPending || isSpanPending, + error: groupError || spanError, + }; +} diff --git a/static/app/views/insights/common/components/widgets/llmEventNumberOfPipelinesChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmEventNumberOfPipelinesChartWidget.tsx new file mode 100644 index 00000000000000..19c15d8a1c1d7a --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmEventNumberOfPipelinesChartWidget.tsx @@ -0,0 +1,50 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {useParams} from 'sentry/utils/useParams'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import {useAiPipelineGroup} from 'sentry/views/insights/common/components/widgets/hooks/useAiPipelineGroup'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmEventNumberOfPipelinesChartWidget( + props: LoadableChartWidgetProps +) { + const params = useParams<{groupId: string; eventId?: string}>(); + const {groupId, isPending, error} = useAiPipelineGroup(params); + + const theme = useTheme(); + const aggregate = 'count()'; + + let query = 'span.category:"ai.pipeline"'; + if (groupId) { + query = `${query} span.group:"${groupId}"`; + } + const { + data, + isPending: spanMetricsSeriesIsPending, + error: spanMetricsSeriesError, + } = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + enabled: !!groupId, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/llmEventTotalTokensUsedChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmEventTotalTokensUsedChartWidget.tsx new file mode 100644 index 00000000000000..3c93ec439ad35b --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmEventTotalTokensUsedChartWidget.tsx @@ -0,0 +1,50 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {useParams} from 'sentry/utils/useParams'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import {useAiPipelineGroup} from 'sentry/views/insights/common/components/widgets/hooks/useAiPipelineGroup'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmEventTotalTokensUsedChartWidget( + props: LoadableChartWidgetProps +) { + const params = useParams<{groupId: string; eventId?: string}>(); + const {groupId, isPending, error} = useAiPipelineGroup(params); + + const theme = useTheme(); + const aggregate = 'sum(ai.total_tokens.used)'; + + let query = 'span.category:"ai"'; + if (groupId) { + query = `${query} span.ai.pipeline.group:"${groupId}"`; + } + const { + data, + isPending: spanMetricsSeriesIsPending, + error: spanMetricsSeriesError, + } = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + enabled: !!groupId, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/llmGroupNumberOfPipelinesChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmGroupNumberOfPipelinesChartWidget.tsx new file mode 100644 index 00000000000000..67e8eb15db17dd --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmGroupNumberOfPipelinesChartWidget.tsx @@ -0,0 +1,39 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {useParams} from 'sentry/utils/useParams'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmGroupNumberOfPipelinesChartWidget( + props: LoadableChartWidgetProps +) { + const {groupId} = useParams<{groupId: string}>(); + const theme = useTheme(); + const aggregate = 'count()'; + + const query = `span.category:"ai.pipeline" span.group:"${groupId}"`; + const {data, isPending, error} = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/llmGroupPipelineDurationChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmGroupPipelineDurationChartWidget.tsx new file mode 100644 index 00000000000000..0fe7933ad226cf --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmGroupPipelineDurationChartWidget.tsx @@ -0,0 +1,39 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {useParams} from 'sentry/utils/useParams'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmGroupPipelineDurationChartWidget( + props: LoadableChartWidgetProps +) { + const {groupId} = useParams<{groupId: string}>(); + const theme = useTheme(); + const aggregate = 'avg(span.duration)'; + + const query = `span.category:"ai.pipeline" span.group:"${groupId}"`; + const {data, isPending, error} = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/llmGroupTotalTokensUsedChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmGroupTotalTokensUsedChartWidget.tsx new file mode 100644 index 00000000000000..5ee76882c85909 --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmGroupTotalTokensUsedChartWidget.tsx @@ -0,0 +1,39 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {useParams} from 'sentry/utils/useParams'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmGroupTotalTokensUsedChartWidget( + props: LoadableChartWidgetProps +) { + const {groupId} = useParams<{groupId: string}>(); + const theme = useTheme(); + const aggregate = 'sum(ai.total_tokens.used)'; + + const query = `span.category:"ai" span.ai.pipeline.group:"${groupId}"`; + const {data, isPending, error} = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/llmNumberOfPipelinesChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmNumberOfPipelinesChartWidget.tsx new file mode 100644 index 00000000000000..08447d11600890 --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmNumberOfPipelinesChartWidget.tsx @@ -0,0 +1,35 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmNumberOfPipelinesChartWidget(props: LoadableChartWidgetProps) { + const theme = useTheme(); + const aggregate = 'count()'; + + const query = 'span.category:"ai.pipeline"'; + const {data, isPending, error} = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/llmPipelineDurationChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmPipelineDurationChartWidget.tsx new file mode 100644 index 00000000000000..baea49d8b62101 --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmPipelineDurationChartWidget.tsx @@ -0,0 +1,35 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmPipelineDurationChartWidget(props: LoadableChartWidgetProps) { + const theme = useTheme(); + const aggregate = 'avg(span.duration)'; + + const query = 'span.category:"ai.pipeline"'; + const {data, isPending, error} = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/llmTotalTokensUsedChartWidget.tsx b/static/app/views/insights/common/components/widgets/llmTotalTokensUsedChartWidget.tsx new file mode 100644 index 00000000000000..814d0ed260ee6d --- /dev/null +++ b/static/app/views/insights/common/components/widgets/llmTotalTokensUsedChartWidget.tsx @@ -0,0 +1,35 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; + +export default function LlmTotalTokensUsedChartWidget(props: LoadableChartWidgetProps) { + const theme = useTheme(); + const aggregate = 'sum(ai.total_tokens.used)'; + + const query = 'span.category:"ai"'; + const {data, isPending, error} = useSpanMetricsSeries( + { + yAxis: [aggregate], + search: new MutableSearch(query), + transformAliasToInputFormat: true, + }, + 'api.ai-pipelines.view', + props.pageFilters + ); + + const colors = theme.chart.getColorPalette(2); + return ( + + ); +} diff --git a/static/app/views/insights/common/components/widgets/types.tsx b/static/app/views/insights/common/components/widgets/types.tsx index 5f9ec743a372f3..6f38ce4a8bf399 100644 --- a/static/app/views/insights/common/components/widgets/types.tsx +++ b/static/app/views/insights/common/components/widgets/types.tsx @@ -5,9 +5,10 @@ import type {PageFilters} from 'sentry/types/core'; * render an Insight Chart Widget */ export interface LoadableChartWidgetProps { - // TODO(billy): This should be required when all chart widgets are converted /** * Unique ID for the widget + * + * TODO(billy): This should be required when all chart widgets are converted */ id?: string; diff --git a/static/app/views/insights/llmMonitoring/components/charts/llmMonitoringCharts.tsx b/static/app/views/insights/llmMonitoring/components/charts/llmMonitoringCharts.tsx deleted file mode 100644 index 76c111e09cc8a0..00000000000000 --- a/static/app/views/insights/llmMonitoring/components/charts/llmMonitoringCharts.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import {useTheme} from '@emotion/react'; - -import {t} from 'sentry/locale'; -import {MutableSearch} from 'sentry/utils/tokenizeSearch'; -import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget'; -import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries'; - -interface TotalTokensUsedChartProps { - groupId?: string; -} - -export function TotalTokensUsedChart({groupId}: TotalTokensUsedChartProps) { - const theme = useTheme(); - const aggregate = 'sum(ai.total_tokens.used)'; - - let query = 'span.category:"ai"'; - if (groupId) { - query = `${query} span.ai.pipeline.group:"${groupId}"`; - } - const {data, isPending, error} = useSpanMetricsSeries( - { - yAxis: [aggregate], - search: new MutableSearch(query), - transformAliasToInputFormat: true, - }, - 'api.ai-pipelines.view' - ); - - const colors = theme.chart.getColorPalette(2); - return ( - - ); -} - -interface NumberOfPipelinesChartProps { - groupId?: string; -} - -export function NumberOfPipelinesChart({groupId}: NumberOfPipelinesChartProps) { - const theme = useTheme(); - const aggregate = 'count()'; - - let query = 'span.category:"ai.pipeline"'; - if (groupId) { - query = `${query} span.group:"${groupId}"`; - } - const {data, isPending, error} = useSpanMetricsSeries( - { - yAxis: [aggregate], - search: new MutableSearch(query), - transformAliasToInputFormat: true, - }, - 'api.ai-pipelines.view' - ); - - const colors = theme.chart.getColorPalette(2); - return ( - - ); -} - -interface PipelineDurationChartProps { - groupId?: string; -} - -export function PipelineDurationChart({groupId}: PipelineDurationChartProps) { - const theme = useTheme(); - - const aggregate = 'avg(span.duration)'; - let query = 'span.category:"ai.pipeline"'; - if (groupId) { - query = `${query} span.group:"${groupId}"`; - } - const {data, isPending, error} = useSpanMetricsSeries( - { - yAxis: [aggregate], - search: new MutableSearch(query), - transformAliasToInputFormat: true, - }, - 'api.ai-pipelines.view' - ); - - const colors = theme.chart.getColorPalette(2); - return ( - - ); -} diff --git a/static/app/views/insights/llmMonitoring/views/llmMonitoringDetailsPage.tsx b/static/app/views/insights/llmMonitoring/views/llmMonitoringDetailsPage.tsx index d0cc41081ccc98..de15f9bc40a00b 100644 --- a/static/app/views/insights/llmMonitoring/views/llmMonitoringDetailsPage.tsx +++ b/static/app/views/insights/llmMonitoring/views/llmMonitoringDetailsPage.tsx @@ -16,12 +16,10 @@ import {ModulePageFilterBar} from 'sentry/views/insights/common/components/modul import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper'; import {ReadoutRibbon, ToolRibbon} from 'sentry/views/insights/common/components/ribbon'; +import LlmGroupNumberOfPipelinesChartWidget from 'sentry/views/insights/common/components/widgets/llmGroupNumberOfPipelinesChartWidget'; +import LlmGroupPipelineDurationChartWidget from 'sentry/views/insights/common/components/widgets/llmGroupPipelineDurationChartWidget'; +import LlmGroupTotalTokensUsedChartWidget from 'sentry/views/insights/common/components/widgets/llmGroupTotalTokensUsedChartWidget'; import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover'; -import { - NumberOfPipelinesChart, - PipelineDurationChart, - TotalTokensUsedChart, -} from 'sentry/views/insights/llmMonitoring/components/charts/llmMonitoringCharts'; import {PipelineSpansTable} from 'sentry/views/insights/llmMonitoring/components/tables/pipelineSpansTable'; import {RELEASE_LEVEL} from 'sentry/views/insights/llmMonitoring/settings'; import {AiHeader} from 'sentry/views/insights/pages/ai/aiPageHeader'; @@ -148,13 +146,13 @@ function LLMMonitoringPage({params}: Props) { - + - + - + diff --git a/static/app/views/insights/llmMonitoring/views/llmMonitoringLandingPage.tsx b/static/app/views/insights/llmMonitoring/views/llmMonitoringLandingPage.tsx index 6585e71bd543ad..1980a5b66da305 100644 --- a/static/app/views/insights/llmMonitoring/views/llmMonitoringLandingPage.tsx +++ b/static/app/views/insights/llmMonitoring/views/llmMonitoringLandingPage.tsx @@ -4,11 +4,9 @@ import {ModulePageFilterBar} from 'sentry/views/insights/common/components/modul import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding'; import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper'; -import { - NumberOfPipelinesChart, - PipelineDurationChart, - TotalTokensUsedChart, -} from 'sentry/views/insights/llmMonitoring/components/charts/llmMonitoringCharts'; +import LlmNumberOfPipelinesChartWidget from 'sentry/views/insights/common/components/widgets/llmNumberOfPipelinesChartWidget'; +import LlmPipelineDurationChartWidget from 'sentry/views/insights/common/components/widgets/llmPipelineDurationChartWidget'; +import LlmTotalTokensUsedChart from 'sentry/views/insights/common/components/widgets/llmTotalTokensUsedChartWidget'; import {PipelinesTable} from 'sentry/views/insights/llmMonitoring/components/tables/pipelinesTable'; import {AiHeader} from 'sentry/views/insights/pages/ai/aiPageHeader'; import {ModuleName} from 'sentry/views/insights/types'; @@ -26,13 +24,13 @@ function LLMMonitoringPage() { - + - + - + diff --git a/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx b/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx index c8614d58aebad9..bd396308a74106 100644 --- a/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx +++ b/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx @@ -213,7 +213,7 @@ export function EventDetailsContent({ lowerText.includes('langchain')) ); }) ? ( - + ) : null} {!hasStreamlinedUI && group.issueType === IssueType.UPTIME_DOMAIN_FAILURE && (