Browse Source

fix(ui): Show all projects even if not member (#25482)

* fix(ui): Show all projects even if not member

When creating an alert rule, display all projects, but disable the projects the user is not a part of.

FIXES WOR-826

* refactor and add in superuser check

* fix for acceptance test
Kelly Carino 3 years ago
parent
commit
282c11b979

+ 39 - 4
static/app/components/contextPickerModal.tsx

@@ -9,6 +9,7 @@ import IdBadge from 'app/components/idBadge';
 import Link from 'app/components/links/link';
 import LoadingIndicator from 'app/components/loadingIndicator';
 import {t, tct} from 'app/locale';
+import ConfigStore from 'app/stores/configStore';
 import OrganizationsStore from 'app/stores/organizationsStore';
 import OrganizationStore from 'app/stores/organizationStore';
 import space from 'app/styles/space';
@@ -70,6 +71,12 @@ const selectStyles = {
     boxShadow: 'none',
     marginBottom: 0,
   }),
+  option: (provided: StylesConfig, state: any) => ({
+    ...provided,
+    opacity: state.isDisabled ? 0.6 : 1,
+    cursor: state.isDisabled ? 'not-allowed' : 'pointer',
+    pointerEvents: state.isDisabled ? 'none' : 'auto',
+  }),
 };
 
 class ContextPickerModal extends React.Component<Props> {
@@ -191,6 +198,17 @@ class ContextPickerModal extends React.Component<Props> {
     this.navigateIfFinish([{slug: organization}], [{slug: value}]);
   };
 
+  getMemberProjects = () => {
+    const {projects} = this.props;
+    const nonMemberProjects: Project[] = [];
+    const memberProjects: Project[] = [];
+    projects.forEach(project =>
+      project.isMember ? memberProjects.push(project) : nonMemberProjects.push(project)
+    );
+
+    return [memberProjects, nonMemberProjects];
+  };
+
   onProjectMenuOpen = () => {
     const {projects, comingFromProjectId} = this.props;
     // Hacky way to pre-focus to an item with newer versions of react select
@@ -251,10 +269,27 @@ class ContextPickerModal extends React.Component<Props> {
 
   renderProjectSelectOrMessage() {
     const {organization, projects} = this.props;
-    // only show projects the user is a part of
-    const memberProjects = projects.filter(project => project.isMember);
-
-    const projectOptions = memberProjects.map(({slug}) => ({label: slug, value: slug}));
+    const [memberProjects, nonMemberProjects] = this.getMemberProjects();
+    const {isSuperuser} = ConfigStore.get('user') || {};
+
+    const projectOptions = [
+      {
+        label: t('Projects I belong to'),
+        options: memberProjects.map(p => ({
+          value: p.slug,
+          label: t(`${p.slug}`),
+          isDisabled: false,
+        })),
+      },
+      {
+        label: t("Projects I don't belong to"),
+        options: nonMemberProjects.map(p => ({
+          value: p.slug,
+          label: t(`${p.slug}`),
+          isDisabled: isSuperuser ? false : true,
+        })),
+      },
+    ];
 
     if (!projects.length) {
       return (

+ 1 - 1
tests/acceptance/test_organization_plugin_detail_view.py

@@ -33,7 +33,7 @@ class OrganizationPluginDetailedView(AcceptanceTestCase):
         detail_view_page = OrganizationAbstractDetailViewPage(browser=self.browser)
         detail_view_page.click_install_button()
 
-        self.browser.click('[id="react-select-2-option-0"]')
+        self.browser.click('[id="react-select-2-option-0-0"]')
         # check if we got to the configuration page with the form
         self.browser.wait_until_not(".loading-indicator")
         self.browser.wait_until_test_id("plugin-config")

+ 48 - 6
tests/js/spec/components/contextPickerModal.spec.jsx

@@ -9,7 +9,7 @@ import OrganizationStore from 'app/stores/organizationStore';
 import ProjectsStore from 'app/stores/projectsStore';
 
 describe('ContextPickerModal', function () {
-  let project, project2, org, org2;
+  let project, project2, project4, org, org2;
   const onFinish = jest.fn();
 
   beforeEach(function () {
@@ -24,6 +24,7 @@ describe('ContextPickerModal', function () {
       slug: 'org2',
       id: '21',
     });
+    project4 = TestStubs.Project({slug: 'project4', isMember: false});
   });
 
   afterEach(async function () {
@@ -121,7 +122,7 @@ describe('ContextPickerModal', function () {
     OrganizationStore.onUpdate(org);
     const fetchProjectsForOrg = MockApiClient.addMockResponse({
       url: `/organizations/${org.slug}/projects/`,
-      body: [project, project2],
+      body: [project, project2, project4],
     });
     await tick();
 
@@ -141,9 +142,33 @@ describe('ContextPickerModal', function () {
     expect(wrapper.find('StyledSelectControl[name="organization"]').prop('value')).toBe(
       org.slug
     );
+
     expect(wrapper.find('StyledSelectControl[name="project"]').prop('options')).toEqual([
-      {value: project.slug, label: project.slug},
-      {value: project2.slug, label: project2.slug},
+      {
+        label: 'Projects I belong to',
+        options: [
+          {
+            value: project.slug,
+            label: project.slug,
+            isDisabled: false,
+          },
+          {
+            value: project2.slug,
+            label: project2.slug,
+            isDisabled: false,
+          },
+        ],
+      },
+      {
+        label: "Projects I don't belong to",
+        options: [
+          {
+            value: project4.slug,
+            label: project4.slug,
+            isDisabled: true,
+          },
+        ],
+      },
     ]);
 
     await tick();
@@ -196,8 +221,25 @@ describe('ContextPickerModal', function () {
     expect(fetchProjectsForOrg).toHaveBeenCalled();
 
     expect(wrapper.find('StyledSelectControl[name="project"]').prop('options')).toEqual([
-      {value: project2.slug, label: project2.slug},
-      {value: 'project3', label: 'project3'},
+      {
+        label: 'Projects I belong to',
+        options: [
+          {
+            value: project2.slug,
+            label: project2.slug,
+            isDisabled: false,
+          },
+          {
+            value: 'project3',
+            label: 'project3',
+            isDisabled: false,
+          },
+        ],
+      },
+      {
+        label: "Projects I don't belong to",
+        options: [],
+      },
     ]);
 
     // Select project3