From f314023a48de38e6e94a67b3753add33131e4693 Mon Sep 17 00:00:00 2001 From: Bozana Bokan Date: Tue, 16 Jan 2024 12:15:27 +0100 Subject: [PATCH 1/4] pkp/pkp-lib#8248 COUNTER R5 TSV reports --- .../forms/counter/CounterReportForm.php | 45 ++++ classes/sushi/IR.php | 236 ++++++++++++++++-- classes/sushi/PR.php | 128 +++++++++- classes/sushi/PR_P1.php | 9 +- js/load.js | 2 + 5 files changed, 400 insertions(+), 20 deletions(-) create mode 100644 classes/components/forms/counter/CounterReportForm.php diff --git a/classes/components/forms/counter/CounterReportForm.php b/classes/components/forms/counter/CounterReportForm.php new file mode 100644 index 0000000000..0012b46370 --- /dev/null +++ b/classes/components/forms/counter/CounterReportForm.php @@ -0,0 +1,45 @@ +reportFields['PR'] = array_map(function ($field) { + $field->groupId = 'default'; + return $field; + }, $formFieldsPR); + + $formFieldsPR_P1 = PR_P1::getReportSettingsFormFields(); + $this->reportFields['PR_P1'] = array_map(function ($field) { + $field->groupId = 'default'; + return $field; + }, $formFieldsPR_P1); + + $formFieldsIR = IR::getReportSettingsFormFields(); + $this->reportFields['IR'] = array_map(function ($field) { + $field->groupId = 'default'; + return $field; + }, $formFieldsIR); + } +} diff --git a/classes/sushi/IR.php b/classes/sushi/IR.php index 9e42f0d907..f8b8f3a0ca 100644 --- a/classes/sushi/IR.php +++ b/classes/sushi/IR.php @@ -18,6 +18,9 @@ namespace APP\sushi; use APP\facades\Repo; +use Illuminate\Support\Collection; +use PKP\components\forms\FieldOptions; +use PKP\components\forms\FieldText; use PKP\statistics\PKPStatisticsHelper; use PKP\sushi\CounterR5Report; @@ -87,7 +90,8 @@ public function getSupportedParams(): array 'attributes_to_show', 'include_component_details', 'include_parent_details', - 'granularity' + 'granularity', + '_', // for ajax requests ]; } @@ -183,10 +187,8 @@ public function setFilters(array $filters): void } } - /** - * Get report items - */ - public function getReportItems(): array + /** Get DB query results for the report */ + protected function getQueryResults(): Collection { $params['contextIds'] = [$this->context->getId()]; $params['institutionId'] = $this->customerId; @@ -194,7 +196,7 @@ public function getReportItems(): array $params['dateEnd'] = $this->endDate; $params['yearsOfPublication'] = $this->yearsOfPublication; if ($this->itemId > 0) { - $allowedParams['submissionIds'] = [$this->itemId]; + $params['submissionIds'] = [$this->itemId]; } // do not consider metric_type filter now, but for display @@ -224,14 +226,18 @@ public function getReportItems(): array 'Data' => __('sushi.exception.3030', ['beginDate' => $this->beginDate, 'endDate' => $this->endDate]) ]); } + return $results; + } - $resultsGroupedBySubmission = $submissions = $items = []; - foreach ($results as $result) { - if (!in_array($result->submission_id, $submissions)) { - $submissions[] = $result->submission_id; - } - $resultsGroupedBySubmission[$result->submission_id][] = $result; - } + /** + * Get report items + */ + public function getReportItems(): array + { + $results = $this->getQueryResults(); + + $items = []; + $resultsGroupedBySubmission = $results->groupBy('submission_id'); foreach ($resultsGroupedBySubmission as $submissionId => $submissionResults) { // Get the submission properties @@ -283,7 +289,7 @@ public function getReportItems(): array $itemContributor['Type'] = 'Author'; $itemContributor['Name'] = $author->getFullName(true, false, $submissionLocale); $orcid = $author->getOrcid(); - if (isset($orcid) && !empty($orcid)) { + if (!empty($orcid)) { $itemContributor['Identifier'] = $orcid; } $itemContributors[] = $itemContributor; @@ -338,4 +344,206 @@ public function getReportItems(): array return $items; } + + /** Get TSV report column names */ + public function getTSVColumnNames(): array + { + $columnRow = ['Item', 'Publisher', 'Publisher ID', 'Platform']; + + if (in_array('Authors', $this->attributesToShow)) { + array_push($columnRow, 'Authors'); + } + if (in_array('Publication_Date', $this->attributesToShow)) { + array_push($columnRow, 'Publication_Date'); + } + if (in_array('Article_Version', $this->attributesToShow)) { + array_push($columnRow, 'Article_Version'); + } + + array_push($columnRow, 'DOI', 'Proprietary_ID', 'ISBN', 'Print_ISSN', 'Online_ISSN', 'URI'); + + if (in_array('Data_Type', $this->attributesToShow)) { + array_push($columnRow, 'Data_Type'); + } + if (in_array('YOP', $this->attributesToShow)) { + array_push($columnRow, 'YOP'); + } + if (in_array('Access_Type', $this->attributesToShow)) { + array_push($columnRow, 'Access_Type'); + } + if (in_array('Access_Method', $this->attributesToShow)) { + array_push($columnRow, 'Access_Method'); + } + + array_push($columnRow, 'Metric_Type', 'Reporting_Period_Total'); + + if ($this->granularity == 'Month') { + $period = $this->getMonthlyDatePeriod(); + foreach ($period as $dt) { + array_push($columnRow, $dt->format('M-Y')); + } + } + + return [$columnRow]; + } + + /** Get TSV report rows */ + public function getTSVReportItems(): array + { + $results = $this->getQueryResults(); + + $resultRows = []; + $resultsGroupedBySubmission = $results->groupBy('submission_id'); + + foreach ($resultsGroupedBySubmission as $submissionId => $submissionResults) { + $results = collect($submissionResults); + + // get total numbers for every metric type + $metricsTotal['Total_Item_Investigations'] = $results->pluck('metric_investigations')->sum(); + $metricsTotal['Unique_Item_Investigations'] = $results->pluck('metric_investigations_unique')->sum(); + $metricsTotal['Total_Item_Requests'] = $results->pluck('metric_requests')->sum(); + $metricsTotal['Unique_Item_Requests'] = $results->pluck('metric_requests_unique')->sum(); + + // filter here by requested metric types + foreach ($this->metricTypes as $metricType) { + // if the total numbers for the given metric type > 0, + // construct the result row + if ($metricsTotal[$metricType] > 0) { + $submission = Repo::submission()->get($submissionId); + if (!$submission || !$submission->getOriginalPublication()) { + break; + } + $currentPublication = $submission->getCurrentPublication(); + $submissionLocale = $submission->getData('locale'); + $datePublished = $submission->getOriginalPublication()->getData('datePublished'); + + $resultRow = [ + $currentPublication->getLocalizedTitle($submissionLocale), // Item + $this->context->getData('publisherInstitution'), // Publisher + '', // Publisher ID + $this->platformName, // Platform + ]; + + if (in_array('Authors', $this->attributesToShow)) { + $authors = $currentPublication->getData('authors'); + $authorRowValue = ''; + foreach ($authors as $author) { + $authorRowValue = $author->getFullName(true, false, $submissionLocale); + $orcid = $author->getOrcid(); + if (!empty($orcid)) { + $authorRowValue .= '(ORCID:' . $orcid . ')'; + } + } + array_push($resultRow, $authorRowValue); // Authors + } + + if (in_array('Publication_Date', $this->attributesToShow)) { + array_push($resultRow, $datePublished); // Publication_Date + } + + if (in_array('Article_Version', $this->attributesToShow)) { + array_push($resultRow, 'VoR'); // Article_Version + } + + $doi = $currentPublication->getDoi() ?? ''; + array_push($resultRow, $doi); // DOI + + array_push($resultRow, $this->platformId . ':' . $submissionId); // Proprietary_ID + array_push($resultRow, '', '', '', ''); // ISBN, Print_ISSN, Online_ISSN, URI + + if (in_array('Data_Type', $this->attributesToShow)) { + array_push($resultRow, self::DATA_TYPE); // Data_Type + } + if (in_array('YOP', $this->attributesToShow)) { + array_push($resultRow, date('Y', strtotime($datePublished))); // YOP + } + if (in_array('Access_Type', $this->attributesToShow)) { + array_push($resultRow, self::ACCESS_TYPE); // Access_Type + } + if (in_array('Access_Method', $this->attributesToShow)) { + array_push($resultRow, self::ACCESS_METHOD); // Access_Method + } + + array_push($resultRow, $metricType); // Metric_Type + array_push($resultRow, $metricsTotal[$metricType]); // Reporting_Period_Total + if ($this->granularity == 'Month') { // metrics for each month in the given period + $period = $this->getMonthlyDatePeriod(); + foreach ($period as $dt) { + $month = $dt->format('Ym'); + $result = $submissionResults->firstWhere('month', '=', $month); + if ($result === null) { + array_push($resultRow, '0'); + } else { + $metrics['Total_Item_Investigations'] = $result->metric_investigations; + $metrics['Unique_Item_Investigations'] = $result->metric_investigations_unique; + $metrics['Total_Item_Requests'] = $result->metric_requests; + $metrics['Unique_Item_Requests'] = $result->metric_requests_unique; + array_push($resultRow, $metrics[$metricType]); + } + } + } + $resultRows[] = $resultRow; + } + } + } + return $resultRows; + } + + /** Get report specific form fields */ + public static function getReportSettingsFormFields(): array + { + $formFields = parent::getCommonReportSettingsFormFields(); + + $metricTypes = ['Total_Item_Investigations', 'Unique_Item_Investigations', 'Total_Item_Requests', 'Unique_Item_Requests']; + $metricTypeOptions = []; + foreach ($metricTypes as $metricType) { + $metricTypeOptions[] = ['value' => $metricType, 'label' => $metricType]; + } + $formFields[] = new FieldOptions('metric_type', [ + 'label' => __('manager.statistics.counterR5Report.settings.metricType'), + 'options' => $metricTypeOptions, + 'value' => $metricTypes, + 'groupId' => 'default', + ]); + + $attributesToShow = ['Article_Version', 'Authors', 'Access_Method', 'Access_Type', 'Data_Type', 'Publication_Date', 'YOP']; + $attributesToShowOptions = []; + foreach ($attributesToShow as $attributeToShow) { + $attributesToShowOptions[] = ['value' => $attributeToShow, 'label' => $attributeToShow]; + } + $formFields[] = new FieldOptions('attributes_to_show', [ + 'label' => __('manager.statistics.counterR5Report.settings.attributesToShow'), + 'options' => $attributesToShowOptions, + 'value' => [], + 'groupId' => 'default', + ]); + + $formFields[] = new FieldText('yop', [ + 'label' => __('manager.statistics.counterR5Report.settings.yop'), + 'description' => __('manager.statistics.counterR5Report.settings.date.yop.description'), + 'size' => 'small', + 'isMultilingual' => false, + 'isRequired' => false, + 'groupId' => 'default', + ]); + + $formFields[] = new FieldText('item_id', [ + 'label' => __('manager.statistics.counterR5Report.settings.itemId'), + 'size' => 'small', + 'isMultilingual' => false, + 'isRequired' => false, + 'groupId' => 'default', + ]); + + $formFields[] = new FieldOptions('granularity', [ + 'label' => __('manager.statistics.counterR5Report.settings.excludeMonthlyDetails'), + 'options' => [ + ['value' => true, 'label' => __('manager.statistics.counterR5Report.settings.excludeMonthlyDetails')], + ], + 'value' => false, + 'groupId' => 'default', + ]); + + return $formFields; + } } diff --git a/classes/sushi/PR.php b/classes/sushi/PR.php index c24bd68d43..a304d76893 100644 --- a/classes/sushi/PR.php +++ b/classes/sushi/PR.php @@ -17,6 +17,8 @@ namespace APP\sushi; +use Illuminate\Support\Collection; +use PKP\components\forms\FieldOptions; use PKP\statistics\PKPStatisticsHelper; use PKP\sushi\CounterR5Report; @@ -71,7 +73,8 @@ public function getSupportedParams(): array 'data_type', 'access_method', 'attributes_to_show', - 'granularity' + 'granularity', + '_', // for ajax requests ]; } @@ -123,10 +126,8 @@ public function getSupportedAttributes(): array ]; } - /** - * Get report items - */ - public function getReportItems(): array + /** Get DB query results for the report */ + protected function getQueryResults(): Collection { $params['contextIds'] = [$this->context->getId()]; $params['institutionId'] = $this->customerId; @@ -158,6 +159,13 @@ public function getReportItems(): array 'Data' => __('sushi.exception.3030', ['beginDate' => $this->beginDate, 'endDate' => $this->endDate]) ]); } + return $results; + } + + /** Get report items */ + public function getReportItems(): array + { + $results = $this->getQueryResults(); // There is only one platform, so there will be only one report item $item['Platform'] = $this->platformName; @@ -207,4 +215,114 @@ public function getReportItems(): array $items = [$item]; return $items; } + + /** Get TSV report column names */ + public function getTSVColumnNames(): array + { + $columnRow = ['Platform']; + if (in_array('Data_Type', $this->attributesToShow)) { + array_push($columnRow, 'Data_Type'); + } + if (in_array('Access_Method', $this->attributesToShow)) { + array_push($columnRow, 'Access_Method'); + } + array_push($columnRow, 'Metric_Type', 'Reporting_Period_Total'); + if ($this->granularity == 'Month') { + $period = $this->getMonthlyDatePeriod(); + foreach ($period as $dt) { + array_push($columnRow, $dt->format('M-Y')); + } + } + return [$columnRow]; + } + + /** Get TSV report rows */ + public function getTSVReportItems(): array + { + $results = $this->getQueryResults(); + + // get total numbers for every metric type + $metricsTotal['Total_Item_Investigations'] = $results->pluck('metric_investigations')->sum(); + $metricsTotal['Unique_Item_Investigations'] = $results->pluck('metric_investigations_unique')->sum(); + $metricsTotal['Total_Item_Requests'] = $results->pluck('metric_requests')->sum(); + $metricsTotal['Unique_Item_Requests'] = $results->pluck('metric_requests_unique')->sum(); + + $resultRows = []; + // filter here by requested metric types + foreach ($this->metricTypes as $metricType) { + // if the total numbers for the given metric type > 0 + if ($metricsTotal[$metricType] > 0) { + // construct the result row + $resultRow = []; + array_push($resultRow, $this->platformName); // Platform + if (in_array('Data_Type', $this->attributesToShow)) { + array_push($resultRow, self::DATA_TYPE); // Data_Type + } + if (in_array('Access_Method', $this->attributesToShow)) { + array_push($resultRow, self::ACCESS_METHOD); // Access_Method + } + array_push($resultRow, $metricType); // Metric_Type + array_push($resultRow, $metricsTotal[$metricType]); // Reporting_Period_Total + if ($this->granularity == 'Month') { // metrics for each month in the given period + $period = $this->getMonthlyDatePeriod(); + foreach ($period as $dt) { + $month = $dt->format('Ym'); + $result = $results->firstWhere('month', '=', $month); + if ($result === null) { + array_push($resultRow, '0'); + } else { + $metrics['Total_Item_Investigations'] = $result->metric_investigations; + $metrics['Unique_Item_Investigations'] = $result->metric_investigations_unique; + $metrics['Total_Item_Requests'] = $result->metric_requests; + $metrics['Unique_Item_Requests'] = $result->metric_requests_unique; + array_push($resultRow, $metrics[$metricType]); + } + } + } + $resultRows[] = $resultRow; + } + } + return $resultRows; + } + + /** Get report specific form fields */ + public static function getReportSettingsFormFields(): array + { + $formFields = parent::getCommonReportSettingsFormFields(); + + $metricTypes = ['Total_Item_Investigations', 'Unique_Item_Investigations', 'Total_Item_Requests', 'Unique_Item_Requests']; + $metricTypeOptions = []; + foreach ($metricTypes as $metricType) { + $metricTypeOptions[] = ['value' => $metricType, 'label' => $metricType]; + } + $formFields[] = new FieldOptions('metric_type', [ + 'label' => __('manager.statistics.counterR5Report.settings.metricType'), + 'options' => $metricTypeOptions, + 'value' => $metricTypes, + 'groupId' => 'default', + ]); + + $attributesToShow = ['Data_Type', 'Access_Method']; + $attributesToShowOptions = []; + foreach ($attributesToShow as $attributeToShow) { + $attributesToShowOptions[] = ['value' => $attributeToShow, 'label' => $attributeToShow]; + } + $formFields[] = new FieldOptions('attributes_to_show', [ + 'label' => __('manager.statistics.counterR5Report.settings.attributesToShow'), + 'options' => $attributesToShowOptions, + 'value' => [], + 'groupId' => 'default', + ]); + + $formFields[] = new FieldOptions('granularity', [ + 'label' => __('manager.statistics.counterR5Report.settings.excludeMonthlyDetails'), + 'options' => [ + ['value' => true, 'label' => __('manager.statistics.counterR5Report.settings.excludeMonthlyDetails')], + ], + 'value' => false, + 'groupId' => 'default', + ]); + + return $formFields; + } } diff --git a/classes/sushi/PR_P1.php b/classes/sushi/PR_P1.php index 118bbaf92d..1b71f54b06 100644 --- a/classes/sushi/PR_P1.php +++ b/classes/sushi/PR_P1.php @@ -66,7 +66,8 @@ public function getSupportedParams(): array 'customer_id', 'begin_date', 'end_date', - 'platform' + 'platform', + '_', // for ajax requests ]; } @@ -113,4 +114,10 @@ public function setAttributes(array $attributes): void { $this->attributes = []; } + + /** Get report specific form fields */ + public static function getReportSettingsFormFields(): array + { + return parent::getCommonReportSettingsFormFields(); + } } diff --git a/js/load.js b/js/load.js index 2cd52939aa..3c7dbcf78f 100644 --- a/js/load.js +++ b/js/load.js @@ -21,6 +21,7 @@ import Page from '@/components/Container/Page.vue'; import AccessPage from '@/components/Container/AccessPage.vue'; import AddContextContainer from '@/components/Container/AddContextContainer.vue'; import AdminPage from '@/components/Container/AdminPage.vue'; +import CounterReportsPage from '@/components/Container/CounterReportsPage.vue'; import DecisionPage from '@/components/Container/DecisionPage.vue'; import DoiPage from '@/components/Container/DoiPageOPS.vue'; import ImportExportPage from '@/components/Container/ImportExportPage.vue'; @@ -43,6 +44,7 @@ window.pkp = Object.assign(PkpLoad, window.pkp || {}, { AddContextContainer, AdminPage, AdvancedSearchReviewerContainer, + CounterReportsPage, DecisionPage, DoiPage, ImportExportPage, From d3fea3b0dfea2cc65a9655dbf6e0e6df8df12431 Mon Sep 17 00:00:00 2001 From: Bozana Bokan Date: Tue, 15 Oct 2024 14:26:58 +0200 Subject: [PATCH 2/4] remove old counter report page container component --- js/load.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/load.js b/js/load.js index 3c7dbcf78f..2cd52939aa 100644 --- a/js/load.js +++ b/js/load.js @@ -21,7 +21,6 @@ import Page from '@/components/Container/Page.vue'; import AccessPage from '@/components/Container/AccessPage.vue'; import AddContextContainer from '@/components/Container/AddContextContainer.vue'; import AdminPage from '@/components/Container/AdminPage.vue'; -import CounterReportsPage from '@/components/Container/CounterReportsPage.vue'; import DecisionPage from '@/components/Container/DecisionPage.vue'; import DoiPage from '@/components/Container/DoiPageOPS.vue'; import ImportExportPage from '@/components/Container/ImportExportPage.vue'; @@ -44,7 +43,6 @@ window.pkp = Object.assign(PkpLoad, window.pkp || {}, { AddContextContainer, AdminPage, AdvancedSearchReviewerContainer, - CounterReportsPage, DecisionPage, DoiPage, ImportExportPage, From ccda3bd8377959905ee32f6e3cdc04d65ce377d8 Mon Sep 17 00:00:00 2001 From: Bozana Bokan Date: Tue, 15 Oct 2024 14:31:26 +0200 Subject: [PATCH 3/4] consider TSV stream response --- api/v1/stats/sushi/StatsSushiController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/v1/stats/sushi/StatsSushiController.php b/api/v1/stats/sushi/StatsSushiController.php index 060deb87a8..1c801db62e 100644 --- a/api/v1/stats/sushi/StatsSushiController.php +++ b/api/v1/stats/sushi/StatsSushiController.php @@ -21,6 +21,7 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; +use Symfony\Component\HttpFoundation\StreamedResponse; class StatsSushiController extends \PKP\API\v1\stats\sushi\PKPStatsSushiController { @@ -40,7 +41,7 @@ public function getGroupRoutes(): void * A customizable report detailing activity at the pre-print level * that allows the user to apply filters and select other configuration options for the report. */ - public function getReportsIR(Request $illuminateRequest): JsonResponse + public function getReportsIR(Request $illuminateRequest): JsonResponse|StreamedResponse { return $this->getReportResponse(new IR(), $illuminateRequest); } From 277d09c3e1ef5bea54e64c5e3257b94609d6c3c6 Mon Sep 17 00:00:00 2001 From: Bozana Bokan Date: Tue, 15 Oct 2024 14:47:47 +0200 Subject: [PATCH 4/4] pkp/pkp-lib#9666 submodules updates ##bozana/9666## --- lib/pkp | 2 +- lib/ui-library | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pkp b/lib/pkp index b065e8c674..8937d74ab8 160000 --- a/lib/pkp +++ b/lib/pkp @@ -1 +1 @@ -Subproject commit b065e8c6745c123e32b38cda163011a81015ce0a +Subproject commit 8937d74ab8e91dc338f02abc8328ea0f957f5877 diff --git a/lib/ui-library b/lib/ui-library index 619f90a9a3..157a8cf99a 160000 --- a/lib/ui-library +++ b/lib/ui-library @@ -1 +1 @@ -Subproject commit 619f90a9a38cf0e88aab74bd0b081ea4b7ed9f70 +Subproject commit 157a8cf99a79218cfd89805843176bb49d9fac7e