cronsLandingPanel.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import {Fragment, useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import HookOrDefault from 'sentry/components/hookOrDefault';
  5. import Panel from 'sentry/components/panels/panel';
  6. import PanelBody from 'sentry/components/panels/panelBody';
  7. import {TabList, TabPanels, Tabs} from 'sentry/components/tabs';
  8. import {IconChevron} from 'sentry/icons';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {trackAnalytics} from 'sentry/utils/analytics';
  12. import {decodeScalar} from 'sentry/utils/queryString';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. import {useNavigate} from 'sentry/utils/useNavigate';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import MonitorCreateForm from 'sentry/views/monitors/components/monitorCreateForm';
  17. import type {SupportedPlatform} from './platformPickerPanel';
  18. import {CRON_SDK_PLATFORMS, PlatformPickerPanel} from './platformPickerPanel';
  19. import type {QuickStartProps} from './quickStartEntries';
  20. import {
  21. CeleryBeatAutoDiscovery,
  22. GoUpsertPlatformGuide,
  23. JavaUpsertPlatformGuide,
  24. LaravelUpsertPlatformGuide,
  25. NodeJsUpsertPlatformGuide,
  26. PHPUpsertPlatformGuide,
  27. RubyRailsMixinPlatformGuide,
  28. RubySidekiqAutoPlatformGuide,
  29. RubyUpsertPlatformGuide,
  30. } from './quickStartEntries';
  31. enum GuideKey {
  32. BEAT_AUTO = 'beat_auto',
  33. UPSERT = 'upsert',
  34. MANUAL = 'manual',
  35. MIXIN = 'mixin',
  36. SIDEKIQ_AUTO = 'sidekiq_auto',
  37. }
  38. interface PlatformGuide {
  39. Guide: React.ComponentType<QuickStartProps>;
  40. key: GuideKey;
  41. title: string;
  42. }
  43. const platformGuides: Record<SupportedPlatform, PlatformGuide[]> = {
  44. 'python-celery': [
  45. {
  46. Guide: CeleryBeatAutoDiscovery,
  47. title: 'Beat Auto Discovery',
  48. key: GuideKey.BEAT_AUTO,
  49. },
  50. ],
  51. php: [
  52. {
  53. Guide: PHPUpsertPlatformGuide,
  54. title: 'Upsert',
  55. key: GuideKey.UPSERT,
  56. },
  57. ],
  58. 'php-laravel': [
  59. {
  60. Guide: LaravelUpsertPlatformGuide,
  61. title: 'Upsert',
  62. key: GuideKey.UPSERT,
  63. },
  64. ],
  65. python: [],
  66. node: [
  67. {
  68. Guide: NodeJsUpsertPlatformGuide,
  69. title: 'Upsert',
  70. key: GuideKey.UPSERT,
  71. },
  72. ],
  73. go: [
  74. {
  75. Guide: GoUpsertPlatformGuide,
  76. title: 'Upsert',
  77. key: GuideKey.UPSERT,
  78. },
  79. ],
  80. java: [
  81. {
  82. Guide: JavaUpsertPlatformGuide,
  83. title: 'Upsert',
  84. key: GuideKey.UPSERT,
  85. },
  86. ],
  87. 'java-spring-boot': [],
  88. ruby: [
  89. {
  90. Guide: RubyUpsertPlatformGuide,
  91. title: 'Upsert',
  92. key: GuideKey.UPSERT,
  93. },
  94. ],
  95. 'ruby-rails': [
  96. {
  97. Guide: RubySidekiqAutoPlatformGuide,
  98. title: 'Sidekiq Auto Discovery',
  99. key: GuideKey.SIDEKIQ_AUTO,
  100. },
  101. {
  102. Guide: RubyRailsMixinPlatformGuide,
  103. title: 'Mixin',
  104. key: GuideKey.MIXIN,
  105. },
  106. ],
  107. };
  108. export function isValidPlatform(platform?: string | null): platform is SupportedPlatform {
  109. return !!(platform && platform in platformGuides);
  110. }
  111. export function isValidGuide(guide?: string): guide is GuideKey {
  112. return !!(guide && Object.values<string>(GuideKey).includes(guide));
  113. }
  114. interface CronsLandingPanelProps {
  115. /**
  116. * TODO(epurkhiser): Remove once crons exists only in alerts
  117. */
  118. linkToAlerts?: boolean;
  119. }
  120. export function CronsLandingPanel({linkToAlerts}: CronsLandingPanelProps) {
  121. const organization = useOrganization();
  122. const location = useLocation();
  123. const navigate = useNavigate();
  124. const platform = decodeScalar(location.query?.platform) ?? null;
  125. const guide = decodeScalar(location.query?.guide);
  126. const OnboardingPanelHook = HookOrDefault({
  127. hookName: 'component:crons-onboarding-panel',
  128. defaultComponent: ({children}) => <Fragment>{children}</Fragment>,
  129. });
  130. useEffect(() => {
  131. if (!platform || !guide) {
  132. return;
  133. }
  134. trackAnalytics('landing_page.platform_guide.viewed', {
  135. organization,
  136. platform,
  137. guide,
  138. });
  139. }, [organization, platform, guide]);
  140. const navigateToPlatformGuide = (
  141. selectedPlatform: SupportedPlatform | null,
  142. selectedGuide?: string
  143. ) => {
  144. if (!selectedPlatform) {
  145. navigate({
  146. pathname: location.pathname,
  147. query: {...location.query, platform: undefined, guide: undefined},
  148. });
  149. return;
  150. }
  151. if (!selectedGuide) {
  152. selectedGuide = platformGuides[selectedPlatform][0]?.key ?? GuideKey.MANUAL;
  153. }
  154. navigate({
  155. pathname: location.pathname,
  156. query: {...location.query, platform: selectedPlatform, guide: selectedGuide},
  157. });
  158. };
  159. if (!isValidPlatform(platform) || !isValidGuide(guide)) {
  160. return (
  161. <OnboardingPanelHook>
  162. <PlatformPickerPanel
  163. linkToAlerts={linkToAlerts}
  164. onSelect={navigateToPlatformGuide}
  165. />
  166. </OnboardingPanelHook>
  167. );
  168. }
  169. const platformText = CRON_SDK_PLATFORMS.find(
  170. ({platform: sdkPlatform}) => sdkPlatform === platform
  171. )?.label;
  172. const guides = platformGuides[platform];
  173. return (
  174. <OnboardingPanelHook>
  175. <Panel>
  176. <BackButton
  177. icon={<IconChevron direction="left" />}
  178. onClick={() => navigateToPlatformGuide(null)}
  179. borderless
  180. >
  181. {t('Back to Platforms')}
  182. </BackButton>
  183. <PanelBody withPadding>
  184. <h3>{t('Get Started with %s', platformText)}</h3>
  185. <Tabs
  186. onChange={guideKey => navigateToPlatformGuide(platform, guideKey)}
  187. value={guide}
  188. >
  189. <TabList>
  190. {[
  191. ...guides.map(({key, title}) => (
  192. <TabList.Item key={key}>{title}</TabList.Item>
  193. )),
  194. <TabList.Item key={GuideKey.MANUAL}>{t('Manual')}</TabList.Item>,
  195. ]}
  196. </TabList>
  197. <TabPanels>
  198. {[
  199. ...guides.map(({key, Guide}) => (
  200. <TabPanels.Item key={key}>
  201. <GuideContainer>
  202. <Guide />
  203. </GuideContainer>
  204. </TabPanels.Item>
  205. )),
  206. <TabPanels.Item key={GuideKey.MANUAL}>
  207. <GuideContainer>
  208. <MonitorCreateForm />
  209. </GuideContainer>
  210. </TabPanels.Item>,
  211. ]}
  212. </TabPanels>
  213. </Tabs>
  214. </PanelBody>
  215. </Panel>
  216. </OnboardingPanelHook>
  217. );
  218. }
  219. const BackButton = styled(Button)`
  220. font-weight: ${p => p.theme.fontWeightNormal};
  221. color: ${p => p.theme.subText};
  222. margin: ${space(1)} 0 0 ${space(1)};
  223. padding-left: ${space(0.5)};
  224. padding-right: ${space(0.5)};
  225. `;
  226. const GuideContainer = styled('div')`
  227. display: flex;
  228. flex-direction: column;
  229. gap: ${space(2)};
  230. padding-top: ${space(2)};
  231. `;