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

ref(debug-files): Add custom repositories new design (#27657)

Priscila Oliveira 3 лет назад
Родитель
Сommit
5b91a62850

+ 3 - 10
static/app/actionCreators/modal.tsx

@@ -6,15 +6,8 @@ import type {DashboardWidgetModalOptions} from 'app/components/modals/addDashboa
 import {InviteRow} from 'app/components/modals/inviteMembersModal/types';
 import type {ReprocessEventModalOptions} from 'app/components/modals/reprocessEventModal';
 import {AppStoreConnectContextProps} from 'app/components/projects/appStoreConnectContext';
-import {
-  DebugFileSource,
-  Group,
-  IssueOwnership,
-  Organization,
-  Project,
-  SentryApp,
-  Team,
-} from 'app/types';
+import {Group, IssueOwnership, Organization, Project, SentryApp, Team} from 'app/types';
+import {CustomRepoType} from 'app/types/debugFiles';
 import {Event} from 'app/types/event';
 
 type ModalProps = Required<React.ComponentProps<typeof GlobalModal>>;
@@ -202,7 +195,7 @@ export type SentryAppDetailsModalOptions = {
 };
 
 type DebugFileSourceModalOptions = {
-  sourceType: DebugFileSource;
+  sourceType: CustomRepoType;
   onSave: (data: Record<string, any>) => Promise<void>;
   appStoreConnectContext?: AppStoreConnectContextProps;
   onClose?: () => void;

+ 4 - 1
static/app/components/dropdownAutoComplete/row.tsx

@@ -81,7 +81,10 @@ const GroupLabel = styled('div')`
   padding: ${space(0.25)} ${space(1)};
 `;
 
-const AutoCompleteItem = styled('div')<{isHighlighted: boolean; itemSize?: ItemSize}>`
+const AutoCompleteItem = styled('div')<{
+  isHighlighted: boolean;
+  itemSize?: ItemSize;
+}>`
   /* needed for virtualized lists that do not fill parent height */
   /* e.g. breadcrumbs (org height > project, but want same fixed height for both) */
   display: flex;

+ 2 - 1
static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx

@@ -2,7 +2,7 @@ import {Fragment, useEffect, useState} from 'react';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
-import {addErrorMessage} from 'app/actionCreators/indicator';
+import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
 import {ModalRenderProps} from 'app/actionCreators/modal';
 import {Client} from 'app/api';
 import Alert from 'app/components/alert';
@@ -346,6 +346,7 @@ function AppStoreConnect({
       );
 
       setSessionContext(response.sessionContext);
+      addSuccessMessage(t("We've sent a SMS code to your phone"));
     } catch {
       addErrorMessage(t('An error occured while sending the SMS. Please try again'));
     }

+ 2 - 5
static/app/components/modals/debugFileCustomRepository/appStoreConnect/stepFour.tsx

@@ -77,11 +77,8 @@ function StepFour({
 export default StepFour;
 
 const StyledAlert = styled(Alert)`
-  display: grid;
-  grid-template-columns: max-content 1fr;
-  align-items: center;
-  span:nth-child(2) {
-    margin: 0;
+  div {
+    align-items: center;
   }
 `;
 

+ 4 - 7
static/app/components/modals/debugFileCustomRepository/index.tsx

@@ -7,7 +7,7 @@ import {ModalRenderProps} from 'app/actionCreators/modal';
 import {AppStoreConnectContextProps} from 'app/components/projects/appStoreConnectContext';
 import {getDebugSourceName} from 'app/data/debugFileSources';
 import {tct} from 'app/locale';
-import {DebugFileSource} from 'app/types';
+import {CustomRepoType} from 'app/types/debugFiles';
 import FieldFromConfig from 'app/views/settings/components/forms/fieldFromConfig';
 import Form from 'app/views/settings/components/forms/form';
 
@@ -31,7 +31,7 @@ type Props = WithRouterProps<RouteParams, {}> & {
   /**
    * Type of this source.
    */
-  sourceType: DebugFileSource;
+  sourceType: CustomRepoType;
 
   appStoreConnectContext?: AppStoreConnectContextProps;
   /**
@@ -56,16 +56,13 @@ function DebugFileCustomRepository({
     onSave({...data, type: sourceType}).then(() => {
       closeModal();
 
-      if (
-        sourceType === 'appStoreConnect' &&
-        appStoreConnectContext?.updateAlertMessage
-      ) {
+      if (sourceType === CustomRepoType.APP_STORE_CONNECT) {
         window.location.reload();
       }
     });
   }
 
-  if (sourceType === 'appStoreConnect') {
+  if (sourceType === CustomRepoType.APP_STORE_CONNECT) {
     return (
       <AppStoreConnect
         Header={Header}

+ 2 - 2
static/app/components/modals/debugFileCustomRepository/utils.tsx

@@ -5,7 +5,7 @@ import {
   DEBUG_SOURCE_LAYOUTS,
 } from 'app/data/debugFileSources';
 import {t, tct} from 'app/locale';
-import {DebugFileSource} from 'app/types';
+import {CustomRepoType} from 'app/types/debugFiles';
 import {Field} from 'app/views/settings/components/forms/type';
 
 function objectToChoices(obj: Record<string, string>): [key: string, value: string][] {
@@ -170,7 +170,7 @@ const gcsFields: FieldMap = {
   },
 };
 
-export function getFormFields(type: DebugFileSource) {
+export function getFormFields(type: CustomRepoType) {
   switch (type) {
     case 'http':
       return [

+ 65 - 0
static/app/types/debugFiles.tsx

@@ -33,6 +33,14 @@ export type DebugFile = {
   data?: {type: DebugFileType; features: DebugFileFeature[]};
 };
 
+// Custom Repositories
+export enum CustomRepoType {
+  HTTP = 'http',
+  S3 = 's3',
+  GCS = 'gcs',
+  APP_STORE_CONNECT = 'appStoreConnect',
+}
+
 export type AppStoreConnectValidationData = {
   id: string;
   appstoreCredentialsValid: boolean;
@@ -53,5 +61,62 @@ export type AppStoreConnectValidationData = {
    * be found.
    */
   latestBuildVersion: string | null;
+  lastCheckedBuilds: string | null;
   updateAlertMessage?: string;
 };
+
+type CustomRepoAppStoreConnect = {
+  type: CustomRepoType.APP_STORE_CONNECT;
+  appId: string;
+  appName: string;
+  appconnectIssuer: string;
+  appconnectKey: string;
+  appconnectPrivateKey: string;
+  bundleId: string;
+  id: string;
+  itunesCreated: string;
+  itunesPassword: string;
+  itunesPersonId: string;
+  itunesSession: string;
+  itunesUser: string;
+  name: string;
+  orgId: number;
+  orgName: string;
+  details?: AppStoreConnectValidationData;
+};
+
+type CustomRepoHttp = {
+  type: CustomRepoType.HTTP;
+  id: string;
+  layout: {casing: string; type: string};
+  name: string;
+  url: string;
+};
+
+type CustomRepoS3 = {
+  type: CustomRepoType.S3;
+  access_key: string;
+  bucket: string;
+  id: string;
+  layout: {type: string; casing: string};
+  name: string;
+  region: string;
+  secret_key: string;
+};
+
+type CustomRepoGCS = {
+  type: CustomRepoType.GCS;
+  bucket: string;
+  client_email: string;
+  id: string;
+  layout: {type: string; casing: string};
+  name: string;
+  prefix: string;
+  private_key: string;
+};
+
+export type CustomRepo =
+  | CustomRepoAppStoreConnect
+  | CustomRepoHttp
+  | CustomRepoS3
+  | CustomRepoGCS;

+ 0 - 5
static/app/types/index.tsx

@@ -2086,11 +2086,6 @@ export type ServerlessFunction = {
   enabled: boolean;
 };
 
-/**
- * File storage service options for debug files
- */
-export type DebugFileSource = 'http' | 's3' | 'gcs' | 'appStoreConnect';
-
 /**
  * Base type for series   style API response
  */

+ 34 - 21
static/app/views/settings/projectDebugFiles/externalSources/buildInSymbolSources.tsx → static/app/views/settings/projectDebugFiles/externalSources/builtInRepositories.tsx

@@ -1,6 +1,7 @@
 import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
 import ProjectActions from 'app/actions/projectActions';
 import {Client} from 'app/api';
+import {Panel, PanelBody, PanelHeader} from 'app/components/panels';
 import {t} from 'app/locale';
 import {Choices, Organization, Project} from 'app/types';
 import {BuiltinSymbolSource} from 'app/types/debugFiles';
@@ -14,7 +15,7 @@ type Props = {
   builtinSymbolSources: string[];
 };
 
-function BuildInSymbolSources({
+function BuiltInRepositories({
   api,
   organization,
   builtinSymbolSourceOptions,
@@ -22,6 +23,12 @@ function BuildInSymbolSources({
   projectSlug,
 }: Props) {
   function getRequestMessages(builtinSymbolSourcesQuantity: number) {
+    if (builtinSymbolSourcesQuantity === 0) {
+      return {
+        errorMessage: t('This field requires at least one built-in repository'),
+      };
+    }
+
     if (builtinSymbolSourcesQuantity > builtinSymbolSources.length) {
       return {
         successMessage: t('Successfully added built-in repository'),
@@ -35,8 +42,8 @@ function BuildInSymbolSources({
     };
   }
 
-  async function handleChange(value: string[]) {
-    const {successMessage, errorMessage} = getRequestMessages(value.length);
+  async function handleChange(value: null | string[]) {
+    const {successMessage, errorMessage} = getRequestMessages((value ?? []).length);
 
     try {
       const updatedProjectDetails: Project = await api.requestPromise(
@@ -57,24 +64,30 @@ function BuildInSymbolSources({
   }
 
   return (
-    <SelectField
-      name="builtinSymbolSources"
-      label={t('Built-in Repositories')}
-      help={t(
-        'Configures which built-in repositories Sentry should use to resolve debug files.'
-      )}
-      value={builtinSymbolSources}
-      onChange={handleChange}
-      choices={
-        builtinSymbolSourceOptions
-          ?.filter(source => !source.hidden)
-          .map(source => [source.sentry_key, t(source.name)]) as Choices
-      }
-      getValue={value => (value === null ? [] : value)}
-      flexibleControlStateSize
-      multiple
-    />
+    <Panel>
+      <PanelHeader>{t('Built-in Repositories')}</PanelHeader>
+      <PanelBody>
+        <SelectField
+          name="builtinSymbolSources"
+          label={t('Built-in Repositories')}
+          help={t(
+            'Configures which built-in repositories Sentry should use to resolve debug files.'
+          )}
+          placeholder={t('Select built-in repository')}
+          value={builtinSymbolSources}
+          onChange={handleChange}
+          choices={
+            builtinSymbolSourceOptions
+              ?.filter(source => !source.hidden)
+              .map(source => [source.sentry_key, t(source.name)]) as Choices
+          }
+          getValue={value => (value === null ? [] : value)}
+          flexibleControlStateSize
+          multiple
+        />
+      </PanelBody>
+    </Panel>
   );
 }
 
-export default BuildInSymbolSources;
+export default BuiltInRepositories;

+ 131 - 0
static/app/views/settings/projectDebugFiles/externalSources/customRepositories/actions.tsx

@@ -0,0 +1,131 @@
+import {Fragment} from 'react';
+import styled from '@emotion/styled';
+
+import ActionButton from 'app/components/actions/button';
+import MenuItemActionLink from 'app/components/actions/menuItemActionLink';
+import Button from 'app/components/button';
+import ButtonBar from 'app/components/buttonBar';
+import ConfirmDelete from 'app/components/confirmDelete';
+import DropdownButton from 'app/components/dropdownButton';
+import DropdownLink from 'app/components/dropdownLink';
+import {IconEllipsis} from 'app/icons/iconEllipsis';
+import {t} from 'app/locale';
+import space from 'app/styles/space';
+import TextBlock from 'app/views/settings/components/text/textBlock';
+
+type Props = {
+  repositoryName: string;
+  isDetailsExpanded: boolean;
+  isDetailsDisabled: boolean;
+  onToggleDetails: () => void;
+  onEdit: () => void;
+  onDelete: () => void;
+  showDetails: boolean;
+};
+
+function Actions({
+  repositoryName,
+  isDetailsExpanded,
+  isDetailsDisabled,
+  onToggleDetails,
+  onEdit,
+  onDelete,
+  showDetails,
+}: Props) {
+  function renderConfirmDelete(element: React.ReactElement) {
+    return (
+      <ConfirmDelete
+        confirmText={t('Delete Repository')}
+        message={
+          <Fragment>
+            <TextBlock>
+              <strong>
+                {t('Removing this repository applies instantly to new events.')}
+              </strong>
+            </TextBlock>
+            <TextBlock>
+              {t(
+                'Debug files from this repository will not be used to symbolicate future events. This may create new issues and alert members in your organization.'
+              )}
+            </TextBlock>
+          </Fragment>
+        }
+        confirmInput={repositoryName}
+        priority="danger"
+        onConfirm={onDelete}
+      >
+        {element}
+      </ConfirmDelete>
+    );
+  }
+  return (
+    <StyledButtonBar gap={1}>
+      {showDetails && (
+        <StyledDropdownButton
+          isOpen={isDetailsExpanded}
+          size="small"
+          onClick={onToggleDetails}
+          hideBottomBorder={false}
+          disabled={isDetailsDisabled}
+        >
+          {t('Details')}
+        </StyledDropdownButton>
+      )}
+      <StyledButton onClick={onEdit} size="small">
+        {t('Configure')}
+      </StyledButton>
+      {renderConfirmDelete(<StyledButton size="small">{t('Delete')}</StyledButton>)}
+      <DropDownWrapper>
+        <DropdownLink
+          caret={false}
+          customTitle={
+            <StyledActionButton label={t('Actions')} icon={<IconEllipsis />} />
+          }
+          anchorRight
+        >
+          <MenuItemActionLink title={t('Configure')} onClick={onEdit}>
+            {t('Configure')}
+          </MenuItemActionLink>
+          {renderConfirmDelete(
+            <MenuItemActionLink title={t('Delete')}>{t('Delete')}</MenuItemActionLink>
+          )}
+        </DropdownLink>
+      </DropDownWrapper>
+    </StyledButtonBar>
+  );
+}
+
+export default Actions;
+
+const StyledActionButton = styled(ActionButton)`
+  height: 32px;
+`;
+
+const StyledDropdownButton = styled(DropdownButton)`
+  border-bottom-right-radius: ${p => p.theme.borderRadius};
+  border-bottom-left-radius: ${p => p.theme.borderRadius};
+`;
+
+const StyledButtonBar = styled(ButtonBar)`
+  @media (min-width: ${p => p.theme.breakpoints[0]}) {
+    grid-row: 1 / 3;
+  }
+
+  @media (max-width: ${p => p.theme.breakpoints[0]}) {
+    grid-auto-flow: row;
+    grid-gap: ${space(1)};
+    margin-top: ${space(0.5)};
+  }
+`;
+
+const StyledButton = styled(Button)`
+  @media (min-width: ${p => p.theme.breakpoints[0]}) {
+    display: none;
+  }
+`;
+
+const DropDownWrapper = styled('div')`
+  @media (max-width: ${p => p.theme.breakpoints[0]}) {
+    display: none;
+  }
+`;

Некоторые файлы не были показаны из-за большого количества измененных файлов