Last active
October 8, 2020 00:16
-
-
Save carlosefonseca/eafee9e40d50dd158cf8e40a62ba25e4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Follow this to install the travis cli: https://github.com/travis-ci/travis.rb#installation | |
// Then generate the API token by running the following commands and place the token in the var | |
// travis login --pro | |
// travis token --pro | |
let travis_token = "insert token" | |
// set to your GitHub username to appear in bold | |
let github_user = "" | |
// The slug of the repo to display | |
let repo = "owner/repo" | |
////////////////////////////// | |
let data1 = await loadItems() | |
let data = transformData(data1) | |
let widget = await createWidget(data) | |
if (!config.runsInWidget) { | |
await widget.presentSmall() | |
} | |
Script.setWidget(widget) | |
Script.complete() | |
async function createWidget(builds) { | |
// I want to find a better way to do this… | |
// smallBuilds returns an array and we want to flatten it into the stack | |
let layout = [space, smallBuilds(builds), [[space, updated]]].flatMap(x => x) | |
let w = new ListWidget() | |
buildLayout(layout, w) | |
return w | |
} | |
// Place in a layout array to force the orientation. | |
function H(stack) { stack.layoutHorizontally() } | |
function V(stack) { stack.layoutVertically() } | |
// Creates an image for the specified state | |
function stateIcon(state) { | |
return function _stateIcon(stack) { | |
addStateIconToStack(stack, state) | |
} | |
} | |
// Creates a small image for the specified state | |
function stateIconSmall(state) { | |
return function _stateIcon(stack) { | |
addStateIconToStack(stack, state, 10) | |
} | |
} | |
// Flexible space. Use without parentheses. | |
function space(column) { column.addSpacer() } | |
// Fixed size space. | |
function spaced(size) { | |
return function _spaced(stack) { stack.addSpacer(size) } | |
} | |
function text(text, font = Font.systemFont(10)) { | |
return function _text(stack) { | |
let txt = stack.addText(text) | |
txt.font = font | |
} | |
} | |
function boldText(txt) { | |
return text(txt, Font.boldSystemFont(10)) | |
} | |
function tinyText(owner) { | |
return text(owner, Font.systemFont(8)) | |
} | |
function user(name) { | |
return text(name, name === github_user ? Font.boldSystemFont(8) : Font.systemFont(8)) | |
} | |
// Creates the jobs per stage grid. Returns an array of stages, | |
// each stage is an array that contains spaces and icons for each job. | |
function smallStages(stages) { | |
return stages.map(stage => | |
Object.values(stage.jobs).flatMap(j => [spaced(1), stateIconSmall(j)])) | |
} | |
// Returns the full line for a build. | |
function smallBuild(build) { | |
const prAndBuildStack = [ | |
boldText(build.short), | |
tinyText(build.number) | |
] | |
const leftPart = [ | |
[stateIcon(build.state), space, prAndBuildStack, space], | |
user(build.owner) | |
] | |
return [leftPart, smallStages(build.stages)] | |
} | |
// Returns an array of build lines. | |
function smallBuilds(builds) { | |
return builds.flatMap(b => [smallBuild(b), space]) | |
} | |
// Adds a date for the timestamp the widget was updated | |
function updated(stack) { | |
let date = stack.addDate(new Date()) | |
date.applyTimerStyle() | |
date.font = Font.systemFont(8) | |
} | |
// Function that takes a widget/stack object and transforms a layout structure into widget elements. | |
// Each nested array in the layout will create a stack with the inverse orientation from the container. | |
// Each function in the layout will be called with the parent stack and should modify the stack by adding elements or modifying stack properties. | |
function buildLayout(layout, origin, isVertical = true) { | |
return layout.map(row => { | |
if (Array.isArray(row)) { | |
let stack = origin.addStack() | |
if (isVertical) { | |
stack.layoutHorizontally() | |
} else { | |
stack.layoutVertically() | |
} | |
return buildLayout(row, stack, !isVertical) | |
} else { | |
row(origin) | |
} | |
}) | |
} | |
// Adds a state icon to a stack | |
function addStateIconToStack(stack, state, size = 20) { | |
let img = stack.addImage(convertStateToImg(state)) | |
img.tintColor = new Color(convertStateToColor(state)) | |
img.resizable = true | |
img.imageSize = new Size(size, size) | |
} | |
// Returns an image for the specified state | |
function convertStateToImg(txt) { | |
return SFSymbol.named(convertStateToSFSymbolName(txt)).image | |
} | |
// Converts a state into an SFSymbol name | |
function convertStateToSFSymbolName(txt) { | |
switch (txt) { | |
case "canceled": return "stop.circle" | |
case "started": return "play.circle.fill" | |
case "failed": return "multiply.circle.fill" | |
case "passed": return "checkmark.circle.fill" | |
case "received": | |
case "queued": | |
case "created": return "ellipsis.circle" | |
} | |
return txt | |
} | |
// Converts a state into a HEX color code | |
function convertStateToColor(txt) { | |
switch (txt) { | |
case "canceled": return "#9d9d9d" | |
case "failed": return "#db4545" | |
case "passed": return "#39aa56" | |
case "started": | |
case "received": | |
case "queued": | |
case "created": return "#cdb62c" | |
} | |
return txt | |
} | |
// Converts a duration into hours/minutes text | |
function durationToText(seconds) { | |
let h = Math.floor(seconds / 60 / 60) | |
let m = Math.floor(seconds / 60 % 60) | |
h = h > 0 ? `${h}h` : "" | |
return `${h}${m}m` | |
} | |
// When used as `[builds].filter(onlyUnique)`, it discards any builds that are similar | |
function onlyUnique(value, index, self) { | |
return self.findIndex(i => i.pull_request_number === value.pull_request_number) === index; | |
} | |
// Downloads builds from Travis | |
async function loadItems() { | |
let url = `https://api.travis-ci.com/repo/${encodeURIComponent(repo)}/builds?include=build.jobs,build.stages&limit=5` | |
let req = new Request(url) | |
req.headers = { "Authorization": `token ${travis_token}`, "Travis-API-Version": "3" } | |
let json = await req.loadJSON() | |
return json | |
} | |
// Simplifies the Travis build data to be consumed by the createWidget method | |
function transformData(data) { | |
return data.builds.filter(onlyUnique).slice(0, 3).map(build => { | |
let stages = {} | |
build.stages.forEach(s => { | |
stages[s.number] = { | |
name: s.name, | |
state: s.state, | |
jobs: {} | |
} | |
}) | |
build.jobs.forEach(j => { | |
stages[j.stage.number].jobs[j.number] = j.state | |
}) | |
stages = Object.values(stages); | |
let duration = null | |
if (build.duration) { | |
duration = build.duration | |
} else if (build.started_at) { | |
duration = (new Date() - new Date(build.started_at)) / 1000 | |
} | |
duration_txt = duration ? durationToText(duration) : "" | |
title = null | |
if (build.event_type == "pull_request") { | |
title = `#${build.pull_request_number} ${build.pull_request_title}` | |
} else { | |
title = build.commit.message.split("\n")[0] | |
} | |
let buildUser = build.created_by.login | |
out = { | |
title: title, | |
short: title.split(" ")[0], | |
number: build.number, | |
state: build.state, | |
updated_at: build.updated_at, | |
stages: stages, | |
url: `https://travis-ci.com/github/${build.repository.slug}/builds/${build.id}`, | |
duration: duration_txt, | |
started_at: build.started_at, | |
owner: buildUser, | |
mine: buildUser === github_user | |
} | |
return out | |
}) | |
} | |
// For testing purposes. Loads a file simulating the Travis API response | |
async function loadTestData() { | |
const files = FileManager.iCloud() | |
const locationPath = files.joinPath(files.documentsDirectory(), "travis-test-data.json") | |
await files.downloadFileFromiCloud(locationPath) | |
if (files.fileExists(locationPath)) { | |
let data = files.readString(locationPath) | |
return JSON.parse(data) | |
} | |
return null | |
} |
Time since the build started should not display if the build hasn't stated yet.
Changed the state icons again…
Handles push
builds.
Updated to use the new Scriptable version with Stacks and SFSymbols
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added ⏳ for received and queued states. Added time since the build started. Tapping the widget opens the travis build page.