Browse Source

ref(onboarding): Update session storage to use project id - (#45510)

Priscila Oliveira 2 years ago
parent
commit
12e89d921f

+ 3 - 2
static/app/views/onboarding/components/footer.tsx

@@ -40,6 +40,7 @@ const DEFAULT_POLL_INTERVAL = 5000;
 type Props = Pick<RouteComponentProps<{}, {}>, 'router' | 'route' | 'location'> & {
   projectSlug: Project['slug'];
   newOrg?: boolean;
+  projectId?: Project['id'];
 };
 
 async function openChangeRouteModal({
@@ -68,7 +69,7 @@ async function openChangeRouteModal({
   ));
 }
 
-export function Footer({projectSlug, router, newOrg}: Props) {
+export function Footer({projectSlug, projectId, router, newOrg}: Props) {
   const organization = useOrganization();
   const preferences = useLegacyStore(PreferencesStore);
   const [firstError, setFirstError] = useState<string | null>(null);
@@ -76,7 +77,7 @@ export function Footer({projectSlug, router, newOrg}: Props) {
   const [clientState, setClientState] = usePersistedOnboardingState();
   const {projects} = useProjects();
 
-  const onboarding_sessionStorage_key = `onboarding-${projectSlug}`;
+  const onboarding_sessionStorage_key = `onboarding-${projectId}`;
 
   const [sessionStorage, setSessionStorage] = useSessionStorage<OnboardingState>(
     onboarding_sessionStorage_key,

+ 1 - 0
static/app/views/onboarding/setupDocs.tsx

@@ -300,6 +300,7 @@ function SetupDocs({
         (heartbeatFooter ? (
           <Footer
             projectSlug={project.slug}
+            projectId={project.id}
             route={route}
             router={router}
             location={location}

+ 88 - 41
static/app/views/projectInstall/platform.spec.jsx

@@ -1,74 +1,110 @@
-import {browserHistory} from 'react-router';
-
-import {render, screen} from 'sentry-test/reactTestingLibrary';
+import {initializeOrg} from 'sentry-test/initializeOrg';
+import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
 
 import {ProjectInstallPlatform} from 'sentry/views/projectInstall/platform';
 
 describe('ProjectInstallPlatform', function () {
   describe('render()', function () {
-    const baseProps = {
-      api: new MockApiClient(),
-      organization: TestStubs.Organization(),
-      project: TestStubs.Project(),
-      location: {query: {}},
-    };
-
-    it('should redirect to if no matching platform', function () {
-      const props = {
-        ...baseProps,
-        params: {
-          projectId: baseProps.project.slug,
-          platform: 'other',
+    const api = new MockApiClient();
+
+    it('should redirect to if no matching platform', async function () {
+      const {organization, router, project, routerContext} = initializeOrg({
+        router: {
+          location: {
+            query: {},
+          },
+          params: {
+            projectId: TestStubs.Project().slug,
+            platform: 'other',
+          },
         },
-      };
+      });
 
       MockApiClient.addMockResponse({
-        url: '/projects/org-slug/project-slug/docs/other/',
+        url: `/projects/org-slug/${project.slug}/docs/other/`,
         body: {},
       });
 
       MockApiClient.addMockResponse({
         method: 'GET',
         url: '/organizations/org-slug/projects/',
-        body: [baseProps.project],
+        body: [project],
       });
 
-      render(<ProjectInstallPlatform {...props} />, {
-        context: TestStubs.routerContext([{organization: {id: '1337'}}]),
+      render(
+        <ProjectInstallPlatform
+          api={api}
+          organization={organization}
+          routes={router.routes}
+          router={router}
+          location={router.location}
+          params={router.params}
+        />,
+        {
+          organization,
+          context: routerContext,
+        }
+      );
+
+      await waitFor(() => {
+        expect(router.push).toHaveBeenCalledTimes(1);
       });
-
-      expect(browserHistory.push).toHaveBeenCalledTimes(1);
     });
 
     it('should render NotFound if no matching integration/platform', async function () {
-      const props = {
-        ...baseProps,
-        params: {
-          projectId: baseProps.project.slug,
-          platform: 'lua',
+      const {organization, router, project, routerContext} = initializeOrg({
+        router: {
+          location: {
+            query: {},
+          },
+          params: {
+            projectId: TestStubs.Project().slug,
+            platform: 'lua',
+          },
         },
-      };
+      });
 
       MockApiClient.addMockResponse({
         url: '/projects/org-slug/project-slug/docs/lua/',
         statusCode: 404,
       });
 
-      render(<ProjectInstallPlatform {...props} />, {
-        context: TestStubs.routerContext([{organization: {id: '1337'}}]),
+      MockApiClient.addMockResponse({
+        method: 'GET',
+        url: '/organizations/org-slug/projects/',
+        body: [project],
       });
 
+      render(
+        <ProjectInstallPlatform
+          api={api}
+          organization={organization}
+          routes={router.routes}
+          router={router}
+          location={router.location}
+          params={router.params}
+        />,
+        {
+          organization,
+          context: routerContext,
+        }
+      );
+
       expect(await screen.findByText('Page Not Found')).toBeInTheDocument();
     });
 
     it('should render documentation', async function () {
-      const props = {
-        ...baseProps,
-        params: {
-          projectId: baseProps.project.slug,
-          platform: 'node',
+      const {organization, router, project, routerContext} = initializeOrg({
+        router: {
+          location: {
+            query: {},
+          },
+          params: {
+            projectId: TestStubs.Project().slug,
+            platform: 'node',
+          },
         },
-      };
+      });
 
       MockApiClient.addMockResponse({
         url: '/projects/org-slug/project-slug/docs/node/',
@@ -78,12 +114,23 @@ describe('ProjectInstallPlatform', function () {
       MockApiClient.addMockResponse({
         method: 'GET',
         url: '/organizations/org-slug/projects/',
-        body: [baseProps.project],
+        body: [project],
       });
 
-      render(<ProjectInstallPlatform {...props} />, {
-        context: TestStubs.routerContext([{organization: {id: '1337'}}]),
-      });
+      render(
+        <ProjectInstallPlatform
+          api={api}
+          organization={organization}
+          routes={router.routes}
+          router={router}
+          location={router.location}
+          params={router.params}
+        />,
+        {
+          organization,
+          context: routerContext,
+        }
+      );
 
       expect(await screen.findByText('Documentation here')).toBeInTheDocument();
     });

+ 152 - 192
static/app/views/projectInstall/platform.tsx

@@ -1,9 +1,8 @@
-import {Component, Fragment} from 'react';
-import {browserHistory, RouteComponentProps} from 'react-router';
+import {Fragment, useCallback, useEffect, useState} from 'react';
+import {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
 
 import {loadDocs} from 'sentry/actionCreators/projects';
-import {Client} from 'sentry/api';
 import Feature from 'sentry/components/acl/feature';
 import {Alert} from 'sentry/components/alert';
 import {Button} from 'sentry/components/button';
@@ -20,219 +19,183 @@ import platforms from 'sentry/data/platforms';
 import {IconChevron} from 'sentry/icons';
 import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import {Organization, Project} from 'sentry/types';
-import Projects from 'sentry/utils/projects';
-import withApi from 'sentry/utils/withApi';
+import useApi from 'sentry/utils/useApi';
+import useOrganization from 'sentry/utils/useOrganization';
+import useProjects from 'sentry/utils/useProjects';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
-import withOrganization from 'sentry/utils/withOrganization';
 import {Footer} from 'sentry/views/onboarding/components/footer';
 
-type Props = {
-  api: Client;
-  organization: Organization;
-} & RouteComponentProps<{platform: string; projectId: string}, {}>;
-
-type State = {
-  error: boolean;
-  html: string;
-  loading: boolean;
-};
-
-class ProjectInstallPlatform extends Component<Props, State> {
-  state: State = {
-    loading: true,
-    error: false,
-    html: '',
-  };
-
-  componentDidMount() {
-    this.fetchData();
-    window.scrollTo(0, 0);
+type Props = RouteComponentProps<{platform: string; projectId: string}, {}>;
 
-    const {platform} = this.props.params;
+export function ProjectInstallPlatform({location, params, route, router}: Props) {
+  const api = useApi();
+  const organization = useOrganization();
 
-    // redirect if platform is not known.
-    if (!platform || platform === 'other') {
-      this.redirectToNeutralDocs();
-    }
-  }
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(false);
+  const [html, setHtml] = useState('');
 
-  get isGettingStarted() {
-    return window.location.href.indexOf('getting-started') > 0;
-  }
+  const {projects, initiallyLoaded} = useProjects({
+    slugs: [params.projectId],
+    orgId: organization.slug,
+  });
 
-  fetchData = async () => {
-    const {api, organization, params} = this.props;
-    const {projectId, platform} = params;
+  const loadingProjects = !initiallyLoaded;
+  const project = projects.filter(proj => proj.slug === params.projectId)[0];
 
-    this.setState({loading: true});
+  const fetchData = useCallback(async () => {
+    setLoading(true);
 
     try {
-      const {html} = await loadDocs(
+      const {html: reponse} = await loadDocs(
         api,
         organization.slug,
-        projectId,
-        platform as PlatformKey
+        params.projectId,
+        params.platform as PlatformKey
       );
-      this.setState({html});
-    } catch (error) {
-      this.setState({error});
+      setHtml(reponse);
+    } catch (err) {
+      setError(err);
     }
 
-    this.setState({loading: false});
-  };
+    setLoading(false);
+  }, [api, organization.slug, params]);
 
-  redirectToNeutralDocs() {
-    const {organization} = this.props;
-    const {projectId} = this.props.params;
+  const redirectToNeutralDocs = useCallback(() => {
+    const url = `/organizations/${organization.slug}/projects/${params.projectId}/getting-started/`;
+    router.push(normalizeUrl(url));
+  }, [organization.slug, params.projectId, router]);
 
-    const url = `/organizations/${organization.slug}/projects/${projectId}/getting-started/`;
+  useEffect(() => {
+    fetchData();
 
-    browserHistory.push(normalizeUrl(url));
-  }
+    window.scrollTo(0, 0);
 
-  render() {
-    const {params, organization} = this.props;
-    const {projectId} = params;
+    // redirect if platform is not known.
+    if (!params.platform || params.platform === 'other') {
+      redirectToNeutralDocs();
+    }
+  }, [fetchData, redirectToNeutralDocs, params.platform]);
 
-    const platform = platforms.find(p => p.id === params.platform);
+  const platform = platforms.find(p => p.id === params.platform);
 
-    if (!platform) {
-      return <NotFound />;
-    }
+  if (!platform) {
+    return <NotFound />;
+  }
 
-    const issueStreamLink = `/organizations/${organization.slug}/issues/`;
-    const performanceOverviewLink = `/organizations/${organization.slug}/performance/`;
-    const gettingStartedLink = `/organizations/${organization.slug}/projects/${projectId}/getting-started/`;
-    const platformLink = platform.link ?? undefined;
-    const showPerformancePrompt = performancePlatforms.includes(
-      platform.id as PlatformKey
-    );
-
-    const heartbeatFooter = !!organization?.features.includes(
-      'onboarding-heartbeat-footer'
-    );
-
-    return (
-      <Fragment>
-        <StyledPageHeader>
-          <h2>{t('Configure %(platform)s', {platform: platform.name})}</h2>
-          <ButtonBar gap={1}>
+  const issueStreamLink = `/organizations/${organization.slug}/issues/`;
+  const performanceOverviewLink = `/organizations/${organization.slug}/performance/`;
+  const gettingStartedLink = `/organizations/${organization.slug}/projects/${params.projectId}/getting-started/`;
+  const platformLink = platform.link ?? undefined;
+  const showPerformancePrompt = performancePlatforms.includes(platform.id as PlatformKey);
+
+  const heartbeatFooter = !!organization?.features.includes(
+    'onboarding-heartbeat-footer'
+  );
+
+  const isGettingStarted = window.location.href.indexOf('getting-started') > 0;
+
+  return (
+    <Fragment>
+      <StyledPageHeader>
+        <h2>{t('Configure %(platform)s', {platform: platform.name})}</h2>
+        <ButtonBar gap={1}>
+          <Button
+            icon={<IconChevron direction="left" size="sm" />}
+            size="sm"
+            to={gettingStartedLink}
+          >
+            {t('Back')}
+          </Button>
+          <Button size="sm" href={platformLink} external>
+            {t('Full Documentation')}
+          </Button>
+        </ButtonBar>
+      </StyledPageHeader>
+
+      <div>
+        <Alert type="info" showIcon>
+          {tct(
+            `
+           This is a quick getting started guide. For in-depth instructions
+           on integrating Sentry with [platform], view
+           [docLink:our complete documentation].`,
+            {
+              platform: platform.name,
+              docLink: <a href={platformLink} />,
+            }
+          )}
+        </Alert>
+
+        {loading ? (
+          <LoadingIndicator />
+        ) : error ? (
+          <LoadingError onRetry={fetchData} />
+        ) : (
+          <Fragment>
+            <SentryDocumentTitle
+              title={`${t('Configure')} ${platform.name}`}
+              projectSlug={params.projectId}
+            />
+            <DocumentationWrapper dangerouslySetInnerHTML={{__html: html}} />
+          </Fragment>
+        )}
+
+        {isGettingStarted && showPerformancePrompt && (
+          <Feature
+            features={['performance-view']}
+            hookName="feature-disabled:performance-new-project"
+          >
+            {({hasFeature}) => {
+              if (hasFeature) {
+                return null;
+              }
+              return (
+                <StyledAlert type="info" showIcon>
+                  {t(
+                    `Your selected platform supports performance, but your organization does not have performance enabled.`
+                  )}
+                </StyledAlert>
+              );
+            }}
+          </Feature>
+        )}
+
+        {isGettingStarted && heartbeatFooter ? (
+          <Footer
+            projectSlug={params.projectId}
+            projectId={project?.id}
+            route={route}
+            router={router}
+            location={location}
+          />
+        ) : (
+          <StyledButtonBar gap={1}>
             <Button
-              icon={<IconChevron direction="left" size="sm" />}
-              size="sm"
-              to={gettingStartedLink}
+              priority="primary"
+              busy={loadingProjects}
+              to={{
+                pathname: issueStreamLink,
+                query: project?.id,
+                hash: '#welcome',
+              }}
             >
-              {t('Back')}
+              {t('Take me to Issues')}
             </Button>
-            <Button size="sm" href={platformLink} external>
-              {t('Full Documentation')}
-            </Button>
-          </ButtonBar>
-        </StyledPageHeader>
-
-        <div>
-          <Alert type="info" showIcon>
-            {tct(
-              `
-             This is a quick getting started guide. For in-depth instructions
-             on integrating Sentry with [platform], view
-             [docLink:our complete documentation].`,
-              {
-                platform: platform.name,
-                docLink: <a href={platformLink} />,
-              }
-            )}
-          </Alert>
-
-          {this.state.loading ? (
-            <LoadingIndicator />
-          ) : this.state.error ? (
-            <LoadingError onRetry={this.fetchData} />
-          ) : (
-            <Fragment>
-              <SentryDocumentTitle
-                title={`${t('Configure')} ${platform.name}`}
-                projectSlug={projectId}
-              />
-              <DocumentationWrapper dangerouslySetInnerHTML={{__html: this.state.html}} />
-            </Fragment>
-          )}
-
-          {this.isGettingStarted && showPerformancePrompt && (
-            <Feature
-              features={['performance-view']}
-              hookName="feature-disabled:performance-new-project"
-            >
-              {({hasFeature}) => {
-                if (hasFeature) {
-                  return null;
-                }
-                return (
-                  <StyledAlert type="info" showIcon>
-                    {t(
-                      `Your selected platform supports performance, but your organization does not have performance enabled.`
-                    )}
-                  </StyledAlert>
-                );
+            <Button
+              busy={loadingProjects}
+              to={{
+                pathname: performanceOverviewLink,
+                query: project?.id,
               }}
-            </Feature>
-          )}
-
-          {this.isGettingStarted && heartbeatFooter ? (
-            <Footer
-              projectSlug={projectId}
-              route={this.props.route}
-              router={this.props.router}
-              location={this.props.location}
-            />
-          ) : (
-            <Projects
-              key={`${organization.slug}-${projectId}`}
-              orgId={organization.slug}
-              slugs={[projectId]}
-              passthroughPlaceholderProject={false}
             >
-              {({projects, initiallyLoaded, fetching, fetchError}) => {
-                const projectsLoading = !initiallyLoaded && fetching;
-                const projectFilter =
-                  !projectsLoading && !fetchError && projects.length
-                    ? {
-                        project: (projects[0] as Project).id,
-                      }
-                    : {};
-
-                return (
-                  <StyledButtonBar gap={1}>
-                    <Button
-                      priority="primary"
-                      busy={projectsLoading}
-                      to={{
-                        pathname: issueStreamLink,
-                        query: projectFilter,
-                        hash: '#welcome',
-                      }}
-                    >
-                      {t('Take me to Issues')}
-                    </Button>
-                    <Button
-                      busy={projectsLoading}
-                      to={{
-                        pathname: performanceOverviewLink,
-                        query: projectFilter,
-                      }}
-                    >
-                      {t('Take me to Performance')}
-                    </Button>
-                  </StyledButtonBar>
-                );
-              }}
-            </Projects>
-          )}
-        </div>
-      </Fragment>
-    );
-  }
+              {t('Take me to Performance')}
+            </Button>
+          </StyledButtonBar>
+        )}
+      </div>
+    </Fragment>
+  );
 }
 
 const DocumentationWrapper = styled('div')`
@@ -295,6 +258,3 @@ const StyledPageHeader = styled('div')`
 const StyledAlert = styled(Alert)`
   margin-top: ${space(2)};
 `;
-
-export {ProjectInstallPlatform};
-export default withApi(withOrganization(ProjectInstallPlatform));

+ 3 - 3
static/app/views/projectInstall/platformOrIntegration.tsx

@@ -2,10 +2,10 @@ import * as qs from 'query-string';
 
 import {platformToIntegrationMap} from 'sentry/utils/integrationUtil';
 
-import Platform from './platform';
+import {ProjectInstallPlatform} from './platform';
 import PlatformIntegrationSetup from './platformIntegrationSetup';
 
-type Props = React.ComponentProps<typeof Platform> &
+type Props = React.ComponentProps<typeof ProjectInstallPlatform> &
   Omit<React.ComponentProps<typeof PlatformIntegrationSetup>, 'integrationSlug'>;
 
 const PlatformOrIntegration = (props: Props) => {
@@ -16,7 +16,7 @@ const PlatformOrIntegration = (props: Props) => {
   if (integrationSlug && parsed.manual !== '1') {
     return <PlatformIntegrationSetup integrationSlug={integrationSlug} {...props} />;
   }
-  return <Platform {...props} />;
+  return <ProjectInstallPlatform {...props} />;
 };
 
 export default PlatformOrIntegration;