Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/DISCUSSION_TEMPLATE/discussions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ body:
label: Relevant Logs
description: |
Please copy and paste any relevant log output.
> [!TIP]
> This will be automatically formatted into code, there is no need to insert backticks.
render: shell
- type: textarea
Expand All @@ -38,7 +37,6 @@ body:
label: Anything Else
description: |
Links? References? Anything that will provide more context.
> [!TIP]
> You can attach images or log files by clicking this area to highlight it and then dragging files in.
- type: checkboxes
id: terms
Expand Down
12 changes: 6 additions & 6 deletions .github/actions/config/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ module.exports = {
*/
issue: {
/**
* Controls automatic label creation for repository
* Controls automatic label update for repository
*
* When enabled, the system will automatically create missing labels defined
* When enabled, the system will automatically update repository labels defined
* in the labels configuration during workflow execution. When disabled,
* the system will only use existing labels without creating missing ones.
* the system will only use existing labels.
*
* Note: If enabled, the system will create GitHub issues during each workflow run,
* Note: If enabled, the system will update GitHub issues during each workflow run,
* including a reminder to set this value to "false" after initial setup. This can result in
* numerous notifications for repository maintainers.
*
* @type {boolean}
* @default false
*/
createLabels: false,
updateLabels: false,

/**
* Predefined issue label definitions used across the repository
*
* Contains standardized label definitions (color, description) that are used
* for categorizing issues in GitHub and generating release notes. These labels
* can be automatically created when createLabels is true.
* can be automatically updated when updateLabels is true.
*
* @type {Object}
*/
Expand Down
30 changes: 27 additions & 3 deletions .github/actions/core/Logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Logger {
*/
#format(message, meta = {}) {
const parts = [`[${this.context}]`];
if (meta.level && meta.level !== 'info') parts.push(`[${meta.level.toUpperCase()}]`);
if (meta.level) parts.push(`[${meta.level.toUpperCase()}]`);
if (meta.timestamp) parts.push(`[${new Date().toISOString()}]`);
if (meta.component) parts.push(`[${meta.component}]`);
return `${parts.join('')} ${message}`;
Expand All @@ -71,7 +71,19 @@ class Logger {
debug(message, meta = {}) {
if (!this.#allow('debug')) return;
const logMeta = { level: 'debug', timestamp: true, ...meta };
this.core.info(this.#format(message, logMeta));
const formattedMessage = this.#format(message, logMeta);
if (meta.file) {
const params = {
file: meta.file,
startLine: meta.line || 1,
startColumn: meta.col || 1,
title: meta.title || 'Debug',
message: formattedMessage
};
this.core.info(formattedMessage, params);
} else {
this.core.info(formattedMessage);
}
}

/**
Expand Down Expand Up @@ -109,7 +121,19 @@ class Logger {
info(message, meta = {}) {
if (!this.#allow('info')) return;
const logMeta = { level: 'info', ...meta };
this.core.info(this.#format(message, logMeta));
const formattedMessage = this.#format(message, logMeta);
if (meta.file) {
const params = {
file: meta.file,
startLine: meta.line || 1,
startColumn: meta.col || 1,
title: meta.title || 'Info',
message: formattedMessage
};
this.core.info(formattedMessage, params);
} else {
this.core.info(formattedMessage);
}
}

/**
Expand Down
22 changes: 3 additions & 19 deletions .github/actions/handlers/Workflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class WorkflowHandler extends Action {
return this.execute('configure repository', async () => {
this.logger.info('Configuring repository for workflow operations...');
await this.gitService.configure();
this.core.setOutput('publish', this.publish());
this.logger.info('Repository configuration complete');
this.core.setOutput('publish', this.publish());
});
}

Expand Down Expand Up @@ -92,14 +92,10 @@ class WorkflowHandler extends Action {
async reportIssue() {
return this.execute('report workflow issue', async () => {
this.logger.info('Checking for workflow issues...');
if (this.config.get('issue.createLabels') === true && this.context.workflow === 'Chart') {
this.logger.warning('Set "createLabels: false" in config.js after initial setup, to optimize workflow performance.');
}
const templatePath = this.config.get('workflow.template');
const templateContent = await this.fileService.read(templatePath);
const issue = await this.issueService.report(
this.context,
this.labelService,
{
content: templateContent,
service: this.templateService
Expand Down Expand Up @@ -132,24 +128,12 @@ class WorkflowHandler extends Action {
*/
async updateCharts() {
return this.execute('update charts', async () => {
if (this.config.get('issue.updateLabels')) await this.labelService.update();
this.logger.info('Starting the charts update process...');
await this.chartHandler.process();
this.logger.info('Successfully completed the charts update process');
this.logger.info('Charts update process complete');
});
}

/**
* Update issue labels
*
* @returns {Promise<void>}
*/
async updateLabels() {
return this.execute('update issue labels', async () => {
this.logger.info('Updating repository issue labels...');
await this.labelService.update();
this.logger.info('Repository issue labels update complete');
}, false);
}
}

module.exports = WorkflowHandler;
54 changes: 7 additions & 47 deletions .github/actions/services/Issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
* @license BSD-3-Clause
*/
const Action = require('../core/Action');
const GraphQLService = require('./github/GraphQL');
const RestService = require('./github/Rest');
const GitHubService = require('./github');

/**
* Issue service for GitHub issue and label operations
Expand All @@ -25,42 +24,8 @@ class IssueService extends Action {
*/
constructor(params) {
super(params);
this.graphqlService = new GraphQLService(params);
this.restService = new RestService(params);
}

/**
* Validates if workflow has issues that warrant creating an issue
*
* @private
* @param {number} id - Workflow run ID
* @returns {Promise<boolean>} True if issues detected
*/
async #validate(id) {
return this.execute('validate workflow status', async () => {
let hasFailures = false;
const workflowRun = await this.restService.getWorkflowRun(id);
if (['cancelled', 'failure'].includes(workflowRun.conclusion)) {
return true;
}
const jobs = await this.restService.listJobs();
for (const job of jobs) {
if (job.steps) {
const failedSteps = job.steps.filter(step => step.conclusion !== 'success');
if (failedSteps.length) {
hasFailures = true;
break;
}
}
}
const hasWarnings = await this.execute('validate workflow warnings', async () => {
const logsData = await this.restService.getWorkflowRunLogs(id);
if (!logsData) return false;
const regex = /(^|:)warning:/i;
return regex.test(logsData);
}, false);
return hasFailures || hasWarnings;
}, false);
this.graphqlService = new GitHubService.GraphQL(params);
this.restService = new GitHubService.Rest(params);
}

/**
Expand Down Expand Up @@ -107,17 +72,16 @@ class IssueService extends Action {
* Prepares and creates a workflow issue
*
* @param {Object} context - GitHub Actions context
* @param {Object} label - Label service instance
* @param {Object} [template={}] - Template configuration
* @param {string} template.content - Issue template content
* @param {Object} template.service - Template service instance
* @returns {Promise<Object|null>} Created issue data or null on failure
*/
async report(context, label, template = {}) {
async report(context, template = {}) {
return this.execute('report workflow issue', async () => {
const { content, service } = template;
const hasIssues = await this.#validate(context.runId);
if (!hasIssues) return null;
const annotations = await this.restService.getAnnotations();
if (!annotations.length) return null;
const repoUrl = context.payload.repository.html_url;
const isPullRequest = Boolean(context.payload.pull_request);
const branchName = isPullRequest
Expand All @@ -134,14 +98,10 @@ class IssueService extends Action {
RepoURL: repoUrl
});
if (!issueBody) return null;
const labelNames = this.config.get('workflow.labels');
if (this.config.get('issue.createLabels') && label) {
await Promise.all(labelNames.map(labelName => label.add(labelName)));
}
return this.restService.createIssue(
this.config.get('workflow.title'),
issueBody,
labelNames
this.config.get('workflow.labels')
);
}, false);
}
Expand Down
42 changes: 16 additions & 26 deletions .github/actions/services/Label.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,62 +28,52 @@ class LabelService extends Action {
}

/**
* Adds a label to the repository if it doesn't exist
* Sets a label to the repository
*
* @param {string} name - Name of the label to add
* @returns {Promise<boolean>} True if label was created or exists
* @private
* @param {string} name - Name of the label to set
* @returns {Promise<boolean>} True if label was created or updated
*/
async add(name) {
return this.execute(`add '${name}' label`, async () => {
async #set(name) {
return this.execute(`set '${name}' label`, async () => {
if (!name) {
this.logger.warning('Label name is required');
return false;
}
const labelConfig = this.config.get(`issue.labels.${name}`);
if (!labelConfig) {
this.logger.warning(`Label configuration not found for '${name}'`);
this.logger.warning(`Label '${name}' configuration not found`);
return false;
}
const existingLabel = await this.restService.getLabel(name);
if (existingLabel) return true;
if (!this.config.get('issue.createLabels')) {
this.logger.warning(`Label '${name}' not found and createLabels is disabled`);
return false;
}
await this.restService.createLabel(
const method = existingLabel ? 'updateLabel' : 'createLabel';
await this.restService[method](
name,
labelConfig.color,
labelConfig.description
);
this.logger.info(`Successfully created '${name}' label`);
return true;
}, false);
}

/**
* Update repository labels based on configuration
*
* @returns {Promise<string[]>} Array of created label names
* @returns {Promise<void>}
*/
async update() {
return this.execute('update repository issue labels', async () => {
if (!this.config.get('issue.createLabels')) {
this.logger.info('Label creation is disabled in configuration, skipping label updates');
return [];
}
this.logger.info('Updating repository issue labels...');
const message = 'Set "updateLabels: false" after initial setup';
await this.restService.createAnnotation(message, { level: 'warning' });
this.logger.warning(message);
const labelNames = Object.keys(this.config.get('issue.labels'));
const results = await Promise.all(
await Promise.all(
labelNames.map(async labelName => {
const created = await this.add(labelName);
return created ? labelName : null;
await this.#set(labelName);
})
);
const createdLabels = results.filter(Boolean);
if (createdLabels.length) {
this.logger.info(`Successfully updated ${createdLabels.length} issue labels`);
}
return createdLabels;
this.logger.info(`Successfully updated ${labelNames.length} issue labels`);
}, false);
}
}
Expand Down
Loading