aiMonitoringDetailsPage.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import styled from '@emotion/styled';
  2. import Feature from 'sentry/components/acl/feature';
  3. import {Alert} from 'sentry/components/alert';
  4. import {Breadcrumbs} from 'sentry/components/breadcrumbs';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import NoProjectMessage from 'sentry/components/noProjectMessage';
  7. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  8. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  9. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  10. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  11. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  12. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  13. import {t} from 'sentry/locale';
  14. import {space} from 'sentry/styles/space';
  15. import {DurationUnit, RateUnit} from 'sentry/utils/discover/fields';
  16. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  19. import {
  20. NumberOfPipelinesChart,
  21. PipelineDurationChart,
  22. TotalTokensUsedChart,
  23. } from 'sentry/views/aiMonitoring/aiMonitoringCharts';
  24. import {PipelineSpansTable} from 'sentry/views/aiMonitoring/pipelineSpansTable';
  25. import {MetricReadout} from 'sentry/views/performance/metricReadout';
  26. import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
  27. import {useSpanMetrics} from 'sentry/views/starfish/queries/useSpanMetrics';
  28. import {
  29. SpanFunction,
  30. SpanMetricsField,
  31. type SpanMetricsQueryFilters,
  32. } from 'sentry/views/starfish/types';
  33. function NoAccessComponent() {
  34. return (
  35. <Layout.Page withPadding>
  36. <Alert type="warning">{t("You don't have access to this feature")}</Alert>
  37. </Layout.Page>
  38. );
  39. }
  40. interface Props {
  41. params: {
  42. groupId: string;
  43. };
  44. }
  45. export default function AiMonitoringPage({params}: Props) {
  46. const organization = useOrganization();
  47. const {groupId} = params;
  48. const filters: SpanMetricsQueryFilters = {
  49. 'span.group': groupId,
  50. 'span.category': 'ai.pipeline',
  51. };
  52. const {data, isLoading: areSpanMetricsLoading} = useSpanMetrics({
  53. search: MutableSearch.fromQueryObject(filters),
  54. fields: [
  55. SpanMetricsField.SPAN_OP,
  56. SpanMetricsField.SPAN_DESCRIPTION,
  57. 'count()',
  58. `${SpanFunction.SPM}()`,
  59. `avg(${SpanMetricsField.SPAN_DURATION})`,
  60. ],
  61. enabled: Boolean(groupId),
  62. referrer: 'api.ai-pipelines.view',
  63. });
  64. const spanMetrics = data[0] ?? {};
  65. const {data: totalTokenData, isLoading: isTotalTokenDataLoading} = useSpanMetrics({
  66. search: MutableSearch.fromQueryObject({
  67. 'span.category': 'ai',
  68. 'span.ai.pipeline.group': groupId,
  69. }),
  70. fields: ['ai_total_tokens_used()'],
  71. enabled: Boolean(groupId),
  72. referrer: 'api.ai-pipelines.view',
  73. });
  74. const tokenUsedMetric = totalTokenData[0] ?? {};
  75. return (
  76. <PageFiltersContainer>
  77. <SentryDocumentTitle
  78. title={`AI Monitoring — ${spanMetrics['span.description'] ?? t('(no name)')}`}
  79. >
  80. <Layout.Page>
  81. <Feature
  82. features="ai-analytics"
  83. organization={organization}
  84. renderDisabled={NoAccessComponent}
  85. >
  86. <NoProjectMessage organization={organization}>
  87. <Layout.Header>
  88. <Layout.HeaderContent>
  89. <Breadcrumbs
  90. crumbs={[
  91. {
  92. label: t('Dashboard'),
  93. },
  94. {
  95. label: t('AI Monitoring'),
  96. },
  97. {
  98. label: spanMetrics['span.description'] ?? t('(no name)'),
  99. to: normalizeUrl(
  100. `/organizations/${organization.slug}/ai-monitoring`
  101. ),
  102. },
  103. ]}
  104. />
  105. <Layout.Title>{t('AI Monitoring')}</Layout.Title>
  106. </Layout.HeaderContent>
  107. </Layout.Header>
  108. <Layout.Body>
  109. <Layout.Main fullWidth>
  110. <ModuleLayout.Layout>
  111. <ModuleLayout.Full>
  112. <SpaceBetweenWrap>
  113. <PageFilterBar condensed>
  114. <ProjectPageFilter />
  115. <EnvironmentPageFilter />
  116. <DatePageFilter />
  117. </PageFilterBar>
  118. <MetricsRibbon>
  119. <MetricReadout
  120. title={t('Total Tokens Used')}
  121. value={tokenUsedMetric['ai_total_tokens_used()']}
  122. unit={'count'}
  123. isLoading={isTotalTokenDataLoading}
  124. />
  125. <MetricReadout
  126. title={t('Pipeline Duration')}
  127. value={
  128. spanMetrics?.[`avg(${SpanMetricsField.SPAN_DURATION})`]
  129. }
  130. unit={DurationUnit.MILLISECOND}
  131. isLoading={areSpanMetricsLoading}
  132. />
  133. <MetricReadout
  134. title={t('Pipeline Runs Per Minute')}
  135. value={spanMetrics?.[`${SpanFunction.SPM}()`]}
  136. unit={RateUnit.PER_MINUTE}
  137. isLoading={areSpanMetricsLoading}
  138. />
  139. </MetricsRibbon>
  140. </SpaceBetweenWrap>
  141. </ModuleLayout.Full>
  142. <ModuleLayout.Third>
  143. <TotalTokensUsedChart groupId={groupId} />
  144. </ModuleLayout.Third>
  145. <ModuleLayout.Third>
  146. <NumberOfPipelinesChart groupId={groupId} />
  147. </ModuleLayout.Third>
  148. <ModuleLayout.Third>
  149. <PipelineDurationChart groupId={groupId} />
  150. </ModuleLayout.Third>
  151. <ModuleLayout.Full>
  152. <PipelineSpansTable groupId={groupId} />
  153. </ModuleLayout.Full>
  154. </ModuleLayout.Layout>
  155. </Layout.Main>
  156. </Layout.Body>
  157. </NoProjectMessage>
  158. </Feature>
  159. </Layout.Page>
  160. </SentryDocumentTitle>
  161. </PageFiltersContainer>
  162. );
  163. }
  164. const SpaceBetweenWrap = styled('div')`
  165. display: flex;
  166. justify-content: space-between;
  167. flex-wrap: wrap;
  168. `;
  169. const MetricsRibbon = styled('div')`
  170. display: flex;
  171. flex-wrap: wrap;
  172. gap: ${space(4)};
  173. `;