diff --git a/.gitignore b/.gitignore index 82c23b6c..dacba595 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ node_modules/ # npm release build public/ +public/* # test results tests/.phpunit.result.cache diff --git a/CodecheckPlugin.php b/CodecheckPlugin.php index a1b03106..4cb1c9aa 100644 --- a/CodecheckPlugin.php +++ b/CodecheckPlugin.php @@ -16,12 +16,16 @@ use PKP\components\forms\FieldOptions; use APP\facades\Repo; use APP\plugins\generic\codecheck\api\v1\CodecheckApiHandler; +use APP\plugins\generic\codecheck\api\v1\CurlApiClient; use PKP\core\JSONMessage; use APP\plugins\generic\codecheck\classes\Constants; +use APP\plugins\generic\codecheck\classes\Workflow\CodecheckStatusHandler; use APP\plugins\generic\codecheck\controllers\page\CodecheckPageHandler; use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleArray; use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleManager; -use APP\core\Request; +use APP\plugins\generic\codecheck\classes\Workflow\CodecheckMetadataHandler; +use PKP\core\Request; +use \Github\Client; class CodecheckPlugin extends GenericPlugin { @@ -71,10 +75,60 @@ public function register($category, $path, $mainContextId = null): bool Hook::add('Template::SubmissionWizard::Section::Review', function($hookName, $params) use ($codecheckWizard) { return $codecheckWizard->addToSubmissionWizardReviewTemplate($hookName, $params); }); + + // Test if we can hook into the publication to block it if codecheck failed + Hook::add('Publication::validatePublish', $this->validateCodecheckStatus(...)); + + // Add Localizations to Codecheck Status Preview + Hook::add('TemplateManager::display', $this->addCodecheckStatusLocalizations(...)); } return $success; } + + public function validateCodecheckStatus(string $hookName, array $args): bool + { + $errors = &$args[0]; + $publication = $args[1]; // sometimes passed by reference depending on version + $request = Application::get()->getRequest(); + $context = $request->getContext(); + $codecheckMetadataHandler = new CodecheckMetadataHandler($request, new Client(), new CurlApiClient()); + $codecheckStatus = CodecheckStatusHandler::getCurrentStatusData($codecheckMetadataHandler->getSubmissionId()); + + CodecheckLogger::debug("Validating CODECHECK before publication!"); + + $codecheckStatusKeysSelected = $this->getSetting($context->getId(), Constants::CODECHECK_STATUS_KEYS_SELECTED); + + if (empty($codecheckStatus)) { + $errors[] = __('plugins.generic.codecheck.status.validation.failed.noStatusSet'); + return false; + } + + if (!in_array($codecheckStatus->status, $codecheckStatusKeysSelected)) { + $errors[] = __('plugins.generic.codecheck.status.validation.failed', [ + 'codecheckStatus' => __($codecheckStatus->status) + ]); + return false; + } + + return true; + } + + public function addCodecheckStatusLocalizations($hookName, $args) { + $templateMgr = $args[0]; + $templateMgr->addJavaScript( + 'codecheck-locale-status', + 'pkp.localeKeys = pkp.localeKeys || {};' . + 'Object.assign(pkp.localeKeys, ' . json_encode( + array_combine( + Constants::CODECHECK_STATUSES, + array_map(fn($status) => __($status), Constants::CODECHECK_STATUSES) + ) + ) . ');', + ['inline' => true, 'contexts' => ['backend']] + ); + return false; + } /** * Setup the `CodecheckApiHandler` @@ -413,9 +467,10 @@ public function setEnabled($enabled, $contextId = null) $result = parent::setEnabled($enabled, $contextId); if ($enabled) { - $migration = new CodecheckSchemaMigration(); - $migration->up(); - $migration->issueLabelsUp(); + $this->migration = new CodecheckSchemaMigration(); + $this->migration->up(); + $this->migration->issueLabelsUp(); + $this->migration->codecheckStatusUp(); } return $result; diff --git a/api/v1/CodecheckApiHandler.php b/api/v1/CodecheckApiHandler.php index afedb097..232e4af6 100644 --- a/api/v1/CodecheckApiHandler.php +++ b/api/v1/CodecheckApiHandler.php @@ -4,9 +4,6 @@ use APP\plugins\generic\codecheck\api\v1\JsonResponse; use APP\core\Request; -use APP\plugins\generic\codecheck\classes\Exceptions\ApiCreateException; -use APP\plugins\generic\codecheck\classes\Exceptions\ApiFetchException; -use APP\plugins\generic\codecheck\classes\Exceptions\NoMatchingIssuesFoundException; use APP\plugins\generic\codecheck\classes\CodecheckRegister\CodecheckGithubRegisterApiClient; use APP\plugins\generic\codecheck\classes\CodecheckRegister\CertificateIdentifierList; use APP\plugins\generic\codecheck\classes\CodecheckRegister\CertificateIdentifier; @@ -16,17 +13,15 @@ use APP\plugins\generic\codecheck\classes\Log\CodecheckLogger; use APP\plugins\generic\codecheck\classes\Constants; use APP\plugins\generic\codecheck\CodecheckPlugin; - use APP\facades\Repo; use \Github\Client; use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleManager; use APP\plugins\generic\codecheck\classes\Exceptions\RoleExceptions\RoleNotFoundException; -use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlInitException; -use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlReadException; use APP\plugins\generic\codecheck\classes\CodecheckRegister\CodecheckIssueLabels; use Exception; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use APP\plugins\generic\codecheck\classes\Workflow\CodecheckStatusHandler; +use Illuminate\Support\Facades\DB; class CodecheckApiHandler { @@ -85,6 +80,16 @@ public function __construct(CodecheckPlugin $plugin, Request $request, Codecheck 'handler' => [$this, 'getGithubRegisterRepositoryUrl'], 'roles' => $roles->readMetadata(), ], + [ + 'route' => 'status', + 'handler' => [$this, 'getCurrentStatus'], + 'roles' => $roles->readMetadata(), + ], + [ + 'route' => 'status/history', + 'handler' => [$this, 'getStatusHistory'], + 'roles' => $roles->readMetadata(), + ], ], 'POST' => [ [ @@ -117,6 +122,16 @@ public function __construct(CodecheckPlugin $plugin, Request $request, Codecheck 'handler' => [$this, 'validateYamlStructure'], 'roles' => $roles->readMetadata(), ], + [ + 'route' => 'status/update', + 'handler' => [$this, 'updateStatus'], + 'roles' => $roles->editMetadata(), + ], + [ + 'route' => 'users/roles/validation', + 'handler' => [$this, 'validateUserAccessRightsToStatus'], + 'roles' => $roles->readMetadata(), + ], ], ]; @@ -259,6 +274,9 @@ private function getCodecheckIssueLabels(): void $githubCustomLabels = $this->plugin->getSetting($context->getId(), Constants::CODECHECK_GITHUB_CUSTOM_LABELS); $codecheckIssueLabels->addLabelArray($githubCustomLabels); + $codecheckStatuses = $this->plugin->getSetting($context->getId(), Constants::CODECHECK_STATUS_KEYS_SELECTED); + error_log(print_r($codecheckStatuses, true)); + // Serve the getCodecheckIssueLabels API route JsonResponse::staticResponse([ 'success' => true, @@ -929,4 +947,141 @@ public function validateYamlStructure(): void 'success' => true, ], 200); } + + public function getCurrentStatus(): void + { + $submissionId = (int) $this->codecheckMetadataHandler->getSubmissionId(); + + $statusRecord = CodecheckStatusHandler::getCurrentStatusData($submissionId); + + if($statusRecord == null) { + JsonResponse::staticResponse([ + 'success' => false, + 'error' => "There doesn't exist any Status in the OJS Databse for this submission Id yet.", + 'statusRecord' => null, + 'allStatuses' => Constants::CODECHECK_STATUSES, + ], 500); + } + + JsonResponse::staticResponse([ + 'success' => true, + 'statusRecord' => $statusRecord, + 'allStatuses' => Constants::CODECHECK_STATUSES, + ], 200); + } + + public function getStatusHistory(): void + { + $submissionId = (int) $this->codecheckMetadataHandler->getSubmissionId(); + + $statusHistory = CodecheckStatusHandler::getStatusDataHistory($submissionId); + + if($statusHistory == null) { + JsonResponse::staticResponse([ + 'success' => false, + 'statusHistory' => $statusHistory, + ], 400); + } + + JsonResponse::staticResponse([ + 'success' => true, + 'statusHistory' => $statusHistory, + ], 200); + } + + public function updateStatus(): void + { + $submissionId = (int) $this->codecheckMetadataHandler->getSubmissionId(); + + $postParams = json_decode(file_get_contents('php://input'), true); + $status = $postParams["status"]; + $userId = $postParams["userId"]; + + if(!is_string($status) || !is_int($userId)) { + JsonResponse::staticResponse([ + 'success' => false, + 'statusRecord' => [ + 'status' => $status, + 'userId' => $userId + ], + 'allStatuses' => Constants::CODECHECK_STATUSES, + 'error' => 'Bad Request: Please provide a Status form of string and a User ID in the form of int.' + ], 400); + } + + if($userId == -1) { + $submissionMetadata = $this->codecheckMetadataHandler->getMetadata($this->request, $submissionId); + if(array_key_exists("error",$submissionMetadata)) { + JsonResponse::staticResponse([ + 'success' => false, + 'error' => $submissionMetadata["error"], + 'allStatuses' => Constants::CODECHECK_STATUSES, + ], 400); + } + $statusUpdate = CodecheckStatusHandler::automaticStatusUpdate($submissionMetadata); + + if($statusUpdate == null) { + JsonResponse::staticResponse([ + 'success' => false, + 'statusRecord' => $statusUpdate, + 'allStatuses' => Constants::CODECHECK_STATUSES, + 'error' => "Status doesn't need to be automatically updated." + ], 200); + } else { + JsonResponse::staticResponse([ + 'success' => true, + 'statusRecord' => $statusUpdate, + 'allStatuses' => Constants::CODECHECK_STATUSES, + ], 200); + } + } + + $statusUpdate = CodecheckStatusHandler::updateStatus($submissionId, $status, $userId); + + if($statusUpdate == false) { + JsonResponse::staticResponse([ + 'success' => true, + 'statusRecord' => [ + 'status' => $status, + 'userId' => $userId + ], + 'allStatuses' => Constants::CODECHECK_STATUSES, + 'error' => "Inserting into the CODECHECK Status Database went wrong." + ], 500); + } + + JsonResponse::staticResponse([ + 'success' => true, + 'statusRecord' => $statusUpdate, + 'allStatuses' => Constants::CODECHECK_STATUSES, + ], 200); + } + + public function validateUserAccessRightsToStatus(): void + { + $postParams = json_decode(file_get_contents('php://input'), true); + $user = $postParams["user"]; + + if(!is_array($user["roles"])) { + JsonResponse::staticResponse([ + 'success' => false, + 'error' => 'Bad Request: Please provide the current User in your request.' + ], 400); + } + + $userRoles = $user["roles"]; + $allowedToAccess = false; + + foreach ($userRoles as $userRole) { + if($userRole == 16) { + $allowedToAccess = true; + break; + } + } + + JsonResponse::staticResponse([ + 'success' => true, + 'userAllowedToAccess' => $allowedToAccess, + ], 200); + } } \ No newline at end of file diff --git a/classes/Constants.php b/classes/Constants.php index 997b9c8a..183b906b 100644 --- a/classes/Constants.php +++ b/classes/Constants.php @@ -22,6 +22,31 @@ class Constants * Basic plugin setting */ public const SETTING_ENABLE_CODECHECK = 'enableCodecheck'; + + /** + * The possible Codecheck Statuses + */ + public const CODECHECK_STATUS_NEEDS_CODECHECKER = 'plugins.generic.codecheck.status.needsCodechecker'; + public const CODECHECK_STATUS_ASSIGNED_CODECHECKER = 'plugins.generic.codecheck.status.assignedCodechecker'; + public const CODECHECK_STATUS_STALLED_AUTHOR = 'plugins.generic.codecheck.status.stalled.author'; + public const CODECHECK_STATUS_STALLED_CODECHECKER = 'plugins.generic.codecheck.status.stalled.codechecker'; + public const CODECHECK_STATUS_COMPLETED_UNSUCCESSFUL = 'plugins.generic.codecheck.status.completed.unsuccessful'; + public const CODECHECK_STATUS_COMPLETED_PARTIAL_REPRODUCTION = 'plugins.generic.codecheck.status.completed.partialReproduction'; + public const CODECHECK_STATUS_COMPLETED_FULL_REPRODUCTION = 'plugins.generic.codecheck.status.completed.fullReproduction'; + public const CODECHECK_STATUS_PUBLISHED_PARTIAL_REPRODUCTION = 'plugins.generic.codecheck.status.publishedCertificate.partialReproduction'; + public const CODECHECK_STATUS_PUBLISHED_FULL_REPRODUCTION = 'plugins.generic.codecheck.status.publishedCertificate.fullReproduction'; + + public const CODECHECK_STATUSES = [ + Constants::CODECHECK_STATUS_NEEDS_CODECHECKER, + Constants::CODECHECK_STATUS_ASSIGNED_CODECHECKER, + Constants::CODECHECK_STATUS_STALLED_AUTHOR, + Constants::CODECHECK_STATUS_STALLED_CODECHECKER, + Constants::CODECHECK_STATUS_COMPLETED_UNSUCCESSFUL, + Constants::CODECHECK_STATUS_COMPLETED_PARTIAL_REPRODUCTION, + Constants::CODECHECK_STATUS_COMPLETED_FULL_REPRODUCTION, + Constants::CODECHECK_STATUS_PUBLISHED_PARTIAL_REPRODUCTION, + Constants::CODECHECK_STATUS_PUBLISHED_FULL_REPRODUCTION, + ]; /** * Plugin settings keys - NEW ADDITIONS @@ -40,4 +65,8 @@ class Constants public const CODECHECK_GITHUB_REGISTER_ISSUE_UPDATE_FIELDS = 'codecheckGithubUpdateFields'; public const CODECHECK_GITHUB_REGISTER_ISSUE_UPDATE_TITLE = 'updateTitle'; public const CODECHECK_GITHUB_REGISTER_ISSUE_UPDATE_BODY = 'updateBody'; + # Codecheck Status + public const CODECHECK_STATUS = 'codecheckStatus'; + public const CODECHECK_STATUSES_SELECTED = 'codecheckStatusesSelected'; + public const CODECHECK_STATUS_KEYS_SELECTED = 'codecheckStatusKeysSelected'; } \ No newline at end of file diff --git a/classes/Settings/SettingsForm.php b/classes/Settings/SettingsForm.php index a35d47da..5a7e86bf 100644 --- a/classes/Settings/SettingsForm.php +++ b/classes/Settings/SettingsForm.php @@ -125,6 +125,14 @@ public function initData(): void ) ?? [] ); + $this->setData( + Constants::CODECHECK_STATUS_KEYS_SELECTED, + $this->plugin->getSetting( + $context->getId(), + Constants::CODECHECK_STATUS_KEYS_SELECTED + ) ?? [] + ); + // Default to true — show the dashboard column unless explicitly disabled $showDashboardColumn = $this->plugin->getSetting( $context->getId(), @@ -166,6 +174,9 @@ public function readInputData(): void Constants::CODECHECK_GITHUB_REGISTER_ISSUE_UPDATE_TITLE, Constants::CODECHECK_GITHUB_REGISTER_ISSUE_UPDATE_BODY, Constants::CODECHECK_GITHUB_REGISTER_ISSUE_UPDATE_FIELDS, + Constants::CODECHECK_STATUS, + Constants::CODECHECK_STATUSES_SELECTED, + Constants::CODECHECK_STATUS_KEYS_SELECTED, ]); parent::readInputData(); @@ -183,7 +194,7 @@ public function fetch($request, $template = null, $display = false): ?string $templateMgr = TemplateManager::getManager($request); $templateMgr->assign('pluginName', $this->plugin->getName()); $templateMgr->assign( - 'githubCustomLabels', + Constants::CODECHECK_GITHUB_CUSTOM_LABELS, $this->getData(Constants::CODECHECK_GITHUB_CUSTOM_LABELS) ?? [] ); $templateMgr->assign('codecheckModes', [ @@ -191,11 +202,18 @@ public function fetch($request, $template = null, $display = false): ?string 'opt-out' => __('plugins.generic.codecheck.settings.mode.opt.out'), 'mandatory' => __('plugins.generic.codecheck.settings.mode.mandatory'), ]); + $templateMgr->assign( 'showDashboardColumn', $this->getData(Constants::CODECHECK_SHOW_DASHBOARD_COLUMN) ); + $templateMgr->assign( + Constants::CODECHECK_STATUSES_SELECTED, + (array) $this->getData(Constants::CODECHECK_STATUSES_SELECTED) ?? [] + ); + $templateMgr->assign('codecheckStatuses', Constants::CODECHECK_STATUSES); + return parent::fetch($request, $template, $display); } @@ -266,6 +284,12 @@ public function execute(...$functionArgs): mixed )) ); + $this->plugin->updateSetting( + $context->getId(), + Constants::CODECHECK_STATUS_KEYS_SELECTED, + (array) $this->getData(Constants::CODECHECK_STATUS_KEYS_SELECTED) + ); + $this->plugin->updateSetting( $context->getId(), Constants::CODECHECK_SHOW_DASHBOARD_COLUMN, diff --git a/classes/Workflow/CodecheckMetadataHandler.php b/classes/Workflow/CodecheckMetadataHandler.php index ac091343..2175e256 100644 --- a/classes/Workflow/CodecheckMetadataHandler.php +++ b/classes/Workflow/CodecheckMetadataHandler.php @@ -64,7 +64,7 @@ public function getMetadata($request, $submissionId): array $metadata = DB::table('codecheck_metadata') ->where('submission_id', $submissionId) ->first(); - + $response = [ 'submissionId' => $submissionId, 'submission' => [ diff --git a/classes/Workflow/CodecheckStatusHandler.php b/classes/Workflow/CodecheckStatusHandler.php new file mode 100644 index 00000000..74d68bf2 --- /dev/null +++ b/classes/Workflow/CodecheckStatusHandler.php @@ -0,0 +1,59 @@ +where('submission_id', $submissionId) + ->orderBy('timestamp', 'desc') + ->orderBy('status_id', 'desc') + ->first(); + } + + public static function getStatusDataHistory(int $submissionId): object|null { + return DB::table('codecheck_status') + ->where('submission_id', $submissionId) + ->orderBy('timestamp', 'desc') + ->orderBy('status_id', 'desc') + ->get(); + } + + public static function updateStatus(int $submissionId, string $status, int $userId): object|false { + $newRecord = [ + 'submission_id' => $submissionId, + 'status' => $status, + 'timestamp' => now(), + 'user_id' => $userId + ]; + + $insertWorked = DB::table('codecheck_status')->insert($newRecord); + + if(!$insertWorked) { + return false; + } + + return CodecheckStatusHandler::getCurrentStatusData($submissionId); + } + + public static function automaticStatusUpdate(array $submissionMetadata): object|null { + $submissionId = $submissionMetadata['submissionId']; + $statusHistory = CodecheckStatusHandler::getStatusDataHistory($submissionId); + + error_log("Status History: " . json_encode($statusHistory)); + error_log("Status History count: " . $statusHistory->count()); + error_log("Is null: " . ($statusHistory === null ? 'true' : 'false')); + + if($statusHistory == null || $statusHistory->count() < 2) { + $status = Constants::CODECHECK_STATUS_NEEDS_CODECHECKER; + if(!empty($submissionMetadata['codecheck']['codecheckers'])) { + $status = Constants::CODECHECK_STATUS_ASSIGNED_CODECHECKER; + } + return CodecheckStatusHandler::updateStatus($submissionId, $status, -1); + } + return CodecheckStatusHandler::getCurrentStatusData($submissionId); + } +} \ No newline at end of file diff --git a/classes/migration/CodecheckSchemaMigration.php b/classes/migration/CodecheckSchemaMigration.php index bfd40141..78f5705a 100644 --- a/classes/migration/CodecheckSchemaMigration.php +++ b/classes/migration/CodecheckSchemaMigration.php @@ -44,6 +44,22 @@ public function issueLabelsUp(): void }); } } + + public function codecheckStatusUp(): void + { + if (!Schema::hasTable('codecheck_status')) { + Schema::create('codecheck_status', function (Blueprint $table) { + $table->bigInteger('status_id')->autoIncrement()->primary(); + $table->bigInteger('submission_id'); + $table->foreign('submission_id', 'codecheck_status_metadata')->references('submission_id')->on('codecheck_metadata')->onDelete('cascade'); + $table->string('status', 300); + $table->timestamp('timestamp'); + $table->bigInteger('user_id'); + $table->timestamps(); + $table->index('status_id'); + }); + } + } private function createCodecheckGenres(): void { @@ -77,6 +93,7 @@ private function createCodecheckGenres(): void public function down(): void { + Schema::dropIfExists('codecheck_status'); Schema::dropIfExists('codecheck_metadata'); } diff --git a/css/codecheck.css b/css/codecheck.css index 9267dd7a..a86adef7 100644 --- a/css/codecheck.css +++ b/css/codecheck.css @@ -315,4 +315,105 @@ textarea[name="dataAvailabilityStatement"] { border-radius: 3px !important; height: 2.5rem !important; background: #fff; +} + +/* Dropdown with Checkbox selects */ +.settings-droptown { + font-size: 14px; + padding: 6px; + border: 1px solid #ccc; + border-radius: 3px; + height: 2.5rem; + width: 100%; + background: #fff; +} + +.settings-droptown.dropdown { + overflow: visible; + color: inherit; + padding: 0 !important; + position: relative; +} + +fieldset:disabled .settings-droptown.dropdown .dropbtn { + /* Centeres the Text in the select */ + text-align: center; + text-align-last: center; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: none !important; /* removes arrow background */ + background-color: #868686; + color: #ffffff; + cursor: not-allowed; + pointer-events: none; + opacity: 0.6; + font-weight: 600; +} + +.settings-droptown.dropdown .dropbtn { + font-size: 14px; + padding-left: 6px; + padding-right: 6px; + padding-bottom: 0; + padding-top: 0; + height: 100%; + border: none; + outline: none; + background-color: inherit; + font-family: inherit; + margin: 0; + width: inherit; + text-align: left; +} + +.settings-droptown.dropdown:hover .dropbtn { + background-color: #eee; +} + +.settings-droptown .dropdown-content { + display: none; + position: absolute; + background-color: #fff; + border-radius: 8px; + border: 1px solid #ccc; + min-width: 160px; + z-index: 1; + padding: 10px; + top: 100%; + bottom: auto; +} + +.settings-droptown.dropdown-up .dropdown-content { + top: auto; + bottom: 100%; +} + +fieldset:disabled .settings-droptown .dropdown-content { + background-color: #868686; + border: 3px solid #ccc; + font-weight: 600; + color: #fff; +} + +.settings-droptown .dropdown-content .dropdown-checkbox-input { + float: none; + text-decoration: none; + display: block; + text-align: left; + outline: none; + border: none; + width: 100%; + padding: 0; +} + +.settings-droptown.dropdown:hover .dropdown-content { + display: block; +} + +.settings-droptown.dropdown .dropdown-content label { + font-style: italic; + margin-left: 5px; + color: inherit; + display: inline-block !important; } \ No newline at end of file diff --git a/cypress/tests/component/CodecheckReviewDisplay.cy.js b/cypress/tests/component/CodecheckReviewDisplay.cy.js index 82bccc2e..fa2f995b 100644 --- a/cypress/tests/component/CodecheckReviewDisplay.cy.js +++ b/cypress/tests/component/CodecheckReviewDisplay.cy.js @@ -2,6 +2,17 @@ import '../../support/pkp-mock.js'; import CodecheckReviewDisplay from '../../../resources/js/Components/CodecheckReviewDisplay.vue'; describe('CodecheckReviewDisplay Component', () => { + beforeEach(() => { + let status = 'plugins.generic.codecheck.status.needsCodechecker'; + + cy.intercept('GET', '**/codecheck/status*', { + statusCode: 200, + body: { + statusRecord: { status: status } + } + }).as('getStatus'); + }); + it('shows not opted in message when codecheckOptIn is false', () => { cy.mount(CodecheckReviewDisplay, { props: { @@ -15,39 +26,28 @@ describe('CodecheckReviewDisplay Component', () => { cy.get('.codecheck-info').should('not.exist'); }); - it('shows pending status when opted in but no metadata', () => { - cy.mount(CodecheckReviewDisplay, { - props: { - submission: { - codecheckOptIn: true, - codecheckMetadata: {} - } - } - }); - - cy.get('.status-pending').should('exist'); - cy.contains('plugins.generic.codecheck.status.pending').should('exist'); - }); + it('shows status', () => { + let status = 'plugins.generic.codecheck.status.needsCodechecker'; + + cy.intercept('GET', '**/codecheck/status*', { + body: { statusRecord: { status: status } } + }).as('getStatus'); - it('shows in-progress status with partial metadata', () => { cy.mount(CodecheckReviewDisplay, { props: { submission: { + id: 1, codecheckOptIn: true, - codecheckMetadata: { - configVersion: 'latest', - manifest: [{ file: 'output.png' }], - codecheckers: [{ name: 'John Doe' }] - } + codecheckMetadata: {} } } }); - cy.get('.status-in-progress').should('exist'); - cy.contains('plugins.generic.codecheck.status.inProgress').should('exist'); + cy.wait('@getStatus'); + cy.contains(status).should('exist'); }); - it('shows complete status with full metadata', () => { + it('shows complete information with full metadata', () => { cy.mount(CodecheckReviewDisplay, { props: { submission: { @@ -67,7 +67,6 @@ describe('CodecheckReviewDisplay Component', () => { } }); - cy.get('.status-complete').should('exist'); cy.contains('CODECHECK-2024-001').should('exist'); cy.contains('John Doe').should('exist'); cy.contains('0000-0001-2345-6789').should('exist'); diff --git a/locale/en/locale.po b/locale/en/locale.po index cb727fd8..bb08ba1a 100644 --- a/locale/en/locale.po +++ b/locale/en/locale.po @@ -20,6 +20,9 @@ msgstr "No" msgid "plugins.generic.codecheck.ok" msgstr "Ok" +msgid "plugins.generic.codecheck.common.reload" +msgstr "Reload" + msgid "plugins.generic.codecheck.request.failed" msgstr "Request failed" @@ -48,7 +51,7 @@ msgid "plugins.generic.codecheck.settings.enableCodecheck" msgstr "Enable CODECHECK for this journal" msgid "plugins.generic.codecheck.settings.mode" -msgstr "Select the CODECHECK mode used during the submission of a paper" +msgstr "Select the CODECHECK mode for submissions" msgid "plugins.generic.codecheck.settings.mode.opt.in" msgstr "opt-in by the author" @@ -110,6 +113,18 @@ msgstr "Update the Issue Title (containing the article's authors)" msgid "plugins.generic.codecheck.settings.updateIssue.body" msgstr "Update the Issue Description (containing the article, codechecker and repository information)" +msgid "plugins.generic.codecheck.settings.status" +msgstr "What status must a check have for a submission to proceed with the publication?" + +msgid "plugins.generic.codecheck.settings.status.description" +msgstr "Please select the minimum statuses required for a CODECHECK, before a submission is allowed to be published" + +msgid "plugins.generic.codecheck.settings.status.selectStatuses" +msgstr "Select Status(es)" + +msgid "plugins.generic.codecheck.settings.status.noBlock" +msgstr "Ignore the CODECHECK status and never block Publication" + msgid "plugins.generic.codecheck.title" msgstr "CODECHECK" @@ -356,17 +371,75 @@ msgstr "CODECHECK Information" msgid "plugins.generic.codecheck.status" msgstr "Status" +msgid "plugins.generic.codecheck.status.history" +msgstr "CODECHECK Status History" + +msgid "plugins.generic.codecheck.status.history.timestamp" +msgstr "Timestamp" + +msgid "plugins.generic.codecheck.status.history.user" +msgstr "Changed By" + +msgid "plugins.generic.codecheck.status.buttons.change" +msgstr "Change" + +msgid "plugins.generic.codecheck.status.buttons.history" +msgstr "History" + +msgid "plugins.generic.codecheck.status.modal.title" +msgstr "Change CODECHECK Status" + +msgid "plugins.generic.codecheck.status.modal.label" +msgstr "Select the new CODECHECK Status for this Submission" + +msgid "plugins.generic.codecheck.status.modal.change" +msgstr "Change" + msgid "plugins.generic.codecheck.status.complete" msgstr "Complete" + +msgid "plugins.generic.codecheck.status.validation.failed" +msgstr "The mandatory CODECHECK for this submission has a status ({$codecheckStatus}), which is not allowed for publication by the journal. Please get in touch with the codechecker(s) to resolve all errors, before this submission can be published!" + +msgid "plugins.generic.codecheck.status.validation.failed.noStatusSet" +msgstr "The mandatory CODECHECK for this submission has no status (so it was not yet started), which is not allowed for publication by the journal. Please get in touch with the codechecker(s) to resolve all errors, before this submission can be published!" + +msgid "plugins.generic.codecheck.status.pending" +msgstr "Pending" + +msgid "plugins.generic.codecheck.status.needsCodechecker" +msgstr "needs codechecker" + +msgid "plugins.generic.codecheck.status.assignedCodechecker" +msgstr "codechecker assigned" + +msgid "plugins.generic.codecheck.status.stalled.author" +msgstr "stalled (author)" + +msgid "plugins.generic.codecheck.status.stalled.codechecker" +msgstr "stalled (codechecker)" + +msgid "plugins.generic.codecheck.status.completed.unsuccessful" +msgstr "completed: unsuccessful" + +msgid "plugins.generic.codecheck.status.completed.partialReproduction" +msgstr "completed: partial reproduction" + +msgid "plugins.generic.codecheck.status.completed.fullReproduction" +msgstr "completed: full reproduction" + +msgid "plugins.generic.codecheck.status.publishedCertificate.partialReproduction" +msgstr "published certificate: partial reproduction" + +msgid "plugins.generic.codecheck.status.publishedCertificate.fullReproduction" +msgstr "published certificate: full reproduction" + msgid "plugins.generic.codecheck.dataAvailability.placeholder" msgstr "Describe how your data and code are available..." msgid "plugins.generic.codecheck.review.title" msgstr "CODECHECK Information" -msgid "plugins.generic.codecheck.status.pending" -msgstr "Pending" - msgid "plugins.generic.codecheck.notYetAssigned" msgstr "Not yet assigned" @@ -523,6 +596,9 @@ msgstr "Cancel" msgid "plugins.generic.codecheck.modal.add" msgstr "Add" +msgid "plugins.generic.codecheck.modal.change" +msgstr "Change" + msgid "plugins.generic.codecheck.review.configVersion" msgstr "Config Version" diff --git a/registry/uiLocaleKeysBackend.json b/registry/uiLocaleKeysBackend.json index a5783fe2..d481ba68 100644 --- a/registry/uiLocaleKeysBackend.json +++ b/registry/uiLocaleKeysBackend.json @@ -86,6 +86,7 @@ "plugins.generic.codecheck.markAsOutputTitle", "plugins.generic.codecheck.modal.add", "plugins.generic.codecheck.modal.cancel", + "plugins.generic.codecheck.modal.change", "plugins.generic.codecheck.modal.close", "plugins.generic.codecheck.no", "plugins.generic.codecheck.noDataFound", @@ -129,11 +130,15 @@ "plugins.generic.codecheck.source.label", "plugins.generic.codecheck.source.placeholder", "plugins.generic.codecheck.status", - "plugins.generic.codecheck.status.complete", - "plugins.generic.codecheck.status.inProgress", + "plugins.generic.codecheck.status.buttons.change", + "plugins.generic.codecheck.status.buttons.history", + "plugins.generic.codecheck.status.history", + "plugins.generic.codecheck.status.history.timestamp", + "plugins.generic.codecheck.status.history.user", "plugins.generic.codecheck.status.marked", + "plugins.generic.codecheck.status.modal.label", + "plugins.generic.codecheck.status.modal.title", "plugins.generic.codecheck.status.notMarked", - "plugins.generic.codecheck.status.pending", "plugins.generic.codecheck.validation.certificateRequired", "plugins.generic.codecheck.validation.codecheckersRequired", "plugins.generic.codecheck.validation.githubIssueLinkRequired", diff --git a/resources/js/Components/CodecheckMetadataForm.vue b/resources/js/Components/CodecheckMetadataForm.vue index 12375ffb..b8208f33 100644 --- a/resources/js/Components/CodecheckMetadataForm.vue +++ b/resources/js/Components/CodecheckMetadataForm.vue @@ -211,7 +211,7 @@
- +
@@ -819,6 +819,15 @@ export default { } }, + triggerCodecheckStatusUpdateEvent() { + const pinia = pkp.registry._piniaInstance; + const workflowStore = pinia?._s?.get('workflow'); + + if (workflowStore?.codecheck) { + workflowStore.codecheck.statusUpdateEvent = Date.now(); + } + }, + async saveMetadata() { if (!this.validateForm()) { return; @@ -876,6 +885,7 @@ export default { this.hasUnsavedChanges = false; + this.triggerCodecheckStatusUpdateEvent(); this.triggerRegisterIssueDisplayUpdateEvent(); this.showMessage(this.t('plugins.generic.codecheck.savedSuccessfully'), 'success'); @@ -1254,10 +1264,10 @@ export default { this.showMessage(this.t('plugins.generic.codecheck.validation.manifestRequired'), 'error'); return false; } - if (this.metadata.codecheckers.length === 0) { + /*if (this.metadata.codecheckers.length === 0) { this.showMessage(this.t('plugins.generic.codecheck.validation.codecheckersRequired'), 'error'); return false; - } + }*/ if (!this.metadata.certificate) { this.showMessage(this.t('plugins.generic.codecheck.validation.certificateRequired'), 'error'); return false; @@ -1541,11 +1551,12 @@ export default { .codecheck-metadata-form .field-group { margin-bottom: 2rem; + background-color: #fff; } .codecheck-metadata-form .form-details .field-group { - border: 2px solid #ccc; - padding: 1.5rem 1.5rem; + border: 2px solid #ccc; + padding: 1.5rem 1.5rem; } .codecheck-metadata-form .field-header { @@ -2143,4 +2154,8 @@ fieldset:disabled .certificate-identifier-select .dropdown-content { margin-left: 5px; color: inherit; } + +.filePanel__header { + background-color: #f3f3f3; +} \ No newline at end of file diff --git a/resources/js/Components/CodecheckReviewDisplay.vue b/resources/js/Components/CodecheckReviewDisplay.vue index 0250dd9b..fa55270d 100644 --- a/resources/js/Components/CodecheckReviewDisplay.vue +++ b/resources/js/Components/CodecheckReviewDisplay.vue @@ -1,95 +1,134 @@ \ No newline at end of file diff --git a/resources/js/main.js b/resources/js/main.js index e42ae62d..14ebeb3c 100644 --- a/resources/js/main.js +++ b/resources/js/main.js @@ -2,8 +2,9 @@ import { createApp, reactive } from 'vue'; import CodecheckManifestFiles from "./Components/CodecheckManifestFiles.vue"; import CodecheckRepositoryList from "./Components/CodecheckRepositoryList.vue"; import CodecheckReviewDisplay from "./Components/CodecheckReviewDisplay.vue"; -import CodecheckMetadataForm from "./Components/CodecheckMetadataForm.vue"; import CodecheckDataAndSoftwareAvailability from "./Components/CodecheckDataAndSoftwareAvailability.vue"; +import CodecheckMetadataForm from './Components/CodecheckMetadataForm.vue'; +import CodecheckStatusForm from './Components/CodecheckStatusForm.vue'; import CodecheckGithubIssueDisplay from "./Components/CodecheckGithubIssueDisplay.vue"; pkp.registry.registerComponent("CodecheckReviewDisplay", CodecheckReviewDisplay); @@ -11,6 +12,7 @@ pkp.registry.registerComponent("CodecheckMetadataForm", CodecheckMetadataForm); pkp.registry.registerComponent("CodecheckManifestFiles", CodecheckManifestFiles); pkp.registry.registerComponent("CodecheckRepositoryList", CodecheckRepositoryList); pkp.registry.registerComponent("CodecheckDataAndSoftwareAvailability", CodecheckDataAndSoftwareAvailability); +pkp.registry.registerComponent("CodecheckStatusForm", CodecheckStatusForm); pkp.registry.registerComponent("CodecheckGithubIssueDisplay", CodecheckGithubIssueDisplay); const { useLocalize } = pkp.modules.useLocalize; @@ -23,6 +25,7 @@ pkp.registry.storeExtend("workflow", (piniaContext) => { registerIssueDisplayUpdateEvent: null, certificateIdentifier: null, issue: null, + statusUpdateEvent: null, }); workflowStore.extender.extendFn("getMenuItems", (menuItems, args) => { @@ -102,6 +105,13 @@ pkp.registry.storeExtend("workflow", (piniaContext) => { args?.selectedMenuState?.stageId === 999 ) { return [ + { + component: "CodecheckStatusForm", + props: { + submission: submission, + canEdit: true + }, + }, { component: "CodecheckGithubIssueDisplay", props: { diff --git a/templates/settings.tpl b/templates/settings.tpl index eb847e8f..d4d83fe9 100644 --- a/templates/settings.tpl +++ b/templates/settings.tpl @@ -70,6 +70,20 @@ ); }); }); + + $('.settings-droptown.dropdown').on('mouseenter', function() { + const $dropdown = $(this); + const $content = $dropdown.find('.dropdown-content'); + const rect = this.getBoundingClientRect(); + const contentHeight = $content.outerHeight() || 200; + const spaceBelow = window.innerHeight - rect.bottom; + + if (spaceBelow < contentHeight) { + $dropdown.addClass('dropdown-up'); + } else { + $dropdown.removeClass('dropdown-up'); + } + }); {/literal} @@ -265,6 +279,38 @@ label="plugins.generic.codecheck.settings.updateIssue.body" } {/fbvFormSection} + + {* Block Publication, when CODECHECK has specific status *} + {fbvFormSection + list=true + } +
+ +
+ +
+ +
+ {/fbvFormSection} + {* TODO: Add more settings in future development *} {* - ORCID integration settings *} {* - Email template settings *}