Skip to content

Commit

Permalink
Merge pull request agiledev-students-fall2023#62 from lesleyzhao/lesl…
Browse files Browse the repository at this point in the history
…ie-forked-branch

Leslie forked branch
  • Loading branch information
Nina1o1 authored Oct 30, 2023
2 parents 8fbe4e3 + e62000d commit 7a3f390
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 3 deletions.
4 changes: 4 additions & 0 deletions automations/.clasp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"scriptId": "1d-At2A2QTQAobdXicLsAai-_P7OrYovTa4qYOWbtV-3CjJaE8XztRgQ6",
"rootDir": "/Users/foobarstein/Documents/courant/courses/agile-dev-devops/generic-mern-stack-project/automations"
}
85 changes: 85 additions & 0 deletions automations/Charts.js
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"
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 = `<img src='cid:chart_${i}' />`
imageTags.push(tag)
})
imageTags = imageTags.join('<br />') // convert to string with line break separator
const message = `<h1>Charts</h1>${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 = `<img src='data:image/png;base64,${chartData}' />`
imageTags.push(tag)
})
imageTags = imageTags.join('<br />') // convert to string with line break separator
const message = `<h1>${sheetName}</h1>${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
}
56 changes: 56 additions & 0 deletions automations/Code.js
Original file line number Diff line number Diff line change
@@ -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<commit_data.length; i++) {
// log this commit!
const commit = commit_data[i]
console.log(JSON.stringify(commit, null, 2))
// append data array to sheet as new row
const row = [commit['repository_url'], commit['event_type'], commit['id'], commit['author_name'], commit['author_email'], commit['date'], commit['message'], commit['files'], commit['additions'], commit['deletions']]
sheet.appendRow(row);
}
return ContentService.createTextOutput(commit_data).setMimeType(ContentService.MimeType.JSON)
}
else {
return ContentService.createTextOutput(typeof(commit_data)).setMimeType(ContentService.MimeType.TEXT)
}
}
14 changes: 14 additions & 0 deletions automations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Automation used to track developer 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](./Code.js) 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. Charts of the developers' contributions over a given time period are [made automatically available](./Charts.js) in the web app via HTTP GET requests.
1. These contribution scores are also automatically pulled into a separate gradebook spreadsheet.
10 changes: 10 additions & 0 deletions automations/appsscript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"timeZone": "America/New_York",
"dependencies": {},
"webapp": {
"executeAs": "USER_DEPLOYING",
"access": "ANYONE_ANONYMOUS"
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
10 changes: 7 additions & 3 deletions instructions-0c-project-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 7a3f390

Please sign in to comment.