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:
parent
a107d8e335
commit
bd3c19320a
2 changed files with 138 additions and 49 deletions
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in a new issue