From 6b6e461a30a760b15701b0072ab24d2aec720767 Mon Sep 17 00:00:00 2001 From: Anudeep Date: Sun, 17 Mar 2024 12:37:42 +0530 Subject: [PATCH] feat: added ceat info (#155) * feat: added ceat info * bump version --- package-lock.json | 4 +- package.json | 2 +- src/extensions/ci-info.js | 126 ++++++++++++++++++++++++++++++ src/extensions/index.js | 3 + src/helpers/metadata.helper.js | 18 ++++- src/index.d.ts | 18 +++-- test/ext-ci-info.spec.js | 138 +++++++++++++++++++++++++++++++++ test/mocks/slack.mock.js | 31 ++++++++ test/mocks/teams.mock.js | 40 ++++++++++ 9 files changed, 369 insertions(+), 11 deletions(-) create mode 100644 src/extensions/ci-info.js create mode 100644 test/ext-ci-info.spec.js diff --git a/package-lock.json b/package-lock.json index a6dd326..342dd18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "test-results-reporter", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "test-results-reporter", - "version": "1.1.1", + "version": "1.1.2", "license": "ISC", "dependencies": { "async-retry": "^1.3.3", diff --git a/package.json b/package.json index dc41350..e76802c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "test-results-reporter", - "version": "1.1.1", + "version": "1.1.2", "description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB", "main": "src/index.js", "types": "./src/index.d.ts", diff --git a/src/extensions/ci-info.js b/src/extensions/ci-info.js new file mode 100644 index 0000000..be67799 --- /dev/null +++ b/src/extensions/ci-info.js @@ -0,0 +1,126 @@ +const { STATUS, HOOK } = require("../helpers/constants"); +const { getCIInformation } = require('../helpers/ci'); +const { addTeamsExtension, addSlackExtension, addChatExtension } = require('../helpers/extension.helper'); +const { getTeamsMetaDataText, getSlackMetaDataText, getChatMetaDataText } = require('../helpers/metadata.helper'); + +/** + * + * @param {object} param0 - the payload object + * @param {import('..').Extension} param0.extension - The result object + * + */ +async function run({ target, extension, payload, result }) { + extension.inputs = Object.assign({}, default_inputs, extension.inputs); + if (target.name === 'teams') { + extension.inputs = Object.assign({}, default_inputs_teams, extension.inputs); + const text = await get_text({ target, extension, result }); + if (text) { + addTeamsExtension({ payload, extension, text }); + } + } else if (target.name === 'slack') { + extension.inputs = Object.assign({}, default_inputs_slack, extension.inputs); + const text = await get_text({ target, extension, result }); + if (text) { + addSlackExtension({ payload, extension, text }); + } + } else if (target.name === 'chat') { + extension.inputs = Object.assign({}, default_inputs_chat, extension.inputs); + const text = await get_text({ target, extension, result }); + if (text) { + addChatExtension({ payload, extension, text }); + } + } +} + +/** + * + * @param {import('..').CIInfoInputs} inputs + */ +function get_repository_elements(inputs) { + const elements = []; + const ci = getCIInformation(); + if (inputs.show_repository && ci && ci.repository_url && ci.repository_name) { + elements.push({ label: 'Repository', key: ci.repository_name, value: ci.repository_url, type: 'hyperlink' }); + } + if (inputs.show_repository_branch && ci && ci.repository_ref) { + elements.push({ key: 'Branch', value: ci.repository_ref.replace('refs/heads/', '') }); + } + return elements; +} + +/** + * + * @param {import('..').CIInfoInputs} inputs + */ +function get_build_elements(inputs) { + let elements = []; + const ci = getCIInformation(); + if (inputs.show_build && ci && ci.build_url) { + const name = (ci.build_name || 'Build') + (ci.build_number ? ` #${ci.build_number}` : ''); + elements.push({ key: name, value: ci.build_url, type: 'hyperlink' }); + } + if (inputs.data) { + elements = elements.concat(inputs.data); + } + return elements; +} + +async function get_text({ target, extension, result }) { + const repository_elements = get_repository_elements(extension.inputs); + const build_elements = get_build_elements(extension.inputs); + if (target.name === 'teams') { + const repository_text = await getTeamsMetaDataText({ elements: repository_elements, target, extension, result, default_condition: default_options.condition }); + const build_text = await getTeamsMetaDataText({ elements: build_elements, target, extension, result, default_condition: default_options.condition }); + if (build_text) { + return `${repository_text ? `${repository_text}\n\n` : '' }${build_text}`; + } else { + return repository_text; + } + } else if (target.name === 'slack') { + const repository_text = await getSlackMetaDataText({ elements: repository_elements, target, extension, result, default_condition: default_options.condition }); + const build_text = await getSlackMetaDataText({ elements: build_elements, target, extension, result, default_condition: default_options.condition }); + if (build_text) { + return `${repository_text ? `${repository_text}\n` : '' }${build_text}`; + } else { + return repository_text; + } + } else if (target.name === 'chat') { + const repository_text = await getChatMetaDataText({ elements: repository_elements, target, extension, result, default_condition: default_options.condition }); + const build_text = await getChatMetaDataText({ elements: build_elements, target, extension, result, default_condition: default_options.condition }); + if (build_text) { + return `${repository_text ? `${repository_text}
` : '' }${build_text}`; + } else { + return repository_text; + } + } +} + +const default_options = { + hook: HOOK.END, + condition: STATUS.PASS_OR_FAIL, +} + +const default_inputs = { + title: '', + show_repository: true, + show_repository_branch: true, + show_build: true, +} + +const default_inputs_teams = { + + separator: true +} + +const default_inputs_slack = { + separator: false +} + +const default_inputs_chat = { + separator: true +} + +module.exports = { + run, + default_options +} \ No newline at end of file diff --git a/src/extensions/index.js b/src/extensions/index.js index 6f7c341..4e174e7 100644 --- a/src/extensions/index.js +++ b/src/extensions/index.js @@ -6,6 +6,7 @@ const qc_test_summary = require('./quick-chart-test-summary'); const percy_analysis = require('./percy-analysis'); const custom = require('./custom'); const metadata = require('./metadata'); +const ci_info = require('./ci-info'); const { EXTENSION } = require('../helpers/constants'); const { checkCondition } = require('../helpers/helper'); @@ -50,6 +51,8 @@ function getExtensionRunner(extension) { return custom; case EXTENSION.METADATA: return metadata; + case EXTENSION.CI_INFO: + return ci_info; default: return require(extension.name); } diff --git a/src/helpers/metadata.helper.js b/src/helpers/metadata.helper.js index f4b85a8..b3cd541 100644 --- a/src/helpers/metadata.helper.js +++ b/src/helpers/metadata.helper.js @@ -17,7 +17,11 @@ async function getSlackMetaDataText({ elements, target, extension, result, defau if (await is_valid({ element, result, default_condition })) { if (element.type === 'hyperlink') { const url = await get_url({ url: element.value, target, result, extension }); - items.push(`<${url}|${element.key}>`); + if (element.label) { + items.push(`*${element.label}:* <${url}|${element.key}>`); + } else { + items.push(`<${url}|${element.key}>`); + } } else if (element.key) { items.push(`*${element.key}:* ${element.value}`); } else { @@ -45,7 +49,11 @@ async function getTeamsMetaDataText({ elements, target, extension, result, defau if (await is_valid({ element, result, default_condition })) { if (element.type === 'hyperlink') { const url = await get_url({ url: element.value, target, result, extension }); - items.push(`[${element.key}](${url})`); + if (element.label) { + items.push(`**${element.label}:** [${element.key}](${url})`); + } else { + items.push(`[${element.key}](${url})`); + } } else if (element.key) { items.push(`**${element.key}:** ${element.value}`); } else { @@ -73,7 +81,11 @@ async function getChatMetaDataText({ elements, target, extension, result, defaul if (await is_valid({ element, result, default_condition })) { if (element.type === 'hyperlink') { const url = await get_url({ url: element.value, target, result, extension }); - items.push(`${element.key}`); + if (element.label) { + items.push(`${element.label}: ${element.key}`); + } else { + items.push(`${element.key}`); + } } else if (element.key) { items.push(`${element.key}: ${element.value}`); } else { diff --git a/src/index.d.ts b/src/index.d.ts index a76fe5d..36541d1 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,10 +1,10 @@ -import { User, Schedule } from 'rosters'; -import TestResult from 'test-results-parser/src/models/TestResult'; import { PerformanceParseOptions } from 'performance-results-parser'; -import { ParseOptions } from 'test-results-parser'; import PerformanceTestResult from 'performance-results-parser/src/models/PerformanceTestResult'; +import { Schedule, User } from 'rosters'; +import { ParseOptions } from 'test-results-parser'; +import TestResult from 'test-results-parser/src/models/TestResult'; -export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'metadata' | 'custom'; +export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'metadata' | 'ci-info' | 'custom'; export type Hook = 'start' | 'end'; export type TargetName = 'slack' | 'teams' | 'chat' | 'custom' | 'delay'; export type PublishReportType = 'test-summary' | 'test-summary-slim' | 'failure-details'; @@ -54,11 +54,18 @@ export interface MentionInputs extends ExtensionInputs { schedule?: Schedule; } +export interface CIInfoInputs extends ExtensionInputs { + show_repository?: boolean; + show_repository_branch?: boolean; + show_build?: boolean; + data?: Metadata[]; +} + export interface Extension { name: ExtensionName; condition?: Condition; hook?: Hook; - inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs | MetadataInputs; + inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs | MetadataInputs | CIInfoInputs; } export interface PercyAnalysisInputs extends ExtensionInputs { @@ -124,6 +131,7 @@ export interface HyperlinksExtension extends Extension { } export interface Metadata { + label?: string; key?: string; value: string; type?: string; diff --git a/test/ext-ci-info.spec.js b/test/ext-ci-info.spec.js new file mode 100644 index 0000000..5db1fea --- /dev/null +++ b/test/ext-ci-info.spec.js @@ -0,0 +1,138 @@ +const { mock } = require('pactum'); +const assert = require('assert'); +const { publish } = require('../src'); + +describe('extensions - ci-info', () => { + + beforeEach(() => { + process.env.GITHUB_ACTIONS = ''; + process.env.GITHUB_SERVER_URL = ''; + process.env.GITHUB_REPOSITORY = ''; + process.env.GITHUB_REF = ''; + process.env.GITHUB_SHA = ''; + process.env.GITHUB_RUN_ID = ''; + process.env.GITHUB_RUN_NUMBER = ''; + process.env.GITHUB_WORKFLOW = ''; + + process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI = ''; + process.env.BUILD_REPOSITORY_URI = ''; + process.env.BUILD_REPOSITORY_NAME = ''; + process.env.BUILD_SOURCEBRANCH = ''; + process.env.BUILD_SOURCEVERSION = ''; + process.env.BUILD_BUILDID = ''; + process.env.BUILD_BUILDNUMBER = ''; + process.env.BUILD_DEFINITIONNAME = ''; + }); + + it('should send test-summary with ci-info to teams with no ci information', async () => { + const id = mock.addInteraction('post test-summary to teams'); + await publish({ + config: { + targets: [ + { + name: 'teams', + inputs: { + url: 'http://localhost:9393/message' + }, + extensions: [ + { + name: 'ci-info' + } + ] + } + ], + results: [ + { + type: 'testng', + files: [ + 'test/data/testng/single-suite.xml' + ] + } + ] + } + }); + assert.equal(mock.getInteraction(id).exercised, true); + }); + + it('should send test-summary with github ci information to teams', async () => { + process.env.GITHUB_ACTIONS = 'GITHUB_ACTIONS'; + process.env.GITHUB_SERVER_URL = 'https://github.com'; + process.env.GITHUB_REPOSITORY = 'test/test'; + process.env.GITHUB_REF = '/refs/heads/feature-test'; + process.env.GITHUB_SHA = 'sha'; + process.env.GITHUB_RUN_ID = 'id-123'; + process.env.GITHUB_RUN_NUMBER = 'number-123'; + process.env.GITHUB_WORKFLOW = 'Build'; + const id = mock.addInteraction('post test-summary with ci-info to teams'); + await publish({ + config: { + targets: [ + { + name: 'teams', + inputs: { + url: 'http://localhost:9393/message' + }, + extensions: [ + { + name: 'ci-info' + } + ] + } + ], + results: [ + { + type: 'testng', + files: [ + 'test/data/testng/single-suite.xml' + ] + } + ] + } + }); + assert.equal(mock.getInteraction(id).exercised, true); + }); + + it('should send test-summary with azure devops ci information to slack', async () => { + process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI = 'https://dev.azure.com/'; + process.env.SYSTEM_TEAMPROJECT = 'test'; + process.env.BUILD_REPOSITORY_URI = 'https://github.com/test/test'; + process.env.BUILD_REPOSITORY_NAME = 'test/test'; + process.env.BUILD_SOURCEBRANCH = '/refs/heads/feature-test'; + process.env.BUILD_SOURCEVERSION = 'sha'; + process.env.BUILD_BUILDID = 'id-123'; + process.env.BUILD_BUILDNUMBER = 'number-123'; + process.env.BUILD_DEFINITIONNAME = 'Build'; + const id = mock.addInteraction('post test-summary with ci-info to slack'); + await publish({ + config: { + targets: [ + { + name: 'slack', + inputs: { + url: 'http://localhost:9393/message' + }, + extensions: [ + { + name: 'ci-info' + } + ] + } + ], + results: [ + { + type: 'testng', + files: [ + 'test/data/testng/single-suite.xml' + ] + } + ] + } + }); + assert.equal(mock.getInteraction(id).exercised, true); + }); + + afterEach(() => { + mock.clearInteractions(); + }); + +}); \ No newline at end of file diff --git a/test/mocks/slack.mock.js b/test/mocks/slack.mock.js index 2b9313b..67a5afe 100644 --- a/test/mocks/slack.mock.js +++ b/test/mocks/slack.mock.js @@ -612,4 +612,35 @@ addInteractionHandler('post test-summary with metadata to slack', () => { status: 200 } } +}); + +addInteractionHandler('post test-summary with ci-info to slack', () => { + return { + request: { + method: 'POST', + path: '/message', + body: { + "attachments": [ + { + "color": "#36A64F", + "blocks": [ + { + "@DATA:TEMPLATE@": "SLACK_ROOT_SINGLE_SUITE" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Repository:* | *Branch:* /feature-test\n" + } + } + ] + } + ] + } + }, + response: { + status: 200 + } + } }); \ No newline at end of file diff --git a/test/mocks/teams.mock.js b/test/mocks/teams.mock.js index 00b30b0..1673f93 100644 --- a/test/mocks/teams.mock.js +++ b/test/mocks/teams.mock.js @@ -1531,4 +1531,44 @@ addInteractionHandler('post test-summary with extensions and testbeats to teams' status: 200 } } +}); + +addInteractionHandler('post test-summary with ci-info to teams', () => { + return { + request: { + method: 'POST', + path: '/message', + body: { + "type": "message", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "@DATA:TEMPLATE@": "TEAMS_ROOT_TITLE_SINGLE_SUITE" + }, + { + "@DATA:TEMPLATE@": "TEAMS_ROOT_RESULTS_SINGLE_SUITE", + }, + { + "type": "TextBlock", + "text": "**Repository:** [test/test](https://github.com/test/test) | **Branch:** /feature-test\n\n[Build #number-123](https://github.com/test/test/commit/sha/checks/id-123)", + "wrap": true, + "separator": true + } + ], + "actions": [] + } + } + ] + } + }, + response: { + status: 200 + } + } }); \ No newline at end of file