From 132d40e6a38fd54e3fb42edadf34229450acf1a0 Mon Sep 17 00:00:00 2001 From: Oliver Giles Date: Mon, 10 Sep 2018 14:51:43 +0300 Subject: [PATCH] resolves #50: badge url Implements serving of an SVG badge at the url /badge/JOB.svg which prettily shows the job's current status --- UserManual.md | 12 ++++++++++++ src/interface.h | 5 +++++ src/laminar.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ src/laminar.h | 1 + src/server.cpp | 6 ++++++ test/test-server.cpp | 1 + 6 files changed, 67 insertions(+) diff --git a/UserManual.md b/UserManual.md index 7831223..0011b25 100644 --- a/UserManual.md +++ b/UserManual.md @@ -542,6 +542,18 @@ This directory is also a good place to add any extra assets needed for this cust --- +# Badges + +Laminar will serve a job's current status as a pretty badge at the url `/badge/JOBNAME.svg`. This can be used as a link to your server instance from your Github README.md file or cat blog: + +``` + + + +``` + +--- + # Reference ## Service configuration file diff --git a/src/interface.h b/src/interface.h index fcca41a..a8c596f 100644 --- a/src/interface.h +++ b/src/interface.h @@ -135,6 +135,11 @@ struct LaminarInterface { // proper web server which handles this url. virtual kj::Own getArtefact(std::string path) = 0; + // Given the name of a job, populate the provided string reference with + // SVG content describing the last known state of the job. Returns false + // if the job is unknown. + virtual bool handleBadgeRequest(std::string job, std::string& badge) = 0; + // Fetches the content of $LAMINAR_HOME/custom/style.css or an empty // string. Ideally, this would instead be served by a proper web server // which handles this url. diff --git a/src/laminar.cpp b/src/laminar.cpp index 2a42f24..7e29f0b 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -924,6 +924,48 @@ kj::Own Laminar::getArtefact(std::string path) { return kj::heap(fs::path(fs::path(homeDir)/"archive"/path).c_str()); } +bool Laminar::handleBadgeRequest(std::string job, std::string &badge) { + RunState rs = RunState::UNKNOWN; + db->stmt("SELECT result FROM builds WHERE name = ? ORDER BY number DESC LIMIT 1") + .bind(job) + .fetch([&](int result){ + rs = (RunState) result; + }); + if(rs == RunState::UNKNOWN) + return false; + + std::string status = to_string(rs); + // Empirical approximation of pixel width. Not particularly stable. + const int jobNameWidth = job.size() * 7 + 10; + const int statusWidth = status.size() * 7 + 10; + const char* gradient1 = (rs == RunState::SUCCESS) ? "#2aff4d" : "#ff2a2a"; + const char* gradient2 = (rs == RunState::SUCCESS) ? "#24b43c" : "#b42424"; + char* svg = NULL; + asprintf(&svg, +R"x( + + + + + + + + + + + + + + + %s + + %s + +)x", jobNameWidth+statusWidth, gradient1, gradient2, jobNameWidth, jobNameWidth/2+1, job.data(), jobNameWidth, statusWidth, jobNameWidth+statusWidth/2, status.data()); + badge = svg; + return true; +} + std::string Laminar::getCustomCss() { MappedFileImpl cssFile(fs::path(fs::path(homeDir)/"custom"/"style.css").c_str()); if(cssFile.address()) { diff --git a/src/laminar.h b/src/laminar.h index 8c898b2..07042bf 100644 --- a/src/laminar.h +++ b/src/laminar.h @@ -56,6 +56,7 @@ public: void sendStatus(LaminarClient* client) override; bool setParam(std::string job, uint buildNum, std::string param, std::string value) override; kj::Own getArtefact(std::string path) override; + bool handleBadgeRequest(std::string job, std::string& badge) override; std::string getCustomCss() override; void abortAll() override; void notifyConfigChanged() override; diff --git a/src/server.cpp b/src/server.cpp index 4713193..207c17d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -311,6 +311,7 @@ private: } else { // handle regular HTTP request const char* start, *end, *content_type; + std::string badge; responseHeaders.clear(); if(resource.compare(0, strlen("/archive/"), "/archive/") == 0) { kj::Own file = laminar.getArtefact(resource.substr(strlen("/archive/"))); @@ -331,6 +332,11 @@ private: responseHeaders.add("Content-Transfer-Encoding", "binary"); auto stream = response.send(200, "OK", responseHeaders, end-start); return stream->write(start, end-start).attach(kj::mv(stream)); + } else if(url.startsWith("/badge/") && url.endsWith(".svg") && laminar.handleBadgeRequest(resource.substr(7,resource.length()-11), badge)) { + responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, "image/svg+xml"); + responseHeaders.add("Cache-Control", "no-cache"); + auto stream = response.send(200, "OK", responseHeaders, badge.size()); + return stream->write(badge.data(), badge.size()).attach(kj::mv(badge)).attach(kj::mv(stream)); } return response.sendError(404, "Not Found", responseHeaders); } diff --git a/test/test-server.cpp b/test/test-server.cpp index e708885..fc2a019 100644 --- a/test/test-server.cpp +++ b/test/test-server.cpp @@ -65,6 +65,7 @@ public: MOCK_METHOD1(sendStatus, void(LaminarClient* client)); MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value)); MOCK_METHOD0(getCustomCss, std::string()); + MOCK_METHOD2(handleBadgeRequest, bool(std::string, std::string&)); MOCK_METHOD0(abortAll, void()); MOCK_METHOD0(notifyConfigChanged, void()); };