Skip to content

Commit

Permalink
ref(insights): Wrap AI module in ModulePageProviders (#70776)
Browse files Browse the repository at this point in the history
Preparation for URL and breadcrumb changes. The IA module doesn't wrap
pages in `ModulePageProviders`. I need the wrapper so that I can
wholesale change the base URL for all modules at once without doing
awkward search-and-replace.

## Changes

- Wrap IA module in `ModulePageProviders`
    
    `ModulePageProviders` includes `Feature`, `SentryDocumentTitle`, and
    `PageFiltersContainer`, so it's not necessary to wrap in those
    components manually.

- Pass pipeline name through URL
    
Makes sure the name is available at page load, and is consistent with
    what the user clicked on _and_ saves a loaded column.
  • Loading branch information
gggritso committed May 14, 2024
1 parent 2f7ff9a commit 7d90ddb
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 193 deletions.
9 changes: 8 additions & 1 deletion static/app/views/aiMonitoring/PipelinesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import styled from '@emotion/styled';
import type {Location} from 'history';
import * as qs from 'query-string';

import GridEditable, {
COL_WIDTH_UNDEFINED,
Expand Down Expand Up @@ -228,10 +229,16 @@ function renderBodyCell(
if (!row['span.group']) {
return <span>{row['span.description']}</span>;
}

const queryString = {
...location.query,
'span.description': row['span.description'],
};

return (
<Link
to={normalizeUrl(
`/organizations/${organization.slug}/ai-monitoring/pipeline-type/${row['span.group']}`
`/organizations/${organization.slug}/ai-monitoring/pipeline-type/${row['span.group']}?${qs.stringify(queryString)}`
)}
>
{row['span.description']}
Expand Down
223 changes: 111 additions & 112 deletions static/app/views/aiMonitoring/aiMonitoringDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import styled from '@emotion/styled';

import Feature from 'sentry/components/acl/feature';
import {Alert} from 'sentry/components/alert';
import {Breadcrumbs} from 'sentry/components/breadcrumbs';
import * as Layout from 'sentry/components/layouts/thirds';
import NoProjectMessage from 'sentry/components/noProjectMessage';
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {CurrencyUnit, DurationUnit, RateUnit} from 'sentry/utils/discover/fields';
import {decodeScalar} from 'sentry/utils/queryString';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
import {
Expand All @@ -25,30 +23,32 @@ import {
import {PipelineSpansTable} from 'sentry/views/aiMonitoring/pipelineSpansTable';
import {MetricReadout} from 'sentry/views/performance/metricReadout';
import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders';
import {useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover';
import {
SpanFunction,
SpanMetricsField,
type SpanMetricsQueryFilters,
} from 'sentry/views/starfish/types';

function NoAccessComponent() {
return (
<Layout.Page withPadding>
<Alert type="warning">{t("You don't have access to this feature")}</Alert>
</Layout.Page>
);
}
interface Props {
params: {
groupId: string;
};
}

export default function AiMonitoringPage({params}: Props) {
type Query = {
'span.description'?: string;
};

export function AiMonitoringPage({params}: Props) {
const location = useLocation<Query>();

const organization = useOrganization();
const {groupId} = params;

const spanDescription = decodeScalar(location.query?.['span.description']);

const filters: SpanMetricsQueryFilters = {
'span.group': groupId,
'span.category': 'ai.pipeline',
Expand All @@ -59,7 +59,6 @@ export default function AiMonitoringPage({params}: Props) {
search: MutableSearch.fromQueryObject(filters),
fields: [
SpanMetricsField.SPAN_OP,
SpanMetricsField.SPAN_DESCRIPTION,
'count()',
`${SpanFunction.SPM}()`,
`avg(${SpanMetricsField.SPAN_DURATION})`,
Expand Down Expand Up @@ -87,108 +86,108 @@ export default function AiMonitoringPage({params}: Props) {
const tokenUsedMetric = totalTokenData[0] ?? {};

return (
<PageFiltersContainer>
<SentryDocumentTitle
title={`AI Monitoring — ${spanMetrics['span.description'] ?? t('(no name)')}`}
>
<Layout.Page>
<Feature
features="ai-analytics"
organization={organization}
renderDisabled={NoAccessComponent}
>
<NoProjectMessage organization={organization}>
<Layout.Header>
<Layout.HeaderContent>
<Breadcrumbs
crumbs={[
{
label: t('Dashboard'),
},
{
label: t('AI Monitoring'),
},
{
label: spanMetrics['span.description'] ?? t('(no name)'),
to: normalizeUrl(
`/organizations/${organization.slug}/ai-monitoring`
),
},
]}
/>
<Layout.Title>{t('AI Monitoring')}</Layout.Title>
</Layout.HeaderContent>
</Layout.Header>
<Layout.Body>
<Layout.Main fullWidth>
<ModuleLayout.Layout>
<ModuleLayout.Full>
<SpaceBetweenWrap>
<PageFilterBar condensed>
<ProjectPageFilter />
<EnvironmentPageFilter />
<DatePageFilter />
</PageFilterBar>
<MetricsRibbon>
<MetricReadout
title={t('Total Tokens Used')}
value={tokenUsedMetric['ai_total_tokens_used()']}
unit={'count'}
isLoading={isTotalTokenDataLoading}
/>

<MetricReadout
title={t('Total Cost')}
value={
tokenUsedMetric[
'ai_total_tokens_used(c:spans/ai.total_cost@usd)'
]
}
unit={CurrencyUnit.USD}
isLoading={isTotalTokenDataLoading}
/>

<MetricReadout
title={t('Pipeline Duration')}
value={
spanMetrics?.[`avg(${SpanMetricsField.SPAN_DURATION})`]
}
unit={DurationUnit.MILLISECOND}
isLoading={areSpanMetricsLoading}
/>

<MetricReadout
title={t('Pipeline Runs Per Minute')}
value={spanMetrics?.[`${SpanFunction.SPM}()`]}
unit={RateUnit.PER_MINUTE}
isLoading={areSpanMetricsLoading}
/>
</MetricsRibbon>
</SpaceBetweenWrap>
</ModuleLayout.Full>
<ModuleLayout.Third>
<TotalTokensUsedChart groupId={groupId} />
</ModuleLayout.Third>
<ModuleLayout.Third>
<NumberOfPipelinesChart groupId={groupId} />
</ModuleLayout.Third>
<ModuleLayout.Third>
<PipelineDurationChart groupId={groupId} />
</ModuleLayout.Third>
<ModuleLayout.Full>
<PipelineSpansTable groupId={groupId} />
</ModuleLayout.Full>
</ModuleLayout.Layout>
</Layout.Main>
</Layout.Body>
</NoProjectMessage>
</Feature>
</Layout.Page>
</SentryDocumentTitle>
</PageFiltersContainer>
<Layout.Page>
<NoProjectMessage organization={organization}>
<Layout.Header>
<Layout.HeaderContent>
<Breadcrumbs
crumbs={[
{
label: t('Dashboard'),
},
{
label: t('AI Monitoring'),
},
{
label: spanDescription ?? t('(no name)'),
to: normalizeUrl(`/organizations/${organization.slug}/ai-monitoring`),
},
]}
/>
<Layout.Title>{t('AI Monitoring')}</Layout.Title>
</Layout.HeaderContent>
</Layout.Header>
<Layout.Body>
<Layout.Main fullWidth>
<ModuleLayout.Layout>
<ModuleLayout.Full>
<SpaceBetweenWrap>
<PageFilterBar condensed>
<ProjectPageFilter />
<EnvironmentPageFilter />
<DatePageFilter />
</PageFilterBar>
<MetricsRibbon>
<MetricReadout
title={t('Total Tokens Used')}
value={tokenUsedMetric['ai_total_tokens_used()']}
unit={'count'}
isLoading={isTotalTokenDataLoading}
/>

<MetricReadout
title={t('Total Cost')}
value={
tokenUsedMetric['ai_total_tokens_used(c:spans/ai.total_cost@usd)']
}
unit={CurrencyUnit.USD}
isLoading={isTotalTokenDataLoading}
/>

<MetricReadout
title={t('Pipeline Duration')}
value={spanMetrics?.[`avg(${SpanMetricsField.SPAN_DURATION})`]}
unit={DurationUnit.MILLISECOND}
isLoading={areSpanMetricsLoading}
/>

<MetricReadout
title={t('Pipeline Runs Per Minute')}
value={spanMetrics?.[`${SpanFunction.SPM}()`]}
unit={RateUnit.PER_MINUTE}
isLoading={areSpanMetricsLoading}
/>
</MetricsRibbon>
</SpaceBetweenWrap>
</ModuleLayout.Full>
<ModuleLayout.Third>
<TotalTokensUsedChart groupId={groupId} />
</ModuleLayout.Third>
<ModuleLayout.Third>
<NumberOfPipelinesChart groupId={groupId} />
</ModuleLayout.Third>
<ModuleLayout.Third>
<PipelineDurationChart groupId={groupId} />
</ModuleLayout.Third>
<ModuleLayout.Full>
<PipelineSpansTable groupId={groupId} />
</ModuleLayout.Full>
</ModuleLayout.Layout>
</Layout.Main>
</Layout.Body>
</NoProjectMessage>
</Layout.Page>
);
}

function PageWithProviders({params}: Props) {
const location = useLocation<Query>();

const {'span.description': spanDescription} = location.query;

return (
<ModulePageProviders
title={[spanDescription ?? t('(no name)'), t('Pipeline Details')].join(' — ')}
baseURL="/ai-monitoring/"
features="ai-analytics"
>
<AiMonitoringPage params={params} />
</ModulePageProviders>
);
}

export default PageWithProviders;

const SpaceBetweenWrap = styled('div')`
display: flex;
justify-content: space-between;
Expand Down

0 comments on commit 7d90ddb

Please sign in to comment.