projectMetrics.tsx 4.8 KB

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