mirror of
https://github.com/actions/stale.git
synced 2026-01-01 12:58:18 +00:00
Add state
This commit is contained in:
@@ -17,6 +17,8 @@ export class ExemptDraftPullRequest {
|
||||
}
|
||||
|
||||
async shouldExemptDraftPullRequest(
|
||||
// keep this for backward compatibility
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
pullRequestCallback: () => Promise<IPullRequest | undefined | void>
|
||||
): Promise<boolean> {
|
||||
if (this._issue.isPullRequest) {
|
||||
|
||||
@@ -71,6 +71,7 @@ describe('Issue', (): void => {
|
||||
number: 8,
|
||||
created_at: 'dummy-created-at',
|
||||
updated_at: 'dummy-updated-at',
|
||||
draft: false,
|
||||
labels: [
|
||||
{
|
||||
name: 'dummy-name'
|
||||
|
||||
@@ -34,7 +34,7 @@ export class Issue implements IIssue {
|
||||
this.number = issue.number;
|
||||
this.created_at = issue.created_at;
|
||||
this.updated_at = issue.updated_at;
|
||||
this.draft = issue.draft || false;
|
||||
this.draft = Boolean(issue.draft);
|
||||
this.labels = mapLabels(issue.labels);
|
||||
this.pull_request = issue.pull_request;
|
||||
this.state = issue.state;
|
||||
|
||||
@@ -26,6 +26,7 @@ import {Statistics} from './statistics';
|
||||
import {LoggerService} from '../services/logger.service';
|
||||
import {OctokitIssue} from '../interfaces/issue';
|
||||
import {retry} from '@octokit/plugin-retry';
|
||||
import {IState} from '../interfaces/state';
|
||||
|
||||
/***
|
||||
* Handle processing of issues for staleness/closure.
|
||||
@@ -72,9 +73,11 @@ export class IssuesProcessor {
|
||||
readonly addedCloseCommentIssues: Issue[] = [];
|
||||
readonly statistics: Statistics | undefined;
|
||||
private readonly _logger: Logger = new Logger();
|
||||
private readonly state: IState;
|
||||
|
||||
constructor(options: IIssuesProcessorOptions) {
|
||||
constructor(options: IIssuesProcessorOptions, state: IState) {
|
||||
this.options = options;
|
||||
this.state = state;
|
||||
this.client = getOctokit(this.options.repoToken, undefined, retry);
|
||||
this.operations = new StaleOperations(this.options);
|
||||
|
||||
@@ -110,6 +113,8 @@ export class IssuesProcessor {
|
||||
?.setOperationsCount(this.operations.getConsumedOperationsCount())
|
||||
.logStats();
|
||||
|
||||
this.state.reset();
|
||||
|
||||
return this.operations.getRemainingOperationsCount();
|
||||
} else {
|
||||
this._logger.info(
|
||||
@@ -196,6 +201,15 @@ export class IssuesProcessor {
|
||||
)}`
|
||||
);
|
||||
|
||||
if (this.state.isIssueProcessed(issue)) {
|
||||
issueLogger.info(
|
||||
' $$type skipped due being processed during the previous run'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.addIssueToProcessed(issue);
|
||||
|
||||
// calculate string based messages for this issue
|
||||
const staleMessage: string = issue.isPullRequest
|
||||
? this.options.stalePrMessage
|
||||
|
||||
69
src/classes/state.ts
Normal file
69
src/classes/state.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {Issue} from './issue';
|
||||
import {IState} from '../interfaces/state';
|
||||
import os from 'os';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import artifact from '@actions/artifact';
|
||||
|
||||
type IssueID = number;
|
||||
export class State implements IState {
|
||||
private processedIssuesIDs: Set<IssueID>;
|
||||
constructor() {
|
||||
this.processedIssuesIDs = new Set();
|
||||
}
|
||||
|
||||
isIssueProcessed(issue: Issue) {
|
||||
return this.processedIssuesIDs.has(issue.number);
|
||||
}
|
||||
|
||||
addIssueToProcessed(issue: Issue) {
|
||||
this.processedIssuesIDs.add(issue.number);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.processedIssuesIDs.clear();
|
||||
}
|
||||
|
||||
private static readonly ARTIFACT_NAME = '_stale_state';
|
||||
async persist() {
|
||||
const serialized = Array.from(this.processedIssuesIDs).join('|');
|
||||
|
||||
const tmpDir = os.tmpdir();
|
||||
const file = crypto.randomBytes(8).readBigUInt64LE(0).toString();
|
||||
fs.writeFileSync(path.join(tmpDir, file), serialized);
|
||||
|
||||
const artifactClient = artifact.create();
|
||||
// TODO: handle errors
|
||||
await artifactClient.uploadArtifact(State.ARTIFACT_NAME, [file], tmpDir);
|
||||
}
|
||||
|
||||
async rehydrate() {
|
||||
this.reset();
|
||||
|
||||
const tmpDir = os.tmpdir();
|
||||
const artifactClient = artifact.create();
|
||||
const downloadResponse = await artifactClient.downloadArtifact(
|
||||
State.ARTIFACT_NAME,
|
||||
tmpDir
|
||||
);
|
||||
|
||||
const downloadedFiles = fs.readdirSync(downloadResponse.downloadPath);
|
||||
if (downloadedFiles.length === 0) {
|
||||
// TODO: handle error
|
||||
}
|
||||
const serialized = fs.readFileSync(
|
||||
path.join(downloadResponse.downloadPath, downloadedFiles[0]),
|
||||
{encoding: 'utf8'}
|
||||
);
|
||||
|
||||
if (serialized.length === 0) return;
|
||||
|
||||
const issueIDs = serialized
|
||||
.split('|')
|
||||
.map(parseInt)
|
||||
.filter(i => !isNaN(i));
|
||||
|
||||
this.processedIssuesIDs = new Set(issueIDs);
|
||||
}
|
||||
}
|
||||
9
src/interfaces/state.ts
Normal file
9
src/interfaces/state.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {IIssue} from './issue';
|
||||
|
||||
export interface IState {
|
||||
isIssueProcessed(issue: IIssue): boolean;
|
||||
addIssueToProcessed(issue: IIssue): void;
|
||||
reset(): void;
|
||||
persist(): Promise<void>;
|
||||
rehydrate(): Promise<void>;
|
||||
}
|
||||
@@ -3,14 +3,20 @@ import {IssuesProcessor} from './classes/issues-processor';
|
||||
import {isValidDate} from './functions/dates/is-valid-date';
|
||||
import {IIssuesProcessorOptions} from './interfaces/issues-processor-options';
|
||||
import {Issue} from './classes/issue';
|
||||
import {StateService} from './services/state.service';
|
||||
|
||||
async function _run(): Promise<void> {
|
||||
try {
|
||||
const args = _getAndValidateArgs();
|
||||
|
||||
const issueProcessor: IssuesProcessor = new IssuesProcessor(args);
|
||||
const state = StateService.getState();
|
||||
await state.rehydrate();
|
||||
|
||||
const issueProcessor: IssuesProcessor = new IssuesProcessor(args, state);
|
||||
await issueProcessor.processIssues();
|
||||
|
||||
await state.persist();
|
||||
|
||||
await processOutput(
|
||||
issueProcessor.staleIssues,
|
||||
issueProcessor.closedIssues
|
||||
|
||||
8
src/services/state.service.ts
Normal file
8
src/services/state.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {IState} from '../interfaces/state';
|
||||
import {State} from '../classes/state';
|
||||
|
||||
export class StateService {
|
||||
static getState(): IState {
|
||||
return new State();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user