summaryTable.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import {t} from 'sentry/locale';
  4. import {space} from 'sentry/styles/space';
  5. import {formatMetricsUsingUnitAndOp, getNameFromMRI} from 'sentry/utils/metrics';
  6. import {Series} from 'sentry/views/ddm/metricsExplorer';
  7. export function SummaryTable({
  8. series,
  9. operation,
  10. onClick,
  11. }: {
  12. onClick: (seriesName: string) => void;
  13. series: Series[];
  14. operation?: string;
  15. }) {
  16. return (
  17. <SummaryTableWrapper>
  18. <HeaderCell />
  19. <HeaderCell>{t('Name')}</HeaderCell>
  20. <HeaderCell>{t('Avg')}</HeaderCell>
  21. <HeaderCell>{t('Min')}</HeaderCell>
  22. <HeaderCell>{t('Max')}</HeaderCell>
  23. <HeaderCell>{t('Sum')}</HeaderCell>
  24. {series
  25. .sort((a, b) => a.seriesName.localeCompare(b.seriesName))
  26. .map(({seriesName, color, hidden, unit, data}) => {
  27. const {avg, min, max, sum} = getValues(data);
  28. return (
  29. <Fragment key={seriesName}>
  30. <FlexCell onClick={() => onClick(seriesName)} hidden={hidden}>
  31. <ColorDot color={color} />
  32. </FlexCell>
  33. <Cell onClick={() => onClick(seriesName)}>
  34. {getNameFromMRI(seriesName)}
  35. </Cell>
  36. {/* TODO(ddm): Add a tooltip with the full value, don't add on click in case users want to copy the value */}
  37. <Cell>{formatMetricsUsingUnitAndOp(avg, unit, operation)}</Cell>
  38. <Cell>{formatMetricsUsingUnitAndOp(min, unit, operation)}</Cell>
  39. <Cell>{formatMetricsUsingUnitAndOp(max, unit, operation)}</Cell>
  40. <Cell>{formatMetricsUsingUnitAndOp(sum, unit, operation)}</Cell>
  41. </Fragment>
  42. );
  43. })}
  44. </SummaryTableWrapper>
  45. );
  46. }
  47. function getValues(seriesData: Series['data']) {
  48. if (!seriesData) {
  49. return {min: null, max: null, avg: null, sum: null};
  50. }
  51. const res = seriesData.reduce(
  52. (acc, {value}) => {
  53. if (value === null) {
  54. return acc;
  55. }
  56. acc.min = Math.min(acc.min, value);
  57. acc.max = Math.max(acc.max, value);
  58. acc.sum += value;
  59. return acc;
  60. },
  61. {min: Infinity, max: -Infinity, sum: 0}
  62. );
  63. return {...res, avg: res.sum / seriesData.length};
  64. }
  65. // TODO(ddm): PanelTable component proved to be a bit too opinionated for this use case,
  66. // so we're using a custom styled component instead. Figure out what we want to do here
  67. const SummaryTableWrapper = styled(`div`)`
  68. display: grid;
  69. grid-template-columns: 0.5fr 8fr 1fr 1fr 1fr 1fr;
  70. `;
  71. // TODO(ddm): This is a copy of PanelTableHeader, try to figure out how to reuse it
  72. const HeaderCell = styled('div')`
  73. color: ${p => p.theme.subText};
  74. font-size: ${p => p.theme.fontSizeSmall};
  75. font-weight: 600;
  76. text-transform: uppercase;
  77. border-radius: ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0 0;
  78. line-height: 1;
  79. display: flex;
  80. flex-direction: column;
  81. justify-content: center;
  82. padding: ${space(0.5)};
  83. `;
  84. const Cell = styled('div')`
  85. padding: ${space(0.25)};
  86. :hover {
  87. cursor: ${p => (p.onClick ? 'pointer' : 'default')};
  88. }
  89. `;
  90. const FlexCell = styled(Cell)`
  91. display: flex;
  92. justify-content: center;
  93. align-items: center;
  94. opacity: ${p => (p.hidden ? 0.5 : 1)};
  95. `;
  96. const ColorDot = styled(`div`)`
  97. background-color: ${p => p.color};
  98. border-radius: 50%;
  99. width: ${space(1)};
  100. height: ${space(1)};
  101. `;