diff --git a/.env.example b/.env.example index 3eab4bc..f4fe38c 100644 --- a/.env.example +++ b/.env.example @@ -9,4 +9,7 @@ LOG_LEVEL=debug LANGUAGE_API_ENDPOINT= LANGUAGE_API_KEY= APPLICATIONINSIGHTS_CONNECTION_STRING= -DATABASE_CONNECTION_STRING= \ No newline at end of file +DATABASE_CONNECTION_STRING= + +# Set a value if you want to check whether a Copilot license has been assigned to a user before opening the survey issue. Leave empty if you don't. +VALIDATE_SEAT_ASSIGNMENT=true \ No newline at end of file diff --git a/app.yml b/app.yml index edf3efb..7d0fc4f 100644 --- a/app.yml +++ b/app.yml @@ -76,6 +76,10 @@ default_permissions: # https://developer.github.com/v3/apps/permissions/#metadata-permissions metadata: read + # Organization permissions for "GitHub Copilot Business" + # https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps?apiVersion=2022-11-28#organization-permissions-for-github-copilot-business + organization_copilot_seat_management: write + # Retrieve Pages statuses, configuration, and builds, as well as create new builds. # https://developer.github.com/v3/apps/permissions/#permission-on-pages # pages: read diff --git a/index.js b/index.js index a3ad268..8e7162b 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ const { TextAnalysisClient, AzureKeyCredential, } = require("@azure/ai-language-text"); -const { LANGUAGE_API_KEY, LANGUAGE_API_ENDPOINT, DATABASE_CONNECTION_STRING } = +const { LANGUAGE_API_KEY, LANGUAGE_API_ENDPOINT, DATABASE_CONNECTION_STRING, VALIDATE_SEAT_ASSIGNMENT, APPLICATIONINSIGHTS_CONNECTION_STRING } = process.env; module.exports = (app) => { @@ -86,18 +86,31 @@ module.exports = (app) => { // display the body for the issue app.log.info(fileContent); - // create an issue using fileContent as body if pr_author is included in copilotSeats - try { - await context.octokit.issues.create({ - owner: organization_name, - repo: context.payload.repository.name, - title: "Copilot Usage - PR#" + pr_number.toString(), - body: fileContent, - assignee: context.payload.pull_request.user.login, - }); - } catch (err) { - app.log.error(err); - appInsights.trackException({ exception: err }); + // create an issue using fileContent as body if pr_author is included in copilotSeats using Copilot Seat Billing api + let copilotSeats = await context.octokit.request( + "GET /orgs/{org}/copilot/billing/seats", + { + org: organization_name, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + } + ); + let copilotSeatUsers = copilotSeats.data.seats; + + if ( (Boolean(VALIDATE_SEAT_ASSIGNMENT) && copilotSeatUsers.some(user => user.assignee.login == pr_author)) || !Boolean(VALIDATE_SEAT_ASSIGNMENT)) { + try { + await context.octokit.issues.create({ + owner: organization_name, + repo: context.payload.repository.name, + title: "Copilot Usage - PR#" + pr_number.toString(), + body: fileContent, + assignee: context.payload.pull_request.user.login, + }); + } catch (err) { + app.log.error(err); + appInsights.trackException({ exception: err }); + } } }); @@ -452,7 +465,7 @@ module.exports = (app) => { // Define class for app insights. If no instrumentation key is provided, then no app insights will be used. class AppInsights { constructor() { - if (process.env.APPLICATIONINSIGHTS_CONNECTION_STRING) { + if (APPLICATIONINSIGHTS_CONNECTION_STRING) { this.appInsights = require("applicationinsights"); this.appInsights.setup().start(); this.appIClient = this.appInsights.defaultClient; diff --git a/package-lock.json b/package-lock.json index 94b9754..3ce9da4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2150,9 +2150,9 @@ } }, "node_modules/@octokit/webhooks": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz", - "integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==", + "version": "9.26.3", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz", + "integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==", "dependencies": { "@octokit/request-error": "^2.0.2", "@octokit/webhooks-methods": "^2.0.0", @@ -3201,12 +3201,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -3214,7 +3214,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -4085,16 +4085,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -4176,9 +4176,9 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -6790,9 +6790,9 @@ } }, "node_modules/probot": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz", - "integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==", + "version": "12.3.3", + "resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz", + "integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==", "dependencies": { "@octokit/core": "^3.2.4", "@octokit/plugin-enterprise-compatibility": "^1.2.8", @@ -6801,7 +6801,7 @@ "@octokit/plugin-retry": "^3.0.6", "@octokit/plugin-throttling": "^3.3.4", "@octokit/types": "^8.0.0", - "@octokit/webhooks": "^9.8.4", + "@octokit/webhooks": "^9.26.3", "@probot/get-private-key": "^1.1.0", "@probot/octokit-plugin-config": "^1.0.0", "@probot/pino": "^2.2.0", @@ -6956,9 +6956,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", diff --git a/test/index.test.js b/test/index.test.js index a237c7e..78724ee 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -81,7 +81,13 @@ describe("My Probot app", () => { expect(body).toMatchObject(expected_issue); return true; }) - .reply(200); + .reply(200) + + // Mock the call to the seats billing API + .get("/orgs/mageroni/copilot/billing/seats") + .reply(200, { + seats: [{"assignee":{"login": "mageroni"}}] + }); // Receive a webhook event await probot.receive({ name: "pull_request", payload : payload_pr_closed });