Skip to content

Commit

Permalink
UI: Group/User/Attribute administration lists revisited (#368)
Browse files Browse the repository at this point in the history
* Draft of user/group lists refactor

* Next changes

* UsersPendingList, AttributesList

* Removed dummy elements

* Small fixes

* Added proper error/success handling

* Fixed group rename
  • Loading branch information
psrok1 committed May 17, 2021
1 parent b6039cc commit e4e966c
Show file tree
Hide file tree
Showing 15 changed files with 499 additions and 583 deletions.
4 changes: 2 additions & 2 deletions mwdb/schema/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from marshmallow import Schema, ValidationError, fields, validates

from .api_key import APIKeyListItemResponseSchema
from .group import GroupBasicResponseSchema, GroupNameSchemaBase
from .group import GroupBasicResponseSchema
from .utils import UTCDateTime


Expand Down Expand Up @@ -78,7 +78,7 @@ class UserListItemResponseSchema(UserLoginSchemaBase):
disabled = fields.Boolean(required=True, allow_none=False)
pending = fields.Boolean(required=True, allow_none=False)
groups = fields.Nested(
GroupNameSchemaBase, many=True, required=True, allow_none=False
GroupBasicResponseSchema, many=True, required=True, allow_none=False
)


Expand Down
16 changes: 8 additions & 8 deletions mwdb/web/src/components/Settings/SettingsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
AdministrativeRoute,
AttributeRoute,
} from "@mwdb-web/commons/ui";
import ShowGroups from "./Views/ShowGroups";
import ShowUsers from "./Views/ShowUsers";
import ShowPendingUsers from "./Views/ShowPendingUsers";
import ManageAttributes from "./Views/ManageAttributes";
import GroupsList from "./Views/GroupsList";
import UsersList from "./Views/UsersList";
import UsersPendingList from "./Views/UsersPendingList";
import AttributesList from "./Views/AttributesList";
import UserCreate from "./Views/UserCreate";
import GroupRegister from "./Views/GroupRegister";
import AttributeDefine from "./Views/AttributeDefine";
Expand Down Expand Up @@ -133,11 +133,11 @@ export default function SettingsView() {
<SettingsOverview />
</Route>
<AdministrativeRoute path="/admin/pending">
<ShowPendingUsers />
<UsersPendingList />
</AdministrativeRoute>

<AdministrativeRoute exact path="/admin/users">
<ShowUsers />
<UsersList />
</AdministrativeRoute>
<AdministrativeRoute exact path="/admin/user/new">
<UserCreate />
Expand Down Expand Up @@ -169,7 +169,7 @@ export default function SettingsView() {
</AdministrativeRoute>

<AdministrativeRoute exact path="/admin/groups">
<ShowGroups />
<GroupsList />
</AdministrativeRoute>
<AdministrativeRoute
exact
Expand All @@ -179,7 +179,7 @@ export default function SettingsView() {
</AdministrativeRoute>

<AttributeRoute exact path="/admin/attributes">
<ManageAttributes />
<AttributesList />
</AttributeRoute>
<AttributeRoute exact path="/admin/attribute/new">
<AttributeDefine />
Expand Down
6 changes: 3 additions & 3 deletions mwdb/web/src/components/Settings/Views/AccessControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ export default function AccessControl() {
if (!groups) return [];

return (
<div>
<h5>Access control</h5>
<p>
<div className="container">
<h2>Access control</h2>
<p className="lead">
Use a form below to enable/disable capabilities for specific
user or group.
</p>
Expand Down
78 changes: 78 additions & 0 deletions mwdb/web/src/components/Settings/Views/AttributesList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useCallback, useEffect, useState } from "react";
import { Link } from "react-router-dom";

import api from "@mwdb-web/commons/api";
import { PagedList, useViewAlert } from "@mwdb-web/commons/ui";

function AttributeItem({ name, label, description, template }) {
return (
<tr>
<td>
<Link to={`/admin/attribute/${name}`}>{name}</Link>
&nbsp;
{label && <span>({label})</span>}
</td>
<td>
{description || <div className="text-muted">(Not defined)</div>}
</td>
<td>
{template || <div className="text-muted">(Not defined)</div>}
</td>
</tr>
);
}

export default function AttributesList() {
const viewAlert = useViewAlert();
const [attributes, setAttributes] = useState([]);
const [activePage, setActivePage] = useState(1);
const [attributeFilter, setAttributeFilter] = useState("");

async function updateAttributes() {
try {
const response = await api.getMetakeyDefinitions();
setAttributes(
response.data["metakeys"].map((attribute) => ({
...attribute,
name: attribute.key,
}))
);
} catch (error) {
viewAlert.setAlert({ error });
}
}

const getAttributes = useCallback(updateAttributes, []);

useEffect(() => {
getAttributes();
}, [getAttributes]);

const query = attributeFilter.toLowerCase();
const items = attributes
.filter((attribute) => attribute.key.toLowerCase().includes(query))
.sort((attrA, attrB) => attrA.key.localeCompare(attrB.key));

return (
<div className="container">
<Link to="/admin/attribute/new">
<button type="button" className="btn btn-success">
Create attribute
</button>
</Link>
<PagedList
listItem={AttributeItem}
columnNames={["Key (Label)", "Description", "URL Template"]}
items={items.slice((activePage - 1) * 10, activePage * 10)}
itemCount={items.length}
activePage={activePage}
filterValue={attributeFilter}
onPageChange={(pageNumber) => setActivePage(pageNumber)}
onFilterChange={(ev) => {
setAttributeFilter(ev.target.value);
setActivePage(1);
}}
/>
</div>
);
}
14 changes: 8 additions & 6 deletions mwdb/web/src/components/Settings/Views/GroupDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ export default function GroupDetails({ group }) {
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
const [isDeleteModalDisabled, setDeleteModalDisabled] = useState(false);

async function handleSubmit(newName) {
async function handleRename(newValue) {
const newName = newValue["name"];
try {
await api.updateGroup(group.name, newName["name"], undefined);
viewAlert.setAlert({
success: `Group successfully updated.`,
await api.updateGroup(group.name, newName, undefined);
viewAlert.redirectToAlert({
target: `/admin/group/${newName}`,
success: `Group successfully renamed.`,
});
} catch (error) {
viewAlert.setAlert({ error });
Expand Down Expand Up @@ -60,7 +62,7 @@ export default function GroupDetails({ group }) {
<EditableItem
name="name"
defaultValue={group.name}
onSubmit={handleSubmit}
onSubmit={handleRename}
/>
</GroupItem>
<GroupItem label="Members">
Expand Down Expand Up @@ -120,7 +122,7 @@ export default function GroupDetails({ group }) {
disabled={isDeleteModalDisabled}
onRequestClose={() => setDeleteModalOpen(false)}
onConfirm={handleRemoveGroup}
message={`Are tou sure you want to delete ${group.name} from mwdb`}
message={`Are you sure you want to delete ${group.name} from mwdb`}
buttonStyle="btn-danger"
/>
</div>
Expand Down
11 changes: 8 additions & 3 deletions mwdb/web/src/components/Settings/Views/GroupMembers.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React, { useEffect, useState, useCallback } from "react";
import { UserLink } from "./ShowUsers";
import api from "@mwdb-web/commons/api";
import { MemberList, useViewAlert } from "@mwdb-web/commons/ui";
import { GroupBadge, MemberList, useViewAlert } from "@mwdb-web/commons/ui";

export let GroupMemberList = (props) => (
<MemberList nameKey="login" itemLinkClass={UserLink} {...props} />
<MemberList
nameKey="login"
itemLinkClass={(user) => (
<GroupBadge group={{ name: user.login, private: true }} clickable />
)}
{...props}
/>
);

export default function GroupMembers({ group, getGroup }) {
Expand Down
88 changes: 88 additions & 0 deletions mwdb/web/src/components/Settings/Views/GroupsList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useCallback, useEffect, useState } from "react";
import { Link } from "react-router-dom";

import api from "@mwdb-web/commons/api";
import {
GroupBadge,
PagedList,
HighlightText,
useViewAlert,
} from "@mwdb-web/commons/ui";

function GroupItem(props) {
return (
<tr key={props.name}>
<td>
<Link to={`/admin/group/${props.name}`}>
<HighlightText filterValue={props.filterValue}>
{props.name}
</HighlightText>
</Link>
</td>
<td>
{props.name === "public"
? "(Group is public and contains all members)"
: props.users.map((login) => (
<GroupBadge
group={{
name: login,
private: true,
}}
clickable
/>
))}
</td>
</tr>
);
}

export default function GroupsList() {
const viewAlert = useViewAlert();
const [groups, setGroups] = useState([]);
const [activePage, setActivePage] = useState(1);
const [groupFilter, setGroupFilter] = useState("");

async function updateGroups() {
try {
const response = await api.getGroups();
setGroups(response.data["groups"]);
} catch (error) {
viewAlert.setAlert({ error });
}
}

const getGroups = useCallback(updateGroups, []);

useEffect(() => {
getGroups();
}, [getGroups]);

const query = groupFilter.toLowerCase();
const items = groups
.filter((group) => !group.private)
.filter((group) => group.name.toLowerCase().includes(query))
.sort((groupA, groupB) => groupA.name.localeCompare(groupB.name));

return (
<div className="container">
<Link to="/admin/group/new">
<button type="button" className="btn btn-success">
Create group
</button>
</Link>
<PagedList
listItem={GroupItem}
columnNames={["Name", "Members"]}
items={items.slice((activePage - 1) * 10, activePage * 10)}
itemCount={items.length}
activePage={activePage}
filterValue={groupFilter}
onPageChange={(pageNumber) => setActivePage(pageNumber)}
onFilterChange={(ev) => {
setGroupFilter(ev.target.value);
setActivePage(1);
}}
/>
</div>
);
}
96 changes: 0 additions & 96 deletions mwdb/web/src/components/Settings/Views/ManageAttributes.js

This file was deleted.

0 comments on commit e4e966c

Please sign in to comment.