From e4ee9eb7d4bc5b992caa3d6db1b2923c0d3e3e2d Mon Sep 17 00:00:00 2001 From: Jonasel Roque <38173852+roquej@users.noreply.github.com> Date: Mon, 6 May 2024 17:56:41 -0700 Subject: [PATCH 01/12] Roquej 20240419 preprod edit (#81) * enable edit project page * add logic to check if a project can't use the editing feature, update config retrieval * fix updating instruments for repeatable data collection windows * add warning to editProject.php if there are non-DUSTER fields/forms in the project * add confirmation dialog to new project Vue app when editing a project --- classes/DusterConfigClass.php | 10 +- config.json | 7 + pages/editProject.php | 147 +++++++++++++++++- .../{index-66f121cc.js => index-4102b31f.js} | 112 ++++++------- pages/js/duster/new-project/dist/index.html | 2 +- .../src/components/ReviewPanel.vue | 57 ++++++- services/updateProject.php | 18 ++- 7 files changed, 279 insertions(+), 74 deletions(-) rename pages/js/duster/new-project/dist/assets/{index-66f121cc.js => index-4102b31f.js} (71%) diff --git a/classes/DusterConfigClass.php b/classes/DusterConfigClass.php index d7861e9..4476acb 100644 --- a/classes/DusterConfigClass.php +++ b/classes/DusterConfigClass.php @@ -12,7 +12,7 @@ class DusterConfigClass { - private $project_id, $duster_config, $module; + private $project_id, $duster_config, $design_config, $module; private $rp_info_form = array('form_name' => 'rp_info', 'form_label' => 'Researcher-Provided Information'); private $demographics_form = array('form_name' => 'demographics', 'form_label' => 'Demographics'); @@ -30,7 +30,9 @@ public function loadConfig() $config_url = $config_url . ((substr($config_url, -1) === '/') ? "" : "/") . SERVER_NAME . '/' . $this->project_id . '?redcap_user=' . $this->module->getUser()->getUserName(); - $this->duster_config = $this->module->starrApiGetRequest($config_url, 'ddp'); + $config_object = $this->module->starrApiGetRequest($config_url, 'ddp'); + $this->duster_config = $config_object['config']; + $this->design_config = $config_object['design_config']; } public function loadDesignConfig () { @@ -53,9 +55,9 @@ public function getDusterConfig() public function getDesignConfig() { if ($this->design_config == null) { - $this->loadDesignConfig(); + $this->loadConfig(); } - return $this->design_config['design_config']; + return $this->design_config; } public function setDusterConfig($config) diff --git a/config.json b/config.json index fb90ece..2c8959f 100644 --- a/config.json +++ b/config.json @@ -31,6 +31,13 @@ "icon": "fas fa-receipt", "url": "pages/populateData.php", "show-header-and-footer": true + }, + { + "name": "DUSTER: Edit Project", + "key": "dustereditproject", + "icon": "fas fa-pen-to-square", + "url": "pages/editProject.php", + "show-header-and-footer": true } ], "control-center": [ diff --git a/pages/editProject.php b/pages/editProject.php index 4415dec..c964239 100644 --- a/pages/editProject.php +++ b/pages/editProject.php @@ -2,6 +2,7 @@ namespace Stanford\Duster; /** @var $module Duster */ +use Project; use REDCap; require_once $module->getModulePath() . "classes/DusterConfigClass.php"; @@ -12,15 +13,117 @@ $pid = $module->getProjectId(); $project_status = $module->getProjectStatus($pid); $user_rights = $module->getUser()->getRights($pid); +$irb = $module->getProjectIrb($pid); +$duster_config_obj = new DusterConfigClass($pid, $module); +$duster_config = json_decode($duster_config_obj->getDusterConfig(), true); +$design_config = $duster_config_obj->getDesignConfig(); +$has_design_config = $design_config !== NULL; // PHP 8.3 provides json_validate(), which checks if a string contains valid JSON. +$design_config = json_encode($design_config); $editable = $project_status === "DEV" && strlen($user_rights['api_token']) === 32 && $user_rights['api_import'] === '1' - && $user_rights['design'] === '1'; + && $user_rights['design'] === '1' + && $has_design_config === true; -if ($editable === true) { - $irb = $module->getProjectIrb($pid); - $duster_config_obj = new DusterConfigClass($pid, $module); - $design_config = json_encode($duster_config_obj->getDesignConfig()); +/* Check for non-DUSTER fields */ +$project_object = new Project($pid, false); +$existing_forms = $project_object->forms; +$non_duster_fields = []; + +// Researcher-Provided Information +if (isset($existing_forms['researcher_provided_information'])) { + $rp_fields = array_keys($existing_forms['researcher_provided_information']['fields']); + $duster_rp_fields = ['redcap_record_id', 'mrn']; + $duster_rp_fields = array_merge($duster_rp_fields, array_keys($duster_config['rp_info']['rp_dates'])); + $duster_rp_fields[] = "researcher_provided_information_complete"; + $non_duster_rp_fields = array_diff($rp_fields, $duster_rp_fields); + if (!empty($non_duster_rp_fields)) { + $non_duster_fields[] = [ + 'name' => 'researcher_provided_information', + 'label' => 'Researcher-Provided Information', + 'fields' => $non_duster_rp_fields + ]; + } + unset($existing_forms['researcher_provided_information']); +} + +// Demographics +if (isset($existing_forms['demographics'])) { + $demographic_fields = array_keys($existing_forms['demographics']['fields']); + $duster_demographic_fields = array_column($duster_config['demographics'], 'redcap_field_name'); + $duster_demographic_fields[] = "demographics_complete"; + $non_duster_demographic_fields = array_diff($demographic_fields, $duster_demographic_fields); + if (!empty($non_duster_demographic_fields)) { + $non_duster_fields[] = [ + 'name' => 'demographics', + 'label' => 'Demographics', + 'fields' => $non_duster_demographic_fields + ]; + } + unset($existing_forms['demographics']); +} + +// Data Collection Windows +foreach ($duster_config['collection_windows'] as $collection_window) { + $cw_form_name = $collection_window['form_name']; + if (isset($existing_forms[$cw_form_name])) { + $cw_fields = array_keys($existing_forms[$cw_form_name]['fields']); + $timing_fields = [ + $collection_window['timing']['start']['redcap_field_name'], + $collection_window['timing']['end']['redcap_field_name'] + ]; + if ($collection_window['type'] === 'repeating' && isset($collection_window['timing']['repeat_interval'])) { + $module->emLog("REPEATING HERE"); + $timing_fields = array_merge($timing_fields, [ + $cw_form_name, + $collection_window['timing']['repeat_interval']['start_instance']['redcap_field_name'], + $collection_window['timing']['repeat_interval']['end_instance']['redcap_field_name'] + ]); + } + + $score_fields = []; + foreach ($collection_window['data']['scores'] as $score) { + $score_fields[] = $score['redcap_field_name']; + foreach ($score['subscores'] as $subscore) { + $score_fields[] = $subscore['redcap_field_name']; + $score_fields = array_merge( + $score_fields, + array_column($subscore['dependencies'], 'redcap_field_name') + ); + } + } + + $duster_cw_fields = array_merge( + $timing_fields, + array_column($collection_window['event'], 'redcap_field_name'), + array_column($collection_window['data']['labs'], 'redcap_field_name'), + array_column($collection_window['data']['vitals'], 'redcap_field_name'), + array_column($collection_window['data']['outcomes'], 'redcap_field_name'), + $score_fields + ); + $duster_cw_fields[] = $cw_form_name . '_complete'; + + $non_duster_cw_fields = array_diff($cw_fields, $duster_cw_fields); + if (!empty($non_duster_cw_fields)) { + $non_duster_fields[] = [ + 'name' => $cw_form_name, + 'label' => $collection_window['label'], + 'fields' => $non_duster_cw_fields + ]; + } + unset($existing_forms[$cw_form_name]); + } +} + +// non-DUSTER forms +foreach ($existing_forms as $non_duster_form_name=>$non_duster_form) { + if (!empty($non_duster_form['fields'])) { + $non_duster_fields[] = [ + 'name' => $non_duster_form_name, + 'label' => $non_duster_form['menu'], + 'fields' => array_keys($non_duster_form['fields']) + ]; + } } ?> @@ -30,7 +133,8 @@
Hitting the button below will launch an application where you may perform the following modifications to your project: