From 30b499738a1759bad3f00ea5d447fb0603b648e7 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Tue, 2 Nov 2021 21:34:41 -0400 Subject: [PATCH 01/15] fix(appstore-connect): Add a new endpoint that returns the status of all configured apps --- .../project_app_store_connect_credentials.py | 83 +++++++++++++++++++ src/sentry/api/urls.py | 6 ++ 2 files changed, 89 insertions(+) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index 4a6b495a6c5808..2e5692e26c3843 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -411,3 +411,86 @@ def get(self, request: Request, project: Project, credentials_id: str) -> Respon }, status=200, ) + + +class AppStoreConnectStatusEndpoint(ProjectEndpoint): # type: ignore + """Returns a summary of the project's App Store Connect configuration + and builds. + + ``GET projects/{org_slug}/{proj_slug}/appstoreconnect/status/{id}/`` + + Response: + ```json + { + "appstoreCredentialsValid": true, + "pendingDownloads": 123, + "latestBuildVersion: "9.8.7" | null, + "latestBuildNumber": "987000" | null, + "lastCheckedBuilds": "YYYY-MM-DDTHH:MM:SS.SSSSSSZ" | null + } + ``` + + * ``pendingDownloads`` is the number of pending build dSYM downloads. + + * ``latestBuildVersion`` and ``latestBuildNumber`` together form a unique identifier for + the latest build recognized by Sentry. + + * ``lastCheckedBuilds`` is when sentry last checked for new builds, regardless + of whether there were any or no builds in App Store Connect at the time. + """ + + permission_classes = [ProjectPermission] + + def get(self, request: Request, project: Project, credentials_id: str) -> Response: + try: + symbol_source_cfg = appconnect.AppStoreConnectConfig.from_project_config( + project, credentials_id + ) + except KeyError: + return Response(status=404) + + credentials = appstore_connect.AppConnectCredentials( + key_id=symbol_source_cfg.appconnectKey, + key=symbol_source_cfg.appconnectPrivateKey, + issuer_id=symbol_source_cfg.appconnectIssuer, + ) + + session = requests.Session() + + apps = appstore_connect.get_apps(session, credentials) + + pending_downloads = AppConnectBuild.objects.filter(project=project, fetched=False).count() + + latest_build = ( + AppConnectBuild.objects.filter(project=project, bundle_id=symbol_source_cfg.bundleId) + .order_by("-uploaded_to_appstore") + .first() + ) + if latest_build is None: + latestBuildVersion = None + latestBuildNumber = None + else: + latestBuildVersion = latest_build.bundle_short_version + latestBuildNumber = latest_build.bundle_version + + try: + check_entry = LatestAppConnectBuildsCheck.objects.get( + project=project, source_id=symbol_source_cfg.id + ) + # If the source was only just created then it's possible that sentry hasn't checked for any + # new builds for it yet. + except LatestAppConnectBuildsCheck.DoesNotExist: + last_checked_builds = None + else: + last_checked_builds = check_entry.last_checked + + return Response( + { + "appstoreCredentialsStatus": apps is not None, + "pendingDownloads": pending_downloads, + "latestBuildVersion": latestBuildVersion, + "latestBuildNumber": latestBuildNumber, + "lastCheckedBuilds": last_checked_builds, + }, + status=200, + ) diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 383afc8e393f15..b42d84b261d898 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -282,6 +282,7 @@ AppStoreConnectAppsEndpoint, AppStoreConnectCreateCredentialsEndpoint, AppStoreConnectCredentialsValidateEndpoint, + AppStoreConnectStatusEndpoint, AppStoreConnectUpdateCredentialsEndpoint, ) from .endpoints.project_avatar import ProjectAvatarEndpoint @@ -2032,6 +2033,11 @@ AppStoreConnectUpdateCredentialsEndpoint.as_view(), name="sentry-api-0-project-appstoreconnect-credentials-update", ), + url( + r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/status/(?P[^\/]+)$", + AppStoreConnectStatusEndpoint.as_view(), + name="sentry-api-0-project-appstoreconnect-status", + ), ] ), ), From a4b3a5ea2454b0fd3881e35600a6d33d4bc3761a Mon Sep 17 00:00:00 2001 From: Betty Da Date: Tue, 2 Nov 2021 21:48:54 -0400 Subject: [PATCH 02/15] return a list of statuses for every config associated with a project --- .../project_app_store_connect_credentials.py | 127 ++++++++++-------- src/sentry/api/urls.py | 2 +- 2 files changed, 75 insertions(+), 54 deletions(-) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index 2e5692e26c3843..d24b7462f6e81b 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -417,17 +417,29 @@ class AppStoreConnectStatusEndpoint(ProjectEndpoint): # type: ignore """Returns a summary of the project's App Store Connect configuration and builds. - ``GET projects/{org_slug}/{proj_slug}/appstoreconnect/status/{id}/`` + ``GET projects/{org_slug}/{proj_slug}/appstoreconnect/status`` Response: ```json - { - "appstoreCredentialsValid": true, - "pendingDownloads": 123, - "latestBuildVersion: "9.8.7" | null, - "latestBuildNumber": "987000" | null, - "lastCheckedBuilds": "YYYY-MM-DDTHH:MM:SS.SSSSSSZ" | null - } + [ + { + "id": "abc123", + "appstoreCredentialsValid": true, + "pendingDownloads": 123, + "latestBuildVersion: "9.8.7" | null, + "latestBuildNumber": "987000" | null, + "lastCheckedBuilds": "YYYY-MM-DDTHH:MM:SS.SSSSSSZ" | null + } + { + "id": ..., + "appstoreCredentialsValid": ..., + "pendingDownloads": ..., + "latestBuildVersion: ..., + "latestBuildNumber": ..., + "lastCheckedBuilds": ... + }, + ... + ] ``` * ``pendingDownloads`` is the number of pending build dSYM downloads. @@ -441,56 +453,65 @@ class AppStoreConnectStatusEndpoint(ProjectEndpoint): # type: ignore permission_classes = [ProjectPermission] - def get(self, request: Request, project: Project, credentials_id: str) -> Response: - try: - symbol_source_cfg = appconnect.AppStoreConnectConfig.from_project_config( - project, credentials_id - ) - except KeyError: - return Response(status=404) + def get(self, request: Request, project: Project) -> Response: + config_ids = appconnect.AppStoreConnectConfig.all_config_ids(project) + statuses = [] + for config_id in config_ids: + try: + symbol_source_cfg = appconnect.AppStoreConnectConfig.from_project_config( + project, config_id + ) + except KeyError: + continue - credentials = appstore_connect.AppConnectCredentials( - key_id=symbol_source_cfg.appconnectKey, - key=symbol_source_cfg.appconnectPrivateKey, - issuer_id=symbol_source_cfg.appconnectIssuer, - ) + credentials = appstore_connect.AppConnectCredentials( + key_id=symbol_source_cfg.appconnectKey, + key=symbol_source_cfg.appconnectPrivateKey, + issuer_id=symbol_source_cfg.appconnectIssuer, + ) - session = requests.Session() + session = requests.Session() - apps = appstore_connect.get_apps(session, credentials) + apps = appstore_connect.get_apps(session, credentials) - pending_downloads = AppConnectBuild.objects.filter(project=project, fetched=False).count() + pending_downloads = AppConnectBuild.objects.filter( + project=project, fetched=False + ).count() - latest_build = ( - AppConnectBuild.objects.filter(project=project, bundle_id=symbol_source_cfg.bundleId) - .order_by("-uploaded_to_appstore") - .first() - ) - if latest_build is None: - latestBuildVersion = None - latestBuildNumber = None - else: - latestBuildVersion = latest_build.bundle_short_version - latestBuildNumber = latest_build.bundle_version + latest_build = ( + AppConnectBuild.objects.filter( + project=project, bundle_id=symbol_source_cfg.bundleId + ) + .order_by("-uploaded_to_appstore") + .first() + ) + if latest_build is None: + latestBuildVersion = None + latestBuildNumber = None + else: + latestBuildVersion = latest_build.bundle_short_version + latestBuildNumber = latest_build.bundle_version - try: - check_entry = LatestAppConnectBuildsCheck.objects.get( - project=project, source_id=symbol_source_cfg.id + try: + check_entry = LatestAppConnectBuildsCheck.objects.get( + project=project, source_id=symbol_source_cfg.id + ) + # If the source was only just created then it's possible that sentry hasn't checked for any + # new builds for it yet. + except LatestAppConnectBuildsCheck.DoesNotExist: + last_checked_builds = None + else: + last_checked_builds = check_entry.last_checked + + statuses.append( + { + "id": config_id, + "appstoreCredentialsValid": apps is not None, + "pendingDownloads": pending_downloads, + "latestBuildVersion": latestBuildVersion, + "latestBuildNumber": latestBuildNumber, + "lastCheckedBuilds": last_checked_builds, + } ) - # If the source was only just created then it's possible that sentry hasn't checked for any - # new builds for it yet. - except LatestAppConnectBuildsCheck.DoesNotExist: - last_checked_builds = None - else: - last_checked_builds = check_entry.last_checked - return Response( - { - "appstoreCredentialsStatus": apps is not None, - "pendingDownloads": pending_downloads, - "latestBuildVersion": latestBuildVersion, - "latestBuildNumber": latestBuildNumber, - "lastCheckedBuilds": last_checked_builds, - }, - status=200, - ) + return Response(statuses, status=200) diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index b42d84b261d898..5b8d48781de1a2 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -2034,7 +2034,7 @@ name="sentry-api-0-project-appstoreconnect-credentials-update", ), url( - r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/status/(?P[^\/]+)$", + r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/status$", AppStoreConnectStatusEndpoint.as_view(), name="sentry-api-0-project-appstoreconnect-status", ), From 7d9492b37fc7d38c57cd4dfcb67548238b447a5d Mon Sep 17 00:00:00 2001 From: Betty Da Date: Thu, 4 Nov 2021 01:48:36 -0400 Subject: [PATCH 03/15] frontend support for multiple statuses being returned --- .../projects/appStoreConnectContext/index.tsx | 17 +++++++++++------ .../settings/project/appStoreConnectContext.tsx | 17 +++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/static/app/components/projects/appStoreConnectContext/index.tsx b/static/app/components/projects/appStoreConnectContext/index.tsx index 83180572292b51..27680a3700e3fe 100644 --- a/static/app/components/projects/appStoreConnectContext/index.tsx +++ b/static/app/components/projects/appStoreConnectContext/index.tsx @@ -71,13 +71,18 @@ const Provider = ({children, project, organization}: ProviderProps) => { } try { - const response = await api.requestPromise( - `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/validate/${appStoreConnectSymbolSourceId}/` + const response: AppStoreConnectValidationData[] = await api.requestPromise( + `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status` ); - setAppStoreConnectValidationData({ - id: appStoreConnectSymbolSourceId, - ...response, - }); + + const sourceStatus = response.find( + s => s.id === appStoreConnectSymbolSourceId, + response + ); + + if (sourceStatus) { + setAppStoreConnectValidationData(sourceStatus); + } } catch { // do nothing } diff --git a/static/app/views/settings/project/appStoreConnectContext.tsx b/static/app/views/settings/project/appStoreConnectContext.tsx index 5636966d61d85a..eb4ae184f98c99 100644 --- a/static/app/views/settings/project/appStoreConnectContext.tsx +++ b/static/app/views/settings/project/appStoreConnectContext.tsx @@ -41,13 +41,18 @@ const Provider = withApi( } try { - const response = await api.requestPromise( - `/projects/${orgSlug}/${project.slug}/appstoreconnect/validate/${appStoreConnectSymbolSourceId}/` + const response: AppStoreConnectValidationData[] = await api.requestPromise( + `/projects/${orgSlug}/${project.slug}/appstoreconnect/status` ); - setAppStoreConnectValidationData({ - id: appStoreConnectSymbolSourceId, - ...response, - }); + + const sourceStatus = response.find( + s => s.id === appStoreConnectSymbolSourceId, + response + ); + + if (sourceStatus) { + setAppStoreConnectValidationData(sourceStatus); + } } catch { // do nothing } From 9bd72a0eef07744800b8dce049cdbced155e994f Mon Sep 17 00:00:00 2001 From: Betty Da Date: Thu, 4 Nov 2021 01:36:18 -0400 Subject: [PATCH 04/15] complete backend implementation --- .../project_app_store_connect_credentials.py | 37 ++++++++++++++++--- src/sentry/api/exceptions.py | 6 +++ .../utils/appleconnect/appstore_connect.py | 4 +- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index d24b7462f6e81b..58e44d730dbd10 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -39,7 +39,11 @@ from sentry import features from sentry.api.bases.project import ProjectEndpoint, ProjectPermission, StrictProjectPermission -from sentry.api.exceptions import AppConnectAuthenticationError, AppConnectMultipleSourcesError +from sentry.api.exceptions import ( + AppConnectAuthenticationError, + AppConnectForbiddenError, + AppConnectMultipleSourcesError, +) from sentry.api.fields.secret import SecretField, validate_secret from sentry.lang.native import appconnect from sentry.lang.native.symbolicator import redact_source_secrets, secret_fields @@ -424,15 +428,15 @@ class AppStoreConnectStatusEndpoint(ProjectEndpoint): # type: ignore [ { "id": "abc123", - "appstoreCredentialsValid": true, + "credentials": { status: "valid" } | { status: "invalid", code: "app-connect-forbidden-error" }, "pendingDownloads": 123, "latestBuildVersion: "9.8.7" | null, "latestBuildNumber": "987000" | null, "lastCheckedBuilds": "YYYY-MM-DDTHH:MM:SS.SSSSSSZ" | null - } + }, { "id": ..., - "appstoreCredentialsValid": ..., + "credentials": ..., "pendingDownloads": ..., "latestBuildVersion: ..., "latestBuildNumber": ..., @@ -472,7 +476,28 @@ def get(self, request: Request, project: Project) -> Response: session = requests.Session() - apps = appstore_connect.get_apps(session, credentials) + try: + apps = appstore_connect.get_apps(session, credentials) + except appstore_connect.UnauthorizedError: + asc_credentials = { + "status": "invalid", + "code": AppConnectAuthenticationError().code, + } + except appstore_connect.ForbiddenError: + asc_credentials = {"status": "invalid", "code": AppConnectForbiddenError().code} + else: + if apps: + asc_credentials = {"status": "valid"} + else: + asc_credentials = { + "status": "invalid", + "code": AppConnectAuthenticationError().code, + } + + asc_credentials = { + "status": "invalid", + "code": AppConnectAuthenticationError().code, + } pending_downloads = AppConnectBuild.objects.filter( project=project, fetched=False @@ -506,7 +531,7 @@ def get(self, request: Request, project: Project) -> Response: statuses.append( { "id": config_id, - "appstoreCredentialsValid": apps is not None, + "credentials": asc_credentials, "pendingDownloads": pending_downloads, "latestBuildVersion": latestBuildVersion, "latestBuildNumber": latestBuildNumber, diff --git a/src/sentry/api/exceptions.py b/src/sentry/api/exceptions.py index 7b43fef82f4f57..d118bfe2b819e8 100644 --- a/src/sentry/api/exceptions.py +++ b/src/sentry/api/exceptions.py @@ -97,6 +97,12 @@ class TwoFactorRequired(SentryAPIException): message = "Organization requires two-factor authentication to be enabled" +class AppConnectForbiddenError(SentryAPIException): + status_code = status.HTTP_403_FORBIDDEN + code = "app-connect-forbidden-error" + message = "App connect Forbidden error" + + class AppConnectAuthenticationError(SentryAPIException): status_code = status.HTTP_401_UNAUTHORIZED code = "app-connect-authentication-error" diff --git a/src/sentry/utils/appleconnect/appstore_connect.py b/src/sentry/utils/appleconnect/appstore_connect.py index 9c8b5d3850c922..9e4d0f3a1d13c5 100644 --- a/src/sentry/utils/appleconnect/appstore_connect.py +++ b/src/sentry/utils/appleconnect/appstore_connect.py @@ -36,7 +36,7 @@ class UnauthorizedError(RequestError): class ForbiddenError(RequestError): - """The App Store Connect session does not have access to the requested dSYM.""" + """Forbidden: authentication token does not have sufficient permissions.""" pass @@ -154,6 +154,8 @@ def _get_appstore_json( if response.status_code == HTTPStatus.UNAUTHORIZED: raise UnauthorizedError(full_url) + elif response.status_code == HTTPStatus.FORBIDDEN: + raise ForbiddenError(full_url) else: raise RequestError(full_url) try: From bb583186cff67ec218339b03d24e287746b66588 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Thu, 4 Nov 2021 01:49:19 -0400 Subject: [PATCH 05/15] full frontend support for new status endpoint --- .../appStoreConnect/index.tsx | 34 +++++++++----- .../appStoreConnect/utils.tsx | 46 +++++++++++-------- .../projects/appStoreConnectContext/index.tsx | 2 +- .../projects/appStoreConnectContext/utils.tsx | 27 ++++++----- static/app/types/debugFiles.tsx | 8 +++- .../customRepositories/status.tsx | 5 +- 6 files changed, 77 insertions(+), 45 deletions(-) diff --git a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx index 178c73de7d9aaf..4ff403dace5c6a 100644 --- a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx +++ b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx @@ -9,7 +9,6 @@ import Button from 'app/components/button'; import ButtonBar from 'app/components/buttonBar'; import LoadingIndicator from 'app/components/loadingIndicator'; import {AppStoreConnectContextProps} from 'app/components/projects/appStoreConnectContext'; -import {appStoreConnectAlertMessage} from 'app/components/projects/appStoreConnectContext/utils'; import {IconWarning} from 'app/icons'; import {t, tct} from 'app/locale'; import space from 'app/styles/space'; @@ -57,7 +56,7 @@ function AppStoreConnect({ onSubmit, appStoreConnectContext, }: Props) { - const {updateAlertMessage} = appStoreConnectContext ?? {}; + const {credentials} = appStoreConnectContext ?? {}; const [isLoading, setIsLoading] = useState(false); const [activeStep, setActiveStep] = useState(0); @@ -114,6 +113,7 @@ function AppStoreConnect({ const appStoreConnnectError = getAppStoreErrorMessage(error); if (typeof appStoreConnnectError === 'string') { // app-connect-authentication-error + // 'app-connect-forbidden-error' addErrorMessage(appStoreConnnectError); return; } @@ -238,15 +238,27 @@ function AppStoreConnect({ if (activeStep !== 0) { return alerts; } - - if (updateAlertMessage === appStoreConnectAlertMessage.appStoreCredentialsInvalid) { - alerts.push( - }> - {t( - 'Your App Store Connect credentials are invalid. To reconnect, update your credentials.' - )} - - ); + if (credentials?.status === 'invalid') { + switch (credentials.code) { + case 'app-connect-forbidden-error': + alerts.push( + }> + {t( + 'Your App Store Connect credentials have insufficient permissions. To reconnect, update your credentials.' + )} + + ); + break; + case 'app-connect-authentication-error': + default: + alerts.push( + }> + {t( + 'Your App Store Connect credentials are invalid. To reconnect, update your credentials.' + )} + + ); + } } return alerts; diff --git a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx index 1c6d439e56e276..a291c4b0b1d161 100644 --- a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx +++ b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx @@ -28,13 +28,17 @@ const fieldErrorMessageMapping = { }, }; -type ErrorCodeDetailed = +export type ErrorCodeDetailed = | 'app-connect-authentication-error' + | 'app-connect-forbidden-error' | 'app-connect-multiple-sources-error'; +export type ValidationErrorDetailed = { + code: ErrorCodeDetailed; +}; + type ResponseJSONDetailed = { - detail: { - code: ErrorCodeDetailed; + detail: ValidationErrorDetailed & { extra: Record; message: string; }; @@ -64,21 +68,7 @@ export function getAppStoreErrorMessage( ?.detail; if (detailedErrorResponse) { - switch (detailedErrorResponse.code) { - case 'app-connect-authentication-error': - return t( - 'We could not establish a connection with App Store Connect. Please check the entered App Store Connect credentials' - ); - case 'app-connect-multiple-sources-error': - return t( - 'Only one Apple App Store Connect application is allowed in this project' - ); - default: { - // this shall not happen - Sentry.captureException(new Error('Unknown app store connect error')); - return unexpectedErrorMessage; - } - } + return getAppStoreValidationErrorMessage(detailedErrorResponse); } const errorResponse = error.responseJSON as undefined | ResponseJSON; @@ -116,3 +106,23 @@ export function getAppStoreErrorMessage( {} ) as Record; } + +export function getAppStoreValidationErrorMessage( + error: ValidationErrorDetailed +): string { + switch (error.code) { + case 'app-connect-authentication-error': + return t( + 'Credentials are invalid, missing, or expired. Check the entered App Store Connect credentials are correct and have not expired.' + ); + case 'app-connect-forbidden-error': + return t('The supplied API key does not have sufficient permissions.'); + case 'app-connect-multiple-sources-error': + return t('Only one Apple App Store Connect application is allowed in this project'); + default: { + // this shall not happen + Sentry.captureException(new Error('Unknown app store connect error')); + return unexpectedErrorMessage; + } + } +} diff --git a/static/app/components/projects/appStoreConnectContext/index.tsx b/static/app/components/projects/appStoreConnectContext/index.tsx index 27680a3700e3fe..6606cd4cd77f92 100644 --- a/static/app/components/projects/appStoreConnectContext/index.tsx +++ b/static/app/components/projects/appStoreConnectContext/index.tsx @@ -95,7 +95,7 @@ const Provider = ({children, project, organization}: ProviderProps) => { ? { ...appStoreConnectValidationData, updateAlertMessage: getAppConnectStoreUpdateAlertMessage( - appStoreConnectValidationData + appStoreConnectValidationData.credentials ), } : undefined diff --git a/static/app/components/projects/appStoreConnectContext/utils.tsx b/static/app/components/projects/appStoreConnectContext/utils.tsx index b54f98abfa05b1..4bc8a551dbd9db 100644 --- a/static/app/components/projects/appStoreConnectContext/utils.tsx +++ b/static/app/components/projects/appStoreConnectContext/utils.tsx @@ -1,17 +1,20 @@ -import {t} from 'app/locale'; -import {AppStoreConnectValidationData} from 'app/types/debugFiles'; +import { + getAppStoreValidationErrorMessage, + ValidationErrorDetailed, +} from 'app/components/modals/debugFileCustomRepository/appStoreConnect/utils'; +import {AppStoreConnectCredentialsStatus} from 'app/types/debugFiles'; -export const appStoreConnectAlertMessage = { - appStoreCredentialsInvalid: t( - 'The credentials of your configured App Store Connect are invalid.' - ), -}; +export function areAppStoreConnectCredentialsValid( + credentialsStatus: AppStoreConnectCredentialsStatus | undefined +): boolean { + return credentialsStatus?.status === 'valid'; +} export function getAppConnectStoreUpdateAlertMessage( - appConnectValidationData: AppStoreConnectValidationData -) { - if (appConnectValidationData.appstoreCredentialsValid === false) { - return appStoreConnectAlertMessage.appStoreCredentialsInvalid; + credentialsStatus: AppStoreConnectCredentialsStatus +): string | undefined { + if (areAppStoreConnectCredentialsValid(credentialsStatus)) { + return undefined; } - return undefined; + return getAppStoreValidationErrorMessage(credentialsStatus as ValidationErrorDetailed); } diff --git a/static/app/types/debugFiles.tsx b/static/app/types/debugFiles.tsx index 5ac1d7a536ac9a..2ed815c96fe46b 100644 --- a/static/app/types/debugFiles.tsx +++ b/static/app/types/debugFiles.tsx @@ -1,3 +1,5 @@ +import {ValidationErrorDetailed} from 'app/components/modals/debugFileCustomRepository/appStoreConnect/utils'; + export enum DebugFileType { EXE = 'exe', DBG = 'dbg', @@ -41,9 +43,13 @@ export enum CustomRepoType { APP_STORE_CONNECT = 'appStoreConnect', } +export type AppStoreConnectCredentialsStatus = + | {status: 'valid'} + | ({status: 'invalid'} & ValidationErrorDetailed); + export type AppStoreConnectValidationData = { id: string; - appstoreCredentialsValid: boolean; + credentials: AppStoreConnectCredentialsStatus; /** * Indicates the number of downloads waiting to be processed and completed, * or the number of downloads waiting for valid credentials to be completed if applicable. diff --git a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx index d16778db991fb8..bc96d89ab26ba7 100644 --- a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx +++ b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx @@ -2,6 +2,7 @@ import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import Placeholder from 'app/components/placeholder'; +import {areAppStoreConnectCredentialsValid} from 'app/components/projects/appStoreConnectContext/utils'; import TimeSince from 'app/components/timeSince'; import Tooltip from 'app/components/tooltip'; import {IconDownload} from 'app/icons/iconDownload'; @@ -23,9 +24,9 @@ function Status({details, onEditRepository}: Props) { return ; } - const {pendingDownloads, appstoreCredentialsValid, lastCheckedBuilds} = details ?? {}; + const {pendingDownloads, credentials, lastCheckedBuilds} = details ?? {}; - if (appstoreCredentialsValid === false) { + if (areAppStoreConnectCredentialsValid(credentials) === false) { return ( Date: Thu, 4 Nov 2021 14:29:07 -0400 Subject: [PATCH 06/15] no need to instantiate --- .../api/endpoints/project_app_store_connect_credentials.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index 58e44d730dbd10..1a18fd069d8ac9 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -481,17 +481,17 @@ def get(self, request: Request, project: Project) -> Response: except appstore_connect.UnauthorizedError: asc_credentials = { "status": "invalid", - "code": AppConnectAuthenticationError().code, + "code": AppConnectAuthenticationError.code, } except appstore_connect.ForbiddenError: - asc_credentials = {"status": "invalid", "code": AppConnectForbiddenError().code} + asc_credentials = {"status": "invalid", "code": AppConnectForbiddenError.code} else: if apps: asc_credentials = {"status": "valid"} else: asc_credentials = { "status": "invalid", - "code": AppConnectAuthenticationError().code, + "code": AppConnectAuthenticationError.code, } asc_credentials = { From 058d684b7aa0c118f679f70be8c1d996c9b0fe53 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Thu, 4 Nov 2021 14:29:30 -0400 Subject: [PATCH 07/15] this was test code oops --- .../api/endpoints/project_app_store_connect_credentials.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index 1a18fd069d8ac9..c88e29ef08c96b 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -494,11 +494,6 @@ def get(self, request: Request, project: Project) -> Response: "code": AppConnectAuthenticationError.code, } - asc_credentials = { - "status": "invalid", - "code": AppConnectAuthenticationError().code, - } - pending_downloads = AppConnectBuild.objects.filter( project=project, fetched=False ).count() From e008a266036562466154c9d80de6ed402c935c69 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Thu, 4 Nov 2021 20:04:35 -0400 Subject: [PATCH 08/15] return a map instead of a list --- .../project_app_store_connect_credentials.py | 20 +++++++++---------- .../projects/appStoreConnectContext/index.tsx | 14 ++++++------- .../project/appStoreConnectContext.tsx | 14 ++++++------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index c88e29ef08c96b..04a1d30d994956 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -459,7 +459,7 @@ class AppStoreConnectStatusEndpoint(ProjectEndpoint): # type: ignore def get(self, request: Request, project: Project) -> Response: config_ids = appconnect.AppStoreConnectConfig.all_config_ids(project) - statuses = [] + statuses = {} for config_id in config_ids: try: symbol_source_cfg = appconnect.AppStoreConnectConfig.from_project_config( @@ -523,15 +523,13 @@ def get(self, request: Request, project: Project) -> Response: else: last_checked_builds = check_entry.last_checked - statuses.append( - { - "id": config_id, - "credentials": asc_credentials, - "pendingDownloads": pending_downloads, - "latestBuildVersion": latestBuildVersion, - "latestBuildNumber": latestBuildNumber, - "lastCheckedBuilds": last_checked_builds, - } - ) + statuses[config_id] = { + "id": config_id, + "credentials": asc_credentials, + "pendingDownloads": pending_downloads, + "latestBuildVersion": latestBuildVersion, + "latestBuildNumber": latestBuildNumber, + "lastCheckedBuilds": last_checked_builds, + } return Response(statuses, status=200) diff --git a/static/app/components/projects/appStoreConnectContext/index.tsx b/static/app/components/projects/appStoreConnectContext/index.tsx index 6606cd4cd77f92..01e9200ea3c09a 100644 --- a/static/app/components/projects/appStoreConnectContext/index.tsx +++ b/static/app/components/projects/appStoreConnectContext/index.tsx @@ -71,15 +71,13 @@ const Provider = ({children, project, organization}: ProviderProps) => { } try { - const response: AppStoreConnectValidationData[] = await api.requestPromise( - `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status` - ); - - const sourceStatus = response.find( - s => s.id === appStoreConnectSymbolSourceId, - response - ); + const response: Map = + await api.requestPromise( + `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status` + ); + const sourceStatus: AppStoreConnectValidationData | undefined = + response[appStoreConnectSymbolSourceId]; if (sourceStatus) { setAppStoreConnectValidationData(sourceStatus); } diff --git a/static/app/views/settings/project/appStoreConnectContext.tsx b/static/app/views/settings/project/appStoreConnectContext.tsx index eb4ae184f98c99..4e156abacd38fa 100644 --- a/static/app/views/settings/project/appStoreConnectContext.tsx +++ b/static/app/views/settings/project/appStoreConnectContext.tsx @@ -41,15 +41,13 @@ const Provider = withApi( } try { - const response: AppStoreConnectValidationData[] = await api.requestPromise( - `/projects/${orgSlug}/${project.slug}/appstoreconnect/status` - ); - - const sourceStatus = response.find( - s => s.id === appStoreConnectSymbolSourceId, - response - ); + const response: Map = + await api.requestPromise( + `/projects/${orgSlug}/${project.slug}/appstoreconnect/status` + ); + const sourceStatus: AppStoreConnectValidationData | undefined = + response[appStoreConnectSymbolSourceId]; if (sourceStatus) { setAppStoreConnectValidationData(sourceStatus); } From f5c48327fa4fee5e8dc1b1ea0ed8aac753a859b4 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Thu, 4 Nov 2021 20:05:14 -0400 Subject: [PATCH 09/15] scope list of pending downloads to a specific app/config --- .../api/endpoints/project_app_store_connect_credentials.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index 04a1d30d994956..a2f36dfb7c9268 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -494,8 +494,9 @@ def get(self, request: Request, project: Project) -> Response: "code": AppConnectAuthenticationError.code, } + # TODO: is it possible to set up two configs pointing to the same app? pending_downloads = AppConnectBuild.objects.filter( - project=project, fetched=False + project=project, app_id=symbol_source_cfg.appId, fetched=False ).count() latest_build = ( From 85b2b9dd2c6ceae03d645f6e49f417c67676b436 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Thu, 4 Nov 2021 02:04:40 -0400 Subject: [PATCH 10/15] s/appstoreconnectvalidationdata/appstoreconnectstatusdata/ --- .../updateAlert.tsx | 8 +++--- .../projects/appStoreConnectContext/index.tsx | 27 +++++++++---------- static/app/types/debugFiles.tsx | 4 +-- .../project/appStoreConnectContext.tsx | 25 +++++++++-------- .../customRepositories/details.tsx | 4 +-- .../customRepositories/status.tsx | 4 +-- 6 files changed, 35 insertions(+), 37 deletions(-) diff --git a/static/app/components/globalAppStoreConnectUpdateAlert/updateAlert.tsx b/static/app/components/globalAppStoreConnectUpdateAlert/updateAlert.tsx index ac01f8dd7934a2..19945eb687d83b 100644 --- a/static/app/components/globalAppStoreConnectUpdateAlert/updateAlert.tsx +++ b/static/app/components/globalAppStoreConnectUpdateAlert/updateAlert.tsx @@ -11,7 +11,7 @@ import {IconClose, IconRefresh} from 'app/icons'; import {t} from 'app/locale'; import space from 'app/styles/space'; import {Organization, Project} from 'app/types'; -import {AppStoreConnectValidationData} from 'app/types/debugFiles'; +import {AppStoreConnectStatusData} from 'app/types/debugFiles'; import {promptIsDismissed} from 'app/utils/promptIsDismissed'; import withApi from 'app/utils/withApi'; @@ -69,14 +69,14 @@ function UpdateAlert({api, Wrapper, isCompact, project, organization, className} } function renderMessage( - appStoreConnectValidationData: AppStoreConnectValidationData, + appStoreConnectStatusData: AppStoreConnectStatusData, projectSettingsLink: string ) { - if (!appStoreConnectValidationData.updateAlertMessage) { + if (!appStoreConnectStatusData.updateAlertMessage) { return null; } - const {updateAlertMessage} = appStoreConnectValidationData; + const {updateAlertMessage} = appStoreConnectStatusData; return (
diff --git a/static/app/components/projects/appStoreConnectContext/index.tsx b/static/app/components/projects/appStoreConnectContext/index.tsx index 01e9200ea3c09a..b4dbc6f85b81fe 100644 --- a/static/app/components/projects/appStoreConnectContext/index.tsx +++ b/static/app/components/projects/appStoreConnectContext/index.tsx @@ -1,10 +1,10 @@ import {createContext, useEffect, useState} from 'react'; import {Organization, Project} from 'app/types'; -import {AppStoreConnectValidationData} from 'app/types/debugFiles'; +import {AppStoreConnectStatusData} from 'app/types/debugFiles'; import useApi from 'app/utils/useApi'; -export type AppStoreConnectContextProps = AppStoreConnectValidationData | undefined; +export type AppStoreConnectContextProps = AppStoreConnectStatusData | undefined; const AppStoreConnectContext = createContext(undefined); @@ -20,7 +20,7 @@ const Provider = ({children, project, organization}: ProviderProps) => { const api = useApi(); const [projectDetails, setProjectDetails] = useState(); - const [appStoreConnectValidationData, setAppStoreConnectValidationData] = + const [appStoreConnectStatusData, setAppStoreConnectStatusData] = useState(undefined); const orgSlug = organization.slug; @@ -30,7 +30,7 @@ const Provider = ({children, project, organization}: ProviderProps) => { }, [project]); useEffect(() => { - fetchAppStoreConnectValidationData(); + fetchAppStoreConnectStatusData(); }, [projectDetails]); async function fetchProjectDetails() { @@ -57,7 +57,7 @@ const Provider = ({children, project, organization}: ProviderProps) => { )?.id; } - async function fetchAppStoreConnectValidationData() { + async function fetchAppStoreConnectStatusData() { if (!projectDetails) { return; } @@ -71,15 +71,14 @@ const Provider = ({children, project, organization}: ProviderProps) => { } try { - const response: Map = - await api.requestPromise( - `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status` - ); + const response: Map = await api.requestPromise( + `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status` + ); - const sourceStatus: AppStoreConnectValidationData | undefined = + const sourceStatus: AppStoreConnectStatusData | undefined = response[appStoreConnectSymbolSourceId]; if (sourceStatus) { - setAppStoreConnectValidationData(sourceStatus); + setAppStoreConnectStatusData(sourceStatus); } } catch { // do nothing @@ -89,11 +88,11 @@ const Provider = ({children, project, organization}: ProviderProps) => { return ( ( +const AppStoreConnectContext = createContext( undefined ); @@ -19,12 +19,12 @@ type ProviderProps = { const Provider = withApi( withProject(({api, children, project, orgSlug}: ProviderProps) => { - const [appStoreConnectValidationData, setAppStoreConnectValidationData] = useState< - AppStoreConnectValidationData | undefined + const [appStoreConnectStatusData, setAppStoreConnectStatusData] = useState< + AppStoreConnectStatusData | undefined >(); useEffect(() => { - fetchAppStoreConnectValidationData(); + fetchAppStoreConnectStatusData(); }, [project]); function getAppStoreConnectSymbolSourceId() { @@ -33,7 +33,7 @@ const Provider = withApi( )?.id; } - async function fetchAppStoreConnectValidationData() { + async function fetchAppStoreConnectStatusData() { const appStoreConnectSymbolSourceId = getAppStoreConnectSymbolSourceId(); if (!appStoreConnectSymbolSourceId) { @@ -41,15 +41,14 @@ const Provider = withApi( } try { - const response: Map = - await api.requestPromise( - `/projects/${orgSlug}/${project.slug}/appstoreconnect/status` - ); + const response: Map = await api.requestPromise( + `/projects/${orgSlug}/${project.slug}/appstoreconnect/status` + ); - const sourceStatus: AppStoreConnectValidationData | undefined = + const sourceStatus: AppStoreConnectStatusData | undefined = response[appStoreConnectSymbolSourceId]; if (sourceStatus) { - setAppStoreConnectValidationData(sourceStatus); + setAppStoreConnectStatusData(sourceStatus); } } catch { // do nothing @@ -57,7 +56,7 @@ const Provider = withApi( } return ( - + {children} ); diff --git a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/details.tsx b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/details.tsx index 52bfc0cffa9e93..fa08eb133239fd 100644 --- a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/details.tsx +++ b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/details.tsx @@ -4,10 +4,10 @@ import DateTime from 'app/components/dateTime'; import NotAvailable from 'app/components/notAvailable'; import {t, tct} from 'app/locale'; import space from 'app/styles/space'; -import {AppStoreConnectValidationData} from 'app/types/debugFiles'; +import {AppStoreConnectStatusData} from 'app/types/debugFiles'; type Props = { - details?: AppStoreConnectValidationData; + details?: AppStoreConnectStatusData; }; function Details({details}: Props) { diff --git a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx index bc96d89ab26ba7..5372fa954ff86c 100644 --- a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx +++ b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx @@ -10,11 +10,11 @@ import {IconRefresh} from 'app/icons/iconRefresh'; import {IconWarning} from 'app/icons/iconWarning'; import {t, tn} from 'app/locale'; import space from 'app/styles/space'; -import {AppStoreConnectValidationData} from 'app/types/debugFiles'; +import {AppStoreConnectStatusData} from 'app/types/debugFiles'; type Props = { onEditRepository: () => void; - details?: AppStoreConnectValidationData; + details?: AppStoreConnectStatusData; }; function Status({details, onEditRepository}: Props) { From da31f16a12f0e1455919ff36123cdcf3e19d789f Mon Sep 17 00:00:00 2001 From: Betty Da Date: Mon, 8 Nov 2021 10:49:16 -0500 Subject: [PATCH 11/15] add trailing slash --- src/sentry/api/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 5b8d48781de1a2..264e1c426bd73c 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -2034,7 +2034,7 @@ name="sentry-api-0-project-appstoreconnect-credentials-update", ), url( - r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/status$", + r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/status/$", AppStoreConnectStatusEndpoint.as_view(), name="sentry-api-0-project-appstoreconnect-status", ), From 8473a41d0c28bf109f363997a8f87c0b8544f9d9 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Mon, 8 Nov 2021 10:57:48 -0500 Subject: [PATCH 12/15] address feedback --- .../appStoreConnect/index.tsx | 27 +++++++------------ .../appStoreConnect/utils.tsx | 2 +- .../projects/appStoreConnectContext/index.tsx | 2 +- .../projects/appStoreConnectContext/utils.tsx | 4 +-- .../project/appStoreConnectContext.tsx | 2 +- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx index 4ff403dace5c6a..7e8df29fe539dc 100644 --- a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx +++ b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/index.tsx @@ -113,7 +113,7 @@ function AppStoreConnect({ const appStoreConnnectError = getAppStoreErrorMessage(error); if (typeof appStoreConnnectError === 'string') { // app-connect-authentication-error - // 'app-connect-forbidden-error' + // app-connect-forbidden-error addErrorMessage(appStoreConnnectError); return; } @@ -239,26 +239,17 @@ function AppStoreConnect({ return alerts; } if (credentials?.status === 'invalid') { - switch (credentials.code) { - case 'app-connect-forbidden-error': - alerts.push( - }> - {t( + alerts.push( + }> + {credentials.code === 'app-connect-forbidden-error' + ? t( 'Your App Store Connect credentials have insufficient permissions. To reconnect, update your credentials.' - )} - - ); - break; - case 'app-connect-authentication-error': - default: - alerts.push( - }> - {t( + ) + : t( 'Your App Store Connect credentials are invalid. To reconnect, update your credentials.' )} - - ); - } + + ); } return alerts; diff --git a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx index a291c4b0b1d161..d7c68e4e701175 100644 --- a/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx +++ b/static/app/components/modals/debugFileCustomRepository/appStoreConnect/utils.tsx @@ -113,7 +113,7 @@ export function getAppStoreValidationErrorMessage( switch (error.code) { case 'app-connect-authentication-error': return t( - 'Credentials are invalid, missing, or expired. Check the entered App Store Connect credentials are correct and have not expired.' + 'Credentials are invalid or missing. Check the entered App Store Connect credentials are correct.' ); case 'app-connect-forbidden-error': return t('The supplied API key does not have sufficient permissions.'); diff --git a/static/app/components/projects/appStoreConnectContext/index.tsx b/static/app/components/projects/appStoreConnectContext/index.tsx index b4dbc6f85b81fe..71575e29f30cd7 100644 --- a/static/app/components/projects/appStoreConnectContext/index.tsx +++ b/static/app/components/projects/appStoreConnectContext/index.tsx @@ -72,7 +72,7 @@ const Provider = ({children, project, organization}: ProviderProps) => { try { const response: Map = await api.requestPromise( - `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status` + `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status/` ); const sourceStatus: AppStoreConnectStatusData | undefined = diff --git a/static/app/components/projects/appStoreConnectContext/utils.tsx b/static/app/components/projects/appStoreConnectContext/utils.tsx index 4bc8a551dbd9db..426f699e64f352 100644 --- a/static/app/components/projects/appStoreConnectContext/utils.tsx +++ b/static/app/components/projects/appStoreConnectContext/utils.tsx @@ -6,13 +6,13 @@ import {AppStoreConnectCredentialsStatus} from 'app/types/debugFiles'; export function areAppStoreConnectCredentialsValid( credentialsStatus: AppStoreConnectCredentialsStatus | undefined -): boolean { +) { return credentialsStatus?.status === 'valid'; } export function getAppConnectStoreUpdateAlertMessage( credentialsStatus: AppStoreConnectCredentialsStatus -): string | undefined { +) { if (areAppStoreConnectCredentialsValid(credentialsStatus)) { return undefined; } diff --git a/static/app/views/settings/project/appStoreConnectContext.tsx b/static/app/views/settings/project/appStoreConnectContext.tsx index 139a9fb75922d0..c0fc35b0d2bd2d 100644 --- a/static/app/views/settings/project/appStoreConnectContext.tsx +++ b/static/app/views/settings/project/appStoreConnectContext.tsx @@ -42,7 +42,7 @@ const Provider = withApi( try { const response: Map = await api.requestPromise( - `/projects/${orgSlug}/${project.slug}/appstoreconnect/status` + `/projects/${orgSlug}/${project.slug}/appstoreconnect/status/` ); const sourceStatus: AppStoreConnectStatusData | undefined = From d8e494ef582740bb072ef497fae0376c8e46df4d Mon Sep 17 00:00:00 2001 From: Betty Da Date: Mon, 8 Nov 2021 13:17:53 -0500 Subject: [PATCH 13/15] remove nested ID in payload --- .../project_app_store_connect_credentials.py | 13 +++++-------- .../projects/appStoreConnectContext/index.tsx | 7 +++++-- .../settings/project/appStoreConnectContext.tsx | 7 +++++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/sentry/api/endpoints/project_app_store_connect_credentials.py b/src/sentry/api/endpoints/project_app_store_connect_credentials.py index a2f36dfb7c9268..3bd22b63f18841 100644 --- a/src/sentry/api/endpoints/project_app_store_connect_credentials.py +++ b/src/sentry/api/endpoints/project_app_store_connect_credentials.py @@ -421,21 +421,19 @@ class AppStoreConnectStatusEndpoint(ProjectEndpoint): # type: ignore """Returns a summary of the project's App Store Connect configuration and builds. - ``GET projects/{org_slug}/{proj_slug}/appstoreconnect/status`` + ``GET projects/{org_slug}/{proj_slug}/appstoreconnect/status/`` Response: ```json - [ - { - "id": "abc123", + { + "abc123": { "credentials": { status: "valid" } | { status: "invalid", code: "app-connect-forbidden-error" }, "pendingDownloads": 123, "latestBuildVersion: "9.8.7" | null, "latestBuildNumber": "987000" | null, "lastCheckedBuilds": "YYYY-MM-DDTHH:MM:SS.SSSSSSZ" | null }, - { - "id": ..., + "...": { "credentials": ..., "pendingDownloads": ..., "latestBuildVersion: ..., @@ -443,7 +441,7 @@ class AppStoreConnectStatusEndpoint(ProjectEndpoint): # type: ignore "lastCheckedBuilds": ... }, ... - ] + } ``` * ``pendingDownloads`` is the number of pending build dSYM downloads. @@ -525,7 +523,6 @@ def get(self, request: Request, project: Project) -> Response: last_checked_builds = check_entry.last_checked statuses[config_id] = { - "id": config_id, "credentials": asc_credentials, "pendingDownloads": pending_downloads, "latestBuildVersion": latestBuildVersion, diff --git a/static/app/components/projects/appStoreConnectContext/index.tsx b/static/app/components/projects/appStoreConnectContext/index.tsx index 71575e29f30cd7..66f9614a16112c 100644 --- a/static/app/components/projects/appStoreConnectContext/index.tsx +++ b/static/app/components/projects/appStoreConnectContext/index.tsx @@ -75,10 +75,13 @@ const Provider = ({children, project, organization}: ProviderProps) => { `/projects/${orgSlug}/${projectDetails.slug}/appstoreconnect/status/` ); - const sourceStatus: AppStoreConnectStatusData | undefined = + const sourceStatus: Omit | undefined = response[appStoreConnectSymbolSourceId]; if (sourceStatus) { - setAppStoreConnectStatusData(sourceStatus); + setAppStoreConnectStatusData({ + ...sourceStatus, + id: appStoreConnectSymbolSourceId, + }); } } catch { // do nothing diff --git a/static/app/views/settings/project/appStoreConnectContext.tsx b/static/app/views/settings/project/appStoreConnectContext.tsx index c0fc35b0d2bd2d..af17d22439f80e 100644 --- a/static/app/views/settings/project/appStoreConnectContext.tsx +++ b/static/app/views/settings/project/appStoreConnectContext.tsx @@ -45,10 +45,13 @@ const Provider = withApi( `/projects/${orgSlug}/${project.slug}/appstoreconnect/status/` ); - const sourceStatus: AppStoreConnectStatusData | undefined = + const sourceStatus: Omit | undefined = response[appStoreConnectSymbolSourceId]; if (sourceStatus) { - setAppStoreConnectStatusData(sourceStatus); + setAppStoreConnectStatusData({ + ...sourceStatus, + id: appStoreConnectSymbolSourceId, + }); } } catch { // do nothing From cc9628402c75f880bed4c959df259f1f2a374c93 Mon Sep 17 00:00:00 2001 From: Betty Da Date: Mon, 8 Nov 2021 13:23:02 -0500 Subject: [PATCH 14/15] just directly check for validity --- .../components/projects/appStoreConnectContext/utils.tsx | 8 +------- .../externalSources/customRepositories/status.tsx | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/static/app/components/projects/appStoreConnectContext/utils.tsx b/static/app/components/projects/appStoreConnectContext/utils.tsx index 426f699e64f352..ce76669eeafd0b 100644 --- a/static/app/components/projects/appStoreConnectContext/utils.tsx +++ b/static/app/components/projects/appStoreConnectContext/utils.tsx @@ -4,16 +4,10 @@ import { } from 'app/components/modals/debugFileCustomRepository/appStoreConnect/utils'; import {AppStoreConnectCredentialsStatus} from 'app/types/debugFiles'; -export function areAppStoreConnectCredentialsValid( - credentialsStatus: AppStoreConnectCredentialsStatus | undefined -) { - return credentialsStatus?.status === 'valid'; -} - export function getAppConnectStoreUpdateAlertMessage( credentialsStatus: AppStoreConnectCredentialsStatus ) { - if (areAppStoreConnectCredentialsValid(credentialsStatus)) { + if (credentialsStatus?.status === 'valid') { return undefined; } return getAppStoreValidationErrorMessage(credentialsStatus as ValidationErrorDetailed); diff --git a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx index 5372fa954ff86c..240ddccfed054c 100644 --- a/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx +++ b/static/app/views/settings/projectDebugFiles/externalSources/customRepositories/status.tsx @@ -2,7 +2,6 @@ import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import Placeholder from 'app/components/placeholder'; -import {areAppStoreConnectCredentialsValid} from 'app/components/projects/appStoreConnectContext/utils'; import TimeSince from 'app/components/timeSince'; import Tooltip from 'app/components/tooltip'; import {IconDownload} from 'app/icons/iconDownload'; @@ -26,7 +25,7 @@ function Status({details, onEditRepository}: Props) { const {pendingDownloads, credentials, lastCheckedBuilds} = details ?? {}; - if (areAppStoreConnectCredentialsValid(credentials) === false) { + if (credentials?.status === 'invalid') { return ( Date: Mon, 8 Nov 2021 19:08:55 -0500 Subject: [PATCH 15/15] fix endpoint --- src/sentry/api/urls.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 264e1c426bd73c..a37ec19be0f10a 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -2028,16 +2028,16 @@ AppStoreConnectCredentialsValidateEndpoint.as_view(), name="sentry-api-0-project-appstoreconnect-validate", ), - url( - r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/(?P[^\/]+)/$", - AppStoreConnectUpdateCredentialsEndpoint.as_view(), - name="sentry-api-0-project-appstoreconnect-credentials-update", - ), url( r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/status/$", AppStoreConnectStatusEndpoint.as_view(), name="sentry-api-0-project-appstoreconnect-status", ), + url( + r"^(?P[^\/]+)/(?P[^\/]+)/appstoreconnect/(?P[^\/]+)/$", + AppStoreConnectUpdateCredentialsEndpoint.as_view(), + name="sentry-api-0-project-appstoreconnect-credentials-update", + ), ] ), ),