Skip to content

Commit

Permalink
Update config tabs for librarians and library managers. (#15)
Browse files Browse the repository at this point in the history
* Update config tabs shown for librarians and library managers.

Remove the analytics tab, and add the individual admins tab.

* Make Admins tab read-only for librarians (non-managers).

* Relabel "library manager" to "administrator" and "librarian" to "user".
  • Loading branch information
Ray Lee authored Dec 16, 2021
1 parent d6bda5e commit d9d0db3
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 33 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
## Changelog

### October 28, 2021
### December 14, 2021

#### Updated

- Updated branding and styling for The Palace Project.
- Updated node-sass and sass-loader dependency versions to reduce the number of high risk vulnerabilities.
- Removed Analytics tab from System Configuration for librarians and library managers, and added Admins tab.
- Relabeled "library manager" role to "administrator", and "librarian" role to "user".

### v0.5.5

Expand Down
2 changes: 1 addition & 1 deletion src/components/ConfigTabContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default class ConfigTabContainer extends TabContainer<
discovery: DiscoveryServices,
};

LIBRARIAN_TABS = ["libraries", "analytics"];
LIBRARIAN_TABS = ["libraries", "individualAdmins"];
LIBRARY_MANAGER_TABS = this.LIBRARIAN_TABS;
SYSTEM_ADMIN_TABS = Object.keys(this.COMPONENT_CLASSES);

Expand Down
10 changes: 7 additions & 3 deletions src/components/EditableConfigList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export abstract class GenericEditableConfigList<
const EditForm = this.EditForm;
const ExtraFormSection = this.ExtraFormSection;
const itemToEdit = this.itemToEdit();
const canEditItem = itemToEdit && this.canEdit(itemToEdit);
return (
<div className={this.getClassName()}>
<h2>{headers["h2"]}</h2>
Expand Down Expand Up @@ -188,13 +189,16 @@ export abstract class GenericEditableConfigList<

{itemToEdit && (
<div>
<h3>Edit {this.label(itemToEdit)}</h3>
<h3>
{canEditItem ? "Edit " : ""}
{this.label(itemToEdit)}
</h3>
<EditForm
item={itemToEdit}
data={this.props.data}
additionalData={this.props.additionalData}
disabled={this.props.isFetching}
save={this.save}
disabled={!canEditItem || this.props.isFetching}
save={canEditItem ? this.save : undefined}
urlBase={this.urlBase}
listDataKey={this.listDataKey}
responseBody={this.props.responseBody}
Expand Down
8 changes: 4 additions & 4 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ export class Header extends React.Component<HeaderProps, HeaderState> {

displayPermissions(isSystemAdmin: boolean, isLibraryManager: boolean) {
let permissions = isSystemAdmin
? "system admin"
? "a system admin"
: isLibraryManager
? "library manager"
: "librarian";
return <li className="permissions">Logged in as a {permissions}</li>;
? "an administrator"
: "a user";
return <li className="permissions">Logged in as {permissions}</li>;
}

render(): JSX.Element {
Expand Down
17 changes: 10 additions & 7 deletions src/components/IndividualAdminEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface IndividualAdminEditFormProps {
data: IndividualAdminsData;
item?: IndividualAdminData;
disabled: boolean;
save: (data: FormData) => void;
save?: (data: FormData) => void;
urlBase: string;
listDataKey: string;
responseBody?: string;
Expand Down Expand Up @@ -83,6 +83,7 @@ export default class IndividualAdminEditForm extends React.Component<
onSubmit={this.submit}
className="edit-form"
disableButton={this.props.disabled}
withoutButton={!this.props.save}
content={[
<Panel
headerText="Admin Information"
Expand Down Expand Up @@ -165,7 +166,7 @@ export default class IndividualAdminEditForm extends React.Component<
disabled={this.isDisabled("manager-all")}
name="manager-all"
ref={this.managerAllRef}
label="Library Manager"
label="Administrator"
checked={this.isSelected("manager-all")}
onChange={() => this.handleRoleChange("manager-all")}
/>
Expand All @@ -177,7 +178,7 @@ export default class IndividualAdminEditForm extends React.Component<
disabled={this.isDisabled("librarian-all")}
name="librarian-all"
ref={this.librarianAllRef}
label="Librarian"
label="User"
checked={this.isSelected("librarian-all")}
onChange={() => this.handleRoleChange("librarian-all")}
/>
Expand All @@ -198,7 +199,7 @@ export default class IndividualAdminEditForm extends React.Component<
name={"manager-" + library.short_name}
ref={this.libraryManagerRef}
label=""
aria-label={`Library Manager for ${library.short_name}`}
aria-label={`Administrator of ${library.short_name}`}
checked={this.isSelected("manager", library.short_name)}
onChange={() =>
this.handleRoleChange("manager", library.short_name)
Expand All @@ -216,7 +217,7 @@ export default class IndividualAdminEditForm extends React.Component<
name={"librarian-" + library.short_name}
ref={this.librarianRef}
label=""
aria-label={`Librarian for ${library.short_name}`}
aria-label={`User of ${library.short_name}`}
checked={this.isSelected("librarian", library.short_name)}
onChange={() =>
this.handleRoleChange("librarian", library.short_name)
Expand Down Expand Up @@ -422,7 +423,9 @@ export default class IndividualAdminEditForm extends React.Component<
}

async submit(data: FormData) {
const modifiedData = this.handleData(data);
await this.props.save(modifiedData);
if (this.props.save) {
const modifiedData = this.handleData(data);
await this.props.save(modifiedData);
}
}
}
12 changes: 12 additions & 0 deletions src/components/IndividualAdmins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,22 @@ export class IndividualAdmins extends EditableConfigList<
admin: PropTypes.object.isRequired,
};

canCreate() {
return (
this.context.admin && this.context.admin.isLibraryManagerOfSomeLibrary()
);
}

canDelete() {
return this.context.admin && this.context.admin.isSystemAdmin();
}

canEdit() {
return (
this.context.admin && this.context.admin.isLibraryManagerOfSomeLibrary()
);
}

getHeaders() {
const h2 = this.props.settingUp
? "Welcome!"
Expand Down
8 changes: 5 additions & 3 deletions src/components/ServiceEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface ServiceEditFormProps<T> {
data: T;
item?: ServiceData;
disabled: boolean;
save: (data: FormData) => void;
save?: (data: FormData) => void;
urlBase: string;
listDataKey: string;
responseBody?: string;
Expand Down Expand Up @@ -670,7 +670,9 @@ export default class ServiceEditForm<
}

async submit(data: FormData) {
const modifiedData = this.handleData(data);
await this.props.save(modifiedData);
if (this.props.save) {
const modifiedData = this.handleData(data);
await this.props.save(modifiedData);
}
}
}
4 changes: 3 additions & 1 deletion src/components/SitewideSettingEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ export default class SitewideSettingEditForm extends React.Component<
}

async submit(data: FormData) {
await this.props.save(data);
if (this.props.save) {
await this.props.save(data);
}
}

UNSAFE_componentWillReceiveProps(nextProps) {
Expand Down
14 changes: 7 additions & 7 deletions src/components/__tests__/ConfigTabContainer-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ describe("ConfigTabContainer", () => {
const links = wrapper.find("ul.nav-tabs").find("a");
const linkTexts = links.map((link) => link.text());
expect(linkTexts).to.contain("Libraries");
expect(linkTexts).not.to.contain("Admins");
expect(linkTexts).to.contain("Analytics");
expect(linkTexts).to.contain("Admins");
expect(linkTexts).not.to.contain("Analytics");
expect(linkTexts).not.to.contain("Collections");
expect(linkTexts).not.to.contain("Admin Authentication");
expect(linkTexts).not.to.contain("Patron Authentication");
Expand All @@ -132,7 +132,7 @@ describe("ConfigTabContainer", () => {
});

it("shows components", () => {
const expectedComponentClasses = [Libraries, AnalyticsServices];
const expectedComponentClasses = [Libraries, IndividualAdmins];
for (const componentClass of expectedComponentClasses) {
const component = wrapper.find(componentClass);
expect(component.props().csrfToken).to.equal("token");
Expand All @@ -151,7 +151,7 @@ describe("ConfigTabContainer", () => {
StorageServices,
CatalogServices,
DiscoveryServices,
IndividualAdmins,
AnalyticsServices,
];
for (const componentClass of hiddenComponentClasses) {
const component = wrapper.find(componentClass);
Expand Down Expand Up @@ -183,11 +183,11 @@ describe("ConfigTabContainer", () => {
expect(links.length).to.equal(2);
const linkTexts = links.map((link) => link.text());
expect(linkTexts).to.contain("Libraries");
expect(linkTexts).to.contain("Analytics");
expect(linkTexts).to.contain("Admins");
});

it("shows components", () => {
const expectedComponentClasses = [Libraries, AnalyticsServices];
const expectedComponentClasses = [Libraries, IndividualAdmins];
for (const componentClass of expectedComponentClasses) {
const component = wrapper.find(componentClass);
expect(component.props().csrfToken).to.equal("token");
Expand All @@ -205,7 +205,7 @@ describe("ConfigTabContainer", () => {
StorageServices,
CatalogServices,
DiscoveryServices,
IndividualAdmins,
AnalyticsServices,
];
for (const componentClass of hiddenComponentClasses) {
const component = wrapper.find(componentClass);
Expand Down
31 changes: 30 additions & 1 deletion src/components/__tests__/EditableConfigList-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ describe("EditableConfigList", () => {

let canCreate: boolean;
let canDelete: boolean;
let canEdit: boolean;

class ThingEditableConfigList extends EditableConfigList<Things, Thing> {
EditForm = ThingEditForm;
Expand All @@ -84,6 +85,10 @@ describe("EditableConfigList", () => {
canDelete() {
return canDelete;
}

canEdit() {
return canEdit;
}
}

class OneThingEditableConfigList extends ThingEditableConfigList {
Expand Down Expand Up @@ -144,6 +149,7 @@ describe("EditableConfigList", () => {
);
canCreate = true;
canDelete = true;
canEdit = true;

wrapper = mount(
<ThingEditableConfigList
Expand Down Expand Up @@ -333,6 +339,7 @@ describe("EditableConfigList", () => {
});

it("displays a view button instead of an edit button if canEdit returns false", () => {
canEdit = false;
const viewableThing = { id: 6, label: "View Only", level: 3 };
wrapper = shallow(
<ThingEditableConfigList
Expand Down Expand Up @@ -419,13 +426,21 @@ describe("EditableConfigList", () => {
expect(form.props().listDataKey).to.equal("things");
});

it("shows correct header on edit form", () => {
it("shows correct header on edit form for an item that can be edited", () => {
wrapper.setProps({ editOrCreate: "edit", identifier: "5" });
const formHeader = wrapper.find("h3");
expect(formHeader.length).to.equal(1);
expect(formHeader.text()).to.equal("Edit test label");
});

it("shows correct header on edit form for an item that can not be edited", () => {
canEdit = false;
wrapper.setProps({ editOrCreate: "edit", identifier: "5" });
const formHeader = wrapper.find("h3");
expect(formHeader.length).to.equal(1);
expect(formHeader.text()).to.equal("test label");
});

it("updates header on edit form", () => {
wrapper.setProps({ editOrCreate: "edit", identifier: "5" });
const formHeader = wrapper.find("h3");
Expand All @@ -438,6 +453,20 @@ describe("EditableConfigList", () => {
expect(formHeader.text()).to.equal("Edit test new thing!");
});

it("does not supply a save function to the edit form if canEdit returns false", () => {
canEdit = false;
wrapper.setProps({ editOrCreate: "edit", identifier: "5" });
const editForm = wrapper.find(ThingEditForm);
expect(editForm.props().save).to.equal(undefined);
});

it("disables the edit form if canEdit returns false", () => {
canEdit = false;
wrapper.setProps({ editOrCreate: "edit", identifier: "5" });
const editForm = wrapper.find(ThingEditForm);
expect(editForm.props().disabled).to.equal(true);
});

it("fetches data on mount and passes save function to form", () => {
expect(fetchData.callCount).to.equal(1);

Expand Down
6 changes: 3 additions & 3 deletions src/components/__tests__/Header-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ describe("Header", () => {
expect(dropdownLinks.length).to.equal(2);

const loggedInAs = dropdownItems.at(0);
expect(loggedInAs.text()).to.equal("Logged in as a librarian");
expect(loggedInAs.text()).to.equal("Logged in as a user");
const changePassword = dropdownLinks.at(0);
expect(changePassword.parent().prop("to")).to.equal(
"/admin/web/account/"
Expand All @@ -305,8 +305,8 @@ describe("Header", () => {
expect(permissions([true, true])).to.equal("Logged in as a system admin");
expect(permissions([true, false])).to.equal("Logged in as a system admin");
expect(permissions([false, true])).to.equal(
"Logged in as a library manager"
"Logged in as an administrator"
);
expect(permissions([false, false])).to.equal("Logged in as a librarian");
expect(permissions([false, false])).to.equal("Logged in as a user");
});
});
8 changes: 7 additions & 1 deletion src/components/__tests__/IndividualAdminEditForm-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,17 @@ describe("IndividualAdminEditForm", () => {
expectPasswordEditable(nyplManagerLibrarianAll, nyplManagerLibrarianAll);
});

it("has a save button", () => {
it("has a save button if a save function is supplied", () => {
const saveButton = wrapper.find(Button);
expect(saveButton.length).to.equal(1);
});

it("does not have a save button if no save function is supplied", () => {
wrapper.setProps({ save: undefined });
const saveButton = wrapper.find(Button);
expect(saveButton.length).to.equal(0);
});

describe("roles", () => {
const expectRole = (
startingRoles,
Expand Down
Loading

0 comments on commit d9d0db3

Please sign in to comment.