PipelinesTable.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {browserHistory} from 'react-router';
  2. import type {Location} from 'history';
  3. import type {GridColumnHeader} from 'sentry/components/gridEditable';
  4. import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
  5. import Link from 'sentry/components/links/link';
  6. import type {CursorHandler} from 'sentry/components/pagination';
  7. import Pagination from 'sentry/components/pagination';
  8. import {t} from 'sentry/locale';
  9. import type {Organization} from 'sentry/types/organization';
  10. import type {EventsMetaType} from 'sentry/utils/discover/eventView';
  11. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  12. import type {Sort} from 'sentry/utils/discover/fields';
  13. import {RATE_UNIT_TITLE, RateUnit} from 'sentry/utils/discover/fields';
  14. import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
  15. import {decodeScalar, decodeSorts} from 'sentry/utils/queryString';
  16. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  17. import {useLocation} from 'sentry/utils/useLocation';
  18. import useOrganization from 'sentry/utils/useOrganization';
  19. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  20. import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell';
  21. import {useSpanMetrics} from 'sentry/views/starfish/queries/useSpanMetrics';
  22. import type {MetricsResponse} from 'sentry/views/starfish/types';
  23. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  24. import {DataTitles} from 'sentry/views/starfish/views/spans/types';
  25. type Row = Pick<
  26. MetricsResponse,
  27. | 'project.id'
  28. | 'span.description'
  29. | 'span.group'
  30. | 'spm()'
  31. | 'avg(span.duration)'
  32. | 'sum(span.duration)'
  33. | 'time_spent_percentage()'
  34. >;
  35. type Column = GridColumnHeader<
  36. 'span.description' | 'spm()' | 'avg(span.duration)' | 'time_spent_percentage()'
  37. >;
  38. const COLUMN_ORDER: Column[] = [
  39. {
  40. key: 'span.description',
  41. name: t('AI Pipeline name'),
  42. width: COL_WIDTH_UNDEFINED,
  43. },
  44. {
  45. key: 'spm()',
  46. name: `${t('Times')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
  47. width: COL_WIDTH_UNDEFINED,
  48. },
  49. {
  50. key: `avg(span.duration)`,
  51. name: DataTitles.avg,
  52. width: COL_WIDTH_UNDEFINED,
  53. },
  54. {
  55. key: 'time_spent_percentage()',
  56. name: DataTitles.timeSpent,
  57. width: COL_WIDTH_UNDEFINED,
  58. },
  59. ];
  60. const SORTABLE_FIELDS = ['avg(span.duration)', 'spm()', 'time_spent_percentage()'];
  61. type ValidSort = Sort & {
  62. field: 'spm()' | 'avg(span.duration)' | 'time_spent_percentage()';
  63. };
  64. export function isAValidSort(sort: Sort): sort is ValidSort {
  65. return (SORTABLE_FIELDS as unknown as string[]).includes(sort.field);
  66. }
  67. export function PipelinesTable() {
  68. const location = useLocation();
  69. const organization = useOrganization();
  70. const cursor = decodeScalar(location.query?.[QueryParameterNames.SPANS_CURSOR]);
  71. const sortField = decodeScalar(location.query?.[QueryParameterNames.SPANS_SORT]);
  72. let sort = decodeSorts(sortField).filter(isAValidSort)[0];
  73. if (!sort) {
  74. sort = {field: 'time_spent_percentage()', kind: 'desc'};
  75. }
  76. const {data, isLoading, meta, pageLinks, error} = useSpanMetrics({
  77. search: new MutableSearch('span.category:ai.pipeline'),
  78. fields: [
  79. 'project.id',
  80. 'span.group',
  81. 'span.description',
  82. 'spm()',
  83. 'avg(span.duration)',
  84. 'sum(span.duration)',
  85. 'time_spent_percentage()',
  86. ],
  87. sorts: [sort],
  88. limit: 25,
  89. cursor,
  90. referrer: 'api.ai-pipelines.view',
  91. });
  92. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  93. browserHistory.push({
  94. pathname,
  95. query: {...query, [QueryParameterNames.SPANS_CURSOR]: newCursor},
  96. });
  97. };
  98. return (
  99. <VisuallyCompleteWithData
  100. id="PipelinesTable"
  101. hasData={data.length > 0}
  102. isLoading={isLoading}
  103. >
  104. <GridEditable
  105. isLoading={isLoading}
  106. error={error}
  107. data={data}
  108. columnOrder={COLUMN_ORDER}
  109. columnSortBy={[
  110. {
  111. key: sort.field,
  112. order: sort.kind,
  113. },
  114. ]}
  115. grid={{
  116. renderHeadCell: column =>
  117. renderHeadCell({
  118. column,
  119. sort,
  120. location,
  121. sortParameterName: QueryParameterNames.SPANS_SORT,
  122. }),
  123. renderBodyCell: (column, row) =>
  124. renderBodyCell(column, row, meta, location, organization),
  125. }}
  126. location={location}
  127. />
  128. <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
  129. </VisuallyCompleteWithData>
  130. );
  131. }
  132. function renderBodyCell(
  133. column: Column,
  134. row: Row,
  135. meta: EventsMetaType | undefined,
  136. location: Location,
  137. organization: Organization
  138. ) {
  139. if (column.key === 'span.description') {
  140. if (!row['span.description']) {
  141. return <span>(unknown)</span>;
  142. }
  143. if (!row['span.group']) {
  144. return <span>{row['span.description']}</span>;
  145. }
  146. return (
  147. <Link
  148. to={normalizeUrl(
  149. `/organizations/${organization.slug}/ai-analytics/pipeline-type/${row['span.group']}`
  150. )}
  151. >
  152. {row['span.description']}
  153. </Link>
  154. );
  155. }
  156. if (!meta || !meta?.fields) {
  157. return row[column.key];
  158. }
  159. const renderer = getFieldRenderer(column.key, meta.fields, false);
  160. const rendered = renderer(row, {
  161. location,
  162. organization,
  163. unit: meta.units?.[column.key],
  164. });
  165. return rendered;
  166. }