From 2c428b1675e331cf2e6ed88db7e81b49eb09a7d7 Mon Sep 17 00:00:00 2001 From: schew2381 Date: Fri, 11 Aug 2023 13:59:31 -0700 Subject: [PATCH 1/6] Ref(api): Update create user token page to use dropdowns --- static/app/constants/index.tsx | 11 ---- .../views/settings/account/apiNewToken.tsx | 57 +++++++++++++------ 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/static/app/constants/index.tsx b/static/app/constants/index.tsx index 753379b6298a6d..f8608f1af9cdf2 100644 --- a/static/app/constants/index.tsx +++ b/static/app/constants/index.tsx @@ -54,17 +54,6 @@ export const API_ACCESS_SCOPES = [ 'team:write', ] as const; -// Default API scopes when adding a new API token or org API token -export const DEFAULT_API_ACCESS_SCOPES = [ - 'event:admin', - 'event:read', - 'member:read', - 'org:read', - 'project:read', - 'project:releases', - 'team:read', -]; - // These should only be used in the case where we cannot obtain roles through // the members endpoint (primarily in cases where a user is admining a // different organization they are not a OrganizationMember of ). diff --git a/static/app/views/settings/account/apiNewToken.tsx b/static/app/views/settings/account/apiNewToken.tsx index 9a32c5b46d4a4f..e422dc7c53979e 100644 --- a/static/app/views/settings/account/apiNewToken.tsx +++ b/static/app/views/settings/account/apiNewToken.tsx @@ -2,23 +2,38 @@ import {Component} from 'react'; import {browserHistory} from 'react-router'; import ApiForm from 'sentry/components/forms/apiForm'; -import MultipleCheckbox from 'sentry/components/forms/controls/multipleCheckbox'; -import FormField from 'sentry/components/forms/formField'; import ExternalLink from 'sentry/components/links/externalLink'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import PanelHeader from 'sentry/components/panels/panelHeader'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; -import {API_ACCESS_SCOPES, DEFAULT_API_ACCESS_SCOPES} from 'sentry/constants'; import {t, tct} from 'sentry/locale'; +import {Permissions} from 'sentry/types'; import {normalizeUrl} from 'sentry/utils/withDomainRequired'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import TextBlock from 'sentry/views/settings/components/text/textBlock'; +import PermissionSelection from 'sentry/views/settings/organizationDeveloperSettings/permissionSelection'; -const SORTED_DEFAULT_API_ACCESS_SCOPES = DEFAULT_API_ACCESS_SCOPES.sort(); const API_INDEX_ROUTE = '/settings/account/api/auth-tokens/'; +type State = { + permissions: Permissions; +}; + +export default class ApiNewToken extends Component<{}, State> { + constructor(props: {}) { + super(props); + this.state = { + permissions: { + Event: 'no-access', + Team: 'no-access', + Member: 'no-access', + Project: 'no-access', + Release: 'no-access', + Organization: 'no-access', + }, + }; + } -export default class ApiNewToken extends Component { onCancel = () => { browserHistory.push(normalizeUrl(API_INDEX_ROUTE)); }; @@ -46,31 +61,37 @@ export default class ApiNewToken extends Component { )} - {t('Create New User Auth Token')} + {t('Permissions')} value === 'no-access' + )} submitLabel={t('Create Token')} > - - {({name, value, onChange}) => ( - - {API_ACCESS_SCOPES.map(scope => ( - - {scope} - - ))} - - )} - + { + this.setState({permissions: value}); + }} + /> From e0354e3c7e18d7afabf28d9c099602ace62ab356 Mon Sep 17 00:00:00 2001 From: schew2381 Date: Tue, 19 Sep 2023 14:51:38 -0700 Subject: [PATCH 2/6] add org:integrations to admin and remove redundant acceptance test --- src/sentry/models/apiscopes.py | 1 + static/app/constants/index.tsx | 5 +- .../settings/account/apiNewToken.spec.tsx | 66 ++++++++++++++++++- tests/acceptance/test_api.py | 23 ------- 4 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/sentry/models/apiscopes.py b/src/sentry/models/apiscopes.py index 3a9a9d29b37f09..736bbf073ba61b 100644 --- a/src/sentry/models/apiscopes.py +++ b/src/sentry/models/apiscopes.py @@ -62,6 +62,7 @@ class Meta: "org:read": bool, "org:write": bool, "org:admin": bool, + "org:integrations": bool, "member:read": bool, "member:write": bool, "member:admin": bool, diff --git a/static/app/constants/index.tsx b/static/app/constants/index.tsx index f8608f1af9cdf2..5cce66adb6c0a8 100644 --- a/static/app/constants/index.tsx +++ b/static/app/constants/index.tsx @@ -157,7 +157,10 @@ export const SENTRY_APP_PERMISSIONS: PermissionObj[] = [ 'no-access': {label: 'No Access', scopes: []}, read: {label: 'Read', scopes: ['org:read']}, write: {label: 'Read & Write', scopes: ['org:read', 'org:write']}, - admin: {label: 'Admin', scopes: ['org:read', 'org:write', 'org:admin']}, + admin: { + label: 'Admin', + scopes: ['org:read', 'org:write', 'org:admin', 'org:integrations'], + }, }, }, { diff --git a/static/app/views/settings/account/apiNewToken.spec.tsx b/static/app/views/settings/account/apiNewToken.spec.tsx index 34df4454843a09..061c7459f29694 100644 --- a/static/app/views/settings/account/apiNewToken.spec.tsx +++ b/static/app/views/settings/account/apiNewToken.spec.tsx @@ -1,4 +1,6 @@ -import {render} from 'sentry-test/reactTestingLibrary'; +import selectEvent from 'react-select-event'; + +import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; import ApiNewToken from 'sentry/views/settings/account/apiNewToken'; @@ -8,4 +10,66 @@ describe('ApiNewToken', function () { context: TestStubs.routerContext(), }); }); + + it('renders with disabled "Create Token" button', async function () { + render(, { + context: TestStubs.routerContext(), + }); + + expect(await screen.getByRole('button', {name: 'Create Token'})).toBeDisabled(); + }); + + it('submits with correct scopes', async function () { + MockApiClient.clearMockResponses(); + const assignMock = MockApiClient.addMockResponse({ + method: 'POST', + url: `/api-tokens/`, + }); + + render(, { + context: TestStubs.routerContext(), + }); + const createButton = await screen.getByRole('button', {name: 'Create Token'}); + + const selectByValue = (name, value) => + selectEvent.select(screen.getByRole('textbox', {name}), value); + + await selectByValue('Project', 'Admin'); + await selectByValue('Release', 'Admin'); + await selectByValue('Team', 'Admin'); + await selectByValue('Issue & Event', 'Admin'); + await selectByValue('Organization', 'Admin'); + await selectByValue('Member', 'Admin'); + + await userEvent.click(createButton); + + await waitFor(() => + expect(assignMock).toHaveBeenCalledWith( + '/api-tokens/', + expect.objectContaining({ + data: expect.objectContaining({ + scopes: expect.arrayContaining([ + 'project:read', + 'project:write', + 'project:admin', + 'project:releases', + 'team:read', + 'team:write', + 'team:admin', + 'event:read', + 'event:write', + 'event:admin', + 'org:read', + 'org:write', + 'org:admin', + 'org:integrations', + 'member:read', + 'member:write', + 'member:admin', + ]), + }), + }) + ) + ); + }); }); diff --git a/tests/acceptance/test_api.py b/tests/acceptance/test_api.py index 376da4667f0f56..f216fdcea237b4 100644 --- a/tests/acceptance/test_api.py +++ b/tests/acceptance/test_api.py @@ -1,27 +1,4 @@ from sentry.testutils.cases import AcceptanceTestCase -from sentry.testutils.silo import no_silo_test - - -@no_silo_test(stable=True) -class ApiTokensTest(AcceptanceTestCase): - def setUp(self): - super().setUp() - self.org = self.create_organization(name="Rowdy Tiger Rowdy Tiger Rowdy Tiger", owner=None) - self.project = self.create_project( - organization=self.org, teams=[self.team], name="Bengal Bengal Bengal Bengal" - ) - self.login_as(self.user) - self.path = "/api/" - - def test_simple(self): - self.browser.get(self.path) - self.browser.wait_until_not('[data-test-id="loading-indicator"]') - - self.browser.click_when_visible('[data-test-id="create-token"]') - self.browser.wait_until_not('[data-test-id="loading-indicator"]') - - self.browser.click_when_visible('[data-test-id="form-submit"]') - self.browser.wait_until_not('[data-test-id="loading-indicator"]') class ApiApplicationTest(AcceptanceTestCase): From 0cec2509acddacc95c3f1318db68a08839a1c85b Mon Sep 17 00:00:00 2001 From: schew2381 Date: Tue, 19 Sep 2023 14:59:31 -0700 Subject: [PATCH 3/6] remove accidental backend change --- src/sentry/models/apiscopes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sentry/models/apiscopes.py b/src/sentry/models/apiscopes.py index 736bbf073ba61b..3a9a9d29b37f09 100644 --- a/src/sentry/models/apiscopes.py +++ b/src/sentry/models/apiscopes.py @@ -62,7 +62,6 @@ class Meta: "org:read": bool, "org:write": bool, "org:admin": bool, - "org:integrations": bool, "member:read": bool, "member:write": bool, "member:admin": bool, From 872675ff3bf300a6f02e4eb48276c4711d11d73d Mon Sep 17 00:00:00 2001 From: schew2381 Date: Tue, 19 Sep 2023 19:23:07 -0700 Subject: [PATCH 4/6] split into: https://github.com/getsentry/sentry/pull/56537 --- tests/acceptance/test_api.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/acceptance/test_api.py b/tests/acceptance/test_api.py index f216fdcea237b4..376da4667f0f56 100644 --- a/tests/acceptance/test_api.py +++ b/tests/acceptance/test_api.py @@ -1,4 +1,27 @@ from sentry.testutils.cases import AcceptanceTestCase +from sentry.testutils.silo import no_silo_test + + +@no_silo_test(stable=True) +class ApiTokensTest(AcceptanceTestCase): + def setUp(self): + super().setUp() + self.org = self.create_organization(name="Rowdy Tiger Rowdy Tiger Rowdy Tiger", owner=None) + self.project = self.create_project( + organization=self.org, teams=[self.team], name="Bengal Bengal Bengal Bengal" + ) + self.login_as(self.user) + self.path = "/api/" + + def test_simple(self): + self.browser.get(self.path) + self.browser.wait_until_not('[data-test-id="loading-indicator"]') + + self.browser.click_when_visible('[data-test-id="create-token"]') + self.browser.wait_until_not('[data-test-id="loading-indicator"]') + + self.browser.click_when_visible('[data-test-id="form-submit"]') + self.browser.wait_until_not('[data-test-id="loading-indicator"]') class ApiApplicationTest(AcceptanceTestCase): From 190d3add781538528e347306112844964fb8eacf Mon Sep 17 00:00:00 2001 From: schew2381 Date: Wed, 20 Sep 2023 11:45:49 -0700 Subject: [PATCH 5/6] add comment + use state variable instead of hardcoding --- static/app/views/settings/account/apiNewToken.spec.tsx | 3 ++- static/app/views/settings/account/apiNewToken.tsx | 9 +-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/static/app/views/settings/account/apiNewToken.spec.tsx b/static/app/views/settings/account/apiNewToken.spec.tsx index 061c7459f29694..01f941be497e41 100644 --- a/static/app/views/settings/account/apiNewToken.spec.tsx +++ b/static/app/views/settings/account/apiNewToken.spec.tsx @@ -19,7 +19,7 @@ describe('ApiNewToken', function () { expect(await screen.getByRole('button', {name: 'Create Token'})).toBeDisabled(); }); - it('submits with correct scopes', async function () { + it('submits with correct hierarchical scopes', async function () { MockApiClient.clearMockResponses(); const assignMock = MockApiClient.addMockResponse({ method: 'POST', @@ -34,6 +34,7 @@ describe('ApiNewToken', function () { const selectByValue = (name, value) => selectEvent.select(screen.getByRole('textbox', {name}), value); + // Assigning Admin here will also grant read + write access to the resource await selectByValue('Project', 'Admin'); await selectByValue('Release', 'Admin'); await selectByValue('Team', 'Admin'); diff --git a/static/app/views/settings/account/apiNewToken.tsx b/static/app/views/settings/account/apiNewToken.tsx index e422dc7c53979e..e6e093f3fef7f3 100644 --- a/static/app/views/settings/account/apiNewToken.tsx +++ b/static/app/views/settings/account/apiNewToken.tsx @@ -80,14 +80,7 @@ export default class ApiNewToken extends Component<{}, State> { { this.setState({permissions: value}); }} From 6358c63988cc1ac7cc575a97b9f3b3b38b3da635 Mon Sep 17 00:00:00 2001 From: schew2381 Date: Thu, 21 Sep 2023 11:15:53 -0700 Subject: [PATCH 6/6] put state into own variable --- static/app/views/settings/account/apiNewToken.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/app/views/settings/account/apiNewToken.tsx b/static/app/views/settings/account/apiNewToken.tsx index e6e093f3fef7f3..baa2d9f943d3e6 100644 --- a/static/app/views/settings/account/apiNewToken.tsx +++ b/static/app/views/settings/account/apiNewToken.tsx @@ -43,6 +43,7 @@ export default class ApiNewToken extends Component<{}, State> { }; render() { + const {permissions} = this.state; return (
@@ -72,7 +73,7 @@ export default class ApiNewToken extends Component<{}, State> { marginTop: 0, paddingRight: 20, }} - submitDisabled={Object.values(this.state.permissions).every( + submitDisabled={Object.values(permissions).every( value => value === 'no-access' )} submitLabel={t('Create Token')} @@ -80,7 +81,7 @@ export default class ApiNewToken extends Component<{}, State> { { this.setState({permissions: value}); }}