module Handler.Utils.Workflow.Form ( FileIdent , WorkflowGraphForm(..) , workflowGraphForm , toWorkflowGraphForm, fromWorkflowGraphForm , WorkflowDescriptionsFormScope(..) , workflowDescriptionsForm ) where import Import import Utils.Form import Handler.Utils.Form import qualified Data.Conduit.Combinators as C import qualified Data.Map as Map import Data.Map ((!)) import qualified Data.Set as Set import qualified Data.CaseInsensitive as CI import Data.Bimap (Bimap) import qualified Data.Bimap as Bimap import qualified Control.Monad.State.Class as State import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Aeson as JSON import Utils.Workflow.Lint newtype FileIdent = FileIdent (CI Text) deriving (Eq, Ord, Read, Show, Generic, Typeable) deriving newtype (ToMessage, ToJSON, FromJSON) makeWrapped ''FileIdent newtype instance FileReferenceTitleMap FileIdent add = FileIdentFileReferenceTitleMap { unFileIdentFileReferenceTitleMap :: Map FilePath (FileIdentFileReferenceTitleMapElem add) } deriving (Eq, Ord, Read, Show, Generic, Typeable) deriving newtype (Semigroup, Monoid) data FileIdentFileReferenceTitleMapElem add = FileIdentFileReferenceTitleMapElem { fIdentTitleMapIdent :: FileIdent , fIdentTitleMapAdditional :: add } deriving (Eq, Ord, Read, Show, Generic, Typeable) makePrisms ''FileIdentFileReferenceTitleMapElem instance FileReferenceTitleMapConvertible add FileIdent FileIdent where _FileReferenceTitleMap = iso unFileIdentFileReferenceTitleMap FileIdentFileReferenceTitleMap . traverse . _FileIdentFileReferenceTitleMapElem instance FileReferenceTitleMapConvertible add FileIdent FileReference where _FileReferenceTitleMap = iso unFileIdentFileReferenceTitleMap FileReferenceFileReferenceTitleMap . iso Map.toList Map.fromList . traverse . iso (view $ _2 . _FileIdentFileReferenceTitleMapElem) (\(FileReference{..}, additional) -> (fileReferenceTitle, FileReferenceFileReferenceTitleMapElem fileReferenceContent fileReferenceModified additional)) instance FileReferenceTitleMapConvertible add FileReference FileIdent where _FileReferenceTitleMap = iso unFileReferenceFileReferenceTitleMap FileIdentFileReferenceTitleMap . itraverse . (\f fileReferenceTitle FileReferenceFileReferenceTitleMapElem{ fRefTitleMapContent = fileReferenceContent, fRefTitleMapModified = fileReferenceModified, fRefTitleMapAdditional } -> review _FileIdentFileReferenceTitleMapElem <$> f (FileReference{..}, fRefTitleMapAdditional)) instance ToJSON (FileField FileIdent) where toJSON FileField{..} = JSON.object $ catMaybes [ ("ident" JSON..=) <$> fieldIdent , pure $ "unpack-zips" JSON..= fieldUnpackZips , pure $ "multiple" JSON..= fieldMultiple , pure $ "restrict-extensions" JSON..= fieldRestrictExtensions , pure $ "max-file-size" JSON..= fieldMaxFileSize , pure $ "additional-files" JSON..= addFiles' ] where addFiles' = unFileIdentFileReferenceTitleMap fieldAdditionalFiles <&> \FileIdentFileReferenceTitleMapElem{..} -> JSON.object [ "ident" JSON..= fIdentTitleMapIdent , "include" JSON..= fIdentTitleMapAdditional ] instance FromJSON (FileField FileIdent) where parseJSON = JSON.withObject "FileField" $ \o -> do fieldIdent <- o JSON..:? "ident" fieldUnpackZips <- o JSON..: "unpack-zips" fieldMultiple <- o JSON..: "multiple" fieldRestrictExtensions <- o JSON..:? "restrict-extensions" fieldMaxFileSize <- o JSON..:? "max-file-size" fieldAllEmptyOk <- o JSON..:? "all-empty-ok" JSON..!= True addFiles' <- o JSON..:? "additional-files" JSON..!= mempty fieldAdditionalFiles <- fmap FileIdentFileReferenceTitleMap . for addFiles' $ JSON.withObject "FileIdentFileReferenceTitleMapElem" $ \o' -> do fIdentTitleMapIdent <- o' JSON..: "Ident" fIdentTitleMapAdditional <- o' JSON..: "include" return FileIdentFileReferenceTitleMapElem{..} return FileField{..} data WorkflowGraphForm = WorkflowGraphForm { wgfGraph :: WorkflowGraph FileIdent CryptoUUIDUser , wgfFiles :: Map FileIdent FileReference } deriving (Generic, Typeable) makeLenses_ ''WorkflowGraphForm workflowGraphForm :: Maybe WorkflowGraphForm -> AForm DB WorkflowGraphForm workflowGraphForm template = validateAForm validateWorkflowGraphForm . hoistAForm lift $ WorkflowGraphForm <$> areq yamlField (fslI MsgWorkflowDefinitionGraph) (wgfGraph <$> template) <*> filesForm where filesForm = Map.fromList <$> massInputAccumEditA fileAdd fileEdit (const Nothing) fileLayout ("workflow-definition-files" :: Text) (fslI MsgWorkflowDefinitionFiles) False (Map.toList . wgfFiles <$> template) where fileAdd nudge submitView csrf = do (formRes, formView) <- fileForm nudge Nothing csrf MsgRenderer mr <- getMsgRenderer let res' = formRes <&> \newFile@(newFileIdent, _) oldFiles -> if | any (\(oldFileIdent, _) -> newFileIdent == oldFileIdent) oldFiles -> FormFailure [mr MsgWorkflowDefinitionFileIdentExists] | otherwise -> FormSuccess $ pure newFile return (res', $(widgetFile "widgets/massinput/workflowDefinitionFiles/add")) fileEdit nudge = fileForm nudge . Just fileForm :: (Text -> Text) -> Maybe (FileIdent, FileReference) -> Form (FileIdent, FileReference) fileForm nudge fileTemplate csrf = do (fileIdentRes, fileIdentView) <- mpreq (isoField _Unwrapped ciField) (fslI MsgWorkflowDefinitionFileIdent & addName (nudge "ident")) (view _1 <$> fileTemplate) (fileRes, fileView) <- mpreq (singleFileField $ maybe (return ()) (views _2 yield) fileTemplate) (fslI MsgWorkflowDefinitionFile & addName (nudge "file")) (views _2 yield <$> fileTemplate) fileRes' <- liftHandler . runDB $ case fileRes of FormSuccess uploads -> maybe FormMissing FormSuccess <$> runConduit (transPipe liftHandler uploads .| C.head) FormFailure errs -> return $ FormFailure errs FormMissing -> return FormMissing return ((,) <$> fileIdentRes <*> fileRes', $(widgetFile "widgets/massinput/workflowDefinitionFiles/form")) fileLayout :: MassInputLayout ListLength (FileIdent, FileReference) (FileIdent, FileReference) fileLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/workflowDefinitionFiles/layout") validateWorkflowGraphForm :: FormValidator WorkflowGraphForm DB () validateWorkflowGraphForm = do fIdentsReferenced <- uses _wgfGraph . setOf $ typesCustom @WorkflowChildren fIdentsAvailable <- uses _wgfFiles Map.keysSet forM_ (fIdentsReferenced `Set.difference` fIdentsAvailable) $ tellValidationError . MsgWorkflowFileIdentDoesNotExist . views _Wrapped CI.original graph <- use _wgfGraph for_ (lintWorkflowGraph graph) $ \lintIssues -> do addMessageModal Warning (i18n MsgWorkflowDefinitionWarningLinterIssuesMessage) $ Right [whamlet| $newline never _{MsgWorkflowDefinitionWarningLinterIssues}