platform.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import 'prism-sentry/index.css';
  2. import {Component, Fragment} from 'react';
  3. import {browserHistory, WithRouterProps} from 'react-router';
  4. import styled from '@emotion/styled';
  5. import {loadDocs} from 'app/actionCreators/projects';
  6. import {Client} from 'app/api';
  7. import Feature from 'app/components/acl/feature';
  8. import Alert from 'app/components/alert';
  9. import Button from 'app/components/button';
  10. import ButtonBar from 'app/components/buttonBar';
  11. import NotFound from 'app/components/errors/notFound';
  12. import LoadingError from 'app/components/loadingError';
  13. import LoadingIndicator from 'app/components/loadingIndicator';
  14. import SentryDocumentTitle from 'app/components/sentryDocumentTitle';
  15. import {
  16. performance as performancePlatforms,
  17. PlatformKey,
  18. } from 'app/data/platformCategories';
  19. import platforms from 'app/data/platforms';
  20. import {IconInfo} from 'app/icons';
  21. import {t, tct} from 'app/locale';
  22. import {PageHeader} from 'app/styles/organization';
  23. import space from 'app/styles/space';
  24. import {Organization, Project} from 'app/types';
  25. import Projects from 'app/utils/projects';
  26. import withApi from 'app/utils/withApi';
  27. import withOrganization from 'app/utils/withOrganization';
  28. type Props = {
  29. api: Client;
  30. organization: Organization;
  31. } & WithRouterProps<{orgId: string; projectId: string; platform: string}, {}>;
  32. type State = {
  33. loading: boolean;
  34. error: boolean;
  35. html: string;
  36. };
  37. class ProjectInstallPlatform extends Component<Props, State> {
  38. state: State = {
  39. loading: true,
  40. error: false,
  41. html: '',
  42. };
  43. componentDidMount() {
  44. this.fetchData();
  45. window.scrollTo(0, 0);
  46. const {platform} = this.props.params;
  47. //redirect if platform is not known.
  48. if (!platform || platform === 'other') {
  49. this.redirectToNeutralDocs();
  50. }
  51. }
  52. get isGettingStarted() {
  53. return window.location.href.indexOf('getting-started') > 0;
  54. }
  55. fetchData = async () => {
  56. const {api, params} = this.props;
  57. const {orgId, projectId, platform} = params;
  58. this.setState({loading: true});
  59. try {
  60. const {html} = await loadDocs(api, orgId, projectId, platform as PlatformKey);
  61. this.setState({html});
  62. } catch (error) {
  63. this.setState({error});
  64. }
  65. this.setState({loading: false});
  66. };
  67. redirectToNeutralDocs() {
  68. const {orgId, projectId} = this.props.params;
  69. const url = `/organizations/${orgId}/projects/${projectId}/getting-started/`;
  70. browserHistory.push(url);
  71. }
  72. render() {
  73. const {params} = this.props;
  74. const {orgId, projectId} = params;
  75. const platform = platforms.find(p => p.id === params.platform);
  76. if (!platform) {
  77. return <NotFound />;
  78. }
  79. const issueStreamLink = `/organizations/${orgId}/issues/`;
  80. const performanceOverviewLink = `/organizations/${orgId}/performance/`;
  81. const gettingStartedLink = `/organizations/${orgId}/projects/${projectId}/getting-started/`;
  82. const platformLink = platform.link ?? undefined;
  83. return (
  84. <Fragment>
  85. <StyledPageHeader>
  86. <h2>{t('Configure %(platform)s', {platform: platform.name})}</h2>
  87. <ButtonBar gap={1}>
  88. <Button size="small" to={gettingStartedLink}>
  89. {t('< Back')}
  90. </Button>
  91. <Button size="small" href={platformLink} external>
  92. {t('Full Documentation')}
  93. </Button>
  94. </ButtonBar>
  95. </StyledPageHeader>
  96. <div>
  97. <Alert type="info" icon={<IconInfo />}>
  98. {tct(
  99. `
  100. This is a quick getting started guide. For in-depth instructions
  101. on integrating Sentry with [platform], view
  102. [docLink:our complete documentation].`,
  103. {
  104. platform: platform.name,
  105. docLink: <a href={platformLink} />,
  106. }
  107. )}
  108. </Alert>
  109. {this.state.loading ? (
  110. <LoadingIndicator />
  111. ) : this.state.error ? (
  112. <LoadingError onRetry={this.fetchData} />
  113. ) : (
  114. <Fragment>
  115. <SentryDocumentTitle
  116. title={`${t('Configure')} ${platform.name}`}
  117. projectSlug={projectId}
  118. />
  119. <DocumentationWrapper dangerouslySetInnerHTML={{__html: this.state.html}} />
  120. </Fragment>
  121. )}
  122. {this.isGettingStarted && (
  123. <Projects
  124. key={`${orgId}-${projectId}`}
  125. orgId={orgId}
  126. slugs={[projectId]}
  127. passthroughPlaceholderProject={false}
  128. >
  129. {({projects, initiallyLoaded, fetching, fetchError}) => {
  130. const projectsLoading = !initiallyLoaded && fetching;
  131. const projectFilter =
  132. !projectsLoading && !fetchError && projects.length
  133. ? {
  134. project: (projects[0] as Project).id,
  135. }
  136. : {};
  137. const showPerformancePrompt = performancePlatforms.includes(
  138. platform.id as PlatformKey
  139. );
  140. return (
  141. <Fragment>
  142. {showPerformancePrompt && (
  143. <Feature
  144. features={['performance-view']}
  145. hookName="feature-disabled:performance-new-project"
  146. >
  147. {({hasFeature}) => {
  148. if (hasFeature) {
  149. return null;
  150. }
  151. return (
  152. <StyledAlert type="info" icon={<IconInfo />}>
  153. {t(
  154. `Your selected platform supports performance, but your organization does not have performance enabled.`
  155. )}
  156. </StyledAlert>
  157. );
  158. }}
  159. </Feature>
  160. )}
  161. <StyledButtonBar gap={1}>
  162. <Button
  163. priority="primary"
  164. busy={projectsLoading}
  165. to={{
  166. pathname: issueStreamLink,
  167. query: projectFilter,
  168. hash: '#welcome',
  169. }}
  170. >
  171. {t('Take me to Issues')}
  172. </Button>
  173. <Button
  174. busy={projectsLoading}
  175. to={{
  176. pathname: performanceOverviewLink,
  177. query: projectFilter,
  178. }}
  179. >
  180. {t('Take me to Performance')}
  181. </Button>
  182. </StyledButtonBar>
  183. </Fragment>
  184. );
  185. }}
  186. </Projects>
  187. )}
  188. </div>
  189. </Fragment>
  190. );
  191. }
  192. }
  193. const DocumentationWrapper = styled('div')`
  194. .gatsby-highlight {
  195. margin-bottom: ${space(3)};
  196. &:last-child {
  197. margin-bottom: 0;
  198. }
  199. }
  200. .alert {
  201. margin-bottom: ${space(3)};
  202. border-radius: ${p => p.theme.borderRadius};
  203. }
  204. p {
  205. line-height: 1.5;
  206. }
  207. pre {
  208. word-break: break-all;
  209. white-space: pre-wrap;
  210. }
  211. `;
  212. const StyledButtonBar = styled(ButtonBar)`
  213. margin-top: ${space(3)};
  214. width: max-content;
  215. @media (max-width: ${p => p.theme.breakpoints[0]}) {
  216. width: auto;
  217. grid-row-gap: ${space(1)};
  218. grid-auto-flow: row;
  219. }
  220. `;
  221. const StyledPageHeader = styled(PageHeader)`
  222. margin-bottom: ${space(3)};
  223. h2 {
  224. margin: 0;
  225. }
  226. @media (max-width: ${p => p.theme.breakpoints[0]}) {
  227. flex-direction: column;
  228. align-items: flex-start;
  229. h2 {
  230. margin-bottom: ${space(2)};
  231. }
  232. }
  233. `;
  234. const StyledAlert = styled(Alert)`
  235. margin-top: ${space(2)};
  236. `;
  237. export {ProjectInstallPlatform};
  238. export default withApi(withOrganization(ProjectInstallPlatform));