From a4d1dd0b7019ef1148b05549fc6e0ea44dc7a652 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Wed, 28 Feb 2024 15:16:10 +0100 Subject: [PATCH 01/14] P1 - myTeams --- src/pages/TeamOverview/TeamOverview.tsx | 39 +++--- src/services/teamOverview.ts | 154 +++++++++++++++++++++--- 2 files changed, 152 insertions(+), 41 deletions(-) diff --git a/src/pages/TeamOverview/TeamOverview.tsx b/src/pages/TeamOverview/TeamOverview.tsx index b0cb8f4c..c1f15722 100644 --- a/src/pages/TeamOverview/TeamOverview.tsx +++ b/src/pages/TeamOverview/TeamOverview.tsx @@ -6,13 +6,14 @@ import PageLayout from '../../components/PageLayout/PageLayout' import Table, { TableData } from '../../components/Table/Table' import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' -import { ErrorResponse } from '../../@types/error' -import { Team } from '../../@types/team' - -import { getTeamOverview, TeamOverviewData } from '../../services/teamOverview' +import { fetchTeamOverviewData, TeamOverviewData, Team } from '../../services/teamOverview' import { formatDisplayName } from '../../utils/utils' +import { ApiError } from '../../utils/services' const TeamOverview = () => { + const accessToken = localStorage.getItem('access_token') || '' + const jwt = JSON.parse(atob(accessToken.split('.')[1])) + const defaultActiveTab = { title: 'Mine team', path: 'myTeams', @@ -22,18 +23,17 @@ const TeamOverview = () => { const [teamOverviewData, setTeamOverviewData] = useState() const [teamOverviewTableData, setTeamOverviewTableData] = useState() const [teamOverviewTableTitle, setTeamOverviewTableTitle] = useState(defaultActiveTab.title) - const [error, setError] = useState() + const [error, setError] = useState() const [loading, setLoading] = useState(true) const prepTeamData = useCallback( (response: TeamOverviewData): TableData['data'] => { - const team = (activeTab as TabProps)?.path ?? activeTab - - return response[team].teams.map((team) => ({ + const teamTab = (activeTab as TabProps)?.path ?? activeTab + return response[teamTab].teams.map((team) => ({ id: team.uniform_name, seksjon: team.section_name, // Makes section name searchable and sortable in table by including the field navn: renderTeamNameColumn(team), - teammedlemmer: team.team_user_count, + teammedlemmer: team.users.length, ansvarlig: formatDisplayName(team.manager.display_name), })) }, @@ -41,18 +41,15 @@ const TeamOverview = () => { ) useEffect(() => { - getTeamOverview() + fetchTeamOverviewData(jwt.email) .then((response) => { - if ((response as ErrorResponse).error) { - setError(response as ErrorResponse) - } else { - setTeamOverviewData(response as TeamOverviewData) - setTeamOverviewTableData(prepTeamData(response as TeamOverviewData)) - } + console.log(response) + setTeamOverviewData(response as TeamOverviewData) + setTeamOverviewTableData(prepTeamData(response as TeamOverviewData)) }) .finally(() => setLoading(false)) .catch((error) => { - setError(error.toString()) + setError(error as ApiError) }) }, [prepTeamData]) @@ -86,8 +83,8 @@ const TeamOverview = () => { const renderErrorAlert = () => { return ( - - {error?.error.message} + + {`${error?.code} - ${error?.message}`} ) } @@ -118,8 +115,8 @@ const TeamOverview = () => { onClick={handleTabClick} activeOnInit={defaultActiveTab.path} items={[ - { title: `Mine team (${teamOverviewData?.myTeams.count ?? 0})`, path: 'myTeams' }, - { title: `Alle team (${teamOverviewData?.allTeams.count ?? 0})`, path: 'allTeams' }, + { title: `Mine team (${teamOverviewData?.myTeams.teams.length ?? 0})`, path: 'myTeams' }, + { title: `Alle team (${teamOverviewData?.allTeams.teams.length ?? 0})`, path: 'allTeams' }, ]} /> diff --git a/src/services/teamOverview.ts b/src/services/teamOverview.ts index 94753a02..03685b68 100644 --- a/src/services/teamOverview.ts +++ b/src/services/teamOverview.ts @@ -1,34 +1,148 @@ -import { Team } from '../@types/team' -import { ErrorResponse } from '../@types/error' +import { ApiError, fetchAPIData } from '../utils/services' + +const DAPLA_TEAM_API_URL = import.meta.env.VITE_DAPLA_TEAM_API_URL +const USERS_URL = `${DAPLA_TEAM_API_URL}/users` +const TEAMS_URL = `${DAPLA_TEAM_API_URL}/teams` export interface TeamOverviewData { - [key: string]: TeamOverviewResult // myTeams, allTeams + [key: string]: TeamsData // myTeams, allTeams } -export interface TeamOverviewResult { +interface TeamsData { teams: Team[] - count: number } -export const getTeamOverview = async (): Promise => { - const accessToken = localStorage.getItem('access_token') +export interface Team { + uniform_name: string + division_name: string + display_name: string + section_name: string + section_code: string + manager: TeamManager + users: User[] + groups: Group[] + // eslint-disable-next-line + _embedded?: any +} + +interface TeamManager { + display_name: string + principal_name: string +} + +interface User { + display_name: string + principal_name: string +} + +interface Group { + uniform_name: string + display_name: string + users: User[] +} + +function flattenEmbedded(json: any): any { + if (json._embedded) { + for (const prop in json._embedded) { + json[prop] = json._embedded[prop] + } + delete json._embedded + } + + for (const prop in json) { + if (typeof json[prop] === 'object') { + json[prop] = flattenEmbedded(json[prop]) + } + } + + return json +} + +const fetchAllTeams = async (accessToken: string): Promise => { + return { teams: [] } +} + +const fetchTeamsForPrincipalName = async (accessToken: string, principalName: string): Promise => { + const usersUrl = new URL(`${USERS_URL}/${principalName}`) + const embeds = ['teams', 'teams.users', 'teams.groups.users'] + + const selects = [ + 'display_name', + 'principal_name', + 'teams.display_name', + 'teams.uniform_name', + 'teams.section_name', + 'teams.users.principal_name', + 'teams.groups.users.display_name', + 'teams.groups.users.principal_name', + ] + + usersUrl.searchParams.set('embed', embeds.join(',')) + usersUrl.searchParams.append('select', selects.join(',')) try { - const response = await fetch(`/api/teamOverview`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}`, - }, + const teams = await fetchAPIData(usersUrl.toString(), accessToken) + + if (!teams) throw new ApiError(500, 'No json data returned') + if (!teams._embedded || !teams._embedded.teams) return {} as TeamsData + + const flattedTeams = flattenEmbedded({ ...teams }) + flattedTeams.teams.forEach((team: Team) => { + if (!team.groups) flattedTeams.teams.groups = [] + if (!team.users) flattedTeams.teams.users = [] }) - if (!response.ok) { - const errorData = await response.json() - return errorData as ErrorResponse + + const flattedTeamsWithManager = flattedTeams.teams.map((team: Team) => { + const managers = team.groups.filter( + ({ uniform_name }: Group) => uniform_name === `${team.uniform_name}-managers` + )[0] + if (managers) { + return { + ...team, + manager: { + display_name: managers.users ? managers.users[0].display_name : 'Mangler ansvarlig', + }, + } + } + }) + + return { teams: flattedTeamsWithManager } + } catch (error) { + if (error instanceof ApiError) { + console.error('Failed to fetch teams for PrincipalName:', error) + throw error + } else { + const apiError = new ApiError(500, 'An unexpected error occurred') + console.error('Failed to fetch teams for PrincipalName:', apiError) + throw apiError } - const data = await response.json() - return data as TeamOverviewData + } +} + +export const fetchTeamOverviewData = async (principalName: string): Promise => { + const accessToken = localStorage.getItem('access_token') + if (!accessToken) { + console.error('No access token available') + const apiError = new ApiError(401, 'No access token available') + console.error('Failed to fetch team members data:', apiError) + throw apiError + } + + try { + const [myTeams, allTeams] = await Promise.all([ + fetchTeamsForPrincipalName(accessToken, principalName), + fetchAllTeams(accessToken), + ]) + + return { myTeams: myTeams, allTeams: allTeams } as TeamOverviewData } catch (error) { - console.error('Error during fetching teams:', error) - throw new Error('Error fetching teams') + if (error instanceof ApiError) { + console.error('Failed to fetch team overview data:', error) + throw error + } else { + const apiError = new ApiError(500, 'An unexpected error occurred while fetching team members data') + console.error('Failed to fetch team overview data:', apiError) + throw apiError + } } } From 9601fa2a252eff6ef74d90ef3e5e7f4b3d11a479 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Thu, 29 Feb 2024 08:41:26 +0100 Subject: [PATCH 02/14] teamOverview p2 --- server.js | 56 ------------------- src/pages/TeamOverview/TeamOverview.tsx | 1 - src/services/teamOverview.ts | 72 +++++++++++++++++++++---- 3 files changed, 61 insertions(+), 68 deletions(-) diff --git a/server.js b/server.js index a0af2cb7..51903a78 100644 --- a/server.js +++ b/server.js @@ -79,62 +79,6 @@ app.post('/api/verify-token', (req, res) => { }) }) -app.get('/api/teamOverview', tokenVerificationMiddleware, async (req, res, next) => { - const token = req.token - const principalName = req.user.email - const allteamsUrl = `${DAPLA_TEAM_API_URL}/teams` - const myTeamsUrl = `${DAPLA_TEAM_API_URL}/users/${principalName}/teams` - - try { - const [allTeams, myTeams] = await Promise.all([ - fetchAPIData(token, allteamsUrl, 'Failed to fetch all teams').then((teams) => getTeamOverviewTeams(token, teams)), - fetchAPIData(token, myTeamsUrl, 'Failed to fetch my teams').then((teams) => getTeamOverviewTeams(token, teams)), - ]) - - const result = { - allTeams: { - count: allTeams.count, - ...allTeams._embedded, - }, - myTeams: { - count: myTeams.count, - ...myTeams._embedded, - }, - } - - res.json(result) - } catch (error) { - next(error) - } -}) - -async function getTeamOverviewTeams(token, teams) { - const teamPromises = teams._embedded.teams.map(async (team) => { - const teamUniformName = team.uniform_name - const teamInfoUrl = `${DAPLA_TEAM_API_URL}/teams/${teamUniformName}` - const teamUsersUrl = `${DAPLA_TEAM_API_URL}/teams/${teamUniformName}/users` - const teamManagerUrl = `${DAPLA_TEAM_API_URL}/groups/${teamUniformName}-managers/users` - - const [teamInfo, teamUsers, teamManager] = await Promise.all([ - fetchAPIData(token, teamInfoUrl, 'Failed to fetch team info').catch(() => sectionFallback(teamUniformName)), - fetchAPIData(token, teamUsersUrl, 'Failed to fetch team users'), - fetchAPIData(token, teamManagerUrl, 'Failed to fetch team manager').catch(() => managerFallback()), - ]) - team['section_name'] = teamInfo.section_name - team['team_user_count'] = teamUsers.count - team['manager'] = teamManager.count > 0 ? teamManager._embedded.users[0] : managerFallback() - - return { ...team } - }) - - const resolvedTeams = await Promise.all(teamPromises) - const validTeams = resolvedTeams.filter((team) => team !== null) - - teams._embedded.teams = validTeams - teams.count = validTeams.length - return teams -} - app.get('/api/userProfile/:principalName', tokenVerificationMiddleware, async (req, res, next) => { try { const token = req.token diff --git a/src/pages/TeamOverview/TeamOverview.tsx b/src/pages/TeamOverview/TeamOverview.tsx index c1f15722..72ff2325 100644 --- a/src/pages/TeamOverview/TeamOverview.tsx +++ b/src/pages/TeamOverview/TeamOverview.tsx @@ -43,7 +43,6 @@ const TeamOverview = () => { useEffect(() => { fetchTeamOverviewData(jwt.email) .then((response) => { - console.log(response) setTeamOverviewData(response as TeamOverviewData) setTeamOverviewTableData(prepTeamData(response as TeamOverviewData)) }) diff --git a/src/services/teamOverview.ts b/src/services/teamOverview.ts index 03685b68..c692d0cc 100644 --- a/src/services/teamOverview.ts +++ b/src/services/teamOverview.ts @@ -59,7 +59,60 @@ function flattenEmbedded(json: any): any { } const fetchAllTeams = async (accessToken: string): Promise => { - return { teams: [] } + const teamsUrl = new URL(`${TEAMS_URL}`) + const embeds = ['users', 'groups.users'] + + const selects = [ + 'display_name', + 'uniform_name', + 'section_name', + 'users.principal_name', + 'groups.users.display_name', + 'groups.users.principal_name', + ] + + teamsUrl.searchParams.set('embed', embeds.join(',')) + teamsUrl.searchParams.append('select', selects.join(',')) + + try { + const teams = await fetchAPIData(teamsUrl.toString(), accessToken) + if (!teams) throw new ApiError(500, 'No json data returned') + if (!teams._embedded || !teams._embedded.teams) return {} as TeamsData + + const flattedTeams = flattenEmbedded({ ...teams }) + flattedTeams.teams.forEach((team: Team, teamIndex: number) => { + if (!team.groups) flattedTeams.teams[teamIndex].groups = [] + + team.groups.forEach((group: Group, groupIndex: number) => { + if (!group.users) { + flattedTeams.teams[teamIndex].groups[groupIndex].users = [] + } + }) + if (!team.users) flattedTeams.teams[teamIndex].users = [] + }) + + const flattedTeamsWithManager = flattedTeams.teams.map((team: Team) => { + const managers = team.groups.find((group) => group.uniform_name === `${team.uniform_name}-managers`) + return { + ...team, + manager: + managers && managers.users && managers.users.length > 0 + ? { display_name: managers.users[0].display_name, principal_name: managers.users[0].principal_name } + : { display_name: 'Mangler manager', principal_name: 'ManglerManager@ssb.no' }, + } + }) + + return { teams: flattedTeamsWithManager } + } catch (error) { + if (error instanceof ApiError) { + console.error('Failed to fetch all teams:', error) + throw error + } else { + const apiError = new ApiError(500, 'An unexpected error occurred') + console.error('Failed to fetch all teams:', apiError) + throw apiError + } + } } const fetchTeamsForPrincipalName = async (accessToken: string, principalName: string): Promise => { @@ -93,16 +146,13 @@ const fetchTeamsForPrincipalName = async (accessToken: string, principalName: st }) const flattedTeamsWithManager = flattedTeams.teams.map((team: Team) => { - const managers = team.groups.filter( - ({ uniform_name }: Group) => uniform_name === `${team.uniform_name}-managers` - )[0] - if (managers) { - return { - ...team, - manager: { - display_name: managers.users ? managers.users[0].display_name : 'Mangler ansvarlig', - }, - } + const managers = team.groups.find((group) => group.uniform_name === `${team.uniform_name}-managers`) + return { + ...team, + manager: + managers && managers.users && managers.users.length > 0 + ? { display_name: managers.users[0].display_name, principal_name: managers.users[0].principal_name } + : { display_name: 'Mangler ansvarlig', principal_name: 'ManglerAnsvarlig@ssb.no' }, } }) From b72a143f1cd3207f8d080c929380251457e25a52 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Thu, 29 Feb 2024 11:16:16 +0100 Subject: [PATCH 03/14] userProfile p1+p2 --- server.js | 92 +---------- src/@types/user.ts | 2 +- src/components/Avatar/Avatar.tsx | 2 +- src/pages/TeamOverview/TeamOverview.tsx | 2 +- src/pages/UserProfile/UserProfile.tsx | 70 +++----- src/services/userProfile.ts | 206 +++++++++++++++++++++--- 6 files changed, 215 insertions(+), 159 deletions(-) diff --git a/server.js b/server.js index 51903a78..b10fbbba 100644 --- a/server.js +++ b/server.js @@ -79,21 +79,16 @@ app.post('/api/verify-token', (req, res) => { }) }) -app.get('/api/userProfile/:principalName', tokenVerificationMiddleware, async (req, res, next) => { +// DO NOT REMOVE, NECCESSARY FOR FRONTEND +app.get('/api/photo/:principalName', tokenVerificationMiddleware, async (req, res, next) => { + const accessToken = req.token + const principalName = req.params.principalName + const userPhotoUrl = `${DAPLA_TEAM_API_URL}/users/${principalName}/photo` + try { - const token = req.token - const principalName = req.params.principalName - const userProfileUrl = `${DAPLA_TEAM_API_URL}/users/${principalName}` - const userManagerUrl = `${DAPLA_TEAM_API_URL}/users/${principalName}/manager` - const userPhotoUrl = `${DAPLA_TEAM_API_URL}/users/${principalName}/photo` - - const [userProfile, userManager, userPhoto] = await Promise.all([ - fetchAPIData(token, userProfileUrl, 'Failed to fetch userProfile'), - fetchAPIData(token, userManagerUrl, 'Failed to fetch user manager').catch(() => managerFallback()), - fetchPhoto(token, userPhotoUrl, 'Failed to fetch user photo'), - ]) + const photoData = await fetchPhoto(accessToken, userPhotoUrl) - return res.json({ ...userProfile, manager: { ...userManager }, photo: userPhoto }) + return res.send({photo: photoData}) } catch (error) { next(error) } @@ -111,77 +106,6 @@ async function fetchPhoto(token, url, fallbackErrorMessage) { return photoBuffer.toString('base64') } -async function getUserProfileTeamData(token, principalName, teams) { - const teamPromises = teams._embedded.teams.map(async (team) => { - const teamUniformName = team.uniform_name - const teamInfoUrl = `${DAPLA_TEAM_API_URL}/teams/${teamUniformName}` - const teamGroupsUrl = `${DAPLA_TEAM_API_URL}/teams/${teamUniformName}/groups` - const teamManagerUrl = `${DAPLA_TEAM_API_URL}/groups/${teamUniformName}-managers/users` - - const [teamInfo, teamGroups, teamManager] = await Promise.all([ - fetchAPIData(token, teamInfoUrl, 'Failed to fetch team info').catch(() => sectionFallback(teamUniformName)), - fetchAPIData(token, teamGroupsUrl, 'Failed to fetch groups').then((response) => { - const groupPromises = response._embedded.groups.map((group) => fetchUserGroups(group, token, principalName)) - return Promise.all(groupPromises).then((groupsArrays) => groupsArrays.flat()) - }), - fetchAPIData(token, teamManagerUrl, 'Failed to fetch team manager').catch(() => managerFallback()), - ]) - - team['section_name'] = teamInfo.section_name - team['manager'] = teamManager.count > 0 ? teamManager._embedded.users[0] : managerFallback() - team['groups'] = teamGroups - - return { ...team } - }) - - const resolvedTeams = await Promise.all(teamPromises) - const validTeams = resolvedTeams.filter((team) => team !== null) - - teams._embedded.teams = validTeams - teams.count = validTeams.length - return teams -} - -async function fetchUserGroups(group, token, principalName) { - const groupUsersUrl = `${DAPLA_TEAM_API_URL}/groups/${group.uniform_name}/users` - try { - const groupUsers = await fetchAPIData(token, groupUsersUrl, 'Failed to fetch group users') - if (!groupUsers._embedded || !groupUsers._embedded.users || groupUsers._embedded.users.length === 0) { - return [] - } - - return groupUsers._embedded.users - .filter((user) => user.principal_name === principalName) - .map(() => group.uniform_name) - } catch (error) { - console.error(`Error processing group ${group.uniform_name}:`, error) - throw error - } -} - -app.get('/api/userProfile/:principalName/team', tokenVerificationMiddleware, async (req, res, next) => { - const token = req.token - const principalName = req.params.principalName - const myTeamsUrl = `${DAPLA_TEAM_API_URL}/users/${principalName}/teams` - - try { - const [myTeams] = await Promise.all([ - fetchAPIData(token, myTeamsUrl, 'Failed to fetch my teams').then((teams) => - getUserProfileTeamData(token, principalName, teams) - ), - ]) - - const result = { - count: myTeams.count, - ...myTeams._embedded, - } - - res.json(result) - } catch (error) { - next(error) - } -}) - app.get('/api/teamDetail/:teamUniformName', tokenVerificationMiddleware, async (req, res, next) => { try { const token = req.token diff --git a/src/@types/user.ts b/src/@types/user.ts index 2cab9bde..609cd082 100644 --- a/src/@types/user.ts +++ b/src/@types/user.ts @@ -11,7 +11,7 @@ export interface User { division_code?: number section_name?: string section_code?: number - manager?: User + section_manager?: User photo?: string groups?: Group[] } diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index d1cf9859..2e8735df 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -22,7 +22,7 @@ const Avatar = () => { setUserProfileData(userProfile) setEncodedURI( - `/teammedlemmer/${encodeURIComponent(userProfile.principal_name ? userProfile.principal_name.split('@')[0] : userProfile.email.split('@')[0])}` + `/teammedlemmer/${userProfile.principal_name}` ) setFallbackInitials(userProfile.first_name[0] + userProfile.last_name[0]) diff --git a/src/pages/TeamOverview/TeamOverview.tsx b/src/pages/TeamOverview/TeamOverview.tsx index 72ff2325..4189d512 100644 --- a/src/pages/TeamOverview/TeamOverview.tsx +++ b/src/pages/TeamOverview/TeamOverview.tsx @@ -82,7 +82,7 @@ const TeamOverview = () => { const renderErrorAlert = () => { return ( - + {`${error?.code} - ${error?.message}`} ) diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index 7f9811cc..8abfb0bf 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -10,73 +10,55 @@ import { useCallback, useContext, useEffect, useState } from 'react' import { DaplaCtrlContext } from '../../provider/DaplaCtrlProvider' import { getGroupType, formatDisplayName } from '../../utils/utils' -import { getUserProfile, getUserTeamsWithGroups, UserProfileTeamResult } from '../../services/userProfile' - -import { User } from '../../@types/user' -import { Team } from '../../@types/team' +import { getUserProfileTeamData, TeamsData, Team } from '../../services/userProfile' import { useParams } from 'react-router-dom' -import { ErrorResponse } from '../../@types/error' import { Skeleton } from '@mui/material' +import { ApiError } from '../../utils/services' const UserProfile = () => { const { setBreadcrumbUserProfileDisplayName } = useContext(DaplaCtrlContext) - const [error, setError] = useState() + const [error, setError] = useState() const [loadingTeamData, setLoadingTeamData] = useState(true) - const [userProfileData, setUserProfileData] = useState() + const [userProfileData, setUserProfileData] = useState() const [teamUserProfileTableData, setUserProfileTableData] = useState() const { principalName } = useParams() const prepTeamData = useCallback( - (response: UserProfileTeamResult): TableData['data'] => { + (response: TeamsData): TableData['data'] => { return response.teams.map((team) => ({ id: team.uniform_name, seksjon: team.section_name, // Makes section name searchable and sortable in table by including the field navn: renderTeamNameColumn(team), - gruppe: team.groups?.map((group) => getGroupType(group)).join(', '), - epost: userProfileData?.principal_name, + gruppe: team.groups + ?.filter(group => group.users.some(user => user.principal_name === principalName)) // Filter groups based on principalName presence + .map(group => getGroupType(group.uniform_name)) + .join(', '), ansvarlig: formatDisplayName(team.manager.display_name), })) }, - [userProfileData] + [principalName] ) useEffect(() => { - getUserProfile(principalName as string) - .then((response) => { - if ((response as ErrorResponse).error) { - setError(response as ErrorResponse) - } else { - setUserProfileData(response as User) - } - }) - .catch((error) => { - setError({ error: { message: error.message, code: '500' } }) - }) - }, [principalName]) - - useEffect(() => { - if (userProfileData) { - getUserTeamsWithGroups(principalName as string) + + getUserProfileTeamData(principalName as string) .then((response) => { - if ((response as ErrorResponse).error) { - setError(response as ErrorResponse) - } else { - setUserProfileTableData(prepTeamData(response as UserProfileTeamResult)) - } + setUserProfileTableData(prepTeamData(response as TeamsData)) + setUserProfileData((response as TeamsData)) }) .finally(() => setLoadingTeamData(false)) .catch((error) => { - setError({ error: { message: error.message, code: '500' } }) + setError(error as ApiError) }) - } - }, [userProfileData, principalName, prepTeamData]) + + }, [principalName, prepTeamData]) // required for breadcrumb useEffect(() => { if (userProfileData) { - const displayName = formatDisplayName(userProfileData.display_name) - userProfileData.display_name = displayName + const displayName = formatDisplayName(userProfileData.user.display_name) + userProfileData.user.display_name = displayName setBreadcrumbUserProfileDisplayName({ displayName }) } }, [userProfileData, setBreadcrumbUserProfileDisplayName]) @@ -96,8 +78,8 @@ const UserProfile = () => { const renderErrorAlert = () => { return ( - - {error?.error.message} + + {`${error?.code} - ${error?.message}`} ) } @@ -116,10 +98,6 @@ const UserProfile = () => { id: 'gruppe', label: 'Gruppe', }, - { - id: 'epost', - label: 'Epost ?', - }, { id: 'ansvarlig', label: 'Ansvarlig', @@ -128,8 +106,8 @@ const UserProfile = () => { return ( <> - {userProfileData?.section_name} - {userProfileData?.principal_name} + {userProfileData?.user.section_name} + {userProfileData?.user.principal_name} { ) diff --git a/src/services/userProfile.ts b/src/services/userProfile.ts index bbd3bf26..35dbe276 100644 --- a/src/services/userProfile.ts +++ b/src/services/userProfile.ts @@ -1,42 +1,178 @@ -import { User } from '../@types/user' -import { Team } from '../@types/team' -import { ErrorResponse } from '../@types/error' +import { ApiError, fetchAPIData } from '../utils/services' + +const DAPLA_TEAM_API_URL = import.meta.env.VITE_DAPLA_TEAM_API_URL +const USERS_URL = `${DAPLA_TEAM_API_URL}/users` export interface UserProfileTeamData { - [key: string]: UserProfileTeamResult + [key: string]: TeamsData } -export interface UserProfileTeamResult { +export interface TeamsData { + user: User teams: Team[] - count: number } -export const getUserProfile = async (principalName: string, token?: string): Promise => { - const accessToken = localStorage.getItem('access_token') +export interface Team { + uniform_name: string + division_name: string + display_name: string + section_name: string + section_code: string + manager: TeamManager + users: User[] + groups: Group[] + // eslint-disable-next-line + _embedded?: any +} + +interface TeamManager { + display_name: string + principal_name: string +} + +interface User { + display_name: string + principal_name: string + section_name: string + azure_ad_id?: string + first_name?: string + last_name?: string + email?: string +} + +interface Group { + uniform_name: string + display_name: string + users: User[] +} + +function flattenEmbedded(json: any): any { + if (json._embedded) { + for (const prop in json._embedded) { + json[prop] = json._embedded[prop] + } + delete json._embedded + } + + for (const prop in json) { + if (typeof json[prop] === 'object') { + json[prop] = flattenEmbedded(json[prop]) + } + } + + return json +} + +export const getUserProfile = async (principalName: string, token?: string): Promise => { + const accessToken = localStorage.getItem('access_token') as string || token as string principalName = principalName.replace(/@ssb\.no$/, '') + '@ssb.no' - return fetch(`/api/userProfile/${principalName}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken || token}`, - }, - }) - .then((response) => { - if (!response.ok) { - console.error('Request failed with status:', response.status) - throw new Error('Request failed') + const usersUrl = new URL(`${USERS_URL}/${principalName}`) + + const embeds = ['section_manager'] + const selects = [ + 'principal_name', + 'display_name', + 'first_name', + 'last_name', + 'section_name', + 'division_name', + 'phone', + 'section_manager.display_name', + 'section_manager.principal_name' + ] + + usersUrl.searchParams.set('embed', embeds.join(',')) + usersUrl.searchParams.append('select', selects.join(',')) + + try { + const [userData, userPhoto] = await Promise.all([ + fetchAPIData(usersUrl.toString(), accessToken), + fetchPhoto(accessToken, principalName) + ]) + + userData.photo = userPhoto + + return flattenEmbedded({...userData}) + } catch (error) { + if (error instanceof ApiError) { + console.error('Failed to fetch userProfile data:', error) + throw error + } else { + const apiError = new ApiError(500, 'An unexpected error occurred') + console.error('Failed to fetch userProfile data:', apiError) + throw apiError + } + } +} + + +export const getUserProfileTeamData = async (principalName: string): Promise => { + const accessToken = localStorage.getItem('access_token') as string + + const usersUrl = new URL(`${USERS_URL}/${principalName}`) + const embeds = [ + 'teams', + 'teams.groups', + 'teams.groups.users', + ] + + const selects = [ + "display_name", + "principal_name", + "teams.section_name", + "teams.display_name", + "teams.uniform_name", + "team.groups.uniform_name", + "teams.groups.users.principal_name", + "teams.groups.users.display_name", + ] + + usersUrl.searchParams.set('embed', embeds.join(',')) + usersUrl.searchParams.append('select', selects.join(',')) + + try { + const userProfileData = await fetchAPIData(usersUrl.toString(), accessToken) + + if (!userProfileData) throw new ApiError(500, 'No json data returned') + if (!userProfileData._embedded || !userProfileData._embedded.teams) return {} as TeamsData + + const flattedTeams = flattenEmbedded({ ...userProfileData }) + flattedTeams.teams.forEach((team: Team, teamIndex: number) => { + if (!team.groups) flattedTeams.teams.groups = [] + + team.groups.forEach((group: Group, groupIndex: number) => { + if (!group.users) flattedTeams.teams[teamIndex].groups[groupIndex].users = [] + }) + }) + + const flattedTeamsWithManager = flattedTeams.teams.map((team: Team) => { + const managers = team.groups.find((group) => group.uniform_name === `${team.uniform_name}-managers`) + return { + ...team, + manager: + managers && managers.users && managers.users.length > 0 + ? { display_name: managers.users[0].display_name, principal_name: managers.users[0].principal_name } + : { display_name: 'Mangler ansvarlig', principal_name: 'ManglerAnsvarlig@ssb.no' }, } - return response.json() }) - .then((data) => data as User) - .catch((error) => { - console.error('Error during fetching userProfile:', error) + + const getUser = await getUserProfile(principalName) + + return { teams: flattedTeamsWithManager, user: getUser as User } + } catch (error) { + if (error instanceof ApiError) { + console.error('Failed to fetch teams for PrincipalName:', error) throw error - }) + } else { + const apiError = new ApiError(500, 'An unexpected error occurred') + console.error('Failed to fetch teams for PrincipalName:', apiError) + throw apiError + } + } } -export const getUserTeamsWithGroups = async (principalName: string): Promise => { +export const getUserTeamsWithGroups = async (principalName: string): Promise => { const accessToken = localStorage.getItem('access_token') principalName = principalName.replace(/@ssb\.no$/, '') + '@ssb.no' @@ -54,7 +190,7 @@ export const getUserTeamsWithGroups = async (principalName: string): Promise data as UserProfileTeamResult) + .then((data) => data as TeamsData) .catch((error) => { console.error('Error during fetching userProfile:', error) throw error @@ -67,8 +203,26 @@ export const getUserProfileFallback = (accessToken: string): User => { principal_name: jwt.upn, azure_ad_id: jwt.oid, // not the real azureAdId, this is actually keycloaks oid display_name: jwt.name, + section_name: "UNSET", first_name: jwt.given_name, last_name: jwt.family_name, email: jwt.email, } } + +const fetchPhoto = async (accessToken: string, principalName: string) => { + const response = await fetch(`/api/photo/${principalName}`, { + method: 'GET', + headers: { + Accept: '*/*', + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (!response.ok) { + throw new ApiError(500, "could not fetch photo"); + } + + const data = await response.json() + return data.photo +} \ No newline at end of file From fe46c9c0f758e73c97a83c4408730459daac715b Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Sun, 3 Mar 2024 19:19:42 +0100 Subject: [PATCH 04/14] final refactor --- server.js | 105 ---------------------- src/pages/TeamDetail/TeamDetail.tsx | 71 +++++++-------- src/pages/UserProfile/UserProfile.tsx | 26 +++--- src/services/teamDetail.ts | 123 +++++++++++++++++++++----- src/services/userProfile.ts | 26 +----- src/utils/utils.ts | 17 ++++ 6 files changed, 159 insertions(+), 209 deletions(-) diff --git a/server.js b/server.js index b10fbbba..3b7c513c 100644 --- a/server.js +++ b/server.js @@ -6,9 +6,6 @@ import jwksClient from 'jwks-rsa' import { getReasonPhrase } from 'http-status-codes' import dotenv from 'dotenv' -// TODO: Do a massive cleanup. There are much of the code that can be re-written for reuseability, and some functions -// may not even be required anymore after dapla-team-api-redux changes. - if (!process.env.VITE_JWKS_URI) { dotenv.config({ path: './.env.local' }) } @@ -106,90 +103,6 @@ async function fetchPhoto(token, url, fallbackErrorMessage) { return photoBuffer.toString('base64') } -app.get('/api/teamDetail/:teamUniformName', tokenVerificationMiddleware, async (req, res, next) => { - try { - const token = req.token - const teamUniformName = req.params.teamUniformName - const teamInfoUrl = `${DAPLA_TEAM_API_URL}/teams/${teamUniformName}` - const teamUsersUrl = `${DAPLA_TEAM_API_URL}/teams/${teamUniformName}/users` - - const [teamInfo, teamUsers] = await Promise.all([ - fetchAPIData(token, teamInfoUrl, 'Failed to fetch team info').then(async (teamInfo) => { - const manager = await fetchTeamManager(token, teamInfo.uniform_name) - return { ...teamInfo, manager } - }), - fetchAPIData(token, teamUsersUrl, 'Failed to fetch team users').then(async (teamUsers) => { - const resolvedUsers = await fetchTeamUsersWithGroups(token, teamUsers, teamUniformName) - return { ...teamUsers, _embedded: { users: resolvedUsers } } - }), - ]) - - // TODO: Implement shared data tab - res.json({ teamUsers: { teamInfo, teamUsers: teamUsers._embedded.users } }) - } catch (error) { - next(error) - } -}) - -async function fetchTeamManager(token, teamUniformName) { - const teamManagerUrl = `${DAPLA_TEAM_API_URL}/groups/${teamUniformName}-managers/users` - return await fetchAPIData(token, teamManagerUrl, 'Failed to fetch team manager') - .then((teamManager) => { - return teamManager.count > 0 ? teamManager._embedded.users[0] : managerFallback() - }) - .catch(() => managerFallback()) -} - -async function fetchTeamUsersWithGroups(token, teamUsers, teamUniformName) { - const userPromises = teamUsers._embedded.users.map(async (user) => { - const userUrl = `${DAPLA_TEAM_API_URL}/users/${user.principal_name}` - const userGroupsUrl = `${DAPLA_TEAM_API_URL}/users/${user.principal_name}/groups` - const currentUser = await fetchAPIData(token, userUrl, 'Failed to fetch user') - const groups = await fetchAPIData(token, userGroupsUrl, 'Failed to fetch groups').catch(() => groupFallback()) - - const flattenedGroups = groups._embedded.groups - .filter((group) => group !== null && group.uniform_name.startsWith(teamUniformName)) - .flatMap((group) => group) - - currentUser.groups = flattenedGroups - - return { ...currentUser } - }) - return await Promise.all(userPromises) -} - -async function fetchAPIData(token, url, fallbackErrorMessage) { - const response = await fetch(url, getFetchOptions(token)) - const wwwAuthenticate = response.headers.get('www-authenticate') - - if (!response.ok) { - const { error_description } = wwwAuthenticate - ? parseWwwAuthenticate(wwwAuthenticate) - : { error_description: fallbackErrorMessage } - throw new APIError(error_description, response.status) - } - - return response.json() -} - -function parseWwwAuthenticate(header) { - const parts = header.split(',') - const result = {} - - parts.forEach((part) => { - const [key, value] = part.trim().split('=') - result[key] = value.replace(/"/g, '') - }) - - return result -} - -class APIError extends Error { - constructor(message, statusCode) { - super(message) - this.statusCode = statusCode - } -} function getFetchOptions(token) { return { @@ -230,24 +143,6 @@ app.use((err, req, res, next) => { }) }) -function managerFallback() { - return { - display_name: 'Mangler ansvarlig', - principal_name: 'Mangler ansvarlig', - } -} - -function sectionFallback(uniformName) { - return { - uniform_name: uniformName, - section_name: 'Mangler seksjon', - } -} - -function groupFallback() { - return { _embedded: { groups: [] }, count: '0' } -} - //const lightship = await createLightship(); // Replace above with below to get liveness and readiness probes when running locally const lightship = await createLightship({ detectKubernetes: false }) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index b7cac73a..282907d9 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -4,18 +4,19 @@ import { useCallback, useContext, useEffect, useState } from 'react' import PageLayout from '../../components/PageLayout/PageLayout' import { TeamDetailData, getTeamDetail } from '../../services/teamDetail' import { useParams } from 'react-router-dom' -import { ErrorResponse } from '../../@types/error' +import { ApiError } from '../../utils/services' + import { DaplaCtrlContext } from '../../provider/DaplaCtrlProvider' import Table, { TableData } from '../../components/Table/Table' import { formatDisplayName, getGroupType } from '../../utils/utils' -import { User } from '../../@types/user' +import { User } from '../../services/teamDetail' import { Text, Link, Dialog, LeadParagraph } from '@statisticsnorway/ssb-component-library' import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' import { Skeleton } from '@mui/material' const TeamDetail = () => { const { setBreadcrumbTeamDetailDisplayName } = useContext(DaplaCtrlContext) - const [error, setError] = useState() + const [error, setError] = useState() const [loadingTeamData, setLoadingTeamData] = useState(true) const [teamDetailData, setTeamDetailData] = useState() const [teamDetailTableData, setTeamDetailTableData] = useState() @@ -23,67 +24,58 @@ const TeamDetail = () => { const { teamId } = useParams<{ teamId: string }>() const prepTeamData = useCallback((response: TeamDetailData): TableData['data'] => { - return response['teamUsers'].teamUsers.map((user) => { + if (!response.team.users) { + return []; + } + + return response.team.users.map((user) => { // Makes data in username column searchable and sortable in table by including these fields const usernameColumn = { user: user.display_name, seksjon: user.section_name, - } - + }; + return { id: user?.principal_name, ...usernameColumn, navn: renderUsernameColumn(user), - gruppe: user.groups?.map((group) => getGroupType(group.uniform_name)).join(', '), + gruppe: user.groups?.filter((group) => group.uniform_name.startsWith(response.team.uniform_name)).map((group) => getGroupType(group.uniform_name)).join(', '), epost: user?.principal_name, - } - }) - }, []) + }; + }); + }, []); useEffect(() => { if (!teamId) return - getTeamDetail(teamId as string) + getTeamDetail(teamId) .then((response) => { - if ((response as ErrorResponse).error) { - setError(response as ErrorResponse) - } else { - setTeamDetailData(response as TeamDetailData) - } + const formattedResponse = response as TeamDetailData; + setTeamDetailData(formattedResponse); + + const displayName = formatDisplayName(formattedResponse.team.display_name); + setBreadcrumbTeamDetailDisplayName({ displayName }); }) .catch((error) => { - setError({ error: { message: error.message, code: '500' } }) + setError(error as ApiError) }) }, [teamId]) useEffect(() => { getTeamDetail(teamId as string) .then((response) => { - if ((response as ErrorResponse).error) { - setError(response as ErrorResponse) - } else { - setTeamDetailTableData(prepTeamData(response as TeamDetailData)) - } + setTeamDetailTableData(prepTeamData(response as TeamDetailData)) }) .finally(() => setLoadingTeamData(false)) .catch((error) => { - setError({ error: { message: error.message, code: '500' } }) + setError(error as ApiError) }) }, [teamId, prepTeamData]) - // required for breadcrumb - useEffect(() => { - if (!teamDetailData) return - - const displayName = teamDetailData['teamUsers'].teamInfo.display_name - teamDetailData['teamUsers'].teamInfo.display_name = displayName - setBreadcrumbTeamDetailDisplayName({ displayName }) - }, [teamDetailData, setBreadcrumbTeamDetailDisplayName]) - const renderUsernameColumn = (user: User) => { return ( <> - + {formatDisplayName(user.display_name)} @@ -94,8 +86,8 @@ const TeamDetail = () => { const renderErrorAlert = () => { return ( - - {error?.error.message} + + {`${error?.code} - ${error?.message}`} ) } @@ -123,12 +115,13 @@ const TeamDetail = () => { <> - {teamDetailData ? teamDetailData['teamUsers'].teamInfo.uniform_name : ''} + {teamDetailData ? teamDetailData.team.uniform_name : ''} - {teamDetailData ? formatDisplayName(teamDetailData['teamUsers'].teamInfo.manager.display_name) : ''} + { teamDetailData ? formatDisplayName(teamDetailData.team.manager?.display_name ?? '') : '' +} - {teamDetailData ? teamDetailData['teamUsers'].teamInfo.section_name : ''} + {teamDetailData ? teamDetailData.team.section_name : ''}
{ ) diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index 8abfb0bf..aec2ec58 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -10,7 +10,7 @@ import { useCallback, useContext, useEffect, useState } from 'react' import { DaplaCtrlContext } from '../../provider/DaplaCtrlProvider' import { getGroupType, formatDisplayName } from '../../utils/utils' -import { getUserProfileTeamData, TeamsData, Team } from '../../services/userProfile' +import { getUserProfileTeamData, TeamsData, Team, UserProfileTeamData } from '../../services/userProfile' import { useParams } from 'react-router-dom' import { Skeleton } from '@mui/material' @@ -30,10 +30,10 @@ const UserProfile = () => { id: team.uniform_name, seksjon: team.section_name, // Makes section name searchable and sortable in table by including the field navn: renderTeamNameColumn(team), - gruppe: team.groups + gruppe: principalName ? team.groups ?.filter(group => group.users.some(user => user.principal_name === principalName)) // Filter groups based on principalName presence .map(group => getGroupType(group.uniform_name)) - .join(', '), + .join(', ') : "INGEN FUNNET", ansvarlig: formatDisplayName(team.manager.display_name), })) }, @@ -41,11 +41,14 @@ const UserProfile = () => { ) useEffect(() => { - getUserProfileTeamData(principalName as string) .then((response) => { - setUserProfileTableData(prepTeamData(response as TeamsData)) - setUserProfileData((response as TeamsData)) + const formattedResponse = response as TeamsData; + setUserProfileTableData(prepTeamData(formattedResponse)) + setUserProfileData((formattedResponse)) + + const displayName = formatDisplayName(formattedResponse.user.display_name); + setBreadcrumbUserProfileDisplayName({ displayName }); }) .finally(() => setLoadingTeamData(false)) .catch((error) => { @@ -54,15 +57,6 @@ const UserProfile = () => { }, [principalName, prepTeamData]) - // required for breadcrumb - useEffect(() => { - if (userProfileData) { - const displayName = formatDisplayName(userProfileData.user.display_name) - userProfileData.user.display_name = displayName - setBreadcrumbUserProfileDisplayName({ displayName }) - } - }, [userProfileData, setBreadcrumbUserProfileDisplayName]) - const renderTeamNameColumn = (team: Team) => { return ( <> @@ -123,7 +117,7 @@ const UserProfile = () => { ) diff --git a/src/services/teamDetail.ts b/src/services/teamDetail.ts index 7e890f0c..a7596a52 100644 --- a/src/services/teamDetail.ts +++ b/src/services/teamDetail.ts @@ -1,36 +1,111 @@ -import { Team } from '../@types/team' -import { User } from '../@types/user' -import { ErrorResponse } from '../@types/error' +import { ApiError, fetchAPIData} from '../utils/services' +import { flattenEmbedded } from '../utils/utils' + +const DAPLA_TEAM_API_URL = import.meta.env.VITE_DAPLA_TEAM_API_URL +const TEAMS_URL = `${DAPLA_TEAM_API_URL}/teams` export interface TeamDetailData { - [key: string]: TeamDetailTeamResult + team: Team } -export interface TeamDetailTeamResult { - teamInfo: Team - teamUsers: User[] - count: number +export interface Team { + uniform_name: string + division_name?: string + display_name: string + section_name: string + section_code?: string + manager?: TeamManager + users?: User[] + groups?: Group[] + // eslint-disable-next-line + _embedded?: any } -export const getTeamDetail = async (teamId: string): Promise => { - const accessToken = localStorage.getItem('access_token') +interface TeamManager { + display_name: string + principal_name: string +} + +export interface User { + display_name: string + principal_name: string + section_name: string + groups: Group[] +} + +interface Group { + uniform_name: string + display_name: string +} + +export const fetchTeamInfo = async (teamId: string, accessToken: string): Promise => { + const teamsUrl = new URL(`${TEAMS_URL}/${teamId}`) + const embeds = ['users', 'users.groups', 'managers'] + const selects = [ + "uniform_name", + "display_name", + "section_name", + "managers.principal_name", + "managers.display_name", + "managers.section_name", + "users.principal_name", + "users.display_name", + "users.section_name", + "users.groups.uniform_name" + ] + + teamsUrl.searchParams.set('embed', embeds.join(',')) + teamsUrl.searchParams.append('select', selects.join(',')) try { - const response = await fetch(`/api/teamDetail/${teamId}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}`, - }, - }) - if (!response.ok) { - const errorData = await response.json() - return errorData as ErrorResponse + + const teamDetailData = await fetchAPIData(teamsUrl.toString(), accessToken) + const flattendTeams = flattenEmbedded(teamDetailData) + if (!flattendTeams) return {} as Team + if (!flattendTeams.users) flattendTeams.users = [] + if (!flattendTeams.managers || flattendTeams.managers.length === 0) { + flattendTeams.manager = { + "display_name": "Ikke funnet", + "principal_name": "Ikke funnet", + "section_name": "Ikke funnet" + } + } else { + flattendTeams.manager = flattendTeams.managers[0] } - const data = await response.json() - return data as TeamDetailData + delete flattendTeams.managers + + return flattendTeams } catch (error) { - console.error('Error during fetching teams:', error) - throw new Error('Error fetching teams') + if (error instanceof ApiError) { + console.error('Failed to fetch teams:', error) + throw error + } else { + const apiError = new ApiError(500, 'An unexpected error occurred') + console.error('Failed to fetch teams:', apiError) + throw apiError + } } } + +export const getTeamDetail = async (teamId: string): Promise => { + const accessToken = localStorage.getItem('access_token') as string + + try { + + const [teamInfo] = await Promise.all([ + fetchTeamInfo(teamId, accessToken) + //TODO: Add shared buckets part + ]) + + return { team: teamInfo} as TeamDetailData + } catch (error) { + if (error instanceof ApiError) { + console.error('Failed to fetch data for teamDetail page:', error) + throw error + } else { + const apiError = new ApiError(500, 'An unexpected error occurred') + console.error('FFailed to fetch data for teamDetail page:', apiError) + throw apiError + } + } +} \ No newline at end of file diff --git a/src/services/userProfile.ts b/src/services/userProfile.ts index 35dbe276..b5993c25 100644 --- a/src/services/userProfile.ts +++ b/src/services/userProfile.ts @@ -109,6 +109,7 @@ export const getUserProfile = async (principalName: string, token?: string): Pro export const getUserProfileTeamData = async (principalName: string): Promise => { const accessToken = localStorage.getItem('access_token') as string + principalName.replace(/@ssb\.no$/, '') + '@ssb.no' const usersUrl = new URL(`${USERS_URL}/${principalName}`) const embeds = [ @@ -172,31 +173,6 @@ export const getUserProfileTeamData = async (principalName: string): Promise => { - const accessToken = localStorage.getItem('access_token') - principalName = principalName.replace(/@ssb\.no$/, '') + '@ssb.no' - - return fetch(`/api/userProfile/${principalName}/team`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}`, - }, - }) - .then((response) => { - if (!response.ok) { - console.error('Request failed with status:', response.status) - throw new Error('Request failed') - } - return response.json() - }) - .then((data) => data as TeamsData) - .catch((error) => { - console.error('Error during fetching userProfile:', error) - throw error - }) -} - export const getUserProfileFallback = (accessToken: string): User => { const jwt = JSON.parse(atob(accessToken.split('.')[1])) return { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 761b62bc..0b43e0d8 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -20,3 +20,20 @@ export const getGroupType = (groupName: string) => { export const formatDisplayName = (displayName: string) => { return displayName.split(', ').reverse().join(' ') } + +export const flattenEmbedded = (json: any): any => { + if (json._embedded) { + for (const prop in json._embedded) { + json[prop] = json._embedded[prop] + } + delete json._embedded + } + + for (const prop in json) { + if (typeof json[prop] === 'object') { + json[prop] = flattenEmbedded(json[prop]) + } + } + + return json +} \ No newline at end of file From 12bdda418fc282e33615c0f7eeb57a2a04b4a3ef Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Sun, 3 Mar 2024 19:21:16 +0100 Subject: [PATCH 05/14] . --- src/pages/UserProfile/UserProfile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index aec2ec58..8438f299 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -10,7 +10,7 @@ import { useCallback, useContext, useEffect, useState } from 'react' import { DaplaCtrlContext } from '../../provider/DaplaCtrlProvider' import { getGroupType, formatDisplayName } from '../../utils/utils' -import { getUserProfileTeamData, TeamsData, Team, UserProfileTeamData } from '../../services/userProfile' +import { getUserProfileTeamData, TeamsData, Team } from '../../services/userProfile' import { useParams } from 'react-router-dom' import { Skeleton } from '@mui/material' From 89677ecc37cb36dd8c5f6f91d638117ddfdca5e2 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Sun, 3 Mar 2024 19:21:32 +0100 Subject: [PATCH 06/14] lint --- server.js | 5 ++-- src/components/Avatar/Avatar.tsx | 4 +-- src/pages/TeamDetail/TeamDetail.tsx | 28 +++++++++--------- src/pages/UserProfile/UserProfile.tsx | 35 ++++++++++++----------- src/services/teamDetail.ts | 38 ++++++++++++------------- src/services/userProfile.ts | 41 ++++++++++++--------------- src/utils/utils.ts | 2 +- 7 files changed, 73 insertions(+), 80 deletions(-) diff --git a/server.js b/server.js index 3b7c513c..4d381ba0 100644 --- a/server.js +++ b/server.js @@ -81,11 +81,11 @@ app.get('/api/photo/:principalName', tokenVerificationMiddleware, async (req, re const accessToken = req.token const principalName = req.params.principalName const userPhotoUrl = `${DAPLA_TEAM_API_URL}/users/${principalName}/photo` - + try { const photoData = await fetchPhoto(accessToken, userPhotoUrl) - return res.send({photo: photoData}) + return res.send({ photo: photoData }) } catch (error) { next(error) } @@ -103,7 +103,6 @@ async function fetchPhoto(token, url, fallbackErrorMessage) { return photoBuffer.toString('base64') } - function getFetchOptions(token) { return { method: 'GET', diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index 2e8735df..d1ff44da 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -21,9 +21,7 @@ const Avatar = () => { if (!userProfile) return setUserProfileData(userProfile) - setEncodedURI( - `/teammedlemmer/${userProfile.principal_name}` - ) + setEncodedURI(`/teammedlemmer/${userProfile.principal_name}`) setFallbackInitials(userProfile.first_name[0] + userProfile.last_name[0]) const base64Image = userProfile?.photo diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 282907d9..af5123c3 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -25,7 +25,7 @@ const TeamDetail = () => { const prepTeamData = useCallback((response: TeamDetailData): TableData['data'] => { if (!response.team.users) { - return []; + return [] } return response.team.users.map((user) => { @@ -33,27 +33,30 @@ const TeamDetail = () => { const usernameColumn = { user: user.display_name, seksjon: user.section_name, - }; - + } + return { id: user?.principal_name, ...usernameColumn, navn: renderUsernameColumn(user), - gruppe: user.groups?.filter((group) => group.uniform_name.startsWith(response.team.uniform_name)).map((group) => getGroupType(group.uniform_name)).join(', '), + gruppe: user.groups + ?.filter((group) => group.uniform_name.startsWith(response.team.uniform_name)) + .map((group) => getGroupType(group.uniform_name)) + .join(', '), epost: user?.principal_name, - }; - }); - }, []); + } + }) + }, []) useEffect(() => { if (!teamId) return getTeamDetail(teamId) .then((response) => { - const formattedResponse = response as TeamDetailData; - setTeamDetailData(formattedResponse); + const formattedResponse = response as TeamDetailData + setTeamDetailData(formattedResponse) - const displayName = formatDisplayName(formattedResponse.team.display_name); - setBreadcrumbTeamDetailDisplayName({ displayName }); + const displayName = formatDisplayName(formattedResponse.team.display_name) + setBreadcrumbTeamDetailDisplayName({ displayName }) }) .catch((error) => { setError(error as ApiError) @@ -118,8 +121,7 @@ const TeamDetail = () => { {teamDetailData ? teamDetailData.team.uniform_name : ''} - { teamDetailData ? formatDisplayName(teamDetailData.team.manager?.display_name ?? '') : '' -} + {teamDetailData ? formatDisplayName(teamDetailData.team.manager?.display_name ?? '') : ''} {teamDetailData ? teamDetailData.team.section_name : ''} diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index 8438f299..12b59c28 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -30,10 +30,12 @@ const UserProfile = () => { id: team.uniform_name, seksjon: team.section_name, // Makes section name searchable and sortable in table by including the field navn: renderTeamNameColumn(team), - gruppe: principalName ? team.groups - ?.filter(group => group.users.some(user => user.principal_name === principalName)) // Filter groups based on principalName presence - .map(group => getGroupType(group.uniform_name)) - .join(', ') : "INGEN FUNNET", + gruppe: principalName + ? team.groups + ?.filter((group) => group.users.some((user) => user.principal_name === principalName)) // Filter groups based on principalName presence + .map((group) => getGroupType(group.uniform_name)) + .join(', ') + : 'INGEN FUNNET', ansvarlig: formatDisplayName(team.manager.display_name), })) }, @@ -41,20 +43,19 @@ const UserProfile = () => { ) useEffect(() => { - getUserProfileTeamData(principalName as string) - .then((response) => { - const formattedResponse = response as TeamsData; - setUserProfileTableData(prepTeamData(formattedResponse)) - setUserProfileData((formattedResponse)) + getUserProfileTeamData(principalName as string) + .then((response) => { + const formattedResponse = response as TeamsData + setUserProfileTableData(prepTeamData(formattedResponse)) + setUserProfileData(formattedResponse) - const displayName = formatDisplayName(formattedResponse.user.display_name); - setBreadcrumbUserProfileDisplayName({ displayName }); - }) - .finally(() => setLoadingTeamData(false)) - .catch((error) => { - setError(error as ApiError) - }) - + const displayName = formatDisplayName(formattedResponse.user.display_name) + setBreadcrumbUserProfileDisplayName({ displayName }) + }) + .finally(() => setLoadingTeamData(false)) + .catch((error) => { + setError(error as ApiError) + }) }, [principalName, prepTeamData]) const renderTeamNameColumn = (team: Team) => { diff --git a/src/services/teamDetail.ts b/src/services/teamDetail.ts index a7596a52..2fa31ee0 100644 --- a/src/services/teamDetail.ts +++ b/src/services/teamDetail.ts @@ -1,4 +1,4 @@ -import { ApiError, fetchAPIData} from '../utils/services' +import { ApiError, fetchAPIData } from '../utils/services' import { flattenEmbedded } from '../utils/utils' const DAPLA_TEAM_API_URL = import.meta.env.VITE_DAPLA_TEAM_API_URL @@ -42,38 +42,37 @@ export const fetchTeamInfo = async (teamId: string, accessToken: string): Promis const teamsUrl = new URL(`${TEAMS_URL}/${teamId}`) const embeds = ['users', 'users.groups', 'managers'] const selects = [ - "uniform_name", - "display_name", - "section_name", - "managers.principal_name", - "managers.display_name", - "managers.section_name", - "users.principal_name", - "users.display_name", - "users.section_name", - "users.groups.uniform_name" + 'uniform_name', + 'display_name', + 'section_name', + 'managers.principal_name', + 'managers.display_name', + 'managers.section_name', + 'users.principal_name', + 'users.display_name', + 'users.section_name', + 'users.groups.uniform_name', ] teamsUrl.searchParams.set('embed', embeds.join(',')) teamsUrl.searchParams.append('select', selects.join(',')) try { - const teamDetailData = await fetchAPIData(teamsUrl.toString(), accessToken) const flattendTeams = flattenEmbedded(teamDetailData) if (!flattendTeams) return {} as Team if (!flattendTeams.users) flattendTeams.users = [] if (!flattendTeams.managers || flattendTeams.managers.length === 0) { flattendTeams.manager = { - "display_name": "Ikke funnet", - "principal_name": "Ikke funnet", - "section_name": "Ikke funnet" + display_name: 'Ikke funnet', + principal_name: 'Ikke funnet', + section_name: 'Ikke funnet', } } else { flattendTeams.manager = flattendTeams.managers[0] } delete flattendTeams.managers - + return flattendTeams } catch (error) { if (error instanceof ApiError) { @@ -91,13 +90,12 @@ export const getTeamDetail = async (teamId: string): Promise => { - const accessToken = localStorage.getItem('access_token') as string || token as string + const accessToken = (localStorage.getItem('access_token') as string) || (token as string) principalName = principalName.replace(/@ssb\.no$/, '') + '@ssb.no' const usersUrl = new URL(`${USERS_URL}/${principalName}`) - + const embeds = ['section_manager'] const selects = [ 'principal_name', @@ -79,7 +79,7 @@ export const getUserProfile = async (principalName: string, token?: string): Pro 'division_name', 'phone', 'section_manager.display_name', - 'section_manager.principal_name' + 'section_manager.principal_name', ] usersUrl.searchParams.set('embed', embeds.join(',')) @@ -88,12 +88,12 @@ export const getUserProfile = async (principalName: string, token?: string): Pro try { const [userData, userPhoto] = await Promise.all([ fetchAPIData(usersUrl.toString(), accessToken), - fetchPhoto(accessToken, principalName) + fetchPhoto(accessToken, principalName), ]) userData.photo = userPhoto - return flattenEmbedded({...userData}) + return flattenEmbedded({ ...userData }) } catch (error) { if (error instanceof ApiError) { console.error('Failed to fetch userProfile data:', error) @@ -106,27 +106,22 @@ export const getUserProfile = async (principalName: string, token?: string): Pro } } - export const getUserProfileTeamData = async (principalName: string): Promise => { const accessToken = localStorage.getItem('access_token') as string principalName.replace(/@ssb\.no$/, '') + '@ssb.no' const usersUrl = new URL(`${USERS_URL}/${principalName}`) - const embeds = [ - 'teams', - 'teams.groups', - 'teams.groups.users', - ] + const embeds = ['teams', 'teams.groups', 'teams.groups.users'] const selects = [ - "display_name", - "principal_name", - "teams.section_name", - "teams.display_name", - "teams.uniform_name", - "team.groups.uniform_name", - "teams.groups.users.principal_name", - "teams.groups.users.display_name", + 'display_name', + 'principal_name', + 'teams.section_name', + 'teams.display_name', + 'teams.uniform_name', + 'team.groups.uniform_name', + 'teams.groups.users.principal_name', + 'teams.groups.users.display_name', ] usersUrl.searchParams.set('embed', embeds.join(',')) @@ -179,7 +174,7 @@ export const getUserProfileFallback = (accessToken: string): User => { principal_name: jwt.upn, azure_ad_id: jwt.oid, // not the real azureAdId, this is actually keycloaks oid display_name: jwt.name, - section_name: "UNSET", + section_name: 'UNSET', first_name: jwt.given_name, last_name: jwt.family_name, email: jwt.email, @@ -193,12 +188,12 @@ const fetchPhoto = async (accessToken: string, principalName: string) => { Accept: '*/*', Authorization: `Bearer ${accessToken}`, }, - }); + }) if (!response.ok) { - throw new ApiError(500, "could not fetch photo"); + throw new ApiError(500, 'could not fetch photo') } const data = await response.json() return data.photo -} \ No newline at end of file +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 0b43e0d8..2bbd528c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -36,4 +36,4 @@ export const flattenEmbedded = (json: any): any => { } return json -} \ No newline at end of file +} From 966e01eafa9c5f0833a15dcd3ac516dc30a27206 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Sun, 3 Mar 2024 19:29:15 +0100 Subject: [PATCH 07/14] lint problems --- src/pages/TeamDetail/TeamDetail.tsx | 2 +- src/pages/TeamOverview/TeamOverview.tsx | 2 +- src/pages/UserProfile/UserProfile.tsx | 3 ++- src/services/teamOverview.ts | 19 +------------------ src/services/userProfile.ts | 18 +----------------- src/utils/utils.ts | 1 + 6 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 702ff49a..15b4b5e2 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -62,7 +62,7 @@ const TeamDetail = () => { .catch((error) => { setError(error as ApiError) }) - }, []) + }, [teamId, setBreadcrumbTeamDetailDisplayName]) useEffect(() => { getTeamDetail(teamId as string) diff --git a/src/pages/TeamOverview/TeamOverview.tsx b/src/pages/TeamOverview/TeamOverview.tsx index 7a0521e1..f7f3bdb4 100644 --- a/src/pages/TeamOverview/TeamOverview.tsx +++ b/src/pages/TeamOverview/TeamOverview.tsx @@ -51,7 +51,7 @@ const TeamOverview = () => { .catch((error) => { setError(error as ApiError) }) - }, []) + }, [jwt.email, prepTeamData]) useEffect(() => { if (teamOverviewData) { diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index 12b59c28..5f0e1e10 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -45,6 +45,7 @@ const UserProfile = () => { useEffect(() => { getUserProfileTeamData(principalName as string) .then((response) => { + console.log(response) const formattedResponse = response as TeamsData setUserProfileTableData(prepTeamData(formattedResponse)) setUserProfileData(formattedResponse) @@ -56,7 +57,7 @@ const UserProfile = () => { .catch((error) => { setError(error as ApiError) }) - }, [principalName, prepTeamData]) + }, [principalName, prepTeamData, setBreadcrumbUserProfileDisplayName]) const renderTeamNameColumn = (team: Team) => { return ( diff --git a/src/services/teamOverview.ts b/src/services/teamOverview.ts index c692d0cc..09cf2d42 100644 --- a/src/services/teamOverview.ts +++ b/src/services/teamOverview.ts @@ -1,5 +1,5 @@ import { ApiError, fetchAPIData } from '../utils/services' - +import { flattenEmbedded } from '../utils/utils' const DAPLA_TEAM_API_URL = import.meta.env.VITE_DAPLA_TEAM_API_URL const USERS_URL = `${DAPLA_TEAM_API_URL}/users` const TEAMS_URL = `${DAPLA_TEAM_API_URL}/teams` @@ -41,23 +41,6 @@ interface Group { users: User[] } -function flattenEmbedded(json: any): any { - if (json._embedded) { - for (const prop in json._embedded) { - json[prop] = json._embedded[prop] - } - delete json._embedded - } - - for (const prop in json) { - if (typeof json[prop] === 'object') { - json[prop] = flattenEmbedded(json[prop]) - } - } - - return json -} - const fetchAllTeams = async (accessToken: string): Promise => { const teamsUrl = new URL(`${TEAMS_URL}`) const embeds = ['users', 'groups.users'] diff --git a/src/services/userProfile.ts b/src/services/userProfile.ts index 2ca19198..a7b110a6 100644 --- a/src/services/userProfile.ts +++ b/src/services/userProfile.ts @@ -1,4 +1,5 @@ import { ApiError, fetchAPIData } from '../utils/services' +import { flattenEmbedded } from '../utils/utils' const DAPLA_TEAM_API_URL = import.meta.env.VITE_DAPLA_TEAM_API_URL const USERS_URL = `${DAPLA_TEAM_API_URL}/users` @@ -46,23 +47,6 @@ interface Group { users: User[] } -function flattenEmbedded(json: any): any { - if (json._embedded) { - for (const prop in json._embedded) { - json[prop] = json._embedded[prop] - } - delete json._embedded - } - - for (const prop in json) { - if (typeof json[prop] === 'object') { - json[prop] = flattenEmbedded(json[prop]) - } - } - - return json -} - export const getUserProfile = async (principalName: string, token?: string): Promise => { const accessToken = (localStorage.getItem('access_token') as string) || (token as string) principalName = principalName.replace(/@ssb\.no$/, '') + '@ssb.no' diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 2bbd528c..e9a0f171 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -21,6 +21,7 @@ export const formatDisplayName = (displayName: string) => { return displayName.split(', ').reverse().join(' ') } +// eslint-disable-next-line export const flattenEmbedded = (json: any): any => { if (json._embedded) { for (const prop in json._embedded) { From abd56731ee3de664d9f8eed501ce2ef74ec761ce Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Sun, 3 Mar 2024 19:36:49 +0100 Subject: [PATCH 08/14] remove unused interface files --- src/@types/error.ts | 8 -------- src/@types/group.ts | 7 ------- src/@types/team.ts | 12 ------------ src/@types/user.ts | 17 ----------------- 4 files changed, 44 deletions(-) delete mode 100644 src/@types/error.ts delete mode 100644 src/@types/group.ts delete mode 100644 src/@types/team.ts delete mode 100644 src/@types/user.ts diff --git a/src/@types/error.ts b/src/@types/error.ts deleted file mode 100644 index 0093a56a..00000000 --- a/src/@types/error.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface ErrorResponse { - error: Error -} - -export interface Error { - code: string - message: string -} diff --git a/src/@types/group.ts b/src/@types/group.ts deleted file mode 100644 index 44575cff..00000000 --- a/src/@types/group.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { User } from '../@types/user' - -export interface Group { - uniform_name: string - display_name: string - manager?: User -} diff --git a/src/@types/team.ts b/src/@types/team.ts deleted file mode 100644 index efcbf5a3..00000000 --- a/src/@types/team.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { User } from './user' - -export interface Team { - uniform_name: string - display_name: string - division_name: string - section_name: string - section_code: number - team_user_count: number - manager: User - groups?: string[] -} diff --git a/src/@types/user.ts b/src/@types/user.ts deleted file mode 100644 index 609cd082..00000000 --- a/src/@types/user.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Group } from '../@types/group' - -export interface User { - principal_name: string - azure_ad_id: string - display_name: string - first_name: string - last_name: string - email: string - division_name?: string - division_code?: number - section_name?: string - section_code?: number - section_manager?: User - photo?: string - groups?: Group[] -} From 2163c1e469ac383d9b8dcfbe7db233744919720d Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Sun, 3 Mar 2024 19:38:50 +0100 Subject: [PATCH 09/14] re-add user --- src/@types/user.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/@types/user.ts diff --git a/src/@types/user.ts b/src/@types/user.ts new file mode 100644 index 00000000..e619f4b0 --- /dev/null +++ b/src/@types/user.ts @@ -0,0 +1,14 @@ +export interface User { + principal_name: string + azure_ad_id: string + display_name: string + first_name: string + last_name: string + email: string + division_name?: string + division_code?: number + section_name?: string + section_code?: number + section_manager?: User + photo?: string +} From 90084d0e7b313b4d9e94541f3f68378e5bed9527 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Sun, 3 Mar 2024 19:41:58 +0100 Subject: [PATCH 10/14] remove log --- src/pages/UserProfile/UserProfile.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index 5f0e1e10..da43a6a9 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -45,7 +45,6 @@ const UserProfile = () => { useEffect(() => { getUserProfileTeamData(principalName as string) .then((response) => { - console.log(response) const formattedResponse = response as TeamsData setUserProfileTableData(prepTeamData(formattedResponse)) setUserProfileData(formattedResponse) From 0f8db02021322a8d1d1cb804d9fda40ad1246368 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Mon, 4 Mar 2024 09:03:45 +0100 Subject: [PATCH 11/14] remove dependencies to useEffect --- src/pages/TeamOverview/TeamOverview.tsx | 2 +- src/pages/UserProfile/UserProfile.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/TeamOverview/TeamOverview.tsx b/src/pages/TeamOverview/TeamOverview.tsx index f7f3bdb4..10f48b16 100644 --- a/src/pages/TeamOverview/TeamOverview.tsx +++ b/src/pages/TeamOverview/TeamOverview.tsx @@ -51,7 +51,7 @@ const TeamOverview = () => { .catch((error) => { setError(error as ApiError) }) - }, [jwt.email, prepTeamData]) + }, [jwt.email]) useEffect(() => { if (teamOverviewData) { diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index da43a6a9..e6546d88 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ import styles from './userprofile.module.scss' import { Dialog, Text, Link, LeadParagraph } from '@statisticsnorway/ssb-component-library' @@ -56,7 +57,7 @@ const UserProfile = () => { .catch((error) => { setError(error as ApiError) }) - }, [principalName, prepTeamData, setBreadcrumbUserProfileDisplayName]) + }, [principalName, setBreadcrumbUserProfileDisplayName]) const renderTeamNameColumn = (team: Team) => { return ( From a1b0fb1d33d3f9ecaa5b85d8b5f34a4b8af15256 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Mon, 4 Mar 2024 09:11:42 +0100 Subject: [PATCH 12/14] dep remove --- src/pages/UserProfile/UserProfile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index e6546d88..c13c0d28 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -57,7 +57,7 @@ const UserProfile = () => { .catch((error) => { setError(error as ApiError) }) - }, [principalName, setBreadcrumbUserProfileDisplayName]) + }, [setBreadcrumbUserProfileDisplayName]) const renderTeamNameColumn = (team: Team) => { return ( From 1ea44defe97ef1d5df4b53d02cadc9fdeccd0266 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Mon, 4 Mar 2024 09:40:00 +0100 Subject: [PATCH 13/14] more dependencies --- src/pages/TeamDetail/TeamDetail.tsx | 2 +- src/pages/UserProfile/UserProfile.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 15b4b5e2..702ff49a 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -62,7 +62,7 @@ const TeamDetail = () => { .catch((error) => { setError(error as ApiError) }) - }, [teamId, setBreadcrumbTeamDetailDisplayName]) + }, []) useEffect(() => { getTeamDetail(teamId as string) diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index c13c0d28..5fa015c8 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -40,7 +40,7 @@ const UserProfile = () => { ansvarlig: formatDisplayName(team.manager.display_name), })) }, - [principalName] + [userProfileData] ) useEffect(() => { From 77ddee0e5bea7e94d9fc19d7a5d81237ae6c32ce Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Mon, 4 Mar 2024 09:45:13 +0100 Subject: [PATCH 14/14] . --- src/pages/TeamOverview/TeamOverview.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/TeamOverview/TeamOverview.tsx b/src/pages/TeamOverview/TeamOverview.tsx index 10f48b16..5760aea7 100644 --- a/src/pages/TeamOverview/TeamOverview.tsx +++ b/src/pages/TeamOverview/TeamOverview.tsx @@ -42,6 +42,7 @@ const TeamOverview = () => { ) useEffect(() => { + if (!jwt) return fetchTeamOverviewData(jwt.email) .then((response) => { setTeamOverviewData(response as TeamOverviewData) @@ -51,7 +52,7 @@ const TeamOverview = () => { .catch((error) => { setError(error as ApiError) }) - }, [jwt.email]) + }, []) useEffect(() => { if (teamOverviewData) {