Skip to content

Commit 0ce7844

Browse files
oiokiandrewshie-sentry
authored andcommitted
fix(plugins): show Configure/View on legacy plugin depending on project access (#90881)
Unlike integrations, plugins (legacy integrations) have project level access. This PR adjusts UI to reflect that considering team level roles as well. Affects two views: - projectPlugins: http://localhost:8000/settings/abc/projects/python-flask/plugins/ - pluginDetailedView: http://localhost:8000/settings/abc/plugins/redmine/?tab=configurations This is not a security fix but a frontend bug fix. # Example A member has these team-level roles: <img width="656" alt="image" src="https://github.com/user-attachments/assets/b2f439d4-c51d-4b7b-a7a2-342fb480cfd1" /> ## projectPlugins **Before.** There is no link on "Configure plugin". <img width="897" alt="image" src="https://github.com/user-attachments/assets/1abfb051-4cea-4b6e-a272-fe2c579bf554" /> **After.** Team contributor has read project-level access to `python-flask` project, hence "View plugin": <img width="897" alt="image" src="https://github.com/user-attachments/assets/82be309f-6d12-4731-a81b-65a7cfdadfee" /> ## pluginDetailedView **Before.** Both "Configure" button links are unavailable: <img width="897" alt="image" src="https://github.com/user-attachments/assets/171d1f33-d919-48c8-9086-68e74713a12e" /> **After.** "Configure" is shown as the user has write permissions on `go` project, and "View" because of the read permissions on `python-flask` project. <img width="897" alt="image" src="https://github.com/user-attachments/assets/5417cdae-562a-4d2c-93f7-9c5dde45594d" />
1 parent 9074abc commit 0ce7844

File tree

3 files changed

+54
-52
lines changed

3 files changed

+54
-52
lines changed

static/app/views/settings/organizationIntegrations/installedPlugin.tsx

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
addSuccessMessage,
88
} from 'sentry/actionCreators/indicator';
99
import type {Client} from 'sentry/api';
10-
import Access from 'sentry/components/acl/access';
1110
import Confirm from 'sentry/components/confirm';
1211
import {Alert} from 'sentry/components/core/alert';
1312
import {Button, LinkButton} from 'sentry/components/core/button';
@@ -31,6 +30,7 @@ type Props = {
3130
projectItem: PluginProjectItem;
3231
trackIntegrationAnalytics: (eventKey: IntegrationAnalyticsKey) => void; // analytics callback
3332
className?: string;
33+
hasAccess?: boolean;
3434
};
3535

3636
class InstalledPlugin extends Component<Props> {
@@ -120,55 +120,50 @@ class InstalledPlugin extends Component<Props> {
120120
}
121121

122122
render() {
123-
const {className, plugin, organization, projectItem} = this.props;
123+
const {className, plugin, organization, hasAccess, projectItem} = this.props;
124124
return (
125125
<Container data-test-id="installed-plugin">
126-
<Access access={['org:integrations']}>
127-
{({hasAccess}) => (
128-
<IntegrationFlex className={className}>
129-
<IntegrationItemBox>
130-
<ProjectBadge project={this.projectForBadge} />
131-
</IntegrationItemBox>
132-
<div>
133-
<StyledLinkButton
134-
borderless
135-
icon={<IconSettings />}
136-
disabled={!hasAccess}
137-
to={`/settings/${organization.slug}/projects/${projectItem.projectSlug}/plugins/${plugin.id}/`}
138-
data-test-id="integration-configure-button"
139-
>
140-
{t('Configure')}
141-
</StyledLinkButton>
142-
</div>
143-
<div>
144-
<Confirm
145-
priority="danger"
146-
onConfirming={this.handleUninstallClick}
147-
disabled={!hasAccess}
148-
confirmText="Delete Installation"
149-
onConfirm={() => this.handleReset()}
150-
message={this.getConfirmMessage()}
151-
>
152-
<StyledButton
153-
disabled={!hasAccess}
154-
borderless
155-
icon={<IconDelete />}
156-
data-test-id="integration-remove-button"
157-
>
158-
{t('Uninstall')}
159-
</StyledButton>
160-
</Confirm>
161-
</div>
162-
<Switch
163-
checked={projectItem.enabled}
164-
onChange={() =>
165-
this.toggleEnablePlugin(projectItem.projectId, !projectItem.enabled)
166-
}
126+
<IntegrationFlex className={className}>
127+
<IntegrationItemBox>
128+
<ProjectBadge project={this.projectForBadge} />
129+
</IntegrationItemBox>
130+
<div>
131+
<StyledLinkButton
132+
borderless
133+
icon={<IconSettings />}
134+
to={`/settings/${organization.slug}/projects/${projectItem.projectSlug}/plugins/${plugin.id}/`}
135+
data-test-id="integration-configure-button"
136+
>
137+
{hasAccess ? t('Configure') : t('View')}
138+
</StyledLinkButton>
139+
</div>
140+
<div>
141+
<Confirm
142+
priority="danger"
143+
onConfirming={this.handleUninstallClick}
144+
disabled={!hasAccess}
145+
confirmText="Delete Installation"
146+
onConfirm={() => this.handleReset()}
147+
message={this.getConfirmMessage()}
148+
>
149+
<StyledButton
167150
disabled={!hasAccess}
168-
/>
169-
</IntegrationFlex>
170-
)}
171-
</Access>
151+
borderless
152+
icon={<IconDelete />}
153+
data-test-id="integration-remove-button"
154+
>
155+
{t('Uninstall')}
156+
</StyledButton>
157+
</Confirm>
158+
</div>
159+
<Switch
160+
checked={projectItem.enabled}
161+
onChange={() =>
162+
this.toggleEnablePlugin(projectItem.projectId, !projectItem.enabled)
163+
}
164+
disabled={!hasAccess}
165+
/>
166+
</IntegrationFlex>
172167
</Container>
173168
);
174169
}

static/app/views/settings/organizationIntegrations/pluginDetailedView.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {Fragment, useCallback, useEffect, useMemo} from 'react';
22
import styled from '@emotion/styled';
33

44
import {openModal} from 'sentry/actionCreators/modal';
5+
import {hasEveryAccess} from 'sentry/components/acl/access';
56
import ContextPickerModal from 'sentry/components/contextPickerModal';
67
import {Button} from 'sentry/components/core/button';
78
import LoadingError from 'sentry/components/loadingError';
@@ -25,6 +26,7 @@ import normalizeUrl from 'sentry/utils/url/normalizeUrl';
2526
import {useNavigate} from 'sentry/utils/useNavigate';
2627
import useOrganization from 'sentry/utils/useOrganization';
2728
import {useParams} from 'sentry/utils/useParams';
29+
import useProjects from 'sentry/utils/useProjects';
2830
import withOrganization from 'sentry/utils/withOrganization';
2931
import {
3032
INSTALLED,
@@ -58,6 +60,8 @@ function PluginDetailedView() {
5860
const navigate = useNavigate();
5961
const {integrationSlug} = useParams<{integrationSlug: string}>();
6062

63+
const {projects} = useProjects();
64+
6165
const {data: plugins, isPending} = useApiQuery<PluginWithProjectList[]>(
6266
makePluginQueryKey({orgSlug: organization.slug, pluginSlug: integrationSlug}),
6367
{staleTime: Infinity, retry: false}
@@ -248,6 +252,10 @@ function PluginDetailedView() {
248252
key={projectItem.projectId}
249253
organization={organization}
250254
plugin={plugin}
255+
hasAccess={hasEveryAccess(['project:write'], {
256+
organization,
257+
project: projects.find(p => p.id === projectItem.projectId.toString()),
258+
})}
251259
projectItem={projectItem}
252260
onResetConfiguration={handleResetConfiguration}
253261
onPluginEnableStatusChange={handlePluginEnableStatus}
@@ -288,6 +296,7 @@ function PluginDetailedView() {
288296
organization,
289297
plugin,
290298
renderTopButton,
299+
projects,
291300
]);
292301

293302
if (isPending) {

static/app/views/settings/projectPlugins/projectPluginRow.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ class ProjectPluginRow extends PureComponent<Props> {
5858
return (
5959
<Access access={['project:write']} project={project}>
6060
{({hasAccess}) => {
61-
const LinkOrSpan = hasAccess ? Link : 'span';
62-
6361
return (
6462
<PluginItem key={id} className={slug}>
6563
<PluginInfo>
@@ -84,9 +82,9 @@ class ProjectPluginRow extends PureComponent<Props> {
8482
<span>
8583
{' '}
8684
&middot;{' '}
87-
<LinkOrSpan css={grayText} to={configureUrl}>
88-
{t('Configure plugin')}
89-
</LinkOrSpan>
85+
<Link css={grayText} to={configureUrl}>
86+
{hasAccess ? t('Configure plugin') : t('View plugin')}
87+
</Link>
9088
</span>
9189
)}
9290
</div>

0 commit comments

Comments
 (0)