From 2ff8512ea86f41ffae169059ad0b7fb0d243587e Mon Sep 17 00:00:00 2001 From: bloombar Date: Mon, 9 Oct 2023 06:52:11 -0400 Subject: [PATCH 1/4] automation explained --- automations/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 automations/README.md diff --git a/automations/README.md b/automations/README.md new file mode 100644 index 0000000..c46b99d --- /dev/null +++ b/automations/README.md @@ -0,0 +1,13 @@ +# Automation used to track contributions + +The developer activity in this repository is tracked using a combination of custom tools. Any action on this repository triggers a sequence of automated steps: + +1. A GitHub Actions [workflow](../.github/workflows/event-logger.yml) is set up to detect actions on this repository, such as push, pull request, etc. +1. This workflow executes a Python package named [gitcommitlogger](https://pypi.org/project/gitcommitlogger/) that parses the `git` logs and determines what has changed and who made the changes. +1. The Python package then posts this data to a "container-bound" [Google Apps Script](https://developers.google.com/apps-script) attached to a Google Sheet and set up as a web app to receive such incoming HTTP POST requests. The web app URL where the data is posted is stored in a [GitHub secret](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions) named, `COMMIT_LOG_API` within this repository. +1. The Google Apps Script inserts the data posted by the Python package into a new row in the bound spreadsheet. +1. The Google Sheet contains formulae to add up the number of pull requests opened, pull requests closed, commits, files changed, lines of code added. lines of code removed, etc, by each developer, over a given time period, based on these logs. +1. The sums of these metrics are capped for each developer at reasonable, relatively low levels. +1. Based on all developers' activity, an average score for each of these metrics is computed, and each developer's own score is compared to the average of all developers. +1. A contribution score is calculated for each developer: those who perform average or better on all metrics are given a perfect score; those who perform less than average for any metric have their score scaled down in proportion to how much lower than average they performed. +1. These contribution scores are then pulled into a gradebook spreadsheet. From 883b3df155ba03817f3397ff1add10a1f233ba9f Mon Sep 17 00:00:00 2001 From: bloombar Date: Mon, 9 Oct 2023 07:01:54 -0400 Subject: [PATCH 2/4] adding clasp files --- automations/.clasp.json | 1 + automations/Charts.js | 85 +++++++++++++++++++++++++++++++++++++ automations/Code.js | 56 ++++++++++++++++++++++++ automations/README.md | 7 +-- automations/appsscript.json | 10 +++++ 5 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 automations/.clasp.json create mode 100644 automations/Charts.js create mode 100644 automations/Code.js create mode 100644 automations/appsscript.json diff --git a/automations/.clasp.json b/automations/.clasp.json new file mode 100644 index 0000000..9b85bca --- /dev/null +++ b/automations/.clasp.json @@ -0,0 +1 @@ +{"scriptId":"1d-At2A2QTQAobdXicLsAai-_P7OrYovTa4qYOWbtV-3CjJaE8XztRgQ6","rootDir":"/Users/foobarstein/Documents/courant/courses/agile-dev-devops/generic-mern-stack-project/automations"} diff --git a/automations/Charts.js b/automations/Charts.js new file mode 100644 index 0000000..ea37665 --- /dev/null +++ b/automations/Charts.js @@ -0,0 +1,85 @@ +function emailSprint1ChartsToMe() { + emailChartsToMe('Sprint 1 - Team Plots') +} + +function emailSprint2ChartsToMe() { + emailChartsToMe('Sprint 2 - Team Plots') +} + +function emailSprint3ChartsToMe() { + emailChartsToMe('Sprint 3 - Team Plots') +} + +function emailSprint4ChartsToMe() { + emailChartsToMe('Sprint 4 - Team Plots') +} + +function emailChartsToMe(sheetName) { + const charts = getCharts(sheetName) + const to = Session.getActiveUser().getEmail(); + const replyTo = "no-reply@knowledge.kitchen" + const subject = "Agile Software Development & DevOps - Team Contribution Charts" + const message = generateEmail(charts) + const options = { + replyTo: replyTo + } + + // prepare inline image object + const inlineImages = {} + charts.forEach( (chart, i) => { + inlineImages[`chart_${i}`] = chart + }) + + // GmailApp.sendEmail(to, subject, message, options); + // Send message with inlineImages object, matching embedded tags. + MailApp.sendEmail(to, subject, "", { + htmlBody: message, + inlineImages: inlineImages + }); + + Logger.log(message) +} + +function generateEmail(charts) { + let imageTags = [] + charts.forEach( (chart, i) => { + const tag = `` + imageTags.push(tag) + }) + imageTags = imageTags.join('
') // convert to string with line break separator + const message = `

Charts

${imageTags}` + return message +} + +function generateHtml(sheetName, charts) { + // data:image/gif;base64, + let imageTags = [] + charts.forEach( (chart, i) => { + const chartData = Utilities.base64Encode(chart.getBytes()) // get base64 encoded data for this chart + const tag = `` + imageTags.push(tag) + }) + imageTags = imageTags.join('
') // convert to string with line break separator + const message = `

${sheetName}

${imageTags}` + return message +} + +function getCharts(sheetName) { + // get all charts + // const sheet = SpreadsheetApp.getActiveSheet(); + const ss = SpreadsheetApp.getActiveSpreadsheet() // container spreadsheet + let sheet = ss.getSheetByName(sheetName) // specific worksheet + const range = sheet.getRange("A:Z") + const charts = sheet.getCharts() + + // loop through charts + const images = [] + charts.forEach( (chart, i) => { + Logger.log(`chart id: ${chart.getChartId()}`) + // Logger.log(JSON.stringify(chart.getOptions(), null, 2)) + const image = chart.getAs('image/png') + image.setName(`chart_${i}`) + images.push(image) // add to array + }) + return images +} diff --git a/automations/Code.js b/automations/Code.js new file mode 100644 index 0000000..8572962 --- /dev/null +++ b/automations/Code.js @@ -0,0 +1,56 @@ +const getConfig = () => { + // global settings + return { + defaultChartSheetName: 'Sprint 1 - Team Plots', + logsSheetName: 'GitHub Logs', + logsSheetFields: ['repository', 'event', 'id', 'username', 'email', 'date', 'message', 'num_files', 'num_additions', 'num_deletions'] + } +} + +const getSheet = () => { + const config = getConfig() + const ss = SpreadsheetApp.getActiveSpreadsheet() // container spreadsheet + let sheet = ss.getSheetByName(config.logsSheetName) // specific worksheet + if (sheet == null) { + // create worksheet if none + sheet = ss.insertSheet(config.logsSheetName) + sheet.appendRow(config.logsSheetFields) // heading row + } + return sheet +} + +function doGet(e) { + // get the sheet name with the charts from the query string + const config = getConfig() + // we expect a `sheet` query string in the request + const sheetName = (e.parameter["sheet"]) ? decodeURIComponent(e.parameter["sheet"]) : config.defaultChartSheetName + Logger.log(`Loading charts from sheet: ${sheetName}`) + charts = getCharts(sheetName) + const content = generateHtml(sheetName, charts) + return HtmlService.createHtmlOutput(content) +} + +function doPost(e) { + console.log("Incoming post request") + console.log(JSON.stringify(e, null, 2)) + const sheet = getSheet() + const res = { + type: 'post', + e: e + } + const commit_data = JSON.parse(e.postData.contents); // should be an array of objects + if (Array.isArray(commit_data)) { + for (let i=0; i Date: Mon, 9 Oct 2023 08:32:35 -0400 Subject: [PATCH 3/4] formatting --- automations/.clasp.json | 5 ++++- automations/appsscript.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/automations/.clasp.json b/automations/.clasp.json index 9b85bca..aa11a3a 100644 --- a/automations/.clasp.json +++ b/automations/.clasp.json @@ -1 +1,4 @@ -{"scriptId":"1d-At2A2QTQAobdXicLsAai-_P7OrYovTa4qYOWbtV-3CjJaE8XztRgQ6","rootDir":"/Users/foobarstein/Documents/courant/courses/agile-dev-devops/generic-mern-stack-project/automations"} +{ + "scriptId": "1d-At2A2QTQAobdXicLsAai-_P7OrYovTa4qYOWbtV-3CjJaE8XztRgQ6", + "rootDir": "/Users/foobarstein/Documents/courant/courses/agile-dev-devops/generic-mern-stack-project/automations" +} diff --git a/automations/appsscript.json b/automations/appsscript.json index 9a12795..170d3c0 100644 --- a/automations/appsscript.json +++ b/automations/appsscript.json @@ -7,4 +7,4 @@ }, "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8" -} \ No newline at end of file +} From c7320edbf2289f334bfb14b88636c6698e4379ac Mon Sep 17 00:00:00 2001 From: bloombar Date: Sun, 29 Oct 2023 04:30:38 -0400 Subject: [PATCH 4/4] feature branch workflow --- instructions-0c-project-setup.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/instructions-0c-project-setup.md b/instructions-0c-project-setup.md index 6081bb1..a12118a 100644 --- a/instructions-0c-project-setup.md +++ b/instructions-0c-project-setup.md @@ -55,9 +55,7 @@ Here is the full recipe: ## Configure GitHub repository -GitHub is each team's primary resource for version control, project -planning, issue tracking, and project documentation. Each member's -contributions within GitHub are tracked and used towards grading. +GitHub is each team's primary resource for version control, project planning, issue tracking, and project documentation. Each member's contributions within GitHub are tracked and used towards grading. Each team member must understand that their contributions are only visible if they commit work using their own git/GitHub account. @@ -133,6 +131,12 @@ Each team must create a Task Board for each of the four Sprints following [these View a [video overview of setting up a Task Board on GitHub](https://youtu.be/Qasz5fhxIqE). +### Git/GitHub workflow + +When in development, teams are expected to follow the [feature branch workflow](https://knowledge.kitchen/content/courses/agile-development-and-devops/slides/feature-branch-workflow/), where all changes are made in branches, peer reviews are done via pull requests, and approved changes are merged into the `main` branch, which is always in a deployable state. + +Team members must make these feature branches in the shared team repository, not in a fork or other copy of the repository. + ## Prepare to git to work Each team member must `clone` their team's GitHub repository onto their own local machine and `push` any changes they make back to GitHub at regular inteverals - at least once between Standup meetings.