├── .gitignore ├── .vscode └── settings.json ├── public ├── digits.png ├── background.png ├── digits-grey.png ├── digits-darkgrey.png ├── index.html ├── default.css └── client.js ├── assets ├── calculator.png ├── index-html.png ├── Cloning the repo.png ├── case-suggestion.png ├── open in browser.png ├── Open in a Codespace.png ├── Add-operator-completed.png └── Node calculator image.png ├── api ├── routes.js └── controller.js ├── test ├── config.json ├── helpers.js └── arithmetic.test.js ├── gulpfile.js ├── .eslintrc.js ├── server.js ├── .github └── workflows │ └── node.js.yml ├── .instructions ├── 4. additional resources.md ├── workshop organisers.md ├── 3. challenge exercises.md ├── 1. setup.md └── 2. core exercises.md ├── LICENSE ├── .devcontainer └── devcontainer.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .npm 2 | node_modules/ 3 | out/ 4 | .nyc_output 5 | coverage/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.fontSize": 14, 3 | "terminal.integrated.fontSize": 14 4 | } 5 | -------------------------------------------------------------------------------- /public/digits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/public/digits.png -------------------------------------------------------------------------------- /assets/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/calculator.png -------------------------------------------------------------------------------- /assets/index-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/index-html.png -------------------------------------------------------------------------------- /public/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/public/background.png -------------------------------------------------------------------------------- /public/digits-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/public/digits-grey.png -------------------------------------------------------------------------------- /assets/Cloning the repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/Cloning the repo.png -------------------------------------------------------------------------------- /assets/case-suggestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/case-suggestion.png -------------------------------------------------------------------------------- /assets/open in browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/open in browser.png -------------------------------------------------------------------------------- /public/digits-darkgrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/public/digits-darkgrey.png -------------------------------------------------------------------------------- /assets/Open in a Codespace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/Open in a Codespace.png -------------------------------------------------------------------------------- /assets/Add-operator-completed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/Add-operator-completed.png -------------------------------------------------------------------------------- /assets/Node calculator image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copilot-workshops/copilot-node-calculator/HEAD/assets/Node calculator image.png -------------------------------------------------------------------------------- /api/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | const arithmetic = require('./controller'); 3 | app.route('/arithmetic').get(arithmetic.calculate); 4 | }; 5 | -------------------------------------------------------------------------------- /test/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "spec,mocha-junit-reporter", 3 | "mochaJunitReporterReporterOptions": { 4 | "mochaFile": "./out/test-results.xml" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var supertest = require('supertest'); 2 | var chai = require('chai'); 3 | var app = require('../server'); 4 | 5 | global.app = app; 6 | global.expect = chai.expect; 7 | global.request = supertest(app); 8 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var inlinesource = require('gulp-inline-source'); 3 | 4 | gulp.task('inlinesource', function () { 5 | return gulp.src('./coverage/*.html') 6 | .pipe(inlinesource({attribute: false})) 7 | .pipe(gulp.dest('./coverage/report')); 8 | }); 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "mocha": true, 6 | "request": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "overrides": [ 10 | ], 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const app = express(); 4 | const port = process.env.PORT || 3000; 5 | 6 | app.use(express.static('public')); 7 | 8 | const routes = require("./api/routes"); 9 | routes(app); 10 | 11 | if (!module.parent) { 12 | app.listen(port, () => { 13 | console.log(`Server running on port ${port}`); 14 | }); 15 | } 16 | 17 | module.exports = app; 18 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | workflow_dispatch: 5 | # push: 6 | pull_request: 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [16.x, 18.x] 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install dependancies 20 | run: npm install 21 | - name: Run tests 22 | run: npm test 23 | - name: Archive test results to GitHub Packages 24 | uses: actions/upload-artifact@v3 25 | with: 26 | name: ${{ github.ref_name }}-${{ github.run_id }}-${{ matrix.node-version }}-test-results 27 | path: ./out/test-results.xml 28 | -------------------------------------------------------------------------------- /.instructions/4. additional resources.md: -------------------------------------------------------------------------------- 1 | # Additional resources 2 | 3 | To learn more about GitHub Copilot, here are a few resources you might be interested in reviewing. 4 | 5 | - [Setup GitHub Copilot for Business](https://www.youtube.com/watch?v=MOM0Fj5V0f0) (YouTube) 6 | - [Benefits of Copilot for Business](https://www.youtube.com/watch?v=iWutvppVwjw) (YouTube) 7 | - [GitHub Copilot tips and tricks](https://www.youtube.com/watch?v=1qs6QKk0DVc) (YouTube) 8 | 9 | **Copilot X videos** (GitHub's vision for what Copilot might evolve to in the future.) 10 | 11 | - [The Download: Everything You Need to Know About GitHub Copilot X, Generative AI Roundup and more!](https://www.youtube.com/watch?v=wNwa4GKryXI0) (YouTube) 12 | - [Getting started with Chat](https://www.youtube.com/watch?v=3surPGP7_4o) (YouTube) 13 | - [GitHub Copilot: Using Voice to Code](https://www.youtube.com/watch?v=Bk7UdqoZUDk) (YouTube) 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 GitHub Copilot Workshops 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 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node 3 | { 4 | "name": "Node.js", 5 | "image": "mcr.microsoft.com/devcontainers/javascript-node:0-18", 6 | "features": { 7 | "ghcr.io/devcontainers/features/github-cli:1": {}, 8 | "ghcr.io/devcontainers/features/docker-in-docker:2": {} 9 | }, 10 | "customizations": { 11 | "vscode": { 12 | "extensions": [ 13 | "GitHub.copilot", 14 | "GitHub.copilot-labs" 15 | ] 16 | } 17 | }, 18 | 19 | // Features to add to the dev container. More info: https://containers.dev/features. 20 | // "features": {}, 21 | 22 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 23 | "forwardPorts": [3000], 24 | 25 | // Use 'postCreateCommand' to run commands after the container is created. 26 | "postCreateCommand": "npm install" 27 | 28 | // Configure tool-specific properties. 29 | // "customizations": {}, 30 | 31 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 32 | // "remoteUser": "root" 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-calculator", 3 | "version": "1.0.0", 4 | "description": "Calculator", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/demo/node-calculator.git" 8 | }, 9 | "main": "index.js", 10 | "scripts": { 11 | "start": "nodemon server.js", 12 | "build": "exit 0", 13 | "lint": "eslint ./api/**/*.js ./server.js", 14 | "test": "nyc --reporter=cobertura --reporter=html --reporter=text mocha --timeout 10000 --reporter mocha-multi-reporters --reporter-options configFile=test/config.json && ./node_modules/.bin/gulp inlinesource" 15 | }, 16 | "keywords": [ 17 | "azure", 18 | "pipelines", 19 | "devops", 20 | "vsts", 21 | "ci", 22 | "cd" 23 | ], 24 | "author": "Demo User", 25 | "license": "MIT", 26 | "dependencies": { 27 | "express": "^4.16.4", 28 | "nodemon": "^2.0.20" 29 | }, 30 | "devDependencies": { 31 | "chai": "^4.2.0", 32 | "eslint": "^8.29.0", 33 | "gulp": "^4.0.0", 34 | "gulp-inline-source": "^4.0.0", 35 | "mocha": "^5.2.0", 36 | "mocha-junit-reporter": "^1.18.0", 37 | "mocha-multi-reporters": "^1.1.7", 38 | "nyc": "^13.3.0", 39 | "supertest": "^3.4.2" 40 | }, 41 | "bugs": { 42 | "url": "https://github.com/demo/node-calculator/issues" 43 | }, 44 | "homepage": "https://github.com/demo/node-calculator#readme", 45 | "directories": { 46 | "test": "test" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.calculate = function(req, res) { 4 | req.app.use(function(err, _req, res, next) { 5 | if (res.headersSent) { 6 | return next(err); 7 | } 8 | 9 | res.status(400); 10 | res.json({ error: err.message }); 11 | }); 12 | 13 | // TODO: Add operator 14 | var operations = { 15 | 'add': function(a, b) { return Number(a) + Number(b) }, 16 | 'subtract': function(a, b) { return a - b }, 17 | 'multiply': function(a, b) { return a * b }, 18 | 'divide': function(a, b) { return a / b }, 19 | }; 20 | 21 | if (!req.query.operation) { 22 | throw new Error("Unspecified operation"); 23 | } 24 | 25 | var operation = operations[req.query.operation]; 26 | 27 | if (!operation) { 28 | throw new Error("Invalid operation: " + req.query.operation); 29 | } 30 | 31 | if (!req.query.operand1 || 32 | !req.query.operand1.match(/^(-)?[0-9\.]+(e(-)?[0-9]+)?$/) || 33 | req.query.operand1.replace(/[-0-9e]/g, '').length > 1) { 34 | throw new Error("Invalid operand1: " + req.query.operand1); 35 | } 36 | 37 | if (!req.query.operand2 || 38 | !req.query.operand2.match(/^(-)?[0-9\.]+(e(-)?[0-9]+)?$/) || 39 | req.query.operand2.replace(/[-0-9e]/g, '').length > 1) { 40 | throw new Error("Invalid operand2: " + req.query.operand2); 41 | } 42 | 43 | res.json({ result: operation(req.query.operand1, req.query.operand2) }); 44 | }; 45 | -------------------------------------------------------------------------------- /.instructions/workshop organisers.md: -------------------------------------------------------------------------------- 1 | # Workshop Organisers 2 | 3 | You can reuse this content to deliver your own GitHub Copilot workshops for your colleagues, partners or customers. This page contains information to help you deliver this as a workshop. 4 | 5 | 6 | Duration | Activity | Notes 7 | --- | --- | --- 8 | 15 mins | Copilot presentation | Deliver a presentation on the benefits of Copilot and how it works 9 | 30 mins | Open Q&A | In delivering many workshops to date, we've found attendees always have many questions about Copilot including how it works, what languages are supported, how it can be used in their day-to-day work, etc. We recommend you allow time for this. Make sure you're familiar with the [differences](https://github.com/features/copilot#pricing) between Copilot for Business and Copilot for Individuals. You should also review and familiarise yourself with the Copilot FAQ section at the bottom of [this page](https://github.com/features/copilot). 10 | 60 mins | Workshop | 15 minute instructor walkthrough of [core exercise](<./2. core exercises.md>), followed by 45 minutes for the [challenge exercises](<./3. challenge exercises.md>). You may even have time to allow some of the participants to share how they solved the challenge exercises. 11 | 15 mins | A look at Copilot X | Finish off the workshop by demonstrating [Copilot X](https://gh.io/copilotx) - GitHub's vision for the future of Copilot. This is a great way to end the workshop and leave attendees excited about the future of Copilot. 12 | 13 | #### Post event survey 14 | You should also provide a short survey at the end of the workshop to help assess the impact of the workshop and allow you to iterate and learn for future workshop deliveries. If you wish, you could copy and edit this [sample survey](https://forms.gle/gq95Y18S4K7M9Jst8) using Google Forms. 15 | 16 | #### Need help running a workshop? 17 | 18 | GitHub has experienced solutions engineers that may be able to help you deliver a workshop. To learn more, please email info@copilot-workshops.com -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Calculator 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
0
12 |
13 | 14 | 15 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /.instructions/3. challenge exercises.md: -------------------------------------------------------------------------------- 1 | # Challenge Exercises 2 | 3 | Now you've had an opportunity to get started using GitHub Copilot, we have a number of challenges for you to attempt. Remember the goal here is not to test your programming abilities but rather, see how you can use GitHub Copilot to help you complete these tasks. Even if you're not a developer, you may be surprised how Copilot can help you be successful with these challenge. 4 | 5 | 6 |
7 | Challenge #1 - Adding Unit Tests 8 | 9 | ### Adding Unit Tests 10 | 11 | 1. Press ```CTRL + ` ``` to open the terminal window in VS Code if it is not already open. 12 | 13 | 2. Enter ```npm test``` in the terminal window and press **ENTER** to execute the existing unit tests for the Calculator application. 14 | 15 | 3. Scroll up in the terminal window to see what tests have been executed. You should see tests for Arithmetic validation, Addition, Multiplication and Division. There are no tests for the subtraction function! 16 | 17 | 4. Open the ```/test/arithmetic.test.js``` file. 18 | 19 | 5. Scroll down to the line with the comment ```TODO: Challenge #1``` (Around line 96) 20 | 21 | 6. On the line following the comment, add a new comment to provide context to GitHub Copilot on what you want assistance to do. Try adding this comment ```// add tests for subtraction``` and press ```ENTER``` to generate a suggestion. 22 | 23 | 7. Accept the suggested line if it looks right by pressing ```TAB``` then ```ENTER```. 24 | 25 | 8. Continue accepting suggestions line by line to see how many unit tests you can have Copiloit assist you in writing. 26 | 27 | 9. Once you're happy with a few unit tests, save the file and return to the terminal window. Enter ```npm test``` and press **ENTER** to execute the unit tests again. 28 | 29 | **NOTE:** The advanced features currently available in GitHub CopilotX Chat, provide far more sophisticated assistance in writing unit tests, including the ability to write complete test suites for you. At the time of creating this exercise, Copilot Chat was only available as a pre-release experiment. 30 | 31 |
32 | 33 |
34 | Challenge #2 - Adding Unit Tests for the power/exponential function 35 | 36 | ### Adding Unit Tests for the power/exponential function 37 | 38 | 1. See if you can now add additional unit tests for the power/exponential function you created in the core exercise. 39 | 40 |
41 | 42 |
43 | Challenge #3 - Adding a new function 44 | 45 | ### Adding a new function 46 | 47 | 1. See if you can now add an entirely new function to the calculator using GitHub Copilot to assist you. The previous exercises will help you locate where you want to add code. 48 | 49 | 2. Once your function is working, consider adding the necessary unit tests to confirm it's functionality. 50 | 51 |
52 | 53 | 54 | #### What's next? 55 | 56 | Once you've completed the challenges, you may like to review the [additional resources](<./4. additional resources.md>) 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Copilot Workshop 2 | 3 | ## Enhance a Node Calculator app using GitHub Copilot 4 | 5 | Node Calculator image 6 | 7 | In this fun workshop, you will learn how to use GitHub Copilot to enhance a node-based Calculator, with little to no coding experience required. 8 | 9 | The project contains a simple node.js application that exposes REST APIs to perform arithmetic on integers, and provides a test suite with mocha and chai. 10 | 11 | Estimated time to complete: `10 to 60 minutes` 12 | 13 | Participants will be guided to install the GitHub Copilot VS Code extension, and then use it to enhance a simple Node Calculator app. You will also use GitHub Copilot to write some missing unit tests for the Calculator app. 14 | 15 | 16 | ## Instructions 17 | 18 | Inside the `.instructions` folder you will find a number of markdown files that contain the instructions for this workshop. 19 | 20 | Filename | Description 21 | --- | --- 22 | [1. setup.md]() | Instructions for installing the GitHub Copilot VS Code extension and joining the GitHub Copilot trial. 23 | [2. core exercises.md]() | Instructions for the core exercise of this workshop. 24 | [3. challenge exercises.md]() | Challenge exercises for participants to complete. 25 | [4. additional resources.md]() | Additional resources for participants to explore after the workshop. 26 | 27 | 28 | ## Running a workshop? 29 | 30 | If you're planning to run a GitHub Copilot workshop, please review the [workshop guide]() for tips and tricks to help you run a successful workshop. 31 | 32 | 33 | ## Project Structure 34 | 35 | In this project you will find: 36 | 37 | * the node-based Calculator application 38 | * a `test` folder containing MOCHA unit tests for the Calculator app 39 | * a devcontainer that installs GitHub Copilot when the Codespace is created (If you want to use Codespaces) 40 | * an `.instructions` folder all the instructions for this workshop. 41 | * an `assets` folder containing images used in this workshop documentation. 42 | 43 | 44 | 45 | ## FAQ 46 | 47 | - **How do I get a GitHub Copilot license?** 48 | - You can request a trial license from your GitHub Sales representative or via Copilot for Individuals or Business licenses. 49 | - **How do I get a GitHub Codespaces license?** 50 | - Codespaces is included with GitHub Enterprise Cloud, GitHub Enterprise Server, and GitHub Free. You can check under your [billing settings page](https://github.com/settings/billing). 51 | - **I am having trouble activating GitHub Copilot after I load the plugin, what should I do?** 52 | - This could be because you launched your Codespace before you activated GitHub Copilot or accepted the invitation to the trial org. Please try to reload your Codespace and try again. 53 | 54 | ## Acknowledgements 55 | 56 | A special thanks to the following awesome Hubbers who have contributed in many different ways to our workshops. 57 | [pierluigi](https://github.com/pierluigi), [parroty](https://github.com/yuichielectric), [yuichielectric](https://github.com/yuichielectric), [dchomh](https://github.com/dchomh), [nolecram](https://github.com/nolecram), [rsymo](https://github.com/rsymo), [damovisa](https://github.com/damovisa) and anyone else I've inadvertently missed. 58 | 59 | Enjoy your workshop! 60 | [anthonyborton](https://github.com/anthonyborton) 61 | 62 | _v1.0 Released June, 2023_ -------------------------------------------------------------------------------- /public/default.css: -------------------------------------------------------------------------------- 1 | BODY { 2 | height: 100%; 3 | width: 100%; 4 | padding: 0; 5 | margin: 0; 6 | background-color: #1b4076; 7 | } 8 | 9 | @media screen and (min-device-width: 500px) and (min-device-height: 1024px) { 10 | BODY { 11 | transform: scale(1.75); 12 | transform-origin: 50% 0; 13 | height: 50%; 14 | } 15 | } 16 | 17 | @media screen and (min-device-width: 1024px) and (min-device-height: 1366px) { 18 | BODY { 19 | transform: scale(2.0); 20 | transform-origin: 50% 0; 21 | height: 50%; 22 | } 23 | } 24 | 25 | .container { 26 | min-width: 250px; 27 | min-height: 531px; 28 | width: 250px; 29 | padding: 25px; 30 | margin: 0 auto; 31 | position: relative; 32 | background-image: url(background.png); 33 | background-size: 300px 531px; 34 | background-repeat: no-repeat; 35 | background-position: center top; 36 | } 37 | 38 | #results { 39 | position: relative; 40 | top: 0; 41 | left: 0; 42 | } 43 | 44 | #buttons { 45 | position: relative; 46 | } 47 | 48 | #result { 49 | background-color: #9b9b9b; 50 | width: 226px; 51 | height: 63px; 52 | margin: 0; 53 | padding: 0 12px; 54 | position: absolute; 55 | border: 0; 56 | left: 0; 57 | top: 32px; 58 | font-family: system-ui; 59 | font-size: 25px; 60 | text-align: right; 61 | } 62 | 63 | /*BUTTON { 64 | background-color: #d5d5d5; 65 | border: 0; 66 | border-radius: 0; 67 | width: 55px; 68 | height: 42px; 69 | position: absolute; 70 | 71 | font-size: 22px; 72 | }*/ 73 | 74 | #sign, 75 | #clear { 76 | font-size: 17px; 77 | } 78 | 79 | #clearEntry { 80 | font-size: 15px; 81 | } 82 | 83 | #image-icon { 84 | left: 0px; 85 | top: 202px; 86 | position: relative; 87 | width: 55px; 88 | height: 42px; 89 | } 90 | 91 | .resultchar { 92 | display: inline-block; 93 | width: 25px; 94 | height: 45px; 95 | background: url('digits.png') 0 0 no-repeat; 96 | background-size: cover; 97 | margin-top: 10px; 98 | font: 0/0 Arial; 99 | color: rgba(255, 255, 255, 0); 100 | text-indent: -9999px; 101 | z-index: 1; 102 | } 103 | 104 | .digit0 { 105 | background-position: 0 0; 106 | } 107 | 108 | .digit1 { 109 | background-position: -25px 0; 110 | } 111 | 112 | .digit2 { 113 | background-position: -50px 0; 114 | } 115 | 116 | .digit3 { 117 | background-position: -75px 0; 118 | } 119 | 120 | .digit4 { 121 | background-position: -100px 0; 122 | } 123 | 124 | .digit5 { 125 | background-position: -125px 0; 126 | } 127 | 128 | .digit6 { 129 | background-position: -150px 0; 130 | } 131 | 132 | .digit7 { 133 | background-position: -175px 0; 134 | } 135 | 136 | .digit8 { 137 | background-position: -200px 0; 138 | } 139 | 140 | .digit9 { 141 | background-position: -225px 0; 142 | } 143 | 144 | .resultchar.decimal { 145 | width: 7px; 146 | background-position: -250px 0; 147 | } 148 | 149 | .resultchar.negative { 150 | background-position: -257px 0; 151 | width: 17px; 152 | } 153 | 154 | .resultchar.exponent { 155 | background-position: -275px 0; 156 | width: 25px; 157 | } 158 | 159 | .box { 160 | box-sizing: border-box; 161 | top: 220px; 162 | width: 250px; 163 | position: absolute; 164 | } 165 | 166 | .span-2 { 167 | grid-column: span 2; 168 | } 169 | 170 | .calculator-buttons { 171 | display: grid; 172 | grid-template-columns: repeat(4, 1fr); 173 | border: 0; 174 | } 175 | 176 | .calculator { 177 | max-width: 320px; 178 | } 179 | 180 | .btn { 181 | font-size: 1em; 182 | height: 45px; 183 | border: 1px solid black; 184 | background-color: #d5d5d5; 185 | margin: 4px; 186 | } 187 | -------------------------------------------------------------------------------- /.instructions/1. setup.md: -------------------------------------------------------------------------------- 1 | ## Environment setup 2 | 3 | To complete the exercises, you will need an environment with GitHub Copilot, and a supported IDE such as VS Code. You can use your local computer and install these tools, or you may choose to use Codespaces. 4 | 5 | 6 | **OPTION A - I want to setup my local machine for the exercises** 7 |
8 | 9 | 1. Ensuring you have access to GitHub Copilot 10 | 11 | ### Accessing GitHub Copilot 12 | 13 | If you __DO NOT__ have one of the following: 14 | - an active Copilot for Individuals trial 15 | - an active Copilot for Individuals subscription 16 | - an active Copilot for Business licence 17 | 18 | you can sign up for a trial [here](https://github.com/github-copilot/signup). 19 | 20 |
21 | 22 |
23 | 24 | 2. Installing a supported IDE on your local machine 25 | 26 | ### Installing a supported IDE on your machine 27 | 28 | If you __DO NOT__ have one of the following: 29 | - VSCode 30 | - Visual Studio 31 | - NeoVIM 32 | - JetBrains IDE 33 | 34 | on your local machine, you will need to install one of these IDEs to use GitHub Copilot and complete the exercises. 35 | 36 | If you have no preference, we suggest you install VSCode. You can download it [here](https://code.visualstudio.com/download). 37 | 38 |
39 | 40 |
41 | 42 | 3. Installing the GitHub Copilot extension 43 | 44 | ### Installing the GitHub Copilot extension 45 | 46 | GitHub Copilot is a client-side extension you install into your supported developer IDE. The extension is available for VSCode, Visual Studio, NeoVIM and JetBrains IDEs. 47 | 48 | Click the appropriate IDE link below for instructions to install the extension. As part of this you will need to log in using your GitHub account to ensure you are a licensed user of GitHub Copilot. 49 | - [VSCode](https://docs.github.com/en/copilot/getting-started-with-github-copilot?tool=vscode#installing-the-visual-studio-code-extension) 50 | - [Visual Studio](https://docs.github.com/en/copilot/getting-started-with-github-copilot?tool=visualstudio#installing-the-visual-studio-extension) 51 | - [NeoVIM](https://docs.github.com/en/copilot/getting-started-with-github-copilot?tool=neovim#installing-the-neovim-extension-on-macos) 52 | - [JetBrains IDE](https://docs.github.com/en/copilot/getting-started-with-github-copilot?tool=jetbrains#installing-the-github-copilot-extension-in-your-jetbrains-ide) 53 | 54 | You should now have the GitHub Copilot extension installed in your IDE of choice. 55 | 56 |
57 | 58 |
59 | 60 | 4. Cloning the exercise repo 61 | 62 | ### Cloning the exercise repo 63 | 64 | 1. Navigate to the [Copilot-node-calculator repo](https://github.com/copilot-workshops/copilot-node-calculator) 65 | 2. Clone this repo to your local machine using your preferred method. You can find options by clicking the Code drop down and clicking on the local tab. 66 | 67 | URL for cloning is https://github.com/copilot-workshops/copilot-node-calculator.git 68 | 69 |
70 | 71 | #### What's next? 72 | You're now ready to start the [core exercises](<./2. core exercises.md>) 73 | 74 | 75 | or 76 | 77 | **OPTION B - I want to use GitHub Codespaces** 78 | 79 | >**NOTE**: GitHub Codespaces provides a cloud hosted development environment. This is not a free service but all GitHub accounts are entitled to up to 60 hours (2-core machine) per month of free usage. You can find out more about GitHub Codespaces [here](https://github.com/features/codespaces). You can also check your remaining balance [here](https://github.com/settings/billing). Choosing this option means you won't need to install anything on your local machine. 80 | 81 |
82 | 83 | 1. Launching Codespaces for the exercises 84 | 85 | ### Launching Codespaces for the exercises 86 | 87 | 1. For our exercises, you'll get started by navigating to the appropriate repo and choosing '**Use this template**', and '**Open in a codespace**' 88 | 89 | Open in a Codespace 90 | 91 |
92 | 93 | #### What's next? 94 | 95 | You're now ready to start the [core exercises](<./2. core exercises.md>) -------------------------------------------------------------------------------- /.instructions/2. core exercises.md: -------------------------------------------------------------------------------- 1 | ## Workshop exercises 2 | 3 | ### Core exercise 4 | 5 | The following exercises will help you get started with GitHub Copilot. You must have completed the [setup instructions](<./1. setup.md>) before starting these steps. 6 | 7 | 8 | ### Step by step instructions 9 | 10 |
11 | 1. Getting the application running 12 | 13 | **Starting Point**: You should have the repo open in VSCode (or your supported IDE) 14 | 15 | 1. Press ```CTRL + ` ``` to open the terminal window in VS Code if it is not already open. 16 | 17 | 2. Enter ```npm install``` in the terminal window and press **ENTER** to install the required dependencies. TIP: Ignore any issues displayed after you run this command. 18 | 19 | Let's start by running the application to learn what it does. 20 | 21 | 3. Enter ```npm start``` in the terminal window and press **ENTER** to run the application. 22 | 23 | 4. In the pop-up window that appears in the bottom right corner of the Codespace window, click the **Open in Browser** button. This will securely map port 3000 from the Codespace environment (if you're using Codespaces) to your local browser so you can see the running calculator application. 24 | 25 | Open in Browser 26 | 27 | 5. Do some simple calculations to show that the calculator is working as expected. 28 | 29 | The Node Calculator 30 | 31 | 6. Close the browser window for now and return to the Codespace window. 32 | 33 | 7. Ensure your focus is in the terminal window and press ``` CTRL + C ``` to stop the application. 34 | 35 |
36 | 37 |
38 | 2. Adding features using GitHub Copilot 39 | 40 | **TO DO** - You've been asked to add a new feature to the calculator application. 41 | 42 | ### Adding the buttons to the calculator UI 43 | 44 | 1. Open the ```public/index.html``` file in the editor window. 45 | 46 | 2. Scroll down to where you see the `````` comment 47 | 48 | 3. Add a new line below this comment and type the following two lines. You should see GitHub Copilot start to autocomplete the second line as you type. When you see this, just press ```TAB``` to accept the completion. 49 | 50 | ``` ``` 51 | ``` ``` 52 | 53 | Your finished snippet should match the following. 54 | 55 | GitHub Copilot suggestions 56 | 57 | ### Adding the logic for the new features 58 | 59 | 5. Open the ```api/controller.js``` file in the editor window. 60 | 61 | 6. Scroll down to where you see the ```// TODO: Add operator``` comment 62 | 63 | 7. Press **ENTER** at the end of the line that defines the divide function. 64 | 65 | 8. Start typing the following line and notice that GitHub Copilot should start to offer code completion half way through the word "power" as you're typing. Press **TAB** to accept the suggestion. 66 | 67 | ```'power': function(a, b) { return Math.pow(a, b) },``` 68 | 69 | 9. Open the ```public/client.js``` file in the editor window. 70 | 71 | 10. Scroll down to where you see the ```// TODO: Add operator``` comment (Line 22) 72 | 73 | 11. Move your cursor to the end of the line 35 (to the right of ```break;``` and press **ENTER**. 74 | 75 | GitHub Copilot should display ghost text suggesting the code shown in the following screenshot. Press **TAB** to accept the suggestion. 76 | 77 | GitHub Copilot suggestions 78 | 79 | 12. Press **ENTER** at the end of the line, then accept the next two lines Copilot suggests. 80 | 81 | Your completed addition should match the following. 82 | 83 | GitHub Copilot suggestions 84 | 85 | 13. Press ```CTRL + ` ``` to open the terminal window in VS Code. 86 | 87 | 14. Enter ```npm start``` in the terminal window and press **ENTER** to run the application. 88 | 89 | 15. You should test the new button by clicking 3, then the "^" (power) button, then click 2. Click "=" and the result should be 9. 90 | 91 | 16. Close the browser window, return to the Terminal window in Codespaces and press ```CTRL+C``` to terminate the application. 92 | 93 | **Success**, you have enhanced the calculator application using GitHub Copilot! 94 | 95 |
96 | 97 | 98 | --- 99 | 100 | >Hopefully your calculator is working! Remember, GitHub Copilot is probabilistic so you may not get the exact same code suggestions as we did. If you're not happy with the suggestions, you can always press **CTRL + Z** to undo the changes and try again. 101 | 102 | 103 | #### What's next? 104 | You're now ready to start the [challenge exercises](<./3. challenge exercises.md>) to see how you can leverage the power of GitHub Copilot to solve a number of challenges yourself. 105 | -------------------------------------------------------------------------------- /public/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var value = 0; 4 | 5 | var states = { 6 | "start": 0, 7 | "operand1": 1, 8 | "operator": 2, 9 | "operand2": 3, 10 | "complete": 4 11 | }; 12 | 13 | var state = states.start; 14 | 15 | var operand1 = 0; 16 | var operand2 = 0; 17 | var operation = null; 18 | 19 | function calculate(operand1, operand2, operation) { 20 | var uri = location.origin + "/arithmetic"; 21 | 22 | // TODO: Add operator 23 | switch (operation) { 24 | case '+': 25 | uri += "?operation=add"; 26 | break; 27 | case '-': 28 | uri += "?operation=subtract"; 29 | break; 30 | case '*': 31 | uri += "?operation=multiply"; 32 | break; 33 | case '/': 34 | uri += "?operation=divide"; 35 | break; 36 | default: 37 | setError(); 38 | return; 39 | } 40 | 41 | uri += "&operand1=" + encodeURIComponent(operand1); 42 | uri += "&operand2=" + encodeURIComponent(operand2); 43 | 44 | setLoading(true); 45 | 46 | var http = new XMLHttpRequest(); 47 | http.open("GET", uri, true); 48 | http.onload = function () { 49 | setLoading(false); 50 | 51 | if (http.status == 200) { 52 | var response = JSON.parse(http.responseText); 53 | setValue(response.result); 54 | } else { 55 | setError(); 56 | } 57 | }; 58 | http.send(null); 59 | } 60 | 61 | function clearPressed() { 62 | setValue(0); 63 | 64 | operand1 = 0; 65 | operand2 = 0; 66 | operation = null; 67 | state = states.start; 68 | } 69 | 70 | function clearEntryPressed() { 71 | setValue(0); 72 | state = (state == states.operand2) ? states.operator : states.start; 73 | } 74 | 75 | function numberPressed(n) { 76 | var value = getValue(); 77 | 78 | if (state == states.start || state == states.complete) { 79 | value = n; 80 | state = (n == '0' ? states.start : states.operand1); 81 | } else if (state == states.operator) { 82 | value = n; 83 | state = (n == '0' ? states.operator : states.operand2); 84 | } else if (value.replace(/[-\.]/g, '').length < 8) { 85 | value += n; 86 | } 87 | 88 | value += ""; 89 | 90 | setValue(value); 91 | } 92 | 93 | function decimalPressed() { 94 | if (state == states.start || state == states.complete) { 95 | setValue('0.'); 96 | state = states.operand1; 97 | } else if (state == states.operator) { 98 | setValue('0.'); 99 | state = states.operand2; 100 | } else if (!getValue().toString().includes('.')) { 101 | setValue(getValue() + '.'); 102 | } 103 | } 104 | 105 | function signPressed() { 106 | var value = getValue(); 107 | 108 | if (value != 0) { 109 | setValue(-1 * value); 110 | } 111 | } 112 | 113 | function operationPressed(op) { 114 | operand1 = getValue(); 115 | operation = op; 116 | state = states.operator; 117 | } 118 | 119 | function equalPressed() { 120 | if (state < states.operand2) { 121 | state = states.complete; 122 | return; 123 | } 124 | 125 | if (state == states.operand2) { 126 | operand2 = getValue(); 127 | state = states.complete; 128 | } else if (state == states.complete) { 129 | operand1 = getValue(); 130 | } 131 | 132 | calculate(operand1, operand2, operation); 133 | } 134 | 135 | // TODO: Add key press logics 136 | document.addEventListener('keypress', (event) => { 137 | if (event.key.match(/^\d+$/)) { 138 | numberPressed(event.key); 139 | } else if (event.key == '.') { 140 | decimalPressed(); 141 | } else if (event.key.match(/^[-*+/]$/)) { 142 | operationPressed(event.key); 143 | } else if (event.key == '=') { 144 | equalPressed(); 145 | } 146 | }); 147 | 148 | function getValue() { 149 | return value; 150 | } 151 | 152 | function setValue(n) { 153 | value = n; 154 | var displayValue = value; 155 | 156 | if (displayValue > 99999999) { 157 | displayValue = displayValue.toExponential(4); 158 | } else if (displayValue < -99999999) { 159 | displayValue = displayValue.toExponential(4); 160 | } else if (displayValue > 0 && displayValue < 0.0000001) { 161 | displayValue = displayValue.toExponential(4); 162 | } else if (displayValue < 0 && displayValue > -0.0000001) { 163 | displayValue = displayValue.toExponential(3); 164 | } 165 | 166 | var chars = displayValue.toString().split(""); 167 | var html = ""; 168 | 169 | for (var c of chars) { 170 | if (c == '-') { 171 | html += "" + c + ""; 172 | } else if (c == '.') { 173 | html += "" + c + ""; 174 | } else if (c == 'e') { 175 | html += "e"; 176 | } else if (c != '+') { 177 | html += "" + c + ""; 178 | } 179 | } 180 | 181 | document.getElementById("result").innerHTML = html; 182 | } 183 | 184 | function setError(n) { 185 | document.getElementById("result").innerHTML = "ERROR"; 186 | } 187 | 188 | function setLoading(loading) { 189 | if (loading) { 190 | document.getElementById("loading").style.visibility = "visible"; 191 | } else { 192 | document.getElementById("loading").style.visibility = "hidden"; 193 | } 194 | 195 | var buttons = document.querySelectorAll("BUTTON"); 196 | 197 | for (var i = 0; i < buttons.length; i++) { 198 | buttons[i].disabled = loading; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /test/arithmetic.test.js: -------------------------------------------------------------------------------- 1 | describe('Arithmetic', function () { 2 | describe('Validation', function () { 3 | it('rejects missing operation', function (done) { 4 | request.get('/arithmetic?operand1=21&operand2=21') 5 | .expect(400) 6 | .end(function (err, res) { 7 | expect(res.body).to.eql({ error: "Unspecified operation" }); 8 | done(); 9 | }); 10 | }); 11 | it('rejects invalid operation', function (done) { 12 | request.get('/arithmetic?operation=foobar&operand1=21&operand2=21') 13 | .expect(400) 14 | .end(function (err, res) { 15 | expect(res.body).to.eql({ error: "Invalid operation: foobar" }); 16 | done(); 17 | }); 18 | }); 19 | it('rejects missing operand1', function (done) { 20 | request.get('/arithmetic?operation=add&operand2=21') 21 | .expect(400) 22 | .end(function (err, res) { 23 | expect(res.body).to.eql({ error: "Invalid operand1: undefined" }); 24 | done(); 25 | }); 26 | }); 27 | it('rejects operands with invalid sign', function (done) { 28 | request.get('/arithmetic?operation=add&operand1=4.2-1&operand2=4') 29 | .expect(400) 30 | .end(function (err, res) { 31 | expect(res.body).to.eql({ error: "Invalid operand1: 4.2-1" }); 32 | done(); 33 | }); 34 | }); 35 | it('rejects operands with invalid decimals', function (done) { 36 | request.get('/arithmetic?operation=add&operand1=4.2.1&operand2=4') 37 | .expect(400) 38 | .end(function (err, res) { 39 | expect(res.body).to.eql({ error: "Invalid operand1: 4.2.1" }); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | describe('Addition', function () { 46 | it('adds two positive integers', function (done) { 47 | request.get('/arithmetic?operation=add&operand1=21&operand2=21') 48 | .expect(200) 49 | .end(function (err, res) { 50 | expect(res.body).to.eql({ result: 42 }); 51 | done(); 52 | }); 53 | }); 54 | it('adds zero to an integer', function (done) { 55 | request.get('/arithmetic?operation=add&operand1=42&operand2=0') 56 | .expect(200) 57 | .end(function (err, res) { 58 | expect(res.body).to.eql({ result: 42 }); 59 | done(); 60 | }); 61 | }); 62 | it('adds a negative integer to a positive integer', function (done) { 63 | request.get('/arithmetic?operation=add&operand1=21&operand2=-42') 64 | .expect(200) 65 | .end(function (err, res) { 66 | expect(res.body).to.eql({ result: -21 }); 67 | done(); 68 | }); 69 | }); 70 | it('adds two negative integers', function (done) { 71 | request.get('/arithmetic?operation=add&operand1=-21&operand2=-21') 72 | .expect(200) 73 | .end(function (err, res) { 74 | expect(res.body).to.eql({ result: -42 }); 75 | done(); 76 | }); 77 | }); 78 | it('adds an integer to a floating point number', function (done) { 79 | request.get('/arithmetic?operation=add&operand1=2.5&operand2=-5') 80 | .expect(200) 81 | .end(function (err, res) { 82 | expect(res.body).to.eql({ result: -2.5 }); 83 | done(); 84 | }); 85 | }); 86 | it('adds with negative exponent', function (done) { 87 | request.get('/arithmetic?operation=add&operand1=1.2e-5&operand2=-1.2e-5') 88 | .expect(200) 89 | .end(function (err, res) { 90 | expect(res.body).to.eql({ result: 0 }); 91 | done(); 92 | }); 93 | }); 94 | }); 95 | 96 | // TODO: Challenge #1 97 | 98 | 99 | describe('Multiplication', function () { 100 | it('multiplies two positive integers', function (done) { 101 | request.get('/arithmetic?operation=multiply&operand1=21&operand2=2') 102 | .expect(200) 103 | .end(function (err, res) { 104 | expect(res.body).to.eql({ result: 42 }); 105 | done(); 106 | }); 107 | }); 108 | it('multiplies a positive integer with zero', function (done) { 109 | request.get('/arithmetic?operation=multiply&operand1=21&operand2=0') 110 | .expect(200) 111 | .end(function (err, res) { 112 | expect(res.body).to.eql({ result: 0 }); 113 | done(); 114 | }); 115 | }); 116 | it('multiplies a positive integer and negative integer', function (done) { 117 | request.get('/arithmetic?operation=multiply&operand1=21&operand2=-2') 118 | .expect(200) 119 | .end(function (err, res) { 120 | expect(res.body).to.eql({ result: -42 }); 121 | done(); 122 | }); 123 | }); 124 | it('multiplies two negative integers', function (done) { 125 | request.get('/arithmetic?operation=multiply&operand1=-21&operand2=-2') 126 | .expect(200) 127 | .end(function (err, res) { 128 | expect(res.body).to.eql({ result: 42 }); 129 | done(); 130 | }); 131 | }); 132 | it('multiplies two floating point numbers', function (done) { 133 | request.get('/arithmetic?operation=multiply&operand1=.5&operand2=0.5') 134 | .expect(200) 135 | .end(function (err, res) { 136 | expect(res.body).to.eql({ result: 0.25 }); 137 | done(); 138 | }); 139 | }); 140 | it('multiplies supporting exponential notation', function (done) { 141 | request.get('/arithmetic?operation=multiply&operand1=4.2e1&operand2=1e0') 142 | .expect(200) 143 | .end(function (err, res) { 144 | expect(res.body).to.eql({ result: 42 }); 145 | done(); 146 | }); 147 | }); 148 | }); 149 | 150 | describe('Division', function () { 151 | it('divides a positive integer by an integer factor ', function (done) { 152 | request.get('/arithmetic?operation=divide&operand1=42&operand2=2') 153 | .expect(200) 154 | .end(function (err, res) { 155 | expect(res.body).to.eql({ result: 21 }); 156 | done(); 157 | }); 158 | }); 159 | it('divides a negative integer by an integer factor ', function (done) { 160 | request.get('/arithmetic?operation=divide&operand1=-42&operand2=2') 161 | .expect(200) 162 | .end(function (err, res) { 163 | expect(res.body).to.eql({ result: -21 }); 164 | done(); 165 | }); 166 | }); 167 | it('divides a positive integer by a non-factor', function (done) { 168 | request.get('/arithmetic?operation=divide&operand1=21&operand2=42') 169 | .expect(200) 170 | .end(function (err, res) { 171 | expect(res.body).to.eql({ result: 0.5 }); 172 | done(); 173 | }); 174 | }); 175 | it('divides a positive integer by a negative integer', function (done) { 176 | request.get('/arithmetic?operation=divide&operand1=21&operand2=-42') 177 | .expect(200) 178 | .end(function (err, res) { 179 | expect(res.body).to.eql({ result: -0.5 }); 180 | done(); 181 | }); 182 | }); 183 | it('divides zero by a positive integer', function (done) { 184 | request.get('/arithmetic?operation=divide&operand1=0&operand2=42') 185 | .expect(200) 186 | .end(function (err, res) { 187 | expect(res.body).to.eql({ result: 0 }); 188 | done(); 189 | }); 190 | }); 191 | it('divides by zero', function (done) { 192 | request.get('/arithmetic?operation=divide&operand1=0.5&operand2=2') 193 | .expect(200) 194 | .end(function (err, res) { 195 | expect(res.body).to.eql({ result: 0.25 }); 196 | done(); 197 | }); 198 | }); 199 | it('divides by zero', function (done) { 200 | request.get('/arithmetic?operation=divide&operand1=21&operand2=0') 201 | .expect(200) 202 | .end(function (err, res) { 203 | expect(res.body).to.eql({ result: null }); 204 | done(); 205 | }); 206 | }); 207 | }); 208 | }); 209 | --------------------------------------------------------------------------------