Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions static/app/components/acl/feature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class Feature extends React.Component<Props> {
customDisabledRender = hooks[0];
}
}

const renderProps = {
organization,
project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ class GlobalSelectionHeader extends React.Component<Props, State> {
searchQuery: '',
};

hasMultipleProjectSelection = () =>
new Set(this.props.organization.features).has('global-views');

// Returns an options object for `update*` actions
getUpdateOptions = () => ({
save: true,
Expand Down Expand Up @@ -343,7 +346,10 @@ class GlobalSelectionHeader extends React.Component<Props, State> {
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}
Expand Down
118 changes: 41 additions & 77 deletions static/app/components/organizations/multipleProjectSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -43,20 +42,14 @@ type State = {

class MultipleProjectSelector extends React.PureComponent<Props, State> {
static defaultProps = {
multi: true,
lockedMessageSubject: t('page'),
};

state: State = {
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);
Expand Down Expand Up @@ -100,12 +93,12 @@ class MultipleProjectSelector extends React.PureComponent<Props, State> {
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();
Expand Down Expand Up @@ -146,9 +139,9 @@ class MultipleProjectSelector extends React.PureComponent<Props, State> {
};

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 (
<Tooltip title={t('Issues Stream')} position="bottom">
<StyledLink
Expand Down Expand Up @@ -188,16 +181,15 @@ class MultipleProjectSelector extends React.PureComponent<Props, State> {
value,
projects,
isGlobalSelectionReady,
disableMultipleProjectSelection,
nonMemberProjects,
multi,
organization,
shouldForceProject,
forceProject,
showProjectSettingsLink,
footerMessage,
} = this.props;
const selectedProjectIds = new Set(value);
const multi = this.multi;

const allProjects = [...projects, ...nonMemberProjects];
const selected = allProjects.filter(project =>
Expand Down Expand Up @@ -254,7 +246,7 @@ class MultipleProjectSelector extends React.PureComponent<Props, State> {
menuFooter={({actions}) => (
<SelectorFooterControls
selected={selectedProjectIds}
disableMultipleProjectSelection={disableMultipleProjectSelection}
multi={multi}
organization={organization}
hasChanges={this.state.hasChanges}
onApply={() => this.handleUpdate(actions)}
Expand Down Expand Up @@ -313,84 +305,62 @@ class MultipleProjectSelector extends React.PureComponent<Props, State> {
}
}

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<number>;
disableMultipleProjectSelection?: boolean;
multi?: boolean;
hasChanges?: boolean;
message?: React.ReactNode;
};

const SelectorFooterControls = ({
selected,
disableMultipleProjectSelection,
multi,
hasChanges,
onApply,
onShowAllProjects,
onShowMyProjects,
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 (
<FooterContainer>
{message && <FooterMessage>{message}</FooterMessage>}

<FooterActions>
{!disableMultipleProjectSelection && (
<Feature
features={['organizations:global-views']}
organization={organization}
hookName="feature-disabled:project-selector-all-projects"
renderDisabled={false}
>
{({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 (
<Button priority="default" size="xsmall" onClick={onProjectClick}>
{buttonText}
</Button>
);
}}
</Feature>
{showAllProjects && (
<Button onClick={onShowAllProjects} priority="default" size="xsmall">
{t('View All Projects')}
</Button>
)}
{showMyProjects && (
<Button onClick={onShowMyProjects} priority="default" size="xsmall">
{t('View My Projects')}
</Button>
)}

{hasChanges && (
<SubmitButton onClick={onApply} size="xsmall" priority="primary">
{t('Apply Filter')}
Expand All @@ -404,28 +374,22 @@ 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;
`;

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)`
Expand Down
1 change: 0 additions & 1 deletion static/app/types/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you going to need to make a PR for getsentry or is that not out yet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evanpurkhiser Haven't merged getsentry yet

};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand All @@ -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 () {
Expand All @@ -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 () {
Expand All @@ -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');
});
});

Expand Down