projectMetrics.tsx 4.7 KB

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