Browse Source

ref(appconnect): Stronger typing and type fixes (#26621)

* ref(appconnect): Stronger typing and type fixes

This makes the appstore connect utilities entirely typed.
Also fixes up the types of some IDs which are numbers.

* Work around mypy bugs

* - Also type-check the api endpoints
- Ids are opaque strings
- Make sure to store bundleId as well, we may need it

* add missing __init__.py

This isn't strictly needed, but without it mypy is unhappy.  who
knows.

* add bundleId to the type, otherwise linting fails

it seems the code works just fine though?  fun

* style(lint): Auto commit lint changes

* please, javascrypt, typescript, whatever

why do you work just fine but fail to run tests/lints?

* please typescrypt

* Actually be somewhat pythonic

* require new bundleId in the schema

* Fixed undefined item index, added 'warning' in the omit of the symbolSources handleChange func, added extra handleCloseImageDetailsModal func as the URL was not getting updated when revalidating the iTunes session

Co-authored-by: sentry-internal-tools[bot] <66042841+sentry-internal-tools[bot]@users.noreply.github.com>
Co-authored-by: Priscila Oliveira <priscila.oliveira@sentry.io>
Floris Bruynooghe 3 years ago
parent
commit
851787d27d

+ 3 - 1
mypy.ini

@@ -6,6 +6,7 @@ files = src/sentry/api/bases/external_actor.py,
         src/sentry/api/endpoints/external_user.py,
         src/sentry/api/endpoints/external_user_details.py,
         src/sentry/api/endpoints/organization_events_trace.py,
+        src/sentry/api/endpoints/project_app_store_connect_credentials.py,
         src/sentry/api/endpoints/project_codeowners.py,
         src/sentry/api/serializers/base.py,
         src/sentry/api/serializers/models/external_actor.py,
@@ -20,12 +21,13 @@ files = src/sentry/api/bases/external_actor.py,
         src/sentry/integrations/slack/message_builder/**/*.py,
         src/sentry/integrations/slack/requests/*.py,
         src/sentry/integrations/slack/util/*.py,
+        src/sentry/killswitches.py,
         src/sentry/notifications/**/*.py,
         src/sentry/snuba/outcomes.py,
         src/sentry/snuba/query_subscription_consumer.py,
+        src/sentry/utils/appleconnect/,
         src/sentry/utils/avatar.py,
         src/sentry/utils/codecs.py,
-        src/sentry/killswitches.py,
         src/sentry/utils/dates.py,
         src/sentry/utils/kvstore,
         src/sentry/utils/snql.py,

+ 36 - 33
src/sentry/api/endpoints/project_app_store_connect_credentials.py

@@ -57,6 +57,7 @@ import dateutil.parser
 import jsonschema
 import requests
 from rest_framework import serializers
+from rest_framework.request import Request
 from rest_framework.response import Response
 
 from sentry import features
@@ -113,7 +114,7 @@ def get_app_store_config(
         raise ValueError("bad sources") from e
 
 
-class AppStoreConnectCredentialsSerializer(serializers.Serializer):
+class AppStoreConnectCredentialsSerializer(serializers.Serializer):  # type: ignore
     """Input validation for :class:`AppStoreConnectAppsEndpoint."""
 
     # an IID with the XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX format
@@ -124,7 +125,7 @@ class AppStoreConnectCredentialsSerializer(serializers.Serializer):
     appconnectPrivateKey = serializers.CharField(max_length=512, required=True)
 
 
-class AppStoreConnectAppsEndpoint(ProjectEndpoint):
+class AppStoreConnectAppsEndpoint(ProjectEndpoint):  # type: ignore
     """Retrieves available applications with provided credentials.
 
     ``POST projects/{org_slug}/{proj_slug}/appstoreconnect/apps/``
@@ -164,7 +165,7 @@ class AppStoreConnectAppsEndpoint(ProjectEndpoint):
 
     permission_classes = [StrictProjectPermission]
 
-    def post(self, request, project):
+    def post(self, request: Request, project: Project) -> Response:
         if not features.has(
             APP_STORE_CONNECT_FEATURE_NAME, project.organization, actor=request.user
         ):
@@ -188,13 +189,15 @@ class AppStoreConnectAppsEndpoint(ProjectEndpoint):
         if apps is None:
             raise AppConnectAuthenticationError()
 
-        apps = [{"name": app.name, "bundleId": app.bundle_id, "appId": app.app_id} for app in apps]
-        result = {"apps": apps}
+        all_apps = [
+            {"name": app.name, "bundleId": app.bundle_id, "appId": app.app_id} for app in apps
+        ]
+        result = {"apps": all_apps}
 
         return Response(result, status=200)
 
 
-class CreateSessionContextSerializer(serializers.Serializer):
+class CreateSessionContextSerializer(serializers.Serializer):  # type: ignore
     auth_key = serializers.CharField(min_length=1, required=True)
     session_id = serializers.CharField(min_length=1, required=True)
     scnt = serializers.CharField(min_length=1, required=True)
@@ -203,7 +206,7 @@ class CreateSessionContextSerializer(serializers.Serializer):
     itunes_created = serializers.DateTimeField(required=True)
 
 
-class AppStoreCreateCredentialsSerializer(serializers.Serializer):
+class AppStoreCreateCredentialsSerializer(serializers.Serializer):  # type: ignore
     """Input validation for :class:`AppStoreConnectCreateCredentialsEndpoint`."""
 
     # an IID with the XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX format
@@ -215,14 +218,15 @@ class AppStoreCreateCredentialsSerializer(serializers.Serializer):
     itunesUser = serializers.CharField(max_length=100, min_length=1, required=True)
     itunesPassword = serializers.CharField(max_length=512, min_length=1, required=True)
     appName = serializers.CharField(max_length=512, min_length=1, required=True)
-    appId = serializers.CharField(max_length=512, min_length=1, required=True)
+    appId = serializers.CharField(min_length=1, required=True)
+    bundleId = serializers.CharField(min_length=1, required=True)
     # this is the ITunes organization the user is a member of ( known as providers in Itunes terminology)
     orgId = serializers.IntegerField(required=True)
     orgName = serializers.CharField(max_length=100, required=True)
     sessionContext = CreateSessionContextSerializer(required=True)
 
 
-class AppStoreConnectCreateCredentialsEndpoint(ProjectEndpoint):
+class AppStoreConnectCreateCredentialsEndpoint(ProjectEndpoint):  # type: ignore
     """Returns all the App Store Connect symbol source settings ready to be saved.
 
     ``POST projects/{org_slug}/{proj_slug}/appstoreconnect/``
@@ -246,7 +250,7 @@ class AppStoreConnectCreateCredentialsEndpoint(ProjectEndpoint):
 
     permission_classes = [StrictProjectPermission]
 
-    def post(self, request, project):
+    def post(self, request: Request, project: Project) -> Response:
         if not features.has(
             APP_STORE_CONNECT_FEATURE_NAME, project.organization, actor=request.user
         ):
@@ -276,7 +280,7 @@ class AppStoreConnectCreateCredentialsEndpoint(ProjectEndpoint):
         return Response(config, status=200)
 
 
-class UpdateSessionContextSerializer(serializers.Serializer):
+class UpdateSessionContextSerializer(serializers.Serializer):  # type: ignore
     auth_key = serializers.CharField(min_length=1, required=True)
     session_id = serializers.CharField(min_length=1, required=True)
     scnt = serializers.CharField(min_length=1, required=True)
@@ -285,7 +289,7 @@ class UpdateSessionContextSerializer(serializers.Serializer):
     itunes_created = serializers.DateTimeField(required=True)
 
 
-class AppStoreUpdateCredentialsSerializer(serializers.Serializer):
+class AppStoreUpdateCredentialsSerializer(serializers.Serializer):  # type: ignore
     """Input validation for :class:`AppStoreConnectUpdateCredentialsEndpoint`."""
 
     # an IID with the XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX format
@@ -297,14 +301,15 @@ class AppStoreUpdateCredentialsSerializer(serializers.Serializer):
     itunesUser = serializers.CharField(max_length=100, min_length=1, required=False)
     itunesPassword = serializers.CharField(max_length=512, min_length=1, required=False)
     appName = serializers.CharField(max_length=512, min_length=1, required=False)
-    appId = serializers.CharField(max_length=512, min_length=1, required=False)
+    appId = serializers.CharField(min_length=1, required=False)
+    bundleId = serializers.CharField(min_length=1, required=False)
     sessionContext = UpdateSessionContextSerializer(required=False)
     # this is the ITunes organization the user is a member of ( known as providers in Itunes terminology)
     orgId = serializers.IntegerField(required=False)
     orgName = serializers.CharField(max_length=100, required=False)
 
 
-class AppStoreConnectUpdateCredentialsEndpoint(ProjectEndpoint):
+class AppStoreConnectUpdateCredentialsEndpoint(ProjectEndpoint):  # type: ignore
     """Updates a subset of the existing credentials.
 
     ``POST projects/{org_slug}/{proj_slug}/appstoreconnect/{id}/``
@@ -319,7 +324,7 @@ class AppStoreConnectUpdateCredentialsEndpoint(ProjectEndpoint):
 
     permission_classes = [StrictProjectPermission]
 
-    def post(self, request, project, credentials_id):
+    def post(self, request: Request, project: Project, credentials_id: str) -> Response:
         if not features.has(
             APP_STORE_CONNECT_FEATURE_NAME, project.organization, actor=request.user
         ):
@@ -357,7 +362,7 @@ class AppStoreConnectUpdateCredentialsEndpoint(ProjectEndpoint):
         return Response(symbol_source_config, status=200)
 
 
-class AppStoreConnectCredentialsValidateEndpoint(ProjectEndpoint):
+class AppStoreConnectCredentialsValidateEndpoint(ProjectEndpoint):  # type: ignore
     """Validates both API credentials and if the stored ITunes session is still active.
 
     ``POST projects/{org_slug}/{proj_slug}/appstoreconnect/validate/{id}/``
@@ -379,7 +384,7 @@ class AppStoreConnectCredentialsValidateEndpoint(ProjectEndpoint):
 
     permission_classes = [StrictProjectPermission]
 
-    def get(self, request, project, credentials_id):
+    def get(self, request: Request, project: Project, credentials_id: str) -> Response:
         if not features.has(
             APP_STORE_CONNECT_FEATURE_NAME, project.organization, actor=request.user
         ):
@@ -390,7 +395,7 @@ class AppStoreConnectCredentialsValidateEndpoint(ProjectEndpoint):
             return Response(status=404)
 
         if symbol_source_cfg.get("itunesCreated") is not None:
-            expiration_date = (
+            expiration_date: Optional[datetime.datetime] = (
                 dateutil.parser.isoparse(symbol_source_cfg.get("itunesCreated"))
                 + ITUNES_TOKEN_VALIDITY
             )
@@ -419,7 +424,7 @@ class AppStoreConnectCredentialsValidateEndpoint(ProjectEndpoint):
         )
 
 
-class AppStoreConnectStartAuthSerializer(serializers.Serializer):
+class AppStoreConnectStartAuthSerializer(serializers.Serializer):  # type: ignore
     """Input validation for :class:`AppStoreConnectStartAuthEndpoint."""
 
     itunesUser = serializers.CharField(max_length=100, min_length=1, required=False)
@@ -427,7 +432,7 @@ class AppStoreConnectStartAuthSerializer(serializers.Serializer):
     id = serializers.CharField(max_length=40, min_length=1, required=False)
 
 
-class AppStoreConnectStartAuthEndpoint(ProjectEndpoint):
+class AppStoreConnectStartAuthEndpoint(ProjectEndpoint):  # type: ignore
     """Starts iTunes login sequence.
 
     ``POST projects/{org_slug}/{proj_slug}/appstoreconnect/start/``
@@ -466,7 +471,7 @@ class AppStoreConnectStartAuthEndpoint(ProjectEndpoint):
 
     permission_classes = [StrictProjectPermission]
 
-    def post(self, request, project):
+    def post(self, request: Request, project: Project) -> Response:
         if not features.has(
             APP_STORE_CONNECT_FEATURE_NAME, project.organization, actor=request.user
         ):
@@ -496,8 +501,6 @@ class AppStoreConnectStartAuthEndpoint(ProjectEndpoint):
         session = requests.session()
 
         auth_key = itunes_connect.get_auth_service_key(session)
-        if auth_key is None:
-            return Response("Could not contact itunes store.", status=500)
 
         init_login_result = itunes_connect.initiate_login(
             session, service_key=auth_key, account_name=user_name, password=password
@@ -517,19 +520,19 @@ class AppStoreConnectStartAuthEndpoint(ProjectEndpoint):
         )
 
 
-class RequestSmsSessionContextSerializer(serializers.Serializer):
+class RequestSmsSessionContextSerializer(serializers.Serializer):  # type: ignore
     auth_key = serializers.CharField(min_length=1, required=True)
     session_id = serializers.CharField(min_length=1, required=True)
     scnt = serializers.CharField(min_length=1, required=True)
 
 
-class AppStoreConnectRequestSmsSerializer(serializers.Serializer):
+class AppStoreConnectRequestSmsSerializer(serializers.Serializer):  # type: ignore
     """Input validation for :class:`AppStoreConnectRequestSmsEndpoint`."""
 
     sessionContext = RequestSmsSessionContextSerializer(required=True)
 
 
-class AppStoreConnectRequestSmsEndpoint(ProjectEndpoint):
+class AppStoreConnectRequestSmsEndpoint(ProjectEndpoint):  # type: ignore
     """Switches an iTunes login to using SMS for 2FA.
 
     ``POST projects/{org_slug}/{proj_slug}/appstoreconnect/requestSms/``
@@ -551,7 +554,7 @@ class AppStoreConnectRequestSmsEndpoint(ProjectEndpoint):
 
     permission_classes = [StrictProjectPermission]
 
-    def post(self, request, project):
+    def post(self, request: Request, project: Project) -> Response:
         if not features.has(
             APP_STORE_CONNECT_FEATURE_NAME, project.organization, actor=request.user
         ):
@@ -580,7 +583,7 @@ class AppStoreConnectRequestSmsEndpoint(ProjectEndpoint):
             phone_id=phone_info.id,
             push_mode=phone_info.push_mode,
         )
-        if init_phone_login is None:
+        if not init_phone_login:
             return Response("Phone 2fa failed", status=500)
 
         # success, return the new session context (add phone_id and push mode to the session context)
@@ -589,7 +592,7 @@ class AppStoreConnectRequestSmsEndpoint(ProjectEndpoint):
         return Response({"sessionContext": data}, status=200)
 
 
-class TwoFactorAuthSessionContextSerializer(serializers.Serializer):
+class TwoFactorAuthSessionContextSerializer(serializers.Serializer):  # type: ignore
     auth_key = serializers.CharField(min_length=1, required=True)
     session_id = serializers.CharField(min_length=1, required=True)
     scnt = serializers.CharField(min_length=1, required=True)
@@ -597,7 +600,7 @@ class TwoFactorAuthSessionContextSerializer(serializers.Serializer):
     push_mode = serializers.CharField(min_length=1, required=False)
 
 
-class AppStoreConnect2FactorAuthSerializer(serializers.Serializer):
+class AppStoreConnect2FactorAuthSerializer(serializers.Serializer):  # type: ignore
     """Input validation for :class:`AppStoreConnect2FactorAuthEndpoint."""
 
     sessionContext = TwoFactorAuthSessionContextSerializer(required=True)
@@ -605,7 +608,7 @@ class AppStoreConnect2FactorAuthSerializer(serializers.Serializer):
     useSms = serializers.BooleanField(required=True)
 
 
-class AppStoreConnect2FactorAuthEndpoint(ProjectEndpoint):
+class AppStoreConnect2FactorAuthEndpoint(ProjectEndpoint):  # type: ignore
     """Completes the 2FA iTunes login, returning a valid session.
 
     ``POST projects/{org_slug}/{proj_slug}/appstoreconnect/2fa/``
@@ -650,7 +653,7 @@ class AppStoreConnect2FactorAuthEndpoint(ProjectEndpoint):
 
     permission_classes = [StrictProjectPermission]
 
-    def post(self, request, project):
+    def post(self, request: Request, project: Project) -> Response:
         if not features.has(
             APP_STORE_CONNECT_FEATURE_NAME, project.organization, actor=request.user
         ):
@@ -701,7 +704,7 @@ class AppStoreConnect2FactorAuthEndpoint(ProjectEndpoint):
                 "session_id": headers.session_id,
                 "scnt": headers.scnt,
                 "itunes_session": itunes_session,
-                "itunes_person_id": prs_id,  # TODO(flub): This is seemingly unused?
+                "itunes_person_id": prs_id,
                 "itunes_created": datetime.datetime.utcnow(),
             }
             return Response(

+ 4 - 2
src/sentry/lang/native/symbolicator.py

@@ -61,10 +61,11 @@ APP_STORE_CONNECT_SCHEMA = {
         "itunesUser": {"type": "string", "minLength": 1, "maxLength": 100},
         "itunesCreated": {"type": "string", "format": "date-time"},
         "itunesPassword": {"type": "string"},
-        "itunesSession": {"type": "string"},
         "itunesPersonId": {"type": "string"},
+        "itunesSession": {"type": "string"},
         "appName": {"type": "string", "minLength": 1, "maxLength": 512},
-        "appId": {"type": "string", "minLength": 1, "maxLength": 512},
+        "appId": {"type": "string", "minLength": 1},
+        "bundleId": {"type": "string", "minLength": 1},
         "orgId": {"type": "integer"},
         "orgName": {"type": "string", "minLength": 1, "maxLength": 512},
     },
@@ -82,6 +83,7 @@ APP_STORE_CONNECT_SCHEMA = {
         "itunesPersonId",
         "appName",
         "appId",
+        "bundleId",
         "orgId",
         "orgName",
     ],

+ 0 - 0
src/sentry/utils/appleconnect/__init__.py


+ 34 - 25
src/sentry/utils/appleconnect/appstore_connect.py

@@ -1,7 +1,7 @@
 import logging
 import time
 from collections import namedtuple
-from typing import Any, Generator, List, Mapping, Optional
+from typing import Any, Dict, Generator, List, Mapping, Optional
 
 import jwt
 from requests import Session
@@ -13,7 +13,9 @@ logger = logging.getLogger(__name__)
 AppConnectCredentials = namedtuple("AppConnectCredentials", ["key_id", "key", "issuer_id"])
 
 
-def _get_authorization_header(credentials=AppConnectCredentials, expiry_sec=None) -> str:
+def _get_authorization_header(
+    credentials: AppConnectCredentials, expiry_sec: Optional[int] = None
+) -> str:
     """
     Creates a JWT (javascript web token) for use with app store connect API
 
@@ -63,18 +65,18 @@ def _get_appstore_info(
     if not response.ok:
         raise ValueError("Request failed", full_url, response.status_code, response.text)
     try:
-        return response.json()
+        return response.json()  # type: ignore
     except Exception as e:
         raise ValueError(
             "Response body not JSON", full_url, response.status_code, response.text
         ) from e
 
 
-def _get_next_page(response_json) -> str:
+def _get_next_page(response_json: Mapping[str, Any]) -> Optional[str]:
     """
     Gets the next page url from a app store connect paged response
     """
-    return safe.get_path(response_json, "links", "next")
+    return safe.get_path(response_json, "links", "next")  # type: ignore
 
 
 def _get_appstore_info_paged_data(
@@ -97,24 +99,29 @@ def _get_appstore_info_paged_data(
 
     :return: a generator with the contents of all the arrays from each page (flattened).
     """
-    while url is not None:
+    next_url: Optional[str] = url
+    while next_url is not None:
         response = _get_appstore_info(session, credentials, url)
+        if response is None:
+            return
         data = response["data"]
         yield from data
-        url = _get_next_page(response)
+        next_url = _get_next_page(response)
 
 
-def get_pre_release_version_info(session: Session, credentials: AppConnectCredentials, app_id: str):
-    """
-    Get all prerelease builds version information for an application
+def get_pre_release_version_info(
+    session: Session, credentials: AppConnectCredentials, app_id: str
+) -> List[Dict[str, Any]]:
+    """Get all prerelease builds version information for an application
 
     The release build version information has the following structure:
     platform: str - the platform for the build (e.g. IOS, MAC_OS ...)
-    short_version: str - the short version build info ( e.g. '1.0.1'), also called "train" in starship documentation
+    short_version: str - the short version build info ( e.g. '1.0.1'), also called "train"
+       in starship documentation
     id: str - the IID of the version
     versions: vec - a vector with builds
-        version: str - the version of the build (e.g. '101'), looks like the build number
-        id: str - the IID of the build
+       version: str - the version of the build (e.g. '101'), looks like the build number
+       id: str - the IID of the build
 
     NOTE: the pre release version information is identical to the release version information
     :return: a list of prerelease builds version information (see above)
@@ -123,7 +130,7 @@ def get_pre_release_version_info(session: Session, credentials: AppConnectCreden
     data = _get_appstore_info_paged_data(session, credentials, url)
     result = []
     for d in data:
-        versions = []
+        versions: List[Dict[str, Any]] = []
         v = {
             "platform": safe.get_path(d, "attributes", "platform"),
             "short_version": safe.get_path(d, "attributes", "version"),
@@ -142,17 +149,19 @@ def get_pre_release_version_info(session: Session, credentials: AppConnectCreden
     return result
 
 
-def get_release_version_info(session: Session, credentials: AppConnectCredentials, app_id: str):
-    """
-    Get all release builds version information for an application
+def get_release_version_info(
+    session: Session, credentials: AppConnectCredentials, app_id: str
+) -> List[Dict[str, Any]]:
+    """Get all release builds version information for an application
 
     The release build version information has the following structure:
     platform: str - the platform for the build (e.g. IOS, MAC_OS ...)
-    short_version: str - the short version build info ( e.g. '1.0.1'), also called "train" in starship documentation
+    short_version: str - the short version build info ( e.g. '1.0.1'), also called "train"
+       in starship documentation
     id: str - the IID of the version
     versions: vec - a vector with builds
-        version: str - the version of the build (e.g. '101'), looks like the build number
-        id: str - the IID of the build
+       version: str - the version of the build (e.g. '101'), looks like the build number
+       id: str - the IID of the build
 
     NOTE: the release version information is identical to the pre release version information
     :return: a list of release builds version information (see above)
@@ -161,7 +170,7 @@ def get_release_version_info(session: Session, credentials: AppConnectCredential
     data = _get_appstore_info_paged_data(session, credentials, url)
     result = []
     for d in data:
-        versions = []
+        versions: List[Dict[str, Any]] = []
         build_url = safe.get_path(d, "relationships", "build", "links", "related")
         v = {
             "platform": safe.get_path(d, "attributes", "platform"),
@@ -184,10 +193,10 @@ def get_release_version_info(session: Session, credentials: AppConnectCredential
     return result
 
 
-def get_build_info(session: Session, credentials: AppConnectCredentials, app_id: str):
-    """
-    Returns the build info for an application
-    """
+def get_build_info(
+    session: Session, credentials: AppConnectCredentials, app_id: str
+) -> Dict[str, List[Dict[str, Any]]]:
+    """Returns the build info for an application."""
     return {
         "pre_releases": get_pre_release_version_info(session, credentials, app_id),
         "releases": get_release_version_info(session, credentials, app_id),

+ 2 - 4
src/sentry/utils/appleconnect/itunes_connect.py

@@ -323,10 +323,8 @@ def get_dsym_url(
         try:
             data = details_response.json()
             dsym_url = safe.get_path(data, "data", "dsymurl")
-            if not isinstance(dsym_url, str) or dsym_url is not None:
-                raise TypeError("dsymurl not a string {dsym_url!r}")
-            return dsym_url
-        except:  # NOQA
+            return dsym_url  # type: ignore
+        except Exception:
             logger.info(
                 f"Could not obtain dsms info for app id={app_id}, bundle_short={bundle_short_version}, "
                 f"bundle={bundle_version}, platform={platform}",

+ 9 - 3
static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx

@@ -51,6 +51,7 @@ type IntialData = {
   appconnectIssuer: string;
   appconnectKey: string;
   appconnectPrivateKey: string;
+  bundleId: string;
   id: string;
   itunesCreated: string;
   itunesPassword: string;
@@ -67,7 +68,7 @@ type Props = Pick<ModalRenderProps, 'Header' | 'Body' | 'Footer'> & {
   api: Client;
   orgSlug: Organization['slug'];
   projectSlug: Project['slug'];
-  onSubmit: (data: Record<string, any>) => void;
+  onSubmit: (data: IntialData) => void;
   location: Location;
   appStoreConnectContext?: AppStoreConnectContextProps;
   initialData?: IntialData;
@@ -116,7 +117,11 @@ function AppStoreConnect({
   const [stepTwoData, setStepTwoData] = useState<StepTwoData>({
     app:
       initialData?.appId && initialData?.appName
-        ? {appId: initialData.appId, name: initialData.appName}
+        ? {
+            appId: initialData.appId,
+            name: initialData.appName,
+            bundleId: initialData.bundleId,
+          }
         : undefined,
   });
 
@@ -252,12 +257,13 @@ function AppStoreConnect({
           appconnectPrivateKey: stepOneData.privateKey,
           appName: stepTwoData.app.name,
           appId: stepTwoData.app.appId,
+          bundleId: stepTwoData.app.bundleId,
           orgId: stepFifthData.org.organizationId,
           orgName: stepFifthData.org.name,
           sessionContext: newSessionContext ?? sessionContext,
         },
       });
-      onSubmit(response);
+      onSubmit(response as IntialData);
     } catch (error) {
       setIsLoading(false);
       addErrorMessage(errorMessage);

+ 1 - 0
static/app/components/modals/debugFileCustomRepository/appStoreConnect/types.tsx

@@ -1,6 +1,7 @@
 export type AppStoreApp = {
   name: string;
   appId: string;
+  bundleId: string;
 };
 
 export type AppleStoreOrg = {

+ 7 - 3
static/app/views/settings/projectDebugFiles/externalSources/symbolSources.tsx

@@ -198,7 +198,8 @@ function SymbolSources({
       return;
     }
 
-    const item = value.find(v => v.id === customRepository);
+    const itemIndex = value.findIndex(v => v.id === customRepository);
+    const item = value[itemIndex];
 
     if (!item) {
       return;
@@ -210,7 +211,7 @@ function SymbolSources({
       sourceConfig,
       sourceType: item.type,
       appStoreConnectContext,
-      onSave: updatedData => handleUpdateSymbolSource(updatedData as Item, item.index),
+      onSave: updatedData => handleUpdateSymbolSource(updatedData as Item, itemIndex),
       onClose: handleCloseImageDetailsModal,
     });
   }
@@ -238,7 +239,7 @@ function SymbolSources({
 
   async function handleChange(updatedSymbolSources: Item[], updatedItem?: Item) {
     const symbolSourcesWithoutErrors = updatedSymbolSources.map(updatedSymbolSource =>
-      omit(updatedSymbolSource, 'error')
+      omit(updatedSymbolSource, ['error', 'warning'])
     );
 
     const {successMessage, errorMessage} = getRequestMessages(
@@ -262,6 +263,9 @@ function SymbolSources({
       addSuccessMessage(successMessage);
       closeModal();
       if (updatedItem && updatedItem.type === 'appStoreConnect') {
+        // TODO(Priscila): check the reason why the closeModal doesn't call the function
+        // handleCloseImageDetailsModal when revalidating
+        handleCloseImageDetailsModal();
         reloadPage();
       }
     } catch {