From 569a211c056a761a5179bfa57dca249dc2a705ac Mon Sep 17 00:00:00 2001 From: "clu@asymmetrik.com" Date: Thu, 21 Jul 2022 15:16:21 -0400 Subject: [PATCH 01/10] [DUOS-1903] Updated dar collection table for chair console view --- .../dar_collection_table/ChairActions.js | 46 ++++++------------- .../DarCollectionTable.js | 46 +++++++------------ .../DarCollectionTableCellData.js | 27 ++++++----- src/libs/ajax.js | 5 ++ src/pages/NewChairConsole.js | 2 +- 5 files changed, 53 insertions(+), 73 deletions(-) diff --git a/src/components/dar_collection_table/ChairActions.js b/src/components/dar_collection_table/ChairActions.js index be58b20b3..36560d3d9 100644 --- a/src/components/dar_collection_table/ChairActions.js +++ b/src/components/dar_collection_table/ChairActions.js @@ -52,31 +52,19 @@ const initUserData = ({dars, elections, relevantDatasets}) => { } }; -const calcComponentState = ({userId, relevantElections, relevantDarsNoElections}) => { +const calcComponentState = ({relevantElections}) => { try{ - let nonOpenRelevantElectionPresent = false; - let closedRelevantElectionPresent = false; - let openRelevantElectionPresent = false; - let userHasVote = false; let label = 'Vote'; //iterate through elections, push open and non-open elections into their respective arrays //also for each election, see if user has a vote and whether or not they've already voted forEach(election => { - const {votes, status} = election; - const isElectionOpen = toLower(status) === 'open'; - isElectionOpen ? openRelevantElectionPresent = true : nonOpenRelevantElectionPresent = true; - if(toLower(status) === 'closed') {closedRelevantElectionPresent = true;} + const {votes} = election; forEach(vote => { - if(vote.dacUserId === userId && isElectionOpen) {userHasVote = true;} if(!isNil(vote.vote)) { label = 'Update'; } })(votes); })(relevantElections); - //To determine open, see if empty dars exist or if any election is non-open - const isOpenEnabled = (!isEmpty(relevantDarsNoElections) || nonOpenRelevantElectionPresent); - //To determine cancel, see if there are no closed and any open relevant elections present - const isCancelEnabled = (!closedRelevantElectionPresent && openRelevantElectionPresent); - return {isCancelEnabled, userHasVote, label, isOpenEnabled}; + return {label}; } catch(error) { throw new Error ('Error initializing chair actions'); } @@ -101,9 +89,9 @@ export default function ChairActions(props) { const { showConfirmationModal, collection, relevantDatasets, goToVote } = props; const { dars } = collection; const collectionId = collection.darCollectionId; - const [openEnabled, setOpenEnabled] = useState(false); - const [cancelEnabled, setCancelEnabled] = useState(false); - const [voteEnabled, setVoteEnabled] = useState(false); + const [openEnabled, setOpenEnabled] = useState(props.openEnabled); + const [cancelEnabled, setCancelEnabled] = useState(props.cancelEnabled); + const [voteEnabled, setVoteEnabled] = useState(props.voteEnabled); const [voteLabel, setVoteLabel] = useState('Vote'); //if there's something wrong with the collection it's best to fail gracefully @@ -111,7 +99,7 @@ export default function ChairActions(props) { const updateStateOnFail = () => { setOpenEnabled(false); setCancelEnabled(false); - setVoteLabel(false); + setVoteEnabled(false); }; useEffect(() => { @@ -122,20 +110,14 @@ export default function ChairActions(props) { elections, relevantDatasets }); - const { isCancelEnabled, userHasVote, label, isOpenEnabled } = - calcComponentState({ - userId, - relevantElections, - relevantDarsNoElections, - setVoteLabel - }); - setOpenEnabled(isOpenEnabled); - //enable cancel button if no elections are closed and at least one is open - setCancelEnabled(isCancelEnabled); + const { label } = calcComponentState({ + userId, + relevantElections, + relevantDarsNoElections, + setVoteLabel + }); //set label based on function return, visibility determined by setVoteEnabled setVoteLabel(label); - //enable vote button, visibility determined by userHasVote - setVoteEnabled(userHasVote); }; try { @@ -177,7 +159,7 @@ export default function ChairActions(props) { fontWeight: 600, color: 'white', marginRight: '8%' - }, + } }; const cancelButtonAttributes = { diff --git a/src/components/dar_collection_table/DarCollectionTable.js b/src/components/dar_collection_table/DarCollectionTable.js index 47071b231..9113c4574 100644 --- a/src/components/dar_collection_table/DarCollectionTable.js +++ b/src/components/dar_collection_table/DarCollectionTable.js @@ -1,15 +1,14 @@ import { useState, useEffect, Fragment, useCallback } from 'react'; -import { div, h } from 'react-hyperscript-helpers'; -import {isNil, isEmpty, find, get} from 'lodash/fp'; +import { h } from 'react-hyperscript-helpers'; +import {isNil, isEmpty, find} from 'lodash/fp'; import { Styles } from '../../libs/theme'; import { Storage } from '../../libs/storage'; import PaginationBar from '../PaginationBar'; -import { recalculateVisibleTable, goToPage as updatePage, darCollectionUtils } from '../../libs/utils'; +import { recalculateVisibleTable, goToPage as updatePage } from '../../libs/utils'; import SimpleTable from '../SimpleTable'; import cellData from './DarCollectionTableCellData'; import CollectionConfirmationModal from './CollectionConfirmationModal'; -const { determineCollectionStatus } = darCollectionUtils; const storageDarCollectionSort = 'storageDarCollectionSort'; export const getProjectTitle = ((collection) => { if(collection.isDraft){return collection.projectTitle;} @@ -105,10 +104,7 @@ const columnHeaderConfig = { name: { label: 'Title', cellStyle: { width: styles.cellWidth.projectTitle }, - cellDataFn: (props) => { - props.projectTitle = getProjectTitle(props.collection); - return cellData.projectTitleCellData(props); - }, + cellDataFn: cellData.projectTitleCellData, sortable: true }, submissionDate: { @@ -120,19 +116,13 @@ const columnHeaderConfig = { researcher: { label: 'Researcher', cellStyle: { width: styles.cellWidth.researcher }, - cellDataFn: (props) => { - props.researcherName = get('displayName')(props.createUser); - return cellData.researcherCellData(props); - }, + cellDataFn: cellData.researcherCellData, sortable: true }, institution: { label: 'Institution', cellStyle: { width: styles.cellWidth.institution }, - cellDataFn: (props) => { - props.institution = isNil(props.createUser) || isNil(props.createUser.institution) ? '- -' : props.createUser.institution.name; - return cellData.institutionCellData(props); - }, + cellDataFn: cellData.institutionCellData, sortable: true }, datasetCount: { @@ -150,11 +140,7 @@ const columnHeaderConfig = { actions: { label: 'Action', cellStyle: { width: styles.cellWidth.actions }, - cellDataFn: (props) => { - return props.actionsDisabled - ? div() - : cellData.consoleActionsCellData(props); - } + cellDataFn: cellData.consoleActionsCellData } }; @@ -167,15 +153,18 @@ const columnHeaderData = (columns = defaultColumns) => { const processCollectionRowData = ({ collections, showConfirmationModal, columns = defaultColumns, consoleType = '', goToVote, reviewCollection, resumeCollection, relevantDatasets}) => { if(!isNil(collections)) { return collections.map((collection) => { - const { darCollectionId, darCode, createDate, datasets, createUser } = collection; - const status = collection.isDraft ? 'Draft' : determineCollectionStatus(collection, relevantDatasets); + const { + darCollectionId, darCode, datasetIds, + submissionDate, status, actions, + researcherName, name, institutionName + } = collection; return columns.map((col) => { return columnHeaderConfig[col].cellDataFn({ - collection, darCollectionId, datasets, darCode, status, - createDate, createUser, + collection, darCollectionId, datasetIds, darCode, status, name, + submissionDate, researcherName, institutionName, showConfirmationModal, consoleType, goToVote, reviewCollection, relevantDatasets, - resumeCollection + resumeCollection, actions }); }); }); @@ -208,7 +197,7 @@ export const DarCollectionTable = function DarCollectionTable(props) { const [consoleAction, setConsoleAction] = useState(); const { collections, columns, isLoading, cancelCollection, reviseCollection, - openCollection, actionsDisabled, goToVote, consoleType, relevantDatasets, deleteDraft, + openCollection, goToVote, consoleType, relevantDatasets, deleteDraft, } = props; /* @@ -231,7 +220,6 @@ export const DarCollectionTable = function DarCollectionTable(props) { collections, columns, showConfirmationModal, - actionsDisabled, consoleType, openCollection, goToVote, @@ -243,7 +231,7 @@ export const DarCollectionTable = function DarCollectionTable(props) { setVisibleList: setVisibleCollections, sort }); - }, [tableSize, currentPage, pageCount, collections, sort, columns, actionsDisabled, consoleType, openCollection, goToVote, relevantDatasets/*, resumeCollection, reviewCollection*/]); + }, [tableSize, currentPage, pageCount, collections, sort, columns, consoleType, openCollection, goToVote, relevantDatasets/*, resumeCollection, reviewCollection*/]); const showConfirmationModal = (collection, action = '') => { setConsoleAction(action); diff --git a/src/components/dar_collection_table/DarCollectionTableCellData.js b/src/components/dar_collection_table/DarCollectionTableCellData.js index a4fb40037..45508e65c 100644 --- a/src/components/dar_collection_table/DarCollectionTableCellData.js +++ b/src/components/dar_collection_table/DarCollectionTableCellData.js @@ -10,9 +10,9 @@ import DarCollectionAdminReviewLink from './DarCollectionAdminReviewLink'; import {Link} from 'react-router-dom'; import { consoleTypes } from '../dar_table/DarTableActions'; -export function projectTitleCellData({projectTitle = '- -', darCollectionId, label= 'project-title'}) { +export function projectTitleCellData({name = '- -', darCollectionId, label= 'project-title'}) { return { - data: isEmpty(projectTitle) ? '- -' : projectTitle, + data: isEmpty(name) ? '- -' : name, id: darCollectionId, style : { color: '#354052', @@ -68,9 +68,9 @@ const dacLinkToCollection = (darCode, status = '', darCollectionId) => { return h(Link, { to: path }, [darCode]); }; -export function submissionDateCellData({createDate, darCollectionId, label = 'submission-date'}) { - const dateString = isNil(createDate) ? '- -' : - toLower(createDate) === 'unsubmitted' ? '- -' : formatDate(createDate); +export function submissionDateCellData({submissionDate, darCollectionId, label = 'submission-date'}) { + const dateString = isNil(submissionDate) ? '- -' : + toLower(submissionDate) === 'unsubmitted' ? '- -' : formatDate(submissionDate); return { data: dateString, id: darCollectionId, @@ -94,9 +94,9 @@ export function researcherCellData({researcherName = '- -', darCollectionId, lab }; } -export function institutionCellData({institution = '- -', darCollectionId, label = 'institution'}) { +export function institutionCellData({institutionName = '- -', darCollectionId, label = 'institution'}) { return { - data: institution, + data: institutionName, id: darCollectionId, style: { color: '#354052', @@ -107,9 +107,9 @@ export function institutionCellData({institution = '- -', darCollectionId, label }; } -export function datasetCountCellData({datasets = [], darCollectionId, label = 'datasets'}) { +export function datasetCountCellData({datasetIds = [], darCollectionId, label = 'datasets'}) { return { - data: datasets.length > 0 ? datasets.length : '- -', + data: datasetIds.length > 0 ? datasetIds.length : '- -', id: darCollectionId, style: { color: '#333F52', @@ -133,7 +133,7 @@ export function statusCellData({status = '- -', darCollectionId, label = 'status }; } -export function consoleActionsCellData({collection, reviewCollection, goToVote, showConfirmationModal, consoleType, relevantDatasets, resumeCollection}) { +export function consoleActionsCellData({collection, reviewCollection, goToVote, showConfirmationModal, consoleType, relevantDatasets, resumeCollection, actions}) { let actionComponent; switch (consoleType) { @@ -141,7 +141,12 @@ export function consoleActionsCellData({collection, reviewCollection, goToVote, actionComponent = h(AdminActions, {collection, showConfirmationModal}); break; case consoleTypes.CHAIR: - actionComponent = h(ChairActions, {collection, showConfirmationModal, goToVote, relevantDatasets}); + actionComponent = h(ChairActions, { + collection, showConfirmationModal, goToVote, relevantDatasets, + openEnabled: actions.includes('Open'), + cancelEnabled: actions.includes('Cancel'), + voteEnabled: actions.includes('Vote') + }); break; case consoleTypes.MEMBER: actionComponent = h(MemberActions, {collection, showConfirmationModal, goToVote}); diff --git a/src/libs/ajax.js b/src/libs/ajax.js index b01c0d14b..ca91de544 100644 --- a/src/libs/ajax.js +++ b/src/libs/ajax.js @@ -201,6 +201,11 @@ export const Collections = { const res = await axios.get(url, Config.authOpts()); return res.data; }, + getCollectionSummariesByRoleName: async(roleName) => { + const url = `${await Config.getApiUrl()}/api/collections/role/${roleName}/summary`; + const res = await axios.get(url, Config.authOpts()); + return res.data; + }, openElectionsById: async(id) => { const url = `${await Config.getApiUrl()}/api/collections/${id}/election`; const res = await axios.post(url, {}, Config.authOpts()); diff --git a/src/pages/NewChairConsole.js b/src/pages/NewChairConsole.js index 53cedc444..17c723632 100644 --- a/src/pages/NewChairConsole.js +++ b/src/pages/NewChairConsole.js @@ -29,7 +29,7 @@ export default function NewChairConsole(props) { const init = async() => { try { const [collections, datasets] = await Promise.all([ - Collections.getCollectionsByRoleName('chairperson'), + Collections.getCollectionSummariesByRoleName('chairperson'), User.getUserRelevantDatasets() ]); setCollections(collections); From 9b2756e7a2faf3d0106903410b3c4e5d03fbf2dc Mon Sep 17 00:00:00 2001 From: "clu@asymmetrik.com" Date: Fri, 22 Jul 2022 13:16:53 -0400 Subject: [PATCH 02/10] [DUOS-1903] Updated unit tests --- .../DarCollectionTable/chair_actions.spec.js | 74 ++----------------- 1 file changed, 7 insertions(+), 67 deletions(-) diff --git a/cypress/component/DarCollectionTable/chair_actions.spec.js b/cypress/component/DarCollectionTable/chair_actions.spec.js index 1ac308df5..6daac7122 100644 --- a/cypress/component/DarCollectionTable/chair_actions.spec.js +++ b/cypress/component/DarCollectionTable/chair_actions.spec.js @@ -215,59 +215,31 @@ describe('Chair Actions - Open Button', () => { openButton.should('exist'); }); - it('should not render if the is no valid election for opening/re-opening', () => { - propCopy.collection.dars = votableDars; - propCopy.relevantDatasets = []; + it('should not render if there is no valid election for opening/re-opening', () => { + propCopy.openEnabled = false; mount(); const openButton = cy.get(`#chair-open-${collectionId}`); openButton.should('not.exist'); }); it('should render if there are valid DARs with no election present', () => { - propCopy.collection.dars = missingElectionSet; - propCopy.relevantDatasets = [{ dataSetId: 1 }]; + propCopy.openEnabled = true; mount(); const openButton = cy.get(`#chair-open-${collectionId}`); openButton.should('exist'); }); - - it('should not render if DARs with missing elections are not under purview', () => { - propCopy.collection.dars = missingElectionSet; - propCopy.relevantDatasets = []; - mount(); - const openButton = cy.get(`#chair-open-${collectionId}`); - openButton.should('not.exist'); - }); }); describe('Chair Actions - Close Button', () => { it('should render if there is a valid election for canceling (all open elections)', () => { - propCopy.collection.dars = cancelableDars; - propCopy.relevantDatasets = [{ dataSetId: 1 }, { dataSetId: 2 }]; + propCopy.cancelEnabled = true; mount(); const closeButton = cy.get(`#chair-cancel-${collectionId}`); closeButton.should('exist'); }); it('should not render if there is no valid election for canceling (no open elections)', () => { - propCopy.collection.dars = nonOpenDars; - propCopy.relevantDatasets = []; - mount(); - const closeButton = cy.get(`#chair-cancel-${collectionId}`); - closeButton.should('not.exist'); - }); - - it('should not render if there are any closed elections (mixed open and closed)', () => { - propCopy.collection.dars = nonCancelableDars; - propCopy.relevantDatasets = []; - mount(); - const closeButton = cy.get(`#chair-cancel-${collectionId}`); - closeButton.should('not.exist'); - }); - - it('should not consider RP elections when determining if close button is rendered', () => { - propCopy.collection.dars = darsWithRpElection; - propCopy.relevantDatasets = []; + propCopy.cancelEnabled = false; mount(); const closeButton = cy.get(`#chair-cancel-${collectionId}`); closeButton.should('not.exist'); @@ -276,49 +248,17 @@ describe('Chair Actions - Close Button', () => { describe('Chair Actions - Vote Button', () => { it('should not render if relevant elections are not votable', () => { - propCopy.collection.dars = nonVoteableDars; - propCopy.relevantDatasets = [{ dataSetId: 1 }, { dataSetId: 2 }]; - mount(); - const voteButton = cy.get(`#chair-vote-${collectionId}`); - voteButton.should('not.exist'); - }); - it('should not render if some DARs are missing elections', () => { - propCopy.collection.dars = missingElectionSet; - propCopy.relevantDatasets = [{ dataSetId: 1 }, { dataSetId: 2 }]; + propCopy.voteEnabled = false; mount(); const voteButton = cy.get(`#chair-vote-${collectionId}`); voteButton.should('not.exist'); }); it('should render if all relevant elections are votable', () => { - propCopy.collection.dars = votableDars; - propCopy.relevantDatasets = [{ dataSetId: 1 }, { dataSetId: 2 }]; + propCopy.voteEnabled = true; mount(); const voteButton = cy.get(`#chair-vote-${collectionId}`); voteButton.should('exist'); }); - it('should not render if only some of the relevant elections are votable', () => { - propCopy.collection.dars = missingElectionSet; - propCopy.relevantDatasets = [{ dataSetId: 1 }, { dataSetId: 2 }]; - mount(); - const voteButton = cy.get(`#chair-vote-${collectionId}`); - voteButton.should('not.exist'); - }); - it('should render the "Update Vote" button if the user can vote and had already submitted a vote previously', () => { - const targetDars = cloneDeep(votableDars); - propCopy.relevantDatasets = [{ dataSetId: 2}]; - targetDars[2].elections[3].votes[2].vote = true; - propCopy.collection.dars = targetDars; - mount(); - const voteButton = cy.get(`#chair-vote-${collectionId}`); - voteButton.should('exist'); - }); - it('should not consider RP elections when determining if vote buttons renders', () => { - propCopy.collection.dars = darsWithRpElection; - propCopy.relevantDatasets = [{ dataSetId: 1 }, { dataSetId: 2 }]; - mount(); - const voteButton = cy.get(`#chair-vote-${collectionId}`); - voteButton.should('not.exist'); - }); }); From 6b2ee9c1575b33a8ebc154fd8c5c9e3b60448893 Mon Sep 17 00:00:00 2001 From: "clu@asymmetrik.com" Date: Mon, 25 Jul 2022 10:56:18 -0400 Subject: [PATCH 03/10] [DUOS-1903] added votel abel back --- src/components/dar_collection_table/ChairActions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/dar_collection_table/ChairActions.js b/src/components/dar_collection_table/ChairActions.js index 36560d3d9..dec4ac6c0 100644 --- a/src/components/dar_collection_table/ChairActions.js +++ b/src/components/dar_collection_table/ChairActions.js @@ -100,6 +100,7 @@ export default function ChairActions(props) { setOpenEnabled(false); setCancelEnabled(false); setVoteEnabled(false); + setVoteLabel(false); }; useEffect(() => { From cb4ce35da78532cc29f249a39c4d8ef4e45f68ed Mon Sep 17 00:00:00 2001 From: Justin Variath Thomas Date: Mon, 1 Aug 2022 09:45:59 -0400 Subject: [PATCH 04/10] DUOS-1992[risk=no] Update search bar function to handle DARCollectionSummary on Console pages (#1715) * DUOS-1992 updated search bar filter function to handle DARCollectionSummary * DUOS-1992 updated util tests for search function --- cypress/component/utils/utils.spec.js | 171 +++++++++----------------- src/libs/utils.js | 28 +---- 2 files changed, 63 insertions(+), 136 deletions(-) diff --git a/cypress/component/utils/utils.spec.js b/cypress/component/utils/utils.spec.js index 1d4a82b96..ebf976b17 100644 --- a/cypress/component/utils/utils.spec.js +++ b/cypress/component/utils/utils.spec.js @@ -1,23 +1,9 @@ /* eslint-disable no-undef */ import {getSearchFilterFunctions, formatDate, darCollectionUtils, processElectionStatus} from '../../../src/libs/utils'; -import {cloneDeep, toLower} from 'lodash/fp'; +import {toLower} from 'lodash/fp'; import { forEach } from 'lodash'; const { determineCollectionStatus } = darCollectionUtils; -const collectionsSkeleton = [ - { - dars: { - 1: { - data: { - institution: undefined, - projectTitle: undefined - } - } - }, - darCode: undefined - } -]; - const collectionWithMixedElectionStatuses = { dars: { 0: { @@ -65,56 +51,6 @@ const collectionWithSameElectionStatus = { } }; -const collectionsWithElection = [ - { - dars: { - 1: { - data: { - institution: undefined, - projectTitle: undefined, - }, - elections: { - 1: { - status: 'Open', - electionType: 'DataAccess' - }, - 2: { - status: 'Open', - electionType: 'RP' - }, - }, - } - }, - datasets: [], - darCode: 'DAR-1' - } -]; - -const collectionsWithProjectTitleAndInstitution = [ - { - dars: { - 1: { - data: { - institution: 'broad institute', - projectTitle: 'Project: test', - }, - elections: { - 1: { - status: 'Open', - electionType: 'DataAccess', - }, - 2: { - status: 'Open', - electionType: 'RP', - }, - }, - }, - }, - datasets: [], - darCode: 'DAR-1', - }, -]; - const sampleLCList = [ { userName: 'Test Person', @@ -157,86 +93,95 @@ const sampleResearcherList = [ } ]; -let collectionSearchFn, cardSearchFn, researcherSearchFn; +const darCollectionSummaryOne = { + darCode: 'DAR-1', + datasetCount: 4005, + name: 'summaryOne', + institutionName: 'CompanyOne', + researcherName: 'researcherOne', + status: 'In Progress', + submissionDate: 1649163460401, +}; + +const darCollectionSummaryTwo = { + darCode: 'DAR-2', + datasetCount: 3005, + name: 'summaryTwo', + institutionName: 'CompanyTwo', + researcherName: 'researcherTwo', + status: 'Complete', + submissionDate: 1629163460401, +}; + +let collectionSearchFn, cardSearchFn, researcherSearchFn, summaryList; beforeEach(() => { const searchFunctionsMap = getSearchFilterFunctions(); collectionSearchFn = searchFunctionsMap.darCollections; cardSearchFn = searchFunctionsMap.libraryCard; researcherSearchFn = searchFunctionsMap.signingOfficialResearchers; + summaryList = [darCollectionSummaryOne, darCollectionSummaryTwo]; }); describe('Dar Collection Search Filter', () => { - it('filters successfully with missing institution, project title, elections, datasets, dar, and institution', () => { - const filteredList = collectionSearchFn('DAR-2', collectionsSkeleton); - expect(filteredList).to.be.empty; - }); - - it('filters on status with elections present', () => { - const filteredList = collectionSearchFn('open', collectionsWithElection); - expect(filteredList).to.not.be.empty; - const emptyFilteredList = collectionSearchFn('closed', collectionsWithElection); - expect(emptyFilteredList).to.be.empty; + it('filters on status', () => { + const filteredList = collectionSearchFn('In Progress', summaryList); + expect(filteredList.length).to.equal(1); + expect(filteredList[0].darCode).to.equal(darCollectionSummaryOne.darCode); + const closedFilteredList = collectionSearchFn('Complete', summaryList); + expect(closedFilteredList.length).to.equal(1); + expect(closedFilteredList[0].darCode).to.equal(darCollectionSummaryTwo.darCode); }); - it('filters on status with datasets present', () => { - const collectionsWithDatasets = cloneDeep(collectionsSkeleton); - collectionsWithDatasets[0].datasets = [{1: {}}]; - const filteredList = collectionSearchFn('1', collectionsWithDatasets); - expect(filteredList).to.not.be.empty; - const emptyList = collectionSearchFn('4', collectionsWithDatasets); - expect(emptyList).to.be.empty; + it('filters on dataset count', () => { + const filteredList = collectionSearchFn('4005', summaryList); + expect(filteredList.length).to.equal(1); + expect(filteredList[0].darCode).to.equal(darCollectionSummaryOne.darCode); }); - it('filters on projectTitle', () => { - const filteredList = collectionSearchFn('project', collectionsWithProjectTitleAndInstitution); - expect(filteredList).to.not.be.empty; - const emptyList = collectionSearchFn('invalid', collectionsWithProjectTitleAndInstitution); + it('filters on collection name', () => { + const filteredList = collectionSearchFn(darCollectionSummaryOne.name, summaryList); + expect(filteredList.length).to.equal(1); + const emptyList = collectionSearchFn('invalid', summaryList); expect(emptyList).to.be.empty; }); it('filters on institution', () => { - const institutionTerm = Object.values(collectionsWithProjectTitleAndInstitution[0].dars)[0].data.institution; - const filteredList = collectionSearchFn(institutionTerm, collectionsWithProjectTitleAndInstitution); - expect(filteredList).to.not.be.empty; - const emptyList = collectionSearchFn('invalid', collectionsWithProjectTitleAndInstitution); + const institutionTerm = darCollectionSummaryOne.institutionName; + const filteredList = collectionSearchFn(institutionTerm, summaryList); + expect(filteredList.length).to.equal(1); + const emptyList = collectionSearchFn('invalid', summaryList); expect(emptyList).to.be.empty; }); it('filters on dar code', () => { - const darTerm = 'dar-1'; - const collectionsWithDarCode = cloneDeep(collectionsSkeleton); - collectionsWithDarCode[0].darCode = 'DAR-1'; - const filteredList = collectionSearchFn(darTerm, collectionsWithDarCode); - expect(filteredList).to.not.be.empty; - const emptyList = collectionSearchFn('invalid', collectionsWithDarCode); + const darTerm = darCollectionSummaryOne.darCode; + const filteredList = collectionSearchFn(darTerm, summaryList); + expect(filteredList.length).to.equal(1); + const emptyList = collectionSearchFn('invalid', summaryList); expect(emptyList).to.be.empty; }); - it('filters on create date', () => { - const createDate = '2020-04-05'; - const collectionsWithCreateDate = cloneDeep(collectionsSkeleton); - collectionsWithCreateDate[0].createDate = createDate; - const filteredList = collectionSearchFn('2020-04', collectionsWithCreateDate); - expect(filteredList).to.not.be.empty; - const emptyList = collectionSearchFn('invalid', collectionsWithCreateDate); + it('filters on submission date', () => { + const formattedSubmissionDate = formatDate(darCollectionSummaryOne.submissionDate); + const filteredList = collectionSearchFn(formattedSubmissionDate, summaryList); + expect(filteredList.length).to.equal(1); + expect(formatDate(filteredList[0].submissionDate)).to.equal(formattedSubmissionDate); + const emptyList = collectionSearchFn('invalid', summaryList); expect(emptyList).to.be.empty; }); it('filters on researcher name', () => { - const createUser = { - displayName: 'Name' - }; - const collectionsWithResearcherName = cloneDeep(collectionsSkeleton); - collectionsWithResearcherName[0].createUser = createUser; - const filteredList = collectionSearchFn('name', collectionsWithResearcherName); - expect(filteredList).to.not.be.empty; - const emptyList = collectionSearchFn('invalid', collectionsWithResearcherName); + const researcherTerm = darCollectionSummaryOne.researcherName; + const filteredList = collectionSearchFn(researcherTerm, summaryList); + expect(filteredList.length).to.equal(1); + expect(filteredList[0].researcherName).to.equal(researcherTerm); + const emptyList = collectionSearchFn('invalid', summaryList); expect(emptyList).to.be.empty; }); }); -describe('LC Serch Filter', () => { +describe('LC Search Filter', () => { it('filters cards on create date', () => { let filteredList; const originalCard = sampleLCList[0]; diff --git a/src/libs/utils.js b/src/libs/utils.js index 8454b8e43..62ff937a5 100644 --- a/src/libs/utils.js +++ b/src/libs/utils.js @@ -542,30 +542,12 @@ export const getSearchFilterFunctions = () => { darCollections: (term, targetList) => isEmpty(term) ? targetList : filter(collection => { - if(isEmpty(term)) {return true;} - let projectTitle, institution, createDate; - if(collection.isDraft) { - projectTitle = collection.projectTitle; - createDate = collection.createDate; - institution = collection.institution; - } else { - const referenceDar = find((dar) => !isEmpty(dar.data))( - collection.dars - ); - const { data } = referenceDar; - projectTitle = data.projectTitle; - institution = data.institution; - } - const datasetCount = !isEmpty(collection.datasets) ? collection.datasets.length.toString() : '0'; - const lowerCaseTerm = toLower(term); - createDate = formatDate(collection.createDate); - const { darCode, isDraft, createUser } = collection; - const researcherName = get('displayName')(createUser); - const status = toLower(isDraft ? collection.status : darCollectionUtils.determineCollectionStatus(collection)) || ''; + const {darCode, datasetCount, institutionName, name, researcherName, status, submissionDate} = collection; + const formattedSubmissionDate = formatDate(submissionDate); const matched = find((phrase) => { - const termArr = lowerCaseTerm.split(' '); - return find(term => includes(term, phrase))(termArr); - })([datasetCount, toLower(darCode), toLower(createDate), toLower(projectTitle), toLower(status), toLower(institution), toLower(researcherName)]); + const termArr = term.split(' '); + return find(term => includes(toLower(term), toLower(phrase)))(termArr); + })([darCode, datasetCount, institutionName, name, researcherName, status, formattedSubmissionDate]); return !isNil(matched); })(targetList), darDrafts: (term, targetList) => filter(draftRecord => { From 931700297bce59e928ac0a61609b1d1b462ffc89 Mon Sep 17 00:00:00 2001 From: lu-c Date: Mon, 1 Aug 2022 15:54:18 -0400 Subject: [PATCH 05/10] [DUOS-1904] Added uniform Actions component (#1710) * fixed merge issue * ???? Testing new actions component. * updated unit tests? * Fixed eslint * fix admin console * fix tests * Fixing unit tests * Apply suggestions from code review Co-authored-by: Gregory Rushton * added unit test for Update button (used to be combined with the vote button) * Update cypress/component/DarCollectionTable/admin_actions.spec.js Co-authored-by: Gregory Rushton * fix codacy * Fixed unit tests * Fixed unit tests? * merged with DUOS-1902, deleted unnecessary files Co-authored-by: Connor Barker Co-authored-by: Gregory Rushton --- .../DarCollectionTable/actions.spec.js | 111 ++++++++ .../DarCollectionTable/admin_actions.spec.js | 49 ---- .../DarCollectionTable/chair_actions.spec.js | 265 ------------------ .../DarCollectionTable/member_actions.spec.js | 212 -------------- .../dar_collection_table/Actions.js | 118 ++++++++ .../dar_collection_table/AdminActions.js | 96 ------- .../dar_collection_table/ChairActions.js | 214 -------------- .../DarCollectionTableCellData.js | 33 +-- .../dar_collection_table/MemberActions.js | 96 ------- src/pages/AdminManageDarCollections.js | 2 +- src/pages/MemberConsole.js | 2 +- 11 files changed, 239 insertions(+), 959 deletions(-) create mode 100644 cypress/component/DarCollectionTable/actions.spec.js delete mode 100644 cypress/component/DarCollectionTable/admin_actions.spec.js delete mode 100644 cypress/component/DarCollectionTable/chair_actions.spec.js delete mode 100644 cypress/component/DarCollectionTable/member_actions.spec.js create mode 100644 src/components/dar_collection_table/Actions.js delete mode 100644 src/components/dar_collection_table/AdminActions.js delete mode 100644 src/components/dar_collection_table/ChairActions.js delete mode 100644 src/components/dar_collection_table/MemberActions.js diff --git a/cypress/component/DarCollectionTable/actions.spec.js b/cypress/component/DarCollectionTable/actions.spec.js new file mode 100644 index 000000000..e0fac2b74 --- /dev/null +++ b/cypress/component/DarCollectionTable/actions.spec.js @@ -0,0 +1,111 @@ +/* eslint-disable no-undef */ +import { React } from 'react'; +import { mount } from 'cypress/react'; +import Actions from '../../../src/components/dar_collection_table/Actions'; +import {cloneDeep} from 'lodash/fp'; +import { Navigation } from '../../../src/libs/utils'; +import { Storage } from '../../../src/libs/storage'; + +let propCopy; +const collectionId = 1; +const collectionSkeleton = { + darCollectionId: collectionId, + dars: undefined +}; + +const user = { + userId: 1, + roles: [ + { + dacId: 2 + }, + {}, //not all roles are tied to a DAC, this is a stub for those roles + { + dacId: 3 + } + ] +}; + +const props = { + consoleType: 'chair', + collection: collectionSkeleton, + showCancelModal: () => {}, + updateCollections: () => {} +}; + +beforeEach(() => { + propCopy = cloneDeep(props); + cy.stub(Navigation, 'console').returns({}); + cy.stub(Storage, 'getCurrentUser').returns(user); +}); + +describe('Actions - Container', () => { + it('renders the actions container div', () => { + mount(); + const container = cy.get('.chair-actions'); + container.should('exist'); + }); +}); + +describe('Actions - Open Button', () => { + it('should render the open button if there is a an Open Action', () => { + propCopy.actions = ['Open']; + mount(); + const openButton = cy.get(`#chair-open-${collectionId}`); + openButton.should('exist'); + }); + + it('should not render Open Button if there is no valid election for opening/re-opening', () => { + propCopy.actions = []; + mount(); + const openButton = cy.get(`#chair-open-${collectionId}`); + openButton.should('not.exist'); + }); +}); + +describe('Actions - Close Button', () => { + it('should render if there is a valid election for canceling (all open elections)', () => { + propCopy.actions = ['Cancel']; + mount(); + const closeButton = cy.get(`#chair-cancel-${collectionId}`); + closeButton.should('exist'); + }); + + it('should not render if there is no valid election for canceling (no open elections)', () => { + propCopy.actions = []; + mount(); + const closeButton = cy.get(`#chair-cancel-${collectionId}`); + closeButton.should('not.exist'); + }); +}); + +describe('Actions - Vote Button', () => { + it('should not render if relevant elections are not votable', () => { + propCopy.actions = []; + mount(); + const voteButton = cy.get(`#chair-vote-${collectionId}`); + voteButton.should('not.exist'); + }); + it('should render if all relevant elections are votable', () => { + propCopy.actions = ['Vote']; + mount(); + const voteButton = cy.get(`#chair-vote-${collectionId}`); + voteButton.should('exist'); + }); +}); + +describe('Actions - Update Button', () => { + it('should not render if relevant elections are not votable', () => { + propCopy.actions = []; + mount(); + const voteButton = cy.get(`#chair-update-${collectionId}`); + voteButton.should('not.exist'); + }); + it('should render if all relevant elections are votable', () => { + propCopy.actions = ['Update']; + mount(); + const voteButton = cy.get(`#chair-update-${collectionId}`); + voteButton.should('exist'); + }); +}); + diff --git a/cypress/component/DarCollectionTable/admin_actions.spec.js b/cypress/component/DarCollectionTable/admin_actions.spec.js deleted file mode 100644 index 4fa4e9369..000000000 --- a/cypress/component/DarCollectionTable/admin_actions.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable no-undef */ -import { React } from 'react'; -import { mount } from 'cypress/react'; -import AdminActions from '../../../src/components/dar_collection_table/AdminActions'; - -const collectionId = 1; -const props = { - collection: { - darCollectionId: collectionId, - dars: { - 1: { - elections: { - 1: {status: "Open"} - } - }, - 2: { - elections: { - 2: {status: "Closed"} - } - } - } - }, - showCancelModal: () => {}, - updateCollections: () => {}, -}; - -describe('Admin Actions - Container', () => { - it('renders the actions container div', () => { - mount(); - const containerDiv = cy.get(`.admin-actions`); - containerDiv.should('exist'); - }); -}); - -describe('Admin Actions - Open Button', () => { - it('renders the open button if some of the elections are not open', () => { - mount(); - const button = cy.get(`#admin-open-${collectionId}`); - button.should('exist'); - }); -}); - -describe('Admin Actions - Closed button', () => { - it('renders the cancel button if cancelable elections exists', () => { - mount(); - const button = cy.get(`#admin-cancel-${collectionId}`); - button.should('exist'); - }); -}); \ No newline at end of file diff --git a/cypress/component/DarCollectionTable/chair_actions.spec.js b/cypress/component/DarCollectionTable/chair_actions.spec.js deleted file mode 100644 index 6daac7122..000000000 --- a/cypress/component/DarCollectionTable/chair_actions.spec.js +++ /dev/null @@ -1,265 +0,0 @@ -/* eslint-disable no-undef */ -import { React } from 'react'; -import { mount } from 'cypress/react'; -import ChairActions from '../../../src/components/dar_collection_table/ChairActions'; -import {cloneDeep} from 'lodash/fp'; -import { Navigation } from '../../../src/libs/utils'; -import { Storage } from '../../../src/libs/storage'; - -let propCopy; -const collectionId = 1; -const collectionSkeleton = { - darCollectionId: collectionId, - dars: undefined -}; - -const nonVoteableDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: {dacUserId: 2} - } - } - }, - data: {} - } -}; - -const votableDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: {dacUserId: 2} - }, - dataSetId: 1 - }, - }, - data: {} - }, - 2: { - elections: { - 3: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 2: {dacUserId: 1} - }, - dataSetId: 2 - } - }, - data: {} - } -}; - -const nonOpenDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Closed', - votes: { - 1: {dacUserId: 1} - }, - dataSetId: 1 - }, - }, - data: {} - }, - 2: { - elections: { - 2: { - electionType: 'DataAccess', - status: 'Closed', - votes: { - 1: {dacUserId: 1} - }, - dataSetId: 2 - } - }, - data: {} - } -}; - -const missingElectionSet = { - 1: { - elections: {}, - data: { - datasetIds: [1] - }, - datasetIds: [1] - }, - 2: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: {dacUserId: 2} - }, - referenceId: 2 - } - }, - data: {} - } -}; - -const cancelableDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: {dacUserId: 1} - }, - dataSetId: 1 - } - }, - data: {} - } -}; - -const nonCancelableDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: {dacUserId: 1} - }, - dataSetId: 1 - } - }, - data: {} - }, - 2: { - elections: { - 2: { - electionType: 'DataAccess', - status: 'Closed', - votes: { - 1: {dacUserId: 1} - }, - dataSetId: 2 - } - }, - data: {} - } -}; - -const darsWithRpElection = { - 1: { - elections: { - 1: { - electionType: 'RP', - status: 'Open', - votes: { - 1: {dacUserId: 1} - }, - dataSetId: 1 - } - }, - data: {} - } -}; - - -const user = { - userId: 1, - roles: [ - { - dacId: 2 - }, - {}, //not all roles are tied to a DAC, this is a stub for those roles - { - dacId: 3 - } - ] -}; - -const props = { - collection: collectionSkeleton, - showCancelModal: () => {}, - updateCollections: () => {} -}; - -beforeEach(() => { - propCopy = cloneDeep(props); - cy.stub(Navigation, 'console').returns({}); - cy.stub(Storage, 'getCurrentUser').returns(user); -}); - -describe('Chair Actions - Container', () => { - it('renders the actions container div', () => { - propCopy.collection.dars = votableDars; - propCopy.relevantDatasets = []; - mount(); - const container = cy.get('.chair-actions'); - container.should('exist'); - }); -}); - -describe('Chair Actions - Open Button', () => { - it('should render the open button if there is a valid election for opening/re-opening', () => { - propCopy.collection.dars = nonOpenDars; - propCopy.relevantDatasets = [{dataSetId: 1}, {dataSetId: 2}, {dataSetId: 3}]; - mount(); - const openButton = cy.get(`#chair-open-${collectionId}`); - openButton.should('exist'); - }); - - it('should not render if there is no valid election for opening/re-opening', () => { - propCopy.openEnabled = false; - mount(); - const openButton = cy.get(`#chair-open-${collectionId}`); - openButton.should('not.exist'); - }); - - it('should render if there are valid DARs with no election present', () => { - propCopy.openEnabled = true; - mount(); - const openButton = cy.get(`#chair-open-${collectionId}`); - openButton.should('exist'); - }); -}); - -describe('Chair Actions - Close Button', () => { - it('should render if there is a valid election for canceling (all open elections)', () => { - propCopy.cancelEnabled = true; - mount(); - const closeButton = cy.get(`#chair-cancel-${collectionId}`); - closeButton.should('exist'); - }); - - it('should not render if there is no valid election for canceling (no open elections)', () => { - propCopy.cancelEnabled = false; - mount(); - const closeButton = cy.get(`#chair-cancel-${collectionId}`); - closeButton.should('not.exist'); - }); -}); - -describe('Chair Actions - Vote Button', () => { - it('should not render if relevant elections are not votable', () => { - propCopy.voteEnabled = false; - mount(); - const voteButton = cy.get(`#chair-vote-${collectionId}`); - voteButton.should('not.exist'); - }); - it('should render if all relevant elections are votable', () => { - propCopy.voteEnabled = true; - mount(); - const voteButton = cy.get(`#chair-vote-${collectionId}`); - voteButton.should('exist'); - }); -}); - - - diff --git a/cypress/component/DarCollectionTable/member_actions.spec.js b/cypress/component/DarCollectionTable/member_actions.spec.js deleted file mode 100644 index 3fb094990..000000000 --- a/cypress/component/DarCollectionTable/member_actions.spec.js +++ /dev/null @@ -1,212 +0,0 @@ -/* eslint-disable no-undef */ -import { React } from 'react'; -import { mount } from 'cypress/react'; -import MemberActions from '../../../src/components/dar_collection_table/MemberActions'; -import { cloneDeep } from 'lodash/fp'; -import { Storage } from '../../../src/libs/storage'; - -let propCopy; -const collectionId = 1; -const collectionSkeleton = { - darCollectionId: collectionId, - dars: undefined -}; - -const votableDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: { dacUserId: 2 }, - }, - datasetId: 1, - }, - }, - }, - 2: { - elections: { - 3: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 2: { dacUserId: 1 }, - }, - datasetId: 2, - }, - }, - }, -}; - -const submittedVoteDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: { dacUserId: 2, }, - }, - datasetId: 1, - }, - }, - }, - 2: { - elections: { - 3: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 2: { dacUserId: 1, vote: true }, - }, - datasetId: 2, - }, - }, - }, -}; - -const closedDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: { dacUserId: 2 }, - }, - datasetId: 1, - }, - }, - }, - 2: { - elections: { - 3: { - electionType: 'DataAccess', - status: 'Closed', - votes: { - 2: { dacUserId: 1 }, - }, - datasetId: 2, - }, - }, - } -}; - -const noVoteInDars = { - 1: { - elections: { - 1: { - electionType: 'DataAccess', - status: 'Open', - votes: { - 1: { dacUserId: 2 }, - }, - datasetId: 1, - }, - }, - }, - 2: { - elections: { - 3: { - electionType: 'DataAccess', - status: 'Open', - votes: {}, - datasetId: 2, - }, - }, - }, -}; - -const darsWithRpElection = { - 1: { - elections: { - 1: { - electionType: 'RP', - status: 'Open', - votes: { - 1: {dacUserId: 1} - }, - dataSetId: 1 - } - }, - data: {} - } -}; - -const user = { - userId: 1, - roles: [ - { - dacId: 2, - }, - {}, //not all roles are tied to a DAC, this is a stub for those roles - { - dacId: 3, - }, - ], -}; - -const props = { - collection: collectionSkeleton, - showCancelModal: () => {}, - updateCollections: () => {} -}; - -beforeEach(() => { - propCopy = cloneDeep(props); - cy.stub(Storage, 'getCurrentUser').returns(user); -}); - -describe('Member Actions - Vote Button', () => { - it('renders the cell container', () => { - propCopy.collection.dars = votableDars; - mount(); - const container = cy.get('.member-actions'); - container.should('exist'); - }); - - it('renders the vote button if the member has a vote present for an open election', () => { - propCopy.collection.dars = votableDars; - mount(); - const voteButton = cy.get(`#member-vote-${collectionId}`); - voteButton.should('exist'); - }); - - it('does not render the vote button if the member does not have a vote present', () => { - propCopy.collection.dars = noVoteInDars; - mount(); - const voteButton = cy.get(`#member-vote-${collectionId}`); - voteButton.should('not.exist'); - }); - - it('does not render the vote button if there are no open elections for the member to vote on', () => { - propCopy.collection.dars = closedDars; - mount(); - const voteButton = cy.get(`#member-vote-${collectionId}`); - voteButton.should('not.exist'); - }); - - it('shows "VOTE" if the user has yet to vote on an election', () => { - propCopy.collection.dars = votableDars; - mount(); - const voteButton = cy.get(`#member-vote-${collectionId}`); - voteButton.should('exist'); - voteButton.contains('VOTE'); - }); - - it('shows "UPDATE" if the user has already voted but the election is still open', () => { - propCopy.collection.dars = submittedVoteDars; - mount(); - const voteButton = cy.get(`#member-vote-${collectionId}`); - voteButton.should('exist'); - voteButton.contains('UPDATE'); - }); - - it('should not consider RP elections when determining if vote buttons renders', () => { - propCopy.collection.dars = darsWithRpElection; - mount(); - const voteButton = cy.get(`#member-vote-${collectionId}`); - voteButton.should('not.exist'); - }); -}); \ No newline at end of file diff --git a/src/components/dar_collection_table/Actions.js b/src/components/dar_collection_table/Actions.js new file mode 100644 index 000000000..9f8edf193 --- /dev/null +++ b/src/components/dar_collection_table/Actions.js @@ -0,0 +1,118 @@ +import { div, h } from 'react-hyperscript-helpers'; +import TableIconButton from '../TableIconButton'; +import { Styles } from '../../libs/theme'; +import { Block } from '@material-ui/icons'; +import SimpleButton from '../SimpleButton'; + +const duosBlue = '#0948B7'; +const cancelGray = '#333F52'; + +const hoverCancelButtonStyle = Styles.TABLE.TABLE_BUTTON_ICON_HOVER; +const baseCancelButtonStyle = Object.assign( + {}, + Styles.TABLE.TABLE_ICON_BUTTON, + {color: cancelGray}, + { alignItems: 'center' } +); + +export default function Actions(props) { + const { showConfirmationModal, collection, goToVote, consoleType, actions = [] } = props; + const collectionId = collection.darCollectionId; + + const openOnClick = async (collection) => { + showConfirmationModal(collection, 'open'); + }; + + const cancelOnClick = (collection) => { + showConfirmationModal(collection, 'cancel'); + }; + + const openButtonAttributes = { + keyProp: `${consoleType}-open-${collectionId}`, + label: 'Open', + isRendered: actions.includes('Open'), + onClick: () => openOnClick(collection), + baseColor: duosBlue, + hoverStyle: { + backgroundColor: duosBlue, + color: 'white' + }, + additionalStyle: { + padding: '3% 7%', + fontSize: '1.45rem', + fontWeight: 600, + color: 'white', + marginRight: '8%' + } + }; + + const cancelButtonAttributes = { + keyProp: `${consoleType}-cancel-${collectionId}`, + isRendered: actions.includes('Cancel'), + onClick: () => cancelOnClick(collection), + style: baseCancelButtonStyle, + hoverStyle: hoverCancelButtonStyle, + dataTip: 'Cancel Elections', + icon: Block, + }; + + const voteButtonAttributes = { + keyProp: `${consoleType}-vote-${collectionId}`, + label: 'Vote', + isRendered: actions.includes('Vote'), + onClick: () => goToVote(collectionId), + baseColor: duosBlue, + hoverStyle: { + backgroundColor: duosBlue, + color: 'white', + }, + additionalStyle: { + padding: '3% 7%', + fontSize: '1.45rem', + fontWeight: 600, + color: 'white', + marginRight: '8%', + border: `1px ${duosBlue} solid`, + }, + }; + + const updateButtonAttributes = { + keyProp: `${consoleType}-update-${collectionId}`, + label: 'Update', + isRendered: actions.includes('Update'), + onClick: () => goToVote(collectionId), + baseColor: 'white', + hoverStyle: { + backgroundColor: 'white', + color: duosBlue + }, + additionalStyle: { + padding: '3% 7%', + fontSize: '1.45rem', + fontWeight: 600, + color: duosBlue, + marginRight: '8%', + border: `1px ${duosBlue} solid` + } + }; + + return div( + { + className: `${consoleType}-actions`, + key: `${consoleType}-actions-${collectionId}`, + id: `${consoleType}-actions-${collectionId}`, + style: { + display: 'flex', + padding: '10px 5px', + justifyContent: 'flex-start', + alignItems: 'center', + }, + }, + [ + h(SimpleButton, openButtonAttributes), + h(SimpleButton, voteButtonAttributes), + h(SimpleButton, updateButtonAttributes), + h(TableIconButton, cancelButtonAttributes), + ] + ); +} diff --git a/src/components/dar_collection_table/AdminActions.js b/src/components/dar_collection_table/AdminActions.js deleted file mode 100644 index c805bd711..000000000 --- a/src/components/dar_collection_table/AdminActions.js +++ /dev/null @@ -1,96 +0,0 @@ -import { useState, useEffect } from 'react'; -import { div, h } from 'react-hyperscript-helpers'; -import TableIconButton from '../TableIconButton'; -import SimpleButton from '../SimpleButton'; -import { Styles, Theme } from '../../libs/theme'; -import { Block } from '@material-ui/icons'; -import { - checkIfOpenableElectionPresent, - checkIfCancelableElectionPresent, -} from '../../utils/DarCollectionUtils'; - -const hoverCancelButtonStyle = Styles.TABLE.TABLE_BUTTON_ICON_HOVER; -const baseCancelButtonStyle = Object.assign( - {}, - Styles.TABLE.TABLE_ICON_BUTTON, - { alignItems: 'center' } -); - -export default function AdminActions(props) { - /* - Admin -> Should only be able to open and close elections (no restrictions in terms of permissions) - Cancel should be unrestricted, should be able to run no matter what - Open should only happen if there's no election - Re-open should pop up if the latest elections are all closed/cancelled (mix should not be possible) - Therefore, to make the above calculations, you'll need... - Elections -> all elections in the collection - */ - - /* - setCollections -> state setter from parent - collections -> state variable from parent - collection -> target collection for the button - */ - - const { collection, showConfirmationModal } = props; - const collectionId = collection.darCollectionId; - const [openEnabled, setOpenEnabled] = useState(false); - const [cancelEnabled, setCancelEnabled] = useState(false); - - useEffect(() => { - const { dars } = collection; - const isCancelable = checkIfCancelableElectionPresent(dars); - const isOpenable = checkIfOpenableElectionPresent(dars); - - setOpenEnabled(isOpenable); - setCancelEnabled(isCancelable); - }, [collection]); - - const openOnClick = async (collection) => { - showConfirmationModal(collection, 'open'); - }; - - const cancelOnClick = (collection) => { - showConfirmationModal(collection, 'cancel'); - }; - - const openButtonAttributes = { - keyProp: `admin-open-${collectionId}`, - label: 'Open', - isRendered: openEnabled, - onClick: () => openOnClick(collection), - baseColor: Theme.palette.secondary, - additionalStyle: { - padding: '5px 10px', - fontSize: '1.45rem', - fontWeight: 600 - }, - }; - - const cancelButtonAttributes = { - keyProp: `admin-cancel-${collectionId}`, - isRendered: cancelEnabled, - onClick: () => cancelOnClick(collection), - style: baseCancelButtonStyle, - hoverStyle: hoverCancelButtonStyle, - dataTip: 'Cancel Elections', - icon: Block, - }; - - return div( - { - className: 'admin-actions', - key: `admin-actions-${collectionId}`, - style: { - display: 'flex', - padding: '10px 0px', - alignItems: 'end', - justifyContent: 'flex-start', - }, - }, - [ - h(SimpleButton, openButtonAttributes), - h(TableIconButton, cancelButtonAttributes), - ] - ); -} \ No newline at end of file diff --git a/src/components/dar_collection_table/ChairActions.js b/src/components/dar_collection_table/ChairActions.js deleted file mode 100644 index dec4ac6c0..000000000 --- a/src/components/dar_collection_table/ChairActions.js +++ /dev/null @@ -1,214 +0,0 @@ -import { useState, useEffect } from 'react'; -import { div, h } from 'react-hyperscript-helpers'; -import TableIconButton from '../TableIconButton'; -import { Styles } from '../../libs/theme'; -import { Block } from '@material-ui/icons'; -import { isEmpty, filter, find, map, flow, includes, intersection, toLower, forEach, flatten, flatMap, uniq, isNil } from 'lodash/fp'; -import { Storage } from '../../libs/storage'; -import SimpleButton from '../SimpleButton'; - -const duosBlue = '#0948B7'; -const cancelGray = '#333F52'; - -const hoverCancelButtonStyle = Styles.TABLE.TABLE_BUTTON_ICON_HOVER; -const baseCancelButtonStyle = Object.assign( - {}, - Styles.TABLE.TABLE_ICON_BUTTON, - {color: cancelGray}, - { alignItems: 'center' } -); - -const initUserData = ({dars, elections, relevantDatasets}) => { - try { - const relevantDatasetIds = flow( - flatten, - map(dataset => dataset.dataSetId), - uniq - )(relevantDatasets); - const relevantDarsNoElections = filter(dar => { - // Dataset IDs should be on the DAR, but if not, pull from the dar.data - const datasetIds = isNil(dar.datasetIds) ? dar.data.datasetIds : dar.datasetIds; - const relevant = flow( - map(id => { return includes(id, relevantDatasetIds) ? isEmpty(dar.elections) : false; }), - find((r) => { return true === r; } ) - )(datasetIds); - return !isNil(relevant); - })(dars); - const relevantElections = filter((election) => { - // NOTE: not all elections have the dataSetId attribute tied to it (https://broadworkbench.atlassian.net/browse/DUOS-1689) - // For this ticket I'm going to use dar.datasetIds/dar.data.datasetIds as a fallback value - if(!isNil(election.dataSetId)) { - return includes(election.dataSetId, relevantDatasetIds); - } else { - // Dataset IDs should be on the DAR, but if not, pull from the dar.data - const datasetIds = isNil(dars[election.referenceId].datasetIds) ? dars[election.referenceId].data.datasetIds : dars[election.referenceId].datasetIds; - return intersection(datasetIds, relevantDatasetIds).length > 0; - } - })(elections); - return {relevantDarsNoElections, relevantElections, relevantDatasetIds}; - } catch(error) { - //if there's an issue with the collection it's best to fail safely, so hide all of the buttons - throw new Error('Error initializing component data'); - } -}; - -const calcComponentState = ({relevantElections}) => { - try{ - let label = 'Vote'; - - //iterate through elections, push open and non-open elections into their respective arrays - //also for each election, see if user has a vote and whether or not they've already voted - forEach(election => { - const {votes} = election; - forEach(vote => { - if(!isNil(vote.vote)) { label = 'Update'; } - })(votes); - })(relevantElections); - return {label}; - } catch(error) { - throw new Error ('Error initializing chair actions'); - } -}; - -export default function ChairActions(props) { - /* - Chair -> Admin actions plus vote button, however there are permission caveats - Analysis cannot be done across the entire collection, only on elections that pertain to the user - - Therefore, for opening and closing you need... - Elections -> only the elections that pertain to the users' DAC - - For the Vote button - Votes -> For the relevant elections (Open), see if the user has a vote (need this to figure out if vote is hidden) - -> See if user's vote has been submitted (Vote|Update Vote) - */ - - //relevantDatasets is the list of datasets that the user has access to - //needed to determine if a user can open an election on a DAR that has no elections - //Done via API call, needs to be done on parent component to avoid making the same request (will return the same result) on each row - const { showConfirmationModal, collection, relevantDatasets, goToVote } = props; - const { dars } = collection; - const collectionId = collection.darCollectionId; - const [openEnabled, setOpenEnabled] = useState(props.openEnabled); - const [cancelEnabled, setCancelEnabled] = useState(props.cancelEnabled); - const [voteEnabled, setVoteEnabled] = useState(props.voteEnabled); - const [voteLabel, setVoteLabel] = useState('Vote'); - - //if there's something wrong with the collection it's best to fail gracefully - //use this function to hide buttons on processing err - const updateStateOnFail = () => { - setOpenEnabled(false); - setCancelEnabled(false); - setVoteEnabled(false); - setVoteLabel(false); - }; - - useEffect(() => { - const init = ({ elections, dars, userId, relevantDatasets }) => { - const { relevantDarsNoElections, relevantElections } = - initUserData({ - dars, - elections, - relevantDatasets - }); - const { label } = calcComponentState({ - userId, - relevantElections, - relevantDarsNoElections, - setVoteLabel - }); - //set label based on function return, visibility determined by setVoteEnabled - setVoteLabel(label); - }; - - try { - const { dars } = collection; - const user = Storage.getCurrentUser(); - const { userId } = user; - const elections = flow( - map((dar) => dar.elections), - flatMap((electionMap) => Object.values(electionMap)), - filter((election) => toLower(election.electionType) === 'dataaccess') - )(dars); - init({ dars, userId, elections, relevantDatasets }); - } catch (error) { - updateStateOnFail(); - } - }, [dars, collection, relevantDatasets]); - - const openOnClick = async (collection) => { - showConfirmationModal(collection, 'open'); - }; - - const cancelOnClick = (collection) => { - showConfirmationModal(collection, 'cancel'); - }; - - const openButtonAttributes = { - keyProp: `chair-open-${collectionId}`, - label: 'Open', - isRendered: openEnabled, - onClick: () => openOnClick(collection), - baseColor: duosBlue, - hoverStyle: { - backgroundColor: duosBlue, - color: 'white' - }, - additionalStyle: { - padding: '3% 7%', - fontSize: '1.45rem', - fontWeight: 600, - color: 'white', - marginRight: '8%' - } - }; - - const cancelButtonAttributes = { - keyProp: `chair-cancel-${collectionId}`, - isRendered: cancelEnabled, - onClick: () => cancelOnClick(collection), - style: baseCancelButtonStyle, - hoverStyle: hoverCancelButtonStyle, - dataTip: 'Cancel Elections', - icon: Block, - }; - - const voteButtonAttributes = { - keyProp: `chair-vote-${collectionId}`, - label: voteLabel, - isRendered: voteEnabled, - onClick: () => goToVote(collectionId), - baseColor: toLower(voteLabel) === 'update' ? 'white' : duosBlue, - hoverStyle: { - backgroundColor: toLower(voteLabel) === 'update' ? 'white' : duosBlue, - color: toLower(voteLabel) === 'update' ? duosBlue : 'white', - }, - additionalStyle: { - padding: '3% 7%', - fontSize: '1.45rem', - fontWeight: 600, - color: toLower(voteLabel) === 'update' ? duosBlue : 'white', - marginRight: '8%', - border: `1px ${duosBlue} solid`, - }, - }; - - return div( - { - className: 'chair-actions', - key: `chair-actions-${collectionId}`, - id: `chair-actions-${collectionId}`, - style: { - display: 'flex', - padding: '10px 5px', - justifyContent: 'flex-start', - alignItems: 'center', - }, - }, - [ - h(SimpleButton, openButtonAttributes), - h(SimpleButton, voteButtonAttributes), - h(TableIconButton, cancelButtonAttributes), - ] - ); -} diff --git a/src/components/dar_collection_table/DarCollectionTableCellData.js b/src/components/dar_collection_table/DarCollectionTableCellData.js index 45508e65c..7f41d3b08 100644 --- a/src/components/dar_collection_table/DarCollectionTableCellData.js +++ b/src/components/dar_collection_table/DarCollectionTableCellData.js @@ -2,10 +2,7 @@ import {includes, isEmpty, isNil, toLower} from 'lodash/fp'; import {formatDate} from '../../libs/utils'; import {h} from 'react-hyperscript-helpers'; import {styles} from './DarCollectionTable'; -import AdminActions from './AdminActions'; -import ChairActions from './ChairActions'; -import MemberActions from './MemberActions'; -import ResearcherActions from './ResearcherActions'; +import Actions from './Actions'; import DarCollectionAdminReviewLink from './DarCollectionAdminReviewLink'; import {Link} from 'react-router-dom'; import { consoleTypes } from '../dar_table/DarTableActions'; @@ -133,29 +130,15 @@ export function statusCellData({status = '- -', darCollectionId, label = 'status }; } -export function consoleActionsCellData({collection, reviewCollection, goToVote, showConfirmationModal, consoleType, relevantDatasets, resumeCollection, actions}) { +export function consoleActionsCellData({collection, reviewCollection, goToVote, showConfirmationModal, consoleType, resumeCollection, actions}) { let actionComponent; - switch (consoleType) { - case consoleTypes.ADMIN: - actionComponent = h(AdminActions, {collection, showConfirmationModal}); - break; - case consoleTypes.CHAIR: - actionComponent = h(ChairActions, { - collection, showConfirmationModal, goToVote, relevantDatasets, - openEnabled: actions.includes('Open'), - cancelEnabled: actions.includes('Cancel'), - voteEnabled: actions.includes('Vote') - }); - break; - case consoleTypes.MEMBER: - actionComponent = h(MemberActions, {collection, showConfirmationModal, goToVote}); - break; - case consoleTypes.RESEARCHER: - default: - actionComponent = h(ResearcherActions, {collection, showConfirmationModal, reviewCollection, resumeCollection}); - break; - } + actionComponent = h(Actions, { + collection, consoleType, + showConfirmationModal, goToVote, + reviewCollection, resumeCollection, + actions + }); return { isComponent: true, diff --git a/src/components/dar_collection_table/MemberActions.js b/src/components/dar_collection_table/MemberActions.js deleted file mode 100644 index 44d4d82e1..000000000 --- a/src/components/dar_collection_table/MemberActions.js +++ /dev/null @@ -1,96 +0,0 @@ -import { div, h } from 'react-hyperscript-helpers'; -import { useEffect, useState } from 'react'; -import { any, filter, flatMap, flow, isEmpty, isNil, map, toLower, toUpper } from 'lodash/fp'; -import { Storage } from '../../libs/storage'; -import SimpleButton from '../SimpleButton'; - -const duosBlue = '#0948B7'; - -const findRelevantVotes = ({ dars = {}, userId}) => { - return flow( - map((dar) => dar.elections), - flatMap((electionMap) => Object.values(electionMap)), - filter((election) => toLower(election.electionType) === 'dataaccess'), - filter((election) => toLower(election.status) === 'open' && !isEmpty(election.votes)), - flatMap((election) => Object.values(election.votes)), - filter((vote) => vote.dacUserId === userId) - )(dars); -}; - - - -const determineButtonLabel = ({relevantVotes}) => { - const submittedVotePresent = any(vote => !isNil(vote.vote))(relevantVotes); - return submittedVotePresent ? 'Update' : 'Vote'; -}; - -export default function MemberActions(props) { - /* - Members can only vote on collections - Only goal is to determine whether or not the vote button appears - //Look through votes on elections, if user has a vote present and the election is open, show button - //Otherwise hide the button - */ - - /* - For a collection, the component needs: - Current User - Elections in the collection, - Votes attached to the collection - */ - const { collection, goToVote } = props; - const collectionId = collection.darCollectionId; - const [voteEnabled, setVoteEnabled] = useState(false); - const [label, setLabel] = useState('Vote'); - - useEffect(() => { - try { - const { dars } = collection; - const user = Storage.getCurrentUser(); - const userId = user.userId; - const relevantVotes = findRelevantVotes({dars, userId}); - if(!isEmpty(relevantVotes)) { - const buttonLabel = determineButtonLabel({relevantVotes}); - setLabel(buttonLabel); - setVoteEnabled(true); - } else { - setVoteEnabled(false); - } - } catch(error) { - setVoteEnabled(false); - } - }, [collection]); - - const voteButtonAttributes = { - keyProp: `member-vote-${collectionId}`, - label: toUpper(label), - isRendered: voteEnabled, - onClick: () => goToVote(collectionId), - baseColor: duosBlue, - hoverStyle: { - backgroundColor: duosBlue, - color: 'white' - }, - additionalStyle: { - padding: '3% 10%', - fontSize: '1.45rem', - fontWeight: 600, - color: 'white', - marginRight: '30%' - } - }; - - return div({ - className: 'member-actions', - key: `member-actions-${collectionId}`, - style: { - display: 'flex', - padding: '10px 5px', - justifyContent: 'flex-start', - alignItems: 'end', - fontFamily: 'Montserrat' - } - }, [ - h(SimpleButton, voteButtonAttributes) - ]); -} diff --git a/src/pages/AdminManageDarCollections.js b/src/pages/AdminManageDarCollections.js index 414bfe86b..a602649d8 100644 --- a/src/pages/AdminManageDarCollections.js +++ b/src/pages/AdminManageDarCollections.js @@ -26,7 +26,7 @@ export default function AdminManageDarCollections() { useEffect(() => { const init = async() => { try { - const collectionsResp = await Collections.getCollectionsByRoleName('admin'); + const collectionsResp = await Collections.getCollectionSummariesByRoleName('admin'); setCollections(collectionsResp); setFilteredList(collectionsResp); setIsLoading(false); diff --git a/src/pages/MemberConsole.js b/src/pages/MemberConsole.js index 5ed291223..f8a9ddcf2 100644 --- a/src/pages/MemberConsole.js +++ b/src/pages/MemberConsole.js @@ -27,7 +27,7 @@ export default function MemberConsole(props) { const init = async () => { try { const [collections, datasets] = await Promise.all([ - Collections.getCollectionsByRoleName('member'), + Collections.getCollectionSummariesByRoleName('member'), User.getUserRelevantDatasets(), //still need this on this console for status cell ]); setCollections(collections); From d93c266bc6361eb7683eed11b665063cc7796d20 Mon Sep 17 00:00:00 2001 From: shaemarks <81024249+shaemarks@users.noreply.github.com> Date: Mon, 1 Aug 2022 16:29:28 -0400 Subject: [PATCH 06/10] [DIOS-1906][risk=no] modify signing official console to use new collection summaries endpoint (#1709) * Switch endpoint * Remove unused prop Co-authored-by: Shae Marks --- src/pages/SigningOfficialDarRequests.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/SigningOfficialDarRequests.js b/src/pages/SigningOfficialDarRequests.js index 3279d128f..05714ed46 100644 --- a/src/pages/SigningOfficialDarRequests.js +++ b/src/pages/SigningOfficialDarRequests.js @@ -18,7 +18,7 @@ export default function SigningOfficialDarRequests() { const init = async() => { try { setIsLoading(true); - const collectionList = await Collections.getCollectionsByRoleName(USER_ROLES.signingOfficial); + const collectionList = await Collections.getCollectionSummariesByRoleName(USER_ROLES.signingOfficial); setCollectionList(collectionList); setIsLoading(false); } catch(error) { @@ -63,7 +63,6 @@ export default function SigningOfficialDarRequests() { isLoading, cancelCollection: null, reviseCollection: null, - actionsDisabled: true, consoleType: consoleTypes.SIGNING_OFFICIAL }, []) ]) From ed7a4b5eecba2c8ae1ebcf64bcfdd9669a1ed800 Mon Sep 17 00:00:00 2001 From: lu-c Date: Tue, 9 Aug 2022 22:26:25 -0400 Subject: [PATCH 07/10] [DUOS-1904] Added researcher actions to Actions component (#1718) * update researcher console dar table + actions * naming nit * fix tests * Apply suggestions from code review Co-authored-by: Gregory Rushton * Update cypress/component/DarCollectionTable/researcher_actions.spec.js Co-authored-by: Gregory Rushton * fixed merge issue * ???? Testing new actions component. * updated unit tests? * Fixed eslint * fix name * fix admin console * fix tests * Fixing unit tests * Apply suggestions from code review Co-authored-by: Gregory Rushton * added unit test for Update button (used to be combined with the vote button) * Update cypress/component/DarCollectionTable/admin_actions.spec.js Co-authored-by: Gregory Rushton * fix codacy * Fixed unit tests * Fixed unit tests? * merged with DUOS-1902, deleted unnecessary files * Merged with 1905 * Fixing unit tests? * Fixing eslint * Fixing unit test, but dont think this will sovle the problem * Fixing unit test, but dont think this will sovle the problem * Fixing unit test, but dont think this will sovle the problem * feeling more confident now that the problem will be solved haha * Updated variable names, due to merge conflict discrepancies! * Updated variable names, due to merge conflict discrepancies! * testing cypress tests on github * Checking original researcher unit test. Getting mildly frustrated! * Converted hard coded researcher to console type * fixing? * fix test * removed unnecessary line * re-added actions (sorry connor, i deleted it) * [DUOS-1904] updated deleteDrafts to delete a draft with multiple referenceIds (previously only had one) * DUOS-setup-proxy initial setup of local proxy * [DUOS-1904] fixed eslint * DUOS-setup-proxy added status urls to proxy * DUOS-setup-proxy updated ontologyService url fetch * DUOS-setup-proxy fixed method reference in getOntologyUrl * DUOS-setup-proxy added comments, commented out notification proxy for now * [DUOS-1904] reverted referenceIds so we just delete the first one * DUOS-2011 [risk=no] Local Proxy setup for npm start (#1735) * DUOS-setup-proxy initial setup of local proxy * DUOS-setup-proxy added status urls to proxy * DUOS-setup-proxy updated ontologyService url fetch * DUOS-setup-proxy fixed method reference in getOntologyUrl * DUOS-setup-proxy added comments, commented out notification proxy for now * [DUOS-1973][risk=no]Change research purpose to Research Use Statement (Narrative) (#1738) * Change research purpose to Research Use Statement (Narrative) * Fix test Co-authored-by: Shae Marks * added "breathing" margins on buttons * [DUOS-1904] Cleaning up more. * [DUOS-1904] fixed the weird bug with applyign style bug Co-authored-by: Connor Barker Co-authored-by: Gregory Rushton Co-authored-by: JVThomas Co-authored-by: Justin Variath Thomas Co-authored-by: shaemarks <81024249+shaemarks@users.noreply.github.com> Co-authored-by: Shae Marks --- .gitignore | 5 + .../DarCollectionTable/actions.spec.js | 115 ++- .../researcher_actions.spec.js | 170 ---- .../research_proposal_vote_slab.spec.js | 50 +- package-lock.json | 738 ++++++++++-------- package.json | 3 +- .../ResearchProposalVoteSlab.js | 6 +- .../dar_collection_table/Actions.js | 124 ++- .../DarCollectionTable.js | 4 +- .../dar_collection_table/ResearcherActions.js | 180 ----- .../dar_drafts_table/DarDraftTable.js | 254 ++++++ src/libs/ajax.js | 212 ++--- src/libs/notificationService.js | 7 +- src/libs/ontologyService.js | 6 +- src/pages/ResearcherConsole.js | 79 +- src/pages/Status.js | 6 +- src/setupProxy.js | 62 ++ 17 files changed, 1108 insertions(+), 913 deletions(-) delete mode 100644 cypress/component/DarCollectionTable/researcher_actions.spec.js delete mode 100644 src/components/dar_collection_table/ResearcherActions.js create mode 100644 src/components/dar_drafts_table/DarDraftTable.js create mode 100644 src/setupProxy.js diff --git a/.gitignore b/.gitignore index 2b1e333d3..951de7b47 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,8 @@ public/config.json /cypress/videos/ /cypress/fixtures/duos-automation*.json /cypress/screenshots + + +# Server +server.crt +server.key \ No newline at end of file diff --git a/cypress/component/DarCollectionTable/actions.spec.js b/cypress/component/DarCollectionTable/actions.spec.js index e0fac2b74..f9db6bc04 100644 --- a/cypress/component/DarCollectionTable/actions.spec.js +++ b/cypress/component/DarCollectionTable/actions.spec.js @@ -8,9 +8,35 @@ import { Storage } from '../../../src/libs/storage'; let propCopy; const collectionId = 1; -const collectionSkeleton = { - darCollectionId: collectionId, - dars: undefined +const refId1 = '0a4jn-g838d-bsdg8-6s7fs7'; + +const darColl = { + 'darCollectionId': collectionId, + 'referenceIds': [ + '4a3fd-g77fd-2f345-4h2g31', + '0a4jn-g838d-bsdg8-6s7fs7', + ], + 'darCode': 'DAR-9583', + 'name': 'Example DAR 1', + 'submissionDate': '2022-07-26', + 'researcherName': 'John Doe', + 'institutionName': 'Broad Institute', + 'status': 'Draft', + 'hasVoted': false, + 'datasetCount': 4 +}; + +const draftDarColl = { + 'darCollectionId': null, + 'referenceIds': [refId1], + 'darCode': 'DRAFT-023', + 'name': null, + 'submissionDate': '2022-07-26', + 'researcherName': null, + 'institutionName': null, + 'status': 'Draft', + 'hasVoted': false, + 'datasetCount': 10 }; const user = { @@ -28,9 +54,9 @@ const user = { const props = { consoleType: 'chair', - collection: collectionSkeleton, - showCancelModal: () => {}, - updateCollections: () => {} + collection: darColl, + showConfirmationModal: () => {}, + history: {} }; beforeEach(() => { @@ -65,7 +91,7 @@ describe('Actions - Open Button', () => { describe('Actions - Close Button', () => { it('should render if there is a valid election for canceling (all open elections)', () => { - propCopy.actions = ['Cancel']; + propCopy.actions = ['Cancel', 'Vote']; mount(); const closeButton = cy.get(`#chair-cancel-${collectionId}`); closeButton.should('exist'); @@ -109,3 +135,78 @@ describe('Actions - Update Button', () => { }); }); +describe('Researcher Actions - Revise Button', () => { + it('renders the revise button if the collection is revisable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Revise', 'Review']; + mount(); + cy.get(`#researcher-revise-${collectionId}`).should('exist'); + }); + it('does not render if the election is not revisable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Review']; + mount(); + cy.get(`#researcher-revise-${collectionId}`).should('not.exist'); + }); +}); + +describe('Researcher Actions - Review Button', () => { + it('renders the review button if the collection is reviewable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Revise', 'Review']; + mount(); + cy.get(`#researcher-review-${collectionId}`).should('exist'); + }); + it('does not render if the election is not reviewable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Revise']; + mount(); + cy.get(`#researcher-review-${collectionId}`).should('not.exist'); + }); +}); + +describe('Researcher Actions - Resume Button', () => { + it('renders the resume button if the collection is resumable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Resume', 'Review']; + mount(); + cy.get(`#researcher-resume-${collectionId}`).should('exist'); + }); + it('does not render if the election is not resumable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Review']; + mount(); + cy.get(`#researcher-resume-${collectionId}`).should('not.exist'); + }); +}); + +describe('Researcher Actions - Delete Button', () => { + it('renders the delete button if the collection is deletable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Delete', 'Review']; + mount(); + cy.get(`#researcher-delete-${collectionId}`).should('exist'); + }); + it('does not render if the election is not deletable', () => { + propCopy.consoleType = 'researcher'; + propCopy.actions = ['Review']; + mount(); + cy.get(`#researcher-delete-${collectionId}`).should('not.exist'); + }); +}); + +describe('Researcher Actions - Draft', () => { + it('uses the referenceId in id if draft', () => { + propCopy.consoleType = 'researcher'; + propCopy.collection = draftDarColl; + propCopy.actions = ['Revise', 'Resume', 'Review', 'Cancel', 'Delete']; + mount(); + cy.get(`#researcher-delete-${collectionId}`).should('not.exist'); + cy.get(`#researcher-resume-${refId1}`).should('exist'); + cy.get(`#researcher-review-${refId1}`).should('exist'); + cy.get(`#researcher-cancel-${refId1}`).should('exist'); + cy.get(`#researcher-delete-${refId1}`).should('exist'); + cy.get(`#researcher-revise-${refId1}`).should('exist'); + }); +}); + diff --git a/cypress/component/DarCollectionTable/researcher_actions.spec.js b/cypress/component/DarCollectionTable/researcher_actions.spec.js deleted file mode 100644 index 2900d1c66..000000000 --- a/cypress/component/DarCollectionTable/researcher_actions.spec.js +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint-disable no-undef */ -import { React } from 'react'; -import { mount } from 'cypress/react'; -import { cloneDeep } from 'lodash/fp'; -import ResearcherActions from '../../../src/components/dar_collection_table/ResearcherActions'; - -let propCopy; -const collectionId = 1; -const collectionSkeleton = { - darCollectionId: collectionId, - dars: undefined, -}; - -const canceledDars = { - 1: { - data: { - status: 'Canceled' - } - }, - 2: { - data: { - status: 'Canceled' - } - } -}; - -const darsWithElections = { - 1: { - data: {}, - elections: { - 1: { status: 'Open' } - } - }, - 2: { - data: {}, - elections: { - 2: { status: 'Closed' } - } - } -}; - -const darsWithNoElections = { - 1: { - data: {}, - elections: {} - }, - 2: { - data: {}, - elections: {} - } -}; - -const mixedCancelElectionDars = { - 1: { - data: { - status: 'Canceled' - }, - elections: { - 1: { status: 'Closed'} - } - }, - 2: { - data: { - status: 'Canceled' - } - } -}; - -const props = { - collection: collectionSkeleton, - showConfirmationModal: () => {}, - history: {} -}; - -beforeEach(() => { - propCopy = cloneDeep(props); -}); - -describe('Researcher Actions - Container', () => { - it('renders the actions container div', () => { - propCopy.collection.dars = canceledDars; - mount(); - cy.get('.researcher-actions').should('exist'); - }); -}); - -describe('Researcher Actions - Cancel Button', () => { - it('renders the cancel button if the collection is cancelable', () => { - propCopy.collection.dars = darsWithNoElections; - mount(); - cy.get(`#researcher-cancel-${collectionId}`).should('exist'); - }); - it('does not render if the election is already canceled', () => { - propCopy.collection.dars = canceledDars; - mount(); - cy.get(`#researcher-cancel-${collectionId}`).should('not.exist'); - }); - it('does not render the cancel button if the collection has elections on it', () => { - propCopy.collection.dars = darsWithElections; - mount(); - cy.get(`#researcher-cancel-${collectionId}`).should('not.exist'); - }); -}); - -describe('Researcher Actions - Revise Button', () => { - it('renders the revise button if all of the DARs are cancelled', () => { - propCopy.collection.dars = canceledDars; - mount(); - cy.get(`#revise-collection-${collectionId}`).should('exist'); - }); - - it('does not render the revise button if there are elections present', () => { - propCopy.collection.dars = mixedCancelElectionDars; - mount(); - cy.get(`#revise-collection-${collectionId}`).should('not.exist'); - }); -}); - -describe('Researcher Actions - Review Button', () => { - it('renders the review button if the DAR has been submitted', () => { - propCopy.collection.dars = darsWithElections; - mount(); - cy.get(`#researcher-review-${collectionId}`).should('exist'); - }); - - it('hides the review button if the collection is in draft status', () => { - propCopy.collection.isDraft = true; - mount(); - cy.get(`#researcher-review-${collectionId}`).should('not.exist'); - }); -}); - -describe('Researcher Actions - Resume Button', () => { - it('renders the resume button if the collection is in draft status', () => { - propCopy.collection.dars = canceledDars; - propCopy.collection.isDraft = true; - mount(); - cy.get(`#researcher-resume-${collectionId}`).should('exist'); - }); - - it('hides the resume button if the collection is not in draft status', () => { - propCopy.collection.dars = {1: {data: {}}}; - propCopy.collection.isDraft = false; - mount(); - cy.get(`#researcher-resume-${collectionId}`).should('not.exist'); - }); -}); - -describe('Researcher Actions - Cancel Button', () => { - it('renders the cancel button if the collection is not in draft status', () => { - propCopy.collection.dars = darsWithNoElections; - propCopy.collection.isDraft = false; - mount(); - cy.get(`#researcher-cancel-${collectionId}`).should('exist'); - }); - - it('hides the cancel button if the collection is in draft status', () => { - propCopy.collection.dars = darsWithNoElections; - propCopy.collection.isDraft = true; - mount(); - cy.get(`#researcher-cancel-${collectionId}`).should('not.exist'); - }); - - it('hides the cancel button if the collection already has elections created', () => { - propCopy.collection.dars = darsWithElections; - propCopy.collection.isDraft = true; - mount(); - cy.get(`#researcher-cancel-${collectionId}`).should('not.exist'); - }); -}); diff --git a/cypress/component/MultiDatasetVoteTab/research_proposal_vote_slab.spec.js b/cypress/component/MultiDatasetVoteTab/research_proposal_vote_slab.spec.js index 1e4fc9cd4..3d8fd9089 100644 --- a/cypress/component/MultiDatasetVoteTab/research_proposal_vote_slab.spec.js +++ b/cypress/component/MultiDatasetVoteTab/research_proposal_vote_slab.spec.js @@ -23,6 +23,9 @@ const darInfoPrimarySecondaryUse = { const primaryUseCode = 'DS'; const secondaryUseCode = 'OTHER'; +const expandSlabLinkText = 'Expand to view Research Use Statement (Narrative)'; +const collapseSlabLinkText = 'Hide Research Use Statement (Narrative)'; + const votesForElection1 = { rp: { finalVotes: [ @@ -117,7 +120,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { darInfo={darInfoPrimarySecondaryUse} /> ); - cy.contains('Expand to view Research Purpose and Vote'); + cy.contains(expandSlabLinkText); }); it('Renders link to collapse when expanded', function() { @@ -127,9 +130,9 @@ describe('ResearchProposalVoteSlab - Tests', function() { bucket={{key: 'test'}} /> ); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); - cy.contains('Hide Research Purpose and Vote'); + cy.contains(collapseSlabLinkText); }); it('Renders data use pills when expanded', function() { @@ -139,7 +142,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { bucket={{ key: 'test' }} /> ); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.contains(primaryUseCode); cy.contains(secondaryUseCode); @@ -152,7 +155,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { bucket={{key: 'test'}} /> ); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=research-purpose]').should('exist'); cy.contains('test'); @@ -176,7 +179,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { /> ); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=alert-box]').should('exist'); }); @@ -188,7 +191,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { bucket={{ key: 'test' }} /> ); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=alert-box]').should('not.exist'); }); @@ -220,8 +223,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { isLoading={true} /> ); - cy.get('Hide Research Purpose and Vote').should('not.exist'); - cy.get('Expand to view Research Purpose and Vote').should('not.exist'); + cy.get('#expand-rp-vote-button').should('not.exist'); }); it('Renders skeleton when loading', function() { @@ -256,7 +258,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { cy.stub(Storage, 'getCurrentUser').returns({userId: 200}); cy.stub(Votes, 'updateVotesByIds'); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=yes-collection-vote-button]').should('have.css', 'background-color', votingColors.default); @@ -279,7 +281,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { cy.stub(Storage, 'getCurrentUser').returns({userId: 200}); cy.stub(Votes, 'updateVotesByIds'); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=yes-collection-vote-button]').should('have.css', 'background-color', votingColors.default); @@ -302,7 +304,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { cy.stub(Storage, 'getCurrentUser').returns({userId: 300}); cy.stub(Votes, 'updateVotesByIds'); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=yes-collection-vote-button]').should('have.css', 'background-color', votingColors.default); @@ -325,7 +327,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { cy.stub(Storage, 'getCurrentUser').returns({userId: 200}); cy.stub(Votes, 'updateVotesByIds'); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=yes-collection-vote-button]').should('have.css', 'background-color', votingColors.default); @@ -348,7 +350,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { cy.stub(Storage, 'getCurrentUser').returns({userId: 100}); cy.stub(Votes, 'updateVotesByIds'); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=vote-subsection-heading]').should('have.text', 'NOT SELECTED'); @@ -370,7 +372,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { cy.stub(Storage, 'getCurrentUser').returns({userId: 200}); cy.stub(Votes, 'updateVotesByIds'); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=vote-subsection-heading]').should('have.text', 'NO'); @@ -390,7 +392,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 300}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=chair-vote-info]').should('not.exist'); @@ -407,7 +409,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 100}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=chair-vote-info]').should('not.exist'); @@ -424,7 +426,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 300}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('[datacy=chair-vote-info]').should('exist'); @@ -441,7 +443,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 100}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); const component = cy.get('.table-data'); @@ -462,7 +464,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 200}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('.table-data').should('exist'); @@ -480,7 +482,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 100}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('.table-data').should('exist'); @@ -498,7 +500,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 200}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('.table-data').should('exist').should('not.contain', 'undefined'); @@ -516,7 +518,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 100}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('.table-data').should('exist'); @@ -535,7 +537,7 @@ describe('ResearchProposalVoteSlab - Tests', function() { ); cy.stub(Storage, 'getCurrentUser').returns({userId: 100}); - const link = cy.contains('Expand to view Research Purpose and Vote'); + const link = cy.contains(expandSlabLinkText); link.click(); cy.get('.table-data').should('exist'); diff --git a/package-lock.json b/package-lock.json index 8798e8eda..753bae216 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "country-list": "2.2.0", "dompurify": "2.3.10", "framer-motion": "^4.1.17", + "http-proxy-middleware": "^2.0.6", "jquery": "3.6.0", "js-file-download": "0.4.12", "lodash": "4.17.21", @@ -4234,6 +4235,14 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", "dev": true }, + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -4297,8 +4306,7 @@ "node_modules/@types/node": { "version": "14.18.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz", - "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==", - "dev": true + "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -6369,7 +6377,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -10527,8 +10534,7 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/events": { "version": "3.3.0", @@ -11173,7 +11179,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -11185,7 +11190,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -12544,7 +12548,6 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -12569,187 +12572,37 @@ } }, "node_modules/http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, - "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "dependencies": { - "kind-of": "^6.0.0" + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "node": ">=12.0.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" + "peerDependencies": { + "@types/express": "^4.17.13" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } } }, - "node_modules/http-proxy-middleware/node_modules/is-number": { + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/micromatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "node": ">=10" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/http-signature": { @@ -13296,7 +13149,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13323,7 +13175,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -13374,7 +13225,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -17241,7 +17091,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -18583,7 +18432,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -21830,8 +21678,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.1", @@ -26321,6 +26168,27 @@ "node": ">=0.10.0" } }, + "node_modules/webpack-dev-server/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -26362,12 +26230,40 @@ "node": ">=6" } }, + "node_modules/webpack-dev-server/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "node_modules/webpack-dev-server/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -26380,6 +26276,21 @@ "node": ">=6" } }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "dependencies": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/webpack-dev-server/node_modules/import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -26405,6 +26316,56 @@ "node": ">=8" } }, + "node_modules/webpack-dev-server/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -26414,6 +26375,39 @@ "node": ">=4" } }, + "node_modules/webpack-dev-server/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -26427,6 +26421,43 @@ "node": ">=6" } }, + "node_modules/webpack-dev-server/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -30583,6 +30614,14 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", "dev": true }, + "@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "requires": { + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -30646,8 +30685,7 @@ "@types/node": { "version": "14.18.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz", - "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==", - "dev": true + "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -32290,7 +32328,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -35553,8 +35590,7 @@ "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "events": { "version": "3.3.0", @@ -36080,7 +36116,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" }, @@ -36089,7 +36124,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -37166,7 +37200,6 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, "requires": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -37185,153 +37218,21 @@ } }, "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" }, "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-number": { + "is-plain-obj": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - } - } + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" } } }, @@ -37731,8 +37632,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -37750,7 +37650,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -37785,8 +37684,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.7", @@ -40652,7 +40550,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -41713,8 +41610,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "2.3.0", @@ -44295,8 +44191,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "resolve": { "version": "1.22.1", @@ -48097,6 +47992,24 @@ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -48131,12 +48044,34 @@ } } }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -48146,6 +48081,18 @@ "locate-path": "^3.0.0" } }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -48162,12 +48109,76 @@ "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", "dev": true }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -48178,6 +48189,39 @@ "path-exists": "^3.0.0" } }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + } + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", diff --git a/package.json b/package.json index 4f2737c35..b298b30ad 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "country-list": "2.2.0", "dompurify": "2.3.10", "framer-motion": "^4.1.17", + "http-proxy-middleware": "^2.0.6", "jquery": "3.6.0", "js-file-download": "0.4.12", "lodash": "4.17.21", @@ -80,4 +81,4 @@ "react": "$react", "react-dom": "$react-dom" } -} \ No newline at end of file +} diff --git a/src/components/collection_voting_slab/ResearchProposalVoteSlab.js b/src/components/collection_voting_slab/ResearchProposalVoteSlab.js index e94e80c1d..ad771fd89 100644 --- a/src/components/collection_voting_slab/ResearchProposalVoteSlab.js +++ b/src/components/collection_voting_slab/ResearchProposalVoteSlab.js @@ -105,8 +105,8 @@ const SkeletonLoader = () => { const CollapseExpandLink = ({expanded, setExpanded}) => { const linkMessage = expanded ? - 'Hide Research Purpose and Vote' : - 'Expand to view Research Purpose and Vote'; + 'Hide Research Use Statement (Narrative)' : + 'Expand to view Research Use Statement (Narrative)'; return a({ style: styles.link, @@ -197,7 +197,7 @@ export default function ResearchProposalVoteSlab(props) { h(ResearchPurposeSummary, {darInfo}), h(DataUseAlertBox, {translatedDataUse}), h(CollectionSubmitVoteBox, { - question: 'Was the research purpose accurately converted to a structured format?', + question: 'Was the Research Use Statement (Narrative) accurately converted to a structured format?', votes: currentUserVotes, isFinal: false, isDisabled: adminPage || readOnly || isEmpty(currentUserVotes), diff --git a/src/components/dar_collection_table/Actions.js b/src/components/dar_collection_table/Actions.js index 9f8edf193..7530015c8 100644 --- a/src/components/dar_collection_table/Actions.js +++ b/src/components/dar_collection_table/Actions.js @@ -1,8 +1,10 @@ import { div, h } from 'react-hyperscript-helpers'; import TableIconButton from '../TableIconButton'; -import { Styles } from '../../libs/theme'; -import { Block } from '@material-ui/icons'; +import { Styles, Theme } from '../../libs/theme'; +import { Block, Delete } from '@material-ui/icons'; import SimpleButton from '../SimpleButton'; +import { useHistory } from 'react-router-dom'; +import { Notifications } from '../../libs/utils'; const duosBlue = '#0948B7'; const cancelGray = '#333F52'; @@ -12,26 +14,44 @@ const baseCancelButtonStyle = Object.assign( {}, Styles.TABLE.TABLE_ICON_BUTTON, {color: cancelGray}, - { alignItems: 'center' } + { alignItems: 'center' }, + { marginRight: '5px' } ); +const hoverPrimaryButtonStyle = { + backgroundColor: 'rgb(38 138 204)', + color: 'white' +}; + +//redirect function on researcher collections to view the collection's initial DAR application +const redirectToDARApplication = (darCollectionId, history) => { + try { + history.push(`/dar_application_review/${darCollectionId}`); + } catch (error) { + Notifications.showError({ + text: 'Error: Cannot view target Data Access Request' + }); + } +}; + +//redirect function on DAR draft to resume DAR application +const resumeDARApplication = (referenceId, history) => { + history.push(`/dar_application/${referenceId}`); +}; + export default function Actions(props) { const { showConfirmationModal, collection, goToVote, consoleType, actions = [] } = props; const collectionId = collection.darCollectionId; - const openOnClick = async (collection) => { - showConfirmationModal(collection, 'open'); - }; + const uniqueId = (collectionId ? collectionId : collection.referenceIds[0]); - const cancelOnClick = (collection) => { - showConfirmationModal(collection, 'cancel'); - }; + const history = useHistory(); const openButtonAttributes = { - keyProp: `${consoleType}-open-${collectionId}`, + keyProp: `${consoleType}-open-${uniqueId}`, label: 'Open', isRendered: actions.includes('Open'), - onClick: () => openOnClick(collection), + onClick: () => showConfirmationModal(collection, 'open'), baseColor: duosBlue, hoverStyle: { backgroundColor: duosBlue, @@ -42,14 +62,14 @@ export default function Actions(props) { fontSize: '1.45rem', fontWeight: 600, color: 'white', - marginRight: '8%' + marginRight: 5 } }; const cancelButtonAttributes = { - keyProp: `${consoleType}-cancel-${collectionId}`, + keyProp: `${consoleType}-cancel-${uniqueId}`, isRendered: actions.includes('Cancel'), - onClick: () => cancelOnClick(collection), + onClick: () => showConfirmationModal(collection, 'cancel'), style: baseCancelButtonStyle, hoverStyle: hoverCancelButtonStyle, dataTip: 'Cancel Elections', @@ -57,7 +77,7 @@ export default function Actions(props) { }; const voteButtonAttributes = { - keyProp: `${consoleType}-vote-${collectionId}`, + keyProp: `${consoleType}-vote-${uniqueId}`, label: 'Vote', isRendered: actions.includes('Vote'), onClick: () => goToVote(collectionId), @@ -71,7 +91,7 @@ export default function Actions(props) { fontSize: '1.45rem', fontWeight: 600, color: 'white', - marginRight: '8%', + marginRight: 5, border: `1px ${duosBlue} solid`, }, }; @@ -91,11 +111,75 @@ export default function Actions(props) { fontSize: '1.45rem', fontWeight: 600, color: duosBlue, - marginRight: '8%', + marginRight: 5, border: `1px ${duosBlue} solid` } }; + const reviewButtonAttributes = { + keyProp: `${consoleType}-review-${uniqueId}`, + label: 'Review', + isRendered: actions.includes('Review'), + onClick: () => redirectToDARApplication(collectionId, history), + baseColor: 'white', + fontColor: Theme.palette.secondary, + hoverStyle: { + backgroundColor: Theme.palette.secondary, + color: 'white' + }, + additionalStyle: { + padding: '3%', + fontSize: '1.45rem', + fontWeight: 600, + border: `1px solid ${Theme.palette.secondary}`, + marginRight: 5 + }, + }; + + const deleteButtonAttributes = { + keyProp: `${consoleType}-delete-${uniqueId}`, + label: 'Delete', + isRendered: actions.includes('Delete'), + onClick: () => showConfirmationModal(collection, 'delete'), + dataTip: 'Delete Collection Draft', + style: baseCancelButtonStyle, + hoverStyle: hoverCancelButtonStyle, + icon: Delete, + }; + + + const resumeButtonAttributes = { + keyProp: `${consoleType}-resume-${uniqueId}`, + isRendered: actions.includes('Resume'), + onClick: () => resumeDARApplication(collection.referenceIds[0], history), + label: 'Resume', + baseColor: Theme.palette.secondary, + fontColor: 'white', + hoverStyle: hoverPrimaryButtonStyle, + additionalStyle: { + padding: '3%', + marginRight: 5, + fontSize: '1.45rem', + fontWeight: 600, + border: `1px solid ${Theme.palette.secondary}`, + }, + }; + + const reviseButtonAttributes = { + keyProp: `${consoleType}-revise-${uniqueId}`, + label: 'Revise', + baseColor: Theme.palette.secondary, + additionalStyle: { + padding: '3%', + fontSize: '1.45rem', + fontWeight: 600, + marginRight: 5 + }, + hoverStyle: hoverPrimaryButtonStyle, + isRendered: actions.includes('Revise'), + onClick: () => showConfirmationModal(collection, 'revise'), + }; + return div( { className: `${consoleType}-actions`, @@ -112,6 +196,12 @@ export default function Actions(props) { h(SimpleButton, openButtonAttributes), h(SimpleButton, voteButtonAttributes), h(SimpleButton, updateButtonAttributes), + + h(SimpleButton, reviseButtonAttributes), + h(SimpleButton, resumeButtonAttributes), + h(SimpleButton, reviewButtonAttributes), + + h(TableIconButton, deleteButtonAttributes), h(TableIconButton, cancelButtonAttributes), ] ); diff --git a/src/components/dar_collection_table/DarCollectionTable.js b/src/components/dar_collection_table/DarCollectionTable.js index 9113c4574..3306dbbd5 100644 --- a/src/components/dar_collection_table/DarCollectionTable.js +++ b/src/components/dar_collection_table/DarCollectionTable.js @@ -233,9 +233,9 @@ export const DarCollectionTable = function DarCollectionTable(props) { }); }, [tableSize, currentPage, pageCount, collections, sort, columns, consoleType, openCollection, goToVote, relevantDatasets/*, resumeCollection, reviewCollection*/]); - const showConfirmationModal = (collection, action = '') => { + const showConfirmationModal = (collectionSummary, action = '') => { setConsoleAction(action); - setSelectedCollection(collection); + setSelectedCollection(collectionSummary); setShowConfirmation(true); }; diff --git a/src/components/dar_collection_table/ResearcherActions.js b/src/components/dar_collection_table/ResearcherActions.js deleted file mode 100644 index 9ed77ec85..000000000 --- a/src/components/dar_collection_table/ResearcherActions.js +++ /dev/null @@ -1,180 +0,0 @@ -import { useState, useEffect } from 'react'; -import {Styles, Theme} from '../../libs/theme'; -import { h, div } from 'react-hyperscript-helpers'; -import TableIconButton from '../TableIconButton'; -import { Block, Delete } from '@material-ui/icons'; -import { every, lowerCase, flow, map, filter, flatMap, isEmpty } from 'lodash/fp'; -import SimpleButton from '../SimpleButton'; -import { useHistory } from 'react-router-dom'; -import { Notifications } from '../../libs/utils'; - -//redirect function on researcher collections to view the collection's initial DAR application -const redirectToDARApplication = (darCollectionId, history) => { - try { - history.push(`/dar_application_review/${darCollectionId}`); - } catch (error) { - Notifications.showError({ - text: 'Error: Cannot view target Data Access Request' - }); - } -}; - -//redirect function on DAR draft to resume DAR application -const resumeDARApplication = (referenceId, history) => { - history.push(`/dar_application/${referenceId}`); -}; - -/* - Researcher -> Review: go to dar application page with disabled fields - -> Revise: use existing revise button functionality - -> Cancel: show modal to confirm collection cancellation - Since researcher console only gets collections that the user has made, we don't need - to do the sort of validations that are seen on the Chair or member actions. -*/ - -const baseCancelButtonStyle = Object.assign({}, Styles.TABLE.TABLE_ICON_BUTTON, {alignItems: 'center', marginLeft: '4%'}); -const hoverCancelButtonStyle = { - backgroundColor: 'red', - color: 'white' -}; - -const hoverPrimaryButtonStyle = { - backgroundColor: 'rgb(38 138 204)', - color: 'white' -}; - -//Function to determine if collection is revisable -//Should only show up if all of the DARs have a canceled status -const isCollectionRevisable = (dars = {}) => { - const elections = flow( - flatMap(dar => !isEmpty(dar.elections) ? Object.values(dar.elections) : []) - )(dars); - return isEmpty(elections) && allCanceledDars(dars); -}; - -//Function to determine if collection is cancelable -//Should only show up if none of the DARs have elections and not all DARs in the collection are canceled -const isCollectionCancelable = (dars = {}) => { - const hasDars = !isEmpty(dars); - const hasNoElections = flow( - filter(dar => lowerCase(dar.data.status) !== 'canceled'), - map(dar => dar.elections), - flatMap((electionMap = {}) => Object.values(electionMap)), - isEmpty - )(dars); - return hasDars && !allCanceledDars(dars) && hasNoElections; -}; - -const allCanceledDars = (dars = {}) => { - return every(dar => lowerCase(dar.data.status) === 'canceled')(dars); -}; - -export default function ResearcherActions(props) { - const { collection, showConfirmationModal } = props; - const collectionId = collection.darCollectionId; - const { dars } = collection; - const history = useHistory(); - - const [cancelEnabled, setCancelEnabled] = useState(false); - const [reviseEnabled, setReviseEnabled] = useState(false); - - useEffect(() => { - const isCancelable = isCollectionCancelable(dars); - const isRevisable = isCollectionRevisable(dars); - setCancelEnabled(isCancelable); - setReviseEnabled(isRevisable); - }, [dars]); - - const reviewButtonAttributes = { - keyProp: `researcher-review-${collectionId}`, - label: 'Review', - isRendered: true, - onClick: () => redirectToDARApplication(collectionId, history), - baseColor: 'white', - fontColor: Theme.palette.secondary, - hoverStyle: { - backgroundColor: Theme.palette.secondary, - color: 'white' - }, - additionalStyle: { - padding: '3%', - fontSize: '1.45rem', - fontWeight: 600, - border: `1px solid ${Theme.palette.secondary}` - }, - }; - - const cancelButtonAttributes = { - keyProp: `researcher-cancel-${collectionId}`, - label: 'Cancel', - isRendered: cancelEnabled && !collection.isDraft, - onClick: () => showConfirmationModal(collection, 'cancel'), - dataTip: 'Cancel Collection', - style: baseCancelButtonStyle, - hoverStyle: hoverCancelButtonStyle, - icon: Block - }; - - const deleteButtonAttributes = { - keyProp: `researcher-delete-${collectionId}`, - label: 'Delete', - isRendered: collection.isDraft === true, - onClick: () => showConfirmationModal(collection, 'delete'), - dataTip: 'Delete Collection Draft', - style: baseCancelButtonStyle, - hoverStyle: hoverCancelButtonStyle, - icon: Delete, - }; - - const resumeButtonAttributes = { - keyProp: `researcher-resume-${collectionId}`, - isRendered: collection.isDraft, - onClick: () => resumeDARApplication(collection.referenceId, history), - label: 'Resume', - baseColor: Theme.palette.secondary, - fontColor: 'white', - hoverStyle: hoverPrimaryButtonStyle, - additionalStyle: { - padding: '3%', - marginRight: '2%', - fontSize: '1.45rem', - fontWeight: 600, - border: `1px solid ${Theme.palette.secondary}`, - }, - }; - - const reviseButtonAttributes = { - keyProp: `revise-collection-${collectionId}`, - label: 'Revise', - baseColor: Theme.palette.secondary, - additionalStyle: { - padding: '3%', - fontSize: '1.45rem', - fontWeight: 600, - }, - hoverStyle: hoverPrimaryButtonStyle, - isRendered: !collection.isDraft && reviseEnabled, - onClick: () => showConfirmationModal(collection, 'revise'), - }; - - return div( - { - className: 'researcher-actions', - key: `researcher-actions-${collectionId}`, - id: `researcher-actions-${collectionId}`, - style: { - display: 'flex', - padding: '10px 5px', - justifyContent: 'flex-start', - alignItems: 'center', - columnGap: '1rem' - } - }, - [ - h(SimpleButton, reviseButtonAttributes), - h(SimpleButton, collection.isDraft ? resumeButtonAttributes : reviewButtonAttributes), - h(TableIconButton, deleteButtonAttributes), - h(TableIconButton, cancelButtonAttributes) - ] - ); -} \ No newline at end of file diff --git a/src/components/dar_drafts_table/DarDraftTable.js b/src/components/dar_drafts_table/DarDraftTable.js new file mode 100644 index 000000000..f3af9756d --- /dev/null +++ b/src/components/dar_drafts_table/DarDraftTable.js @@ -0,0 +1,254 @@ +import { useState, useEffect, Fragment, useCallback } from 'react'; +import { div, h } from 'react-hyperscript-helpers'; +import { isEmpty } from 'lodash/fp'; +import { Styles, Theme } from '../../libs/theme'; +import PaginationBar from '../PaginationBar'; +import SimpleButton from '../SimpleButton'; +import ConfirmationModal from '../modals/ConfirmationModal'; +import { + formatDate, + recalculateVisibleTable, + goToPage as updatePage +} from '../../libs/utils'; +import SimpleTable from '../SimpleTable'; + +const styles = { + baseStyle: { + fontFamily: 'Arial', + fontSize: '1.6rem', + fontWeight: 400, + display: 'flex', + padding: '1rem 2%', + justifyContent: 'space-between', + alignItems: 'center', + }, + columnStyle: Object.assign({}, Styles.TABLE.HEADER_ROW, { + justifyContent: 'space-between', + }), + cellWidth: { + partialDarCode: '15%', + projectTitle: '30%', + updateDate: '15%', + actions: '25%', + }, +}; + +//data struct to outline column widths and header labels +const columnHeaderFormat = { + partialDarCode: {label: 'Partial DAR Code', cellStyle: { width: styles.cellWidth.partialDarCode}}, + projectTitle: {label: 'Title', cellStyle: { width: styles.cellWidth.projectTitle}}, + updateDate: {label: 'Last Updated', cellStyle: {width: styles.cellWidth.updateDate}}, + actions: {label: 'DAR Actions', cellStyle: { width: styles.cellWidth.actions}} +}; + +//basic helper function to format above keys in fixed order array +const columnHeaderData = () => { + const { partialDarCode, projectTitle, updateDate, actions } = columnHeaderFormat; + return [partialDarCode, projectTitle, updateDate, actions]; +}; + +//helper function to create table metadata for dar code cell data format +const partialDarCodeCell = ({partialDarCode = '- -', id, style = {}, label = 'partial-dar-code'}) => { + return { + data: partialDarCode, + id, + style, + label + }; +}; + +//helper function to create table metadata for projectTitle cell data format +const projectTitleCell = ({projectTitle = '- -', id, style = {}, label = 'draft-project-title' }) => { + return { + data: projectTitle, + id, + style, + label + }; +}; + +//helper function to create table metadata for update date cell metadata +const updateDateCell = ({updateDate, id, style = {}, label = 'draft-update-date'}) => { + return { + data: formatDate(updateDate), + id, + style, + label + }; +}; + +//sub-component that redirects user to draft applictaion page +const ResumeDraftButton = (props) => { + const { draft, history } = props; + const { referenceId } = draft; + return h(SimpleButton, { + keyProp: `resume-draft-${referenceId}`, + label: 'Resume', + baseColor: Theme.palette.secondary, + additionalStyle: { + width: '30%', + padding: '2%', + marginRight: '2%', + fontSize: '1.45rem' + }, + onClick: () => history.push(`/dar_application/${referenceId}`) + }); +}; + +//helper function to create id string for error messages +const getIdentifier = ({id, data}) => { + return !isEmpty(data) ? (data.projectTitle || data.partialDarCode) : id; +}; + +//sub-component that renders draft delete button +const DeleteDraftButton = (props) => { + const { targetDraft, showConfirmationModal } = props; + const { id, data } = targetDraft; + const identifier = getIdentifier({id, data}); + return h(SimpleButton, { + keyProp: `delete-draft-${identifier}`, + label: 'Delete', + baseColor: Theme.palette.error, + additionalStyle: { + width: '30%', + padding: '2%', + fontSize: '1.45rem', + }, + onClick: () => showConfirmationModal(targetDraft), + }); +}; + +//helper function that formats table metadata and components for draft action buttons +const actionCellData = ({ + targetDraft, + showConfirmationModal, + history, +}) => { + const { id } = targetDraft; + const deleteButtonTemplate = h(DeleteDraftButton, { + targetDraft, + showConfirmationModal + }); + const resumeButtonTemplate = h(ResumeDraftButton, { + draft: targetDraft, + history, + }); + + return { + isComponent: true, + label: 'draft-buttons', + data: div( + { + style: { + display: 'flex', + justifyContent: 'left', + }, + key: `draft-buttons-cell-${id}`, + }, + [resumeButtonTemplate, deleteButtonTemplate] + ), + }; +}; + +//helper function that foramts table metadata for cells row by row, returns an array of arrays +const processDraftsRowData = ({visibleDrafts, showConfirmationModal, history}) => { + if(!isEmpty(visibleDrafts)) { + return visibleDrafts.map((draft) => { + const { id, data, createDate, updateDate } = draft; + const { projectTitle } = !isEmpty(data) ? data : {}; + const partialDarCode = 'DRAFT_DAR_' + formatDate(createDate); + return [ + partialDarCodeCell({partialDarCode, id}), + projectTitleCell({projectTitle, id}), + updateDateCell({updateDate: updateDate || createDate, id}), + actionCellData({targetDraft: draft, showConfirmationModal, history}) + ]; + }); + } +}; + +export default function DarDraftTable(props) { + const [visibleDrafts, setVisibleDrafts] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [pageCount, setPageCount] = useState(1); + const [tableSize, setTableSize] = useState(10); + const [showConfirmation, setShowConfirmation] = useState(false); + const [selectedDraft, setSelectedDraft] = useState({}); + + const { history, deleteDraft, isLoading, drafts } = props; + + //sequence of events that fires when table either changes its visible window (data updates, page change, table size change, etc) + useEffect(() => { + recalculateVisibleTable({ + tableSize, + pageCount, + filteredList: drafts, + currentPage, + setPageCount, + setCurrentPage, + setVisibleList: setVisibleDrafts + }); + }, [tableSize, pageCount, currentPage, drafts]); + + //callback function to change tableSize, passed to pagination bar + const changeTableSize = useCallback((value) => { + if (value > 0 && !isNaN(parseInt(value))) { + setTableSize(value); + } + }, []); + + //function that fires on delete button press, makes confirmation modal visible and selectes target draft + const showConfirmationModal = (draft) => { + setSelectedDraft(draft); + setShowConfirmation(true); + }; + + //function that fires when user updates current page (buttons, manual input), passed to pagination bar + const goToPage = useCallback( + (value) => { + updatePage(value, pageCount, setCurrentPage); + }, + [pageCount] + ); + + //helper function that formats string for modal header + const getModalHeader = () => { + if(!isEmpty(selectedDraft)) { + const { data, id } = selectedDraft; + return getIdentifier({id, data}); + } + }; + + //delete function that fires on modal confirmation, removes draft from listing via prop delete function + const deleteOnClick = async() => { + const {id, data, referenceIds} = selectedDraft; + const identifier = getIdentifier({id: id || referenceIds, data}); + await deleteDraft({ referenceIds, identifier, }); + setShowConfirmation(false); + }; + + return h(Fragment, {}, [ + h(SimpleTable, { + isLoading, + rowData: processDraftsRowData({visibleDrafts, showConfirmationModal, history}), + columnHeaders: columnHeaderData(), + styles, + tableSize, + paginationBar: h(PaginationBar, { + pageCount, + currentPage, + tableSize, + goToPage, + changeTableSize + }) + }), + h(ConfirmationModal, { + showConfirmation, + closeConfirmation: () => setShowConfirmation(false), + title: 'Delete Draft Data Access Request', + message: `Are you sure you want to delete Data Access Request draft ${getIdentifier({id: selectedDraft.id, data: selectedDraft.data})}`, + header: getModalHeader(), + onConfirm: deleteOnClick + }) + ]); +} diff --git a/src/libs/ajax.js b/src/libs/ajax.js index 18c84a30a..5673cb257 100644 --- a/src/libs/ajax.js +++ b/src/libs/ajax.js @@ -31,6 +31,16 @@ axios.interceptors.response.use(function (response) { return Promise.reject(error); }); +export const getApiUrl = async(baseUrl = '') => { + const env = await Config.getEnv(); + return env === 'local' ? baseUrl : await Config.getApiUrl(); +}; + +export const getOntologyUrl = async(baseUrl = '') => { + const env = await Config.getEnv(); + return env === 'local' ? baseUrl : await Config.getOntologyApiUrl(); +}; + const dataTemplate = { accessTotal: [ ['Results', 'Votes'], @@ -78,70 +88,70 @@ const dataTemplate = { export const DAC = { list: async (withUsers) => { - const url = `${await Config.getApiUrl()}/api/dac` + (fp.isEmpty(withUsers) ? '' : `?withUsers=${withUsers}`); + const url = `${await getApiUrl()}/api/dac` + (fp.isEmpty(withUsers) ? '' : `?withUsers=${withUsers}`); const res = await fetchOk(url, Config.authOpts()); return res.json(); }, create: async (name, description) => { - const url = `${await Config.getApiUrl()}/api/dac`; + const url = `${await getApiUrl()}/api/dac`; const dac = { 'name': name, 'description': description }; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(dac), { method: 'POST' }])); return res.json(); }, update: async (dacId, name, description) => { - const url = `${await Config.getApiUrl()}/api/dac`; + const url = `${await getApiUrl()}/api/dac`; const dac = { 'dacId': dacId, 'name': name, 'description': description }; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(dac), { method: 'PUT' }])); return res.json(); }, delete: async (dacId) => { - const url = `${await Config.getApiUrl()}/api/dac/${dacId}`; + const url = `${await getApiUrl()}/api/dac/${dacId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); //we do not call .json() on res because the response has no body return res; }, get: async (dacId) => { - const url = `${await Config.getApiUrl()}/api/dac/${dacId}`; + const url = `${await getApiUrl()}/api/dac/${dacId}`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, datasets: async (dacId) => { - const url = `${await Config.getApiUrl()}/api/dac/${dacId}/datasets`; + const url = `${await getApiUrl()}/api/dac/${dacId}/datasets`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, autocompleteUsers: async (term) => { - const url = `${await Config.getApiUrl()}/api/dac/users/${term}`; + const url = `${await getApiUrl()}/api/dac/users/${term}`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, addDacChair: async (dacId, userId) => { - const url = `${await Config.getApiUrl()}/api/dac/${dacId}/chair/${userId}`; + const url = `${await getApiUrl()}/api/dac/${dacId}/chair/${userId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'POST' }])); return res.status; }, removeDacChair: async (dacId, userId) => { - const url = `${await Config.getApiUrl()}/api/dac/${dacId}/chair/${userId}`; + const url = `${await getApiUrl()}/api/dac/${dacId}/chair/${userId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); return res.status; }, addDacMember: async (dacId, userId) => { - const url = `${await Config.getApiUrl()}/api/dac/${dacId}/member/${userId}`; + const url = `${await getApiUrl()}/api/dac/${dacId}/member/${userId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'POST' }])); return res.status; }, removeDacMember: async (dacId, userId) => { - const url = `${await Config.getApiUrl()}/api/dac/${dacId}/member/${userId}`; + const url = `${await getApiUrl()}/api/dac/${dacId}/member/${userId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); return res.status; } @@ -149,38 +159,38 @@ export const DAC = { export const Collections = { getCollectionsForResearcher: async () => { - const url = `${await Config.getApiUrl()}/api/collections`; + const url = `${await getApiUrl()}/api/collections`; const res = await axios.get(url, Config.authOpts()); return res.data; }, cancelCollection: async(id, roleName) => { - const url = `${await Config.getApiUrl()}/api/collections/${id}/cancel`; + const url = `${await getApiUrl()}/api/collections/${id}/cancel`; const config = Object.assign({params: {roleName}}, Config.authOpts()); const res = await axios.put(url, {}, config); return res.data; }, reviseCollection: async(id) => { - const url = `${await Config.getApiUrl()}/api/collections/${id}/resubmit`; + const url = `${await getApiUrl()}/api/collections/${id}/resubmit`; const res = await axios.put(url, {}, Config.authOpts()); return res.data; }, getCollectionById: async(id) => { - const url = `${await Config.getApiUrl()}/api/collections/${id}`; + const url = `${await getApiUrl()}/api/collections/${id}`; const res = await axios.get(url, Config.authOpts()); return res.data; }, getCollectionsByRoleName: async(roleName) => { - const url = `${await Config.getApiUrl()}/api/collections/role/${roleName}`; + const url = `${await getApiUrl()}/api/collections/role/${roleName}`; const res = await axios.get(url, Config.authOpts()); return res.data; }, getCollectionSummariesByRoleName: async(roleName) => { - const url = `${await Config.getApiUrl()}/api/collections/role/${roleName}/summary`; + const url = `${await getApiUrl()}/api/collections/role/${roleName}/summary`; const res = await axios.get(url, Config.authOpts()); return res.data; }, openElectionsById: async(id) => { - const url = `${await Config.getApiUrl()}/api/collections/${id}/election`; + const url = `${await getApiUrl()}/api/collections/${id}/election`; const res = await axios.post(url, {}, Config.authOpts()); return res.data; } @@ -190,7 +200,7 @@ export const DAR = { //v2 get for DARs describeDar: async (darId) => { - const apiUrl = await Config.getApiUrl(); + const apiUrl = await getApiUrl(); const authOpts = Config.authOpts(); const rawDarRes = await axios.get(`${apiUrl}/api/dar/v2/${darId}`, authOpts); const rawDar = rawDarRes.data; @@ -261,41 +271,41 @@ export const DAR = { //v2 get for DARs getPartialDarRequest: async darId => { - const url = `${await Config.getApiUrl()}/api/dar/v2/${darId}`; + const url = `${await getApiUrl()}/api/dar/v2/${darId}`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, //v2 update for dar partials updateDarDraft: async (dar, referenceId) => { - const url = `${await Config.getApiUrl()}/api/dar/v2/draft/${referenceId}`; + const url = `${await getApiUrl()}/api/dar/v2/draft/${referenceId}`; const res = await axios.put(url, dar, Config.authOpts()); return res.data; }, //api endpoint for v2 draft submission postDarDraft: async(dar) => { - const url = `${await Config.getApiUrl()}/api/dar/v2/draft/`; + const url = `${await getApiUrl()}/api/dar/v2/draft/`; const res = await axios.post(url, dar, Config.authOpts()); return res.data; }, //v2 delete dar deleteDar: async (darId) => { - const url = `${await Config.getApiUrl()}/api/dar/v2/${darId}`; + const url = `${await getApiUrl()}/api/dar/v2/${darId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); return await res; }, //v2 get draft dars getDraftDarRequestList: async () => { - const url = `${await Config.getApiUrl()}/api/dar/v2/draft/manage`; + const url = `${await getApiUrl()}/api/dar/v2/draft/manage`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, getDarConsent: async id => { - const url = `${await Config.getApiUrl()}/api/dar/find/${id}/consent`; + const url = `${await getApiUrl()}/api/dar/find/${id}/consent`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, @@ -303,25 +313,25 @@ export const DAR = { //v2 endpoint for DAR POST postDar: async (dar) => { const filteredDar = fp.omit(['createDate', 'sortDate', 'data_access_request_id'])(dar); - const url = `${await Config.getApiUrl()}/api/dar/v2`; + const url = `${await getApiUrl()}/api/dar/v2`; const res = axios.post(url, filteredDar, Config.authOpts()); return await res.data; }, cancelDar: async referenceId => { - const url = `${await Config.getApiUrl()}/api/dar/cancel/${referenceId}`; + const url = `${await getApiUrl()}/api/dar/cancel/${referenceId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'PUT' }])); return await res.json(); }, getAutoCompleteDS: async partial => { - const url = `${await Config.getApiUrl()}/api/dataset/autocomplete/${partial}`; + const url = `${await getApiUrl()}/api/dataset/autocomplete/${partial}`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, getAutoCompleteOT: async partial => { - const url = `${await Config.getOntologyApiUrl()}/autocomplete?q=${partial}`; + const url = `${await getOntologyUrl()}/autocomplete?q=${partial}`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, @@ -332,16 +342,16 @@ export const DAR = { 'Content-Type': 'application/octet-stream', 'Accept': 'application/octet-stream' }); - const url = `${await Config.getApiUrl()}/api/dar/v2/${referenceId}/${fileType}`; + const url = `${await getApiUrl()}/api/dar/v2/${referenceId}/${fileType}`; const res = await axios.get(url, authOpts); return res.data; }, //new manage endpoint, should be renamed once v1 variant is removed from use getDataAccessManageV2: async(roleName) => { - let url = `${await Config.getApiUrl()}/api/dar/manage/v2`; + let url = `${await getApiUrl()}/api/dar/manage/v2`; if (!isNil(roleName)) { - url = `${await Config.getApiUrl()}/api/dar/manage/v2/?roleName=${roleName}`; + url = `${await getApiUrl()}/api/dar/manage/v2/?roleName=${roleName}`; } const res = await axios.get(url, Config.authOpts()); return res.data; @@ -366,7 +376,7 @@ export const DAR = { authOpts.headers['Content-Type'] = 'multipart/form-data'; let formData = new FormData(); formData.append('file', file); - const url = `${await Config.getApiUrl()}/api/dar/v2/${darId}/${fileType}`; + const url = `${await getApiUrl()}/api/dar/v2/${darId}/${fileType}`; return axios.post(url, formData, authOpts); } } @@ -375,13 +385,13 @@ export const DAR = { export const DataSet = { postDatasetForm: async (form) => { - const url = `${await Config.getApiUrl()}/api/dataset/v2`; + const url = `${await getApiUrl()}/api/dataset/v2`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(form), { method: 'POST' }])); return await res.json(); }, getDatasets: async () => { - const url = `${await Config.getApiUrl()}/api/dataset`; + const url = `${await getApiUrl()}/api/dataset`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, @@ -396,13 +406,13 @@ export const DataSet = { }, getDataSetsByDatasetId: async dataSetId => { - const url = `${await Config.getApiUrl()}/api/dataset/${dataSetId}`; + const url = `${await getApiUrl()}/api/dataset/${dataSetId}`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, downloadDataSets: async (objectIdList, fileName) => { - const url = `${await Config.getApiUrl()}/api/dataset/download`; + const url = `${await getApiUrl()}/api/dataset/download`; const res = await fetchOk(url, fp.mergeAll([Config.jsonBody(objectIdList), Config.fileOpts(), { method: 'POST' }])); fileName = fileName === null ? getFileNameFromHttpResponse(res) : fileName; @@ -417,30 +427,30 @@ export const DataSet = { }, deleteDataset: async (datasetObjectId) => { - const url = `${await Config.getApiUrl()}/api/dataset/${datasetObjectId}`; + const url = `${await getApiUrl()}/api/dataset/${datasetObjectId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); return await res; }, disableDataset: async (datasetObjectId, active) => { - const url = `${await Config.getApiUrl()}/api/dataset/disable/${datasetObjectId}/${active}`; + const url = `${await getApiUrl()}/api/dataset/disable/${datasetObjectId}/${active}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); return res; }, reviewDataSet: async (dataSetId, needsApproval) => { - const url = `${await Config.getApiUrl()}/api/dataset?dataSetId=${dataSetId}&needsApproval=${needsApproval}`; + const url = `${await getApiUrl()}/api/dataset?dataSetId=${dataSetId}&needsApproval=${needsApproval}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'PUT' }])); return res.json(); }, updateDataset: async (datasetId, dataSetObject) => { - const url = `${await Config.getApiUrl()}/api/dataset/${datasetId}`; + const url = `${await getApiUrl()}/api/dataset/${datasetId}`; return await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(dataSetObject), {method: 'PUT'}])); }, validateDatasetName: async (name) => { - const url = `${await Config.getApiUrl()}/api/dataset/validate?name=${name}`; + const url = `${await getApiUrl()}/api/dataset/validate?name=${name}`; try { // We expect a 404 in the case where the dataset name does not exist const res = await fetchAny(url, fp.mergeAll([Config.authOpts(), {method: 'GET'}])); @@ -458,19 +468,19 @@ export const DataSet = { export const DatasetAssociation = { createDatasetAssociations: async (objectId, usersIdList) => { - const url = `${await Config.getApiUrl()}/api/datasetAssociation/${objectId}`; + const url = `${await getApiUrl()}/api/datasetAssociation/${objectId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(usersIdList), { method: 'POST' }])); return await res.json(); }, getAssociatedAndToAssociateUsers: async (objectId) => { - const url = `${await Config.getApiUrl()}/api/datasetAssociation/${objectId}`; + const url = `${await getApiUrl()}/api/datasetAssociation/${objectId}`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, updateDatasetAssociations: async (objectId, usersIdList) => { - const url = `${await Config.getApiUrl()}/api/datasetAssociation/${objectId}`; + const url = `${await getApiUrl()}/api/datasetAssociation/${objectId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(usersIdList), { method: 'PUT' }])); return res.json(); } @@ -480,19 +490,19 @@ export const DatasetAssociation = { export const Election = { getElectionVotes: async (electionId) => { - const url = `${await Config.getApiUrl()}/api/election/${electionId}/votes`; + const url = `${await getApiUrl()}/api/election/${electionId}/votes`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, findElectionByDarId: async (requestId) => { - const url = `${await Config.getApiUrl()}/api/dataRequest/${requestId}/election`; + const url = `${await getApiUrl()}/api/dataRequest/${requestId}/election`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, findDataAccessElectionReview: async (electionId) => { - const url = `${await Config.getApiUrl()}/api/electionReview/access/${electionId}`; + const url = `${await getApiUrl()}/api/electionReview/access/${electionId}`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, @@ -500,7 +510,7 @@ export const Election = { // RP Election Information. Can be null for manual review DARs. // N.B. We get the rpElectionReview from the Access election id, not the rp election id. This is a legacy behavior. findRPElectionReview: async (electionId) => { - const url = `${await Config.getApiUrl()}/api/electionReview/rp/${electionId}`; + const url = `${await getApiUrl()}/api/electionReview/rp/${electionId}`; const res = await fetchOk(url, Config.authOpts()); if (res.status === 204) { return {}; @@ -509,14 +519,14 @@ export const Election = { }, updateElection: async (electionId, document) => { - const url = `${await Config.getApiUrl()}/api/election/${electionId}`; + const url = `${await getApiUrl()}/api/election/${electionId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(document), { method: 'PUT' }])); return await res.json(); }, createDARElection: async (requestId) => { const election = { status: 'Open', finalAccessVote: false }; - const url = `${await Config.getApiUrl()}/api/dataRequest/${requestId}/election`; + const url = `${await getApiUrl()}/api/dataRequest/${requestId}/election`; const res = await fetchOk(url, fp.mergeAll([Config.jsonBody(election), Config.authOpts(), { method: 'POST' }])); return res.json(); } @@ -525,7 +535,7 @@ export const Election = { export const Email = { sendReminderEmail: async (voteId) => { - const url = `${await Config.getApiUrl()}/api/emailNotifier/reminderMessage/${voteId}`; + const url = `${await getApiUrl()}/api/emailNotifier/reminderMessage/${voteId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'POST' }])); return res; } @@ -535,7 +545,7 @@ export const Email = { export const Files = { getApprovedUsersFile: async (fileName, dataSetId) => { - const url = `${await Config.getApiUrl()}/api/dataset/${dataSetId}/approved/users`; + const url = `${await getApiUrl()}/api/dataset/${dataSetId}/approved/users`; return getFile(url, fileName); } }; @@ -543,7 +553,7 @@ export const Files = { export const Summary = { getFile: async (fileName) => { const URI = `/api/consent/cases/summary/file?type=${fileName}`; - const url = `${await Config.getApiUrl()}/${URI}`; + const url = `${await getApiUrl()}/${URI}`; if (fileName === 'TranslateDUL') { return await getFile(url, 'DUL_summary.tsv'); } else { @@ -555,7 +565,7 @@ export const Summary = { export const Metrics = { getDatasetStats: async (datasetId) => { - const url = `${await Config.getApiUrl()}/metrics/dataset/${datasetId}`; + const url = `${await getApiUrl()}/metrics/dataset/${datasetId}`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); } @@ -601,14 +611,14 @@ export const Support = { export const Match = { findMatch: async (consentId, purposeId) => { - const url = `${await Config.getApiUrl()}/api/match/${consentId}/${purposeId}`; + const url = `${await getApiUrl()}/api/match/${consentId}/${purposeId}`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, findMatchBatch: async (purposeIdsArr = []) => { const purposeIds = purposeIdsArr.join(','); - const url = `${await Config.getApiUrl()}/api/match/purpose/batch`; + const url = `${await getApiUrl()}/api/match/purpose/batch`; const config = Object.assign({}, Config.authOpts(), {params: { purposeIds}}); const res = await axios.get(url, config); return res.data; @@ -618,7 +628,7 @@ export const Match = { export const PendingCases = { findDataRequestPendingCases: async () => { - const url = `${await Config.getApiUrl()}/api/dataRequest/cases/pending`; + const url = `${await getApiUrl()}/api/dataRequest/cases/pending`; const res = await fetchOk(url, Config.authOpts()); const dars = await res.json(); let resp = { @@ -638,7 +648,7 @@ export const PendingCases = { findConsentPendingCasesByUser: async (userId) => { - const url = `${await Config.getApiUrl()}/api/consent/cases/pending/${userId}`; + const url = `${await getApiUrl()}/api/consent/cases/pending/${userId}`; const res = await fetchOk(url, Config.authOpts()); const duls = await res.json(); @@ -658,22 +668,22 @@ export const PendingCases = { findConsentUnReviewed: async () => { - const url = `${await Config.getApiUrl()}/api/consent/unreviewed`; + const url = `${await getApiUrl()}/api/consent/unreviewed`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, findDARUnReviewed: async () => { - const url = `${await Config.getApiUrl()}/api/dar/cases/unreviewed`; + const url = `${await getApiUrl()}/api/dar/cases/unreviewed`; const res = await fetchOk(url, Config.authOpts()); return await res.json(); }, findSummary: async () => { - const consentUrl = `${await Config.getApiUrl()}/api/consent/cases/summary`; - const dataAccessUrl = `${await Config.getApiUrl()}/api/dataRequest/cases/summary/DataAccess`; - const rpUrl = `${await Config.getApiUrl()}/api/dataRequest/cases/summary/RP`; - const matchUrl = `${await Config.getApiUrl()}/api/dataRequest/cases/matchsummary`; + const consentUrl = `${await getApiUrl()}/api/consent/cases/summary`; + const dataAccessUrl = `${await getApiUrl()}/api/dataRequest/cases/summary/DataAccess`; + const rpUrl = `${await getApiUrl()}/api/dataRequest/cases/summary/RP`; + const matchUrl = `${await getApiUrl()}/api/dataRequest/cases/matchsummary`; const accessResponse = await fetchOk(consentUrl, Config.authOpts()); const access = await accessResponse.json(); let data = dataTemplate; @@ -729,13 +739,13 @@ export const PendingCases = { export const Researcher = { createProperties: async (properties) => { - const url = `${await Config.getApiUrl()}/api/user/profile`; + const url = `${await getApiUrl()}/api/user/profile`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(properties), { method: 'POST' }])); return await res; }, updateProperties: async (userId, validate, properties) => { - const url = `${await Config.getApiUrl()}/api/user/profile?validate=${validate}`; + const url = `${await getApiUrl()}/api/user/profile?validate=${validate}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(properties), { method: 'PUT' }])); return res.json(); }, @@ -745,7 +755,7 @@ export const Researcher = { export const StatFiles = { getDARsReport: async (reportType, fileName) => { - const url = `${await Config.getApiUrl()}/api/dataRequest/${reportType}`; + const url = `${await getApiUrl()}/api/dataRequest/${reportType}`; return getFile(url, fileName); } }; @@ -756,31 +766,31 @@ export const User = { //Therefore the possibility that the email registered to a DAR will differ from the researcher's current email, leading to invalid queries //Instead, use getById for more predictable results getByEmail: async email => { - const url = `${await Config.getApiUrl()}/api/dacuser/${email}`; + const url = `${await getApiUrl()}/api/dacuser/${email}`; const res = await axios.get(url, Config.authOpts()); return res.data; }, getMe: async () => { - const url = `${await Config.getApiUrl()}/api/user/me`; + const url = `${await getApiUrl()}/api/user/me`; const res = await axios.get(url, Config.authOpts()); return res.data; }, getById: async id => { - const url = `${await Config.getApiUrl()}/api/user/${id}`; + const url = `${await getApiUrl()}/api/user/${id}`; const res = await axios.get(url, Config.authOpts()); return res.data; }, list: async roleName => { - const url = `${await Config.getApiUrl()}/api/user/role/${roleName}`; + const url = `${await getApiUrl()}/api/user/role/${roleName}`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, create: async user => { - const url = `${await Config.getApiUrl()}/api/dacuser`; + const url = `${await getApiUrl()}/api/dacuser`; try { const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(user), { method: 'POST' }])); if (res.ok) { @@ -792,7 +802,7 @@ export const User = { }, updateSelf: async (payload) => { - const url = `${await Config.getApiUrl()}/api/user`; + const url = `${await getApiUrl()}/api/user`; // We should not be updating the user's create date, associated institution, or library cards try { const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(payload), { method: 'PUT' }])); @@ -805,7 +815,7 @@ export const User = { }, update: async (user, userId) => { - const url = `${await Config.getApiUrl()}/api/user/${userId}`; + const url = `${await getApiUrl()}/api/user/${userId}`; // We should not be updating the user's create date, associated institution, or library cards let filteredUser = flow( cloneDeep, @@ -824,36 +834,36 @@ export const User = { }, registerUser: async () => { - const url = `${await Config.getApiUrl()}/api/user`; + const url = `${await getApiUrl()}/api/user`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'POST' }])); return res.json(); }, getSOsForCurrentUser: async () => { - const url = `${await Config.getApiUrl()}/api/user/signing-officials`; + const url = `${await getApiUrl()}/api/user/signing-officials`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'GET' }])); return res.json(); }, getUnassignedUsers: async () => { - const url = `${await Config.getApiUrl()}/api/user/institution/unassigned`; + const url = `${await getApiUrl()}/api/user/institution/unassigned`; const res = await axios.get(url, Config.authOpts()); return res.data; }, addRoleToUser: async (userId, roleId) => { - const url = `${await Config.getApiUrl()}/api/user/${userId}/${roleId}`; + const url = `${await getApiUrl()}/api/user/${userId}/${roleId}`; const res = await fetchAny(url, fp.mergeAll([Config.authOpts(), { method: 'PUT' }])); return res.json(); }, deleteRoleFromUser: async (userId, roleId) => { - const url = `${await Config.getApiUrl()}/api/user/${userId}/${roleId}`; + const url = `${await getApiUrl()}/api/user/${userId}/${roleId}`; const res = await fetchAny(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); return res.json(); }, getUserRelevantDatasets: async() => { - const url = `${await Config.getApiUrl()}/api/user/me/dac/datasets`; + const url = `${await getApiUrl()}/api/user/me/dac/datasets`; const res = await axios.get(url, Config.authOpts()); return res.data; } @@ -867,7 +877,7 @@ export const Votes = { postObject.dacUserId = vote.dacUserId; postObject.rationale = vote.rationale; postObject.hasConcerns = vote.hasConcerns; - const url = `${await Config.getApiUrl()}/api/dataRequest/${requestId}/vote/${vote.voteId}`; + const url = `${await getApiUrl()}/api/dataRequest/${requestId}/vote/${vote.voteId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(postObject), { method: 'POST' }])); return res.json(); }, @@ -878,13 +888,13 @@ export const Votes = { postObject.dacUserId = vote.dacUserId; postObject.rationale = vote.rationale; postObject.hasConcerns = vote.hasConcerns; - const url = `${await Config.getApiUrl()}/api/dataRequest/${requestId}/vote/${vote.voteId}`; + const url = `${await getApiUrl()}/api/dataRequest/${requestId}/vote/${vote.voteId}`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(postObject), { method: 'PUT' }])); return await res.json(); }, updateFinalAccessDarVote: async (requestId, vote) => { - const url = `${await Config.getApiUrl()}/api/dataRequest/${requestId}/vote/${vote.voteId}/final`; + const url = `${await getApiUrl()}/api/dataRequest/${requestId}/vote/${vote.voteId}/final`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(vote), { method: 'POST' }])); return await res.json(); }, @@ -895,7 +905,7 @@ export const Votes = { voteUpdate.rationale = vote.rationale; voteUpdate.voteIds = voteIds; - let url = `${await Config.getApiUrl()}/api/votes`; + let url = `${await getApiUrl()}/api/votes`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(voteUpdate), { method: 'PUT' }])); return await res.json(); }, @@ -905,7 +915,7 @@ export const Votes = { rationaleUpdate.rationale = rationale; rationaleUpdate.voteIds = voteIds; - let url = `${await Config.getApiUrl()}/api/votes/rationale`; + let url = `${await getApiUrl()}/api/votes/rationale`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(rationaleUpdate), { method: 'PUT' }])); return await res.json(); } @@ -942,13 +952,13 @@ export const AuthenticateNIH = { }, saveNihUsr: async (decodedData) => { - const url = `${await Config.getApiUrl()}/api/nih`; + const url = `${await getApiUrl()}/api/nih`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), Config.jsonBody(decodedData), { method: 'POST' }])); return await res.json(); }, eliminateAccount: async () => { - const url = `${await Config.getApiUrl()}/api/nih`; + const url = `${await getApiUrl()}/api/nih`; const res = await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); return await res; }, @@ -976,60 +986,60 @@ export const AuthenticateNIH = { export const Institution = { list: async () => { - const url = `${await Config.getApiUrl()}/api/institutions`; + const url = `${await getApiUrl()}/api/institutions`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, getById: async (id) => { - const url = `${await Config.getApiUrl()}/api/institutions/${id}`; + const url = `${await getApiUrl()}/api/institutions/${id}`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, postInstitution: async (institution) => { - const url = `${await Config.getApiUrl()}/api/institutions`; + const url = `${await getApiUrl()}/api/institutions`; const res = await axios.post(url, institution, Config.authOpts()); return res.data; }, putInstitution: async (id, institution) => { - const url = `${await Config.getApiUrl()}/api/institutions/${id}`; + const url = `${await getApiUrl()}/api/institutions/${id}`; const res = await axios.put(url, institution, Config.authOpts()); return res.data; }, deleteInstitution: async (id) => { - const url = `${await Config.getApiUrl()}/api/institutions/${id}`; + const url = `${await getApiUrl()}/api/institutions/${id}`; return await fetchOk(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); } }; export const LibraryCard = { getAllLibraryCards: async () => { - const url = `${await Config.getApiUrl()}/api/libraryCards`; + const url = `${await getApiUrl()}/api/libraryCards`; const res = await axios.get(url, Config.authOpts()); return res.data; }, createLibraryCard: async (card) => { - const url = `${await Config.getApiUrl()}/api/libraryCards`; + const url = `${await getApiUrl()}/api/libraryCards`; const res = await axios.post(url, card, Config.authOpts()); return res.data; }, updateLibraryCard: async (card) => { - const url = `${await Config.getApiUrl()}/api/libraryCards/${card.id}`; + const url = `${await getApiUrl()}/api/libraryCards/${card.id}`; const res = await axios.put(url, card, Config.authOpts()); return res.data; }, deleteLibraryCard: async (id) => { - const url = `${await Config.getApiUrl()}/api/libraryCards/${id}`; + const url = `${await getApiUrl()}/api/libraryCards/${id}`; return await axios.delete(url, Config.authOpts()); } }; export const ToS = { getDUOSText: async () => { - const url = `${await Config.getApiUrl()}/tos/text/duos`; + const url = `${await getApiUrl()}/tos/text/duos`; const res = await axios.get(url, Config.textPlain()); return res.data; }, @@ -1047,17 +1057,17 @@ export const ToS = { * @returns {Promise} */ getStatus: async () => { - const url = `${await Config.getApiUrl()}/api/sam/register/self/diagnostics`; + const url = `${await getApiUrl()}/api/sam/register/self/diagnostics`; const res = await axios.get(url, Config.authOpts()); return res.data; }, acceptToS: async () => { - const url = `${await Config.getApiUrl()}/api/sam/register/self/tos`; + const url = `${await getApiUrl()}/api/sam/register/self/tos`; const res = await axios.post(url, {}, Config.authOpts()); return res.data; }, rejectToS: async () => { - const url = `${await Config.getApiUrl()}/api/sam/register/self/tos`; + const url = `${await getApiUrl()}/api/sam/register/self/tos`; const res = await axios.delete(url, Config.authOpts()); return res.data; } diff --git a/src/libs/notificationService.js b/src/libs/notificationService.js index 5a2ecd282..a70c85ea9 100644 --- a/src/libs/notificationService.js +++ b/src/libs/notificationService.js @@ -15,7 +15,12 @@ export const NotificationService = { */ getBanners: async () => { const env = await Config.getEnv(); - return $.getJSON(gcs + '/' + env + '_' + bannerFileName, (data) => { + const hash = await Config.getHash(); + const url = + env === 'local' + ? `/broad-duos-banner/${hash}_${bannerFileName}` + : gcs + '/' + env + '_' + bannerFileName; + return $.getJSON(url, (data) => { return data; }); }, diff --git a/src/libs/ontologyService.js b/src/libs/ontologyService.js index 954c8fb5c..8c697d6d2 100644 --- a/src/libs/ontologyService.js +++ b/src/libs/ontologyService.js @@ -1,12 +1,12 @@ import axios from 'axios'; -import { Config } from '../libs/config'; import { Notifications } from '../libs/utils'; +import { getOntologyUrl } from './ajax'; export async function searchOntology(obolibraryURL) { - const baseURL = await Config.getOntologyApiUrl(); + const baseURL = await getOntologyUrl(); const params = {id: obolibraryURL}; try{ - let resp = await axios.get(`${baseURL}search`, {params}); + let resp = await axios.get(`${baseURL}/search`, {params}); return resp.data; } catch(error) { Notifications.showError('Error: Ontology Search Request failed'); diff --git a/src/pages/ResearcherConsole.js b/src/pages/ResearcherConsole.js index 1a25edd24..2426d6515 100644 --- a/src/pages/ResearcherConsole.js +++ b/src/pages/ResearcherConsole.js @@ -1,6 +1,6 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { div, h, img } from 'react-hyperscript-helpers'; -import {cloneDeep, map, findIndex, isEmpty, flow, concat, uniqBy} from 'lodash/fp'; +import {cloneDeep, findIndex} from 'lodash/fp'; import { Styles } from '../libs/theme'; import { Collections, DAR } from '../libs/ajax'; import { DarCollectionTableColumnOptions, DarCollectionTable } from '../components/dar_collection_table/DarCollectionTable'; @@ -39,15 +39,6 @@ export default function ResearcherConsole() { const [filteredList, setFilteredList] = useState(); const searchRef = useRef(''); - //basic helper to process promises for collections and drafts in init - const handlePromise = (promise, targetArray, errorMsg, newError) => { - if(promise.status === 'rejected') { - errorMsg.push(newError); - } else { - return concat(targetArray, promise.value); - } - }; - //callback function passed to search bar to perform filter const handleSearchChange = useCallback(() => searchOnFilteredList( searchRef.current.value, @@ -60,40 +51,9 @@ export default function ResearcherConsole() { //sequence of init events on component load useEffect(() => { const init = async () => { - let errorMsg = []; - const promiseReturns = await Promise.allSettled([ - Collections.getCollectionsForResearcher(), - DAR.getDraftDarRequestList() - ]); - const [fetchedCollections, fetchedDraftsPayload] = promiseReturns; - // Need some extra formatting steps for drafts due to different payload format - // Workaround for the API returning a cartesian product of draft dars - const uniqueFetchedDrafts = uniqBy('dar.id')(fetchedDraftsPayload.value || []); - const fetchedDrafts = { - status: fetchedDraftsPayload.status, - value: flow( - map((draftPayload) => draftPayload.dar), - map(formatDraft), - )(uniqueFetchedDrafts || []) - }; - let collectionArray = []; - collectionArray = handlePromise( - fetchedCollections, - collectionArray, - errorMsg, - 'Failed to fetch Data Access Request Collection' - ); - collectionArray = handlePromise( - fetchedDrafts, - collectionArray, - errorMsg, - 'Failed to fetch Data Access Request Drafts' - ); - if(!isEmpty(errorMsg)) { - Notifications.showError({text: errorMsg.join('\n')}); - } - setResearcherCollections(collectionArray); - setFilteredList(collectionArray); + const collections = await Collections.getCollectionSummariesByRoleName('Researcher'); + setResearcherCollections(collections); + setFilteredList(collections); setIsLoading(false); }; init(); @@ -152,24 +112,35 @@ export default function ResearcherConsole() { } }; + + //Draft delete, by referenceIds + const deleteDraftById = async ({ referenceId }) => { + const collectionsClone = cloneDeep(researcherCollections); + await DAR.deleteDar(referenceId); + const targetIndex = findIndex((draft) => { + return draft.referenceIds[0] === referenceId; + })(collectionsClone); + + // if deleted index, remove it from the collections array + collectionsClone.splice(targetIndex, 1); + setResearcherCollections(collectionsClone); + + return targetIndex; + }; + //Draft delete, passed down to draft table to be used with delete button - const deleteDraft = async ({ referenceId, identifier }) => { + const deleteDraft = async ({ referenceIds, darCode }) => { try { - await DAR.deleteDar(referenceId); - const collectionsClone = cloneDeep(researcherCollections); - const targetIndex = findIndex((draft) => { - return draft.referenceId === referenceId; - })(collectionsClone); + const targetIndex = deleteDraftById({ referenceId: referenceIds[0] }); + if (targetIndex === -1) { Notifications.showError({ text: 'Error processing delete request' }); } else { - collectionsClone.splice(targetIndex, 1); - setResearcherCollections(collectionsClone); - Notifications.showSuccess({text: `Deleted Data Access Request Draft ${identifier}`}); + Notifications.showSuccess({text: `Deleted Data Access Request Draft ${darCode}`}); } } catch (error) { Notifications.showError({ - text: `Failed to delete Data Access Request Draft ${identifier}`, + text: `Failed to delete Data Access Request Draft ${darCode}`, }); } diff --git a/src/pages/Status.js b/src/pages/Status.js index e00eeb04a..344d2086a 100644 --- a/src/pages/Status.js +++ b/src/pages/Status.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { a, div, h2, hh, hr, li, pre, ul } from 'react-hyperscript-helpers'; import CheckboxMarkedCircleOutline from 'react-material-icon-svg/dist/CheckboxMarkedCircleOutline'; import DiameterVariant from 'react-material-icon-svg/dist/DiameterVariant'; -import { Config } from '../libs/config'; +import { getApiUrl, getOntologyUrl } from '../libs/ajax'; export const Status = hh(class Status extends Component { @@ -33,8 +33,8 @@ export const Status = hh(class Status extends Component { }; async componentDidMount() { - const consentStatusUrl = `${ await Config.getApiUrl() }/status`; - const ontologyStatusUrl = `${ await Config.getOntologyApiUrl() }/status`; + const consentStatusUrl = `${ await getApiUrl('/api') }/status`; + const ontologyStatusUrl = `${ await getOntologyUrl('/ontology') }/status`; fetch(consentStatusUrl, { method: 'GET' }) .then(response => response.json()) .then(data => this.setState({ consentStatus: data })); diff --git a/src/setupProxy.js b/src/setupProxy.js new file mode 100644 index 000000000..b0aa73bc4 --- /dev/null +++ b/src/setupProxy.js @@ -0,0 +1,62 @@ +const { createProxyMiddleware } = require('http-proxy-middleware'); +const configUrls = require('../public/config.json'); + +/* + Rough outline of the proxy approach is described here + https://create-react-app.dev/docs/proxying-api-requests-in-development/#configuring-the-proxy-manually + + In short, this file is setting up proxy middleware that will listen for requests made to the application's open port. + It's the reason why the ajax file initializes a url string for our APIs depending on our environment. + If the config.json file has an env=local, then the url string will remove the host portion of the url, causing those requests to be self-referenced. + If the path matches any of the proxies, the request will be intercepted and altered based on its (proxy) configuration. + For most of the proxies, it means simply rewriting the urls so that the origin is or rather than hostname. + In the instance of status though, requests to ontology's and consent's status endpoints had their pathName changed. + The above needed to be done since, from an application perspective. '/status' is the request made to fetch assets (icons, html, etc.) for the status page. + Simply checking on '/status' would intercept the above request on page load, therefore consent's and ontology's status path needed to be different. + In addition, a pathRewrite option was defined to alter the pathName back to '/status' once the request is intercepted to ensure the endpoint is hit on those APIs. + + If you need to add additional paths to be intercepted, you can do one of two things + 1) If there exists a proxy that fits your needs, update the path listing for that proxy with the new value. + NOTE: multiple paths require an array argument instead of a string + 2) Append a new proxy by using app.use(). Format should be similar to the defined options below, but reference http-proxy-middleware docs if you need more + https://github.com/chimurai/http-proxy-middleware + NOTE: Middleware are executed in the order they are added. This may need to be taken into consideration if you want to add a proxy for a specific subpath. + + This file will only be read and evaluated when running 'react-scripts start' (which has been shortcuted to 'npm start' on package.json). + Our deployment processes use 'npm build' to generate bundled files that are served by nginx, with the files included having references in the application itself. + Since this file isn't imported or referenced by the application code in any way, it will not be included in the build files, therefore this file will not exist in + any deployed environments. +*/ + +module.exports = function (app) { + app.use( + '/api', + createProxyMiddleware({ + target: configUrls.apiUrl, + secure: false, + pathRewrite: { + '/api/status': '/status' + } + }) + ); + + app.use( + ['/autocomplete', '/search', '/ontology/status'], + createProxyMiddleware({ + target: configUrls.ontologyApiUrl, + secure: false, + pathRewrite: { + '/ontology/status': '/status' + } + }) + ); + + //Still need to work on this one + // app.use( + // '/broad-duos-banner', + // createProxyMiddleware({ + // target: 'https://storage.googleapis.com', + // secure: false + // }) + // ); +}; From 103d37262e64f2814093352a15c85656a2acc86c Mon Sep 17 00:00:00 2001 From: Justin Variath Thomas Date: Wed, 10 Aug 2022 09:13:10 -0400 Subject: [PATCH 08/10] DUOS-1997[risk=no] Updated Open and Close Collection functions for Admin and Chair Consoles (#1723) * update researcher console dar table + actions * naming nit * fix tests * Apply suggestions from code review Co-authored-by: Gregory Rushton * Update cypress/component/DarCollectionTable/researcher_actions.spec.js Co-authored-by: Gregory Rushton * fixed merge issue * ???? Testing new actions component. * updated unit tests? * Fixed eslint * fix name * fix admin console * fix tests * Fixing unit tests * Apply suggestions from code review Co-authored-by: Gregory Rushton * added unit test for Update button (used to be combined with the vote button) * Update cypress/component/DarCollectionTable/admin_actions.spec.js Co-authored-by: Gregory Rushton * fix codacy * Fixed unit tests * Fixed unit tests? * merged with DUOS-1902, deleted unnecessary files * Merged with 1905 * Fixing unit tests? * Fixing eslint * Fixing unit test, but dont think this will sovle the problem * Fixing unit test, but dont think this will sovle the problem * Fixing unit test, but dont think this will sovle the problem * feeling more confident now that the problem will be solved haha * DUOS-1997 updated open and close collection functions for Admin and Chair pages * DUOS-1997 updated integration tests for collection callback functions * Updated variable names, due to merge conflict discrepancies! * Updated variable names, due to merge conflict discrepancies! * testing cypress tests on github * Checking original researcher unit test. Getting mildly frustrated! * Converted hard coded researcher to console type * fixing? * fix test * removed unnecessary line * re-added actions (sorry connor, i deleted it) * DUOS-1997 used USER_ROLES instead of explicit string values * DUOS-1997 updated spec to use USER_ROLES instead of hard coded strings Co-authored-by: Connor Barker Co-authored-by: Gregory Rushton Co-authored-by: clu@asymmetrik.com --- .../utils/dar_collection_utils.spec.js | 34 ++++++----- src/libs/ajax.js | 5 ++ src/pages/AdminManageDarCollections.js | 6 +- src/pages/ChairConsole.js | 6 +- src/utils/DarCollectionUtils.js | 57 ++++++++++--------- 5 files changed, 63 insertions(+), 45 deletions(-) diff --git a/cypress/component/utils/dar_collection_utils.spec.js b/cypress/component/utils/dar_collection_utils.spec.js index d084bd727..5b9db0ab0 100644 --- a/cypress/component/utils/dar_collection_utils.spec.js +++ b/cypress/component/utils/dar_collection_utils.spec.js @@ -15,7 +15,7 @@ import { rpVoteKey } from '../../../src/utils/DarCollectionUtils'; import {Collections, Match} from '../../../src/libs/ajax'; -import {formatDate, Notifications} from '../../../src/libs/utils'; +import {formatDate, Notifications, USER_ROLES} from '../../../src/libs/utils'; import {cloneDeep, forEach, includes, concat} from 'lodash/fp'; const openableAndClosableDars = { @@ -177,40 +177,48 @@ describe('updateCollectionFn', () => { describe('cancelCollectionFn', () => { it('returns a callback function for consoles to use', () => { const updateCollections = (arr) => collections = arr; - const callback = cancelCollectionFn({updateCollections, role: 'admin'}); + const callback = cancelCollectionFn({updateCollections, role: USER_ROLES.admin}); expect(callback).to.exist; }); it('updates collections and filteredList on successful cancel', async () => { - let collections = [{dars: {1: {status: 'Open'}}}]; - const updatedCollection = {dars: {1: {status: 'Canceled'}}}; + let collections = [{status: 'In Progress', darCode: 'DAR-1', darCollectionId: 1}]; + const updatedCollection = {status: 'Complete', darCode: 'DAR-1', darCollectionId: 1}; const updateCollections = (collection) => collections = [collection]; const callback = cancelCollectionFn({updateCollections}); - cy.stub(Collections, 'cancelCollection').returns(updatedCollection); + cy.stub(Collections, 'cancelCollection').returns(); + cy.stub(Collections, 'getCollectionSummaryByRoleNameAndId').returns( + updatedCollection + ); cy.stub(Notifications, 'showSuccess').returns(undefined); cy.stub(Notifications, 'showError').returns(undefined); - await callback({darCode: 'test', darCollectionId: 1}); + await callback({darCode: '', darCollectionId: 1}); expect(collections).to.not.be.empty; - expect(collections[0].dars[1].status).to.equal(updatedCollection.dars[1].status); + expect(collections[0].darCollectionId).to.equal(1); + expect(collections[0].status).to.equal('Complete'); }); }); describe('openCollectionFn', () => { it('returns a callback function for consoles to use', () => { const updateCollections = (arr) => collections = arr; - const callback = openCollectionFn(updateCollections); + const callback = openCollectionFn({updateCollections, role: USER_ROLES.admin}); expect(callback).to.exist; }); it('updatesCollections on a successful open', async () => { - const updatedCollection = {dars: {1: {status: 'Open'}}}; - cy.stub(Collections, 'openElectionsById').returns(updatedCollection); - let collections = [{dars: {1: {status: 'Canceled'}}}]; + let collections = [{status: 'Complete', darCode: 'DAR-1', darCollectionId: 1}]; + const updatedCollection = {status: 'In Progress', darCode: 'DAR-1', darCollectionId: 1}; + cy.stub(Collections, 'openElectionsById').returns({}); + cy.stub(Collections, 'getCollectionSummaryByRoleNameAndId').returns( + updatedCollection + ); const updateCollections = (collection) => collections = [collection]; const callback = openCollectionFn({updateCollections}); - await callback({darCode: 'test', darCollectionId: 1}); - expect(collections[0].dars[1].status).to.equal(updatedCollection.dars[1].status); + await callback({darCode: '', darCollectionId: 1}); + expect(collections[0].darCode).to.equal('DAR-1'); + expect(collections[0].status).to.equal('In Progress'); }); }); diff --git a/src/libs/ajax.js b/src/libs/ajax.js index 5673cb257..03cce1f4a 100644 --- a/src/libs/ajax.js +++ b/src/libs/ajax.js @@ -189,6 +189,11 @@ export const Collections = { const res = await axios.get(url, Config.authOpts()); return res.data; }, + getCollectionSummaryByRoleNameAndId: async({roleName, id}) => { + const url = `${await Config.getApiUrl()}/api/collections/role/${roleName}/summary/${id}`; + const res = await axios.get(url, Config.authOpts()); + return res.data; + }, openElectionsById: async(id) => { const url = `${await getApiUrl()}/api/collections/${id}/election`; const res = await axios.post(url, {}, Config.authOpts()); diff --git a/src/pages/AdminManageDarCollections.js b/src/pages/AdminManageDarCollections.js index a602649d8..f5fa9a9cd 100644 --- a/src/pages/AdminManageDarCollections.js +++ b/src/pages/AdminManageDarCollections.js @@ -1,7 +1,7 @@ import {useState, useEffect, useRef, useCallback } from 'react'; import SearchBar from '../components/SearchBar'; import { Collections } from '../libs/ajax'; -import { Notifications, searchOnFilteredList, getSearchFilterFunctions } from '../libs/utils'; +import { Notifications, searchOnFilteredList, getSearchFilterFunctions, USER_ROLES } from '../libs/utils'; import { Styles } from '../libs/theme'; import {h, div, img} from 'react-hyperscript-helpers'; import lockIcon from '../images/lock-icon.png'; @@ -38,8 +38,8 @@ export default function AdminManageDarCollections() { }, []); const updateCollections = updateCollectionFn({collections, filterFn, searchRef, setCollections, setFilteredList}); - const cancelCollection = cancelCollectionFn({updateCollections, role: 'admin'}); - const openCollection = openCollectionFn({updateCollections}); + const cancelCollection = cancelCollectionFn({updateCollections, role: USER_ROLES.admin}); + const openCollection = openCollectionFn({updateCollections, role: USER_ROLES.admin}); return div({ style: Styles.PAGE }, [ div({ style: { display: 'flex', justifyContent: 'space-between', width: '112%', marginLeft: '-6%', padding: '0 2.5%' } }, [ diff --git a/src/pages/ChairConsole.js b/src/pages/ChairConsole.js index 9c879bfd6..9479e4e8b 100644 --- a/src/pages/ChairConsole.js +++ b/src/pages/ChairConsole.js @@ -1,7 +1,7 @@ import {useState, useEffect, useRef, useCallback } from 'react'; import SearchBar from '../components/SearchBar'; import { Collections, User } from '../libs/ajax'; -import { Notifications, searchOnFilteredList, getSearchFilterFunctions } from '../libs/utils'; +import { Notifications, searchOnFilteredList, getSearchFilterFunctions, USER_ROLES } from '../libs/utils'; import { Styles } from '../libs/theme'; import {h, div, img} from 'react-hyperscript-helpers'; import lockIcon from '../images/lock-icon.png'; @@ -44,8 +44,8 @@ export default function ChairConsole(props) { }, []); const updateCollections = updateCollectionFn({collections, filterFn, searchRef, setCollections, setFilteredList}); - const cancelCollection = cancelCollectionFn({updateCollections, role: 'chairperson'}); - const openCollection = openCollectionFn({updateCollections}); + const cancelCollection = cancelCollectionFn({updateCollections, role: USER_ROLES.chairperson}); + const openCollection = openCollectionFn({updateCollections, role: USER_ROLES.chairperson}); const goToVote = useCallback((collectionId) => history.push(`/dar_collection/${collectionId}`), [history]); return div({ style: Styles.PAGE }, [ diff --git a/src/utils/DarCollectionUtils.js b/src/utils/DarCollectionUtils.js index c3de7771e..1514fab37 100644 --- a/src/utils/DarCollectionUtils.js +++ b/src/utils/DarCollectionUtils.js @@ -395,9 +395,7 @@ export const updateCollectionFn = ({collections, filterFn, searchRef, setCollect }); } else { const collectionsCopy = cloneDeep(collections); - //NOTE: update does not return datasets, so a direct collection update will mess up the datasets column - //That's not a big deal, we know the only things updated were the elections, so we can still update the dars (sicne elections are nested inside) - collectionsCopy[targetIndex].dars = updatedCollection.dars; + collectionsCopy[targetIndex] = updatedCollection; const updatedFilteredList = filterFn( searchRef.current.value, collectionsCopy @@ -407,30 +405,37 @@ export const updateCollectionFn = ({collections, filterFn, searchRef, setCollect } }; -export const cancelCollectionFn = ({updateCollections, role}) => - async ({ darCode, darCollectionId }) => { - try { - const canceledCollection = await Collections.cancelCollection( - darCollectionId, - role - ); - updateCollections(canceledCollection); - Notifications.showSuccess({ text: `Successfully canceled ${darCode}` }); - } catch (error) { - Notifications.showError({ text: `Error canceling ${darCode}` }); - } - }; +export const cancelCollectionFn = + ({ updateCollections, role }) => + async ({ darCode, darCollectionId }) => { + try { + await Collections.cancelCollection(darCollectionId, role); + const summary = await Collections.getCollectionSummaryByRoleNameAndId({ + id: darCollectionId, + roleName: role, + }); + updateCollections(summary); + Notifications.showSuccess({ text: `Successfully canceled ${darCode}` }); + } catch (error) { + Notifications.showError({ text: `Error canceling ${darCode}` }); + } + }; -export const openCollectionFn = ({updateCollections}) => - async ({ darCode, darCollectionId }) => { - try { - const openCollection = await Collections.openElectionsById(darCollectionId); - updateCollections(openCollection); - Notifications.showSuccess({ text: `Successfully opened ${darCode}` }); - } catch (error) { - Notifications.showError({ text: `Error opening ${darCode}` }); - } - }; +export const openCollectionFn = + ({ updateCollections, role }) => + async ({ darCode, darCollectionId }) => { + try { + await Collections.openElectionsById(darCollectionId); + const summary = await Collections.getCollectionSummaryByRoleNameAndId({ + id: darCollectionId, + roleName: role, + }); + updateCollections(summary); + Notifications.showSuccess({ text: `Successfully opened ${darCode}` }); + } catch (error) { + Notifications.showError({ text: `Error opening ${darCode}` }); + } + }; //helper function used in DarCollectionReview to update final vote on source of truth From 9c9132826fa5bcd1f829ba842834437c267fedee Mon Sep 17 00:00:00 2001 From: Justin Variath Thomas Date: Wed, 10 Aug 2022 16:44:39 -0400 Subject: [PATCH 09/10] DUOS-2014[risk=no] Updated Researcher Actions functionality to account for DarCollectionSummary (#1744) * update researcher console dar table + actions * naming nit * fix tests * Apply suggestions from code review Co-authored-by: Gregory Rushton * Update cypress/component/DarCollectionTable/researcher_actions.spec.js Co-authored-by: Gregory Rushton * fixed merge issue * ???? Testing new actions component. * updated unit tests? * Fixed eslint * fix name * fix admin console * fix tests * Fixing unit tests * Apply suggestions from code review Co-authored-by: Gregory Rushton * added unit test for Update button (used to be combined with the vote button) * Update cypress/component/DarCollectionTable/admin_actions.spec.js Co-authored-by: Gregory Rushton * fix codacy * Fixed unit tests * Fixed unit tests? * merged with DUOS-1902, deleted unnecessary files * Merged with 1905 * Fixing unit tests? * Fixing eslint * Fixing unit test, but dont think this will sovle the problem * Fixing unit test, but dont think this will sovle the problem * Fixing unit test, but dont think this will sovle the problem * feeling more confident now that the problem will be solved haha * DUOS-1997 updated open and close collection functions for Admin and Chair pages * DUOS-1997 updated integration tests for collection callback functions * Updated variable names, due to merge conflict discrepancies! * Updated variable names, due to merge conflict discrepancies! * testing cypress tests on github * Checking original researcher unit test. Getting mildly frustrated! * Converted hard coded researcher to console type * fixing? * fix test * removed unnecessary line * re-added actions (sorry connor, i deleted it) * DUOS-1997 used USER_ROLES instead of explicit string values * DUOS-1997 updated spec to use USER_ROLES instead of hard coded strings * DUOS-2014 fixed missing forEach immport from lodash, updated actions to use new summary endpoint, removed formatDraft call for revise * DUOS-2014 adjusted tooltip on cancel action, removed unused function, updated url reference in summary fetch method * DUOS-2014 updated iterating function on applyHoverEffects to match non-fp forEach arguments * DUOS-2014 updated getProjectTitle helper method, updated style update on applyStyleOnEnter, updated aliases for forEach methods in utils.js * DUOS-2014 removed getProjectTitle, inlined collection name in string Co-authored-by: Connor Barker Co-authored-by: Gregory Rushton Co-authored-by: clu@asymmetrik.com --- src/components/TableIconButton.js | 1 + .../dar_collection_table/Actions.js | 3 +- .../CollectionConfirmationModal.js | 13 ++++---- .../DarCollectionTable.js | 9 +---- src/libs/ajax.js | 2 +- src/libs/utils.js | 22 ++++++------- src/pages/ResearcherConsole.js | 33 ++++--------------- 7 files changed, 27 insertions(+), 56 deletions(-) diff --git a/src/components/TableIconButton.js b/src/components/TableIconButton.js index ce209124f..4e689799f 100644 --- a/src/components/TableIconButton.js +++ b/src/components/TableIconButton.js @@ -22,6 +22,7 @@ export default function TableIconButton(props) { }, []); const onMouseEnterFn = (e) => { + e.target.style.cursor = 'pointer'; applyHoverEffects(e, hoverStyle); }; diff --git a/src/components/dar_collection_table/Actions.js b/src/components/dar_collection_table/Actions.js index 7530015c8..147c431be 100644 --- a/src/components/dar_collection_table/Actions.js +++ b/src/components/dar_collection_table/Actions.js @@ -42,7 +42,6 @@ const resumeDARApplication = (referenceId, history) => { export default function Actions(props) { const { showConfirmationModal, collection, goToVote, consoleType, actions = [] } = props; const collectionId = collection.darCollectionId; - const uniqueId = (collectionId ? collectionId : collection.referenceIds[0]); const history = useHistory(); @@ -72,7 +71,7 @@ export default function Actions(props) { onClick: () => showConfirmationModal(collection, 'cancel'), style: baseCancelButtonStyle, hoverStyle: hoverCancelButtonStyle, - dataTip: 'Cancel Elections', + dataTip: `Cancel ${consoleType === 'researcher' ? 'Collection' : 'Elections'}`, icon: Block, }; diff --git a/src/components/dar_collection_table/CollectionConfirmationModal.js b/src/components/dar_collection_table/CollectionConfirmationModal.js index 3ec1d1903..2ec3de496 100644 --- a/src/components/dar_collection_table/CollectionConfirmationModal.js +++ b/src/components/dar_collection_table/CollectionConfirmationModal.js @@ -2,14 +2,13 @@ import {isNil} from 'lodash/fp'; import {h} from 'react-hyperscript-helpers'; import ConfirmationModal from '../modals/ConfirmationModal'; import {isCollectionCanceled} from '../../libs/utils'; -import {getProjectTitle} from './DarCollectionTable'; export default function CollectionConfirmationModal(props) { const {collection, showConfirmation, setShowConfirmation, cancelCollection, reviseCollection, openCollection, consoleAction, deleteDraft} = props; - const getModalHeader = () => { + const getModalHeader = (collection) => { if(!isNil(collection)) { - return `${collection.darCode} - ${getProjectTitle(collection)}`; + return `${collection.darCode} - ${collection.name}`; } return ''; }; @@ -40,7 +39,7 @@ export default function CollectionConfirmationModal(props) { closeConfirmation: () => setShowConfirmation(false), title: 'Cancel Data Access Request', message: `Are you sure you want to cancel ${collection.darCode}?`, - header: getModalHeader(), + header: getModalHeader(collection), onConfirm: cancelOnClick }); @@ -51,7 +50,7 @@ export default function CollectionConfirmationModal(props) { closeConfirmation: () => setShowConfirmation(false), title: 'Revise Data Access Request', message: `Are you sure you want to revise ${collection.darCode}?`, - header: getModalHeader(), + header: getModalHeader(collection), onConfirm: reviseOnClick }); @@ -60,7 +59,7 @@ export default function CollectionConfirmationModal(props) { closeConfirmation: () => setShowConfirmation(false), title: 'Open Data Access Request', message: `Are you sure you want to open ${collection.darCode}?`, - header: getModalHeader(), + header: getModalHeader(collection), onConfirm: openOnClick, }); @@ -70,7 +69,7 @@ export default function CollectionConfirmationModal(props) { closeConfirmation: () => setShowConfirmation(false), title: 'Delete Data Access Request Draft', message: `Are you sure you want to delete ${collection.darCode}?`, - header: getModalHeader(), + header: getModalHeader(collection), onConfirm: deleteOnClick, }); diff --git a/src/components/dar_collection_table/DarCollectionTable.js b/src/components/dar_collection_table/DarCollectionTable.js index 3306dbbd5..adee0e01b 100644 --- a/src/components/dar_collection_table/DarCollectionTable.js +++ b/src/components/dar_collection_table/DarCollectionTable.js @@ -1,6 +1,6 @@ import { useState, useEffect, Fragment, useCallback } from 'react'; import { h } from 'react-hyperscript-helpers'; -import {isNil, isEmpty, find} from 'lodash/fp'; +import {isNil} from 'lodash/fp'; import { Styles } from '../../libs/theme'; import { Storage } from '../../libs/storage'; import PaginationBar from '../PaginationBar'; @@ -10,13 +10,6 @@ import cellData from './DarCollectionTableCellData'; import CollectionConfirmationModal from './CollectionConfirmationModal'; const storageDarCollectionSort = 'storageDarCollectionSort'; -export const getProjectTitle = ((collection) => { - if(collection.isDraft){return collection.projectTitle;} - if(!isNil(collection) && !isEmpty(collection.dars)) { - const darData = find((dar) => !isEmpty(dar.data))(collection.dars).data; - return darData.projectTitle; - } -}); export const styles = { baseStyle: { diff --git a/src/libs/ajax.js b/src/libs/ajax.js index 03cce1f4a..d5aeb9004 100644 --- a/src/libs/ajax.js +++ b/src/libs/ajax.js @@ -190,7 +190,7 @@ export const Collections = { return res.data; }, getCollectionSummaryByRoleNameAndId: async({roleName, id}) => { - const url = `${await Config.getApiUrl()}/api/collections/role/${roleName}/summary/${id}`; + const url = `${await getApiUrl()}/api/collections/role/${roleName}/summary/${id}`; const res = await axios.get(url, Config.authOpts()); return res.data; }, diff --git a/src/libs/utils.js b/src/libs/utils.js index 4b168b256..118a474c3 100644 --- a/src/libs/utils.js +++ b/src/libs/utils.js @@ -1,10 +1,10 @@ import Noty from 'noty'; import 'noty/lib/noty.css'; import 'noty/lib/themes/bootstrap-v3.css'; -import {map as nonFPMap} from 'lodash'; +import {map as lodashMap, forEach as lodashForEach} from 'lodash'; import { DAR, DataSet } from './ajax'; import {Theme, Styles } from './theme'; -import { each, flatMap, flatten, flow, forEach, get, getOr, indexOf, uniq, values, find, first, map, isEmpty, filter, cloneDeep, isNil, toLower, includes, every, capitalize } from 'lodash/fp'; +import { each, flatMap, flatten, flow, forEach as lodashFPForEach, get, getOr, indexOf, uniq, values, find, first, map, isEmpty, filter, cloneDeep, isNil, toLower, includes, every, capitalize } from 'lodash/fp'; import {User} from './ajax'; export const UserProperties = { @@ -39,7 +39,7 @@ export const darCollectionUtils = { if(isEmpty(elections)) { // Dataset IDs should be on the DAR, but if not, pull from the dar.data const datasetIds = isNil(dar.datasetIds) ? dar.data.datasetIds : dar.datasetIds; - forEach((datasetId) => { + lodashFPForEach((datasetId) => { if (includes(relevantDatasets, datasetId)) { if (isNil(electionStatusCount['Submitted'])) { electionStatusCount['Submitted'] = 0; @@ -72,7 +72,7 @@ export const darCollectionUtils = { } electionStatusCount[status]++; })(targetElections); - output = nonFPMap(electionStatusCount, (value, key) => { + output = lodashMap(electionStatusCount, (value, key) => { return `${key}: ${value}`; }).join('\n'); } else { @@ -110,9 +110,9 @@ export const getPropertyValuesFromUser = (user) => { }; export const applyHoverEffects = (e, style) => { - forEach((key, value) => { + lodashForEach(style, (value, key) => { e.target.style[key] = value; - })(style); + }); }; export const highlightExactMatches = (highlightedWords, content) => { @@ -484,7 +484,7 @@ export const darSearchHandler = (electionList, setFilteredList, setCurrentPage) setFilteredList(electionList); } else { let newFilteredList = cloneDeep(electionList); - searchTermValues.forEach((splitTerm) => { + lodashFPForEach((splitTerm) => { const term = splitTerm.trim(); if(!isEmpty(term)) { newFilteredList = filter(electionData => { @@ -496,7 +496,7 @@ export const darSearchHandler = (electionList, setFilteredList, setCurrentPage) return includes(term, targetDarAttrs) || includes(term, targetDacAttrs) || includes(term, targetElectionAttrs); }, newFilteredList); } - }); + })(searchTermValues); setFilteredList(newFilteredList); } setCurrentPage(1); @@ -589,13 +589,13 @@ export const tableSearchHandler = (list, setFilteredList, setCurrentPage, modelN setFilteredList(list); } else { let newFilteredList = cloneDeep(list); - searchTermValues.forEach((splitTerm) => { + lodashFPForEach((splitTerm) => { const term = splitTerm.trim(); if(!isEmpty(term)) { const filterFn = filterFnMap[modelName]; newFilteredList = filterFn(term, newFilteredList); } - }); + })(searchTermValues); setFilteredList(newFilteredList); } setCurrentPage(1); @@ -721,7 +721,7 @@ export const searchOnFilteredList = (searchTerms, originalList, filterFn, setFil let searchList = (!isNil(originalList) ? [...originalList] : []); if(!isEmpty(searchTerms)) { const terms = searchTerms.split(' '); - terms.forEach((term => searchList = filterFn(term, searchList))); + lodashFPForEach((term => searchList = filterFn(term, searchList)))(terms); } setFilteredList(searchList); }; diff --git a/src/pages/ResearcherConsole.js b/src/pages/ResearcherConsole.js index 2426d6515..e6f584e33 100644 --- a/src/pages/ResearcherConsole.js +++ b/src/pages/ResearcherConsole.js @@ -5,31 +5,10 @@ import { Styles } from '../libs/theme'; import { Collections, DAR } from '../libs/ajax'; import { DarCollectionTableColumnOptions, DarCollectionTable } from '../components/dar_collection_table/DarCollectionTable'; import accessIcon from '../images/icon_access.png'; -import {Notifications, searchOnFilteredList, getSearchFilterFunctions, formatDate} from '../libs/utils'; +import {Notifications, searchOnFilteredList, getSearchFilterFunctions } from '../libs/utils'; import SearchBar from '../components/SearchBar'; import { consoleTypes } from '../components/dar_table/DarTableActions'; - -const formatDraft = (draft) => { - const { data, referenceId, id, createDate } = draft; - const { - projectTitle, - datasets, - institution, - } = data; - const darCode = 'DRAFT_DAR_' + formatDate(createDate); - - const output = { - darCode, - referenceId, - darCollectionId: id, - projectTitle, - isDraft: true, - createDate: 'Unsubmitted', - datasets, - institution, - }; - return output; -}; +import { USER_ROLES } from '../libs/utils'; const filterFn = getSearchFilterFunctions().darCollections; @@ -73,14 +52,15 @@ export default function ResearcherConsole() { const cancelCollection = async (darCollection) => { try { const { darCollectionId, darCode } = darCollection; - const canceledCollection = await Collections.cancelCollection(darCollectionId); + await Collections.cancelCollection(darCollectionId); + const updatedCollection = await Collections.getCollectionSummaryByRoleNameAndId({roleName: USER_ROLES.researcher, id: darCollectionId}); const targetIndex = researcherCollections.findIndex((collection) => collection.darCollectionId === darCollectionId); if (targetIndex < 0) { throw new Error('Error: Could not find target Data Access Request'); } const clonedCollections = cloneDeep(researcherCollections); - clonedCollections[targetIndex] = canceledCollection; + clonedCollections[targetIndex] = updatedCollection; setResearcherCollections(clonedCollections); Notifications.showSuccess({text: `Deleted Data Access Request ${darCode}`}); } catch (error) { @@ -102,7 +82,7 @@ export default function ResearcherConsole() { } //remove resubmitted collection from DAR Collection table const clonedCollections = cloneDeep(researcherCollections); - clonedCollections[targetIndex] = formatDraft(draftCollection); + clonedCollections[targetIndex] = draftCollection; setResearcherCollections(clonedCollections); Notifications.showSuccess({text: `Revising Data Access Request ${darCode}`}); } catch (error) { @@ -132,7 +112,6 @@ export default function ResearcherConsole() { const deleteDraft = async ({ referenceIds, darCode }) => { try { const targetIndex = deleteDraftById({ referenceId: referenceIds[0] }); - if (targetIndex === -1) { Notifications.showError({ text: 'Error processing delete request' }); } else { From c48a0d40a761630bf1bb4f1d05101688f2304a0e Mon Sep 17 00:00:00 2001 From: JVThomas Date: Fri, 12 Aug 2022 09:52:49 -0400 Subject: [PATCH 10/10] DUOS-1903 pr feedback --- .../DarCollectionTableCellData.js | 4 +- .../dar_drafts_table/DarDraftTable.js | 254 ------------------ src/pages/AdminManageDarCollections.js | 2 +- src/pages/ChairConsole.js | 2 +- src/pages/MemberConsole.js | 4 +- src/pages/ResearcherConsole.js | 2 +- 6 files changed, 7 insertions(+), 261 deletions(-) delete mode 100644 src/components/dar_drafts_table/DarDraftTable.js diff --git a/src/components/dar_collection_table/DarCollectionTableCellData.js b/src/components/dar_collection_table/DarCollectionTableCellData.js index 7f41d3b08..db82ca1f1 100644 --- a/src/components/dar_collection_table/DarCollectionTableCellData.js +++ b/src/components/dar_collection_table/DarCollectionTableCellData.js @@ -104,9 +104,9 @@ export function institutionCellData({institutionName = '- -', darCollectionId, l }; } -export function datasetCountCellData({datasetIds = [], darCollectionId, label = 'datasets'}) { +export function datasetCountCellData({collection, darCollectionId, label = 'datasets'}) { return { - data: datasetIds.length > 0 ? datasetIds.length : '- -', + data: collection.datasetCount || '- -', id: darCollectionId, style: { color: '#333F52', diff --git a/src/components/dar_drafts_table/DarDraftTable.js b/src/components/dar_drafts_table/DarDraftTable.js deleted file mode 100644 index f3af9756d..000000000 --- a/src/components/dar_drafts_table/DarDraftTable.js +++ /dev/null @@ -1,254 +0,0 @@ -import { useState, useEffect, Fragment, useCallback } from 'react'; -import { div, h } from 'react-hyperscript-helpers'; -import { isEmpty } from 'lodash/fp'; -import { Styles, Theme } from '../../libs/theme'; -import PaginationBar from '../PaginationBar'; -import SimpleButton from '../SimpleButton'; -import ConfirmationModal from '../modals/ConfirmationModal'; -import { - formatDate, - recalculateVisibleTable, - goToPage as updatePage -} from '../../libs/utils'; -import SimpleTable from '../SimpleTable'; - -const styles = { - baseStyle: { - fontFamily: 'Arial', - fontSize: '1.6rem', - fontWeight: 400, - display: 'flex', - padding: '1rem 2%', - justifyContent: 'space-between', - alignItems: 'center', - }, - columnStyle: Object.assign({}, Styles.TABLE.HEADER_ROW, { - justifyContent: 'space-between', - }), - cellWidth: { - partialDarCode: '15%', - projectTitle: '30%', - updateDate: '15%', - actions: '25%', - }, -}; - -//data struct to outline column widths and header labels -const columnHeaderFormat = { - partialDarCode: {label: 'Partial DAR Code', cellStyle: { width: styles.cellWidth.partialDarCode}}, - projectTitle: {label: 'Title', cellStyle: { width: styles.cellWidth.projectTitle}}, - updateDate: {label: 'Last Updated', cellStyle: {width: styles.cellWidth.updateDate}}, - actions: {label: 'DAR Actions', cellStyle: { width: styles.cellWidth.actions}} -}; - -//basic helper function to format above keys in fixed order array -const columnHeaderData = () => { - const { partialDarCode, projectTitle, updateDate, actions } = columnHeaderFormat; - return [partialDarCode, projectTitle, updateDate, actions]; -}; - -//helper function to create table metadata for dar code cell data format -const partialDarCodeCell = ({partialDarCode = '- -', id, style = {}, label = 'partial-dar-code'}) => { - return { - data: partialDarCode, - id, - style, - label - }; -}; - -//helper function to create table metadata for projectTitle cell data format -const projectTitleCell = ({projectTitle = '- -', id, style = {}, label = 'draft-project-title' }) => { - return { - data: projectTitle, - id, - style, - label - }; -}; - -//helper function to create table metadata for update date cell metadata -const updateDateCell = ({updateDate, id, style = {}, label = 'draft-update-date'}) => { - return { - data: formatDate(updateDate), - id, - style, - label - }; -}; - -//sub-component that redirects user to draft applictaion page -const ResumeDraftButton = (props) => { - const { draft, history } = props; - const { referenceId } = draft; - return h(SimpleButton, { - keyProp: `resume-draft-${referenceId}`, - label: 'Resume', - baseColor: Theme.palette.secondary, - additionalStyle: { - width: '30%', - padding: '2%', - marginRight: '2%', - fontSize: '1.45rem' - }, - onClick: () => history.push(`/dar_application/${referenceId}`) - }); -}; - -//helper function to create id string for error messages -const getIdentifier = ({id, data}) => { - return !isEmpty(data) ? (data.projectTitle || data.partialDarCode) : id; -}; - -//sub-component that renders draft delete button -const DeleteDraftButton = (props) => { - const { targetDraft, showConfirmationModal } = props; - const { id, data } = targetDraft; - const identifier = getIdentifier({id, data}); - return h(SimpleButton, { - keyProp: `delete-draft-${identifier}`, - label: 'Delete', - baseColor: Theme.palette.error, - additionalStyle: { - width: '30%', - padding: '2%', - fontSize: '1.45rem', - }, - onClick: () => showConfirmationModal(targetDraft), - }); -}; - -//helper function that formats table metadata and components for draft action buttons -const actionCellData = ({ - targetDraft, - showConfirmationModal, - history, -}) => { - const { id } = targetDraft; - const deleteButtonTemplate = h(DeleteDraftButton, { - targetDraft, - showConfirmationModal - }); - const resumeButtonTemplate = h(ResumeDraftButton, { - draft: targetDraft, - history, - }); - - return { - isComponent: true, - label: 'draft-buttons', - data: div( - { - style: { - display: 'flex', - justifyContent: 'left', - }, - key: `draft-buttons-cell-${id}`, - }, - [resumeButtonTemplate, deleteButtonTemplate] - ), - }; -}; - -//helper function that foramts table metadata for cells row by row, returns an array of arrays -const processDraftsRowData = ({visibleDrafts, showConfirmationModal, history}) => { - if(!isEmpty(visibleDrafts)) { - return visibleDrafts.map((draft) => { - const { id, data, createDate, updateDate } = draft; - const { projectTitle } = !isEmpty(data) ? data : {}; - const partialDarCode = 'DRAFT_DAR_' + formatDate(createDate); - return [ - partialDarCodeCell({partialDarCode, id}), - projectTitleCell({projectTitle, id}), - updateDateCell({updateDate: updateDate || createDate, id}), - actionCellData({targetDraft: draft, showConfirmationModal, history}) - ]; - }); - } -}; - -export default function DarDraftTable(props) { - const [visibleDrafts, setVisibleDrafts] = useState([]); - const [currentPage, setCurrentPage] = useState(1); - const [pageCount, setPageCount] = useState(1); - const [tableSize, setTableSize] = useState(10); - const [showConfirmation, setShowConfirmation] = useState(false); - const [selectedDraft, setSelectedDraft] = useState({}); - - const { history, deleteDraft, isLoading, drafts } = props; - - //sequence of events that fires when table either changes its visible window (data updates, page change, table size change, etc) - useEffect(() => { - recalculateVisibleTable({ - tableSize, - pageCount, - filteredList: drafts, - currentPage, - setPageCount, - setCurrentPage, - setVisibleList: setVisibleDrafts - }); - }, [tableSize, pageCount, currentPage, drafts]); - - //callback function to change tableSize, passed to pagination bar - const changeTableSize = useCallback((value) => { - if (value > 0 && !isNaN(parseInt(value))) { - setTableSize(value); - } - }, []); - - //function that fires on delete button press, makes confirmation modal visible and selectes target draft - const showConfirmationModal = (draft) => { - setSelectedDraft(draft); - setShowConfirmation(true); - }; - - //function that fires when user updates current page (buttons, manual input), passed to pagination bar - const goToPage = useCallback( - (value) => { - updatePage(value, pageCount, setCurrentPage); - }, - [pageCount] - ); - - //helper function that formats string for modal header - const getModalHeader = () => { - if(!isEmpty(selectedDraft)) { - const { data, id } = selectedDraft; - return getIdentifier({id, data}); - } - }; - - //delete function that fires on modal confirmation, removes draft from listing via prop delete function - const deleteOnClick = async() => { - const {id, data, referenceIds} = selectedDraft; - const identifier = getIdentifier({id: id || referenceIds, data}); - await deleteDraft({ referenceIds, identifier, }); - setShowConfirmation(false); - }; - - return h(Fragment, {}, [ - h(SimpleTable, { - isLoading, - rowData: processDraftsRowData({visibleDrafts, showConfirmationModal, history}), - columnHeaders: columnHeaderData(), - styles, - tableSize, - paginationBar: h(PaginationBar, { - pageCount, - currentPage, - tableSize, - goToPage, - changeTableSize - }) - }), - h(ConfirmationModal, { - showConfirmation, - closeConfirmation: () => setShowConfirmation(false), - title: 'Delete Draft Data Access Request', - message: `Are you sure you want to delete Data Access Request draft ${getIdentifier({id: selectedDraft.id, data: selectedDraft.data})}`, - header: getModalHeader(), - onConfirm: deleteOnClick - }) - ]); -} diff --git a/src/pages/AdminManageDarCollections.js b/src/pages/AdminManageDarCollections.js index f5fa9a9cd..855333472 100644 --- a/src/pages/AdminManageDarCollections.js +++ b/src/pages/AdminManageDarCollections.js @@ -26,7 +26,7 @@ export default function AdminManageDarCollections() { useEffect(() => { const init = async() => { try { - const collectionsResp = await Collections.getCollectionSummariesByRoleName('admin'); + const collectionsResp = await Collections.getCollectionSummariesByRoleName(USER_ROLES.admin); setCollections(collectionsResp); setFilteredList(collectionsResp); setIsLoading(false); diff --git a/src/pages/ChairConsole.js b/src/pages/ChairConsole.js index 9479e4e8b..7dd834a11 100644 --- a/src/pages/ChairConsole.js +++ b/src/pages/ChairConsole.js @@ -29,7 +29,7 @@ export default function ChairConsole(props) { const init = async() => { try { const [collections, datasets] = await Promise.all([ - Collections.getCollectionSummariesByRoleName('chairperson'), + Collections.getCollectionSummariesByRoleName(USER_ROLES.chairperson), User.getUserRelevantDatasets() ]); setCollections(collections); diff --git a/src/pages/MemberConsole.js b/src/pages/MemberConsole.js index f8a9ddcf2..f0d0cac5a 100644 --- a/src/pages/MemberConsole.js +++ b/src/pages/MemberConsole.js @@ -1,7 +1,7 @@ import {useState, useEffect, useRef, useCallback } from 'react'; import SearchBar from '../components/SearchBar'; import { Collections, User } from '../libs/ajax'; -import { Notifications, searchOnFilteredList, getSearchFilterFunctions } from '../libs/utils'; +import { Notifications, searchOnFilteredList, getSearchFilterFunctions, USER_ROLES } from '../libs/utils'; import { Styles } from '../libs/theme'; import {h, div, img} from 'react-hyperscript-helpers'; import lockIcon from '../images/lock-icon.png'; @@ -27,7 +27,7 @@ export default function MemberConsole(props) { const init = async () => { try { const [collections, datasets] = await Promise.all([ - Collections.getCollectionSummariesByRoleName('member'), + Collections.getCollectionSummariesByRoleName(USER_ROLES.member), User.getUserRelevantDatasets(), //still need this on this console for status cell ]); setCollections(collections); diff --git a/src/pages/ResearcherConsole.js b/src/pages/ResearcherConsole.js index e6f584e33..b6cc2d7e1 100644 --- a/src/pages/ResearcherConsole.js +++ b/src/pages/ResearcherConsole.js @@ -30,7 +30,7 @@ export default function ResearcherConsole() { //sequence of init events on component load useEffect(() => { const init = async () => { - const collections = await Collections.getCollectionSummariesByRoleName('Researcher'); + const collections = await Collections.getCollectionSummariesByRoleName(USER_ROLES.researcher); setResearcherCollections(collections); setFilteredList(collections); setIsLoading(false);