Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion application/admin-client/cypress/e2e/multiStudy.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ beforeEach(() => {
describe('Multi-study features', () => {
function changeStudy(name) {
cy.get('[data-cy="study-dropdown"]').click()
cy.contains(name).click()
cy.get('ul[role="menu"]').contains(name).click()
cy.get('[data-cy="study-dropdown"]').contains(name).should('exist')
}
it('Can changes studies, all data is updated accordingly', () => {
cy.visit('/studies')
Expand Down
20 changes: 2 additions & 18 deletions application/admin-client/cypress/e2e/settings.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,7 @@ describe('Settings page', () => {
cy.contains('Invalid url').should('not.exist')
})

it('Attempting to invite participants while email is not set up shows error message', () => {
cy.login(UserType.ADMIN)
cy.visit('/settings')

cy.get('[data-cy="mailerHost"] input').should('have.value', 'smtp.ethereal.email')

cy.get('[data-cy="mailerHost"] input').clear()
cy.get('[data-cy="save-button"]').click()

cy.visit('/participants')
cy.get('[data-cy="invite-button"]').click()

cy.contains('You need to set up your email').should('exist')
cy.contains('Go to').click()

cy.url().should('contain', '/settings')
})

/*
it('Redcap import page does not allow API features if not set up', () => {
cy.login(UserType.ADMIN)
cy.visit('/settings')
Expand All @@ -106,6 +89,7 @@ describe('Settings page', () => {
cy.contains('Redcap settings').click()
cy.url().should('contain', '/settings#redcap')
})
*/

it('Can update logo', () => {
cy.login(UserType.ADMIN)
Expand Down
48 changes: 48 additions & 0 deletions application/admin-client/cypress/e2e/studyAdmin.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// <reference types="cypress" />

const { UserType } = require('../support/commands')

beforeEach(() => {
cy.task('reset')
})

describe('Study Admins', () => {
it('Add and remove studyadmin from studies', () => {
cy.login(UserType.ADMIN)
cy.visit('/users')
cy.get('[data-cy="edit-button"]').eq(3).click()
cy.contains('Study 2').click()
cy.contains('Added').should('exist')
cy.contains('Study 2').click()
cy.contains('Removed').should('exist')
})

it('Restricted functionality for study admin users', () => {
cy.login(UserType.STUDY_ADMIN)
cy.visit('/users')
//Can't see or visit Settings page
cy.contains('Settings').should('not.exist')
//Can't edit their own role
cy.visit('/users/update/106')
cy.get('[data-cy="role-select"] input').should('be.disabled')
cy.get('[data-cy="first"] input').should('not.be.disabled')
//Can't edit another admin
cy.visit('/users/update/97')
cy.get('[data-cy="role-select"] input').should('be.disabled')
cy.get('[data-cy="first"] input').should('be.disabled')
})
it('Can only see studies they are admin of', () => {
cy.login(UserType.STUDY_ADMIN)
cy.visit('/studies')
cy.contains('Study FE').should('not.exist')
cy.contains('Test Study').should('exist')
})

it('Cannot edit a family if they are not admin of every related study', () => {
cy.login(UserType.STUDY_ADMIN)
cy.visit('/participants/family/edit/100')
cy.get('[data-cy="remove-member-button"]').click()
cy.get('[data-cy="remove-icon-button"]').eq(1).click()
cy.contains('Failed to remove').should('exist')
})
})
6 changes: 4 additions & 2 deletions application/admin-client/cypress/e2e/users.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ describe('Users', () => {
it('Edit user', () => {
cy.login(UserType.ADMIN)
cy.visit('/users')
cy.get('[data-cy="edit-button"]').first().click()
cy.get('[data-cy="edit-button"]').eq(1).click()
cy.get('input').eq(0).type('A')
cy.contains('Save').click()
cy.contains('OperatorA').should('exist')
cy.contains('Success').should('exist')
cy.visit('/users')
cy.contains('OrganisationA').should('exist')
})
})
4 changes: 3 additions & 1 deletion application/admin-client/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum UserType {
PARTICIPANT_COMPLETED = '[email protected]',
PARTICIPANT_UNANSWERED = '[email protected]',
ADMIN = '[email protected]',
STUDY_ADMIN = '[email protected]',
}
Cypress.Commands.add('login', (type: UserType) => {
cy.request({
Expand All @@ -32,8 +33,9 @@ Cypress.Commands.add('login', (type: UserType) => {
}).then((res) => {
expect(res.status).to.equal(200)
expect(res.body.token).to.exist
console.log('TOKEN', res.body.token)
window.localStorage.setItem('refine-auth', res.body.token)
window.localStorage.setItem('userrole', res.body.role)
window.localStorage.setItem('userid', res.body.id)
})
})

Expand Down
2 changes: 2 additions & 0 deletions application/admin-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import OTP from './pages/login/OTP'
import { Box, Typography } from '@mui/material'
import RestorePage from './pages/restore'
import { accessControlProvider } from './providers/accessControlProvider'

function App() {
return (
Expand All @@ -67,6 +68,7 @@
notificationProvider={notificationProvider}
routerProvider={routerBindings}
authProvider={authProvider}
accessControlProvider={accessControlProvider}
resources={[
{
name: 'surveys',
Expand Down Expand Up @@ -177,7 +179,7 @@
clientConfig: {
defaultOptions: {
queries: {
retry: (failureCount: number, error: any) => {

Check warning on line 182 in application/admin-client/src/App.tsx

View workflow job for this annotation

GitHub Actions / build and check

Unexpected any. Specify a different type
if (error.status == 401) {
return false
}
Expand Down
80 changes: 33 additions & 47 deletions application/admin-client/src/components/RedcapImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ import {
DialogContentText,
DialogActions,
} from '@mui/material'
import { useEffect, useState } from 'react'
import { useState } from 'react'
import { Close, ArrowBack } from '@mui/icons-material'
import ReactMarkdown from 'react-markdown'
import { useNotification, useBack } from '@refinedev/core'
import { GetSettingsResponse } from '@common/types/api/settings'
import { HashLink } from 'react-router-hash-link'
import { axiosInstance } from '../providers/dataProvider'
//import { GetSettingsResponse } from '@common/types/api/settings'
//import { axiosInstance } from '../providers/dataProvider'

interface RedcapImportProps {
type: 'survey' | 'participant'
Expand Down Expand Up @@ -80,16 +79,14 @@ export const RedcapImport = ({
const openDialog = () => setDialogOpen(true)
const closeDialog = () => setDialogOpen(false)

const [redcapIsSetup, setRedcapIsSetup] = useState(true)

useEffect(() => {
/*useEffect(() => {
axiosInstance.get('/settings').then((res) => {
const settings = res.data.data as GetSettingsResponse['data']
if (!settings.redcapToken || !settings.redcapURL) {
setRedcapIsSetup(false)
}
})
}, [])
}, [])*/

return (
<Box>
Expand Down Expand Up @@ -226,45 +223,34 @@ export const RedcapImport = ({
<Typography variant="h6" gutterBottom>
Import from REDCap API
</Typography>
{redcapIsSetup ? (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
{formNameInput && (
<TextField
label="Form Name"
variant="outlined"
value={formName}
onChange={(e) => setFormName(e.target.value)}
sx={{ mb: 2 }}
data-cy="formName"
/>
)}
<Button
variant="contained"
color="primary"
onClick={() => {
if (confirmDialog) {
setDialogType('API')
openDialog()
} else {
handleApiSubmission()
}
}}
disabled={formNameInput && !formName}
data-cy="apiSubmit"
>
Import from API
</Button>
</Box>
) : (
<>
<Typography>Redcap API is not set up</Typography>
{/*
// @ts-ignore */}
<Button component={HashLink} to="/settings#redcap">
Redcap settings
</Button>
</>
)}
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
{formNameInput && (
<TextField
label="Form Name"
variant="outlined"
value={formName}
onChange={(e) => setFormName(e.target.value)}
sx={{ mb: 2 }}
data-cy="formName"
/>
)}
<Button
variant="contained"
color="primary"
onClick={() => {
if (confirmDialog) {
setDialogType('API')
openDialog()
} else {
handleApiSubmission()
}
}}
disabled={formNameInput && !formName}
data-cy="apiSubmit"
>
Import from API
</Button>
</Box>
</Box>
</Box>
</Box>
Expand Down
26 changes: 23 additions & 3 deletions application/admin-client/src/components/StudyLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCustom, useInvalidate } from '@refinedev/core'
import { useCustom, useInvalidate, useLogout } from '@refinedev/core'
import React, { PropsWithChildren, useEffect } from 'react'
import { useStudyStore } from '../studyStore'
import { Box, Button, Typography } from '@mui/material'

export const StudyLoader: React.FC<PropsWithChildren> = ({ children }) => {
const { data } = useCustom({
Expand All @@ -10,19 +11,38 @@
})
const { studies, setStudies, activeStudyIndex, setActiveStudyIndex } = useStudyStore()
const invalidate = useInvalidate()
const { mutate: logout } = useLogout()

useEffect(() => {
if (data) {
const studiesLength = studies.length
setStudies(data.data.data as any)

Check warning on line 19 in application/admin-client/src/components/StudyLoader.tsx

View workflow job for this annotation

GitHub Actions / build and check

Unexpected any. Specify a different type
if (data.data.data.length - studiesLength == 1) setActiveStudyIndex(studies.length) // Ie new study created
if (
studies.length > 0 &&
(data.data.data.length - studiesLength == 1 || activeStudyIndex > studies.length)
) {
setActiveStudyIndex(studies.length) // Ie new study created
}
}
}, [data])

Check warning on line 27 in application/admin-client/src/components/StudyLoader.tsx

View workflow job for this annotation

GitHub Actions / build and check

React Hook useEffect has missing dependencies: 'activeStudyIndex', 'setActiveStudyIndex', 'setStudies', and 'studies.length'. Either include them or remove the dependency array

useEffect(() => {
localStorage.setItem('activeStudyIndex', String(activeStudyIndex))
invalidate({ invalidates: ['all'] })
}, [activeStudyIndex])

Check warning on line 32 in application/admin-client/src/components/StudyLoader.tsx

View workflow job for this annotation

GitHub Actions / build and check

React Hook useEffect has a missing dependency: 'invalidate'. Either include it or remove the dependency array

return studies.length > 0 && children
return studies.length > 0 ? (
children
) : (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
minHeight="60vh"
>
<Typography>You do not have access to any studies</Typography>
<Button onClick={() => logout()}>Log out</Button>
</Box>
)
}
10 changes: 8 additions & 2 deletions application/admin-client/src/pages/integrations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import GroupAddIcon from '@mui/icons-material/GroupAdd'
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'
import { useNavigate } from 'react-router-dom'
import { SensitiveTextField } from '../../components/SensitiveTextField'
import { useCustom } from '@refinedev/core'
import { useCustom, useGetIdentity } from '@refinedev/core'
import { GetElsaTokenResponse } from '@common/types/api/integrations/getElsaToken'
import { axiosInstance } from '../../providers/dataProvider'
import { useState } from 'react'

export const IntegrationsHome = () => {
const navigate = useNavigate()
const [dialogOpen, setDialogOpen] = useState(false)
const { data: identity } = useGetIdentity<{ role: string; id: number }>()

// Add a queryKey for invalidation
const { data, refetch } = useCustom<GetElsaTokenResponse>({
Expand Down Expand Up @@ -97,7 +98,12 @@ export const IntegrationsHome = () => {
<FormControl>
<FormControlLabel
control={
<Checkbox data-cy="elsa-checkbox" checked={enabled} onChange={handleCheckboxChange} />
<Checkbox
disabled={identity?.role != 'OrganisationAdmin'}
data-cy="elsa-checkbox"
checked={enabled}
onChange={handleCheckboxChange}
/>
}
label={<Typography>Enable Elsa Integration</Typography>}
/>
Expand Down
Loading
Loading