feat(workflows): further work on WorkflowWorkflowWorkflow
This commit is contained in:
parent
3e6935490b
commit
5b897c7a42
@ -1426,3 +1426,69 @@ pre, tt, code
|
||||
|
||||
.workflow-instance--actions
|
||||
margin: 0 0 0.5rem 11px
|
||||
|
||||
.workflow-history
|
||||
@extend .list--iconless
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
position: relative
|
||||
|
||||
.workflow-history--item
|
||||
border: 1px solid var(--color-grey)
|
||||
align-self: flex-start
|
||||
padding: 7px
|
||||
margin: 12px 0
|
||||
min-width: 45%
|
||||
display: grid
|
||||
grid-template-areas: 'user time' 'action-states action-states' 'payload payload'
|
||||
|
||||
&.workflow-history-item__self
|
||||
align-self: flex-end
|
||||
|
||||
&:last-child
|
||||
margin-bottom: 0
|
||||
&:first-child
|
||||
margin-bottom: 0
|
||||
|
||||
.workflow-history--item-user
|
||||
grid-area: user
|
||||
.workflow-history--item-time
|
||||
grid-area: time
|
||||
text-align: right
|
||||
.workflow-history--item-action-states
|
||||
grid-area: action-states
|
||||
|
||||
margin-top: 7px
|
||||
|
||||
.deflist__dt, .deflist__dd
|
||||
padding-top: 0
|
||||
padding-bottom: 0
|
||||
.workflow-history--item-payload-changes
|
||||
grid-area: payload
|
||||
|
||||
margin-top: 12px
|
||||
border-top: 1px solid var(--color-grey)
|
||||
padding-top: 12px
|
||||
|
||||
.workflow-history--item-payload-changes-label
|
||||
font-size: 20px
|
||||
font-weight: 600
|
||||
|
||||
|
||||
.workflow-history--item-user-special, .workflow-history--item-action-special, .workflow-history--item-state-special
|
||||
@extend .explanation
|
||||
|
||||
.workflow-state
|
||||
margin-top: 7px
|
||||
|
||||
.deflist__dt, .deflist__dd
|
||||
padding-top: 0
|
||||
padding-bottom: 0
|
||||
|
||||
.workflow-payload
|
||||
margin-top: 12px
|
||||
|
||||
.workflow-payload--label
|
||||
font-size: 20px
|
||||
font-weight: 600
|
||||
|
||||
@ -231,8 +231,6 @@ export class Datepicker {
|
||||
|
||||
// format the date value of the form input element of this datepicker before form submission
|
||||
this._element.form.addEventListener('submit', this._submitHandler.bind(this));
|
||||
|
||||
window.addEventListener('beforeunload', this._beforeUnloadHandler.bind(this));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -276,23 +274,6 @@ export class Datepicker {
|
||||
this.formatElementValue(true);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Restore input element to it's original type and format it's new value for input-value persisting by the browser
|
||||
*/
|
||||
_beforeUnloadHandler() {
|
||||
if (this._unloadIsDueToSubmit)
|
||||
return;
|
||||
|
||||
let oldValue = this._element.value;
|
||||
let newValue = this.unformat(false);
|
||||
this._element.type = this.elementType;
|
||||
this._element.value = newValue;
|
||||
|
||||
defer(() => { // Restore state after event loop is settled
|
||||
this._element.setAttribute('type', 'text');
|
||||
this._element.value = oldValue;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a datestring in internal format from the current state of the input element value.
|
||||
|
||||
@ -98,9 +98,8 @@ export class NavigateAwayPrompt {
|
||||
// allow the event to happen if the form was not touched by the
|
||||
// user (i.e. if the current FormData is equal to the initial FormData)
|
||||
// or the unload event was initiated by a form submit
|
||||
if (!formDataHasChanged || this._unloadDueToSubmit) {
|
||||
return false;
|
||||
}
|
||||
if (!formDataHasChanged || this._unloadDueToSubmit)
|
||||
return;
|
||||
|
||||
// cancel the unload event. This is the standard to force the prompt to appear.
|
||||
event.preventDefault();
|
||||
|
||||
@ -3017,7 +3017,15 @@ WorkflowWorkflowWorkflowHistoryUserHidden: Versteckter Benutzer
|
||||
WorkflowWorkflowWorkflowHistoryUserAutomatic: Automatisch
|
||||
WorkflowWorkflowWorkflowHistoryActionAutomatic: Automatisch
|
||||
WorkflowWorkflowWorkflowHistoryStateHidden: Versteckter Zustand
|
||||
WorkflowWorkflowWorkflowHistoryActionLabel: Aktion
|
||||
WorkflowWorkflowWorkflowHistoryFromLabel: Vorheriger Zustand
|
||||
WorkflowWorkflowWorkflowHistoryToLabel: Neuer Zustand
|
||||
WorkflowWorkflowWorkflowHistoryPayloadLabel: Datensatz-Änderungen
|
||||
WorkflowWorkflowFilesArchiveName wwCID@CryptoFileNameWorkflowWorkflow wpl@WorkflowPayloadLabel stCID@CryptoUUIDWorkflowStateIndex: #{foldCase (toPathPiece wwCID)}-#{foldCase (toPathPiece stCID)}-#{foldCase (foldMap unidecode (toPathPiece wpl))}.zip
|
||||
WorkflowWorkflowWorkflowStateHeading: Zustand/Daten
|
||||
WorkflowWorkflowWorkflowPayloadHeading: Aktueller Datensatz
|
||||
WorkflowWorkflowWorkflowStateStateLabel: Aktueller Zustand
|
||||
WorkflowWorkflowWorkflowStateStateHidden: Versteckter Zustand
|
||||
|
||||
WorkflowPayloadFiles: Datei(en)
|
||||
WorkflowPayloadBoolTrue: Ja
|
||||
|
||||
@ -16,6 +16,7 @@ module Foundation.Authorization
|
||||
, orAR, andAR, notAR, trueAR, falseAR
|
||||
, evalWorkflowRoleFor, evalWorkflowRoleFor'
|
||||
, hasWorkflowRole
|
||||
, mayViewWorkflowAction
|
||||
) where
|
||||
|
||||
import Import.NoFoundation hiding (Last(..))
|
||||
@ -38,7 +39,7 @@ import qualified Data.HashSet as HashSet
|
||||
import qualified Data.Map as Map
|
||||
import Data.Map ((!?))
|
||||
import qualified Data.Text as Text
|
||||
import Data.List (findIndex)
|
||||
import Data.List (findIndex, inits)
|
||||
import Data.Semigroup (Last(..))
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
@ -1416,9 +1417,11 @@ tagAccessPredicate AuthWorkflow = APDB $ \mAuthId route isWrite -> do
|
||||
WorkflowNodeView{..} <- hoistMaybe wgnViewers
|
||||
return $ toNullable wnvViewers
|
||||
payloadViewers = do
|
||||
WorkflowAction{..} <- otoList workflowWorkflowState
|
||||
payload <- Map.keys wpPayload
|
||||
fmap (toNullable . wpvViewers) . hoistMaybe $ wgPayloadView wwGraph Map.!? payload
|
||||
(prevActs, act) <- zip (inits $ otoList workflowWorkflowState) $ otoList workflowWorkflowState
|
||||
prevAct <- hoistMaybe $ prevActs ^? _last
|
||||
payload <- Map.keys $ wpPayload act
|
||||
guard $ Map.lookup payload (workflowStateCurrentPayloads prevActs) /= Map.lookup payload (wpPayload act)
|
||||
fmap (toNullable . wpvViewers) . hoistMaybe $ Map.lookup payload . wgnPayloadView =<< Map.lookup (wpTo prevAct) (wgNodes wwGraph)
|
||||
|
||||
evalRole role = lift $ evalWorkflowRoleFor mAuthId (Just wwId) role route isWrite
|
||||
guardM . fmap (is _Authorized) $ ofoldr1 orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable . otoList $ fold nodeViewers <> fold payloadViewers)
|
||||
@ -1430,13 +1433,13 @@ tagAccessPredicate AuthWorkflow = APDB $ \mAuthId route isWrite -> do
|
||||
let
|
||||
wwGraph :: WorkflowGraph FileReference UserId
|
||||
wwGraph = workflowWorkflowGraph & over (typesCustom @WorkflowChildren) (review _SqlKey :: SqlBackendKey -> UserId)
|
||||
|
||||
payloadViewers = Map.findWithDefault Set.empty wpl $ toNullable . wpvViewers <$> wgPayloadView wwGraph
|
||||
act <- workflowStateIndex stIx $ _DBWorkflowState # workflowWorkflowState
|
||||
let
|
||||
cState = wpTo act
|
||||
payloadViewers = Map.findWithDefault Set.empty wpl $ toNullable . wpvViewers <$> Map.findWithDefault Map.empty cState (wgnPayloadView <$> wgNodes wwGraph)
|
||||
evalRole role = lift $ evalWorkflowRoleFor mAuthId (Just wwId) role route isWrite
|
||||
guardM . anyM (otoList payloadViewers) $ fmap (is _Authorized) . evalRole
|
||||
WorkflowAction{wpTo} <- workflowStateIndex stIx workflowWorkflowState
|
||||
WorkflowNodeView{wnvViewers} <- hoistMaybe $ wgnViewers =<< wgNodes wwGraph Map.!? wpTo
|
||||
guardM . anyM (otoList wnvViewers) $ fmap (is _Authorized) . evalRole
|
||||
guardM . lift $ mayViewWorkflowAction mAuthId wwId act
|
||||
return Authorized
|
||||
|
||||
case route of
|
||||
@ -1686,3 +1689,32 @@ hasWorkflowRole :: ( MonadHandler m
|
||||
hasWorkflowRole mwwId wRole route isWrite = do
|
||||
mAuthId <- maybeAuthId
|
||||
evalWorkflowRoleFor mAuthId mwwId wRole route isWrite
|
||||
|
||||
mayViewWorkflowAction :: forall backend m fileid.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ UniWorX
|
||||
, BearerAuthSite UniWorX
|
||||
, BackendCompatible SqlReadBackend backend
|
||||
, MonadCrypto m, MonadCryptoKey m ~ CryptoIDKey
|
||||
, MonadCatch m
|
||||
)
|
||||
=> Maybe UserId
|
||||
-> WorkflowWorkflowId
|
||||
-> WorkflowAction fileid UserId
|
||||
-> ReaderT backend m Bool
|
||||
mayViewWorkflowAction mAuthId wwId WorkflowAction{..} = withReaderT (projectBackend @SqlReadBackend) . maybeT (return False) $ do
|
||||
WorkflowWorkflow{..} <- MaybeT $ get wwId
|
||||
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
|
||||
cID <- catchMaybeT (Proxy @CryptoIDError) . lift $ encrypt wwId
|
||||
let WorkflowGraph{..} = _DBWorkflowGraph # workflowWorkflowGraph
|
||||
canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
|
||||
evalWorkflowRole' role = lift $ is _Authorized <$> evalWorkflowRoleFor mAuthId (Just wwId) role canonRoute False
|
||||
WorkflowNodeView{..} <- hoistMaybe $ Map.lookup wpTo wgNodes >>= wgnViewers
|
||||
guardM $ orM
|
||||
[ return $ is _Just mAuthId && wpUser == Just mAuthId
|
||||
, anyM wnvViewers evalWorkflowRole'
|
||||
, anyM (Map.keys wpPayload) $ \payloadLbl -> maybeT (return False) $ do
|
||||
WorkflowPayloadView{..} <- hoistMaybe . Map.lookup payloadLbl $ Map.findWithDefault Map.empty wpTo (wgnPayloadView <$> wgNodes)
|
||||
lift $ anyM wpvViewers evalWorkflowRole'
|
||||
]
|
||||
return True
|
||||
|
||||
@ -257,7 +257,7 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
|
||||
(_, WorkflowWorkflowR cID (WWFilesR wpl _)) <- hoistMaybe $ route ^? _WorkflowScopeRoute
|
||||
wwId <- decrypt cID
|
||||
WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId . lift $ get wwId
|
||||
[wpl'] <- return . filter (== wpl) . Map.keys $ wgPayloadView workflowWorkflowGraph
|
||||
[wpl'] <- return . filter (== wpl) . sortOn (CI.original . unWorkflowPayloadLabel) . foldMap Map.keys $ wgnPayloadView <$> wgNodes workflowWorkflowGraph
|
||||
(caseChanged `on` unWorkflowPayloadLabel) wpl wpl'
|
||||
return $ route
|
||||
& typesUsing @RouteChildren @WorkflowPayloadLabel . filtered (== wpl) .~ wpl'
|
||||
|
||||
@ -68,7 +68,7 @@ workflowEdgeForm :: ( MonadHandler m
|
||||
, HandlerSite m ~ UniWorX
|
||||
, MonadHandler m'
|
||||
, HandlerSite m' ~ UniWorX
|
||||
, MonadThrow m'
|
||||
, MonadCatch m'
|
||||
)
|
||||
=> Either WorkflowInstanceId WorkflowWorkflowId
|
||||
-> Maybe WorkflowEdgeForm
|
||||
@ -84,8 +84,12 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
||||
Right WorkflowWorkflow{..} -> ( _DBWorkflowScope # workflowWorkflowScope
|
||||
, _DBWorkflowGraph # workflowWorkflowGraph
|
||||
)
|
||||
wPayload = ctx ^? _Right . _workflowWorkflowState . re _DBWorkflowState . to workflowStateCurrentPayloads
|
||||
wState = ctx ^? _Right . _workflowWorkflowState . to last . _wpTo
|
||||
wPayload' = ctx ^? _Right . _workflowWorkflowState . re _DBWorkflowState
|
||||
mAuthId <- maybeAuthId
|
||||
wPayload <- case mwwId of
|
||||
Right wwId -> workflowStateCurrentPayloads <$> filterM (lift . hoist liftHandler . mayViewWorkflowAction mAuthId wwId) (maybe [] otoList wPayload')
|
||||
Left _ -> return Map.empty
|
||||
|
||||
rScope <- toRouteWorkflowScope scope
|
||||
|
||||
@ -146,7 +150,7 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
||||
let edges' = flip sortOn edges $ \(edgeIdent, _) -> flip findIndex (olOptions edgesOptList) $ (== edgeIdent) . optionInternalValue
|
||||
|
||||
let edgeForms :: Map (WorkflowGraphNodeLabel, WorkflowGraphEdgeLabel) (AForm Handler WorkflowEdgeForm)
|
||||
edgeForms = Map.fromList . flip map edges' $ \(edgeIdent, (_, WorkflowGraphEdgeForm{..})) -> (edgeIdent, ) . fmap (WorkflowEdgeForm edgeIdent) . wFormToAForm . fmap sequenceA $ do
|
||||
edgeForms = Map.fromList . flip map edges' $ \(edgeIdent@(tState, _), (_, WorkflowGraphEdgeForm{..})) -> (edgeIdent, ) . fmap (WorkflowEdgeForm edgeIdent) . wFormToAForm . fmap sequenceA $ do
|
||||
let fieldSort :: [(WorkflowPayloadLabel, [[(Either WorkflowGraphEdgeFormOrder ByteString, WorkflowPayloadSpec FileReference UserId)]])]
|
||||
-> _
|
||||
fieldSort
|
||||
@ -166,7 +170,7 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
||||
orderedFields' <- flip evalStateT 1 . for orderedFields $ \x@(payloadLabel, _) -> do
|
||||
let generateDisplayLabel = State.state $ \n -> (mr $ MsgWorkflowEdgeFormHiddenPayload n, succ n)
|
||||
(mayView, payloadDisplayLabel) <- hoist (lift . lift . runDB) . maybeT ((False, ) <$> generateDisplayLabel) $ do
|
||||
WorkflowPayloadView{..} <- hoistMaybe . Map.lookup payloadLabel $ wgPayloadView graph
|
||||
WorkflowPayloadView{..} <- hoistMaybe . Map.lookup payloadLabel $ Map.findWithDefault Map.empty (fromMaybe tState wState) (wgnPayloadView <$> wgNodes graph)
|
||||
wRoute <- case (mwwId, ctx) of
|
||||
(Right wwId, Right _) -> do
|
||||
cID <- encrypt wwId
|
||||
@ -184,7 +188,7 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
||||
payloadFields = workflowEdgePayloadFields payloadSpecs' $ fmap otoList . Map.lookup payloadLabel =<< prevSrc
|
||||
where prevSrc = asum
|
||||
[ wefPayload <$> assertM ((== edgeIdent) . wefEdge) mPrev
|
||||
, guardOnM mayView wPayload
|
||||
, guardOn mayView wPayload
|
||||
]
|
||||
((payloadRes, isOptional), payloadFieldViews) <- wFormFields payloadFields
|
||||
return ((payloadDisplayLabel, getAll isOptional), (payloadRes, payloadFieldViews))
|
||||
|
||||
@ -46,7 +46,7 @@ followAutomaticEdges :: forall m.
|
||||
-> WorkflowState FileReference UserId -> m (WorkflowState FileReference UserId)
|
||||
followAutomaticEdges WorkflowGraph{..} = go []
|
||||
where
|
||||
go :: [(Set WorkflowPayloadLabel, (WorkflowGraphEdgeLabel, WorkflowGraphNodeLabel))] -- ^ Should encode all state from which automatic edges decide whether they can be followed
|
||||
go :: [((WorkflowGraphNodeLabel, Set WorkflowPayloadLabel), (WorkflowGraphEdgeLabel, WorkflowGraphNodeLabel))] -- ^ Should encode all state from which automatic edges decide whether they can be followed
|
||||
-> WorkflowState FileReference UserId
|
||||
-> m (WorkflowState FileReference UserId)
|
||||
go automaticEdgesTaken history
|
||||
@ -68,14 +68,16 @@ followAutomaticEdges WorkflowGraph{..} = go []
|
||||
(nodeLbl, WGN{..}) <- Map.toList wgNodes
|
||||
(edgeLbl, WorkflowGraphEdgeAutomatic{..}) <- Map.toList wgnEdges
|
||||
guard $ wgeSource == cState
|
||||
whenIsJust wgePayloadRestriction $ guard . checkPayloadRestriction
|
||||
whenIsJust wgeRestriction $ guard . checkRestriction
|
||||
return (edgeLbl, nodeLbl)
|
||||
checkPayloadRestriction :: PredDNF WorkflowPayloadLabel -> Bool
|
||||
checkPayloadRestriction dnf = maybe False (ofoldr1 (||)) . fromNullable $ map evalConj dnf'
|
||||
checkRestriction :: PredDNF WorkflowGraphEdgeAutomaticRestriction -> Bool
|
||||
checkRestriction dnf = maybe False (ofoldr1 (||)) . fromNullable $ map evalConj dnf'
|
||||
where
|
||||
evalConj = maybe True (ofoldr1 (&&)) . fromNullable . map evalPred
|
||||
evalPred PLVariable{..} = plVar `Set.member` filledPayloads
|
||||
evalPred PLNegated{..} = plVar `Set.notMember` filledPayloads
|
||||
evalPred PLVariable{ plVar = WorkflowGraphEdgeAutomaticRestrictionPayloadFilled{..} } = wgearPayloadFilled `Set.member` filledPayloads
|
||||
evalPred PLNegated{ plVar = WorkflowGraphEdgeAutomaticRestrictionPayloadFilled{..} } = wgearPayloadFilled `Set.notMember` filledPayloads
|
||||
evalPred PLVariable{ plVar = WorkflowGraphEdgeAutomaticRestrictionPreviousNode{..} } = wgearPreviousNode == cState
|
||||
evalPred PLNegated{ plVar = WorkflowGraphEdgeAutomaticRestrictionPreviousNode{..} } = wgearPreviousNode /= cState
|
||||
dnf' = map (Set.toList . toNullable) . Set.toList $ dnfTerms dnf
|
||||
filledPayloads = Map.keysSet . Map.filter (not . Set.null) $ workflowStateCurrentPayloads history
|
||||
edgeDecisionInput = filledPayloads
|
||||
edgeDecisionInput = (cState, filledPayloads)
|
||||
|
||||
@ -4,11 +4,13 @@ module Handler.Workflow.Workflow.Workflow
|
||||
, workflowR
|
||||
) where
|
||||
|
||||
import Import
|
||||
import Import hiding (Last(..))
|
||||
|
||||
import Utils.Form
|
||||
import Utils.Workflow
|
||||
|
||||
import Data.Semigroup (Last(..))
|
||||
|
||||
import Handler.Utils
|
||||
import Handler.Utils.Workflow.EdgeForm
|
||||
import Handler.Utils.Workflow.CanonicalRoute
|
||||
@ -16,7 +18,6 @@ import Handler.Utils.Workflow.Workflow
|
||||
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
import qualified Data.Sequence as Seq
|
||||
|
||||
import qualified Control.Monad.State.Class as State
|
||||
import Control.Monad.Trans.RWS.Strict (RWST, execRWST)
|
||||
@ -29,12 +30,12 @@ import Crypto.Hash.Algorithms (SHAKE256)
|
||||
import qualified Data.Text as Text
|
||||
import Data.RFC5051 (compareUnicode)
|
||||
|
||||
import Data.List (inits)
|
||||
|
||||
import qualified Data.Scientific as Scientific
|
||||
import Text.Blaze (toMarkup)
|
||||
import Data.Void (absurd)
|
||||
|
||||
import Data.List (inits)
|
||||
|
||||
|
||||
data WorkflowHistoryItemActor = WHIASelf | WHIAOther (Maybe (Entity User)) | WHIAHidden | WHIAGone
|
||||
deriving (Generic, Typeable)
|
||||
@ -45,9 +46,14 @@ data WorkflowHistoryItem = WorkflowHistoryItem
|
||||
, whiPayloadChanges :: [(Text, ([WorkflowFieldPayloadW Void (Maybe (Entity User))], Maybe (Route UniWorX)))]
|
||||
, whiFrom :: Maybe (Maybe Text) -- ^ outer maybe encodes existence, inner maybe encodes permission to view
|
||||
, whiVia :: Maybe Text
|
||||
, whiTo :: Text
|
||||
, whiTo :: Maybe Text
|
||||
} deriving (Generic, Typeable)
|
||||
|
||||
data WorkflowCurrentState = WorkflowCurrentState
|
||||
{ wcsState :: Maybe Text
|
||||
, wcsPayload :: [(Text, ([WorkflowFieldPayloadW Void (Maybe (Entity User))], Maybe (Route UniWorX)))]
|
||||
}
|
||||
|
||||
makePrisms ''WorkflowHistoryItemActor
|
||||
|
||||
|
||||
@ -62,7 +68,7 @@ workflowR :: WorkflowWorkflowId -> Handler Html
|
||||
workflowR wwId = do
|
||||
cID <- encrypt wwId
|
||||
|
||||
(mEdge, rScope, workflowHistory) <- runDB $ do
|
||||
(mEdge, rScope, (workflowState, workflowHistory)) <- runDB $ do
|
||||
WorkflowWorkflow{..} <- get404 wwId
|
||||
rScope <- maybeT notFound . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
|
||||
mEdgeForm <- workflowEdgeForm (Right wwId) Nothing
|
||||
@ -81,18 +87,21 @@ workflowR wwId = do
|
||||
redirect canonRoute
|
||||
return ((edgeAct, edgeView), edgeEnc)
|
||||
|
||||
workflowHistory <-
|
||||
(fmap getLast -> workflowState, workflowHistory) <-
|
||||
let go :: forall m.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ UniWorX
|
||||
, MonadThrow m
|
||||
, MonadCatch m
|
||||
)
|
||||
=> WorkflowStateIndex
|
||||
-> Maybe WorkflowGraphNodeLabel
|
||||
-> Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW FileReference UserId))
|
||||
-> WorkflowAction FileReference UserId
|
||||
-> RWST () [WorkflowHistoryItem] (Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW FileReference UserId))) (SqlPersistT m) ()
|
||||
go stIx wpFrom currentPayload WorkflowAction{..} = maybeT (return ()) $ do
|
||||
-> RWST () (Maybe (Last WorkflowCurrentState), [WorkflowHistoryItem]) (Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW FileReference UserId))) (SqlPersistT m) ()
|
||||
go stIx wpFrom currentPayload act@WorkflowAction{..} = maybeT (return ()) $ do
|
||||
mAuthId <- maybeAuthId
|
||||
guardM . lift . lift . hoist liftHandler $ mayViewWorkflowAction mAuthId wwId act
|
||||
|
||||
stCID <- encrypt stIx
|
||||
let nodeView nodeLbl = do
|
||||
WorkflowNodeView{..} <- hoistMaybe $ Map.lookup nodeLbl wgNodes >>= wgnViewers
|
||||
@ -102,62 +111,9 @@ workflowR wwId = do
|
||||
mVia = Map.lookup wpVia . wgnEdges =<< Map.lookup wpTo wgNodes
|
||||
hasWorkflowRole' role = $cachedHereBinary (rScope, wwId, role) . lift . lift $ is _Authorized <$> hasWorkflowRole (Just wwId) role canonRoute False
|
||||
|
||||
whiTo <- nodeView wpTo
|
||||
whiVia <- traverse selectLanguageI18n $ preview _wgeDisplayLabel =<< mVia
|
||||
|
||||
payloadChanges <- State.state $ \oldPayload ->
|
||||
( Map.filterWithKey (\k v -> Map.findWithDefault Set.empty k oldPayload /= v) currentPayload
|
||||
, currentPayload
|
||||
)
|
||||
sBoxKey <- secretBoxKey
|
||||
let payloadLabelToDigest :: WorkflowPayloadLabel -> ByteString
|
||||
payloadLabelToDigest = BA.convert . kmaclazy @(SHAKE256 256) ("workflow-workflow-payload-sorting" :: ByteString) (Saltine.encode sBoxKey) . Binary.encode . (wwId, )
|
||||
payloadLabelSort = (compareUnicode `on` views (_2 . _1) Text.toLower)
|
||||
<> comparing (views _1 payloadLabelToDigest)
|
||||
whiPayloadChanges' <- fmap (map (view _2) . sortBy payloadLabelSort) . forMaybeM (Map.toList payloadChanges) $ \(payloadLbl, newPayload) -> do
|
||||
WorkflowPayloadView{..} <- hoistMaybe $ Map.lookup payloadLbl wgPayloadView
|
||||
guardM . anyM (otoList wpvViewers) $ lift . hasWorkflowRole'
|
||||
let fRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID (WWFilesR payloadLbl stCID))
|
||||
(payloadLbl, ) . (, (newPayload, fRoute)) <$> selectLanguageI18n wpvDisplayLabel
|
||||
let
|
||||
payloadSort :: WorkflowFieldPayloadW Void (Maybe (Entity User))
|
||||
-> WorkflowFieldPayloadW Void (Maybe (Entity User))
|
||||
-> Ordering
|
||||
payloadSort (WorkflowFieldPayloadW a) (WorkflowFieldPayloadW b) = case (a, b) of
|
||||
(WFPText a', WFPText b' ) -> compareUnicode a' b'
|
||||
(WFPText{}, _ ) -> LT
|
||||
(WFPNumber a', WFPNumber b') -> compare a' b'
|
||||
(WFPNumber{}, WFPText{} ) -> GT
|
||||
(WFPNumber{}, _ ) -> LT
|
||||
(WFPBool a', WFPBool b' ) -> compare a' b'
|
||||
(WFPBool{}, WFPText{} ) -> GT
|
||||
(WFPBool{}, WFPNumber{} ) -> GT
|
||||
(WFPBool{}, _ ) -> LT
|
||||
(WFPDay a', WFPDay b' ) -> compare a' b'
|
||||
(WFPDay{}, WFPText{} ) -> GT
|
||||
(WFPDay{}, WFPNumber{} ) -> GT
|
||||
(WFPDay{}, WFPBool{} ) -> GT
|
||||
(WFPDay{}, _ ) -> LT
|
||||
(WFPFile a', _ ) -> absurd a'
|
||||
(WFPUser a', WFPUser b' ) -> case (a', b') of
|
||||
(Nothing, _) -> GT
|
||||
(_, Nothing) -> LT
|
||||
(Just (Entity _ uA), Just (Entity _ uB))
|
||||
-> (compareUnicode `on` userSurname) uA uB
|
||||
<> (compareUnicode `on` userDisplayName) uA uB
|
||||
<> comparing userIdent uA uB
|
||||
(WFPUser{}, _ ) -> GT
|
||||
whiPayloadChanges <- flip mapM whiPayloadChanges' $ \(lblText, (otoList -> payloads, fRoute)) -> fmap ((lblText, ) . over _1 (sortBy payloadSort) . over _2 (bool Nothing (Just fRoute). getAny)) . execWriterT . flip mapM_ payloads $ \case
|
||||
WorkflowFieldPayloadW (WFPText t ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPText t)
|
||||
WorkflowFieldPayloadW (WFPNumber n ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPNumber n)
|
||||
WorkflowFieldPayloadW (WFPBool b ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPBool b)
|
||||
WorkflowFieldPayloadW (WFPDay d ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPDay d)
|
||||
WorkflowFieldPayloadW (WFPFile _ ) -> tell (mempty, Any True)
|
||||
WorkflowFieldPayloadW (WFPUser uid) -> tell . (, mempty) . pure . review (_WorkflowFieldPayloadW . _WorkflowFieldPayload) =<< lift (lift . lift $ getEntity uid)
|
||||
whiTo <- lift . runMaybeT $ nodeView wpTo
|
||||
let wcsState = whiTo
|
||||
|
||||
whiFrom <- for wpFrom $ lift . runMaybeT . nodeView
|
||||
|
||||
mAuthId <- maybeAuthId
|
||||
whiUser <- for wpUser $ \wpUser' -> if
|
||||
| is _Just mAuthId
|
||||
, wpUser' == mAuthId -> return WHIASelf
|
||||
@ -169,17 +125,77 @@ workflowR wwId = do
|
||||
Nothing -> WHIAOther Nothing
|
||||
Just Nothing -> WHIAGone
|
||||
Just (Just uEnt) -> WHIAOther $ Just uEnt
|
||||
tell $ pure WorkflowHistoryItem{..}
|
||||
|
||||
whiVia <- traverse selectLanguageI18n $ preview _wgeDisplayLabel =<< mVia
|
||||
whiFrom <- for wpFrom $ lift . runMaybeT . nodeView
|
||||
|
||||
let renderPayload payload = do
|
||||
sBoxKey <- secretBoxKey
|
||||
let payloadLabelToDigest :: WorkflowPayloadLabel -> ByteString
|
||||
payloadLabelToDigest = BA.convert . kmaclazy @(SHAKE256 256) ("workflow-workflow-payload-sorting" :: ByteString) (Saltine.encode sBoxKey) . Binary.encode . (wwId, )
|
||||
payloadLabelSort = (compareUnicode `on` views (_2 . _1) Text.toLower)
|
||||
<> comparing (views _1 payloadLabelToDigest)
|
||||
payload' <- fmap (map (view _2) . sortBy payloadLabelSort) . forMaybeM (Map.toList payload) $ \(payloadLbl, newPayload) -> do
|
||||
WorkflowPayloadView{..} <- hoistMaybe . Map.lookup payloadLbl $ Map.findWithDefault Map.empty wpTo (wgnPayloadView <$> wgNodes)
|
||||
guardM . $cachedHereBinary payloadLbl . anyM (otoList wpvViewers) $ lift . hasWorkflowRole'
|
||||
let fRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID (WWFilesR payloadLbl stCID))
|
||||
(payloadLbl, ) . (, (newPayload, fRoute)) <$> selectLanguageI18n wpvDisplayLabel
|
||||
let
|
||||
payloadSort :: WorkflowFieldPayloadW Void (Maybe (Entity User))
|
||||
-> WorkflowFieldPayloadW Void (Maybe (Entity User))
|
||||
-> Ordering
|
||||
payloadSort (WorkflowFieldPayloadW a) (WorkflowFieldPayloadW b) = case (a, b) of
|
||||
(WFPText a', WFPText b' ) -> compareUnicode a' b'
|
||||
(WFPText{}, _ ) -> LT
|
||||
(WFPNumber a', WFPNumber b') -> compare a' b'
|
||||
(WFPNumber{}, WFPText{} ) -> GT
|
||||
(WFPNumber{}, _ ) -> LT
|
||||
(WFPBool a', WFPBool b' ) -> compare a' b'
|
||||
(WFPBool{}, WFPText{} ) -> GT
|
||||
(WFPBool{}, WFPNumber{} ) -> GT
|
||||
(WFPBool{}, _ ) -> LT
|
||||
(WFPDay a', WFPDay b' ) -> compare a' b'
|
||||
(WFPDay{}, WFPText{} ) -> GT
|
||||
(WFPDay{}, WFPNumber{} ) -> GT
|
||||
(WFPDay{}, WFPBool{} ) -> GT
|
||||
(WFPDay{}, _ ) -> LT
|
||||
(WFPFile a', _ ) -> absurd a'
|
||||
(WFPUser a', WFPUser b' ) -> case (a', b') of
|
||||
(Nothing, _) -> GT
|
||||
(_, Nothing) -> LT
|
||||
(Just (Entity _ uA), Just (Entity _ uB))
|
||||
-> (compareUnicode `on` userSurname) uA uB
|
||||
<> (compareUnicode `on` userDisplayName) uA uB
|
||||
<> comparing userIdent uA uB
|
||||
(WFPUser{}, _ ) -> GT
|
||||
flip mapM payload' $ \(lblText, (otoList -> payloads, fRoute)) -> fmap ((lblText, ) . over _1 (sortBy payloadSort) . over _2 (bool Nothing (Just fRoute). getAny)) . execWriterT . flip mapM_ payloads $ \case
|
||||
WorkflowFieldPayloadW (WFPText t ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPText t)
|
||||
WorkflowFieldPayloadW (WFPNumber n ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPNumber n)
|
||||
WorkflowFieldPayloadW (WFPBool b ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPBool b)
|
||||
WorkflowFieldPayloadW (WFPDay d ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPDay d)
|
||||
WorkflowFieldPayloadW (WFPFile _ ) -> tell (mempty, Any True)
|
||||
WorkflowFieldPayloadW (WFPUser uid) -> tell . (, mempty) . pure . review (_WorkflowFieldPayloadW . _WorkflowFieldPayload) =<< lift (lift . lift $ getEntity uid)
|
||||
|
||||
payloadChanges <- State.state $ \oldPayload ->
|
||||
( Map.filterWithKey (\k v -> Map.findWithDefault Set.empty k oldPayload /= v) currentPayload
|
||||
, currentPayload
|
||||
)
|
||||
whiPayloadChanges <- renderPayload payloadChanges
|
||||
wcsPayload <- renderPayload currentPayload
|
||||
|
||||
tell ( Just $ Last WorkflowCurrentState{..}
|
||||
, pure WorkflowHistoryItem{..}
|
||||
)
|
||||
WorkflowGraph{..} = _DBWorkflowGraph # workflowWorkflowGraph
|
||||
wState = otoList $ review _DBWorkflowState workflowWorkflowState
|
||||
in fmap (view _2) . (\act -> execRWST act () Map.empty) $ sequence_
|
||||
in fmap (over _2 (sortOn (Down . whiTime) . reverse) . view _2) . (\act -> execRWST act () Map.empty) $ sequence_
|
||||
[ go stIx fromSt payload act
|
||||
| fromSt <- Nothing : map (Just . wpTo) wState
|
||||
| act <- wState
|
||||
| payload <- map (maybe Map.empty workflowStateCurrentPayloads . fromNullable . Seq.fromList) . tailEx $ inits wState
|
||||
| stIx <- [minBound..]
|
||||
| payload <- map workflowStateCurrentPayloads . tailEx $ inits wState
|
||||
]
|
||||
return (mEdge, rScope, workflowHistory)
|
||||
return (mEdge, rScope, (workflowState, workflowHistory))
|
||||
|
||||
sequenceOf_ (_Just . _1 . _1 . _Just) mEdge
|
||||
|
||||
@ -223,7 +239,8 @@ getGWWFilesR wwCID wpl stCID = do
|
||||
WorkflowWorkflow{..} <- get404 wwId
|
||||
stIx <- decrypt stCID
|
||||
payloads <- maybeT notFound . workflowStateSection stIx $ _DBWorkflowState # workflowWorkflowState
|
||||
payloads' <- maybe notFound return . Map.lookup wpl $ workflowStateCurrentPayloads payloads
|
||||
mAuthId <- maybeAuthId
|
||||
payloads' <- fmap (Map.findWithDefault Set.empty wpl . workflowStateCurrentPayloads) . filterM (mayViewWorkflowAction mAuthId wwId) $ otoList payloads
|
||||
let
|
||||
payloads'' :: [FileReference]
|
||||
payloads'' = payloads' ^.. folded . _WorkflowFieldPayloadW . _WorkflowFieldPayload
|
||||
|
||||
@ -7,6 +7,7 @@ module Model.Types.Workflow
|
||||
, WorkflowNodeView(..)
|
||||
, WorkflowGraphEdgeLabel
|
||||
, WorkflowGraphEdge(..)
|
||||
, WorkflowGraphEdgeAutomaticRestriction(..)
|
||||
, WorkflowGraphEdgeFormOrder
|
||||
, WorkflowGraphEdgeForm(..)
|
||||
, WorkflowRole(..)
|
||||
@ -61,7 +62,6 @@ import Utils.Lens.TH
|
||||
|
||||
data WorkflowGraph fileid userid = WorkflowGraph
|
||||
{ wgNodes :: Map WorkflowGraphNodeLabel (WorkflowGraphNode fileid userid)
|
||||
, wgPayloadView :: Map WorkflowPayloadLabel (WorkflowPayloadView userid)
|
||||
}
|
||||
deriving (Generic, Typeable)
|
||||
|
||||
@ -79,6 +79,7 @@ data WorkflowGraphNode fileid userid = WGN
|
||||
{ wgnFinal :: Bool
|
||||
, wgnViewers :: Maybe (WorkflowNodeView userid)
|
||||
, wgnEdges :: Map WorkflowGraphEdgeLabel (WorkflowGraphEdge fileid userid)
|
||||
, wgnPayloadView :: Map WorkflowPayloadLabel (WorkflowPayloadView userid)
|
||||
}
|
||||
deriving (Generic, Typeable)
|
||||
|
||||
@ -97,6 +98,11 @@ newtype WorkflowGraphEdgeLabel = WorkflowGraphEdgeLabel { unWorkflowGraphEdgeLab
|
||||
deriving stock (Eq, Ord, Read, Show, Data, Generic, Typeable)
|
||||
deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary)
|
||||
|
||||
data WorkflowGraphEdgeAutomaticRestriction
|
||||
= WorkflowGraphEdgeAutomaticRestrictionPayloadFilled { wgearPayloadFilled :: WorkflowPayloadLabel }
|
||||
| WorkflowGraphEdgeAutomaticRestrictionPreviousNode { wgearPreviousNode :: WorkflowGraphNodeLabel }
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
|
||||
data WorkflowGraphEdge fileid userid
|
||||
= WorkflowGraphEdgeManual
|
||||
{ wgeSource :: WorkflowGraphNodeLabel
|
||||
@ -106,8 +112,8 @@ data WorkflowGraphEdge fileid userid
|
||||
, wgeViewActor :: Set (WorkflowRole userid)
|
||||
}
|
||||
| WorkflowGraphEdgeAutomatic
|
||||
{ wgeSource :: WorkflowGraphNodeLabel
|
||||
, wgePayloadRestriction :: Maybe (PredDNF WorkflowPayloadLabel)
|
||||
{ wgeSource :: WorkflowGraphNodeLabel
|
||||
, wgeRestriction :: Maybe (PredDNF WorkflowGraphEdgeAutomaticRestriction)
|
||||
}
|
||||
| WorkflowGraphEdgeInitial
|
||||
{ wgeActors :: Set (WorkflowRole userid)
|
||||
@ -430,8 +436,11 @@ instance Typeable userid => IsWorkflowFieldPayload fileid fileid userid userid'
|
||||
-- | otherwise
|
||||
-- -> traceShow ("none", fieldPayload) mempty
|
||||
|
||||
workflowStateCurrentPayloads :: forall fileid userid.
|
||||
WorkflowState fileid userid
|
||||
workflowStateCurrentPayloads :: forall fileid userid mono.
|
||||
( Element mono ~ WorkflowAction fileid userid
|
||||
, MonoFoldable mono
|
||||
)
|
||||
=> mono
|
||||
-> Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW fileid userid))
|
||||
workflowStateCurrentPayloads = Map.unionsWith (\_ v -> v) . map wpPayload . otoList
|
||||
|
||||
@ -596,6 +605,11 @@ deriveToJSON workflowPayloadViewAesonOptions ''WorkflowPayloadView
|
||||
pathPieceJSON ''WorkflowFieldPayload'
|
||||
pathPieceJSON ''WorkflowPayloadField'
|
||||
|
||||
deriveJSON defaultOptions
|
||||
{ fieldLabelModifier = camelToPathPiece' 1
|
||||
, constructorTagModifier = camelToPathPiece' 5
|
||||
} ''WorkflowGraphEdgeAutomaticRestriction
|
||||
|
||||
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowNodeView userid) where
|
||||
parseJSON = genericParseJSON workflowNodeViewAesonOptions
|
||||
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowPayloadView userid) where
|
||||
|
||||
@ -1,4 +1,46 @@
|
||||
$newline never
|
||||
$maybe WorkflowCurrentState{..} <- workflowState
|
||||
<section>
|
||||
<h2>
|
||||
_{MsgWorkflowWorkflowWorkflowStateHeading}
|
||||
|
||||
<div .workflow-state>
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt>
|
||||
_{MsgWorkflowWorkflowWorkflowStateStateLabel}
|
||||
<dd .deflist__dd>
|
||||
$maybe stLbl <- wcsState
|
||||
#{stLbl}
|
||||
$nothing
|
||||
<span .workflow-state--state-special>
|
||||
_{MsgWorkflowWorkflowWorkflowStateStateHidden}
|
||||
$if not (onull wcsPayload)
|
||||
<div .workflow-payload>
|
||||
<div .workflow-payload--label>
|
||||
_{MsgWorkflowWorkflowWorkflowPayloadHeading}
|
||||
<dl .deflist>
|
||||
$forall (payloadLbl, (newPayload, mFileRoute)) <- wcsPayload
|
||||
<dt .deflist__dt>
|
||||
#{payloadLbl}
|
||||
<dd .deflist__dd>
|
||||
$if is _Nothing mFileRoute && null newPayload
|
||||
—
|
||||
$else
|
||||
<ul .list--iconless>
|
||||
$maybe fileRoute <- mFileRoute
|
||||
<li>
|
||||
<a href=@{fileRoute}>
|
||||
_{MsgWorkflowPayloadFiles}
|
||||
$forall pItem <- newPayload
|
||||
<li>
|
||||
^{payloadToWidget pItem}
|
||||
|
||||
$maybe edgeView <- mEdgeView
|
||||
<section>
|
||||
<h2>
|
||||
_{MsgWorkflowWorkflowWorkflowEdgeFormHeading}
|
||||
^{edgeView}
|
||||
|
||||
<section>
|
||||
<h2>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryHeading}
|
||||
@ -6,9 +48,3 @@ $newline never
|
||||
<ul .workflow-history>
|
||||
$forall histItem <- workflowHistory
|
||||
^{historyToWidget histItem}
|
||||
$maybe edgeView <- mEdgeView
|
||||
<section>
|
||||
<h2>
|
||||
_{MsgWorkflowWorkflowWorkflowEdgeFormHeading}
|
||||
^{edgeView}
|
||||
|
||||
|
||||
@ -23,24 +23,37 @@ $newline never
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryUserAutomatic}
|
||||
<div .workflow-history--item-time>
|
||||
^{formatTimeW SelFormatDateTime whiTime}
|
||||
<div .workflow-history--item-action>
|
||||
$maybe actionLbl <- whiVia
|
||||
#{actionLbl}
|
||||
$nothing
|
||||
<span .workflow-history--item-action-special>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryActionAutomatic}
|
||||
<div .workflow-history--item-states>
|
||||
<div .workflow-history--item-state-from>
|
||||
<div .workflow-history--item-action-states>
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryActionLabel}
|
||||
<dd .deflist__dd>
|
||||
$maybe actionLbl <- whiVia
|
||||
#{actionLbl}
|
||||
$nothing
|
||||
<span .workflow-history--item-action-special>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryActionAutomatic}
|
||||
$maybe mFromLbl <- whiFrom
|
||||
$maybe fromLbl <- mFromLbl
|
||||
#{fromLbl}
|
||||
<dt .deflist__dt>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryFromLabel}
|
||||
<dd .deflist__dd>
|
||||
$maybe fromLbl <- mFromLbl
|
||||
#{fromLbl}
|
||||
$nothing
|
||||
<span .workflow-history--item-state-special>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryStateHidden}
|
||||
<dt .deflist__dt>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryToLabel}
|
||||
<dd .deflist__dd>
|
||||
$maybe toLbl <- whiTo
|
||||
#{toLbl}
|
||||
$nothing
|
||||
<span .workflow-history--item-state-special>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryStateHidden}
|
||||
<div .workflow-history--item-state-to>
|
||||
#{whiTo}
|
||||
$if not (onull whiPayloadChanges)
|
||||
<div .workflow-history--item-payload-changes>
|
||||
<div .workflow-history--item-payload-changes-label>
|
||||
_{MsgWorkflowWorkflowWorkflowHistoryPayloadLabel}
|
||||
<dl .deflist>
|
||||
$forall (payloadLbl, (newPayload, mFileRoute)) <- whiPayloadChanges
|
||||
<dt .deflist__dt>
|
||||
|
||||
145
testdata/theses.yaml
vendored
145
testdata/theses.yaml
vendored
@ -15,6 +15,70 @@ nodes:
|
||||
- &student
|
||||
tag: payload-reference
|
||||
payload-label: "student"
|
||||
payload-view: &payload-view
|
||||
"hochschullehrer":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Verantwortliche Hochschullehrer"
|
||||
"betreuer":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Betreuer"
|
||||
"student":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Student"
|
||||
"anmeldetag":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Tag der Anmeldung"
|
||||
"sprache":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Sprache der Arbeit"
|
||||
"titel":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Titel, in Sprache der Arbeit"
|
||||
"titel, englisch":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Titel, Englisch"
|
||||
"notizen":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Notizen"
|
||||
final: false
|
||||
edges:
|
||||
"antrag als pruefungsamt":
|
||||
@ -287,6 +351,7 @@ nodes:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges:
|
||||
"antrag bestaetigen als hochschullehrer":
|
||||
@ -315,6 +380,7 @@ nodes:
|
||||
- *student
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges:
|
||||
"antrag bestaetigen als student":
|
||||
@ -342,6 +408,7 @@ nodes:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges:
|
||||
"antrag bestaetigen als student":
|
||||
@ -369,12 +436,18 @@ nodes:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges:
|
||||
"anmeldetag ist eingetragen":
|
||||
mode: automatic
|
||||
source: "antrag, student&hochschullehrer"
|
||||
payload-restriction: { "dnf-terms": [[{"tag": "variable", "var": "anmeldetag"}]] }
|
||||
restriction:
|
||||
dnf-terms:
|
||||
- - tag: variable
|
||||
var:
|
||||
tag: payload-filled
|
||||
payload-filled: "anmeldetag"
|
||||
"angemeldet":
|
||||
viewers:
|
||||
display-label: "Angemeldet"
|
||||
@ -383,6 +456,7 @@ nodes:
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges:
|
||||
"anmelden, bestaetigt student&hochschullehrer, anmeldetag":
|
||||
@ -441,6 +515,7 @@ nodes:
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges: {}
|
||||
"abgegeben":
|
||||
@ -451,6 +526,7 @@ nodes:
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges: {}
|
||||
"benotet":
|
||||
@ -461,6 +537,7 @@ nodes:
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges: {}
|
||||
"abgebrochen":
|
||||
@ -471,6 +548,7 @@ nodes:
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
payload-view: *payload-view
|
||||
final: false
|
||||
edges: {}
|
||||
"fertig":
|
||||
@ -478,69 +556,6 @@ nodes:
|
||||
display-label: "Fertig"
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
payload-view: *payload-view
|
||||
final: true
|
||||
edges: {}
|
||||
payload-view:
|
||||
"hochschullehrer":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Verantwortliche Hochschullehrer"
|
||||
"betreuer":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Betreuer"
|
||||
"student":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Student"
|
||||
"anmeldetag":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Tag der Anmeldung"
|
||||
"sprache":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Sprache der Arbeit"
|
||||
"titel":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Titel, in Sprache der Arbeit"
|
||||
"titel, englisch":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- *student
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Titel, Englisch"
|
||||
"notizen":
|
||||
viewers:
|
||||
- *pruefungsamt
|
||||
- *hochschullehrer
|
||||
- *betreuer
|
||||
- {"tag": "initiator"}
|
||||
display-label: "Notizen"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user