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

Added Selenium tests for inventory page #75

Open
wants to merge 29 commits into
base: react
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4b53fc0
columns and searching work
wwick Apr 11, 2021
64cb3f7
basic inventory list
wwick Apr 25, 2021
6f941b1
able to submit forms from modal
wwick May 22, 2021
2f8eeeb
remove extra link to selenium
wwick Feb 1, 2022
63a0264
Merge branch 'react' into react-inventory
wwick Feb 6, 2022
0e263ee
Merge branch 'react' into react-inventory
wwick Feb 6, 2022
b5beac0
add example drugs
wwick Feb 6, 2022
f40744f
editing date
BenMueller1 Feb 11, 2022
2cd5f12
editing date
BenMueller1 Feb 11, 2022
89876e7
highlighted expired drugs and fixed dispense button styling
BenMueller1 Feb 12, 2022
8e8aec2
changed pre expiration period to 60 days
BenMueller1 Feb 12, 2022
9f7d9e3
colors fixed and small error fixed
BenMueller1 Feb 13, 2022
ccb900b
fixed active patient dropdown
BenMueller1 Feb 13, 2022
300c291
fixed drug dispense form
BenMueller1 Feb 13, 2022
dff1b0c
Merge remote-tracking branch 'origin/react-inventory' into react-inve…
BenMueller1 Feb 13, 2022
4654203
added links to drug pages
BenMueller1 Feb 14, 2022
874c362
Merge branch 'react' into react-inventory
wwick Feb 15, 2022
6a74f87
removed non react drug inventory
BenMueller1 Feb 15, 2022
718f5bc
Merge remote-tracking branch 'origin' into react-inventory
BenMueller1 Feb 20, 2022
3068e09
Merge remote-tracking branch 'origin/react-inventory' into react-inve…
BenMueller1 Feb 20, 2022
85767ca
got one inventory test working
BenMueller1 Feb 21, 2022
9bb3030
second test is halfway complete
BenMueller1 Feb 21, 2022
26cd5fa
two tests done except integrityerror on second one
BenMueller1 Mar 3, 2022
464dca3
fixed dispense form test assertion
BenMueller1 Mar 4, 2022
0a744e6
cleaning up code
BenMueller1 Mar 6, 2022
d8dace6
cleaning up
BenMueller1 Mar 6, 2022
9b334f7
cleaning up
BenMueller1 Apr 15, 2022
c112398
fixed searching
BenMueller1 Apr 17, 2022
e5c0c5c
updated codecov version as needed
BenMueller1 Apr 25, 2023
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
9 changes: 4 additions & 5 deletions osler/assets/core/all-patients/PatientTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import axios from 'axios';
import TableManager from './TableManager';
import Container from 'react-bootstrap/Container';
import globalFilter from './globalFilter';

import { DateTime } from 'luxon'

function PatientTable(props) {

Expand All @@ -29,12 +29,11 @@ function PatientTable(props) {
if (wu == null) {
return 'Intake';
}
const date = new Date(wu.written_datetime);
const dt = DateTime.fromISO(wu.written_datetime);
const options = {
year: 'numeric', month: 'short', day: 'numeric'
}
// should default to current locale
const dateString = date.toLocaleDateString(undefined, options);
const dateString = dt.toLocaleString(options);
const infoString = wu.is_pending ? "Pending from" : "Seen";
return (
<div>
Expand Down Expand Up @@ -98,7 +97,7 @@ function PatientTable(props) {
columns={columns}
data={data}
globalFilter={globalFilter}
id='all-patients-table'
id='all-patients-table'
/>
)}
</Container>
Expand Down
4 changes: 2 additions & 2 deletions osler/assets/core/all-patients/SearchBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ function SearchBar({
<FormControl
aria-label="search bar"
aria-describedby="search-addon"
id="all-patients-filter-input"
placeholder="Filter by patient name"
id="search-bar"
placeholder="Search"
value={value || ""}
onChange={e => {
setValue(e.target.value);
Expand Down
29 changes: 21 additions & 8 deletions osler/assets/core/all-patients/TableManager.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from "react";
import { useTable, useGlobalFilter, usePagination } from "react-table";
import { useTable, useGlobalFilter, usePagination, useSortBy } from "react-table";
import SearchBar from "./SearchBar";
import PaginationBar from "./PaginationBar";
import Table from "react-bootstrap/Table";

import { BsArrowUp, BsArrowDown } from "react-icons/bs"

function TableManager({ columns, data, globalFilter, id }) {
// mainly follow official example from react-table

const {
getTableProps,
getTableBodyProps,
Expand All @@ -27,24 +27,37 @@ function TableManager({ columns, data, globalFilter, id }) {
columns,
data,
globalFilter: globalFilter,
initialState: { pageIndex: 0 },
initialState: {
pageIndex: 0,
},
},
useGlobalFilter,
usePagination
useSortBy,
usePagination,
);

return (
<div>
<SearchBar
{<SearchBar
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
/>}
Comment on lines +41 to +44
Copy link
Member

Choose a reason for hiding this comment

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

remove extra brackets

<Table {...getTableProps()} id={id}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render("Header")}
{column.isSorted &&
<span>
{column.isSortedDesc
? <BsArrowDown />
: <BsArrowUp />
}
</span>
}
</th>
))}
</tr>
))}
Expand Down
193 changes: 193 additions & 0 deletions osler/assets/inventory/drug-list/DrugListTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import TableManager from '../../core/all-patients/TableManager';
import CSRFToken from '../../util/CSRFToken';
import compare from '../../util/compare';
import simpleComparator from '../../util/simpleComparator';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import Form from 'react-bootstrap/Form';
import globalFilter from './globalFilter';
import { DateTime } from 'luxon';


function DrugListTable(props) {

const [state, setState] = useState({ 'show': false, 'drug': {} });

const handleClose = () => {
setState({ 'show': false, 'drug': {} });
}
const handleShow = (e, row) => {
setState({ 'show': true, 'drug': row });
}

const nameComparator = simpleComparator('name');
const categoryComparator = simpleComparator('category');
const expirationDateComparator = simpleComparator('expiration_date');
const stockComparator = simpleComparator('stock');
Comment on lines +26 to +29
Copy link
Member

Choose a reason for hiding this comment

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

There's still a bug with the comparators. It seems that all the comparators after nameComparator are just returning the same result as the name comparator. I'm fairly certain this is a bug related to the memoization in simpleComparator. useMemo() is required by React Table, and it allows you to specify dependencies, which I'm guessing we need to do

Copy link
Member

Choose a reason for hiding this comment

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

Correct sorting is something that the selenium tests really need to check for


const manufacturerComparator = React.useMemo(() => (rowA,rowB,columnId,desc) => {
let cmp = compare(rowA.original.unit,rowB.original.unit);
if (cmp == 0) {
cmp = compare(rowA.original.manufacturer.toLowerCase(), rowB.original.manufacturer.toLowerCase());
}
return cmp;
});
Comment on lines +31 to +37
Copy link
Member

Choose a reason for hiding this comment

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

There shouldn't be any comparison of units. The simple lowerCase() comparator should work fine here


const doseComparator = React.useMemo(() => (rowA,rowB,columnId,desc) => {
let cmp = compare(rowA.original.unit,rowB.original.unit);
if (cmp == 0) {
cmp = compare(rowA.original.dose, rowB.original.dose);
}
return cmp;
});

const columns = React.useMemo(
() => {
let cols = [
{
Header: 'Name',
accessor: (row) => <a href={"drug/update/" + row.id}>{row.name}</a>,
sortType: nameComparator,
},
{
Header: 'Dose',
accessor: (row) => `${row.dose} ${row.unit}`,
sortType: doseComparator,
},
{
Header: 'Category',
accessor: (row) => <strong>{row.category}</strong>,
sortType: categoryComparator,
},
{
Header: 'Stock',
accessor: (row) => <strong>{row.stock}</strong>,
sortType: stockComparator,
},
{
Header: 'Lot Number',
accessor: 'lot_number',
},
{
Header: 'Expiration Date',
accessor: (row) => {
const dt = DateTime.fromISO(row.expiration_date);
const now = DateTime.now();
const options = {
year: 'numeric', month: 'short', day: 'numeric'
}

if (dt <= now) {
return <strong style={{color: "red"}}>{dt.toLocaleString(options)}</strong>
}
if (dt <= now.plus({ days: 60})) {
return <strong style={{color: "gold"}}>{dt.toLocaleString(options)}</strong>
}
return dt.toLocaleString(options);
},
sortType: expirationDateComparator,
},
{
Header: 'Manufacturer',
accessor: 'manufacturer',
sortType: manufacturerComparator,
},
{
Header: 'Dispense',
accessor: (row) => {
return (
<Button variant="success" onClick={e => handleShow(e, row)}>
Dispense
</Button>
);
},
disableSortBy: true,
}
]
return cols;
}
,
[]
);

const [loading, setLoading] = useState(true);
const [drugs, setDrugs] = useState([]);
const [patients, setPatients] = useState([]);

useEffect(() => {
const drugUrl = "/api/drugs";
const ptUrl = "/api/patients/?fields=name,age,gender,id&filter=active";
Promise.all([
axios.get(drugUrl),
axios.get(ptUrl),
]).then(function(res) {
setDrugs(res[0].data);
setPatients(res[1].data);
setLoading(false);
});
}, []);

return (
<Container>
{loading ? (
<span>Loading...</span>
) : (
<>
<TableManager
columns={columns}
data={drugs}
globalFilter={globalFilter}
id='drug-list-table'
/>
<Modal show={state.show} onHide={handleClose}>
{state.drug &&
<Form action="/inventory/drug-dispense/" method="post">
<CSRFToken />
<Modal.Header closeButton>
<Modal.Title>Dispense Drug</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Group>
<Form.Label>
How much <strong>{state.drug.dose} {state.drug.unit}</strong> would you like to dispense? (Current stock: <strong>{state.drug.stock}</strong>)
</Form.Label>
<Form.Control type="number" min={1} max={state.drug.stock} defaultValue={1} name="num" id="num"/>
</Form.Group>
<Form.Group>
<Form.Control type="hidden" name="pk" id="pk" value={state.drug.id} />
</Form.Group>
<Form.Group>
<Form.Label>
For which patient would you like to dispense <strong>{state.drug.name}</strong>?
</Form.Label>
<Form.Control as="select" name="patient_pk" id="patient_pk">
<option disabled value> -- select patient -- </option>
{patients.map(pt =>
<option value={pt.id} key={pt.id}>
{pt.name} {pt.age}/{pt.gender}
</option>
)}
</Form.Control>
</Form.Group>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" type="submit">
Dispense Drug
</Button>
</Modal.Footer>
</Form>
}
</Modal>
</>
)}
</Container>
);
}

export default DrugListTable
10 changes: 10 additions & 0 deletions osler/assets/inventory/drug-list/globalFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function globalFilter(rows, columnIds, globalFilterValue) {
const filterValue = globalFilterValue.toLowerCase();
return rows.filter((row) => {
const name = row.original.name.toLowerCase();
const lot_number = row.original.lot_number.toLowerCase();
return (name.includes(filterValue) || lot_number.includes(filterValue));
});
}

export default globalFilter;
13 changes: 13 additions & 0 deletions osler/assets/inventory/drug-list/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import "regenerator-runtime/runtime";

import React from 'react';
import ReactDOM from 'react-dom';
import DrugListTable from './DrugListTable';


export function render(props) {
ReactDOM.render(
<DrugListTable {...props} />,
document.getElementById('root')
);
}
11 changes: 11 additions & 0 deletions osler/assets/util/CSRFToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import getCookie from './getCookie';

const csrftoken = getCookie('csrftoken');

const CSRFToken = () => {
return (
<input type="hidden" name="csrfmiddlewaretoken" value={csrftoken} />
);
};
export default CSRFToken;
7 changes: 7 additions & 0 deletions osler/assets/util/compare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function compare(a,b) {
if (a < b) return -1;
if (b > a) return 1;
return 0;
}

export default compare;
17 changes: 17 additions & 0 deletions osler/assets/util/getCookie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}

export default getCookie;
7 changes: 7 additions & 0 deletions osler/assets/util/simpleComparator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import compare from './compare';
import React from 'react';

const simpleComparator = (field) => React.useMemo(
() => (rowA,rowB,columnId,desc) => compare(rowA.original[field],rowB.original[field]));

export default simpleComparator;
Loading