diff --git a/lib/utils.js b/lib/utils.js index 06a3214f9..47a02ff9d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -191,8 +191,9 @@ class Utils { const exchangeUrl = jfrogCredentials.jfrogUrl.replace(/\/$/, '') + '/access/api/v1/oidc/token'; core.debug('Exchanging GitHub JSON web token with a JFrog access token...'); let projectKey = process.env.JF_PROJECT || ''; - let jobId = process.env.GITHUB_JOB || ''; + let jobId = this.getGithubJobId(); let runId = process.env.GITHUB_RUN_ID || ''; + let githubRepository = process.env.GITHUB_REPOSITORY || ''; const httpClient = new http_client_1.HttpClient(); const data = `{ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -202,6 +203,7 @@ class Utils { "project_key": "${projectKey}", "gh_job_id": "${jobId}", "gh_run_id": "${runId}", + "gh_repo": "${githubRepository}", "application_key": "${applicationKey}" }`; const additionalHeaders = { @@ -825,13 +827,16 @@ class Utils { } static getUsageBadge() { const platformUrl = Utils.getPlatformUrl(); - const githubJobId = Utils.encodeForUrl(process.env.GITHUB_JOB || ''); - const gitRepo = Utils.encodeForUrl(process.env.GITHUB_REPOSITORY || ''); + const githubJobId = this.getGithubJobId(); + const gitRepo = process.env.GITHUB_REPOSITORY || ''; const runId = process.env.GITHUB_RUN_ID || ''; - return `![](${platformUrl}ui/api/v1/u?s=1&m=1&job_id=${githubJobId}&run_id=${runId}&git_repo=${gitRepo})`; - } - static encodeForUrl(value) { - return encodeURIComponent(value); + const url = new URL(`${platformUrl}ui/api/v1/u`); + url.searchParams.set(Utils.SOURCE_PARAM_KEY, Utils.SOURCE_PARAM_VALUE); + url.searchParams.set(Utils.METRIC_PARAM_KEY, Utils.METRIC_PARAM_VALUE); + url.searchParams.set(Utils.JOB_ID_PARAM_KEY, githubJobId); + url.searchParams.set(Utils.RUN_ID_PARAM_KEY, runId); + url.searchParams.set(Utils.GIT_REPO_PARAM_KEY, gitRepo); + return `![](${url.toString()})`; } /** * Checks if the header image is accessible via the internet. @@ -872,6 +877,14 @@ class Utils { } return tempDir; } + /** + * Retrieves the GitHub job ID, which in this context refers to the GitHub workflow name. + * Note: We use "job" instead of "workflow" to align with our terminology, where "GitHub job summary" + * refers to the entire workflow summary. Here, "job ID" means the workflow name, not individual jobs within the workflow. + */ + static getGithubJobId() { + return process.env.GITHUB_WORKFLOW || ''; + } } exports.Utils = Utils; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -939,3 +952,15 @@ Utils.CUSTOM_SERVER_ID = 'custom-server-id'; Utils.MARKDOWN_HEADER_PNG_URL = 'https://media.jfrog.com/wp-content/uploads/2024/09/02161430/jfrog-job-summary.svg'; // Flag to indicate if the summary header is accessible, can be undefined if not checked yet. Utils.isSummaryHeaderAccessible = undefined; +// Job ID query parameter key +Utils.JOB_ID_PARAM_KEY = 'job_id'; +// Run ID query parameter key +Utils.RUN_ID_PARAM_KEY = 'run_id'; +// Git repository query parameter key +Utils.GIT_REPO_PARAM_KEY = 'git_repo'; +// Source query parameter indicating the source of the request +Utils.SOURCE_PARAM_KEY = 's'; +Utils.SOURCE_PARAM_VALUE = '1'; +// Metric query parameter indicating the metric type +Utils.METRIC_PARAM_KEY = 'm'; +Utils.METRIC_PARAM_VALUE = '1'; diff --git a/src/utils.ts b/src/utils.ts index 73133db39..342cacbca 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -83,6 +83,18 @@ export class Utils { private static MARKDOWN_HEADER_PNG_URL: string = 'https://media.jfrog.com/wp-content/uploads/2024/09/02161430/jfrog-job-summary.svg'; // Flag to indicate if the summary header is accessible, can be undefined if not checked yet. private static isSummaryHeaderAccessible: boolean | undefined = undefined; + // Job ID query parameter key + private static readonly JOB_ID_PARAM_KEY: string = 'job_id'; + // Run ID query parameter key + private static readonly RUN_ID_PARAM_KEY: string = 'run_id'; + // Git repository query parameter key + private static readonly GIT_REPO_PARAM_KEY: string = 'git_repo'; + // Source query parameter indicating the source of the request + private static readonly SOURCE_PARAM_KEY: string = 's'; + private static readonly SOURCE_PARAM_VALUE: string = '1'; + // Metric query parameter indicating the metric type + private static readonly METRIC_PARAM_KEY: string = 'm'; + private static readonly METRIC_PARAM_VALUE: string = '1'; /** * Retrieves server credentials for accessing JFrog's server @@ -234,8 +246,9 @@ export class Utils { core.debug('Exchanging GitHub JSON web token with a JFrog access token...'); let projectKey: string = process.env.JF_PROJECT || ''; - let jobId: string = process.env.GITHUB_JOB || ''; + let jobId: string = this.getGithubJobId(); let runId: string = process.env.GITHUB_RUN_ID || ''; + let githubRepository: string = process.env.GITHUB_REPOSITORY || ''; const httpClient: HttpClient = new HttpClient(); const data: string = `{ @@ -246,6 +259,7 @@ export class Utils { "project_key": "${projectKey}", "gh_job_id": "${jobId}", "gh_run_id": "${runId}", + "gh_repo": "${githubRepository}", "application_key": "${applicationKey}" }`; @@ -912,15 +926,17 @@ export class Utils { static getUsageBadge(): string { const platformUrl: string = Utils.getPlatformUrl(); - const githubJobId: string = Utils.encodeForUrl(process.env.GITHUB_JOB || ''); - const gitRepo: string = Utils.encodeForUrl(process.env.GITHUB_REPOSITORY || ''); + const githubJobId: string = this.getGithubJobId(); + const gitRepo: string = process.env.GITHUB_REPOSITORY || ''; const runId: string = process.env.GITHUB_RUN_ID || ''; + const url: URL = new URL(`${platformUrl}ui/api/v1/u`); - return `![](${platformUrl}ui/api/v1/u?s=1&m=1&job_id=${githubJobId}&run_id=${runId}&git_repo=${gitRepo})`; - } - - private static encodeForUrl(value: string): string { - return encodeURIComponent(value); + url.searchParams.set(Utils.SOURCE_PARAM_KEY, Utils.SOURCE_PARAM_VALUE); + url.searchParams.set(Utils.METRIC_PARAM_KEY, Utils.METRIC_PARAM_VALUE); + url.searchParams.set(Utils.JOB_ID_PARAM_KEY, githubJobId); + url.searchParams.set(Utils.RUN_ID_PARAM_KEY, runId); + url.searchParams.set(Utils.GIT_REPO_PARAM_KEY, gitRepo); + return `![](${url.toString()})`; } /** @@ -959,6 +975,15 @@ export class Utils { } return tempDir; } + + /** + * Retrieves the GitHub job ID, which in this context refers to the GitHub workflow name. + * Note: We use "job" instead of "workflow" to align with our terminology, where "GitHub job summary" + * refers to the entire workflow summary. Here, "job ID" means the workflow name, not individual jobs within the workflow. + */ + static getGithubJobId(): string { + return process.env.GITHUB_WORKFLOW || ''; + } } export interface DownloadDetails { diff --git a/test/main.spec.ts b/test/main.spec.ts index 19bedf1c2..6e13f31f9 100644 --- a/test/main.spec.ts +++ b/test/main.spec.ts @@ -568,36 +568,44 @@ describe('setUsageEnvVars', () => { }); }); -describe('Utils', () => { +describe('Test correct encoding of badge URL', () => { describe('getUsageBadge', () => { beforeEach(() => { process.env.JF_URL = 'https://example.jfrog.io/'; - process.env.GITHUB_JOB = 'test-job'; - process.env.GITHUB_REPOSITORY = 'test/repo'; process.env.GITHUB_RUN_ID = '123'; }); afterEach(() => { delete process.env.JF_URL; - delete process.env.GITHUB_JOB; + delete process.env.GITHUB_WORKFLOW; delete process.env.GITHUB_REPOSITORY; delete process.env.GITHUB_RUN_ID; }); it('should return the correct usage badge URL', () => { + process.env.GITHUB_WORKFLOW = 'test-job'; + process.env.GITHUB_REPOSITORY = 'test/repo'; const expectedBadge: string = '![](https://example.jfrog.io/ui/api/v1/u?s=1&m=1&job_id=test-job&run_id=123&git_repo=test%2Frepo)'; expect(Utils.getUsageBadge()).toBe(expectedBadge); }); - it('should URL encode the job ID and repository', () => { - process.env.GITHUB_JOB = 'test job'; + it('should URL encode the job ID and repository with spaces', () => { + process.env.GITHUB_WORKFLOW = 'test job'; process.env.GITHUB_REPOSITORY = 'test repo'; - const expectedBadge: string = '![](https://example.jfrog.io/ui/api/v1/u?s=1&m=1&job_id=test%20job&run_id=123&git_repo=test%20repo)'; + const expectedBadge: string = '![](https://example.jfrog.io/ui/api/v1/u?s=1&m=1&job_id=test+job&run_id=123&git_repo=test+repo)'; + expect(Utils.getUsageBadge()).toBe(expectedBadge); + }); + + it('should URL encode the job ID and repository with special characters', () => { + process.env.GITHUB_WORKFLOW = 'test/job@workflow'; + process.env.GITHUB_REPOSITORY = 'test/repo@special'; + const expectedBadge: string = + '![](https://example.jfrog.io/ui/api/v1/u?s=1&m=1&job_id=test%2Fjob%40workflow&run_id=123&git_repo=test%2Frepo%40special)'; expect(Utils.getUsageBadge()).toBe(expectedBadge); }); it('should handle missing environment variables gracefully', () => { - delete process.env.GITHUB_JOB; + delete process.env.GITHUB_WORKFLOW; delete process.env.GITHUB_REPOSITORY; const expectedBadge: string = '![](https://example.jfrog.io/ui/api/v1/u?s=1&m=1&job_id=&run_id=123&git_repo=)'; expect(Utils.getUsageBadge()).toBe(expectedBadge);