From d8d19249a3a81629a3686a1b5229bc3489cb4dd4 Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 09:40:27 +0530 Subject: [PATCH 01/10] removes redundant variable --- web-server/pages/teams/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-server/pages/teams/index.tsx b/web-server/pages/teams/index.tsx index 70d1a922c..f3aeedbfa 100644 --- a/web-server/pages/teams/index.tsx +++ b/web-server/pages/teams/index.tsx @@ -14,7 +14,7 @@ import { PageLayout } from '@/types/resources'; function Page() { const dispatch = useDispatch(); - const { role, orgId } = useAuth(); + const { orgId } = useAuth(); useEffect(() => { if (!orgId) return; From 3abdeb691c8dce23240376ffdbfc4cc73409b951 Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 09:43:24 +0530 Subject: [PATCH 02/10] fixes variable heights of team cards, adds tooltip to display repos, sorts repos --- web-server/src/components/TeamsList.tsx | 134 ++++++++++++++---------- 1 file changed, 78 insertions(+), 56 deletions(-) diff --git a/web-server/src/components/TeamsList.tsx b/web-server/src/components/TeamsList.tsx index 2c5334cfb..f2d400bbd 100644 --- a/web-server/src/components/TeamsList.tsx +++ b/web-server/src/components/TeamsList.tsx @@ -1,8 +1,8 @@ -import { Delete, MoreVert } from '@mui/icons-material'; -import Edit from '@mui/icons-material/Edit'; +import { Delete, Edit, MoreVert } from '@mui/icons-material'; import { Button, Card, Divider, Menu, MenuItem } from '@mui/material'; import pluralize from 'pluralize'; -import { MouseEventHandler } from 'react'; +import { ascend } from 'ramda'; +import { MouseEventHandler, useMemo } from 'react'; import { useBoolState, useEasyState } from '@/hooks/useEasyState'; import { useSelector } from '@/store'; @@ -21,7 +21,7 @@ export const TeamsList = () => { const teamsArray = useSelector((state) => state.team.teams); return ( - + {teamsArray.map((team, index) => ( ))} @@ -32,64 +32,86 @@ export const TeamsList = () => { const TeamCard: React.FC = ({ team }) => { const { name: teamName, id: teamId } = team; const teamReposMap = useSelector((state) => state.team.teamReposMaps); - const assignedReposToTeam = teamReposMap[teamId] ?? []; + const assignedReposToTeam = useMemo( + () => [...(teamReposMap[teamId] ?? [])]?.sort(ascend((item) => item.name)), + [teamId, teamReposMap] + ); const visibleReposName = assignedReposToTeam.slice(0, VISIBLE_REPOS_COUNT); - return ( - - - - - {teamName} - - - - - + const tooltipRepos = useMemo( + () => + assignedReposToTeam + .slice(VISIBLE_REPOS_COUNT) + .map((item) => item.name) + .join(', '), + [assignedReposToTeam] + ); - - - - {assignedReposToTeam.length}{' '} - {pluralize('Repo', assignedReposToTeam.length)} Added + return ( + + + + + + {teamName} - {Boolean(assignedReposToTeam.length) && ( - <> - - - - {visibleReposName.map( - (r, idx) => - `${r.name} ${ - idx === visibleReposName.length - 1 ? '' : ', ' - }` - )} - {assignedReposToTeam.length > VISIBLE_REPOS_COUNT && ( - - +{assignedReposToTeam.length - VISIBLE_REPOS_COUNT} more - - )} - - - { - // TODO: IMPLEMENT EDIT TEAM - // disableCreation(); - // addPage({ - // page: { - // title: `Editing team: ${team.name}`, - // ui: 'team_edit', - // props: { teamId: team.id } - // } - // }); - }} - /> + + + + + {assignedReposToTeam.length}{' '} + {pluralize('Repo', assignedReposToTeam.length)} Added + + + {Boolean(assignedReposToTeam.length) && ( + <> + + + + {visibleReposName.map((r) => r.name).join(', ')}{' '} + {assignedReposToTeam.length > VISIBLE_REPOS_COUNT && ( + {tooltipRepos} + } + > + + +{assignedReposToTeam.length - VISIBLE_REPOS_COUNT}{' '} + more + + + )} + - - - )} + + )} + + + + + + { + // TODO: IMPLEMENT EDIT TEAM + // disableCreation(); + // addPage({ + // page: { + // title: `Editing team: ${team.name}`, + // ui: 'team_edit', + // props: { teamId: team.id } + // } + // }); + }} + /> + + + From 08874225055555afa5bcd11f667de4ec1ef13763 Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 09:56:52 +0530 Subject: [PATCH 03/10] adds team-deletion functionality --- web-server/src/components/TeamsList.tsx | 61 ++++++++++++++++++++++--- web-server/src/slices/team.ts | 5 +- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/web-server/src/components/TeamsList.tsx b/web-server/src/components/TeamsList.tsx index f2d400bbd..a4199d01d 100644 --- a/web-server/src/components/TeamsList.tsx +++ b/web-server/src/components/TeamsList.tsx @@ -1,12 +1,17 @@ import { Delete, Edit, MoreVert } from '@mui/icons-material'; import { Button, Card, Divider, Menu, MenuItem } from '@mui/material'; +import { useSnackbar } from 'notistack'; import pluralize from 'pluralize'; import { ascend } from 'ramda'; -import { MouseEventHandler, useMemo } from 'react'; +import { MouseEventHandler, useCallback, useMemo } from 'react'; +import { Integration } from '@/constants/integrations'; +import { useAuth } from '@/hooks/useAuth'; import { useBoolState, useEasyState } from '@/hooks/useEasyState'; -import { useSelector } from '@/store'; +import { deleteTeam, fetchTeams } from '@/slices/team'; +import { useDispatch, useSelector } from '@/store'; import { Team } from '@/types/api/teams'; +import { depFn } from '@/utils/fn'; import { FlexBox } from './FlexBox'; import { Line } from './Text'; @@ -119,17 +124,59 @@ const TeamCard: React.FC = ({ team }) => { }; const MoreOptions = ({ teamId }: { teamId: ID }) => { + const dispatch = useDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const { orgId } = useAuth(); const anchorEl = useEasyState(); + const loading = useBoolState(false); + const cancelMenu = useBoolState(false); const handleOpenMenu: MouseEventHandler = (event) => { anchorEl.set(event.currentTarget); }; - const handleCloseMenu = () => { - anchorEl.set(null); - }; + const handleCloseMenu = useCallback(() => { + depFn(anchorEl.set, null); + }, [anchorEl.set]); - const cancelMenu = useBoolState(false); + const handleTeamDeletion = useCallback( + (teamId: ID) => { + depFn(loading.true); + dispatch( + deleteTeam({ + org_id: orgId, + team_id: teamId + }) + ) + .then((res) => { + if (res.meta.requestStatus === 'fulfilled') { + enqueueSnackbar('Team deleted successfully', { + variant: 'success', + autoHideDuration: 2000 + }); + dispatch( + fetchTeams({ org_id: orgId, provider: Integration.GITHUB }) + ); + handleCloseMenu(); + } else { + enqueueSnackbar('Failed to delete team', { + variant: 'error', + autoHideDuration: 2000 + }); + console.error('Failed to delete team', res.meta); + } + }) + .finally(loading.false); + }, + [ + dispatch, + enqueueSnackbar, + handleCloseMenu, + loading.false, + loading.true, + orgId + ] + ); return ( <> @@ -190,7 +237,7 @@ const MoreOptions = ({ teamId }: { teamId: ID }) => { variant="contained" onClick={() => { // TODO: IMPLEMENT DELETE TEAM - // handleTeamDeletion(teamId); + handleTeamDeletion(teamId); }} > Delete diff --git a/web-server/src/slices/team.ts b/web-server/src/slices/team.ts index c15fcbc43..ef515320e 100644 --- a/web-server/src/slices/team.ts +++ b/web-server/src/slices/team.ts @@ -252,7 +252,10 @@ export const deleteTeam = createAsyncThunk( return await handleApi( `/resources/orgs/${params.org_id}/teams/v2`, { - method: 'DELETE' + method: 'DELETE', + data: { + id: params.team_id + } } ); } From 9999d3723fff50436388284d9d86c1dcd04a2d77 Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 09:59:35 +0530 Subject: [PATCH 04/10] removes outdated TODO tag --- web-server/src/components/TeamsList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web-server/src/components/TeamsList.tsx b/web-server/src/components/TeamsList.tsx index a4199d01d..7c32acb68 100644 --- a/web-server/src/components/TeamsList.tsx +++ b/web-server/src/components/TeamsList.tsx @@ -236,7 +236,6 @@ const MoreOptions = ({ teamId }: { teamId: ID }) => { size="small" variant="contained" onClick={() => { - // TODO: IMPLEMENT DELETE TEAM handleTeamDeletion(teamId); }} > From ca436e30c061ba6df786a16daa23d924c8e52d3e Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 10:07:10 +0530 Subject: [PATCH 05/10] refactors fetchTeams into a function to allow multiple calls --- .../src/components/Teams/useTeamsConfig.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web-server/src/components/Teams/useTeamsConfig.tsx b/web-server/src/components/Teams/useTeamsConfig.tsx index 3266c2940..9479b09bf 100644 --- a/web-server/src/components/Teams/useTeamsConfig.tsx +++ b/web-server/src/components/Teams/useTeamsConfig.tsx @@ -65,8 +65,8 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => { const isPageLoading = useSelector( (s) => s.team.requests?.teams === FetchState.REQUEST ); - useEffect(() => { - dispatch( + const fetchTeamsAndRepos = useCallback(() => { + return dispatch( fetchTeams({ org_id: orgId, provider: Integration.GITHUB @@ -74,6 +74,10 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => { ); }, [dispatch, orgId]); + useEffect(() => { + fetchTeamsAndRepos(); + }, [dispatch, fetchTeamsAndRepos, orgId]); + // team name logic const teamName = useEasyState(''); const teamNameError = useBoolState(false); @@ -160,12 +164,7 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => { variant: 'success', autoHideDuration: 2000 }); - dispatch( - fetchTeams({ - org_id: orgId, - provider: Integration.GITHUB - }) - ); + fetchTeamsAndRepos(); callBack?.(res); }) .finally(isSaveLoading.false); @@ -173,6 +172,7 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => { [ dispatch, enqueueSnackbar, + fetchTeamsAndRepos, isSaveLoading.false, isSaveLoading.true, org.name, From 53ea938a2ab48000afc84949268bd30d546acdbd Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 10:51:47 +0530 Subject: [PATCH 06/10] adds search functionality to the teams page --- .../src/components/Teams/CreateTeams.tsx | 32 +++++--- web-server/src/components/TeamsList.tsx | 78 +++++++++++++++++-- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/web-server/src/components/Teams/CreateTeams.tsx b/web-server/src/components/Teams/CreateTeams.tsx index ae6d94b6b..9537d28e7 100644 --- a/web-server/src/components/Teams/CreateTeams.tsx +++ b/web-server/src/components/Teams/CreateTeams.tsx @@ -32,10 +32,7 @@ export const TeamsCRUD = () => { return ( <> {isPageLoading ? ( - - - Loading... - + ) : ( { p={2} maxWidth={'900px'} > - - - Create a Team - - Create a team to generate metric insights - + @@ -60,6 +52,26 @@ export const TeamsCRUD = () => { ); }; +const Loader = () => { + return ( + + + Loading... + + ); +}; + +const Heading = () => { + return ( + + + Create a Team + + Create a team to generate metric insights + + ); +}; + const TeamName = () => { const { teamName, diff --git a/web-server/src/components/TeamsList.tsx b/web-server/src/components/TeamsList.tsx index 7c32acb68..7b8b6e671 100644 --- a/web-server/src/components/TeamsList.tsx +++ b/web-server/src/components/TeamsList.tsx @@ -1,9 +1,16 @@ -import { Delete, Edit, MoreVert } from '@mui/icons-material'; -import { Button, Card, Divider, Menu, MenuItem } from '@mui/material'; +import { Add, Delete, Edit, MoreVert } from '@mui/icons-material'; +import { + Button, + Card, + Divider, + Menu, + MenuItem, + TextField +} from '@mui/material'; import { useSnackbar } from 'notistack'; import pluralize from 'pluralize'; import { ascend } from 'ramda'; -import { MouseEventHandler, useCallback, useMemo } from 'react'; +import { FC, MouseEventHandler, useCallback, useMemo } from 'react'; import { Integration } from '@/constants/integrations'; import { useAuth } from '@/hooks/useAuth'; @@ -24,12 +31,69 @@ const VISIBLE_REPOS_COUNT = 3; export const TeamsList = () => { const teamsArray = useSelector((state) => state.team.teams); + const searchQuery = useEasyState(''); + + const teamsArrayFiltered = useMemo(() => { + if (!searchQuery.value) { + return teamsArray; + } + return teamsArray.filter((team) => + team.name.toLowerCase().includes(searchQuery.value.toLowerCase()) + ); + }, [searchQuery.value, teamsArray]); return ( - - {teamsArray.map((team, index) => ( - - ))} + <> + + {!teamsArrayFiltered.length && teamsArray.length ? ( + + No teams found + + ) : null} + + {teamsArrayFiltered.map((team, index) => ( + + ))} + + + ); +}; + +const SearchFilter: FC<{ + searchQuery: string; + onChange: (value: string) => void; +}> = ({ searchQuery, onChange }) => { + return ( + + + { + onChange(e.target.value); + }} + fullWidth + placeholder="Search for teams" + /> + + + + + + + + + ); }; From 0cadedd9696155ebaa8a4487ffa8a9e91d4661e5 Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 11:19:23 +0530 Subject: [PATCH 07/10] conditionally renders teams-list or create-teams based on number-of-teams --- web-server/pages/teams/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web-server/pages/teams/index.tsx b/web-server/pages/teams/index.tsx index f3aeedbfa..5a8e86e2d 100644 --- a/web-server/pages/teams/index.tsx +++ b/web-server/pages/teams/index.tsx @@ -9,12 +9,13 @@ import { Integration } from '@/constants/integrations'; import { PageWrapper } from '@/content/PullRequests/PageWrapper'; import { useAuth } from '@/hooks/useAuth'; import { fetchTeams } from '@/slices/team'; -import { useDispatch } from '@/store'; +import { useDispatch, useSelector } from '@/store'; import { PageLayout } from '@/types/resources'; function Page() { const dispatch = useDispatch(); const { orgId } = useAuth(); + const teamsList = useSelector((state) => state.team.teams); useEffect(() => { if (!orgId) return; @@ -37,8 +38,9 @@ function Page() { showEvenIfNoTeamSelected hideAllSelectors > - - + + {teamsList.length ? : } + ); } From 2c2dc780547cfbf89a28b7a64169cfd0898ce735 Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 11:20:14 +0530 Subject: [PATCH 08/10] adds create-teams CTA for building new teams, adds a discard button to creation logic --- .../src/components/Teams/CreateTeams.tsx | 34 ++++++++--- .../src/components/Teams/useTeamsConfig.tsx | 27 +++++---- web-server/src/components/TeamsList.tsx | 58 +++++++++++-------- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/web-server/src/components/Teams/CreateTeams.tsx b/web-server/src/components/Teams/CreateTeams.tsx index 9537d28e7..dad9f9cbd 100644 --- a/web-server/src/components/Teams/CreateTeams.tsx +++ b/web-server/src/components/Teams/CreateTeams.tsx @@ -19,15 +19,20 @@ import { BaseRepo } from '@/types/resources'; import { FlexBox } from '../FlexBox'; import { Line } from '../Text'; -export const CreateTeams = () => { +type CRUDCallBacks = { + onSave?: AnyFunction; + onDiscard?: AnyFunction; +}; + +export const CreateTeams: FC = ({ onSave, onDiscard }) => { return ( - + ); }; -export const TeamsCRUD = () => { +export const TeamsCRUD: FC = ({ onSave, onDiscard }) => { const { isPageLoading } = useTeamCRUD(); return ( <> @@ -45,7 +50,7 @@ export const TeamsCRUD = () => { - + )} @@ -166,12 +171,16 @@ const TeamRepos = () => { ); }; -const ActionTray = () => { - const { onSave, isSaveLoading, teamName, selectedRepos } = useTeamCRUD(); +const ActionTray: FC = ({ + onSave: onSaveCallBack, + onDiscard: onDiscardCallBack +}) => { + const { onSave, isSaveLoading, teamName, selectedRepos, onDiscard } = + useTeamCRUD(); const { enqueueSnackbar } = useSnackbar(); return ( - + { if (!teamName) { @@ -191,7 +200,7 @@ const ActionTray = () => { + ); }; diff --git a/web-server/src/components/Teams/useTeamsConfig.tsx b/web-server/src/components/Teams/useTeamsConfig.tsx index 9479b09bf..854a0e8bb 100644 --- a/web-server/src/components/Teams/useTeamsConfig.tsx +++ b/web-server/src/components/Teams/useTeamsConfig.tsx @@ -1,11 +1,5 @@ import { useSnackbar } from 'notistack'; -import { - createContext, - useContext, - useEffect, - SyntheticEvent, - useCallback -} from 'react'; +import { createContext, useContext, SyntheticEvent, useCallback } from 'react'; import { Integration } from '@/constants/integrations'; import { FetchState } from '@/constants/ui-states'; @@ -38,6 +32,7 @@ interface TeamsCRUDContextType { isSaveLoading: boolean; unselectRepo: (id: BaseRepo['id']) => void; isPageLoading: boolean; + onDiscard: (callBack?: AnyFunction) => void; } const TeamsCRUDContext = createContext( @@ -74,10 +69,6 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => { ); }, [dispatch, orgId]); - useEffect(() => { - fetchTeamsAndRepos(); - }, [dispatch, fetchTeamsAndRepos, orgId]); - // team name logic const teamName = useEasyState(''); const teamNameError = useBoolState(false); @@ -182,6 +173,17 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => { ] ); + const onDiscard = useCallback( + (callBack?: AnyFunction) => { + depFn(teamName.set, ''); + depFn(selections.set, []); + depFn(teamRepoError.false); + depFn(teamNameError.false); + callBack?.(); + }, + [selections.set, teamName.set, teamRepoError.false, teamNameError.false] + ); + const contextValue: TeamsCRUDContextType = { teamName: teamName.value, showTeamNameError: teamNameError.value, @@ -198,7 +200,8 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => { onSave, isSaveLoading: isSaveLoading.value, unselectRepo, - isPageLoading + isPageLoading, + onDiscard }; return ( diff --git a/web-server/src/components/TeamsList.tsx b/web-server/src/components/TeamsList.tsx index 7b8b6e671..382a28820 100644 --- a/web-server/src/components/TeamsList.tsx +++ b/web-server/src/components/TeamsList.tsx @@ -21,6 +21,7 @@ import { Team } from '@/types/api/teams'; import { depFn } from '@/utils/fn'; import { FlexBox } from './FlexBox'; +import { CreateTeams } from './Teams/CreateTeams'; import { Line } from './Text'; type TeamCardProps = { @@ -66,34 +67,45 @@ const SearchFilter: FC<{ searchQuery: string; onChange: (value: string) => void; }> = ({ searchQuery, onChange }) => { + const showCreate = useBoolState(false); + const showCreateTeam = useCallback(() => { + depFn(showCreate.toggle); + }, [showCreate.toggle]); return ( - - - { - onChange(e.target.value); - }} - fullWidth - placeholder="Search for teams" - /> - - + + - + { + onChange(e.target.value); + }} + fullWidth + placeholder="Search for teams" + /> - - + + + + + + + + {showCreate.value && } ); }; From 7744567e264121ea6c3e11b6985ff8b413be29cf Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 11:45:09 +0530 Subject: [PATCH 09/10] fixes the overflow in displaying of selected repos --- web-server/src/components/Teams/CreateTeams.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-server/src/components/Teams/CreateTeams.tsx b/web-server/src/components/Teams/CreateTeams.tsx index dad9f9cbd..8be999dfc 100644 --- a/web-server/src/components/Teams/CreateTeams.tsx +++ b/web-server/src/components/Teams/CreateTeams.tsx @@ -119,7 +119,7 @@ const TeamRepos = () => { } = useTeamCRUD(); return ( - + Add Repositories @@ -227,7 +227,7 @@ const ActionTray: FC = ({ const DisplayRepos = () => { const { selectedRepos } = useTeamCRUD(); return ( - + {!!selectedRepos.length && } {selectedRepos.map((repo) => ( From 79416b35a1a9e0a038fdf697a413dfb87ea655ae Mon Sep 17 00:00:00 2001 From: e-for-eshaan Date: Tue, 16 Apr 2024 12:00:46 +0530 Subject: [PATCH 10/10] adds link to the Dora metrics CTA --- web-server/src/components/TeamsList.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web-server/src/components/TeamsList.tsx b/web-server/src/components/TeamsList.tsx index 382a28820..a3747d493 100644 --- a/web-server/src/components/TeamsList.tsx +++ b/web-server/src/components/TeamsList.tsx @@ -13,6 +13,7 @@ import { ascend } from 'ramda'; import { FC, MouseEventHandler, useCallback, useMemo } from 'react'; import { Integration } from '@/constants/integrations'; +import { ROUTES } from '@/constants/routes'; import { useAuth } from '@/hooks/useAuth'; import { useBoolState, useEasyState } from '@/hooks/useEasyState'; import { deleteTeam, fetchTeams } from '@/slices/team'; @@ -97,7 +98,11 @@ const SearchFilter: FC<{ -