├── .deployment ├── .dockerignore ├── .env.example ├── .github ├── CODEOWNERS └── workflows │ └── node.js.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── __mocks__ ├── applicationinsights.js └── mssql.js ├── app.yml ├── deployment ├── parameters.json.example ├── template.bicep └── template.json ├── docs ├── ApplicationInsightsSetting.png ├── DabaseConnectionString.png ├── GitHubAppConfigurationPage.png ├── GitHubAppWebhookURL.png ├── LanguageServiceEndpoint.png └── LanguageServiceKeys.png ├── index.js ├── issue_template ├── copilot-usage-en.md ├── copilot-usage-es.md ├── copilot-usage-fr.md └── copilot-usage-pt.md ├── package-lock.json ├── package.json └── test ├── fixtures ├── issue_body.md ├── issue_comment.created.json ├── issues.edited.json ├── mock-cert.pem └── pull_request.closed.json └── index.test.js /.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | SCM_DO_BUILD_DURING_DEPLOYMENT=true -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/.git 3 | **/README.md 4 | **/LICENSE 5 | **/.vscode 6 | **/npm-debug.log 7 | **/coverage 8 | **/.env 9 | **/.editorconfig 10 | **/dist 11 | **/*.pem 12 | Dockerfile 13 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # The ID of your GitHub App 2 | APP_ID= 3 | PRIVATE_KEY= 4 | WEBHOOK_SECRET=development 5 | 6 | # Use `trace` to get verbose logging or `info` to show less 7 | LOG_LEVEL=debug 8 | 9 | LANGUAGE_API_ENDPOINT= 10 | LANGUAGE_API_KEY= 11 | APPLICATIONINSIGHTS_CONNECTION_STRING= 12 | DATABASE_CONNECTION_STRING= 13 | 14 | # Set to "YES" if you want to check whether a Copilot license has been assigned to a user before opening the survey issue. Any other value if you don't. 15 | VALIDATE_SEAT_ASSIGNMENT=YES 16 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This repository is maintained by: 2 | * @mageroni -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | permissions: 6 | contents: read 7 | 8 | on: 9 | push: 10 | branches: [ "main" ] 11 | pull_request: 12 | branches: [ "main" ] 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Use Node.js 22 | uses: actions/setup-node@v3 23 | - run: npm ci 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.pem 4 | !mock-cert.pem 5 | .env 6 | coverage 7 | lib 8 | .vscode -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@github.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: /fork 4 | [pr]: /compare 5 | [code-of-conduct]: CODE_OF_CONDUCT.md 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Contributions to this project are released to the public under the project's open source license. 10 | 11 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 12 | 13 | ## Issues and PRs 14 | 15 | If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. 16 | 17 | We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. 18 | 19 | ## Submitting a pull request 20 | 21 | 1. [Fork][fork] and clone the repository. 22 | 1. Configure and install the dependencies: `npm install`. 23 | 1. Make sure the tests pass on your machine: `npm test`, note: these tests also apply the linter, so there's no need to lint separately. 24 | 1. Create a new branch: `git checkout -b my-branch-name`. 25 | 1. Make your change, add tests, and make sure the tests still pass. 26 | 1. Push to your fork and [submit a pull request][pr]. 27 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 28 | 29 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 30 | 31 | - Follow the style guide. 32 | - Write and update tests. 33 | - Keep your changes as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 34 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 35 | 36 | Work in Progress pull requests are also welcome to get feedback early on, or if there is something blocked you. 37 | 38 | ## Resources 39 | 40 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 41 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 42 | - [GitHub Help](https://help.github.com) 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-slim 2 | WORKDIR /usr/src/app 3 | COPY package.json package-lock.json ./ 4 | RUN npm ci --production 5 | RUN npm cache clean --force 6 | ENV NODE_ENV="production" 7 | COPY . . 8 | CMD [ "npm", "start" ] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # copilot-survey-engine 2 | 3 | > A GitHub App built with [Probot](https://github.com/probot/probot) to prompt developers about their experience with Copilot! 4 | 5 | ## App Objective 6 | 7 | As more companies adopt GitHub Copilot, it becomes increasingly important to measure the benefits it brings to the organization. This survey is an effort to combine both quantitative and qualitative data. To improve validity of the quantitative responses, Developers are asked to document their rationale for the time-savings percentage they choose. 8 | 9 | Quantitative feedback from the Developer at the time of creating a PR provides valuable insights on the time savings experienced by the Developer. Time savings is needed first before other downstream impacts like velocity increases, or other improvements can happen. The level of granularity provides multiple feedback opportunities for Developers and can capture a variety of PRs so we can understand adoption challenges and improvement opportunities. If helpful, the Survey results may also be combined with Key Performance Indicators (KPIs) that the product provides to further contextualize the survey responses. 10 | 11 | The survey responses are stored in your private Azure SQL database to provide insights into how developers are using the tool, the value they report, and the challenges they encounter. 12 | 13 | We hope that this project provides value to your organization, and we encourage you to contribute and build upon it. Your contributions can help further enhance the survey capabilities and provide even greater insights into the developer experience with Copilot. 14 | 15 | ## How it works 16 | 17 | The application actively monitors three key events: the closure of a pull request, editing of an issue, and creation of an issue comment. 18 | 19 | ### How a survey gets created 20 | 21 | When a pull request is closed, the app automatically creates an issue that prompts the user with relevant survey questions. Our application is equipped to handle multiple languages, including English, Spanish, Portuguese, and French. For this, the engine performs a language analysis on the pull request description, matching it with the appropriate language when generating the corresponding issue. 22 | 23 | Note: *If the env file does not contain a Language API Key or Endpoint, the analysis will be skipped and the default language will always be English.* 24 | 25 | ### Sample screenshot of a survey 26 | ### Copilot Usage Survey 27 | 28 | 1. ***Did you use Copilot in developing this PR? (If you select No, just answer question 5*** 29 | - [ ] No 30 | - [ ] Yes 31 | 32 | 2. Compared to your previous experience coding WITHOUT using Copilot, 33 | 34 | ***How much less time did the coding take during this PR with Copilot?*** 35 | 36 | (Example: The PR would normally take 5 days, but only took 4 days with Copilot then the answer is 20%) 37 | - [ ] 0% 38 | - [ ] > 0% but < 10% 39 | - [ ] > 11% but < 20% 40 | - [ ] > 21% but < 30% 41 | - [ ] ≥ 31% but < 40% 42 | - [ ] ≥ 41% 43 | 44 | 3. ***Describe your thought process for calculating (or estimating) the time saved in Question 2*** 45 | 46 | - [ replace this line with your answer. ] 47 | 48 | 4. ***How often did you use Copilot in this PR?*** 49 | - [ ] All or most of the time 50 | - [ ] About Half of the time 51 | - [ ] Some of the time 52 | - [ ] Not very much 53 | 54 | 5. ***What other information can you share about Copilot's ability to save you time coding?*** 55 | 56 | - [ replace this line with your answer. ] 57 | 58 | 6. ***Where did you invest your Copilot Time Savings?*** 59 | - [ ] Resolve vulnerabilites 60 | - [ ] Experiment, Learn and Wellness 61 | - [ ] Technical debt and refactorization 62 | - [ ] Work on other items in the backlog 63 | - [ ] Other. Please explain in the comment 64 | 65 | ### Where does the app store surveys? 66 | 67 | As we receive edits on the issue, the App will validate the responses received (options selected) and once all questions have been answered, the issue will be closed automatically and the responses will be saved into the configured SQL database. 68 | 69 | ## Setup. Deploy on your own environment 70 | 71 | For doing a local deployment of this GitHub App, you will need to set up an environment with the following components: 72 | - Web Server 73 | - SQL Database 74 | - Azure Cognitive Service for Language (optional) 75 | - Azure Applications Insights (optional) 76 | 77 | The web server and SQL database are the minimum requirements for this app to work and can be hosted on any environment of your choosing (cloud or on-prem). If you decide to do the deployment on Azure, this guide will help you! 78 | 79 | ### Step 1. Create the resources 80 | 81 | You can use this link to deploy all resources automatically in your Azure subscription [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmageroni%2Fcopilot-survey-engine%2Fmain%2Fdeployment%2Ftemplate.json) 82 | 83 | ### Step 2. Execute locally and configure secrets 84 | 85 | To run the application on you local machine you'll need to have installed NodeJS. Once you have it, you can access to the folder where you've cloned this project and run the following: 86 | 87 | ```sh 88 | # Install dependencies 89 | npm install 90 | 91 | # Run the bot 92 | npm start 93 | ``` 94 | 95 | Once the service is running, you will see a listening endpoint in your terminal. Please follow the link, and you will find a screen like the following image. 96 | 97 | ![Image for GitHub App Configuration Page](docs/GitHubAppConfigurationPage.png) 98 | 99 | As a first time execution probot will prompt you for creating a new GitHub App or connect it with an existing App. As you complete the requested information, a .env file will get created in your local source code and all the private information regarding your GitHub App will be automatically written there. If you need guidance on how to configure your first GitHub App, please review this guide https://probot.github.io/docs/development/#configuring-a-github-app. 100 | 101 | > **Note**: Make sure to delete the `WEBHOOK_PROXY_URL` value from the env file and confirm that all details regarding the GitHub App are correct. 102 | 103 | You will also need to provide the `DATABASE_CONNECTION_STRING` in your env file. Be sure to use your ODBC Connection String and substitue your password information. 104 | 105 | ![Image for Database Connection String setting in the Azure portal](docs/DabaseConnectionString.png) 106 | 107 | Optionally if you'll be also using Application Ingishts please provide the value for `APPLICATIONINSIGHTS_CONNECTION_STRING`. You can search for this in your Azure Portal, going to the resource group you've created previously. Select the resource of type Application Insights and copy the Connection String in the Essentials section of the Overview page. 108 | 109 | ![Image for Application Insigths setting in the Azure portal](docs/ApplicationInsightsSetting.png) 110 | 111 | Optionally if you'll be also using Languange detection API please provide the value for `LANGUAGE_API_ENDPOINT`, `LANGUAGE_API_KEY`. You can search for this in your Azure Portal, going to the resource group you've created previously. Select the resource of type Language and go to Keys and Endpoint. Copy one of the keys and corresponding endpoint. 112 | 113 | ![Image for Endpoint in Language Detection Service](docs/LanguageServiceEndpoint.png) 114 | ![Image for Key in Language Detection Service](docs/LanguageServiceKeys.png) 115 | 116 | To test your code, please run the following. You should see the 3 test cases pass. 117 | 118 | ```sh 119 | #Run the test cases! 120 | npm run test 121 | ``` 122 | 123 | ### Step 3. Deploy your App! 124 | 125 | For a quick deployment you could open your Visual Studio Code and open the cloned project. Make sure you have the Azure Tools extension installed. If not, please install it https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack 126 | 127 | Once you have the extension sign in to Azure with your credentials go to your Explorer view and right click in the file directory to select the option `Deploy to web app`. Select the subscription in which you've created the resources in step 1. Select the name you chose for the App Service created in step 1. 128 | 129 | Finally go to your GitHub App and update your webhook URL to reflect your App Service URL. Do not append "/probot". 130 | 131 | ![Alt text](docs/GitHubAppWebhookURL.png) 132 | 133 | ### Step 4. Test your App! 134 | 135 | Make sure you have your app installed in at least one repo. 136 | 137 | In such repo, create a pull request and close it (either close it or merge/complete it). Confirm that an issue has been created with the name "Copilot Usage - PR#XX". Answer the questions and confirm that the issue is closed and the data has been recorded into your database. 138 | 139 | Congrats!!!!! Enjoy and keep expanding the project. 140 | 141 | ## Contributing 142 | 143 | If you have suggestions for how copilot-survey-engine could be improved, or want to report a bug, open an issue! We'd love all and any contributions. 144 | 145 | For more, check out the [Contributing Guide](CONTRIBUTING.md). 146 | 147 | ## License 148 | 149 | [ISC](LICENSE) © 2023 Mabel Geronimo 150 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Thanks for helping make GitHub safe for everyone. 2 | 3 | # Security 4 | 5 | GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 6 | 7 | Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. 8 | 9 | ## Reporting Security Issues 10 | 11 | If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 14 | 15 | Instead, please send an email to opensource-security[@]github.com. 16 | 17 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 18 | 19 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Policy 30 | 31 | See [GitHub's Safe Harbor Policy](https://docs.github.com/en/github/site-policy/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) 32 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | If you need support using this project or have questions about it, please open up an issue in this repository. Requests made directly to GitHub staff or support team will be redirected here to open an issue. GitHub SLA's and support/services contracts do not apply to this repository. 4 | 5 | ## How to file issues and get help 6 | 7 | This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. 8 | 9 | - **Copilot Survey Engine** is under active development and maintained by GitHub staff **AND THE COMMUNITY**. We will do our best to respond to support, feature requests, and community questions in a timely manner. 10 | 11 | ## GitHub Support Policy 12 | 13 | Support for this project is limited to the resources listed above. 14 | -------------------------------------------------------------------------------- /__mocks__/applicationinsights.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setup: jest.fn().mockReturnThis(), 3 | start: jest.fn().mockReturnThis(), 4 | defaultClient: { 5 | trackEvent: jest.fn(), 6 | trackDependency: jest.fn(), 7 | trackException: jest.fn(), 8 | }, 9 | }; -------------------------------------------------------------------------------- /__mocks__/mssql.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | connect: jest.fn(), 3 | query: jest.fn().mockResolvedValue({ recordset: [] }), 4 | close: jest.fn() 5 | }; -------------------------------------------------------------------------------- /app.yml: -------------------------------------------------------------------------------- 1 | # This is a GitHub App Manifest. These settings will be used by default when 2 | # initially configuring your GitHub App. 3 | # 4 | # NOTE: changing this file will not update your GitHub App settings. 5 | # You must visit github.com/settings/apps/your-app-name to edit them. 6 | # 7 | # Read more about configuring your GitHub App: 8 | # https://probot.github.io/docs/development/#configuring-a-github-app 9 | # 10 | # Read more about GitHub App Manifests: 11 | # https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/ 12 | 13 | # The list of events the GitHub App subscribes to. 14 | # Uncomment the event names below to enable them. 15 | default_events: 16 | # - check_run 17 | # - check_suite 18 | # - commit_comment 19 | # - create 20 | # - delete 21 | # - deployment 22 | # - deployment_status 23 | # - fork 24 | # - gollum 25 | - issue_comment 26 | - issues 27 | # - label 28 | # - milestone 29 | # - member 30 | # - membership 31 | # - org_block 32 | # - organization 33 | # - page_build 34 | # - project 35 | # - project_card 36 | # - project_column 37 | # - public 38 | - pull_request 39 | # - pull_request_review 40 | # - pull_request_review_comment 41 | # - push 42 | # - release 43 | # - repository 44 | # - repository_import 45 | # - status 46 | # - team 47 | # - team_add 48 | # - watch 49 | 50 | # The set of permissions needed by the GitHub App. The format of the object uses 51 | # the permission name for the key (for example, issues) and the access type for 52 | # the value (for example, write). 53 | # Valid values are `read`, `write`, and `none` 54 | default_permissions: 55 | # Repository creation, deletion, settings, teams, and collaborators. 56 | # https://developer.github.com/v3/apps/permissions/#permission-on-administration 57 | # administration: read 58 | 59 | # Checks on code. 60 | # https://developer.github.com/v3/apps/permissions/#permission-on-checks 61 | # checks: read 62 | 63 | # Repository contents, commits, branches, downloads, releases, and merges. 64 | # https://developer.github.com/v3/apps/permissions/#permission-on-contents 65 | # contents: read 66 | 67 | # Deployments and deployment statuses. 68 | # https://developer.github.com/v3/apps/permissions/#permission-on-deployments 69 | # deployments: read 70 | 71 | # Issues and related comments, assignees, labels, and milestones. 72 | # https://developer.github.com/v3/apps/permissions/#permission-on-issues 73 | issues: write 74 | 75 | # Search repositories, list collaborators, and access repository metadata. 76 | # https://developer.github.com/v3/apps/permissions/#metadata-permissions 77 | metadata: read 78 | 79 | # Organization permissions for "GitHub Copilot Business" 80 | # https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps?apiVersion=2022-11-28#organization-permissions-for-github-copilot-business 81 | organization_copilot_seat_management: write 82 | 83 | # Retrieve Pages statuses, configuration, and builds, as well as create new builds. 84 | # https://developer.github.com/v3/apps/permissions/#permission-on-pages 85 | # pages: read 86 | 87 | # Pull requests and related comments, assignees, labels, milestones, and merges. 88 | # https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests 89 | pull_requests: read 90 | 91 | # Manage the post-receive hooks for a repository. 92 | # https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks 93 | # repository_hooks: read 94 | 95 | # Manage repository projects, columns, and cards. 96 | # https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects 97 | # repository_projects: read 98 | 99 | # Retrieve security vulnerability alerts. 100 | # https://developer.github.com/v4/object/repositoryvulnerabilityalert/ 101 | # vulnerability_alerts: read 102 | 103 | # Commit statuses. 104 | # https://developer.github.com/v3/apps/permissions/#permission-on-statuses 105 | # statuses: read 106 | 107 | # Organization members and teams. 108 | # https://developer.github.com/v3/apps/permissions/#permission-on-members 109 | # members: read 110 | 111 | # View and manage users blocked by the organization. 112 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking 113 | # organization_user_blocking: read 114 | 115 | # Manage organization projects, columns, and cards. 116 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-projects 117 | # organization_projects: read 118 | 119 | # Manage team discussions and related comments. 120 | # https://developer.github.com/v3/apps/permissions/#permission-on-team-discussions 121 | # team_discussions: read 122 | 123 | # Manage the post-receive hooks for an organization. 124 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-hooks 125 | # organization_hooks: read 126 | 127 | # Get notified of, and update, content references. 128 | # https://developer.github.com/v3/apps/permissions/ 129 | # organization_administration: read 130 | # The name of the GitHub App. Defaults to the name specified in package.json 131 | # name: My Probot App 132 | 133 | # The homepage of your GitHub App. 134 | # url: https://example.com/ 135 | 136 | # A description of the GitHub App. 137 | # description: A description of my awesome app 138 | 139 | # Set to true when your GitHub App is available to the public or false when it is only accessible to the owner of the app. 140 | # Default: true 141 | # public: false 142 | -------------------------------------------------------------------------------- /deployment/parameters.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "sites_app_name": { 6 | "value": "" 7 | }, 8 | "serverfarms_ASP_name": { 9 | "value": "" 10 | }, 11 | "sql_server_name": { 12 | "value": "" 13 | }, 14 | "components_insight_name": { 15 | "value": "" 16 | }, 17 | "accounts_ghazlanguage_name": { 18 | "value": "" 19 | }, 20 | "virtualNetworks_vnet_name": { 21 | "value": "" 22 | }, 23 | "networkSecurityGroups_NSG_name": { 24 | "value": "" 25 | }, 26 | "privateEndpoints_prv_endpoint_name": { 27 | "value": "" 28 | }, 29 | "networkInterfaces_prv_endpoint_nic_name": { 30 | "value": "" 31 | }, 32 | "workspaces_log_analytics_name": { 33 | "value": "" 34 | }, 35 | "github_app_id": { 36 | "value": "" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /deployment/template.bicep: -------------------------------------------------------------------------------- 1 | @description('azure region for deployment') 2 | param azureRegion string = resourceGroup().location 3 | 4 | @description('unique web app name') 5 | param appServiceName string 6 | 7 | @description('unique server app name') 8 | param aspServerFarmName string 9 | 10 | @description('unique database server name') 11 | param sqlServerName string 12 | 13 | @description('Azure SQL DB administrator login name') 14 | param sqlAdministratorLogin string 15 | 16 | @description('Azure SQL DB administrator password') 17 | @secure() 18 | param sqlAdministratorLoginPassword string 19 | 20 | @description('application insights name') 21 | param appInsightsName string 22 | 23 | @description('cognitive service for language name') 24 | param languageServiceName string 25 | 26 | @description('virtual network name') 27 | param virtualNetworksVnetName string 28 | 29 | @description('virtual network address space') 30 | param virtualNetworksAddressSpace string = '10.0.0.0/24' 31 | 32 | @description('subnet address space for database') 33 | param subnetsSqldatabaseAddressSpace string = '10.0.0.128/28' 34 | 35 | @description('subnet address space for app service') 36 | param subnetsAppserviceAddressSpace string = '10.0.0.0/25' 37 | 38 | @description('network security group name') 39 | param networkSecurityGroupsNsgName string 40 | 41 | @description('private endpoint name') 42 | param privateEndpointSqlServerName string 43 | 44 | @description('private endpoint nic name') 45 | param networkInterfacesPrvEndpointNicName string 46 | 47 | @description('log analytics workspace id') 48 | param workspacesLogAnalyticsName string 49 | 50 | @description('github app id') 51 | param githubAppId string 52 | 53 | @description('github app webhook secret') 54 | @secure() 55 | param githubAppWebhookSecret string 56 | 57 | var privateDnsZoneDatabaseName = 'privatelink${environment().suffixes.sqlServerHostname}' 58 | 59 | resource nsg 'Microsoft.Network/networkSecurityGroups@2022-09-01' = { 60 | name: networkSecurityGroupsNsgName 61 | location: azureRegion 62 | properties: { 63 | securityRules: [] 64 | } 65 | dependsOn: [] 66 | } 67 | 68 | // log analytics workspace resource 69 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { 70 | name: workspacesLogAnalyticsName 71 | location: azureRegion 72 | properties: { 73 | retentionInDays: 90 74 | publicNetworkAccessForIngestion: 'Enabled' 75 | publicNetworkAccessForQuery: 'Enabled' 76 | } 77 | } 78 | 79 | resource vnet 'Microsoft.Network/virtualNetworks@2022-09-01' = { 80 | name: virtualNetworksVnetName 81 | location: azureRegion 82 | properties: { 83 | addressSpace: { 84 | addressPrefixes: [ 85 | virtualNetworksAddressSpace 86 | ] 87 | } 88 | virtualNetworkPeerings: [] 89 | enableDdosProtection: false 90 | subnets: [ 91 | { 92 | name: 'AppServiceSnet' 93 | properties: { 94 | addressPrefix: subnetsAppserviceAddressSpace 95 | networkSecurityGroup: { 96 | id: nsg.id 97 | } 98 | delegations: [ 99 | { 100 | name: 'delegation' 101 | properties: { 102 | serviceName: 'Microsoft.Web/serverfarms' 103 | } 104 | type: 'Microsoft.Network/virtualNetworks/subnets/delegations' 105 | } 106 | ] 107 | privateEndpointNetworkPolicies: 'Disabled' 108 | privateLinkServiceNetworkPolicies: 'Enabled' 109 | } 110 | } 111 | { 112 | name: 'SqlDatabaseSnet' 113 | properties: { 114 | addressPrefix: subnetsSqldatabaseAddressSpace 115 | networkSecurityGroup: { 116 | id: nsg.id 117 | } 118 | serviceEndpoints: [] 119 | delegations: [] 120 | privateEndpointNetworkPolicies: 'Disabled' 121 | privateLinkServiceNetworkPolicies: 'Enabled' 122 | } 123 | } 124 | ] 125 | } 126 | } 127 | 128 | resource languageService 'Microsoft.CognitiveServices/accounts@2022-12-01' = { 129 | name: languageServiceName 130 | location: azureRegion 131 | sku: { 132 | name: 'S' 133 | } 134 | kind: 'TextAnalytics' 135 | identity: { 136 | type: 'SystemAssigned' 137 | } 138 | properties: { 139 | apiProperties: {} 140 | customSubDomainName: languageServiceName 141 | networkAcls: { 142 | defaultAction: 'Allow' 143 | virtualNetworkRules: [] 144 | ipRules: [] 145 | } 146 | publicNetworkAccess: 'Enabled' 147 | } 148 | } 149 | 150 | resource appInsights 'microsoft.insights/components@2020-02-02' = { 151 | name: appInsightsName 152 | location: azureRegion 153 | kind: 'web' 154 | properties: { 155 | Application_Type: 'web' 156 | Flow_Type: 'Redfield' 157 | Request_Source: 'IbizaWebAppExtensionCreate' 158 | RetentionInDays: 90 159 | WorkspaceResourceId: logAnalyticsWorkspace.id 160 | publicNetworkAccessForIngestion: 'Enabled' 161 | publicNetworkAccessForQuery: 'Enabled' 162 | } 163 | } 164 | 165 | resource privateDnsZoneDatabase 'Microsoft.Network/privateDnsZones@2018-09-01' = { 166 | name: privateDnsZoneDatabaseName 167 | location: 'global' 168 | } 169 | 170 | resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { 171 | name: sqlServerName 172 | location: azureRegion 173 | properties: { 174 | administratorLogin: sqlAdministratorLogin 175 | administratorLoginPassword: sqlAdministratorLoginPassword 176 | version: '12.0' 177 | publicNetworkAccess: 'Disabled' 178 | } 179 | } 180 | 181 | resource aspServerFarm 'Microsoft.Web/serverfarms@2022-09-01' = { 182 | name: aspServerFarmName 183 | location: azureRegion 184 | sku: { 185 | name: 'S1' 186 | tier: 'Standard' 187 | } 188 | kind: 'linux' 189 | properties: { 190 | reserved: true 191 | } 192 | } 193 | 194 | resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-08-01-preview' = { 195 | parent: sqlServer 196 | name: 'copilotUsage' 197 | location: azureRegion 198 | sku: { 199 | name: 'GP_Gen5' 200 | tier: 'GeneralPurpose' 201 | family: 'Gen5' 202 | capacity: 2 203 | } 204 | properties: { 205 | collation: 'SQL_Latin1_General_CP1_CI_AS' 206 | maxSizeBytes: 34359738368 207 | catalogCollation: 'SQL_Latin1_General_CP1_CI_AS' 208 | zoneRedundant: false 209 | licenseType: 'LicenseIncluded' 210 | readScale: 'Disabled' 211 | requestedBackupStorageRedundancy: 'Local' 212 | isLedgerOn: false 213 | availabilityZone: 'NoPreference' 214 | } 215 | } 216 | 217 | resource vnetLinkDb 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2018-09-01' = { 218 | parent: privateDnsZoneDatabase 219 | name: '${privateDnsZoneDatabaseName}-${virtualNetworksVnetName}-link' 220 | location: 'global' 221 | properties: { 222 | registrationEnabled: false 223 | virtualNetwork: { 224 | id: vnet.id 225 | } 226 | } 227 | } 228 | 229 | resource privateEndpointSqlServer 'Microsoft.Network/privateEndpoints@2022-09-01' = { 230 | name: privateEndpointSqlServerName 231 | location: azureRegion 232 | properties: { 233 | privateLinkServiceConnections: [ 234 | { 235 | name: privateEndpointSqlServerName 236 | properties: { 237 | privateLinkServiceId: sqlServer.id 238 | groupIds: [ 239 | 'sqlServer' 240 | ] 241 | privateLinkServiceConnectionState: { 242 | status: 'Approved' 243 | description: 'Auto-approved' 244 | actionsRequired: 'None' 245 | } 246 | } 247 | } 248 | ] 249 | manualPrivateLinkServiceConnections: [] 250 | customNetworkInterfaceName: networkInterfacesPrvEndpointNicName 251 | subnet: { 252 | id: vnet.properties.subnets[1].id 253 | } 254 | ipConfigurations: [] 255 | customDnsConfigs: [] 256 | } 257 | } 258 | 259 | resource privatednsZoneGroupSqlServer 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-09-01' = { 260 | parent: privateEndpointSqlServer 261 | name: 'default' 262 | properties: { 263 | privateDnsZoneConfigs: [ 264 | { 265 | name: 'privatelink-database-windows-net' 266 | properties: { 267 | privateDnsZoneId: privateDnsZoneDatabase.id 268 | } 269 | } 270 | ] 271 | } 272 | } 273 | 274 | resource appService 'Microsoft.Web/sites@2022-09-01' = { 275 | name: appServiceName 276 | location: azureRegion 277 | kind: 'app,linux' 278 | properties: { 279 | serverFarmId: aspServerFarm.id 280 | vnetRouteAllEnabled: true 281 | 282 | siteConfig: { 283 | numberOfWorkers: 1 284 | linuxFxVersion: 'NODE|18-lts' 285 | alwaysOn: true 286 | appSettings: [ 287 | { 288 | name: 'APP_ID' 289 | value: githubAppId 290 | } 291 | { 292 | name: 'PRIVATE_KEY' 293 | value: '' 294 | } 295 | { 296 | name: 'WEBHOOK_SECRET' 297 | value: githubAppWebhookSecret 298 | } 299 | { 300 | name: 'NODE_ENV' 301 | value: 'production' 302 | } 303 | { 304 | name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' 305 | value: appInsights.properties.ConnectionString 306 | } 307 | { 308 | name: 'ApplicationInsightsAgent_EXTENSION_VERSION' 309 | value: '~3' 310 | } 311 | { 312 | name: 'DATABASE_CONNECTION_STRING' 313 | value: 'Server=tcp:${sqlServer.name}${environment().suffixes.sqlServerHostname},1433;Initial Catalog=copilotUsage;Persist Security Info=False;User ID=${sqlAdministratorLogin};Password=${sqlAdministratorLoginPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;' 314 | } 315 | { 316 | name: 'LANGUAGE_API_ENDPOINT' 317 | value: languageService.properties.endpoint 318 | } 319 | { 320 | name: 'LANGUAGE_API_KEY' 321 | value: '' 322 | } 323 | ] 324 | } 325 | httpsOnly: true 326 | redundancyMode: 'None' 327 | publicNetworkAccess: 'Enabled' 328 | virtualNetworkSubnetId: vnet.properties.subnets[0].id 329 | 330 | } 331 | } 332 | 333 | resource appServiceVnetConnection 'Microsoft.Web/sites/virtualNetworkConnections@2022-09-01' = { 334 | parent: appService 335 | name: '0b978347-989b-4d5a-a91a-cc98ca852b73_AppServiceSnet' 336 | properties: { 337 | vnetResourceId: vnet.properties.subnets[0].id 338 | isSwift: true 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /deployment/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "sites_app_name": { 6 | "type": "String", 7 | "metadata": { 8 | "description": "unique web app name" 9 | } 10 | }, 11 | "serverfarms_ASP_name": { 12 | "type": "String", 13 | "metadata": { 14 | "description": "unique server app name" 15 | } 16 | }, 17 | "sql_server_name": { 18 | "type": "String", 19 | "metadata": { 20 | "description": "unique database server name" 21 | } 22 | }, 23 | "sql_administrator_login": { 24 | "type": "String", 25 | "metadata": { 26 | "description": "Azure SQL DB administrator login name" 27 | } 28 | }, 29 | "sql_administrator_login_password": { 30 | "type": "SecureString", 31 | "metadata": { 32 | "description": "Azure SQL DB administrator password" 33 | } 34 | }, 35 | "components_insight_name": { 36 | "type": "String", 37 | "metadata": { 38 | "description": "application insights name" 39 | }, 40 | "defaultValue": "Optional" 41 | }, 42 | "accounts_ghazlanguage_name": { 43 | "type": "String", 44 | "metadata": { 45 | "description": "cognitive service for language name" 46 | }, 47 | "defaultValue": "Optional" 48 | }, 49 | "virtualNetworks_vnet_name": { 50 | "type": "String", 51 | "metadata": { 52 | "description": "virtual network name" 53 | } 54 | }, 55 | "virtualNetworks_address_space": { 56 | "defaultValue": "10.0.0.0/24", 57 | "type": "String", 58 | "metadata": { 59 | "description": "virtual network address space" 60 | } 61 | }, 62 | "subnets_sqldatabase_address_space": { 63 | "defaultValue": "10.0.0.128/28", 64 | "type": "String", 65 | "metadata": { 66 | "description": "subnet address space for database" 67 | } 68 | }, 69 | "subnets_appservice_address_space": { 70 | "defaultValue": "10.0.0.0/25", 71 | "type": "String", 72 | "metadata": { 73 | "description": "subnet address space for app service" 74 | } 75 | }, 76 | "networkSecurityGroups_NSG_name": { 77 | "type": "String", 78 | "metadata": { 79 | "description": "network security group name" 80 | } 81 | }, 82 | "privateEndpoints_prv_endpoint_name": { 83 | "type": "String", 84 | "metadata": { 85 | "description": "private endpoint name" 86 | } 87 | }, 88 | "networkInterfaces_prv_endpoint_nic_name": { 89 | "type": "String", 90 | "metadata": { 91 | "description": "private endpoint nic name" 92 | } 93 | }, 94 | "workspaces_log_analytics_resource_id": { 95 | "defaultValue": "Optional", 96 | "type": "String", 97 | "metadata": { 98 | "description": "log analytics workspace resource id" 99 | } 100 | } 101 | }, 102 | "variables": { 103 | "resourceGroup_location": "[resourceGroup().location]", 104 | "privateDnsZones_privatelink_database_windows_net_name": "privatelink.database.windows.net" 105 | }, 106 | "resources": [ 107 | { 108 | "type": "Microsoft.Network/networkSecurityGroups", 109 | "apiVersion": "2022-09-01", 110 | "name": "[parameters('networkSecurityGroups_NSG_name')]", 111 | "location": "[variables('resourceGroup_location')]", 112 | "dependsOn": [], 113 | "properties": { 114 | "securityRules": [] 115 | } 116 | }, 117 | { 118 | "type": "Microsoft.Network/virtualNetworks", 119 | "apiVersion": "2022-09-01", 120 | "name": "[parameters('virtualNetworks_vnet_name')]", 121 | "location": "[variables('resourceGroup_location')]", 122 | "dependsOn": [ 123 | "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_NSG_name'))]" 124 | ], 125 | "properties": { 126 | "addressSpace": { 127 | "addressPrefixes": [ 128 | "[parameters('virtualNetworks_address_space')]" 129 | ] 130 | }, 131 | "virtualNetworkPeerings": [], 132 | "enableDdosProtection": false 133 | } 134 | }, 135 | { 136 | "type": "Microsoft.Network/virtualNetworks/subnets", 137 | "apiVersion": "2022-09-01", 138 | "name": "[concat(parameters('virtualNetworks_vnet_name'), '/AppServiceSnet')]", 139 | "dependsOn": [ 140 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_name'))]", 141 | "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_NSG_name'))]" 142 | ], 143 | "properties": { 144 | "addressPrefix": "[parameters('subnets_appservice_address_space')]", 145 | "networkSecurityGroup": { 146 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_NSG_name'))]" 147 | }, 148 | "delegations": [ 149 | { 150 | "name": "delegation", 151 | "id": "[concat(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'AppServiceSnet'), '/delegations/delegation')]", 152 | "properties": { 153 | "serviceName": "Microsoft.Web/serverfarms" 154 | }, 155 | "type": "Microsoft.Network/virtualNetworks/subnets/delegations" 156 | } 157 | ], 158 | "privateEndpointNetworkPolicies": "Disabled", 159 | "privateLinkServiceNetworkPolicies": "Enabled" 160 | } 161 | }, 162 | { 163 | "type": "Microsoft.Network/virtualNetworks/subnets", 164 | "apiVersion": "2022-09-01", 165 | "name": "[concat(parameters('virtualNetworks_vnet_name'), '/SqlDatabaseSnet')]", 166 | "dependsOn": [ 167 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_name'))]", 168 | "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_NSG_name'))]", 169 | "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'AppServiceSnet')]" 170 | ], 171 | "properties": { 172 | "addressPrefix": "[parameters('subnets_sqldatabase_address_space')]", 173 | "networkSecurityGroup": { 174 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_NSG_name'))]" 175 | }, 176 | "serviceEndpoints": [], 177 | "delegations": [], 178 | "privateEndpointNetworkPolicies": "Disabled", 179 | "privateLinkServiceNetworkPolicies": "Enabled" 180 | } 181 | }, 182 | { 183 | "condition": "[not(equals(parameters('accounts_ghazlanguage_name'), 'Optional'))]", 184 | "type": "Microsoft.CognitiveServices/accounts", 185 | "apiVersion": "2022-12-01", 186 | "name": "[parameters('accounts_ghazlanguage_name')]", 187 | "location": "[variables('resourceGroup_location')]", 188 | "sku": { 189 | "name": "S" 190 | }, 191 | "kind": "TextAnalytics", 192 | "identity": { 193 | "type": "SystemAssigned" 194 | }, 195 | "properties": { 196 | "apiProperties": {}, 197 | "customSubDomainName": "[parameters('accounts_ghazlanguage_name')]", 198 | "networkAcls": { 199 | "defaultAction": "Allow", 200 | "virtualNetworkRules": [], 201 | "ipRules": [] 202 | }, 203 | "publicNetworkAccess": "Enabled" 204 | } 205 | }, 206 | { 207 | "condition": "[not(equals(parameters('components_insight_name'), 'Optional'))]", 208 | "type": "microsoft.insights/components", 209 | "apiVersion": "2020-02-02", 210 | "name": "[parameters('components_insight_name')]", 211 | "location": "[variables('resourceGroup_location')]", 212 | "kind": "web", 213 | "properties": { 214 | "Application_Type": "web", 215 | "Flow_Type": "Redfield", 216 | "Request_Source": "IbizaWebAppExtensionCreate", 217 | "RetentionInDays": 90, 218 | "WorkspaceResourceId": "[parameters('workspaces_log_analytics_resource_id')]", 219 | "IngestionMode": "LogAnalytics", 220 | "publicNetworkAccessForIngestion": "Enabled", 221 | "publicNetworkAccessForQuery": "Enabled" 222 | } 223 | }, 224 | { 225 | "type": "Microsoft.Network/privateDnsZones", 226 | "apiVersion": "2018-09-01", 227 | "name": "[variables('privateDnsZones_privatelink_database_windows_net_name')]", 228 | "location": "global", 229 | "properties": { 230 | "maxNumberOfRecordSets": 25000, 231 | "maxNumberOfVirtualNetworkLinks": 1000, 232 | "maxNumberOfVirtualNetworkLinksWithRegistration": 100, 233 | "numberOfRecordSets": 2, 234 | "numberOfVirtualNetworkLinks": 1, 235 | "numberOfVirtualNetworkLinksWithRegistration": 0, 236 | "provisioningState": "Succeeded" 237 | } 238 | }, 239 | { 240 | "type": "Microsoft.Sql/servers", 241 | "apiVersion": "2022-05-01-preview", 242 | "name": "[parameters('sql_server_name')]", 243 | "location": "[variables('resourceGroup_location')]", 244 | "properties": { 245 | "administratorLogin": "[parameters('sql_administrator_login')]", 246 | "administratorLoginPassword": "[parameters('sql_administrator_login_password')]", 247 | "version": "12.0", 248 | "publicNetworkAccess": "Disabled" 249 | } 250 | }, 251 | { 252 | "type": "Microsoft.Web/serverfarms", 253 | "apiVersion": "2022-09-01", 254 | "name": "[parameters('serverfarms_ASP_name')]", 255 | "location": "[variables('resourceGroup_location')]", 256 | "sku": { 257 | "name": "S1", 258 | "tier": "Standard" 259 | }, 260 | "kind": "linux", 261 | "properties": { 262 | "reserved": true 263 | } 264 | }, 265 | { 266 | "type": "Microsoft.Sql/servers/databases", 267 | "apiVersion": "2022-08-01-preview", 268 | "name": "[concat(parameters('sql_server_name'), '/copilotUsage')]", 269 | "location": "[variables('resourceGroup_location')]", 270 | "dependsOn": [ 271 | "[resourceId('Microsoft.Sql/servers', parameters('sql_server_name'))]" 272 | ], 273 | "sku": { 274 | "name": "GP_Gen5", 275 | "tier": "GeneralPurpose", 276 | "family": "Gen5", 277 | "capacity": 2 278 | }, 279 | "kind": "v12.0,user,vcore", 280 | "properties": { 281 | "collation": "SQL_Latin1_General_CP1_CI_AS", 282 | "maxSizeBytes": 34359738368, 283 | "catalogCollation": "SQL_Latin1_General_CP1_CI_AS", 284 | "zoneRedundant": false, 285 | "licenseType": "LicenseIncluded", 286 | "readScale": "Disabled", 287 | "requestedBackupStorageRedundancy": "Local", 288 | "isLedgerOn": false, 289 | "availabilityZone": "NoPreference" 290 | } 291 | }, 292 | { 293 | "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", 294 | "apiVersion": "2018-09-01", 295 | "name": "[concat(variables('privateDnsZones_privatelink_database_windows_net_name'), '/', variables('privateDnsZones_privatelink_database_windows_net_name'), '-', parameters('virtualNetworks_vnet_name'), '-link')]", 296 | "location": "global", 297 | "dependsOn": [ 298 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateDnsZones_privatelink_database_windows_net_name'))]", 299 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_name'))]" 300 | ], 301 | "properties": { 302 | "registrationEnabled": false, 303 | "virtualNetwork": { 304 | "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_name'))]" 305 | } 306 | } 307 | }, 308 | { 309 | "type": "Microsoft.Network/privateEndpoints", 310 | "apiVersion": "2022-09-01", 311 | "name": "[parameters('privateEndpoints_prv_endpoint_name')]", 312 | "location": "[variables('resourceGroup_location')]", 313 | "dependsOn": [ 314 | "[resourceId('Microsoft.Sql/servers', parameters('sql_server_name'))]", 315 | "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'SqlDatabaseSnet')]" 316 | ], 317 | "properties": { 318 | "privateLinkServiceConnections": [ 319 | { 320 | "name": "[parameters('privateEndpoints_prv_endpoint_name')]", 321 | "id": "[concat(resourceId('Microsoft.Network/privateEndpoints', parameters('privateEndpoints_prv_endpoint_name')), concat('/privateLinkServiceConnections/', parameters('privateEndpoints_prv_endpoint_name')))]", 322 | "properties": { 323 | "privateLinkServiceId": "[resourceId('Microsoft.Sql/servers', parameters('sql_server_name'))]", 324 | "groupIds": [ 325 | "sqlServer" 326 | ], 327 | "privateLinkServiceConnectionState": { 328 | "status": "Approved", 329 | "description": "Auto-approved", 330 | "actionsRequired": "None" 331 | } 332 | } 333 | } 334 | ], 335 | "manualPrivateLinkServiceConnections": [], 336 | "customNetworkInterfaceName": "[parameters('networkInterfaces_prv_endpoint_nic_name')]", 337 | "subnet": { 338 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'SqlDatabaseSnet')]" 339 | }, 340 | "ipConfigurations": [], 341 | "customDnsConfigs": [] 342 | } 343 | }, 344 | { 345 | "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", 346 | "apiVersion": "2022-09-01", 347 | "name": "[concat(parameters('privateEndpoints_prv_endpoint_name'), '/default')]", 348 | "dependsOn": [ 349 | "[resourceId('Microsoft.Network/privateEndpoints', parameters('privateEndpoints_prv_endpoint_name'))]", 350 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateDnsZones_privatelink_database_windows_net_name'))]" 351 | ], 352 | "properties": { 353 | "privateDnsZoneConfigs": [ 354 | { 355 | "name": "privatelink-database-windows-net", 356 | "properties": { 357 | "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('privateDnsZones_privatelink_database_windows_net_name'))]" 358 | } 359 | } 360 | ] 361 | } 362 | }, 363 | { 364 | "type": "Microsoft.Web/sites", 365 | "apiVersion": "2022-09-01", 366 | "name": "[parameters('sites_app_name')]", 367 | "location": "[variables('resourceGroup_location')]", 368 | "dependsOn": [ 369 | "[resourceId('Microsoft.Web/serverfarms', parameters('serverfarms_ASP_name'))]", 370 | "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'AppServiceSnet')]", 371 | "[resourceId('microsoft.insights/components', parameters('components_insight_name'))]" 372 | ], 373 | "kind": "app,linux", 374 | "properties": { 375 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('serverfarms_ASP_name'))]", 376 | "vnetRouteAllEnabled": true, 377 | "siteConfig": { 378 | "numberOfWorkers": 1, 379 | "linuxFxVersion": "NODE|18-lts", 380 | "alwaysOn": true, 381 | "appSettings": [ 382 | { 383 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 384 | "value": "[reference(resourceId('microsoft.insights/components', parameters('components_insight_name'))).InstrumentationKey]" 385 | } 386 | ] 387 | }, 388 | "httpsOnly": true, 389 | "redundancyMode": "None", 390 | "publicNetworkAccess": "Enabled", 391 | "virtualNetworkSubnetId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'AppServiceSnet')]" 392 | } 393 | }, 394 | { 395 | "type": "Microsoft.Web/sites/virtualNetworkConnections", 396 | "apiVersion": "2022-09-01", 397 | "name": "[concat(parameters('sites_app_name'), '/0b978347-989b-4d5a-a91a-cc98ca852b73_AppServiceSnet')]", 398 | "location": "[variables('resourceGroup_location')]", 399 | "dependsOn": [ 400 | "[resourceId('Microsoft.Web/sites', parameters('sites_app_name'))]", 401 | "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'AppServiceSnet')]" 402 | ], 403 | "properties": { 404 | "vnetResourceId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_name'), 'AppServiceSnet')]", 405 | "isSwift": true 406 | } 407 | } 408 | ], 409 | "outputs": { 410 | "appInsightsInstrumentationKey": { 411 | "type": "string", 412 | "value": "[reference(resourceId('microsoft.insights/components', parameters('components_insight_name'))).InstrumentationKey]" 413 | } 414 | } 415 | } -------------------------------------------------------------------------------- /docs/ApplicationInsightsSetting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/copilot-survey-engine/a3768a643f59f7c1abaa6d44d0370f3eaee6b4e6/docs/ApplicationInsightsSetting.png -------------------------------------------------------------------------------- /docs/DabaseConnectionString.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/copilot-survey-engine/a3768a643f59f7c1abaa6d44d0370f3eaee6b4e6/docs/DabaseConnectionString.png -------------------------------------------------------------------------------- /docs/GitHubAppConfigurationPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/copilot-survey-engine/a3768a643f59f7c1abaa6d44d0370f3eaee6b4e6/docs/GitHubAppConfigurationPage.png -------------------------------------------------------------------------------- /docs/GitHubAppWebhookURL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/copilot-survey-engine/a3768a643f59f7c1abaa6d44d0370f3eaee6b4e6/docs/GitHubAppWebhookURL.png -------------------------------------------------------------------------------- /docs/LanguageServiceEndpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/copilot-survey-engine/a3768a643f59f7c1abaa6d44d0370f3eaee6b4e6/docs/LanguageServiceEndpoint.png -------------------------------------------------------------------------------- /docs/LanguageServiceKeys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/copilot-survey-engine/a3768a643f59f7c1abaa6d44d0370f3eaee6b4e6/docs/LanguageServiceKeys.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the main entrypoint to your Probot app 3 | * @param {import('probot').Probot} app 4 | */ 5 | 6 | const dedent = require("dedent"); 7 | const fs = require("fs"); 8 | const path = require("path"); 9 | const sql = require("mssql"); 10 | require("dotenv").config(); 11 | 12 | const { 13 | TextAnalysisClient, 14 | AzureKeyCredential, 15 | } = require("@azure/ai-language-text"); 16 | const { LANGUAGE_API_KEY, LANGUAGE_API_ENDPOINT, DATABASE_CONNECTION_STRING, VALIDATE_SEAT_ASSIGNMENT, APPLICATIONINSIGHTS_CONNECTION_STRING } = 17 | process.env; 18 | 19 | module.exports = (app) => { 20 | // Your code here 21 | app.log.info("Yay, the app was loaded!"); 22 | 23 | let appInsights = new AppInsights(); 24 | 25 | app.on("pull_request.closed", async (context) => { 26 | 27 | let pr_number = context.payload.pull_request.number; 28 | let pr_author = context.payload.pull_request.user.login; 29 | let organization_name = context.payload.repository.owner.login; 30 | let pr_body = context.payload.pull_request.body; 31 | let detectedLanguage = "en"; 32 | 33 | appInsights.trackEvent({ 34 | name: "Pull Request Close Payload", 35 | properties: { 36 | pr_number: pr_number, 37 | pr_author: pr_author, 38 | 39 | }, 40 | }); 41 | 42 | // check language for pr_body 43 | if (LANGUAGE_API_ENDPOINT && LANGUAGE_API_KEY) { 44 | const TAclient = new TextAnalysisClient( 45 | LANGUAGE_API_ENDPOINT, 46 | new AzureKeyCredential(LANGUAGE_API_KEY) 47 | ); 48 | if (pr_body) { 49 | try { 50 | let startTime = Date.now(); 51 | let result = await TAclient.analyze("LanguageDetection", [pr_body]); 52 | let duration = Date.now() - startTime; 53 | appInsights.trackDependency({ 54 | target: "API:Language Detection", 55 | name: "detect pull request description language", 56 | duration: duration, 57 | resultCode: 200, 58 | success: true, 59 | dependencyTypeName: "HTTP", 60 | }); 61 | if (result.length > 0 && !result[0].error && ["en", "es", "pt", "fr"].includes(result[0].primaryLanguage.iso6391Name) ) { 62 | detectedLanguage = result[0].primaryLanguage.iso6391Name; 63 | }else { 64 | detectedLanguage = "en"; 65 | } 66 | } catch (err) { 67 | app.log.error(err); 68 | appInsights.trackException({ exception: err }); 69 | } 70 | } 71 | } 72 | 73 | // read file that aligns with detected language 74 | const issue_body = fs.readFileSync( 75 | "./issue_template/copilot-usage-" + 76 | detectedLanguage + 77 | ".md", 78 | "utf-8" 79 | ); 80 | 81 | // find XXX in file and replace with pr_number 82 | let fileContent = dedent( 83 | issue_body.replace(/XXX/g, "#" + pr_number.toString()) 84 | ); 85 | 86 | // display the body for the issue 87 | app.log.info(fileContent); 88 | 89 | // create an issue using fileContent as body if pr_author is included in copilotSeats using Copilot Seat Billing api 90 | let copilotSeatUsers = []; 91 | if (VALIDATE_SEAT_ASSIGNMENT == "YES") { 92 | let copilotSeats = await context.octokit.request( 93 | "GET /orgs/{org}/copilot/billing/seats", 94 | { 95 | org: organization_name, 96 | headers: { 97 | 'X-GitHub-Api-Version': '2022-11-28' 98 | } 99 | } 100 | ); 101 | copilotSeatUsers = copilotSeats.data.seats; 102 | } 103 | 104 | if ( VALIDATE_SEAT_ASSIGNMENT != "YES" || (VALIDATE_SEAT_ASSIGNMENT == "YES" && copilotSeatUsers.some(user => user.assignee.login == pr_author))) { 105 | try { 106 | await context.octokit.issues.create({ 107 | owner: organization_name, 108 | repo: context.payload.repository.name, 109 | title: "Copilot Usage - PR#" + pr_number.toString(), 110 | body: fileContent, 111 | assignee: context.payload.pull_request.user.login, 112 | }); 113 | } catch (err) { 114 | app.log.error(err); 115 | appInsights.trackException({ exception: err }); 116 | } 117 | } 118 | }); 119 | 120 | app.on("issues.edited", async (context) => { 121 | if (context.payload.issue.title.startsWith("Copilot Usage - PR#")) { 122 | appInsights.trackEvent({ 123 | name: "Issue Edited Payload", 124 | properties: { 125 | issue_number: context.payload.issue.number 126 | }, 127 | }); 128 | await insertIntoDB(context); 129 | } 130 | }); 131 | 132 | app.on("issue_comment.created", async (context) => { 133 | if (context.payload.issue.title.startsWith("Copilot Usage - PR#")) { 134 | appInsights.trackEvent({ 135 | name: "Issue Comment Created Payload", 136 | properties: { 137 | issue_number: context.payload.issue.number, 138 | comment: context.payload.comment, 139 | }, 140 | }); 141 | await insertIntoDB(context); 142 | } 143 | }); 144 | 145 | async function GetSurveyData(context) { 146 | let issue_body = context.payload.issue.body; 147 | 148 | // save comment body if present 149 | let comment = null; 150 | if(context.payload.comment) { 151 | comment = context.payload.comment.body.replace(/\n/g, ' ').trim(); 152 | } 153 | 154 | // find regex [0-9]\+ in issue_body and get first result 155 | let pr_number = issue_body.match(/[0-9]+/)[0]; 156 | 157 | // Get answers to first question and find if they contain affirmative answer 158 | let firstQuestionResponse = await getQuestionResponse(1, 2, issue_body); 159 | let isCopilotUsed = firstQuestionResponse.some((response) => { 160 | return ( 161 | response.includes("Sim") || 162 | response.includes("Si") || 163 | response.includes("Yes") || 164 | response.includes("Oui") 165 | ); 166 | }); 167 | 168 | // Get answers to second question and store in pctValue 169 | let pctValue = await getQuestionResponse(2, 3, issue_body); 170 | let freqValue = await getQuestionResponse(4, 5, issue_body); 171 | let savingsInvestedValue = await getQuestionResponse(6, '', issue_body); 172 | 173 | if( isCopilotUsed && pctValue && freqValue && savingsInvestedValue){ 174 | // All questions have been answered and we can close the issue 175 | app.log.info("Closing the issue"); 176 | try { 177 | await context.octokit.issues.update({ 178 | owner: context.payload.repository.owner.login, 179 | repo: context.payload.repository.name, 180 | issue_number: context.payload.issue.number, 181 | state: "closed", 182 | }); 183 | } catch (err) { 184 | app.log.error(err); 185 | } 186 | } 187 | 188 | if ( 189 | firstQuestionResponse.some((response) => { 190 | return ( 191 | response.includes("Não") || 192 | response.includes("No") || 193 | response.includes("Non") 194 | ); 195 | }) 196 | ){ 197 | if (comment) { 198 | try { 199 | // close the issue 200 | await context.octokit.issues.update({ 201 | owner: context.payload.repository.owner.login, 202 | repo: context.payload.repository.name, 203 | issue_number: context.payload.issue.number, 204 | state: "closed", 205 | }); 206 | } catch (err) { 207 | app.log.error(err); 208 | } 209 | } 210 | } 211 | 212 | let data = { 213 | enterprise_name: context.payload.enterprise ? context.payload.enterprise.name : '', 214 | organization_name: context.payload.organization ? context.payload.organization.login : '', 215 | repository_name: context.payload.repository ? context.payload.repository.name : '', 216 | issue_id: context.payload.issue ? context.payload.issue.id : '', 217 | issue_number: context.payload.issue ? context.payload.issue.number : '', 218 | PR_number: pr_number || '', 219 | assignee_name: context.payload.issue.assignee ? context.payload.issue.assignee.login : '', 220 | is_copilot_used: isCopilotUsed ? 1 : 0, 221 | saving_percentage: pctValue ? pctValue.join(" || ") : '', 222 | usage_frequency: freqValue ? freqValue.join(" || ") : '', 223 | savings_invested: savingsInvestedValue ? savingsInvestedValue.join(" || ") : '', 224 | comment: comment || '', 225 | created_at: context.payload.issue ? context.payload.issue.created_at : '', 226 | completed_at: context.payload.issue ? context.payload.issue.updated_at : '' 227 | }; 228 | 229 | return data; 230 | 231 | } 232 | 233 | async function insertIntoDB(context) { 234 | let conn = null; 235 | let query = null; 236 | let status = true; 237 | let newContent = null; 238 | 239 | try { 240 | 241 | newContent = await GetSurveyData(context); 242 | 243 | conn = await sql.connect(DATABASE_CONNECTION_STRING); 244 | 245 | // Check if table exists 246 | let tableCheckResult = await sql.query` 247 | SELECT * 248 | FROM INFORMATION_SCHEMA.TABLES 249 | WHERE TABLE_NAME = 'SurveyResults' 250 | `; 251 | 252 | if (tableCheckResult.recordset.length === 0) { 253 | // Create table if it doesn't exist 254 | await sql.query` 255 | CREATE TABLE SurveyResults ( 256 | record_ID INT IDENTITY(1,1), 257 | enterprise_name VARCHAR(50), 258 | organization_name VARCHAR(50), 259 | repository_name VARCHAR(50), 260 | issue_id BIGINT, 261 | issue_number INT, 262 | PR_number INT, 263 | assignee_name VARCHAR(50), 264 | is_copilot_used BIT, 265 | saving_percentage VARCHAR(200), 266 | usage_frequency VARCHAR(200), 267 | savings_invested VARCHAR(200), 268 | comment VARCHAR(550), 269 | created_at DATETIME, 270 | completed_at DATETIME 271 | ); 272 | `; 273 | } 274 | 275 | let result = 276 | await sql.query`SELECT * FROM SurveyResults WHERE Issue_id = ${newContent.issue_id}`; 277 | app.log.info("Database has been created and issue id existence has been confirmed"); 278 | 279 | if (result.recordset.length > 0) { 280 | // create query 281 | let update_query = `UPDATE [SurveyResults] SET [is_copilot_used] = ${newContent.is_copilot_used}`; 282 | 283 | for (let key in newContent) { 284 | if (newContent.hasOwnProperty(key) && newContent[key] !== undefined && key !== "is_copilot_used") { 285 | update_query += `, [${key}] = '${newContent[key]}'`; 286 | } 287 | } 288 | 289 | update_query += ` WHERE [issue_id] = ${newContent.issue_id}`; 290 | 291 | // update existing record 292 | let update_result = await sql.query(update_query); 293 | app.log.info(update_result); 294 | } else { 295 | // insert new record if it doesn't exist 296 | let keys = Object.keys(newContent).join(', '); 297 | let values = Object.values(newContent).map(value => `'${value}'`).join(', '); 298 | 299 | let insert_query = `INSERT INTO SurveyResults (${keys}) VALUES (${values})`; 300 | let insert_result = await sql.query(insert_query); 301 | app.log.info(insert_result); 302 | } 303 | } catch (err) { 304 | app.log.error(err); 305 | appInsights.trackException({ exception: err }); 306 | status = false; 307 | } finally { 308 | if (conn) { 309 | conn.close(); 310 | } 311 | return { 312 | query: query, 313 | status: status 314 | }; 315 | } 316 | } 317 | 318 | async function getQuestionResponse(start, end, issue_body) { 319 | app.log.info("Getting answers for question " + start + " to " + end); 320 | let AnswerSelected = false; 321 | let Answers = new Array(); 322 | let Expression = end ? new RegExp(start + "\\. (.*" + end + "\\." + ")?", "s") : new RegExp(start + "\\. (.*" + ")?", "s"); 323 | let QuestionOptions = issue_body.match(Expression)[0].match(/\[x\].*/g); 324 | if(QuestionOptions){ 325 | AnswerSelected = true; 326 | QuestionOptions.forEach((option) => { 327 | let cleanAnswer = option; 328 | cleanAnswer = cleanAnswer.replace(/\[x\] /g, ""); 329 | Answers.push(cleanAnswer); 330 | }); 331 | } 332 | return AnswerSelected ? Answers : null; 333 | } 334 | }; 335 | 336 | // Define class for app insights. If no instrumentation key is provided, then no app insights will be used. 337 | class AppInsights { 338 | constructor() { 339 | if (APPLICATIONINSIGHTS_CONNECTION_STRING) { 340 | this.appInsights = require("applicationinsights"); 341 | this.appInsights.setup().start(); 342 | this.appIClient = this.appInsights.defaultClient; 343 | } else { 344 | this.appIClient = null; 345 | } 346 | } 347 | trackEvent(event) { 348 | if (this.appIClient) { 349 | this.appIClient.trackEvent(event); 350 | } 351 | } 352 | trackDependency( dependency ) { 353 | if (this.appIClient) { 354 | this.appIClient.trackDependency(dependency); 355 | } 356 | } 357 | trackException(exception) { 358 | if (this.appIClient) { 359 | this.appIClient.trackException(exception); 360 | } 361 | } 362 | } -------------------------------------------------------------------------------- /issue_template/copilot-usage-en.md: -------------------------------------------------------------------------------- 1 | ### Copilot Usage Survey 2 | 3 | For Pull Request XXX: 4 | 5 | 1. ***Did you use Copilot in developing this PR? (If you select No, just answer question 5)*** 6 | - [ ] No 7 | - [ ] Yes 8 | 9 | 2. Compared to your previous experience coding WITHOUT using Copilot (This number represents 100%) ***How much less time did the coding take during this PR with Copilot?*** 10 | 11 | (Example: The PR would normally take 5 days, but only took 4 days with Copilot then the answer is 20%) 12 | - [ ] 0% 13 | - [ ] > 0% but < 10% 14 | - [ ] ≥ 11% but < 20% 15 | - [ ] ≥ 21% but < 30% 16 | - [ ] ≥ 31% but < 40% 17 | - [ ] ≥ 41% 18 | 19 | 3. ***Describe your thought process for calculating (or estimating) the time saved in Question 2*** 20 | 21 | - (Please tell us in a comment) 22 | 23 | 4. ***How often did you use Copilot in this PR?*** 24 | - [ ] All or most of the time 25 | - [ ] About Half of the time 26 | - [ ] Some of the time 27 | - [ ] Not very much 28 | 29 | 5. ***What other information can you share about Copilot's ability to save you time coding?*** 30 | 31 | - (Please tell us in a comment) 32 | 33 | 6. ***Where did you invest your Copilot Time Savings?*** 34 | - [ ] Resolve vulnerabilites 35 | - [ ] Experiment, Learn and Wellness 36 | - [ ] Technical debt and refactorization 37 | - [ ] Work on other items in the backlog 38 | - [ ] Other. Please explain in the comment -------------------------------------------------------------------------------- /issue_template/copilot-usage-es.md: -------------------------------------------------------------------------------- 1 | ### Encuesta sobre el uso de Copilot 2 | 3 | Para el Pull Request XXX: 4 | 5 | 1. ***¿Utilizó Copilot para desarrollar este PR? (Si selecciona No, simplemente responda la pregunta 5)*** 6 | - [ ] No 7 | - [ ] Si 8 | 9 | 2. En comparación con su experiencia anterior codificando sin usar Copilot (este número representa el 100%) ***¿Cuánto menos tiempo tomó la codificación durante este PR con Copilot?*** 10 | 11 | (Ejemplo: el PR normalmente tomaría 5 días, pero solo tomó 4 días con Copilot, entonces la respuesta es 20%) 12 | - [ ] 0% 13 | - [ ] > 0% pero < 10% 14 | - [ ] ≥ 11% pero < 20% 15 | - [ ] ≥ 21% pero < 30% 16 | - [ ] ≥ 31% pero < 40% 17 | - [ ] ≥ 41% 18 | 19 | 3. ***Describe tu proceso de pensamiento para calcular (o estimar) el tiempo ahorrado en la Pregunta 2*** 20 | 21 | - (Por favor díganos en un comentario) 22 | 23 | 4. ***¿Con qué frecuencia usaste Copilot en este PR?*** 24 | - [ ] Todo o la mayor parte del tiempo 25 | - [ ] Aproximadamente la mitad del tiempo 26 | - [ ] Algo de tiempo 27 | - [ ] No mucho 28 | 29 | 5. ***¿Qué otra información puedes compartir sobre la capacidad de Copilot para ahorrarte tiempo codificando?*** 30 | 31 | - (Por favor díganos en un comentario) 32 | 33 | 6. ***¿En qué has invertido el tiempo ganado con Copilot?*** 34 | - [ ] Resolver vulnerabilidades 35 | - [ ] Experimentar, Aprender y Bienestar 36 | - [ ] Reducir deuda técnica y/o refactorización 37 | - [ ] Trabajar en otras tareas en el backlog 38 | - [ ] Otros. Por favor explica en el comentario -------------------------------------------------------------------------------- /issue_template/copilot-usage-fr.md: -------------------------------------------------------------------------------- 1 | ### Enquête d'utilisation de Copilot 2 | 3 | Pour le Pull Request XXX : 4 | 5 | 1. ***Avez-vous utilisé Copilot pour développer ce PR ? (Si vous sélectionnez Non, répondez simplement à la question 5)*** 6 | - [ ] Non 7 | - [ ] Oui 8 | 9 | 2. Par rapport à votre expérience précédente de codage SANS utiliser Copilot (ce nombre représente 100 %) ***Combien de temps le codage a-t-il pris pendant ce PR avec Copilot ?*** 10 | 11 | (Exemple : le PR prendrait normalement 5 jours, mais n'a pris que 4 jours avec Copilot alors la réponse est 20 %) 12 | - [ ] 0% 13 | - [ ] > 0 % mais < 10 % 14 | - [ ] ≥ 11 % mais < 20 % 15 | - [ ] ≥ 21 % mais < 30 % 16 | - [ ] ≥ 31 % mais < 40 % 17 | - [ ] ≥ 41% 18 | 19 | 3. ***Décrivez votre processus de réflexion pour calculer (ou estimer) le temps gagné à la question 2*** 20 | 21 | - (Merci de nous le dire dans un commentaire) 22 | 23 | 4. ***À quelle fréquence avez-vous utilisé Copilot dans ce PR ?*** 24 | - [ ] Tout le temps ou la plupart du temps 25 | - [ ] Environ la moitié du temps 26 | - [ ] Une partie du temps 27 | - [ ] Pas beaucoup 28 | 29 | 5. ***Quelles autres informations pouvez-vous partager sur la capacité de Copilot à vous faire gagner du temps en matière de codage ?*** 30 | 31 | - (Merci de nous le dire dans un commentaire) 32 | 33 | 6. ***Qu'avez-vous investi dans le temps gagné avec Copilot ?*** 34 | - [ ] Résoudre les vulnérabilités 35 | - [ ] Expérience, Apprendre et/ou Bien-être 36 | - [ ] Réduire la dette technique et/ou refactoring 37 | - [ ] Travailler sur d'autres tâches dans le backlog 38 | - [ ] Autres. Veuillez expliquer dans le commentaire -------------------------------------------------------------------------------- /issue_template/copilot-usage-pt.md: -------------------------------------------------------------------------------- 1 | ### Pesquisa de uso do Copilot 2 | 3 | Para o Pull Request XXX: 4 | 5 | 1. ***Você usou o Copilot no desenvolvimento deste PR? (Se você selecionar Não, basta responder à pergunta 5)*** 6 | - [ ] Não 7 | - [ ] Sim 8 | 9 | 2. Em comparação com sua experiência anterior de codificação SEM usar o Copilot (este número representa 100%) ***Quanto menos tempo demorou a codificação durante este PR com o Copilot?*** 10 | 11 | (Exemplo: o PR normalmente levaria 5 dias, mas demorou apenas 4 dias com o Copilot, então a resposta é 20%) 12 | - [ ] 0% 13 | - [ ] > 0% mas < 10% 14 | - [ ] ≥ 11% mas < 20% 15 | - [ ] ≥ 21% mas < 30% 16 | - [ ] ≥ 31% mas < 40% 17 | - [ ] ≥ 41% 18 | 19 | 3. ***Descreva seu processo de cálculo para calcular (ou estimar) o tempo economizado na Pergunta 2*** 20 | 21 | - (Por favor, conte-nos em um comentário) 22 | 23 | 4. ***Com que frequência você usou o Copilot neste PR?*** 24 | - [ ] Todo ou a maior parte do tempo 25 | - [ ] Cerca de metade das vezes 26 | - [ ] Algumas vezes 27 | - [ ] Não muito 28 | 29 | 5. ***Que outras informações você pode compartilhar sobre a capacidade do Copilot de economizar seu tempo de codificação?*** 30 | 31 | - (Por favor, conte-nos em um comentário) 32 | 33 | 6. ***Quanto você investiu do tempo que ganhou com o Copilot?*** 34 | - [ ] Resolver vulnerabilidades 35 | - [ ] Experiência, Aprendizagem e Bem-estar 36 | - [ ] Reduzir dívida técnica e/ou refatoração 37 | - [ ] Trabalhar em outras tarefas do backlog 38 | - [ ] Outros. Por favor explique no comentário -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copilot-survey-engine", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Engine to prompt developers about their experience with Copilot!", 6 | "author": "Mabel Geronimo", 7 | "license": "ISC", 8 | "homepage": "https://github.com//", 9 | "keywords": [ 10 | "probot", 11 | "github", 12 | "probot-app" 13 | ], 14 | "scripts": { 15 | "start": "probot run ./index.js", 16 | "test": "jest --detectOpenHandles" 17 | }, 18 | "dependencies": { 19 | "@azure/ai-language-text": "^1.1.0", 20 | "@azure/ai-text-analytics": "^5.1.0", 21 | "ai-text-analytics": "^0.0.1-security", 22 | "applicationinsights": "^2.7.3", 23 | "dedent": "^1.5.1", 24 | "dotenv": "^16.3.1", 25 | "mssql": "^11.0.1", 26 | "probot": "^13.4.3" 27 | }, 28 | "devDependencies": { 29 | "jest": "^29.6.4", 30 | "nock": "^14.0.0-beta.5", 31 | "semver": ">= 7.5.4", 32 | "smee-client": "^2.0.0" 33 | }, 34 | "engines": { 35 | "node": ">= 18" 36 | }, 37 | "jest": { 38 | "testEnvironment": "node" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/fixtures/issue_body.md: -------------------------------------------------------------------------------- 1 | ### Copilot Usage Survey 2 | 3 | For Pull Request #44: 4 | 5 | 1. ***Did you use Copilot in developing this PR? (If you select No, just answer question 5)*** 6 | - [ ] No 7 | - [ ] Yes 8 | 9 | 2. Compared to your previous experience coding WITHOUT using Copilot (This number represents 100%) ***How much less time did the coding take during this PR with Copilot?*** 10 | 11 | (Example: The PR would normally take 5 days, but only took 4 days with Copilot then the answer is 20%) 12 | - [ ] 0% 13 | - [ ] > 0% but < 10% 14 | - [ ] ≥ 11% but < 20% 15 | - [ ] ≥ 21% but < 30% 16 | - [ ] ≥ 31% but < 40% 17 | - [ ] ≥ 41% 18 | 19 | 3. ***Describe your thought process for calculating (or estimating) the time saved in Question 2*** 20 | 21 | - (Please tell us in a comment) 22 | 23 | 4. ***How often did you use Copilot in this PR?*** 24 | - [ ] All or most of the time 25 | - [ ] About Half of the time 26 | - [ ] Some of the time 27 | - [ ] Not very much 28 | 29 | 5. ***What other information can you share about Copilot's ability to save you time coding?*** 30 | 31 | - (Please tell us in a comment) 32 | 33 | 6. ***Where did you invest your Copilot Time Savings?*** 34 | - [ ] Resolve vulnerabilites 35 | - [ ] Experiment, Learn and Wellness 36 | - [ ] Technical debt and refactorization 37 | - [ ] Work on other items in the backlog 38 | - [ ] Other. Please explain in the comment -------------------------------------------------------------------------------- /test/fixtures/issue_comment.created.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "created", 3 | "comment": { 4 | "author_association": "COLLABORATOR", 5 | "body": "Comentario especial", 6 | "created_at": "2023-03-19T02:56:35Z", 7 | "html_url": "https://github.com/mageroni/TestRepo/issues/60#issuecomment-1475081580", 8 | "id": 1475081580, 9 | "issue_url": "https://api.github.com/repos/mageroni/TestRepo/issues/60", 10 | "node_id": "IC_kwDOH5NwUM5X6_Vs", 11 | "performed_via_github_app": null, 12 | "reactions": { 13 | "+1": 0, 14 | "-1": 0, 15 | "confused": 0, 16 | "eyes": 0, 17 | "heart": 0, 18 | "hooray": 0, 19 | "laugh": 0, 20 | "rocket": 0, 21 | "total_count": 0, 22 | "url": "https://api.github.com/repos/mageroni/TestRepo/issues/comments/1475081580/reactions" 23 | }, 24 | "updated_at": "2023-03-19T02:56:35Z", 25 | "url": "https://api.github.com/repos/mageroni/TestRepo/issues/comments/1475081580", 26 | "user": { 27 | "avatar_url": "https://avatars.githubusercontent.com/u/107436170?v=4", 28 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 29 | "followers_url": "https://api.github.com/users/mageroni/followers", 30 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 31 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 32 | "gravatar_id": "", 33 | "html_url": "https://github.com/mageroni", 34 | "id": 107436170, 35 | "login": "mageroni", 36 | "node_id": "U_kgDOBmdYig", 37 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 38 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 39 | "repos_url": "https://api.github.com/users/mageroni/repos", 40 | "site_admin": true, 41 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 42 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 43 | "type": "User", 44 | "url": "https://api.github.com/users/mageroni" 45 | } 46 | }, 47 | "issue": { 48 | "active_lock_reason": null, 49 | "assignee": null, 50 | "assignees": [], 51 | "author_association": "NONE", 52 | "body": "### Copilot Usage Survey\n\nFor Pull Request #44:\n\n1. ***Did you use Copilot in developing this PR? (If you select No, just answer question 5)***\n- [ ] No\n- [x] Yes\n\n2. Compared to your previous experience coding WITHOUT using Copilot (This number represents 100%) ***How much less time did the coding take during this PR with Copilot?***\n\n(Example: The PR would normally take 5 days, but only took 4 days with Copilot then the answer is 20%)\n- [ ] 0%\n- [ ] > 0% but < 10%\n- [x] ≥ 11% but < 20%\n- [ ] ≥ 21% but < 30%\n- [ ] ≥ 31% but < 40%\n- [ ] ≥ 41%\n\n3. ***Describe your thought process for calculating (or estimating) the time saved in Question 2***\n\n- (Please tell us in a comment)\n\n4. ***How often did you use Copilot in this PR?***\n- [x] All or most of the time\n- [ ] About Half of the time\n- [ ] Some of the time\n- [ ] Not very much\n\n5. ***What other information can you share about Copilot's ability to save you time coding?*** \n\n- (Please tell us in a comment)\n\n6. ***Where did you invest your Copilot Time Savings?***\n- [x] Resolve vulnerabilites\n- [ ] Experiment, Learn and Wellness\n- [ ] Technical debt and refactorization\n- [ ] Work on other items in the backlog\n- [ ] Other. Please explain in the comment", 53 | "closed_at": null, 54 | "comments": 1, 55 | "comments_url": "https://api.github.com/repos/mageroni/TestRepo/issues/60/comments", 56 | "created_at": "2023-03-18T15:16:49Z", 57 | "events_url": "https://api.github.com/repos/mageroni/TestRepo/issues/60/events", 58 | "html_url": "https://github.com/mageroni/TestRepo/issues/60", 59 | "id": 1630365014, 60 | "labels": [], 61 | "labels_url": "https://api.github.com/repos/mageroni/TestRepo/issues/60/labels{/name}", 62 | "locked": false, 63 | "milestone": null, 64 | "node_id": "I_kwDOH5NwUM5hLWVW", 65 | "number": 60, 66 | "performed_via_github_app": null, 67 | "reactions": { 68 | "+1": 0, 69 | "-1": 0, 70 | "confused": 0, 71 | "eyes": 0, 72 | "heart": 0, 73 | "hooray": 0, 74 | "laugh": 0, 75 | "rocket": 0, 76 | "total_count": 0, 77 | "url": "https://api.github.com/repos/mageroni/TestRepo/issues/60/reactions" 78 | }, 79 | "repository_url": "https://api.github.com/repos/mageroni/TestRepo", 80 | "state": "open", 81 | "state_reason": null, 82 | "timeline_url": "https://api.github.com/repos/mageroni/TestRepo/issues/60/timeline", 83 | "title": "Copilot Usage - PR#44", 84 | "updated_at": "2023-03-19T02:56:35Z", 85 | "url": "https://api.github.com/repos/mageroni/TestRepo/issues/60", 86 | "user": { 87 | "avatar_url": "https://avatars.githubusercontent.com/in/304911?v=4", 88 | "events_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/events{/privacy}", 89 | "followers_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/followers", 90 | "following_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/following{/other_user}", 91 | "gists_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/gists{/gist_id}", 92 | "gravatar_id": "", 93 | "html_url": "https://github.com/apps/copilot-survey-engine", 94 | "id": 127805006, 95 | "login": "copilot-survey-engine[bot]", 96 | "node_id": "BOT_kgDOB54mTg", 97 | "organizations_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/orgs", 98 | "received_events_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/received_events", 99 | "repos_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/repos", 100 | "site_admin": false, 101 | "starred_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/starred{/owner}{/repo}", 102 | "subscriptions_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/subscriptions", 103 | "type": "Bot", 104 | "url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D" 105 | } 106 | }, 107 | "organization": { 108 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 109 | "description": "Public Org GitHub.com", 110 | "events_url": "https://api.github.com/orgs/mageroni/events", 111 | "hooks_url": "https://api.github.com/orgs/mageroni/hooks", 112 | "id": 111672832, 113 | "issues_url": "https://api.github.com/orgs/mageroni/issues", 114 | "login": "mageroni", 115 | "members_url": "https://api.github.com/orgs/mageroni/members{/member}", 116 | "node_id": "O_kgDOBqf-AA", 117 | "public_members_url": "https://api.github.com/orgs/mageroni/public_members{/member}", 118 | "repos_url": "https://api.github.com/orgs/mageroni/repos", 119 | "url": "https://api.github.com/orgs/mageroni" 120 | }, 121 | "repository": { 122 | "allow_forking": true, 123 | "archive_url": "https://api.github.com/repos/mageroni/TestRepo/{archive_format}{/ref}", 124 | "archived": false, 125 | "assignees_url": "https://api.github.com/repos/mageroni/TestRepo/assignees{/user}", 126 | "blobs_url": "https://api.github.com/repos/mageroni/TestRepo/git/blobs{/sha}", 127 | "branches_url": "https://api.github.com/repos/mageroni/TestRepo/branches{/branch}", 128 | "clone_url": "https://github.com/mageroni/TestRepo.git", 129 | "collaborators_url": "https://api.github.com/repos/mageroni/TestRepo/collaborators{/collaborator}", 130 | "comments_url": "https://api.github.com/repos/mageroni/TestRepo/comments{/number}", 131 | "commits_url": "https://api.github.com/repos/mageroni/TestRepo/commits{/sha}", 132 | "compare_url": "https://api.github.com/repos/mageroni/TestRepo/compare/{base}...{head}", 133 | "contents_url": "https://api.github.com/repos/mageroni/TestRepo/contents/{+path}", 134 | "contributors_url": "https://api.github.com/repos/mageroni/TestRepo/contributors", 135 | "created_at": "2022-08-28T03:54:10Z", 136 | "default_branch": "master", 137 | "deployments_url": "https://api.github.com/repos/mageroni/TestRepo/deployments", 138 | "description": null, 139 | "disabled": false, 140 | "downloads_url": "https://api.github.com/repos/mageroni/TestRepo/downloads", 141 | "events_url": "https://api.github.com/repos/mageroni/TestRepo/events", 142 | "fork": false, 143 | "forks": 0, 144 | "forks_count": 0, 145 | "forks_url": "https://api.github.com/repos/mageroni/TestRepo/forks", 146 | "full_name": "mageroni/TestRepo", 147 | "git_commits_url": "https://api.github.com/repos/mageroni/TestRepo/git/commits{/sha}", 148 | "git_refs_url": "https://api.github.com/repos/mageroni/TestRepo/git/refs{/sha}", 149 | "git_tags_url": "https://api.github.com/repos/mageroni/TestRepo/git/tags{/sha}", 150 | "git_url": "git://github.com/mageroni/TestRepo.git", 151 | "has_discussions": false, 152 | "has_downloads": true, 153 | "has_issues": true, 154 | "has_pages": false, 155 | "has_projects": true, 156 | "has_wiki": true, 157 | "homepage": null, 158 | "hooks_url": "https://api.github.com/repos/mageroni/TestRepo/hooks", 159 | "html_url": "https://github.com/mageroni/TestRepo", 160 | "id": 529756240, 161 | "is_template": false, 162 | "issue_comment_url": "https://api.github.com/repos/mageroni/TestRepo/issues/comments{/number}", 163 | "issue_events_url": "https://api.github.com/repos/mageroni/TestRepo/issues/events{/number}", 164 | "issues_url": "https://api.github.com/repos/mageroni/TestRepo/issues{/number}", 165 | "keys_url": "https://api.github.com/repos/mageroni/TestRepo/keys{/key_id}", 166 | "labels_url": "https://api.github.com/repos/mageroni/TestRepo/labels{/name}", 167 | "language": "Shell", 168 | "languages_url": "https://api.github.com/repos/mageroni/TestRepo/languages", 169 | "license": null, 170 | "merges_url": "https://api.github.com/repos/mageroni/TestRepo/merges", 171 | "milestones_url": "https://api.github.com/repos/mageroni/TestRepo/milestones{/number}", 172 | "mirror_url": null, 173 | "name": "TestRepo", 174 | "node_id": "R_kgDOH5NwUA", 175 | "notifications_url": "https://api.github.com/repos/mageroni/TestRepo/notifications{?since,all,participating}", 176 | "open_issues": 17, 177 | "open_issues_count": 17, 178 | "owner": { 179 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 180 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 181 | "followers_url": "https://api.github.com/users/mageroni/followers", 182 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 183 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 184 | "gravatar_id": "", 185 | "html_url": "https://github.com/mageroni", 186 | "id": 111672832, 187 | "login": "mageroni", 188 | "node_id": "O_kgDOBqf-AA", 189 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 190 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 191 | "repos_url": "https://api.github.com/users/mageroni/repos", 192 | "site_admin": false, 193 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 194 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 195 | "type": "Organization", 196 | "url": "https://api.github.com/users/mageroni" 197 | }, 198 | "private": false, 199 | "pulls_url": "https://api.github.com/repos/mageroni/TestRepo/pulls{/number}", 200 | "pushed_at": "2023-03-18T22:50:20Z", 201 | "releases_url": "https://api.github.com/repos/mageroni/TestRepo/releases{/id}", 202 | "size": 135, 203 | "ssh_url": "git@github.com:mageroni/TestRepo.git", 204 | "stargazers_count": 0, 205 | "stargazers_url": "https://api.github.com/repos/mageroni/TestRepo/stargazers", 206 | "statuses_url": "https://api.github.com/repos/mageroni/TestRepo/statuses/{sha}", 207 | "subscribers_url": "https://api.github.com/repos/mageroni/TestRepo/subscribers", 208 | "subscription_url": "https://api.github.com/repos/mageroni/TestRepo/subscription", 209 | "svn_url": "https://github.com/mageroni/TestRepo", 210 | "tags_url": "https://api.github.com/repos/mageroni/TestRepo/tags", 211 | "teams_url": "https://api.github.com/repos/mageroni/TestRepo/teams", 212 | "topics": [], 213 | "trees_url": "https://api.github.com/repos/mageroni/TestRepo/git/trees{/sha}", 214 | "updated_at": "2023-03-03T23:53:44Z", 215 | "url": "https://api.github.com/repos/mageroni/TestRepo", 216 | "visibility": "public", 217 | "watchers": 0, 218 | "watchers_count": 0, 219 | "web_commit_signoff_required": false 220 | }, 221 | "sender": { 222 | "avatar_url": "https://avatars.githubusercontent.com/u/107436170?v=4", 223 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 224 | "followers_url": "https://api.github.com/users/mageroni/followers", 225 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 226 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 227 | "gravatar_id": "", 228 | "html_url": "https://github.com/mageroni", 229 | "id": 107436170, 230 | "login": "mageroni", 231 | "node_id": "U_kgDOBmdYig", 232 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 233 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 234 | "repos_url": "https://api.github.com/users/mageroni/repos", 235 | "site_admin": true, 236 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 237 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 238 | "type": "User", 239 | "url": "https://api.github.com/users/mageroni" 240 | }, 241 | "installation": { 242 | "id": 35217443, 243 | "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzUyMTc0NDM=" 244 | } 245 | } -------------------------------------------------------------------------------- /test/fixtures/issues.edited.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "edited", 3 | "issue": { 4 | "active_lock_reason": null, 5 | "assignee": null, 6 | "assignees": [], 7 | "author_association": "NONE", 8 | "body": "### Copilot Usage Survey\n\nFor Pull Request #44:\n\n1. ***Did you use Copilot in developing this PR? (If you select No, just answer question 5)***\n- [ ] No\n- [x] Yes\n\n2. Compared to your previous experience coding WITHOUT using Copilot (This number represents 100%) ***How much less time did the coding take during this PR with Copilot?***\n\n(Example: The PR would normally take 5 days, but only took 4 days with Copilot then the answer is 20%)\n- [ ] 0%\n- [ ] > 0% but < 10%\n- [x] ≥ 11% but < 20%\n- [ ] ≥ 21% but < 30%\n- [ ] ≥ 31% but < 40%\n- [ ] ≥ 41%\n\n3. ***Describe your thought process for calculating (or estimating) the time saved in Question 2***\n\n- (Please tell us in a comment)\n\n4. ***How often did you use Copilot in this PR?***\n- [x] All or most of the time\n- [ ] About Half of the time\n- [ ] Some of the time\n- [x] Not very much\n\n5. ***What other information can you share about Copilot's ability to save you time coding?*** \n\n- (Please tell us in a comment)\n\n6. ***Where did you invest your Copilot Time Savings?***\n- [x] Resolve vulnerabilites\n- [ ] Experiment, Learn and Wellness\n- [ ] Technical debt and refactorization\n- [ ] Work on other items in the backlog\n- [ ] Other. Please explain in the comment", 9 | "closed_at": "2023-03-19T01:12:10Z", 10 | "comments": 0, 11 | "comments_url": "https://api.github.com/repos/mageroni/TestRepo/issues/62/comments", 12 | "created_at": "2023-03-18T22:00:34Z", 13 | "events_url": "https://api.github.com/repos/mageroni/TestRepo/issues/62/events", 14 | "html_url": "https://github.com/mageroni/TestRepo/issues/62", 15 | "id": 1630633875, 16 | "labels": [], 17 | "labels_url": "https://api.github.com/repos/mageroni/TestRepo/issues/62/labels{/name}", 18 | "locked": false, 19 | "milestone": null, 20 | "node_id": "I_kwDOH5NwUM5hMX-T", 21 | "number": 62, 22 | "performed_via_github_app": null, 23 | "reactions": { 24 | "+1": 0, 25 | "-1": 0, 26 | "confused": 0, 27 | "eyes": 0, 28 | "heart": 0, 29 | "hooray": 0, 30 | "laugh": 0, 31 | "rocket": 0, 32 | "total_count": 0, 33 | "url": "https://api.github.com/repos/mageroni/TestRepo/issues/62/reactions" 34 | }, 35 | "repository_url": "https://api.github.com/repos/mageroni/TestRepo", 36 | "state": "closed", 37 | "state_reason": "completed", 38 | "timeline_url": "https://api.github.com/repos/mageroni/TestRepo/issues/62/timeline", 39 | "title": "Copilot Usage - PR#44", 40 | "updated_at": "2023-03-19T03:39:06Z", 41 | "url": "https://api.github.com/repos/mageroni/TestRepo/issues/62", 42 | "user": { 43 | "avatar_url": "https://avatars.githubusercontent.com/in/304911?v=4", 44 | "events_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/events{/privacy}", 45 | "followers_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/followers", 46 | "following_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/following{/other_user}", 47 | "gists_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/gists{/gist_id}", 48 | "gravatar_id": "", 49 | "html_url": "https://github.com/apps/copilot-survey-engine", 50 | "id": 127805006, 51 | "login": "copilot-survey-engine[bot]", 52 | "node_id": "BOT_kgDOB54mTg", 53 | "organizations_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/orgs", 54 | "received_events_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/received_events", 55 | "repos_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/repos", 56 | "site_admin": false, 57 | "starred_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/starred{/owner}{/repo}", 58 | "subscriptions_url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D/subscriptions", 59 | "type": "Bot", 60 | "url": "https://api.github.com/users/copilot-survey-engine%5Bbot%5D" 61 | } 62 | }, 63 | "organization": { 64 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 65 | "description": "Public Org GitHub.com", 66 | "events_url": "https://api.github.com/orgs/mageroni/events", 67 | "hooks_url": "https://api.github.com/orgs/mageroni/hooks", 68 | "id": 111672832, 69 | "issues_url": "https://api.github.com/orgs/mageroni/issues", 70 | "login": "mageroni", 71 | "members_url": "https://api.github.com/orgs/mageroni/members{/member}", 72 | "node_id": "O_kgDOBqf-AA", 73 | "public_members_url": "https://api.github.com/orgs/mageroni/public_members{/member}", 74 | "repos_url": "https://api.github.com/orgs/mageroni/repos", 75 | "url": "https://api.github.com/orgs/mageroni" 76 | }, 77 | "repository": { 78 | "allow_forking": true, 79 | "archive_url": "https://api.github.com/repos/mageroni/TestRepo/{archive_format}{/ref}", 80 | "archived": false, 81 | "assignees_url": "https://api.github.com/repos/mageroni/TestRepo/assignees{/user}", 82 | "blobs_url": "https://api.github.com/repos/mageroni/TestRepo/git/blobs{/sha}", 83 | "branches_url": "https://api.github.com/repos/mageroni/TestRepo/branches{/branch}", 84 | "clone_url": "https://github.com/mageroni/TestRepo.git", 85 | "collaborators_url": "https://api.github.com/repos/mageroni/TestRepo/collaborators{/collaborator}", 86 | "comments_url": "https://api.github.com/repos/mageroni/TestRepo/comments{/number}", 87 | "commits_url": "https://api.github.com/repos/mageroni/TestRepo/commits{/sha}", 88 | "compare_url": "https://api.github.com/repos/mageroni/TestRepo/compare/{base}...{head}", 89 | "contents_url": "https://api.github.com/repos/mageroni/TestRepo/contents/{+path}", 90 | "contributors_url": "https://api.github.com/repos/mageroni/TestRepo/contributors", 91 | "created_at": "2022-08-28T03:54:10Z", 92 | "default_branch": "master", 93 | "deployments_url": "https://api.github.com/repos/mageroni/TestRepo/deployments", 94 | "description": null, 95 | "disabled": false, 96 | "downloads_url": "https://api.github.com/repos/mageroni/TestRepo/downloads", 97 | "events_url": "https://api.github.com/repos/mageroni/TestRepo/events", 98 | "fork": false, 99 | "forks": 0, 100 | "forks_count": 0, 101 | "forks_url": "https://api.github.com/repos/mageroni/TestRepo/forks", 102 | "full_name": "mageroni/TestRepo", 103 | "git_commits_url": "https://api.github.com/repos/mageroni/TestRepo/git/commits{/sha}", 104 | "git_refs_url": "https://api.github.com/repos/mageroni/TestRepo/git/refs{/sha}", 105 | "git_tags_url": "https://api.github.com/repos/mageroni/TestRepo/git/tags{/sha}", 106 | "git_url": "git://github.com/mageroni/TestRepo.git", 107 | "has_discussions": false, 108 | "has_downloads": true, 109 | "has_issues": true, 110 | "has_pages": false, 111 | "has_projects": true, 112 | "has_wiki": true, 113 | "homepage": null, 114 | "hooks_url": "https://api.github.com/repos/mageroni/TestRepo/hooks", 115 | "html_url": "https://github.com/mageroni/TestRepo", 116 | "id": 529756240, 117 | "is_template": false, 118 | "issue_comment_url": "https://api.github.com/repos/mageroni/TestRepo/issues/comments{/number}", 119 | "issue_events_url": "https://api.github.com/repos/mageroni/TestRepo/issues/events{/number}", 120 | "issues_url": "https://api.github.com/repos/mageroni/TestRepo/issues{/number}", 121 | "keys_url": "https://api.github.com/repos/mageroni/TestRepo/keys{/key_id}", 122 | "labels_url": "https://api.github.com/repos/mageroni/TestRepo/labels{/name}", 123 | "language": "Shell", 124 | "languages_url": "https://api.github.com/repos/mageroni/TestRepo/languages", 125 | "license": null, 126 | "merges_url": "https://api.github.com/repos/mageroni/TestRepo/merges", 127 | "milestones_url": "https://api.github.com/repos/mageroni/TestRepo/milestones{/number}", 128 | "mirror_url": null, 129 | "name": "TestRepo", 130 | "node_id": "R_kgDOH5NwUA", 131 | "notifications_url": "https://api.github.com/repos/mageroni/TestRepo/notifications{?since,all,participating}", 132 | "open_issues": 16, 133 | "open_issues_count": 16, 134 | "owner": { 135 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 136 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 137 | "followers_url": "https://api.github.com/users/mageroni/followers", 138 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 139 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 140 | "gravatar_id": "", 141 | "html_url": "https://github.com/mageroni", 142 | "id": 111672832, 143 | "login": "mageroni", 144 | "node_id": "O_kgDOBqf-AA", 145 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 146 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 147 | "repos_url": "https://api.github.com/users/mageroni/repos", 148 | "site_admin": false, 149 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 150 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 151 | "type": "Organization", 152 | "url": "https://api.github.com/users/mageroni" 153 | }, 154 | "private": false, 155 | "pulls_url": "https://api.github.com/repos/mageroni/TestRepo/pulls{/number}", 156 | "pushed_at": "2023-03-18T22:50:20Z", 157 | "releases_url": "https://api.github.com/repos/mageroni/TestRepo/releases{/id}", 158 | "size": 135, 159 | "ssh_url": "git@github.com:mageroni/TestRepo.git", 160 | "stargazers_count": 0, 161 | "stargazers_url": "https://api.github.com/repos/mageroni/TestRepo/stargazers", 162 | "statuses_url": "https://api.github.com/repos/mageroni/TestRepo/statuses/{sha}", 163 | "subscribers_url": "https://api.github.com/repos/mageroni/TestRepo/subscribers", 164 | "subscription_url": "https://api.github.com/repos/mageroni/TestRepo/subscription", 165 | "svn_url": "https://github.com/mageroni/TestRepo", 166 | "tags_url": "https://api.github.com/repos/mageroni/TestRepo/tags", 167 | "teams_url": "https://api.github.com/repos/mageroni/TestRepo/teams", 168 | "topics": [], 169 | "trees_url": "https://api.github.com/repos/mageroni/TestRepo/git/trees{/sha}", 170 | "updated_at": "2023-03-03T23:53:44Z", 171 | "url": "https://api.github.com/repos/mageroni/TestRepo", 172 | "visibility": "public", 173 | "watchers": 0, 174 | "watchers_count": 0, 175 | "web_commit_signoff_required": false 176 | }, 177 | "sender": { 178 | "avatar_url": "https://avatars.githubusercontent.com/u/107436170?v=4", 179 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 180 | "followers_url": "https://api.github.com/users/mageroni/followers", 181 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 182 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 183 | "gravatar_id": "", 184 | "html_url": "https://github.com/mageroni", 185 | "id": 107436170, 186 | "login": "mageroni", 187 | "node_id": "U_kgDOBmdYig", 188 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 189 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 190 | "repos_url": "https://api.github.com/users/mageroni/repos", 191 | "site_admin": true, 192 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 193 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 194 | "type": "User", 195 | "url": "https://api.github.com/users/mageroni" 196 | }, 197 | "installation": { 198 | "id": 35217443, 199 | "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzUyMTc0NDM=" 200 | } 201 | } -------------------------------------------------------------------------------- /test/fixtures/mock-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAli7V49NdZe+XYC1pLaHM0te8kiDmZBJ1u2HJHN8GdbROB6NO 3 | VpC3xK7NxQn6xpvZ9ux20NvcDvGle+DOptZztBH+np6h2jZQ1/kD1yG1eQvVH4th 4 | /9oqHuIjmIfO8lIe4Hyd5Fw5xHkGqVETTGR+0c7kdZIlHmkOregUGtMYZRUi4YG+ 5 | q0w+uFemiHpGKXbeCIAvkq7aIkisEzvPWfSyYdA6WJHpxFk7tD7D8VkzABLVRHCq 6 | AuyqPG39BhGZcGLXx5rGK56kDBJkyTR1t3DkHpwX+JKNG5UYNwOG4LcQj1fteeta 7 | TdkYUMjIyWbanlMYyC+dq7B5fe7el99jXQ1gXwIDAQABAoIBADKfiPOpzKLOtzzx 8 | MbHzB0LO+75aHq7+1faayJrVxqyoYWELuB1P3NIMhknzyjdmU3t7S7WtVqkm5Twz 9 | lBUC1q+NHUHEgRQ4GNokExpSP4SU63sdlaQTmv0cBxmkNarS6ZuMBgDy4XoLvaYX 10 | MSUf/uukDLhg0ehFS3BteVFtdJyllhDdTenF1Nb1rAeN4egt8XLsE5NQDr1szFEG 11 | xH5lb+8EDtzgsGpeIddWR64xP0lDIKSZWst/toYKWiwjaY9uZCfAhvYQ1RsO7L/t 12 | sERmpYgh+rAZUh/Lr98EI8BPSPhzFcSHmtqzzejvC5zrZPHcUimz0CGA3YBiLoJX 13 | V1OrxmECgYEAxkd8gpmVP+LEWB3lqpSvJaXcGkbzcDb9m0OPzHUAJDZtiIIf0UmO 14 | nvL68/mzbCHSj+yFjZeG1rsrAVrOzrfDCuXjAv+JkEtEx0DIevU1u60lGnevOeky 15 | r8Be7pmymFB9/gzQAd5ezIlTv/COgoO986a3h1yfhzrrzbqSiivw308CgYEAwecI 16 | aZZwqH3GifR+0+Z1B48cezA5tC8LZt5yObGzUfxKTWy30d7lxe9N59t0KUVt/QL5 17 | qVkd7mqGzsUMyxUN2U2HVnFTWfUFMhkn/OnCnayhILs8UlCTD2Xxoy1KbQH/9FIr 18 | xf0pbMNJLXeGfyRt/8H+BzSZKBw9opJBWE4gqfECgYBp9FdvvryHuBkt8UQCRJPX 19 | rWsRy6pY47nf11mnazpZH5Cmqspv3zvMapF6AIxFk0leyYiQolFWvAv+HFV5F6+t 20 | Si1mM8GCDwbA5zh6pEBDewHhw+UqMBh63HSeUhmi1RiOwrAA36CO8i+D2Pt+eQHv 21 | ir52IiPJcs4BUNrv5Q1BdwKBgBHgVNw3LGe8QMOTMOYkRwHNZdjNl2RPOgPf2jQL 22 | d/bFBayhq0jD/fcDmvEXQFxVtFAxKAc+2g2S8J67d/R5Gm/AQAvuIrsWZcY6n38n 23 | pfOXaLt1x5fnKcevpFlg4Y2vM4O416RHNLx8PJDehh3Oo/2CSwMrDDuwbtZAGZok 24 | icphAoGBAI74Tisfn+aeCZMrO8KxaWS5r2CD1KVzddEMRKlJvSKTY+dOCtJ+XKj1 25 | OsZdcDvDC5GtgcywHsYeOWHldgDWY1S8Z/PUo4eK9qBXYBXp3JEZQ1dqzFdz+Txi 26 | rBn2WsFLsxV9j2/ugm0PqWVBcU2bPUCwvaRu3SOms2teaLwGCkhr 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /test/fixtures/pull_request.closed.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "closed", 3 | "number": 44, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/mageroni/TestRepo/pulls/44", 6 | "id": 1280983238, 7 | "number": 44, 8 | "body":"This is a test pull request to see if language is being detected", 9 | "state": "closed", 10 | "user": { 11 | "login": "mageroni" 12 | }, 13 | "created_at": "2023-03-18T06:50:48Z", 14 | "updated_at": "2023-03-18T07:52:09Z", 15 | "closed_at": "2023-03-18T07:52:09Z", 16 | "assignee": null, 17 | "assignees": [], 18 | "requested_reviewers": [], 19 | "requested_teams": [], 20 | "head": { 21 | "label": "mageroni:new-branch", 22 | "ref": "new-branch", 23 | "sha": "2a5d1a5fd2e30696942dde6047d04a50eb9291f2", 24 | "user": { 25 | "login": "mageroni", 26 | "id": 111672832 27 | }, 28 | "repo": { 29 | "id": 529756240, 30 | "name": "TestRepo", 31 | "full_name": "mageroni/TestRepo", 32 | "private": false, 33 | "owner": { 34 | "login": "mageroni", 35 | "id": 111672832, 36 | "node_id": "O_kgDOBqf-AA", 37 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 38 | "gravatar_id": "", 39 | "url": "https://api.github.com/users/mageroni", 40 | "html_url": "https://github.com/mageroni", 41 | "followers_url": "https://api.github.com/users/mageroni/followers", 42 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 43 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 44 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 45 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 46 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 47 | "repos_url": "https://api.github.com/users/mageroni/repos", 48 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 49 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 50 | "type": "Organization", 51 | "site_admin": false 52 | }, 53 | "html_url": "https://github.com/mageroni/TestRepo", 54 | "description": null, 55 | "fork": false, 56 | "url": "https://api.github.com/repos/mageroni/TestRepo", 57 | "forks_url": "https://api.github.com/repos/mageroni/TestRepo/forks", 58 | "keys_url": "https://api.github.com/repos/mageroni/TestRepo/keys{/key_id}", 59 | "collaborators_url": "https://api.github.com/repos/mageroni/TestRepo/collaborators{/collaborator}", 60 | "teams_url": "https://api.github.com/repos/mageroni/TestRepo/teams", 61 | "hooks_url": "https://api.github.com/repos/mageroni/TestRepo/hooks", 62 | "issue_events_url": "https://api.github.com/repos/mageroni/TestRepo/issues/events{/number}", 63 | "events_url": "https://api.github.com/repos/mageroni/TestRepo/events", 64 | "assignees_url": "https://api.github.com/repos/mageroni/TestRepo/assignees{/user}", 65 | "branches_url": "https://api.github.com/repos/mageroni/TestRepo/branches{/branch}", 66 | "tags_url": "https://api.github.com/repos/mageroni/TestRepo/tags", 67 | "blobs_url": "https://api.github.com/repos/mageroni/TestRepo/git/blobs{/sha}", 68 | "git_tags_url": "https://api.github.com/repos/mageroni/TestRepo/git/tags{/sha}", 69 | "git_refs_url": "https://api.github.com/repos/mageroni/TestRepo/git/refs{/sha}", 70 | "trees_url": "https://api.github.com/repos/mageroni/TestRepo/git/trees{/sha}", 71 | "statuses_url": "https://api.github.com/repos/mageroni/TestRepo/statuses/{sha}", 72 | "languages_url": "https://api.github.com/repos/mageroni/TestRepo/languages", 73 | "stargazers_url": "https://api.github.com/repos/mageroni/TestRepo/stargazers", 74 | "contributors_url": "https://api.github.com/repos/mageroni/TestRepo/contributors", 75 | "subscribers_url": "https://api.github.com/repos/mageroni/TestRepo/subscribers", 76 | "subscription_url": "https://api.github.com/repos/mageroni/TestRepo/subscription", 77 | "commits_url": "https://api.github.com/repos/mageroni/TestRepo/commits{/sha}", 78 | "git_commits_url": "https://api.github.com/repos/mageroni/TestRepo/git/commits{/sha}", 79 | "comments_url": "https://api.github.com/repos/mageroni/TestRepo/comments{/number}", 80 | "issue_comment_url": "https://api.github.com/repos/mageroni/TestRepo/issues/comments{/number}", 81 | "contents_url": "https://api.github.com/repos/mageroni/TestRepo/contents/{+path}", 82 | "compare_url": "https://api.github.com/repos/mageroni/TestRepo/compare/{base}...{head}", 83 | "merges_url": "https://api.github.com/repos/mageroni/TestRepo/merges", 84 | "archive_url": "https://api.github.com/repos/mageroni/TestRepo/{archive_format}{/ref}", 85 | "downloads_url": "https://api.github.com/repos/mageroni/TestRepo/downloads", 86 | "issues_url": "https://api.github.com/repos/mageroni/TestRepo/issues{/number}", 87 | "pulls_url": "https://api.github.com/repos/mageroni/TestRepo/pulls{/number}", 88 | "milestones_url": "https://api.github.com/repos/mageroni/TestRepo/milestones{/number}", 89 | "notifications_url": "https://api.github.com/repos/mageroni/TestRepo/notifications{?since,all,participating}", 90 | "labels_url": "https://api.github.com/repos/mageroni/TestRepo/labels{/name}", 91 | "releases_url": "https://api.github.com/repos/mageroni/TestRepo/releases{/id}", 92 | "deployments_url": "https://api.github.com/repos/mageroni/TestRepo/deployments", 93 | "created_at": "2022-08-28T03:54:10Z", 94 | "updated_at": "2023-03-03T23:53:44Z", 95 | "pushed_at": "2023-03-18T06:50:49Z", 96 | "git_url": "git://github.com/mageroni/TestRepo.git", 97 | "ssh_url": "git@github.com:mageroni/TestRepo.git", 98 | "clone_url": "https://github.com/mageroni/TestRepo.git", 99 | "svn_url": "https://github.com/mageroni/TestRepo", 100 | "homepage": null, 101 | "size": 133, 102 | "stargazers_count": 0, 103 | "watchers_count": 0, 104 | "language": "Shell", 105 | "has_issues": true, 106 | "has_projects": true, 107 | "has_downloads": true, 108 | "has_wiki": true, 109 | "has_pages": false, 110 | "has_discussions": false, 111 | "forks_count": 0, 112 | "mirror_url": null, 113 | "archived": false, 114 | "disabled": false, 115 | "open_issues_count": 3, 116 | "license": null, 117 | "allow_forking": true, 118 | "is_template": false, 119 | "web_commit_signoff_required": false, 120 | "topics": [], 121 | "visibility": "public", 122 | "forks": 0, 123 | "open_issues": 3, 124 | "watchers": 0, 125 | "default_branch": "master", 126 | "allow_squash_merge": true, 127 | "allow_merge_commit": true, 128 | "allow_rebase_merge": true, 129 | "allow_auto_merge": false, 130 | "delete_branch_on_merge": false, 131 | "allow_update_branch": false, 132 | "use_squash_pr_title_as_default": false, 133 | "squash_merge_commit_message": "COMMIT_MESSAGES", 134 | "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", 135 | "merge_commit_message": "PR_TITLE", 136 | "merge_commit_title": "MERGE_MESSAGE" 137 | } 138 | }, 139 | "base": { 140 | "label": "mageroni:master", 141 | "ref": "master", 142 | "sha": "9414a03235de69242912d44ac2b6e778e05cea4e", 143 | "user": { 144 | "login": "mageroni", 145 | "id": 111672832, 146 | "node_id": "O_kgDOBqf-AA", 147 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 148 | "gravatar_id": "", 149 | "url": "https://api.github.com/users/mageroni", 150 | "html_url": "https://github.com/mageroni", 151 | "followers_url": "https://api.github.com/users/mageroni/followers", 152 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 153 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 154 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 155 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 156 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 157 | "repos_url": "https://api.github.com/users/mageroni/repos", 158 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 159 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 160 | "type": "Organization", 161 | "site_admin": false 162 | }, 163 | "repo": { 164 | "id": 529756240, 165 | "node_id": "R_kgDOH5NwUA", 166 | "name": "TestRepo", 167 | "full_name": "mageroni/TestRepo", 168 | "private": false, 169 | "owner": { 170 | "login": "mageroni", 171 | "id": 111672832, 172 | "node_id": "O_kgDOBqf-AA", 173 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 174 | "gravatar_id": "", 175 | "url": "https://api.github.com/users/mageroni", 176 | "html_url": "https://github.com/mageroni", 177 | "followers_url": "https://api.github.com/users/mageroni/followers", 178 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 179 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 180 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 181 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 182 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 183 | "repos_url": "https://api.github.com/users/mageroni/repos", 184 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 185 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 186 | "type": "Organization", 187 | "site_admin": false 188 | }, 189 | "html_url": "https://github.com/mageroni/TestRepo", 190 | "description": null, 191 | "fork": false, 192 | "url": "https://api.github.com/repos/mageroni/TestRepo", 193 | "forks_url": "https://api.github.com/repos/mageroni/TestRepo/forks", 194 | "keys_url": "https://api.github.com/repos/mageroni/TestRepo/keys{/key_id}", 195 | "collaborators_url": "https://api.github.com/repos/mageroni/TestRepo/collaborators{/collaborator}", 196 | "teams_url": "https://api.github.com/repos/mageroni/TestRepo/teams", 197 | "hooks_url": "https://api.github.com/repos/mageroni/TestRepo/hooks", 198 | "issue_events_url": "https://api.github.com/repos/mageroni/TestRepo/issues/events{/number}", 199 | "events_url": "https://api.github.com/repos/mageroni/TestRepo/events", 200 | "assignees_url": "https://api.github.com/repos/mageroni/TestRepo/assignees{/user}", 201 | "branches_url": "https://api.github.com/repos/mageroni/TestRepo/branches{/branch}", 202 | "tags_url": "https://api.github.com/repos/mageroni/TestRepo/tags", 203 | "blobs_url": "https://api.github.com/repos/mageroni/TestRepo/git/blobs{/sha}", 204 | "git_tags_url": "https://api.github.com/repos/mageroni/TestRepo/git/tags{/sha}", 205 | "git_refs_url": "https://api.github.com/repos/mageroni/TestRepo/git/refs{/sha}", 206 | "trees_url": "https://api.github.com/repos/mageroni/TestRepo/git/trees{/sha}", 207 | "statuses_url": "https://api.github.com/repos/mageroni/TestRepo/statuses/{sha}", 208 | "languages_url": "https://api.github.com/repos/mageroni/TestRepo/languages", 209 | "stargazers_url": "https://api.github.com/repos/mageroni/TestRepo/stargazers", 210 | "contributors_url": "https://api.github.com/repos/mageroni/TestRepo/contributors", 211 | "subscribers_url": "https://api.github.com/repos/mageroni/TestRepo/subscribers", 212 | "subscription_url": "https://api.github.com/repos/mageroni/TestRepo/subscription", 213 | "commits_url": "https://api.github.com/repos/mageroni/TestRepo/commits{/sha}", 214 | "git_commits_url": "https://api.github.com/repos/mageroni/TestRepo/git/commits{/sha}", 215 | "comments_url": "https://api.github.com/repos/mageroni/TestRepo/comments{/number}", 216 | "issue_comment_url": "https://api.github.com/repos/mageroni/TestRepo/issues/comments{/number}", 217 | "contents_url": "https://api.github.com/repos/mageroni/TestRepo/contents/{+path}", 218 | "compare_url": "https://api.github.com/repos/mageroni/TestRepo/compare/{base}...{head}", 219 | "merges_url": "https://api.github.com/repos/mageroni/TestRepo/merges", 220 | "archive_url": "https://api.github.com/repos/mageroni/TestRepo/{archive_format}{/ref}", 221 | "downloads_url": "https://api.github.com/repos/mageroni/TestRepo/downloads", 222 | "issues_url": "https://api.github.com/repos/mageroni/TestRepo/issues{/number}", 223 | "pulls_url": "https://api.github.com/repos/mageroni/TestRepo/pulls{/number}", 224 | "milestones_url": "https://api.github.com/repos/mageroni/TestRepo/milestones{/number}", 225 | "notifications_url": "https://api.github.com/repos/mageroni/TestRepo/notifications{?since,all,participating}", 226 | "labels_url": "https://api.github.com/repos/mageroni/TestRepo/labels{/name}", 227 | "releases_url": "https://api.github.com/repos/mageroni/TestRepo/releases{/id}", 228 | "deployments_url": "https://api.github.com/repos/mageroni/TestRepo/deployments", 229 | "created_at": "2022-08-28T03:54:10Z", 230 | "updated_at": "2023-03-03T23:53:44Z", 231 | "pushed_at": "2023-03-18T06:50:49Z", 232 | "git_url": "git://github.com/mageroni/TestRepo.git", 233 | "ssh_url": "git@github.com:mageroni/TestRepo.git", 234 | "clone_url": "https://github.com/mageroni/TestRepo.git", 235 | "svn_url": "https://github.com/mageroni/TestRepo", 236 | "homepage": null, 237 | "size": 133, 238 | "stargazers_count": 0, 239 | "watchers_count": 0, 240 | "language": "Shell", 241 | "has_issues": true, 242 | "has_projects": true, 243 | "has_downloads": true, 244 | "has_wiki": true, 245 | "has_pages": false, 246 | "has_discussions": false, 247 | "forks_count": 0, 248 | "mirror_url": null, 249 | "archived": false, 250 | "disabled": false, 251 | "open_issues_count": 3, 252 | "license": null, 253 | "allow_forking": true, 254 | "is_template": false, 255 | "web_commit_signoff_required": false, 256 | "topics": [], 257 | "visibility": "public", 258 | "forks": 0, 259 | "open_issues": 3, 260 | "watchers": 0, 261 | "default_branch": "master", 262 | "allow_squash_merge": true, 263 | "allow_merge_commit": true, 264 | "allow_rebase_merge": true, 265 | "allow_auto_merge": false, 266 | "delete_branch_on_merge": false, 267 | "allow_update_branch": false, 268 | "use_squash_pr_title_as_default": false, 269 | "squash_merge_commit_message": "COMMIT_MESSAGES", 270 | "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", 271 | "merge_commit_message": "PR_TITLE", 272 | "merge_commit_title": "MERGE_MESSAGE" 273 | } 274 | }, 275 | "_links": { 276 | "self": { 277 | "href": "https://api.github.com/repos/mageroni/TestRepo/pulls/44" 278 | }, 279 | "html": { 280 | "href": "https://github.com/mageroni/TestRepo/pull/44" 281 | }, 282 | "issue": { 283 | "href": "https://api.github.com/repos/mageroni/TestRepo/issues/44" 284 | }, 285 | "comments": { 286 | "href": "https://api.github.com/repos/mageroni/TestRepo/issues/44/comments" 287 | }, 288 | "review_comments": { 289 | "href": "https://api.github.com/repos/mageroni/TestRepo/pulls/44/comments" 290 | }, 291 | "review_comment": { 292 | "href": "https://api.github.com/repos/mageroni/TestRepo/pulls/comments{/number}" 293 | }, 294 | "commits": { 295 | "href": "https://api.github.com/repos/mageroni/TestRepo/pulls/44/commits" 296 | }, 297 | "statuses": { 298 | "href": "https://api.github.com/repos/mageroni/TestRepo/statuses/2a5d1a5fd2e30696942dde6047d04a50eb9291f2" 299 | } 300 | }, 301 | "author_association": "COLLABORATOR", 302 | "auto_merge": null, 303 | "active_lock_reason": null, 304 | "merged": false, 305 | "mergeable": true, 306 | "rebaseable": false, 307 | "mergeable_state": "unstable", 308 | "merged_by": null, 309 | "comments": 12, 310 | "review_comments": 0, 311 | "maintainer_can_modify": false, 312 | "commits": 1, 313 | "additions": 2, 314 | "deletions": 0, 315 | "changed_files": 1 316 | }, 317 | "repository": { 318 | "id": 529756240, 319 | "node_id": "R_kgDOH5NwUA", 320 | "name": "TestRepo", 321 | "full_name": "mageroni/TestRepo", 322 | "private": false, 323 | "owner": { 324 | "login": "mageroni", 325 | "id": 111672832, 326 | "node_id": "O_kgDOBqf-AA", 327 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 328 | "gravatar_id": "", 329 | "url": "https://api.github.com/users/mageroni", 330 | "html_url": "https://github.com/mageroni", 331 | "followers_url": "https://api.github.com/users/mageroni/followers", 332 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 333 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 334 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 335 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 336 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 337 | "repos_url": "https://api.github.com/users/mageroni/repos", 338 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 339 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 340 | "type": "Organization", 341 | "site_admin": false 342 | }, 343 | "html_url": "https://github.com/mageroni/TestRepo", 344 | "description": null, 345 | "fork": false, 346 | "url": "https://api.github.com/repos/mageroni/TestRepo", 347 | "forks_url": "https://api.github.com/repos/mageroni/TestRepo/forks", 348 | "keys_url": "https://api.github.com/repos/mageroni/TestRepo/keys{/key_id}", 349 | "collaborators_url": "https://api.github.com/repos/mageroni/TestRepo/collaborators{/collaborator}", 350 | "teams_url": "https://api.github.com/repos/mageroni/TestRepo/teams", 351 | "hooks_url": "https://api.github.com/repos/mageroni/TestRepo/hooks", 352 | "issue_events_url": "https://api.github.com/repos/mageroni/TestRepo/issues/events{/number}", 353 | "events_url": "https://api.github.com/repos/mageroni/TestRepo/events", 354 | "assignees_url": "https://api.github.com/repos/mageroni/TestRepo/assignees{/user}", 355 | "branches_url": "https://api.github.com/repos/mageroni/TestRepo/branches{/branch}", 356 | "tags_url": "https://api.github.com/repos/mageroni/TestRepo/tags", 357 | "blobs_url": "https://api.github.com/repos/mageroni/TestRepo/git/blobs{/sha}", 358 | "git_tags_url": "https://api.github.com/repos/mageroni/TestRepo/git/tags{/sha}", 359 | "git_refs_url": "https://api.github.com/repos/mageroni/TestRepo/git/refs{/sha}", 360 | "trees_url": "https://api.github.com/repos/mageroni/TestRepo/git/trees{/sha}", 361 | "statuses_url": "https://api.github.com/repos/mageroni/TestRepo/statuses/{sha}", 362 | "languages_url": "https://api.github.com/repos/mageroni/TestRepo/languages", 363 | "stargazers_url": "https://api.github.com/repos/mageroni/TestRepo/stargazers", 364 | "contributors_url": "https://api.github.com/repos/mageroni/TestRepo/contributors", 365 | "subscribers_url": "https://api.github.com/repos/mageroni/TestRepo/subscribers", 366 | "subscription_url": "https://api.github.com/repos/mageroni/TestRepo/subscription", 367 | "commits_url": "https://api.github.com/repos/mageroni/TestRepo/commits{/sha}", 368 | "git_commits_url": "https://api.github.com/repos/mageroni/TestRepo/git/commits{/sha}", 369 | "comments_url": "https://api.github.com/repos/mageroni/TestRepo/comments{/number}", 370 | "issue_comment_url": "https://api.github.com/repos/mageroni/TestRepo/issues/comments{/number}", 371 | "contents_url": "https://api.github.com/repos/mageroni/TestRepo/contents/{+path}", 372 | "compare_url": "https://api.github.com/repos/mageroni/TestRepo/compare/{base}...{head}", 373 | "merges_url": "https://api.github.com/repos/mageroni/TestRepo/merges", 374 | "archive_url": "https://api.github.com/repos/mageroni/TestRepo/{archive_format}{/ref}", 375 | "downloads_url": "https://api.github.com/repos/mageroni/TestRepo/downloads", 376 | "issues_url": "https://api.github.com/repos/mageroni/TestRepo/issues{/number}", 377 | "pulls_url": "https://api.github.com/repos/mageroni/TestRepo/pulls{/number}", 378 | "milestones_url": "https://api.github.com/repos/mageroni/TestRepo/milestones{/number}", 379 | "notifications_url": "https://api.github.com/repos/mageroni/TestRepo/notifications{?since,all,participating}", 380 | "labels_url": "https://api.github.com/repos/mageroni/TestRepo/labels{/name}", 381 | "releases_url": "https://api.github.com/repos/mageroni/TestRepo/releases{/id}", 382 | "deployments_url": "https://api.github.com/repos/mageroni/TestRepo/deployments", 383 | "created_at": "2022-08-28T03:54:10Z", 384 | "updated_at": "2023-03-03T23:53:44Z", 385 | "pushed_at": "2023-03-18T06:50:49Z", 386 | "git_url": "git://github.com/mageroni/TestRepo.git", 387 | "ssh_url": "git@github.com:mageroni/TestRepo.git", 388 | "clone_url": "https://github.com/mageroni/TestRepo.git", 389 | "svn_url": "https://github.com/mageroni/TestRepo", 390 | "homepage": null, 391 | "size": 133, 392 | "stargazers_count": 0, 393 | "watchers_count": 0, 394 | "language": "Shell", 395 | "has_issues": true, 396 | "has_projects": true, 397 | "has_downloads": true, 398 | "has_wiki": true, 399 | "has_pages": false, 400 | "has_discussions": false, 401 | "forks_count": 0, 402 | "mirror_url": null, 403 | "archived": false, 404 | "disabled": false, 405 | "open_issues_count": 3, 406 | "license": null, 407 | "allow_forking": true, 408 | "is_template": false, 409 | "web_commit_signoff_required": false, 410 | "topics": [], 411 | "visibility": "public", 412 | "forks": 0, 413 | "open_issues": 3, 414 | "watchers": 0, 415 | "default_branch": "master" 416 | }, 417 | "organization": { 418 | "login": "mageroni", 419 | "id": 111672832, 420 | "node_id": "O_kgDOBqf-AA", 421 | "url": "https://api.github.com/orgs/mageroni", 422 | "repos_url": "https://api.github.com/orgs/mageroni/repos", 423 | "events_url": "https://api.github.com/orgs/mageroni/events", 424 | "hooks_url": "https://api.github.com/orgs/mageroni/hooks", 425 | "issues_url": "https://api.github.com/orgs/mageroni/issues", 426 | "members_url": "https://api.github.com/orgs/mageroni/members{/member}", 427 | "public_members_url": "https://api.github.com/orgs/mageroni/public_members{/member}", 428 | "avatar_url": "https://avatars.githubusercontent.com/u/111672832?v=4", 429 | "description": "Public Org GitHub.com" 430 | }, 431 | "sender": { 432 | "login": "mageroni", 433 | "id": 107436170, 434 | "node_id": "U_kgDOBmdYig", 435 | "avatar_url": "https://avatars.githubusercontent.com/u/107436170?v=4", 436 | "gravatar_id": "", 437 | "url": "https://api.github.com/users/mageroni", 438 | "html_url": "https://github.com/mageroni", 439 | "followers_url": "https://api.github.com/users/mageroni/followers", 440 | "following_url": "https://api.github.com/users/mageroni/following{/other_user}", 441 | "gists_url": "https://api.github.com/users/mageroni/gists{/gist_id}", 442 | "starred_url": "https://api.github.com/users/mageroni/starred{/owner}{/repo}", 443 | "subscriptions_url": "https://api.github.com/users/mageroni/subscriptions", 444 | "organizations_url": "https://api.github.com/users/mageroni/orgs", 445 | "repos_url": "https://api.github.com/users/mageroni/repos", 446 | "events_url": "https://api.github.com/users/mageroni/events{/privacy}", 447 | "received_events_url": "https://api.github.com/users/mageroni/received_events", 448 | "type": "User", 449 | "site_admin": true 450 | }, 451 | "installation": { 452 | "id": 35217443, 453 | "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzUyMTc0NDM=" 454 | } 455 | } -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const nock = require("nock"); 2 | // Requiring our app implementation 3 | const myProbotApp = require(".."); 4 | const { Probot, ProbotOctokit } = require("probot"); 5 | // Requiring our fixtures 6 | const payload_pr_closed = require("./fixtures/pull_request.closed.json"); 7 | const payload_issues_edited = require("./fixtures/issues.edited.json"); 8 | const issue_comment_created = require("./fixtures/issue_comment.created.json"); 9 | const fs = require("fs"); 10 | const path = require("path"); 11 | const LANGUAGE_API_ENDPOINT = process.env.LANGUAGE_API_ENDPOINT; 12 | 13 | const issue_body = fs.readFileSync( 14 | path.join(__dirname, "fixtures/issue_body.md"), 15 | 'utf-8' 16 | ); 17 | 18 | const expected_issue = { 19 | title: "Copilot Usage - PR#44", 20 | body: issue_body, 21 | assignee: "mageroni" 22 | } 23 | 24 | const privateKey = fs.readFileSync( 25 | path.join(__dirname, "fixtures/mock-cert.pem"), 26 | "utf-8" 27 | ); 28 | 29 | describe("My Probot app", () => { 30 | let probot; 31 | 32 | beforeEach(() => { 33 | nock.disableNetConnect(); 34 | if(LANGUAGE_API_ENDPOINT) { 35 | nock.enableNetConnect(LANGUAGE_API_ENDPOINT); 36 | nock(LANGUAGE_API_ENDPOINT) 37 | .post('/language/:analyze-text?api-version=2023-04-01') 38 | .reply(200, { 39 | "kind": "LanguageDetectionResults", 40 | "results": { 41 | "documents": [{ 42 | "id": "1", 43 | "detectedLanguage": { 44 | "name": "English", 45 | "iso6391Name": "en", 46 | "confidenceScore": 1.0 47 | }, 48 | "warnings": [] 49 | }], 50 | "errors": [], 51 | "modelVersion": "2022-10-01" 52 | } 53 | }); 54 | } 55 | probot = new Probot({ 56 | appId: 123, 57 | privateKey, 58 | // disable request throttling and retries for testing 59 | Octokit: ProbotOctokit.defaults({ 60 | retry: { enabled: false }, 61 | throttle: { enabled: false }, 62 | }), 63 | }); 64 | // Load our app into probot 65 | probot.load(myProbotApp); 66 | }); 67 | 68 | test("creates an issue when a Pull Request is closed", async () => { 69 | const mock = nock("https://api.github.com") 70 | // Test that we correctly return a test token 71 | .post("/app/installations/35217443/access_tokens") 72 | .reply(200, { 73 | token: "test", 74 | permissions: { 75 | issues: "write", 76 | }, 77 | }) 78 | 79 | // Test that a issue is created 80 | .post("/repos/mageroni/TestRepo/issues", (body) => { 81 | expect(body).toMatchObject(expected_issue); 82 | return true; 83 | }) 84 | .reply(200); 85 | 86 | // Mock the call to the seats billing API 87 | if (process.env.VALIDATE_SEAT_ASSIGNMENT == 'YES') { 88 | mock.get("/orgs/mageroni/copilot/billing/seats") 89 | .reply(200, { 90 | seats: [{"assignee":{"login": "mageroni"}}] 91 | }); 92 | } 93 | 94 | // Receive a webhook event 95 | await probot.receive({ name: "pull_request", payload : payload_pr_closed }); 96 | 97 | expect(mock.pendingMocks()).toStrictEqual([]); 98 | }); 99 | 100 | test("closes an issue after it's been completed", async () => { 101 | const mock = nock("https://api.github.com") 102 | // Test that we correctly return a test token 103 | .post("/app/installations/35217443/access_tokens") 104 | .reply(200, { 105 | token: "test", 106 | permissions: { 107 | issues: "write", 108 | }, 109 | }) 110 | 111 | .patch("/repos/mageroni/TestRepo/issues/62", (body) => { 112 | expect(body).toMatchObject({state: 'closed'}); 113 | return true; 114 | }) 115 | .reply(200); 116 | 117 | // Receive a webhook event 118 | await probot.receive({ name: "issues", payload : payload_issues_edited }); 119 | 120 | expect(mock.pendingMocks()).toStrictEqual([]); 121 | }); 122 | 123 | test("closes an issue if a comment is received", async () => { 124 | const mock = nock("https://api.github.com") 125 | // Test that we correctly return a test token 126 | .post("/app/installations/35217443/access_tokens") 127 | .reply(200, { 128 | token: "test", 129 | permissions: { 130 | issues: "write", 131 | }, 132 | }) 133 | 134 | .patch("/repos/mageroni/TestRepo/issues/60", (body) => { 135 | expect(body).toMatchObject({state: 'closed'}); 136 | return true; 137 | }) 138 | .reply(200); 139 | 140 | // Receive a webhook event 141 | await probot.receive({ name: "issue_comment", payload : issue_comment_created }); 142 | 143 | expect(mock.pendingMocks()).toStrictEqual([]); 144 | }); 145 | 146 | afterEach(() => { 147 | nock.cleanAll(); 148 | nock.enableNetConnect(); 149 | }); 150 | }); 151 | 152 | // For more information about testing with Jest see: 153 | // https://facebook.github.io/jest/ 154 | 155 | // For more information about testing with Nock see: 156 | // https://github.com/nock/nock 157 | --------------------------------------------------------------------------------