Skip to content
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

feat: ✨ validation status + trigger bot rerun full validation #56

Merged
merged 6 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions bot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ export default async (app, { getRouter }) => {
app.on("issues.edited", async (context) => {
const issueBody = context.payload.issue.body;
const issueTitle = context.payload.issue.title;
const { repository } = context.payload;
const owner = context.payload.repository.owner.login;

if (issueTitle === ISSUE_TITLE) {
const installationCollection = db.collection("installation");
Expand Down Expand Up @@ -523,8 +525,6 @@ export default async (app, { getRouter }) => {
issueTitle === ISSUE_TITLE
) {
consola.start("Rerunning CWL Validation...");
const owner = context.payload.repository.owner.login;
const repository = context.payload.repository;

const cwl = await getCWLFiles(context, owner, repository.name);

Expand Down Expand Up @@ -571,11 +571,55 @@ export default async (app, { getRouter }) => {

consola.success("CWL Validation rerun successfully!");
}

if (
issueBody.includes("<!-- @codefair-bot rerun-full-repo-validation -->") &&
issueTitle === ISSUE_TITLE
) {
consola.start("Rerunning full repository validation...");

const license = await checkForLicense(context, owner, repository.name);
const citation = await checkForCitation(context, owner, repository.name);
const codemeta = await checkForCodeMeta(context, owner, repository.name);
const cwl = await getCWLFiles(context, owner, repository.name);

const cwlObject = {
contains_cwl: cwl.length > 0 || false,
files: cwl,
removed_files: [],
};

// If existing cwl validation exists, update the contains_cwl value
const cwlExists = await db.collection("cwlValidation").findOne({
repositoryId: repository.id,
});

if (cwlExists?.contains_cwl_files) {
cwlObject.contains_cwl = cwlExists.contains_cwl_files;
}

const subjects = {
citation,
codemeta,
cwl: cwlObject,
license,
};

const issueBody = await renderIssues(
context,
owner,
repository,
false,
subjects,
);

await createIssue(context, owner, repository, ISSUE_TITLE, issueBody);
}
});

// When an issue is deleted or closed
app.on(["issues.deleted", "issues.closed"], async (context) => {
const repository = context.payload.repository;
const { repository } = context.payload;
const issueTitle = context.payload.issue.title;

if (issueTitle === ISSUE_TITLE) {
Expand Down Expand Up @@ -607,7 +651,7 @@ export default async (app, { getRouter }) => {

// When an issue is reopened
app.on("issues.reopened", async (context) => {
const repository = context.payload.repository;
const { repository } = context.payload;
const owner = context.payload.repository.owner.login;
const issueTitle = context.payload.issue.title;

Expand Down
1 change: 1 addition & 0 deletions bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"axios": "^1.6.8",
"consola": "^3.2.3",
"cwl-ts-auto": "^0.1.3",
"dayjs": "^1.11.11",
"humanparser": "^2.7.0",
"js-yaml": "^4.1.0",
"mongodb": "^6.5.0",
Expand Down
141 changes: 69 additions & 72 deletions bot/utils/license/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,100 +173,97 @@ export async function applyLicenseTemplate(
owner,
context,
) {
if (!subjects.license) {
const identifier = createId();
let url = `${CODEFAIR_DOMAIN}/add/license/${identifier}`;
const licenseCollection = dbInstance.getDb().collection("licenseRequests");
const existingLicense = await licenseCollection.findOne({
repositoryId: repository.id,
});
const licenseCollection = dbInstance.getDb().collection("licenseRequests");
const identifier = createId();
let badgeURL = `${CODEFAIR_DOMAIN}/add/license/${identifier}`;
const existingLicense = await licenseCollection.findOne({
repositoryId: repository.id,
});
let licenseId = null;
let licenseContent = null;
let licenseContentNotEmpty = null;

if (!existingLicense) {
// Entry does not exist in db, create a new one
const newDate = Date.now();
await licenseCollection.insertOne({
contains_license: false,
created_at: newDate,
identifier,
open: true,
owner,
repo: repository.name,
repositoryId: repository.id,
updated_at: newDate,
});
} else {
// Get the identifier of the existing license request
// Update the database
await licenseCollection.updateOne(
{ repositoryId: repository.id },
{ $set: { contains_license: false, updated_at: Date.now() } },
);
url = `${CODEFAIR_DOMAIN}/add/license/${existingLicense.identifier}`;
}
// No license file found text
const licenseBadge = `[![License](https://img.shields.io/badge/Add_License-dc2626.svg)](${url})`;
baseTemplate += `## LICENSE ❌\n\nTo make your software reusable a license file is expected at the root level of your repository, as recommended in the [FAIR-BioRS Guidelines](https://fair-biors.org). If you would like Codefair to add a license file, click the "Add license" button below to go to our interface for selecting and adding a license. You can also add a license file yourself and Codefair will update the the dashboard when it detects it on the main branch.\n\n${licenseBadge}`;
} else {
if (subjects.license) {
// Get the license identifier
const licenseRequest = await context.octokit.rest.licenses.getForRepo({
owner,
repo: repository.name,
});

let licenseId = licenseRequest.data.license.spdx_id;
let licenseContent = Buffer.from(
licenseId = licenseRequest.data.license.spdx_id;
licenseContent = Buffer.from(
licenseRequest.data.content,
"base64",
).toString("utf-8");

if (
licenseRequest.data.license.spdx_id === "no-license" ||
licenseRequest.data.license.spdx_id === "NOASSERTION"
) {
consola.info("Resetting license id and content back to null");
licenseId = null;
licenseContent = null;
}

// License file found text
const identifier = createId();
let url = `${CODEFAIR_DOMAIN}/add/license/${identifier}`;
const licenseCollection = dbInstance.getDb().collection("licenseRequests");
const existingLicense = await licenseCollection.findOne({
licenseContentNotEmpty = licenseContent && licenseContent.trim().length > 0;
}

if (existingLicense) {
// Determine if the existing license is still valid
const isExistingLicenseValid =
existingLicense?.licenseContent && existingLicense.licenseId;

// Use the new license data if the existing license is invalid or the license has changed
const finalLicenseId =
isExistingLicenseValid && licenseId === null && !subjects.license
? existingLicense?.licenseId
: licenseId;
const finalLicenseContent =
isExistingLicenseValid && licenseContent === null && !subjects.license
? existingLicense?.licenseContent
: licenseContent;

badgeURL = `${CODEFAIR_DOMAIN}/add/license/${existingLicense.identifier}`;
await licenseCollection.updateOne(
{ repositoryId: repository.id },
{
$set: {
contains_license: subjects.license,
updated_at: Date.now(),
license_status:
finalLicenseContent &&
finalLicenseContent.trim().length > 0 &&
subjects.license
? "valid"
: "invalid",
licenseId: finalLicenseId,
licenseContent: finalLicenseContent,
},
},
);
} else {
const newDate = Date.now();
await licenseCollection.insertOne({
contains_license: subjects.license,
license_status: licenseContentNotEmpty ? "valid" : "invalid",
created_at: newDate,
licenseId,
licenseContent,
identifier,
open: true,
owner,
repo: repository.name,
repositoryId: repository.id,
updated_at: newDate,
});
}

if (!existingLicense) {
// Entry does not exist in db, create a new one
const newDate = Date.now();
await licenseCollection.insertOne({
contains_license: true,
created_at: newDate,
identifier,
licenseContent,
licenseId,
open: true,
owner,
repo: repository.name,
repositoryId: repository.id,
updated_at: newDate,
});
} else {
// Get the identifier of the existing license request
// Update the database
await licenseCollection.updateOne(
{ repositoryId: repository.id },
{
$set: {
contains_license: true,
licenseContent,
licenseId,
updated_at: Date.now(),
},
},
);
url = `${CODEFAIR_DOMAIN}/add/license/${existingLicense.identifier}`;
}
const licenseBadge = `[![License](https://img.shields.io/badge/Edit_License-0ea5e9.svg)](${url})`;
const licenseBadge = `[![License](https://img.shields.io/badge/${subjects.license ? "Edit_License-0ea5e9" : "Add_License-dc2626"}.svg)](${badgeURL})`;

if (subjects.license) {
baseTemplate += `## LICENSE ✔️\n\nA LICENSE file is found at the root level of the repository.\n\n${licenseBadge}`;
} else {
baseTemplate += `## LICENSE ❌\n\nTo make your software reusable a license file is expected at the root level of your repository, as recommended in the [FAIR-BioRS Guidelines](https://fair-biors.org). If you would like Codefair to add a license file, click the "Add license" button below to go to our interface for selecting and adding a license. You can also add a license file yourself and Codefair will update the the dashboard when it detects it on the main branch.\n\n${licenseBadge}`;
}

return baseTemplate;
Expand Down
38 changes: 33 additions & 5 deletions bot/utils/metadata/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,35 @@ export async function applyMetadataTemplate(

if (subjects.codemeta && subjects.citation && subjects.license) {
// Download the codemeta.json file from the repo
const codemetaFile = await context.octokit.repos.getContent({
owner,
path: "codemeta.json",
repo: repository.name,
});
let validCodemeta = false;
let validCitation = false;

try {
const codemetaFile = await context.octokit.repos.getContent({
owner,
path: "codemeta.json",
repo: repository.name,
});

JSON.parse(Buffer.from(codemetaFile.data.content, "base64").toString());

validCodemeta = true;
} catch (error) {
consola.error("Error getting codemeta.json file", error);
}

try {
const citationFile = await context.octokit.repos.getContent({
owner,
path: "CITATION.cff",
repo: repository.name,
});

yaml.load(Buffer.from(citationFile.data.content, "base64").toString());
validCitation = true;
} catch (error) {
consola.error("Error getting CITATION.cff file", error);
}

// Convert the content to a json object
const codemetaContent = JSON.parse(
Expand All @@ -351,6 +375,8 @@ export async function applyMetadataTemplate(
const newDate = Date.now();
// const gatheredMetadata = await gatherMetadata(context, owner, repository);
await metadataCollection.insertOne({
citation_status: validCitation ? "valid" : "invalid",
codemeta_status: validCodemeta ? "valid" : "invalid",
contains_citation: subjects.citation,
contains_codemeta: subjects.codemeta,
contains_metadata: subjects.codemeta && subjects.citation,
Expand All @@ -369,6 +395,8 @@ export async function applyMetadataTemplate(
{ repositoryId: repository.id },
{
$set: {
citation_status: validCitation ? "valid" : "invalid",
codemeta_status: validCodemeta ? "valid" : "invalid",
contains_citation: subjects.citation,
contains_codemeta: subjects.codemeta,
contains_metadata: subjects.codemeta && subjects.citation,
Expand Down
Loading