Per-workflow custom ticket fields

This commit is contained in:
fr33domlover 2016-08-08 14:01:06 +00:00
parent 2b364e006a
commit 01385c480b
14 changed files with 248 additions and 6 deletions

View file

@ -162,6 +162,16 @@ Workflow
UniqueWorkflow sharer ident UniqueWorkflow sharer ident
WorkflowField
workflow WorkflowId
ident FldIdent
name Text
desc Text Maybe
type WorkflowFieldType
-- filter TicketStatusFilterId
UniqueWorkflowField workflow ident
Ticket Ticket
project ProjectId project ProjectId
number Int number Int

View file

@ -99,6 +99,9 @@
/s/#ShrIdent/w WorkflowsR GET POST /s/#ShrIdent/w WorkflowsR GET POST
/s/#ShrIdent/w/!new WorkflowNewR GET /s/#ShrIdent/w/!new WorkflowNewR GET
/s/#ShrIdent/w/#WflIdent WorkflowR GET DELETE POST /s/#ShrIdent/w/#WflIdent WorkflowR GET DELETE POST
/s/#ShrIdent/w/#WflIdent/f WorkflowFieldsR GET POST
/s/#ShrIdent/w/#WflIdent/f/!new WorkflowFieldNewR GET
/s/#ShrIdent/w/#WflIdent/f/#FldIdent WorkflowFieldR GET DELETE POST
/s/#ShrIdent/p/#PrjIdent/t TicketsR GET POST /s/#ShrIdent/p/#PrjIdent/t TicketsR GET POST
/s/#ShrIdent/p/#PrjIdent/t/!tree TicketTreeR GET /s/#ShrIdent/p/#PrjIdent/t/!tree TicketTreeR GET

View file

@ -15,6 +15,7 @@
module Vervis.Field.Workflow module Vervis.Field.Workflow
( newWorkflowIdentField ( newWorkflowIdentField
, newFieldIdentField
) )
where where
@ -25,7 +26,7 @@ import Data.Char.Local (isAsciiLetter)
import Data.Text (split) import Data.Text (split)
import Database.Esqueleto import Database.Esqueleto
import Vervis.Model.Ident (WflIdent, wfl2text, text2wfl) import Vervis.Model.Ident
checkTemplate :: Field Handler Text -> Field Handler Text checkTemplate :: Field Handler Text -> Field Handler Text
checkTemplate = checkTemplate =
@ -33,14 +34,14 @@ checkTemplate =
wordOk w = (not . null) w && all charOk w wordOk w = (not . null) w && all charOk w
identOk t = (not . null) t && all wordOk (split (== '-') t) identOk t = (not . null) t && all wordOk (split (== '-') t)
msg :: Text msg :: Text
msg = msg = "The identifier must be a sequence of one or more words \
"The workflow identifier must be a sequence of one or more words \
\separated by hyphens (-), and each such word may contain \ \separated by hyphens (-), and each such word may contain \
\ASCII letters and digits." \ASCII letters and digits."
in checkBool identOk msg in checkBool identOk msg
checkUniqueCI :: SharerId -> Field Handler WflIdent -> Field Handler WflIdent checkWflUniqueCI
checkUniqueCI sid = checkM $ \ wfl -> do :: SharerId -> Field Handler WflIdent -> Field Handler WflIdent
checkWflUniqueCI sid = checkM $ \ wfl -> do
sames <- runDB $ select $ from $ \ workflow -> do sames <- runDB $ select $ from $ \ workflow -> do
where_ $ where_ $
workflow ^. WorkflowSharer ==. val sid &&. workflow ^. WorkflowSharer ==. val sid &&.
@ -55,4 +56,23 @@ workflowIdentField :: Field Handler WflIdent
workflowIdentField = convertField text2wfl wfl2text $ checkTemplate textField workflowIdentField = convertField text2wfl wfl2text $ checkTemplate textField
newWorkflowIdentField :: SharerId -> Field Handler WflIdent newWorkflowIdentField :: SharerId -> Field Handler WflIdent
newWorkflowIdentField sid = checkUniqueCI sid workflowIdentField newWorkflowIdentField sid = checkWflUniqueCI sid workflowIdentField
checkFldUniqueCI
:: WorkflowId -> Field Handler FldIdent -> Field Handler FldIdent
checkFldUniqueCI wid = checkM $ \ fld -> do
sames <- runDB $ select $ from $ \ field -> do
where_ $
field ^. WorkflowFieldWorkflow ==. val wid &&.
lower_ (field ^. WorkflowFieldIdent) ==. lower_ (val fld)
limit 1
return ()
return $ if null sames
then Right fld
else Left ("There is already a field by that name" :: Text)
fieldIdentField :: Field Handler FldIdent
fieldIdentField = convertField text2fld fld2text $ checkTemplate textField
newFieldIdentField :: WorkflowId -> Field Handler FldIdent
newFieldIdentField wid = checkFldUniqueCI wid fieldIdentField

View file

@ -16,6 +16,8 @@
module Vervis.Form.Workflow module Vervis.Form.Workflow
( NewWorkflow (..) ( NewWorkflow (..)
, newWorkflowForm , newWorkflowForm
, NewField (..)
, newFieldForm
) )
where where
@ -28,6 +30,7 @@ import qualified Database.Esqueleto as E ((==.))
import Vervis.Field.Workflow import Vervis.Field.Workflow
import Vervis.Model import Vervis.Model
import Vervis.Model.Ident import Vervis.Model.Ident
import Vervis.Model.Workflow
data NewWorkflow = NewWorkflow data NewWorkflow = NewWorkflow
{ nwIdent :: WflIdent { nwIdent :: WflIdent
@ -43,3 +46,20 @@ newWorkflowAForm sid = NewWorkflow
newWorkflowForm :: SharerId -> Form NewWorkflow newWorkflowForm :: SharerId -> Form NewWorkflow
newWorkflowForm sid = renderDivs $ newWorkflowAForm sid newWorkflowForm sid = renderDivs $ newWorkflowAForm sid
data NewField = NewField
{ nfIdent :: FldIdent
, nfName :: Text
, nfDesc :: Maybe Text
, nfType :: WorkflowFieldType
}
newFieldAForm :: WorkflowId -> AForm Handler NewField
newFieldAForm wid = NewField
<$> areq (newFieldIdentField wid) "Identifier*" Nothing
<*> areq textField "Name*" Nothing
<*> aopt textField "Description" Nothing
<*> areq (selectField optionsEnum) "Type*" Nothing
newFieldForm :: WorkflowId -> Form NewField
newFieldForm wid = renderDivs $ newFieldAForm wid

View file

@ -168,6 +168,9 @@ instance Yesod App where
(WorkflowsR shr , _ ) -> personOrGroupAdmin shr (WorkflowsR shr , _ ) -> personOrGroupAdmin shr
(WorkflowNewR shr , _ ) -> personOrGroupAdmin shr (WorkflowNewR shr , _ ) -> personOrGroupAdmin shr
(WorkflowR shr _wfl , _ ) -> personOrGroupAdmin shr (WorkflowR shr _wfl , _ ) -> personOrGroupAdmin shr
(WorkflowFieldsR shr _ , _ ) -> personOrGroupAdmin shr
(WorkflowFieldNewR shr _ , _ ) -> personOrGroupAdmin shr
(WorkflowFieldR shr _ _ , _ ) -> personOrGroupAdmin shr
(TicketsR shar _ , True) -> person shar (TicketsR shar _ , True) -> person shar
(TicketNewR _ _ , _ ) -> personAny (TicketNewR _ _ , _ ) -> personAny
@ -468,6 +471,15 @@ instance YesodBreadcrumbs App where
WorkflowR shr wfl -> ( wfl2text wfl WorkflowR shr wfl -> ( wfl2text wfl
, Just $ WorkflowsR shr , Just $ WorkflowsR shr
) )
WorkflowFieldsR shr wfl -> ( "Fields"
, Just $ WorkflowR shr wfl
)
WorkflowFieldNewR shr wfl -> ( "New"
, Just $ WorkflowFieldsR shr wfl
)
WorkflowFieldR shr wfl fld -> ( fld2text fld
, Just $ WorkflowFieldsR shr wfl
)
TicketsR shar proj -> ( "Tickets" TicketsR shar proj -> ( "Tickets"
, Just $ ProjectR shar proj , Just $ ProjectR shar proj

View file

@ -20,6 +20,12 @@ module Vervis.Handler.Workflow
, getWorkflowR , getWorkflowR
, deleteWorkflowR , deleteWorkflowR
, postWorkflowR , postWorkflowR
, getWorkflowFieldsR
, postWorkflowFieldsR
, getWorkflowFieldNewR
, getWorkflowFieldR
, deleteWorkflowFieldR
, postWorkflowFieldR
) )
where where
@ -95,3 +101,66 @@ postWorkflowR shr wfl = do
case mmethod of case mmethod of
Just "DELETE" -> deleteWorkflowR shr wfl Just "DELETE" -> deleteWorkflowR shr wfl
_ -> notFound _ -> notFound
getWorkflowFieldsR :: ShrIdent -> WflIdent -> Handler Html
getWorkflowFieldsR shr wfl = do
fs <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity wid _ <- getBy404 $ UniqueWorkflow sid wfl
selectList [WorkflowFieldWorkflow ==. wid] []
defaultLayout $(widgetFile "workflow/field/list")
postWorkflowFieldsR :: ShrIdent -> WflIdent -> Handler Html
postWorkflowFieldsR shr wfl = do
wid <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity wid _ <- getBy404 $ UniqueWorkflow sid wfl
return wid
((result, widget), enctype) <- runFormPost $ newFieldForm wid
case result of
FormSuccess nf -> do
let field = WorkflowField
{ workflowFieldWorkflow = wid
, workflowFieldIdent = nfIdent nf
, workflowFieldName = nfName nf
, workflowFieldDesc = nfDesc nf
, workflowFieldType = nfType nf
}
runDB $ insert_ field
setMessage "Workflow field added."
redirect $ WorkflowFieldR shr wfl (nfIdent nf)
FormMissing -> do
setMessage "Field(s) missing"
defaultLayout $(widgetFile "workflow/field/new")
FormFailure _l -> do
setMessage "Workflow field creation failed, see below"
defaultLayout $(widgetFile "workflow/field/new")
getWorkflowFieldNewR :: ShrIdent -> WflIdent -> Handler Html
getWorkflowFieldNewR shr wfl = do
wid <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity wid _ <- getBy404 $ UniqueWorkflow sid wfl
return wid
((_result, widget), enctype) <- runFormPost $ newFieldForm wid
defaultLayout $(widgetFile "workflow/field/new")
getWorkflowFieldR :: ShrIdent -> WflIdent -> FldIdent -> Handler Html
getWorkflowFieldR shr wfl fld = do
f <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity wid _ <- getBy404 $ UniqueWorkflow sid wfl
Entity _ f <- getBy404 $ UniqueWorkflowField wid fld
return f
defaultLayout $(widgetFile "workflow/field/one")
deleteWorkflowFieldR :: ShrIdent -> WflIdent -> FldIdent -> Handler Html
deleteWorkflowFieldR shr wfl fld =
error "Not implemented, not sure whether to allow it"
postWorkflowFieldR :: ShrIdent -> WflIdent -> FldIdent -> Handler Html
postWorkflowFieldR shr wfl fld = do
mmethod <- lookupPostParam "_method"
case mmethod of
Just "DELETE" -> deleteWorkflowFieldR shr wfl fld
_ -> notFound

View file

@ -29,6 +29,7 @@ import Vervis.Model.Group
import Vervis.Model.Ident import Vervis.Model.Ident
import Vervis.Model.Repo import Vervis.Model.Repo
import Vervis.Model.Role import Vervis.Model.Role
import Vervis.Model.Workflow
-- You can define all of your database entities in the entities file. -- You can define all of your database entities in the entities file.
-- You can find more information on persistent and how to declare entities at: -- You can find more information on persistent and how to declare entities at:

View file

@ -34,6 +34,9 @@ module Vervis.Model.Ident
, WflIdent (..) , WflIdent (..)
, wfl2text , wfl2text
, text2wfl , text2wfl
, FldIdent (..)
, fld2text
, text2fld
) )
where where
@ -112,3 +115,13 @@ wfl2text = CI.original . unWflIdent
text2wfl :: Text -> WflIdent text2wfl :: Text -> WflIdent
text2wfl = WflIdent . CI.mk text2wfl = WflIdent . CI.mk
newtype FldIdent = FldIdent { unFldIdent :: CI Text }
deriving
(Eq, Show, Read, PersistField, PersistFieldSql, SqlString, PathPiece)
fld2text :: FldIdent -> Text
fld2text = CI.original . unFldIdent
text2fld :: Text -> FldIdent
text2fld = FldIdent . CI.mk

View file

@ -0,0 +1,28 @@
{- This file is part of Vervis.
-
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Model.Workflow
( WorkflowFieldType (..)
)
where
import Prelude
import Database.Persist.TH
data WorkflowFieldType = WFTText
deriving (Eq, Show, Read, Bounded, Enum)
derivePersistField "WorkflowFieldType"

View file

@ -0,0 +1,19 @@
$# This file is part of Vervis.
$#
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
$# The author(s) have dedicated all copyright and related and neighboring
$# rights to this software to the public domain worldwide. This software is
$# distributed without any warranty.
$#
$# You should have received a copy of the CC0 Public Domain Dedication along
$# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
<ul>
$forall Entity _fid f <- fs
<li>
<a href=@{WorkflowFieldR shr wfl $ workflowFieldIdent f}>
#{workflowFieldName f}

View file

@ -0,0 +1,17 @@
$# This file is part of Vervis.
$#
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
$# The author(s) have dedicated all copyright and related and neighboring
$# rights to this software to the public domain worldwide. This software is
$# distributed without any warranty.
$#
$# You should have received a copy of the CC0 Public Domain Dedication along
$# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
<form method="POST" action=@{WorkflowFieldsR shr wfl} enctype=#{enctype}>
^{widget}
<input type="submit">

View file

@ -0,0 +1,26 @@
$# This file is part of Vervis.
$#
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
$# The author(s) have dedicated all copyright and related and neighboring
$# rights to this software to the public domain worldwide. This software is
$# distributed without any warranty.
$#
$# You should have received a copy of the CC0 Public Domain Dedication along
$# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
<p>
<form method="POST" action=@{WorkflowFieldR shr wfl fld}>
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="Delete this field">
<ul>
<li>
Display name: #{workflowFieldName f}
<li>
Description: #{fromMaybe "(none)" $ workflowFieldDesc f}
<li>
Type: #{show $ workflowFieldType f}

View file

@ -22,3 +22,6 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
Human-friendly name: #{fromMaybe "(none)" $ workflowName w} Human-friendly name: #{fromMaybe "(none)" $ workflowName w}
<li> <li>
Description: #{fromMaybe "(none)" $ workflowDesc w} Description: #{fromMaybe "(none)" $ workflowDesc w}
<li>
<a href=@{WorkflowFieldsR shr $ workflowIdent w}>
Fields

View file

@ -147,6 +147,7 @@ library
Vervis.Model.Ident Vervis.Model.Ident
Vervis.Model.Repo Vervis.Model.Repo
Vervis.Model.Role Vervis.Model.Role
Vervis.Model.Workflow
Vervis.Paginate Vervis.Paginate
Vervis.Palette Vervis.Palette
Vervis.Path Vervis.Path