cronsLandingPanel.tsx 5.8 KB

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