From 435ad79af3f27c97a0598e90301d042a2b9a6acb Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Wed, 29 Oct 2025 13:24:28 -0700 Subject: [PATCH 1/3] fix(eco): Allow superusers to view integration configuration Currently blocks the link and redirects you away. Let me see it. --- .../configureIntegration.tsx | 7 +- .../installedIntegration.tsx | 8 +- .../integrationDetailedView.tsx | 81 ++++++++++--------- 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/static/app/views/settings/organizationIntegrations/configureIntegration.tsx b/static/app/views/settings/organizationIntegrations/configureIntegration.tsx index 61f45b8a3ddf45..54d4402660bb0b 100644 --- a/static/app/views/settings/organizationIntegrations/configureIntegration.tsx +++ b/static/app/views/settings/organizationIntegrations/configureIntegration.tsx @@ -25,6 +25,7 @@ import type { } from 'sentry/types/integrations'; import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import type {Organization} from 'sentry/types/organization'; +import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser'; import {singleLineRenderer} from 'sentry/utils/marked/marked'; import type {ApiQueryKey} from 'sentry/utils/queryClient'; import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient'; @@ -127,7 +128,11 @@ function ConfigureIntegration({params, router, routes, location}: Props) { useEffect(() => { // This page should not be accessible by members (unless its github or gitlab) const allowMemberConfiguration = ['github', 'gitlab'].includes(providerKey); - if (!allowMemberConfiguration && !organization.access.includes('org:integrations')) { + if ( + !allowMemberConfiguration && + !organization.access.includes('org:integrations') && + !isActiveSuperuser() + ) { router.push( normalizeUrl({ pathname: `/settings/${organization.slug}/integrations/${providerKey}/`, diff --git a/static/app/views/settings/organizationIntegrations/installedIntegration.tsx b/static/app/views/settings/organizationIntegrations/installedIntegration.tsx index 60b8cfbf89481b..8ffda1c6b53d58 100644 --- a/static/app/views/settings/organizationIntegrations/installedIntegration.tsx +++ b/static/app/views/settings/organizationIntegrations/installedIntegration.tsx @@ -17,6 +17,7 @@ import type {Integration, IntegrationProvider} from 'sentry/types/integrations'; import type {Organization} from 'sentry/types/organization'; import type {IntegrationAnalyticsKey} from 'sentry/utils/analytics/integrations'; import {getIntegrationStatus} from 'sentry/utils/integrationUtil'; +import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser'; import {AddIntegrationButton} from './addIntegrationButton'; import IntegrationItem from './integrationItem'; @@ -114,6 +115,9 @@ export default class InstalledIntegration extends Component { return ( {({hasAccess}) => { + const superuser = isActiveSuperuser(); + const canConfigure = + (hasAccess || superuser) && this.integrationStatus === 'active'; const disableAction = !(hasAccess && this.integrationStatus === 'active'); return ( @@ -122,7 +126,7 @@ export default class InstalledIntegration extends Component {
{ } - disabled={!allowMemberConfiguration && disableAction} + disabled={!allowMemberConfiguration && !canConfigure} to={`/settings/${organization.slug}/integrations/${provider.key}/${integration.id}/`} data-test-id="integration-configure-button" > diff --git a/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx b/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx index 14cf30e940a5ce..f515e0df0b925d 100644 --- a/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx +++ b/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx @@ -12,6 +12,7 @@ import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Panel from 'sentry/components/panels/panel'; import PanelItem from 'sentry/components/panels/panelItem'; +import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t} from 'sentry/locale'; import {PluginIcon} from 'sentry/plugins/components/pluginIcon'; import {space} from 'sentry/styles/space'; @@ -556,47 +557,49 @@ export default function IntegrationDetailedView() { } return ( - } - topSection={ - } - addInstallButton={ - - } - additionalCTA={ - - } - /> - } - tabs={renderTabs()} - content={ - activeTab === 'overview' ? ( - + } + topSection={ + } + addInstallButton={ + + } + additionalCTA={ + + } /> - ) : activeTab === 'configurations' ? ( - renderConfigurations() - ) : ( - renderFeatures() - ) - } - /> + } + tabs={renderTabs()} + content={ + activeTab === 'overview' ? ( + + ) : activeTab === 'configurations' ? ( + renderConfigurations() + ) : ( + renderFeatures() + ) + } + /> + ); } From fcb297816ca48738dcc4a6fdea5953997447a6a3 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Wed, 29 Oct 2025 14:26:57 -0700 Subject: [PATCH 2/3] feat(issues): Display resolution integration info displays integration activity information if the activity came from an activity --- static/app/types/group.tsx | 8 +++ .../sidebar/activitySection.spec.tsx | 48 +++++++++++++++++ .../streamline/sidebar/groupActivityItem.tsx | 52 ++++++++++++++----- 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/static/app/types/group.tsx b/static/app/types/group.tsx index 0a592868a53b4b..65b1c40078a7d5 100644 --- a/static/app/types/group.tsx +++ b/static/app/types/group.tsx @@ -617,12 +617,20 @@ interface GroupActivitySetByResolvedInNextSemverRelease extends GroupActivityBas data: { // Set for semver releases current_release_version: string; + inNextRelease?: boolean; + integration_id?: number; + provider?: string; + provider_key?: string; }; type: GroupActivityType.SET_RESOLVED_IN_RELEASE; } interface GroupActivitySetByResolvedInRelease extends GroupActivityBase { data: { + inNextRelease?: boolean; + integration_id?: number; + provider?: string; + provider_key?: string; version?: string; }; type: GroupActivityType.SET_RESOLVED_IN_RELEASE; diff --git a/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx b/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx index 3268437c163d87..05143223bf9aa9 100644 --- a/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx @@ -329,4 +329,52 @@ describe('StreamlinedActivitySection', () => { } } }); + + it('renders resolved in release with integration', async () => { + const resolvedGroup = GroupFixture({ + id: '1339', + activity: [ + { + type: GroupActivityType.SET_RESOLVED_IN_RELEASE, + id: 'resolved-in-release-1', + dateCreated: '2020-01-01T00:00:00', + data: { + version: 'frontend@1.0.0', + integration_id: 408, + provider: 'Jira Server', + provider_key: 'jira_server', + }, + user, + }, + ], + project, + }); + + render(); + expect(await screen.findByText('Resolved')).toBeInTheDocument(); + expect(screen.getByRole('link', {name: '1.0.0'})).toBeInTheDocument(); + expect(screen.getByRole('link', {name: 'Jira Server'})).toBeInTheDocument(); + }); + + it('renders resolved in release without integration', async () => { + const resolvedGroup = GroupFixture({ + id: '1340', + activity: [ + { + type: GroupActivityType.SET_RESOLVED_IN_RELEASE, + id: 'resolved-in-release-2', + dateCreated: '2020-01-01T00:00:00', + data: { + version: 'frontend@1.0.0', + }, + user, + }, + ], + project, + }); + + render(); + expect(await screen.findByText('Resolved')).toBeInTheDocument(); + expect(screen.getByRole('link', {name: '1.0.0'})).toBeInTheDocument(); + }); }); diff --git a/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx b/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx index 52df2a49492630..c77abd083d01d9 100644 --- a/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx @@ -284,30 +284,54 @@ export default function getGroupActivityItem( }), }; case GroupActivityType.SET_RESOLVED_IN_RELEASE: { - // Resolved in the next release + const hasIntegration = + 'integration_id' in activity.data && activity.data.integration_id; + const integrationLink = hasIntegration ? ( + + {activity.data.provider} + + ) : null; + const viaIntegration = hasIntegration + ? tct(' via [integration]', {integration: integrationLink}) + : ''; + if ('current_release_version' in activity.data) { const currentVersion = activity.data.current_release_version; return { title: t('Resolved'), - message: tct('by [author] in releases greater than [version] [semver]', { + message: tct( + 'by [author] in releases greater than [version] [semver][viaIntegration]', + { + author, + version: , + semver: isSemverRelease(currentVersion) + ? t('(semver)') + : t('(non-semver)'), + viaIntegration, + } + ), + }; + } + const version = activity.data.version; + if (version) { + return { + title: t('Resolved'), + message: tct('by [author] in [version] [semver][viaIntegration]', { author, - version: , - semver: isSemverRelease(currentVersion) ? t('(semver)') : t('(non-semver)'), + version: , + semver: isSemverRelease(version) ? t('(semver)') : t('(non-semver)'), + viaIntegration, }), }; } - const version = activity.data.version; return { title: t('Resolved'), - message: version - ? tct('by [author] in [version] [semver]', { - author, - version: , - semver: isSemverRelease(version) ? t('(semver)') : t('(non-semver)'), - }) - : tct('by [author] in the upcoming release', { - author, - }), + message: tct('by [author] in the upcoming release[viaIntegration]', { + author, + viaIntegration, + }), }; } case GroupActivityType.SET_RESOLVED_IN_COMMIT: { From b1316f88f3aa6a479874bd652d343e3cb6e4f3a6 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Wed, 29 Oct 2025 14:27:49 -0700 Subject: [PATCH 3/3] Revert "feat(issues): Display resolution integration info" This reverts commit fcb297816ca48738dcc4a6fdea5953997447a6a3. --- static/app/types/group.tsx | 8 --- .../sidebar/activitySection.spec.tsx | 48 ----------------- .../streamline/sidebar/groupActivityItem.tsx | 52 +++++-------------- 3 files changed, 14 insertions(+), 94 deletions(-) diff --git a/static/app/types/group.tsx b/static/app/types/group.tsx index 65b1c40078a7d5..0a592868a53b4b 100644 --- a/static/app/types/group.tsx +++ b/static/app/types/group.tsx @@ -617,20 +617,12 @@ interface GroupActivitySetByResolvedInNextSemverRelease extends GroupActivityBas data: { // Set for semver releases current_release_version: string; - inNextRelease?: boolean; - integration_id?: number; - provider?: string; - provider_key?: string; }; type: GroupActivityType.SET_RESOLVED_IN_RELEASE; } interface GroupActivitySetByResolvedInRelease extends GroupActivityBase { data: { - inNextRelease?: boolean; - integration_id?: number; - provider?: string; - provider_key?: string; version?: string; }; type: GroupActivityType.SET_RESOLVED_IN_RELEASE; diff --git a/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx b/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx index 05143223bf9aa9..3268437c163d87 100644 --- a/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/activitySection.spec.tsx @@ -329,52 +329,4 @@ describe('StreamlinedActivitySection', () => { } } }); - - it('renders resolved in release with integration', async () => { - const resolvedGroup = GroupFixture({ - id: '1339', - activity: [ - { - type: GroupActivityType.SET_RESOLVED_IN_RELEASE, - id: 'resolved-in-release-1', - dateCreated: '2020-01-01T00:00:00', - data: { - version: 'frontend@1.0.0', - integration_id: 408, - provider: 'Jira Server', - provider_key: 'jira_server', - }, - user, - }, - ], - project, - }); - - render(); - expect(await screen.findByText('Resolved')).toBeInTheDocument(); - expect(screen.getByRole('link', {name: '1.0.0'})).toBeInTheDocument(); - expect(screen.getByRole('link', {name: 'Jira Server'})).toBeInTheDocument(); - }); - - it('renders resolved in release without integration', async () => { - const resolvedGroup = GroupFixture({ - id: '1340', - activity: [ - { - type: GroupActivityType.SET_RESOLVED_IN_RELEASE, - id: 'resolved-in-release-2', - dateCreated: '2020-01-01T00:00:00', - data: { - version: 'frontend@1.0.0', - }, - user, - }, - ], - project, - }); - - render(); - expect(await screen.findByText('Resolved')).toBeInTheDocument(); - expect(screen.getByRole('link', {name: '1.0.0'})).toBeInTheDocument(); - }); }); diff --git a/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx b/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx index c77abd083d01d9..52df2a49492630 100644 --- a/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/groupActivityItem.tsx @@ -284,54 +284,30 @@ export default function getGroupActivityItem( }), }; case GroupActivityType.SET_RESOLVED_IN_RELEASE: { - const hasIntegration = - 'integration_id' in activity.data && activity.data.integration_id; - const integrationLink = hasIntegration ? ( - - {activity.data.provider} - - ) : null; - const viaIntegration = hasIntegration - ? tct(' via [integration]', {integration: integrationLink}) - : ''; - + // Resolved in the next release if ('current_release_version' in activity.data) { const currentVersion = activity.data.current_release_version; return { title: t('Resolved'), - message: tct( - 'by [author] in releases greater than [version] [semver][viaIntegration]', - { - author, - version: , - semver: isSemverRelease(currentVersion) - ? t('(semver)') - : t('(non-semver)'), - viaIntegration, - } - ), - }; - } - const version = activity.data.version; - if (version) { - return { - title: t('Resolved'), - message: tct('by [author] in [version] [semver][viaIntegration]', { + message: tct('by [author] in releases greater than [version] [semver]', { author, - version: , - semver: isSemverRelease(version) ? t('(semver)') : t('(non-semver)'), - viaIntegration, + version: , + semver: isSemverRelease(currentVersion) ? t('(semver)') : t('(non-semver)'), }), }; } + const version = activity.data.version; return { title: t('Resolved'), - message: tct('by [author] in the upcoming release[viaIntegration]', { - author, - viaIntegration, - }), + message: version + ? tct('by [author] in [version] [semver]', { + author, + version: , + semver: isSemverRelease(version) ? t('(semver)') : t('(non-semver)'), + }) + : tct('by [author] in the upcoming release', { + author, + }), }; } case GroupActivityType.SET_RESOLVED_IN_COMMIT: {