flagSeries.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import {useTheme} from '@emotion/react';
  2. import moment from 'moment-timezone';
  3. import MarkLine from 'sentry/components/charts/components/markLine';
  4. import {t} from 'sentry/locale';
  5. import type {Event} from 'sentry/types/event';
  6. import type {Organization} from 'sentry/types/organization';
  7. import {getFormattedDate} from 'sentry/utils/dates';
  8. import {useApiQuery} from 'sentry/utils/queryClient';
  9. import useOrganization from 'sentry/utils/useOrganization';
  10. import usePageFilters from 'sentry/utils/usePageFilters';
  11. type RawFlag = {
  12. action: string;
  13. created_at: string;
  14. created_by: string;
  15. created_by_type: string;
  16. flag: string;
  17. id: number;
  18. tags: Record<string, any>;
  19. };
  20. export type RawFlagData = {data: RawFlag[]};
  21. type FlagSeriesDatapoint = {
  22. // flag action
  23. label: {formatter: () => string};
  24. // flag name
  25. name: string;
  26. // unix timestamp
  27. xAxis: number;
  28. };
  29. interface FlagSeriesProps {
  30. event: Event;
  31. query: Record<string, any>;
  32. }
  33. function useOrganizationFlagLog({
  34. organization,
  35. query,
  36. }: {
  37. organization: Organization;
  38. query: Record<string, any>;
  39. }) {
  40. const {data, isError, isPending} = useApiQuery<RawFlagData>(
  41. [`/organizations/${organization.slug}/flags/logs/`, {query}],
  42. {
  43. staleTime: 0,
  44. enabled: organization.features?.includes('feature-flag-ui'),
  45. }
  46. );
  47. return {data, isError, isPending};
  48. }
  49. function hydrateFlagData({
  50. rawFlagData,
  51. }: {
  52. rawFlagData: RawFlagData;
  53. }): FlagSeriesDatapoint[] {
  54. // transform raw flag data into series data
  55. // each data point needs to be type FlagSeriesDatapoint
  56. const flagData = rawFlagData.data.map(f => {
  57. return {
  58. xAxis: Date.parse(f.created_at),
  59. label: {formatter: () => f.action},
  60. name: `${f.flag}`,
  61. };
  62. });
  63. return flagData;
  64. }
  65. export default function useFlagSeries({query = {}, event}: FlagSeriesProps) {
  66. const theme = useTheme();
  67. const organization = useOrganization();
  68. const {
  69. data: rawFlagData,
  70. isError,
  71. isPending,
  72. } = useOrganizationFlagLog({organization, query});
  73. const {selection} = usePageFilters();
  74. if (!rawFlagData || isError || isPending) {
  75. return {
  76. seriesName: t('Feature Flags'),
  77. markLine: {},
  78. data: [],
  79. };
  80. }
  81. const hydratedFlagData: FlagSeriesDatapoint[] = hydrateFlagData({rawFlagData});
  82. // create a markline series using hydrated flag data
  83. const markLine = MarkLine({
  84. animation: false,
  85. lineStyle: {
  86. color: theme.purple300,
  87. opacity: 0.3,
  88. type: 'solid',
  89. },
  90. label: {
  91. show: false,
  92. },
  93. data: hydratedFlagData,
  94. tooltip: {
  95. trigger: 'item',
  96. formatter: ({data}: any) => {
  97. const time = getFormattedDate(data.xAxis, 'MMM D, YYYY LT z', {
  98. local: !selection.datetime.utc,
  99. });
  100. return [
  101. '<div class="tooltip-series">',
  102. `<div><span class="tooltip-label"><strong>${t(
  103. 'Feature Flag'
  104. )}</strong></span></div>`,
  105. `<span class="tooltip-label-align-start"><code class="tooltip-code-no-margin">${data.name}</code>${data.label.formatter()}</span>`,
  106. '</div>',
  107. '<div class="tooltip-footer">',
  108. time,
  109. event.dateCreated &&
  110. ` (${moment(time).from(event.dateCreated, true)} ${t('before this event')})`,
  111. '</div>',
  112. '<div class="tooltip-arrow"></div>',
  113. ].join('');
  114. },
  115. },
  116. });
  117. return {
  118. seriesName: t('Feature Flags'),
  119. data: [],
  120. markLine,
  121. type: 'line', // use this type so the bar chart doesn't shrink/grow
  122. };
  123. }