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

refactor(config-error): generalize error issue creation for future reuse #22289

Merged
merged 3 commits into from May 17, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 43 additions & 6 deletions lib/workers/repository/error-config.spec.ts
@@ -1,7 +1,13 @@
import { mock } from 'jest-mock-extended';
import { RenovateConfig, getConfig, platform } from '../../../test/util';
import {
RenovateConfig,
getConfig,
partial,
platform,
} from '../../../test/util';
import { GlobalConfig } from '../../config/global';
import { CONFIG_VALIDATION } from '../../constants/error-messages';
import { logger } from '../../logger';
import type { Pr } from '../../modules/platform';
import { raiseConfigWarningIssue } from './error-config';

Expand All @@ -24,9 +30,16 @@ describe('workers/repository/error-config', () => {
const error = new Error(CONFIG_VALIDATION);
rarkins marked this conversation as resolved.
Show resolved Hide resolved
error.validationSource = 'package.json';
error.validationMessage = 'some-message';
error.validationError = 'some-error';
platform.ensureIssue.mockResolvedValueOnce('created');

const res = await raiseConfigWarningIssue(config, error);

expect(res).toBeUndefined();
expect(logger.warn).toHaveBeenCalledWith(
{ configError: error, res: 'created' },
'Configuration Warning'
);
});

it('creates issues (dryRun)', async () => {
Expand All @@ -35,50 +48,74 @@ describe('workers/repository/error-config', () => {
error.validationMessage = 'some-message';
platform.ensureIssue.mockResolvedValueOnce('created');
GlobalConfig.set({ dryRun: 'full' });

const res = await raiseConfigWarningIssue(config, error);

expect(res).toBeUndefined();
expect(logger.info).toHaveBeenCalledWith(
{ configError: error },
'DRY-RUN: Would ensure configuration error issue'
);
});

it('handles onboarding', async () => {
const error = new Error(CONFIG_VALIDATION);
error.validationSource = 'package.json';
error.validationMessage = 'some-message';
platform.getBranchPr.mockResolvedValue({
...mock<Pr>(),
const pr = partial<Pr>({
title: 'onboarding',
number: 1,
state: 'open',
});
platform.getBranchPr.mockResolvedValue(pr);

const res = await raiseConfigWarningIssue(config, error);

expect(res).toBeUndefined();
expect(platform.updatePr).toHaveBeenCalledWith(
expect.objectContaining({ prTitle: pr.title, number: pr.number })
);
});

it('handles onboarding (dryRun)', async () => {
const error = new Error(CONFIG_VALIDATION);
error.validationSource = 'package.json';
error.validationMessage = 'some-message';
platform.getBranchPr.mockResolvedValue({
...mock<Pr>(),
const pr = partial<Pr>({
number: 1,
state: 'open',
});
platform.getBranchPr.mockResolvedValue(pr);
GlobalConfig.set({ dryRun: 'full' });

const res = await raiseConfigWarningIssue(config, error);

expect(res).toBeUndefined();
expect(logger.info).toHaveBeenCalledWith(
`DRY-RUN: Would update PR #${pr.number}`
);
});

it('disable issue creation on config failure', async () => {
const notificationName = 'configErrorIssue';
const error = new Error(CONFIG_VALIDATION);
error.validationSource = 'package.json';
error.validationMessage = 'some-message';
// config.suppressNotifications = ['deprecationWarningIssues']
config.suppressNotifications = ['configErrorIssue'];
config.suppressNotifications = [notificationName];
platform.getBranchPr.mockResolvedValueOnce({
...mock<Pr>(),
number: 1,
state: '!open',
});

const res = await raiseConfigWarningIssue(config, error);

expect(res).toBeUndefined();
expect(logger.info).toHaveBeenCalledWith(
{ notificationName },
'Configuration failure, issues will be suppressed'
);
});
});
});
103 changes: 67 additions & 36 deletions lib/workers/repository/error-config.ts
Expand Up @@ -2,61 +2,92 @@
import { GlobalConfig } from '../../config/global';
import type { RenovateConfig } from '../../config/types';
import { logger } from '../../logger';
import { platform } from '../../modules/platform';
import { Pr, platform } from '../../modules/platform';
import { regEx } from '../../util/regex';

export async function raiseConfigWarningIssue(
export function raiseConfigWarningIssue(
config: RenovateConfig,
error: Error
): Promise<void> {
logger.debug('raiseConfigWarningIssue()');
let body = `There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.\n\n`;
const title = `Action Required: Fix Renovate Configuration`;
const body = `There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.\n\n`;
const notificationName = 'configErrorIssue';
return raiseWarningIssue(config, notificationName, title, body, error);
}

async function raiseWarningIssue(
config: RenovateConfig,
notificationName: string,
title: string,
initialBody: string,
error: Error
): Promise<void> {
let body = initialBody;
if (error.validationSource) {
body += `Location: \`${error.validationSource}\`\n`;
}
body += `Error type: ${error.validationError!}\n`;
if (error.validationError) {
body += `Error type: ${error.validationError}\n`;
}
if (error.validationMessage) {
body += `Message: \`${error.validationMessage.replace(
regEx(/`/g),
"'"
)}\`\n`;
}

const pr = await platform.getBranchPr(config.onboardingBranch!);
if (pr?.state === 'open') {
logger.debug('Updating onboarding PR with config error notice');
body = `## Action Required: Fix Renovate Configuration\n\n${body}`;
body += `\n\nOnce you have resolved this problem (in this onboarding branch), Renovate will return to providing you with a preview of your repository's configuration.`;
if (GlobalConfig.get('dryRun')) {
logger.info(`DRY-RUN: Would update PR #${pr.number}`);
} else {
try {
await platform.updatePr({
number: pr.number,
prTitle: config.onboardingPrTitle!,
prBody: body,
});
} catch (err) /* istanbul ignore next */ {
logger.warn({ err }, 'Error updating onboarding PR');
}
}
} else if (GlobalConfig.get('dryRun')) {
logger.info('DRY-RUN: Would ensure config error issue');
} else if (config.suppressNotifications?.includes('configErrorIssue')) {
await handleOnboardingPr(pr, body);
return;
}

if (GlobalConfig.get('dryRun')) {
logger.info(
'configErrorIssue - configuration failure, issues will be suppressed'
{ configError: error },
'DRY-RUN: Would ensure configuration error issue'
);
} else {
const once = false;
const shouldReopen = config.configWarningReuseIssue;
const res = await platform.ensureIssue({
title: `Action Required: Fix Renovate Configuration`,
body,
once,
shouldReOpen: shouldReopen,
confidential: config.confidential,
return;
}

if (config.suppressNotifications?.includes(notificationName)) {
logger.info(
{ notificationName },
'Configuration failure, issues will be suppressed'
);
return;
}

const res = await platform.ensureIssue({
title,
body,
once: false,
shouldReOpen: config.configWarningReuseIssue,
confidential: config.confidential,
});
if (res === 'created') {
logger.warn({ configError: error, res }, 'Configuration Warning');
}
}

async function handleOnboardingPr(pr: Pr, issueMessage: string): Promise<void> {
logger.debug('Updating onboarding PR with config error notice');
if (GlobalConfig.get('dryRun')) {
logger.info(`DRY-RUN: Would update PR #${pr.number}`);
return;
}

let prBody = `## Action Required: Fix Renovate Configuration\n\n${issueMessage}`;
prBody += `\n\nOnce you have resolved this problem (in this onboarding branch), Renovate will return to providing you with a preview of your repository's configuration.`;

try {
await platform.updatePr({
number: pr.number,
prTitle: pr.title,
prBody,
});
if (res === 'created') {
logger.warn({ configError: error, res }, 'Config Warning');
}
} catch (err) /* istanbul ignore next */ {
logger.warn({ err }, 'Error updating onboarding PR');
}
}