@@ -118,15 +126,15 @@ class UploadFileForm extends Component {
Filesize: undefined,
};
- let hasError = {
- Filename: undefined,
- Filesize: undefined,
- };
-
if (!formData.file) {
errorMessage.Filename = 'You must select a file to upload';
- hasError.Filename = true;
- this.setState({errorMessage, hasError});
+ this.setState({errorMessage});
+ return;
+ }
+
+ if (!formData.project) {
+ errorMessage.Project = 'You must select a project';
+ this.setState({errorMessage});
return;
}
@@ -138,14 +146,13 @@ class UploadFileForm extends Component {
+ maxSizeAllowed
+ ')';
errorMessage['Filesize'] = msg;
- hasError['Filesize'] = true;
swal.fire({
title: 'Error',
text: msg,
type: 'error',
showCancelButton: true,
});
- this.setState({errorMessage, hasError});
+ this.setState({errorMessage});
return;
}
@@ -175,30 +182,30 @@ class UploadFileForm extends Component {
cache: 'no-cache',
}).then(async (response) => {
if (response.status === 409) {
- swal.fire({
- title: 'Are you sure?',
- text: 'A file with this name already exists!\n '
+ swal.fire({
+ title: 'Are you sure?',
+ text: 'A file with this name already exists!\n '
+ 'Would you like to overwrite existing file?\n '
+ 'Note that the version associated with '
+ 'the file will also be overwritten.',
- type: 'warning',
- showCancelButton: true,
- confirmButtonText: 'Yes, I am sure!',
- cancelButtonText: 'No, cancel it!',
- }).then((isConfirm) => {
- if (isConfirm && isConfirm.value) {
- this.uploadFile(true);
- }
- });
+ type: 'warning',
+ showCancelButton: true,
+ confirmButtonText: 'Yes, I am sure!',
+ cancelButtonText: 'No, cancel it!',
+ }).then((isConfirm) => {
+ if (isConfirm && isConfirm.value) {
+ this.uploadFile(true);
+ }
+ });
} else if (!response.ok) {
const body = await response.json();
let msg;
if (body && body.error) {
- msg = body.error;
+ msg = body.error;
} else if (response.statusText) {
- msg = response.statusText;
+ msg = response.statusText;
} else {
- msg = 'Upload error!';
+ msg = 'Upload error!';
}
this.setState({
errorMessage: msg,
@@ -207,13 +214,13 @@ class UploadFileForm extends Component {
swal.fire(msg, '', 'error');
console.error(msg);
} else {
- swal.fire({
- text: 'Upload Successful!',
- title: '',
- type: 'success',
- }).then(function() {
- window.location.assign('/data_release');
- });
+ swal.fire({
+ text: 'Upload Successful!',
+ title: '',
+ type: 'success',
+ }).then(function() {
+ window.location.assign('/data_release');
+ });
}
}).catch( (error) => {
let msg = error.message ? error.message : 'Upload error!';
@@ -230,6 +237,7 @@ class UploadFileForm extends Component {
UploadFileForm.propTypes = {
DataURL: PropTypes.string.isRequired,
action: PropTypes.string.isRequired,
+ projects: PropTypes.array.isRequired,
};
export default UploadFileForm;
diff --git a/modules/data_release/php/data_release.class.inc b/modules/data_release/php/data_release.class.inc
index 63bb7ec4f8c..47ffded4583 100644
--- a/modules/data_release/php/data_release.class.inc
+++ b/modules/data_release/php/data_release.class.inc
@@ -1,4 +1,5 @@
-pselectColWithIndexKey(
- "SELECT ID, UserID FROM users",
- [],
+ "SELECT DISTINCT ID, LOWER(users.UserID) as UserID FROM users
+ JOIN user_project_rel upr ON upr.UserID = users.ID
+ WHERE upr.ProjectID IN
+ (SELECT ProjectID FROM user_project_rel WHERE UserID = :userID)
+ ORDER BY LOWER(users.UserID)",
+ [':userID' => \User::singleton()->getID()],
"ID"
);
}
@@ -74,8 +79,10 @@ class Data_Release extends \DataFrameworkMenu
function getFilesList(\Database $DB)
{
$result = $DB->pselectWithIndexKey(
- "SELECT id, file_name, version FROM data_release",
- [],
+ "SELECT id, file_name, version, ProjectID FROM data_release
+ WHERE ProjectID IS NULL OR ProjectID IN
+ (SELECT ProjectID FROM user_project_rel WHERE UserID = :userID)",
+ [':userID' => \User::singleton()->getID()],
"id"
);
@@ -98,9 +105,12 @@ class Data_Release extends \DataFrameworkMenu
*/
function getVersionsList(\Database $DB)
{
+ $user =& \User::singleton();
$versions = $DB->pselectCol(
- "SELECT DISTINCT version FROM data_release",
- [],
+ "SELECT DISTINCT version FROM data_release
+ WHERE ProjectID IS NULL OR ProjectID IN
+ (SELECT ProjectID FROM user_project_rel WHERE UserID = :userID)",
+ ['userID' => $user->getID()],
"version"
);
@@ -124,8 +134,10 @@ class Data_Release extends \DataFrameworkMenu
function getVersionFiles(\Database $db)
{
$result = $db->pselect(
- "SELECT version, id FROM data_release",
- []
+ "SELECT version, id FROM data_release
+ WHERE ProjectID IS NULL OR ProjectID IN
+ (SELECT ProjectID FROM user_project_rel WHERE UserID = :userID)",
+ [':userID' => \User::singleton()->getID()]
);
$versionFiles = [];
@@ -150,16 +162,23 @@ class Data_Release extends \DataFrameworkMenu
$result = $db->pselect(
"SELECT u.ID as userId,
u.UserID as userName,
- drp.data_release_id fileId
+ drp.data_release_id fileId,
+ dr.ProjectID as ProjectID
FROM users u
LEFT JOIN data_release_permissions drp ON (u.ID=drp.userid)
- LEFT JOIN data_release dr ON (drp.data_release_id=dr.id)",
- []
+ LEFT JOIN data_release dr ON (drp.data_release_id=dr.id)
+ JOIN user_project_rel upr ON upr.UserID = u.ID
+ WHERE upr.ProjectID IN
+ (SELECT ProjectID FROM user_project_rel WHERE UserID = :userID)",
+ [':userID' => \User::singleton()->getID()]
);
+ error_log(print_r($result, true));
+
$userFiles = [];
foreach ($result as $row) {
$userFiles[$row['userId']]['name'] = $row['userName'];
+ $userFiles[$row['userId']]['id'] = $row['userId'];
if (empty($userFiles[$row['userId']]['files'])) {
$userFiles[$row['userId']]['files'] = [];
}
@@ -205,11 +224,13 @@ class Data_Release extends \DataFrameworkMenu
*/
protected function getFieldOptions() : array
{
- $db = $this->loris->getDatabaseConnection();
+ $db = $this->loris->getDatabaseConnection();
+ $projects = \Utility::getProjectList();
return [
'users' => $this->getUsersList($db),
'versions' => $this->getVersionsList($db),
'filenames' => $this->getFilesList($db),
+ 'projects' => array_combine($projects, $projects)
];
}
diff --git a/modules/data_release/php/datareleaseprovisioner.class.inc b/modules/data_release/php/datareleaseprovisioner.class.inc
index 73939c2c7bf..f3eab13e526 100644
--- a/modules/data_release/php/datareleaseprovisioner.class.inc
+++ b/modules/data_release/php/datareleaseprovisioner.class.inc
@@ -1,4 +1,5 @@
hasPermission("superuser")) {
$query .= "
@@ -53,7 +56,11 @@ class DataReleaseProvisioner extends \LORIS\Data\Provisioners\DBRowProvisioner
ON
(dr.id=drp.data_release_id)
WHERE
- drp.UserID=".$user->getID();
+ drp.UserID=".$user->getID()." AND (
+ dr.ProjectID IS NULL OR dr.ProjectID IN
+ (SELECT ProjectID FROM user_project_rel
+ WHERE UserID = ".$user->getID().")
+ )";
}
$query .= " ORDER BY uploadDate";
diff --git a/modules/data_release/php/datareleaserow.class.inc b/modules/data_release/php/datareleaserow.class.inc
index f1ec2d5c702..4d876055b46 100644
--- a/modules/data_release/php/datareleaserow.class.inc
+++ b/modules/data_release/php/datareleaserow.class.inc
@@ -1,4 +1,5 @@
loris->getDatabaseConnection();
if ($version !== null) {
$version = strtolower($version);
}
+ // Get ProjectID
+ $ProjectID = $DB->pselectOne(
+ "SELECT ProjectID FROM Project WHERE Name=:project",
+ ['project' => $projectName]
+ );
+
$upload_date = date('Y-m-d');
if ($overwrite) {
// update file in data_release table.
@@ -212,6 +223,7 @@ class Files extends \NDB_Page
[
'version' => $version,
'upload_date' => $upload_date,
+ 'ProjectID' => $ProjectID,
],
['file_name' => $fileName]
);
@@ -223,6 +235,7 @@ class Files extends \NDB_Page
'file_name' => $fileName,
'version' => $version,
'upload_date' => $upload_date,
+ 'ProjectID' => $ProjectID,
]
);
@@ -309,7 +322,7 @@ class Files extends \NDB_Page
[]
);
- $dataReleaseFiles = array_column($filesList, 'file_name');
+ $dataReleaseFiles = array_column(iterator_to_array($filesList), 'file_name');
$results = [
'files' => $dataReleaseFiles,
diff --git a/modules/data_release/php/module.class.inc b/modules/data_release/php/module.class.inc
index e313b6675a7..25db09da0f1 100644
--- a/modules/data_release/php/module.class.inc
+++ b/modules/data_release/php/module.class.inc
@@ -1,4 +1,5 @@
- resp.json())
- .then((data) => this.setState({fieldOptions: data, isLoaded: true}))
- .catch((error) => {
- this.setState({error: true});
- console.error(error);
- });
+ // Load the field options. This comes from a separate request than the
+ // table data stream. Once the fieldOptions are loaded, we set isLoaded
+ // to true so that the page is displayed with the data that's been
+ // retrieved.
+ fetch(this.props.fieldsURL, {credentials: 'same-origin'})
+ .then((resp) => resp.json())
+ .then((data) => this.setState({fieldOptions: data, isLoaded: true}))
+ .catch((error) => {
+ this.setState({error: true});
+ console.error(error);
+ });
this.fetchData();
}
/**
* Retrive data from the provided URL and save it in state
*/
- fetchData() {
- fetchDataStream(this.props.dataURL,
- (row) => this.state.data.push(row),
- (end) => {
- this.setState({isLoading: !end, data: this.state.data});
- },
- () => {},
- );
+ fetchData() {
+ fetchDataStream(this.props.dataURL,
+ (row) => this.state.data.push(row),
+ (end) => {
+ this.setState({isLoading: !end, data: this.state.data});
+ },
+ () => {},
+ );
}
/**
@@ -129,7 +129,7 @@ class DataDictIndex extends Component {
contentEditable="true"
className="description"
onBlur={updateDict(rowData)}>
- {cell}
+ {cell}
);
}
@@ -143,7 +143,7 @@ class DataDictIndex extends Component {
*/
render() {
if (this.state.error) {
- return
An error occured while loading the page.
;
+ return
An error occured while loading the page.
;
}
// Waiting for async data to load
@@ -153,61 +153,61 @@ class DataDictIndex extends Component {
const options = this.state.fieldOptions;
let fields = [
- {
- label: 'Source From',
- show: true,
- filter: {
- name: 'Source From',
- type: 'multiselect',
- options: options.sourceFrom,
- },
+ {
+ label: 'Source From',
+ show: true,
+ filter: {
+ name: 'Source From',
+ type: 'multiselect',
+ options: options.sourceFrom,
},
- {
- label: 'Name',
- show: true,
- filter: {
- name: 'Name',
- type: 'text',
- },
+ },
+ {
+ label: 'Name',
+ show: true,
+ filter: {
+ name: 'Name',
+ type: 'text',
},
- {
- label: 'Source Field',
- show: true,
- filter: {
- name: 'Source Field',
- type: 'text',
- },
+ },
+ {
+ label: 'Source Field',
+ show: true,
+ filter: {
+ name: 'Source Field',
+ type: 'text',
},
- {
- label: 'Description',
- show: true,
- filter: {
- name: 'Description',
- type: 'text',
- },
+ },
+ {
+ label: 'Description',
+ show: true,
+ filter: {
+ name: 'Description',
+ type: 'text',
},
- {
- label: 'Description Status',
- show: true,
- filter: {
- name: 'DescriptionStatus',
- type: 'select',
- options: {
- 'empty': 'Empty',
- 'modified': 'Modified',
- 'unchanged': 'Unchanged',
- },
- },
+ },
+ {
+ label: 'Description Status',
+ show: true,
+ filter: {
+ name: 'DescriptionStatus',
+ type: 'select',
+ options: {
+ 'empty': 'Empty',
+ 'modified': 'Modified',
+ 'unchanged': 'Unchanged',
+ },
},
+ },
];
return (
-
+
);
}
}
diff --git a/modules/datadict/php/datadict.class.inc b/modules/datadict/php/datadict.class.inc
index 9c7161ccc7c..4eb1c7746bb 100644
--- a/modules/datadict/php/datadict.class.inc
+++ b/modules/datadict/php/datadict.class.inc
@@ -1,4 +1,5 @@
- {
- const fieldpayload: APIQueryField = {
- module: val.module,
- category: val.category,
- field: val.field,
- };
- if (val.visits) {
- fieldpayload.visits = val.visits;
- }
- return fieldpayload;
- },
- ),
+ const payload: APIQueryObject = {
+ type: 'candidates',
+ fields: fields.map((val: APIQueryField) => {
+ const fieldpayload: APIQueryField = {
+ module: val.module,
+ category: val.category,
+ field: val.field,
+ };
+ if (val.visits) {
+ fieldpayload.visits = val.visits;
+ }
+ return fieldpayload;
+ },
+ ),
+ };
+ if (filters.group.length > 0) {
+ payload.criteria = {
+ operator: filters.operator,
+ group: filters.group.map( (val) => {
+ if (val instanceof QueryTerm) {
+ return val as APIQueryGroupField;
+ } else if (val instanceof QueryGroup) {
+ return val as APIQueryCriteriaGroup;
+ } else {
+ throw new Error('Invalid query');
+ }
+ }),
};
- if (filters.group.length > 0) {
- payload.criteria = {
- operator: filters.operator,
- group: filters.group.map( (val) => {
- if (val instanceof QueryTerm) {
- return val as APIQueryGroupField;
- } else if (val instanceof QueryGroup) {
- return val as APIQueryCriteriaGroup;
- } else {
- throw new Error('Invalid query');
- }
- }),
- };
- }
- return payload;
+ }
+ return payload;
}
diff --git a/modules/dataquery/jsx/components/expansionpanels.tsx b/modules/dataquery/jsx/components/expansionpanels.tsx
index 960b8b90755..1c0dfd4536f 100644
--- a/modules/dataquery/jsx/components/expansionpanels.tsx
+++ b/modules/dataquery/jsx/components/expansionpanels.tsx
@@ -21,7 +21,7 @@ const ExpansionPanels = (props: {
}) => {
return (
+ style={{margin: '0 auto', maxWidth: '900px'}}>
{ props.panels.map((panel, index) => (
string,
}) {
- const groups: SelectGroup[] = [];
- const placeholder = props.placeholder || 'Select a category';
- for (const [module, subcategories]
- of Object.entries(props.groups)) {
- const options: SelectOption[] = [];
- for (const [value, desc]
- of Object.entries(subcategories) as unknown as [string, string]) {
- options.push({
- value: value,
- label: desc,
- module: module,
- });
- }
-
- let label = module;
- if (props.mapGroupName) {
- label = props.mapGroupName(module);
- }
- groups.push({
- label: label,
- options: options,
+ const groups: SelectGroup[] = [];
+ const placeholder = props.placeholder || 'Select a category';
+ for (const [module, subcategories]
+ of Object.entries(props.groups)) {
+ const options: SelectOption[] = [];
+ for (const [value, desc]
+ of Object.entries(subcategories) as unknown as [string, string]) {
+ options.push({
+ value: value,
+ label: desc,
+ module: module,
});
}
- /**
- * Callback to call when the selection changes.
- *
- * @param {object} e - The click event callback
- * @param {string} e.module - The module
- * @param {string} e.value - The value
- * @returns {void}
- */
- const selected = (e: SingleValue) => {
- // The callback should be (e: SelectOption) but typescript
- // is convinced that it's a SingleValue.
- // console.log(e) confirms that it has the same structure
- // as SelectOption, so just convert it and explicitly
- // cast it unsafely to make the compiler happy.
- const val: SelectOption = e as unknown as SelectOption;
- props.onChange(val.module, val.value);
- };
- return (
-
-
+ );
}
export default FilterableSelectGroup;
diff --git a/modules/dataquery/jsx/criteriaterm.tsx b/modules/dataquery/jsx/criteriaterm.tsx
index 8a90309b849..9fe6b2d457a 100644
--- a/modules/dataquery/jsx/criteriaterm.tsx
+++ b/modules/dataquery/jsx/criteriaterm.tsx
@@ -11,25 +11,25 @@ import {FieldDictionary, FullDictionary} from './types';
* @returns {string} - The frontend value to display for op
*/
function op2str(op: string): string {
- switch (op) {
- case 'lt': return '<';
- case 'lte': return '≤';
- case 'eq': return '=';
- case 'neq': return '≠';
- case 'gte': return '≥';
- case 'gt': return '>';
- case 'in': return 'in';
- case 'startsWith': return 'starts with';
- case 'contains': return 'contains';
- case 'endsWith': return 'ends with';
- case 'isnotnull': return 'has data';
- case 'isnull': return 'has no data';
- case 'exists': return 'exists';
- case 'notexists': return 'does not exist';
- case 'numberof': return 'number of';
- default: console.error('Unhandle operator');
- return '';
- }
+ switch (op) {
+ case 'lt': return '<';
+ case 'lte': return '≤';
+ case 'eq': return '=';
+ case 'neq': return '≠';
+ case 'gte': return '≥';
+ case 'gt': return '>';
+ case 'in': return 'in';
+ case 'startsWith': return 'starts with';
+ case 'contains': return 'contains';
+ case 'endsWith': return 'ends with';
+ case 'isnotnull': return 'has data';
+ case 'isnull': return 'has no data';
+ case 'exists': return 'exists';
+ case 'notexists': return 'does not exist';
+ case 'numberof': return 'number of';
+ default: console.error('Unhandle operator');
+ return '';
+ }
}
/**
@@ -41,14 +41,14 @@ function op2str(op: string): string {
* @returns {FieldDictionary} - The dictionary for this term
*/
function getDictionary(
- term: QueryTerm,
- dict: FullDictionary
+ term: QueryTerm,
+ dict: FullDictionary
): FieldDictionary|null {
- if (!dict || !dict[term.module] || !dict[term.module][term.category]
+ if (!dict || !dict[term.module] || !dict[term.module][term.category]
|| !dict[term.module][term.category][term.fieldname]) {
- return null;
- }
- return dict[term.module][term.category][term.fieldname];
+ return null;
+ }
+ return dict[term.module][term.category][term.fieldname];
}
/**
@@ -67,101 +67,101 @@ export function CriteriaTerm(props: {
mapModuleName: (module: string) => string,
mapCategoryName: (module: string, category: string) => string,
}) {
- const containerStyle: React.CSSProperties ={
- display: 'flex' as const,
- flexWrap: 'nowrap' as const,
- flexDirection: 'row' as const,
- justifyContent: 'space-evenly',
- width: '100%',
- alignItems: 'center',
- };
+ const containerStyle: React.CSSProperties ={
+ display: 'flex' as const,
+ flexWrap: 'nowrap' as const,
+ flexDirection: 'row' as const,
+ justifyContent: 'space-evenly',
+ width: '100%',
+ alignItems: 'center',
+ };
- const fieldStyle = {
- width: '33%',
- };
- const opStyle = {
- width: '33%',
- textAlign: 'center' as const,
- };
- const valueStyle = {
- width: '33%',
- display: 'flex',
- alignItems: 'center',
- };
+ const fieldStyle = {
+ width: '33%',
+ };
+ const opStyle = {
+ width: '33%',
+ textAlign: 'center' as const,
+ };
+ const valueStyle = {
+ width: '33%',
+ display: 'flex',
+ alignItems: 'center',
+ };
- let visits;
- if (props.term.visits) {
- visits = '';
- if (props.term.visits.length == 1) {
- visits += props.term.visits[0];
- } else {
- for (let i = 0; i < props.term.visits.length; i++) {
- visits += props.term.visits[i];
- if (i == props.term.visits.length-2) {
- visits += ' or ';
- } else if (i < props.term.visits.length-2) {
- visits += ', ';
- }
- }
+ let visits;
+ if (props.term.visits) {
+ visits = '';
+ if (props.term.visits.length == 1) {
+ visits += props.term.visits[0];
+ } else {
+ for (let i = 0; i < props.term.visits.length; i++) {
+ visits += props.term.visits[i];
+ if (i == props.term.visits.length-2) {
+ visits += ' or ';
+ } else if (i < props.term.visits.length-2) {
+ visits += ', ';
}
- visits =
- at visit
- {visits}
-
;
+ }
}
+ visits =
+ at visit
+ {visits}
+
;
+ }
- let value;
- if (props.term.op == Operators.IN) {
- const liststyle = {
- margin: 0,
- padding: 0,
- listStylePosition: 'inside' as const,
- listStyleType: 'disc',
- };
+ let value;
+ if (props.term.op == Operators.IN) {
+ const liststyle = {
+ margin: 0,
+ padding: 0,
+ listStylePosition: 'inside' as const,
+ listStyleType: 'disc',
+ };
- value =
- {(props.term.value as string[]).map(
- (val, idx) => - {val}
- )}
-
;
- } else {
- value = {props.term.value};
- }
+ value =
+ {(props.term.value as string[]).map(
+ (val, idx) => - {val}
+ )}
+
;
+ } else {
+ value = {props.term.value};
+ }
- let cardinalityWarning;
- const dict = getDictionary(props.term, props.fulldictionary);
- if (!dict) {
- // This sometimes happens when first loading, before the dictionary
- // is retrieved, so we do not print an error.
- } else if (dict.cardinality == 'many') {
- cardinalityWarning = ;
- }
- return (
-
-
-
-
-
{op2str(props.term.op)}
-
-
{value}
-
{visits}
- {cardinalityWarning}
-
-
);
+ >;
+ }
+ return (
+
+
+
+
+
{op2str(props.term.op)}
+
+
{value}
+
{visits}
+ {cardinalityWarning}
+
+
);
}
diff --git a/modules/dataquery/jsx/definefields.tsx b/modules/dataquery/jsx/definefields.tsx
index b0a42771bbd..90c71789fdc 100644
--- a/modules/dataquery/jsx/definefields.tsx
+++ b/modules/dataquery/jsx/definefields.tsx
@@ -53,12 +53,12 @@ function QueryField(props: {
const value=props.value;
const scrollRef = useRef(null);
useEffect(() => {
- if (props.scrollTo == true && scrollRef.current !== null) {
- scrollRef.current.scrollIntoView({
- behavior: 'smooth',
- });
- props.resetScrollTo();
- }
+ if (props.scrollTo == true && scrollRef.current !== null) {
+ scrollRef.current.scrollIntoView({
+ behavior: 'smooth',
+ });
+ props.resetScrollTo();
+ }
}, [props.scrollTo]);
let visits;
@@ -73,41 +73,41 @@ function QueryField(props: {
* @returns {void}
*/
const selected = (newvisits: readonly VisitOption[]) => {
- props.onChangeVisitList(
- props.module,
- props.category,
- item,
- newvisits.map( (visit: VisitOption) => visit.value),
- );
+ props.onChangeVisitList(
+ props.module,
+ props.category,
+ item,
+ newvisits.map( (visit: VisitOption) => visit.value),
+ );
};
const selectOptions: string[] = value.visits || [];
if (props.selected && (typeof props.selected.visits !== 'undefined')) {
- selectedVisits = props.selected.visits;
+ selectedVisits = props.selected.visits;
} else {
- selectedVisits = selectOptions.filter((visit: string) => {
- return props.defaultVisits.includes(visit);
- });
+ selectedVisits = selectOptions.filter((visit: string) => {
+ return props.defaultVisits.includes(visit);
+ });
}
if (props.selected) {
- visits = e.stopPropagation()}>
-
Visits
-
{
- return {value: visit, label: visit};
- })
- }
- isMulti
- onChange={selected}
- placeholder='Select Visits'
- value={selectedVisits.map( (visit: string): VisitOption => {
- return {value: visit, label: visit};
- })
- }
- menuPortalTarget={document.body}
- styles={
- {menuPortal:
+ visits = e.stopPropagation()}>
+
Visits
+ {
+ return {value: visit, label: visit};
+ })
+ }
+ isMulti
+ onChange={selected}
+ placeholder='Select Visits'
+ value={selectedVisits.map( (visit: string): VisitOption => {
+ return {value: visit, label: visit};
+ })
+ }
+ menuPortalTarget={document.body}
+ styles={
+ {menuPortal:
/**
* Adds appropriate zIndex to the react select's base CSS
*
@@ -115,34 +115,34 @@ function QueryField(props: {
* @returns {object} New CSS with z-index added
*/
(base) => ({...base, zIndex: 9999}),
- }
- }
- closeMenuOnSelect={false}
- />
- ;
+ }
+ }
+ closeMenuOnSelect={false}
+ />
+ ;
}
}
const download = value.type == 'URI' ?
: null;
return (
props.onFieldToggle(
- props.module,
- props.category,
- item,
- selectedVisits,
- )}>
-
- - {item}
- - {value.description} {download}
-
- {visits}
+ ref={scrollRef}
+ style={{
+ cursor: 'pointer',
+ display: 'flex',
+ justifyContent: 'space-between',
+ }}
+ onClick={() => props.onFieldToggle(
+ props.module,
+ props.category,
+ item,
+ selectedVisits,
+ )}>
+
+ - {item}
+ - {value.description} {download}
+
+ {visits}
);
}
@@ -202,73 +202,73 @@ function DefineFields(props: {
const [syncVisits, setSyncVisits] = useState(false);
const [zoomTo, setZoomTo] = useState(null);
useEffect(() => {
- if (!syncVisits) {
- return;
- }
- let modifiedvisits = false;
- props.selected.forEach( (field: APIQueryField) => {
- // Valid visits according to the dictionary
- const category = props.fulldictionary[field.module][field.category];
- const dict = category[field.field];
+ if (!syncVisits) {
+ return;
+ }
+ let modifiedvisits = false;
+ props.selected.forEach( (field: APIQueryField) => {
+ // Valid visits according to the dictionary
+ const category = props.fulldictionary[field.module][field.category];
+ const dict = category[field.field];
- if (dict.scope == 'candidate') {
- return;
- }
- if (typeof dict.visits !== 'undefined') {
- const newvisits = dict.visits.filter((visit) => {
- return props.defaultVisits.includes(visit);
- });
- field.visits = newvisits;
- modifiedvisits = true;
- }
- });
- if (modifiedvisits) {
- props.setSelected([...props.selected]);
+ if (dict.scope == 'candidate') {
+ return;
}
+ if (typeof dict.visits !== 'undefined') {
+ const newvisits = dict.visits.filter((visit) => {
+ return props.defaultVisits.includes(visit);
+ });
+ field.visits = newvisits;
+ modifiedvisits = true;
+ }
+ });
+ if (modifiedvisits) {
+ props.setSelected([...props.selected]);
+ }
}, [syncVisits, props.defaultVisits]);
const displayed: string[] = Object.keys(
props.displayedFields || {}
- ).filter((value) => {
- if (activeFilter === '') {
- // No filter set
- return true;
- }
+ ).filter((value) => {
+ if (activeFilter === '') {
+ // No filter set
+ return true;
+ }
- // Filter with a case insensitive comparison to either the description or
- // the field name displayed to the user
- const lowerFilter = activeFilter.toLowerCase();
- const desc = props.displayedFields[value].description;
- return (value.toLowerCase().includes(lowerFilter)
+ // Filter with a case insensitive comparison to either the description or
+ // the field name displayed to the user
+ const lowerFilter = activeFilter.toLowerCase();
+ const desc = props.displayedFields[value].description;
+ return (value.toLowerCase().includes(lowerFilter)
|| desc.toLowerCase().includes(lowerFilter));
});
const fields = displayed.map((item: string) => {
- /**
- * Return true if this element equals
- * the selected.
- *
- * @param {APIQueryField} element - The element
- * @returns {boolean} - true if equal
- */
- const equalField = (element: APIQueryField) => {
- return (element.module == props.module
+ /**
+ * Return true if this element equals
+ * the selected.
+ *
+ * @param {APIQueryField} element - The element
+ * @returns {boolean} - true if equal
+ */
+ const equalField = (element: APIQueryField) => {
+ return (element.module == props.module
&& element.category === props.category
&& element.field == item);
- };
- const selobj = props.selected.find(equalField);
- return setZoomTo(null)}
- key={item}
- item={item}
- value={props.displayedFields[item]}
- selected={selobj}
- module={props.module}
- category={props.category}
- onFieldToggle={props.onFieldToggle}
- onChangeVisitList={props.onChangeVisitList}
- defaultVisits={props.defaultVisits}
- />;
+ };
+ const selobj = props.selected.find(equalField);
+ return setZoomTo(null)}
+ key={item}
+ item={item}
+ value={props.displayedFields[item]}
+ selected={selobj}
+ module={props.module}
+ category={props.category}
+ onFieldToggle={props.onFieldToggle}
+ onChangeVisitList={props.onChangeVisitList}
+ defaultVisits={props.defaultVisits}
+ />;
});
/**
@@ -277,8 +277,8 @@ function DefineFields(props: {
* @param {React.ChangeEventHandler} e - The mouse event
*/
const setFilter = (e: React.FormEvent) => {
- const target = e.target as HTMLInputElement;
- setActiveFilter(target.value);
+ const target = e.target as HTMLInputElement;
+ setActiveFilter(target.value);
};
/**
@@ -287,24 +287,24 @@ function DefineFields(props: {
* @returns {void}
*/
const addAll = () => {
- const toAdd = displayed.map((item) => {
- const dict = props.displayedFields[item];
- const retObj: APIQueryField = {
- module: props.module,
- category: props.category,
- field: item,
- };
- // Only include defined visits which intersect
- // with the default ones, convert to the react-select
- // format used internally.
- if (dict.visits) {
- retObj['visits'] = dict.visits.filter((visit) => {
- return props.defaultVisits.includes(visit);
- });
- }
- return retObj;
- });
- props.onAddAll(toAdd);
+ const toAdd = displayed.map((item) => {
+ const dict = props.displayedFields[item];
+ const retObj: APIQueryField = {
+ module: props.module,
+ category: props.category,
+ field: item,
+ };
+ // Only include defined visits which intersect
+ // with the default ones, convert to the react-select
+ // format used internally.
+ if (dict.visits) {
+ retObj['visits'] = dict.visits.filter((visit) => {
+ return props.defaultVisits.includes(visit);
+ });
+ }
+ return retObj;
+ });
+ props.onAddAll(toAdd);
};
/**
* Removes all items from the currently selected category
@@ -312,40 +312,40 @@ function DefineFields(props: {
* @returns {void}
*/
const removeAll = () => {
- const toRemove = displayed.map((item) => {
- const dict = props.displayedFields[item];
- return {
- module: props.module,
- category: props.category,
- field: item,
- dictionary: dict,
- };
- });
- props.onRemoveAll(toRemove);
+ const toRemove = displayed.map((item) => {
+ const dict = props.displayedFields[item];
+ return {
+ module: props.module,
+ category: props.category,
+ field: item,
+ dictionary: dict,
+ };
+ });
+ props.onRemoveAll(toRemove);
};
let fieldList: React.ReactElement|null = null;
if (props.category) {
- // Put into a short variable name for line length
- const mCategories = props.allCategories.categories[props.module];
- const cname = mCategories[props.category];
- let defaultVisits;
- if (props.defaultVisits) {
- const allVisits = props.allVisits.map((el) => {
- return {value: el, label: el};
- });
- const selectedVisits = props.defaultVisits.map((el) => {
- return {value: el, label: el};
- });
- defaultVisits =
-
Default Visits
-
{
+ return {value: el, label: el};
+ });
+ const selectedVisits = props.defaultVisits.map((el) => {
+ return {value: el, label: el};
+ });
+ defaultVisits =
+
Default Visits
+
({...base, zIndex: 9999}),
- }
- }
- value={selectedVisits}
- closeMenuOnSelect={false}
- />
-
- setSyncVisits(value)
- } />
-
- ;
- }
+ }
+ }
+ value={selectedVisits}
+ closeMenuOnSelect={false}
+ />
+
+ setSyncVisits(value)
+ } />
+
+ ;
+ }
- fieldList = (
-
-
{cname} fields
-
- {defaultVisits}
-
-
-
-
-
-
-
-
+
+
+
+
{fields}
+
);
}
return (
-
-
-
Available Fields
- props.allCategories.modules[key]}
- onChange={props.onCategoryChange}
- />
- {fieldList}
-
-
+
+
Available Fields
+ props.allCategories.modules[key]}
+ onChange={props.onCategoryChange}
+ />
+ {fieldList}
+
+
-
+
-
Selected Fields
-
- Clear
-
+
Selected Fields
+
+ Clear
+
{
- setZoomTo(item);
- props.onCategoryChange(module, category);
+ selected={props.selected}
+ removeField={props.removeField}
+ fulldictionary={props.fulldictionary}
+ setSelected={props.setSelected}
+ snapToView={
+ (module: string, category: string, item: string) => {
+ setZoomTo(item);
+ props.onCategoryChange(module, category);
}}
/>
+
-
-
);
+
);
}
/**
@@ -490,125 +490,125 @@ function SelectedFieldList(props: {
* @returns {void}
*/
const moveSelected = () => {
- if (draggingIdx=== null || droppingIdx === null) {
- return;
- }
- const newSelected: APIQueryField[] = props.selected;
+ if (draggingIdx=== null || droppingIdx === null) {
+ return;
+ }
+ const newSelected: APIQueryField[] = props.selected;
- const removed: APIQueryField = newSelected.splice(draggingIdx, 1)[0];
- const newIdx: number|null = (droppingIdx||0 <= draggingIdx||0)
- ? droppingIdx
- : (droppingIdx - 1);
+ const removed: APIQueryField = newSelected.splice(draggingIdx, 1)[0];
+ const newIdx: number|null = (droppingIdx||0 <= draggingIdx||0)
+ ? droppingIdx
+ : (droppingIdx - 1);
- if (newIdx == null) {
- return;
- }
- newSelected.splice(
- newIdx,
- 0,
- removed,
- );
- props.setSelected([...newSelected]);
- setDroppingIdx(null);
- setDraggingIdx(null);
+ if (newIdx == null) {
+ return;
+ }
+ newSelected.splice(
+ newIdx,
+ 0,
+ removed,
+ );
+ props.setSelected([...newSelected]);
+ setDroppingIdx(null);
+ setDraggingIdx(null);
};
const fields = props.selected.map((item, i) => {
- /**
- * Removes an item from the selected
- *
- * @param {APIQueryField} item - The field to remove
- * @returns {void}
- */
- const removeField = (item: APIQueryField) => {
- props.removeField(item.module, item.category, item.field);
+ /**
+ * Removes an item from the selected
+ *
+ * @param {APIQueryField} item - The field to remove
+ * @returns {void}
+ */
+ const removeField = (item: APIQueryField) => {
+ props.removeField(item.module, item.category, item.field);
+ };
+ const style: React.CSSProperties = {display: 'flex',
+ flexWrap: 'nowrap' as const,
+ cursor: 'grab',
+ justifyContent: 'space-between'};
+ if (removingIdx === i) {
+ style.textDecoration = 'line-through' as const;
+ }
+ if (droppingIdx === i) {
+ style.borderTop = 'thin solid black';
+ }
+ if (draggingIdx == i) {
+ style.background = '#f5f5f5';
+ }
+ let fieldvisits;
+ if (item.visits) {
+ const style = {
+ fontStyle: 'italic',
+ color: '#aaa',
+ fontSize: '0.7em',
+ marginLeft: 20,
};
- const style: React.CSSProperties = {display: 'flex',
- flexWrap: 'nowrap' as const,
- cursor: 'grab',
- justifyContent: 'space-between'};
- if (removingIdx === i) {
- style.textDecoration = 'line-through' as const;
- }
- if (droppingIdx === i) {
- style.borderTop = 'thin solid black';
- }
- if (draggingIdx == i) {
- style.background = '#f5f5f5';
- }
- let fieldvisits;
- if (item.visits) {
- const style = {
- fontStyle: 'italic',
- color: '#aaa',
- fontSize: '0.7em',
- marginLeft: 20,
- };
- fieldvisits = {item.visits.join(', ')};
- }
- return ( {
- props.snapToView(item.module, item.category, item.field);
- }}
- onDragStart={() => {
- setDraggingIdx(i);
- }}
+ fieldvisits =
{item.visits.join(', ')};
+ }
+ return (
{
+ props.snapToView(item.module, item.category, item.field);
+ }}
+ onDragStart={() => {
+ setDraggingIdx(i);
+ }}
- onDragEnd={() => {
- setDraggingIdx(null);
- setDroppingIdx(null);
- }}
+ onDragEnd={() => {
+ setDraggingIdx(null);
+ setDroppingIdx(null);
+ }}
- onDragEnter={() => {
- setDroppingIdx(i);
- }}
+ onDragEnter={() => {
+ setDroppingIdx(i);
+ }}
- onDragOver ={(e) => {
- e.stopPropagation();
- e.preventDefault();
- }}
- onDrop={() => moveSelected()}
- >
-
-
{item.field}
- {getDictionaryDescription(
- item.module,
- item.category,
- item.field,
- props.fulldictionary,
- )}
- {fieldvisits}
-
-
setRemovingIdx(i)}
- onMouseLeave={() => setRemovingIdx(null)}>
- {
- removeField(item);
- setRemovingIdx(null);
- }}
- style={{cursor: 'pointer'}} />
-
-
);
+ onDragOver ={(e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ onDrop={() => moveSelected()}
+ >
+
+
{item.field}
+ {getDictionaryDescription(
+ item.module,
+ item.category,
+ item.field,
+ props.fulldictionary,
+ )}
+ {fieldvisits}
+
+
setRemovingIdx(i)}
+ onMouseLeave={() => setRemovingIdx(null)}>
+ {
+ removeField(item);
+ setRemovingIdx(null);
+ }}
+ style={{cursor: 'pointer'}} />
+
+
);
});
if (draggingIdx !== null) {
- // Add a sink after the last element, so that we can drop on
- // the end
- const style: React.CSSProperties = {height: 50};
- const nItems = fields.length;
- if (droppingIdx === nItems) {
- style.borderTop = 'thin solid black' as const;
- }
- fields.push( setDroppingIdx(nItems) }
- onDragOver ={(e) => {
- e.stopPropagation();
- e.preventDefault();
- }}
- onDrop={() => moveSelected()}>
-
);
+ // Add a sink after the last element, so that we can drop on
+ // the end
+ const style: React.CSSProperties = {height: 50};
+ const nItems = fields.length;
+ if (droppingIdx === nItems) {
+ style.borderTop = 'thin solid black' as const;
+ }
+ fields.push( setDroppingIdx(nItems) }
+ onDragOver ={(e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ onDrop={() => moveSelected()}>
+
);
}
return {fields}
;
diff --git a/modules/dataquery/jsx/definefilters.addfiltermodal.tsx b/modules/dataquery/jsx/definefilters.addfiltermodal.tsx
index b24349826ad..42dff210300 100644
--- a/modules/dataquery/jsx/definefilters.addfiltermodal.tsx
+++ b/modules/dataquery/jsx/definefilters.addfiltermodal.tsx
@@ -4,18 +4,18 @@ import Modal from 'jsx/Modal';
import Select from 'react-select';
import swal from 'sweetalert2';
import {
- NumericElement,
- DateElement,
- TimeElement,
- SelectElement,
- TextboxElement,
+ NumericElement,
+ DateElement,
+ TimeElement,
+ SelectElement,
+ TextboxElement,
} from 'jsx/Form';
import {QueryTerm, QueryGroup} from './querydef';
import {
- Operators,
- FieldDictionary,
- DictionaryCategory,
- VisitOption,
+ Operators,
+ FieldDictionary,
+ DictionaryCategory,
+ VisitOption,
} from './types';
import {CategoriesAPIReturn} from './hooks/usedatadictionary';
@@ -33,27 +33,27 @@ function VisitList(props: {
options: string[],
onChange: (newvals: string[]) => void,
}) {
- const selectOptions: VisitOption[] = props.options.map(
- (vl) => {
- return {value: vl, label: vl};
- }
- );
+ const selectOptions: VisitOption[] = props.options.map(
+ (vl) => {
+ return {value: vl, label: vl};
+ }
+ );
- const selectedVisits = selectOptions.filter((opt) => {
- return props.selected.includes(opt.value);
- });
+ const selectedVisits = selectOptions.filter((opt) => {
+ return props.selected.includes(opt.value);
+ });
- return {
- props.onChange(
- newvals.map((valobj) => valobj.value)
- );
- }}
- placeholder='Select Visits'
- value={selectedVisits}
- menuPortalTarget={document.body}
- styles={{menuPortal:
+ return {
+ props.onChange(
+ newvals.map((valobj) => valobj.value)
+ );
+ }}
+ placeholder='Select Visits'
+ value={selectedVisits}
+ menuPortalTarget={document.body}
+ styles={{menuPortal:
/**
* Required for rendering properly on top of window.
*
@@ -61,9 +61,9 @@ function VisitList(props: {
* @returns {object} - The new CSS object
*/
(base) => ({...base, zIndex: 9999})}
- }
- closeMenuOnSelect={false}
- />;
+ }
+ closeMenuOnSelect={false}
+ />;
}
/**
@@ -92,186 +92,186 @@ function AddFilterModal(props: {
module: string,
category: string,
}) {
- let fieldSelect;
- let criteriaSelect;
- let visitSelect;
- let cardinalityWarning;
- const [fieldDictionary, setFieldDictionary]
+ let fieldSelect;
+ let criteriaSelect;
+ let visitSelect;
+ let cardinalityWarning;
+ const [fieldDictionary, setFieldDictionary]
= useState(null);
- const [fieldname, setFieldname] = useState(null);
- const [op, setOp] = useState(null);
- const [value, setValue] = useState('');
- const [selectedVisits, setSelectedVisits] = useState(null);
+ const [fieldname, setFieldname] = useState(null);
+ const [op, setOp] = useState(null);
+ const [value, setValue] = useState('');
+ const [selectedVisits, setSelectedVisits] = useState(null);
- if (props.displayedFields) {
- const options: { Fields: {[key: string]: string}} = {'Fields': {}};
- for (const [key, value] of Object.entries(props.displayedFields)) {
- options['Fields'][key] = value.description;
- }
- fieldSelect = {
- const dict = props.displayedFields[fieldname];
- setFieldDictionary(dict);
- setFieldname(fieldname);
- if (dict.visits) {
- setSelectedVisits(dict.visits);
- }
- }}
- placeholder="Select a field" />;
+ if (props.displayedFields) {
+ const options: { Fields: {[key: string]: string}} = {'Fields': {}};
+ for (const [key, value] of Object.entries(props.displayedFields)) {
+ options['Fields'][key] = value.description;
}
-
- if (fieldDictionary) {
- let valueSelect;
- if (op) {
- valueSelect = valueInput(fieldDictionary, op, value, setValue);
+ fieldSelect = {
+ const dict = props.displayedFields[fieldname];
+ setFieldDictionary(dict);
+ setFieldname(fieldname);
+ if (dict.visits) {
+ setSelectedVisits(dict.visits);
}
+ }}
+ placeholder="Select a field" />;
+ }
+
+ if (fieldDictionary) {
+ let valueSelect;
+ if (op) {
+ valueSelect = valueInput(fieldDictionary, op, value, setValue);
+ }
- criteriaSelect =
-
Criteria
-
-
- {
- setOp(operator as Operators);
- }}
- placeholder="Select an operator"
- />
-
-
{valueSelect}
-
-
;
+ criteriaSelect =
+
Criteria
+
+
+ {
+ setOp(operator as Operators);
+ }}
+ placeholder="Select an operator"
+ />
+
+
{valueSelect}
+
+
;
if (fieldDictionary.scope == 'session' && fieldDictionary.visits) {
- visitSelect = e.stopPropagation()}>
-
for at least one of the following visits
-
- ;
+ visitSelect = e.stopPropagation()}>
+
for at least one of the following visits
+
+ ;
}
if (fieldDictionary.cardinality == 'many') {
- cardinalityWarning =
-
-
This field may exist multiple times for a
+ cardinalityWarning =
+
+
This field may exist multiple times for a
single {fieldDictionary.scope}. Adding a criteria
based on it means that it must match for at least
one of the data points.
-
;
- }
+
;
}
+ }
- /**
- * Function that returns a promise on the modal's submission to check
- * if the input is valid.
- *
- * @returns {Promise} - Promise that resolves if input is valid and rejects otherwise
- */
- const submitPromise = () =>
- new Promise((resolve, reject) => {
- // Validate and reject if invalid
- if (!fieldname) {
- swal.fire({
- type: 'error',
- title: 'Invalid field',
- text: 'You must select a field for the criteria.',
- });
- reject();
- return;
- }
- if (!op) {
- swal.fire({
- type: 'error',
- title: 'Invalid operator',
- text: 'You must select an operator for the criteria.',
- });
- reject();
- return;
- }
+ /**
+ * Function that returns a promise on the modal's submission to check
+ * if the input is valid.
+ *
+ * @returns {Promise} - Promise that resolves if input is valid and rejects otherwise
+ */
+ const submitPromise = () =>
+ new Promise((resolve, reject) => {
+ // Validate and reject if invalid
+ if (!fieldname) {
+ swal.fire({
+ type: 'error',
+ title: 'Invalid field',
+ text: 'You must select a field for the criteria.',
+ });
+ reject();
+ return;
+ }
+ if (!op) {
+ swal.fire({
+ type: 'error',
+ title: 'Invalid operator',
+ text: 'You must select an operator for the criteria.',
+ });
+ reject();
+ return;
+ }
- if (!value) {
- if (op != 'isnotnull' && op != 'isnull'
+ if (!value) {
+ if (op != 'isnotnull' && op != 'isnull'
&& op != 'exists' && op != 'notexists') {
- swal.fire({
- type: 'error',
- title: 'Invalid value',
- text: 'You must enter a value to compare the ' +
+ swal.fire({
+ type: 'error',
+ title: 'Invalid value',
+ text: 'You must enter a value to compare the ' +
'field against.',
- });
- reject();
- return;
- }
- }
+ });
+ reject();
+ return;
+ }
+ }
- if (fieldDictionary && fieldDictionary.scope == 'session') {
- if (!selectedVisits || selectedVisits.length == 0) {
- swal.fire({
- type: 'error',
- title: 'Invalid visits',
- text: 'No visits selected for criteria.',
- });
- reject();
- return;
- }
- }
+ if (fieldDictionary && fieldDictionary.scope == 'session') {
+ if (!selectedVisits || selectedVisits.length == 0) {
+ swal.fire({
+ type: 'error',
+ title: 'Invalid visits',
+ text: 'No visits selected for criteria.',
+ });
+ reject();
+ return;
+ }
+ }
- // It's been validated, now save the data and resolve
- const crit: QueryTerm = new QueryTerm(
- props.module,
- props.category,
- fieldname,
- op,
- value,
- );
- if (fieldDictionary && fieldDictionary.scope == 'session') {
- crit.visits = selectedVisits || [];
- }
+ // It's been validated, now save the data and resolve
+ const crit: QueryTerm = new QueryTerm(
+ props.module,
+ props.category,
+ fieldname,
+ op,
+ value,
+ );
+ if (fieldDictionary && fieldDictionary.scope == 'session') {
+ crit.visits = selectedVisits || [];
+ }
- props.addQueryGroupItem(
- props.query,
- crit,
- );
+ props.addQueryGroupItem(
+ props.query,
+ crit,
+ );
- props.closeModal();
- resolve(null);
- }
+ props.closeModal();
+ resolve(null);
+ }
);
- return
-
-
Field
-
-
- props.categories.modules[key]}
- onChange={props.onCategoryChange} />
-
-
- {fieldSelect}
-
-
- {cardinalityWarning}
- {criteriaSelect}
- {visitSelect}
-
- ;
+ return
+
+
Field
+
+
+ props.categories.modules[key]}
+ onChange={props.onCategoryChange} />
+
+
+ {fieldSelect}
+
+
+ {cardinalityWarning}
+ {criteriaSelect}
+ {visitSelect}
+
+ ;
}
@@ -282,59 +282,59 @@ function AddFilterModal(props: {
* @returns {object} - list of options for this dictionary
*/
function getOperatorOptions(dict: FieldDictionary) {
- let options: {[operator: string]: string};
+ let options: {[operator: string]: string};
- if (dict.type == 'integer' || dict.type == 'date' ||
+ if (dict.type == 'integer' || dict.type == 'date' ||
dict.type == 'interval' || dict.type == 'time' ||
dict.type == 'decimal') {
- // Comparable data types
- options = {
- 'lt': '<',
- 'lte': '≤',
- 'eq': '=',
- 'neq': '≠',
- 'gte': '≥',
- 'gt': '>',
- };
- } else if (dict.type == 'enumeration') {
- // Enumerations are a dropdown. Comparable operators
- // are meaningless, but the options are a dropdown
- // and we might be looking for an option "in" any
- // of the selected choices.
- options = {
- 'eq': '=',
- 'neq': '≠',
- 'in': 'in',
- };
- } else if (dict.type == 'string' ||
+ // Comparable data types
+ options = {
+ 'lt': '<',
+ 'lte': '≤',
+ 'eq': '=',
+ 'neq': '≠',
+ 'gte': '≥',
+ 'gt': '>',
+ };
+ } else if (dict.type == 'enumeration') {
+ // Enumerations are a dropdown. Comparable operators
+ // are meaningless, but the options are a dropdown
+ // and we might be looking for an option "in" any
+ // of the selected choices.
+ options = {
+ 'eq': '=',
+ 'neq': '≠',
+ 'in': 'in',
+ };
+ } else if (dict.type == 'string' ||
dict.type == 'URI') {
- // We might be looking for a substring.
- options = {
- 'eq': '=',
- 'neq': '≠',
- 'startsWith': 'starts with',
- 'contains': 'contains',
- 'endsWith': 'ends with',
- };
- } else {
- // fall back to == and !=, valid for any type.
- options = {'eq': '=', 'neq': '≠'};
- }
+ // We might be looking for a substring.
+ options = {
+ 'eq': '=',
+ 'neq': '≠',
+ 'startsWith': 'starts with',
+ 'contains': 'contains',
+ 'endsWith': 'ends with',
+ };
+ } else {
+ // fall back to == and !=, valid for any type.
+ options = {'eq': '=', 'neq': '≠'};
+ }
- // Possible cardinalities are unique, single,
- // optional, or many. Unique and single don't
- // change the possible operators, optional or
- // 1-many cardinalities have a couple more
- // things you can check.
- if (dict.cardinality == 'optional') {
- options['isnotnull'] = 'has data';
- options['isnull'] = 'has no data';
- } else if (dict.cardinality == 'many') {
- options['exists'] = 'exists';
- options['notexists'] = 'does not exist';
- options['numberof'] = 'number of';
- }
- return options;
+ // Possible cardinalities are unique, single,
+ // optional, or many. Unique and single don't
+ // change the possible operators, optional or
+ // 1-many cardinalities have a couple more
+ // things you can check.
+ if (dict.cardinality == 'optional') {
+ options['isnotnull'] = 'has data';
+ options['isnull'] = 'has no data';
+ } else if (dict.cardinality == 'many') {
+ options['exists'] = 'exists';
+ options['notexists'] = 'does not exist';
+ options['numberof'] = 'number of';
+ }
+ return options;
}
@@ -349,101 +349,101 @@ function getOperatorOptions(dict: FieldDictionary) {
* @returns {React.ReactElement} - the react element
*/
function valueInput(fielddict: FieldDictionary,
- op: Operators,
- value: string|string[],
- setValue: (val: string) => void
+ op: Operators,
+ value: string|string[],
+ setValue: (val: string) => void
) {
- const vs: string = value as string;
- switch (op) {
- case 'exists':
- case 'notexists':
- case 'isnull':
- case 'isnotnull':
- return
;
- case 'numberof':
- return
setValue(value)} />;
- }
+ const vs: string = value as string;
+ switch (op) {
+ case 'exists':
+ case 'notexists':
+ case 'isnull':
+ case 'isnotnull':
+ return ;
+ case 'numberof':
+ return setValue(value)} />;
+ }
- switch (fielddict.type) {
- case 'date':
- return setValue(value)} />;
- case 'time':
- // There's no time element type in LORIS, so use the HTML5
- // one with bootstrap styling that matches the rest of our
- // elements
- return setValue(value)
- }
- />;
- case 'URI':
- // Should this be input type="url"?
- return setValue(value)}
- name='value'
- value={vs} />;
- case 'integer':
- return setValue(value)} />;
- case 'boolean':
- return {
- setValue(value);
- }}
- placeholder="Select a value"
- />;
- case 'enumeration':
- const opts: {[key: string]: string} = {};
- for (let i = 0;
- fielddict.options && i < fielddict.options.length;
- i++
- ) {
- const opt = fielddict.options[i];
- if (fielddict.labels) {
- opts[opt] = fielddict.labels[i];
- } else {
- opts[opt] = opt;
- }
- }
- if (op == 'in') {
- return setValue(value)}
- value={value}
- sortByValue={false}
- />;
- }
- return {
- setValue(value);
- }}
- placeholder="Select a value"
- />;
- default:
- return setValue(value)}
- name='value'
- value={vs} />;
- }
+ switch (fielddict.type) {
+ case 'date':
+ return setValue(value)} />;
+ case 'time':
+ // There's no time element type in LORIS, so use the HTML5
+ // one with bootstrap styling that matches the rest of our
+ // elements
+ return setValue(value)
+ }
+ />;
+ case 'URI':
+ // Should this be input type="url"?
+ return setValue(value)}
+ name='value'
+ value={vs} />;
+ case 'integer':
+ return setValue(value)} />;
+ case 'boolean':
+ return {
+ setValue(value);
+ }}
+ placeholder="Select a value"
+ />;
+ case 'enumeration':
+ const opts: {[key: string]: string} = {};
+ for (let i = 0;
+ fielddict.options && i < fielddict.options.length;
+ i++
+ ) {
+ const opt = fielddict.options[i];
+ if (fielddict.labels) {
+ opts[opt] = fielddict.labels[i];
+ } else {
+ opts[opt] = opt;
+ }
+ }
+ if (op == 'in') {
+ return setValue(value)}
+ value={value}
+ sortByValue={false}
+ />;
+ }
+ return {
+ setValue(value);
+ }}
+ placeholder="Select a value"
+ />;
+ default:
+ return setValue(value)}
+ name='value'
+ value={vs} />;
+ }
}
export default AddFilterModal;
diff --git a/modules/dataquery/jsx/definefilters.importcsvmodal.tsx b/modules/dataquery/jsx/definefilters.importcsvmodal.tsx
index 512f6bd837b..2246203ecb1 100644
--- a/modules/dataquery/jsx/definefilters.importcsvmodal.tsx
+++ b/modules/dataquery/jsx/definefilters.importcsvmodal.tsx
@@ -17,192 +17,192 @@ function ImportCSVModal(props: {
setQuery: (root: QueryGroup) => void,
closeModal: () => void,
}) {
- const [csvFile, setCSVFile] = useState(null);
- const [csvHeader, setCSVHeader] = useState(false);
- const [csvType, setCSVType] = useState('candidate');
- const [idType, setIdType] = useState('PSCID');
- /**
- * Promise for handling modal closing. Always accepts.
- *
- * @returns {Promise} - a stub promise
- */
- const submitPromise = () =>
- new Promise((resolve) => {
- resolve(null);
- }
+ const [csvFile, setCSVFile] = useState(null);
+ const [csvHeader, setCSVHeader] = useState(false);
+ const [csvType, setCSVType] = useState('candidate');
+ const [idType, setIdType] = useState('PSCID');
+ /**
+ * Promise for handling modal closing. Always accepts.
+ *
+ * @returns {Promise} - a stub promise
+ */
+ const submitPromise = () =>
+ new Promise((resolve) => {
+ resolve(null);
+ }
);
- const candIDRegex = new RegExp('^[1-9][0-9]{5}$');
+ const candIDRegex = new RegExp('^[1-9][0-9]{5}$');
- /**
- * Callback function for after papaparse has parsed the csv
- *
- * @param {any} value - the value from papaparse callback
- */
- const csvParsed = (value: Papa.ParseResult) => {
- if (value.errors && value.errors.length > 0) {
- console.error(value.errors);
- swal.fire({
- type: 'error',
- title: 'Invalid CSV',
- text: 'Could not parse CSV file',
- });
- }
+ /**
+ * Callback function for after papaparse has parsed the csv
+ *
+ * @param {any} value - the value from papaparse callback
+ */
+ const csvParsed = (value: Papa.ParseResult) => {
+ if (value.errors && value.errors.length > 0) {
+ console.error(value.errors);
+ swal.fire({
+ type: 'error',
+ title: 'Invalid CSV',
+ text: 'Could not parse CSV file',
+ });
+ }
- // If candidates: validate 1 column
- // If sessions: validate 2 columns
- const expectedLength = (csvType === 'session' ? 2 : 1);
- const startLine = csvHeader ? 1 : 0;
- for (let i = startLine; i < value.data.length; i++) {
- if (value.data[i].length != expectedLength) {
- swal.fire({
- type: 'error',
- title: 'Invalid CSV',
- text: 'Expected ' + expectedLength + ' columns in CSV.'
+ // If candidates: validate 1 column
+ // If sessions: validate 2 columns
+ const expectedLength = (csvType === 'session' ? 2 : 1);
+ const startLine = csvHeader ? 1 : 0;
+ for (let i = startLine; i < value.data.length; i++) {
+ if (value.data[i].length != expectedLength) {
+ swal.fire({
+ type: 'error',
+ title: 'Invalid CSV',
+ text: 'Expected ' + expectedLength + ' columns in CSV.'
+ ' Got ' + value.data[i].length + ' on line ' +
(i+1) + '.',
- });
- return;
- }
- if (idType === 'CandID') {
- if (candIDRegex.test(value.data[i][0]) !== true) {
- swal.fire({
- type: 'error',
- title: 'Invalid DCC ID',
- text: 'Invalid DCC ID (' + value.data[i][0]
+ });
+ return;
+ }
+ if (idType === 'CandID') {
+ if (candIDRegex.test(value.data[i][0]) !== true) {
+ swal.fire({
+ type: 'error',
+ title: 'Invalid DCC ID',
+ text: 'Invalid DCC ID (' + value.data[i][0]
+ ') on line '
+ (i+1) + '.',
- });
- }
- }
+ });
}
+ }
+ }
- // Now that it's been validated, build a new query
- const newQuery = new QueryGroup('or');
- for (let i = startLine; i < value.data.length; i++) {
- if (csvType === 'session') {
- const sessionGroup = new QueryGroup('and');
- sessionGroup.addTerm(
- new QueryTerm(
- 'candidate_parameters',
- 'Identifiers',
- idType,
- 'eq',
- value.data[i][0],
- ),
- );
- sessionGroup.addTerm(
- new QueryTerm(
- 'candidate_parameters',
- 'Meta',
- 'VisitLabel',
- 'eq',
- value.data[i][1],
- ),
- );
- newQuery.group.push(sessionGroup);
- } else {
- newQuery.addTerm(
- new QueryTerm(
- 'candidate_parameters',
- 'Identifiers',
- idType,
- 'eq',
- value.data[i][0],
- ),
- );
- }
- }
+ // Now that it's been validated, build a new query
+ const newQuery = new QueryGroup('or');
+ for (let i = startLine; i < value.data.length; i++) {
+ if (csvType === 'session') {
+ const sessionGroup = new QueryGroup('and');
+ sessionGroup.addTerm(
+ new QueryTerm(
+ 'candidate_parameters',
+ 'Identifiers',
+ idType,
+ 'eq',
+ value.data[i][0],
+ ),
+ );
+ sessionGroup.addTerm(
+ new QueryTerm(
+ 'candidate_parameters',
+ 'Meta',
+ 'VisitLabel',
+ 'eq',
+ value.data[i][1],
+ ),
+ );
+ newQuery.group.push(sessionGroup);
+ } else {
+ newQuery.addTerm(
+ new QueryTerm(
+ 'candidate_parameters',
+ 'Identifiers',
+ idType,
+ 'eq',
+ value.data[i][0],
+ ),
+ );
+ }
+ }
- props.setQuery(newQuery);
- props.closeModal();
- };
+ props.setQuery(newQuery);
+ props.closeModal();
+ };
- const dtstyle = {
- marginLeft: '1em',
- marginTop: '1em',
- };
+ const dtstyle = {
+ marginLeft: '1em',
+ marginTop: '1em',
+ };
- return
-
+ ;
}
export default ImportCSVModal;
diff --git a/modules/dataquery/jsx/definefilters.tsx b/modules/dataquery/jsx/definefilters.tsx
index bce5c2cc9ea..ddeb64baebd 100644
--- a/modules/dataquery/jsx/definefilters.tsx
+++ b/modules/dataquery/jsx/definefilters.tsx
@@ -60,342 +60,342 @@ function DefineFilters(props: {
addNewQueryGroup: (group: QueryGroup) => void,
removeQueryGroupItem: (group: QueryGroup, i: number) => QueryGroup,
}) : React.ReactElement {
- let displayquery: React.ReactNode = null;
- const [addModal, setAddModal] = useState(false);
- const [csvModal, setCSVModal] = useState(false);
- const [showAdvanced, setShowAdvanced] = useState(false);
- // The subgroup used for the "Add Filter" modal window
- // to add to. Default to top level unless click from a
- // query group, in which case the callback changes it
- // to that group.
- const [modalQueryGroup, setModalGroup] = useState(props.query);
- const [deleteItemIndex, setDeleteItemIndex] = useState(null);
- const [queryMatches, setQueryMatches] = useState(null);
- useEffect(() => {
- setQueryMatches(null);
- const payload = calcPayload(props.fields, props.query);
+ let displayquery: React.ReactNode = null;
+ const [addModal, setAddModal] = useState(false);
+ const [csvModal, setCSVModal] = useState(false);
+ const [showAdvanced, setShowAdvanced] = useState(false);
+ // The subgroup used for the "Add Filter" modal window
+ // to add to. Default to top level unless click from a
+ // query group, in which case the callback changes it
+ // to that group.
+ const [modalQueryGroup, setModalGroup] = useState(props.query);
+ const [deleteItemIndex, setDeleteItemIndex] = useState(null);
+ const [queryMatches, setQueryMatches] = useState(null);
+ useEffect(() => {
+ setQueryMatches(null);
+ const payload = calcPayload(props.fields, props.query);
+ fetch(
+ '/dataquery/queries',
+ {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify(payload),
+ },
+ ).then(
+ (resp) => {
+ if (!resp.ok) {
+ throw new Error('Error creating query.');
+ }
+ return resp.json();
+ }
+ ).then(
+ (data) => {
fetch(
- '/dataquery/queries',
- {
- method: 'POST',
- credentials: 'same-origin',
- body: JSON.stringify(payload),
- },
- ).then(
- (resp) => {
- if (!resp.ok) {
- throw new Error('Error creating query.');
- }
- return resp.json();
- }
- ).then(
- (data) => {
- fetch(
- '/dataquery/queries/'
+ '/dataquery/queries/'
+ data.QueryID + '/count',
- {
- method: 'GET',
- credentials: 'same-origin',
- }
- ).then((resp) => {
- if (!resp.ok) {
- throw new Error('Could not get count.');
- }
- return resp.json();
- }).then((result) => {
- setQueryMatches(result.count);
- });
- }
- );
- }, [props.fields, props.query]);
+ {
+ method: 'GET',
+ credentials: 'same-origin',
+ }
+ ).then((resp) => {
+ if (!resp.ok) {
+ throw new Error('Could not get count.');
+ }
+ return resp.json();
+ }).then((result) => {
+ setQueryMatches(result.count);
+ });
+ }
+ );
+ }, [props.fields, props.query]);
- const bGroupStyle = {
- display: 'flex' as const,
- flexWrap: 'wrap' as const,
- marginTop: '1ex',
- };
+ const bGroupStyle = {
+ display: 'flex' as const,
+ flexWrap: 'wrap' as const,
+ marginTop: '1ex',
+ };
- const mapModuleName = props.mapModuleName;
- const mapCategoryName = props.mapCategoryName;
+ const mapModuleName = props.mapModuleName;
+ const mapCategoryName = props.mapCategoryName;
- const advancedLabel = showAdvanced ? 'Hide Advanced' : 'Show Advanced';
- let advancedButtons;
- const toggleAdvancedButton = (
+ const advancedLabel = showAdvanced ? 'Hide Advanced' : 'Show Advanced';
+ let advancedButtons;
+ const toggleAdvancedButton = (
+
+
+ {
+ e.preventDefault();
+ setShowAdvanced(!showAdvanced);
+ }}
+ />
+
+
+ );
+ if (props.query.group.length == 0) {
+ if (showAdvanced) {
+ advancedButtons = (
-
- {
- e.preventDefault();
- setShowAdvanced(!showAdvanced);
- }}
- />
-
-
- );
- if (props.query.group.length == 0) {
- if (showAdvanced) {
- advancedButtons = (
-
-
The "nested groups" options are advanced options for queries
+
The "nested groups" options are advanced options for queries
that do not have any specific condition at the
base of the query.
Use Add nested "or" condition groups
if
you need to build a query of the form.
- (a or b) and (c or d) [or (e and f)..].
-
-
- {
- e.preventDefault();
- props.query.operator = 'and';
- props.addNewQueryGroup(props.query);
- }}
- />
-
-
+ (a or b) and (c or d) [or (e and f)..].
+
+
+ {
+ e.preventDefault();
+ props.query.operator = 'and';
+ props.addNewQueryGroup(props.query);
+ }}
+ />
+
+
Use Add nested "and" condition groups
if you
need to build a query of the form
- (a and b) or (c and d) [or (e and f)..].
-
-
- {
- e.preventDefault();
- props.query.operator = 'or';
- props.addNewQueryGroup(props.query);
- }}
- />
-
-
- );
- }
- // Only 1 add condition button since "and" or "or"
- // are the same with only 1 term
- displayquery =
-
-
Currently querying for ALL candidates.
-
You can add conditions by clicking one of the buttons below.
-
Click Add Condition
to add one or more conditions
+ (a and b) or (c and d) [or (e and f)..].
+
+
+ {
+ e.preventDefault();
+ props.query.operator = 'or';
+ props.addNewQueryGroup(props.query);
+ }}
+ />
+
+
+ );
+ }
+ // Only 1 add condition button since "and" or "or"
+ // are the same with only 1 term
+ displayquery =
+
+
Currently querying for ALL candidates.
+
You can add conditions by clicking one of the buttons below.
+
Click Add Condition
to add one or more conditions
to your filters (ie. "Date Of Birth < 2015-02-15"). This is
most likely where you want to start your filters.
-
-
You can also import a population from a CSV by clicking
+
+
You can also import a population from a CSV by clicking
the Import from CSV
button.
-
The advanced options are for queries that do not have
+
The advanced options are for queries that do not have
a condition to add at the base of the query.
+
+
+ {toggleAdvancedButton}
+ {advancedButtons}
+
+
+
;
+ } else if (props.query.group.length == 1 &&
props.query.group[0] instanceof QueryTerm
- ) {
- if (showAdvanced) {
- advancedButtons = (
-
-
-
Use New "and" subgroup
if the rest of the
+ ) {
+ if (showAdvanced) {
+ advancedButtons = (
+
+
+
Use New "and" subgroup
if the rest of the
query you need to write is a subgroup consisting
of "and" conditions. ie your query is of the form:
-
- (your condition above) or (c and d [and e and f..])
-
-
-
{
- e.preventDefault();
- props.query.operator = 'or';
- props.addNewQueryGroup(props.query);
- }} />
- Use New "or" subgroup
if the rest of the
+
+ (your condition above) or (c and d [and e and f..])
+
+
+ {
+ e.preventDefault();
+ props.query.operator = 'or';
+ props.addNewQueryGroup(props.query);
+ }} />
+ Use New "or" subgroup
if the rest of the
query you need to write is a subgroup consisting
of "or" conditions. ie your query is of the form:
-
- (your condition above) and (c or d [or e or f..])
-
-
- {
- e.preventDefault();
- props.query.operator = 'and';
- props.addNewQueryGroup(props.query);
- }} />
-
-
- );
- }
- // buttons for 1. Add "and" condition 2. Add "or" condition
- displayquery = (
-
Currently querying for any candidates with:
+
+ (your condition above) and (c or d [or e or f..])
+
+
+
{
+ e.preventDefault();
+ props.query.operator = 'and';
+ props.addNewQueryGroup(props.query);
+ }} />
+
+
+ );
+ }
+ // buttons for 1. Add "and" condition 2. Add "or" condition
+ displayquery = (
+
Currently querying for any candidates with:
-
-
-
-
);
- } else {
- // Add buttons are delegated to the QueryTree rendering so they
- // can be placed at the right level
- displayquery =
-
Currently querying for any candidates with:
-
-
;
- }
- const modal = addModal ? (
-
setAddModal(false)}
- addQueryGroupItem={(querygroup, condition) => {
- const newquery = props.addQueryGroupItem(
- querygroup,
- condition,
+
+
+
{
+ const newquery = props.removeQueryGroupItem(
+ props.query,
+ 0
);
setModalGroup(newquery);
+ setDeleteItemIndex(null);
+ }}
+ onMouseEnter={() => setDeleteItemIndex(0)}
+ onMouseLeave={() => setDeleteItemIndex(null)}
+ style={{cursor: 'pointer'}} />
+
+
+
+
+ {
+ e.preventDefault();
+ props.query.operator = 'and';
+ setAddModal(true);
+ }} />
+ {
+ e.preventDefault();
+ setAddModal(true);
+ props.query.operator = 'or';
+ }} />
+
+
+ {toggleAdvancedButton}
+ {advancedButtons}
+
+
+ );
+ } else {
+ // Add buttons are delegated to the QueryTree rendering so they
+ // can be placed at the right level
+ displayquery =
+
Currently querying for any candidates with:
+
+
+
+
;
+ }
+ const modal = addModal ? (
+ setAddModal(false)}
+ addQueryGroupItem={(querygroup, condition) => {
+ const newquery = props.addQueryGroupItem(
+ querygroup,
+ condition,
+ );
+ setModalGroup(newquery);
+ }}
+ categories={props.categories}
+ onCategoryChange={props.onCategoryChange}
+ displayedFields={props.displayedFields}
- module={props.module}
- category={props.category}
- />)
- : '';
- const csvModalHTML = csvModal ? (
- setCSVModal(false)}
- />
- ) : '';
+ module={props.module}
+ category={props.category}
+ />)
+ : '';
+ const csvModalHTML = csvModal ? (
+ setCSVModal(false)}
+ />
+ ) : '';
- const matchCount = queryMatches === null
- ?
// So the header doesn't jump around
- : Query matches {queryMatches} candidates
;
- return (
- {modal}
- {csvModalHTML}
-
-
Current Query
- {matchCount}
-
-
+ const matchCount = queryMatches === null
+ ?
// So the header doesn't jump around
+ : Query matches {queryMatches} candidates
;
+ return (
+ {modal}
+ {csvModalHTML}
+
+
Current Query
+ {matchCount}
+
+
Note that only candidates which you have permission to
access in LORIS are included in results. Number of
results may vary from other users running the same query.
-
- {displayquery}
-
- );
+
+ {displayquery}
+
+ );
}
export default DefineFilters;
diff --git a/modules/dataquery/jsx/fielddisplay.tsx b/modules/dataquery/jsx/fielddisplay.tsx
index 13b94664899..e58cad1f60a 100644
--- a/modules/dataquery/jsx/fielddisplay.tsx
+++ b/modules/dataquery/jsx/fielddisplay.tsx
@@ -23,22 +23,22 @@ function FieldDisplay(props: {
mapModuleName: (module: string) => string,
mapCategoryName: (module: string, category: string) => string,
}) {
- const description = getDictionaryDescription(
- props.module,
- props.category,
- props.fieldname,
- props.fulldictionary,
- );
+ const description = getDictionaryDescription(
+ props.module,
+ props.category,
+ props.fieldname,
+ props.fulldictionary,
+ );
- return (
-
- {description}
-
-
- {props.mapCategoryName(props.module, props.category)}
+ return (
+
+ {description}
+
+
+ {props.mapCategoryName(props.module, props.category)}
({props.mapModuleName(props.module)})
-
-
- );
+
+
+ );
}
export default FieldDisplay;
diff --git a/modules/dataquery/jsx/getdictionarydescription.tsx b/modules/dataquery/jsx/getdictionarydescription.tsx
index 02d5f9c32ed..c403f277214 100644
--- a/modules/dataquery/jsx/getdictionarydescription.tsx
+++ b/modules/dataquery/jsx/getdictionarydescription.tsx
@@ -10,20 +10,20 @@ import {FullDictionary} from './types';
* @returns {string} - the description if available, otherwise the fieldname
*/
function getDictionaryDescription(
- module: string,
- category: string,
- fieldname: string,
- dict: FullDictionary,
+ module: string,
+ category: string,
+ fieldname: string,
+ dict: FullDictionary,
): string {
- if (!dict
+ if (!dict
|| !dict[module]
|| !dict[module][category]
|| !dict[module][category][fieldname]
- ) {
- return fieldname;
- }
+ ) {
+ return fieldname;
+ }
- return dict[module][category][fieldname].description;
+ return dict[module][category][fieldname].description;
}
export default getDictionaryDescription;
diff --git a/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx b/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx
index be9023cdeba..ba65fb9c5b7 100644
--- a/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx
+++ b/modules/dataquery/jsx/hooks/usebreadcrumbs.tsx
@@ -12,85 +12,85 @@ declare const loris: any;
* @param {function} setActiveTab - set the state on click
*/
function useBreadcrumbs(
- activeTab: string,
- setActiveTab: (newtab: string) => void
+ activeTab: string,
+ setActiveTab: (newtab: string) => void
) {
- // update breadcrumbs breadcrumbs
- useEffect(() => {
- const breadcrumbs = [
- {
- text: 'Data Query Tool (Beta)',
- /**
- * OnClick handler for the main breadcrumb
- *
- * @param {React.MouseEvent} e - Callback for when hovering over the delete icon
- * @returns {void}
- */
- onClick: (e: React.MouseEvent) => {
- e.preventDefault();
- setActiveTab('Info');
- },
- },
- ];
- if (activeTab == 'DefineFields'
+ // update breadcrumbs breadcrumbs
+ useEffect(() => {
+ const breadcrumbs = [
+ {
+ text: 'Data Query Tool (Beta)',
+ /**
+ * OnClick handler for the main breadcrumb
+ *
+ * @param {React.MouseEvent} e - Callback for when hovering over the delete icon
+ * @returns {void}
+ */
+ onClick: (e: React.MouseEvent) => {
+ e.preventDefault();
+ setActiveTab('Info');
+ },
+ },
+ ];
+ if (activeTab == 'DefineFields'
|| activeTab == 'DefineFilters'
|| activeTab == 'ViewData') {
- breadcrumbs.push({
- text: 'Define Fields',
- /**
- * OnClick handler for the define fields breadcrumb
- *
- * @param {React.MouseEventHandler} e - Callback for when hovering over the delete icon
- * @returns {void}
- */
- onClick: (e) => {
- e.preventDefault();
- setActiveTab('DefineFields');
- },
- });
- }
- if (activeTab == 'DefineFilters'
+ breadcrumbs.push({
+ text: 'Define Fields',
+ /**
+ * OnClick handler for the define fields breadcrumb
+ *
+ * @param {React.MouseEventHandler} e - Callback for when hovering over the delete icon
+ * @returns {void}
+ */
+ onClick: (e) => {
+ e.preventDefault();
+ setActiveTab('DefineFields');
+ },
+ });
+ }
+ if (activeTab == 'DefineFilters'
|| activeTab == 'ViewData') {
- breadcrumbs.push({
- text: 'Define Filters',
- /**
- * OnClick handler for the define filters breadcrumb
- *
- * @param {React.MouseEventHandler} e - Callback for when hovering over the delete icon
- * @returns {void}
- */
- onClick: (e) => {
- e.preventDefault();
- setActiveTab('DefineFilters');
- },
- });
- }
+ breadcrumbs.push({
+ text: 'Define Filters',
+ /**
+ * OnClick handler for the define filters breadcrumb
+ *
+ * @param {React.MouseEventHandler} e - Callback for when hovering over the delete icon
+ * @returns {void}
+ */
+ onClick: (e) => {
+ e.preventDefault();
+ setActiveTab('DefineFilters');
+ },
+ });
+ }
- if (activeTab == 'ViewData') {
- breadcrumbs.push({
- text: 'View Data',
- /**
- * OnClick handler for the View Data breadcrumb
- *
- * @param {React.MouseEventHandler} e - Callback for when hovering over the delete icon
- * @returns {void}
- */
- onClick: (e) => {
- e.preventDefault();
- setActiveTab('ViewData');
- },
- });
- }
+ if (activeTab == 'ViewData') {
+ breadcrumbs.push({
+ text: 'View Data',
+ /**
+ * OnClick handler for the View Data breadcrumb
+ *
+ * @param {React.MouseEventHandler} e - Callback for when hovering over the delete icon
+ * @returns {void}
+ */
+ onClick: (e) => {
+ e.preventDefault();
+ setActiveTab('ViewData');
+ },
+ });
+ }
- if (breadcrumbsRoot) {
- breadcrumbsRoot.render(
- ,
- );
- }
- }, [activeTab]);
+ if (breadcrumbsRoot) {
+ breadcrumbsRoot.render(
+ ,
+ );
+ }
+ }, [activeTab]);
}
export default useBreadcrumbs;
diff --git a/modules/dataquery/jsx/hooks/usedatadictionary.tsx b/modules/dataquery/jsx/hooks/usedatadictionary.tsx
index bc94f750a60..82d7108f62e 100644
--- a/modules/dataquery/jsx/hooks/usedatadictionary.tsx
+++ b/modules/dataquery/jsx/hooks/usedatadictionary.tsx
@@ -22,25 +22,25 @@ export type CategoriesAPIReturn = {
* @returns {CategoriesAPIReturn} - Categories returned by dictionary API
*/
function useCategories(): CategoriesAPIReturn|null {
- const [categories, setCategories] = useState(null);
- useEffect(() => {
- if (categories !== null) {
- return;
+ const [categories, setCategories] = useState(null);
+ useEffect(() => {
+ if (categories !== null) {
+ return;
+ }
+ fetch('/dictionary/categories', {credentials: 'same-origin'})
+ .then((resp) => {
+ if (!resp.ok) {
+ throw new Error('Invalid response');
}
- fetch('/dictionary/categories', {credentials: 'same-origin'})
- .then((resp) => {
- if (!resp.ok) {
- throw new Error('Invalid response');
- }
- return resp.json();
- }).then((result) => {
- setCategories(result);
- }
- ).catch( (error) => {
- console.error(error);
- });
- }, []);
- return categories;
+ return resp.json();
+ }).then((result) => {
+ setCategories(result);
+ }
+ ).catch( (error) => {
+ console.error(error);
+ });
+ }, []);
+ return categories;
}
type DataDictionaryReturnType = [
@@ -54,52 +54,52 @@ type DataDictionaryReturnType = [
* @returns {array} - The retrieved dictionary and a callback to populate a new module into it
*/
function useDataDictionary(): DataDictionaryReturnType {
- const [fulldictionary, setDictionary] = useState({});
- // XXX: This should be {[module: string]: Promise} but then
- // typescript says the key is always defined when we try and check if
- // it's set, need to figure out the correct way to do that, for now just use any
- const [pendingModules, setPendingModules] = useState({});
+ const [fulldictionary, setDictionary] = useState({});
+ // XXX: This should be {[module: string]: Promise} but then
+ // typescript says the key is always defined when we try and check if
+ // it's set, need to figure out the correct way to do that, for now just use any
+ const [pendingModules, setPendingModules] = useState({});
- /**
- * Fetch a module's dictionary and cache it into fulldictionary.
- *
- * @param {string} module - The module name to fetch the dictionary for
- * @returns {Promise} - A promise that resolves to module's dictionary
- */
- const fetchModuleDictionary = (module: string): Promise => {
- if (fulldictionary[module]) {
- const promise = Promise.resolve(fulldictionary[module]);
- return promise;
- }
- if (pendingModules[module]) {
- return pendingModules[module];
- }
- const promise: Promise = new Promise(
- (resolve, reject) => {
- fetch('/dictionary/module/' + module,
- {credentials: 'same-origin'}
- ).then((resp) => {
- if (!resp.ok) {
- throw new Error('Invalid response');
- }
- return resp.json();
- }).then((result) => {
- fulldictionary[module] = result;
- setDictionary({...fulldictionary});
+ /**
+ * Fetch a module's dictionary and cache it into fulldictionary.
+ *
+ * @param {string} module - The module name to fetch the dictionary for
+ * @returns {Promise} - A promise that resolves to module's dictionary
+ */
+ const fetchModuleDictionary = (module: string): Promise => {
+ if (fulldictionary[module]) {
+ const promise = Promise.resolve(fulldictionary[module]);
+ return promise;
+ }
+ if (pendingModules[module]) {
+ return pendingModules[module];
+ }
+ const promise: Promise = new Promise(
+ (resolve, reject) => {
+ fetch('/dictionary/module/' + module,
+ {credentials: 'same-origin'}
+ ).then((resp) => {
+ if (!resp.ok) {
+ throw new Error('Invalid response');
+ }
+ return resp.json();
+ }).then((result) => {
+ fulldictionary[module] = result;
+ setDictionary({...fulldictionary});
- resolve(result);
- }).catch( (error) => {
- console.error(error);
- reject(error);
- });
- }
- );
- const newUsedModules = pendingModules;
- newUsedModules[module] = promise;
- setPendingModules(newUsedModules);
- return promise;
- };
- return [fulldictionary, fetchModuleDictionary];
+ resolve(result);
+ }).catch( (error) => {
+ console.error(error);
+ reject(error);
+ });
+ }
+ );
+ const newUsedModules = pendingModules;
+ newUsedModules[module] = promise;
+ setPendingModules(newUsedModules);
+ return promise;
+ };
+ return [fulldictionary, fetchModuleDictionary];
}
export {useDataDictionary, useCategories};
diff --git a/modules/dataquery/jsx/hooks/usequery.tsx b/modules/dataquery/jsx/hooks/usequery.tsx
index 20a84bbe2c8..75a34e300cb 100644
--- a/modules/dataquery/jsx/hooks/usequery.tsx
+++ b/modules/dataquery/jsx/hooks/usequery.tsx
@@ -47,281 +47,281 @@ type useQueryReturnType = [
* @returns {useQueryReturnType} - A veritable plethora of actions and values
*/
function useQuery(): useQueryReturnType {
- const [fields, setFields] = useState([]);
- const [criteria, setCriteria] = useState(new QueryGroup('and'));
+ const [fields, setFields] = useState([]);
+ const [criteria, setCriteria] = useState(new QueryGroup('and'));
- /**
- * Add a term to the current QueryGroup
- *
- * @param {QueryGroup} querygroup - The group to add the term to
- * @param {QueryTerm} condition - The term to add to the group
- * @returns {QueryGroup} - The new QueryGroup
- */
- const addQueryGroupItem = (
- querygroup: QueryGroup,
- condition: QueryTerm,
- ): QueryGroup => {
- // clone the top level query to force
- // a new rendering
- const newquery = new QueryGroup(criteria.operator);
+ /**
+ * Add a term to the current QueryGroup
+ *
+ * @param {QueryGroup} querygroup - The group to add the term to
+ * @param {QueryTerm} condition - The term to add to the group
+ * @returns {QueryGroup} - The new QueryGroup
+ */
+ const addQueryGroupItem = (
+ querygroup: QueryGroup,
+ condition: QueryTerm,
+ ): QueryGroup => {
+ // clone the top level query to force
+ // a new rendering
+ const newquery = new QueryGroup(criteria.operator);
- // Add to this level of the tree
- querygroup.addTerm(condition);
+ // Add to this level of the tree
+ querygroup.addTerm(condition);
- newquery.group = [...criteria.group];
- setCriteria(newquery);
- return newquery;
- };
+ newquery.group = [...criteria.group];
+ setCriteria(newquery);
+ return newquery;
+ };
- /**
- * Remove a given index from the current QueryGroup and return
- * a new group.
- *
- * @param {QueryGroup} querygroup - The querygroup to remove an item from
- * @param {number} idx - The index to remove
- * @returns {QueryGroup} - the new QueryGroup
- */
- const removeQueryGroupItem = (
- querygroup: QueryGroup,
- idx: number
- ): QueryGroup => {
- // Remove from this level of the tree
- querygroup.removeTerm(idx);
+ /**
+ * Remove a given index from the current QueryGroup and return
+ * a new group.
+ *
+ * @param {QueryGroup} querygroup - The querygroup to remove an item from
+ * @param {number} idx - The index to remove
+ * @returns {QueryGroup} - the new QueryGroup
+ */
+ const removeQueryGroupItem = (
+ querygroup: QueryGroup,
+ idx: number
+ ): QueryGroup => {
+ // Remove from this level of the tree
+ querygroup.removeTerm(idx);
+
+ // clone the top level query to force
+ // a new rendering
+ const newquery = new QueryGroup(criteria.operator);
- // clone the top level query to force
- // a new rendering
- const newquery = new QueryGroup(criteria.operator);
+ newquery.group = [...criteria.group];
+ setCriteria(newquery);
- newquery.group = [...criteria.group];
- setCriteria(newquery);
+ return newquery;
+ };
- return newquery;
- };
+ /**
+ * Add a new, empty query group to the end of a QueryGroup
+ *
+ * @param {QueryGroup} parentgroup - the group to get a new querygroup child
+ * @returns {void}
+ */
+ const addNewQueryGroup = (parentgroup: QueryGroup): void => {
+ // Add to this level of the tree
+ parentgroup.addGroup();
+ // clone the top level query to force
+ // a new rendering
+ const newquery = new QueryGroup(criteria.operator);
+ newquery.group = [...criteria.group];
+
+ setCriteria(newquery);
+ };
+
+ /**
+ * Load a new query as the currently managed query by this hook.
+ *
+ * @param {APIQueryField[]} fields - The fields of the new query
+ * @param {QueryGroup} filters - The filters of the new query.
+ * @returns {void}
+ */
+ const loadQuery = (
+ fields: APIQueryField[],
+ filters: QueryGroup|null
+ ): void => {
+ setFields(fields);
+ if (!filters) {
+ setCriteria(new QueryGroup('and'));
+ } else {
+ setCriteria(filters);
+ }
+ };
+ const fieldActions: FieldActions = {
/**
- * Add a new, empty query group to the end of a QueryGroup
+ * Clear all fields from this query
*
- * @param {QueryGroup} parentgroup - the group to get a new querygroup child
* @returns {void}
*/
- const addNewQueryGroup = (parentgroup: QueryGroup): void => {
- // Add to this level of the tree
- parentgroup.addGroup();
-
- // clone the top level query to force
- // a new rendering
- const newquery = new QueryGroup(criteria.operator);
- newquery.group = [...criteria.group];
-
- setCriteria(newquery);
- };
-
+ clear: function() {
+ setFields([]);
+ },
/**
- * Load a new query as the currently managed query by this hook.
+ * Remove a field from this query
*
- * @param {APIQueryField[]} fields - The fields of the new query
- * @param {QueryGroup} filters - The filters of the new query.
+ * @param {string} module - The module of the field to remove
+ * @param {string} category - The category of the field to remove
+ * @param {string} field - The field to remove
* @returns {void}
*/
- const loadQuery = (
- fields: APIQueryField[],
- filters: QueryGroup|null
+ remove: (
+ module: string,
+ category: string,
+ field: string,
): void => {
- setFields(fields);
- if (!filters) {
- setCriteria(new QueryGroup('and'));
- } else {
- setCriteria(filters);
- }
- };
- const fieldActions: FieldActions = {
- /**
- * Clear all fields from this query
- *
- * @returns {void}
- */
- clear: function() {
- setFields([]);
- },
- /**
- * Remove a field from this query
- *
- * @param {string} module - The module of the field to remove
- * @param {string} category - The category of the field to remove
- * @param {string} field - The field to remove
- * @returns {void}
- */
- remove: (
- module: string,
- category: string,
- field: string,
- ): void => {
- /**
- * Returns true if an element in fields is equal to this field
- *
- * @param {APIQueryField} element - The element to compare
- * @returns {boolean} - true if equal
- */
- const equalField = (element: APIQueryField): boolean => {
- return (element.module == module
+ /**
+ * Returns true if an element in fields is equal to this field
+ *
+ * @param {APIQueryField} element - The element to compare
+ * @returns {boolean} - true if equal
+ */
+ const equalField = (element: APIQueryField): boolean => {
+ return (element.module == module
&& element.category === category
&& element.field == field);
- };
- const newfields = fields.filter((el) => !(equalField(el)));
- setFields(newfields);
- },
- /**
- * Modify the visits for a selected field
- *
- * @param {string} module - The module of the field to modify
- * @param {string} category - The category of the field to modify
- * @param {string} field - The field to modify
- * @param {string[]} visits - The new visits for the field
- * @returns {void}
- */
- modifyVisits: (
- module: string,
- category: string,
- field: string,
- visits: string[]
- ) => {
- const newfields: APIQueryField[] = [...fields];
+ };
+ const newfields = fields.filter((el) => !(equalField(el)));
+ setFields(newfields);
+ },
+ /**
+ * Modify the visits for a selected field
+ *
+ * @param {string} module - The module of the field to modify
+ * @param {string} category - The category of the field to modify
+ * @param {string} field - The field to modify
+ * @param {string[]} visits - The new visits for the field
+ * @returns {void}
+ */
+ modifyVisits: (
+ module: string,
+ category: string,
+ field: string,
+ visits: string[]
+ ) => {
+ const newfields: APIQueryField[] = [...fields];
- /**
- * Returns true if an element in fields is equal to this field
- *
- * @param {APIQueryField} element - The element to compare
- * @returns {boolean} - true if equal
- */
- const equalField = (element: APIQueryField) => {
- return (element.module == module
+ /**
+ * Returns true if an element in fields is equal to this field
+ *
+ * @param {APIQueryField} element - The element to compare
+ * @returns {boolean} - true if equal
+ */
+ const equalField = (element: APIQueryField) => {
+ return (element.module == module
&& element.category === category
&& element.field == field);
- };
+ };
- for (let i = 0; i < newfields.length; i++) {
- if (equalField(newfields[i])) {
- newfields[i].visits = visits;
- setFields(newfields);
- return;
- }
- }
- },
- /**
- * Toggle whether a field is present by adding it if missing or removing it if
- * present.
- *
- * @param {string} module - the module for the field
- * @param {string} category - the category for the field
- * @param {string} field - the field name
- * @param {string[]} visits - the list of visits to add if adding
- */
- addRemoveField: (
- module: string,
- category: string,
- field: string,
- visits: string[]
- ): void => {
- const newFieldObj: APIQueryField = {
- module: module,
- category: category,
- field: field,
- visits: visits,
- };
- /**
- * Returns true if an element in fields is equal to this field
- *
- * @param {APIQueryField} element - The element to compare
- * @returns {boolean} - true if equal
- */
- const equalField = (element: APIQueryField) => {
- return (element.module == module
+ for (let i = 0; i < newfields.length; i++) {
+ if (equalField(newfields[i])) {
+ newfields[i].visits = visits;
+ setFields(newfields);
+ return;
+ }
+ }
+ },
+ /**
+ * Toggle whether a field is present by adding it if missing or removing it if
+ * present.
+ *
+ * @param {string} module - the module for the field
+ * @param {string} category - the category for the field
+ * @param {string} field - the field name
+ * @param {string[]} visits - the list of visits to add if adding
+ */
+ addRemoveField: (
+ module: string,
+ category: string,
+ field: string,
+ visits: string[]
+ ): void => {
+ const newFieldObj: APIQueryField = {
+ module: module,
+ category: category,
+ field: field,
+ visits: visits,
+ };
+ /**
+ * Returns true if an element in fields is equal to this field
+ *
+ * @param {APIQueryField} element - The element to compare
+ * @returns {boolean} - true if equal
+ */
+ const equalField = (element: APIQueryField) => {
+ return (element.module == module
&& element.category === category
&& element.field == field);
- };
- if (fields.some(equalField)) {
- // Remove
- const newfields: APIQueryField[] = fields.filter(
- (el) => !(equalField(el))
- );
- setFields(newfields);
- } else {
- // Add
- const newfields: APIQueryField[] = [...fields, newFieldObj];
- setFields(newfields);
- }
- },
- /**
- * Remove multiple elements from the current query
- *
- * @param {APIQueryField[]} removeelements - The elements to remove
- * @returns {void}
- */
- removeMany: (removeelements: APIQueryField[]): void => {
- /**
- * Returns true if el1 is equal to el2
- *
- * @param {APIQueryField} el1 - The first element to compare
- * @param {APIQueryField} el2 - The second element to compare
- * @returns {boolean} - true if equal
- */
- const equalField = (
- el1: APIQueryField,
- el2: APIQueryField
- ): boolean => {
- return (el1.module == el2.module
+ };
+ if (fields.some(equalField)) {
+ // Remove
+ const newfields: APIQueryField[] = fields.filter(
+ (el) => !(equalField(el))
+ );
+ setFields(newfields);
+ } else {
+ // Add
+ const newfields: APIQueryField[] = [...fields, newFieldObj];
+ setFields(newfields);
+ }
+ },
+ /**
+ * Remove multiple elements from the current query
+ *
+ * @param {APIQueryField[]} removeelements - The elements to remove
+ * @returns {void}
+ */
+ removeMany: (removeelements: APIQueryField[]): void => {
+ /**
+ * Returns true if el1 is equal to el2
+ *
+ * @param {APIQueryField} el1 - The first element to compare
+ * @param {APIQueryField} el2 - The second element to compare
+ * @returns {boolean} - true if equal
+ */
+ const equalField = (
+ el1: APIQueryField,
+ el2: APIQueryField
+ ): boolean => {
+ return (el1.module == el2.module
&& el1.category === el2.category
&& el1.field == el2.field);
- };
- const newfields = fields.filter((el) => {
- if (removeelements.some((rel) => equalField(rel, el))) {
- return false;
- }
- return true;
- });
- setFields(newfields);
- },
+ };
+ const newfields = fields.filter((el) => {
+ if (removeelements.some((rel) => equalField(rel, el))) {
+ return false;
+ }
+ return true;
+ });
+ setFields(newfields);
+ },
+ /**
+ * Adds many fields to the selected query
+ *
+ * @param {APIQueryField[]} elements - the fields to add
+ * @returns {void}
+ */
+ addMany: (elements: APIQueryField[]): void => {
+ let newfields = fields;
+ for (let i = 0; i < elements.length; i++) {
+ const newFieldObj = elements[i];
/**
- * Adds many fields to the selected query
+ * Returns true if an element in fields is equal to this field
*
- * @param {APIQueryField[]} elements - the fields to add
- * @returns {void}
+ * @param {APIQueryField} element - The element to compare
+ * @returns {boolean} - true if equal
*/
- addMany: (elements: APIQueryField[]): void => {
- let newfields = fields;
- for (let i = 0; i < elements.length; i++) {
- const newFieldObj = elements[i];
- /**
- * Returns true if an element in fields is equal to this field
- *
- * @param {APIQueryField} element - The element to compare
- * @returns {boolean} - true if equal
- */
- const equalField = (element: APIQueryField) => {
- return (element.module == newFieldObj.module
+ const equalField = (element: APIQueryField) => {
+ return (element.module == newFieldObj.module
&& element.category === newFieldObj.category
&& element.field == newFieldObj.field);
- };
- if (!newfields.some((el: APIQueryField) => equalField(el))) {
- newfields = [...newfields, newFieldObj];
- }
- }
- setFields(newfields);
- },
- setFields: setFields,
- };
- return [
- criteria,
- loadQuery,
- fields,
- fieldActions,
- {
- addQueryGroupItem: addQueryGroupItem,
- removeQueryGroupItem: removeQueryGroupItem,
- addNewQueryGroup: addNewQueryGroup,
- setCriteria: setCriteria,
- },
+ };
+ if (!newfields.some((el: APIQueryField) => equalField(el))) {
+ newfields = [...newfields, newFieldObj];
+ }
+ }
+ setFields(newfields);
+ },
+ setFields: setFields,
+ };
+ return [
+ criteria,
+ loadQuery,
+ fields,
+ fieldActions,
+ {
+ addQueryGroupItem: addQueryGroupItem,
+ removeQueryGroupItem: removeQueryGroupItem,
+ addNewQueryGroup: addNewQueryGroup,
+ setCriteria: setCriteria,
+ },
];
}
diff --git a/modules/dataquery/jsx/hooks/usesharedqueries.tsx b/modules/dataquery/jsx/hooks/usesharedqueries.tsx
index 2ac2e1cba9e..642b063baf9 100644
--- a/modules/dataquery/jsx/hooks/usesharedqueries.tsx
+++ b/modules/dataquery/jsx/hooks/usesharedqueries.tsx
@@ -26,28 +26,28 @@ interface FlattenedQueryMap {
* @returns {array} - QueryAction functions
*/
function useStarredQueries(onCompleteCallback: () => void): QueryActionType {
- const [starQueryID, setStarQueryID] = useState(null);
- const [starAction, setStarAction] = useState('true');
- useEffect(() => {
- if (starQueryID == null) {
- return;
- }
+ const [starQueryID, setStarQueryID] = useState(null);
+ const [starAction, setStarAction] = useState('true');
+ useEffect(() => {
+ if (starQueryID == null) {
+ return;
+ }
- fetch(
- '/dataquery/queries/' + starQueryID + '?star=' + starAction,
- {
- method: 'PATCH',
- credentials: 'same-origin',
- },
- ).then( () => {
- setStarQueryID(null);
- if (onCompleteCallback) {
- onCompleteCallback();
- }
- }
- );
- }, [starQueryID, starAction]);
- return [setStarQueryID, setStarAction];
+ fetch(
+ '/dataquery/queries/' + starQueryID + '?star=' + starAction,
+ {
+ method: 'PATCH',
+ credentials: 'same-origin',
+ },
+ ).then( () => {
+ setStarQueryID(null);
+ if (onCompleteCallback) {
+ onCompleteCallback();
+ }
+ }
+ );
+ }, [starQueryID, starAction]);
+ return [setStarQueryID, setStarAction];
}
/**
@@ -58,28 +58,28 @@ function useStarredQueries(onCompleteCallback: () => void): QueryActionType {
* @returns {array} - QueryAction functions
*/
function useShareQueries(onCompleteCallback: () => void): QueryActionType {
- const [shareQueryID, setShareQueryID] = useState(null);
- const [shareAction, setShareAction] = useState('true');
- useEffect(() => {
- if (shareQueryID == null) {
- return;
- }
+ const [shareQueryID, setShareQueryID] = useState(null);
+ const [shareAction, setShareAction] = useState('true');
+ useEffect(() => {
+ if (shareQueryID == null) {
+ return;
+ }
- fetch(
- '/dataquery/queries/' + shareQueryID + '?share=' + shareAction,
- {
- method: 'PATCH',
- credentials: 'same-origin',
- },
- ).then( () => {
- setShareQueryID(null);
- if (onCompleteCallback) {
- onCompleteCallback();
- }
- }
- );
- }, [shareQueryID, shareAction]);
- return [setShareQueryID, setShareAction];
+ fetch(
+ '/dataquery/queries/' + shareQueryID + '?share=' + shareAction,
+ {
+ method: 'PATCH',
+ credentials: 'same-origin',
+ },
+ ).then( () => {
+ setShareQueryID(null);
+ if (onCompleteCallback) {
+ onCompleteCallback();
+ }
+ }
+ );
+ }, [shareQueryID, shareAction]);
+ return [setShareQueryID, setShareAction];
}
type SharedQueriesType = [
@@ -103,156 +103,156 @@ type SharedQueriesType = [
* @returns {array} - [{queries}, reload function(), {queryActions}]
*/
function useSharedQueries(username: string): SharedQueriesType {
- const [recentQueries, setRecentQueries] = useState([]);
- const [sharedQueries, setSharedQueries] = useState([]);
- const [topQueries, setTopQueries] = useState([]);
+ const [recentQueries, setRecentQueries] = useState([]);
+ const [sharedQueries, setSharedQueries] = useState([]);
+ const [topQueries, setTopQueries] = useState([]);
- const [loadQueriesForce, setLoadQueriesForce] = useState(0);
- /**
- * Force the client to reload queries
- *
- * @returns {void}
- */
- const reloadQueries = () => setLoadQueriesForce(loadQueriesForce+1);
- const [setStarQueryID, setStarAction] = useStarredQueries(reloadQueries);
- const [setShareQueryID, setShareAction] = useShareQueries(reloadQueries);
+ const [loadQueriesForce, setLoadQueriesForce] = useState(0);
+ /**
+ * Force the client to reload queries
+ *
+ * @returns {void}
+ */
+ const reloadQueries = () => setLoadQueriesForce(loadQueriesForce+1);
+ const [setStarQueryID, setStarAction] = useStarredQueries(reloadQueries);
+ const [setShareQueryID, setShareAction] = useShareQueries(reloadQueries);
- useEffect(() => {
- fetch('/dataquery/queries', {credentials: 'same-origin'})
- .then((resp) => {
- if (!resp.ok) {
- throw new Error('Invalid response');
- }
- return resp.json();
- }).then((result) => {
- const convertedshared: FlattenedQuery[] = [];
- const convertedtop: FlattenedQuery[] = [];
- const allQueries: FlattenedQueryMap = {};
- if (result.queries) {
- result.queries.forEach( (query: APIQuery) => {
- const flattened: FlattenedQuery = query2flattened(query);
- allQueries[query.QueryID] = flattened;
+ useEffect(() => {
+ fetch('/dataquery/queries', {credentials: 'same-origin'})
+ .then((resp) => {
+ if (!resp.ok) {
+ throw new Error('Invalid response');
+ }
+ return resp.json();
+ }).then((result) => {
+ const convertedshared: FlattenedQuery[] = [];
+ const convertedtop: FlattenedQuery[] = [];
+ const allQueries: FlattenedQueryMap = {};
+ if (result.queries) {
+ result.queries.forEach( (query: APIQuery) => {
+ const flattened: FlattenedQuery = query2flattened(query);
+ allQueries[query.QueryID] = flattened;
- if (query.Pinned == true) {
- convertedtop.push(flattened);
- }
- if (query.Public == true) {
- // If we're the only person who shared it, don't show it in our
- // shared queries.
- // If other people shared it too, then remove ourselves from the
- // "shared by" list in the Shared Queries panel.
- if (query.SharedBy.length == 1
+ if (query.Pinned == true) {
+ convertedtop.push(flattened);
+ }
+ if (query.Public == true) {
+ // If we're the only person who shared it, don't show it in our
+ // shared queries.
+ // If other people shared it too, then remove ourselves from the
+ // "shared by" list in the Shared Queries panel.
+ if (query.SharedBy.length == 1
&& query.SharedBy[0] == username
- ) {
- // don't include
- } else {
- // filter
- query.SharedBy = query.SharedBy.filter(
- (item: string) => {
- return item != username;
- }
- );
- // Make a new copy to avoid mutating the version used by other
- // tabs
- const flattened2: FlattenedQuery
- = query2flattened(query);
- convertedshared.push(flattened2);
- }
+ ) {
+ // don't include
+ } else {
+ // filter
+ query.SharedBy = query.SharedBy.filter(
+ (item: string) => {
+ return item != username;
}
- });
- }
- setSharedQueries(convertedshared);
- setTopQueries(convertedtop);
- return allQueries;
- }).then((allQueries) => {
+ );
+ // Make a new copy to avoid mutating the version used by other
+ // tabs
+ const flattened2: FlattenedQuery
+ = query2flattened(query);
+ convertedshared.push(flattened2);
+ }
+ }
+ });
+ }
+ setSharedQueries(convertedshared);
+ setTopQueries(convertedtop);
+ return allQueries;
+ }).then((allQueries) => {
fetch('/dataquery/queries/runs', {credentials: 'same-origin'})
- .then((resp) => {
- if (!resp.ok) {
- throw new Error('Invalid response');
- }
- return resp.json();
- }).then((result) => {
+ .then((resp) => {
+ if (!resp.ok) {
+ throw new Error('Invalid response');
+ }
+ return resp.json();
+ }).then((result) => {
if (result.queryruns) {
- const convertedrecent: FlattenedQuery[] = [];
- result.queryruns.forEach( (queryRun: APIQueryRun) => {
- const queryObj: FlattenedQuery
+ const convertedrecent: FlattenedQuery[] = [];
+ result.queryruns.forEach( (queryRun: APIQueryRun) => {
+ const queryObj: FlattenedQuery
= allQueries[queryRun.QueryID];
- if (!queryObj) {
- console.error(
- 'Could not get ',
- queryRun.QueryID,
- ' from ',
- allQueries);
- return;
- }
- convertedrecent.push({
- RunTime: queryRun.RunTime,
- ...queryObj,
- });
+ if (!queryObj) {
+ console.error(
+ 'Could not get ',
+ queryRun.QueryID,
+ ' from ',
+ allQueries);
+ return;
+ }
+ convertedrecent.push({
+ RunTime: queryRun.RunTime,
+ ...queryObj,
});
- setRecentQueries(convertedrecent);
+ });
+ setRecentQueries(convertedrecent);
}
- });
- }).catch( (error) => {
- console.error(error);
- });
- }, [loadQueriesForce]);
+ });
+ }).catch( (error) => {
+ console.error(error);
+ });
+ }, [loadQueriesForce]);
- return [
- {
- recent: recentQueries,
- shared: sharedQueries,
- top: topQueries,
- },
- reloadQueries,
- {
- /**
- * Stars a query on the server
- *
- * @param {number} queryID - The queryID to star
- * @returns {void}
- */
- star: (queryID: number) => {
- setShareQueryID(null);
- setStarAction('true');
- setStarQueryID(queryID);
- },
- /**
- * Unstars a query on the server
- *
- * @param {number} queryID - The queryID to unstar
- * @returns {void}
- */
- unstar: (queryID: number) => {
- setShareQueryID(null);
- setStarAction('false');
- setStarQueryID(queryID);
- },
- /**
- * Shares a query on the server
- *
- * @param {number} queryID - The queryID to share
- * @returns {void}
- */
- share: (queryID: number) => {
- setStarQueryID(null);
- setShareAction('true');
- setShareQueryID(queryID);
- },
- /**
- * Unshares a query on the server
- *
- * @param {number} queryID - The queryID to unshare
- * @returns {void}
- */
- unshare: (queryID: number) => {
- setStarQueryID(null);
- setShareAction('false');
- setShareQueryID(queryID);
- },
- },
- ];
+ return [
+ {
+ recent: recentQueries,
+ shared: sharedQueries,
+ top: topQueries,
+ },
+ reloadQueries,
+ {
+ /**
+ * Stars a query on the server
+ *
+ * @param {number} queryID - The queryID to star
+ * @returns {void}
+ */
+ star: (queryID: number) => {
+ setShareQueryID(null);
+ setStarAction('true');
+ setStarQueryID(queryID);
+ },
+ /**
+ * Unstars a query on the server
+ *
+ * @param {number} queryID - The queryID to unstar
+ * @returns {void}
+ */
+ unstar: (queryID: number) => {
+ setShareQueryID(null);
+ setStarAction('false');
+ setStarQueryID(queryID);
+ },
+ /**
+ * Shares a query on the server
+ *
+ * @param {number} queryID - The queryID to share
+ * @returns {void}
+ */
+ share: (queryID: number) => {
+ setStarQueryID(null);
+ setShareAction('true');
+ setShareQueryID(queryID);
+ },
+ /**
+ * Unshares a query on the server
+ *
+ * @param {number} queryID - The queryID to unshare
+ * @returns {void}
+ */
+ unshare: (queryID: number) => {
+ setStarQueryID(null);
+ setShareAction('false');
+ setShareQueryID(queryID);
+ },
+ },
+ ];
}
/**
@@ -263,9 +263,9 @@ function useSharedQueries(username: string): SharedQueriesType {
* @returns {boolean} - true if the term is an APIQueryCriteriaGroup
*/
function isAPIQueryCriteriaGroup(
- term: APIQueryGroupField|APIQueryCriteriaGroup
+ term: APIQueryGroupField|APIQueryCriteriaGroup
): term is APIQueryCriteriaGroup {
- return (term as APIQueryCriteriaGroup).operator !== undefined;
+ return (term as APIQueryCriteriaGroup).operator !== undefined;
}
/**
* Takes a saved query from a JSON object and marshal
@@ -275,39 +275,39 @@ function isAPIQueryCriteriaGroup(
* @returns {QueryGroup} - The object converted into a QueryGroup
*/
function unserializeSavedQuery(query: APIQueryCriteriaGroup): QueryGroup {
- if (!query.operator) {
- throw new Error('Invalid query tree');
- }
- const root = new QueryGroup(query.operator);
- query.group.forEach((val) => {
- if (isAPIQueryCriteriaGroup(val)) {
- const childTree: QueryGroup = unserializeSavedQuery(val);
- root.group.push(childTree);
- return;
- } else {
- if (!val.module
+ if (!query.operator) {
+ throw new Error('Invalid query tree');
+ }
+ const root = new QueryGroup(query.operator);
+ query.group.forEach((val) => {
+ if (isAPIQueryCriteriaGroup(val)) {
+ const childTree: QueryGroup = unserializeSavedQuery(val);
+ root.group.push(childTree);
+ return;
+ } else {
+ if (!val.module
|| !val.category
|| !val.fieldname
|| !val.op) {
- console.error('Invalid criteria', val);
- return;
- }
+ console.error('Invalid criteria', val);
+ return;
+ }
- const term = val as APIQueryGroupField;
+ const term = val as APIQueryGroupField;
- root.addTerm(
- new QueryTerm(
- term.module,
- term.category,
- term.fieldname,
- term.op,
- term.value,
- term.visits,
- )
- );
- }
- });
- return root;
+ root.addTerm(
+ new QueryTerm(
+ term.module,
+ term.category,
+ term.fieldname,
+ term.op,
+ term.value,
+ term.visits,
+ )
+ );
+ }
+ });
+ return root;
}
/**
@@ -316,45 +316,45 @@ function unserializeSavedQuery(query: APIQueryCriteriaGroup): QueryGroup {
* @param {function} loadQuery - function to load the query into React state
*/
function useLoadQueryFromURL(
- loadQuery: (fields: APIQueryField[], filters: QueryGroup|null) => void
+ loadQuery: (fields: APIQueryField[], filters: QueryGroup|null) => void
) {
- // Load query if queryID was passed
- useEffect(() => {
- const params = new URLSearchParams(window.location.search);
- const queryID = params.get('queryID');
- if (!queryID) {
- return;
- }
- fetch(
- '/dataquery/queries/' + queryID,
- {
- method: 'GET',
- credentials: 'same-origin',
- },
- ).then((resp) => {
- if (!resp.ok) {
- throw new Error('Invalid response');
- }
- return resp.json();
- }).then((result) => {
- if (result.Query.criteria) {
- result.Query.criteria = unserializeSavedQuery(
- result.Query.criteria
- );
- }
- loadQuery(result.Query.fields, result.Query.criteria);
- swal.fire({
- type: 'success',
- text: 'Loaded query',
- });
- }).catch( (error) => {
- swal.fire({
- type: 'error',
- text: 'Could not load query',
- });
- console.error(error);
- });
- }, []);
+ // Load query if queryID was passed
+ useEffect(() => {
+ const params = new URLSearchParams(window.location.search);
+ const queryID = params.get('queryID');
+ if (!queryID) {
+ return;
+ }
+ fetch(
+ '/dataquery/queries/' + queryID,
+ {
+ method: 'GET',
+ credentials: 'same-origin',
+ },
+ ).then((resp) => {
+ if (!resp.ok) {
+ throw new Error('Invalid response');
+ }
+ return resp.json();
+ }).then((result) => {
+ if (result.Query.criteria) {
+ result.Query.criteria = unserializeSavedQuery(
+ result.Query.criteria
+ );
+ }
+ loadQuery(result.Query.fields, result.Query.criteria);
+ swal.fire({
+ type: 'success',
+ text: 'Loaded query',
+ });
+ }).catch( (error) => {
+ swal.fire({
+ type: 'error',
+ text: 'Could not load query',
+ });
+ console.error(error);
+ });
+ }, []);
}
@@ -366,51 +366,51 @@ function useLoadQueryFromURL(
* @returns {FlattenedQuery} - The query converted to the new type
*/
function query2flattened(query: APIQuery): FlattenedQuery {
- const rv: FlattenedQuery = {
- QueryID: query.QueryID,
- fields: query.Query.fields.map(
- (field: APIQueryField): FlattenedField => {
- if (!field.visits) {
- return {
- module: field.module,
- category: field.category,
- field: field.field,
- visits: null,
- };
- }
- return {
- module: field.module,
- category: field.category,
- field: field.field,
- visits: field.visits.map( (vl: string): VisitOption => {
- return {label: vl, value: vl};
- }),
- };
- }),
- };
+ const rv: FlattenedQuery = {
+ QueryID: query.QueryID,
+ fields: query.Query.fields.map(
+ (field: APIQueryField): FlattenedField => {
+ if (!field.visits) {
+ return {
+ module: field.module,
+ category: field.category,
+ field: field.field,
+ visits: null,
+ };
+ }
+ return {
+ module: field.module,
+ category: field.category,
+ field: field.field,
+ visits: field.visits.map( (vl: string): VisitOption => {
+ return {label: vl, value: vl};
+ }),
+ };
+ }),
+ };
- if (query.Query.criteria) {
- rv.criteria = unserializeSavedQuery(query.Query.criteria);
- }
- if (query.Name) {
- rv.Name = query.Name;
- }
- if (query.AdminName) {
- rv.AdminName = query.AdminName;
- }
- if (query.Public) {
- rv.Public = query.Public;
- }
- if (query.Starred) {
- rv.Starred = query.Starred;
- }
- if (query.SharedBy) {
- rv.SharedBy = query.SharedBy;
- }
- return rv;
+ if (query.Query.criteria) {
+ rv.criteria = unserializeSavedQuery(query.Query.criteria);
+ }
+ if (query.Name) {
+ rv.Name = query.Name;
+ }
+ if (query.AdminName) {
+ rv.AdminName = query.AdminName;
+ }
+ if (query.Public) {
+ rv.Public = query.Public;
+ }
+ if (query.Starred) {
+ rv.Starred = query.Starred;
+ }
+ if (query.SharedBy) {
+ rv.SharedBy = query.SharedBy;
+ }
+ return rv;
}
export {
- useSharedQueries,
- useLoadQueryFromURL,
+ useSharedQueries,
+ useLoadQueryFromURL,
};
diff --git a/modules/dataquery/jsx/hooks/usevisits.tsx b/modules/dataquery/jsx/hooks/usevisits.tsx
index 1fbc4d4f527..18a0adbfda1 100644
--- a/modules/dataquery/jsx/hooks/usevisits.tsx
+++ b/modules/dataquery/jsx/hooks/usevisits.tsx
@@ -16,36 +16,36 @@ type UseVisitsReturn = {
* @returns {UseVisitsReturn} - list of default and all visits
*/
function useVisits(): UseVisitsReturn {
- const [allVisits, setAllVisits] = useState([]);
- const [defaultVisits, setDefaultVisits] = useState([]);
- useEffect(() => {
- fetch('/dataquery/visitlist', {credentials: 'same-origin'})
- .then((resp) => {
- if (!resp.ok) {
- throw new Error('Invalid response');
- }
- return resp.json();
- }).then((result) => {
- setDefaultVisits(result.Visits);
- setAllVisits(result.Visits);
- }
- ).catch( (error) => {
- console.error(error);
- });
- }, []);
- return {
- all: allVisits,
- default_: defaultVisits,
- /**
- * Modify the default visits to use
- *
- * @param {VisitOption[]} values - The selected options from ReactSelect
- * @returns {void}
- */
- modifyDefault: (values: readonly VisitOption[]) => {
- setDefaultVisits(values.map((el) => el.value));
- },
- };
+ const [allVisits, setAllVisits] = useState([]);
+ const [defaultVisits, setDefaultVisits] = useState([]);
+ useEffect(() => {
+ fetch('/dataquery/visitlist', {credentials: 'same-origin'})
+ .then((resp) => {
+ if (!resp.ok) {
+ throw new Error('Invalid response');
+ }
+ return resp.json();
+ }).then((result) => {
+ setDefaultVisits(result.Visits);
+ setAllVisits(result.Visits);
+ }
+ ).catch( (error) => {
+ console.error(error);
+ });
+ }, []);
+ return {
+ all: allVisits,
+ default_: defaultVisits,
+ /**
+ * Modify the default visits to use
+ *
+ * @param {VisitOption[]} values - The selected options from ReactSelect
+ * @returns {void}
+ */
+ modifyDefault: (values: readonly VisitOption[]) => {
+ setDefaultVisits(values.map((el) => el.value));
+ },
+ };
}
export default useVisits;
diff --git a/modules/dataquery/jsx/index.tsx b/modules/dataquery/jsx/index.tsx
index f234e44eaa1..4b0d3a88b54 100644
--- a/modules/dataquery/jsx/index.tsx
+++ b/modules/dataquery/jsx/index.tsx
@@ -32,32 +32,32 @@ type ActiveCategoryType = {
* @returns {ActiveCategoryType} - an object of the current dictionary and action to change it
*/
function useActiveCategory(
- retrieveModuleDictionary: (module: string) => Promise
+ retrieveModuleDictionary: (module: string) => Promise
): ActiveCategoryType {
- const [module, setModule] = useState('');
- const [category, setCategory] = useState('');
- const [moduleDict, setModuleDict] = useState({});
- /**
- * Change the current category, retrieving the module dictionary from
- * the server if necessary.
- *
- * @param {string} module - the module to become active
- * @param {string} category - the category to become active
- * @returns {void}
- */
- const changeCategory = (module: string, category: string) => {
- retrieveModuleDictionary(module).then( (dict) => {
- setModule(module);
- setCategory(category);
- setModuleDict(dict[category]);
- });
- };
- return {
- module: module,
- category: category,
- currentDictionary: moduleDict,
- changeCategory: changeCategory,
- };
+ const [module, setModule] = useState('');
+ const [category, setCategory] = useState('');
+ const [moduleDict, setModuleDict] = useState({});
+ /**
+ * Change the current category, retrieving the module dictionary from
+ * the server if necessary.
+ *
+ * @param {string} module - the module to become active
+ * @param {string} category - the category to become active
+ * @returns {void}
+ */
+ const changeCategory = (module: string, category: string) => {
+ retrieveModuleDictionary(module).then( (dict) => {
+ setModule(module);
+ setCategory(category);
+ setModuleDict(dict[category]);
+ });
+ };
+ return {
+ module: module,
+ category: category,
+ currentDictionary: moduleDict,
+ changeCategory: changeCategory,
+ };
}
/**
@@ -72,50 +72,50 @@ function DataQueryApp(props: {
queryAdmin: boolean,
username: string
}) {
- const [activeTab, setActiveTab] = useState('Info');
- useBreadcrumbs(activeTab, setActiveTab);
+ const [activeTab, setActiveTab] = useState('Info');
+ useBreadcrumbs(activeTab, setActiveTab);
- const [queries, reloadQueries, queryActions]
+ const [queries, reloadQueries, queryActions]
= useSharedQueries(props.username);
- const visits = useVisits();
+ const visits = useVisits();
- const [
- fulldictionary,
- fetchModuleDictionary,
- ] = useDataDictionary();
- const categories = useCategories();
+ const [
+ fulldictionary,
+ fetchModuleDictionary,
+ ] = useDataDictionary();
+ const categories = useCategories();
- const activeCategory = useActiveCategory(
- fetchModuleDictionary,
- );
+ const activeCategory = useActiveCategory(
+ fetchModuleDictionary,
+ );
- const [query,
- loadQuery,
- selectedFields,
- fieldActions,
- criteriaActions,
- ] = useQuery();
+ const [query,
+ loadQuery,
+ selectedFields,
+ fieldActions,
+ criteriaActions,
+ ] = useQuery();
- useLoadQueryFromURL(loadQuery);
+ useLoadQueryFromURL(loadQuery);
- if (!categories) {
- return Loading...
;
+ if (!categories) {
+ return Loading...
;
+ }
+ let content;
+
+ /**
+ * Maps a module name from the backend name to a human friendly name.
+ *
+ * @param {string} name - The module name
+ * @returns {string} - the human friendly name
+ */
+ const mapModuleName = (name: string): string => {
+ if (categories && categories.modules) {
+ return categories.modules[name];
}
- let content;
-
- /**
- * Maps a module name from the backend name to a human friendly name.
- *
- * @param {string} name - The module name
- * @returns {string} - the human friendly name
- */
- const mapModuleName = (name: string): string => {
- if (categories && categories.modules) {
- return categories.modules[name];
- }
- return name;
- };
+ return name;
+ };
/**
* Maps a category name from the backend name to a human friendly name.
*
@@ -123,137 +123,137 @@ function DataQueryApp(props: {
* @param {string} category - The category name within the module
* @returns {string} - the human friendly name
*/
- const mapCategoryName = (module: string, category: string): string => {
- if (categories && categories.categories
+ const mapCategoryName = (module: string, category: string): string => {
+ if (categories && categories.categories
&& categories.categories[module]) {
- return categories.categories[module][category];
- }
- return category;
- };
-
- /**
- * Function to retrieve a module's data dictionary from the server.
- *
- * @param {string} module - the module whole fields should be retrieved
- * @returns {void}
- */
- const getModuleFields = (module: string): void => {
- fetchModuleDictionary(module);
- };
-
- switch (activeTab) {
- case 'Info':
- content = setActiveTab('DefineFields')}
-
- queryAdmin={props.queryAdmin}
- />;
- break;
- case 'DefineFields':
- content = ;
- break;
- case 'DefineFilters':
- content = ;
- break;
- case 'ViewData':
- content = ;
- break;
- default:
- content = Invalid tab
;
+ return categories.categories[module][category];
}
- return
-
{content}
-
setActiveTab(page)
- }/>
- ;
+ return category;
+ };
+
+ /**
+ * Function to retrieve a module's data dictionary from the server.
+ *
+ * @param {string} module - the module whole fields should be retrieved
+ * @returns {void}
+ */
+ const getModuleFields = (module: string): void => {
+ fetchModuleDictionary(module);
+ };
+
+ switch (activeTab) {
+ case 'Info':
+ content = setActiveTab('DefineFields')}
+
+ queryAdmin={props.queryAdmin}
+ />;
+ break;
+ case 'DefineFields':
+ content = ;
+ break;
+ case 'DefineFilters':
+ content = ;
+ break;
+ case 'ViewData':
+ content = ;
+ break;
+ default:
+ content = Invalid tab
;
+ }
+ return
+
{content}
+
setActiveTab(page)
+ }/>
+ ;
}
declare const loris: any;
window.addEventListener('load', () => {
const element = document.getElementById('lorisworkspace');
if (!element) {
- throw new Error('Missing lorisworkspace');
+ throw new Error('Missing lorisworkspace');
}
const root = createRoot(element);
root.render(
,
);
});
diff --git a/modules/dataquery/jsx/nextsteps.tsx b/modules/dataquery/jsx/nextsteps.tsx
index 0264501e693..59a88cdfac2 100644
--- a/modules/dataquery/jsx/nextsteps.tsx
+++ b/modules/dataquery/jsx/nextsteps.tsx
@@ -19,146 +19,146 @@ function NextSteps(props: {
page: string,
changePage: (newpage: string) => void,
}) {
- const [expanded, setExpanded] = useState(true);
- const steps: React.ReactElement[] = [];
+ const [expanded, setExpanded] = useState(true);
+ const steps: React.ReactElement[] = [];
- const canRun = (props.fields && props.fields.length > 0);
- const fieldLabel = (props.fields && props.fields.length > 0)
- ? 'Modify Fields'
- : 'Choose Fields';
- const filterLabel = (props.filters && props.filters.group.length > 0)
- ? 'Modify Filters'
- : 'Add Filters';
- switch (props.page) {
- case 'Info':
- if (canRun) {
- // A previous query was loaded, it can be either
- // modified or run
- steps.push( props.changePage('DefineFields')}
- />);
- steps.push( props.changePage('DefineFilters')}
- />);
- steps.push( props.changePage('ViewData')}
- />);
- } else {
- // No query loaded, must define fields
- steps.push( props.changePage('DefineFields')}
- />);
- }
- break;
- case 'DefineFields':
- steps.push( props.changePage('DefineFilters')}
- />);
- if (canRun) {
- steps.push( props.changePage('ViewData')}
- />);
- }
- break;
- case 'DefineFilters':
- if (canRun) {
- steps.push( props.changePage('ViewData')}
- />);
- }
- steps.push( props.changePage('DefineFields')}
- />);
- break;
- case 'ViewData':
- steps.push( props.changePage('DefineFields')}
- />);
- steps.push( props.changePage('DefineFilters')}
- />);
- break;
+ const canRun = (props.fields && props.fields.length > 0);
+ const fieldLabel = (props.fields && props.fields.length > 0)
+ ? 'Modify Fields'
+ : 'Choose Fields';
+ const filterLabel = (props.filters && props.filters.group.length > 0)
+ ? 'Modify Filters'
+ : 'Add Filters';
+ switch (props.page) {
+ case 'Info':
+ if (canRun) {
+ // A previous query was loaded, it can be either
+ // modified or run
+ steps.push( props.changePage('DefineFields')}
+ />);
+ steps.push( props.changePage('DefineFilters')}
+ />);
+ steps.push( props.changePage('ViewData')}
+ />);
+ } else {
+ // No query loaded, must define fields
+ steps.push( props.changePage('DefineFields')}
+ />);
}
+ break;
+ case 'DefineFields':
+ steps.push( props.changePage('DefineFilters')}
+ />);
+ if (canRun) {
+ steps.push( props.changePage('ViewData')}
+ />);
+ }
+ break;
+ case 'DefineFilters':
+ if (canRun) {
+ steps.push( props.changePage('ViewData')}
+ />);
+ }
+ steps.push( props.changePage('DefineFields')}
+ />);
+ break;
+ case 'ViewData':
+ steps.push( props.changePage('DefineFields')}
+ />);
+ steps.push( props.changePage('DefineFilters')}
+ />);
+ break;
+ }
- const expandIcon = setExpanded(!expanded)}
- >;
- const style = expanded ? {
- background: 'white',
- padding: '0.5em',
- paddingLeft: '2em',
- } : {
- display: 'none',
- visibility: 'hidden' as const,
- padding: '0.5em',
- paddingLeft: '2em',
- };
+ const expandIcon = setExpanded(!expanded)}
+ >;
+ const style = expanded ? {
+ background: 'white',
+ padding: '0.5em',
+ paddingLeft: '2em',
+ } : {
+ display: 'none',
+ visibility: 'hidden' as const,
+ padding: '0.5em',
+ paddingLeft: '2em',
+ };
- return (
-
-
-
-
Next Steps
-
- {steps}
-
-
-
{expandIcon}
+ return (
+
+ );
}
export default NextSteps;
diff --git a/modules/dataquery/jsx/querydef.tsx b/modules/dataquery/jsx/querydef.tsx
index 5c28b34362a..2cac2d7bdf4 100644
--- a/modules/dataquery/jsx/querydef.tsx
+++ b/modules/dataquery/jsx/querydef.tsx
@@ -20,19 +20,19 @@ export class QueryTerm {
* @param {array} visits - the visits for the criteria
*/
constructor(
- module: string,
- category: string,
- fieldname: string,
- op: string,
- value: string|string[],
- visits?: string[]
+ module: string,
+ category: string,
+ fieldname: string,
+ op: string,
+ value: string|string[],
+ visits?: string[]
) {
- this.module = module;
- this.category = category;
- this.fieldname = fieldname;
- this.op = op;
- this.value = value;
- this.visits = visits;
+ this.module = module;
+ this.category = category;
+ this.fieldname = fieldname;
+ this.op = op;
+ this.value = value;
+ this.visits = visits;
}
}
@@ -49,8 +49,8 @@ export class QueryGroup {
* @param {string} op -- 'and' or 'or' -- the operator used for this group
*/
constructor(op: 'and' | 'or') {
- this.operator = op;
- this.group = [];
+ this.operator = op;
+ this.group = [];
}
/**
@@ -59,7 +59,7 @@ export class QueryGroup {
* @param {object} condition - the term's conditions
*/
addTerm(condition: QueryTerm) {
- this.group.push(condition);
+ this.group.push(condition);
}
/**
@@ -69,10 +69,10 @@ export class QueryGroup {
* @returns {QueryGroup} - the new querygroup
*/
removeTerm(idx: number): QueryGroup {
- this.group = this.group.filter((el, fidx) => {
- return idx != fidx;
- });
- return this;
+ this.group = this.group.filter((el, fidx) => {
+ return idx != fidx;
+ });
+ return this;
}
/**
@@ -80,10 +80,10 @@ export class QueryGroup {
* as a subgroup.
*/
addGroup(): void {
- // The default operation for a subgroup
- // is the opposite of this one, otherwise
- // there would be no reason for a new group
- const newOp = this.operator == 'and' ? 'or' : 'and';
- this.group.push(new QueryGroup(newOp));
+ // The default operation for a subgroup
+ // is the opposite of this one, otherwise
+ // there would be no reason for a new group
+ const newOp = this.operator == 'and' ? 'or' : 'and';
+ this.group.push(new QueryGroup(newOp));
}
}
diff --git a/modules/dataquery/jsx/querytree.tsx b/modules/dataquery/jsx/querytree.tsx
index 1cb319674cd..ca741839935 100644
--- a/modules/dataquery/jsx/querytree.tsx
+++ b/modules/dataquery/jsx/querytree.tsx
@@ -11,10 +11,10 @@ import {FullDictionary} from './types';
* @returns {string} - The next colour after c
*/
function alternateColour(c: string): string {
- if (c == 'rgb(255, 255, 255)') {
- return 'rgb(240, 240, 240)';
- }
- return 'rgb(255, 255, 255)';
+ if (c == 'rgb(255, 255, 255)') {
+ return 'rgb(240, 240, 240)';
+ }
+ return 'rgb(255, 255, 255)';
}
/**
@@ -58,228 +58,228 @@ function QueryTree(props: {
mapModuleName: (module: string) => string,
mapCategoryName: (module: string, category: string) => string,
}) {
- const [deleteItemIndex, setDeleteItemIndex] = useState
(null);
+ const [deleteItemIndex, setDeleteItemIndex] = useState(null);
- /**
- * Render a single term of the QueryTree group.
- *
- * @param {QueryGroup|QueryTerm} item - The item to render from a group
- * @param {number} i - the index being rendered
- * @returns {React.ReactElement} - The react element
- */
- const renderitem =
+ /**
+ * Render a single term of the QueryTree group.
+ *
+ * @param {QueryGroup|QueryTerm} item - The item to render from a group
+ * @param {number} i - the index being rendered
+ * @returns {React.ReactElement} - The react element
+ */
+ const renderitem =
(item: QueryGroup|QueryTerm, i: number): React.ReactElement => {
- const operator = i != props.items.group.length-1 ?
+ const operator = i != props.items.group.length-1 ?
props.items.operator : '';
- const style: React.CSSProperties = {
+ const style: React.CSSProperties = {
display: 'flex' as const,
flexDirection: 'column' as const,
width: '100%',
- };
- const operatorStyle = {
+ };
+ const operatorStyle = {
alignSelf: 'center',
fontWeight: 'bold',
- };
- if (deleteItemIndex == i) {
+ };
+ if (deleteItemIndex == i) {
style.textDecoration = 'line-through';
- }
+ }
- /**
- * Deletes an item from the group and call the removeQueryGroupItem
- * callback.
- *
- * @returns {void}
- */
- const deleteItem = () => {
+ /**
+ * Deletes an item from the group and call the removeQueryGroupItem
+ * callback.
+ *
+ * @returns {void}
+ */
+ const deleteItem = () => {
if (props.removeQueryGroupItem) {
- const newquery = props.removeQueryGroupItem(
- props.items,
- i,
- );
- if (props.setModalGroup) {
- props.setModalGroup(newquery);
- }
+ const newquery = props.removeQueryGroupItem(
+ props.items,
+ i,
+ );
+ if (props.setModalGroup) {
+ props.setModalGroup(newquery);
+ }
}
- };
- if (item instanceof QueryTerm) {
+ };
+ if (item instanceof QueryTerm) {
const deleteIcon = props.removeQueryGroupItem ? (
-
- setDeleteItemIndex(i)}
- onMouseLeave={() => setDeleteItemIndex(null)}
- style={{cursor: 'pointer'}}
- />
-
+
+ setDeleteItemIndex(i)}
+ onMouseLeave={() => setDeleteItemIndex(null)}
+ style={{cursor: 'pointer'}}
+ />
+
) : '';
return
-
-
- {deleteIcon}
-
- {operator}
- ;
- } else if (item instanceof QueryGroup) {
+
+
+ {deleteIcon}
+
+ {operator}
+ ;
+ } else if (item instanceof QueryGroup) {
const buttonStyle: React.CSSProperties = deleteItemIndex == i ? {
- textDecoration: 'line-through',
+ textDecoration: 'line-through',
} : {};
return (
-
-
+ setDeleteItemIndex(i)}
- onDeleteLeave={
- () => setDeleteItemIndex(null)
- }
- subtree={true}
- fulldictionary={props.fulldictionary}
+ )
+ }
+ deleteItem={deleteItem}
+ buttonStyle={buttonStyle}
+ onDeleteHover={() => setDeleteItemIndex(i)}
+ onDeleteLeave={
+ () => setDeleteItemIndex(null)
+ }
+ subtree={true}
+ fulldictionary={props.fulldictionary}
- />
-
- {operator}
+ />
+
+
{operator}
);
- } else {
+ } else {
console.error('Invalid tree');
- }
- return
{i};
- };
+ }
+ return
{i};
+ };
- const terms: React.ReactElement[] = props.items.group.map(renderitem);
- let warning;
- switch (props.items.group.length) {
- case 0:
- warning =
-
-
+ const terms: React.ReactElement[] = props.items.group.map(renderitem);
+ let warning;
+ switch (props.items.group.length) {
+ case 0:
+ warning =
+
+
Group does not have any items.
-
-
;
- break;
- case 1:
- warning =
;
+ break;
+ case 1:
+ warning =
+
+
Group only has 1 item. A group with only 1 item is equivalent
to not having the group.
-
-
;
- break;
- }
+
+
;
+ break;
+ }
- /**
- * Handler to calls newItem callback onClick
- *
- * @param {React.MouseEvent} e - The event
- * @returns {void}
- */
- const newItemClick = (e: React.MouseEvent) => {
- e.preventDefault();
- if (props.newItem) {
- props.newItem(props.items);
- }
- };
-
- /**
- * Call newGroup callback onClick
- *
- * @param {React.MouseEvent} e - The event
- * @returns {void}
- */
- const newGroupClick = (e: React.MouseEvent) => {
- e.preventDefault();
- if (props.newGroup) {
- props.newGroup(props.items);
- }
- };
+ /**
+ * Handler to calls newItem callback onClick
+ *
+ * @param {React.MouseEvent} e - The event
+ * @returns {void}
+ */
+ const newItemClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ if (props.newItem) {
+ props.newItem(props.items);
+ }
+ };
- const antiOperator = props.items.operator == 'and' ? 'or' : 'and';
- const style: React.CSSProperties= {};
- if (props.activeGroup == props.items) {
- style.background = 'pink';
+ /**
+ * Call newGroup callback onClick
+ *
+ * @param {React.MouseEvent} e - The event
+ * @returns {void}
+ */
+ const newGroupClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ if (props.newGroup) {
+ props.newGroup(props.items);
}
+ };
- let deleteGroupHTML;
- if (props.deleteItem) {
- deleteGroupHTML = (
-
-
-
- );
- }
- const marginStyle: React.CSSProperties = props.subtree === true ? {} : {
- margin: 0,
- padding: 0,
- };
- return (
-
-
- {terms}
- -
-
-
-
+
+
+ );
+ }
+ const marginStyle: React.CSSProperties = props.subtree === true ? {} : {
+ margin: 0,
+ padding: 0,
+ };
+ return (
+
+
+ {terms}
+ -
+
+
+
-
-
-
-
- {warning}
- {deleteGroupHTML}
+ onUserInput={newItemClick}
+ style={props.buttonStyle}
+ columnSize='col-sm-12'
+ />
+
+
+
+
+ {warning}
+ {deleteGroupHTML}
-
-
- );
+
+
+ );
}
export default QueryTree;
diff --git a/modules/dataquery/jsx/viewdata.tsx b/modules/dataquery/jsx/viewdata.tsx
index cea6e7d6bb5..cf3afb68c38 100644
--- a/modules/dataquery/jsx/viewdata.tsx
+++ b/modules/dataquery/jsx/viewdata.tsx
@@ -28,17 +28,17 @@ type SessionRowCell = {
* @returns {string} the non-JSON value
*/
function cellValue(data: string) {
- try {
- const parsed = JSON.parse(data);
- if (typeof parsed === 'object') {
- // Can't include objects as react children, if we got here
- // there's probably a bug.
- return data;
- }
- return parsed;
- } catch (e) {
- return data;
+ try {
+ const parsed = JSON.parse(data);
+ if (typeof parsed === 'object') {
+ // Can't include objects as react children, if we got here
+ // there's probably a bug.
+ return data;
}
+ return parsed;
+ } catch (e) {
+ return data;
+ }
}
/**
@@ -50,7 +50,7 @@ function cellValue(data: string) {
* @returns {React.ReactElement} - the Table Cell
*/
function TableCell(props: {data: string}) {
- return
{cellValue(props.data)} | ;
+ return
{cellValue(props.data)} | ;
}
enum EnumDisplayTypes {
@@ -72,34 +72,34 @@ function DisplayValue(props: {
dictionary: FieldDictionary,
enumDisplay: EnumDisplayTypes}
) {
- let display = props.value;
- switch (props.enumDisplay) {
- case EnumDisplayTypes.EnumLabel:
- if (props.dictionary.labels && props.dictionary.options) {
- for (let i = 0; i < props.dictionary.options.length; i++) {
- if (props.dictionary.options[i] == props.value) {
- display= props.dictionary.labels[i];
- break;
- }
- }
- }
- break;
+ let display = props.value;
+ switch (props.enumDisplay) {
+ case EnumDisplayTypes.EnumLabel:
+ if (props.dictionary.labels && props.dictionary.options) {
+ for (let i = 0; i < props.dictionary.options.length; i++) {
+ if (props.dictionary.options[i] == props.value) {
+ display= props.dictionary.labels[i];
+ break;
+ }
+ }
}
+ break;
+ }
- if (props.value === true) {
- return 'True';
- } else if (props.value === false) {
- return 'False';
- }
+ if (props.value === true) {
+ return 'True';
+ } else if (props.value === false) {
+ return 'False';
+ }
- if (props.dictionary.type == 'URI') {
- display = (
-
- {display}
-
- );
- }
- return display;
+ if (props.dictionary.type == 'URI') {
+ display = (
+
+ {display}
+
+ );
+ }
+ return display;
}
/**
@@ -112,36 +112,36 @@ function DisplayValue(props: {
* @returns {React.ReactElement} - The ProgressBar element
*/
function ProgressBar(props: {type: string, value: number, max: number}) {
- switch (props.type) {
- case 'loading':
- if (props.value == 0) {
- return
Query not yet run
;
- }
- return (
-
-
-
);
- case 'headers':
- return (
-
-
-
);
- case 'dataorganization':
- return (
-
-
-
);
+ switch (props.type) {
+ case 'loading':
+ if (props.value == 0) {
+ return
Query not yet run
;
}
- return
Invalid progress type: {props.type}
;
+ return (
+
+
+
);
+ case 'headers':
+ return (
+
+
+
);
+ case 'dataorganization':
+ return (
+
+
+
);
+ }
+ return
Invalid progress type: {props.type}
;
}
type RunQueryType = {
@@ -158,77 +158,77 @@ type RunQueryType = {
* @returns {RunQueryType} - a description of the status of the loading and the loaded values
*/
function useRunQuery(
- fields: APIQueryField[],
- filters: QueryGroup,
- onRun: () => void
+ fields: APIQueryField[],
+ filters: QueryGroup,
+ onRun: () => void
): RunQueryType {
- const [expectedResults, setExpectedResults] = useState
(0);
- const [resultData, setResultData] = useState([]);
- const [loading, setLoading] = useState(false);
+ const [expectedResults, setExpectedResults] = useState(0);
+ const [resultData, setResultData] = useState([]);
+ const [loading, setLoading] = useState(false);
- useEffect(() => {
- setLoading(true);
- const payload: APIQueryObject = calcPayload(fields, filters);
+ useEffect(() => {
+ setLoading(true);
+ const payload: APIQueryObject = calcPayload(fields, filters);
+ fetch(
+ '/dataquery/queries',
+ {
+ method: 'post',
+ credentials: 'same-origin',
+ body: JSON.stringify(payload),
+ },
+ ).then(
+ (resp) => {
+ if (!resp.ok) {
+ throw new Error('Error creating query.');
+ }
+ return resp.json();
+ }
+ ).then(
+ (data) => {
+ const resultbuffer: any[] = [];
fetch(
- '/dataquery/queries',
- {
- method: 'post',
- credentials: 'same-origin',
- body: JSON.stringify(payload),
- },
- ).then(
- (resp) => {
- if (!resp.ok) {
- throw new Error('Error creating query.');
- }
- return resp.json();
- }
- ).then(
- (data) => {
- const resultbuffer: any[] = [];
- fetch(
- '/dataquery/queries/'
+ '/dataquery/queries/'
+ data.QueryID + '/count',
- {
- method: 'GET',
- credentials: 'same-origin',
- }
- ).then((resp) => resp.json()
- ).then( (json) => {
- setExpectedResults(json.count);
- });
- fetchDataStream(
- '/dataquery/queries/' + data.QueryID + '/run',
- (row: any) => {
- resultbuffer.push(row);
- },
- () => {
- if (resultbuffer.length % 10 == 0) {
- setResultData([...resultbuffer]);
- }
- },
- () => {
- setResultData([...resultbuffer]);
- setLoading(false);
- },
- 'post',
- );
- onRun(); // forces query list to be reloaded
- }
- ).catch(
- (msg) => {
- swal.fire({
- type: 'error',
- text: msg,
- });
+ {
+ method: 'GET',
+ credentials: 'same-origin',
+ }
+ ).then((resp) => resp.json()
+ ).then( (json) => {
+ setExpectedResults(json.count);
+ });
+ fetchDataStream(
+ '/dataquery/queries/' + data.QueryID + '/run',
+ (row: any) => {
+ resultbuffer.push(row);
+ },
+ () => {
+ if (resultbuffer.length % 10 == 0) {
+ setResultData([...resultbuffer]);
}
+ },
+ () => {
+ setResultData([...resultbuffer]);
+ setLoading(false);
+ },
+ 'post',
);
- }, [fields, filters]);
- return {
- loading: loading,
- data: resultData,
- totalcount: expectedResults,
- };
+ onRun(); // forces query list to be reloaded
+ }
+ ).catch(
+ (msg) => {
+ swal.fire({
+ type: 'error',
+ text: msg,
+ });
+ }
+ );
+ }, [fields, filters]);
+ return {
+ loading: loading,
+ data: resultData,
+ totalcount: expectedResults,
+ };
}
type DataOrganizationType = {
@@ -248,50 +248,50 @@ type DataOrganizationType = {
* @returns {object} - the headers and data re-organised according to the user's selection
*/
function useDataOrganization(
- queryData: RunQueryType,
- visitOrganization: VisitOrgType,
- headerDisplay: HeaderDisplayType,
- fields: APIQueryField[],
- fulldictionary: FullDictionary
+ queryData: RunQueryType,
+ visitOrganization: VisitOrgType,
+ headerDisplay: HeaderDisplayType,
+ fields: APIQueryField[],
+ fulldictionary: FullDictionary
) : DataOrganizationType {
- const [tableData, setTableData] = useState([]);
- const [orgStatus, setOrgStatus]
+ const [tableData, setTableData] = useState([]);
+ const [orgStatus, setOrgStatus]
= useState<'headers'|'data'|'done'|null>(null);
- const [progress, setProgress] = useState(0);
- const [headers, setHeaders] = useState([]);
- useEffect( () => {
- if (queryData.loading == true) {
- return;
- }
- setOrgStatus('headers');
- organizeHeaders(fields,
- visitOrganization,
- headerDisplay,
- fulldictionary,
- (i) => setProgress(i),
- ).then( (headers: string[]) => {
- setHeaders(headers);
- setOrgStatus('data');
+ const [progress, setProgress] = useState(0);
+ const [headers, setHeaders] = useState([]);
+ useEffect( () => {
+ if (queryData.loading == true) {
+ return;
+ }
+ setOrgStatus('headers');
+ organizeHeaders(fields,
+ visitOrganization,
+ headerDisplay,
+ fulldictionary,
+ (i) => setProgress(i),
+ ).then( (headers: string[]) => {
+ setHeaders(headers);
+ setOrgStatus('data');
- organizeData(
- queryData.data,
- visitOrganization,
- fulldictionary,
- fields,
- (i) => setProgress(i),
- ).then((data: TableRow[]) => {
- setTableData(data);
- setOrgStatus('done');
- });
- });
- }, [visitOrganization, headerDisplay, queryData.loading, queryData.data]);
- return {
- 'headers': headers,
- 'data': tableData,
+ organizeData(
+ queryData.data,
+ visitOrganization,
+ fulldictionary,
+ fields,
+ (i) => setProgress(i),
+ ).then((data: TableRow[]) => {
+ setTableData(data);
+ setOrgStatus('done');
+ });
+ });
+ }, [visitOrganization, headerDisplay, queryData.loading, queryData.data]);
+ return {
+ 'headers': headers,
+ 'data': tableData,
- 'status': orgStatus,
- 'progress': progress,
- };
+ 'status': orgStatus,
+ 'progress': progress,
+ };
}
/**
@@ -310,163 +310,163 @@ function ViewData(props: {
onRun: () => void
fulldictionary: FullDictionary,
}) {
- const [visitOrganization, setVisitOrganization]
+ const [visitOrganization, setVisitOrganization]
= useState('inline');
- const [headerDisplay, setHeaderDisplay]
+ const [headerDisplay, setHeaderDisplay]
= useState('fieldnamedesc');
- const [enumDisplay, setEnumDisplay]
+ const [enumDisplay, setEnumDisplay]
= useState(EnumDisplayTypes.EnumLabel);
- const queryData = useRunQuery(props.fields, props.filters, props.onRun);
- const organizedData = useDataOrganization(
- queryData,
- visitOrganization,
- headerDisplay,
- props.fields,
- props.fulldictionary
- );
- const [emptyVisits, setEmptyVisits] = useState(true);
+ const queryData = useRunQuery(props.fields, props.filters, props.onRun);
+ const organizedData = useDataOrganization(
+ queryData,
+ visitOrganization,
+ headerDisplay,
+ props.fields,
+ props.fulldictionary
+ );
+ const [emptyVisits, setEmptyVisits] = useState(true);
- let queryTable;
- if (queryData.loading) {
- queryTable = ;
- } else {
- switch (organizedData['status']) {
- case null:
- return queryTable = Query not yet run
;
- case 'headers':
- queryTable = ;
- break;
- case 'data':
- queryTable = ;
- break;
- case 'done':
- try {
- queryTable = {
- return {show: true, label: val};
- })
- }
- data={organizedData.data}
- getMappedCell={
- organizedMapper(
- visitOrganization,
- props.fields,
- props.fulldictionary,
- )
- }
- getFormattedCell={
- organizedFormatter(
- queryData.data,
- visitOrganization,
- props.fields,
- props.fulldictionary,
- emptyVisits,
- enumDisplay,
- )
- }
- hide={
- {
- rowsPerPage: false,
- defaultColumn: true,
- downloadCSV: visitOrganization == 'inline',
- }
- }
- />;
- } catch (e) {
- // OrganizedMapper/Formatter can throw an error
- // before the loading is complete
- return Loading..
;
+ let queryTable;
+ if (queryData.loading) {
+ queryTable = ;
+ } else {
+ switch (organizedData['status']) {
+ case null:
+ return queryTable = Query not yet run
;
+ case 'headers':
+ queryTable = ;
+ break;
+ case 'data':
+ queryTable = ;
+ break;
+ case 'done':
+ try {
+ queryTable = {
+ return {show: true, label: val};
+ })
+ }
+ data={organizedData.data}
+ getMappedCell={
+ organizedMapper(
+ visitOrganization,
+ props.fields,
+ props.fulldictionary,
+ )
+ }
+ getFormattedCell={
+ organizedFormatter(
+ queryData.data,
+ visitOrganization,
+ props.fields,
+ props.fulldictionary,
+ emptyVisits,
+ enumDisplay,
+ )
+ }
+ hide={
+ {
+ rowsPerPage: false,
+ defaultColumn: true,
+ downloadCSV: visitOrganization == 'inline',
}
- break;
- default:
- throw new Error('Unhandled organization status');
- }
+ }
+ />;
+ } catch (e) {
+ // OrganizedMapper/Formatter can throw an error
+ // before the loading is complete
+ return Loading..
;
+ }
+ break;
+ default:
+ throw new Error('Unhandled organization status');
}
+ }
- const emptyCheckbox = (visitOrganization === 'inline' ?
-
- setEmptyVisits(value)
- }
- />
- : );
- return
-
- setHeaderDisplay(value)
- }
- sortByValue={false}
- />
-
- setVisitOrganization(value)
- }
- sortByValue={false}
- />
- {
- if (value == 'labels') {
- setEnumDisplay(EnumDisplayTypes.EnumLabel);
- } else {
- setEnumDisplay(EnumDisplayTypes.EnumValue);
- }
- }
- }
- sortByValue={false}
- />
- {emptyCheckbox}
- {queryTable}
-
;
+ const emptyCheckbox = (visitOrganization === 'inline' ?
+
+ setEmptyVisits(value)
+ }
+ />
+ : );
+ return
+
+ setHeaderDisplay(value)
+ }
+ sortByValue={false}
+ />
+
+ setVisitOrganization(value)
+ }
+ sortByValue={false}
+ />
+ {
+ if (value == 'labels') {
+ setEnumDisplay(EnumDisplayTypes.EnumLabel);
+ } else {
+ setEnumDisplay(EnumDisplayTypes.EnumValue);
+ }
+ }
+ }
+ sortByValue={false}
+ />
+ {emptyCheckbox}
+ {queryTable}
+
;
}
/**
@@ -485,135 +485,135 @@ function ViewData(props: {
* for the sessions or headers.
*/
function organizeData(
- resultData: string[][],
- visitOrganization: VisitOrgType,
- fulldict: FullDictionary,
- fields: APIQueryField[],
- onProgress: (i: number) => void
+ resultData: string[][],
+ visitOrganization: VisitOrgType,
+ fulldict: FullDictionary,
+ fields: APIQueryField[],
+ onProgress: (i: number) => void
) : Promise {
- switch (visitOrganization) {
- case 'raw':
- return Promise.resolve(resultData);
- case 'inline':
- // Organize with flexbox within the cell by the
- // formatter
- return Promise.resolve(resultData);
- case 'longitudinal':
- // the formatter splits into multiple cells
- return Promise.resolve(resultData);
- case 'crosssection':
- return new Promise((resolve) => {
- let rowNum = 0;
- const promises: Promise[] = [];
- for (const candidaterow of resultData) {
- promises.push(new Promise((resolve) => {
- // Collect list of visits for this candidate
- setTimeout( () => {
- const candidatevisits: {[visit: string]: boolean} = {};
- for (const i in candidaterow) {
- if (!candidaterow.hasOwnProperty(i)) {
- continue;
- }
- const dictionary = getDictionary(fields[i], fulldict);
- if (dictionary && dictionary.scope == 'session') {
- if (candidaterow[i] === null
+ switch (visitOrganization) {
+ case 'raw':
+ return Promise.resolve(resultData);
+ case 'inline':
+ // Organize with flexbox within the cell by the
+ // formatter
+ return Promise.resolve(resultData);
+ case 'longitudinal':
+ // the formatter splits into multiple cells
+ return Promise.resolve(resultData);
+ case 'crosssection':
+ return new Promise((resolve) => {
+ let rowNum = 0;
+ const promises: Promise[] = [];
+ for (const candidaterow of resultData) {
+ promises.push(new Promise((resolve) => {
+ // Collect list of visits for this candidate
+ setTimeout( () => {
+ const candidatevisits: {[visit: string]: boolean} = {};
+ for (const i in candidaterow) {
+ if (!candidaterow.hasOwnProperty(i)) {
+ continue;
+ }
+ const dictionary = getDictionary(fields[i], fulldict);
+ if (dictionary && dictionary.scope == 'session') {
+ if (candidaterow[i] === null
|| candidaterow[i] == '') {
- continue;
- }
- const cellobj: any = JSON.parse(candidaterow[i]);
- for (const session in cellobj) {
- if (!cellobj.hasOwnProperty(session)
+ continue;
+ }
+ const cellobj: any = JSON.parse(candidaterow[i]);
+ for (const session in cellobj) {
+ if (!cellobj.hasOwnProperty(session)
|| session === 'keytype') {
- continue;
- }
- const vl: string = cellobj[session].VisitLabel;
- candidatevisits[vl] = true;
- }
- }
- }
+ continue;
+ }
+ const vl: string = cellobj[session].VisitLabel;
+ candidatevisits[vl] = true;
+ }
+ }
+ }
- const dataRows: TableRow[] = [];
- for (const visit in candidatevisits) {
- if (!candidatevisits.hasOwnProperty(visit)) {
- continue;
- }
- const dataRow: TableRow = [];
- dataRow.push(visit);
- for (let i = 0; i < candidaterow.length; i++) {
- const dictionary = getDictionary(fields[i], fulldict);
- if (dictionary && dictionary.scope == 'session') {
- if (candidaterow[i] === null
+ const dataRows: TableRow[] = [];
+ for (const visit in candidatevisits) {
+ if (!candidatevisits.hasOwnProperty(visit)) {
+ continue;
+ }
+ const dataRow: TableRow = [];
+ dataRow.push(visit);
+ for (let i = 0; i < candidaterow.length; i++) {
+ const dictionary = getDictionary(fields[i], fulldict);
+ if (dictionary && dictionary.scope == 'session') {
+ if (candidaterow[i] === null
|| candidaterow[i] == '') {
- dataRow.push(null);
- continue;
- }
- const allCells: SessionRowCell[] = Object.values(
- JSON.parse(candidaterow[i]));
- const values: SessionRowCell[] = allCells.filter(
- (sessionval: SessionRowCell) => {
- return sessionval.VisitLabel == visit;
- }
- );
- switch (values.length) {
- case 0:
- dataRow.push(null);
- break;
- case 1:
- switch (dictionary.cardinality) {
- case 'many':
- if (typeof values[0].values === 'undefined') {
- dataRow.push(null);
- } else {
- const thevalues = values[0].values;
- // I don't think this if statement should be required because of the
- // above if statement, but without it typescript gives an error
- // about Object.keys on possible type undefined.
- if (!thevalues) {
- dataRow.push(null);
- } else {
- const mappedVals = Object.keys(thevalues)
- .map(
- (key) => key + '=' + thevalues[key]
- )
- .join(';');
- dataRow.push(mappedVals);
- }
- }
- break;
- default:
- if (typeof values[0].value === 'undefined') {
- dataRow.push(null);
- } else {
- dataRow.push(values[0].value);
- }
- break;
- }
- break;
- default:
- throw new Error('Too many visit values');
- }
- } else {
- dataRow.push(candidaterow[i]);
- }
+ dataRow.push(null);
+ continue;
+ }
+ const allCells: SessionRowCell[] = Object.values(
+ JSON.parse(candidaterow[i]));
+ const values: SessionRowCell[] = allCells.filter(
+ (sessionval: SessionRowCell) => {
+ return sessionval.VisitLabel == visit;
+ }
+ );
+ switch (values.length) {
+ case 0:
+ dataRow.push(null);
+ break;
+ case 1:
+ switch (dictionary.cardinality) {
+ case 'many':
+ if (typeof values[0].values === 'undefined') {
+ dataRow.push(null);
+ } else {
+ const thevalues = values[0].values;
+ // I don't think this if statement should be required because of the
+ // above if statement, but without it typescript gives an error
+ // about Object.keys on possible type undefined.
+ if (!thevalues) {
+ dataRow.push(null);
+ } else {
+ const mappedVals = Object.keys(thevalues)
+ .map(
+ (key) => key + '=' + thevalues[key]
+ )
+ .join(';');
+ dataRow.push(mappedVals);
}
- dataRows.push(dataRow);
+ }
+ break;
+ default:
+ if (typeof values[0].value === 'undefined') {
+ dataRow.push(null);
+ } else {
+ dataRow.push(values[0].value);
+ }
+ break;
}
- onProgress(rowNum++);
- resolve(dataRows);
- });
- }));
+ break;
+ default:
+ throw new Error('Too many visit values');
+ }
+ } else {
+ dataRow.push(candidaterow[i]);
+ }
+ }
+ dataRows.push(dataRow);
}
+ onProgress(rowNum++);
+ resolve(dataRows);
+ });
+ }));
+ }
- Promise.all(promises).then((values: TableRow[][]) => {
- const mappedData: TableRow[] = [];
- for (const row of values) {
- mappedData.push(...row);
- }
- resolve(mappedData);
- });
- });
- default: throw new Error('Unhandled visit organization');
- }
+ Promise.all(promises).then((values: TableRow[][]) => {
+ const mappedData: TableRow[] = [];
+ for (const row of values) {
+ mappedData.push(...row);
+ }
+ resolve(mappedData);
+ });
+ });
+ default: throw new Error('Unhandled visit organization');
+ }
}
/**
@@ -626,48 +626,48 @@ function organizeData(
* @returns {function} - the appropriate column formatter for this data organization
*/
function organizedMapper(
- visitOrganization: VisitOrgType,
- fields: APIQueryField[],
- dict: FullDictionary
+ visitOrganization: VisitOrgType,
+ fields: APIQueryField[],
+ dict: FullDictionary
) {
- switch (visitOrganization) {
- case 'raw':
- return (fieldlabel: string, value: string|null): string => {
- if (value === null) {
- return '';
- }
- return value;
- };
- case 'crosssection':
- return (fieldlabel: string, value: string|null): string => {
- if (value === null) {
- return '';
- }
+ switch (visitOrganization) {
+ case 'raw':
+ return (fieldlabel: string, value: string|null): string => {
+ if (value === null) {
+ return '';
+ }
+ return value;
+ };
+ case 'crosssection':
+ return (fieldlabel: string, value: string|null): string => {
+ if (value === null) {
+ return '';
+ }
- return cellValue(value);
- };
- case 'longitudinal':
- return (label: string,
- value: string|null,
- row: TableRow,
- headers: string[],
- fieldNo: number): (string|null)[]|string|null => {
- if (value === null) {
- return '';
- }
- const cells = expandLongitudinalCells(value, fieldNo, fields, dict);
- if (cells === null) {
- return null;
- }
- return cells.map( (cell: LongitudinalExpansion): string => {
- if (cell.value === null) {
- return '';
- }
- return cellValue(cell.value);
- });
- };
- default: return (): string => 'error';
- }
+ return cellValue(value);
+ };
+ case 'longitudinal':
+ return (label: string,
+ value: string|null,
+ row: TableRow,
+ headers: string[],
+ fieldNo: number): (string|null)[]|string|null => {
+ if (value === null) {
+ return '';
+ }
+ const cells = expandLongitudinalCells(value, fieldNo, fields, dict);
+ if (cells === null) {
+ return null;
+ }
+ return cells.map( (cell: LongitudinalExpansion): string => {
+ if (cell.value === null) {
+ return '';
+ }
+ return cellValue(cell.value);
+ });
+ };
+ default: return (): string => 'error';
+ }
}
type LongitudinalExpansion = {
@@ -689,90 +689,90 @@ type LongitudinalExpansion = {
* there are no table cells to be added based on this data.
*/
function expandLongitudinalCells(
- value: string|null,
- fieldNo: number,
- fields: APIQueryField[],
- dict: FullDictionary
+ value: string|null,
+ fieldNo: number,
+ fields: APIQueryField[],
+ dict: FullDictionary
): LongitudinalExpansion[]|null {
- // We added num fields * num visits headers, but
- // resultData only has numFields rows. For each row
- // we add multiple table cells for the number of visits
- // for that fieldNo. ie. we treat cellPos as fieldNo.
- // This means we need to bail once we've passed the
- // number of fields we have in resultData.
- if (fieldNo >= fields.length) {
- return null;
+ // We added num fields * num visits headers, but
+ // resultData only has numFields rows. For each row
+ // we add multiple table cells for the number of visits
+ // for that fieldNo. ie. we treat cellPos as fieldNo.
+ // This means we need to bail once we've passed the
+ // number of fields we have in resultData.
+ if (fieldNo >= fields.length) {
+ return null;
+ }
+ // if candidate -- return directly
+ // if session -- get visits from query def, put in
+ const fieldobj = fields[fieldNo];
+ const fielddict = getDictionary(fieldobj, dict);
+ if (fielddict === null) {
+ return null;
+ }
+ switch (fielddict.scope) {
+ case 'candidate':
+ if (fielddict.cardinality == 'many') {
+ throw new Error('Candidate cardinality many not implemented');
}
- // if candidate -- return directly
- // if session -- get visits from query def, put in
- const fieldobj = fields[fieldNo];
- const fielddict = getDictionary(fieldobj, dict);
- if (fielddict === null) {
- return null;
+ return [{value: value, dictionary: fielddict}];
+ case 'session':
+ let displayedVisits: string[];
+ if (fieldobj.visits) {
+ displayedVisits = fieldobj.visits;
+ } else {
+ // All visits
+ if (fielddict.visits) {
+ displayedVisits = fielddict.visits;
+ } else {
+ displayedVisits = [];
+ }
}
- switch (fielddict.scope) {
- case 'candidate':
- if (fielddict.cardinality == 'many') {
- throw new Error('Candidate cardinality many not implemented');
- }
- return [{value: value, dictionary: fielddict}];
- case 'session':
- let displayedVisits: string[];
- if (fieldobj.visits) {
- displayedVisits = fieldobj.visits;
- } else {
- // All visits
- if (fielddict.visits) {
- displayedVisits = fielddict.visits;
- } else {
- displayedVisits = [];
- }
- }
- if (!displayedVisits) {
- displayedVisits = [];
- }
- let celldata: {[sessionid: string]: SessionRowCell};
- try {
- celldata = JSON.parse(value || '{}');
- } catch (e) {
- // This can sometimes happen when we go between Cross-Sectional
- // and Longitudinal and the data is in an inconsistent state
- // between renders, so instead of throwing an error (which crashes
- // the whole app), we just log to the console and return null.
- console.error('Internal error parsing: "' + value + '"');
- return null;
- }
- const values = displayedVisits.map((visit): LongitudinalExpansion => {
- if (!value) {
- return {value: null, dictionary: fielddict};
+ if (!displayedVisits) {
+ displayedVisits = [];
+ }
+ let celldata: {[sessionid: string]: SessionRowCell};
+ try {
+ celldata = JSON.parse(value || '{}');
+ } catch (e) {
+ // This can sometimes happen when we go between Cross-Sectional
+ // and Longitudinal and the data is in an inconsistent state
+ // between renders, so instead of throwing an error (which crashes
+ // the whole app), we just log to the console and return null.
+ console.error('Internal error parsing: "' + value + '"');
+ return null;
+ }
+ const values = displayedVisits.map((visit): LongitudinalExpansion => {
+ if (!value) {
+ return {value: null, dictionary: fielddict};
+ }
+ for (const session in celldata) {
+ if (celldata[session].VisitLabel == visit) {
+ const thissession: SessionRowCell = celldata[session];
+ switch (fielddict.cardinality) {
+ case 'many':
+ if (thissession.values === undefined) {
+ return {value: null, dictionary: fielddict};
}
- for (const session in celldata) {
- if (celldata[session].VisitLabel == visit) {
- const thissession: SessionRowCell = celldata[session];
- switch (fielddict.cardinality) {
- case 'many':
- if (thissession.values === undefined) {
- return {value: null, dictionary: fielddict};
- }
- const thevalues = thissession.values;
- return {value: Object.keys(thevalues)
- .map( (key) => key + '=' + thevalues[key])
- .join(';'), dictionary: fielddict};
- default:
- if (thissession.value !== undefined) {
- return {
- value: thissession.value,
- dictionary: fielddict,
- };
- }
- throw new Error('Value was undefined');
- }
- }
+ const thevalues = thissession.values;
+ return {value: Object.keys(thevalues)
+ .map( (key) => key + '=' + thevalues[key])
+ .join(';'), dictionary: fielddict};
+ default:
+ if (thissession.value !== undefined) {
+ return {
+ value: thissession.value,
+ dictionary: fielddict,
+ };
}
- return {value: null, dictionary: fielddict};
- });
- return values;
- }
+ throw new Error('Value was undefined');
+ }
+ }
+ }
+ return {value: null, dictionary: fielddict};
+ });
+ return values;
+ }
}
/**
@@ -791,339 +791,339 @@ function expandLongitudinalCells(
this data organization
*/
function organizedFormatter(
- resultData: string[][],
- visitOrganization: VisitOrgType,
- fields: APIQueryField[],
- dict: FullDictionary,
- displayEmptyVisits: boolean,
- enumDisplay: EnumDisplayTypes,
+ resultData: string[][],
+ visitOrganization: VisitOrgType,
+ fields: APIQueryField[],
+ dict: FullDictionary,
+ displayEmptyVisits: boolean,
+ enumDisplay: EnumDisplayTypes,
) {
- let callback;
- switch (visitOrganization) {
- case 'raw':
- /**
- * Callback to return the raw JSON data as returned by the API, in
- * table form for the DataTable
- *
- * @param {string} label - The table header
- * @param {string} cell - The cell value
- * @returns {React.ReactElement} - The table cell
- */
- callback = (label: string, cell: string): ReactNode => {
- return {cell} | ;
- };
- return callback;
- case 'inline':
- /**
- * Callback to format the data as inline data, with a list for each
- * session inside of a cell for the candidate.
- *
- * @param {string} label - The table header
- * @param {string} cell - The cell value
- * @param {string[]} row - The entire row
- * @param {string[]} headers - The entire row's headers
- * @param {number} fieldNo - the cell index
- * @returns {React.ReactElement} - The table cell
- */
- callback = (
- label: string,
- cell: string,
- row: TableRow,
- headers: string[],
- fieldNo: number
- ): ReactNode => {
- // if candidate -- return directly
- // if session -- get visits from query def, put in
- const fieldobj = fields[fieldNo];
- const fielddict = getDictionary(fieldobj, dict);
- if (fielddict === null) {
+ let callback;
+ switch (visitOrganization) {
+ case 'raw':
+ /**
+ * Callback to return the raw JSON data as returned by the API, in
+ * table form for the DataTable
+ *
+ * @param {string} label - The table header
+ * @param {string} cell - The cell value
+ * @returns {React.ReactElement} - The table cell
+ */
+ callback = (label: string, cell: string): ReactNode => {
+ return {cell} | ;
+ };
+ return callback;
+ case 'inline':
+ /**
+ * Callback to format the data as inline data, with a list for each
+ * session inside of a cell for the candidate.
+ *
+ * @param {string} label - The table header
+ * @param {string} cell - The cell value
+ * @param {string[]} row - The entire row
+ * @param {string[]} headers - The entire row's headers
+ * @param {number} fieldNo - the cell index
+ * @returns {React.ReactElement} - The table cell
+ */
+ callback = (
+ label: string,
+ cell: string,
+ row: TableRow,
+ headers: string[],
+ fieldNo: number
+ ): ReactNode => {
+ // if candidate -- return directly
+ // if session -- get visits from query def, put in
+ const fieldobj = fields[fieldNo];
+ const fielddict = getDictionary(fieldobj, dict);
+ if (fielddict === null) {
+ return null;
+ }
+ if (fielddict.scope == 'candidate') {
+ if (cell === '') {
+ return (No data) | ;
+ }
+ switch (fielddict.cardinality) {
+ case 'many':
+ return (Not implemented) | ;
+ case 'single':
+ case 'unique':
+ case 'optional':
+ return ;
+ default:
+ return (
+ (Internal Error. Unhandled cardinality:
+ {fielddict.cardinality})
+
+ | );
+ }
+ }
+ let val: React.ReactNode;
+ if (fielddict.scope == 'session') {
+ let displayedVisits: string[];
+ if (fields[fieldNo] && fields[fieldNo].visits) {
+ // need to explicitly tell typescript it's defined otherwise
+ // it thinks visits is string[]|undefined
+ displayedVisits = fields[fieldNo].visits as string[];
+ } else {
+ // All visits
+ if (fielddict.visits) {
+ displayedVisits = fielddict.visits;
+ } else {
+ displayedVisits = [];
+ }
+ }
+ switch (fielddict.cardinality) {
+ case 'many':
+ val = displayedVisits.map((visit): React.ReactNode => {
+ let hasdata = false;
+ /**
+ * Map the JSON string from the cell returned by the
+ * API to a string to display to the user in the
+ * frontend for this visit.
+ *
+ * @param {string} visit - The visit being displayed
+ * @param {string} cell - The raw cell value
+ * @returns {string|null} - the display string
+ */
+ const visitval = (visit: string, cell: string) => {
+ if (cell === '') {
return null;
- }
- if (fielddict.scope == 'candidate') {
- if (cell === '') {
- return (No data) | ;
- }
- switch (fielddict.cardinality) {
- case 'many':
- return (Not implemented) | ;
- case 'single':
- case 'unique':
- case 'optional':
- return ;
- default:
- return (
- (Internal Error. Unhandled cardinality:
- {fielddict.cardinality})
-
- | );
- }
- }
- let val: React.ReactNode;
- if (fielddict.scope == 'session') {
- let displayedVisits: string[];
- if (fields[fieldNo] && fields[fieldNo].visits) {
- // need to explicitly tell typescript it's defined otherwise
- // it thinks visits is string[]|undefined
- displayedVisits = fields[fieldNo].visits as string[];
- } else {
- // All visits
- if (fielddict.visits) {
- displayedVisits = fielddict.visits;
- } else {
- displayedVisits = [];
- }
- }
- switch (fielddict.cardinality) {
- case 'many':
- val = displayedVisits.map((visit): React.ReactNode => {
- let hasdata = false;
- /**
- * Map the JSON string from the cell returned by the
- * API to a string to display to the user in the
- * frontend for this visit.
- *
- * @param {string} visit - The visit being displayed
- * @param {string} cell - The raw cell value
- * @returns {string|null} - the display string
- */
- const visitval = (visit: string, cell: string) => {
- if (cell === '') {
- return null;
- }
+ }
- try {
- const json = JSON.parse(cell);
- for (const sessionid in json) {
- if (json[sessionid].VisitLabel == visit) {
- const values = json[sessionid].values;
- return ({
- Object.keys(values).map(
- (keyid: string):
+ try {
+ const json = JSON.parse(cell);
+ for (const sessionid in json) {
+ if (json[sessionid].VisitLabel == visit) {
+ const values = json[sessionid].values;
+ return ({
+ Object.keys(values).map(
+ (keyid: string):
React.ReactNode => {
- const val = values[keyid];
- if (val === null) {
- return;
- }
- hasdata = true;
- // Workarounds for line length
- const f = fielddict;
- const e = enumDisplay;
- const dval = (
-
- );
+ const val = values[keyid];
+ if (val === null) {
+ return;
+ }
+ hasdata = true;
+ // Workarounds for line length
+ const f = fielddict;
+ const e = enumDisplay;
+ const dval = (
+
+ );
- return (
-
-
- {keyid}
- - {dval}
-
- );
- })
- }
-
);
- }
- }
- return null;
- } catch (e) {
- console.error(e);
- return (Internal error);
- }
- };
- let theval = visitval(visit, cell);
- if (!displayEmptyVisits && !hasdata) {
- return ;
- }
- if (theval === null) {
- theval = (No data);
- }
- return (
-
{visit}
-
-
- {theval}
-
-
);
- });
- break;
- default:
- val = displayedVisits.map((visit) => {
- let hasdata = false;
- /**
- * Maps the JSON value from the session to a list of
- * values to display to the user
- *
- * @param {string} visit - The visit label
- * @param {string} cell - The JSON returned by the API
- * for this cell
- * @returns {React.ReactElement} - The HTML list react element
- */
- const visitval = (visit: string, cell: string) => {
- if (cell === '') {
- return null;
- }
- try {
- const json = JSON.parse(cell);
- for (const sessionid in json) {
- if (json[sessionid].VisitLabel == visit) {
- hasdata = true;
- return ;
- }
- }
- } catch (e) {
- return (Internal error);
- }
- return null;
- };
- let theval = visitval(visit, cell);
- if (!displayEmptyVisits && !hasdata) {
- return ;
- }
- if (theval === null) {
- theval = (No data);
+ return (
+
+
- {keyid}
+ - {dval}
+
+ );
+ })
}
- return (
-
{visit}
-
-
- {theval}
-
-
);
- });
+
);
+ }
}
+ return null;
+ } catch (e) {
+ console.error(e);
+ return (Internal error);
+ }
+ };
+ let theval = visitval(visit, cell);
+ if (!displayEmptyVisits && !hasdata) {
+ return ;
+ }
+ if (theval === null) {
+ theval = (No data);
}
- const value = (
+
- {val}
+ }
+ >{visit}
+
+
+ {theval}
+
);
- return {value} | ;
- };
- return callback;
- case 'longitudinal':
- /**
- * Callback to organize this data longitudinally
- *
- * @param {string} label - The header label
- * @param {string} cell - the JSON value of the cell
- * @param {string[]} row - the entire row
- * @param {string[]} headers - the headers for the table
- * @param {number} fieldNo - The field number of this cell
- * @returns {React.ReactElement} - The table cell
- */
- callback = (
- label: string,
- cell: string,
- row: TableRow,
- headers: string[],
- fieldNo: number
- ): ReactNode => {
- const cells = expandLongitudinalCells(cell, fieldNo, fields, dict);
- if (cells === null) {
+ });
+ break;
+ default:
+ val = displayedVisits.map((visit) => {
+ let hasdata = false;
+ /**
+ * Maps the JSON value from the session to a list of
+ * values to display to the user
+ *
+ * @param {string} visit - The visit label
+ * @param {string} cell - The JSON returned by the API
+ * for this cell
+ * @returns {React.ReactElement} - The HTML list react element
+ */
+ const visitval = (visit: string, cell: string) => {
+ if (cell === '') {
return null;
- }
- return <>{cells.map((cell: LongitudinalExpansion) => {
- if (cell.value === null) {
- return (No data) | ;
+ }
+ try {
+ const json = JSON.parse(cell);
+ for (const sessionid in json) {
+ if (json[sessionid].VisitLabel == visit) {
+ hasdata = true;
+ return ;
+ }
}
-
- return (
-
- | );
- })}>;
- };
- return callback;
- case 'crosssection':
- /**
- * Callback that organizes data cross-sectionally
- *
- * @param {string} label - The header label
- * @param {string} cell - the JSON value of the cell
- * @param {string[]} row - the entire row
- * @param {string[]} headers - the headers for the table
- * @param {number} fieldNo - The field number of this cell
- * @returns {React.ReactElement} - The table cell for this cell.
- */
- callback = (
- label: string,
- cell: string,
- row: TableRow,
- headers: string[],
- fieldNo: number
- ): ReactNode => {
- if (cell === null) {
- return No data for visit | ;
+ } catch (e) {
+ return (Internal error);
+ }
+ return null;
+ };
+ let theval = visitval(visit, cell);
+ if (!displayEmptyVisits && !hasdata) {
+ return ;
}
- if (fieldNo == 0) {
- // automatically added Visit column
- return ;
+ if (theval === null) {
+ theval = (No data);
}
+ return (
+
{visit}
+
+
+ {theval}
+
+
);
+ });
+ }
+ }
+ const value = (
+ {val}
+
);
+ return {value} | ;
+ };
+ return callback;
+ case 'longitudinal':
+ /**
+ * Callback to organize this data longitudinally
+ *
+ * @param {string} label - The header label
+ * @param {string} cell - the JSON value of the cell
+ * @param {string[]} row - the entire row
+ * @param {string[]} headers - the headers for the table
+ * @param {number} fieldNo - The field number of this cell
+ * @returns {React.ReactElement} - The table cell
+ */
+ callback = (
+ label: string,
+ cell: string,
+ row: TableRow,
+ headers: string[],
+ fieldNo: number
+ ): ReactNode => {
+ const cells = expandLongitudinalCells(cell, fieldNo, fields, dict);
+ if (cells === null) {
+ return null;
+ }
+ return <>{cells.map((cell: LongitudinalExpansion) => {
+ if (cell.value === null) {
+ return (No data) | ;
+ }
+
+ return (
+
+ | );
+ })}>;
+ };
+ return callback;
+ case 'crosssection':
+ /**
+ * Callback that organizes data cross-sectionally
+ *
+ * @param {string} label - The header label
+ * @param {string} cell - the JSON value of the cell
+ * @param {string[]} row - the entire row
+ * @param {string[]} headers - the headers for the table
+ * @param {number} fieldNo - The field number of this cell
+ * @returns {React.ReactElement} - The table cell for this cell.
+ */
+ callback = (
+ label: string,
+ cell: string,
+ row: TableRow,
+ headers: string[],
+ fieldNo: number
+ ): ReactNode => {
+ if (cell === null) {
+ return No data for visit | ;
+ }
+ if (fieldNo == 0) {
+ // automatically added Visit column
+ return ;
+ }
- const fieldobj = fields[fieldNo-1];
- const fielddict = getDictionary(fieldobj, dict);
+ const fieldobj = fields[fieldNo-1];
+ const fielddict = getDictionary(fieldobj, dict);
- return fielddict === null
- ?
- : (
-
- | );
- };
- return callback;
- }
+ return fielddict === null
+ ?
+ : (
+
+ | );
+ };
+ return callback;
+ }
}
/**
@@ -1134,17 +1134,17 @@ function organizedFormatter(
* @returns {FieldDictionary?} - The field dictionary for this field
*/
function getDictionary(
- fieldobj: APIQueryField,
- dict: FullDictionary,
+ fieldobj: APIQueryField,
+ dict: FullDictionary,
): FieldDictionary|null {
- if (!dict || !fieldobj
+ if (!dict || !fieldobj
|| !dict[fieldobj.module]
|| !dict[fieldobj.module][fieldobj.category]
|| !dict[fieldobj.module][fieldobj.category][fieldobj.field]
- ) {
- return null;
- }
- return dict[fieldobj.module][fieldobj.category][fieldobj.field];
+ ) {
+ return null;
+ }
+ return dict[fieldobj.module][fieldobj.category][fieldobj.field];
}
type VisitOrgType = 'raw' | 'inline' | 'longitudinal' | 'crosssection';
@@ -1162,83 +1162,83 @@ type HeaderDisplayType = 'fieldname' | 'fielddesc' | 'fieldnamedesc';
* in the frontend table
*/
function organizeHeaders(
- fields: APIQueryField[],
- org: VisitOrgType,
- display: HeaderDisplayType,
- fulldict: FullDictionary,
- onProgress: (i: number) => void): Promise {
- /**
- * Format a header according to the selected display type
- *
- * @param {APIQueryField} header - The header to format
- * @returns {string} - The string to display to the user
- */
- const formatHeader = (header: APIQueryField): string => {
- switch (display) {
- case 'fieldname': return header.field;
- case 'fielddesc': return getDictionaryDescription(
- header.module,
- header.category,
- header.field,
- fulldict
- );
- case 'fieldnamedesc': return header.field +
+ fields: APIQueryField[],
+ org: VisitOrgType,
+ display: HeaderDisplayType,
+ fulldict: FullDictionary,
+ onProgress: (i: number) => void): Promise {
+ /**
+ * Format a header according to the selected display type
+ *
+ * @param {APIQueryField} header - The header to format
+ * @returns {string} - The string to display to the user
+ */
+ const formatHeader = (header: APIQueryField): string => {
+ switch (display) {
+ case 'fieldname': return header.field;
+ case 'fielddesc': return getDictionaryDescription(
+ header.module,
+ header.category,
+ header.field,
+ fulldict
+ );
+ case 'fieldnamedesc': return header.field +
': ' + getDictionaryDescription(
- header.module,
- header.category,
- header.field,
- fulldict
- );
- default:
- throw new Error('Unhandled field display type');
- }
- };
- switch (org) {
- case 'raw':
- return Promise.resolve(fields.map((val, i) => {
- onProgress(i);
- return formatHeader(val);
- }));
- case 'inline':
- return Promise.resolve(fields.map((val, i) => {
- onProgress(i);
- return formatHeader(val);
- }));
- case 'longitudinal':
- const headers: string[] = [];
- let i = 0;
- for (const field of fields) {
- i++;
- const dict = getDictionary(field, fulldict);
+ header.module,
+ header.category,
+ header.field,
+ fulldict
+ );
+ default:
+ throw new Error('Unhandled field display type');
+ }
+ };
+ switch (org) {
+ case 'raw':
+ return Promise.resolve(fields.map((val, i) => {
+ onProgress(i);
+ return formatHeader(val);
+ }));
+ case 'inline':
+ return Promise.resolve(fields.map((val, i) => {
+ onProgress(i);
+ return formatHeader(val);
+ }));
+ case 'longitudinal':
+ const headers: string[] = [];
+ let i = 0;
+ for (const field of fields) {
+ i++;
+ const dict = getDictionary(field, fulldict);
- if (dict === null) {
- headers.push('Internal Error');
- } else if (dict.scope == 'candidate') {
- headers.push(formatHeader(field));
- } else {
- if (typeof field.visits !== 'undefined') {
- for (const visit of field.visits) {
- headers.push(formatHeader(field) + ': ' + visit);
- }
- }
- }
- onProgress(i);
+ if (dict === null) {
+ headers.push('Internal Error');
+ } else if (dict.scope == 'candidate') {
+ headers.push(formatHeader(field));
+ } else {
+ if (typeof field.visits !== 'undefined') {
+ for (const visit of field.visits) {
+ headers.push(formatHeader(field) + ': ' + visit);
+ }
}
- // Split session level selections into multiple headers
- return Promise.resolve(headers);
- case 'crosssection':
- return new Promise( (resolve) => {
- setTimeout( () => {
- resolve(['Visit Label',
- ...fields.map((val, i) => {
- onProgress(i);
- return formatHeader(val);
- }),
- ]);
- });
- });
- default: throw new Error('Unhandled visit organization');
+ }
+ onProgress(i);
}
+ // Split session level selections into multiple headers
+ return Promise.resolve(headers);
+ case 'crosssection':
+ return new Promise( (resolve) => {
+ setTimeout( () => {
+ resolve(['Visit Label',
+ ...fields.map((val, i) => {
+ onProgress(i);
+ return formatHeader(val);
+ }),
+ ]);
+ });
+ });
+ default: throw new Error('Unhandled visit organization');
+ }
}
export default ViewData;
diff --git a/modules/dataquery/jsx/welcome.adminquerymodal.tsx b/modules/dataquery/jsx/welcome.adminquerymodal.tsx
index 959750ccf6c..52350901a29 100644
--- a/modules/dataquery/jsx/welcome.adminquerymodal.tsx
+++ b/modules/dataquery/jsx/welcome.adminquerymodal.tsx
@@ -20,76 +20,76 @@ function AdminQueryModal(props: {
onSubmit: (name: string, topQuery: boolean, dashboardQuery: boolean)
=> void,
}) {
- const [queryName, setQueryName] = useState(props.defaultName || '');
- const [topQuery, setTopQuery] = useState(true);
- const [dashboardQuery, setDashboardQuery] = useState(true);
- /**
- * Convert the onSubmit callback to a promise function of the format
- * expected by jsx/Modal.
- *
- * @returns {Promise} - The promise
- */
- const submitPromise = () => {
- let sbmt: Promise = new Promise((resolve, reject) => {
- if (queryName.trim() == '') {
- swal.fire({
- type: 'error',
- text: 'Must provide a query name to pin query as.',
- });
- reject();
- return;
- }
- if (!topQuery && !dashboardQuery) {
- swal.fire({
- type: 'error',
- text: 'Must pin as study query or pin to dashboard.',
- });
- reject();
- return;
- }
- resolve([queryName.trim(), topQuery, dashboardQuery]);
+ const [queryName, setQueryName] = useState(props.defaultName || '');
+ const [topQuery, setTopQuery] = useState(true);
+ const [dashboardQuery, setDashboardQuery] = useState(true);
+ /**
+ * Convert the onSubmit callback to a promise function of the format
+ * expected by jsx/Modal.
+ *
+ * @returns {Promise} - The promise
+ */
+ const submitPromise = () => {
+ let sbmt: Promise = new Promise((resolve, reject) => {
+ if (queryName.trim() == '') {
+ swal.fire({
+ type: 'error',
+ text: 'Must provide a query name to pin query as.',
});
- if (props.onSubmit) {
- sbmt = sbmt.then((val: [string, boolean, boolean]) => {
- const [name, topq, dashq] = val;
- props.onSubmit(name, topq, dashq);
- });
- }
- return sbmt;
- };
- return
-
-
- setQueryName(value)
- }
- />
- setTopQuery(value)
- }
- label='Pin Study Query'
- />
-
- setDashboardQuery(value)
- }
- />
-
-
- ;
+ reject();
+ return;
+ }
+ if (!topQuery && !dashboardQuery) {
+ swal.fire({
+ type: 'error',
+ text: 'Must pin as study query or pin to dashboard.',
+ });
+ reject();
+ return;
+ }
+ resolve([queryName.trim(), topQuery, dashboardQuery]);
+ });
+ if (props.onSubmit) {
+ sbmt = sbmt.then((val: [string, boolean, boolean]) => {
+ const [name, topq, dashq] = val;
+ props.onSubmit(name, topq, dashq);
+ });
+ }
+ return sbmt;
+ };
+ return
+
+
+ setQueryName(value)
+ }
+ />
+ setTopQuery(value)
+ }
+ label='Pin Study Query'
+ />
+
+ setDashboardQuery(value)
+ }
+ />
+
+
+ ;
}
export default AdminQueryModal;
diff --git a/modules/dataquery/jsx/welcome.namequerymodal.tsx b/modules/dataquery/jsx/welcome.namequerymodal.tsx
index 78c28a4c618..138ee981459 100644
--- a/modules/dataquery/jsx/welcome.namequerymodal.tsx
+++ b/modules/dataquery/jsx/welcome.namequerymodal.tsx
@@ -19,47 +19,47 @@ function NameQueryModal(props: {
closeModal: () => void,
onSubmit: (name: string) => void,
}) {
- const [queryName, setQueryName] = useState(props.defaultName || '');
- /**
- * Convert the onSubmit callback function to a promise expected by Modal
- *
- * @returns {Promise} - The submit promise
- */
- const submitPromise = (): Promise => {
- const sbmt = new Promise((resolve, reject) => {
- if (queryName == '') {
- swal.fire({
- type: 'error',
- text: 'Must provide a query name.',
- });
- reject();
- return;
- }
- resolve(queryName);
+ const [queryName, setQueryName] = useState(props.defaultName || '');
+ /**
+ * Convert the onSubmit callback function to a promise expected by Modal
+ *
+ * @returns {Promise} - The submit promise
+ */
+ const submitPromise = (): Promise => {
+ const sbmt = new Promise((resolve, reject) => {
+ if (queryName == '') {
+ swal.fire({
+ type: 'error',
+ text: 'Must provide a query name.',
});
- if (props.onSubmit) {
- return sbmt.then(props.onSubmit);
- }
- return sbmt;
- };
- return
-
-
- setQueryName(value)
- }
- />
-
-
- ;
+ reject();
+ return;
+ }
+ resolve(queryName);
+ });
+ if (props.onSubmit) {
+ return sbmt.then(props.onSubmit);
+ }
+ return sbmt;
+ };
+ return
+
+
+ setQueryName(value)
+ }
+ />
+
+
+ ;
}
export default NameQueryModal;
diff --git a/modules/dataquery/jsx/welcome.tsx b/modules/dataquery/jsx/welcome.tsx
index bcd70584916..40140048675 100644
--- a/modules/dataquery/jsx/welcome.tsx
+++ b/modules/dataquery/jsx/welcome.tsx
@@ -52,119 +52,119 @@ function Welcome(props: {
mapCategoryName: (module: string, category: string) => string,
fulldictionary:FullDictionary,
}) {
- const panels: {
+ const panels: {
title: string,
content: React.ReactElement,
alwaysOpen: boolean,
defaultOpen: boolean,
id: string,
}[] = [];
- if (props.topQueries.length > 0) {
- panels.push({
- title: 'Study Queries',
- content: (
-
-
-
- ),
- alwaysOpen: false,
- defaultOpen: true,
- id: 'p1',
- });
- }
+ if (props.topQueries.length > 0) {
panels.push({
- title: 'Instructions',
- content: 0}
- onContinue={props.onContinue}
- />,
- alwaysOpen: false,
- defaultOpen: true,
- id: 'p2',
+ title: 'Study Queries',
+ content: (
+
+
+
+ ),
+ alwaysOpen: false,
+ defaultOpen: true,
+ id: 'p1',
});
+ }
+ panels.push({
+ title: 'Instructions',
+ content: 0}
+ onContinue={props.onContinue}
+ />,
+ alwaysOpen: false,
+ defaultOpen: true,
+ id: 'p2',
+ });
+ panels.push({
+ title: 'Recent Queries',
+ content: (
+
+
+
+ ),
+ alwaysOpen: false,
+ defaultOpen: true,
+ id: 'p3',
+ });
+
+ if (props.sharedQueries.length > 0) {
panels.push({
- title: 'Recent Queries',
- content: (
-
-
-
- ),
- alwaysOpen: false,
- defaultOpen: true,
- id: 'p3',
- });
+ title: 'Shared Queries',
+ content: (
+
+
0) {
- panels.push({
- title: 'Shared Queries',
- content: (
-
-
-
- ),
- alwaysOpen: false,
- defaultOpen: true,
- id: 'p4',
- });
- }
+ queries={props.sharedQueries}
+ loadQuery={props.loadQuery}
+ defaultCollapsed={true}
- return (
-
-
+ getModuleFields={props.getModuleFields}
+ mapModuleName={props.mapModuleName}
+ mapCategoryName={props.mapCategoryName}
+ fulldictionary={props.fulldictionary}
+
+ queryAdmin={props.queryAdmin}
+ />
+
+ ),
+ alwaysOpen: false,
+ defaultOpen: true,
+ id: 'p4',
+ });
+ }
+
+ return (
+
+
Welcome to the Data Query Tool
-
-
-
- );
+
+
+
+ );
}
/**
@@ -207,402 +207,402 @@ function QueryList(props: {
mapCategoryName: (module: string, category: string) => string,
fulldictionary:FullDictionary,
}) {
- const [nameModalID, setNameModalID] = useState(null);
- const [adminModalID, setAdminModalID] = useState(null);
- const [queryName, setQueryName] = useState(null);
- const [defaultModalQueryName, setDefaultModalQueryName]
+ const [nameModalID, setNameModalID] = useState(null);
+ const [adminModalID, setAdminModalID] = useState(null);
+ const [queryName, setQueryName] = useState(null);
+ const [defaultModalQueryName, setDefaultModalQueryName]
= useState('');
- const [onlyStarred, setOnlyStarred] = useState(false);
- const [onlyShared, setOnlyShared] = useState(false);
- const [onlyNamed, setOnlyNamed] = useState(false);
- const [noDuplicates, setNoDuplicates] = useState(false);
- const [queryFilter, setQueryFilter] = useState('');
- const [fullQuery, setFullQuery]
+ const [onlyStarred, setOnlyStarred] = useState(false);
+ const [onlyShared, setOnlyShared] = useState(false);
+ const [onlyNamed, setOnlyNamed] = useState(false);
+ const [noDuplicates, setNoDuplicates] = useState(false);
+ const [queryFilter, setQueryFilter] = useState('');
+ const [fullQuery, setFullQuery]
= useState(!props.defaultCollapsed);
- const [unpinAdminQuery, setUnpinAdminQuery] = useState(null);
- const [adminPinAction, setAdminPinAction]
+ const [unpinAdminQuery, setUnpinAdminQuery] = useState(null);
+ const [adminPinAction, setAdminPinAction]
= useState<'top'|'dashboard'|'top,dashboard'>('top');
- useEffect(() => {
- const modules = new Set