projectMetrics.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {Fragment, useMemo} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import {browserHistory} from 'react-router';
  4. import styled from '@emotion/styled';
  5. import * as Sentry from '@sentry/react';
  6. import debounce from 'lodash/debounce';
  7. import {Button} from 'sentry/components/button';
  8. import ExternalLink from 'sentry/components/links/externalLink';
  9. import Link from 'sentry/components/links/link';
  10. import PanelTable from 'sentry/components/panels/panelTable';
  11. import SearchBar from 'sentry/components/searchBar';
  12. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  13. import Tag from 'sentry/components/tag';
  14. import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
  15. import {t, tct} from 'sentry/locale';
  16. import {space} from 'sentry/styles/space';
  17. import type {Organization, Project} from 'sentry/types';
  18. import {METRICS_DOCS_URL} from 'sentry/utils/metrics/constants';
  19. import {getReadableMetricType} from 'sentry/utils/metrics/formatters';
  20. import {formatMRI} from 'sentry/utils/metrics/mri';
  21. import {useMetricsMeta} from 'sentry/utils/metrics/useMetricsMeta';
  22. import {middleEllipsis} from 'sentry/utils/middleEllipsis';
  23. import {decodeScalar} from 'sentry/utils/queryString';
  24. import routeTitleGen from 'sentry/utils/routeTitle';
  25. import {useMetricsOnboardingSidebar} from 'sentry/views/ddm/ddmOnboarding/useMetricsOnboardingSidebar';
  26. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  27. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  28. import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
  29. type Props = {
  30. organization: Organization;
  31. project: Project;
  32. } & RouteComponentProps<{projectId: string}, {}>;
  33. function ProjectMetrics({project, location}: Props) {
  34. const {data: meta, isLoading} = useMetricsMeta([parseInt(project.id, 10)], ['custom']);
  35. const query = decodeScalar(location.query.query, '').trim();
  36. const {activateSidebar} = useMetricsOnboardingSidebar();
  37. const debouncedSearch = useMemo(
  38. () =>
  39. debounce(
  40. (searchQuery: string) =>
  41. browserHistory.replace({
  42. pathname: location.pathname,
  43. query: {...location.query, query: searchQuery},
  44. }),
  45. DEFAULT_DEBOUNCE_DURATION
  46. ),
  47. [location.pathname, location.query]
  48. );
  49. const metrics = meta.filter(
  50. ({mri, type, unit}) =>
  51. mri.includes(query) ||
  52. getReadableMetricType(type).includes(query) ||
  53. unit.includes(query)
  54. );
  55. return (
  56. <Fragment>
  57. <SentryDocumentTitle title={routeTitleGen(t('Metrics'), project.slug, false)} />
  58. <SettingsPageHeader
  59. title={t('Metrics')}
  60. action={
  61. <Button
  62. priority="primary"
  63. onClick={() => {
  64. Sentry.metrics.increment('ddm.add_custom_metric', 1, {
  65. tags: {
  66. referrer: 'settings',
  67. },
  68. });
  69. activateSidebar();
  70. }}
  71. size="sm"
  72. >
  73. {t('Add Metric')}
  74. </Button>
  75. }
  76. />
  77. <TextBlock>
  78. {tct(
  79. `Metrics are numerical values that can track anything about your environment over time, from latency to error rates to user signups. To learn more about metrics, [link:read the docs].`,
  80. {
  81. link: <ExternalLink href={METRICS_DOCS_URL} />,
  82. }
  83. )}
  84. </TextBlock>
  85. <PermissionAlert project={project} />
  86. <SearchWrapper>
  87. <SearchBar
  88. placeholder={t('Search Metrics')}
  89. onChange={debouncedSearch}
  90. query={query}
  91. />
  92. </SearchWrapper>
  93. <StyledPanelTable
  94. headers={[
  95. t('Metric'),
  96. <RightAligned key="type"> {t('Type')}</RightAligned>,
  97. <RightAligned key="unit">{t('Unit')}</RightAligned>,
  98. ]}
  99. emptyMessage={
  100. query
  101. ? t('No metrics match the query.')
  102. : t('There are no custom metrics for this project.')
  103. }
  104. isEmpty={metrics.length === 0}
  105. isLoading={isLoading}
  106. >
  107. {metrics.map(({mri, type, unit}) => (
  108. <Fragment key={mri}>
  109. <Link
  110. to={`/settings/projects/${project.slug}/metrics/${encodeURIComponent(mri)}`}
  111. >
  112. {middleEllipsis(formatMRI(mri), 65, /\.|-|_/)}
  113. </Link>
  114. <RightAligned>
  115. <Tag>{getReadableMetricType(type)}</Tag>
  116. </RightAligned>
  117. <RightAligned>
  118. <Tag>{unit}</Tag>
  119. </RightAligned>
  120. </Fragment>
  121. ))}
  122. </StyledPanelTable>
  123. </Fragment>
  124. );
  125. }
  126. const SearchWrapper = styled('div')`
  127. margin-bottom: ${space(2)};
  128. `;
  129. const StyledPanelTable = styled(PanelTable)`
  130. grid-template-columns: 1fr minmax(115px, min-content) minmax(115px, min-content);
  131. `;
  132. const RightAligned = styled('div')`
  133. text-align: right;
  134. `;
  135. export default ProjectMetrics;