Skip to content

Commit

Permalink
implement minimal logic
Browse files Browse the repository at this point in the history
  • Loading branch information
wechuli committed Jan 24, 2024
1 parent 9ce4055 commit 882eba8
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 166 deletions.
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ inputs:
description: "If set to true, skipped checks will be treated as passed"
required: false
default: "true"
treat_neutral_as_passed:
description: "If set to true, neutral checks will be treated as passed"
required: false
default: "true"
create_check:
description: "If set to true, the action will create it's own check on the commit, rather than the default created by GitHub Action. Must be a GitHub App token"
required: false
Expand Down
59 changes: 48 additions & 11 deletions src/checks/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import {IInputs} from '../utils/inputsExtractor';
import {getAllChecks, getAllStatusCommits} from './checksAPI';
import {ICheckInput, ICheck, IStatus} from './checksInterfaces';
import {
checkOneOfTheChecksInputIsEmpty,
checkOneOfTheChecksInputIsEmpty, filterChecksByConclusion, filterChecksByStatus,
filterChecksWithMatchingNameAndAppId,
removeChecksWithMatchingNameAndAppId,
removeDuplicateChecksEntriesFromSelf,
removeDuplicateEntriesChecksInputsFromSelf
} from './checksFilters';
import {sleep} from "../utils/timeFuncs";
import {extractOwnCheckNameFromWorkflow} from "../utils/fileExtractor";
import {GitHubActionsBotId} from "./checksConstants";
import {checkConclusion, checkStatus, GitHubActionsBotId} from "./checksConstants";

interface IRepo {
owner: string;
Expand All @@ -36,6 +36,7 @@ export default class Checks {
private checksExclude: ICheckInput[];
private checksInclude: ICheckInput[];
private treatSkippedAsPassed: boolean;
private treatNeutralAsPassed: boolean;
private createCheck: boolean;
private includeCommitStatuses: boolean;
private poll: boolean;
Expand All @@ -53,6 +54,7 @@ export default class Checks {
this.checksExclude = props.checksExclude;
this.checksInclude = props.checksInclude;
this.treatSkippedAsPassed = props.treatSkippedAsPassed;
this.treatNeutralAsPassed = props.treatNeutralAsPassed;
this.createCheck = props.createCheck;
this.includeCommitStatuses = props.includeCommitStatuses;
this.poll = props.poll;
Expand Down Expand Up @@ -82,11 +84,14 @@ export default class Checks {

async filterChecks() {

// lets get the check from the workflow run itself
let ownCheckName = await extractOwnCheckNameFromWorkflow();
let gitHubActionsBotId = GitHubActionsBotId;
// lets get the check from the workflow run itself, if the value already exists, don't re-fetch it

this.ownCheck = this.allChecks.find(check => check.name === ownCheckName && check.app.id === gitHubActionsBotId);
if (!this.ownCheck) {
let ownCheckName = await extractOwnCheckNameFromWorkflow();
let gitHubActionsBotId = GitHubActionsBotId;

this.ownCheck = this.allChecks.find(check => check.name === ownCheckName && check.app.id === gitHubActionsBotId);
}


// start by checking if the user has defined both checks_include and checks_exclude inputs and fail if that is the case
Expand Down Expand Up @@ -123,17 +128,49 @@ export default class Checks {

};

reportChecks() {
// create table showing the filtered checks, with names and conclusion, created at, updated at, and app id and status
core.info("Filtered checks:");
core.info("Name | Conclusion | Created at | Updated at | | Status");
determineChecksFailure(checks: ICheck[]): boolean {
// if any of the checks are still in_progress or queued or waiting, then we will return false
let inProgressQueuedWaiting = [checkStatus.IN_PROGRESS, checkStatus.QUEUED, checkStatus.WAITING]
let anyInProgressQueuedWaiting = checks.filter(check => inProgressQueuedWaiting.includes(check.status));
if (anyInProgressQueuedWaiting.length > 0) {
return false;
}
// conclusions that determine a fail
let failureConclusions: string[] = [checkConclusion.FAILURE, checkConclusion.TIMED_OUT, checkConclusion.CANCELLED, checkConclusion.ACTION_REQUIRED, checkConclusion.STALE];
// if the user wanted us to treat skipped as a failure, then we will add it to the failureConclusions array
if (!this.treatSkippedAsPassed) {
failureConclusions.push(checkConclusion.SKIPPED);
}

// if the user wanted us to treat neutral as a failure, then we will add it to the failureConclusions array
if (!this.treatNeutralAsPassed) {
failureConclusions.push(checkConclusion.NEUTRAL);
}

// if any of the checks are failing, then we will return true
let failingChecks = checks.filter(check => failureConclusions.includes(check.conclusion!));

if (failingChecks.length > 0) {
return false;
}

return true;

};

async runLogic() {
sleep(this.delay);
await this.fetchAllChecks();
await this.filterChecks();

// check for any in_progess checks in the filtered checks excluding the check from the workflow run itself

let filteredChecksExcludingOwnCheck = this.filteredChecks.filter(check => check.id !== this.ownCheck?.id);
let allChecksPass = this.determineChecksFailure(filteredChecksExcludingOwnCheck);
this.allChecksPassed = allChecksPass;
return {
allChecksPass, missingChecks: this.missingChecks, filteredChecksExcludingOwnCheck
}

}


Expand Down
37 changes: 19 additions & 18 deletions src/checks/checksConstants.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
export enum checkConclusion {
ACTION_REQUIRED = "action_required",
CANCELLED = "cancelled",
FAILURE = "failure",
NEUTRAL = "neutral",
SUCCESS = "success",
SKIPPED = "skipped",
STALE = "stale",
TIMED_OUT = "timed_out"
export const checkConclusion = {
ACTION_REQUIRED: "action_required",
CANCELLED: "cancelled",
FAILURE: "failure",
NEUTRAL: "neutral",
SUCCESS: "success",
SKIPPED: "skipped",
STALE: "stale",
TIMED_OUT: "timed_out"
}

export enum checkStatus {
QUEUED = "queued",
IN_PROGRESS = "in_progress",
COMPLETED = "completed"
export const checkStatus = {
QUEUED: "queued",
IN_PROGRESS: "in_progress",
COMPLETED: "completed",
WAITING: "waiting"
}


export enum commitStatusState {
ERROR = "error",
FAILURE = "failure",
PENDING = "pending",
SUCCESS = "success"
export const commitStatusState = {
ERROR: "error",
FAILURE: "failure",
PENDING: "pending",
SUCCESS: "success"
}

export const GitHubActionsBotId = 15368;
113 changes: 55 additions & 58 deletions src/checks/checksFilters.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import { ICheck,IStatus,ICheckInput} from './checksInterfaces';
import {ICheck, IStatus, ICheckInput} from './checksInterfaces';
import {checkStatus} from "./checksConstants";

export enum FilterTypes{
export enum FilterTypes {
exclude = "exclude",
include = "include"
}


export function returnChecksWithMatchingNameAndAppId(checks:ICheck[], name:string, appId:number):ICheck[] | null {
export function returnChecksWithMatchingNameAndAppId(checks: ICheck[], name: string, appId: number): ICheck[] | null {
const regex = new RegExp(name);
let checksWithNameAndAppID:ICheck[] = [];
let checksWithNameAndAppID: ICheck[] = [];
//if appId is -1, then we don't care about the app id, just the name
if(appId === -1){
checksWithNameAndAppID = checks.filter((check) => regex.test(check.name));
}
else{
if (appId === -1) {
checksWithNameAndAppID = checks.filter((check) => regex.test(check.name));
} else {
checksWithNameAndAppID = checks.filter((check) => regex.test(check.name) && check.app.id === appId);
}

//if no check is found, return null
if(checksWithNameAndAppID.length === 0){
if (checksWithNameAndAppID.length === 0) {
return null;
}
// if there is only one check with the name and app id, return it
else if(checksWithNameAndAppID.length === 1){
else if (checksWithNameAndAppID.length === 1) {
return checksWithNameAndAppID;
}

// if there are multiple checks with the same name, use the app id to pick the most recent check on each unique app id

const getUniqueAppId = [...new Set(checksWithNameAndAppID.map((check) => check.app.id))];
let mostRecentChecks:ICheck[] = [];
let mostRecentChecks: ICheck[] = [];
getUniqueAppId.forEach((appId) => {
const checksWithMatchingAppId = checksWithNameAndAppID.filter((check) => check.app.id === appId);

Expand All @@ -45,99 +45,96 @@ export function returnChecksWithMatchingNameAndAppId(checks:ICheck[], name:strin
return mostRecentChecks;
}

export function filterChecksWithMatchingNameAndAppId(checks:ICheck[], checksInputs: ICheckInput[]) {
let missingChecks:ICheckInput[] = [];
let filteredChecks:ICheck[] = [];
checksInputs.forEach(checkInput=>{
const checksWithNameAndAppId = returnChecksWithMatchingNameAndAppId(checks,checkInput.name,checkInput.app_id);
if(checksWithNameAndAppId === null){
missingChecks.push(checkInput);
}
else{
filteredChecks = [...filteredChecks,...checksWithNameAndAppId];
}
});
return {filteredChecks,missingChecks};
export function filterChecksWithMatchingNameAndAppId(checks: ICheck[], checksInputs: ICheckInput[]) {
let missingChecks: ICheckInput[] = [];
let filteredChecks: ICheck[] = [];
checksInputs.forEach(checkInput => {
const checksWithNameAndAppId = returnChecksWithMatchingNameAndAppId(checks, checkInput.name, checkInput.app_id);
if (checksWithNameAndAppId === null) {
missingChecks.push(checkInput);
} else {
filteredChecks = [...filteredChecks, ...checksWithNameAndAppId];
}
});
return {filteredChecks, missingChecks};
}


export function removeChecksWithMatchingNameAndAppId(checks:ICheck[], checksInputs: ICheckInput[]) {

let newChecks = [...checks];
newChecks.forEach(check=>{
checksInputs.forEach(checkInput=>{
const regex = new RegExp(checkInput.name);
if(checkInput.app_id === -1){
if(regex.test(check.name)){
newChecks = newChecks.filter((newCheck) => newCheck.id !== check.id);
}
}
else{
if(regex.test(check.name) && checkInput.app_id === check.app.id){
newChecks = newChecks.filter((newCheck) => newCheck.id !== check.id);
export function removeChecksWithMatchingNameAndAppId(checks: ICheck[], checksInputs: ICheckInput[]) {

let newChecks = [...checks];
newChecks.forEach(check => {
checksInputs.forEach(checkInput => {
const regex = new RegExp(checkInput.name);
if (checkInput.app_id === -1) {
if (regex.test(check.name)) {
newChecks = newChecks.filter((newCheck) => newCheck.id !== check.id);
}
} else {
if (regex.test(check.name) && checkInput.app_id === check.app.id) {
newChecks = newChecks.filter((newCheck) => newCheck.id !== check.id);
}
}
}
});
});

});
return newChecks;
});
return newChecks;
}

export function checkOneOfTheChecksInputIsEmpty(checksInputs1:ICheckInput[], checksInputs2:ICheckInput[]):boolean {
export function checkOneOfTheChecksInputIsEmpty(checksInputs1: ICheckInput[], checksInputs2: ICheckInput[]): boolean {

if (checksInputs1.length === 0 || checksInputs2.length === 0) {
return true;
}
return false;
}
export function removeDuplicateEntriesChecksInputsFromSelf(checksInputs:ICheckInput[]):ICheckInput[] {
let uniqueCheckInputs:ICheckInput[] = [];

export function removeDuplicateEntriesChecksInputsFromSelf(checksInputs: ICheckInput[]): ICheckInput[] {
let uniqueCheckInputs: ICheckInput[] = [];
checksInputs.forEach((checkInput) => {
const checkInputAlreadyExists = uniqueCheckInputs.some((uniqueCheckInput) => uniqueCheckInput.name === checkInput.name && uniqueCheckInput.app_id === checkInput.app_id);
if(!checkInputAlreadyExists){
if (!checkInputAlreadyExists) {
uniqueCheckInputs.push(checkInput);
}
});
return uniqueCheckInputs;
}

export function removeDuplicateChecksEntriesFromSelf(checks:ICheck[]):ICheck[] {
export function removeDuplicateChecksEntriesFromSelf(checks: ICheck[]): ICheck[] {
// use the check id to determine uniqueness
let uniqueChecks:ICheck[] = [];
let uniqueChecks: ICheck[] = [];
checks.forEach((check) => {
const checkAlreadyExists = uniqueChecks.some((uniqueCheck) => uniqueCheck.id === check.id);
if(!checkAlreadyExists){
if (!checkAlreadyExists) {
uniqueChecks.push(check);
}
});
return uniqueChecks;
}


export function filterStatusesByContext(statuses:IStatus[], context:string,filterType:FilterTypes = FilterTypes.include) {
export function filterStatusesByContext(statuses: IStatus[], context: string, filterType: FilterTypes = FilterTypes.include) {
const regex = new RegExp(context);
if(filterType === FilterTypes.include){
if (filterType === FilterTypes.include) {
return statuses.filter((status) => regex.test(status.context));
}
else{
} else {
return statuses.filter((status) => !regex.test(status.context));
}
}

export function filterChecksByStatus(checks:ICheck[], status:string) {
export function filterChecksByStatus(checks: ICheck[], status: checkStatus) {
return checks.filter((check) => check.status === status);
}

export function filterChecksByConclusion(checks:ICheck[], conclusion:string) {
export function filterChecksByConclusion(checks: ICheck[], conclusion: string) {
return checks.filter((check) => check.conclusion === conclusion);
}

export function filterStatusesByState(statuses:IStatus[], state:string) {
export function filterStatusesByState(statuses: IStatus[], state: string) {
return statuses.filter((status) => status.state === state);
}



export function filterStatusesByCreatorId(statuses:IStatus[], creatorId:number) {
export function filterStatusesByCreatorId(statuses: IStatus[], creatorId: number) {
return statuses.filter((status) => status.creator.id === creatorId);
}
14 changes: 5 additions & 9 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,24 @@ import * as github from "@actions/github";
import Checks from "./checks/checks";
import {sanitizedInputs} from "./utils/inputsExtractor";
import {extractOwnCheckNameFromWorkflow} from "./utils/fileExtractor";
import {sleep} from "./utils/timeFuncs";

/**
* The main function for the action.
* @returns {Promise<void>} Resolves when the action is complete.
*/
export async function run(): Promise<void> {
try {
core.debug("Hello from the action!");
// delay execution
sleep(sanitizedInputs.delay * 1000);
const owner = github.context.repo.owner;
const repo = github.context.repo.repo;

const inputs = sanitizedInputs;

const checks = new Checks({...inputs, owner, repo});
await checks.runLogic();

console.log(await extractOwnCheckNameFromWorkflow());

console.log(`checks: ${JSON.stringify(checks.allChecks)}`);

console.log(`filtered checks: ${JSON.stringify(checks.filteredChecks)}`);
console.log(`own check: ${JSON.stringify(checks.ownCheck)}`);
const results = await checks.runLogic();
console.log(JSON.stringify(results, null, 2));

} catch (error) {
// Fail the workflow run if an error occurs
Expand Down
Loading

0 comments on commit 882eba8

Please sign in to comment.