diff --git a/static/app/components/acl/feature.tsx b/static/app/components/acl/feature.tsx index fd22e8c343c222..d96eedab7e9020 100644 --- a/static/app/components/acl/feature.tsx +++ b/static/app/components/acl/feature.tsx @@ -177,6 +177,7 @@ class Feature extends React.Component { customDisabledRender = hooks[0]; } } + const renderProps = { organization, project, diff --git a/static/app/components/organizations/globalSelectionHeader/globalSelectionHeader.tsx b/static/app/components/organizations/globalSelectionHeader/globalSelectionHeader.tsx index 5d7ba77a5493c5..fdb68556ef5f9a 100644 --- a/static/app/components/organizations/globalSelectionHeader/globalSelectionHeader.tsx +++ b/static/app/components/organizations/globalSelectionHeader/globalSelectionHeader.tsx @@ -171,6 +171,9 @@ class GlobalSelectionHeader extends React.Component { searchQuery: '', }; + hasMultipleProjectSelection = () => + new Set(this.props.organization.features).has('global-views'); + // Returns an options object for `update*` actions getUpdateOptions = () => ({ save: true, @@ -343,7 +346,10 @@ class GlobalSelectionHeader extends React.Component { value={this.state.projects || this.props.selection.projects} onChange={this.handleChangeProjects} onUpdate={this.handleUpdateProjects} - disableMultipleProjectSelection={disableMultipleProjectSelection} + multi={ + !disableMultipleProjectSelection && + this.hasMultipleProjectSelection() + } {...(loadingProjects ? paginatedProjectSelectorCallbacks : {})} showIssueStreamLink={showIssueStreamLink} showProjectSettingsLink={showProjectSettingsLink} diff --git a/static/app/components/organizations/multipleProjectSelector.tsx b/static/app/components/organizations/multipleProjectSelector.tsx index b3a84c46eb2e42..a1499e646c1f51 100644 --- a/static/app/components/organizations/multipleProjectSelector.tsx +++ b/static/app/components/organizations/multipleProjectSelector.tsx @@ -3,7 +3,6 @@ import {withRouter, WithRouterProps} from 'react-router'; import {ClassNames} from '@emotion/react'; import styled from '@emotion/styled'; -import Feature from 'app/components/acl/feature'; import Button from 'app/components/button'; import Link from 'app/components/links/link'; import HeaderItem from 'app/components/organizations/headerItem'; @@ -28,7 +27,7 @@ type Props = WithRouterProps & { onChange: (selected: number[]) => unknown; onUpdate: () => unknown; isGlobalSelectionReady?: boolean; - disableMultipleProjectSelection?: boolean; + multi?: boolean; shouldForceProject?: boolean; forceProject?: MinimalProject | null; showIssueStreamLink?: boolean; @@ -43,6 +42,7 @@ type State = { class MultipleProjectSelector extends React.PureComponent { static defaultProps = { + multi: true, lockedMessageSubject: t('page'), }; @@ -50,13 +50,6 @@ class MultipleProjectSelector extends React.PureComponent { hasChanges: false, }; - get multi() { - const {organization, disableMultipleProjectSelection} = this.props; - return ( - !disableMultipleProjectSelection && organization.features.includes('global-views') - ); - } - // Reset "hasChanges" state and call `onUpdate` callback doUpdate = () => { this.setState({hasChanges: false}, this.props.onUpdate); @@ -100,12 +93,12 @@ class MultipleProjectSelector extends React.PureComponent { return; } - const {value} = this.props; + const {value, multi} = this.props; analytics('projectselector.update', { count: value.length, path: getRouteStringFromRoutes(this.props.router.routes), org_id: parseInt(this.props.organization.id, 10), - multi: this.multi, + multi, }); this.doUpdate(); @@ -146,9 +139,9 @@ class MultipleProjectSelector extends React.PureComponent { }; renderProjectName() { - const {forceProject, location, organization, showIssueStreamLink} = this.props; + const {forceProject, location, multi, organization, showIssueStreamLink} = this.props; - if (showIssueStreamLink && forceProject && this.multi) { + if (showIssueStreamLink && forceProject && multi) { return ( { value, projects, isGlobalSelectionReady, - disableMultipleProjectSelection, nonMemberProjects, + multi, organization, shouldForceProject, forceProject, @@ -197,7 +190,6 @@ class MultipleProjectSelector extends React.PureComponent { footerMessage, } = this.props; const selectedProjectIds = new Set(value); - const multi = this.multi; const allProjects = [...projects, ...nonMemberProjects]; const selected = allProjects.filter(project => @@ -254,7 +246,7 @@ class MultipleProjectSelector extends React.PureComponent { menuFooter={({actions}) => ( this.handleUpdate(actions)} @@ -313,28 +305,20 @@ class MultipleProjectSelector extends React.PureComponent { } } -type FeatureRenderProps = { - hasFeature: boolean; - renderShowAllButton?: (p: { - canShowAllProjects: boolean; - onButtonClick: () => void; - }) => React.ReactNode; -}; - type ControlProps = { organization: Organization; - onApply: () => void; - onShowAllProjects: () => void; - onShowMyProjects: () => void; + onApply: (e: React.MouseEvent) => void; + onShowAllProjects: (e: React.MouseEvent) => void; + onShowMyProjects: (e: React.MouseEvent) => void; selected?: Set; - disableMultipleProjectSelection?: boolean; + multi?: boolean; hasChanges?: boolean; message?: React.ReactNode; }; const SelectorFooterControls = ({ selected, - disableMultipleProjectSelection, + multi, hasChanges, onApply, onShowAllProjects, @@ -342,55 +326,41 @@ const SelectorFooterControls = ({ organization, message, }: ControlProps) => { + let showMyProjects = false; + let showAllProjects = false; + if (multi) { + showMyProjects = true; + + const hasGlobalRole = + organization.role === 'owner' || organization.role === 'manager'; + const hasOpenMembership = organization.features.includes('open-membership'); + const allSelected = selected && selected.has(ALL_ACCESS_PROJECTS); + if ((hasGlobalRole || hasOpenMembership) && !allSelected) { + showAllProjects = true; + showMyProjects = false; + } + } + // Nothing to show. - if (disableMultipleProjectSelection && !hasChanges && !message) { + if (!(showAllProjects || showMyProjects || hasChanges || message)) { return null; } - // see if we should show "All Projects" or "My Projects" if disableMultipleProjectSelection isn't true - const hasGlobalRole = organization.role === 'owner' || organization.role === 'manager'; - const hasOpenMembership = organization.features.includes('open-membership'); - const allSelected = selected && selected.has(ALL_ACCESS_PROJECTS); - - const canShowAllProjects = (hasGlobalRole || hasOpenMembership) && !allSelected; - const onProjectClick = canShowAllProjects ? onShowAllProjects : onShowMyProjects; - const buttonText = canShowAllProjects - ? t('Select All Projects') - : t('Select My Projects'); - return ( {message && {message}} + - {!disableMultipleProjectSelection && ( - - {({renderShowAllButton, hasFeature}: FeatureRenderProps) => { - // if our hook is adding renderShowAllButton, render that - if (renderShowAllButton) { - return renderShowAllButton({ - onButtonClick: onProjectClick, - canShowAllProjects, - }); - } - // if no hook, render null if feature is disabled - if (!hasFeature) { - return null; - } - // otherwise render the buton - return ( - - ); - }} - + {showAllProjects && ( + + )} + {showMyProjects && ( + )} - {hasChanges && ( {t('Apply Filter')} @@ -404,20 +374,14 @@ const SelectorFooterControls = ({ export default withRouter(MultipleProjectSelector); const FooterContainer = styled('div')` - display: flex; - justify-content: space-between; + padding: ${space(1)} 0; `; - const FooterActions = styled('div')` - padding: ${space(1)} 0; display: flex; justify-content: flex-end; & > * { margin-left: ${space(0.5)}; } - &:empty { - display: none; - } `; const SubmitButton = styled(Button)` animation: 0.1s ${growIn} ease-in; @@ -425,7 +389,7 @@ const SubmitButton = styled(Button)` const FooterMessage = styled('div')` font-size: ${p => p.theme.fontSizeSmall}; - padding: ${space(1)} ${space(0.5)}; + padding: 0 ${space(0.5)}; `; const StyledProjectSelector = styled(ProjectSelector)` diff --git a/static/app/types/hooks.tsx b/static/app/types/hooks.tsx index aa85213ecc40fd..0a7f1ac5e0dae9 100644 --- a/static/app/types/hooks.tsx +++ b/static/app/types/hooks.tsx @@ -145,7 +145,6 @@ export type FeatureDisabledHooks = { 'feature-disabled:sso-saml2': FeatureDisabledHook; 'feature-disabled:trace-view-link': FeatureDisabledHook; 'feature-disabled:alert-wizard-performance': FeatureDisabledHook; - 'feature-disabled:project-selector-all-projects': FeatureDisabledHook; }; /** diff --git a/tests/js/spec/components/organizations/globalSelectionHeader.spec.jsx b/tests/js/spec/components/organizations/globalSelectionHeader.spec.jsx index d817c98fdc62f9..fdc934e10b8afa 100644 --- a/tests/js/spec/components/organizations/globalSelectionHeader.spec.jsx +++ b/tests/js/spec/components/organizations/globalSelectionHeader.spec.jsx @@ -1031,7 +1031,7 @@ describe('GlobalSelectionHeader', function () { // My projects in the footer expect( projectSelector.find('SelectorFooterControls Button').first().text() - ).toEqual('Select My Projects'); + ).toEqual('View My Projects'); }); it('shows "All Projects" button based on features', async function () { @@ -1056,7 +1056,7 @@ describe('GlobalSelectionHeader', function () { // All projects in the footer expect( projectSelector.find('SelectorFooterControls Button').first().text() - ).toEqual('Select All Projects'); + ).toEqual('View All Projects'); }); it('shows "All Projects" button based on role', async function () { @@ -1081,7 +1081,7 @@ describe('GlobalSelectionHeader', function () { // All projects in the footer expect( projectSelector.find('SelectorFooterControls Button').first().text() - ).toEqual('Select All Projects'); + ).toEqual('View All Projects'); }); it('shows "My Projects" when "all projects" is selected', async function () { @@ -1106,7 +1106,7 @@ describe('GlobalSelectionHeader', function () { // My projects in the footer expect( projectSelector.find('SelectorFooterControls Button').first().text() - ).toEqual('Select My Projects'); + ).toEqual('View My Projects'); }); });