presetSidebar.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import {useState} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import LoadingIndicator from 'sentry/components/loadingIndicator';
  5. import {t} from 'sentry/locale';
  6. import space from 'sentry/styles/space';
  7. import {Organization, Project} from 'sentry/types';
  8. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  9. import useApi from 'sentry/utils/useApi';
  10. import {Preset, PRESET_AGGREGATES, PresetContext} from './presets';
  11. type Props = {
  12. organization: Organization;
  13. project: Project;
  14. className?: string;
  15. onSelect?(preset: Preset, ctx: PresetContext): void;
  16. selectedPresetId?: string;
  17. };
  18. export default function PresetSidebar(props: Props) {
  19. return (
  20. <div className={props.className}>
  21. <Header>{t('Suggested Alerts')}</Header>
  22. {PRESET_AGGREGATES.map((preset, i) => (
  23. <PresetSidebarItem
  24. key={preset.id}
  25. preset={preset}
  26. index={i}
  27. organization={props.organization}
  28. project={props.project}
  29. selected={props.selectedPresetId === preset.id}
  30. onClick={ctx => props.onSelect && props.onSelect(preset, ctx)}
  31. />
  32. ))}
  33. </div>
  34. );
  35. }
  36. function PresetSidebarItem(props: {
  37. index: number;
  38. organization: Organization;
  39. preset: Preset;
  40. project: Project;
  41. onClick?: (ctx: PresetContext) => void;
  42. selected?: boolean;
  43. }) {
  44. const theme = useTheme();
  45. const api = useApi();
  46. const [loading, setLoading] = useState(false);
  47. const iconColor = theme.charts.getColorPalette(PRESET_AGGREGATES.length)[props.index];
  48. return (
  49. <StyledPresetSidebarItemContainer
  50. selected={props.selected || false}
  51. onClick={() => {
  52. if (loading) {
  53. return;
  54. }
  55. trackAdvancedAnalyticsEvent('growth.metric_alert_preset_sidebar_clicked', {
  56. organization: props.organization,
  57. preset: props.preset.id,
  58. });
  59. setLoading(true);
  60. props.preset
  61. .makeContext(api, props.project, props.organization)
  62. .then(props.onClick)
  63. .finally(() => setLoading(false));
  64. }}
  65. >
  66. {loading && (
  67. <LoadingWrapper>
  68. <StyledLoadingIndicator hideMessage />
  69. </LoadingWrapper>
  70. )}
  71. <IconWrapper backgroundColor={iconColor}>
  72. {<props.preset.Icon color="white" />}
  73. </IconWrapper>
  74. <div>
  75. <h1>{props.preset.title}</h1>
  76. <small>{props.preset.description}</small>
  77. </div>
  78. </StyledPresetSidebarItemContainer>
  79. );
  80. }
  81. const LoadingWrapper = styled('div')`
  82. position: absolute;
  83. background-color: ${p => p.theme.overlayBackgroundAlpha};
  84. top: 0;
  85. bottom: 0;
  86. left: 0;
  87. right: 0;
  88. display: flex;
  89. justify-content: center;
  90. align-items: center;
  91. cursor: default;
  92. `;
  93. const StyledLoadingIndicator = styled(LoadingIndicator)`
  94. margin: 0;
  95. `;
  96. const StyledPresetSidebarItemContainer = styled('div')<{selected: boolean}>`
  97. border: 1px solid transparent;
  98. position: relative;
  99. overflow: hidden;
  100. border-radius: ${p => p.theme.borderRadius};
  101. transition: border-color 0.3s ease;
  102. padding: ${space(2)};
  103. h1 {
  104. font-size: ${p => p.theme.fontSizeLarge};
  105. font-weight: 500;
  106. margin-bottom: 0;
  107. color: ${p => p.theme.gray500};
  108. }
  109. small {
  110. color: ${p => p.theme.gray300};
  111. }
  112. display: flex;
  113. flex-direction: row;
  114. align-items: start;
  115. cursor: pointer;
  116. user-select: none;
  117. &:hover {
  118. border-color: ${p => p.theme.gray100};
  119. }
  120. ${p => p.selected && `border-color: ${p.theme.gray200};`}
  121. `;
  122. const Header = styled('h5')`
  123. margin-left: ${space(1)};
  124. `;
  125. const IconWrapper = styled('div')<{backgroundColor: string}>`
  126. display: flex;
  127. justify-content: center;
  128. align-items: center;
  129. padding: ${space(1)};
  130. min-width: 40px;
  131. height: 40px;
  132. border-radius: ${p => p.theme.borderRadius};
  133. background: ${p => p.backgroundColor};
  134. margin-right: ${space(1)};
  135. `;