diff --git a/modules/electrophysiology_browser/locale/electrophysiology_browser.pot b/modules/electrophysiology_browser/locale/electrophysiology_browser.pot index ebf242f364..7bd0564a8d 100644 --- a/modules/electrophysiology_browser/locale/electrophysiology_browser.pot +++ b/modules/electrophysiology_browser/locale/electrophysiology_browser.pot @@ -615,3 +615,12 @@ msgstr "" msgid "Channel Types" msgstr "" + +# EEG charts +msgid "EEG Session" +msgid_plural "EEG Sessions" +msgstr[0] "" + +msgid "EEG Event" +msgid_plural "EEG Events" +msgstr[0] "" diff --git a/modules/electrophysiology_browser/locale/fr/LC_MESSAGES/electrophysiology_browser.po b/modules/electrophysiology_browser/locale/fr/LC_MESSAGES/electrophysiology_browser.po index 85509ccf4e..faaa985ffb 100644 --- a/modules/electrophysiology_browser/locale/fr/LC_MESSAGES/electrophysiology_browser.po +++ b/modules/electrophysiology_browser/locale/fr/LC_MESSAGES/electrophysiology_browser.po @@ -755,3 +755,14 @@ msgstr "Voir la session" msgid "Channel Types" msgstr "Types de chaînes" + +# Widgets +msgid "EEG Session" +msgid_plural "EEG Sessions" +msgstr[0] "Session EEG" +msgstr[1] "Sessions EEG" + +msgid "EEG Event" +msgid_plural "EEG Events" +msgstr[0] "Événement EEG" +msgstr[1] "Événements EEG" diff --git a/modules/electrophysiology_browser/locale/ja/LC_MESSAGES/electrophysiology_browser.po b/modules/electrophysiology_browser/locale/ja/LC_MESSAGES/electrophysiology_browser.po index f0f8602a36..9815600e28 100644 --- a/modules/electrophysiology_browser/locale/ja/LC_MESSAGES/electrophysiology_browser.po +++ b/modules/electrophysiology_browser/locale/ja/LC_MESSAGES/electrophysiology_browser.po @@ -615,3 +615,4 @@ msgstr "セッションを見る" msgid "Channel Types" msgstr "チャネルタイプ" + diff --git a/modules/electrophysiology_browser/php/module.class.inc b/modules/electrophysiology_browser/php/module.class.inc index ca7657d988..4471beb55c 100644 --- a/modules/electrophysiology_browser/php/module.class.inc +++ b/modules/electrophysiology_browser/php/module.class.inc @@ -64,4 +64,85 @@ class Module extends \Module { return dgettext("electrophysiology_browser", "Electrophysiology Browser"); } + + /** + * {@inheritDoc} + * + * @param string $type The type of widgets to get. + * @param \User $user The user widgets are being retrieved for. + * @param array $options A type dependent list of options to provide + * to the widget. + * + * @return \LORIS\GUI\Widget[] + */ + public function getWidgets(string $type, \User $user, array $options) : array + { + $factory = \NDB_Factory::singleton(); + $baseURL = $factory->settings()->getBaseURL(); + + switch ($type) { + case 'study-progression': + $DB = $factory->database(); + $sessionData = $DB->pselectWithIndexKey( + "SELECT + p.ProjectID, + p.Name AS ProjectName, + COUNT(s.ID) AS count, + CONCAT('" + . $baseURL . + "/electrophysiology_browser/?project=', p.Name + ) as url + FROM session s + JOIN Project p ON p.ProjectID = s.ProjectID + JOIN physiological_file pf ON pf.SessionID = s.ID + WHERE s.Active <> 'N' + AND s.CenterID <> 1 + GROUP BY p.Name", + [], + 'ProjectID' + ); + $eventData = $DB->pselectWithIndexKey( + "SELECT + p.ProjectID, + p.Name AS ProjectName, + COUNT(pte.PhysiologicalTaskEventID) AS count, + CONCAT('" + . $baseURL . + "/electrophysiology_browser/?project=', p.Name + ) as url + FROM session s + JOIN Project p ON p.ProjectID = s.ProjectID + JOIN physiological_file pf ON pf.SessionID = s.ID + JOIN physiological_task_event pte USING (PhysiologicalFileID) + WHERE s.Active <> 'N' + AND s.CenterID <> 1 + GROUP BY p.Name", + [], + 'ProjectID' + ); + return [ + new \LORIS\dashboard\DataWidget( + new \LORIS\GUI\LocalizableString( + "electrophysiology_browser", + "EEG Session", + "EEG Sessions", + ), + $sessionData, + "", + 'rgb(186,255,201)', + ), + new \LORIS\dashboard\DataWidget( + new \LORIS\GUI\LocalizableString( + "electrophysiology_browser", + "EEG Event", + "EEG Events", + ), + $eventData, + "", + 'rgb(186,255,201)', + ) + ]; + } + return []; + } } diff --git a/modules/statistics/css/recruitment.css b/modules/statistics/css/recruitment.css index 54fdb65ba0..b7c535030d 100644 --- a/modules/statistics/css/recruitment.css +++ b/modules/statistics/css/recruitment.css @@ -3,7 +3,15 @@ margin-bottom: 0; } -.study-progression-container { +.progress-bar-male { + background-color: #1C70B6; +} + +.progress-bar-female { + background-color: #2FA4E7; +} + +.study-progression-container, .eeg-data-container { max-height: 415px; overflow-y: auto; } @@ -34,4 +42,4 @@ transform: translateY(-10px); background-color: #f9fdff; box-shadow: 0 12px 12px rgba(0, 0, 0, 0.25); -} \ No newline at end of file +} diff --git a/modules/statistics/jsx/WidgetIndex.js b/modules/statistics/jsx/WidgetIndex.js index 00fad3e8ab..6638bee81a 100644 --- a/modules/statistics/jsx/WidgetIndex.js +++ b/modules/statistics/jsx/WidgetIndex.js @@ -3,6 +3,7 @@ import React, {useEffect, useState} from 'react'; import PropTypes from 'prop-types'; import Recruitment from './widgets/recruitment'; import StudyProgression from './widgets/studyprogression'; +import Electrophysiology from './widgets/electrophysiology'; import {fetchData} from './Fetch'; import Modal from 'Modal'; import Loader from 'Loader'; @@ -25,6 +26,7 @@ import zhStrings from '../locale/zh/LC_MESSAGES/statistics.json'; const WidgetIndex = (props) => { const [recruitmentData, setRecruitmentData] = useState({}); const [studyProgressionData, setStudyProgressionData] = useState({}); + const [electrophysiologyData, setElectrophysiologyData] = useState({}); const [modalChart, setModalChart] = useState(null); const {t, i18n} = useTranslation(); useEffect( () => { @@ -273,6 +275,7 @@ const WidgetIndex = (props) => { ); setRecruitmentData(data); setStudyProgressionData(data); + setElectrophysiologyData(data); }; setup().catch( (error) => { @@ -368,6 +371,12 @@ const WidgetIndex = (props) => { showChart ={showChart} updateFilters ={updateFilters} /> + ); }; diff --git a/modules/statistics/jsx/widgets/electrophysiology.js b/modules/statistics/jsx/widgets/electrophysiology.js new file mode 100644 index 0000000000..a16b6d1bce --- /dev/null +++ b/modules/statistics/jsx/widgets/electrophysiology.js @@ -0,0 +1,321 @@ +import React, {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; +import i18n from 'I18nSetup'; +import Loader from 'Loader'; +import Panel from 'Panel'; +import {QueryChartForm} from './helpers/queryChartForm'; +import {setupCharts} from './helpers/chartBuilder'; +import {useTranslation} from 'react-i18next'; +import jaStrings from '../../locale/ja/LC_MESSAGES/statistics.json'; +import frStrings from '../../locale/fr/LC_MESSAGES/statistics.json'; +import zhStrings from '../../locale/zh/LC_MESSAGES/statistics.json'; + +/** + * Electrophysiology - a widget containing statistics for EEG data. + * + * @param {object} props + * @return {JSX.Element} + */ +const Electrophysiology = (props) => { + const {t} = useTranslation(); + const [loading, setLoading] = useState(true); + const [showFiltersBreakdown, setShowFiltersBreakdown] = useState(false); + + useEffect( () => { + i18n.addResourceBundle('ja', 'statistics', jaStrings); + i18n.addResourceBundle('fr', 'statistics', frStrings); + i18n.addResourceBundle('zh', 'statistics', zhStrings); + + // Re-set default state that depended on the translation + let newdetails = {...chartDetails}; + newdetails['project_recordings']['eeg_recordings_by_project']['title'] + = t('EEG Recordings by project', {ns: 'statistics'}); + newdetails['project_recordings']['eeg_recordings_by_project']['label'] + = t('EEG Recordings', {ns: 'statistics'}); + newdetails['project_recordings']['eeg_recordings_by_project']['yLabel'] + = t('EEG Recordings', {ns: 'statistics'}); + newdetails['project_recordings']['eeg_recordings_by_project']['titlePrefix'] + = t('Project', {ns: 'loris'}); + + newdetails['site_recordings']['eeg_recordings_by_site']['title'] + = t('EEG Recordings by site', {ns: 'statistics'}); + newdetails['site_recordings']['eeg_recordings_by_site']['label'] + = t('EEG Recordings', {ns: 'statistics'}); + newdetails['site_recordings']['eeg_recordings_by_site']['yLabel'] + = t('EEG Recordings', {ns: 'statistics'}); + newdetails['site_recordings']['eeg_recordings_by_site']['titlePrefix'] + = t('Site', {ns: 'loris'}); + + + newdetails['project_events']['eeg_events_by_project']['title'] + = t('EEG Recordings by project', {ns: 'statistics'}); + newdetails['project_events']['eeg_events_by_project']['label'] + = t('EEG Events', {ns: 'statistics'}); + newdetails['project_events']['eeg_events_by_project']['yLabel'] + = t('EEG Events', {ns: 'statistics'}); + newdetails['project_events']['eeg_events_by_project']['titlePrefix'] + = t('Project', {ns: 'loris'}); + + setChartDetails(newdetails); + }, []); + + let json = props.data; + + const [chartDetails, setChartDetails] = useState({ + 'project_recordings': { + 'eeg_recordings_by_project': { + sizing: 11, + title: t('EEG Recordings by project', {ns: 'statistics'}), + filters: '', + chartType: 'pie', + dataType: 'pie', + label: t('EEG Recordings', {ns: 'statistics'}), + units: null, + showPieLabelRatio: true, + legend: '', + options: {pie: 'pie', bar: 'bar'}, + chartObject: null, + yLabel: t('EEG Recordings', {ns: 'statistics'}), + titlePrefix: t('Project', {ns: 'loris'}), + }, + }, + 'site_recordings': { + 'eeg_recordings_by_site': { + sizing: 11, + title: t('EEG Recordings by site', {ns: 'statistics'}), + filters: '', + chartType: 'pie', + dataType: 'pie', + label: t('EEG Recordings', {ns: 'statistics'}), + units: null, + showPieLabelRatio: true, + legend: '', + options: {pie: 'pie', bar: 'bar'}, + chartObject: null, + yLabel: t('EEG Recordings', {ns: 'statistics'}), + titlePrefix: t('Site', {ns: 'loris'}), + }, + }, + 'project_events': { + 'eeg_events_by_project': { + sizing: 11, + title: t('EEG Events by project', {ns: 'statistics'}), + filters: '', + chartType: 'pie', + dataType: 'pie', + label: t('EEG Events', {ns: 'statistics'}), + units: null, + showPieLabelRatio: true, + legend: '', + options: {pie: 'pie', bar: 'bar'}, + chartObject: null, + yLabel: t('EEG Events', {ns: 'statistics'}), + titlePrefix: t('Project', {ns: 'loris'}), + }, + }, + }); + + const showChart = ((section, chartID) => { + return props.showChart(section, chartID, + chartDetails, setChartDetails); + }); + + /** + * useEffect - modified to run when props.data updates. + */ + useEffect(() => { + if (json && Object.keys(json).length !== 0) { + setupCharts( + t, + false, + chartDetails, + t('Total', {ns: 'loris'}) + ).then((data) => { + setChartDetails(data); + }); + json = props.data; + setLoading(false); + } + }, [props.data]); + + const updateFilters = (formDataObj, section) => { + props.updateFilters(formDataObj, section, + chartDetails, setChartDetails); + }; + + // Helper function to calculate total recruitment + const getTotalRecordings = () => { + return json['eeg_data']['total_recordings'] || -1; + }; + const getTotalEvents = () => { + return json['eeg_data']['total_events'] || -1; + }; + const title = (subtitle) => t('EEG data', {ns: 'statistics'}) + + ' — ' + t(subtitle, {ns: 'statistics'}); + const filterLabel = (hide) => hide ? + t('Hide Filters', {ns: 'loris'}) + : t('Show Filters', {ns: 'loris'}); + return loading ? : ( + <> + { + setupCharts(t, false, chartDetails, t('Total', {ns: 'loris'})); + + // reset filters when switching views + setShowFiltersBreakdown(false); + }} + views={[ + { + content: + getTotalRecordings() > 0 ? ( +
+
+ +
+ {showFiltersBreakdown && ( + { + updateFilters(formDataObj, 'project_recordings'); + }} + /> + )} + {showChart('project_recordings', 'eeg_recordings_by_project')} +
+ ) : ( +

{t('There is no data yet.', {ns: 'statistics'})}

+ ), + + title: title('Recordings by Project'), + subtitle: t( + 'Total Recordings: {{count}}', + { + ns: 'statistics', + count: getTotalRecordings(), + } + ), + onToggleFilters: () => setShowFiltersBreakdown((prev) => !prev), + }, + { + content: + getTotalRecordings() > 0 ? ( +
+
+ +
+ {showFiltersBreakdown && ( + { + updateFilters(formDataObj, 'site_recordings'); + }} + /> + )} + {showChart('site_recordings', 'eeg_recordings_by_site')} +
+ ) : ( +

{t('There is no data yet.', {ns: 'statistics'})}

+ ), + title: title('Recordings by Site'), + subtitle: t( + 'Total Recordings: {{count}}', + { + ns: 'statistics', + count: getTotalRecordings(), + } + ), + onToggleFilters: () => setShowFiltersBreakdown((prev) => !prev), + }, + { + content: + getTotalEvents() > 0 ? ( +
+
+ +
+ {showFiltersBreakdown && ( + { + updateFilters(formDataObj, 'project_events'); + }} + /> + )} + {showChart('project_events', 'eeg_events_by_project')} +
+ ) : ( +

{t('There is no data yet.', {ns: 'statistics'})}

+ ), + title: title('Events by Project'), + subtitle: t( + 'Total Events: {{count}}', + { + ns: 'statistics', + count: getTotalEvents(), + } + ), + onToggleFilters: () => setShowFiltersBreakdown((prev) => !prev), + }, + ]} + /> + + ); +}; +Electrophysiology.propTypes = { + data: PropTypes.object, + baseURL: PropTypes.string, + updateFilters: PropTypes.func, + showChart: PropTypes.func, +}; +Electrophysiology.defaultProps = { + data: {}, +}; + +export default Electrophysiology; diff --git a/modules/statistics/jsx/widgets/helpers/chartBuilder.js b/modules/statistics/jsx/widgets/helpers/chartBuilder.js index 65836ce520..2c479d97b5 100644 --- a/modules/statistics/jsx/widgets/helpers/chartBuilder.js +++ b/modules/statistics/jsx/widgets/helpers/chartBuilder.js @@ -296,6 +296,7 @@ const setupCharts = async (t, targetIsModal, chartDetails, totalLabel) => { let labels = []; let colours = []; if (chart.dataType === 'pie') { + console.log('charDetails', chartDetails); columns = formatPieData(chartData); colours = siteColours; // reformating the columns for a bar chart when it was originally pie data diff --git a/modules/statistics/jsx/widgets/studyprogression.js b/modules/statistics/jsx/widgets/studyprogression.js index 6bf4e9b39c..3e375a7579 100644 --- a/modules/statistics/jsx/widgets/studyprogression.js +++ b/modules/statistics/jsx/widgets/studyprogression.js @@ -8,6 +8,7 @@ import {setupCharts} from './helpers/chartBuilder'; import {useTranslation} from 'react-i18next'; import jaStrings from '../../locale/ja/LC_MESSAGES/statistics.json'; import frStrings from '../../locale/fr/LC_MESSAGES/statistics.json'; +import zhStrings from '../../locale/zh/LC_MESSAGES/statistics.json'; /** * StudyProgression - a widget containing statistics for study data. @@ -23,6 +24,7 @@ const StudyProgression = (props) => { useEffect( () => { i18n.addResourceBundle('ja', 'statistics', jaStrings); i18n.addResourceBundle('fr', 'statistics', frStrings); + i18n.addResourceBundle('zh', 'statistics', zhStrings); // Re-set default state that depended on the translation let newdetails = {...chartDetails}; @@ -30,6 +32,7 @@ const StudyProgression = (props) => { = t('Scan sessions per site', {ns: 'statistics'}); newdetails['total_recruitment']['siterecruitment_bymonth']['title'] = t('Recruitment per site', {ns: 'statistics'}); + newdetails['project_sizes']['size_byproject']['title'] = t('Dataset size breakdown by project', {ns: 'statistics'}); newdetails['project_sizes']['size_byproject']['label'] @@ -40,6 +43,11 @@ const StudyProgression = (props) => { = t('Size (GB)', {ns: 'statistics'}); newdetails['project_sizes']['size_byproject']['titlePrefix'] = t('Project', {ns: 'loris'}); + newdetails['project_sizes']['size_byproject']['title'] + = t('Dataset size breakdown by project', {ns: 'statistics'}); + + newdetails['project_sizes']['size_byproject']['label'] + = t('Size (GB)', {ns: 'statistics'}); setChartDetails(newdetails); }, []); diff --git a/modules/statistics/locale/fr/LC_MESSAGES/statistics.po b/modules/statistics/locale/fr/LC_MESSAGES/statistics.po index c74019580b..33637f9f28 100644 --- a/modules/statistics/locale/fr/LC_MESSAGES/statistics.po +++ b/modules/statistics/locale/fr/LC_MESSAGES/statistics.po @@ -166,6 +166,40 @@ msgstr "Circulaire" msgid "Bar" msgstr "Barre" +# EEG charts +msgid "EEG Recordings by project" +msgstr "Enregistrements EEG par project" + +msgid "EEG Recordings by site" +msgstr "Enregistrements EEG par site" + +msgid "EEG Recordings" +msgstr "Enregistrements EEG" + +msgid "EEG Events by project" +msgstr "Événements EEG par project" + +msgid "EEG Events" +msgstr "Événements EEG" + +msgid "EEG data" +msgstr "Donnés EEG" + +msgid "Recordings by Project" +msgstr "Enregistrements par project" + +msgid "Total Recordings: {{count}}" +msgstr "Enregistrements totaux: {{count}}" + +msgid "Recordings by Site" +msgstr "Enregistrements par site" + +msgid "Events by Project" +msgstr "Événements par projet" + +msgid "Total Events: {{count}}" +msgstr "Événements totaux: {{count}}" + msgid "General Description" msgstr "Description générale" diff --git a/modules/statistics/locale/statistics.pot b/modules/statistics/locale/statistics.pot index 2afa3bef30..8435aaf54a 100644 --- a/modules/statistics/locale/statistics.pot +++ b/modules/statistics/locale/statistics.pot @@ -163,6 +163,41 @@ msgstr "" msgid "Bar" msgstr "" + +# EEG charts +msgid "EEG Recordings by project" +msgstr "" + +msgid "EEG Recordings by site" +msgstr "" + +msgid "EEG Recordings" +msgstr "" + +msgid "EEG Events by project" +msgstr "" + +msgid "EEG Events" +msgstr "" + +msgid "EEG data" +msgstr "" + +msgid "Recordings by Project" +msgstr "" + +msgid "Total Recordings: {{count}}" +msgstr "" + +msgid "Recordings by Site" +msgstr "" + +msgid "Events by Project" +msgstr "" + +msgid "Total Events: {{count}}" +msgstr "" + msgid "General Description" msgstr "" diff --git a/modules/statistics/php/charts.class.inc b/modules/statistics/php/charts.class.inc index c9e3bed0a8..4f763883dd 100644 --- a/modules/statistics/php/charts.class.inc +++ b/modules/statistics/php/charts.class.inc @@ -109,6 +109,14 @@ class Charts extends \NDB_Page return $this->_handleAgeDistributionByProject($request); case 'size_byproject': return $this->_handleProjectSizeBreakdown(); + case 'eeg_recordings_by_site': + return $this->_handleSiteEEGRecordingsBreakdown($request); + case 'eeg_recordings_by_project': + return $this->_handleProjectEEGRecordingsBreakdown($request); + case 'eeg_events_by_project': + return $this->_handleProjectEEGEventsBreakdown($request); + case 'size_byproject': + return $this->_handleProjectSizeBreakdown(); default: return new \LORIS\Http\Response\JSON\NotFound(); } @@ -931,6 +939,191 @@ class Charts extends \NDB_Page } } } + return (new \LORIS\Http\Response\JsonResponse($projectData)); + } + + /** + * Handle an incoming request for EEG recordings by site breakdown. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface + */ + private function _handleSiteEEGRecordingsBreakdown( + ServerRequestInterface $request + ) { + $DB = \NDB_Factory::singleton()->database(); + $user = \NDB_Factory::singleton()->user(); + $sites = $user->getSites(); + + $conditions = $this->_buildQueryConditions($request); + + $data = iterator_to_array( + $DB->pselect( + "SELECT + s.CenterID, + psc.Name, + COUNT(distinct s.ID) as count + FROM physiological_file pf + JOIN session s ON (s.ID=pf.SessionID) + JOIN candidate c ON (c.ID=s.CandidateID) + JOIN psc USING (CenterID) + {$conditions['participantStatusJoin']} + WHERE 1 = 1 + {$conditions['projectQuery']} + {$conditions['cohortQuery']} + {$conditions['visitQuery']} + {$conditions['siteQuery']} + {$conditions['participantStatusQuery']} + GROUP BY s.CenterID + ORDER BY s.CenterID", + $conditions['params'] + ) + ); + + $processedData = []; + foreach ($data as $row) { + $processedData[$row['CenterID']] = [ + 'CenterID' => $row['CenterID'], + 'Name' => $row['Name'], + 'count' => intval($row['count']), + ]; + } + + $siteData = []; + foreach ($sites as $siteID => $site) { + if (in_array($siteID, array_keys($processedData))) { + $siteData[] = [ + 'label' => $site->getCenterName(), + 'total' => $processedData[$siteID]['count'], + ]; + } + } + + return (new \LORIS\Http\Response\JsonResponse($siteData)); + } + + /** + * Handle an incoming request for EEG recordings by project breakdown. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface + */ + private function _handleProjectEEGRecordingsBreakdown( + ServerRequestInterface $request + ) { + $DB = \NDB_Factory::singleton()->database(); + $user = \NDB_Factory::singleton()->user(); + $projects = $user->getProjects(); + + $conditions = $this->_buildQueryConditions($request); + + $data = iterator_to_array( + $DB->pselect( + "SELECT + s.ProjectID, + p.Name, + COUNT(distinct s.ID) as count + FROM physiological_file pf + JOIN session s ON (s.ID=pf.SessionID) + JOIN Project p USING (ProjectID) + JOIN candidate c ON (c.ID=s.CandidateID) + JOIN psc ON (s.CenterID = psc.CenterID) + {$conditions['participantStatusJoin']} + WHERE 1 = 1 + {$conditions['projectQuery']} + {$conditions['cohortQuery']} + {$conditions['visitQuery']} + {$conditions['siteQuery']} + {$conditions['participantStatusQuery']} + GROUP BY s.CenterID + ORDER BY s.CenterID", + $conditions['params'] + ) + ); + + $processedData = []; + foreach ($data as $row) { + $processedData[$row['ProjectID']] = [ + 'ProjectID' => $row['ProjectID'], + 'Name' => $row['Name'], + 'count' => intval($row['count']), + ]; + } + + $projectData = []; + foreach ($projects as $projectID => $projectName) { + if (in_array($projectID, array_keys($processedData))) { + $projectData[] = [ + 'label' => $projectName, + 'total' => $processedData[$projectID]['count'], + ]; + } + } + + return (new \LORIS\Http\Response\JsonResponse($projectData)); + } + + /** + * Handle an incoming request for EEG events by project breakdown. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface + */ + private function _handleProjectEEGEventsBreakdown( + ServerRequestInterface $request + ) { + $DB = \NDB_Factory::singleton()->database(); + $user = \NDB_Factory::singleton()->user(); + $projects = $user->getProjects(); + + $conditions = $this->_buildQueryConditions($request); + + $data = iterator_to_array( + $DB->pselect( + "SELECT + s.ProjectID, + p.Name, + COUNT(pte.PhysiologicalTaskEventID) as count + FROM physiological_task_event pte + JOIN physiological_file pf USING (PhysiologicalFileID) + JOIN session s ON (s.ID=pf.SessionID) + JOIN Project p USING (ProjectID) + JOIN candidate c ON (c.ID=s.CandidateID) + JOIN psc ON (s.CenterID = psc.CenterID) + {$conditions['participantStatusJoin']} + WHERE 1 = 1 + {$conditions['projectQuery']} + {$conditions['cohortQuery']} + {$conditions['visitQuery']} + {$conditions['siteQuery']} + {$conditions['participantStatusQuery']} + GROUP BY s.CenterID + ORDER BY s.CenterID", + $conditions['params'] + ) + ); + + $processedData = []; + foreach ($data as $row) { + $processedData[$row['ProjectID']] = [ + 'ProjectID' => $row['ProjectID'], + 'Name' => $row['Name'], + 'count' => intval($row['count']), + ]; + } + + $projectData = []; + foreach ($projects as $projectID => $projectName) { + if (in_array($projectID, array_keys($processedData))) { + $projectData[] = [ + 'label' => $projectName, + 'total' => $processedData[$projectID]['count'], + ]; + } + } return (new \LORIS\Http\Response\JsonResponse($projectData)); } diff --git a/modules/statistics/php/widgets.class.inc b/modules/statistics/php/widgets.class.inc index e43b49aad1..4f46699245 100644 --- a/modules/statistics/php/widgets.class.inc +++ b/modules/statistics/php/widgets.class.inc @@ -211,6 +211,26 @@ class Widgets extends \NDB_Page implements ETagCalculator } } + $eeg_recordings = $db->pselectOneInt( + "SELECT COUNT(distinct s.ID) as total_recordings + FROM physiological_file pf + JOIN session s ON (s.ID=pf.SessionID) + WHERE s.ProjectID IN (" . + join(',', $user->getProjectIDs()) + . ")", + [] + ); + $eeg_events = $db->pselectOneInt( + "SELECT COUNT(pte.PhysiologicalTaskEventID) as total_events + FROM physiological_task_event pte + JOIN physiological_file pf USING (PhysiologicalFileID) + JOIN session s ON (s.ID=pf.SessionID) + WHERE s.ProjectID IN (" . + join(',', $user->getProjectIDs()) + . ")", + [] + ); + $participantStatusOptions = \Candidate::getParticipantStatusOptions(); @@ -236,6 +256,11 @@ class Widgets extends \NDB_Page implements ETagCalculator $values['size_byproject'] = $projectsSizes; + $values['eeg_data'] = [ + 'total_recordings' => $eeg_recordings, + 'total_events' => $eeg_events + ]; + $this->_cache = new \LORIS\Http\Response\JsonResponse($values); return $this->_cache;