Просмотр исходного кода

fix(project-creation): Fix self hosted not seeing setup docs (#47078)

Priscila Oliveira 1 год назад
Родитель
Сommit
5989c5315a

+ 7 - 0
static/app/data/platformCategories.tsx

@@ -347,3 +347,10 @@ export type PlatformKey =
   | 'other';
 
 export default categoryList;
+
+export type Platform = {
+  key: PlatformKey;
+  id?: string;
+  link?: string | null;
+  name?: string;
+};

+ 2 - 2
static/app/types/hooks.tsx

@@ -1,5 +1,4 @@
 import type {Route, RouteComponentProps, RouteContextInterface} from 'react-router';
-import {InjectedRouter} from 'react-router';
 import {Location} from 'history';
 
 import type {ChildrenRenderFn} from 'sentry/components/acl/feature';
@@ -8,6 +7,7 @@ import {ButtonProps} from 'sentry/components/button';
 import type DateRange from 'sentry/components/organizations/timeRangeSelector/dateRange';
 import type SelectorItems from 'sentry/components/organizations/timeRangeSelector/selectorItems';
 import type SidebarItem from 'sentry/components/sidebar/sidebarItem';
+import {Platform} from 'sentry/data/platformCategories';
 import type {Group} from 'sentry/types';
 import {UseExperiment} from 'sentry/utils/useExperiment';
 import {UsageStatsOrganizationProps} from 'sentry/views/organizationStats/usageStatsOrg';
@@ -93,8 +93,8 @@ type ProfilingBetaAlertBannerProps = {
 type SetUpSdkDocProps = {
   location: Location;
   organization: Organization;
+  platform: Platform;
   project: Project;
-  router: InjectedRouter;
 };
 
 type FirstPartyIntegrationAlertProps = {

+ 5 - 5
static/app/views/onboarding/setupDocs.tsx

@@ -79,12 +79,12 @@ const MissingExampleWarning = ({
 export function ProjectDocsReact({
   organization,
   location,
-  project,
+  projectSlug,
   newOrg,
 }: {
   location: Location;
   organization: Organization;
-  project: Project;
+  projectSlug: Project['slug'];
   newOrg?: boolean;
 }) {
   const {
@@ -119,10 +119,10 @@ export function ProjectDocsReact({
   }, [productSelectionLogExperiment, newOrg]);
 
   const {data, isLoading, isError, refetch} = useApiQuery<PlatformDoc>(
-    [`/projects/${organization.slug}/${project.slug}/docs/${loadPlatform}/`],
+    [`/projects/${organization.slug}/${projectSlug}/docs/${loadPlatform}/`],
     {
       staleTime: Infinity,
-      enabled: !!project.slug && !!organization.slug && !!loadPlatform,
+      enabled: !!projectSlug && !!organization.slug && !!loadPlatform,
     }
   );
 
@@ -462,7 +462,7 @@ function SetupDocs({search, route, router, location, ...props}: Props) {
           ) : showReactOnboarding ? (
             <ProjectDocsReact
               organization={organization}
-              project={project}
+              projectSlug={project.slug}
               location={location}
               newOrg
             />

+ 43 - 1
static/app/views/projectInstall/platform.spec.tsx

@@ -1,5 +1,5 @@
 import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen} from 'sentry-test/reactTestingLibrary';
+import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
 
 import {ProjectInstallPlatform} from 'sentry/views/projectInstall/platform';
 
@@ -42,4 +42,46 @@ describe('ProjectInstallPlatform', function () {
 
     expect(await screen.findByText('Page Not Found')).toBeInTheDocument();
   });
+
+  it('should redirect to if no matching platform', async function () {
+    const routeParams = {
+      projectId: TestStubs.Project().slug,
+      platform: 'other',
+    };
+
+    const {organization, router, route, project, routerContext} = initializeOrg({
+      ...initializeOrg(),
+      router: {
+        location: {
+          query: {},
+        },
+        params: routeParams,
+      },
+    });
+
+    MockApiClient.addMockResponse({
+      method: 'GET',
+      url: '/organizations/org-slug/projects/',
+      body: [project],
+    });
+
+    render(
+      <ProjectInstallPlatform
+        router={router}
+        route={route}
+        location={router.location}
+        routeParams={routeParams}
+        routes={router.routes}
+        params={routeParams}
+      />,
+      {
+        organization,
+        context: routerContext,
+      }
+    );
+
+    await waitFor(() => {
+      expect(router.push).toHaveBeenCalledTimes(1);
+    });
+  });
 });

+ 124 - 11
static/app/views/projectInstall/platform.tsx

@@ -1,24 +1,35 @@
-import {Fragment} from 'react';
+import {Fragment, useCallback, useEffect, useState} from 'react';
 import {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
 
+import {loadDocs} from 'sentry/actionCreators/projects';
 import Feature from 'sentry/components/acl/feature';
 import {Alert} from 'sentry/components/alert';
 import {Button} from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
 import NotFound from 'sentry/components/errors/notFound';
 import HookOrDefault from 'sentry/components/hookOrDefault';
+import ExternalLink from 'sentry/components/links/externalLink';
+import LoadingError from 'sentry/components/loadingError';
+import LoadingIndicator from 'sentry/components/loadingIndicator';
+import {DocumentationWrapper} from 'sentry/components/onboarding/documentationWrapper';
 import {Footer} from 'sentry/components/onboarding/footer';
+import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
 import {
   performance as performancePlatforms,
+  Platform,
   PlatformKey,
 } from 'sentry/data/platformCategories';
 import platforms from 'sentry/data/platforms';
 import {IconChevron} from 'sentry/icons';
-import {t} from 'sentry/locale';
+import {t, tct} from 'sentry/locale';
+import ConfigStore from 'sentry/stores/configStore';
 import {space} from 'sentry/styles/space';
+import {Organization, Project} from 'sentry/types';
+import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
 import useProjects from 'sentry/utils/useProjects';
+import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 
 // in this case, the default is rendered inside the hook
 const SetUpSdkDoc = HookOrDefault({
@@ -27,8 +38,77 @@ const SetUpSdkDoc = HookOrDefault({
 
 type Props = RouteComponentProps<{platform: string; projectId: string}, {}>;
 
+export function SetUpGeneralSdkDoc({
+  organization,
+  projectSlug,
+  platform,
+}: {
+  organization: Organization;
+  platform: Platform;
+  projectSlug: Project['slug'];
+}) {
+  const api = useApi();
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(false);
+  const [html, setHtml] = useState('');
+
+  const fetchDocs = useCallback(async () => {
+    setLoading(true);
+
+    try {
+      const {html: reponse} = await loadDocs({
+        api,
+        orgSlug: organization.slug,
+        projectSlug,
+        platform: platform.key as PlatformKey,
+      });
+      setHtml(reponse);
+      window.scrollTo(0, 0);
+    } catch (err) {
+      setError(err);
+    }
+
+    setLoading(false);
+  }, [api, organization.slug, projectSlug, platform.key]);
+
+  useEffect(() => {
+    fetchDocs();
+  }, [fetchDocs]);
+
+  return (
+    <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: <ExternalLink href={platform.link ?? undefined} />,
+          }
+        )}
+      </Alert>
+      {loading ? (
+        <LoadingIndicator />
+      ) : error ? (
+        <LoadingError onRetry={fetchDocs} />
+      ) : (
+        <Fragment>
+          <SentryDocumentTitle
+            title={`${t('Configure')} ${platform.name}`}
+            projectSlug={projectSlug}
+          />
+          <DocumentationWrapper dangerouslySetInnerHTML={{__html: html}} />
+        </Fragment>
+      )}
+    </div>
+  );
+}
+
 export function ProjectInstallPlatform({location, params, route, router}: Props) {
   const organization = useOrganization();
+  const isSelfHosted = ConfigStore.get('isSelfHosted');
 
   const {projects, initiallyLoaded} = useProjects({
     slugs: [params.projectId],
@@ -42,9 +122,35 @@ export function ProjectInstallPlatform({location, params, route, router}: Props)
     'onboarding-heartbeat-footer'
   );
 
-  const platform = platforms.find(p => p.id === params.platform);
+  const currentPlatform = params.platform ?? 'other';
+  const platformIntegration = platforms.find(p => p.id === currentPlatform);
+  const platform: Platform = {
+    key: currentPlatform as PlatformKey,
+    id: platformIntegration?.id,
+    name: platformIntegration?.name,
+    link: platformIntegration?.link,
+  };
 
-  if (!platform) {
+  const redirectToNeutralDocs = useCallback(() => {
+    if (!project.slug) {
+      return;
+    }
+
+    router.push(
+      normalizeUrl(
+        `/organizations/${organization.slug}/projects/${project.slug}/getting-started/`
+      )
+    );
+  }, [organization.slug, project?.slug, router]);
+
+  useEffect(() => {
+    // redirect if platform is not known.
+    if (!platform.key || platform.key === 'other') {
+      redirectToNeutralDocs();
+    }
+  }, [platform.key, redirectToNeutralDocs]);
+
+  if (!platform.id) {
     return <NotFound />;
   }
 
@@ -71,14 +177,21 @@ export function ProjectInstallPlatform({location, params, route, router}: Props)
           </Button>
         </ButtonBar>
       </StyledPageHeader>
-
       <div>
-        <SetUpSdkDoc
-          organization={organization}
-          project={{...project, platform: params.platform as PlatformKey}}
-          location={location}
-          router={router}
-        />
+        {isSelfHosted ? (
+          <SetUpGeneralSdkDoc
+            organization={organization}
+            projectSlug={project.slug}
+            platform={platform}
+          />
+        ) : (
+          <SetUpSdkDoc
+            organization={organization}
+            project={project}
+            location={location}
+            platform={platform}
+          />
+        )}
 
         {isGettingStarted && showPerformancePrompt && (
           <Feature