Skip to content

Commit

Permalink
feat(ai-monitoring): Work on implementing the design (#70158)
Browse files Browse the repository at this point in the history
Change fields in the table, make charts be line charts, move some gauges
around.
  • Loading branch information
colin-sentry authored May 2, 2024
1 parent 0cb2771 commit 9b097d5
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 73 deletions.
105 changes: 68 additions & 37 deletions static/app/views/aiMonitoring/PipelinesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import styled from '@emotion/styled';
import type {Location} from 'history';

import GridEditable, {
Expand All @@ -7,7 +8,9 @@ import GridEditable, {
import Link from 'sentry/components/links/link';
import type {CursorHandler} from 'sentry/components/pagination';
import Pagination from 'sentry/components/pagination';
import SearchBar from 'sentry/components/searchBar';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types/organization';
import {browserHistory} from 'sentry/utils/browserHistory';
import type {EventsMetaType} from 'sentry/utils/discover/eventView';
Expand All @@ -24,7 +27,6 @@ import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/render
import {useSpanMetrics} from 'sentry/views/starfish/queries/useSpanMetrics';
import type {MetricsResponse} from 'sentry/views/starfish/types';
import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
import {DataTitles} from 'sentry/views/starfish/views/spans/types';

type Row = Pick<
MetricsResponse,
Expand All @@ -47,24 +49,24 @@ const COLUMN_ORDER: Column[] = [
name: t('AI Pipeline Name'),
width: COL_WIDTH_UNDEFINED,
},
{
key: 'spm()',
name: `${t('Times')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
width: COL_WIDTH_UNDEFINED,
},
{
key: 'ai_total_tokens_used()',
name: t('Total tokens used'),
width: 180,
},
{
key: `avg(span.duration)`,
name: DataTitles.avg,
name: t('Pipeline Duration'),
width: COL_WIDTH_UNDEFINED,
},
{
key: 'spm()',
name: `${t('Pipeline runs')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
width: COL_WIDTH_UNDEFINED,
},
];

const SORTABLE_FIELDS = ['avg(span.duration)', 'spm()'];
const SORTABLE_FIELDS = ['ai_total_tokens_used()', 'avg(span.duration)', 'spm()'];

type ValidSort = Sort & {
field: 'spm()' | 'avg(span.duration)';
Expand All @@ -79,13 +81,17 @@ export function PipelinesTable() {
const organization = useOrganization();
const cursor = decodeScalar(location.query?.[QueryParameterNames.SPANS_CURSOR]);
const sortField = decodeScalar(location.query?.[QueryParameterNames.SPANS_SORT]);
const spanDescription = decodeScalar(location.query?.['span.description'], '');

let sort = decodeSorts(sortField).filter(isAValidSort)[0];
if (!sort) {
sort = {field: 'spm()', kind: 'desc'};
}
const {data, isLoading, meta, pageLinks, error} = useSpanMetrics({
search: new MutableSearch('span.category:ai.pipeline'),
search: MutableSearch.fromQueryObject({
'span.category': 'ai.pipeline',
'span.description': spanDescription ? `*${spanDescription}*` : undefined,
}),
fields: [
'project.id',
'span.group',
Expand All @@ -106,9 +112,10 @@ export function PipelinesTable() {
isLoading: tokensUsedLoading,
error: tokensUsedError,
} = useSpanMetrics({
search: new MutableSearch(
`span.ai.pipeline.group:[${(data as Row[])?.map(x => x['span.group']).join(',')}] span.category:ai`
),
search: MutableSearch.fromQueryObject({
'span.ai.pipeline.group': (data as Row[])?.map(x => x['span.group']).join(','),
'span.category': 'ai',
}),
fields: ['span.ai.pipeline.group', 'ai_total_tokens_used()'],
});
if (!tokensUsedLoading) {
Expand All @@ -128,37 +135,55 @@ export function PipelinesTable() {
});
};

const handleSearch = (newQuery: string) => {
browserHistory.push({
...location,
query: {
...location.query,
'span.description': newQuery === '' ? undefined : newQuery,
[QueryParameterNames.SPANS_CURSOR]: undefined,
},
});
};

return (
<VisuallyCompleteWithData
id="PipelinesTable"
hasData={data.length > 0}
isLoading={isLoading}
>
<GridEditable
isLoading={isLoading}
error={error ?? tokensUsedError}
data={data}
columnOrder={COLUMN_ORDER}
columnSortBy={[
{
key: sort.field,
order: sort.kind,
},
]}
grid={{
renderHeadCell: column =>
renderHeadCell({
column,
sort,
location,
sortParameterName: QueryParameterNames.SPANS_SORT,
}),
renderBodyCell: (column, row) =>
renderBodyCell(column, row, meta, location, organization),
}}
location={location}
/>
<Pagination pageLinks={pageLinks} onCursor={handleCursor} />
<Container>
<SearchBar
placeholder={t('Search for pipeline')}
query={spanDescription}
onSearch={handleSearch}
/>
<GridEditable
isLoading={isLoading}
error={error ?? tokensUsedError}
data={data}
columnOrder={COLUMN_ORDER}
columnSortBy={[
{
key: sort.field,
order: sort.kind,
},
]}
grid={{
renderHeadCell: column =>
renderHeadCell({
column,
sort,
location,
sortParameterName: QueryParameterNames.SPANS_SORT,
}),
renderBodyCell: (column, row) =>
renderBodyCell(column, row, meta, location, organization),
}}
location={location}
/>
<Pagination pageLinks={pageLinks} onCursor={handleCursor} />
</Container>
</VisuallyCompleteWithData>
);
}
Expand Down Expand Up @@ -202,3 +227,9 @@ function renderBodyCell(

return rendered;
}

const Container = styled('div')`
display: flex;
flex-direction: column;
gap: ${space(1)};
`;
8 changes: 4 additions & 4 deletions static/app/views/aiMonitoring/aiMonitoringCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function TotalTokensUsedChart({groupId}: TotalTokensUsedChartProps) {
formula: '$total',
},
]}
displayType={MetricDisplayType.AREA}
displayType={MetricDisplayType.LINE}
chartHeight={200}
/>
</TokenChartContainer>
Expand Down Expand Up @@ -113,7 +113,7 @@ export function NumberOfPipelinesChart({groupId}: NumberOfPipelinesChartProps) {
formula: '$number',
},
]}
displayType={MetricDisplayType.AREA}
displayType={MetricDisplayType.LINE}
chartHeight={200}
/>
</TokenChartContainer>
Expand Down Expand Up @@ -151,7 +151,7 @@ export function PipelineDurationChart({groupId}: PipelineDurationChartProps) {
const lastMeta = timeseriesData?.meta?.findLast(_ => true);
if (lastMeta && lastMeta.length >= 2) {
// TODO hack: there is a bug somewhere that is dropping the unit
(lastMeta[1] as MetricsQueryApiResponseLastMeta).unit = 'millisecond';
(lastMeta[1] as MetricsQueryApiResponseLastMeta).unit ??= 'millisecond';
}

if (!isGlobalSelectionReady) {
Expand All @@ -174,7 +174,7 @@ export function PipelineDurationChart({groupId}: PipelineDurationChartProps) {
formula: '$duration',
},
]}
displayType={MetricDisplayType.AREA}
displayType={MetricDisplayType.LINE}
chartHeight={200}
/>
</TokenChartContainer>
Expand Down
53 changes: 27 additions & 26 deletions static/app/views/aiMonitoring/aiMonitoringDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ 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 {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {DurationUnit, RateUnit} from 'sentry/utils/discover/fields';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import useOrganization from 'sentry/utils/useOrganization';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
import {
NumberOfPipelinesChart,
PipelineDurationChart,
Expand All @@ -30,7 +31,6 @@ import {
SpanMetricsField,
type SpanMetricsQueryFilters,
} from 'sentry/views/starfish/types';
import {DataTitles} from 'sentry/views/starfish/views/spans/types';

function NoAccessComponent() {
return (
Expand Down Expand Up @@ -93,15 +93,23 @@ export default function AiMonitoringPage({params}: Props) {
<NoProjectMessage organization={organization}>
<Layout.Header>
<Layout.HeaderContent>
<Layout.Title>
{`${t('AI Monitoring')} - ${spanMetrics['span.description'] ?? t('(no name)')}`}
<PageHeadingQuestionTooltip
title={t(
'If this name is too generic, read the docs to learn how to change it.'
)}
docsUrl="https://docs.sentry.io/product/ai-monitoring/"
/>
</Layout.Title>
<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>
Expand All @@ -115,13 +123,6 @@ export default function AiMonitoringPage({params}: Props) {
<DatePageFilter />
</PageFilterBar>
<MetricsRibbon>
<MetricReadout
title={t('Total Runs')}
value={spanMetrics['count()']}
unit={'count'}
isLoading={areSpanMetricsLoading}
/>

<MetricReadout
title={t('Total Tokens Used')}
value={tokenUsedMetric['ai_total_tokens_used()']}
Expand All @@ -130,20 +131,20 @@ export default function AiMonitoringPage({params}: Props) {
/>

<MetricReadout
title={t('Runs Per Minute')}
value={spanMetrics?.[`${SpanFunction.SPM}()`]}
unit={RateUnit.PER_MINUTE}
isLoading={areSpanMetricsLoading}
/>

<MetricReadout
title={DataTitles.avg}
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>
Expand Down
11 changes: 11 additions & 0 deletions static/app/views/aiMonitoring/landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Fragment} from 'react';

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';
Expand Down Expand Up @@ -48,6 +49,16 @@ export default function AiMonitoringPage() {
<NoProjectMessage organization={organization}>
<Layout.Header>
<Layout.HeaderContent>
<Breadcrumbs
crumbs={[
{
label: t('Dashboard'),
},
{
label: t('AI Monitoring'),
},
]}
/>
<Layout.Title>
{t('AI Monitoring')}
<PageHeadingQuestionTooltip
Expand Down
12 changes: 6 additions & 6 deletions static/app/views/aiMonitoring/pipelineSpansTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,9 @@ type Column = GridColumnHeader<
const COLUMN_ORDER: Column[] = [
{
key: SpanIndexedField.ID,
name: t('ID'),
name: t('Span ID'),
width: COL_WIDTH_UNDEFINED,
},
{
key: SpanIndexedField.SPAN_DURATION,
name: t('Total duration'),
width: 150,
},
{
key: SpanIndexedField.USER,
name: t('User'),
Expand All @@ -49,6 +44,11 @@ const COLUMN_ORDER: Column[] = [
name: t('Timestamp'),
width: COL_WIDTH_UNDEFINED,
},
{
key: SpanIndexedField.SPAN_DURATION,
name: t('Total duration'),
width: 150,
},
];

const SORTABLE_FIELDS = [
Expand Down

0 comments on commit 9b097d5

Please sign in to comment.