Browse Source

ref(pageFilters): Clean up old components (#58284)

Cleaning up page filter-related components (this is the first part, more
to come). Changes include:
 - Updating import paths to point directly to the new components
 - Removing old selector/filter components
 - Changing the now-obsolete `alignDropdown` prop to `position`
Vu Luong 1 year ago
parent
commit
f99c483927

+ 0 - 106
static/app/components/datePageFilter.spec.tsx

@@ -1,106 +0,0 @@
-import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-
-import DatePageFilter from 'sentry/components/datePageFilter';
-import OrganizationStore from 'sentry/stores/organizationStore';
-import PageFiltersStore from 'sentry/stores/pageFiltersStore';
-
-const {organization, router, routerContext} = initializeOrg({
-  router: {
-    location: {
-      query: {},
-      pathname: '/test',
-    },
-    params: {},
-  },
-});
-
-describe('DatePageFilter', function () {
-  beforeEach(() => {
-    PageFiltersStore.init();
-    OrganizationStore.init();
-
-    OrganizationStore.onUpdate(organization, {replace: true});
-    PageFiltersStore.onInitializeUrlState(
-      {
-        projects: [],
-        environments: [],
-        datetime: {
-          period: '7d',
-          start: null,
-          end: null,
-          utc: null,
-        },
-      },
-      new Set()
-    );
-  });
-
-  it('can change period', async function () {
-    render(<DatePageFilter />, {
-      context: routerContext,
-      organization,
-    });
-
-    // Open time period dropdown
-    expect(screen.getByText('7D')).toBeInTheDocument();
-    await userEvent.click(screen.getByText('7D'));
-
-    // Click 30 day period
-    await userEvent.click(screen.getByText('Last 30 days'));
-
-    // Confirm selection changed visible text and query params
-    expect(await screen.findByText('30D')).toBeInTheDocument();
-    expect(router.push).toHaveBeenCalledWith(
-      expect.objectContaining({query: {statsPeriod: '30d'}})
-    );
-    expect(PageFiltersStore.getState()).toEqual({
-      isReady: true,
-      shouldPersist: true,
-      desyncedFilters: new Set(),
-      pinnedFilters: new Set(),
-      selection: {
-        datetime: {
-          period: '30d',
-          end: undefined,
-          start: undefined,
-          utc: null,
-        },
-        environments: [],
-        projects: [],
-      },
-    });
-  });
-
-  it('can pin datetime', async function () {
-    render(<DatePageFilter />, {
-      context: routerContext,
-      organization,
-    });
-
-    // Confirm no filters are pinned
-    expect(PageFiltersStore.getState()).toEqual(
-      expect.objectContaining({
-        pinnedFilters: new Set(),
-      })
-    );
-
-    // Open time period dropdown
-    await userEvent.click(screen.getByText('7D'));
-
-    // Click the pin button
-    const pinButton = screen.getByRole('button', {name: 'Lock filter'});
-    await userEvent.click(pinButton);
-
-    await screen.findByRole('button', {name: 'Lock filter', pressed: true});
-
-    // Check if the pin indicator has been added
-    expect(screen.getByLabelText('Filter applied across pages')).toBeInTheDocument();
-
-    expect(PageFiltersStore.getState()).toEqual(
-      expect.objectContaining({
-        pinnedFilters: new Set(['datetime']),
-      })
-    );
-  });
-});

+ 0 - 131
static/app/components/datePageFilter.tsx

@@ -1,131 +0,0 @@
-import {Fragment} from 'react';
-import styled from '@emotion/styled';
-
-import {updateDateTime} from 'sentry/actionCreators/pageFilters';
-import Datetime from 'sentry/components/dateTime';
-import {DatePageFilter as NewDatePageFilter} from 'sentry/components/organizations/datePageFilter';
-import PageFilterDropdownButton from 'sentry/components/organizations/pageFilters/pageFilterDropdownButton';
-import PageFilterPinIndicator from 'sentry/components/organizations/pageFilters/pageFilterPinIndicator';
-import TimeRangeSelector, {
-  ChangeData,
-} from 'sentry/components/organizations/timeRangeSelector';
-import {IconCalendar} from 'sentry/icons';
-import {
-  DEFAULT_DAY_END_TIME,
-  DEFAULT_DAY_START_TIME,
-  getFormattedDate,
-} from 'sentry/utils/dates';
-import useOrganization from 'sentry/utils/useOrganization';
-import usePageFilters from 'sentry/utils/usePageFilters';
-import useRouter from 'sentry/utils/useRouter';
-
-type Props = Omit<
-  React.ComponentProps<typeof TimeRangeSelector>,
-  'organization' | 'start' | 'end' | 'utc' | 'relative' | 'onUpdate'
-> & {
-  menuFooterMessage?: string;
-  /**
-   * Reset these URL params when we fire actions (custom routing only)
-   */
-  resetParamsOnChange?: string[];
-};
-
-function OldDatePageFilter({
-  resetParamsOnChange,
-  disabled,
-  storageNamespace,
-  ...props
-}: Props) {
-  const router = useRouter();
-  const {selection, desyncedFilters} = usePageFilters();
-  const organization = useOrganization();
-  const {start, end, period, utc} = selection.datetime;
-
-  const handleUpdate = (timePeriodUpdate: ChangeData) => {
-    const {relative, ...startEndUtc} = timePeriodUpdate;
-    const newTimePeriod = {
-      period: relative,
-      ...startEndUtc,
-    };
-
-    updateDateTime(newTimePeriod, router, {
-      save: true,
-      resetParams: resetParamsOnChange,
-      storageNamespace,
-    });
-  };
-
-  const customDropdownButton = ({getActorProps, isOpen}) => {
-    let label;
-    if (start && end) {
-      const startTimeFormatted = getFormattedDate(start, 'HH:mm:ss', {local: true});
-      const endTimeFormatted = getFormattedDate(end, 'HH:mm:ss', {local: true});
-
-      const showDateOnly =
-        startTimeFormatted === DEFAULT_DAY_START_TIME &&
-        endTimeFormatted === DEFAULT_DAY_END_TIME;
-
-      label = (
-        <Fragment>
-          <Datetime date={start} dateOnly={showDateOnly} />
-          {' – '}
-          <Datetime date={end} dateOnly={showDateOnly} />
-        </Fragment>
-      );
-    } else {
-      label = period?.toUpperCase();
-    }
-
-    return (
-      <PageFilterDropdownButton
-        detached
-        disabled={disabled}
-        hideBottomBorder={false}
-        isOpen={isOpen}
-        highlighted={desyncedFilters.has('datetime')}
-        data-test-id="page-filter-timerange-selector"
-        icon={
-          <PageFilterPinIndicator filter="datetime">
-            <IconCalendar />
-          </PageFilterPinIndicator>
-        }
-        {...getActorProps()}
-      >
-        <TitleContainer>{label}</TitleContainer>
-      </PageFilterDropdownButton>
-    );
-  };
-
-  return (
-    <TimeRangeSelector
-      organization={organization}
-      start={start}
-      end={end}
-      relative={period}
-      utc={utc}
-      onUpdate={handleUpdate}
-      customDropdownButton={customDropdownButton}
-      disabled={disabled}
-      showPin
-      detached
-      {...props}
-    />
-  );
-}
-
-const TitleContainer = styled('div')`
-  text-align: left;
-  ${p => p.theme.overflowEllipsis}
-`;
-
-function DatePageFilter(props: Props) {
-  const organization = useOrganization();
-
-  if (organization.features.includes('new-page-filter')) {
-    return <NewDatePageFilter {...props} />;
-  }
-
-  return <OldDatePageFilter {...props} />;
-}
-
-export default DatePageFilter;

+ 0 - 137
static/app/components/environmentPageFilter.spec.tsx

@@ -1,137 +0,0 @@
-import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-
-import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
-import OrganizationStore from 'sentry/stores/organizationStore';
-import PageFiltersStore from 'sentry/stores/pageFiltersStore';
-import ProjectsStore from 'sentry/stores/projectsStore';
-
-const {organization, router, routerContext} = initializeOrg({
-  organization: {features: ['global-views']},
-  project: undefined,
-  projects: [
-    {
-      id: '2',
-      slug: 'project-2',
-      environments: ['prod', 'staging'],
-    },
-  ],
-  router: {
-    location: {
-      pathname: '/organizations/org-slug/issues/',
-      query: {},
-    },
-    params: {},
-  },
-});
-
-describe('EnvironmentPageFilter', function () {
-  beforeEach(() => {
-    OrganizationStore.init();
-    OrganizationStore.onUpdate(organization, {replace: true});
-
-    ProjectsStore.init();
-    ProjectsStore.loadInitialData(organization.projects);
-
-    PageFiltersStore.reset();
-    PageFiltersStore.onInitializeUrlState(
-      {
-        projects: [2],
-        environments: [],
-        datetime: {start: null, end: null, period: '14d', utc: null},
-      },
-      new Set()
-    );
-  });
-
-  it('can pick environment', async function () {
-    render(<EnvironmentPageFilter />, {
-      context: routerContext,
-      organization,
-    });
-
-    // Open the environment dropdown
-    expect(screen.getByText('All Envs')).toBeInTheDocument();
-    await userEvent.click(screen.getByText('All Envs'));
-
-    // Select the 'prod' env
-    await userEvent.click(screen.getByRole('checkbox', {name: 'prod'}));
-
-    // Close the dropdown
-    await userEvent.click(screen.getAllByText('prod')[0]);
-
-    // Verify we were redirected
-    expect(router.push).toHaveBeenCalledWith(
-      expect.objectContaining({query: {environment: ['prod']}})
-    );
-  });
-
-  it('can pin environment', async function () {
-    render(<EnvironmentPageFilter />, {
-      context: routerContext,
-      organization,
-    });
-    // Confirm no filters are pinned
-    expect(PageFiltersStore.getState()).toEqual(
-      expect.objectContaining({
-        pinnedFilters: new Set(),
-      })
-    );
-
-    // Open the environment dropdown
-    expect(screen.getByText('All Envs')).toBeInTheDocument();
-    await userEvent.click(screen.getByText('All Envs'));
-
-    // Click the pin button
-    const pinButton = screen.getByRole('button', {name: 'Lock filter'});
-    await userEvent.click(pinButton, {skipHover: true});
-
-    await screen.findByRole('button', {name: 'Lock filter', pressed: true});
-
-    // Check if the pin indicator has been added
-    expect(screen.getByLabelText('Filter applied across pages')).toBeInTheDocument();
-
-    expect(PageFiltersStore.getState()).toEqual(
-      expect.objectContaining({
-        pinnedFilters: new Set(['environments']),
-      })
-    );
-  });
-
-  it('can quick select', async function () {
-    render(<EnvironmentPageFilter />, {
-      context: routerContext,
-      organization,
-    });
-
-    // Open the environment dropdown
-    expect(screen.getByText('All Envs')).toBeInTheDocument();
-    await userEvent.click(screen.getByText('All Envs'));
-
-    // Click the first environment directly
-    await userEvent.click(screen.getByText('prod'));
-
-    // Verify we were redirected
-    expect(router.push).toHaveBeenCalledWith(
-      expect.objectContaining({query: {environment: ['prod']}})
-    );
-
-    await screen.findByText('prod');
-
-    expect(PageFiltersStore.getState()).toEqual(
-      expect.objectContaining({
-        isReady: true,
-        selection: {
-          datetime: {
-            end: null,
-            period: '14d',
-            start: null,
-            utc: null,
-          },
-          environments: ['prod'],
-          projects: [2],
-        },
-      })
-    );
-  });
-});

+ 0 - 132
static/app/components/environmentPageFilter.tsx

@@ -1,132 +0,0 @@
-import styled from '@emotion/styled';
-
-import {updateEnvironments} from 'sentry/actionCreators/pageFilters';
-import Badge from 'sentry/components/badge';
-import {EnvironmentPageFilter as NewEnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
-import EnvironmentSelector from 'sentry/components/organizations/environmentSelector';
-import PageFilterDropdownButton from 'sentry/components/organizations/pageFilters/pageFilterDropdownButton';
-import PageFilterPinIndicator from 'sentry/components/organizations/pageFilters/pageFilterPinIndicator';
-import {IconWindow} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import {FormSize} from 'sentry/utils/theme';
-import {trimSlug} from 'sentry/utils/trimSlug';
-import useOrganization from 'sentry/utils/useOrganization';
-import usePageFilters from 'sentry/utils/usePageFilters';
-import useProjects from 'sentry/utils/useProjects';
-import useRouter from 'sentry/utils/useRouter';
-
-type EnvironmentSelectorProps = React.ComponentProps<typeof EnvironmentSelector>;
-
-type Props = {
-  alignDropdown?: EnvironmentSelectorProps['alignDropdown'];
-  disabled?: EnvironmentSelectorProps['disabled'];
-  /**
-   * Max character length for the dropdown title. Default is 20. This number
-   * is used to determine how many projects to show, and how much to truncate.
-   */
-  maxTitleLength?: number;
-  /**
-   * Reset these URL params when we fire actions (custom routing only)
-   */
-  resetParamsOnChange?: string[];
-  size?: FormSize;
-};
-
-function OldEnvironmentPageFilter({
-  resetParamsOnChange = [],
-  alignDropdown,
-  disabled,
-  maxTitleLength = 20,
-  size = 'md',
-}: Props) {
-  const router = useRouter();
-
-  const {projects, initiallyLoaded: projectsLoaded} = useProjects();
-  const organization = useOrganization();
-  const {selection, isReady, desyncedFilters} = usePageFilters();
-
-  const handleUpdateEnvironments = (environments: string[]) => {
-    updateEnvironments(environments, router, {
-      save: true,
-      resetParams: resetParamsOnChange,
-    });
-  };
-
-  const customDropdownButton: EnvironmentSelectorProps['customDropdownButton'] = ({
-    isOpen,
-    value,
-  }) => {
-    const environmentsToShow =
-      value[0]?.length + value[1]?.length <= maxTitleLength - 2
-        ? value.slice(0, 2)
-        : value.slice(0, 1);
-    const summary = value.length
-      ? environmentsToShow.map(env => trimSlug(env, maxTitleLength)).join(', ')
-      : t('All Envs');
-
-    return (
-      <PageFilterDropdownButton
-        isOpen={isOpen}
-        highlighted={desyncedFilters.has('environments')}
-        data-test-id="page-filter-environment-selector"
-        disabled={disabled}
-        size={size}
-        icon={
-          <PageFilterPinIndicator filter="environments">
-            <IconWindow />
-          </PageFilterPinIndicator>
-        }
-      >
-        <TitleContainer>
-          {summary}
-          {!!value.length && value.length > environmentsToShow.length && (
-            <Badge text={`+${value.length - environmentsToShow.length}`} />
-          )}
-        </TitleContainer>
-      </PageFilterDropdownButton>
-    );
-  };
-
-  const customLoadingIndicator = (
-    <PageFilterDropdownButton
-      icon={<IconWindow />}
-      showChevron={false}
-      disabled
-      data-test-id="page-filter-environment-selector"
-    >
-      <TitleContainer>{t('Loading\u2026')}</TitleContainer>
-    </PageFilterDropdownButton>
-  );
-
-  return (
-    <EnvironmentSelector
-      organization={organization}
-      projects={projects}
-      loadingProjects={!projectsLoaded || !isReady}
-      selectedProjects={selection.projects}
-      value={selection.environments}
-      onUpdate={handleUpdateEnvironments}
-      customDropdownButton={customDropdownButton}
-      customLoadingIndicator={customLoadingIndicator}
-      alignDropdown={alignDropdown}
-      disabled={disabled}
-    />
-  );
-}
-
-const TitleContainer = styled('div')`
-  text-align: left;
-  ${p => p.theme.overflowEllipsis}
-`;
-
-function EnvironmentPageFilter(props: Props) {
-  const organization = useOrganization();
-
-  if (organization.features.includes('new-page-filter')) {
-    return <NewEnvironmentPageFilter {...props} />;
-  }
-
-  return <OldEnvironmentPageFilter {...props} />;
-}
-
-export default EnvironmentPageFilter;

+ 4 - 4
static/app/components/feedback/feedbackFilters.tsx

@@ -1,9 +1,9 @@
 import {CSSProperties} from 'react';
 import {CSSProperties} from 'react';
 
 
-import DatePageFilter from 'sentry/components/datePageFilter';
-import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
+import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
+import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
 import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
 import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
-import ProjectPageFilter from 'sentry/components/projectPageFilter';
+import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
 
 
 interface Props {
 interface Props {
   className?: string;
   className?: string;
@@ -15,7 +15,7 @@ export default function FeedbackFilters({className, style}: Props) {
     <PageFilterBar className={className} style={style}>
     <PageFilterBar className={className} style={style}>
       <ProjectPageFilter resetParamsOnChange={['cursor']} />
       <ProjectPageFilter resetParamsOnChange={['cursor']} />
       <EnvironmentPageFilter resetParamsOnChange={['cursor']} />
       <EnvironmentPageFilter resetParamsOnChange={['cursor']} />
-      <DatePageFilter alignDropdown="left" resetParamsOnChange={['cursor']} />
+      <DatePageFilter resetParamsOnChange={['cursor']} />
     </PageFilterBar>
     </PageFilterBar>
   );
   );
 }
 }

+ 0 - 322
static/app/components/organizations/environmentSelector.spec.tsx

@@ -1,322 +0,0 @@
-import {Organization} from 'sentry-fixture/organization';
-
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-
-import EnvironmentSelector from 'sentry/components/organizations/environmentSelector';
-import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
-import ConfigStore from 'sentry/stores/configStore';
-
-describe('EnvironmentSelector', function () {
-  const onUpdate = jest.fn();
-
-  const projects = [
-    TestStubs.Project({
-      id: '1',
-      slug: 'first',
-      environments: ['production', 'staging'],
-    }),
-    TestStubs.Project({
-      id: '2',
-      slug: 'second',
-      environments: ['dev'],
-    }),
-    TestStubs.Project({
-      id: '3',
-      slug: 'no member',
-      environments: ['no-env'],
-      isMember: false,
-    }),
-  ];
-  const organization = Organization({projects});
-  const selectedProjects = [1, 2];
-  const routerContext = TestStubs.routerContext([
-    {
-      organization,
-    },
-  ]);
-
-  beforeEach(function () {
-    ConfigStore.init();
-    ConfigStore.loadInitialData(TestStubs.Config());
-    onUpdate.mockReset();
-  });
-
-  const customDropdownButton = ({value}) => {
-    const summary = value.length ? value.join(', ') : 'All Environments';
-    return <div>{summary}</div>;
-  };
-
-  const selectorProps = {
-    organization,
-    projects,
-    value: [],
-    loadingProjects: false,
-    selectedProjects,
-    onUpdate,
-    customDropdownButton,
-    customLoadingIndicator: () => 'Loading...',
-  };
-
-  function renderSelector(
-    props?: Partial<React.ComponentProps<typeof EnvironmentSelector>>
-  ) {
-    return render(<EnvironmentSelector {...selectorProps} {...props} />, {
-      context: routerContext,
-    });
-  }
-
-  async function clickMenu() {
-    const button = await screen.findByRole('button', {name: 'All Environments'});
-    await userEvent.click(button);
-  }
-
-  it('can select and change environments', async function () {
-    renderSelector();
-    await clickMenu();
-
-    for (const box of screen.queryAllByRole('checkbox')) {
-      await userEvent.click(box);
-    }
-    await userEvent.click(await screen.findByLabelText('Apply'));
-
-    expect(onUpdate).toHaveBeenCalledWith(['dev', 'production', 'staging']);
-  });
-
-  it('selects multiple environments and uses button to update', async function () {
-    renderSelector();
-    await clickMenu();
-
-    await userEvent.click(screen.queryAllByRole('checkbox')[0]);
-    expect(onUpdate).not.toHaveBeenCalled();
-
-    await clickMenu();
-    expect(onUpdate).toHaveBeenCalledWith(['dev']);
-  });
-
-  it('does not update when there are no changes', async function () {
-    renderSelector();
-    await clickMenu();
-
-    // Click and unclick boxes
-    for (const box of screen.queryAllByRole('checkbox')) {
-      await userEvent.click(box);
-    }
-    for (const box of screen.queryAllByRole('checkbox')) {
-      await userEvent.click(box);
-    }
-
-    await clickMenu();
-    expect(onUpdate).not.toHaveBeenCalled();
-  });
-
-  it('updates environment options when projects selection changes', async function () {
-    const {rerender} = renderSelector();
-
-    await clickMenu();
-    for (const box of screen.queryAllByRole('checkbox')) {
-      await userEvent.click(box);
-    }
-    await clickMenu();
-
-    // Changing projects will unselect environments. Project 2 has 1 environment
-    rerender(<EnvironmentSelector {...selectorProps} selectedProjects={[2]} />);
-
-    // There should just be 1 environment now
-    await clickMenu();
-    expect(screen.getByLabelText('dev')).toBeInTheDocument();
-    expect(screen.queryByLabelText('production')).not.toBeInTheDocument();
-  });
-
-  it('shows non-member project environments when selected', async function () {
-    renderSelector({selectedProjects: [3]});
-    await clickMenu();
-
-    expect(screen.getByRole('checkbox')).toBeInTheDocument();
-
-    expect(screen.getByLabelText('no-env')).toBeInTheDocument();
-  });
-
-  it('shows member project environments when there are no projects selected', async function () {
-    renderSelector({selectedProjects: []});
-    await clickMenu();
-
-    expect(screen.queryAllByRole('checkbox')).toHaveLength(3);
-
-    expect(screen.getByLabelText('production')).toBeInTheDocument();
-    expect(screen.getByLabelText('staging')).toBeInTheDocument();
-    expect(screen.getByLabelText('dev')).toBeInTheDocument();
-  });
-
-  it('does not open selector menu when disabled', async function () {
-    renderSelector({disabled: true});
-    await clickMenu();
-
-    // Dropdown not open
-    expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
-  });
-
-  describe('Superuser My Projects / all environments', function () {
-    it('shows env when no team belonging', async function () {
-      ConfigStore.set('user', {...ConfigStore.get('user'), isSuperuser: true});
-
-      renderSelector({
-        selectedProjects: [],
-        projects: [
-          TestStubs.Project({
-            id: '1',
-            slug: 'first',
-            environments: ['production', 'staging'],
-            isMember: false,
-          }),
-          TestStubs.Project({
-            id: '2',
-            slug: 'second',
-            environments: ['dev'],
-            isMember: false,
-          }),
-        ],
-      });
-
-      await clickMenu();
-
-      expect(screen.queryAllByRole('checkbox')).toHaveLength(3);
-
-      expect(screen.getByLabelText('production')).toBeInTheDocument();
-      expect(screen.getByLabelText('staging')).toBeInTheDocument();
-      expect(screen.getByLabelText('dev')).toBeInTheDocument();
-    });
-
-    it('shows env when belongs one team', async function () {
-      // XXX: Ideally, "My Projects" and "All Projects" should be different if a
-      // superuser was to belong to at least one project
-      ConfigStore.set('user', {...ConfigStore.get('user'), isSuperuser: true});
-
-      // This user is member of one project
-      renderSelector({
-        selectedProjects: [],
-        projects: [
-          TestStubs.Project({
-            id: '1',
-            slug: 'first',
-            environments: ['production', 'staging'],
-          }),
-          TestStubs.Project({
-            id: '2',
-            slug: 'second',
-            environments: ['dev'],
-            isMember: false,
-          }),
-        ],
-      });
-
-      await clickMenu();
-
-      expect(screen.queryAllByRole('checkbox')).toHaveLength(3);
-
-      expect(screen.getByLabelText('production')).toBeInTheDocument();
-      expect(screen.getByLabelText('staging')).toBeInTheDocument();
-      expect(screen.getByLabelText('dev')).toBeInTheDocument();
-    });
-  });
-
-  describe('Superuser All Projects / all environments', function () {
-    it('shows env when no team belonging', async function () {
-      ConfigStore.set('user', {...ConfigStore.get('user'), isSuperuser: true});
-
-      renderSelector({
-        selectedProjects: [ALL_ACCESS_PROJECTS],
-        projects: [
-          TestStubs.Project({
-            id: '1',
-            slug: 'first',
-            environments: ['production', 'staging'],
-            isMember: false,
-          }),
-          TestStubs.Project({
-            id: '2',
-            slug: 'second',
-            environments: ['dev'],
-            isMember: false,
-          }),
-        ],
-      });
-
-      await clickMenu();
-
-      expect(screen.queryAllByRole('checkbox')).toHaveLength(3);
-
-      expect(screen.getByLabelText('production')).toBeInTheDocument();
-      expect(screen.getByLabelText('staging')).toBeInTheDocument();
-      expect(screen.getByLabelText('dev')).toBeInTheDocument();
-    });
-
-    it('shows env when belongs one team', async function () {
-      // XXX: Ideally, "My Projects" and "All Projects" should be different if a
-      // superuser was to belong to at least one project
-      ConfigStore.set('user', {...ConfigStore.get('user'), isSuperuser: true});
-
-      renderSelector({
-        selectedProjects: [ALL_ACCESS_PROJECTS],
-        projects: [
-          TestStubs.Project({
-            id: '1',
-            slug: 'first',
-            environments: ['production', 'staging'],
-          }),
-          TestStubs.Project({
-            id: '2',
-            slug: 'second',
-            environments: ['dev'],
-            isMember: false,
-          }),
-        ],
-      });
-
-      await clickMenu();
-
-      expect(screen.queryAllByRole('checkbox')).toHaveLength(3);
-
-      expect(screen.getByLabelText('production')).toBeInTheDocument();
-      expect(screen.getByLabelText('staging')).toBeInTheDocument();
-      expect(screen.getByLabelText('dev')).toBeInTheDocument();
-    });
-  });
-
-  it('shows all project environments when "all projects" is selected', async function () {
-    renderSelector({selectedProjects: [ALL_ACCESS_PROJECTS]});
-    await clickMenu();
-
-    expect(screen.queryAllByRole('checkbox')).toHaveLength(4);
-
-    expect(screen.getByLabelText('production')).toBeInTheDocument();
-    expect(screen.getByLabelText('staging')).toBeInTheDocument();
-    expect(screen.getByLabelText('dev')).toBeInTheDocument();
-    expect(screen.getByLabelText('no-env')).toBeInTheDocument();
-  });
-
-  it('shows the distinct union of environments across all projects', async function () {
-    renderSelector({selectedProjects: [1, 2]});
-    await clickMenu();
-
-    expect(screen.queryAllByRole('checkbox')).toHaveLength(3);
-
-    expect(screen.getByLabelText('production')).toBeInTheDocument();
-    expect(screen.getByLabelText('staging')).toBeInTheDocument();
-    expect(screen.getByLabelText('dev')).toBeInTheDocument();
-  });
-
-  it('can quick select an environment', async function () {
-    renderSelector();
-    await clickMenu();
-
-    // Select something first, we want to make sure that having a changed
-    // selection doesn't effect the quick select
-    await userEvent.click(screen.getByRole('checkbox', {name: 'dev'}));
-
-    // Now 'quick select' the production environment
-    await userEvent.click(screen.getByText('production'));
-
-    expect(onUpdate).toHaveBeenCalledTimes(1);
-    expect(onUpdate).toHaveBeenCalledWith(['production']);
-  });
-});

+ 0 - 258
static/app/components/organizations/environmentSelector.tsx

@@ -1,258 +0,0 @@
-import {Fragment, useEffect, useRef, useState} from 'react';
-import {ClassNames} from '@emotion/react';
-import styled from '@emotion/styled';
-import isEqual from 'lodash/isEqual';
-import sortBy from 'lodash/sortBy';
-
-import {MenuActions} from 'sentry/components/deprecatedDropdownMenu';
-import DropdownAutoComplete from 'sentry/components/dropdownAutoComplete';
-import {MenuFooterChildProps} from 'sentry/components/dropdownAutoComplete/menu';
-import {Item} from 'sentry/components/dropdownAutoComplete/types';
-import Highlight from 'sentry/components/highlight';
-import MultipleSelectorSubmitRow from 'sentry/components/organizations/multipleSelectorSubmitRow';
-import PageFilterRow from 'sentry/components/organizations/pageFilterRow';
-import PageFilterPinButton from 'sentry/components/organizations/pageFilters/pageFilterPinButton';
-import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
-import {t} from 'sentry/locale';
-import ConfigStore from 'sentry/stores/configStore';
-import {space} from 'sentry/styles/space';
-import {Organization, Project} from 'sentry/types';
-import {trackAnalytics} from 'sentry/utils/analytics';
-import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes';
-import theme from 'sentry/utils/theme';
-import useRouter from 'sentry/utils/useRouter';
-
-type Props = {
-  customDropdownButton: (config: {
-    actions: MenuActions;
-    isOpen: boolean;
-    value: string[];
-  }) => React.ReactElement;
-  customLoadingIndicator: React.ReactNode;
-  loadingProjects: boolean;
-  /**
-   * When menu is closed
-   */
-  onUpdate: (environments: string[]) => void;
-  organization: Organization;
-  projects: Project[];
-  selectedProjects: number[];
-  /**
-   * This component must be controlled using a value array
-   */
-  value: string[];
-  /**
-   * Aligns dropdown menu to left or right of button
-   */
-  alignDropdown?: 'left' | 'right';
-  disabled?: boolean;
-};
-
-/**
- * Environment Selector
- *
- * Note we only fetch environments when this component is mounted
- */
-function EnvironmentSelector({
-  loadingProjects,
-  onUpdate,
-  organization,
-  projects,
-  selectedProjects,
-  value,
-  alignDropdown,
-  customDropdownButton,
-  customLoadingIndicator,
-  disabled,
-}: Props) {
-  const router = useRouter();
-  const [selectedEnvs, setSelectedEnvs] = useState(value);
-  const hasChanges = !isEqual(selectedEnvs, value);
-
-  // Update selected envs value on change
-  useEffect(() => {
-    setSelectedEnvs(previousSelectedEnvs => {
-      lastSelectedEnvs.current = previousSelectedEnvs;
-      return value;
-    });
-  }, [value]);
-
-  // We keep a separate list of selected environments to use for sorting. This
-  // allows us to only update it after the list is closed, to avoid the list
-  // jumping around while selecting projects.
-  const lastSelectedEnvs = useRef(value);
-
-  // Ref to help avoid updating stale selected values
-  const didQuickSelect = useRef(false);
-
-  /**
-   * Toggle selected state of an environment
-   */
-  const toggleCheckbox = (environment: string) => {
-    const willRemove = selectedEnvs.includes(environment);
-
-    const updatedSelectedEnvs = willRemove
-      ? selectedEnvs.filter(env => env !== environment)
-      : [...selectedEnvs, environment];
-
-    trackAnalytics('environmentselector.toggle', {
-      organization,
-      action: willRemove ? 'removed' : 'added',
-      path: getRouteStringFromRoutes(router.routes),
-    });
-
-    setSelectedEnvs(updatedSelectedEnvs);
-  };
-
-  const handleSave = (actions: MenuFooterChildProps['actions']) => {
-    actions.close();
-    onUpdate(selectedEnvs);
-  };
-
-  const handleMenuClose = () => {
-    // Only update if there are changes
-    if (!hasChanges || didQuickSelect.current) {
-      didQuickSelect.current = false;
-      return;
-    }
-
-    trackAnalytics('environmentselector.update', {
-      organization,
-      count: selectedEnvs.length,
-      path: getRouteStringFromRoutes(router.routes),
-    });
-
-    onUpdate(selectedEnvs);
-  };
-
-  const handleQuickSelect = (item: Item) => {
-    trackAnalytics('environmentselector.direct_selection', {
-      organization,
-      path: getRouteStringFromRoutes(router.routes),
-    });
-
-    const selectedEnvironments = [item.value];
-
-    setSelectedEnvs(selectedEnvironments);
-    onUpdate(selectedEnvironments);
-
-    // Track that we just did a click select so we don't trigger an update in
-    // the close handler.
-    didQuickSelect.current = true;
-  };
-
-  const {user} = ConfigStore.getState();
-
-  const unsortedEnvironments = projects.flatMap(project => {
-    const projectId = parseInt(project.id, 10);
-    // Include environments from:
-    // - all projects if the user is a superuser
-    // - the requested projects
-    // - all member projects if 'my projects' (empty list) is selected.
-    // - all projects if -1 is the only selected project.
-    if (
-      (selectedProjects.length === 1 &&
-        selectedProjects[0] === ALL_ACCESS_PROJECTS &&
-        project.hasAccess) ||
-      (selectedProjects.length === 0 && (project.isMember || user.isSuperuser)) ||
-      selectedProjects.includes(projectId)
-    ) {
-      return project.environments;
-    }
-
-    return [];
-  });
-
-  const uniqueEnvironments = Array.from(new Set(unsortedEnvironments));
-
-  // Sort with the last selected environments at the top
-  const environments = sortBy(uniqueEnvironments, env => [
-    !lastSelectedEnvs.current.find(e => e === env),
-    env,
-  ]);
-
-  const validatedValue = value.filter(env => environments.includes(env));
-
-  if (loadingProjects) {
-    return <Fragment>{customLoadingIndicator}</Fragment>;
-  }
-
-  return (
-    <ClassNames>
-      {({css}) => (
-        <StyledDropdownAutoComplete
-          alignMenu={alignDropdown}
-          allowActorToggle
-          closeOnSelect
-          blendCorner={false}
-          detached
-          disabled={disabled}
-          searchPlaceholder={t('Filter environments')}
-          onSelect={handleQuickSelect}
-          onClose={handleMenuClose}
-          maxHeight={500}
-          rootClassName={css`
-            position: relative;
-            display: flex;
-          `}
-          inputProps={{style: {padding: 8, paddingLeft: 14}}}
-          emptyMessage={t('You have no environments')}
-          noResultsMessage={t('No environments found')}
-          virtualizedHeight={theme.headerSelectorRowHeight}
-          emptyHidesInput
-          inputActions={
-            <StyledPinButton
-              organization={organization}
-              filter="environments"
-              size="xs"
-            />
-          }
-          menuFooter={({actions}) =>
-            hasChanges ? (
-              <MultipleSelectorSubmitRow onSubmit={() => handleSave(actions)} />
-            ) : null
-          }
-          items={environments.map(env => ({
-            value: env,
-            searchKey: env,
-            label: ({inputValue}) => (
-              <PageFilterRow
-                data-test-id={`environment-${env}`}
-                checked={selectedEnvs.includes(env)}
-                onSelectedChange={() => {
-                  toggleCheckbox(env);
-                }}
-              >
-                <Highlight text={inputValue}>{env}</Highlight>
-              </PageFilterRow>
-            ),
-          }))}
-        >
-          {({isOpen, actions}) =>
-            customDropdownButton({isOpen, actions, value: validatedValue})
-          }
-        </StyledDropdownAutoComplete>
-      )}
-    </ClassNames>
-  );
-}
-
-export default EnvironmentSelector;
-
-const StyledDropdownAutoComplete = styled(DropdownAutoComplete)`
-  background: ${p => p.theme.background};
-  border: 1px solid ${p => p.theme.border};
-  position: absolute;
-  top: 100%;
-
-  ${p =>
-    !p.detached &&
-    `
-    margin-top: 0;
-    border-radius: ${p.theme.borderRadiusBottom};
-  `};
-`;
-
-const StyledPinButton = styled(PageFilterPinButton)`
-  margin: 0 ${space(1)};
-`;

+ 0 - 110
static/app/components/organizations/pageFilterRow.tsx

@@ -1,110 +0,0 @@
-import {useMemo} from 'react';
-import {css} from '@emotion/react';
-import styled from '@emotion/styled';
-
-import Checkbox from 'sentry/components/checkbox';
-import {space} from 'sentry/styles/space';
-import domId from 'sentry/utils/domId';
-
-const defaultRenderCheckbox = ({checkbox}) => checkbox;
-
-type CheckboxRenderOptions = {
-  checkbox: React.ReactNode;
-  checked?: boolean;
-};
-
-interface Props extends React.HTMLAttributes<HTMLDivElement> {
-  checked: boolean;
-  children: React.ReactNode;
-  onSelectedChange: () => void;
-  multi?: boolean;
-  /**
-   * This is a render prop which may be used to augment the checkbox rendered
-   * to the right of the row. It will receive the default `checkbox` as a
-   * prop along with the `checked` boolean.
-   */
-  renderCheckbox?: (options: CheckboxRenderOptions) => React.ReactNode;
-}
-
-function PageFilterRow({
-  checked,
-  onSelectedChange,
-  children,
-  multi = true,
-  renderCheckbox = defaultRenderCheckbox,
-  ...props
-}: Props) {
-  const rowId = useMemo(() => domId('page_filter_row'), []);
-
-  const checkbox = (
-    <MultiselectCheckbox
-      disabled={!multi}
-      checked={checked}
-      onClick={e => e.stopPropagation()}
-      onChange={multi ? onSelectedChange : undefined}
-      aria-labelledby={rowId}
-      inputCss={multi && checkboxInputStyles}
-    />
-  );
-
-  return (
-    <Container aria-checked={checked} isChecked={checked} {...props}>
-      <Label id={rowId} multi={multi}>
-        {children}
-      </Label>
-      <CheckboxContainer>{renderCheckbox({checkbox, checked})}</CheckboxContainer>
-    </Container>
-  );
-}
-
-const checkboxInputStyles = css`
-  /* Make the hitbox of the checkbox a bit larger */
-  top: -${space(2)};
-  left: -${space(0.5)};
-  width: 34px;
-  height: 44px;
-`;
-
-const MultiselectCheckbox = styled(Checkbox)`
-  margin: 0 ${space(0.5)};
-  margin-right: ${space(0.75)};
-`;
-
-const Container = styled('div')<{isChecked: boolean}>`
-  display: grid;
-  grid-template-columns: 1fr max-content;
-  align-items: center;
-  font-size: ${p => p.theme.fontSizeMedium};
-  font-weight: 400;
-  padding-left: ${space(0.5)};
-
-  ${MultiselectCheckbox} {
-    opacity: ${p => (p.isChecked ? 1 : 0.33)};
-  }
-
-  &:hover ${MultiselectCheckbox} {
-    opacity: 1;
-  }
-`;
-
-const Label = styled('div')<{multi: boolean}>`
-  display: flex;
-  flex-shrink: 1;
-  overflow: hidden;
-  align-items: center;
-  height: 100%;
-  flex-grow: 1;
-  user-select: none;
-  white-space: nowrap;
-
-  &:hover {
-    text-decoration: ${p => (p.multi ? 'underline' : null)};
-    color: ${p => (p.multi ? p.theme.linkColor : null)};
-  }
-`;
-
-const CheckboxContainer = styled('div')`
-  line-height: 0;
-`;
-
-export default PageFilterRow;

+ 0 - 146
static/app/components/organizations/projectSelector/footer.tsx

@@ -1,146 +0,0 @@
-import styled from '@emotion/styled';
-
-import Feature from 'sentry/components/acl/feature';
-import {Button} from 'sentry/components/button';
-import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
-import {IconAdd} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import {growIn} from 'sentry/styles/animations';
-import {space} from 'sentry/styles/space';
-import {Organization} from 'sentry/types';
-
-type ShowAllButtonProps = {
-  canShowAllProjects: boolean;
-  onButtonClick: () => void;
-};
-
-type FeatureRenderProps = {
-  hasFeature: boolean;
-  renderShowAllButton?: (p: ShowAllButtonProps) => React.ReactNode;
-};
-
-type Props = {
-  onApply: () => void;
-  onShowAllProjects: () => void;
-  onShowMyProjects: () => void;
-  organization: Organization;
-  disableMultipleProjectSelection?: boolean;
-  hasChanges?: boolean;
-  message?: React.ReactNode;
-  selected?: Set<number>;
-};
-
-function ProjectSelectorFooter({
-  selected,
-  disableMultipleProjectSelection,
-  hasChanges,
-  onApply,
-  onShowAllProjects,
-  onShowMyProjects,
-  organization,
-  message,
-}: Props) {
-  // Nothing to show.
-  if (disableMultipleProjectSelection && !hasChanges && !message) {
-    return null;
-  }
-
-  // see if we should show "All Projects" or "My Projects" if disableMultipleProjectSelection isn't true
-  const hasGlobalRole =
-    organization.orgRole === 'owner' || organization.orgRole === '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');
-
-  const hasProjectWrite = organization.access.includes('project:write');
-  const newProjectUrl = `/organizations/${organization.slug}/projects/new/`;
-
-  return (
-    <FooterContainer hasMessage={!!message}>
-      {message && <FooterMessage>{message}</FooterMessage>}
-      <FooterActions>
-        <Button
-          aria-label={t('Add Project')}
-          disabled={!hasProjectWrite}
-          to={newProjectUrl}
-          size="xs"
-          icon={<IconAdd size="xs" isCircled />}
-          title={
-            !hasProjectWrite ? t("You don't have permission to add a project") : undefined
-          }
-        >
-          {t('Project')}
-        </Button>
-        {!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="xs" onClick={onProjectClick}>
-                  {buttonText}
-                </Button>
-              );
-            }}
-          </Feature>
-        )}
-        {hasChanges && (
-          <SubmitButton onClick={onApply} size="xs" priority="primary">
-            {t('Apply Filter')}
-          </SubmitButton>
-        )}
-      </FooterActions>
-    </FooterContainer>
-  );
-}
-
-export default ProjectSelectorFooter;
-
-const FooterContainer = styled('div')<{hasMessage: boolean}>`
-  display: flex;
-  justify-content: ${p => (p.hasMessage ? 'space-between' : 'flex-end')};
-`;
-
-const FooterActions = styled('div')`
-  display: grid;
-  grid-auto-flow: column dense;
-  justify-items: end;
-  padding: ${space(1)} 0;
-  gap: ${space(1)};
-
-  & > * {
-    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)};
-`;

+ 0 - 452
static/app/components/organizations/projectSelector/index.spec.tsx

@@ -1,452 +0,0 @@
-import {Organization} from 'sentry-fixture/organization';
-
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-
-import ProjectSelector, {
-  ProjectSelectorProps,
-} from 'sentry/components/organizations/projectSelector';
-import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
-
-describe('ProjectSelector', function () {
-  const testProject = TestStubs.Project({
-    id: '1',
-    slug: 'test-project',
-    isBookmarked: true,
-    isMember: true,
-  });
-  const anotherProject = TestStubs.Project({
-    id: '2',
-    slug: 'another-project',
-    isMember: true,
-  });
-
-  const mockOrg = Organization({
-    id: '1',
-    slug: 'org',
-    features: ['new-teams', 'global-views'],
-    access: [],
-  });
-
-  const routerContext = TestStubs.routerContext([{organization: mockOrg}]);
-
-  async function openMenu() {
-    await userEvent.click(screen.getByRole('button'));
-  }
-
-  async function applyMenu() {
-    await userEvent.click(screen.getByRole('button', {name: 'Apply Filter'}));
-  }
-
-  const props: ProjectSelectorProps = {
-    customDropdownButton: () => <span>Project Picker</span>,
-    customLoadingIndicator: () => 'Loading...',
-    isGlobalSelectionReady: true,
-    organization: mockOrg,
-    memberProjects: [testProject, anotherProject],
-    nonMemberProjects: [],
-    value: [],
-    onApplyChange: () => {},
-    onChange: () => {},
-  };
-
-  it('should show empty message with no projects button, when no projects, and has no "project:write" access', async function () {
-    render(
-      <ProjectSelector
-        {...props}
-        memberProjects={[]}
-        organization={Organization({
-          id: 'org',
-          slug: 'org-slug',
-          access: [],
-          features: [],
-        })}
-      />,
-      {context: routerContext}
-    );
-
-    await openMenu();
-    expect(screen.getByText('You have no projects')).toBeInTheDocument();
-
-    // Should not have "Create Project" button
-    const createProject = screen.getByLabelText('Add Project');
-    expect(createProject).toBeInTheDocument();
-    expect(createProject).toBeDisabled();
-  });
-
-  it('should show empty message and create project button, when no projects and has "project:write" access', async function () {
-    render(
-      <ProjectSelector
-        {...props}
-        memberProjects={[]}
-        organization={Organization({
-          id: 'org',
-          slug: 'org-slug',
-          access: ['project:write'],
-          features: [],
-        })}
-      />,
-      {context: routerContext}
-    );
-
-    await openMenu();
-    expect(screen.getByText('You have no projects')).toBeInTheDocument();
-
-    // Should not have "Create Project" button
-    const createProject = screen.getByLabelText('Add Project');
-    expect(createProject).toBeInTheDocument();
-    expect(createProject).toBeEnabled();
-  });
-
-  it('does not open selector menu when disabled', async function () {
-    render(<ProjectSelector {...props} disabled />, {context: routerContext});
-    await openMenu();
-
-    expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
-  });
-
-  it('lists projects and has filter', async function () {
-    render(<ProjectSelector {...props} />, {context: routerContext});
-    await openMenu();
-
-    expect(screen.getByText(testProject.slug)).toBeInTheDocument();
-    expect(screen.getByText(anotherProject.slug)).toBeInTheDocument();
-  });
-
-  it('can filter projects by project name', async function () {
-    render(<ProjectSelector {...props} />, {context: routerContext});
-    await openMenu();
-
-    screen.getByRole('textbox').focus();
-    await userEvent.keyboard('TEST');
-
-    const item = screen.getByTestId('badge-display-name');
-    expect(item).toBeInTheDocument();
-    expect(item).toHaveTextContent(testProject.slug);
-  });
-
-  it('shows empty filter message when filtering has no results', async function () {
-    render(<ProjectSelector {...props} />, {context: routerContext});
-    await openMenu();
-
-    screen.getByRole('textbox').focus();
-    await userEvent.keyboard('Foo');
-
-    expect(screen.queryByTestId('badge-display-name')).not.toBeInTheDocument();
-    expect(screen.getByText('No projects found')).toBeInTheDocument();
-  });
-
-  it('does not close dropdown when input is clicked', async function () {
-    render(<ProjectSelector {...props} />, {context: routerContext});
-    await openMenu();
-
-    await userEvent.click(screen.getByRole('textbox'));
-
-    // Dropdown is still open
-    expect(screen.getByText(testProject.slug)).toBeInTheDocument();
-  });
-
-  it('closes dropdown when project is selected', async function () {
-    render(<ProjectSelector {...props} />, {context: routerContext});
-    await openMenu();
-
-    await userEvent.click(screen.getByText(testProject.slug));
-
-    // Dropdown is closed
-    expect(screen.queryByText(testProject.slug)).not.toBeInTheDocument();
-  });
-
-  it('calls callback when project is selected', async function () {
-    const onApplyChangeMock = jest.fn();
-    render(<ProjectSelector {...props} onApplyChange={onApplyChangeMock} />, {
-      context: routerContext,
-    });
-    await openMenu();
-
-    // Select first project
-    await userEvent.click(screen.getByText(testProject.slug));
-
-    expect(onApplyChangeMock).toHaveBeenCalledWith([parseInt(testProject.id, 10)]);
-  });
-
-  it('does not call `onUpdate` when using multi select', async function () {
-    const onChangeMock = jest.fn();
-    const onApplyChangeMock = jest.fn();
-    render(
-      <ProjectSelector
-        {...props}
-        onChange={onChangeMock}
-        onApplyChange={onApplyChangeMock}
-      />,
-      {context: routerContext}
-    );
-    await openMenu();
-
-    // Check the first project
-    await userEvent.click(screen.getByRole('checkbox', {name: testProject.slug}));
-
-    expect(onChangeMock).toHaveBeenCalled();
-    expect(onApplyChangeMock).not.toHaveBeenCalled();
-  });
-
-  it('displays multi projects with non member projects', async function () {
-    const nonMemberProject = TestStubs.Project({id: '2'});
-
-    render(<ProjectSelector {...props} nonMemberProjects={[nonMemberProject]} />, {
-      context: routerContext,
-    });
-    await openMenu();
-
-    expect(screen.getByText("Projects I don't belong to")).toBeInTheDocument();
-    expect(screen.getAllByTestId('badge-display-name')).toHaveLength(3);
-  });
-
-  it('displays projects in alphabetical order partitioned by project membership', async function () {
-    const projectA = TestStubs.Project({id: '1', slug: 'a-project'});
-    const projectB = TestStubs.Project({id: '2', slug: 'b-project'});
-    const projectANonM = TestStubs.Project({id: '3', slug: 'a-non-m-project'});
-    const projectBNonM = TestStubs.Project({id: '4', slug: 'b-non-m-project'});
-
-    const multiProjectProps = {
-      ...props,
-      memberProjects: [projectB, projectA],
-      nonMemberProjects: [projectBNonM, projectANonM],
-      value: [],
-    };
-
-    render(<ProjectSelector {...multiProjectProps} />, {context: routerContext});
-    await openMenu();
-
-    expect(screen.getByText("Projects I don't belong to")).toBeInTheDocument();
-
-    const projectLabels = screen.getAllByTestId('badge-display-name');
-    expect(projectLabels).toHaveLength(4);
-
-    expect(projectLabels[0]).toHaveTextContent(projectA.slug);
-    expect(projectLabels[1]).toHaveTextContent(projectB.slug);
-    expect(projectLabels[2]).toHaveTextContent(projectANonM.slug);
-    expect(projectLabels[3]).toHaveTextContent(projectBNonM.slug);
-  });
-
-  it('displays multi projects in sort order rules: selected, bookmarked, alphabetical', async function () {
-    const projectA = TestStubs.Project({id: '1', slug: 'a-project'});
-    const projectBBookmarked = TestStubs.Project({
-      id: '2',
-      slug: 'b-project',
-      isBookmarked: true,
-    });
-    const projectCBookmarked = TestStubs.Project({
-      id: '3',
-      slug: 'c-project',
-      isBookmarked: true,
-    });
-    const projectDSelected = TestStubs.Project({id: '4', slug: 'd-project'});
-    const projectESelected = TestStubs.Project({id: '5', slug: 'e-project'});
-    const projectFSelectedBookmarked = TestStubs.Project({
-      id: '6',
-      slug: 'f-project',
-      isBookmarked: true,
-    });
-    const projectGSelectedBookmarked = TestStubs.Project({
-      id: '7',
-      slug: 'g-project',
-      isBookmarked: true,
-    });
-    const projectH = TestStubs.Project({id: '8', slug: 'h-project'});
-    const projectJ = TestStubs.Project({id: '9', slug: 'j-project'});
-    const projectKSelectedBookmarked = TestStubs.Project({
-      id: '10',
-      slug: 'k-project',
-      isBookmarked: true,
-    });
-    const projectL = TestStubs.Project({id: '11', slug: 'l-project'});
-
-    const multiProjectProps = {
-      ...props,
-      // XXX: Intentionally sorted arbitrarily
-      memberProjects: [
-        projectBBookmarked,
-        projectFSelectedBookmarked,
-        projectDSelected,
-        projectA,
-        projectESelected,
-        projectGSelectedBookmarked,
-        projectCBookmarked,
-        projectH,
-      ],
-      nonMemberProjects: [projectL, projectJ, projectKSelectedBookmarked],
-      value: [
-        projectESelected.id,
-        projectDSelected.id,
-        projectGSelectedBookmarked.id,
-        projectFSelectedBookmarked.id,
-        projectKSelectedBookmarked.id,
-      ].map(p => parseInt(p, 10)),
-    };
-
-    render(<ProjectSelector {...multiProjectProps} />, {context: routerContext});
-    await openMenu();
-
-    const projectLabels = screen.getAllByTestId('badge-display-name');
-    expect(projectLabels).toHaveLength(11);
-
-    // member projects
-    expect(projectLabels[0]).toHaveTextContent(projectFSelectedBookmarked.slug);
-    expect(projectLabels[1]).toHaveTextContent(projectGSelectedBookmarked.slug);
-    expect(projectLabels[2]).toHaveTextContent(projectDSelected.slug);
-    expect(projectLabels[3]).toHaveTextContent(projectESelected.slug);
-    expect(projectLabels[4]).toHaveTextContent(projectBBookmarked.slug);
-    expect(projectLabels[5]).toHaveTextContent(projectCBookmarked.slug);
-    expect(projectLabels[6]).toHaveTextContent(projectA.slug);
-    expect(projectLabels[7]).toHaveTextContent(projectH.slug);
-    expect(projectLabels[6]).toHaveTextContent(projectA.slug);
-    expect(projectLabels[7]).toHaveTextContent(projectH.slug);
-
-    // non member projects
-    expect(projectLabels[8]).toHaveTextContent(projectKSelectedBookmarked.slug);
-    expect(projectLabels[9]).toHaveTextContent(projectJ.slug);
-    expect(projectLabels[10]).toHaveTextContent(projectL.slug);
-  });
-
-  it('does not change sort order while selecting projects with the dropdown open', async function () {
-    const projectA = TestStubs.Project({id: '1', slug: 'a-project'});
-    const projectBBookmarked = TestStubs.Project({
-      id: '2',
-      slug: 'b-project',
-      isBookmarked: true,
-    });
-    const projectDSelected = TestStubs.Project({id: '4', slug: 'd-project'});
-
-    const multiProjectProps = {
-      ...props,
-      // XXX: Intentionally sorted arbitrarily
-      memberProjects: [projectBBookmarked, projectDSelected, projectA],
-      nonMemberProjects: [],
-      value: [projectDSelected.id].map(p => parseInt(p, 10)),
-    };
-
-    const {rerender} = render(<ProjectSelector {...multiProjectProps} />, {
-      context: routerContext,
-    });
-
-    await openMenu();
-
-    const projectLabels = screen.getAllByTestId('badge-display-name');
-    expect(projectLabels).toHaveLength(3);
-
-    // member projects
-    expect(projectLabels[0]).toHaveTextContent(projectDSelected.slug);
-    expect(projectLabels[1]).toHaveTextContent(projectBBookmarked.slug);
-    expect(projectLabels[2]).toHaveTextContent(projectA.slug);
-
-    // Unselect project D (re-render with the updated selection value)
-    await userEvent.click(screen.getByRole('checkbox', {name: projectDSelected.slug}));
-    rerender(<ProjectSelector {...multiProjectProps} value={[]} />);
-
-    // Project D is no longer checked
-    expect(screen.getByRole('checkbox', {name: projectDSelected.slug})).not.toBeChecked();
-
-    // Project D is still the first selected item
-    expect(screen.getAllByTestId('badge-display-name')[0]).toHaveTextContent(
-      projectDSelected.slug
-    );
-
-    // Open and close the menu
-    await applyMenu();
-    await openMenu();
-
-    const resortedProjectLabels = screen.getAllByTestId('badge-display-name');
-
-    // Project D has been moved to the bottom since it was unselected
-    expect(resortedProjectLabels[0]).toHaveTextContent(projectBBookmarked.slug);
-    expect(resortedProjectLabels[1]).toHaveTextContent(projectA.slug);
-    expect(resortedProjectLabels[2]).toHaveTextContent(projectDSelected.slug);
-  });
-
-  it('can select all projects when role=owner', async function () {
-    const mockOnApplyChange = jest.fn();
-    render(
-      <ProjectSelector
-        {...props}
-        nonMemberProjects={[anotherProject]}
-        organization={Organization({...mockOrg, orgRole: 'owner'})}
-        onApplyChange={mockOnApplyChange}
-      />,
-      {context: routerContext}
-    );
-
-    await openMenu();
-
-    await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
-
-    expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
-    expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
-  });
-
-  it('can select all projects when role=manager', async function () {
-    const mockOnApplyChange = jest.fn();
-    render(
-      <ProjectSelector
-        {...props}
-        nonMemberProjects={[anotherProject]}
-        organization={Organization({...mockOrg, orgRole: 'manager'})}
-        onApplyChange={mockOnApplyChange}
-      />,
-      {context: routerContext}
-    );
-
-    await openMenu();
-
-    await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
-
-    expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
-    expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
-  });
-
-  it('can select all projects when org has open membership', async function () {
-    const mockOnApplyChange = jest.fn();
-    render(
-      <ProjectSelector
-        {...props}
-        nonMemberProjects={[anotherProject]}
-        organization={Organization({
-          ...mockOrg,
-          features: [...mockOrg.features, 'open-membership'],
-        })}
-        onApplyChange={mockOnApplyChange}
-      />,
-      {context: routerContext}
-    );
-
-    await openMenu();
-
-    await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
-
-    expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
-    expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
-  });
-
-  it('can select all projects after first selecting a project', async function () {
-    const mockOnApplyChange = jest.fn();
-    render(
-      <ProjectSelector
-        {...props}
-        nonMemberProjects={[anotherProject]}
-        organization={Organization({...mockOrg, orgRole: 'manager'})}
-        onApplyChange={mockOnApplyChange}
-      />,
-      {context: routerContext}
-    );
-
-    await openMenu();
-
-    // Check the first project
-    await userEvent.click(screen.getByRole('checkbox', {name: testProject.slug}));
-
-    await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
-
-    // Menu should close and all projects should be selected, not previously selected project
-    expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
-    expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
-    expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
-  });
-});

Some files were not shown because too many files changed in this diff