projectMetricsDetails.tsx 5.7 KB

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