Skip to content

Commit

Permalink
[DCJ-286] Add bulk User operations to dropdown on SO Console (#2563)
Browse files Browse the repository at this point in the history
  • Loading branch information
aarohinadkarni authored May 10, 2024
1 parent 8ffc55b commit e4f2ba0
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 88 deletions.
58 changes: 35 additions & 23 deletions src/components/SimpleTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,44 @@ const ColumnRow = ({columnHeaders, baseStyle, columnStyle, sort, onSort}) => {
return (
<div style={rowStyle} key="column-row-container" role="row">
{columnHeaders.map((header, colIndex) => {
const { cellStyle, label } = header;
const { cellStyle, label, data } = header;
//style here pertains to styling for individual cells
//should be used to set dimensions of specific columns
return (
<div style={cellStyle} key={`column-row-${label}`} className="column-header">
{header.sortable && onSort ? (
<div
style={Styles.TABLE.HEADER_SORT}
key="data_id_cell"
className="cell-sort"
onClick={() => {
onSort({
colIndex: colIndex,
dir: sort.colIndex === colIndex ? sort.dir * -1 : 1
});
}}
>
{label}
<div className="sort-container">
<ArrowDropUp className={`sort-icon sort-icon-up ${sort.colIndex === colIndex && sort.dir === -1 ? 'active' : ''}`} />
<ArrowDropDown className={`sort-icon sort-icon-down ${sort.colIndex === colIndex && sort.dir === 1 ? 'active' : ''}`} />
</div>
</div>
) : (
label
)}
{(() => {
if (header.sortable && onSort) {
return (<div
style={Styles.TABLE.HEADER_SORT}
key="data_id_cell"
className="cell-sort"
onClick={() => {
onSort({
colIndex: colIndex,
dir: sort.colIndex === colIndex ? sort.dir * -1 : 1
});
}}
>
{label}
<div className="sort-container">
<ArrowDropUp className={`sort-icon sort-icon-up ${sort.colIndex === colIndex && sort.dir === -1 ? 'active' : ''}`} />
<ArrowDropDown className={`sort-icon sort-icon-down ${sort.colIndex === colIndex && sort.dir === 1 ? 'active' : ''}`} />
</div>
</div>);
} else if (header.data) {
return (<li className="dropdown" style={{ listStyleType: 'none' }}>
<div role="button" data-toggle="dropdown">
<div id="dacUser">
{label}
<span className="caret caret-margin" style={{color: '#337ab7'}}></span>
</div>
</div>
{data}
</li>);
} else {
return (label);
}
})()}
</div>
);
})}
Expand All @@ -83,7 +95,7 @@ const DataRows = ({rowData, baseStyle, columnHeaders, rowWrapper = ({renderedRow
return rowData.map((row, index) => {
const id = rowData[index][0].id;
const mapKey = id || `noId-index-${index}`;
if (rowData[index][0].label === "display-names") {
if (rowData[index][0].striped) {
baseStyle.backgroundColor = index % 2 === 0 ? 'white' : '#e2e8f4';
}
const renderedRow = (
Expand Down
7 changes: 3 additions & 4 deletions src/libs/ajax/DAA.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as fp from 'lodash/fp';
import fileDownload from 'js-file-download';
import { getApiUrl, fetchOk } from '../ajax';
import { getApiUrl } from '../ajax';
import { Config } from '../config';
import axios from 'axios';

Expand Down Expand Up @@ -38,7 +37,7 @@ export const DAA = {

bulkRemoveUsersFromDaa: async (daaId, userList) => {
const url = `${await getApiUrl()}/api/daa/bulk/${daaId}`;
const res = await axios.delete(url, userList, Config.authOpts());
const res = await axios.delete(url, { ...Config.authOpts(), data: userList });
return res.data;
},

Expand All @@ -52,7 +51,7 @@ export const DAA = {
const url = `${await getApiUrl()}/api/daa/bulk/user/${userId}`;
const res = await axios.delete(url, { ...Config.authOpts(), data: daaList });
return res.data;
},
},

getDaaFileById: async (daaId, daaFileName) => {
const authOpts = Object.assign(Config.authOpts(), { responseType: 'blob' });
Expand Down
54 changes: 54 additions & 0 deletions src/pages/signing_official_console/DAACell.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { Checkbox } from '@mui/material';
import { DAA } from '../../libs/ajax/DAA';
import { Notifications } from '../../libs/utils';

export default function DAACell(props) {
const {rowDac, researcher, institutionId, daas, refreshResearchers, setResearchers} = props;
const id = researcher?.userId || researcher?.email;
const libraryCards = researcher?.libraryCards;
const card = libraryCards?.find(card => card.institutionId === institutionId);
const daaIds = researcher && card?.daaIds;
const filteredDaas = daaIds && daas?.filter(daa => daaIds.includes(daa.daaId));
const hasDacId = filteredDaas && filteredDaas.some(daa => daa.dacs.some(dac => dac.dacId === rowDac.dacId));

const createDaaLcLink = async (daaId, researcher, dacName) => {
try {
await DAA.createDaaLcLink(daaId, researcher.userId);
Notifications.showSuccess({text: `Approved access to ${dacName} to user: ${researcher.displayName}`});
refreshResearchers(setResearchers);
} catch(error) {
Notifications.showError({text: `Error approving access to ${dacName} to user: ${researcher.displayName}`});
}
};

const deleteDaaLcLink = async (daaId, researcher, dacName) => {
try {
await DAA.deleteDaaLcLink(daaId, researcher.userId);
Notifications.showSuccess({text: `Removed approval of access to ${dacName} to user: ${researcher.displayName}`});
refreshResearchers(setResearchers);
} catch(error) {
Notifications.showError({text: `Error removing approval of access to ${dacName} to user: ${researcher.displayName}`});
}
};

const handleClick = async (daas, specificDac, researcher) => {
const daaId = daas.find(daa => daa.dacs.some(dac => dac.dacId === specificDac.dacId))?.daaId;
if (!hasDacId) {
createDaaLcLink(daaId, researcher, specificDac.name);
} else {
deleteDaaLcLink(daaId, researcher, specificDac.name);
}
};

return {
isComponent: true,
id,
label: 'lc-button',
data: (
<div>
<Checkbox checked={hasDacId} onClick={() => handleClick(daas, rowDac, researcher)}/>
</div>
),
};
}
87 changes: 87 additions & 0 deletions src/pages/signing_official_console/ManageDaasDropdown.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useState } from 'react';
import { Button } from '@mui/material';
import { DownloadLink } from '../../components/DownloadLink';
import { DAA } from '../../libs/ajax/DAA';
import { Notifications } from '../../libs/utils';

export default function ManageDaasDropdown(props) {
const [applyAll, setApplyAll] = useState(null);
const {actionsTitle, download, moreData, researchers, refreshResearchers, setResearchers} = props;

const handleApplyAllChange = (event) => {
setApplyAll(event.target.checked);
};

const handleRemoveAllChange = (event) => {
setApplyAll(!event.target.checked);
};

const addUsersToDaa = async (userList) => {
try {
await DAA.bulkAddUsersToDaa(moreData.id, userList);
Notifications.showSuccess({text: `Approved all users access to request from: ${moreData.name}`});
refreshResearchers(setResearchers);
} catch(error) {
Notifications.showError({text: `Error approving all users access to request from: ${moreData.name}`});
}
};

const removeUsersFromDaa = async (userList) => {
try {
await DAA.bulkRemoveUsersFromDaa(moreData.id, userList);
Notifications.showSuccess({text: `Removed all users' approval to request from: ${moreData.name}`});
refreshResearchers(setResearchers);
} catch(error) {
Notifications.showError({text: `Error removing all users' approval to request from: ${moreData.name}`});
}
};

const handleApplyAll = async () => {
const userList = { 'users': researchers.map(researcher => researcher.userId) };
if (applyAll) {
addUsersToDaa(userList);
} else {
removeUsersFromDaa(userList);
}
};

return (
<ul className="dropdown-menu" role="menu" style={{ padding: '20px', textTransform:'none'}}>
<div id="link_signOut" style={{display:'flex', padding: '5px', textAlign: 'left'}}>
<strong>{actionsTitle}</strong>
</div>
<form>
<li style={{paddingTop: '5px', paddingBottom: '5px'}}>
<DownloadLink label={`Download agreement`} onDownload={() => {DAA.getDaaFileById(download.id, download.fileName);}}/>
</li>
<li style={{paddingTop: '5px', paddingBottom: '5px'}}>
<label style={{fontWeight: 'normal', whiteSpace: 'nowrap'}}>
<input type="radio" name="daa" value="apply" checked={applyAll === true} onChange={handleApplyAllChange}/>
&nbsp;&nbsp;Apply agreement to all users
</label>
</li>
<li style={{paddingTop: '5px', paddingBottom: '5px'}}>
<label style={{fontWeight: 'normal', whiteSpace: 'nowrap' }}>
<input type="radio" name="daa" value="remove" checked={applyAll === false} onChange={handleRemoveAllChange}/>
&nbsp;&nbsp;Remove agreement from all users
</label>
</li>
</form>
<li style={{paddingTop: '5px', paddingBottom: '5px'}}>
<Button style={{
fontSize: '15px',
fontWeight: 'normal',
fontFamily: 'Montserrat',
border: '1px solid #0948B7',
borderRadius: '5px',
height: '40px',
marginRight: '1em',
cursor: 'pointer',
color: '#0948B7',
padding: '10px 20px',
textTransform: 'none'
}} onClick={() => handleApplyAll()}>Apply</Button>
</li>
</ul>
);
}
84 changes: 23 additions & 61 deletions src/pages/signing_official_console/ManageResearcherDAAsTable.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { Checkbox } from '@mui/material';
import { Styles } from '../../libs/theme';
import { isEmpty } from 'lodash/fp';
import SimpleTable from '../../components/SimpleTable';
Expand All @@ -12,9 +11,11 @@ import {
getSearchFilterFunctions,
searchOnFilteredList
} from '../../libs/utils';
import { DAA } from '../../libs/ajax/DAA';
import {User} from '../../libs/ajax/User';
import { USER_ROLES } from '../../libs/utils';
import ManageUsersDropdown from './ManageUsersDropdown';
import ManageDaasDropdown from './ManageDaasDropdown';
import DAACell from './DAACell';

//Styles specific to this table
const styles = {
Expand Down Expand Up @@ -50,28 +51,6 @@ let columnHeaderFormat = {

const researcherFilterFunction = getSearchFilterFunctions().signingOfficialResearchers;

const handleClick = async (researcher, specificDac, filteredDaas, checked, refreshResearchers, setResearchers) => {
if (!checked) {
try {
const daaId = filteredDaas.find(daa => daa.dacs.some(dac => dac.dacId === specificDac.dacId))?.daaId;
await DAA.createDaaLcLink(daaId, researcher.userId);
Notifications.showSuccess({text: `Approved access to ${specificDac.name} to user: ${researcher.displayName}`});
refreshResearchers(setResearchers);
} catch(error) {
Notifications.showError({text: `Error approving access to ${specificDac.name} to user: ${researcher.displayName}`});
}
} else {
try {
const daaId = filteredDaas.find(daa => daa.dacs.some(dac => dac.dacId === specificDac.dacId))?.daaId;
await DAA.deleteDaaLcLink(daaId, researcher.userId);
Notifications.showSuccess({text: `Removed approval of access to ${specificDac.name} to user: ${researcher.displayName}`});
refreshResearchers(setResearchers);
} catch(error) {
Notifications.showError({text: `Error removing approval of access to ${specificDac.name} to user: ${researcher.displayName}`});
}
}
};

const refreshResearchers = async (setResearchers) => {
const researcherList = await User.list(USER_ROLES.signingOfficial);
// the construction of this list is currently a work-around because our endpoint in the backend
Expand All @@ -84,46 +63,26 @@ const refreshResearchers = async (setResearchers) => {
setResearchers(researcherObjectList);
};

const DAACell = (
rowDac,
researcher,
institutionId,
daas,
refreshResearchers,
setResearchers
) => {
const id = researcher && (researcher.userId || researcher.email);
const libraryCards = researcher && researcher.libraryCards;
const card = libraryCards && libraryCards.find(card => card.institutionId === institutionId);
const daaIds = researcher && card && card.daaIds;
const filteredDaas = daaIds && daas.filter(daa => daaIds.includes(daa.daaId));
const hasDacId = filteredDaas && filteredDaas.some(daa => daa.dacs.some(dac => dac.dacId === rowDac.dacId));

return {
isComponent: true,
id,
label: 'lc-button',
data: (
<div>
<Checkbox checked={hasDacId} onClick={() => handleClick(researcher,rowDac, daas, hasDacId, refreshResearchers, setResearchers)}/>
</div>
),
};
};



const displayNameCell = (displayName, email, id) => {
const displayNameCell = (displayName, email, id, daas, setResearchers) => {
return {
data: (
<>
<div>{displayName || 'Invite sent, pending registration'}</div>
<div><a href={`mailto:${email}`}>{email || '- -'}</a></div>
<li className="dropdown" style={{ listStyleType: 'none' }}>
<div role="button" data-toggle="dropdown">
<div id="dacUser" style={{ color: 'black' }}>
{displayName || 'Invite sent, pending registration'}
<span className="caret caret-margin" style={{color: '#337ab7', float: 'right', marginTop: '15px'}}></span>
<small><a href={`mailto:${email}`}>{email || '- -'}</a></small>
</div>
</div>
<ManageUsersDropdown daas={daas} refreshResearchers={refreshResearchers} setResearchers={setResearchers} moreData={{id: id, name: displayName}}/>
</li>
</>
),
id,
style: {},
label: 'display-names'
label: 'display-names',
striped: true
};
};

Expand All @@ -142,7 +101,10 @@ export default function ManageResearcherDAAsTable(props) {
columnHeaderFormat = {
...columnHeaderFormat,
...dacs.reduce((acc, dac) => {
acc[dac.name] = { label: dac.name, cellStyle: { width: `${dacColumnWidth}%` }};
const daa = daas.find(daa => daa.dacs.some(d => d.dacId === dac.dacId));
const id = daa.daaId;
const fileName = daa.file.fileName;
acc[dac.name] = { label: dac.name, cellStyle: { width: `${dacColumnWidth}%` }, data: <ManageDaasDropdown actionsTitle={`${dac.name} Actions`} download={{id: id, fileName: fileName}} moreData={{id: id, name: dac.name}} researchers={props.researchers} refreshResearchers={refreshResearchers} setResearchers={setResearchers}/>};
return acc;
}, {}),
};
Expand Down Expand Up @@ -214,13 +176,13 @@ export default function ManageResearcherDAAsTable(props) {

const processResearcherRowData = (researchers = []) => {
return researchers.map(researcher => {
const {displayName, libraryCards} = researcher;
const { displayName, libraryCards } = researcher;
const libraryCard = !isEmpty(libraryCards) ? libraryCards[0] : {};
const email = researcher.email || libraryCard.userEmail;
const id = researcher.userId || email;
return [
displayNameCell(displayName, email, id),
...dacs.map(dac => DAACell(dac, researcher, signingOfficial.institutionId, daas, refreshResearchers, setResearchers))
displayNameCell(displayName, email, id, daas, setResearchers),
...dacs.map(dac => DAACell({ rowDac: dac, researcher: researcher, institutionId: signingOfficial.institutionId, daas: daas, refreshResearchers: refreshResearchers,setResearchers: setResearchers })),
];
});
};
Expand Down
Loading

0 comments on commit e4f2ba0

Please sign in to comment.