cronsLandingPanel.tsx 5.6 KB

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