diff --git a/static/app/components/events/autofix/v3/prIterationFeedbackForm.spec.tsx b/static/app/components/events/autofix/v3/prIterationFeedbackForm.spec.tsx
index 466856d1d690..2e96c2a74bdb 100644
--- a/static/app/components/events/autofix/v3/prIterationFeedbackForm.spec.tsx
+++ b/static/app/components/events/autofix/v3/prIterationFeedbackForm.spec.tsx
@@ -75,7 +75,10 @@ describe('PrIterationFeedbackForm', () => {
// The ticket asks for the form to stay visible, just greyed out.
expect(screen.getByRole('textbox')).toBeDisabled();
- expect(screen.getByRole('button', {name: 'Submit'})).toBeDisabled();
+ expect(screen.getByRole('button', {name: 'Submit'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
await userEvent.hover(screen.getByRole('button', {name: 'Submit'}));
expect(
diff --git a/static/app/components/events/autofix/v3/prIterationFeedbackForm.tsx b/static/app/components/events/autofix/v3/prIterationFeedbackForm.tsx
index 1b6757b54766..ed246a2b36f4 100644
--- a/static/app/components/events/autofix/v3/prIterationFeedbackForm.tsx
+++ b/static/app/components/events/autofix/v3/prIterationFeedbackForm.tsx
@@ -120,16 +120,15 @@ export function PrIterationFeedbackForm({
{onClose && (
} onClick={onClose} />
)}
-
- }
- disabled={isSubmitting || isPaused || !feedback.trim()}
- onClick={handleSubmit}
- >
- {isSubmitting ? t('Submitting feedback') : t('Submit')}
-
-
+ }
+ disabled={isSubmitting || isPaused || !feedback.trim()}
+ tooltipProps={{title: isPaused ? pausedTooltip : undefined}}
+ onClick={handleSubmit}
+ >
+ {isSubmitting ? t('Submitting feedback') : t('Submit')}
+
);
diff --git a/static/app/components/events/interfaces/debugMeta/debugImageDetails/candidate/actions.tsx b/static/app/components/events/interfaces/debugMeta/debugImageDetails/candidate/actions.tsx
index 60d4b6e0030d..caf1e1f3b2c6 100644
--- a/static/app/components/events/interfaces/debugMeta/debugImageDetails/candidate/actions.tsx
+++ b/static/app/components/events/interfaces/debugMeta/debugImageDetails/candidate/actions.tsx
@@ -54,32 +54,30 @@ export function Actions({
{({hasAccess}) => (
-
- }
+ href={downloadUrl}
+ disabled={!hasRole}
+ tooltipProps={{title: noPermissionToDownloadDebugFilesInfo}}
+ >
+ {t('Download')}
+
+ onDelete(debugFileId)}
+ disabled={!hasAccess}
+ >
+ }
size="xs"
- icon={}
- href={downloadUrl}
- disabled={!hasRole}
- >
- {t('Download')}
-
-
-
- onDelete(debugFileId)}
disabled={!hasAccess}
- >
- }
- size="xs"
- disabled={!hasAccess}
- aria-label={t('Delete')}
- />
-
-
+ aria-label={t('Delete')}
+ tooltipProps={{title: noPermissionToDeleteDebugFilesInfo}}
+ />
+
)}
diff --git a/static/app/components/feedback/feedbackItem/feedbackActions.tsx b/static/app/components/feedback/feedbackItem/feedbackActions.tsx
index 2de632fdde2f..971eb8df156a 100644
--- a/static/app/components/feedback/feedbackItem/feedbackActions.tsx
+++ b/static/app/components/feedback/feedbackItem/feedbackActions.tsx
@@ -140,14 +140,14 @@ function LargeWidth({
-
-
-
+ {hasSeen ? t('Mark Unread') : t('Mark Read')}
+
-
-
-
+ {t('Delete')}
+
);
}
diff --git a/static/app/components/modals/dataWidgetViewerModal.tsx b/static/app/components/modals/dataWidgetViewerModal.tsx
index 195748b323f3..098096bf3c20 100644
--- a/static/app/components/modals/dataWidgetViewerModal.tsx
+++ b/static/app/components/modals/dataWidgetViewerModal.tsx
@@ -828,13 +828,15 @@ function OpenButton({
!widgetTypeSupportsExploreMultiQuery(widget.widgetType);
if (multiQueryUnsupported) {
return (
-
-
-
+ {openLabel}
+
);
}
path = getWidgetExploreUrl(widget, dashboardFilters, selection, organization)!;
@@ -861,22 +863,21 @@ function OpenButton({
}
return (
-
- {
- trackAnalytics('dashboards_views.widget_viewer.open_source', {
- organization,
- widget_type: widget.widgetType ?? WidgetType.ERRORS,
- display_type: widget.displayType,
- });
- }}
- >
- {openLabel}
-
-
+ {
+ trackAnalytics('dashboards_views.widget_viewer.open_source', {
+ organization,
+ widget_type: widget.widgetType ?? WidgetType.ERRORS,
+ display_type: widget.displayType,
+ });
+ }}
+ >
+ {openLabel}
+
);
}
diff --git a/static/app/components/preprod/preprodBuildsTableCommon.tsx b/static/app/components/preprod/preprodBuildsTableCommon.tsx
index 3bc994adabcf..4797807409c7 100644
--- a/static/app/components/preprod/preprodBuildsTableCommon.tsx
+++ b/static/app/components/preprod/preprodBuildsTableCommon.tsx
@@ -100,23 +100,19 @@ export function PreprodBuildsRowCells({
variant="icon"
/>
) : (
-
-
- }
- variant="transparent"
- size="zero"
- disabled
- />
-
-
+ }
+ variant="transparent"
+ size="zero"
+ disabled
+ tooltipProps={{
+ title: getDistributionErrorTooltip(
+ build.distribution_info?.error_code,
+ build.distribution_info?.error_message
+ ),
+ }}
+ />
)}
)}
diff --git a/static/app/components/replays/table/deleteReplays.tsx b/static/app/components/replays/table/deleteReplays.tsx
index 6784e297ab05..32b6f6291ecc 100644
--- a/static/app/components/replays/table/deleteReplays.tsx
+++ b/static/app/components/replays/table/deleteReplays.tsx
@@ -10,7 +10,6 @@ import {Flex} from '@sentry/scraps/layout';
import {Link} from '@sentry/scraps/link';
import type {TableColumnConfig} from '@sentry/scraps/table';
import {Text} from '@sentry/scraps/text';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {useAnalyticsArea} from 'sentry/components/analyticsArea';
@@ -87,81 +86,76 @@ export function DeleteReplays({selectedIds, replays, queryOptions}: Props) {
};
return (
-
-
- }
- onClick={() =>
- openConfirmModal({
- bypass: selectedIds !== 'all' && selectedIds.length === 1,
- renderMessage: _props =>
- selectedIds === 'all' ? (
-
- ) : (
-
-
- {tn(
- 'The following %s replay will be deleted',
- 'The following %s replays will be deleted',
- selectedIds.length
- )}
-
-
-
- ),
- renderConfirmButton: ({defaultOnClick}) => (
-
- ),
- onConfirm: () => {
- bulkDelete([deletePayload], {
- onSuccess: () => {
- addSuccessMessage(
- tct('Replays are being deleted. [settings:View progress]', {
- settings: ,
- })
- );
- // TODO: get the list to refetch
- refetchAuditLog();
- },
- onError: error => {
- const count =
- selectedIds === 'all'
- ? Number.MAX_SAFE_INTEGER
- : selectedIds.length;
- const reason = getBulkDeleteErrorReason(error);
- addErrorMessage(
- reason
- ? tn(
- 'Failed to delete replay: %2$s',
- 'Failed to delete replays: %2$s',
- count,
- reason
- )
- : tn('Failed to delete replay', 'Failed to delete replays', count)
- );
- },
- onSettled: () => {},
- });
+ }
+ tooltipProps={{
+ title: oneProjectEligible
+ ? hasAccess
+ ? undefined
+ : t(
+ 'You must have project:write, project:admin, or org:admin access to delete replays'
+ )
+ : t('Select a single project from the dropdown to delete replays'),
+ }}
+ onClick={() =>
+ openConfirmModal({
+ bypass: selectedIds !== 'all' && selectedIds.length === 1,
+ renderMessage: _props =>
+ selectedIds === 'all' ? (
+
+ ) : (
+
+
+ {tn(
+ 'The following %s replay will be deleted',
+ 'The following %s replays will be deleted',
+ selectedIds.length
+ )}
+
+
+
+ ),
+ renderConfirmButton: ({defaultOnClick}) => (
+
+ ),
+ onConfirm: () => {
+ bulkDelete([deletePayload], {
+ onSuccess: () => {
+ addSuccessMessage(
+ tct('Replays are being deleted. [settings:View progress]', {
+ settings: ,
+ })
+ );
+ // TODO: get the list to refetch
+ refetchAuditLog();
+ },
+ onError: error => {
+ const count =
+ selectedIds === 'all' ? Number.MAX_SAFE_INTEGER : selectedIds.length;
+ const reason = getBulkDeleteErrorReason(error);
+ addErrorMessage(
+ reason
+ ? tn(
+ 'Failed to delete replay: %2$s',
+ 'Failed to delete replays: %2$s',
+ count,
+ reason
+ )
+ : tn('Failed to delete replay', 'Failed to delete replays', count)
+ );
},
- })
- }
- size="xs"
- >
- {t('Delete')}
-
-
-
+ onSettled: () => {},
+ });
+ },
+ })
+ }
+ size="xs"
+ >
+ {t('Delete')}
+
);
}
diff --git a/static/app/views/authV2/authLogin/components/organizationAuth.spec.tsx b/static/app/views/authV2/authLogin/components/organizationAuth.spec.tsx
index f9ed9bbf817a..78f47a691d7d 100644
--- a/static/app/views/authV2/authLogin/components/organizationAuth.spec.tsx
+++ b/static/app/views/authV2/authLogin/components/organizationAuth.spec.tsx
@@ -71,14 +71,14 @@ describe('OrganizationAuth', () => {
);
const ssoButton = screen.getByRole('button', {name: 'SSO'});
- expect(ssoButton).toBeDisabled();
+ expect(ssoButton).toHaveAttribute('aria-disabled', 'true');
expect(
screen.getByText('Members sign in with email and password')
).toBeInTheDocument();
expect(
screen.queryByText('This organization does not have Single Sign-On configured')
).not.toBeInTheDocument();
- await userEvent.hover(ssoButton.parentElement!);
+ await userEvent.hover(ssoButton);
expect(
await screen.findByText('This organization does not have Single Sign-On configured')
).toBeInTheDocument();
diff --git a/static/app/views/authV2/authLogin/components/organizationAuth.tsx b/static/app/views/authV2/authLogin/components/organizationAuth.tsx
index 8da3bc5b4036..0d567bf88048 100644
--- a/static/app/views/authV2/authLogin/components/organizationAuth.tsx
+++ b/static/app/views/authV2/authLogin/components/organizationAuth.tsx
@@ -6,7 +6,6 @@ import {Avatar} from '@sentry/scraps/avatar';
import {Button, LinkButton} from '@sentry/scraps/button';
import {Flex, Stack} from '@sentry/scraps/layout';
import {Text} from '@sentry/scraps/text';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {IconClose, IconMegaphone} from 'sentry/icons';
import {IdentityIcon} from 'sentry/icons/identityIcon';
@@ -65,19 +64,17 @@ export function OrganizationAuth({
);
diff --git a/static/app/views/automations/components/disabledAlert.spec.tsx b/static/app/views/automations/components/disabledAlert.spec.tsx
index 6f4de5b6f9e5..5860f9be9576 100644
--- a/static/app/views/automations/components/disabledAlert.spec.tsx
+++ b/static/app/views/automations/components/disabledAlert.spec.tsx
@@ -87,7 +87,7 @@ describe('DisabledAlert', () => {
});
const enableButton = screen.getByRole('button', {name: 'Enable'});
- expect(enableButton).toBeDisabled();
+ expect(enableButton).toHaveAttribute('aria-disabled', 'true');
await userEvent.hover(enableButton);
expect(
diff --git a/static/app/views/automations/components/disabledAlert.tsx b/static/app/views/automations/components/disabledAlert.tsx
index a1fd2852e525..1dbbe997e7a4 100644
--- a/static/app/views/automations/components/disabledAlert.tsx
+++ b/static/app/views/automations/components/disabledAlert.tsx
@@ -1,6 +1,5 @@
import {Alert} from '@sentry/scraps/alert';
import {Button} from '@sentry/scraps/button';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {IconPlay} from 'sentry/icons';
import {t} from 'sentry/locale';
@@ -44,17 +43,16 @@ export function DisabledAlert({automation}: DisabledAlertProps) {
- }
- onClick={handleEnable}
- disabled={isEnabling || !canEdit}
- aria-label={t('Enable')}
- >
- {t('Enable')}
-
-
+ }
+ onClick={handleEnable}
+ disabled={isEnabling || !canEdit}
+ aria-label={t('Enable')}
+ tooltipProps={{title: canEdit ? undefined : permissionTooltipText}}
+ >
+ {t('Enable')}
+
}
>
{t('This alert is disabled and will not send notifications.')}
diff --git a/static/app/views/dashboards/widgetCard/toolbar.tsx b/static/app/views/dashboards/widgetCard/toolbar.tsx
index 8aca4af9a3ad..891fdf05ac4d 100644
--- a/static/app/views/dashboards/widgetCard/toolbar.tsx
+++ b/static/app/views/dashboards/widgetCard/toolbar.tsx
@@ -3,7 +3,6 @@ import styled from '@emotion/styled';
import color from 'color';
import {Button} from '@sentry/scraps/button';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {IconCopy, IconDelete, IconEdit, IconGrabbable} from 'sentry/icons';
import {t} from 'sentry/locale';
@@ -41,37 +40,27 @@ export function Toolbar({
/>
)}
{onEdit && (
-
- }
- disabled={disableEdit}
- />
-
+ }
+ disabled={disableEdit}
+ tooltipProps={{title: disabledReason}}
+ />
)}
{onDuplicate && (
-
- }
- disabled={disableDuplicate}
- />
-
+ }
+ disabled={disableDuplicate}
+ tooltipProps={{title: disabledReason}}
+ />
)}
{onDelete && (
)}
{showDisable && (
-
- handleUpdate({enabled: false})}
- disabled={isUpdating || !canEdit}
- >
- {t('Disable')}
-
-
- )}
-
handleUpdate({enabled: false})}
+ disabled={isUpdating || !canEdit}
+ tooltipProps={{
+ title: canEdit
+ ? undefined
+ : 'You do not have permission to modify the selected monitors.',
+ }}
>
- {t('Delete')}
+ {t('Disable')}
-
+ )}
+
+ {t('Delete')}
+
diff --git a/static/app/views/detectors/list/allMonitors.spec.tsx b/static/app/views/detectors/list/allMonitors.spec.tsx
index 2fc2e4e49d09..4e3f0dbedb2c 100644
--- a/static/app/views/detectors/list/allMonitors.spec.tsx
+++ b/static/app/views/detectors/list/allMonitors.spec.tsx
@@ -599,7 +599,10 @@ describe('DetectorsList', () => {
await userEvent.click(firstRowCheckbox);
// Verify that delete button is disabled
- expect(screen.getByRole('button', {name: 'Delete'})).toBeDisabled();
+ expect(screen.getByRole('button', {name: 'Delete'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
});
it('shows option to select all query results when page is selected', async () => {
@@ -695,9 +698,18 @@ describe('DetectorsList', () => {
const enabledCheckbox = within(enabledRow!).getByRole('checkbox');
await userEvent.click(enabledCheckbox);
- expect(screen.getByRole('button', {name: 'Enable'})).toBeDisabled();
- expect(screen.getByRole('button', {name: 'Disable'})).toBeDisabled();
- expect(screen.getByRole('button', {name: 'Delete'})).toBeDisabled();
+ expect(screen.getByRole('button', {name: 'Enable'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
+ expect(screen.getByRole('button', {name: 'Disable'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
+ expect(screen.getByRole('button', {name: 'Delete'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
});
});
});
diff --git a/static/app/views/discover/results.tsx b/static/app/views/discover/results.tsx
index eb341a75429c..271a96f2298f 100644
--- a/static/app/views/discover/results.tsx
+++ b/static/app/views/discover/results.tsx
@@ -1365,22 +1365,20 @@ function SaveQueryButton({
if (!isNewQuery && isEditingQuery) {
return (
-
-
-
- {t('Save Changes')}
-
-
+
+ {t('Save Changes')}
+
{
additionalWrapper: ProviderWrapper,
});
const refreshButton = await screen.findByRole('button', {name: 'Refresh'});
- expect(refreshButton).toBeDisabled();
+ expect(refreshButton).toHaveAttribute('aria-disabled', 'true');
});
});
diff --git a/static/app/views/explore/logs/logsTab.tsx b/static/app/views/explore/logs/logsTab.tsx
index b167d3fa9654..5bd927a9cee0 100644
--- a/static/app/views/explore/logs/logsTab.tsx
+++ b/static/app/views/explore/logs/logsTab.tsx
@@ -7,7 +7,6 @@ import {DropdownMenu} from '@sentry/scraps/dropdownMenu';
import {Container, Flex, Grid} from '@sentry/scraps/layout';
import {useModal} from '@sentry/scraps/modal';
import {TabList, Tabs} from '@sentry/scraps/tabs';
-import {Tooltip} from '@sentry/scraps/tooltip';
import * as Layout from 'sentry/components/layouts/thirds';
import type {DatePageFilterProps} from 'sentry/components/pageFilters/date/datePageFilter';
@@ -538,19 +537,14 @@ function LogsTabContentInner({datePageFilterProps}: LogsTabProps) {
{tableTab === 'logs' && (
-
- }
- disabled={!canManuallyRefresh}
- onClick={refreshTable}
- aria-label={t('Refresh')}
- />
-
+ }
+ disabled={!canManuallyRefresh}
+ onClick={refreshTable}
+ aria-label={t('Refresh')}
+ tooltipProps={{title: manualRefreshDisabledReason}}
+ />
- {t('Select a supported project from the projects dropdown.')}
-
- }
- disabled={!disabled} // we only want to show the tooltip when the button is disabled
+ activateSidebar()}
+ variant="primary"
+ disabled={disabled}
+ tooltipProps={{
+ title: (
+
+ {t('Select a supported project from the projects dropdown.')}
+
+ ),
+ }}
>
- activateSidebar()}
- variant="primary"
- disabled={disabled}
- >
- {t('Set Up Replays')}
-
-
+ {t('Set Up Replays')}
+
) : (
-
- {t('You do not have permission to create a project.')}
-
- }
- disabled={!disabled}
+
+ {t('You do not have permission to create a project.')}
+
+ ),
+ }}
>
-
- {t('Create Project')}
-
-
+ {t('Create Project')}
+
);
return (
diff --git a/static/app/views/explore/tables/index.tsx b/static/app/views/explore/tables/index.tsx
index ec152e77f2bf..c02e9b738e82 100644
--- a/static/app/views/explore/tables/index.tsx
+++ b/static/app/views/explore/tables/index.tsx
@@ -5,7 +5,6 @@ import {Button} from '@sentry/scraps/button';
import {Flex} from '@sentry/scraps/layout';
import {useModal} from '@sentry/scraps/modal';
import {TabList, Tabs} from '@sentry/scraps/tabs';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {IconEdit} from 'sentry/icons/iconEdit';
import {t} from 'sentry/locale';
@@ -150,17 +149,20 @@ export function ExploreTables(props: ExploreTablesProps) {
{t('Edit Table')}
) : (
- }
+ size="sm"
+ tooltipProps={{
+ title:
+ tab === Tab.TRACE
+ ? t('Editing columns is available for span samples only')
+ : t('Use the Group By and Visualize controls to change table columns'),
+ }}
>
- } size="sm">
- {t('Edit Table')}
-
-
+ {t('Edit Table')}
+
)}
{tab === Tab.SPAN && (
diff --git a/static/app/views/issueDetails/groupDistributions/tagsDistributionDrawer.tsx b/static/app/views/issueDetails/groupDistributions/tagsDistributionDrawer.tsx
index 20d88598125f..bdbf4520470a 100644
--- a/static/app/views/issueDetails/groupDistributions/tagsDistributionDrawer.tsx
+++ b/static/app/views/issueDetails/groupDistributions/tagsDistributionDrawer.tsx
@@ -2,7 +2,6 @@ import {Fragment, useState} from 'react';
import {Button} from '@sentry/scraps/button';
import {Grid} from '@sentry/scraps/layout';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {
EventDrawerBody,
@@ -79,9 +78,13 @@ export function TagsDistributionDrawer({
}}
/>
{includeFeatureFlagsTab ? (
-
- } />
-
+ }
+ tooltipProps={{title: 'Highlighted tags are shown first'}}
+ />
) : null}
)}
diff --git a/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx b/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx
index fde3b3b3cbad..7137b8657c15 100644
--- a/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx
+++ b/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx
@@ -305,15 +305,9 @@ export function EventsTable({
handleCellAction={cellActionHandler}
allowActions={allowActions}
>
-
-
-
-
-
+
+
+
);
diff --git a/static/app/views/projectInstall/createProject.spec.tsx b/static/app/views/projectInstall/createProject.spec.tsx
index 7f52aba02633..83bfc2755e41 100644
--- a/static/app/views/projectInstall/createProject.spec.tsx
+++ b/static/app/views/projectInstall/createProject.spec.tsx
@@ -912,7 +912,7 @@ describe('CreateProject', () => {
// as re-renders can create new DOM nodes
const getSubmitButton = () => screen.getByRole('button', {name: 'Create Project'});
- expect(getSubmitButton()).toBeDisabled();
+ expect(getSubmitButton()).toHaveAttribute('aria-disabled', 'true');
// Fills the project slug
await userEvent.type(screen.getByPlaceholderText('project-slug'), 'my-project');
@@ -931,20 +931,20 @@ describe('CreateProject', () => {
expect(getSubmitButton()).toBeEnabled();
await userEvent.clear(screen.getByTestId('range-input'));
- expect(getSubmitButton()).toBeDisabled();
+ expect(getSubmitButton()).toHaveAttribute('aria-disabled', 'true');
await userEvent.type(screen.getByTestId('range-input'), '2712');
expect(getSubmitButton()).toBeEnabled();
await userEvent.clear(screen.getByTestId('range-input'));
- expect(getSubmitButton()).toBeDisabled();
+ expect(getSubmitButton()).toHaveAttribute('aria-disabled', 'true');
await userEvent.click(
screen.getByRole('checkbox', {
name: 'Notify via integration (Slack, Discord, MS Teams, etc.)',
})
);
- expect(getSubmitButton()).toBeDisabled();
+ expect(getSubmitButton()).toHaveAttribute('aria-disabled', 'true');
await userEvent.click(
screen.getByRole('radio', {name: /create my own alerts later/i})
@@ -1029,7 +1029,10 @@ describe('CreateProject', () => {
createOptionText: '#custom-channel',
});
expect(await screen.findByText('Channel not found')).toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Create Project'})).toBeDisabled();
+ expect(screen.getByRole('button', {name: 'Create Project'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
await userEvent.hover(screen.getByRole('button', {name: 'Create Project'}));
await waitFor(() =>
expect(screen.getAllByText('Channel not found')).toHaveLength(2)
@@ -1065,7 +1068,10 @@ describe('CreateProject', () => {
createOptionText: '#custom-channel',
});
expect(await screen.findByText('Channel not found')).toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Create Project'})).toBeDisabled();
+ expect(screen.getByRole('button', {name: 'Create Project'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
await userEvent.click(
screen.getByRole('checkbox', {
name: /Notify via integration/,
@@ -1099,7 +1105,10 @@ describe('CreateProject', () => {
createOptionText: '#custom-channel',
});
await waitFor(() =>
- expect(screen.getByRole('button', {name: 'Create Project'})).toBeDisabled()
+ expect(screen.getByRole('button', {name: 'Create Project'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ )
);
await userEvent.hover(screen.getByRole('button', {name: 'Create Project'}));
expect(
diff --git a/static/app/views/projectInstall/createProject.tsx b/static/app/views/projectInstall/createProject.tsx
index 8b9211ea9bc0..b390c422def7 100644
--- a/static/app/views/projectInstall/createProject.tsx
+++ b/static/app/views/projectInstall/createProject.tsx
@@ -10,7 +10,6 @@ import {Input} from '@sentry/scraps/input';
import {Grid} from '@sentry/scraps/layout';
import {ExternalLink} from '@sentry/scraps/link';
import {useModal} from '@sentry/scraps/modal';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {openConsoleModal} from 'sentry/actionCreators/modal';
@@ -621,39 +620,31 @@ export function CreateProject() {
)}
- debounceHandleProjectCreation(formData)}
>
- debounceHandleProjectCreation(formData)}
- >
- {t('Create Project')}
-
-
+ {t('Create Project')}
+
{!isModalVisible && (
diff --git a/static/app/views/settings/featureFlags/changeTracking/index.spec.tsx b/static/app/views/settings/featureFlags/changeTracking/index.spec.tsx
index f8ea4a739e8a..a831f8cfc3b3 100644
--- a/static/app/views/settings/featureFlags/changeTracking/index.spec.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/index.spec.tsx
@@ -186,7 +186,7 @@ describe('OrganizationFeatureFlagsIndex', () => {
expect(await secretsTable.findByText('launchdarkly')).toBeInTheDocument();
expect(
secretsTable.getByLabelText('Remove secret for launchdarkly provider')
- ).toBeDisabled();
+ ).toHaveAttribute('aria-disabled', 'true');
});
});
});
diff --git a/static/app/views/settings/featureFlags/changeTracking/index.tsx b/static/app/views/settings/featureFlags/changeTracking/index.tsx
index afb10545e2c3..57cc2a1a18b2 100644
--- a/static/app/views/settings/featureFlags/changeTracking/index.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/index.tsx
@@ -6,7 +6,6 @@ import {LinkButton} from '@sentry/scraps/button';
import {Flex} from '@sentry/scraps/layout';
import {ExternalLink} from '@sentry/scraps/link';
import type {TableColumnConfig} from '@sentry/scraps/table';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {hasEveryAccess} from 'sentry/components/acl/access';
@@ -83,20 +82,16 @@ function AddNewProvider({
organizationSlug: string;
}) {
return (
-
-
- {t('Add New Provider')}
-
-
+ {t('Add New Provider')}
+
);
}
diff --git a/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.spec.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.spec.tsx
index 496ec756a47e..58c59dc386e1 100644
--- a/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.spec.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.spec.tsx
@@ -67,7 +67,7 @@ describe('OrganizationFeatureFlagsProviderRow', () => {
expect(
screen.getByRole('button', {name: 'Remove secret for launchdarkly provider'})
- ).toBeDisabled();
+ ).toHaveAttribute('aria-disabled', 'true');
});
it('allows to remove', async () => {
diff --git a/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.tsx
index bf598a08095a..bb4da6a8a33c 100644
--- a/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.tsx
@@ -1,6 +1,5 @@
import {Button} from '@sentry/scraps/button';
import {Text} from '@sentry/scraps/text';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {Confirm} from 'sentry/components/confirm';
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
@@ -44,30 +43,30 @@ export function OrganizationFeatureFlagsProviderRow({
- removeSecret(secret.id) : undefined}
+ message={t(
+ 'Are you sure you want to remove the secret for %s provider? It will not be usable anymore, and this cannot be undone.',
+ secret.provider
)}
- disabled={!!removeSecret}
>
- removeSecret(secret.id) : undefined}
- message={t(
- 'Are you sure you want to remove the secret for %s provider? It will not be usable anymore, and this cannot be undone.',
- secret.provider
- )}
+ : }
+ tooltipProps={{
+ title: removeSecret
+ ? undefined
+ : t(
+ 'You must be an organization owner, manager or admin to remove a secret.'
+ ),
+ }}
>
- : }
- >
- {t('Remove')}
-
-
-
+ {t('Remove')}
+
+
);
diff --git a/static/app/views/settings/organizationAuthTokens/authTokenRow.spec.tsx b/static/app/views/settings/organizationAuthTokens/authTokenRow.spec.tsx
index 5f8a666b9b70..a80285f2c2aa 100644
--- a/static/app/views/settings/organizationAuthTokens/authTokenRow.spec.tsx
+++ b/static/app/views/settings/organizationAuthTokens/authTokenRow.spec.tsx
@@ -149,7 +149,10 @@ describe('OrganizationAuthTokensAuthTokenRow', () => {
);
- expect(screen.getByRole('button', {name: 'Revoke My Token'})).toBeDisabled();
+ expect(screen.getByRole('button', {name: 'Revoke My Token'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
});
it('allows to revoke', async () => {
diff --git a/static/app/views/settings/organizationAuthTokens/authTokenRow.tsx b/static/app/views/settings/organizationAuthTokens/authTokenRow.tsx
index 3a0ba8ab6ca1..a49e83f84cc8 100644
--- a/static/app/views/settings/organizationAuthTokens/authTokenRow.tsx
+++ b/static/app/views/settings/organizationAuthTokens/authTokenRow.tsx
@@ -3,7 +3,6 @@ import styled from '@emotion/styled';
import {Button} from '@sentry/scraps/button';
import {Link} from '@sentry/scraps/link';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {Confirm} from 'sentry/components/confirm';
import {Placeholder} from 'sentry/components/placeholder';
@@ -119,28 +118,28 @@ export function OrganizationAuthTokensAuthTokenRow({
- revokeToken(token) : undefined}
+ message={t(
+ 'Are you sure you want to revoke %s token? It will not be usable anymore, and this cannot be undone.',
+ tokenPreview(token.tokenLastCharacters || '', 'sntrys_')
+ )}
>
- revokeToken(token) : undefined}
- message={t(
- 'Are you sure you want to revoke %s token? It will not be usable anymore, and this cannot be undone.',
- tokenPreview(token.tokenLastCharacters || '', 'sntrys_')
- )}
+ }
+ tooltipProps={{
+ title: revokeToken
+ ? undefined
+ : t('You must be an organization owner or manager to revoke a token.'),
+ }}
>
- }
- >
- {t('Revoke')}
-
-
-
+ {t('Revoke')}
+
+
);
diff --git a/static/app/views/settings/organizationAuthTokens/index.spec.tsx b/static/app/views/settings/organizationAuthTokens/index.spec.tsx
index 8a2db46cfe9f..7d41580c0db3 100644
--- a/static/app/views/settings/organizationAuthTokens/index.spec.tsx
+++ b/static/app/views/settings/organizationAuthTokens/index.spec.tsx
@@ -301,7 +301,10 @@ describe('OrganizationAuthTokensIndex', () => {
expect(await screen.findByText('My Token 1')).toBeInTheDocument();
- expect(screen.getByLabelText('Revoke My Token 1')).toBeDisabled();
+ expect(screen.getByLabelText('Revoke My Token 1')).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
});
});
});
diff --git a/static/app/views/settings/organizationIntegrations/addIntegrationButton.tsx b/static/app/views/settings/organizationIntegrations/addIntegrationButton.tsx
index f6a4b70a76e5..2065ebf5eeaa 100644
--- a/static/app/views/settings/organizationIntegrations/addIntegrationButton.tsx
+++ b/static/app/views/settings/organizationIntegrations/addIntegrationButton.tsx
@@ -1,6 +1,5 @@
import type {ButtonProps} from '@sentry/scraps/button';
import {Button} from '@sentry/scraps/button';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {t} from 'sentry/locale';
import type {IntegrationWithConfig} from 'sentry/types/integrations';
@@ -44,7 +43,6 @@ export function AddIntegrationButton({
const {startFlow} = useAddIntegration();
- // This is hooked to the button since the button is only rendered when all the flags/plan checks pass.
useAutoOpenInstallModal({
provider,
organization,
@@ -55,34 +53,32 @@ export function AddIntegrationButton({
});
return (
-
- {
- if (label === t('Reinstall')) {
- trackAnalytics('integrations.integration_reinstall_clicked', {
- organization,
- provider: provider.metadata.noun,
- });
- }
- startFlow({
- provider,
+ {
+ if (label === t('Reinstall')) {
+ trackAnalytics('integrations.integration_reinstall_clicked', {
organization,
- onInstall: onAddIntegration,
- analyticsParams,
- suppressSuccessMessage,
- onCancel,
- onError,
+ provider: provider.metadata.noun,
});
- }}
- >
- {label}
-
-
+ }
+ startFlow({
+ provider,
+ organization,
+ onInstall: onAddIntegration,
+ analyticsParams,
+ suppressSuccessMessage,
+ onCancel,
+ onError,
+ });
+ }}
+ >
+ {label}
+
);
}
diff --git a/static/app/views/settings/organizationIntegrations/directEnableButton.spec.tsx b/static/app/views/settings/organizationIntegrations/directEnableButton.spec.tsx
index cf6b89e08779..9adbc4a8b594 100644
--- a/static/app/views/settings/organizationIntegrations/directEnableButton.spec.tsx
+++ b/static/app/views/settings/organizationIntegrations/directEnableButton.spec.tsx
@@ -53,6 +53,9 @@ describe('DirectEnableButton', () => {
organization,
});
- expect(screen.getByRole('button', {name: 'Enable Integration'})).toBeDisabled();
+ expect(screen.getByRole('button', {name: 'Enable Integration'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
});
});
diff --git a/static/app/views/settings/organizationIntegrations/directEnableButton.tsx b/static/app/views/settings/organizationIntegrations/directEnableButton.tsx
index b95a61cbf060..939a4c227e8c 100644
--- a/static/app/views/settings/organizationIntegrations/directEnableButton.tsx
+++ b/static/app/views/settings/organizationIntegrations/directEnableButton.tsx
@@ -1,7 +1,6 @@
import {useMutation, useQueryClient} from '@tanstack/react-query';
import {Button} from '@sentry/scraps/button';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {addErrorMessage} from 'sentry/actionCreators/indicator';
import {t} from 'sentry/locale';
@@ -58,18 +57,18 @@ export function DirectEnableButton({
});
return (
- enable()}
>
- enable()}
- >
- {t('Enable Integration')}
-
-
+ {t('Enable Integration')}
+
);
}
diff --git a/static/app/views/settings/organizationIntegrations/installedIntegration.tsx b/static/app/views/settings/organizationIntegrations/installedIntegration.tsx
index 666ccc0e5dbb..2214740cc889 100644
--- a/static/app/views/settings/organizationIntegrations/installedIntegration.tsx
+++ b/static/app/views/settings/organizationIntegrations/installedIntegration.tsx
@@ -199,28 +199,26 @@ export class InstalledIntegration extends Component {
)}
-
- }
+ data-test-id="integration-remove-button"
+ tooltipProps={{
+ title: t(
+ 'You must be an organization owner, manager or admin to uninstall'
+ ),
+ }}
>
- }
- data-test-id="integration-remove-button"
- >
- {t('Uninstall')}
-
-
-
+ {t('Uninstall')}
+
+
{
// The reinstall button (which auto-opens the install modal) must never
// render for members without access; a disabled Update button shows instead.
- expect(await screen.findByRole('button', {name: 'Update'})).toBeDisabled();
+ expect(await screen.findByRole('button', {name: 'Update'})).toHaveAttribute(
+ 'aria-disabled',
+ 'true'
+ );
expect(screen.queryByTestId('integration-upgrade-button')).not.toBeInTheDocument();
});
});
diff --git a/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx b/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx
index aa2e91857b32..41d537b16e92 100644
--- a/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx
+++ b/static/app/views/settings/organizationIntegrations/integrationDetailedView.tsx
@@ -8,7 +8,6 @@ import {Button} from '@sentry/scraps/button';
import {AutoSaveForm, FieldGroup} from '@sentry/scraps/form';
import {Flex} from '@sentry/scraps/layout';
import {Text} from '@sentry/scraps/text';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {addErrorMessage} from 'sentry/actionCreators/indicator';
import {updateOrganization} from 'sentry/actionCreators/organizations';
@@ -89,11 +88,16 @@ function IntegrationUpgradeButton({
}) {
if (!canManageIntegrations(organization)) {
return (
-
-
- {t('Update')}
-
-
+
+ {t('Update')}
+
);
}
diff --git a/static/app/views/settings/organizationTeams/teamNotifications.tsx b/static/app/views/settings/organizationTeams/teamNotifications.tsx
index 7d9a56f6edc9..bffa56ac27bb 100644
--- a/static/app/views/settings/organizationTeams/teamNotifications.tsx
+++ b/static/app/views/settings/organizationTeams/teamNotifications.tsx
@@ -4,7 +4,6 @@ import styled from '@emotion/styled';
import {Button} from '@sentry/scraps/button';
import {Flex} from '@sentry/scraps/layout';
import {ExternalLink} from '@sentry/scraps/link';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {hasEveryAccess} from 'sentry/components/acl/access';
@@ -125,22 +124,23 @@ function TeamNotificationSettingsPanel({
/>
- onDelete(externalTeam)}
+ message={t('Are you sure you want to remove this Slack team link?')}
>
- }
disabled={!hasWriteAccess}
- onConfirm={() => onDelete(externalTeam)}
- message={t('Are you sure you want to remove this Slack team link?')}
+ tooltipProps={{
+ title: t(
+ 'You must be an organization owner, manager or admin to remove a Slack team link'
+ ),
+ }}
>
- } disabled={!hasWriteAccess}>
- {t('Unlink')}
-
-
-
+ {t('Unlink')}
+
+
));
diff --git a/static/app/views/settings/organizationTeams/teamProjects.tsx b/static/app/views/settings/organizationTeams/teamProjects.tsx
index 0e6efd719678..100a92c9ea8e 100644
--- a/static/app/views/settings/organizationTeams/teamProjects.tsx
+++ b/static/app/views/settings/organizationTeams/teamProjects.tsx
@@ -7,7 +7,6 @@ import {Button} from '@sentry/scraps/button';
import {CompactSelect, type SelectOption} from '@sentry/scraps/compactSelect';
import {OverlayTrigger} from '@sentry/scraps/overlayTrigger';
import {Pagination} from '@sentry/scraps/pagination';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {hasEveryAccess} from 'sentry/components/acl/access';
@@ -167,24 +166,22 @@ export default function TeamProjects() {
sortProjects(linkedProjects).map(project => (
- }
+ aria-label={t('Remove')}
+ tooltipProps={{
+ title: t(
+ 'You do not have enough permission to change project association.'
+ ),
+ }}
+ onClick={() => {
+ handleLinkProject(project, 'remove');
+ }}
>
- }
- aria-label={t('Remove')}
- onClick={() => {
- handleLinkProject(project, 'remove');
- }}
- >
- {t('Remove')}
-
-
+ {t('Remove')}
+
))
) : linkedProjectsLoading ? null : (
diff --git a/static/app/views/settings/project/tempest/CredentialRow.tsx b/static/app/views/settings/project/tempest/CredentialRow.tsx
index 70d56e3efdbc..5ecf5550c1fb 100644
--- a/static/app/views/settings/project/tempest/CredentialRow.tsx
+++ b/static/app/views/settings/project/tempest/CredentialRow.tsx
@@ -37,27 +37,27 @@ export function CredentialRow({
- removeCredential({id: credential.id}) : undefined
+ }
+ disabled={isRemoving || !removeCredential}
>
- removeCredential({id: credential.id}) : undefined
- }
+ : }
+ tooltipProps={{
+ title: removeCredential
+ ? undefined
+ : t('You do not have permission to remove credentials.'),
+ }}
>
- : }
- >
- {t('Remove')}
-
-
-
+ {t('Remove')}
+
+
);
diff --git a/static/app/views/settings/project/tempest/addCredentialsButton.tsx b/static/app/views/settings/project/tempest/addCredentialsButton.tsx
index 409a014da46c..95f12926ad67 100644
--- a/static/app/views/settings/project/tempest/addCredentialsButton.tsx
+++ b/static/app/views/settings/project/tempest/addCredentialsButton.tsx
@@ -1,5 +1,4 @@
import {Button} from '@sentry/scraps/button';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {openAddTempestCredentialsModal} from 'sentry/actionCreators/modal';
import {IconAdd} from 'sentry/icons/iconAdd';
@@ -19,27 +18,23 @@ export function AddCredentialsButton({project, origin}: AddCredentialsButtonProp
const hasWriteAccess = useHasTempestWriteAccess(project);
return (
- }
+ tooltipProps={{title: t('You do not have permission to add new credentials.')}}
+ onClick={() => {
+ openAddTempestCredentialsModal({organization, project, origin});
+ trackAnalytics('tempest.credentials.add_modal_opened', {
+ organization,
+ project_slug: project.slug,
+ origin,
+ });
+ }}
>
- }
- onClick={() => {
- openAddTempestCredentialsModal({organization, project, origin});
- trackAnalytics('tempest.credentials.add_modal_opened', {
- organization,
- project_slug: project.slug,
- origin,
- });
- }}
- >
- {t('Add Credentials')}
-
-
+ {t('Add Credentials')}
+
);
}
diff --git a/static/app/views/settings/projectSourceMaps/debugIdBundleDeleteButton.tsx b/static/app/views/settings/projectSourceMaps/debugIdBundleDeleteButton.tsx
index 0820905d8d80..182cc15b9d97 100644
--- a/static/app/views/settings/projectSourceMaps/debugIdBundleDeleteButton.tsx
+++ b/static/app/views/settings/projectSourceMaps/debugIdBundleDeleteButton.tsx
@@ -1,6 +1,5 @@
import type {ButtonProps} from '@sentry/scraps/button';
import {Button} from '@sentry/scraps/button';
-import {Tooltip} from '@sentry/scraps/tooltip';
import {Access} from 'sentry/components/acl/access';
import {Confirm} from 'sentry/components/confirm';
@@ -19,20 +18,22 @@ export function DebugIdBundleDeleteButton({
return (
{({hasAccess}) => (
-
- }
+ size={size}
disabled={!hasAccess}
+ tooltipProps={{
+ title: t('You do not have permission to delete source maps.'),
+ }}
>
- } size={size} disabled={!hasAccess}>
- {t('Delete Source Maps')}
-
-
-
+ {t('Delete Source Maps')}
+
+
)}
);
diff --git a/tests/acceptance/test_auth_react.py b/tests/acceptance/test_auth_react.py
index 24418145754e..2be9a7552d22 100644
--- a/tests/acceptance/test_auth_react.py
+++ b/tests/acceptance/test_auth_react.py
@@ -295,7 +295,7 @@ def test_organization_without_sso(self) -> None:
xpath="//*[contains(normalize-space(.), 'Members sign in with email and password')]"
)
sso_button = self.browser.element(xpath="//button[normalize-space(.)='SSO']")
- assert not sso_button.is_enabled()
+ assert sso_button.get_attribute("aria-disabled") == "true"
# Password authentication remains available for the selected organization.
self.submit_visible_credentials(user.email, PASSWORD)