Last active
February 28, 2020 17:55
-
-
Save ajvargo/b33c3003d6cf8dab0e900705f79bba34 to your computer and use it in GitHub Desktop.
First solid pass at getting code pipeline work flow updates in emacs.
This file contains 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
;;; codepipeline-watch-mode.el --- Watch the progress of AWS Codepipeline Builds -*- lexical-binding: t; -*- | |
;;; Commentary: | |
;; Shells out to `aws` command line to get data for display. | |
;; My theme is dark. No idea how these colors look in a light theme. | |
;; M-x codepipeline-watch-get-status | |
;; `g` will refresh - it'll update on its own now and then. | |
;;; Code: | |
(require 'json) | |
(defgroup codepipeline-watch-mode nil | |
"Utility for seeing AWS Codepipeline progress" | |
:prefix "codepipeline-watch-" | |
:group 'convenience) | |
(defface codepipeline-watch-inprogress-face | |
'((t :inherit warn | |
:foreground "yellow")) | |
"Face for InProgress indicator" | |
:group 'codepipeline-watch-mode) | |
(defface codepipeline-watch-succeeded-face | |
'((t :inherit success)) | |
"Face for Succeeded indicator" | |
:group 'codepipeline-watch-mode) | |
(defface codepipeline-watch-superseded-face | |
'((t :inherit default | |
:foreground "lightblue")) | |
"Face for Superseded indicator" | |
:group 'codepipeline-watch-mode) | |
(defface codepipeline-watch-failed-face | |
'((t :inherit error)) | |
"Face for Failed indicator" | |
:group 'codepipeline-watch-mode) | |
(defface codepipeline-watch-waiting-face | |
'((t :inherit default | |
:foreground "lightblue")) | |
"Face for waiting indicator" | |
:group 'codepipeline-watch-mode) | |
(defface codepipeline-watch-header-face | |
'((t :inherit bold | |
:foreground "blue" | |
:height 1.3)) | |
"Face for the header line" | |
:group 'codepipeline-watch-mode) | |
(defface codepipeline-watch-stage-face | |
'((t :inherit bold | |
:foreground "DarkViolet" | |
:height 1.1)) | |
"Face for the stage line" | |
:group 'codepipeline-watch-mode) | |
(defface codepipeline-watch-action-face | |
'((t :inherit default | |
:foreground "DarkOrange3")) | |
"Face for the action line" | |
:group 'codepipeline-watch-mode) | |
(defcustom codepipeline-watch-indent-depth 2 | |
"Tab stop in spaces for formatting." | |
:type 'integer | |
:group 'codepipeline-watch-mode) | |
(defcustom codepipeline-watch-pipeline-name-prefix "tf2-production-" | |
"Prefix that will be appended to any given name when fetching pipeline status." | |
:type 'string | |
:group 'codepipeline-watch-mode) | |
(defcustom codepipeline-watch-staleness-threshold 300 | |
"Length in seconds before allowing and automatic refresh." | |
:type 'integer | |
:group 'codepipeline-watch-mode) | |
(defcustom codepipeline-watch-pipeline-names | |
'("airflow" "bastion" "caravan" | |
"chirp" "dewey" "hedwig" | |
"iris" "lello" "mycroft" | |
"nile" "pince" "plural" | |
"pulp" "taft" "templater" | |
"valve") | |
"Set of pipeline names that will be offered for selection." | |
:type 'list | |
:group 'codepipeline-watch-mode) | |
(defcustom codepipeline-watch-header-time-format "%F %R" | |
"Format for the last updated time displayed in the header." | |
:type 'string | |
:group 'codepipeline-watch-mode) | |
(defcustom codepipeline-watch-time-ago-format "%dd %hh %mm ago%z" | |
"Format used to show time since action was updated. | |
See `format-seconds'." | |
:type 'string | |
:group 'codepipeline-watch-mode) | |
(defvar codepipeline-watch-status-face-mappings | |
'(("InProgress" . codepipeline-watch-inprogress-face) | |
("Succeeded" . codepipeline-watch-succeeded-face) | |
("Superseded" . codepipeline-watch-superseded-face) | |
("Failed" . codepipeline-watch-failed-face) | |
("Waiting" . codepipeline-watch-waiting-face)) | |
"An alist of pipeline statuses to the faced used to display them.") | |
(defvar-local codepipeline-watch-pipeline-name nil "The AWS name for a given buffers pipeline.") | |
(defvar-local codepipeline-watch-last-updated-at nil "The time a given buffer was updated.") | |
(defun codepipeline-watch-status-face (status) | |
"Lookup face for `STATUS'." | |
(alist-get status codepipeline-watch-status-face-mappings nil nil #'string-equal)) | |
(defun codepipeline-watch-draw-header () | |
"Insert pipeline name and last fetched as a header." | |
(insert (propertize codepipeline-watch-pipeline-name 'face 'codepipeline-watch-header-face) | |
"\nLast fetched: " | |
(format-time-string codepipeline-watch-header-time-format codepipeline-watch-last-updated-at) | |
"\n\n")) | |
(defun codepipeline-watch-stages () | |
"Assemble stage states from AWS network request." | |
(let ((cmd-out (shell-command-to-string (concat "aws codepipeline get-pipeline-state --output json --name " codepipeline-watch-pipeline-name)))) | |
(alist-get 'stageStates | |
(condition-case err | |
(json-read-from-string cmd-out) | |
(error (message "%s" (error-message-string err)) | |
(message cmd-out) | |
'()))))) | |
(defun codepipeline-watch-actions (stage) | |
"Return action states of a given STAGE." | |
(alist-get 'actionStates stage)) | |
(defun codepipeline-watch-draw-action (action) | |
"Insert ACTION name, status and last execution. | |
Last execution is in time-ago format." | |
(let ((status (alist-get 'status (alist-get 'latestExecution action '((status . "waiting")))))) | |
(insert (make-string (* 2 codepipeline-watch-indent-depth) ?\s) | |
(propertize (alist-get 'actionName action) 'face 'codepipeline-watch-action-face) | |
" " | |
(propertize status 'face (codepipeline-watch-status-face status)) | |
" " | |
(format-seconds codepipeline-watch-time-ago-format (float-time (time-since (alist-get 'lastStatusChange (alist-get 'latestExecution action))))) | |
"\n"))) | |
(defun codepipeline-watch-draw-stage (stage) | |
"Insert STAGE with is staus and last execution followed by any actions." | |
(let ((stage-name (alist-get 'stageName stage)) | |
(status (alist-get 'status (alist-get 'latestExecution stage)))) | |
(insert (make-string codepipeline-watch-indent-depth ?\s) | |
(propertize stage-name 'face 'codepipeline-watch-stage-face) | |
" " | |
(propertize status 'face (codepipeline-watch-status-face status)) | |
"\n") | |
(mapc #'codepipeline-watch-draw-action (codepipeline-watch-actions stage)) | |
(insert "\n"))) | |
(defun codepipeline-watch-draw-summary (source-stage) | |
"Insert a summary of SOURCE-STAGE. | |
This includes a link to the commit and the last commit message." | |
(let ((action-state (aref (alist-get 'actionStates source-stage) 0))) | |
(insert-button "Commit" 'action (lambda (x) (browse-url (button-get x 'url))) 'url | |
(alist-get 'revisionUrl action-state) | |
'face '(:weight bold :underline "blue" :foreground "blue" :height 1.1)) | |
(insert | |
"\n" | |
(replace-regexp-in-string " | |
" "" | |
(alist-get 'summary (alist-get 'latestExecution action-state))) | |
"\n\n"))) | |
(defun codepipeline-watch-draw-status-buffer () | |
"Draw entire status buffer for the project." | |
(setq codepipeline-watch-last-updated-at (current-time)) | |
(let ((inhibit-read-only t)) | |
(erase-buffer) | |
(codepipeline-watch-draw-header) | |
(let ((watch-stages (codepipeline-watch-stages))) | |
(codepipeline-watch-draw-summary (aref watch-stages 0)) | |
(mapc #'codepipeline-watch-draw-stage watch-stages)) | |
(goto-char (point-min)))) | |
(defun codepipeline-watch-buffer-stale-p (&optional noconfirm) | |
(if noconfirm | |
(when codepipeline-watch-staleness-threshold | |
(> (float-time (time-subtract (current-time) codepipeline-watch-last-updated-at)) codepipeline-watch-staleness-threshold)) | |
(message (concat (buffer-name) " is stale. You might want to refresh.")))) | |
(defun codepipeline-watch-revert (&optional _ignore_auto _noconfirm) | |
(codepipeline-watch-draw-status-buffer)) | |
(defun codepipeline-watch-completion-table (completions) | |
(lambda (string pred action) | |
(if (eq action 'metadata) | |
`(metadata (display-sort-function . ,#'identity)) | |
(complete-with-action action completions string pred)))) | |
(defun codepipeline-watch-get-status(pipeline) | |
(interactive (list | |
(let* ((ivy-sort-functions-alist nil)) | |
(completing-read "Project: " (codepipeline-watch-completion-table codepipeline-watch-pipeline-names))))) | |
(setq codepipeline-watch-pipeline-names (cons pipeline (remove pipeline codepipeline-watch-pipeline-names))) | |
(let* ((buf (get-buffer-create (concat "*codepipeline-watch-" pipeline "*")))) | |
(with-current-buffer buf | |
(codepipeline-watch-mode) | |
(setq-local codepipeline-watch-pipeline-name (concat codepipeline-watch-pipeline-name-prefix pipeline)) | |
(codepipeline-watch-draw-status-buffer)) | |
(switch-to-buffer-other-window buf))) | |
(define-derived-mode codepipeline-watch-mode special-mode "Codepipeline-Watch" | |
"Mode for viewing the state of a codepipeline execution in AWS." | |
:group 'codepipeline-watch-mode | |
(setq-local revert-buffer-function #'codepipeline-watch-revert) | |
(setq-local buffer-stale-function #'codepipeline-watch-buffer-stale-p)) | |
(provide 'codepipeline-watch-mode) | |
;;; codepipeline-watch-mode.el ends here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment