Add cache prefix support

This commit is contained in:
Edward Chang 2024-08-01 21:52:13 -04:00
parent 3f3b0175e8
commit ee531bffac
9 changed files with 43 additions and 13 deletions

View File

@ -19,6 +19,7 @@ This can be achieved with the following [configuration in the action](https://do
```yaml ```yaml
permissions: permissions:
actions: write # for caching state
contents: write # only for delete-branch option contents: write # only for delete-branch option
issues: write issues: write
pull-requests: write pull-requests: write
@ -97,6 +98,7 @@ Every argument is optional.
| [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | | | [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | |
| [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | | | [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | |
| [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` | | [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` |
| [cache-prefix](#cache-prefix) | Add a prefix to the stored cache | |
### List of output options ### List of output options
@ -547,6 +549,11 @@ If set to `true`, only the issues or the pull requests with an assignee will be
Default value: `false` Default value: `false`
#### cache-prefix
Beneficial so the action has a more unique cahce key. Useful when calling this action multiple times, independent of each other.
An example for usage would be closing all PRs with `x` label after 7 days in one action and closing all PRs except for `x` label after 10 days in another.
### Usage ### Usage
See also [action.yml](./action.yml) for a comprehensive list of all the options. See also [action.yml](./action.yml) for a comprehensive list of all the options.

View File

@ -55,5 +55,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
ignorePrUpdates: undefined, ignorePrUpdates: undefined,
exemptDraftPr: false, exemptDraftPr: false,
closeIssueReason: 'not_planned', closeIssueReason: 'not_planned',
includeOnlyAssigned: false includeOnlyAssigned: false,
cachePrefix: ''
}); });

View File

@ -204,6 +204,10 @@ inputs:
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.' description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
default: 'false' default: 'false'
required: false required: false
cache-prefix:
description: 'Add a prefix to the stored cache. Useful when using this action multiple times with different params.'
default: ''
required: false
outputs: outputs:
closed-issues-prs: closed-issues-prs:
description: 'List of all closed issues and pull requests.' description: 'List of all closed issues and pull requests.'

View File

@ -64,7 +64,8 @@ describe('Issue', (): void => {
ignorePrUpdates: undefined, ignorePrUpdates: undefined,
exemptDraftPr: false, exemptDraftPr: false,
closeIssueReason: '', closeIssueReason: '',
includeOnlyAssigned: false includeOnlyAssigned: false,
cachePrefix: ''
}; };
issueInterface = { issueInterface = {
title: 'dummy-title', title: 'dummy-title',

View File

@ -6,6 +6,7 @@ import * as core from '@actions/core';
import {context, getOctokit} from '@actions/github'; import {context, getOctokit} from '@actions/github';
import {retry as octokitRetry} from '@octokit/plugin-retry'; import {retry as octokitRetry} from '@octokit/plugin-retry';
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import {IIssuesProcessorOptions} from '../../interfaces/issues-processor-options';
const CACHE_KEY = '_state'; const CACHE_KEY = '_state';
const STATE_FILE = 'state.txt'; const STATE_FILE = 'state.txt';
@ -65,15 +66,26 @@ const resetCacheWithOctokit = async (cacheKey: string): Promise<void> => {
} }
}; };
export class StateCacheStorage implements IStateStorage { export class StateCacheStorage implements IStateStorage {
/**
* @private don't mutate in the debug mode
*/
private readonly statePrefix: string;
constructor(options: IIssuesProcessorOptions) {
this.statePrefix = options.cachePrefix;
}
async save(serializedState: string): Promise<void> { async save(serializedState: string): Promise<void> {
const tmpDir = mkTempDir(); const tmpDir = mkTempDir();
const filePath = path.join(tmpDir, STATE_FILE); const stateFile = `${this.statePrefix}_${STATE_FILE}`;
const filePath = path.join(tmpDir, stateFile);
fs.writeFileSync(filePath, serializedState); fs.writeFileSync(filePath, serializedState);
try { try {
const cacheExists = await checkIfCacheExists(CACHE_KEY); const cacheKey = `${this.statePrefix}${CACHE_KEY}`;
const cacheExists = await checkIfCacheExists(cacheKey);
if (cacheExists) { if (cacheExists) {
await resetCacheWithOctokit(CACHE_KEY); await resetCacheWithOctokit(cacheKey);
} }
const fileSize = fs.statSync(filePath).size; const fileSize = fs.statSync(filePath).size;
@ -82,7 +94,7 @@ export class StateCacheStorage implements IStateStorage {
return; return;
} }
await cache.saveCache([path.dirname(filePath)], CACHE_KEY); await cache.saveCache([path.dirname(filePath)], cacheKey);
} catch (error) { } catch (error) {
core.warning( core.warning(
`Saving the state was not successful due to "${ `Saving the state was not successful due to "${
@ -96,10 +108,12 @@ export class StateCacheStorage implements IStateStorage {
async restore(): Promise<string> { async restore(): Promise<string> {
const tmpDir = mkTempDir(); const tmpDir = mkTempDir();
const filePath = path.join(tmpDir, STATE_FILE); const stateFile = `${this.statePrefix}_${STATE_FILE}`;
const filePath = path.join(tmpDir, stateFile);
unlinkSafely(filePath); unlinkSafely(filePath);
try { try {
const cacheExists = await checkIfCacheExists(CACHE_KEY); const cacheKey = `${this.statePrefix}${CACHE_KEY}`;
const cacheExists = await checkIfCacheExists(cacheKey);
if (!cacheExists) { if (!cacheExists) {
core.info( core.info(
'The saved state was not found, the process starts from the first issue.' 'The saved state was not found, the process starts from the first issue.'
@ -107,7 +121,7 @@ export class StateCacheStorage implements IStateStorage {
return ''; return '';
} }
await cache.restoreCache([path.dirname(filePath)], CACHE_KEY); await cache.restoreCache([path.dirname(filePath)], cacheKey);
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
core.warning( core.warning(
@ -115,7 +129,7 @@ export class StateCacheStorage implements IStateStorage {
); );
return ''; return '';
} }
return fs.readFileSync(path.join(tmpDir, STATE_FILE), { return fs.readFileSync(path.join(tmpDir, stateFile), {
encoding: 'utf8' encoding: 'utf8'
}); });
} catch (error) { } catch (error) {

View File

@ -48,5 +48,6 @@ export enum Option {
IgnoreIssueUpdates = 'ignore-issue-updates', IgnoreIssueUpdates = 'ignore-issue-updates',
IgnorePrUpdates = 'ignore-pr-updates', IgnorePrUpdates = 'ignore-pr-updates',
ExemptDraftPr = 'exempt-draft-pr', ExemptDraftPr = 'exempt-draft-pr',
CloseIssueReason = 'close-issue-reason' CloseIssueReason = 'close-issue-reason',
CachePrefix = 'cache-prefix'
} }

View File

@ -54,4 +54,5 @@ export interface IIssuesProcessorOptions {
exemptDraftPr: boolean; exemptDraftPr: boolean;
closeIssueReason: string; closeIssueReason: string;
includeOnlyAssigned: boolean; includeOnlyAssigned: boolean;
cachePrefix: string;
} }

View File

@ -123,7 +123,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true', exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
closeIssueReason: core.getInput('close-issue-reason'), closeIssueReason: core.getInput('close-issue-reason'),
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true' includeOnlyAssigned: core.getInput('include-only-assigned') === 'true',
cachePrefix: core.getInput('cache-prefix')
}; };
for (const numberInput of ['days-before-stale']) { for (const numberInput of ['days-before-stale']) {

View File

@ -4,6 +4,6 @@ import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import {StateCacheStorage} from '../classes/state/state-cache-storage'; import {StateCacheStorage} from '../classes/state/state-cache-storage';
export const getStateInstance = (options: IIssuesProcessorOptions): IState => { export const getStateInstance = (options: IIssuesProcessorOptions): IState => {
const storage = new StateCacheStorage(); const storage = new StateCacheStorage(options);
return new State(storage, options); return new State(storage, options);
}; };