diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index dd66c502a46..75658436bac 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released [Commits](https://github.com/scalableminds/webknossos/compare/23.03.1...HEAD) ### Added +- Added list of all respective team members to the administration page for teams. [#6915](https://github.com/scalableminds/webknossos/pull/6915) ### Changed - Updated the styling of the "welcome" screen for new users to be in line with the new branding. [#6904](https://github.com/scalableminds/webknossos/pull/6904) diff --git a/frontend/javascripts/admin/team/team_list_view.tsx b/frontend/javascripts/admin/team/team_list_view.tsx index fcd554e933f..b3b616d6c10 100644 --- a/frontend/javascripts/admin/team/team_list_view.tsx +++ b/frontend/javascripts/admin/team/team_list_view.tsx @@ -1,27 +1,72 @@ // @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module '@sca... Remove this comment to see the full error message import { PropTypes } from "@scalableminds/prop-types"; -import { Table, Spin, Button, Input, Modal, Alert } from "antd"; +import { Table, Spin, Button, Input, Modal, Alert, Tag } from "antd"; import { DeleteOutlined, PlusOutlined } from "@ant-design/icons"; import * as React from "react"; import _ from "lodash"; -import type { APITeam } from "types/api_flow_types"; -import { getEditableTeams, deleteTeam } from "admin/admin_rest_api"; +import type { APITeam, APITeamMembership, APIUser } from "types/api_flow_types"; +import { getEditableTeams, deleteTeam, getEditableUsers } from "admin/admin_rest_api"; import { handleGenericError } from "libs/error_handling"; import LinkButton from "components/link_button"; import CreateTeamModal from "admin/team/create_team_modal_view"; import Persistence from "libs/persistence"; import * as Utils from "libs/utils"; import messages from "messages"; +import { stringToColor } from "libs/format_utils"; const { Column } = Table; const { Search } = Input; const typeHint: APITeam[] = []; + type Props = {}; type State = { isLoading: boolean; - teams: Array; + teams: APITeam[]; + users: APIUser[]; searchQuery: string; isTeamCreationModalVisible: boolean; }; + +export function renderTeamRolesAndPermissionsForUser(user: APIUser) { + //used by user list page + const tags = [ + ...(user.isOrganizationOwner ? [["Organization Owner", "cyan"]] : []), + ...(user.isAdmin + ? [["Admin - Access to all Teams", "red"]] + : [ + ...(user.isDatasetManager ? [["Dataset Manager - Edit all Datasets", "geekblue"]] : []), + ...user.teams.map((team) => { + const roleName = team.isTeamManager ? "Team Manager" : "Member"; + return [`${team.name}: ${roleName}`, stringToColor(roleName)]; + }), + ]), + ]; + + return tags.map(([text, color]) => ( + + {text} + + )); +} + +function renderTeamRolesForUser(user: APIUser, highlightedTeam: APITeam) { + // used by teams list page + // does not include dataset managers and team names + const tags = user.isAdmin + ? [["Admin - Access to all Teams", "red"]] + : user.teams + .filter((team) => team.id === highlightedTeam.id) + .map((team) => { + const roleName = team.isTeamManager ? "Team Manager" : "Member"; + return [`${roleName}`, stringToColor(roleName)]; + }); + + return tags.map(([text, color]) => ( + + {text} + + )); +} + const persistence = new Persistence>( { searchQuery: PropTypes.string, @@ -33,6 +78,7 @@ class TeamListView extends React.PureComponent { state: State = { isLoading: true, teams: [], + users: [], searchQuery: "", isTeamCreationModalVisible: false, }; @@ -48,10 +94,11 @@ class TeamListView extends React.PureComponent { } async fetchData(): Promise { - const teams = await getEditableTeams(); + const [teams, users] = await Promise.all([getEditableTeams(), getEditableUsers()]); this.setState({ isLoading: false, teams, + users, }); } @@ -112,6 +159,26 @@ class TeamListView extends React.PureComponent { ); } + renderUsersForTeam(team: APITeam) { + const teamMembers = this.state.users.filter( + (user) => + user.teams.some((userTeam: APITeamMembership) => userTeam.id === team.id) || user.isAdmin, + ); + + if (teamMembers.length === 0) return messages["team.no_members"]; + + return ( +
    + {teamMembers.map((teamMember) => ( +
  • + {teamMember.firstName} {teamMember.lastName} ({teamMember.email}){" "} + {renderTeamRolesForUser(teamMember, team)} +
  • + ))} +
+ ); + } + render() { const marginRight = { marginRight: 20, @@ -165,6 +232,10 @@ class TeamListView extends React.PureComponent { pagination={{ defaultPageSize: 50, }} + expandable={{ + expandedRowRender: (team) => this.renderUsersForTeam(team), + rowExpandable: (_team) => true, + }} style={{ marginTop: 30, marginBottom: 30, diff --git a/frontend/javascripts/admin/user/user_list_view.tsx b/frontend/javascripts/admin/user/user_list_view.tsx index 744d6896307..8aad0ed4772 100644 --- a/frontend/javascripts/admin/user/user_list_view.tsx +++ b/frontend/javascripts/admin/user/user_list_view.tsx @@ -31,7 +31,6 @@ import type { OxalisState } from "oxalis/store"; import { enforceActiveUser } from "oxalis/model/accessors/user_accessor"; import LinkButton from "components/link_button"; import { getEditableUsers, updateUser } from "admin/admin_rest_api"; -import { stringToColor } from "libs/format_utils"; import EditableTextLabel from "oxalis/view/components/editable_text_label"; import ExperienceModalView from "admin/user/experience_modal_view"; import Persistence from "libs/persistence"; @@ -43,6 +42,7 @@ import messages from "messages"; import { logoutUserAction } from "../../oxalis/model/actions/user_actions"; import Store from "../../oxalis/store"; import { enforceActiveOrganization } from "oxalis/model/accessors/organization_accessors"; +import { renderTeamRolesAndPermissionsForUser } from "admin/team/team_list_view"; const { Column } = Table; const { Search } = Input; @@ -543,28 +543,9 @@ class UserListView extends React.PureComponent { dataIndex="teams" key="teams_" width={250} - render={(_teams: APITeamMembership[], user: APIUser) => { - const tags = [ - ...(user.isOrganizationOwner ? [["Organization Owner", "cyan"]] : []), - ...(user.isAdmin - ? [["Admin - Access to all Teams", "red"]] - : [ - ...(user.isDatasetManager - ? [["Dataset Manager - Edit all Datasets", "geekblue"]] - : []), - ...user.teams.map((team) => { - const roleName = team.isTeamManager ? "Team Manager" : "Member"; - return [`${team.name}: ${roleName}`, stringToColor(roleName)]; - }), - ]), - ]; - - return tags.map(([text, color]) => ( - - {text} - - )); - }} + render={(_teams: APITeamMembership[], user: APIUser) => + renderTeamRolesAndPermissionsForUser(user) + } />