* fix: restructure code and show content based on permission - Restructure Invite user forms into separate App and Org forms - Show Modal content based on permissions. * update: Permission handling. * fix: Modify permission handling * fix: check manage permission for manage users btn and show copy to clipboard always. * Dummy commit to run the tests * Fix: Test cases * Another dummy commit to run the tests Co-authored-by: Trisha Anand <trisha@appsmith.com>
391 lines
10 KiB
TypeScript
391 lines
10 KiB
TypeScript
import React, { useEffect } from "react";
|
|
import { connect } from "react-redux";
|
|
import { Icon } from "@blueprintjs/core";
|
|
import { TableWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
|
|
import { CompactModeTypes, TABLE_SIZES } from "widgets/TableWidget";
|
|
import { AppState } from "reducers";
|
|
import {
|
|
getAllUsers,
|
|
getAllRoles,
|
|
getCurrentOrg,
|
|
} from "selectors/organizationSelectors";
|
|
import PageSectionDivider from "pages/common/PageSectionDivider";
|
|
import PageSectionHeader from "pages/common/PageSectionHeader";
|
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
|
import OrgInviteUsersForm from "pages/organization/OrgInviteUsersForm";
|
|
import Button from "components/editorComponents/Button";
|
|
import { OrgUser, Org } from "constants/orgConstants";
|
|
import { Menu, MenuItem, Popover, Position } from "@blueprintjs/core";
|
|
import styled from "styled-components";
|
|
import { FormIcons } from "icons/FormIcons";
|
|
import { RouteComponentProps } from "react-router";
|
|
import Spinner from "components/editorComponents/Spinner";
|
|
import FormDialogComponent from "components/editorComponents/form/FormDialogComponent";
|
|
import { getCurrentUser } from "selectors/usersSelectors";
|
|
import { User } from "constants/userConstants";
|
|
import { useTable, useFlexLayout } from "react-table";
|
|
|
|
type OrgProps = {
|
|
currentOrg: Org;
|
|
changeOrgName: (value: string) => void;
|
|
fetchCurrentOrg: (orgId: string) => void;
|
|
fetchUser: (orgId: string) => void;
|
|
fetchAllRoles: (orgId: string) => void;
|
|
deleteOrgUser: (orgId: string, username: string) => void;
|
|
changeOrgUserRole: (orgId: string, role: string, username: string) => void;
|
|
allUsers: OrgUser[];
|
|
allRole: object;
|
|
currentUser: User | undefined;
|
|
isFetchAllUsers: boolean;
|
|
isFetchAllRoles: boolean;
|
|
};
|
|
|
|
export type PageProps = OrgProps &
|
|
RouteComponentProps<{
|
|
orgId: string;
|
|
}>;
|
|
|
|
export type MenuItemProps = {
|
|
rolename: string;
|
|
};
|
|
|
|
type DropdownProps = {
|
|
activeItem: string;
|
|
userRoles: object;
|
|
username: string;
|
|
changeOrgUserRole: (orgId: string, role: string, username: string) => void;
|
|
orgId: string;
|
|
};
|
|
|
|
const StyledDropDown = styled.div`
|
|
cursor: pointer;
|
|
`;
|
|
|
|
const StyledTableWrapped = styled(TableWrapper)`
|
|
min-height: 0px;
|
|
height: auto;
|
|
.tableWrap {
|
|
display: flex;
|
|
flex: 1;
|
|
}
|
|
.table {
|
|
display: flex;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
height: auto;
|
|
.tbody {
|
|
overflow: auto;
|
|
}
|
|
}
|
|
`;
|
|
|
|
const StyledMenu = styled(Menu)`
|
|
&&&&.bp3-menu {
|
|
max-width: 250px;
|
|
cursor: pointer;
|
|
}
|
|
`;
|
|
|
|
const RoleNameCell = (props: any) => {
|
|
const {
|
|
roleName,
|
|
roles,
|
|
username,
|
|
isCurrentUser,
|
|
isChangingRole,
|
|
} = props.cellProps.row.original;
|
|
|
|
if (isCurrentUser) {
|
|
return <div>{roleName}</div>;
|
|
}
|
|
|
|
return (
|
|
<Popover
|
|
content={
|
|
<Dropdown
|
|
activeItem={roleName}
|
|
userRoles={roles}
|
|
username={username}
|
|
changeOrgUserRole={props.changeOrgUserRole}
|
|
orgId={props.orgId}
|
|
/>
|
|
}
|
|
position={Position.BOTTOM_LEFT}
|
|
>
|
|
<StyledDropDown>
|
|
{roleName}
|
|
<Icon icon="chevron-down" />
|
|
{isChangingRole ? <Spinner size={20} /> : undefined}
|
|
</StyledDropDown>
|
|
</Popover>
|
|
);
|
|
};
|
|
|
|
const DeleteActionCell = (props: any) => {
|
|
const { username, isCurrentUser, isDeleting } = props.cellProps.row.original;
|
|
|
|
return (
|
|
!isCurrentUser &&
|
|
(isDeleting ? (
|
|
<Spinner size={20} />
|
|
) : (
|
|
<FormIcons.DELETE_ICON
|
|
height={20}
|
|
width={20}
|
|
color={"grey"}
|
|
background={"grey"}
|
|
onClick={() => props.deleteOrgUser(props.orgId, username)}
|
|
style={{ alignSelf: "center", cursor: "pointer" }}
|
|
/>
|
|
))
|
|
);
|
|
};
|
|
|
|
const Dropdown = (props: DropdownProps) => {
|
|
return (
|
|
<StyledMenu>
|
|
{Object.entries(props.userRoles).map((role, index) => {
|
|
const MenuContent = (
|
|
<div>
|
|
<span>
|
|
<b>{role[0]}</b>
|
|
</span>
|
|
<div>{role[1]}</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<MenuItem
|
|
multiline
|
|
key={index}
|
|
onClick={() =>
|
|
props.changeOrgUserRole(props.orgId, role[0], props.username)
|
|
}
|
|
active={props.activeItem === role[0]}
|
|
text={MenuContent}
|
|
/>
|
|
);
|
|
})}
|
|
</StyledMenu>
|
|
);
|
|
};
|
|
|
|
export const OrgSettings = (props: PageProps) => {
|
|
const {
|
|
match: {
|
|
params: { orgId },
|
|
},
|
|
deleteOrgUser,
|
|
changeOrgUserRole,
|
|
fetchCurrentOrg,
|
|
fetchUser,
|
|
fetchAllRoles,
|
|
currentOrg,
|
|
} = props;
|
|
|
|
const userTableData = props.allUsers.map(user => ({
|
|
...user,
|
|
roles: props.allRole,
|
|
isCurrentUser: user.username === props.currentUser?.username,
|
|
}));
|
|
const data = React.useMemo(() => userTableData, [userTableData]);
|
|
|
|
const columns = React.useMemo(() => {
|
|
return [
|
|
{
|
|
Header: "Email",
|
|
accessor: "username",
|
|
},
|
|
{
|
|
Header: "Name",
|
|
accessor: "name",
|
|
},
|
|
{
|
|
Header: "Role",
|
|
accessor: "roleName",
|
|
Cell: (cellProps: any) => {
|
|
return RoleNameCell({ cellProps, changeOrgUserRole, orgId });
|
|
},
|
|
},
|
|
{
|
|
Header: "Delete",
|
|
accessor: "delete",
|
|
Cell: (cellProps: any) => {
|
|
return DeleteActionCell({ cellProps, deleteOrgUser, orgId });
|
|
},
|
|
},
|
|
];
|
|
}, [orgId, deleteOrgUser, changeOrgUserRole]);
|
|
|
|
const currentOrgName = currentOrg?.name ?? "";
|
|
const {
|
|
getTableProps,
|
|
getTableBodyProps,
|
|
headerGroups,
|
|
rows,
|
|
prepareRow,
|
|
} = useTable(
|
|
{
|
|
columns,
|
|
data,
|
|
manualPagination: true,
|
|
},
|
|
useFlexLayout,
|
|
);
|
|
|
|
useEffect(() => {
|
|
fetchUser(orgId);
|
|
fetchAllRoles(orgId);
|
|
fetchCurrentOrg(orgId);
|
|
}, [orgId, fetchUser, fetchAllRoles, fetchCurrentOrg]);
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<PageSectionHeader>
|
|
<h2>{currentOrgName}</h2>
|
|
</PageSectionHeader>
|
|
<PageSectionDivider />
|
|
<PageSectionHeader>
|
|
<h2>Users</h2>
|
|
<FormDialogComponent
|
|
trigger={
|
|
<Button
|
|
intent="primary"
|
|
text="Invite Users"
|
|
icon="plus"
|
|
iconAlignment="left"
|
|
filled
|
|
/>
|
|
}
|
|
canOutsideClickClose={true}
|
|
Form={OrgInviteUsersForm}
|
|
orgId={orgId}
|
|
title={`Invite Users to ${currentOrgName}`}
|
|
/>
|
|
</PageSectionHeader>
|
|
{props.isFetchAllUsers && props.isFetchAllRoles ? (
|
|
<Spinner size={30} />
|
|
) : (
|
|
<StyledTableWrapped
|
|
width={200}
|
|
height={200}
|
|
tableSizes={TABLE_SIZES[CompactModeTypes.DEFAULT]}
|
|
>
|
|
<div className="tableWrap">
|
|
<div {...getTableProps()} className="table">
|
|
{headerGroups.map((headerGroup: any, index: number) => (
|
|
<div
|
|
key={index}
|
|
{...headerGroup.getHeaderGroupProps()}
|
|
className="tr"
|
|
>
|
|
{headerGroup.headers.map(
|
|
(column: any, columnIndex: number) => (
|
|
<div
|
|
key={columnIndex}
|
|
{...column.getHeaderProps()}
|
|
className="th header-reorder"
|
|
>
|
|
<div
|
|
className={
|
|
!column.isHidden
|
|
? "draggable-header"
|
|
: "hidden-header"
|
|
}
|
|
>
|
|
{column.render("Header")}
|
|
</div>
|
|
</div>
|
|
),
|
|
)}
|
|
</div>
|
|
))}
|
|
<div {...getTableBodyProps()} className="tbody">
|
|
{rows.map((row: any, index: number) => {
|
|
prepareRow(row);
|
|
return (
|
|
<div key={index} {...row.getRowProps()} className={"tr"}>
|
|
{row.cells.map((cell: any, cellIndex: number) => {
|
|
return (
|
|
<div
|
|
key={cellIndex}
|
|
{...cell.getCellProps()}
|
|
className="td"
|
|
data-rowindex={index}
|
|
data-colindex={cellIndex}
|
|
>
|
|
{cell.render("Cell")}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</StyledTableWrapped>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const mapStateToProps = (state: AppState) => ({
|
|
allUsers: getAllUsers(state),
|
|
allRole: getAllRoles(state),
|
|
isFetchAllUsers: state.ui.orgs.loadingStates.isFetchAllUsers,
|
|
isFetchAllRoles: state.ui.orgs.loadingStates.isFetchAllRoles,
|
|
currentOrg: getCurrentOrg(state),
|
|
currentUser: getCurrentUser(state),
|
|
});
|
|
|
|
const mapDispatchToProps = (dispatch: any) => ({
|
|
fetchCurrentOrg: (orgId: string) =>
|
|
dispatch({
|
|
type: ReduxActionTypes.FETCH_CURRENT_ORG,
|
|
payload: {
|
|
orgId,
|
|
},
|
|
}),
|
|
changeOrgName: (name: string) =>
|
|
dispatch({
|
|
type: ReduxActionTypes.UPDATE_ORG_NAME_INIT,
|
|
payload: {
|
|
name,
|
|
},
|
|
}),
|
|
changeOrgUserRole: (orgId: string, role: string, username: string) =>
|
|
dispatch({
|
|
type: ReduxActionTypes.CHANGE_ORG_USER_ROLE_INIT,
|
|
payload: {
|
|
orgId,
|
|
role,
|
|
username,
|
|
},
|
|
}),
|
|
deleteOrgUser: (orgId: string, username: string) =>
|
|
dispatch({
|
|
type: ReduxActionTypes.DELETE_ORG_USER_INIT,
|
|
payload: {
|
|
orgId,
|
|
username,
|
|
},
|
|
}),
|
|
fetchUser: (orgId: string) =>
|
|
dispatch({
|
|
type: ReduxActionTypes.FETCH_ALL_USERS_INIT,
|
|
payload: {
|
|
orgId,
|
|
},
|
|
}),
|
|
fetchAllRoles: (orgId: string) =>
|
|
dispatch({
|
|
type: ReduxActionTypes.FETCH_ALL_ROLES_INIT,
|
|
payload: {
|
|
orgId,
|
|
},
|
|
}),
|
|
});
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(OrgSettings);
|