-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/retrieve reserve identifiers #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
42cb404
3bc9807
5041366
63eb5b5
0a0e2e5
00f0651
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# Passwords, Tokens, API keys | ||
.env | ||
|
||
# CakePHP 3 | ||
|
||
/vendor/* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,339 @@ | ||
<?php | ||
|
||
require __DIR__ . '/vendor/autoload.php'; | ||
|
||
use Github\Client; | ||
use Dotenv\Dotenv; | ||
|
||
// Load .env variables | ||
$dotenv = Dotenv::createImmutable(__DIR__); | ||
$dotenv->load(); | ||
|
||
class Set | ||
{ | ||
protected $arraySet = []; | ||
|
||
public function insert($element): void | ||
{ | ||
foreach ($this->arraySet as $listElement) { | ||
if($listElement->toStr() == $element->toStr()) { | ||
return; | ||
} | ||
} | ||
$this->arraySet[] = $element; | ||
} | ||
|
||
public function insertArray(array $array): void | ||
{ | ||
foreach ($array as $element) { | ||
$this->insert($element); | ||
} | ||
} | ||
|
||
public function getArray(): array | ||
{ | ||
return $this->arraySet; | ||
} | ||
} | ||
|
||
enum CodecheckType | ||
{ | ||
case checkNL; | ||
case community; | ||
case conference_workshop; | ||
case institution; | ||
case journal; | ||
case lifecycleJournal; | ||
|
||
public function labels(): array | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be a fixed list, but retrieved from the CODECHECK register (https://codecheck.org.uk/register/venues/index.json) or from the GitHub API (https://api.github.com/repos/codecheckers/register/labels). The problem with the latter is, that there are labels that are irrelevant for the OJS plugin. So, we probably need a configuration option that, for each journal, allows to select labels that are set to checks of that journal. Can you please create that one? There should be a selection of existing labels from GitHub. |
||
{ | ||
return match($this) { | ||
self::checkNL => ['check-nl', 'community'], | ||
self::community => ['community'], | ||
self::conference_workshop => ['conference/workshop'], | ||
self::institution => ['institution'], | ||
self::journal => ['journal'], | ||
self::lifecycleJournal => ['lifecycle journal'], | ||
}; | ||
} | ||
} | ||
|
||
class CertificateIdentifier | ||
{ | ||
private $year; | ||
private $id; | ||
|
||
public function setYear(int $year): void | ||
{ | ||
$this->year = $year; | ||
} | ||
|
||
public function setId(int $id): void | ||
{ | ||
$this->id = $id; | ||
} | ||
|
||
public function getYear(): int | ||
{ | ||
return $this->year; | ||
} | ||
|
||
public function getId(): int | ||
{ | ||
return $this->id; | ||
} | ||
|
||
// Factory Method for Certificate Identifier | ||
static function fromStr(string $identifier_str): CertificateIdentifier | ||
{ | ||
// split Identifier String at '-' | ||
list($year, $id) = explode('-', $identifier_str); | ||
// create new instance of $certificateIdentifier | ||
$certificateIdentifier = new CertificateIdentifier(); | ||
// set year and id (cast to int from str) | ||
$certificateIdentifier->setYear((int) $year); | ||
$certificateIdentifier->setId((int) $id); | ||
// return new instance of $certificateIdentifier | ||
return $certificateIdentifier; | ||
} | ||
|
||
// Factory Method for new unique Identifier | ||
static function newUniqueIdentifier(CodecheckRegister $codecheckRegister): CertificateIdentifier | ||
{ | ||
$latest_identifier = $codecheckRegister->getNewestIdentifier(); | ||
$current_year = (int) date("Y"); | ||
|
||
$new_identifier = new CertificateIdentifier(); | ||
|
||
// different year, so this is the first CODECHECK certificate of the year -> id 001 | ||
if($current_year != $latest_identifier->getYear()) { | ||
// configure new Identifier | ||
$new_identifier->setYear($current_year); | ||
$new_identifier->setId(1); | ||
return $new_identifier; | ||
} | ||
|
||
// get the latest id | ||
$latest_id = (int) $latest_identifier->getId(); | ||
// increment the latest id by one to get a new unique one | ||
$latest_id++; | ||
// configure new Identifier | ||
$new_identifier->setYear($latest_identifier->getYear()); | ||
$new_identifier->setId($latest_id); | ||
return $new_identifier; | ||
} | ||
|
||
public function toStr(): string | ||
{ | ||
// pad with leading zeros (3 digits) in case number doesn't have 3 digits already | ||
return $this->year . '-' . str_pad($this->id, 3, '0', STR_PAD_LEFT);; | ||
} | ||
} | ||
|
||
class CodecheckRegister extends Set | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not so happy with the name here, because this set is only about identifiers of the register. Please rename. If you want to create a PHP representation of the whole register, then you should base it on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I maybe just call it something like |
||
{ | ||
// Factory Method to create a new CodecheckRegister from a GitHub API fetch | ||
static function fromApi( | ||
CodecheckRegisterGithubIssuesApiParser $apiParser | ||
): CodecheckRegister { | ||
$newCodecheckRegister = new CodecheckRegister(); | ||
|
||
// fetch API | ||
$apiParser->fetchApi(); | ||
|
||
foreach ($apiParser->getIssues() as $issue) { | ||
// raw identifier (can still have ranges of identifiers); | ||
$rawIdentifier = getRawIdentifier($issue['title']); | ||
|
||
// append to all identifiers in new Register | ||
$newCodecheckRegister->appendToCertificateIdList($rawIdentifier); | ||
} | ||
|
||
// return the new Register | ||
return $newCodecheckRegister; | ||
} | ||
|
||
public function appendToCertificateIdList(string $rawIdentifier): void | ||
{ | ||
// list of certificate identifiers in range | ||
$idRange = []; | ||
|
||
// if it is a range | ||
if(strpos($rawIdentifier, '/')) { | ||
// split into "fromIdStr" and "toIdStr" | ||
list($fromIdStr, $toIdStr) = explode('/', $rawIdentifier); | ||
|
||
$from_identifier = CertificateIdentifier::fromStr($fromIdStr); | ||
$to_identifier = CertificateIdentifier::fromStr($toIdStr); | ||
|
||
// append to $idRange list | ||
for ($id_count = $from_identifier->getId(); $id_count <= $to_identifier->getId(); $id_count++) { | ||
$new_identifier = new CertificateIdentifier(); | ||
$new_identifier->setYear($from_identifier->getYear()); | ||
$new_identifier->setId($id_count); | ||
// append new identifier | ||
$idRange[] = $new_identifier; | ||
} | ||
} | ||
// if it isn't a list then just append on identifier | ||
else { | ||
$new_identifier = CertificateIdentifier::fromStr($rawIdentifier); | ||
$idRange[] = $new_identifier; | ||
} | ||
|
||
// append to all certificate identifiers | ||
$this->insertArray($idRange); | ||
} | ||
|
||
// sort ascending Certificate Identifiers | ||
public function sortAsc(): void | ||
{ | ||
usort($this->arraySet, function($a, $b) { | ||
// First, compare year | ||
if ($a->getYear() !== $b->getYear()) { | ||
return $a->getYear() <=> $b->getYear(); | ||
} | ||
// If years are equal, compare ID | ||
return $a->getId() <=> $b->getId(); | ||
}); | ||
} | ||
|
||
public function sortDesc(): void | ||
{ | ||
usort($this->arraySet, function($a, $b) { | ||
// First, compare year descending | ||
if ($a->getYear() !== $b->getYear()) { | ||
return $b->getYear() <=> $a->getYear(); | ||
} | ||
// If years are equal, compare ID descending | ||
return $b->getId() <=> $a->getId(); | ||
}); | ||
} | ||
|
||
public function getNumberOfIdentifiers(): int | ||
{ | ||
return count($this->arraySet); | ||
} | ||
|
||
// return the latest identifier | ||
public function getNewestIdentifier(): CertificateIdentifier | ||
{ | ||
$this->sortDesc(); | ||
// get first element of sort descending -> newest element | ||
return $this->arraySet[0]; | ||
} | ||
|
||
public function toStr(): string | ||
{ | ||
$return_str = "Certificate Identifiers:\n"; | ||
foreach ($this->arraySet as $id) { | ||
$return_str = $return_str . $id->toStr() . "\n"; | ||
} | ||
return $return_str; | ||
} | ||
} | ||
|
||
// get the certificate ID from the issue description | ||
function getRawIdentifier(string $title): string | ||
{ | ||
$title = strtolower($title); // convert whole title to lowercase | ||
|
||
//$title = "Arabsheibani, Winter, Tomko | 2025-026/2025-029"; | ||
|
||
if (strpos($title, '|') !== false) { | ||
// find the last "|" | ||
$seperator = strrpos($title, '|'); | ||
// move one position forwards (so we get character after '|') | ||
$seperator++; | ||
|
||
// Find where the next line break occurs after "certificate" | ||
$rawIdentifier = substr($title, $seperator); | ||
// remove white spaces | ||
$rawIdentifier = preg_replace('/[\s]+/', '', $rawIdentifier); | ||
} | ||
|
||
return $rawIdentifier; | ||
} | ||
|
||
// api call | ||
class CodecheckRegisterGithubIssuesApiParser | ||
{ | ||
private $issues = []; | ||
private $client; | ||
|
||
function __construct() | ||
{ | ||
$this->client = new Client(); | ||
} | ||
|
||
public function fetchApi(): void | ||
{ | ||
$allissues = $this->client->api('issue')->all('codecheckers', 'register', [ | ||
'state' => 'all', // 'open', 'closed', or 'all' | ||
'labels' => 'id assigned', // label | ||
'sort' => 'updated', | ||
'direction' => 'desc', | ||
'per_page' => 100, // get all issues in page | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's start with a lower number of issues, say... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if there is by any chance no issue with the correct format inside these 20? The chance of that happening are very slim, but theoretically it would be possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could just return an error and require editors to create the issue manually, but I do not like that. Having a iterative approach here (retrieve 20, if no clear data then retrieve 20 more if need be, etc.) should work, and in most cases we can give a quick response in the UI then. |
||
]); | ||
|
||
foreach ($allissues as $issue) { | ||
// check if this issue has the certificate identifier in the title | ||
if(strpos($issue['title'], '|') !== false) { | ||
$this->issues[] = $issue; | ||
} | ||
} | ||
} | ||
|
||
public function addIssue( | ||
CertificateIdentifier $certificateIdentifier, | ||
CodecheckType $codecheckType | ||
): void { | ||
$token = $_ENV['CODECHECK_REGISTER_GITHUB_TOKEN']; | ||
|
||
$this->client->authenticate($token, null, Client::AUTH_ACCESS_TOKEN); | ||
|
||
$repositoryOwner = 'dxL1nus'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use this repo for your testing: https://github.com/codecheckers/testing-dev-register You can also create the existing labels etc. there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does it work with the GitHub Token there? Can I use my own one or do I need one especially provided to me to work for that repository? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use your own for your local development, you're not sharing that in the repository anyway. For our demo server I can create one using a CODECHECK user, and other users of the plugin will probably have to add their own token. |
||
$repositoryName = 'dxL1nus'; | ||
$issueTitle = 'New CODECHECK | ' . $certificateIdentifier->toStr(); | ||
$issueBody = ''; | ||
$labels = ['id assigned']; | ||
Comment on lines
+297
to
+299
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this is information that will have to be provided by the plugin and the journal configuration, right? Please think about a useful function/internal API or how a class could look like that provides all information that can be configured by a journal or provided from the submission (author name, unless anonymous; repo URL for the body; assigned codechecker, if already known; ...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would only work if we keep the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ha, good point. So let's go with "minimal information for reservation" first. Please create an issue about posting relevant information for the check in the issue. Probably we need something like an "Update information in register issue" workflow ? |
||
|
||
$labels = array_merge($labels, $codecheckType->labels()); | ||
|
||
$issue = $this->client->api('issue')->create( | ||
$repositoryOwner, | ||
$repositoryName, | ||
[ | ||
'title' => $issueTitle, | ||
'body' => $issueBody, | ||
'labels' => $labels | ||
] | ||
); | ||
} | ||
|
||
public function getIssues(): array | ||
{ | ||
return $this->issues; | ||
} | ||
} | ||
|
||
|
||
// CODECHECK GitHub Issue Register API parser | ||
$apiParser = new CodecheckRegisterGithubIssuesApiParser(); | ||
|
||
// CODECHECK Register with list of all identifiers in range | ||
$codecheckRegister = CodecheckRegister::fromApi($apiParser); | ||
|
||
// print Certificate Identifier list | ||
$codecheckRegister->sortDesc(); | ||
echo $codecheckRegister->toStr(); | ||
|
||
echo $codecheckRegister->getNewestIdentifier()->toStr() . "\n"; | ||
|
||
$new_identifier = CertificateIdentifier::newUniqueIdentifier($codecheckRegister); | ||
|
||
$apiParser->addIssue($new_identifier, CodecheckType::checkNL); | ||
|
||
echo "Added new issue with identifier: " . $new_identifier->toStr() . "\n"; | ||
|
||
//echo "{$num_of_issues}"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"require": { | ||
"knplabs/github-api": "^3.0", | ||
"guzzlehttp/guzzle": "^7.0.1", | ||
"http-interop/http-factory-guzzle": "^1.0", | ||
"vlucas/phpdotenv": "^5.6" | ||
}, | ||
"config": { | ||
"allow-plugins": { | ||
"php-http/discovery": true | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to add different options here without a new release of the plugin, so this cannot be a fixed enum.
Note that you are mixing types and venues here, too.
The best way to get all types is probably to look at the property
type
inregister.json
: https://codecheck.org.uk/register/register.json