projectMetricsDetails.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import {Fragment} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {LinkButton} from 'sentry/components/button';
  5. import MiniBarChart from 'sentry/components/charts/miniBarChart';
  6. import EmptyMessage from 'sentry/components/emptyMessage';
  7. import FieldGroup from 'sentry/components/forms/fieldGroup';
  8. import Panel from 'sentry/components/panels/panel';
  9. import PanelBody from 'sentry/components/panels/panelBody';
  10. import PanelHeader from 'sentry/components/panels/panelHeader';
  11. import PanelTable from 'sentry/components/panels/panelTable';
  12. import Placeholder from 'sentry/components/placeholder';
  13. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  14. import {CHART_PALETTE} from 'sentry/constants/chartPalette';
  15. import {t} from 'sentry/locale';
  16. import type {
  17. MetricsOperation,
  18. MetricType,
  19. MRI,
  20. Organization,
  21. Project,
  22. } from 'sentry/types';
  23. import {getDdmUrl} from 'sentry/utils/metrics';
  24. import {getReadableMetricType} from 'sentry/utils/metrics/formatters';
  25. import {formatMRI, formatMRIField, MRIToField, parseMRI} from 'sentry/utils/metrics/mri';
  26. import {MetricDisplayType} from 'sentry/utils/metrics/types';
  27. import {useMetricsData} from 'sentry/utils/metrics/useMetricsData';
  28. import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags';
  29. import routeTitleGen from 'sentry/utils/routeTitle';
  30. import {CodeLocations} from 'sentry/views/ddm/codeLocations';
  31. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  32. function getSettingsOperationForType(type: MetricType): MetricsOperation {
  33. switch (type) {
  34. case 'c':
  35. return 'sum';
  36. case 's':
  37. return 'count_unique';
  38. case 'd':
  39. return 'count';
  40. case 'g':
  41. return 'count';
  42. default:
  43. return 'sum';
  44. }
  45. }
  46. type Props = {
  47. organization: Organization;
  48. project: Project;
  49. } & RouteComponentProps<{mri: MRI; projectId: string}, {}>;
  50. function ProjectMetricsDetails({project, params, organization}: Props) {
  51. const {mri} = params;
  52. const {type, name, unit} = parseMRI(mri) ?? {};
  53. const operation = getSettingsOperationForType(type ?? 'c');
  54. const field = MRIToField(mri, operation);
  55. const {data: tagsData = []} = useMetricsTags(mri, [parseInt(project.id, 10)]);
  56. const {data: metricsData, isLoading} = useMetricsData(
  57. {
  58. datetime: {
  59. period: '30d',
  60. start: '',
  61. end: '',
  62. utc: false,
  63. },
  64. environments: [],
  65. mri,
  66. projects: [parseInt(project.id, 10)],
  67. op: operation,
  68. },
  69. {interval: '1d'}
  70. );
  71. const series = [
  72. {
  73. seriesName: formatMRIField(field) ?? 'Metric',
  74. data:
  75. metricsData?.intervals.map((interval, index) => ({
  76. name: interval,
  77. value: metricsData.groups[0].series[field][index] ?? 0,
  78. })) ?? [],
  79. },
  80. ];
  81. const isChartEmpty = series[0].data.every(({value}) => value === 0);
  82. const tags = tagsData.sort((a, b) => a.key.localeCompare(b.key));
  83. return (
  84. <Fragment>
  85. <SentryDocumentTitle title={routeTitleGen(formatMRI(mri), project.slug, false)} />
  86. <SettingsPageHeader
  87. title={t('Metric Details')}
  88. action={
  89. <LinkButton
  90. to={getDdmUrl(organization.slug, {
  91. statsPeriod: '30d',
  92. project: [project.id],
  93. widgets: [
  94. {
  95. mri,
  96. displayType: MetricDisplayType.BAR,
  97. op: operation,
  98. query: '',
  99. groupBy: undefined,
  100. },
  101. ],
  102. })}
  103. size="sm"
  104. >
  105. {t('Open in Metrics')}
  106. </LinkButton>
  107. }
  108. />
  109. <Panel>
  110. <PanelHeader>{t('Metric Details')}</PanelHeader>
  111. <PanelBody>
  112. <FieldGroup
  113. label={t('Name')}
  114. help={t('Name of the metric (invoked in your code).')}
  115. >
  116. <MetricName>
  117. <strong>{name}</strong>
  118. </MetricName>
  119. </FieldGroup>
  120. <FieldGroup
  121. label={t('Type')}
  122. help={t('Either counter, distribution, gauge, or set.')}
  123. >
  124. <div>{getReadableMetricType(type)}</div>
  125. </FieldGroup>
  126. <FieldGroup
  127. label={t('Unit')}
  128. help={t('Unit specified in the code - affects formatting.')}
  129. >
  130. <div>{unit}</div>
  131. </FieldGroup>
  132. </PanelBody>
  133. </Panel>
  134. <Panel>
  135. <PanelHeader>{t('Activity in the last 30 days (by day)')}</PanelHeader>
  136. <PanelBody withPadding>
  137. {isLoading && <Placeholder height="100px" />}
  138. {!isLoading && (
  139. <MiniBarChart
  140. series={series}
  141. colors={CHART_PALETTE[0]}
  142. height={100}
  143. isGroupedByDate
  144. stacked
  145. labelYAxisExtents
  146. />
  147. )}
  148. {!isLoading && isChartEmpty && (
  149. <EmptyMessage
  150. title={t('No activity.')}
  151. description={t("We don't have data for this metric in the last 30 days.")}
  152. />
  153. )}
  154. </PanelBody>
  155. </Panel>
  156. <PanelTable
  157. headers={[<TableHeading key="tags"> {t('Tags')}</TableHeading>]}
  158. emptyMessage={t('There are no tags for this metric.')}
  159. isEmpty={tags.length === 0}
  160. isLoading={isLoading}
  161. >
  162. {tags.map(({key}) => (
  163. <div key={key}>{key}</div>
  164. ))}
  165. </PanelTable>
  166. <Panel>
  167. <PanelHeader>{t('Code Location')}</PanelHeader>
  168. <PanelBody withPadding>
  169. <CodeLocations mri={mri} />
  170. </PanelBody>
  171. </Panel>
  172. </Fragment>
  173. );
  174. }
  175. const TableHeading = styled('div')`
  176. color: ${p => p.theme.textColor};
  177. `;
  178. const MetricName = styled('div')`
  179. word-break: break-word;
  180. `;
  181. export default ProjectMetricsDetails;