mirror of
https://github.com/getredash/redash.git
synced 2026-03-23 04:00:09 -04:00
* Added screen reader CSS * Added description to external links * Added spinner icon accessibility * Added accessibility to exclamation and big message * Added question and exclamation accessibility * Hide decorative icons * Standardized link design * Added a11y to refresh icons * Added aria-label to anchors and buttons * Added a11y to conditional icons * Added applicable labels to Ant Icons * Changed escape to interpolation * Replaced external links with opens in new tab * Improved Tooltip hosts * Added aria live to temporary elements * Removed mistakenly added redundant helper * Undoes unnecessarily added interpolation * Replaced empty label with hidden * Improved full icon label * Improved display of live regions * Added note * remove unused class * Created unique id * Remove TODOs * Proper action label * Improved feedback for autocomplete toggle * feature: add id hook * refactor: use id hook * standardize white space
219 lines
7.0 KiB
JavaScript
219 lines
7.0 KiB
JavaScript
import { includes, map } from "lodash";
|
|
import React from "react";
|
|
import Button from "antd/lib/button";
|
|
|
|
import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession";
|
|
import navigateTo from "@/components/ApplicationArea/navigateTo";
|
|
import Paginator from "@/components/Paginator";
|
|
|
|
import { wrap as itemsList, ControllerType } from "@/components/items-list/ItemsList";
|
|
import { ResourceItemsSource } from "@/components/items-list/classes/ItemsSource";
|
|
import { StateStorage } from "@/components/items-list/classes/StateStorage";
|
|
|
|
import LoadingState from "@/components/items-list/components/LoadingState";
|
|
import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable";
|
|
import SelectItemsDialog from "@/components/SelectItemsDialog";
|
|
import { UserPreviewCard } from "@/components/PreviewCard";
|
|
|
|
import GroupName from "@/components/groups/GroupName";
|
|
import ListItemAddon from "@/components/groups/ListItemAddon";
|
|
import Sidebar from "@/components/groups/DetailsPageSidebar";
|
|
import Layout from "@/components/layouts/ContentWithSidebar";
|
|
import wrapSettingsTab from "@/components/SettingsWrapper";
|
|
|
|
import notification from "@/services/notification";
|
|
import { currentUser } from "@/services/auth";
|
|
import Group from "@/services/group";
|
|
import User from "@/services/user";
|
|
import routes from "@/services/routes";
|
|
|
|
class GroupMembers extends React.Component {
|
|
static propTypes = {
|
|
controller: ControllerType.isRequired,
|
|
};
|
|
|
|
groupId = parseInt(this.props.controller.params.groupId, 10);
|
|
|
|
group = null;
|
|
|
|
sidebarMenu = [
|
|
{
|
|
key: "users",
|
|
href: `groups/${this.groupId}`,
|
|
title: "Members",
|
|
},
|
|
{
|
|
key: "datasources",
|
|
href: `groups/${this.groupId}/data_sources`,
|
|
title: "Data Sources",
|
|
isAvailable: () => currentUser.isAdmin,
|
|
},
|
|
];
|
|
|
|
listColumns = [
|
|
Columns.custom((text, user) => <UserPreviewCard user={user} withLink />, {
|
|
title: "Name",
|
|
field: "name",
|
|
width: null,
|
|
}),
|
|
Columns.custom(
|
|
(text, user) => {
|
|
if (!this.group) {
|
|
return null;
|
|
}
|
|
|
|
// cannot remove self from built-in groups
|
|
if (this.group.type === "builtin" && currentUser.id === user.id) {
|
|
return null;
|
|
}
|
|
return (
|
|
<Button className="w-100" type="danger" onClick={event => this.removeGroupMember(event, user)}>
|
|
Remove
|
|
</Button>
|
|
);
|
|
},
|
|
{
|
|
width: "1%",
|
|
isAvailable: () => currentUser.isAdmin,
|
|
}
|
|
),
|
|
];
|
|
|
|
componentDidMount() {
|
|
Group.get({ id: this.groupId })
|
|
.then(group => {
|
|
this.group = group;
|
|
this.forceUpdate();
|
|
})
|
|
.catch(error => {
|
|
this.props.controller.handleError(error);
|
|
});
|
|
}
|
|
|
|
removeGroupMember = (event, user) =>
|
|
Group.removeMember({ id: this.groupId, userId: user.id })
|
|
.then(() => {
|
|
this.props.controller.updatePagination({ page: 1 });
|
|
this.props.controller.update();
|
|
})
|
|
.catch(() => {
|
|
notification.error("Failed to remove member from group.");
|
|
});
|
|
|
|
addMembers = () => {
|
|
const alreadyAddedUsers = map(this.props.controller.allItems, u => u.id);
|
|
SelectItemsDialog.showModal({
|
|
dialogTitle: "Add Members",
|
|
inputPlaceholder: "Search users...",
|
|
selectedItemsTitle: "New Members",
|
|
searchItems: searchTerm => User.query({ q: searchTerm }).then(({ results }) => results),
|
|
renderItem: (item, { isSelected }) => {
|
|
const alreadyInGroup = includes(alreadyAddedUsers, item.id);
|
|
return {
|
|
content: (
|
|
<UserPreviewCard user={item}>
|
|
<ListItemAddon isSelected={isSelected} alreadyInGroup={alreadyInGroup} />
|
|
</UserPreviewCard>
|
|
),
|
|
isDisabled: alreadyInGroup,
|
|
className: isSelected || alreadyInGroup ? "selected" : "",
|
|
};
|
|
},
|
|
renderStagedItem: (item, { isSelected }) => ({
|
|
content: (
|
|
<UserPreviewCard user={item}>
|
|
<ListItemAddon isSelected={isSelected} isStaged />
|
|
</UserPreviewCard>
|
|
),
|
|
}),
|
|
}).onClose(items => {
|
|
const promises = map(items, u => Group.addMember({ id: this.groupId }, { user_id: u.id }));
|
|
return Promise.all(promises).then(() => this.props.controller.update());
|
|
});
|
|
};
|
|
|
|
render() {
|
|
const { controller } = this.props;
|
|
return (
|
|
<div data-test="Group">
|
|
<GroupName className="d-block m-t-0 m-b-15" group={this.group} onChange={() => this.forceUpdate()} />
|
|
<Layout>
|
|
<Layout.Sidebar>
|
|
<Sidebar
|
|
controller={controller}
|
|
group={this.group}
|
|
items={this.sidebarMenu}
|
|
canAddMembers={currentUser.isAdmin}
|
|
onAddMembersClick={this.addMembers}
|
|
onGroupDeleted={() => navigateTo("groups")}
|
|
/>
|
|
</Layout.Sidebar>
|
|
<Layout.Content>
|
|
{!controller.isLoaded && <LoadingState className="" />}
|
|
{controller.isLoaded && controller.isEmpty && (
|
|
<div className="text-center">
|
|
<p>There are no members in this group yet.</p>
|
|
{currentUser.isAdmin && (
|
|
<Button type="primary" onClick={this.addMembers}>
|
|
<i className="fa fa-plus m-r-5" aria-hidden="true" />
|
|
Add Members
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
{controller.isLoaded && !controller.isEmpty && (
|
|
<div className="table-responsive">
|
|
<ItemsTable
|
|
items={controller.pageItems}
|
|
columns={this.listColumns}
|
|
showHeader={false}
|
|
context={this.actions}
|
|
orderByField={controller.orderByField}
|
|
orderByReverse={controller.orderByReverse}
|
|
toggleSorting={controller.toggleSorting}
|
|
/>
|
|
<Paginator
|
|
showPageSizeSelect
|
|
totalCount={controller.totalItemsCount}
|
|
pageSize={controller.itemsPerPage}
|
|
onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}
|
|
page={controller.page}
|
|
onChange={page => controller.updatePagination({ page })}
|
|
/>
|
|
</div>
|
|
)}
|
|
</Layout.Content>
|
|
</Layout>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
const GroupMembersPage = wrapSettingsTab(
|
|
"Groups.Members",
|
|
null,
|
|
itemsList(
|
|
GroupMembers,
|
|
() =>
|
|
new ResourceItemsSource({
|
|
isPlainList: true,
|
|
getRequest(unused, { params: { groupId } }) {
|
|
return { id: groupId };
|
|
},
|
|
getResource() {
|
|
return Group.members.bind(Group);
|
|
},
|
|
}),
|
|
() => new StateStorage({ orderByField: "name" })
|
|
)
|
|
);
|
|
|
|
routes.register(
|
|
"Groups.Members",
|
|
routeWithUserSession({
|
|
path: "/groups/:groupId",
|
|
title: "Group Members",
|
|
render: pageProps => <GroupMembersPage {...pageProps} currentPage="users" />,
|
|
})
|
|
);
|