From 4189e32c126d05f507bd1a111503d2219b72636a Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Fri, 25 Apr 2025 08:47:26 +0200 Subject: [PATCH 1/5] fix(project-dashboard): Show projects without teams --- static/app/views/projectsDashboard/index.tsx | 90 +++++++++++++++----- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/static/app/views/projectsDashboard/index.tsx b/static/app/views/projectsDashboard/index.tsx index 4f95a37443655d..c760be45faaa0f 100644 --- a/static/app/views/projectsDashboard/index.tsx +++ b/static/app/views/projectsDashboard/index.tsx @@ -3,6 +3,7 @@ import LazyLoad, {forceCheck} from 'react-lazyload'; import styled from '@emotion/styled'; import {withProfiler} from '@sentry/react'; import debounce from 'lodash/debounce'; +import partition from 'lodash/partition'; import uniqBy from 'lodash/uniqBy'; import {LinkButton} from 'sentry/components/core/button'; @@ -81,6 +82,64 @@ function addProjectsToTeams(teams: Team[], projects: Project[]): TeamWithProject })); } +function getFilteredProjectsBasedOnTeams({ + allTeams, + userTeams, + selectedTeams, + isAllTeams, + showNonMemberProjects, + projects, + projectQuery, +}: { + allTeams: Team[]; + isAllTeams: boolean; + projectQuery: string; + projects: Project[]; + selectedTeams: string[]; + showNonMemberProjects: boolean; + userTeams: Team[]; +}): Project[] { + const myTeamIds = new Set(userTeams.map(team => team.id)); + + const [myTeamsInAll, otherTeamsInAll] = partition(allTeams, team => + myTeamIds.has(team.id) + ); + + const includeMyTeams = isAllTeams || selectedTeams.includes('myteams'); + const selectedOtherTeamIds = new Set( + selectedTeams.filter(teamId => teamId !== 'myteams') + ); + + const myTeams = includeMyTeams ? myTeamsInAll : []; + const otherTeams = isAllTeams + ? otherTeamsInAll + : [...otherTeamsInAll].filter(team => selectedOtherTeamIds.has(String(team.id))); + + const visibleTeams = [...myTeams, ...otherTeams].filter(team => { + if (showNonMemberProjects) { + return true; + } + return team.isMember; + }); + + const teamsWithProjects = addProjectsToTeams(visibleTeams, projects); + + const currentProjects = uniqBy( + teamsWithProjects.flatMap(team => team.projects), + 'id' + ); + + const currentProjectIds = new Set(currentProjects.map(p => p.id)); + + const unassignedProjects = isAllTeams + ? projects.filter(project => !currentProjectIds.has(project.id)) + : []; + + return [...currentProjects, ...unassignedProjects].filter(project => + project.slug.includes(projectQuery) + ); +} + function Dashboard() { const navigate = useNavigate(); const location = useLocation(); @@ -123,33 +182,18 @@ function Dashboard() { return ; } - const includeMyTeams = isAllTeams || selectedTeams.includes('myteams'); - const hasOtherTeams = selectedTeams.some(team => team !== 'myteams'); - const myTeams = includeMyTeams ? userTeams : []; - const otherTeams = isAllTeams - ? allTeams - : hasOtherTeams - ? allTeams.filter(team => selectedTeams.includes(`${team.id}`)) - : []; - const filteredTeams = [...myTeams, ...otherTeams].filter(team => { - if (showNonMemberProjects) { - return true; - } - - return team.isMember; + const filteredProjects = getFilteredProjectsBasedOnTeams({ + allTeams, + userTeams, + selectedTeams, + isAllTeams, + showNonMemberProjects, + projects, + projectQuery, }); - const filteredTeamsWithProjects = addProjectsToTeams(filteredTeams, projects); - const currentProjects = uniqBy( - filteredTeamsWithProjects.flatMap(team => team.projects), - 'id' - ); setGroupedEntityTag('projects.total', 1000, projects.length); - const filteredProjects = currentProjects.filter(project => - project.slug.includes(projectQuery) - ); - const showResources = projects.length === 1 && !projects[0]!.firstEvent; const canJoinTeam = organization.access.includes('team:read'); From 73452d9ea6e4790758ac2ed69c67b7879d594158 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Fri, 25 Apr 2025 09:31:16 +0200 Subject: [PATCH 2/5] fix tests --- .../app/views/projectsDashboard/index.spec.tsx | 14 +++++++++++--- static/app/views/projectsDashboard/index.tsx | 18 +++--------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/static/app/views/projectsDashboard/index.spec.tsx b/static/app/views/projectsDashboard/index.spec.tsx index 5bb5cd1b28331e..2147e1d55a3184 100644 --- a/static/app/views/projectsDashboard/index.spec.tsx +++ b/static/app/views/projectsDashboard/index.spec.tsx @@ -257,7 +257,7 @@ describe('ProjectsDashboard', function () { expect(screen.getAllByTestId('badge-display-name')).toHaveLength(1); }); - it('renders only projects for my teams if open membership is disabled', async function () { + it('renders only projects for my teams if open membership is disabled + unassigned projects', async function () { const {organization: closedOrg, router} = initializeOrg({ organization: {features: []}, router: { @@ -297,9 +297,9 @@ describe('ProjectsDashboard', function () { organization: closedOrg, }); expect(await screen.findByText('All Teams')).toBeInTheDocument(); - expect(screen.getAllByTestId('badge-display-name')).toHaveLength(1); + expect(screen.getAllByTestId('badge-display-name')).toHaveLength(2); expect(screen.getByText('project1')).toBeInTheDocument(); - expect(screen.queryByText('project2')).not.toBeInTheDocument(); + expect(screen.getByText('project2')).toBeInTheDocument(); }); it('renders correct project with selected team', async function () { @@ -358,6 +358,13 @@ describe('ProjectsDashboard', function () { firstEvent: new Date().toISOString(), stats: [], }), + ProjectFixture({ + id: '4', + slug: 'project4', + teams: [], + firstEvent: new Date().toISOString(), + stats: [], + }), ]; ProjectsStore.loadInitialData(projects); @@ -382,6 +389,7 @@ describe('ProjectsDashboard', function () { expect(await screen.findByText('project3')).toBeInTheDocument(); expect(screen.queryByText('project2')).not.toBeInTheDocument(); + expect(screen.queryByText('project4')).not.toBeInTheDocument(); }); it('renders projects by search', async function () { diff --git a/static/app/views/projectsDashboard/index.tsx b/static/app/views/projectsDashboard/index.tsx index c760be45faaa0f..165eac8fe93251 100644 --- a/static/app/views/projectsDashboard/index.tsx +++ b/static/app/views/projectsDashboard/index.tsx @@ -3,7 +3,6 @@ import LazyLoad, {forceCheck} from 'react-lazyload'; import styled from '@emotion/styled'; import {withProfiler} from '@sentry/react'; import debounce from 'lodash/debounce'; -import partition from 'lodash/partition'; import uniqBy from 'lodash/uniqBy'; import {LinkButton} from 'sentry/components/core/button'; @@ -100,20 +99,14 @@ function getFilteredProjectsBasedOnTeams({ userTeams: Team[]; }): Project[] { const myTeamIds = new Set(userTeams.map(team => team.id)); - - const [myTeamsInAll, otherTeamsInAll] = partition(allTeams, team => - myTeamIds.has(team.id) - ); - const includeMyTeams = isAllTeams || selectedTeams.includes('myteams'); const selectedOtherTeamIds = new Set( selectedTeams.filter(teamId => teamId !== 'myteams') ); - - const myTeams = includeMyTeams ? myTeamsInAll : []; + const myTeams = includeMyTeams ? allTeams.filter(team => myTeamIds.has(team.id)) : []; const otherTeams = isAllTeams - ? otherTeamsInAll - : [...otherTeamsInAll].filter(team => selectedOtherTeamIds.has(String(team.id))); + ? allTeams + : [...allTeams].filter(team => selectedOtherTeamIds.has(String(team.id))); const visibleTeams = [...myTeams, ...otherTeams].filter(team => { if (showNonMemberProjects) { @@ -121,20 +114,15 @@ function getFilteredProjectsBasedOnTeams({ } return team.isMember; }); - const teamsWithProjects = addProjectsToTeams(visibleTeams, projects); - const currentProjects = uniqBy( teamsWithProjects.flatMap(team => team.projects), 'id' ); - const currentProjectIds = new Set(currentProjects.map(p => p.id)); - const unassignedProjects = isAllTeams ? projects.filter(project => !currentProjectIds.has(project.id)) : []; - return [...currentProjects, ...unassignedProjects].filter(project => project.slug.includes(projectQuery) ); From c54b576cb7fe98b128d923f46b822cdc8d880995 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Mon, 28 Apr 2025 07:55:38 +0200 Subject: [PATCH 3/5] Update static/app/views/projectsDashboard/index.tsx Co-authored-by: Scott Cooper --- static/app/views/projectsDashboard/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/views/projectsDashboard/index.tsx b/static/app/views/projectsDashboard/index.tsx index 165eac8fe93251..203d8aeae066a6 100644 --- a/static/app/views/projectsDashboard/index.tsx +++ b/static/app/views/projectsDashboard/index.tsx @@ -106,7 +106,7 @@ function getFilteredProjectsBasedOnTeams({ const myTeams = includeMyTeams ? allTeams.filter(team => myTeamIds.has(team.id)) : []; const otherTeams = isAllTeams ? allTeams - : [...allTeams].filter(team => selectedOtherTeamIds.has(String(team.id))); + : allTeams.filter(team => selectedOtherTeamIds.has(String(team.id))); const visibleTeams = [...myTeams, ...otherTeams].filter(team => { if (showNonMemberProjects) { From a07d25ef0838561ae4ec205e4c1e9121b7af184e Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 29 Apr 2025 09:47:08 +0200 Subject: [PATCH 4/5] feedback --- static/app/views/projectsDashboard/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/static/app/views/projectsDashboard/index.tsx b/static/app/views/projectsDashboard/index.tsx index 203d8aeae066a6..d861a61239130a 100644 --- a/static/app/views/projectsDashboard/index.tsx +++ b/static/app/views/projectsDashboard/index.tsx @@ -120,9 +120,10 @@ function getFilteredProjectsBasedOnTeams({ 'id' ); const currentProjectIds = new Set(currentProjects.map(p => p.id)); - const unassignedProjects = isAllTeams - ? projects.filter(project => !currentProjectIds.has(project.id)) - : []; + const unassignedProjects = + isAllTeams && showNonMemberProjects + ? projects.filter(project => !currentProjectIds.has(project.id)) + : []; return [...currentProjects, ...unassignedProjects].filter(project => project.slug.includes(projectQuery) ); From e3c80975c500c4086f37bceddb13d1795c54a3b6 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 29 Apr 2025 10:24:34 +0200 Subject: [PATCH 5/5] fix test --- static/app/views/projectsDashboard/index.spec.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/app/views/projectsDashboard/index.spec.tsx b/static/app/views/projectsDashboard/index.spec.tsx index 9d78aa96f267ff..b167c3b4b169ab 100644 --- a/static/app/views/projectsDashboard/index.spec.tsx +++ b/static/app/views/projectsDashboard/index.spec.tsx @@ -263,7 +263,7 @@ describe('ProjectsDashboard', function () { expect(screen.getAllByTestId('badge-display-name')).toHaveLength(1); }); - it('renders only projects for my teams if open membership is disabled + unassigned projects', async function () { + it('renders only projects for my teams if open membership is disabled', async function () { const {organization: closedOrg, router} = initializeOrg({ organization: {features: []}, router: { @@ -304,9 +304,9 @@ describe('ProjectsDashboard', function () { deprecatedRouterMocks: true, }); expect(await screen.findByText('All Teams')).toBeInTheDocument(); - expect(screen.getAllByTestId('badge-display-name')).toHaveLength(2); + expect(screen.getAllByTestId('badge-display-name')).toHaveLength(1); expect(screen.getByText('project1')).toBeInTheDocument(); - expect(screen.getByText('project2')).toBeInTheDocument(); + expect(screen.queryByText('project2')).not.toBeInTheDocument(); }); it('renders correct project with selected team', async function () {