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

Add cache-prefixing #1169

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This can be achieved with the following [configuration in the action](https://do

```yaml
permissions:
actions: write # for caching state
contents: write # only for delete-branch option
issues: write
pull-requests: write
Expand Down Expand Up @@ -97,6 +98,7 @@ Every argument is optional.
| [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 | |
| [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

Expand Down Expand Up @@ -547,6 +549,11 @@ If set to `true`, only the issues or the pull requests with an assignee will be

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.
echang49 marked this conversation as resolved.
Show resolved Hide resolved
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.
echang49 marked this conversation as resolved.
Show resolved Hide resolved

### Usage

See also [action.yml](./action.yml) for a comprehensive list of all the options.
Expand Down
3 changes: 2 additions & 1 deletion __tests__/constants/default-processor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
ignorePrUpdates: undefined,
exemptDraftPr: false,
closeIssueReason: 'not_planned',
includeOnlyAssigned: false
includeOnlyAssigned: false,
cachePrefix: ''
});
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ inputs:
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
default: '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:
closed-issues-prs:
description: 'List of all closed issues and pull requests.'
Expand Down
29 changes: 19 additions & 10 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1644,22 +1644,27 @@ const resetCacheWithOctokit = (cacheKey) => __awaiter(void 0, void 0, void 0, fu
}
});
class StateCacheStorage {
constructor(options) {
this.statePrefix = options.cachePrefix;
}
save(serializedState) {
return __awaiter(this, void 0, void 0, function* () {
const tmpDir = mkTempDir();
const filePath = path_1.default.join(tmpDir, STATE_FILE);
const stateFile = `${this.statePrefix}_${STATE_FILE}`;
const filePath = path_1.default.join(tmpDir, stateFile);
fs_1.default.writeFileSync(filePath, serializedState);
try {
const cacheExists = yield checkIfCacheExists(CACHE_KEY);
const cacheKey = `${this.statePrefix}${CACHE_KEY}`;
const cacheExists = yield checkIfCacheExists(cacheKey);
if (cacheExists) {
yield resetCacheWithOctokit(CACHE_KEY);
yield resetCacheWithOctokit(cacheKey);
}
const fileSize = fs_1.default.statSync(filePath).size;
if (fileSize === 0) {
core.info(`the state will be removed`);
return;
}
yield cache.saveCache([path_1.default.dirname(filePath)], CACHE_KEY);
yield cache.saveCache([path_1.default.dirname(filePath)], cacheKey);
}
catch (error) {
core.warning(`Saving the state was not successful due to "${error.message || 'unknown reason'}"`);
Expand All @@ -1672,20 +1677,22 @@ class StateCacheStorage {
restore() {
return __awaiter(this, void 0, void 0, function* () {
const tmpDir = mkTempDir();
const filePath = path_1.default.join(tmpDir, STATE_FILE);
const stateFile = `${this.statePrefix}_${STATE_FILE}`;
const filePath = path_1.default.join(tmpDir, stateFile);
unlinkSafely(filePath);
try {
const cacheExists = yield checkIfCacheExists(CACHE_KEY);
const cacheKey = `${this.statePrefix}${CACHE_KEY}`;
const cacheExists = yield checkIfCacheExists(cacheKey);
if (!cacheExists) {
core.info('The saved state was not found, the process starts from the first issue.');
return '';
}
yield cache.restoreCache([path_1.default.dirname(filePath)], CACHE_KEY);
yield cache.restoreCache([path_1.default.dirname(filePath)], cacheKey);
if (!fs_1.default.existsSync(filePath)) {
core.warning('Unknown error when unpacking the cache, the process starts from the first issue.');
return '';
}
return fs_1.default.readFileSync(path_1.default.join(tmpDir, STATE_FILE), {
return fs_1.default.readFileSync(path_1.default.join(tmpDir, stateFile), {
encoding: 'utf8'
});
}
Expand Down Expand Up @@ -2222,6 +2229,7 @@ var Option;
Option["IgnorePrUpdates"] = "ignore-pr-updates";
Option["ExemptDraftPr"] = "exempt-draft-pr";
Option["CloseIssueReason"] = "close-issue-reason";
Option["CachePrefix"] = "cache-prefix";
})(Option || (exports.Option = Option = {}));


Expand Down Expand Up @@ -2567,7 +2575,8 @@ function _getAndValidateArgs() {
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
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']) {
if (isNaN(parseFloat(core.getInput(numberInput)))) {
Expand Down Expand Up @@ -2694,7 +2703,7 @@ exports.getStateInstance = void 0;
const state_1 = __nccwpck_require__(5186);
const state_cache_storage_1 = __nccwpck_require__(3709);
const getStateInstance = (options) => {
const storage = new state_cache_storage_1.StateCacheStorage();
const storage = new state_cache_storage_1.StateCacheStorage(options);
return new state_1.State(storage, options);
};
exports.getStateInstance = getStateInstance;
Expand Down
3 changes: 2 additions & 1 deletion src/classes/issue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ describe('Issue', (): void => {
ignorePrUpdates: undefined,
exemptDraftPr: false,
closeIssueReason: '',
includeOnlyAssigned: false
includeOnlyAssigned: false,
cachePrefix: ''
};
issueInterface = {
title: 'dummy-title',
Expand Down
30 changes: 22 additions & 8 deletions src/classes/state/state-cache-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as core from '@actions/core';
import {context, getOctokit} from '@actions/github';
import {retry as octokitRetry} from '@octokit/plugin-retry';
import * as cache from '@actions/cache';
import {IIssuesProcessorOptions} from '../../interfaces/issues-processor-options';

const CACHE_KEY = '_state';
const STATE_FILE = 'state.txt';
Expand Down Expand Up @@ -65,15 +66,26 @@ const resetCacheWithOctokit = async (cacheKey: string): Promise<void> => {
}
};
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> {
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);

try {
const cacheExists = await checkIfCacheExists(CACHE_KEY);
const cacheKey = `${this.statePrefix}${CACHE_KEY}`;
const cacheExists = await checkIfCacheExists(cacheKey);
if (cacheExists) {
await resetCacheWithOctokit(CACHE_KEY);
await resetCacheWithOctokit(cacheKey);
}
const fileSize = fs.statSync(filePath).size;

Expand All @@ -82,7 +94,7 @@ export class StateCacheStorage implements IStateStorage {
return;
}

await cache.saveCache([path.dirname(filePath)], CACHE_KEY);
await cache.saveCache([path.dirname(filePath)], cacheKey);
} catch (error) {
core.warning(
`Saving the state was not successful due to "${
Expand All @@ -96,26 +108,28 @@ export class StateCacheStorage implements IStateStorage {

async restore(): Promise<string> {
const tmpDir = mkTempDir();
const filePath = path.join(tmpDir, STATE_FILE);
const stateFile = `${this.statePrefix}_${STATE_FILE}`;
const filePath = path.join(tmpDir, stateFile);
unlinkSafely(filePath);
try {
const cacheExists = await checkIfCacheExists(CACHE_KEY);
const cacheKey = `${this.statePrefix}${CACHE_KEY}`;
const cacheExists = await checkIfCacheExists(cacheKey);
if (!cacheExists) {
core.info(
'The saved state was not found, the process starts from the first issue.'
);
return '';
}

await cache.restoreCache([path.dirname(filePath)], CACHE_KEY);
await cache.restoreCache([path.dirname(filePath)], cacheKey);

if (!fs.existsSync(filePath)) {
core.warning(
'Unknown error when unpacking the cache, the process starts from the first issue.'
);
return '';
}
return fs.readFileSync(path.join(tmpDir, STATE_FILE), {
return fs.readFileSync(path.join(tmpDir, stateFile), {
encoding: 'utf8'
});
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion src/enums/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ export enum Option {
IgnoreIssueUpdates = 'ignore-issue-updates',
IgnorePrUpdates = 'ignore-pr-updates',
ExemptDraftPr = 'exempt-draft-pr',
CloseIssueReason = 'close-issue-reason'
CloseIssueReason = 'close-issue-reason',
CachePrefix = 'cache-prefix'
}
1 change: 1 addition & 0 deletions src/interfaces/issues-processor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ export interface IIssuesProcessorOptions {
exemptDraftPr: boolean;
closeIssueReason: string;
includeOnlyAssigned: boolean;
cachePrefix: string;
}
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
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']) {
Expand Down
2 changes: 1 addition & 1 deletion src/services/state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import {StateCacheStorage} from '../classes/state/state-cache-storage';

export const getStateInstance = (options: IIssuesProcessorOptions): IState => {
const storage = new StateCacheStorage();
const storage = new StateCacheStorage(options);
return new State(storage, options);
};