scaleTimeSeriesData.tsx 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import * as Sentry from '@sentry/react';
  2. import partialRight from 'lodash/partialRight';
  3. import type {
  4. AggregationOutputType,
  5. DurationUnit,
  6. RateUnit,
  7. SizeUnit,
  8. } from 'sentry/utils/discover/fields';
  9. import {convertDuration} from 'sentry/utils/unitConversion/convertDuration';
  10. import {convertRate} from 'sentry/utils/unitConversion/convertRate';
  11. import {convertSize} from 'sentry/utils/unitConversion/convertSize';
  12. import {
  13. isADurationUnit,
  14. isARateUnit,
  15. isASizeUnit,
  16. isAUnitConvertibleFieldType,
  17. } from '../common/typePredicates';
  18. import type {TimeseriesData} from '../common/types';
  19. import {FALLBACK_TYPE, FALLBACK_UNIT_FOR_FIELD_TYPE} from './settings';
  20. export function scaleTimeSeriesData(
  21. timeserie: Readonly<TimeseriesData>,
  22. destinationUnit: DurationUnit | SizeUnit | RateUnit | null
  23. ): TimeseriesData {
  24. // TODO: Instead of a fallback, allow this to be `null`, which might happen
  25. const sourceType =
  26. (timeserie.meta?.fields[timeserie.field] as AggregationOutputType) ??
  27. (FALLBACK_TYPE as AggregationOutputType);
  28. // Don't bother trying to convert numbers, dates, etc.
  29. if (!isAUnitConvertibleFieldType(sourceType)) {
  30. return timeserie;
  31. }
  32. const sourceUnit = timeserie.meta?.units?.[timeserie.field] ?? null;
  33. if (!destinationUnit || sourceUnit === destinationUnit) {
  34. return timeserie;
  35. }
  36. // Don't bother with invalid conversions
  37. if (
  38. (sourceType === 'duration' && !isADurationUnit(destinationUnit)) ||
  39. (sourceType === 'size' && !isASizeUnit(destinationUnit)) ||
  40. (sourceType === 'rate' && !isARateUnit(destinationUnit))
  41. ) {
  42. Sentry.captureMessage(
  43. `Attempted invalid timeseries conversion from ${sourceType} in ${sourceUnit} to ${destinationUnit}`
  44. );
  45. return timeserie;
  46. }
  47. let scaler: (value: number) => number;
  48. if (sourceType === 'duration') {
  49. scaler = partialRight(
  50. convertDuration,
  51. sourceUnit ?? FALLBACK_UNIT_FOR_FIELD_TYPE.duration,
  52. destinationUnit
  53. );
  54. } else if (sourceType === 'size') {
  55. scaler = partialRight(
  56. convertSize,
  57. sourceUnit ?? FALLBACK_UNIT_FOR_FIELD_TYPE.size,
  58. destinationUnit
  59. );
  60. } else if (sourceType === 'rate') {
  61. scaler = partialRight(
  62. convertRate,
  63. sourceUnit ?? FALLBACK_UNIT_FOR_FIELD_TYPE.rate,
  64. destinationUnit
  65. );
  66. }
  67. return {
  68. ...timeserie,
  69. data: timeserie.data.map(datum => {
  70. const {value} = datum;
  71. return {
  72. ...datum,
  73. value: scaler(value),
  74. };
  75. }),
  76. meta: {
  77. ...timeserie.meta,
  78. fields: {
  79. [timeserie.field]: sourceType,
  80. },
  81. units: {
  82. [timeserie.field]: destinationUnit,
  83. },
  84. },
  85. };
  86. }