Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter User Groups to Show Only Team Groups #391

Merged
merged 8 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build-and-lint-on-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ jobs:

- name: Check for ESLint warnings and errors
run: pnpm run lint

- name: Run tests
run: pnpm run tests
9 changes: 9 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
transformIgnorePatterns: ['<rootDir>/node_modules/'],
}
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint ./src --quiet --fix",
"lint:format": "prettier --loglevel warn --write \"./**/*.{js,jsx,ts,tsx,css,md,json}\" ",
"tests": "pnpm jest",
"preview": "vite preview"
},
"dependencies": {
Expand Down Expand Up @@ -45,6 +46,8 @@
},
"devDependencies": {
"@eslint/compat": "^1.2.4",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.2",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.18.1",
Expand All @@ -54,6 +57,9 @@
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.13.0",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.7.2",
"vite": "^5.4.8",
"vite-envs": "^4.4.10"
Expand Down
2,254 changes: 2,200 additions & 54 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

129 changes: 71 additions & 58 deletions src/pages/TeamDetail/TeamDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,68 +106,81 @@ const TeamDetail = () => {
const prepTeamData = useCallback(
(response: TeamDetailData): TableData['data'] => {
const sharedBucketsTab = SHARED_BUCKETS_TAB.path

if (teamDetailTab === sharedBucketsTab) {
const sharedBuckets = (response[sharedBucketsTab] as SharedBuckets).items
if (!sharedBuckets) return []

return sharedBuckets.map(({ short_name, bucket_name, metrics }) => {
const teams_count = metrics?.teams_count
return {
id: short_name,
navn: <FormattedTableColumn href={`/${teamId}/${short_name}`} linkText={short_name} text={bucket_name} />,
tilgang: typeof teams_count === 'number' ? `${teams_count} team` : teams_count,
// delte_data: '-', // To be implemented; data does not exist in the API yet.
antall_personer: metrics?.users_count,
}
})
return getSharedBucketsData(response, sharedBucketsTab)
} else {
const teamUsers = (response[TEAM_USERS_TAB.path] as Team).users
if (!teamUsers) return []

return teamUsers.map(({ display_name, principal_name, section_name, groups }) => {
const userFullName = formatDisplayName(display_name)
const userGroups = groups?.filter((group) =>
group.uniform_name.startsWith((response.team as Team).uniform_name)
) as Group[]
return {
id: userFullName,
navn: (
<FormattedTableColumn
href={`/teammedlemmer/${principal_name}`}
linkText={formatDisplayName(display_name)}
text={section_name}
/>
),
seksjon: section_name, // Makes section name searchable and sortable in table by including the field
gruppe: groups
?.filter((group) => {
const baseUniformName = (response.team as Team).uniform_name
return group.uniform_name.startsWith(baseUniformName)
})
.map((group) => getGroupType((response.team as Team).uniform_name, group.uniform_name))
.join(', '),
epost: principal_name,
editUser: (
<span>
<Link
onClick={() => {
setOpenEditUserSidebarModal(true)
setEditUserInfo({
name: formatDisplayName(display_name),
email: principal_name,
groups: userGroups,
})
}}
>
Endre
</Link>
</span>
),
}
})
return getTeamUsersData(response)
}
},
[activeTab]
[teamDetailTab, teamId, setOpenEditUserSidebarModal, setEditUserInfo]
)

const getSharedBucketsData = (response: TeamDetailData, sharedBucketsTab: string): TableData['data'] => {
const sharedBuckets = (response[sharedBucketsTab] as SharedBuckets).items
if (!sharedBuckets) return []

return sharedBuckets.map(({ short_name, bucket_name, metrics }) => {
const teams_count = metrics?.teams_count
return {
id: short_name,
navn: <FormattedTableColumn href={`/${teamId}/${short_name}`} linkText={short_name} text={bucket_name} />,
tilgang: typeof teams_count === 'number' ? `${teams_count} team` : teams_count,
// delte_data: '-', // To be implemented; data does not exist in the API yet.
antall_personer: metrics?.users_count,
}
})
}

const getTeamUsersData = (response: TeamDetailData): TableData['data'] => {
const teamUsers = (response[TEAM_USERS_TAB.path] as Team).users
const teamGroups = (response.team as Team).groups ?? []
if (!teamUsers) return []

return teamUsers.map(({ display_name, principal_name, section_name, groups }) => {
const userFullName = formatDisplayName(display_name)

const userGroups = filterUserGroups(groups, teamGroups)
return {
id: userFullName,
navn: (
<FormattedTableColumn
href={`/teammedlemmer/${principal_name}`}
linkText={formatDisplayName(display_name)}
text={section_name}
/>
),
seksjon: section_name, // Makes section name searchable and sortable in table by including the field
gruppe: formatUserGroups(response.team as Team, userGroups),
epost: principal_name,
editUser: renderEditUserLink(display_name, principal_name, userGroups),
}
})
}

const filterUserGroups = (userGroups: Group[] | undefined, teamGroups: Group[]) =>
userGroups?.filter((userGroup) =>
teamGroups.some((teamGroup) => userGroup.uniform_name === teamGroup.uniform_name)
) as Group[]

const formatUserGroups = (team: Team, userGroups: Group[]) =>
userGroups.map((group) => getGroupType(team.uniform_name, group.uniform_name)).join(', ')

const renderEditUserLink = (display_name: string, principal_name: string, userGroups: Group[]) => (
<span>
<Link
onClick={() => {
setOpenEditUserSidebarModal(true)
setEditUserInfo({
name: formatDisplayName(display_name),
email: principal_name,
groups: userGroups,
})
}}
>
Endre
</Link>
</span>
)

useEffect(() => {
Expand Down
15 changes: 8 additions & 7 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ export const getGroupType = (teamName: string, groupName: string): string => {
return groupName.slice(teamName.length + 1)
}

// Returns the closest match of a team name from a group name
export const getTeamFromGroup = (allTeams: Team[], groupName: string): string => {
if (allTeams?.length) {
const teamName = allTeams.filter(({ uniform_name }) => {
if (groupName.includes(uniform_name)) return uniform_name
})
return teamName[0].uniform_name
}
return ''
if (!allTeams?.length) return ''

const matchedTeams = allTeams
.filter(({ uniform_name }) => groupName.includes(uniform_name))
.sort((a, b) => b.uniform_name.length - a.uniform_name.length)

return matchedTeams.length ? matchedTeams[0].uniform_name : ''
}

export const formatDisplayName = (displayName: string) => {
Expand Down
31 changes: 31 additions & 0 deletions tests/utils/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Team } from '../../src/services/teamDetail'
import { getTeamFromGroup } from '../../src/utils/utils'

describe('getTeamFromGroup', () => {
it('should return the longest matching team name for a group name', () => {
const allTeams = [{ uniform_name: 'donald-du' }, { uniform_name: 'donald-duck' }, { uniform_name: 'mickey-mouse' }]
const groupName = 'donald-duck-data-admins'

const result = getTeamFromGroup(allTeams, groupName)

expect(result).toBe('donald-duck') // Longest match
})

it('should return an empty string when no matches are found', () => {
const allTeams = [{ uniform_name: 'mickey-mouse' }, { uniform_name: 'goofy' }]
const groupName = 'donald-duck-data-admins'

const result = getTeamFromGroup(allTeams, groupName)

expect(result).toBe('') // No matches
})

it('should handle empty input arrays', () => {
const allTeams: Team[] = []
const groupName = 'donald-duck-data-admins'

const result = getTeamFromGroup(allTeams, groupName)

expect(result).toBe('') // Empty array
})
})
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"types": ["jest", "node"],
"target": "ES2021",
"useDefineForClassFields": true,
"lib": ["ES2021", "DOM", "DOM.Iterable"],
Expand All @@ -19,7 +20,7 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"include": ["src", "tests"],
"references": [
{
"path": "./tsconfig.node.json"
Expand Down
Loading