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

feat(onboarding): Add code tabs to platform spring boot (#56318)

* Add code tabs to platform spring boot

Requires https://github.com/getsentry/sentry/pull/56317
Closes https://github.com/getsentry/sentry/issues/56238
Closes https://github.com/getsentry/sentry/issues/50226
ArthurKnaus 1 год назад
Родитель
Сommit
dc8f169b64

+ 4 - 4
static/app/components/events/rrwebReplayer/baseRRWebReplayer.tsx

@@ -1,4 +1,4 @@
-import {useEffect, useRef} from 'react';
+import {useCallback, useEffect, useRef} from 'react';
 import styled from '@emotion/styled';
 import RRWebPlayer from '@sentry-internal/rrweb-player';
 
@@ -14,7 +14,7 @@ interface Props {
 function BaseRRWebReplayerComponent({events, className}: Props) {
   const playerEl = useRef<HTMLDivElement>(null);
 
-  const initPlayer = () => {
+  const initPlayer = useCallback(() => {
     if (events === undefined) {
       return;
     }
@@ -28,9 +28,9 @@ function BaseRRWebReplayerComponent({events, className}: Props) {
       target: playerEl.current,
       props: {events, autoPlay: false},
     });
-  };
+  }, [events]);
 
-  useEffect(() => void initPlayer(), [events]);
+  useEffect(() => void initPlayer(), [initPlayer]);
 
   return <div ref={playerEl} className={className} />;
 }

+ 86 - 17
static/app/components/onboarding/gettingStartedDoc/step.tsx

@@ -1,4 +1,4 @@
-import {Fragment} from 'react';
+import {Fragment, useState} from 'react';
 import styled from '@emotion/styled';
 import beautify from 'js-beautify';
 
@@ -18,6 +18,65 @@ export const StepTitle = {
   [StepType.VERIFY]: t('Verify'),
 };
 
+interface CodeSnippetTab {
+  code: string;
+  label: string;
+  language: string;
+  value: string;
+}
+
+interface TabbedCodeSnippetProps {
+  /**
+   * An array of tabs to be displayed
+   */
+  tabs: CodeSnippetTab[];
+  /**
+   * A callback to be invoked when the configuration is copied to the clipboard
+   */
+  onCopy?: () => void;
+  /**
+   * A callback to be invoked when the configuration is selected and copied to the clipboard
+   */
+  onSelectAndCopy?: () => void;
+  /**
+   * Whether or not the configuration or parts of it are currently being loaded
+   */
+  partialLoading?: boolean;
+}
+
+function TabbedCodeSnippet({
+  tabs,
+  onCopy,
+  onSelectAndCopy,
+  partialLoading,
+}: TabbedCodeSnippetProps) {
+  const [selectedTabValue, setSelectedTabValue] = useState(tabs[0].value);
+  const selectedTab = tabs.find(tab => tab.value === selectedTabValue) ?? tabs[0];
+  const {code, language} = selectedTab;
+
+  return (
+    <CodeSnippet
+      dark
+      language={language}
+      onCopy={onCopy}
+      onSelectAndCopy={onSelectAndCopy}
+      hideCopyButton={partialLoading}
+      disableUserSelection={partialLoading}
+      tabs={tabs}
+      selectedTab={selectedTabValue}
+      onTabClick={value => setSelectedTabValue(value)}
+    >
+      {language === 'javascript'
+        ? beautify.js(code, {
+            indent_size: 2,
+            e4x: true,
+            brace_style: 'preserve-inline',
+          })
+        : code.trim()}
+    </CodeSnippet>
+  );
+}
+
 type ConfigurationType = {
   /**
    * Additional information to be displayed below the code snippet
@@ -26,7 +85,7 @@ type ConfigurationType = {
   /**
    * The code snippet to display
    */
-  code?: string;
+  code?: string | CodeSnippetTab[];
   /**
    * Nested configurations provide a convenient way to accommodate diverse layout styles, like the Spring Boot configuration.
    */
@@ -88,23 +147,33 @@ function getConfiguration({
   return (
     <Configuration>
       {description && <Description>{description}</Description>}
-      {language && code && (
-        <CodeSnippet
-          dark
-          language={language}
+      {Array.isArray(code) ? (
+        <TabbedCodeSnippet
+          tabs={code}
           onCopy={onCopy}
           onSelectAndCopy={onSelectAndCopy}
-          hideCopyButton={partialLoading}
-          disableUserSelection={partialLoading}
-        >
-          {language === 'javascript'
-            ? beautify.js(code, {
-                indent_size: 2,
-                e4x: true,
-                brace_style: 'preserve-inline',
-              })
-            : code.trim()}
-        </CodeSnippet>
+          partialLoading={partialLoading}
+        />
+      ) : (
+        language &&
+        code && (
+          <CodeSnippet
+            dark
+            language={language}
+            onCopy={onCopy}
+            onSelectAndCopy={onSelectAndCopy}
+            hideCopyButton={partialLoading}
+            disableUserSelection={partialLoading}
+          >
+            {language === 'javascript'
+              ? beautify.js(code, {
+                  indent_size: 2,
+                  e4x: true,
+                  brace_style: 'preserve-inline',
+                })
+              : code.trim()}
+          </CodeSnippet>
+        )
       )}
       {additionalInfo && <AdditionalInfo>{additionalInfo}</AdditionalInfo>}
     </Configuration>

+ 48 - 42
static/app/gettingStartedDocs/java/spring-boot.tsx

@@ -265,35 +265,37 @@ sentry {
     ),
     configurations: [
       {
-        language: 'properties',
-        description: (
-          <p>{tct('Modify [code:src/main/application.properties]:', {code: <code />})}</p>
-        ),
-        code: `
+        code: [
+          {
+            label: 'Properties',
+            value: 'properties',
+            language: 'properties',
+            code: `
 sentry.dsn=${dsn}${
-          hasPerformance
-            ? `
+              hasPerformance
+                ? `
 # Set traces-sample-rate to 1.0 to capture 100% of transactions for performance monitoring.
 # We recommend adjusting this value in production.
 sentry.traces-sample-rate=1.0`
-            : ''
-        }`,
-      },
-      {
-        language: 'properties',
-        description: (
-          <p>{tct('Or, modify [code:src/main/application.yml]:', {code: <code />})}</p>
-        ),
-        code: `
+                : ''
+            }`,
+          },
+          {
+            label: 'YAML',
+            value: 'yaml',
+            language: 'properties',
+            code: `
 sentry:
-  dsn:${dsn}${
+  dsn: ${dsn}${
     hasPerformance
       ? `
   # Set traces-sample-rate to 1.0 to capture 100% of transactions for performance monitoring.
   # We recommend adjusting this value in production.
-  sentry.traces-sample-rate=1.0`
+  sentry.traces-sample-rate: 1.0`
       : ''
   }`,
+          },
+        ],
       },
     ],
   },
@@ -304,32 +306,36 @@ sentry:
     ),
     configurations: [
       {
-        description: <h5>Java</h5>,
-        language: 'javascript', // TODO: This shouldn't be javascript but because of better formatting we use it for now
-        code: `
-        import java.lang.Exception;
-        import io.sentry.Sentry;
+        code: [
+          {
+            label: 'Java',
+            value: 'java',
+            language: 'javascript', // TODO: This shouldn't be javascript but because of better formatting we use it for now
+            code: `
+import java.lang.Exception;
+import io.sentry.Sentry;
 
-        try {
-          throw new Exception("This is a test.");
-        } catch (Exception e) {
-          Sentry.captureException(e);
-        }
-        `,
-      },
-      {
-        description: <h5>Kotlin</h5>,
-        language: 'javascript', // TODO: This shouldn't be javascript but because of better formatting we use it for now
-        code: `
-        import java.lang.Exception
-        import io.sentry.Sentry
+try {
+  throw new Exception("This is a test.");
+} catch (Exception e) {
+  Sentry.captureException(e);
+}`,
+          },
+          {
+            label: 'Kotlin',
+            value: 'kotlin',
+            language: 'javascript', // TODO: This shouldn't be javascript but because of better formatting we use it for now
+            code: `
+import java.lang.Exception
+import io.sentry.Sentry
 
-        try {
-          throw Exception("This is a test.")
-        } catch (e: Exception) {
-          Sentry.captureException(e)
-        }
-        `,
+try {
+  throw Exception("This is a test.")
+} catch (e: Exception) {
+  Sentry.captureException(e)
+}`,
+          },
+        ],
       },
     ],
     additionalInfo: (

+ 76 - 65
static/app/views/settings/projectDebugFiles/sources/customRepositories/index.tsx

@@ -1,4 +1,4 @@
-import {useContext, useEffect} from 'react';
+import {useCallback, useContext, useEffect} from 'react';
 import {InjectedRouter} from 'react-router';
 import styled from '@emotion/styled';
 import {Location} from 'history';
@@ -51,16 +51,74 @@ function CustomRepositories({
 }: Props) {
   const appStoreConnectContext = useContext(AppStoreConnectContext);
 
-  useEffect(() => {
-    openDebugFileSourceDialog();
-  }, [location.query, appStoreConnectContext]);
-
   const orgSlug = organization.slug;
   const appStoreConnectSourcesQuantity = repositories.filter(
     repository => repository.type === CustomRepoType.APP_STORE_CONNECT
   ).length;
 
-  function openDebugFileSourceDialog() {
+  const persistData = useCallback(
+    ({
+      updatedItems,
+      updatedItem,
+      index,
+      refresh,
+    }: {
+      index?: number;
+      refresh?: boolean;
+      updatedItem?: CustomRepo;
+      updatedItems?: CustomRepo[];
+    }) => {
+      let items = updatedItems ?? [];
+
+      if (updatedItem && defined(index)) {
+        items = [...repositories];
+        items.splice(index, 1, updatedItem);
+      }
+
+      const {successMessage, errorMessage} = getRequestMessages(
+        items.length,
+        repositories.length
+      );
+
+      const symbolSources = JSON.stringify(items.map(expandKeys));
+
+      const promise: Promise<any> = api.requestPromise(
+        `/projects/${orgSlug}/${project.slug}/`,
+        {
+          method: 'PUT',
+          data: {symbolSources},
+        }
+      );
+
+      promise.catch(() => {
+        addErrorMessage(errorMessage);
+      });
+
+      promise.then(result => {
+        ProjectsStore.onUpdateSuccess(result);
+        addSuccessMessage(successMessage);
+
+        if (refresh) {
+          window.location.reload();
+        }
+      });
+
+      return promise;
+    },
+    [api, orgSlug, project.slug, repositories]
+  );
+
+  const handleCloseModal = useCallback(() => {
+    router.push({
+      ...location,
+      query: {
+        ...location.query,
+        customRepository: undefined,
+      },
+    });
+  }, [location, router]);
+
+  const openDebugFileSourceDialog = useCallback(() => {
     const {customRepository} = location.query;
 
     if (!customRepository) {
@@ -87,66 +145,19 @@ function CustomRepositories({
         persistData({updatedItem: updatedItem as CustomRepo, index: itemIndex}),
       onClose: handleCloseModal,
     });
-  }
-
-  function persistData({
-    updatedItems,
-    updatedItem,
-    index,
-    refresh,
-  }: {
-    index?: number;
-    refresh?: boolean;
-    updatedItem?: CustomRepo;
-    updatedItems?: CustomRepo[];
-  }) {
-    let items = updatedItems ?? [];
-
-    if (updatedItem && defined(index)) {
-      items = [...repositories];
-      items.splice(index, 1, updatedItem);
-    }
-
-    const {successMessage, errorMessage} = getRequestMessages(
-      items.length,
-      repositories.length
-    );
-
-    const symbolSources = JSON.stringify(items.map(expandKeys));
-
-    const promise: Promise<any> = api.requestPromise(
-      `/projects/${orgSlug}/${project.slug}/`,
-      {
-        method: 'PUT',
-        data: {symbolSources},
-      }
-    );
-
-    promise.catch(() => {
-      addErrorMessage(errorMessage);
-    });
-
-    promise.then(result => {
-      ProjectsStore.onUpdateSuccess(result);
-      addSuccessMessage(successMessage);
-
-      if (refresh) {
-        window.location.reload();
-      }
-    });
-
-    return promise;
-  }
+  }, [
+    appStoreConnectContext,
+    appStoreConnectSourcesQuantity,
+    handleCloseModal,
+    location.query,
+    organization,
+    persistData,
+    repositories,
+  ]);
 
-  function handleCloseModal() {
-    router.push({
-      ...location,
-      query: {
-        ...location.query,
-        customRepository: undefined,
-      },
-    });
-  }
+  useEffect(() => {
+    openDebugFileSourceDialog();
+  }, [location.query, appStoreConnectContext, openDebugFileSourceDialog]);
 
   function handleAddRepository(repoType: CustomRepoType) {
     openDebugFileSourceModal({