Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JN-477] admin help for export #481

Merged
merged 4 commits into from
Jul 20, 2023
Merged
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
5 changes: 4 additions & 1 deletion ui-admin/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext } from 'react'
import React, { lazy, useContext } from 'react'
import 'react-notifications-component/dist/theme.css'
import 'styles/notifications.css'
import 'survey-core/defaultV2.min.css'
Expand All @@ -22,6 +22,7 @@ import UserList from './user/UserList'
import InvestigatorTermsOfUsePage from './terms/InvestigatorTermsOfUsePage'
import PrivacyPolicyPage from 'terms/PrivacyPolicyPage'
import { IdleStatusMonitor } from 'login/IdleStatusMonitor'
const HelpRouter = lazy(() => import('./help/HelpRouter'))


/** container for the app including the router */
Expand All @@ -39,11 +40,13 @@ function App() {
<BrowserRouter>
<Routes>
<Route path="/" element={<PageFrame/>}>
<Route path="help/*" element={<HelpRouter />} />
<Route element={<ProtectedRoute/>}>
<Route path="users" element={<UserList/>}/>
<Route path=":portalShortcode/*" element={<PortalProvider><PortalRouter/></PortalProvider>}/>
<Route index element={<PortalList/>}/>
</Route>

<Route path="privacy" element={<PrivacyPolicyPage />} />
<Route path="terms" element={<InvestigatorTermsOfUsePage />} />
<Route path="*" element={<div>Unknown page</div>}/>
Expand Down
115 changes: 115 additions & 0 deletions ui-admin/src/help/ExportHelp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react'

/** guide to using the participant export/download function */
export default function ExportHelp() {
return <div>
<h3>Participant List Export Info</h3>
<p>Participant export enables download of tabular files (.tsv or .xlsx) containing all participants
except those who have withdrawn from the study.
One row will be generated per participant.</p>

<h4>File format</h4>
<div className="panel">
<ul>
<li>
<b>.xlsx</b> will create an Excel spreadsheet of the participant data. Empty cells will represent
null values.
</li>
<li>
<b>.tsv</b> (tab-delimited values). Will export a tab-delimited file. This may be useful in
environments where Excel is unavailable, or if the
number of columns to be exported exceeds 16K. In order to have data be compliant, double-quotes will
be replaced by single quotes, and any values including
Comment on lines +21 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't really necessary to replace double quotes with single quotes. It's unlikely that the meaning of any responses will be different, but I have to confess to being a little uneasy about any modification of responses.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right that they can be escaped, but other applications handling of escaped quotes in csv files varies enough that it was decided for Pepper that this strategy would result in less user issues (and they can always grab the .xlsx format if quote fidelity is important)

tabs or line breaks will be surrounded in double-quotes.
</li>
</ul>


</div>

<h4>Human readable / Analysis friendly</h4>
<ul>
<li>
<b>Analysis friendly</b> Each picklist answers will be displayed as a stable id, rather than the
displayed text. For multiselects, each answer option will appear in a separate column. For example, the
question &quot;Which symptoms have you had?&quot;
with options &quot;fever&quot;, &quot;nausea&quot;, and &quot;persisent cough&quot;,
will be exported into 3 columns.
<table className="table table-striped">
<thead>
<tr>
<td>MEDICAL_HISTORY.SYMTPOMS.FEVER</td>
<td>MEDICAL_HISTORY.SYMPTOMS.NAUSEA</td>
<td>MEDICAL_HISTORY.SYMPTOMS.COUGH</td>
</tr>
<tr>
<td>fever</td>
<td>nausea</td>
<td>persistent cough</td>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</tbody>
</table>

</li>
<li>
<b>Human readable</b> will use display text where possible, and will show multi-select questions as a
single column, with a comma-delimited string of the answers given. For example, the question &quot;Which
symptoms have you had?&quot;
with options &quot;fever&quot;, &quot;nausea&quot;, and &quot;persisent cough&quot;,
will be exported into 1 column.
<table className="table table-striped">
<thead>
<tr>
<td>MEDICAL_HISTORY.SYMTPOMS</td>
</tr>
<tr>
<td>symptoms</td>
</tr>
</thead>
<tbody>
<tr>
<td>nausea</td>
</tr>
<tr>
<td>fever, nausea</td>
</tr>
<tr>
<td></td>
</tr>
</tbody>
</table>
</li>
</ul>
<h4>Include all completions of an activity</h4>
This option controls how the export will behave if a participant has completed an activity multiple times.
<ul>
<li>
<b>Yes</b> A new set of columns will be added to the export for each time the activity was completed.
These will be
denoted by _2, _3, etc... Columns will appear in order of *recency*. So e.g. MEDICAL_HISTORY.SYMPTOMS
represents the
most recent completion, while MEDICAL_HISTORY_2.SYMPTOMS represents the next-most recent, and so on.
</li>
<li>
<b>No</b> Only the most recent completion for each activity will be included in the export.
</li>
</ul>
</div>
}
12 changes: 12 additions & 0 deletions ui-admin/src/help/HelpPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react'
import { Link } from 'react-router-dom'

/** shows the root help page. No structure yet */
export default function HelpPage() {
return <div>
<h1 className="h3 mb-3">Juniper help topics</h1>
<div>
<Link to="export">Participant export</Link>
</div>
</div>
}
15 changes: 15 additions & 0 deletions ui-admin/src/help/HelpRouter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import ExportHelp from './ExportHelp'
import HelpPage from './HelpPage'

/** routes across individual help pages -- catches any unmatched routes to the main index */
export default function HelpRouter() {
return <div className="container p-4">
<Routes>
<Route path="export" element={<ExportHelp/>}/>
<Route index element={<HelpPage/>}/>
<Route path="*" element={<HelpPage/>}/>
</Routes>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from 'react'

import { setupRouterTest } from 'test-utils/router-testing-utils'
import { mockStudyEnvContext } from 'test-utils/mocking-utils'
import { render, screen } from '@testing-library/react'
import { render, screen, waitFor } from '@testing-library/react'
import ExportDataControl from './ExportDataControl'
import userEvent from '@testing-library/user-event'

test('renders the file types', async () => {
const { RoutedComponent } = setupRouterTest(
Expand All @@ -13,3 +14,12 @@ test('renders the file types', async () => {
expect(screen.getByText('Tab-delimted (.tsv)')).toBeInTheDocument()
expect(screen.getByText('Excel (.xlsx)')).toBeInTheDocument()
})

test('help page loads', async () => {
const { RoutedComponent } = setupRouterTest(
// eslint-disable-next-line @typescript-eslint/no-empty-function
<ExportDataControl studyEnvContext={mockStudyEnvContext()} show={true} setShow={() => {}}/>)
render(RoutedComponent)
userEvent.click(screen.getByText('help page'))
waitFor(() => expect(screen.getByText('Participant List Export Info')).toBeInTheDocument())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I didn't expect to not need any code to recognize the content opened in a new window.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing that the headless browser just ignores the target="_blank" and opens it in the same window

})
13 changes: 9 additions & 4 deletions ui-admin/src/study/participants/export/ExportDataControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Api from 'api/api'
import { currentIsoDate } from 'util/timeUtils'
import { failureNotification } from 'util/notifications'
import { Store } from 'react-notifications-component'
import { Link } from 'react-router-dom'

const FILE_FORMATS = [{
label: 'Tab-delimted (.tsv)',
Expand Down Expand Up @@ -74,10 +75,9 @@ const ExportDataControl = ({ studyEnvContext, show, setShow }: {studyEnvContext:

return <Modal show={show} onHide={() => setShow(false)}>
<Modal.Header closeButton>
<Modal.Title>Download</Modal.Title>
<div className="ms-4">
{studyEnvContext.study.name}: {studyEnvContext.currentEnv.environmentName}
</div>
<Modal.Title>
Download
</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={e => e.preventDefault()}>
Expand Down Expand Up @@ -114,6 +114,11 @@ const ExportDataControl = ({ studyEnvContext, show, setShow }: {studyEnvContext:
{format.label}
</label>)}
</div>
<hr/>
<div>
For more information about download formats,
see the <Link to={'/help/export'} target="_blank">help page</Link>.
</div>
</form>
</Modal.Body>
<Modal.Footer>
Expand Down
Loading