Skip to content

Commit

Permalink
Merge pull request #9600 from bozana/8248-3_4_0
Browse files Browse the repository at this point in the history
#8248 COUNTER R5 TSV reports
  • Loading branch information
bozana authored Jan 24, 2024
2 parents 790783c + c1267f7 commit fbf8089
Show file tree
Hide file tree
Showing 10 changed files with 541 additions and 23 deletions.
104 changes: 103 additions & 1 deletion api/v1/stats/sushi/PKPStatsSushiHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use PKP\security\Role;
use PKP\sushi\CounterR5Report;
use PKP\sushi\SushiException;
use PKP\validation\ValidatorFactory;
use Slim\Http\Request as SlimHttpRequest;

class PKPStatsSushiHandler extends APIHandler
Expand Down Expand Up @@ -232,22 +233,123 @@ public function getReportsPR1(SlimHttpRequest $slimRequest, APIResponse $respons
return $this->getReportResponse(new PR_P1(), $slimRequest, $response, $args);
}

/** Validate user input for TSV reports */
protected function _validateUserInput(CounterR5Report $report, array $params): array
{
$request = $this->getRequest();
$context = $request->getContext();
$earliestDate = CounterR5Report::getEarliestDate();
$lastDate = CounterR5Report::getLastDate();
$submissionIds = Repo::submission()->getCollector()->filterByContextIds([$context->getId()])->getIds()->implode(',');

$rules = [
'begin_date' => [
'regex:/^\d{4}-\d{2}(-\d{2})?$/',
'after_or_equal:' . $earliestDate,
'before_or_equal:end_date',
],
'end_date' => [
'regex:/^\d{4}-\d{2}(-\d{2})?$/',
'before_or_equal:' . $lastDate,
'after_or_equal:begin_date',
],
'item_id' => [
// TO-ASK: shell this rather be just validation for positive integer?
'in:' . $submissionIds,
],
'yop' => [
'regex:/^\d{4}((\||-)\d{4})*$/',
],
];
$reportId = $report->getID();
if (in_array($reportId, ['PR', 'TR', 'IR'])) {
$rules['metric_type'] = ['required'];
}

$errors = [];
$validator = ValidatorFactory::make(
$params,
$rules,
[
'begin_date.regex' => __(
'manager.statistics.counterR5Report.settings.wrongDateFormat'
),
'end_date.regex' => __(
'manager.statistics.counterR5Report.settings.wrongDateFormat'
),
'begin_date.after_or_equal' => __(
'stats.dateRange.invalidStartDateMin'
),
'end_date.before_or_equal' => __(
'stats.dateRange.invalidEndDateMax'
),
'begin_date.before_or_equal' => __(
'stats.dateRange.invalidDateRange'
),
'end_date.after_or_equal' => __(
'stats.dateRange.invalidDateRange'
),
'item_id.*' => __(
'manager.statistics.counterR5Report.settings.wrongItemId'
),
'yop.regex' => __(
'manager.statistics.counterR5Report.settings.wrongYOPFormat'
),
]
);

if ($validator->fails()) {
$errors = $validator->errors()->getMessages();
}

return $errors;
}

/**
* Get the requested report
*/
protected function getReportResponse(CounterR5Report $report, SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse
{
$responseTSV = str_contains($slimRequest->getHeaderLine('Accept'), APIResponse::RESPONSE_TSV) ? true : false;

$params = $slimRequest->getQueryParams();

if ($responseTSV) {
$errors = $this->_validateUserInput($report, $params);
if (!empty($errors)) {
return $response->withJson($errors, 400);
}
}

try {
$report->processReportParams($this->getRequest(), $params);
} catch (SushiException $e) {
return $response->withJson($e->getResponseData(), $e->getHttpStatusCode());
}

if ($responseTSV) {
$reportHeader = $report->getTSVReportHeader();
$reportColumnNames = $report->getTSVColumnNames();
$reportItems = $report->getTSVReportItems();
// consider 3030 error (no usage available)
$key = array_search('3030', array_column($report->warnings, 'Code'));
if ($key !== false) {
$error = $report->warnings[$key]['Code'] . ':' . $report->warnings[$key]['Message'] . '(' . $report->warnings[$key]['Data'] . ')';
foreach ($reportHeader as &$headerRow) {
if (in_array('Exceptions', $headerRow)) {
$headerRow[1] =
$headerRow[1] == '' ?
$error :
$headerRow[1] . ';' . $error;
}
}
}
$report = array_merge($reportHeader, [['']], $reportColumnNames, $reportItems);
return $response->withCSV($report, [], count($reportItems), APIResponse::RESPONSE_TSV);
}

$reportHeader = $report->getReportHeader();
$reportItems = $report->getReportItems();

return $response->withJson([
'Report_Header' => $reportHeader,
'Report_Items' => $reportItems,
Expand Down
66 changes: 66 additions & 0 deletions classes/components/forms/counter/PKPCounterReportForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* @file classes/components/form/counter/PKPCounterReportForm.php
*
* Copyright (c) 2024 Simon Fraser University
* Copyright (c) 2024 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPCounterReportForm
*
* @ingroup classes_controllers_form
*
* @brief A form for setting a COUNTER R5 report
*/

namespace PKP\components\forms\counter;

use PKP\components\forms\FormComponent;

define('FORM_COUNTER', 'counter');

abstract class PKPCounterReportForm extends FormComponent
{
/** @copydoc FormComponent::$id */
public $id = FORM_COUNTER;

/** @copydoc FormComponent::$method */
public $method = 'GET';

/** Form fields for each COUNTER R5 report */
public $reportFields = [];

/** Set reportFields, that will contain form fields for each COUNTER R5 report */
abstract public function setReportFields(): void;

/**
* Constructor
*
* @param string $action URL to submit the form to
* @param array $locales Supported locales
*/
public function __construct(string $action, array $locales)
{
$this->action = $action;
$this->locales = $locales;

$this->addPage(['id' => 'default', 'submitButton' => ['label' => __('common.download')]]);
$this->addGroup(['id' => 'default', 'pageId' => 'default']);

$this->setReportFields();
}

public function getConfig()
{
$config = parent::getConfig();
$config['reportFields'] = array_map(function ($reportFields) {
return array_map(function ($reportField) {
$field = $this->getFieldConfig($reportField);
$field['groupId'] = 'default';
return $field;
}, $reportFields);
}, $this->reportFields);

return $config;
}
}
53 changes: 53 additions & 0 deletions classes/components/listPanels/PKPCounterReportsListPanel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/**
* @file classes/components/listPanels/PKPCounterReportsListPanel.php
*
* Copyright (c) 2024 Simon Fraser University
* Copyright (c) 2024 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPCounterReportsListPanel
*
* @ingroup classes_components_list
*
* @brief A ListPanel component for viewing, editing and downloading COUNTER R5 reports
*/

namespace PKP\components\listPanels;

use APP\components\forms\counter\CounterReportForm;
use PKP\sushi\CounterR5Report;

class PKPCounterReportsListPanel extends ListPanel
{
/** URL to the API endpoint where items can be retrieved */
public string $apiUrl = '';

/** Form for setting up and downloading a report*/
public ?CounterReportForm $form = null;

/** Query parameters to pass if this list executes GET requests */
public array $getParams = [];

/**
* @copydoc ListPanel::getConfig()
*/
public function getConfig(): array
{
$config = parent::getConfig();

$earliestDate = CounterR5Report::getEarliestDate();
$lastDate = CounterR5Report::getLastDate();

$config = array_merge(
$config,
[
'apiUrl' => $this->apiUrl,
'editCounterReportLabel' => __('manager.statistics.counterR5Report.settings'),
'form' => $this->form->getConfig(),
'usagePossible' => $lastDate > $earliestDate,
]
);
return $config;
}
}
17 changes: 12 additions & 5 deletions classes/core/APIResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,35 @@
class APIResponse extends Response
{
public const RESPONSE_CSV = 'text/csv';
public const RESPONSE_TSV = 'text/tab-separated-values';

/**
* CSV Response
*
* @param integer $maxRows The total amount of rows, that is provided within the onw X-Total-Count header field
*/
public function withCSV(array $rows, array $columns, int $maxRows): self
public function withCSV(array $rows, array $columns, int $maxRows, string $mimeType = self::RESPONSE_CSV): self
{
$separator = ',' ;
if ($mimeType == self::RESPONSE_TSV) {
$separator = "\t";
}
$fp = fopen('php://output', 'wt');
//Add BOM (byte order mark) to fix UTF-8 in Excel
fprintf($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($fp, ['']);
fputcsv($fp, $columns);
if (!empty($columns)) {
fputcsv($fp, [''], $separator);
fputcsv($fp, $columns, $separator);
}
foreach ($rows as $row) {
fputcsv($fp, $row);
fputcsv($fp, $row, $separator);
}
$csvData = stream_get_contents($fp);
fclose($fp);
$this->getBody()->rewind();
$this->getBody()->write($csvData);
$this->withStatus(200);
return $this->withHeader('X-Total-Count', $maxRows)->withHeader('Content-Type', self::RESPONSE_CSV);
return $this->withHeader('X-Total-Count', $maxRows)->withHeader('Content-Type', $mimeType);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions classes/services/queryBuilders/PKPStatsSushiQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ public function getSum(array $groupBy = []): Builder
$q->leftJoin('publications as p', function ($q) {
$q->on('p.submission_id', '=', 'm.submission_id')
->whereIn('p.publication_id', function ($q) {
$q->selectRaw('MIN(p2.publication_id)')->from('publications as p2')->where('p2.status', Submission::STATUS_PUBLISHED);
$q->selectRaw('MIN(p2.publication_id)')
->from('publications as p2')
->where('p2.status', Submission::STATUS_PUBLISHED)
->where('p2.submission_id', '=', DB::raw('m.submission_id'));
});
});
}
Expand Down Expand Up @@ -121,7 +124,10 @@ protected function _getObject(): Builder
$q->leftJoin('publications as p', function ($q) {
$q->on('p.submission_id', '=', 'm.submission_id')
->whereIn('p.publication_id', function ($q) {
$q->selectRaw('MIN(p2.publication_id)')->from('publications as p2')->where('p2.status', Submission::STATUS_PUBLISHED);
$q->selectRaw('MIN(p2.publication_id)')
->from('publications as p2')
->where('p2.status', Submission::STATUS_PUBLISHED)
->where('p2.submission_id', '=', DB::raw('m.submission_id'));
});
});
foreach ($this->yearsOfPublication as $yop) {
Expand Down
Loading

0 comments on commit fbf8089

Please sign in to comment.