cronsLandingPanel.tsx 5.9 KB

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