projectMetrics.tsx 4.5 KB

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