feat(web/panettone): Add issue statuses

Add support for issue statuses, which is currently a trivial groupoid of
open and closed. On the show page for open issues there's a Close
button, and on the show page for closed issues there's a Reopen button.
In addition, the index page is filtered by open issues only and there's
a link to view closed issues.

Change-Id: I6c0c3d2e874b1c801e9e06c804f5c1b12db5dbdc
Reviewed-on: https://cl.tvl.fyi/c/depot/+/1352
Tested-by: BuildkiteCI
Reviewed-by: glittershark <grfn@gws.fyi>
This commit is contained in:
Griffin Smith 2020-07-22 23:11:10 -04:00 committed by glittershark
parent a107d8e335
commit bd3c19320a
2 changed files with 138 additions and 49 deletions

View file

@ -19,6 +19,9 @@
(defparameter color/success-2 (defparameter color/success-2
"rgb(168, 249, 166)") "rgb(168, 249, 166)")
(defparameter color/failure
"rgb(247, 167, 167)")
(defun button (selector) (defun button (selector)
`((,selector `((,selector
:background-color ,color/success :background-color ,color/success
@ -32,8 +35,7 @@
((:and ,selector (:or :active :focus)) ((:and ,selector (:or :active :focus))
:box-shadow "0.1rem" "0.1rem" "0" "0" "rgba(0,0,0,0.05)" :box-shadow "0.1rem" "0.1rem" "0" "0" "rgba(0,0,0,0.05)"
:outline "none" :outline "none"
:border "none" :border "none")))
:background-color ,color/success-2)))
(defparameter issue-list-styles (defparameter issue-list-styles
`((.issue-list `((.issue-list
@ -103,9 +105,19 @@
,@(button '(:and input (:= type "submit"))))) ,@(button '(:and input (:= type "submit")))))
(defparameter issue-styles
`((.issue-info
:display "flex"
:justify-content "space-between"
:align-items "center"
(.close-issue
:background-color ,color/failure))))
(defparameter styles (defparameter styles
`(,@form-styles `(,@form-styles
,@issue-list-styles ,@issue-list-styles
,@issue-styles
,@comment-styles ,@comment-styles
(body (body

View file

@ -5,6 +5,9 @@
;;; Data model ;;; Data model
;;; ;;;
(deftype issue-status ()
'(member :open :closed))
(defclass/std issue-comment () (defclass/std issue-comment ()
((body :type string) ((body :type string)
(author-dn :type string) (author-dn :type string)
@ -15,6 +18,7 @@
((subject body :type string :std "") ((subject body :type string :std "")
(author-dn :type string) (author-dn :type string)
(comments :std nil :type list :with-prefix) (comments :std nil :type list :with-prefix)
(status :std :open :type issue-status)
(created-at :type local-time:timestamp (created-at :type local-time:timestamp
:std (local-time:now)))) :std (local-time:now))))
@ -123,6 +127,13 @@ successful, `nil' otherwise"
(defun list-issues (system) (defun list-issues (system)
(cl-prevalence:find-all-objects system 'issue)) (cl-prevalence:find-all-objects system 'issue))
(defun issues-with-status (system status)
(remove-if-not (lambda (issue) (eq (status issue) status))
(list-issues system)))
(defun open-issues (system) (issues-with-status system :open))
(defun closed-issues (system) (issues-with-status system :closed))
(defun create-issue (system &rest attrs) (defun create-issue (system &rest attrs)
(cl-prevalence:tx-create-object (cl-prevalence:tx-create-object
system system
@ -214,6 +225,30 @@ updated issue"
(who:esc (who:esc
(format-dottime (created-at issue))))))) (format-dottime (created-at issue)))))))
(defun render/issue-list (&key issues)
(who:with-html-output (*standard-output*)
(:ol
:class "issue-list"
(dolist (issue issues)
(let ((issue-id (get-id issue)))
(who:htm
(:li
(:a :href (format nil "/issues/~A" issue-id)
(:p
(:span :class "issue-subject"
(who:esc (subject issue))))
(:span :class "issue-number"
(who:esc (format nil "#~A" issue-id)))
" - "
(created-by-at issue)
(let ((num-comments (length (issue-comments issue))))
(unless (zerop num-comments)
(who:htm
(:span :class "comment-count"
" - "
(who:esc
(format nil "~A comment~:p" num-comments))))))))))))))
(defun render/index (&key issues) (defun render/index (&key issues)
(render (render
(:header (:header
@ -222,27 +257,20 @@ updated issue"
:class "new-issue" :class "new-issue"
:href "/issues/new" "New Issue")) :href "/issues/new" "New Issue"))
(:main (:main
(:ol (:div
:class "issue-list" :class "issue-links"
(dolist (issue issues) (:a :href "/issues/closed" "View closed issues"))
(let ((issue-id (get-id issue))) (render/issue-list :issues issues))))
(who:htm
(:li (defun render/closed-issues (&key issues)
(:a :href (format nil "/issues/~A" issue-id) (render
(:p (:header
(:span :class "issue-subject" (:h1 "Closed issues"))
(who:esc (subject issue)))) (:main
(:span :class "issue-number" (:div
(who:esc (format nil "#~A" issue-id))) :class "issue-links"
" - " (:a :href "/" "View open isues"))
(created-by-at issue) (render/issue-list :issues issues))))
(let ((num-comments (length (issue-comments issue))))
(unless (zerop num-comments)
(who:htm
(:span :class "comment-count"
" - "
(who:esc
(format nil "~A comment~:p" num-comments)))))))))))))))
(defun render/new-issue () (defun render/new-issue ()
(render (render
@ -281,31 +309,50 @@ updated issue"
(defun render/issue (issue) (defun render/issue (issue)
(check-type issue issue) (check-type issue issue)
(render (let ((issue-id (get-id issue))
(:header (issue-status (status issue)))
(:h1 (who:esc (subject issue))) (render
(:div :class "issue-number" (:header
(who:esc (format nil "#~A" (get-id issue))))) (:h1 (who:esc (subject issue)))
(:main (:div :class "issue-number"
(:p (created-by-at issue)) (who:esc (format nil "#~A" issue-id))))
(:p (who:esc (body issue))) (:main
(let ((comments (issue-comments issue))) (:div
(who:htm :class "issue-info"
(:div (created-by-at issue)
:class "issue-comments"
(dolist (comment comments) (:form :class "set-issue-status"
(let ((author (author comment))) :method "post"
(who:htm :action (format nil "/issues/~A/~A"
(:div issue-id
:class "comment" (case issue-status
(:p (who:esc (body comment))) (:open "close")
(:p (:closed "open")))
:class "comment-info" (:input :type "submit"
(:span :class "username" :class (case issue-status
(who:esc (displayname author)) (:open "close-issue")
" at " (:closed "open-issue"))
(who:esc (format-dottime (created-at comment))))))))) :value (case issue-status
(render/new-comment (get-id issue)))))))) (:open "Close")
(:closed "Reopen")))))
(:p (who:esc (body issue)))
(let ((comments (issue-comments issue)))
(who:htm
(:div
:class "issue-comments"
(dolist (comment comments)
(let ((author (author comment)))
(who:htm
(:div
:class "comment"
(:p (who:esc (body comment)))
(:p
:class "comment-info"
(:span :class "username"
(who:esc (displayname author))
" at "
(who:esc (format-dottime (created-at comment)))))))))
(render/new-comment (get-id issue)))))))))
(defun render/not-found (entity-type) (defun render/not-found (entity-type)
(render (render
@ -336,9 +383,13 @@ updated issue"
(render/login "Invalid credentials"))) (render/login "Invalid credentials")))
(defroute index ("/" :decorators (@auth)) () (defroute index ("/" :decorators (@auth)) ()
(let ((issues (list-issues *p-system*))) (let ((issues (open-issues *p-system*)))
(render/index :issues issues))) (render/index :issues issues)))
(defroute handle-closed-issues ("/issues/closed" :decorators (@auth)) ()
(let ((issues (closed-issues *p-system*)))
(render/closed-issues :issues issues)))
(defroute new-issue ("/issues/new" :decorators (@auth)) () (defroute new-issue ("/issues/new" :decorators (@auth)) ()
(render/new-issue)) (render/new-issue))
@ -375,6 +426,32 @@ updated issue"
(issue-not-found (_) (issue-not-found (_)
(render/not-found "Issue")))) (render/not-found "Issue"))))
(defroute close-issue
("/issues/:id/close" :decorators (@auth)
:method :post)
(&path (id 'integer))
(cl-prevalence:execute-transaction
(cl-prevalence:tx-change-object-slots
*p-system*
'issue
id
'((status :closed))))
(cl-prevalence:snapshot *p-system*)
(hunchentoot:redirect (format nil "/issues/~A" id)))
(defroute open-issue
("/issues/:id/open" :decorators (@auth)
:method :put)
(&path (id 'integer))
(cl-prevalence:execute-transaction
(cl-prevalence:tx-change-object-slots
*p-system*
'issue
id
'((status open))))
(cl-prevalence:snapshot *p-system*)
(hunchentoot:redirect (format nil "/issues/~A" id)))
(defroute styles ("/main.css") () (defroute styles ("/main.css") ()
(setf (hunchentoot:content-type*) "text/css") (setf (hunchentoot:content-type*) "text/css")
(apply #'lass:compile-and-write panettone.css:styles)) (apply #'lass:compile-and-write panettone.css:styles))