feat(workflows): additional work on WorkflowWorkflowWorkflow
This commit is contained in:
parent
fd7c91f5b8
commit
5108e1494a
@ -720,7 +720,7 @@ section
|
|||||||
background-color: hsla($hue, 75%, 50%, $opacity) !important
|
background-color: hsla($hue, 75%, 50%, $opacity) !important
|
||||||
|
|
||||||
|
|
||||||
.uuid, .pseudonym, .ldap-primary-key, .email, .file-path, .metric-value, .metric-label
|
.uuid, .pseudonym, .ldap-primary-key, .email, .file-path, .metric-value, .metric-label, .workflow-payload--text
|
||||||
font-family: var(--font-monospace)
|
font-family: var(--font-monospace)
|
||||||
|
|
||||||
.token
|
.token
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import './datepicker.css';
|
|||||||
import { Utility } from '../../core/utility';
|
import { Utility } from '../../core/utility';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import * as defer from 'lodash.defer';
|
||||||
|
|
||||||
const KEYCODE_ESCAPE = 27;
|
const KEYCODE_ESCAPE = 27;
|
||||||
const Z_INDEX_MODAL = 9999;
|
const Z_INDEX_MODAL = 9999;
|
||||||
|
|
||||||
@ -77,8 +79,11 @@ export class Datepicker {
|
|||||||
datepickerInstance;
|
datepickerInstance;
|
||||||
_element;
|
_element;
|
||||||
elementType;
|
elementType;
|
||||||
|
initialValue;
|
||||||
_locale;
|
_locale;
|
||||||
|
|
||||||
|
_unloadIsDueToSubmit = false;
|
||||||
|
|
||||||
constructor(element) {
|
constructor(element) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
throw new Error('Datepicker utility needs to be passed an element!');
|
throw new Error('Datepicker utility needs to be passed an element!');
|
||||||
@ -100,6 +105,9 @@ export class Datepicker {
|
|||||||
// store the previously set type to select the input format
|
// store the previously set type to select the input format
|
||||||
this.elementType = this._element.getAttribute('type');
|
this.elementType = this._element.getAttribute('type');
|
||||||
|
|
||||||
|
// store initial value prior to changing type
|
||||||
|
this.initialValue = this._element.value || this._element.getAttribute('value');
|
||||||
|
|
||||||
// manually set the type attribute to text because datepicker handles displaying the date
|
// manually set the type attribute to text because datepicker handles displaying the date
|
||||||
this._element.setAttribute('type', 'text');
|
this._element.setAttribute('type', 'text');
|
||||||
|
|
||||||
@ -120,7 +128,7 @@ export class Datepicker {
|
|||||||
// FIXME dirty hack below; fix tail.datetime instead
|
// FIXME dirty hack below; fix tail.datetime instead
|
||||||
|
|
||||||
// get date object from internal format before datetime does nasty things with it
|
// get date object from internal format before datetime does nasty things with it
|
||||||
let parsedMomentDate = moment(this._element.value, [ FORM_DATE_FORMAT[this.elementType], FORM_DATE_FORMAT_MOMENT[this.elementType] ], true);
|
let parsedMomentDate = moment(this.initialValue, [ FORM_DATE_FORMAT[this.elementType], FORM_DATE_FORMAT_MOMENT[this.elementType] ], true);
|
||||||
if (parsedMomentDate && parsedMomentDate.isValid()) {
|
if (parsedMomentDate && parsedMomentDate.isValid()) {
|
||||||
parsedMomentDate = parsedMomentDate.toDate();
|
parsedMomentDate = parsedMomentDate.toDate();
|
||||||
} else {
|
} else {
|
||||||
@ -222,7 +230,9 @@ export class Datepicker {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// format the date value of the form input element of this datepicker before form submission
|
// format the date value of the form input element of this datepicker before form submission
|
||||||
this._element.form.addEventListener('submit', () => this.formatElementValue());
|
this._element.form.addEventListener('submit', this._submitHandler.bind(this));
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', this._beforeUnloadHandler.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -257,6 +267,33 @@ export class Datepicker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_submitHandler() {
|
||||||
|
this._unloadIsDueToSubmit = true;
|
||||||
|
this.formatElementValue(false);
|
||||||
|
|
||||||
|
defer(() => { // Restore state after event loop is settled
|
||||||
|
this._unloadIsDueToSubmit = false;
|
||||||
|
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.
|
* Returns a datestring in internal format from the current state of the input element value.
|
||||||
* @param {*} toFancy Format date from internal to fancy or vice versa. When omitted, toFancy is falsy and results in fancy -> internal
|
* @param {*} toFancy Format date from internal to fancy or vice versa. When omitted, toFancy is falsy and results in fancy -> internal
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { AUTO_SUBMIT_INPUT_UTIL_SELECTOR } from './auto-submit-input';
|
|||||||
import { InteractiveFieldset } from './interactive-fieldset';
|
import { InteractiveFieldset } from './interactive-fieldset';
|
||||||
import { Datepicker } from './datepicker';
|
import { Datepicker } from './datepicker';
|
||||||
|
|
||||||
|
import * as defer from 'lodash.defer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key generator from an arbitrary number of FormData objects.
|
* Key generator from an arbitrary number of FormData objects.
|
||||||
* @param {...any} formDatas FormData objects
|
* @param {...any} formDatas FormData objects
|
||||||
@ -67,6 +69,7 @@ export class NavigateAwayPrompt {
|
|||||||
|
|
||||||
this._element.addEventListener('submit', () => {
|
this._element.addEventListener('submit', () => {
|
||||||
this._unloadDueToSubmit = true;
|
this._unloadDueToSubmit = true;
|
||||||
|
defer(() => { this._unloadDueToSubmit = false; } ); // Restore state after event loop is settled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -527,11 +527,12 @@ UnauthorizedPasswordResetToken: Dieses Authorisierungs-Token kann nicht mehr zum
|
|||||||
UnauthorizedAllocatedCourseRegister: Direkte Anmeldungen zum Kurs sind aufgrund einer Zentralanmeldung aktuell nicht gestattet
|
UnauthorizedAllocatedCourseRegister: Direkte Anmeldungen zum Kurs sind aufgrund einer Zentralanmeldung aktuell nicht gestattet
|
||||||
UnauthorizedAllocatedCourseDeregister: Abmeldungen vom Kurs sind aufgrund einer Zentralanmeldung aktuell nicht gestattet
|
UnauthorizedAllocatedCourseDeregister: Abmeldungen vom Kurs sind aufgrund einer Zentralanmeldung aktuell nicht gestattet
|
||||||
UnauthorizedAllocatedCourseDelete: Kurse, die an einer Zentralanmeldung teilnehmen, dürfen nicht gelöscht werden
|
UnauthorizedAllocatedCourseDelete: Kurse, die an einer Zentralanmeldung teilnehmen, dürfen nicht gelöscht werden
|
||||||
UnauthorizedWorkflowInitiate: Der Workflow darf nicht im angegebenen Zustand unter Verwendung der angegebenen Kante initiiert werden
|
UnauthorizedWorkflowInitiate: Sie dürfen keinen neuen laufenden Workflow initiieren
|
||||||
UnauthorizedWorkflowWrite: Sie dürfen aktuell keinen Zustandsübergang im Workflow auslösen
|
UnauthorizedWorkflowWrite: Sie dürfen aktuell keinen Zustandsübergang im Workflow auslösen
|
||||||
UnauthorizedWorkflowRead: Der Workflow enthält aktuell keine Zustände oder Daten die Sie einsehen dürfen
|
UnauthorizedWorkflowRead: Der Workflow enthält aktuell keine Zustände oder Daten die Sie einsehen dürfen
|
||||||
UnauthorizedWorkflowInstancesNotEmpty: Es gibt Workflow Instanzen für die Sie einen neuen laufenden Workflow initiieren dürfen
|
UnauthorizedWorkflowInstancesNotEmpty: Es gibt Workflow Instanzen für die Sie einen neuen laufenden Workflow initiieren dürfen
|
||||||
UnauthorizedWorkflowWorkflowsNotEmpty: Es gibt laufende Workflows, die Sie einsehen dürfen
|
UnauthorizedWorkflowWorkflowsNotEmpty: Es gibt laufende Workflows, die Sie einsehen dürfen
|
||||||
|
UnauthorizedWorkflowFiles: Sie dürfen die angegebenen Workflow-Dateien nicht im angegebenen historischen Zustand herunterladen
|
||||||
|
|
||||||
WorkflowRoleUserMismatch: Sie sind nicht einer der vom Workflow geforderten Benutzer
|
WorkflowRoleUserMismatch: Sie sind nicht einer der vom Workflow geforderten Benutzer
|
||||||
WorkflowRoleAlreadyInitiated: Dieser Workflow wurde bereits initiiert
|
WorkflowRoleAlreadyInitiated: Dieser Workflow wurde bereits initiiert
|
||||||
@ -1501,6 +1502,7 @@ BreadcrumbWorkflowInstanceInitiate: Workflow starten
|
|||||||
BreadcrumbWorkflowInstanceList: Workflows
|
BreadcrumbWorkflowInstanceList: Workflows
|
||||||
BreadcrumbWorkflowInstanceNew: Neuer Workflow
|
BreadcrumbWorkflowInstanceNew: Neuer Workflow
|
||||||
BreadcrumbWorkflowWorkflow workflow@CryptoFileNameWorkflowWorkflow: #{toPathPiece workflow}
|
BreadcrumbWorkflowWorkflow workflow@CryptoFileNameWorkflowWorkflow: #{toPathPiece workflow}
|
||||||
|
BreadcrumbWorkflowWorkflowFiles: Dateien
|
||||||
BreadcrumbWorkflowWorkflowEdit: Editieren
|
BreadcrumbWorkflowWorkflowEdit: Editieren
|
||||||
BreadcrumbWorkflowWorkflowDelete: Löschen
|
BreadcrumbWorkflowWorkflowDelete: Löschen
|
||||||
BreadcrumbGlobalWorkflowInstanceList: Workflows
|
BreadcrumbGlobalWorkflowInstanceList: Workflows
|
||||||
@ -2951,6 +2953,8 @@ WorkflowDefinitionCollision: Es existiert bereits eine Workflow-Definition mit d
|
|||||||
WorkflowDefinitionNewTitle: Workflow-Definition anlegen
|
WorkflowDefinitionNewTitle: Workflow-Definition anlegen
|
||||||
WorkflowDefinitionEditTitle: Workflow-Definition Bearbeiten
|
WorkflowDefinitionEditTitle: Workflow-Definition Bearbeiten
|
||||||
WorkflowDefinitionInstanceCategory: Kategorie
|
WorkflowDefinitionInstanceCategory: Kategorie
|
||||||
|
WorkflowDefinitionWarningLinterIssuesMessage: Es sind Linter issues aufgetreten
|
||||||
|
WorkflowDefinitionWarningLinterIssues: Es sind folgende Linter issues aufgetreten:
|
||||||
|
|
||||||
WorkflowDefinitionListTitle: Workflow-Definitionen
|
WorkflowDefinitionListTitle: Workflow-Definitionen
|
||||||
WorkflowDefinitionInstanceCount: Instanzen
|
WorkflowDefinitionInstanceCount: Instanzen
|
||||||
@ -2995,6 +2999,27 @@ WorkflowEdgeFormFieldNumberTooSmall minSci@Scientific: Zahl muss mindestens #{fo
|
|||||||
WorkflowEdgeFormFieldNumberTooLarge maxSci@Scientific: Zahl muss höchstens #{formatScientific Scientific.Generic Nothing maxSci} sein
|
WorkflowEdgeFormFieldNumberTooLarge maxSci@Scientific: Zahl muss höchstens #{formatScientific Scientific.Generic Nothing maxSci} sein
|
||||||
WorkflowEdgeFormFieldUserNotFound: E-Mail Adresse konnte keinem Benutzer zugeordnet werden
|
WorkflowEdgeFormFieldUserNotFound: E-Mail Adresse konnte keinem Benutzer zugeordnet werden
|
||||||
WorkflowEdgeFormFieldMultipleNoneAdded: (Noch) keine Einträge
|
WorkflowEdgeFormFieldMultipleNoneAdded: (Noch) keine Einträge
|
||||||
|
WorkflowEdgeFormFieldCaptureUserLabel: Aktueller Benutzer
|
||||||
|
|
||||||
|
WorkflowWorkflowWorkflowHistoryHeading: Verlauf
|
||||||
|
WorkflowWorkflowWorkflowEdgeFormHeading: Aktion im Workflow auslösen
|
||||||
|
WorkflowWorkflowWorkflowEdgeSuccess: Aktion erfolgreich ausgelöst
|
||||||
|
WorkflowWorkflowWorkflowHistoryUserSelf: Sie selbst
|
||||||
|
WorkflowWorkflowWorkflowHistoryUserNotLoggedIn: Nicht eingeloggter Benutzer
|
||||||
|
WorkflowWorkflowWorkflowHistoryUserGone: Gelöschter Benutzer
|
||||||
|
WorkflowWorkflowWorkflowHistoryUserHidden: Versteckter Benutzer
|
||||||
|
WorkflowWorkflowWorkflowHistoryUserAutomatic: Automatisch
|
||||||
|
WorkflowWorkflowWorkflowHistoryActionAutomatic: Automatisch
|
||||||
|
WorkflowWorkflowWorkflowHistoryStateHidden: Versteckter Zustand
|
||||||
|
WorkflowWorkflowFilesArchiveName wwCID@CryptoFileNameWorkflowWorkflow wpl@WorkflowPayloadLabel stCID@CryptoUUIDWorkflowStateIndex: #{foldCase (toPathPiece wwCID)}-#{foldCase (toPathPiece stCID)}-#{foldCase (foldMap unidecode (toPathPiece wpl))}.zip
|
||||||
|
|
||||||
|
WorkflowPayloadFiles: Datei(en)
|
||||||
|
WorkflowPayloadBoolTrue: Ja
|
||||||
|
WorkflowPayloadBoolFalse: Nein
|
||||||
|
WorkflowPayloadUserGone: Gelöschter Benutzer
|
||||||
|
|
||||||
|
GlobalWorkflowWorkflowWorkflowHeading workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{toPathPiece workflowWorkflowId}
|
||||||
|
GlobalWorkflowWorkflowWorkflowTitle workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{toPathPiece workflowWorkflowId}
|
||||||
|
|
||||||
ChangelogItemFeature: Feature
|
ChangelogItemFeature: Feature
|
||||||
ChangelogItemBugfix: Bugfix
|
ChangelogItemBugfix: Bugfix
|
||||||
|
|||||||
1
routes
1
routes
@ -79,6 +79,7 @@
|
|||||||
/workflows GlobalWorkflowWorkflowListR GET !¬empty
|
/workflows GlobalWorkflowWorkflowListR GET !¬empty
|
||||||
/workflows/#CryptoFileNameWorkflowWorkflow GlobalWorkflowWorkflowR:
|
/workflows/#CryptoFileNameWorkflowWorkflow GlobalWorkflowWorkflowR:
|
||||||
/ GWWWorkflowR GET POST !workflow
|
/ GWWWorkflowR GET POST !workflow
|
||||||
|
/files/#WorkflowPayloadLabel/#CryptoUUIDWorkflowStateIndex GWWFilesR GET !workflow
|
||||||
/edit GWWEditR GET POST
|
/edit GWWEditR GET POST
|
||||||
/delete GWWDeleteR GET POST
|
/delete GWWDeleteR GET POST
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,9 @@ decCryptoIDs [ ''SubmissionId
|
|||||||
, ''WorkflowWorkflowId
|
, ''WorkflowWorkflowId
|
||||||
]
|
]
|
||||||
|
|
||||||
|
type instance CryptoIDNamespace a WorkflowStateIndex = "WorkflowStateIndex"
|
||||||
|
type CryptoUUIDWorkflowStateIndex = CryptoUUID WorkflowStateIndex
|
||||||
|
|
||||||
decCryptoIDKeySize
|
decCryptoIDKeySize
|
||||||
|
|
||||||
-- CryptoIDNamespace (CI FilePath) SubmissionId ~ "Submission"
|
-- CryptoIDNamespace (CI FilePath) SubmissionId ~ "Submission"
|
||||||
|
|||||||
@ -1224,6 +1224,8 @@ tagAccessPredicate AuthEmpty = APDB $ \mAuthId route _ -> do
|
|||||||
return Authorized
|
return Authorized
|
||||||
|
|
||||||
case route of
|
case route of
|
||||||
|
_ | Just (rScope, WorkflowInstanceListR) <- route ^? _WorkflowScopeRoute -> wInstances rScope
|
||||||
|
_ | Just (rScope, WorkflowWorkflowListR) <- route ^? _WorkflowScopeRoute -> wWorkflows rScope
|
||||||
EExamListR -> exceptT return return $ do
|
EExamListR -> exceptT return return $ do
|
||||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
hasExternalExams <- $cachedHereBinary authId . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamStaff) -> do
|
hasExternalExams <- $cachedHereBinary authId . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamStaff) -> do
|
||||||
@ -1243,8 +1245,6 @@ tagAccessPredicate AuthEmpty = APDB $ \mAuthId route _ -> do
|
|||||||
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
|
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
|
||||||
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
||||||
return Authorized
|
return Authorized
|
||||||
GlobalWorkflowInstanceListR -> wInstances WSGlobal
|
|
||||||
GlobalWorkflowWorkflowListR -> wWorkflows WSGlobal
|
|
||||||
r -> $unsupportedAuthPredicate AuthEmpty r
|
r -> $unsupportedAuthPredicate AuthEmpty r
|
||||||
tagAccessPredicate AuthMaterials = APDB $ \_ route _ -> case route of
|
tagAccessPredicate AuthMaterials = APDB $ \_ route _ -> case route of
|
||||||
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do
|
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do
|
||||||
@ -1364,7 +1364,8 @@ tagAccessPredicate AuthWorkflow = APDB $ \mAuthId route isWrite -> do
|
|||||||
orAR' = shortCircuitM (is _Authorized) (orAR mr)
|
orAR' = shortCircuitM (is _Authorized) (orAR mr)
|
||||||
_andAR' = shortCircuitM (is _Unauthorized) (andAR mr)
|
_andAR' = shortCircuitM (is _Unauthorized) (andAR mr)
|
||||||
|
|
||||||
wInitiate win scope = maybeT (unauthorizedI MsgUnauthorizedWorkflowInitiate) $ do
|
wInitiate win rScope = maybeT (unauthorizedI MsgUnauthorizedWorkflowInitiate) $ do
|
||||||
|
scope <- MaybeT . $cachedHereBinary rScope . runMaybeT $ fromRouteWorkflowScope rScope
|
||||||
Entity _ WorkflowInstance{..} <- $cachedHereBinary (win, scope) . MaybeT . getBy . UniqueWorkflowInstance win $ scope ^. _DBWorkflowScope
|
Entity _ WorkflowInstance{..} <- $cachedHereBinary (win, scope) . MaybeT . getBy . UniqueWorkflowInstance win $ scope ^. _DBWorkflowScope
|
||||||
let
|
let
|
||||||
wiGraph :: WorkflowGraph FileReference UserId
|
wiGraph :: WorkflowGraph FileReference UserId
|
||||||
@ -1376,11 +1377,12 @@ tagAccessPredicate AuthWorkflow = APDB $ \mAuthId route isWrite -> do
|
|||||||
let
|
let
|
||||||
evalRole role = lift $ evalWorkflowRoleFor mAuthId Nothing role route isWrite
|
evalRole role = lift $ evalWorkflowRoleFor mAuthId Nothing role route isWrite
|
||||||
checkEdge actors = ofoldr1 orAR' (mapNonNull evalRole actors)
|
checkEdge actors = ofoldr1 orAR' (mapNonNull evalRole actors)
|
||||||
ofoldr1 orAR' . mapNonNull checkEdge =<< hoistMaybe (fromNullable edges)
|
guardM . fmap (is _Authorized) $ ofoldr1 orAR' . mapNonNull checkEdge =<< hoistMaybe (fromNullable edges)
|
||||||
|
return Authorized
|
||||||
|
|
||||||
wWorkflow isWrite' cID
|
wWorkflow isWrite' cID
|
||||||
| isWrite' = maybeT (unauthorizedI MsgUnauthorizedWorkflowWrite) $ do
|
| isWrite' = maybeT (unauthorizedI MsgUnauthorizedWorkflowWrite) $ do
|
||||||
wwId <- decrypt cID
|
wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
|
||||||
WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId
|
WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId
|
||||||
|
|
||||||
let
|
let
|
||||||
@ -1397,9 +1399,10 @@ tagAccessPredicate AuthWorkflow = APDB $ \mAuthId route isWrite -> do
|
|||||||
|
|
||||||
evalRole role = lift $ evalWorkflowRoleFor mAuthId (Just wwId) role route isWrite
|
evalRole role = lift $ evalWorkflowRoleFor mAuthId (Just wwId) role route isWrite
|
||||||
checkEdge actors = ofoldr1 orAR' (mapNonNull evalRole actors)
|
checkEdge actors = ofoldr1 orAR' (mapNonNull evalRole actors)
|
||||||
ofoldr1 orAR' . mapNonNull checkEdge =<< hoistMaybe (fromNullable edges)
|
guardM . fmap (is _Authorized) $ ofoldr1 orAR' . mapNonNull checkEdge =<< hoistMaybe (fromNullable edges)
|
||||||
|
return Authorized
|
||||||
| otherwise = flip orAR' (wWorkflow True cID) . maybeT (unauthorizedI MsgUnauthorizedWorkflowRead) $ do
|
| otherwise = flip orAR' (wWorkflow True cID) . maybeT (unauthorizedI MsgUnauthorizedWorkflowRead) $ do
|
||||||
wwId <- decrypt cID
|
wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
|
||||||
WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId
|
WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId
|
||||||
|
|
||||||
let
|
let
|
||||||
@ -1410,18 +1413,36 @@ tagAccessPredicate AuthWorkflow = APDB $ \mAuthId route isWrite -> do
|
|||||||
WorkflowAction{..} <- otoList workflowWorkflowState
|
WorkflowAction{..} <- otoList workflowWorkflowState
|
||||||
(node, WGN{..}) <- itoListOf (_wgNodes . ifolded) wwGraph
|
(node, WGN{..}) <- itoListOf (_wgNodes . ifolded) wwGraph
|
||||||
guard $ node == wpTo
|
guard $ node == wpTo
|
||||||
return wgnViewers
|
WorkflowNodeView{..} <- hoistMaybe wgnViewers
|
||||||
|
return $ toNullable wnvViewers
|
||||||
payloadViewers = do
|
payloadViewers = do
|
||||||
WorkflowAction{..} <- otoList workflowWorkflowState
|
WorkflowAction{..} <- otoList workflowWorkflowState
|
||||||
payload <- Map.keys wpPayload
|
payload <- Map.keys wpPayload
|
||||||
fmap (toNullable . wpvViewers) . hoistMaybe $ wgPayloadView wwGraph Map.!? payload
|
fmap (toNullable . wpvViewers) . hoistMaybe $ wgPayloadView wwGraph Map.!? payload
|
||||||
|
|
||||||
evalRole role = lift $ evalWorkflowRoleFor mAuthId (Just wwId) role route isWrite
|
evalRole role = lift $ evalWorkflowRoleFor mAuthId (Just wwId) role route isWrite
|
||||||
ofoldr1 orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable . otoList $ fold nodeViewers <> fold payloadViewers)
|
guardM . fmap (is _Authorized) $ ofoldr1 orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable . otoList $ fold nodeViewers <> fold payloadViewers)
|
||||||
|
return Authorized
|
||||||
|
wFiles wwCID wpl stCID = maybeT (unauthorizedI MsgUnauthorizedWorkflowFiles) $ do
|
||||||
|
wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt wwCID
|
||||||
|
WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId
|
||||||
|
stIx <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decryptWorkflowStateIndex wwId stCID
|
||||||
|
let
|
||||||
|
wwGraph :: WorkflowGraph FileReference UserId
|
||||||
|
wwGraph = workflowWorkflowGraph & over (typesCustom @WorkflowChildren) (review _SqlKey :: SqlBackendKey -> UserId)
|
||||||
|
|
||||||
|
payloadViewers = Map.findWithDefault Set.empty wpl $ toNullable . wpvViewers <$> wgPayloadView 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
|
||||||
|
return Authorized
|
||||||
|
|
||||||
case route of
|
case route of
|
||||||
GlobalWorkflowInstanceR win GWIInitiateR -> wInitiate win WSGlobal
|
_ | Just (rScope, WorkflowInstanceR win WIInitiateR) <- route ^? _WorkflowScopeRoute -> wInitiate win rScope
|
||||||
GlobalWorkflowWorkflowR cID GWWWorkflowR -> wWorkflow isWrite cID
|
_ | Just (_, WorkflowWorkflowR cID WWWorkflowR) <- route ^? _WorkflowScopeRoute -> wWorkflow isWrite cID
|
||||||
|
_ | Just (_, WorkflowWorkflowR wwCID (WWFilesR wpl stCID)) <- route ^? _WorkflowScopeRoute -> wFiles wwCID wpl stCID
|
||||||
r -> $unsupportedAuthPredicate AuthWorkflow r
|
r -> $unsupportedAuthPredicate AuthWorkflow r
|
||||||
tagAccessPredicate AuthRead = APPure $ \_ _ isWrite -> do
|
tagAccessPredicate AuthRead = APPure $ \_ _ isWrite -> do
|
||||||
MsgRenderer mr <- ask
|
MsgRenderer mr <- ask
|
||||||
@ -1625,7 +1646,7 @@ evalWorkflowRoleFor' tagActive mAuthId mwwId wRole route isWrite = $cachedHereBi
|
|||||||
uid <- maybeExceptT AuthenticationRequired $ return mAuthId
|
uid <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||||
wwId <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoPayload) $ return mwwId
|
wwId <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoPayload) $ return mwwId
|
||||||
WorkflowWorkflow{..} <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoSuchWorkflowWorkflow) . lift . withReaderT (projectBackend @SqlReadBackend) $ get wwId
|
WorkflowWorkflow{..} <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoSuchWorkflowWorkflow) . lift . withReaderT (projectBackend @SqlReadBackend) $ get wwId
|
||||||
let uids = maybe Set.empty getLast . foldMap (fmap Last . assertM' (not . Set.null)) . workflowStatePayload workflowRolePayloadLabel $ _DBWorkflowState # workflowWorkflowState
|
let uids = maybe Set.empty getLast . foldMap (fmap Last) . workflowStatePayload workflowRolePayloadLabel $ _DBWorkflowState # workflowWorkflowState
|
||||||
unless (uid `Set.member` uids) $
|
unless (uid `Set.member` uids) $
|
||||||
throwE =<< unauthorizedI MsgWorkflowRoleUserMismatch
|
throwE =<< unauthorizedI MsgWorkflowRoleUserMismatch
|
||||||
return Authorized
|
return Authorized
|
||||||
|
|||||||
@ -15,6 +15,7 @@ module Foundation.I18n
|
|||||||
, ShortStudyFieldType(..)
|
, ShortStudyFieldType(..)
|
||||||
, StudyDegreeTermType(..)
|
, StudyDegreeTermType(..)
|
||||||
, ErrorResponseTitle(..)
|
, ErrorResponseTitle(..)
|
||||||
|
, WorkflowPayloadBool(..)
|
||||||
, UniWorXMessages(..)
|
, UniWorXMessages(..)
|
||||||
, uniworxMessages
|
, uniworxMessages
|
||||||
, unRenderMessage, unRenderMessage', unRenderMessageLenient
|
, unRenderMessage, unRenderMessage', unRenderMessageLenient
|
||||||
@ -348,6 +349,10 @@ instance HasResolution a => ToMessage (Fixed a) where
|
|||||||
newtype ErrorResponseTitle = ErrorResponseTitle ErrorResponse
|
newtype ErrorResponseTitle = ErrorResponseTitle ErrorResponse
|
||||||
embedRenderMessageVariant ''UniWorX ''ErrorResponseTitle ("ErrorResponseTitle" <>)
|
embedRenderMessageVariant ''UniWorX ''ErrorResponseTitle ("ErrorResponseTitle" <>)
|
||||||
|
|
||||||
|
newtype WorkflowPayloadBool = WorkflowPayloadBool { unWorkflowPayloadBool :: Bool }
|
||||||
|
embedRenderMessageVariant ''UniWorX ''WorkflowPayloadBool ("WorkflowPayloadBool" <>)
|
||||||
|
|
||||||
|
|
||||||
newtype UniWorXMessages = UniWorXMessages [SomeMessage UniWorX]
|
newtype UniWorXMessages = UniWorXMessages [SomeMessage UniWorX]
|
||||||
deriving stock (Generic, Typeable)
|
deriving stock (Generic, Typeable)
|
||||||
deriving newtype (Semigroup, Monoid)
|
deriving newtype (Semigroup, Monoid)
|
||||||
|
|||||||
@ -359,6 +359,7 @@ instance BearerAuthSite UniWorX => YesodBreadcrumbs UniWorX where
|
|||||||
breadcrumb GlobalWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbGlobalWorkflowWorkflowList $ Just GlobalWorkflowInstanceListR
|
breadcrumb GlobalWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbGlobalWorkflowWorkflowList $ Just GlobalWorkflowInstanceListR
|
||||||
breadcrumb (GlobalWorkflowWorkflowR cID sRoute) = case sRoute of
|
breadcrumb (GlobalWorkflowWorkflowR cID sRoute) = case sRoute of
|
||||||
GWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) $ Just GlobalWorkflowInstanceListR
|
GWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) $ Just GlobalWorkflowInstanceListR
|
||||||
|
GWWFilesR _ _ -> i18nCrumb MsgBreadcrumbWorkflowWorkflowFiles . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
|
||||||
GWWEditR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowEdit . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
|
GWWEditR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowEdit . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
|
||||||
GWWDeleteR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowDelete . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
|
GWWDeleteR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowDelete . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import Control.Monad.Writer.Class (MonadWriter(..))
|
|||||||
|
|
||||||
import Yesod.Core.Types (GHState(..), HandlerData(..), RunHandlerEnv(rheSite, rheChild))
|
import Yesod.Core.Types (GHState(..), HandlerData(..), RunHandlerEnv(rheSite, rheChild))
|
||||||
|
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
|
||||||
yesodMiddleware :: ( BearerAuthSite UniWorX
|
yesodMiddleware :: ( BearerAuthSite UniWorX
|
||||||
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
|
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
|
||||||
@ -160,6 +162,7 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
|
|||||||
, ncExternalExam
|
, ncExternalExam
|
||||||
, ncAdminWorkflowDefinition
|
, ncAdminWorkflowDefinition
|
||||||
, ncWorkflowInstance
|
, ncWorkflowInstance
|
||||||
|
, ncWorkflowPayloadLabel
|
||||||
, verifySubmission
|
, verifySubmission
|
||||||
, verifyCourseApplication
|
, verifyCourseApplication
|
||||||
, verifyCourseNews
|
, verifyCourseNews
|
||||||
@ -250,6 +253,14 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
|
|||||||
caseChanged win workflowInstanceName
|
caseChanged win workflowInstanceName
|
||||||
return $ route
|
return $ route
|
||||||
& typesUsing @RouteChildren @WorkflowInstanceName . filtered (== win) .~ workflowInstanceName
|
& typesUsing @RouteChildren @WorkflowInstanceName . filtered (== win) .~ workflowInstanceName
|
||||||
|
ncWorkflowPayloadLabel = maybeOrig $ \route -> do
|
||||||
|
(_, 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
|
||||||
|
(caseChanged `on` unWorkflowPayloadLabel) wpl wpl'
|
||||||
|
return $ route
|
||||||
|
& typesUsing @RouteChildren @WorkflowPayloadLabel . filtered (== wpl) .~ wpl'
|
||||||
verifySubmission = maybeOrig $ \route -> do
|
verifySubmission = maybeOrig $ \route -> do
|
||||||
CSubmissionR _tid _ssh _csh _shn cID sr <- return route
|
CSubmissionR _tid _ssh _csh _shn cID sr <- return route
|
||||||
sId <- $cachedHereBinary cID $ decrypt cID
|
sId <- $cachedHereBinary cID $ decrypt cID
|
||||||
|
|||||||
@ -18,7 +18,7 @@ data WorkflowInstanceR
|
|||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
data WorkflowWorkflowR
|
data WorkflowWorkflowR
|
||||||
= WWWorkflowR | WWEditR | WWDeleteR
|
= WWWorkflowR | WWFilesR WorkflowPayloadLabel CryptoUUIDWorkflowStateIndex | WWEditR | WWDeleteR
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
|
|
||||||
@ -31,30 +31,32 @@ _WorkflowScopeRoute = prism' (uncurry toRoute) toWorkflowScopeRoute
|
|||||||
where
|
where
|
||||||
toRoute = \case
|
toRoute = \case
|
||||||
WSGlobal -> \case
|
WSGlobal -> \case
|
||||||
WorkflowInstanceListR -> GlobalWorkflowInstanceListR
|
WorkflowInstanceListR -> GlobalWorkflowInstanceListR
|
||||||
WorkflowInstanceNewR -> GlobalWorkflowInstanceNewR
|
WorkflowInstanceNewR -> GlobalWorkflowInstanceNewR
|
||||||
WorkflowInstanceR win subRoute -> GlobalWorkflowInstanceR win $ case subRoute of
|
WorkflowInstanceR win subRoute -> GlobalWorkflowInstanceR win $ case subRoute of
|
||||||
WIEditR -> GWIEditR
|
WIEditR -> GWIEditR
|
||||||
WIDeleteR -> GWIDeleteR
|
WIDeleteR -> GWIDeleteR
|
||||||
WIWorkflowsR -> GWIWorkflowsR
|
WIWorkflowsR -> GWIWorkflowsR
|
||||||
WIInitiateR -> GWIInitiateR
|
WIInitiateR -> GWIInitiateR
|
||||||
WorkflowWorkflowListR -> GlobalWorkflowWorkflowListR
|
WorkflowWorkflowListR -> GlobalWorkflowWorkflowListR
|
||||||
WorkflowWorkflowR cID subRoute -> GlobalWorkflowWorkflowR cID $ case subRoute of
|
WorkflowWorkflowR wwCID subRoute -> GlobalWorkflowWorkflowR wwCID $ case subRoute of
|
||||||
WWWorkflowR -> GWWWorkflowR
|
WWWorkflowR -> GWWWorkflowR
|
||||||
WWEditR -> GWWEditR
|
WWFilesR wpl stCID -> GWWFilesR wpl stCID
|
||||||
WWDeleteR -> GWWDeleteR
|
WWEditR -> GWWEditR
|
||||||
|
WWDeleteR -> GWWDeleteR
|
||||||
other -> error $ "not implemented _WorkflowScopeRoute for: " <> show other
|
other -> error $ "not implemented _WorkflowScopeRoute for: " <> show other
|
||||||
toWorkflowScopeRoute = \case
|
toWorkflowScopeRoute = \case
|
||||||
GlobalWorkflowInstanceListR -> Just ( WSGlobal, WorkflowInstanceListR )
|
GlobalWorkflowInstanceListR -> Just ( WSGlobal, WorkflowInstanceListR )
|
||||||
GlobalWorkflowInstanceNewR -> Just ( WSGlobal, WorkflowInstanceNewR )
|
GlobalWorkflowInstanceNewR -> Just ( WSGlobal, WorkflowInstanceNewR )
|
||||||
GlobalWorkflowInstanceR win subRoute -> Just . (WSGlobal, ) . WorkflowInstanceR win $ case subRoute of
|
GlobalWorkflowInstanceR win subRoute -> Just . (WSGlobal, ) . WorkflowInstanceR win $ case subRoute of
|
||||||
GWIEditR -> WIEditR
|
GWIEditR -> WIEditR
|
||||||
GWIDeleteR -> WIDeleteR
|
GWIDeleteR -> WIDeleteR
|
||||||
GWIWorkflowsR -> WIWorkflowsR
|
GWIWorkflowsR -> WIWorkflowsR
|
||||||
GWIInitiateR -> WIInitiateR
|
GWIInitiateR -> WIInitiateR
|
||||||
GlobalWorkflowWorkflowListR -> Just ( WSGlobal, WorkflowWorkflowListR )
|
GlobalWorkflowWorkflowListR -> Just ( WSGlobal, WorkflowWorkflowListR )
|
||||||
GlobalWorkflowWorkflowR cID subRoute -> Just . (WSGlobal, ) . WorkflowWorkflowR cID $ case subRoute of
|
GlobalWorkflowWorkflowR wwCID subRoute -> Just . (WSGlobal, ) . WorkflowWorkflowR wwCID $ case subRoute of
|
||||||
GWWWorkflowR -> WWWorkflowR
|
GWWWorkflowR -> WWWorkflowR
|
||||||
GWWEditR -> WWEditR
|
GWWFilesR wpl stCID -> WWFilesR wpl stCID
|
||||||
GWWDeleteR -> WWDeleteR
|
GWWEditR -> WWEditR
|
||||||
|
GWWDeleteR -> WWDeleteR
|
||||||
_other -> Nothing
|
_other -> Nothing
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import Utils.Form
|
|||||||
import Utils.Workflow
|
import Utils.Workflow
|
||||||
import Handler.Utils.Form
|
import Handler.Utils.Form
|
||||||
import Handler.Utils.Workflow.CanonicalRoute
|
import Handler.Utils.Workflow.CanonicalRoute
|
||||||
|
import Handler.Utils.Widgets
|
||||||
|
|
||||||
import qualified ListT
|
import qualified ListT
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ import Control.Monad.Trans.RWS.Strict (RWST, evalRWST)
|
|||||||
import Data.Bitraversable
|
import Data.Bitraversable
|
||||||
|
|
||||||
import Data.List (findIndex)
|
import Data.List (findIndex)
|
||||||
|
import qualified Data.List as List (delete)
|
||||||
|
|
||||||
import qualified Data.Aeson as Aeson
|
import qualified Data.Aeson as Aeson
|
||||||
import qualified Data.Scientific as Scientific
|
import qualified Data.Scientific as Scientific
|
||||||
@ -107,6 +109,8 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
|||||||
return (wgeDisplayLabel, wgeForm)
|
return (wgeDisplayLabel, wgeForm)
|
||||||
_other -> mzero
|
_other -> mzero
|
||||||
|
|
||||||
|
guard . not $ null edges
|
||||||
|
|
||||||
-- edgesOptList :: OptionList (WorkflowGraphNodeLabel, WorkflowGraphEdgeLabel)
|
-- edgesOptList :: OptionList (WorkflowGraphNodeLabel, WorkflowGraphEdgeLabel)
|
||||||
edgesOptList <- do
|
edgesOptList <- do
|
||||||
sBoxKey <- secretBoxKey
|
sBoxKey <- secretBoxKey
|
||||||
@ -124,7 +128,7 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
|||||||
let olOptions = concat $ do
|
let olOptions = concat $ do
|
||||||
let optSort = (compareUnicode `on` (Text.toLower . optionDisplay))
|
let optSort = (compareUnicode `on` (Text.toLower . optionDisplay))
|
||||||
<> comparing (fallbackSortKey . optionInternalValue)
|
<> comparing (fallbackSortKey . optionInternalValue)
|
||||||
where fallbackSortKey = toDigest . kmaclazy ("workflow-edge-sorting" :: ByteString) (Saltine.encode sBoxKey) . Binary.encode
|
where fallbackSortKey = toDigest . kmaclazy ("workflow-edge-sorting" :: ByteString) (Saltine.encode sBoxKey) . Binary.encode . (mwwId, )
|
||||||
where toDigest :: Crypto.KMAC (SHAKE256 256) -> ByteString
|
where toDigest :: Crypto.KMAC (SHAKE256 256) -> ByteString
|
||||||
toDigest = BA.convert
|
toDigest = BA.convert
|
||||||
opts <- sortBy optSort olOptions'
|
opts <- sortBy optSort olOptions'
|
||||||
@ -156,7 +160,7 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
|||||||
payloadSpec' <- traverseOf (typesCustom @WorkflowChildren @(WorkflowPayloadSpec FileReference UserId) @(WorkflowPayloadSpec FileReference CryptoUUIDUser) @UserId @CryptoUUIDUser) encrypt payloadSpec
|
payloadSpec' <- traverseOf (typesCustom @WorkflowChildren @(WorkflowPayloadSpec FileReference UserId) @(WorkflowPayloadSpec FileReference CryptoUUIDUser) @UserId @CryptoUUIDUser) encrypt payloadSpec
|
||||||
let toDigest :: Crypto.KMAC (SHAKE256 256) -> ByteString
|
let toDigest :: Crypto.KMAC (SHAKE256 256) -> ByteString
|
||||||
toDigest = BA.convert
|
toDigest = BA.convert
|
||||||
fallbackSortKey = toDigest . kmaclazy ("workflow-edge-form-payload-field-sorting" :: ByteString) (Saltine.encode sBoxKey) $ Aeson.encode payloadSpec'
|
fallbackSortKey = toDigest . kmaclazy ("workflow-edge-form-payload-field-sorting" :: ByteString) (Saltine.encode sBoxKey) $ Aeson.encode (mwwId, payloadSpec')
|
||||||
return (Right fallbackSortKey, payloadSpec)
|
return (Right fallbackSortKey, payloadSpec)
|
||||||
|
|
||||||
orderedFields' <- flip evalStateT 1 . for orderedFields $ \x@(payloadLabel, _) -> do
|
orderedFields' <- flip evalStateT 1 . for orderedFields $ \x@(payloadLabel, _) -> do
|
||||||
@ -187,9 +191,9 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
|||||||
|
|
||||||
fields' <-
|
fields' <-
|
||||||
let payloadReferenceAdjacency = fieldsMap <&> setOf (_2 . _1 . folded . _Left)
|
let payloadReferenceAdjacency = fieldsMap <&> setOf (_2 . _1 . folded . _Left)
|
||||||
fieldsMap :: Map WorkflowPayloadLabel ((Text, Bool), ([Either WorkflowPayloadLabel (Bool, FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId)))], [FieldView UniWorX]))
|
fieldsMap :: Map WorkflowPayloadLabel ((Text, Bool), ([Either WorkflowPayloadLabel (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))], [FieldView UniWorX]))
|
||||||
fieldsMap = Map.fromList fields
|
fieldsMap = Map.fromList fields
|
||||||
resolveReferences :: forall i. Topograph.G WorkflowPayloadLabel i -> [(WorkflowPayloadLabel, ((Text, Bool), ([(Bool, FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId)))], [FieldView UniWorX])))]
|
resolveReferences :: forall i. Topograph.G WorkflowPayloadLabel i -> [(WorkflowPayloadLabel, ((Text, Bool), ([(Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))], [FieldView UniWorX])))]
|
||||||
resolveReferences Topograph.G{gVertices, gFromVertex} = resort . Map.toList . flip execState Map.empty . for topoOrder $ \payloadLabel -> whenIsJust (Map.lookup payloadLabel fieldsMap) $ \(payloadDisplay, (payloadRes, payloadFieldViews)) -> State.modify' $ \oldState ->
|
resolveReferences Topograph.G{gVertices, gFromVertex} = resort . Map.toList . flip execState Map.empty . for topoOrder $ \payloadLabel -> whenIsJust (Map.lookup payloadLabel fieldsMap) $ \(payloadDisplay, (payloadRes, payloadFieldViews)) -> State.modify' $ \oldState ->
|
||||||
let payloadRes' = flip concatMap payloadRes $ \case
|
let payloadRes' = flip concatMap payloadRes $ \case
|
||||||
Right res -> pure res
|
Right res -> pure res
|
||||||
@ -202,7 +206,7 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
|||||||
|
|
||||||
fmap Map.fromList . for fields' $ \(payloadLabel, ((payloadDisplayLabel, isOptional), (payloadRes, payloadFieldViews))) -> (payloadLabel, ) <$> do
|
fmap Map.fromList . for fields' $ \(payloadLabel, ((payloadDisplayLabel, isOptional), (payloadRes, payloadFieldViews))) -> (payloadLabel, ) <$> do
|
||||||
$logWarnS "WorkflowEdgeForm" $ toPathPiece payloadLabel <> ": " <> tshow payloadRes
|
$logWarnS "WorkflowEdgeForm" $ toPathPiece payloadLabel <> ": " <> tshow payloadRes
|
||||||
let payloadRes' = let res = foldMap (views _2 . fmap $ maybe Set.empty Set.singleton) payloadRes
|
let payloadRes' = let res = foldMap (views _2 . fmap $ maybe Set.empty (Set.fromList . otoList)) payloadRes
|
||||||
in if | doErrMsg -> FormFailure $ view _FormFailure res ++ pure (mr $ MsgWorkflowEdgeFormPayloadOneFieldRequiredFor payloadDisplayLabel)
|
in if | doErrMsg -> FormFailure $ view _FormFailure res ++ pure (mr $ MsgWorkflowEdgeFormPayloadOneFieldRequiredFor payloadDisplayLabel)
|
||||||
| otherwise -> res
|
| otherwise -> res
|
||||||
doErrMsg = flip none payloadRes $ \res -> view _1 res || hasn't (_2 . _FormSuccess) res
|
doErrMsg = flip none payloadRes $ \res -> view _1 res || hasn't (_2 . _FormSuccess) res
|
||||||
@ -235,25 +239,52 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do
|
|||||||
|
|
||||||
workflowEdgePayloadFields :: [WorkflowPayloadSpec FileReference UserId]
|
workflowEdgePayloadFields :: [WorkflowPayloadSpec FileReference UserId]
|
||||||
-> Maybe [WorkflowFieldPayloadW FileReference UserId]
|
-> Maybe [WorkflowFieldPayloadW FileReference UserId]
|
||||||
-> WForm Handler ([Either WorkflowPayloadLabel (Bool, FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId)))], All) -- ^ @isFilled@, @foldMap ala All . map isOptional@
|
-> WForm Handler ([Either WorkflowPayloadLabel (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))], All) -- ^ @isFilled@, @foldMap ala All . map isOptional@
|
||||||
workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpecField) Nothing . fromMaybe []
|
workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpecField) Nothing . fromMaybe []
|
||||||
where
|
where
|
||||||
renderSpecField :: WorkflowPayloadSpec FileReference UserId
|
renderSpecField :: WorkflowPayloadSpec FileReference UserId
|
||||||
-> ExceptT WorkflowPayloadLabel (RWST (Maybe (Text -> Text)) All [WorkflowFieldPayloadW FileReference UserId] (MForm (WriterT [FieldView UniWorX] Handler))) (Bool, FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId)))
|
-> ExceptT WorkflowPayloadLabel (RWST (Maybe (Text -> Text)) All [WorkflowFieldPayloadW FileReference UserId] (MForm (WriterT [FieldView UniWorX] Handler))) (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))
|
||||||
renderSpecField (WorkflowPayloadSpec (specField :: WorkflowPayloadField FileReference UserId payload)) = do
|
renderSpecField (WorkflowPayloadSpec (specField :: WorkflowPayloadField FileReference UserId payload)) = do
|
||||||
let f isOpt fld fs mx = lift . (<* tell (All isOpt)) . lift $ over (_2 . mapped . mapped) (review $ _WorkflowFieldPayloadW . _WorkflowFieldPayload) . bool (is (_FormSuccess . _Just) &&& id) (True, ) isOpt <$> wopt fld fs (Just <$> mx)
|
let f' :: forall payload' payload''.
|
||||||
|
_
|
||||||
|
=> (payload' -> Maybe (NonEmpty payload''))
|
||||||
|
-> Bool -- ^ @isOpt@
|
||||||
|
-> Field Handler payload'
|
||||||
|
-> FieldSettings UniWorX
|
||||||
|
-> Maybe payload'
|
||||||
|
-> _ ((Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))))
|
||||||
|
f' toNonEmpty' isOpt fld fs mx = lift . (<* tell (All isOpt)) . lift $ over (_2 . mapped) (fmap (fmap . review $ _WorkflowFieldPayloadW . _WorkflowFieldPayload) . toNonEmpty' =<<) . bool (is (_FormSuccess . _Just) &&& id) (True, ) isOpt <$> wopt fld fs (Just <$> mx)
|
||||||
|
f :: forall payload'.
|
||||||
|
_
|
||||||
|
=> Bool -- ^ @isOpt@
|
||||||
|
-> Field Handler payload'
|
||||||
|
-> FieldSettings UniWorX
|
||||||
|
-> Maybe payload'
|
||||||
|
-> _ ((Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))))
|
||||||
|
f = f' (nonEmpty . pure)
|
||||||
|
extractPrevs :: forall payload' m xs.
|
||||||
|
( IsWorkflowFieldPayload' FileReference UserId payload'
|
||||||
|
, State.MonadState [WorkflowFieldPayloadW FileReference UserId] m
|
||||||
|
)
|
||||||
|
=> (payload' -> Maybe xs -> Maybe xs)
|
||||||
|
-> m (Maybe xs)
|
||||||
|
extractPrevs accum = State.state $ foldl' go (Nothing, []) . map (matching $ _WorkflowFieldPayloadW @payload' @FileReference @UserId . _WorkflowFieldPayload)
|
||||||
|
where go (mPrev', xs) (Left x) = (mPrev', xs ++ [x])
|
||||||
|
go (acc, xs) (Right p) = case accum p acc of
|
||||||
|
acc'@(Just _) -> (acc', xs)
|
||||||
|
Nothing -> (acc, xs ++ [_WorkflowFieldPayloadW @payload' @FileReference @UserId . _WorkflowFieldPayload # p])
|
||||||
extractPrev :: forall payload' m.
|
extractPrev :: forall payload' m.
|
||||||
( IsWorkflowFieldPayload FileReference UserId payload'
|
( IsWorkflowFieldPayload' FileReference UserId payload'
|
||||||
, State.MonadState [WorkflowFieldPayloadW FileReference UserId] m
|
, State.MonadState [WorkflowFieldPayloadW FileReference UserId] m
|
||||||
)
|
)
|
||||||
=> m (Maybe payload')
|
=> m (Maybe payload')
|
||||||
extractPrev = State.state $ foldl' go (Nothing, []) . map (matching $ _WorkflowFieldPayloadW . _WorkflowFieldPayload)
|
extractPrev = extractPrevs $ \p -> \case
|
||||||
where go (mPrev' , xs) (Left x ) = (mPrev', xs ++ [x])
|
Nothing -> Just p
|
||||||
go (Nothing, xs) (Right p ) = (Just p, xs)
|
Just _ -> Nothing
|
||||||
go (Just p , xs) (Right p') = (Just p, xs ++ [_WorkflowFieldPayloadW . _WorkflowFieldPayload # p'])
|
|
||||||
wSetTooltip' :: _ => Maybe Html -> t' (t (MForm (WriterT [FieldView UniWorX] Handler))) a -> t' (t (MForm (WriterT [FieldView UniWorX] Handler))) a
|
wSetTooltip' :: _ => Maybe Html -> t' (t (MForm (WriterT [FieldView UniWorX] Handler))) a -> t' (t (MForm (WriterT [FieldView UniWorX] Handler))) a
|
||||||
wSetTooltip' tip = hoist (hoist (wSetTooltip tip))
|
wSetTooltip' tip = hoist (hoist (wSetTooltip tip))
|
||||||
|
|
||||||
|
MsgRenderer mr <- getMsgRenderer
|
||||||
LanguageSelectI18n{..} <- getLanguageSelectI18n
|
LanguageSelectI18n{..} <- getLanguageSelectI18n
|
||||||
mNudge <- ask
|
mNudge <- ask
|
||||||
|
|
||||||
@ -262,7 +293,7 @@ workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpec
|
|||||||
prev <- extractPrev @Text
|
prev <- extractPrev @Text
|
||||||
wSetTooltip' (fmap slI18n wpftTooltip) $
|
wSetTooltip' (fmap slI18n wpftTooltip) $
|
||||||
f wpftOptional
|
f wpftOptional
|
||||||
(textField & cfStrip)
|
(bool (textField & cfStrip) (textareaField & isoField _Wrapped & cfStrip) wpftLarge)
|
||||||
( fsl (slI18n wpftLabel)
|
( fsl (slI18n wpftLabel)
|
||||||
& maybe id (addPlaceholder . slI18n) wpftPlaceholder
|
& maybe id (addPlaceholder . slI18n) wpftPlaceholder
|
||||||
& maybe id (addName . ($ "text")) mNudge
|
& maybe id (addName . ($ "text")) mNudge
|
||||||
@ -294,10 +325,23 @@ workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpec
|
|||||||
& maybe id (addName . ($ "bool")) mNudge
|
& maybe id (addName . ($ "bool")) mNudge
|
||||||
)
|
)
|
||||||
(prev <|> wpfbDefault)
|
(prev <|> wpfbDefault)
|
||||||
|
WorkflowPayloadFieldDay{..} -> do
|
||||||
|
prev <- extractPrev @Day
|
||||||
|
wSetTooltip' (fmap slI18n wpfdTooltip) $
|
||||||
|
f wpfdOptional
|
||||||
|
dayField
|
||||||
|
( fsl (slI18n wpfdLabel)
|
||||||
|
& maybe id (addName . ($ "day")) mNudge
|
||||||
|
)
|
||||||
|
(prev <|> wpfdDefault)
|
||||||
WorkflowPayloadFieldFile{..} -> do
|
WorkflowPayloadFieldFile{..} -> do
|
||||||
fRefs <- extractPrev @(Set FileReference)
|
fRefs <- extractPrevs @FileReference $ \p -> if
|
||||||
|
| fieldMultiple wpffConfig -> Just . maybe (Set.singleton p) (Set.insert p)
|
||||||
|
| otherwise -> \case
|
||||||
|
Nothing -> Just $ Set.singleton p
|
||||||
|
Just _ -> Nothing
|
||||||
wSetTooltip' (fmap slI18n wpffTooltip) $
|
wSetTooltip' (fmap slI18n wpffTooltip) $
|
||||||
f wpffOptional
|
f' (nonEmpty . Set.toList) wpffOptional
|
||||||
(convertFieldM (\p -> runConduit $ transPipe liftHandler p .| C.foldMap Set.singleton) yieldMany . genericFileField $ return wpffConfig)
|
(convertFieldM (\p -> runConduit $ transPipe liftHandler p .| C.foldMap Set.singleton) yieldMany . genericFileField $ return wpffConfig)
|
||||||
( fsl (slI18n wpffLabel)
|
( fsl (slI18n wpffLabel)
|
||||||
& maybe id (addName . ($ "file")) mNudge
|
& maybe id (addName . ($ "file")) mNudge
|
||||||
@ -316,29 +360,44 @@ workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpec
|
|||||||
)
|
)
|
||||||
(fRefs <|> wpfuDefault)
|
(fRefs <|> wpfuDefault)
|
||||||
WorkflowPayloadFieldCaptureUser -> do
|
WorkflowPayloadFieldCaptureUser -> do
|
||||||
mAuthId <- liftHandler maybeAuthId
|
mAuthId <- liftHandler maybeAuth
|
||||||
case mAuthId of
|
case mAuthId of
|
||||||
Just uid -> (True, FormSuccess $ _Just . _WorkflowFieldPayloadW . _WorkflowFieldPayload # uid) <$ tell (All True)
|
Just (Entity uid User{userDisplayName, userSurname}) -> do
|
||||||
|
fvId <- newIdent
|
||||||
|
State.modify . List.delete $ _WorkflowFieldPayloadW . _WorkflowFieldPayload # uid
|
||||||
|
lift . lift . lift . tell $ pure FieldView
|
||||||
|
{ fvLabel = [shamlet|#{mr MsgWorkflowEdgeFormFieldCaptureUserLabel}|]
|
||||||
|
, fvTooltip = Nothing
|
||||||
|
, fvId
|
||||||
|
, fvInput = [whamlet|
|
||||||
|
$newline never
|
||||||
|
<span ##{fvId}>
|
||||||
|
^{nameWidget userDisplayName userSurname}
|
||||||
|
|]
|
||||||
|
, fvErrors = Nothing
|
||||||
|
, fvRequired = False
|
||||||
|
}
|
||||||
|
(True, FormSuccess . Just . (:| []) $ _WorkflowFieldPayloadW . _WorkflowFieldPayload # uid) <$ tell (All True)
|
||||||
Nothing -> (False, FormMissing) <$ tell (All False)
|
Nothing -> (False, FormMissing) <$ tell (All False)
|
||||||
WorkflowPayloadFieldReference{..} -> throwE wpfrTarget
|
WorkflowPayloadFieldReference{..} -> throwE wpfrTarget
|
||||||
WorkflowPayloadFieldMultiple{..} -> do
|
WorkflowPayloadFieldMultiple{..} -> do
|
||||||
fRefs <- extractPrev @(NonEmpty (WorkflowFieldPayloadW FileReference UserId))
|
fRefs <- nonEmpty <$> State.state (maybe (, []) (splitAt . fromIntegral) $ (+ wpfmMin) <$> wpfmRange)
|
||||||
miIdent <- newIdent
|
miIdent <- newIdent
|
||||||
wSetTooltip' (fmap slI18n wpfmTooltip) $
|
wSetTooltip' (fmap slI18n wpfmTooltip) $
|
||||||
let mPrev' :: Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))
|
let mPrev' :: Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))
|
||||||
mPrev' = fRefs <|> wpfmDefault
|
mPrev' = fRefs <|> wpfmDefault
|
||||||
mPrev :: Maybe (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId), Maybe (WorkflowFieldPayloadW FileReference UserId)))
|
mPrev :: Maybe (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId), Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))
|
||||||
mPrev = Just . Map.fromList . zip [0..] . ensureLength . map (\x -> (Just x, Just x)) $ maybe [] otoList mPrev'
|
mPrev = Just . Map.fromList . zip [0..] . ensureLength . map (\x -> (Just x, Just $ x :| [])) $ mPrev' ^.. _Just . folded
|
||||||
where
|
where
|
||||||
ensureLength :: forall a. [(Maybe a, Maybe a)] -> [(Maybe a, Maybe a)]
|
ensureLength :: forall a b. [(Maybe a, Maybe b)] -> [(Maybe a, Maybe b)]
|
||||||
ensureLength = (\l -> (l ++) $ replicate (fromIntegral wpfmMin - length l) (Nothing, Nothing)) . maybe id (take . fromIntegral) ((+ wpfmMin) <$> wpfmRange)
|
ensureLength = (\l -> (l ++) $ replicate (fromIntegral wpfmMin - length l) (Nothing, Nothing)) . maybe id (take . fromIntegral) ((+ wpfmMin) <$> wpfmRange)
|
||||||
mangleResult :: forall a.
|
mangleResult :: forall a.
|
||||||
FormResult (Map ListPosition (a, Maybe (WorkflowFieldPayloadW FileReference UserId)))
|
FormResult (Map ListPosition (a, Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))
|
||||||
-> (Bool, FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId)))
|
-> (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))
|
||||||
-- FieldMultiple are always filled since `massInput` ensures cardinality constraints (iff @mPrev'@ correctly initializes `massInput` with a list of fields of the appropriate length)
|
-- FieldMultiple are always filled since `massInput` ensures cardinality constraints (iff @mPrev'@ correctly initializes `massInput` with a list of fields of the appropriate length)
|
||||||
mangleResult res = case matching _FormSuccess res of
|
mangleResult res = case matching _FormSuccess res of
|
||||||
Right ress
|
Right ress
|
||||||
-> (True, FormSuccess . fmap (review $ _WorkflowFieldPayloadW . _WorkflowFieldPayload) . nonEmpty $ ress ^.. folded . _2 . _Just)
|
-> (True, FormSuccess . nonEmpty $ ress ^.. folded . _2 . _Just . folded)
|
||||||
Left res'
|
Left res'
|
||||||
-> (False, res')
|
-> (False, res')
|
||||||
runMI :: forall a.
|
runMI :: forall a.
|
||||||
@ -361,31 +420,30 @@ workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpec
|
|||||||
-> FieldView UniWorX
|
-> FieldView UniWorX
|
||||||
-> Maybe (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)) -> FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)))), Widget))
|
-> Maybe (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)) -> FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)))), Widget))
|
||||||
miAdd _pos _dim nudge submitView = Just $ \csrf -> over (_1 . _FormSuccess) tweakRes <$> miForm nudge (Left submitView) csrf
|
miAdd _pos _dim nudge submitView = Just $ \csrf -> over (_1 . _FormSuccess) tweakRes <$> miForm nudge (Left submitView) csrf
|
||||||
where tweakRes :: Maybe (WorkflowFieldPayloadW FileReference UserId)
|
where tweakRes :: Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))
|
||||||
-> Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId))
|
-> Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId))
|
||||||
-> FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)))
|
-> FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)))
|
||||||
tweakRes newDat prevData = Map.fromList . zip [startKey..] <$> pure (pure newDat)
|
tweakRes newDat prevData = Map.fromList . zip [startKey..] <$> pure (map Just $ newDat ^.. _Just . folded)
|
||||||
where startKey = maybe 0 succ $ fst <$> Map.lookupMax prevData
|
where startKey = maybe 0 succ $ fst <$> Map.lookupMax prevData
|
||||||
|
|
||||||
miCell :: ListPosition
|
miCell :: ListPosition
|
||||||
-> Maybe (WorkflowFieldPayloadW FileReference UserId)
|
-> Maybe (WorkflowFieldPayloadW FileReference UserId)
|
||||||
-> Maybe (Maybe (WorkflowFieldPayloadW FileReference UserId))
|
-> Maybe (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))
|
||||||
-> (Text -> Text)
|
-> (Text -> Text)
|
||||||
-> (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId)), Widget))
|
-> (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))), Widget))
|
||||||
miCell _pos dat mPrev'' nudge = miForm nudge . Right $ fromMaybe dat mPrev''
|
miCell _pos dat mPrev'' nudge = miForm nudge . Right $ fromMaybe (fmap (:| []) dat) mPrev''
|
||||||
|
|
||||||
miForm :: (Text -> Text)
|
miForm :: (Text -> Text)
|
||||||
-> Either (FieldView UniWorX) (Maybe (WorkflowFieldPayloadW FileReference UserId))
|
-> Either (FieldView UniWorX) (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))
|
||||||
-> (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId)), Widget))
|
-> (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))), Widget))
|
||||||
miForm nudge mode csrf = do
|
miForm nudge mode csrf = do
|
||||||
let runSpecRender :: WriterT [FieldView UniWorX] Handler (Either WorkflowPayloadLabel (Bool, FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId))), Ints, Enctype)
|
let runSpecRender :: WriterT [FieldView UniWorX] Handler (Either WorkflowPayloadLabel (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))), Ints, Enctype)
|
||||||
-> ExceptT WorkflowPayloadLabel Handler (((Bool, FormResult (Maybe (WorkflowFieldPayloadW FileReference UserId))), [FieldView UniWorX]), Ints, Enctype)
|
-> ExceptT WorkflowPayloadLabel Handler (((Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))), [FieldView UniWorX]), Ints, Enctype)
|
||||||
runSpecRender mSR = do
|
runSpecRender mSR = do
|
||||||
((eRes, s, w), fvs) <- lift $ runWriterT mSR
|
((eRes, s, w), fvs) <- lift $ runWriterT mSR
|
||||||
ExceptT . return $ (, s, w) . (, fvs) <$> eRes
|
ExceptT . return $ (, s, w) . (, fvs) <$> eRes
|
||||||
((fFilled, fmRes), fvs') <- mapRWST runSpecRender . fmap (view _1) $ evalRWST (runExceptT $ renderSpecField wpfmSub) (Just $ fromMaybe id mNudge . nudge) (mode ^.. _Right . _Just)
|
((fFilled, fmRes), fvs') <- mapRWST runSpecRender . fmap (view _1) $ evalRWST (runExceptT $ renderSpecField wpfmSub) (Just $ fromMaybe id mNudge . nudge) (mode ^.. _Right . _Just . folded)
|
||||||
|
|
||||||
MsgRenderer mr <- getMsgRenderer
|
|
||||||
let fFilled' = fFilled || isn't _FormSuccess fmRes
|
let fFilled' = fFilled || isn't _FormSuccess fmRes
|
||||||
fmRes' | not fFilled' = FormFailure . pure . maybe (mr MsgValueRequired) (mr . valueRequired) $ fvs ^? _head . to fvLabel'
|
fmRes' | not fFilled' = FormFailure . pure . maybe (mr MsgValueRequired) (mr . valueRequired) $ fvs ^? _head . to fvLabel'
|
||||||
| otherwise = fmRes
|
| otherwise = fmRes
|
||||||
@ -435,7 +493,7 @@ workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpec
|
|||||||
p -> Maybe (SomeRoute UniWorX)
|
p -> Maybe (SomeRoute UniWorX)
|
||||||
miButtonAction _ = Nothing
|
miButtonAction _ = Nothing
|
||||||
|
|
||||||
miLayout :: MassInputLayout ListLength (Maybe (WorkflowFieldPayloadW FileReference UserId)) (Maybe (WorkflowFieldPayloadW FileReference UserId))
|
miLayout :: MassInputLayout ListLength (Maybe (WorkflowFieldPayloadW FileReference UserId)) (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))
|
||||||
miLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/workflow-payload-field-multiple/layout")
|
miLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/workflow-payload-field-multiple/layout")
|
||||||
in runMI . fmap mangleResult $ massInputW MassInput{..} (fslI $ slI18n wpfmLabel) False mPrev
|
in runMI . fmap mangleResult $ massInputW MassInput{..} (fslI $ slI18n wpfmLabel) False mPrev
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,8 @@ import qualified Data.List.NonEmpty as NonEmpty
|
|||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
|
|
||||||
|
import Utils.Workflow.Lint
|
||||||
|
|
||||||
|
|
||||||
newtype FileIdent = FileIdent (CI Text)
|
newtype FileIdent = FileIdent (CI Text)
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
@ -125,6 +127,18 @@ validateWorkflowGraphForm = do
|
|||||||
fIdentsAvailable <- uses _wgfFiles Map.keysSet
|
fIdentsAvailable <- uses _wgfFiles Map.keysSet
|
||||||
forM_ (fIdentsReferenced `Set.difference` fIdentsAvailable) $ tellValidationError . MsgWorkflowFileIdentDoesNotExist . views _Wrapped CI.original
|
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}
|
||||||
|
<ul>
|
||||||
|
$forall issue <- otoList lintIssues
|
||||||
|
<li>
|
||||||
|
#{displayException issue}
|
||||||
|
|]
|
||||||
|
|
||||||
toWorkflowGraphForm :: ( MonadHandler m, HandlerSite m ~ UniWorX
|
toWorkflowGraphForm :: ( MonadHandler m, HandlerSite m ~ UniWorX
|
||||||
)
|
)
|
||||||
=> WorkflowGraph FileReference SqlBackendKey
|
=> WorkflowGraph FileReference SqlBackendKey
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
module Handler.Utils.Workflow.Workflow
|
module Handler.Utils.Workflow.Workflow
|
||||||
( ensureScope
|
( ensureScope
|
||||||
|
, followEdge
|
||||||
|
, followAutomaticEdges, WorkflowAutomaticEdgeException(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
|
import Handler.Utils.Workflow.EdgeForm
|
||||||
|
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
|
||||||
ensureScope :: WorkflowScope TermId SchoolId CourseId -> CryptoFileNameWorkflowWorkflow -> MaybeT DB WorkflowWorkflowId
|
ensureScope :: WorkflowScope TermId SchoolId CourseId -> CryptoFileNameWorkflowWorkflow -> MaybeT DB WorkflowWorkflowId
|
||||||
ensureScope wiScope cID = do
|
ensureScope wiScope cID = do
|
||||||
@ -15,3 +22,60 @@ ensureScope wiScope cID = do
|
|||||||
& _wisCourse %~ view _SqlKey
|
& _wisCourse %~ view _SqlKey
|
||||||
guard $ workflowWorkflowScope == wiScope'
|
guard $ workflowWorkflowScope == wiScope'
|
||||||
return wId
|
return wId
|
||||||
|
|
||||||
|
followEdge :: ( MonadHandler m
|
||||||
|
, HandlerSite m ~ UniWorX
|
||||||
|
, MonadThrow m
|
||||||
|
)
|
||||||
|
=> WorkflowGraph FileReference UserId -> WorkflowEdgeForm -> Maybe (WorkflowState FileReference UserId) -> m (WorkflowState FileReference UserId)
|
||||||
|
followEdge graph edgeRes cState = do
|
||||||
|
act <- workflowEdgeFormToAction edgeRes
|
||||||
|
followAutomaticEdges graph $ maybe id (<>) cState (act `ncons` mempty)
|
||||||
|
|
||||||
|
data WorkflowAutomaticEdgeException
|
||||||
|
= WorkflowAutomaticEdgeCycle [(WorkflowGraphEdgeLabel, WorkflowGraphNodeLabel)]
|
||||||
|
| WorkflowAutomaticEdgeAmbiguity (Set (WorkflowGraphEdgeLabel, WorkflowGraphNodeLabel))
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
deriving anyclass (Exception)
|
||||||
|
|
||||||
|
followAutomaticEdges :: forall m.
|
||||||
|
( MonadIO m
|
||||||
|
, MonadThrow m
|
||||||
|
)
|
||||||
|
=> WorkflowGraph FileReference UserId
|
||||||
|
-> 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
|
||||||
|
-> WorkflowState FileReference UserId
|
||||||
|
-> m (WorkflowState FileReference UserId)
|
||||||
|
go automaticEdgesTaken history
|
||||||
|
| null automaticEdgeOptions = return history
|
||||||
|
| [(edgeLbl, nodeLbl)] <- automaticEdgeOptions = if
|
||||||
|
| (edgeDecisionInput, (edgeLbl, nodeLbl)) `elem` automaticEdgesTaken
|
||||||
|
-> throwM . WorkflowAutomaticEdgeCycle . reverse $ map (view _2) automaticEdgesTaken
|
||||||
|
| otherwise -> do
|
||||||
|
wpTime <- liftIO getCurrentTime
|
||||||
|
let wpUser = Nothing
|
||||||
|
wpPayload = mempty
|
||||||
|
wpTo = nodeLbl
|
||||||
|
wpVia = edgeLbl
|
||||||
|
go ((edgeDecisionInput, (edgeLbl, nodeLbl)) : automaticEdgesTaken) $ history <> (WorkflowAction{..} `ncons` mempty)
|
||||||
|
| otherwise = throwM . WorkflowAutomaticEdgeAmbiguity $ Set.fromList automaticEdgeOptions
|
||||||
|
where
|
||||||
|
cState = wpTo $ last history
|
||||||
|
automaticEdgeOptions = nub $ do
|
||||||
|
(nodeLbl, WGN{..}) <- Map.toList wgNodes
|
||||||
|
(edgeLbl, WorkflowGraphEdgeAutomatic{..}) <- Map.toList wgnEdges
|
||||||
|
guard $ wgeSource == cState
|
||||||
|
whenIsJust wgePayloadRestriction $ guard . checkPayloadRestriction
|
||||||
|
return (edgeLbl, nodeLbl)
|
||||||
|
checkPayloadRestriction :: PredDNF WorkflowPayloadLabel -> Bool
|
||||||
|
checkPayloadRestriction 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
|
||||||
|
dnf' = map (Set.toList . toNullable) . Set.toList $ dnfTerms dnf
|
||||||
|
filledPayloads = Map.keysSet . Map.filter (not . Set.null) $ workflowStateCurrentPayloads history
|
||||||
|
edgeDecisionInput = filledPayloads
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import Utils.Workflow
|
|||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
import Handler.Utils.Workflow.EdgeForm
|
import Handler.Utils.Workflow.EdgeForm
|
||||||
import Handler.Utils.Workflow.CanonicalRoute
|
import Handler.Utils.Workflow.CanonicalRoute
|
||||||
|
import Handler.Utils.Workflow.Workflow (followEdge)
|
||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
import qualified Data.List.NonEmpty as NonEmpty
|
import qualified Data.List.NonEmpty as NonEmpty
|
||||||
@ -23,7 +24,7 @@ postGWIInitiateR win
|
|||||||
|
|
||||||
workflowInstanceInitiateR :: WorkflowInstanceId -> Handler Html
|
workflowInstanceInitiateR :: WorkflowInstanceId -> Handler Html
|
||||||
workflowInstanceInitiateR wiId = do
|
workflowInstanceInitiateR wiId = do
|
||||||
(WorkflowInstance{..}, edgeForm, rScope, mDesc) <- runDB $ do
|
(WorkflowInstance{..}, ((edgeAct, edgeView'), edgeEnc), rScope, mDesc) <- runDB $ do
|
||||||
wi@WorkflowInstance{..} <- get404 wiId
|
wi@WorkflowInstance{..} <- get404 wiId
|
||||||
edgeForm <- maybeT notFound . MaybeT $ workflowEdgeForm (Left wiId) Nothing
|
edgeForm <- maybeT notFound . MaybeT $ workflowEdgeForm (Left wiId) Nothing
|
||||||
rScope <- maybeT notFound . toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope
|
rScope <- maybeT notFound . toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope
|
||||||
@ -37,29 +38,31 @@ workflowInstanceInitiateR wiId = do
|
|||||||
guard $ workflowInstanceDescriptionLanguage == lang
|
guard $ workflowInstanceDescriptionLanguage == lang
|
||||||
return desc
|
return desc
|
||||||
|
|
||||||
return (wi, edgeForm, rScope, mDesc)
|
((edgeRes, edgeView), edgeEnc) <- liftHandler . runFormPost $ renderAForm FormStandard edgeForm
|
||||||
|
|
||||||
((edgeRes, edgeView'), edgeEnc) <- runFormPost $ renderAForm FormStandard edgeForm
|
edgeAct <- formResultMaybe edgeRes $ \edgeRes' -> do
|
||||||
|
workflowWorkflowState <- view _DBWorkflowState <$> followEdge (_DBWorkflowGraph # workflowInstanceGraph) edgeRes' Nothing
|
||||||
formResult edgeRes $ \edgeRes' -> do
|
|
||||||
wwId <- runDB $ do
|
wwId <- insert WorkflowWorkflow
|
||||||
act <- workflowEdgeFormToAction edgeRes'
|
|
||||||
|
|
||||||
insert WorkflowWorkflow
|
|
||||||
{ workflowWorkflowInstance = Just wiId
|
{ workflowWorkflowInstance = Just wiId
|
||||||
, workflowWorkflowScope = workflowInstanceScope
|
, workflowWorkflowScope = workflowInstanceScope
|
||||||
, workflowWorkflowGraph = workflowInstanceGraph
|
, workflowWorkflowGraph = workflowInstanceGraph
|
||||||
, workflowWorkflowState = view _DBWorkflowState $ act `ncons` mempty
|
, workflowWorkflowState
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessageI Success MsgWorkflowInstanceInitiateSuccess
|
return . Just $ do
|
||||||
|
addMessageI Success MsgWorkflowInstanceInitiateSuccess
|
||||||
cID <- encrypt wwId
|
|
||||||
redirectAlternatives $ NonEmpty.fromList
|
cID <- encrypt wwId
|
||||||
[ _WorkflowScopeRoute # ( rScope, WorkflowWorkflowR cID WWWorkflowR )
|
redirectAlternatives $ NonEmpty.fromList
|
||||||
, _WorkflowScopeRoute # ( rScope, WorkflowInstanceR workflowInstanceName WIWorkflowsR )
|
[ _WorkflowScopeRoute # ( rScope, WorkflowWorkflowR cID WWWorkflowR )
|
||||||
, _WorkflowScopeRoute # ( rScope, WorkflowInstanceListR )
|
, _WorkflowScopeRoute # ( rScope, WorkflowInstanceR workflowInstanceName WIWorkflowsR )
|
||||||
]
|
, _WorkflowScopeRoute # ( rScope, WorkflowInstanceListR )
|
||||||
|
]
|
||||||
|
|
||||||
|
return (wi, ((edgeAct, edgeView), edgeEnc), rScope, mDesc)
|
||||||
|
|
||||||
|
sequence_ edgeAct
|
||||||
|
|
||||||
(heading, title) <- case rScope of
|
(heading, title) <- case rScope of
|
||||||
WSGlobal -> return (MsgGlobalWorkflowInstanceInitiateHeading $ maybe (CI.original workflowInstanceName) workflowInstanceDescriptionTitle mDesc, MsgGlobalWorkflowInstanceInitiateTitle)
|
WSGlobal -> return (MsgGlobalWorkflowInstanceInitiateHeading $ maybe (CI.original workflowInstanceName) workflowInstanceDescriptionTitle mDesc, MsgGlobalWorkflowInstanceInitiateTitle)
|
||||||
@ -67,7 +70,12 @@ workflowInstanceInitiateR wiId = do
|
|||||||
|
|
||||||
siteLayoutMsg heading $ do
|
siteLayoutMsg heading $ do
|
||||||
setTitleI title
|
setTitleI title
|
||||||
let edgeView = wrapForm edgeView' def
|
let edgeView = wrapForm edgeView' FormSettings
|
||||||
{ formEncoding = edgeEnc
|
{ formMethod = POST
|
||||||
|
, formAction = Just . SomeRoute $ _WorkflowScopeRoute # (rScope, WorkflowInstanceR workflowInstanceName WIInitiateR)
|
||||||
|
, formEncoding = edgeEnc
|
||||||
|
, formAttrs = []
|
||||||
|
, formSubmit = FormSubmit
|
||||||
|
, formAnchor = Nothing :: Maybe Text
|
||||||
}
|
}
|
||||||
$(widgetFile "workflows/instance-initiate")
|
$(widgetFile "workflows/instance-initiate")
|
||||||
|
|||||||
@ -1,12 +1,55 @@
|
|||||||
module Handler.Workflow.Workflow.Workflow
|
module Handler.Workflow.Workflow.Workflow
|
||||||
( getGWWWorkflowR, postGWWWorkflowR
|
( getGWWWorkflowR, postGWWWorkflowR
|
||||||
|
, getGWWFilesR
|
||||||
, workflowR
|
, workflowR
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
|
import Utils.Form
|
||||||
|
import Utils.Workflow
|
||||||
|
|
||||||
|
import Handler.Utils
|
||||||
|
import Handler.Utils.Workflow.EdgeForm
|
||||||
|
import Handler.Utils.Workflow.CanonicalRoute
|
||||||
import Handler.Utils.Workflow.Workflow
|
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)
|
||||||
|
|
||||||
|
import qualified Crypto.Saltine.Class as Saltine
|
||||||
|
import qualified Data.Binary as Binary
|
||||||
|
import qualified Data.ByteArray as BA
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
data WorkflowHistoryItemActor = WHIASelf | WHIAOther (Maybe (Entity User)) | WHIAHidden | WHIAGone
|
||||||
|
deriving (Generic, Typeable)
|
||||||
|
|
||||||
|
data WorkflowHistoryItem = WorkflowHistoryItem
|
||||||
|
{ whiUser :: Maybe WorkflowHistoryItemActor
|
||||||
|
, whiTime :: UTCTime
|
||||||
|
, 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
|
||||||
|
} deriving (Generic, Typeable)
|
||||||
|
|
||||||
|
makePrisms ''WorkflowHistoryItemActor
|
||||||
|
|
||||||
|
|
||||||
getGWWWorkflowR, postGWWWorkflowR :: CryptoFileNameWorkflowWorkflow -> Handler Html
|
getGWWWorkflowR, postGWWWorkflowR :: CryptoFileNameWorkflowWorkflow -> Handler Html
|
||||||
getGWWWorkflowR = postGWWWorkflowR
|
getGWWWorkflowR = postGWWWorkflowR
|
||||||
@ -16,4 +59,177 @@ postGWWWorkflowR cID = do
|
|||||||
workflowR wId
|
workflowR wId
|
||||||
|
|
||||||
workflowR :: WorkflowWorkflowId -> Handler Html
|
workflowR :: WorkflowWorkflowId -> Handler Html
|
||||||
workflowR = error "not implemented"
|
workflowR wwId = do
|
||||||
|
cID <- encrypt wwId
|
||||||
|
|
||||||
|
(mEdge, rScope, workflowHistory) <- runDB $ do
|
||||||
|
WorkflowWorkflow{..} <- get404 wwId
|
||||||
|
rScope <- maybeT notFound . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
|
||||||
|
mEdgeForm <- workflowEdgeForm (Right wwId) Nothing
|
||||||
|
let canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
|
||||||
|
|
||||||
|
mEdge <- for mEdgeForm $ \edgeForm -> do
|
||||||
|
((edgeRes, edgeView), edgeEnc) <- liftHandler . runFormPost $ renderAForm FormStandard edgeForm
|
||||||
|
edgeAct <- formResultMaybe edgeRes $ \edgeRes' -> do
|
||||||
|
nState <- followEdge (_DBWorkflowGraph # workflowWorkflowGraph) edgeRes' . Just $ _DBWorkflowState # workflowWorkflowState
|
||||||
|
|
||||||
|
update wwId [ WorkflowWorkflowState =. view _DBWorkflowState nState ]
|
||||||
|
|
||||||
|
return . Just $ do
|
||||||
|
addMessageI Success MsgWorkflowWorkflowWorkflowEdgeSuccess
|
||||||
|
|
||||||
|
redirect canonRoute
|
||||||
|
return ((edgeAct, edgeView), edgeEnc)
|
||||||
|
|
||||||
|
workflowHistory <-
|
||||||
|
let go :: forall m.
|
||||||
|
( MonadHandler m
|
||||||
|
, HandlerSite m ~ UniWorX
|
||||||
|
, MonadThrow 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
|
||||||
|
stCID <- encrypt stIx
|
||||||
|
let nodeView nodeLbl = do
|
||||||
|
WorkflowNodeView{..} <- hoistMaybe $ Map.lookup nodeLbl wgNodes >>= wgnViewers
|
||||||
|
guardM $ anyM (otoList wnvViewers) hasWorkflowRole'
|
||||||
|
selectLanguageI18n wnvDisplayLabel
|
||||||
|
whiTime = wpTime
|
||||||
|
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)
|
||||||
|
|
||||||
|
whiFrom <- for wpFrom $ lift . runMaybeT . nodeView
|
||||||
|
|
||||||
|
mAuthId <- maybeAuthId
|
||||||
|
whiUser <- for wpUser $ \wpUser' -> if
|
||||||
|
| is _Just mAuthId
|
||||||
|
, wpUser' == mAuthId -> return WHIASelf
|
||||||
|
| otherwise -> lift . maybeT (return WHIAHidden) $ do
|
||||||
|
viewActors <- hoistMaybe $ preview _wgeViewActor =<< mVia
|
||||||
|
guardM $ anyM (otoList viewActors) hasWorkflowRole'
|
||||||
|
resUser <- lift . lift $ traverse getEntity wpUser'
|
||||||
|
return $ case resUser of
|
||||||
|
Nothing -> WHIAOther Nothing
|
||||||
|
Just Nothing -> WHIAGone
|
||||||
|
Just (Just uEnt) -> WHIAOther $ Just uEnt
|
||||||
|
tell $ pure WorkflowHistoryItem{..}
|
||||||
|
WorkflowGraph{..} = _DBWorkflowGraph # workflowWorkflowGraph
|
||||||
|
wState = otoList $ review _DBWorkflowState workflowWorkflowState
|
||||||
|
in fmap (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..]
|
||||||
|
]
|
||||||
|
return (mEdge, rScope, workflowHistory)
|
||||||
|
|
||||||
|
sequenceOf_ (_Just . _1 . _1 . _Just) mEdge
|
||||||
|
|
||||||
|
(heading, title) <- case rScope of
|
||||||
|
WSGlobal -> return (MsgGlobalWorkflowWorkflowWorkflowHeading cID, MsgGlobalWorkflowWorkflowWorkflowTitle cID)
|
||||||
|
_other -> error "not implemented"
|
||||||
|
|
||||||
|
siteLayoutMsg heading $ do
|
||||||
|
setTitleI title
|
||||||
|
let mEdgeView = mEdge <&> \((_, edgeView'), edgeEnc) -> wrapForm edgeView' FormSettings
|
||||||
|
{ formMethod = POST
|
||||||
|
, formAction = Just . SomeRoute $ _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
|
||||||
|
, formEncoding = edgeEnc
|
||||||
|
, formAttrs = []
|
||||||
|
, formSubmit = FormSubmit
|
||||||
|
, formAnchor = Nothing :: Maybe Text
|
||||||
|
}
|
||||||
|
historyToWidget WorkflowHistoryItem{..} = $(widgetFile "workflows/workflow/history-item")
|
||||||
|
payloadToWidget :: WorkflowFieldPayloadW Void (Maybe (Entity User)) -> Widget
|
||||||
|
payloadToWidget = \case
|
||||||
|
WorkflowFieldPayloadW (WFPText t )
|
||||||
|
-> [whamlet|
|
||||||
|
$newline never
|
||||||
|
<p .workflow-payload--text>
|
||||||
|
#{t}
|
||||||
|
|]
|
||||||
|
WorkflowFieldPayloadW (WFPNumber n ) -> toWidget . toMarkup $ formatScientific Scientific.Fixed Nothing n
|
||||||
|
WorkflowFieldPayloadW (WFPBool b ) -> i18n $ WorkflowPayloadBool b
|
||||||
|
WorkflowFieldPayloadW (WFPDay d ) -> formatTimeW SelFormatDate d
|
||||||
|
WorkflowFieldPayloadW (WFPUser mUserEnt) -> case mUserEnt of
|
||||||
|
Nothing -> i18n MsgWorkflowPayloadUserGone
|
||||||
|
Just (Entity _ User{..}) -> nameWidget userDisplayName userSurname
|
||||||
|
WorkflowFieldPayloadW (WFPFile v ) -> absurd v
|
||||||
|
$(widgetFile "workflows/workflow")
|
||||||
|
|
||||||
|
|
||||||
|
getGWWFilesR :: CryptoFileNameWorkflowWorkflow -> WorkflowPayloadLabel -> CryptoUUIDWorkflowStateIndex -> Handler TypedContent
|
||||||
|
getGWWFilesR wwCID wpl stCID = do
|
||||||
|
fRefs <- runDB $ do
|
||||||
|
wwId <- decrypt wwCID
|
||||||
|
WorkflowWorkflow{..} <- get404 wwId
|
||||||
|
stIx <- decrypt stCID
|
||||||
|
payloads <- maybeT notFound . workflowStateSection stIx $ _DBWorkflowState # workflowWorkflowState
|
||||||
|
payloads' <- maybe notFound return . Map.lookup wpl $ workflowStateCurrentPayloads payloads
|
||||||
|
let
|
||||||
|
payloads'' :: [FileReference]
|
||||||
|
payloads'' = payloads' ^.. folded . _WorkflowFieldPayloadW . _WorkflowFieldPayload
|
||||||
|
when (null payloads'') notFound
|
||||||
|
return payloads''
|
||||||
|
|
||||||
|
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack) . ap getMessageRender . pure $ MsgWorkflowWorkflowFilesArchiveName wwCID wpl stCID
|
||||||
|
|
||||||
|
serveSomeFiles archiveName $ yieldMany fRefs
|
||||||
|
|||||||
@ -76,7 +76,7 @@ workflowFileReferences :: MonadResource m => ConduitT () FileContentReference (S
|
|||||||
workflowFileReferences = mconcat [ E.selectSource (E.from $ pure . (E.^. WorkflowDefinitionGraph)) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue)
|
workflowFileReferences = mconcat [ E.selectSource (E.from $ pure . (E.^. WorkflowDefinitionGraph)) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue)
|
||||||
, E.selectSource (E.from $ pure . (E.^. WorkflowInstanceGraph )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue)
|
, E.selectSource (E.from $ pure . (E.^. WorkflowInstanceGraph )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue)
|
||||||
, E.selectSource (E.from $ pure . (E.^. WorkflowWorkflowGraph )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue)
|
, E.selectSource (E.from $ pure . (E.^. WorkflowWorkflowGraph )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue)
|
||||||
, E.selectSource (E.from $ pure . (E.^. WorkflowWorkflowState )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . folded @Set . _fileReferenceContent . _Just) yield . E.unValue)
|
, E.selectSource (E.from $ pure . (E.^. WorkflowWorkflowState )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -988,6 +988,11 @@ customMigrations = Map.fromListWith (>>)
|
|||||||
, ( AppliedMigrationKey [migrationVersion|42.0.0|] [version|43.0.0|]
|
, ( AppliedMigrationKey [migrationVersion|42.0.0|] [version|43.0.0|]
|
||||||
, return () -- Unused; used to create and fill `ChangelogItemFirstSeen`
|
, return () -- Unused; used to create and fill `ChangelogItemFirstSeen`
|
||||||
)
|
)
|
||||||
|
, ( AppliedMigrationKey [migrationVersion|43.0.0|] [version|44.0.0|]
|
||||||
|
, whenM (tableExists "school") $ do
|
||||||
|
schools <- [sqlQQ| SELECT "shorthand", "exam_discouraged_modes" FROM "school"; |]
|
||||||
|
forM_ schools $ \(sid, Single edModes) -> update sid [SchoolExamDiscouragedModes =. Legacy.examModeDNF edModes]
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
module Model.Migration.Types where
|
module Model.Migration.Types where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod
|
import ClassyPrelude.Yesod
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.Aeson.TH (deriveJSON)
|
import Data.Aeson.TH (deriveJSON, mkParseJSON)
|
||||||
|
|
||||||
import Utils.PathPiece
|
import Utils.PathPiece
|
||||||
|
|
||||||
@ -12,6 +14,8 @@ import qualified Model.Types.TH.JSON as Current
|
|||||||
import Data.Universe
|
import Data.Universe
|
||||||
import Data.Universe.TH
|
import Data.Universe.TH
|
||||||
|
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
|
||||||
data SheetType
|
data SheetType
|
||||||
= Normal { maxPoints :: Current.Points } -- Erhöht das Maximum, wird gutgeschrieben
|
= Normal { maxPoints :: Current.Points } -- Erhöht das Maximum, wird gutgeschrieben
|
||||||
@ -84,3 +88,31 @@ deriveJSON defaultOptions
|
|||||||
} ''Transaction
|
} ''Transaction
|
||||||
|
|
||||||
Current.derivePersistFieldJSON ''Transaction
|
Current.derivePersistFieldJSON ''Transaction
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
data PredLiteral a = PLVariable { plVar :: a } | PLNegated { plVar :: a }
|
||||||
|
deriving (Eq, Ord)
|
||||||
|
deriveJSON defaultOptions
|
||||||
|
{ constructorTagModifier = camelToPathPiece' 1
|
||||||
|
, sumEncoding = TaggedObject "val" "var"
|
||||||
|
} ''PredLiteral
|
||||||
|
|
||||||
|
newtype PredDNF a = PredDNF { dnfTerms :: Set (NonNull (Set (PredLiteral a))) }
|
||||||
|
|
||||||
|
$(return [])
|
||||||
|
|
||||||
|
instance ToJSON (PredDNF a) where
|
||||||
|
toJSON = error "toJSON @(Legacy.PredDNF _): not implemented"
|
||||||
|
instance (Ord a, FromJSON a) => FromJSON (PredDNF a) where
|
||||||
|
parseJSON = $(mkParseJSON defaultOptions{ tagSingleConstructors = True, sumEncoding = ObjectWithSingleField } ''PredDNF)
|
||||||
|
|
||||||
|
newtype ExamModeDNF = ExamModeDNF (PredDNF Current.ExamModePredicate)
|
||||||
|
deriving newtype (ToJSON, FromJSON)
|
||||||
|
|
||||||
|
Current.derivePersistFieldJSON ''ExamModeDNF
|
||||||
|
|
||||||
|
examModeDNF :: ExamModeDNF -> Current.ExamModeDNF
|
||||||
|
examModeDNF (ExamModeDNF PredDNF{..}) = Current.ExamModeDNF . Current.PredDNF $ Set.map (impureNonNull . Set.map toCurrentPredLiteral . toNullable) dnfTerms
|
||||||
|
where toCurrentPredLiteral PLVariable{..} = Current.PLVariable plVar
|
||||||
|
toCurrentPredLiteral PLNegated{..} = Current.PLNegated plVar
|
||||||
|
|||||||
@ -163,7 +163,7 @@ makePrisms ''PredLiteral
|
|||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = camelToPathPiece' 1
|
{ constructorTagModifier = camelToPathPiece' 1
|
||||||
, fieldLabelModifier = camelToPathPiece' 1
|
, fieldLabelModifier = camelToPathPiece' 1
|
||||||
, sumEncoding = TaggedObject "val" "var"
|
, sumEncoding = TaggedObject "tag" "variable"
|
||||||
} ''PredLiteral
|
} ''PredLiteral
|
||||||
|
|
||||||
instance PathPiece a => PathPiece (PredLiteral a) where
|
instance PathPiece a => PathPiece (PredLiteral a) where
|
||||||
|
|||||||
@ -68,7 +68,7 @@ predNFAesonOptions = defaultOptions
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
workflowGraphAesonOptions, workflowGraphEdgeAesonOptions, workflowGraphNodeAesonOptions, workflowActionAesonOptions, workflowPayloadViewAesonOptions :: Options
|
workflowGraphAesonOptions, workflowGraphEdgeAesonOptions, workflowGraphNodeAesonOptions, workflowActionAesonOptions, workflowPayloadViewAesonOptions, workflowNodeViewAesonOptions :: Options
|
||||||
workflowGraphAesonOptions = defaultOptions
|
workflowGraphAesonOptions = defaultOptions
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
}
|
}
|
||||||
@ -86,3 +86,6 @@ workflowActionAesonOptions = defaultOptions
|
|||||||
workflowPayloadViewAesonOptions = defaultOptions
|
workflowPayloadViewAesonOptions = defaultOptions
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
}
|
}
|
||||||
|
workflowNodeViewAesonOptions = defaultOptions
|
||||||
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ module Model.Types.Workflow
|
|||||||
( WorkflowGraph(..)
|
( WorkflowGraph(..)
|
||||||
, WorkflowGraphNodeLabel
|
, WorkflowGraphNodeLabel
|
||||||
, WorkflowGraphNode(..)
|
, WorkflowGraphNode(..)
|
||||||
|
, WorkflowNodeView(..)
|
||||||
, WorkflowGraphEdgeLabel
|
, WorkflowGraphEdgeLabel
|
||||||
, WorkflowGraphEdge(..)
|
, WorkflowGraphEdge(..)
|
||||||
, WorkflowGraphEdgeFormOrder
|
, WorkflowGraphEdgeFormOrder
|
||||||
@ -15,10 +16,11 @@ module Model.Types.Workflow
|
|||||||
, WorkflowPayloadField(..)
|
, WorkflowPayloadField(..)
|
||||||
, WorkflowScope(..)
|
, WorkflowScope(..)
|
||||||
, WorkflowScope'(..), classifyWorkflowScope
|
, WorkflowScope'(..), classifyWorkflowScope
|
||||||
, WorkflowPayloadLabel
|
, WorkflowPayloadLabel(..)
|
||||||
|
, WorkflowStateIndex(..), workflowStateIndex, workflowStateSection
|
||||||
, WorkflowState
|
, WorkflowState
|
||||||
, WorkflowAction(..), _wpTo, _wpVia, _wpPayload, _wpUser, _wpTime
|
, WorkflowAction(..), _wpTo, _wpVia, _wpPayload, _wpUser, _wpTime
|
||||||
, WorkflowFieldPayloadW(..), _WorkflowFieldPayloadW, IsWorkflowFieldPayload
|
, WorkflowFieldPayloadW(..), _WorkflowFieldPayloadW, IsWorkflowFieldPayload', IsWorkflowFieldPayload
|
||||||
, WorkflowFieldPayload(..), _WorkflowFieldPayload
|
, WorkflowFieldPayload(..), _WorkflowFieldPayload
|
||||||
, workflowStatePayload, workflowStateCurrentPayloads
|
, workflowStatePayload, workflowStateCurrentPayloads
|
||||||
, WorkflowChildren
|
, WorkflowChildren
|
||||||
@ -26,7 +28,7 @@ module Model.Types.Workflow
|
|||||||
|
|
||||||
import Import.NoModel
|
import Import.NoModel
|
||||||
|
|
||||||
import Model.Types.Security (AuthDNF)
|
import Model.Types.Security (AuthDNF, PredDNF)
|
||||||
import Model.Types.File (FileContentReference, FileFieldUserOption, FileField, _fieldAdditionalFiles, FileReferenceTitleMapConvertible(..))
|
import Model.Types.File (FileContentReference, FileFieldUserOption, FileField, _fieldAdditionalFiles, FileReferenceTitleMapConvertible(..))
|
||||||
|
|
||||||
import Database.Persist.Sql (PersistFieldSql(..))
|
import Database.Persist.Sql (PersistFieldSql(..))
|
||||||
@ -74,9 +76,8 @@ newtype WorkflowGraphNodeLabel = WorkflowGraphNodeLabel { unWorkflowGraphNodeLab
|
|||||||
deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary)
|
deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary)
|
||||||
|
|
||||||
data WorkflowGraphNode fileid userid = WGN
|
data WorkflowGraphNode fileid userid = WGN
|
||||||
{ wgnDisplayLabel :: Maybe I18nText
|
{ wgnFinal :: Bool
|
||||||
, wgnFinal :: Bool
|
, wgnViewers :: Maybe (WorkflowNodeView userid)
|
||||||
, wgnViewers :: Set (WorkflowRole userid)
|
|
||||||
, wgnEdges :: Map WorkflowGraphEdgeLabel (WorkflowGraphEdge fileid userid)
|
, wgnEdges :: Map WorkflowGraphEdgeLabel (WorkflowGraphEdge fileid userid)
|
||||||
}
|
}
|
||||||
deriving (Generic, Typeable)
|
deriving (Generic, Typeable)
|
||||||
@ -85,6 +86,10 @@ deriving instance (Eq fileid, Eq userid, Typeable fileid, Typeable userid, Eq (F
|
|||||||
deriving instance (Ord fileid, Ord userid, Typeable fileid, Typeable userid, Ord (FileField fileid)) => Ord (WorkflowGraphNode fileid userid)
|
deriving instance (Ord fileid, Ord userid, Typeable fileid, Typeable userid, Ord (FileField fileid)) => Ord (WorkflowGraphNode fileid userid)
|
||||||
deriving instance (Show fileid, Show userid, Show (FileField fileid)) => Show (WorkflowGraphNode fileid userid)
|
deriving instance (Show fileid, Show userid, Show (FileField fileid)) => Show (WorkflowGraphNode fileid userid)
|
||||||
|
|
||||||
|
data WorkflowNodeView userid = WorkflowNodeView
|
||||||
|
{ wnvViewers :: NonNull (Set (WorkflowRole userid))
|
||||||
|
, wnvDisplayLabel :: I18nText
|
||||||
|
} deriving (Eq, Ord, Read, Show, Data, Generic, Typeable)
|
||||||
|
|
||||||
----- WORKFLOW GRAPH: EDGES -----
|
----- WORKFLOW GRAPH: EDGES -----
|
||||||
|
|
||||||
@ -98,14 +103,17 @@ data WorkflowGraphEdge fileid userid
|
|||||||
, wgeActors :: Set (WorkflowRole userid)
|
, wgeActors :: Set (WorkflowRole userid)
|
||||||
, wgeForm :: WorkflowGraphEdgeForm fileid userid
|
, wgeForm :: WorkflowGraphEdgeForm fileid userid
|
||||||
, wgeDisplayLabel :: I18nText
|
, wgeDisplayLabel :: I18nText
|
||||||
|
, wgeViewActor :: Set (WorkflowRole userid)
|
||||||
}
|
}
|
||||||
| WorkflowGraphEdgeAutomatic
|
| WorkflowGraphEdgeAutomatic
|
||||||
{ wgeSource :: WorkflowGraphNodeLabel
|
{ wgeSource :: WorkflowGraphNodeLabel
|
||||||
|
, wgePayloadRestriction :: Maybe (PredDNF WorkflowPayloadLabel)
|
||||||
}
|
}
|
||||||
| WorkflowGraphEdgeInitial
|
| WorkflowGraphEdgeInitial
|
||||||
{ wgeActors :: Set (WorkflowRole userid)
|
{ wgeActors :: Set (WorkflowRole userid)
|
||||||
, wgeForm :: WorkflowGraphEdgeForm fileid userid
|
, wgeForm :: WorkflowGraphEdgeForm fileid userid
|
||||||
, wgeDisplayLabel :: I18nText
|
, wgeDisplayLabel :: I18nText
|
||||||
|
, wgeViewActor :: Set (WorkflowRole userid)
|
||||||
}
|
}
|
||||||
deriving (Generic, Typeable)
|
deriving (Generic, Typeable)
|
||||||
|
|
||||||
@ -167,6 +175,7 @@ data WorkflowPayloadField fileid userid (payload :: Type) where
|
|||||||
, wpftPlaceholder :: Maybe I18nText
|
, wpftPlaceholder :: Maybe I18nText
|
||||||
, wpftTooltip :: Maybe I18nHtml
|
, wpftTooltip :: Maybe I18nHtml
|
||||||
, wpftDefault :: Maybe Text
|
, wpftDefault :: Maybe Text
|
||||||
|
, wpftLarge :: Bool
|
||||||
, wpftOptional :: Bool
|
, wpftOptional :: Bool
|
||||||
} -> WorkflowPayloadField fileid userid Text
|
} -> WorkflowPayloadField fileid userid Text
|
||||||
WorkflowPayloadFieldNumber :: { wpfnLabel :: I18nText
|
WorkflowPayloadFieldNumber :: { wpfnLabel :: I18nText
|
||||||
@ -183,6 +192,11 @@ data WorkflowPayloadField fileid userid (payload :: Type) where
|
|||||||
, wpfbDefault :: Maybe Bool
|
, wpfbDefault :: Maybe Bool
|
||||||
, wpfbOptional :: Maybe I18nText -- ^ Optional if `Just`; encodes label of `Nothing`-Option
|
, wpfbOptional :: Maybe I18nText -- ^ Optional if `Just`; encodes label of `Nothing`-Option
|
||||||
} -> WorkflowPayloadField fileid userid Bool
|
} -> WorkflowPayloadField fileid userid Bool
|
||||||
|
WorkflowPayloadFieldDay :: { wpfdLabel :: I18nText
|
||||||
|
, wpfdTooltip :: Maybe I18nHtml
|
||||||
|
, wpfdDefault :: Maybe Day
|
||||||
|
, wpfdOptional :: Bool
|
||||||
|
} -> WorkflowPayloadField fileid userid Day
|
||||||
WorkflowPayloadFieldFile :: { wpffLabel :: I18nText
|
WorkflowPayloadFieldFile :: { wpffLabel :: I18nText
|
||||||
, wpffTooltip :: Maybe I18nHtml
|
, wpffTooltip :: Maybe I18nHtml
|
||||||
, wpffConfig :: FileField fileid
|
, wpffConfig :: FileField fileid
|
||||||
@ -226,24 +240,32 @@ instance (Ord fileid, Ord userid, Typeable fileid, Typeable userid, Ord (FileFie
|
|||||||
(WorkflowPayloadFieldBool{}, WorkflowPayloadFieldText{}) -> GT
|
(WorkflowPayloadFieldBool{}, WorkflowPayloadFieldText{}) -> GT
|
||||||
(WorkflowPayloadFieldBool{}, WorkflowPayloadFieldNumber{}) -> GT
|
(WorkflowPayloadFieldBool{}, WorkflowPayloadFieldNumber{}) -> GT
|
||||||
(WorkflowPayloadFieldBool{}, _) -> LT
|
(WorkflowPayloadFieldBool{}, _) -> LT
|
||||||
|
(WorkflowPayloadFieldDay{}, WorkflowPayloadFieldText{}) -> GT
|
||||||
|
(WorkflowPayloadFieldDay{}, WorkflowPayloadFieldNumber{}) -> GT
|
||||||
|
(WorkflowPayloadFieldDay{}, WorkflowPayloadFieldBool{}) -> GT
|
||||||
|
(WorkflowPayloadFieldDay{}, _) -> LT
|
||||||
(WorkflowPayloadFieldFile{}, WorkflowPayloadFieldText{}) -> GT
|
(WorkflowPayloadFieldFile{}, WorkflowPayloadFieldText{}) -> GT
|
||||||
(WorkflowPayloadFieldFile{}, WorkflowPayloadFieldNumber{}) -> GT
|
(WorkflowPayloadFieldFile{}, WorkflowPayloadFieldNumber{}) -> GT
|
||||||
(WorkflowPayloadFieldFile{}, WorkflowPayloadFieldBool{}) -> GT
|
(WorkflowPayloadFieldFile{}, WorkflowPayloadFieldBool{}) -> GT
|
||||||
|
(WorkflowPayloadFieldFile{}, WorkflowPayloadFieldDay{}) -> GT
|
||||||
(WorkflowPayloadFieldFile{}, _) -> LT
|
(WorkflowPayloadFieldFile{}, _) -> LT
|
||||||
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldText{}) -> GT
|
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldText{}) -> GT
|
||||||
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldNumber{}) -> GT
|
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldNumber{}) -> GT
|
||||||
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldBool{}) -> GT
|
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldBool{}) -> GT
|
||||||
|
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldDay{}) -> GT
|
||||||
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldFile{}) -> GT
|
(WorkflowPayloadFieldUser{}, WorkflowPayloadFieldFile{}) -> GT
|
||||||
(WorkflowPayloadFieldUser{}, _) -> LT
|
(WorkflowPayloadFieldUser{}, _) -> LT
|
||||||
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldText{}) -> GT
|
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldText{}) -> GT
|
||||||
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldNumber{}) -> GT
|
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldNumber{}) -> GT
|
||||||
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldBool{}) -> GT
|
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldBool{}) -> GT
|
||||||
|
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldDay{}) -> GT
|
||||||
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldFile{}) -> GT
|
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldFile{}) -> GT
|
||||||
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldUser{}) -> GT
|
(WorkflowPayloadFieldCaptureUser{}, WorkflowPayloadFieldUser{}) -> GT
|
||||||
(WorkflowPayloadFieldCaptureUser{}, _) -> LT
|
(WorkflowPayloadFieldCaptureUser{}, _) -> LT
|
||||||
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldText{}) -> GT
|
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldText{}) -> GT
|
||||||
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldNumber{}) -> GT
|
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldNumber{}) -> GT
|
||||||
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldBool{}) -> GT
|
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldBool{}) -> GT
|
||||||
|
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldDay{}) -> GT
|
||||||
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldFile{}) -> GT
|
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldFile{}) -> GT
|
||||||
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldUser{}) -> GT
|
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldUser{}) -> GT
|
||||||
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldCaptureUser{}) -> GT
|
(WorkflowPayloadFieldReference{}, WorkflowPayloadFieldCaptureUser{}) -> GT
|
||||||
@ -255,7 +277,7 @@ _WorkflowPayloadSpec :: forall payload fileid userid.
|
|||||||
=> Prism' (WorkflowPayloadSpec fileid userid) (WorkflowPayloadField fileid userid payload)
|
=> Prism' (WorkflowPayloadSpec fileid userid) (WorkflowPayloadField fileid userid payload)
|
||||||
_WorkflowPayloadSpec = prism' WorkflowPayloadSpec $ \(WorkflowPayloadSpec pF) -> cast pF
|
_WorkflowPayloadSpec = prism' WorkflowPayloadSpec $ \(WorkflowPayloadSpec pF) -> cast pF
|
||||||
|
|
||||||
data WorkflowPayloadField' = WPFText' | WPFNumber' | WPFBool' | WPFFile' | WPFUser' | WPFCaptureUser' | WPFReference' | WPFMultiple'
|
data WorkflowPayloadField' = WPFText' | WPFNumber' | WPFBool' | WPFDay' | WPFFile' | WPFUser' | WPFCaptureUser' | WPFReference' | WPFMultiple'
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Data, Generic, Typeable)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read, Data, Generic, Typeable)
|
||||||
deriving anyclass (Universe, Finite)
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
@ -289,8 +311,24 @@ newtype WorkflowPayloadLabel = WorkflowPayloadLabel { unWorkflowPayloadLabel ::
|
|||||||
deriving stock (Eq, Ord, Show, Read, Data, Generic, Typeable)
|
deriving stock (Eq, Ord, Show, Read, Data, Generic, Typeable)
|
||||||
deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary)
|
deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary)
|
||||||
|
|
||||||
|
newtype WorkflowStateIndex = WorkflowStateIndex { unWorkflowStateIndex :: Word64 }
|
||||||
|
deriving stock (Eq, Ord, Show, Read, Data, Generic, Typeable)
|
||||||
|
deriving newtype (Num, Real, Integral, Enum, Bounded, ToJSON, FromJSON, PathPiece, Binary)
|
||||||
|
|
||||||
type WorkflowState fileid userid = NonNull (Seq (WorkflowAction fileid userid))
|
type WorkflowState fileid userid = NonNull (Seq (WorkflowAction fileid userid))
|
||||||
|
|
||||||
|
workflowStateIndex :: Alternative m
|
||||||
|
=> WorkflowStateIndex
|
||||||
|
-> WorkflowState fileid userid
|
||||||
|
-> m (WorkflowAction fileid userid)
|
||||||
|
workflowStateIndex (fromIntegral -> i) = maybe empty pure . flip index i . toNullable
|
||||||
|
|
||||||
|
workflowStateSection :: MonadPlus m
|
||||||
|
=> WorkflowStateIndex
|
||||||
|
-> WorkflowState fileid userid
|
||||||
|
-> m (WorkflowState fileid userid)
|
||||||
|
workflowStateSection i wSt = maybe mzero return . fromNullable . Seq.fromList =<< sequenceA (map (flip workflowStateIndex wSt) [0..i])
|
||||||
|
|
||||||
data WorkflowAction fileid userid = WorkflowAction
|
data WorkflowAction fileid userid = WorkflowAction
|
||||||
{ wpTo :: WorkflowGraphNodeLabel
|
{ wpTo :: WorkflowGraphNodeLabel
|
||||||
, wpVia :: WorkflowGraphEdgeLabel
|
, wpVia :: WorkflowGraphEdgeLabel
|
||||||
@ -300,7 +338,7 @@ data WorkflowAction fileid userid = WorkflowAction
|
|||||||
}
|
}
|
||||||
deriving (Eq, Ord, Show, Generic, Typeable)
|
deriving (Eq, Ord, Show, Generic, Typeable)
|
||||||
|
|
||||||
data WorkflowFieldPayloadW fileid userid = forall payload. IsWorkflowFieldPayload fileid userid payload => WorkflowFieldPayloadW (WorkflowFieldPayload fileid userid payload)
|
data WorkflowFieldPayloadW fileid userid = forall payload. IsWorkflowFieldPayload' fileid userid payload => WorkflowFieldPayloadW (WorkflowFieldPayload fileid userid payload)
|
||||||
deriving (Typeable)
|
deriving (Typeable)
|
||||||
|
|
||||||
instance (Eq fileid, Eq userid, Typeable fileid, Typeable userid) => Eq (WorkflowFieldPayloadW fileid userid) where
|
instance (Eq fileid, Eq userid, Typeable fileid, Typeable userid) => Eq (WorkflowFieldPayloadW fileid userid) where
|
||||||
@ -320,16 +358,16 @@ instance (Ord fileid, Ord userid, Typeable fileid, Typeable userid) => Ord (Work
|
|||||||
(WFPBool{}, WFPText{}) -> GT
|
(WFPBool{}, WFPText{}) -> GT
|
||||||
(WFPBool{}, WFPNumber{}) -> GT
|
(WFPBool{}, WFPNumber{}) -> GT
|
||||||
(WFPBool{}, _) -> LT
|
(WFPBool{}, _) -> LT
|
||||||
(WFPFiles{}, WFPText{}) -> GT
|
(WFPDay{}, WFPText{}) -> GT
|
||||||
(WFPFiles{}, WFPNumber{}) -> GT
|
(WFPDay{}, WFPNumber{}) -> GT
|
||||||
(WFPFiles{}, WFPBool{}) -> GT
|
(WFPDay{}, WFPDay{}) -> GT
|
||||||
(WFPFiles{}, _) -> LT
|
(WFPDay{}, _) -> LT
|
||||||
(WFPUser{}, WFPText{}) -> GT
|
(WFPFile{}, WFPText{}) -> GT
|
||||||
(WFPUser{}, WFPNumber{}) -> GT
|
(WFPFile{}, WFPNumber{}) -> GT
|
||||||
(WFPUser{}, WFPBool{}) -> GT
|
(WFPFile{}, WFPBool{}) -> GT
|
||||||
(WFPUser{}, WFPFiles{}) -> GT
|
(WFPFile{}, WFPDay{}) -> GT
|
||||||
(WFPUser{}, _) -> LT
|
(WFPFile{}, _) -> LT
|
||||||
(WFPMultiple{}, _) -> GT
|
(WFPUser{}, _) -> GT
|
||||||
|
|
||||||
instance (Show fileid, Show userid) => Show (WorkflowFieldPayloadW fileid userid) where
|
instance (Show fileid, Show userid) => Show (WorkflowFieldPayloadW fileid userid) where
|
||||||
show (WorkflowFieldPayloadW payload) = show payload
|
show (WorkflowFieldPayloadW payload) = show payload
|
||||||
@ -338,9 +376,9 @@ data WorkflowFieldPayload fileid userid (payload :: Type) where
|
|||||||
WFPText :: Text -> WorkflowFieldPayload fileid userid Text
|
WFPText :: Text -> WorkflowFieldPayload fileid userid Text
|
||||||
WFPNumber :: Scientific -> WorkflowFieldPayload fileid userid Scientific
|
WFPNumber :: Scientific -> WorkflowFieldPayload fileid userid Scientific
|
||||||
WFPBool :: Bool -> WorkflowFieldPayload fileid userid Bool
|
WFPBool :: Bool -> WorkflowFieldPayload fileid userid Bool
|
||||||
WFPFiles :: Set fileid -> WorkflowFieldPayload fileid userid (Set fileid)
|
WFPDay :: Day -> WorkflowFieldPayload fileid userid Day
|
||||||
|
WFPFile :: fileid -> WorkflowFieldPayload fileid userid fileid
|
||||||
WFPUser :: userid -> WorkflowFieldPayload fileid userid userid
|
WFPUser :: userid -> WorkflowFieldPayload fileid userid userid
|
||||||
WFPMultiple :: NonEmpty (WorkflowFieldPayloadW fileid userid) -> WorkflowFieldPayload fileid userid (NonEmpty (WorkflowFieldPayloadW fileid userid))
|
|
||||||
deriving (Typeable)
|
deriving (Typeable)
|
||||||
|
|
||||||
deriving instance (Show fileid, Show userid) => Show (WorkflowFieldPayload fileid userid payload)
|
deriving instance (Show fileid, Show userid) => Show (WorkflowFieldPayload fileid userid payload)
|
||||||
@ -348,44 +386,49 @@ deriving instance (Typeable fileid, Typeable userid, Eq fileid, Eq userid) => Eq
|
|||||||
deriving instance (Typeable fileid, Typeable userid, Ord fileid, Ord userid) => Ord (WorkflowFieldPayload fileid userid payload)
|
deriving instance (Typeable fileid, Typeable userid, Ord fileid, Ord userid) => Ord (WorkflowFieldPayload fileid userid payload)
|
||||||
|
|
||||||
_WorkflowFieldPayloadW :: forall payload fileid userid.
|
_WorkflowFieldPayloadW :: forall payload fileid userid.
|
||||||
( IsWorkflowFieldPayload fileid userid payload, Typeable fileid, Typeable userid )
|
( IsWorkflowFieldPayload' fileid userid payload, Typeable fileid, Typeable userid )
|
||||||
=> Prism' (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayload fileid userid payload)
|
=> Prism' (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayload fileid userid payload)
|
||||||
_WorkflowFieldPayloadW = prism' WorkflowFieldPayloadW $ \(WorkflowFieldPayloadW fp) -> cast fp
|
_WorkflowFieldPayloadW = prism' WorkflowFieldPayloadW $ \(WorkflowFieldPayloadW fp) -> cast fp
|
||||||
|
|
||||||
data WorkflowFieldPayload' = WFPText' | WFPNumber' | WFPBool' | WFPFiles' | WFPUser' | WFPMultiple'
|
data WorkflowFieldPayload' = WFPText' | WFPNumber' | WFPBool' | WFPDay' | WFPFile' | WFPUser'
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Data, Generic, Typeable)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read, Data, Generic, Typeable)
|
||||||
deriving anyclass (Universe, Finite)
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
class Typeable payload => IsWorkflowFieldPayload fileid userid payload where
|
type IsWorkflowFieldPayload' fileid userid payload = IsWorkflowFieldPayload fileid fileid userid userid payload payload
|
||||||
_WorkflowFieldPayload :: Prism' (WorkflowFieldPayload fileid userid payload) payload
|
|
||||||
|
|
||||||
instance IsWorkflowFieldPayload fileid userid Text where
|
class Typeable payload => IsWorkflowFieldPayload fileid fileid' userid userid' payload payload' where
|
||||||
|
_WorkflowFieldPayload :: Prism (WorkflowFieldPayload fileid userid payload) (WorkflowFieldPayload fileid' userid' payload') payload payload'
|
||||||
|
|
||||||
|
instance IsWorkflowFieldPayload fileid fileid userid userid Text Text where
|
||||||
_WorkflowFieldPayload = prism' WFPText $ \case { WFPText x -> Just x; _other -> Nothing }
|
_WorkflowFieldPayload = prism' WFPText $ \case { WFPText x -> Just x; _other -> Nothing }
|
||||||
instance IsWorkflowFieldPayload fileid userid Scientific where
|
instance IsWorkflowFieldPayload fileid fileid userid userid Scientific Scientific where
|
||||||
_WorkflowFieldPayload = prism' WFPNumber $ \case { WFPNumber x -> Just x; _other -> Nothing }
|
_WorkflowFieldPayload = prism' WFPNumber $ \case { WFPNumber x -> Just x; _other -> Nothing }
|
||||||
instance IsWorkflowFieldPayload fileid userid Bool where
|
instance IsWorkflowFieldPayload fileid fileid userid userid Bool Bool where
|
||||||
_WorkflowFieldPayload = prism' WFPBool $ \case { WFPBool x -> Just x; _other -> Nothing }
|
_WorkflowFieldPayload = prism' WFPBool $ \case { WFPBool x -> Just x; _other -> Nothing }
|
||||||
instance {-# OVERLAPPING #-} Typeable fileid => IsWorkflowFieldPayload fileid userid (Set fileid) where
|
instance IsWorkflowFieldPayload fileid fileid userid userid Day Day where
|
||||||
_WorkflowFieldPayload = prism' WFPFiles $ \case { WFPFiles x -> Just x; _other -> Nothing }
|
_WorkflowFieldPayload = prism' WFPDay $ \case { WFPDay x -> Just x; _other -> Nothing }
|
||||||
instance {-# OVERLAPPING #-} Typeable userid => IsWorkflowFieldPayload fileid userid userid where
|
instance Typeable fileid => IsWorkflowFieldPayload fileid fileid' userid userid fileid fileid' where
|
||||||
_WorkflowFieldPayload = prism' WFPUser $ \case { WFPUser x -> Just x; _other -> Nothing }
|
_WorkflowFieldPayload = prism WFPFile $ \case { WFPFile x -> Right x; other -> Left $ unsafeCoerce other }
|
||||||
instance (Typeable fileid, Typeable userid) => IsWorkflowFieldPayload fileid userid (NonEmpty (WorkflowFieldPayloadW fileid userid)) where
|
instance Typeable userid => IsWorkflowFieldPayload fileid fileid userid userid' userid userid' where
|
||||||
_WorkflowFieldPayload = iso (\(WFPMultiple x) -> x) WFPMultiple
|
_WorkflowFieldPayload = prism WFPUser $ \case { WFPUser x -> Right x; other -> Left $ unsafeCoerce other }
|
||||||
|
|
||||||
workflowStatePayload :: forall fileid userid payload.
|
-- workflowStatePayload :: forall fileid userid payload.
|
||||||
( IsWorkflowFieldPayload fileid userid payload
|
-- ( IsWorkflowFieldPayload' fileid userid payload
|
||||||
, Ord fileid, Ord userid, Ord payload
|
-- , Ord fileid, Ord userid, Ord payload
|
||||||
, Typeable fileid, Typeable userid
|
-- , Typeable fileid, Typeable userid
|
||||||
)
|
-- , Show userid, Show fileid
|
||||||
=> WorkflowPayloadLabel -> WorkflowState fileid userid -> Seq (Set payload)
|
-- )
|
||||||
workflowStatePayload label acts = flip ofoldMap acts $ \WorkflowAction{..} -> Seq.singleton . Map.findWithDefault Set.empty label $ fmap (Set.fromList . concatMap extractPayload . otoList) wpPayload
|
-- => WorkflowPayloadLabel -> WorkflowState fileid userid -> Seq (Maybe (Set payload))
|
||||||
where extractPayload (WorkflowFieldPayloadW fieldPayload)
|
-- workflowStatePayload label acts = flip ofoldMap acts $ \WorkflowAction{..} -> Seq.singleton . Map.lookup label $ fmap (Set.fromList . concatMap extractPayload . otoList) wpPayload
|
||||||
| Just HRefl <- typeOf fieldPayload `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid payload)
|
-- where
|
||||||
= fieldPayload ^.. _WorkflowFieldPayload
|
-- extractPayload :: WorkflowFieldPayloadW fileid userid -> [payload]
|
||||||
| Just HRefl <- typeOf fieldPayload `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid (NonEmpty (WorkflowFieldPayloadW fileid userid)))
|
-- extractPayload = \case
|
||||||
= concatMap extractPayload . maybe mempty otoList $ fieldPayload ^? _WorkflowFieldPayload
|
-- WorkflowFieldPayloadW fieldPayload@(WFPMultiple ps) -> traceShow ("multiple", fieldPayload) . concatMap extractPayload $ otoList ps
|
||||||
| otherwise
|
-- WorkflowFieldPayloadW fieldPayload
|
||||||
= mempty
|
-- | Just HRefl <- traceShow ("single", fieldPayload) $ typeOf fieldPayload `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid payload)
|
||||||
|
-- -> fieldPayload ^.. _WorkflowFieldPayload
|
||||||
|
-- | otherwise
|
||||||
|
-- -> traceShow ("none", fieldPayload) mempty
|
||||||
|
|
||||||
workflowStateCurrentPayloads :: forall fileid userid.
|
workflowStateCurrentPayloads :: forall fileid userid.
|
||||||
WorkflowState fileid userid
|
WorkflowState fileid userid
|
||||||
@ -420,6 +463,7 @@ type family ChildrenWorkflowChildren a where
|
|||||||
ChildrenWorkflowChildren (Key record) = '[]
|
ChildrenWorkflowChildren (Key record) = '[]
|
||||||
ChildrenWorkflowChildren FileContentReference = '[]
|
ChildrenWorkflowChildren FileContentReference = '[]
|
||||||
ChildrenWorkflowChildren UTCTime = '[]
|
ChildrenWorkflowChildren UTCTime = '[]
|
||||||
|
ChildrenWorkflowChildren Day = '[]
|
||||||
ChildrenWorkflowChildren (WorkflowPayloadSpec fileid userid)
|
ChildrenWorkflowChildren (WorkflowPayloadSpec fileid userid)
|
||||||
= ChildrenWorkflowChildren I18nText
|
= ChildrenWorkflowChildren I18nText
|
||||||
`Concat` ChildrenWorkflowChildren (Maybe I18nText)
|
`Concat` ChildrenWorkflowChildren (Maybe I18nText)
|
||||||
@ -427,6 +471,7 @@ type family ChildrenWorkflowChildren a where
|
|||||||
`Concat` ChildrenWorkflowChildren (Maybe Text)
|
`Concat` ChildrenWorkflowChildren (Maybe Text)
|
||||||
`Concat` ChildrenWorkflowChildren (Maybe Scientific)
|
`Concat` ChildrenWorkflowChildren (Maybe Scientific)
|
||||||
`Concat` ChildrenWorkflowChildren (Maybe Bool)
|
`Concat` ChildrenWorkflowChildren (Maybe Bool)
|
||||||
|
`Concat` ChildrenWorkflowChildren (Maybe Day)
|
||||||
`Concat` ChildrenWorkflowChildren (Maybe fileid)
|
`Concat` ChildrenWorkflowChildren (Maybe fileid)
|
||||||
`Concat` ChildrenWorkflowChildren (Maybe userid)
|
`Concat` ChildrenWorkflowChildren (Maybe userid)
|
||||||
`Concat` ChildrenWorkflowChildren Bool
|
`Concat` ChildrenWorkflowChildren Bool
|
||||||
@ -435,6 +480,7 @@ type family ChildrenWorkflowChildren a where
|
|||||||
= ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid Text)
|
= ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid Text)
|
||||||
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid Scientific)
|
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid Scientific)
|
||||||
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid Bool)
|
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid Bool)
|
||||||
|
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid Day)
|
||||||
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid fileid)
|
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid fileid)
|
||||||
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid userid)
|
`Concat` ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid userid)
|
||||||
ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid payload)
|
ChildrenWorkflowChildren (WorkflowFieldPayload fileid userid payload)
|
||||||
@ -469,6 +515,7 @@ instance (Typeable userid, Typeable fileid, Typeable fileid', Ord fileid', useri
|
|||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldText{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldText{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldText{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldText{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldBool{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldBool{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldBool{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldBool{..}
|
||||||
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldDay{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldDay{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldUser{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldUser{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldUser{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldUser{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldReference{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldReference{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldReference{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldReference{..}
|
||||||
@ -481,39 +528,49 @@ instance (Typeable userid, Typeable userid', Typeable fileid, fileid ~ fileid')
|
|||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldText{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldText{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldText{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldText{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldBool{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldBool{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldBool{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldBool{..}
|
||||||
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldDay{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldDay{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldFile{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldFile{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldFile{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldFile{..}
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldCaptureUser
|
||||||
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldReference{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldReference{..}
|
typesCustom _ (WorkflowPayloadSpec WorkflowPayloadFieldReference{..}) = pure $ WorkflowPayloadSpec WorkflowPayloadFieldReference{..}
|
||||||
|
|
||||||
instance (Typeable payload, Typeable fileid, Typeable userid, IsWorkflowFieldPayload fileid userid payload, IsWorkflowFieldPayload fileid' userid' payload', fileid ~ fileid', userid ~ userid') => HasTypesCustom WorkflowChildren (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayloadW fileid' userid') payload payload' where
|
instance (Typeable payload, Typeable fileid, Typeable userid, IsWorkflowFieldPayload' fileid userid payload, IsWorkflowFieldPayload' fileid' userid' payload', fileid ~ fileid', userid ~ userid') => HasTypesCustom WorkflowChildren (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayloadW fileid' userid') payload payload' where
|
||||||
typesCustom f pw@(WorkflowFieldPayloadW p) = case typeOf p `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid payload) of
|
typesCustom f pw@(WorkflowFieldPayloadW p) = case typeOf p `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid payload) of
|
||||||
Just HRefl -> WorkflowFieldPayloadW <$> typesCustom @WorkflowChildren @(WorkflowFieldPayload fileid userid payload) @(WorkflowFieldPayload fileid' userid' payload') @payload @payload' f p
|
Just HRefl -> WorkflowFieldPayloadW <$> typesCustom @WorkflowChildren @(WorkflowFieldPayload fileid userid payload) @(WorkflowFieldPayload fileid' userid' payload') @payload @payload' f p
|
||||||
Nothing -> pure pw
|
Nothing -> pure pw
|
||||||
|
|
||||||
instance {-# OVERLAPPING #-} (Typeable fileid, Typeable userid, IsWorkflowFieldPayload fileid userid userid, IsWorkflowFieldPayload fileid' userid' userid', fileid ~ fileid') => HasTypesCustom WorkflowChildren (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayloadW fileid' userid') userid userid' where
|
instance {-# OVERLAPPING #-} (Typeable fileid, Typeable userid, IsWorkflowFieldPayload' fileid userid userid, IsWorkflowFieldPayload' fileid' userid' userid', fileid ~ fileid') => HasTypesCustom WorkflowChildren (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayloadW fileid' userid') userid userid' where
|
||||||
typesCustom f pw@(WorkflowFieldPayloadW p) = case typeOf p `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid userid) of
|
typesCustom f pw@(WorkflowFieldPayloadW p) = case typeOf p `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid userid) of
|
||||||
Just HRefl -> WorkflowFieldPayloadW <$> typesCustom @WorkflowChildren @(WorkflowFieldPayload fileid userid userid) @(WorkflowFieldPayload fileid' userid' userid') @userid @userid' f p
|
Just HRefl -> WorkflowFieldPayloadW <$> typesCustom @WorkflowChildren @(WorkflowFieldPayload fileid userid userid) @(WorkflowFieldPayload fileid' userid' userid') @userid @userid' f p
|
||||||
Nothing -> pure $ unsafeCoerce @(WorkflowFieldPayloadW fileid userid) @(WorkflowFieldPayloadW fileid userid') pw -- We have proof that @p@ does not contain a value of type @userid@, therefor coercion is safe
|
Nothing -> pure $ unsafeCoerce @(WorkflowFieldPayloadW fileid userid) @(WorkflowFieldPayloadW fileid userid') pw -- We have proof that @p@ does not contain a value of type @userid@, therefor coercion is safe
|
||||||
|
|
||||||
instance {-# OVERLAPPING #-} (Typeable userid, Typeable fileid, Ord fileid', IsWorkflowFieldPayload fileid userid (Set fileid), IsWorkflowFieldPayload fileid' userid' (Set fileid'), userid ~ userid') => HasTypesCustom WorkflowChildren (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayloadW fileid' userid') fileid fileid' where
|
instance {-# OVERLAPPING #-} (Typeable userid, Typeable fileid, IsWorkflowFieldPayload' fileid userid fileid, IsWorkflowFieldPayload' fileid' userid' fileid', userid ~ userid') => HasTypesCustom WorkflowChildren (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayloadW fileid' userid') fileid fileid' where
|
||||||
typesCustom f pw@(WorkflowFieldPayloadW p) = case typeOf p `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid (Set fileid)) of
|
typesCustom f pw@(WorkflowFieldPayloadW p) = case typeOf p `eqTypeRep` typeRep @(WorkflowFieldPayload fileid userid fileid) of
|
||||||
Just HRefl -> WorkflowFieldPayloadW <$> typesCustom @WorkflowChildren @(WorkflowFieldPayload fileid userid (Set fileid)) @(WorkflowFieldPayload fileid' userid' (Set fileid')) @(Set fileid) @(Set fileid') (traverseOf (iso Set.toList Set.fromList . traverse) f) p
|
Just HRefl -> WorkflowFieldPayloadW <$> typesCustom @WorkflowChildren @(WorkflowFieldPayload fileid userid fileid) @(WorkflowFieldPayload fileid' userid' fileid') @fileid @fileid' f p
|
||||||
Nothing -> pure $ unsafeCoerce @(WorkflowFieldPayloadW fileid userid) @(WorkflowFieldPayloadW fileid' userid) pw -- We have proof that @p@ does not contain a value of type @fileid@, therefor coercion is safe
|
Nothing -> pure $ unsafeCoerce @(WorkflowFieldPayloadW fileid userid) @(WorkflowFieldPayloadW fileid' userid) pw -- We have proof that @p@ does not contain a value of type @fileid@, therefor coercion is safe
|
||||||
|
|
||||||
instance (IsWorkflowFieldPayload fileid userid payload, IsWorkflowFieldPayload fileid' userid' payload') => HasTypesCustom WorkflowChildren (WorkflowFieldPayload fileid userid payload) (WorkflowFieldPayload fileid' userid' payload') payload payload' where
|
instance (IsWorkflowFieldPayload' fileid userid payload, IsWorkflowFieldPayload' fileid' userid' payload') => HasTypesCustom WorkflowChildren (WorkflowFieldPayload fileid userid payload) (WorkflowFieldPayload fileid' userid' payload') payload payload' where
|
||||||
typesCustom f x = case x ^? _WorkflowFieldPayload of
|
typesCustom f x = case x ^? _WorkflowFieldPayload of
|
||||||
Just x' -> review _WorkflowFieldPayload <$> f x'
|
Just x' -> review _WorkflowFieldPayload <$> f x'
|
||||||
Nothing -> error "@WorkflowFieldPayload fileid userid payload@ does not contain value of type @payload@; this means `IsWorkflowFieldPayload` is invalid"
|
Nothing -> error "@WorkflowFieldPayload fileid userid payload@ does not contain value of type @payload@; this means `IsWorkflowFieldPayload` is invalid"
|
||||||
|
|
||||||
instance (Ord userid, Ord fileid, Typeable payload, Typeable fileid, Typeable userid, IsWorkflowFieldPayload fileid userid payload, IsWorkflowFieldPayload fileid' userid' payload', fileid ~ fileid', userid ~ userid') => HasTypesCustom WorkflowChildren (WorkflowAction fileid userid) (WorkflowAction fileid' userid') payload payload' where
|
instance (Ord userid, Ord fileid, Typeable payload, Typeable fileid, Typeable userid, IsWorkflowFieldPayload' fileid userid payload, IsWorkflowFieldPayload' fileid' userid' payload', fileid ~ fileid', userid ~ userid') => HasTypesCustom WorkflowChildren (WorkflowAction fileid userid) (WorkflowAction fileid' userid') payload payload' where
|
||||||
typesCustom = _wpPayload . typesCustom @WorkflowChildren
|
typesCustom = _wpPayload . typesCustom @WorkflowChildren
|
||||||
|
|
||||||
instance {-# OVERLAPPING #-} (Ord userid', Ord fileid, Typeable fileid, IsWorkflowFieldPayload fileid userid userid, IsWorkflowFieldPayload fileid' userid' userid', fileid ~ fileid') => HasTypesCustom WorkflowChildren (WorkflowAction fileid userid) (WorkflowAction fileid' userid') userid userid' where
|
instance {-# OVERLAPPING #-} (Ord userid', Ord fileid, Typeable fileid, IsWorkflowFieldPayload' fileid userid userid, IsWorkflowFieldPayload' fileid' userid' userid', fileid ~ fileid') => HasTypesCustom WorkflowChildren (WorkflowAction fileid userid) (WorkflowAction fileid' userid') userid userid' where
|
||||||
typesCustom f WorkflowAction{..} = WorkflowAction wpTo wpVia
|
typesCustom f WorkflowAction{..} = WorkflowAction wpTo wpVia
|
||||||
<$> traverseOf (typesCustom @WorkflowChildren @_ @_ @userid @userid') f wpPayload
|
<$> traverseOf (typesCustom @WorkflowChildren @_ @_ @userid @userid') f wpPayload
|
||||||
<*> traverseOf (_Just . _Just) f wpUser
|
<*> traverseOf (_Just . _Just) f wpUser
|
||||||
<*> pure wpTime
|
<*> pure wpTime
|
||||||
|
|
||||||
|
|
||||||
|
workflowStatePayload :: forall fileid userid payload.
|
||||||
|
( HasTypesCustom WorkflowChildren (WorkflowFieldPayloadW fileid userid) (WorkflowFieldPayloadW fileid userid) payload payload
|
||||||
|
, Ord payload
|
||||||
|
)
|
||||||
|
=> WorkflowPayloadLabel -> WorkflowState fileid userid -> Seq (Maybe (Set payload))
|
||||||
|
workflowStatePayload label acts = flip ofoldMap acts $ \WorkflowAction{..} -> Seq.singleton . Map.lookup label $ fmap (setOf $ folded . typesCustom @WorkflowChildren @(WorkflowFieldPayloadW fileid userid) @(WorkflowFieldPayloadW fileid userid) @payload @payload) wpPayload
|
||||||
|
|
||||||
|
|
||||||
----- PathPiece instances -----
|
----- PathPiece instances -----
|
||||||
|
|
||||||
nullaryPathPiece ''WorkflowScope' $ camelToPathPiece' 1 . fromJust . stripSuffix "'"
|
nullaryPathPiece ''WorkflowScope' $ camelToPathPiece' 1 . fromJust . stripSuffix "'"
|
||||||
@ -528,15 +585,19 @@ derivePathPiece ''WorkflowScope (camelToPathPiece' 1) "--"
|
|||||||
omitNothing :: [JSON.Pair] -> [JSON.Pair]
|
omitNothing :: [JSON.Pair] -> [JSON.Pair]
|
||||||
omitNothing = filter . hasn't $ _2 . _Null
|
omitNothing = filter . hasn't $ _2 . _Null
|
||||||
|
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ fieldLabelModifier = camelToPathPiece' 2
|
{ fieldLabelModifier = camelToPathPiece' 2
|
||||||
, constructorTagModifier = camelToPathPiece' 2
|
, constructorTagModifier = camelToPathPiece' 2
|
||||||
} ''WorkflowRole
|
} ''WorkflowRole
|
||||||
|
|
||||||
|
deriveToJSON workflowNodeViewAesonOptions ''WorkflowNodeView
|
||||||
deriveToJSON workflowPayloadViewAesonOptions ''WorkflowPayloadView
|
deriveToJSON workflowPayloadViewAesonOptions ''WorkflowPayloadView
|
||||||
pathPieceJSON ''WorkflowFieldPayload'
|
pathPieceJSON ''WorkflowFieldPayload'
|
||||||
pathPieceJSON ''WorkflowPayloadField'
|
pathPieceJSON ''WorkflowPayloadField'
|
||||||
|
|
||||||
|
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowNodeView userid) where
|
||||||
|
parseJSON = genericParseJSON workflowNodeViewAesonOptions
|
||||||
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowPayloadView userid) where
|
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowPayloadView userid) where
|
||||||
parseJSON = genericParseJSON workflowPayloadViewAesonOptions
|
parseJSON = genericParseJSON workflowPayloadViewAesonOptions
|
||||||
|
|
||||||
@ -567,7 +628,8 @@ instance ToJSON WorkflowGraphEdgeFormOrder where
|
|||||||
instance FromJSON WorkflowGraphEdgeFormOrder where
|
instance FromJSON WorkflowGraphEdgeFormOrder where
|
||||||
parseJSON v = fmap WorkflowGraphEdgeFormOrder $ asum
|
parseJSON v = fmap WorkflowGraphEdgeFormOrder $ asum
|
||||||
[ Just <$> parseJSON v
|
[ Just <$> parseJSON v
|
||||||
, JSON.withText "WorkflowGraphEdgeFormOrder" (bool (fail "WorkflowGraphEdgeFormOrder: unexpected String, expecting either number or \"_\"") (pure Nothing) . (== "_")) v
|
, flip (JSON.withText "WorkflowGraphEdgeFormOrder") v $ \t -> maybe (fail "WorkflowGraphEdgeFormOrder: could not parse String as Number") (return . Just) $ readMay t
|
||||||
|
, flip (JSON.withText "WorkflowGraphEdgeFormOrder") v $ bool (fail "WorkflowGraphEdgeFormOrder: unexpected String, expecting either number or \"_\"") (pure Nothing) . (== "_")
|
||||||
]
|
]
|
||||||
|
|
||||||
instance ToJSONKey WorkflowGraphEdgeFormOrder where
|
instance ToJSONKey WorkflowGraphEdgeFormOrder where
|
||||||
@ -575,9 +637,7 @@ instance ToJSONKey WorkflowGraphEdgeFormOrder where
|
|||||||
where toText' = decodeUtf8 . toStrict . JSON.encodingToLazyByteString . JSON.scientific
|
where toText' = decodeUtf8 . toStrict . JSON.encodingToLazyByteString . JSON.scientific
|
||||||
toEncoding' = JSON.scientificText
|
toEncoding' = JSON.scientificText
|
||||||
instance FromJSONKey WorkflowGraphEdgeFormOrder where
|
instance FromJSONKey WorkflowGraphEdgeFormOrder where
|
||||||
fromJSONKey = JSON.FromJSONKeyTextParser $ \t -> if
|
fromJSONKey = JSON.FromJSONKeyTextParser $ parseJSON . JSON.String
|
||||||
| t == "_" -> pure $ WorkflowGraphEdgeFormOrder Nothing
|
|
||||||
| otherwise -> WorkflowGraphEdgeFormOrder . Just <$> parseJSON (JSON.String t)
|
|
||||||
|
|
||||||
instance (ToJSON fileid, ToJSON userid, ToJSON (FileField fileid)) => ToJSON (WorkflowGraphEdgeForm fileid userid) where
|
instance (ToJSON fileid, ToJSON userid, ToJSON (FileField fileid)) => ToJSON (WorkflowGraphEdgeForm fileid userid) where
|
||||||
toJSON WorkflowGraphEdgeForm{..} = toJSON . flip map wgefFields $ \(toNullable -> disj) -> flip Set.map disj $ \(toNullable -> orderedFields) -> if
|
toJSON WorkflowGraphEdgeForm{..} = toJSON . flip map wgefFields $ \(toNullable -> disj) -> flip Set.map disj $ \(toNullable -> orderedFields) -> if
|
||||||
@ -608,6 +668,7 @@ instance (ToJSON fileid, ToJSON userid, ToJSON (FileField fileid)) => ToJSON (Wo
|
|||||||
, "placeholder" JSON..= wpftPlaceholder
|
, "placeholder" JSON..= wpftPlaceholder
|
||||||
, "tooltip" JSON..= wpftTooltip
|
, "tooltip" JSON..= wpftTooltip
|
||||||
, "default" JSON..= wpftDefault
|
, "default" JSON..= wpftDefault
|
||||||
|
, "large" JSON..= wpftLarge
|
||||||
, "optional" JSON..= wpftOptional
|
, "optional" JSON..= wpftOptional
|
||||||
]
|
]
|
||||||
toJSON (WorkflowPayloadFieldNumber{..}) = JSON.object $ omitNothing
|
toJSON (WorkflowPayloadFieldNumber{..}) = JSON.object $ omitNothing
|
||||||
@ -628,6 +689,13 @@ instance (ToJSON fileid, ToJSON userid, ToJSON (FileField fileid)) => ToJSON (Wo
|
|||||||
, "default" JSON..= wpfbDefault
|
, "default" JSON..= wpfbDefault
|
||||||
, "optional" JSON..= wpfbOptional
|
, "optional" JSON..= wpfbOptional
|
||||||
]
|
]
|
||||||
|
toJSON (WorkflowPayloadFieldDay{..}) = JSON.object $ omitNothing
|
||||||
|
[ "tag" JSON..= WPFDay'
|
||||||
|
, "label" JSON..= wpfdLabel
|
||||||
|
, "tooltip" JSON..= wpfdTooltip
|
||||||
|
, "default" JSON..= wpfdDefault
|
||||||
|
, "optional" JSON..= wpfdOptional
|
||||||
|
]
|
||||||
toJSON (WorkflowPayloadFieldFile{..}) = JSON.object $ omitNothing
|
toJSON (WorkflowPayloadFieldFile{..}) = JSON.object $ omitNothing
|
||||||
[ "tag" JSON..= WPFFile'
|
[ "tag" JSON..= WPFFile'
|
||||||
, "label" JSON..= wpffLabel
|
, "label" JSON..= wpffLabel
|
||||||
@ -672,6 +740,7 @@ instance ( FromJSON fileid, FromJSON userid
|
|||||||
wpftPlaceholder <- o JSON..:? "placeholder"
|
wpftPlaceholder <- o JSON..:? "placeholder"
|
||||||
wpftTooltip <- o JSON..:? "tooltip"
|
wpftTooltip <- o JSON..:? "tooltip"
|
||||||
wpftDefault <- o JSON..:? "default"
|
wpftDefault <- o JSON..:? "default"
|
||||||
|
wpftLarge <- o JSON..:? "large" JSON..!= False
|
||||||
wpftOptional <- o JSON..: "optional"
|
wpftOptional <- o JSON..: "optional"
|
||||||
return $ WorkflowPayloadSpec WorkflowPayloadFieldText{..}
|
return $ WorkflowPayloadSpec WorkflowPayloadFieldText{..}
|
||||||
WPFNumber' -> do
|
WPFNumber' -> do
|
||||||
@ -681,15 +750,21 @@ instance ( FromJSON fileid, FromJSON userid
|
|||||||
wpfnDefault <- (o JSON..:? "default" :: Parser (Maybe Scientific))
|
wpfnDefault <- (o JSON..:? "default" :: Parser (Maybe Scientific))
|
||||||
wpfnMin <- o JSON..:? "min"
|
wpfnMin <- o JSON..:? "min"
|
||||||
wpfnMax <- o JSON..:? "max"
|
wpfnMax <- o JSON..:? "max"
|
||||||
wpfnStep <- o JSON..: "step"
|
wpfnStep <- o JSON..:? "step"
|
||||||
wpfnOptional <- o JSON..: "optional"
|
wpfnOptional <- o JSON..: "optional"
|
||||||
return $ WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}
|
return $ WorkflowPayloadSpec WorkflowPayloadFieldNumber{..}
|
||||||
WPFBool' -> do
|
WPFBool' -> do
|
||||||
wpfbLabel <- o JSON..: "label"
|
wpfbLabel <- o JSON..: "label"
|
||||||
wpfbTooltip <- o JSON..:? "tooltip"
|
wpfbTooltip <- o JSON..:? "tooltip"
|
||||||
wpfbOptional <- o JSON..: "optional"
|
wpfbOptional <- o JSON..:? "optional"
|
||||||
wpfbDefault <- (o JSON..: "default" :: Parser (Maybe Bool))
|
wpfbDefault <- (o JSON..:? "default" :: Parser (Maybe Bool))
|
||||||
return $ WorkflowPayloadSpec WorkflowPayloadFieldBool{..}
|
return $ WorkflowPayloadSpec WorkflowPayloadFieldBool{..}
|
||||||
|
WPFDay' -> do
|
||||||
|
wpfdLabel <- o JSON..: "label"
|
||||||
|
wpfdTooltip <- o JSON..:? "tooltip"
|
||||||
|
wpfdOptional <- o JSON..: "optional"
|
||||||
|
wpfdDefault <- (o JSON..:? "default" :: Parser (Maybe Day))
|
||||||
|
return $ WorkflowPayloadSpec WorkflowPayloadFieldDay{..}
|
||||||
WPFFile' -> do
|
WPFFile' -> do
|
||||||
wpffLabel <- o JSON..: "label"
|
wpffLabel <- o JSON..: "label"
|
||||||
wpffTooltip <- o JSON..:? "tooltip"
|
wpffTooltip <- o JSON..:? "tooltip"
|
||||||
@ -753,18 +828,18 @@ instance (ToJSON fileid, ToJSON userid) => ToJSON (WorkflowFieldPayloadW fileid
|
|||||||
[ "tag" JSON..= WFPBool'
|
[ "tag" JSON..= WFPBool'
|
||||||
, toPathPiece WFPBool' JSON..= b
|
, toPathPiece WFPBool' JSON..= b
|
||||||
]
|
]
|
||||||
toJSON (WorkflowFieldPayloadW (WFPFiles fid)) = JSON.object
|
toJSON (WorkflowFieldPayloadW (WFPDay d)) = JSON.object
|
||||||
[ "tag" JSON..= WFPFiles'
|
[ "tag" JSON..= WFPDay'
|
||||||
, toPathPiece WFPFiles' JSON..= fid
|
, toPathPiece WFPDay' JSON..= d
|
||||||
|
]
|
||||||
|
toJSON (WorkflowFieldPayloadW (WFPFile fid)) = JSON.object
|
||||||
|
[ "tag" JSON..= WFPFile'
|
||||||
|
, toPathPiece WFPFile' JSON..= fid
|
||||||
]
|
]
|
||||||
toJSON (WorkflowFieldPayloadW (WFPUser uid)) = JSON.object
|
toJSON (WorkflowFieldPayloadW (WFPUser uid)) = JSON.object
|
||||||
[ "tag" JSON..= WFPUser'
|
[ "tag" JSON..= WFPUser'
|
||||||
, toPathPiece WFPUser' JSON..= uid
|
, toPathPiece WFPUser' JSON..= uid
|
||||||
]
|
]
|
||||||
toJSON (WorkflowFieldPayloadW (WFPMultiple uid)) = JSON.object
|
|
||||||
[ "tag" JSON..= WFPMultiple'
|
|
||||||
, toPathPiece WFPMultiple' JSON..= uid
|
|
||||||
]
|
|
||||||
instance (Ord fileid, FromJSON fileid, FromJSON userid, Typeable fileid, Typeable userid) => FromJSON (WorkflowFieldPayloadW fileid userid) where
|
instance (Ord fileid, FromJSON fileid, FromJSON userid, Typeable fileid, Typeable userid) => FromJSON (WorkflowFieldPayloadW fileid userid) where
|
||||||
parseJSON = JSON.withObject "WorkflowFieldPayloadW" $ \o -> do
|
parseJSON = JSON.withObject "WorkflowFieldPayloadW" $ \o -> do
|
||||||
fieldTag <- o JSON..: "tag"
|
fieldTag <- o JSON..: "tag"
|
||||||
@ -778,15 +853,15 @@ instance (Ord fileid, FromJSON fileid, FromJSON userid, Typeable fileid, Typeabl
|
|||||||
WFPBool' -> do
|
WFPBool' -> do
|
||||||
b <- o JSON..: toPathPiece WFPBool'
|
b <- o JSON..: toPathPiece WFPBool'
|
||||||
return $ WorkflowFieldPayloadW $ WFPBool b
|
return $ WorkflowFieldPayloadW $ WFPBool b
|
||||||
WFPFiles' -> do
|
WFPDay' -> do
|
||||||
fid <- o JSON..: toPathPiece WFPFiles'
|
b <- o JSON..: toPathPiece WFPDay'
|
||||||
return $ WorkflowFieldPayloadW $ WFPFiles fid
|
return $ WorkflowFieldPayloadW $ WFPDay b
|
||||||
|
WFPFile' -> do
|
||||||
|
fid <- o JSON..: toPathPiece WFPFile'
|
||||||
|
return $ WorkflowFieldPayloadW $ WFPFile fid
|
||||||
WFPUser' -> do
|
WFPUser' -> do
|
||||||
uid <- o JSON..: toPathPiece WFPUser'
|
uid <- o JSON..: toPathPiece WFPUser'
|
||||||
return $ WorkflowFieldPayloadW $ WFPUser uid
|
return $ WorkflowFieldPayloadW $ WFPUser uid
|
||||||
WFPMultiple' -> do
|
|
||||||
uid <- o JSON..: toPathPiece WFPMultiple'
|
|
||||||
return $ WorkflowFieldPayloadW $ WFPMultiple uid
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,22 @@
|
|||||||
|
{-# OPTIONS -Wno-error=redundant-constraints #-}
|
||||||
|
|
||||||
module Utils.Workflow
|
module Utils.Workflow
|
||||||
( _DBWorkflowScope
|
( _DBWorkflowScope
|
||||||
, fromRouteWorkflowScope, toRouteWorkflowScope
|
, fromRouteWorkflowScope, toRouteWorkflowScope
|
||||||
, _DBWorkflowGraph
|
, _DBWorkflowGraph
|
||||||
, _DBWorkflowState
|
, _DBWorkflowState
|
||||||
|
, decryptWorkflowStateIndex, encryptWorkflowStateIndex
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import.NoFoundation
|
import Import.NoFoundation
|
||||||
|
|
||||||
|
import qualified Data.CryptoID.Class.ImplicitNamespace as I
|
||||||
|
import qualified Crypto.MAC.KMAC as Crypto
|
||||||
|
import qualified Data.ByteArray as BA
|
||||||
|
import qualified Data.Binary as Binary
|
||||||
|
import Crypto.Hash.Algorithms (SHAKE256)
|
||||||
|
import Language.Haskell.TH (nameBase)
|
||||||
|
|
||||||
|
|
||||||
_DBWorkflowScope :: Iso' (WorkflowScope TermId SchoolId CourseId) (WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey)
|
_DBWorkflowScope :: Iso' (WorkflowScope TermId SchoolId CourseId) (WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey)
|
||||||
_DBWorkflowScope = iso toScope' toScope
|
_DBWorkflowScope = iso toScope' toScope
|
||||||
@ -46,3 +56,31 @@ _DBWorkflowState = iso toDB fromDB
|
|||||||
where
|
where
|
||||||
toDB = over (typesCustom @WorkflowChildren @(WorkflowState FileReference UserId) @(WorkflowState FileReference SqlBackendKey) @UserId @SqlBackendKey) (view _SqlKey)
|
toDB = over (typesCustom @WorkflowChildren @(WorkflowState FileReference UserId) @(WorkflowState FileReference SqlBackendKey) @UserId @SqlBackendKey) (view _SqlKey)
|
||||||
fromDB = over (typesCustom @WorkflowChildren @(WorkflowState FileReference SqlBackendKey) @(WorkflowState FileReference UserId) @SqlBackendKey @UserId) (review _SqlKey)
|
fromDB = over (typesCustom @WorkflowChildren @(WorkflowState FileReference SqlBackendKey) @(WorkflowState FileReference UserId) @SqlBackendKey @UserId) (review _SqlKey)
|
||||||
|
|
||||||
|
|
||||||
|
data WorkflowStateIndexKeyException
|
||||||
|
= WorkflowStateIndexCryptoIDKeyCouldNotDecodeRandom
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
deriving anyclass (Exception)
|
||||||
|
|
||||||
|
workflowStateIndexCryptoIDKey :: (MonadCrypto m, MonadCryptoKey m ~ CryptoIDKey) => WorkflowWorkflowId -> m CryptoIDKey
|
||||||
|
workflowStateIndexCryptoIDKey wwId = cryptoIDKey $ \cIDKey -> either (const $ throwM WorkflowStateIndexCryptoIDKeyCouldNotDecodeRandom) (views _3 return) . Binary.decodeOrFail . fromStrict . BA.convert $
|
||||||
|
Crypto.kmac @(SHAKE256 CryptoIDCipherKeySize) (encodeUtf8 . (pack :: String -> Text) $ nameBase 'workflowStateIndexCryptoIDKey) (toStrict $ Binary.encode wwId) cIDKey
|
||||||
|
|
||||||
|
encryptWorkflowStateIndex :: ( MonadCrypto m
|
||||||
|
, MonadCryptoKey m ~ CryptoIDKey
|
||||||
|
, MonadHandler m
|
||||||
|
)
|
||||||
|
=> WorkflowWorkflowId -> WorkflowStateIndex -> m CryptoUUIDWorkflowStateIndex
|
||||||
|
encryptWorkflowStateIndex wwId stIx = do
|
||||||
|
cIDKey <- workflowStateIndexCryptoIDKey wwId
|
||||||
|
$cachedHereBinary (wwId, stIx) . flip runReaderT cIDKey $ I.encrypt stIx
|
||||||
|
|
||||||
|
decryptWorkflowStateIndex :: ( MonadCrypto m
|
||||||
|
, MonadCryptoKey m ~ CryptoIDKey
|
||||||
|
, MonadHandler m
|
||||||
|
)
|
||||||
|
=> WorkflowWorkflowId -> CryptoUUIDWorkflowStateIndex -> m WorkflowStateIndex
|
||||||
|
decryptWorkflowStateIndex wwId cID = do
|
||||||
|
cIDKey <- workflowStateIndexCryptoIDKey wwId
|
||||||
|
$cachedHereBinary (wwId, cID) . flip runReaderT cIDKey $ I.decrypt cID
|
||||||
|
|||||||
41
src/Utils/Workflow/Lint.hs
Normal file
41
src/Utils/Workflow/Lint.hs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
module Utils.Workflow.Lint
|
||||||
|
( lintWorkflowGraph
|
||||||
|
, WorkflowGraphLinterIssue(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import.NoFoundation
|
||||||
|
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
|
||||||
|
data WorkflowGraphLinterIssue
|
||||||
|
= WGLUnknownGraphNodeLabel WorkflowGraphNodeLabel
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
|
instance Exception WorkflowGraphLinterIssue where
|
||||||
|
displayException = \case
|
||||||
|
WGLUnknownGraphNodeLabel nodeLbl -> unpack [st|Unknown GraphNodeLabel: “#{toPathPiece nodeLbl}”|]
|
||||||
|
|
||||||
|
lintWorkflowGraph :: WorkflowGraph fileid userid -> Maybe (NonNull (Set WorkflowGraphLinterIssue))
|
||||||
|
lintWorkflowGraph graph = fromNullable . Set.fromList $ concatMap ($ graph)
|
||||||
|
[ checkEdgesForUnknownGraphNodeLabel
|
||||||
|
-- Future ideas:
|
||||||
|
-- - node with no outgoing edges that isn't final
|
||||||
|
-- - WorkflowRolePayloadReference for unknown payload
|
||||||
|
-- - wgePayloadRestriction for unknown payload
|
||||||
|
-- - Undefined field order
|
||||||
|
-- - FieldReference for payload not defined in same form
|
||||||
|
-- - WorkflowRolePayloadReference to payload without user fields
|
||||||
|
-- - unreachable nodes
|
||||||
|
-- - all initial edges have only payload-reference
|
||||||
|
-- - cycles of automatic edges (also consider payload restrictions; computationally equivalent to SAT)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
checkEdgesForUnknownGraphNodeLabel WorkflowGraph{wgNodes} = foldMap (pure . WGLUnknownGraphNodeLabel) $ Set.fromList edgeNodeLabels `Set.difference` Map.keysSet wgNodes
|
||||||
|
where
|
||||||
|
edges = foldMap (Map.elems . wgnEdges) wgNodes
|
||||||
|
edgeNodeLabels = flip foldMap edges $ \case
|
||||||
|
WorkflowGraphEdgeManual{wgeSource} -> pure wgeSource
|
||||||
|
WorkflowGraphEdgeAutomatic{wgeSource} -> pure wgeSource
|
||||||
|
WorkflowGraphEdgeInitial{} -> []
|
||||||
14
templates/workflows/workflow.hamlet
Normal file
14
templates/workflows/workflow.hamlet
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
$newline never
|
||||||
|
<section>
|
||||||
|
<h2>
|
||||||
|
_{MsgWorkflowWorkflowWorkflowHistoryHeading}
|
||||||
|
|
||||||
|
<ul .workflow-history>
|
||||||
|
$forall histItem <- workflowHistory
|
||||||
|
^{historyToWidget histItem}
|
||||||
|
$maybe edgeView <- mEdgeView
|
||||||
|
<section>
|
||||||
|
<h2>
|
||||||
|
_{MsgWorkflowWorkflowWorkflowEdgeFormHeading}
|
||||||
|
^{edgeView}
|
||||||
|
|
||||||
59
templates/workflows/workflow/history-item.hamlet
Normal file
59
templates/workflows/workflow/history-item.hamlet
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
$newline never
|
||||||
|
<li .workflow-history--item :is (_Just . _WHIASelf) whiUser:.workflow-history-item__self>
|
||||||
|
<div .workflow-history--item-user>
|
||||||
|
$maybe user <- whiUser
|
||||||
|
$case user
|
||||||
|
$of WHIASelf
|
||||||
|
<span .workflow-history--item-user-special>
|
||||||
|
_{MsgWorkflowWorkflowWorkflowHistoryUserSelf}
|
||||||
|
$of WHIAOther mUser
|
||||||
|
$maybe Entity _ User{userDisplayName, userSurname} <- mUser
|
||||||
|
^{nameWidget userDisplayName userSurname}
|
||||||
|
$nothing
|
||||||
|
<span .workflow-history--item-user-special>
|
||||||
|
_{MsgWorkflowWorkflowWorkflowHistoryUserNotLoggedIn}
|
||||||
|
$of WHIAGone
|
||||||
|
<span .workflow-history--item-user-special>
|
||||||
|
_{MsgWorkflowWorkflowWorkflowHistoryUserGone}
|
||||||
|
$of WHIAHidden
|
||||||
|
<span .workflow-history--item-user-special>
|
||||||
|
_{MsgWorkflowWorkflowWorkflowHistoryUserHidden}
|
||||||
|
$nothing
|
||||||
|
<span .workflow-history--item-user-special>
|
||||||
|
_{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>
|
||||||
|
$maybe mFromLbl <- whiFrom
|
||||||
|
$maybe fromLbl <- mFromLbl
|
||||||
|
#{fromLbl}
|
||||||
|
$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>
|
||||||
|
<dl .deflist>
|
||||||
|
$forall (payloadLbl, (newPayload, mFileRoute)) <- whiPayloadChanges
|
||||||
|
<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}
|
||||||
9
test/Data/NonNull/TestInstances.hs
Normal file
9
test/Data/NonNull/TestInstances.hs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module Data.NonNull.TestInstances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
|
||||||
|
|
||||||
|
instance (Arbitrary a, MonoFoldable a) => Arbitrary (NonNull a) where
|
||||||
|
arbitrary = arbitrary `suchThatMap` fromNullable
|
||||||
@ -33,6 +33,8 @@ import qualified Data.Conduit.Combinators as C
|
|||||||
|
|
||||||
import qualified Data.Yaml as Yaml
|
import qualified Data.Yaml as Yaml
|
||||||
|
|
||||||
|
import Utils.Workflow.Lint
|
||||||
|
|
||||||
|
|
||||||
testdataDir :: FilePath
|
testdataDir :: FilePath
|
||||||
testdataDir = "testdata"
|
testdataDir = "testdata"
|
||||||
@ -1285,25 +1287,9 @@ fillDb = do
|
|||||||
|
|
||||||
liftIO . LBS.writeFile (testdataDir </> "bigAlloc_ordinal.csv") $ Csv.encode ordinalPriorities
|
liftIO . LBS.writeFile (testdataDir </> "bigAlloc_ordinal.csv") $ Csv.encode ordinalPriorities
|
||||||
|
|
||||||
do
|
|
||||||
workflowDefinitionGraph <- Yaml.decodeFileThrow $ testdataDir </> "exam-rooms.yaml"
|
|
||||||
let
|
|
||||||
examRoomsWorkflowDef = WorkflowDefinition{..}
|
|
||||||
where workflowDefinitionInstanceCategory = Just "rooms"
|
|
||||||
workflowDefinitionName = "exam-rooms"
|
|
||||||
workflowDefinitionScope = WSGlobal' -- TODO
|
|
||||||
wdId <- insert examRoomsWorkflowDef
|
|
||||||
let
|
|
||||||
examRoomsWorkflowInst = WorkflowInstance{..}
|
|
||||||
where workflowInstanceDefinition = Just wdId
|
|
||||||
workflowInstanceGraph = workflowDefinitionGraph
|
|
||||||
workflowInstanceScope = WSGlobal -- TODO
|
|
||||||
workflowInstanceName = workflowDefinitionName examRoomsWorkflowDef
|
|
||||||
workflowInstanceCategory = workflowDefinitionInstanceCategory examRoomsWorkflowDef
|
|
||||||
insert_ examRoomsWorkflowInst
|
|
||||||
|
|
||||||
do
|
do
|
||||||
workflowDefinitionGraph <- Yaml.decodeFileThrow $ testdataDir </> "theses.yaml"
|
workflowDefinitionGraph <- Yaml.decodeFileThrow $ testdataDir </> "theses.yaml"
|
||||||
|
for_ (lintWorkflowGraph workflowDefinitionGraph) $ mapM_ throwM
|
||||||
let
|
let
|
||||||
thesesWorkflowDef = WorkflowDefinition{..}
|
thesesWorkflowDef = WorkflowDefinition{..}
|
||||||
where workflowDefinitionInstanceCategory = Just "theses"
|
where workflowDefinitionInstanceCategory = Just "theses"
|
||||||
|
|||||||
12
test/Database/Persist/Sql/Types/TestInstances.hs
Normal file
12
test/Database/Persist/Sql/Types/TestInstances.hs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module Database.Persist.Sql.Types.TestInstances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
|
||||||
|
import Database.Persist.Sql
|
||||||
|
|
||||||
|
|
||||||
|
deriving newtype instance Arbitrary (BackendKey SqlBackend)
|
||||||
|
deriving newtype instance Arbitrary (BackendKey SqlWriteBackend)
|
||||||
|
deriving newtype instance Arbitrary (BackendKey SqlReadBackend)
|
||||||
@ -82,6 +82,18 @@ instance Arbitrary CourseEventR where
|
|||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary AdminWorkflowDefinitionR where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary GlobalWorkflowInstanceR where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary GlobalWorkflowWorkflowR where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary (Route UniWorX) where
|
instance Arbitrary (Route UniWorX) where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|||||||
72
test/Model/Types/FileSpec.hs
Normal file
72
test/Model/Types/FileSpec.hs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
|
module Model.Types.FileSpec where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
import TestInstances ()
|
||||||
|
|
||||||
|
import Data.Conduit
|
||||||
|
import qualified Data.Conduit.Combinators as C
|
||||||
|
|
||||||
|
import Data.Ratio ((%))
|
||||||
|
|
||||||
|
import System.FilePath
|
||||||
|
import Data.Time
|
||||||
|
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
|
||||||
|
scaleRatio :: Rational -> Int -> Int
|
||||||
|
scaleRatio r = ceiling . (* r) . fromIntegral
|
||||||
|
|
||||||
|
instance (LazySequence lazy strict, Arbitrary lazy, Monad m) => Arbitrary (ConduitT () strict m ()) where
|
||||||
|
arbitrary = C.sourceLazy <$> arbitrary
|
||||||
|
|
||||||
|
instance Monad m => Arbitrary (File m) where
|
||||||
|
arbitrary = do
|
||||||
|
fileTitle <- scale (scaleRatio $ 1 % 8) $ (joinPath <$> arbitrary) `suchThat` (any $ not . isPathSeparator)
|
||||||
|
date <- addDays <$> arbitrary <*> pure (fromGregorian 2043 7 2)
|
||||||
|
fileModified <- (addUTCTime <$> arbitrary <*> pure (UTCTime date 0)) `suchThat` inZipRange
|
||||||
|
fileContent <- oneof
|
||||||
|
[ pure Nothing
|
||||||
|
, Just <$> scale (scaleRatio $ 7 % 8) arbitrary
|
||||||
|
]
|
||||||
|
return File{..}
|
||||||
|
where
|
||||||
|
inZipRange :: UTCTime -> Bool
|
||||||
|
inZipRange time
|
||||||
|
| time > UTCTime (fromGregorian 1980 1 1) 0
|
||||||
|
, time < UTCTime (fromGregorian 2107 1 1) 0
|
||||||
|
= True
|
||||||
|
| otherwise
|
||||||
|
= False
|
||||||
|
|
||||||
|
instance Arbitrary FileReference where
|
||||||
|
arbitrary = pureFileToFileReference <$> arbitrary
|
||||||
|
|
||||||
|
instance Arbitrary a => Arbitrary (FileFieldUserOption a) where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary add => Arbitrary (FileReferenceTitleMap FileReference add) where
|
||||||
|
arbitrary = do
|
||||||
|
fRefs <- arbitrary
|
||||||
|
fmap (review _FileReferenceFileReferenceTitleMap . Map.fromList) . for fRefs $ \FileReference{..} -> (fileReferenceTitle, ) . (fileReferenceContent, fileReferenceModified, ) <$> arbitrary
|
||||||
|
|
||||||
|
instance Arbitrary (FileReferenceTitleMap fileid (FileFieldUserOption Bool)) => Arbitrary (FileField fileid) where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
parallel $ do
|
||||||
|
lawsCheckHspec (Proxy @PureFile)
|
||||||
|
[ eqLaws, ordLaws, showLaws ]
|
||||||
|
lawsCheckHspec (Proxy @FileReference)
|
||||||
|
[ eqLaws, ordLaws, hashableLaws, binaryLaws, jsonLaws ]
|
||||||
|
lawsCheckHspec (Proxy @(FileFieldUserOption Bool))
|
||||||
|
[ eqLaws, ordLaws, showLaws, showReadLaws, jsonLaws ]
|
||||||
|
lawsCheckHspec (Proxy @(FileReferenceTitleMap FileReference (FileFieldUserOption Bool)))
|
||||||
|
[ eqLaws, ordLaws, semigroupLaws, monoidLaws, semigroupMonoidLaws, idempotentSemigroupLaws, commutativeSemigroupLaws ]
|
||||||
|
lawsCheckHspec (Proxy @(FileField FileReference))
|
||||||
|
[ eqLaws, ordLaws, jsonLaws ]
|
||||||
150
test/Model/Types/WorkflowSpec.hs
Normal file
150
test/Model/Types/WorkflowSpec.hs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
module Model.Types.WorkflowSpec where
|
||||||
|
|
||||||
|
import TestImport hiding (NonEmpty)
|
||||||
|
import TestInstances ()
|
||||||
|
|
||||||
|
import Data.Scientific (Scientific)
|
||||||
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
|
|
||||||
|
import Utils.I18nSpec ()
|
||||||
|
import Model.Types.FileSpec ()
|
||||||
|
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
import qualified Data.Aeson as Aeson
|
||||||
|
|
||||||
|
import Control.Lens.Extras (is)
|
||||||
|
|
||||||
|
import Utils.I18n
|
||||||
|
|
||||||
|
|
||||||
|
instance Arbitrary WorkflowPayloadLabel where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadSpec fileid userid) where
|
||||||
|
arbitrary = oneof
|
||||||
|
[ WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Text)
|
||||||
|
, WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Scientific)
|
||||||
|
, WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Bool)
|
||||||
|
, WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Day)
|
||||||
|
, WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid (Set fileid))
|
||||||
|
, WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid userid)
|
||||||
|
, WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid WorkflowPayloadFieldReference)
|
||||||
|
, WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid (NonEmpty (WorkflowFieldPayloadW fileid userid)))
|
||||||
|
]
|
||||||
|
|
||||||
|
instance Arbitrary (WorkflowPayloadField fileid userid Text) where
|
||||||
|
arbitrary = WorkflowPayloadFieldText
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
instance Arbitrary (WorkflowPayloadField fileid userid Scientific) where
|
||||||
|
arbitrary = WorkflowPayloadFieldNumber
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
instance Arbitrary (WorkflowPayloadField fileid userid Bool) where
|
||||||
|
arbitrary = WorkflowPayloadFieldBool
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
instance Arbitrary (WorkflowPayloadField fileid userid Day) where
|
||||||
|
arbitrary = WorkflowPayloadFieldDay
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
instance (Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadField fileid userid (Set fileid)) where
|
||||||
|
arbitrary = WorkflowPayloadFieldFile
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
instance Arbitrary userid => Arbitrary (WorkflowPayloadField fileid userid userid) where
|
||||||
|
arbitrary = oneof
|
||||||
|
[ WorkflowPayloadFieldUser
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
, pure WorkflowPayloadFieldCaptureUser
|
||||||
|
]
|
||||||
|
instance Arbitrary (WorkflowPayloadField fileid userid WorkflowPayloadFieldReference) where
|
||||||
|
arbitrary = WorkflowPayloadFieldReference
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadField fileid userid (NonEmpty (WorkflowFieldPayloadW fileid userid))) where
|
||||||
|
arbitrary = WorkflowPayloadFieldMultiple
|
||||||
|
<$> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
<*> scale (`div` 2) arbitrary
|
||||||
|
|
||||||
|
instance Arbitrary WorkflowGraphEdgeFormOrder where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Ord userid, Ord (FileField fileid), Arbitrary (FileField fileid)) => Arbitrary (WorkflowGraphEdgeForm fileid userid) where
|
||||||
|
arbitrary = WorkflowGraphEdgeForm . Map.fromList . mapMaybe (\(l, s) -> (l, ) <$> fromNullable (Set.fromList . mapMaybe fromNullable $ map Map.fromList s)) <$> listOf ((,) <$> scale (`div` 2) arbitrary <*> scale (`div` 2) (listOf . scale (`div` 2) . listOf $ (,) <$> scale (`div` 2) arbitrary <*> scale (`div` 2) arbitrary))
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance (Arbitrary fileid, Arbitrary userid, Ord fileid, Typeable userid, Typeable fileid) => Arbitrary (WorkflowFieldPayloadW fileid userid) where
|
||||||
|
arbitrary = oneof
|
||||||
|
[ WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Text)
|
||||||
|
, WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Scientific)
|
||||||
|
, WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Bool)
|
||||||
|
, WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Day)
|
||||||
|
, WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid (Set fileid))
|
||||||
|
, WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid userid)
|
||||||
|
]
|
||||||
|
|
||||||
|
instance (Arbitrary payload, IsWorkflowFieldPayload fileid userid payload) => Arbitrary (WorkflowFieldPayload fileid userid payload) where
|
||||||
|
arbitrary = review _WorkflowFieldPayload <$> arbitrary
|
||||||
|
|
||||||
|
instance Arbitrary WorkflowScope' where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
describe "WorkflowPayloadSpec" $ do
|
||||||
|
it "json-roundtrips some examples" $ do
|
||||||
|
let roundtrip val = Aeson.eitherDecode (Aeson.encode val) `shouldBe` Right val
|
||||||
|
|
||||||
|
-- Generated tests that failed previously
|
||||||
|
roundtrip $ WorkflowPayloadSpec @FileReference @SqlBackendKey (WorkflowPayloadFieldNumber {wpfnLabel = I18n {i18nFallback = "\368366\901557\714616k", i18nFallbackLang = Nothing, i18nTranslations = Map.fromList [("",""),("Jak8","\125553E")]}, wpfnPlaceholder = Just (I18n {i18nFallback = "\303706\543092", i18nFallbackLang = Nothing, i18nTranslations = Map.fromList []}), wpfnTooltip = Nothing, wpfnDefault = Nothing, wpfnMin = Nothing, wpfnMax = Just 0.1, wpfnStep = Nothing, wpfnOptional = False})
|
||||||
|
|
||||||
|
describe "WorkflowGraphEdgeForm" $ do
|
||||||
|
it "json-decodes some examples" $ do
|
||||||
|
let decodes bs = Aeson.decode bs `shouldSatisfy` is (_Just @(WorkflowGraphEdgeForm FileReference SqlBackendKey))
|
||||||
|
|
||||||
|
decodes "{\"\": [{\"tag\": \"capture-user\"}]}"
|
||||||
|
decodes "{\"\": [{\"_\": {\"tag\": \"capture-user\"}}]}"
|
||||||
|
decodes "{\"\": [{\"1\": {\"tag\": \"capture-user\"}}]}"
|
||||||
|
decodes "{\"\": [{\"-1\": {\"tag\": \"capture-user\"}}]}"
|
||||||
|
decodes "{\"\": [{\"tag\": \"capture-user\"}, {\"_\": {\"tag\": \"capture-user\"}}]}"
|
||||||
|
decodes "{\"\": [{\"tag\": \"capture-user\"}, {\"1\": {\"tag\": \"capture-user\"}}]}"
|
||||||
|
decodes "{\"\": [{\"_\": {\"tag\": \"capture-user\"}}, {\"1\": {\"tag\": \"capture-user\"}}]}"
|
||||||
|
decodes "{\"\": [{\"0.1\":{\"tag\": \"capture-user\"}}, {\"-0.1\":{\"tag\": \"capture-user\"}}]}"
|
||||||
|
|
||||||
|
parallel $ do
|
||||||
|
lawsCheckHspec (Proxy @WorkflowGraphEdgeFormOrder)
|
||||||
|
[ eqLaws, ordLaws, semigroupLaws, monoidLaws, semigroupMonoidLaws, commutativeSemigroupLaws, idempotentSemigroupLaws, showLaws, showReadLaws, jsonLaws, jsonKeyLaws ]
|
||||||
|
lawsCheckHspec (Proxy @(WorkflowPayloadSpec FileReference SqlBackendKey))
|
||||||
|
[ eqLaws, ordLaws, jsonLaws ]
|
||||||
|
modifyMaxSize (`div` 4) $ lawsCheckHspec (Proxy @(WorkflowGraphEdgeForm FileReference SqlBackendKey))
|
||||||
|
[ eqLaws, ordLaws, jsonLaws ]
|
||||||
|
lawsCheckHspec (Proxy @WorkflowScope')
|
||||||
|
[ eqLaws, ordLaws, boundedEnumLaws, showLaws, showReadLaws, universeLaws, finiteLaws, pathPieceLaws, jsonLaws, persistFieldLaws, binaryLaws ]
|
||||||
@ -1,6 +1,8 @@
|
|||||||
{-# LANGUAGE UndecidableInstances #-}
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
module Model.TypesSpec where
|
module Model.TypesSpec
|
||||||
|
( module Model.TypesSpec
|
||||||
|
) where
|
||||||
|
|
||||||
import TestImport
|
import TestImport
|
||||||
import Settings
|
import Settings
|
||||||
@ -17,9 +19,6 @@ import Yesod.Auth.Util.PasswordStore
|
|||||||
|
|
||||||
import Database.Persist.Sql (SqlBackend, fromSqlKey, toSqlKey)
|
import Database.Persist.Sql (SqlBackend, fromSqlKey, toSqlKey)
|
||||||
|
|
||||||
import Text.Blaze.Html
|
|
||||||
import Text.Blaze.Renderer.Text
|
|
||||||
|
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
import Network.IP.Addr
|
import Network.IP.Addr
|
||||||
@ -39,9 +38,10 @@ import qualified Data.ByteString.Lazy as LBS
|
|||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
import Model.Types.WorkflowSpec as Model.TypesSpec ()
|
||||||
|
|
||||||
|
import Text.Blaze.TestInstances ()
|
||||||
|
|
||||||
instance (Arbitrary a, MonoFoldable a) => Arbitrary (NonNull a) where
|
|
||||||
arbitrary = arbitrary `suchThatMap` fromNullable
|
|
||||||
|
|
||||||
instance Arbitrary Season where
|
instance Arbitrary Season where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
@ -211,10 +211,6 @@ instance {-# OVERLAPPABLE #-} ToBackendKey SqlBackend record => Arbitrary (Key r
|
|||||||
arbitrary = toSqlKey <$> arbitrary
|
arbitrary = toSqlKey <$> arbitrary
|
||||||
shrink = map toSqlKey . shrink . fromSqlKey
|
shrink = map toSqlKey . shrink . fromSqlKey
|
||||||
|
|
||||||
instance Arbitrary Html where
|
|
||||||
arbitrary = (preEscapedToHtml :: String -> Html) . getPrintableString <$> arbitrary
|
|
||||||
shrink = map preEscapedToHtml . shrink . renderMarkup
|
|
||||||
|
|
||||||
instance Arbitrary OccurrenceSchedule where
|
instance Arbitrary OccurrenceSchedule where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|||||||
@ -24,9 +24,6 @@ import qualified Data.Char as Char
|
|||||||
|
|
||||||
import Utils
|
import Utils
|
||||||
|
|
||||||
import System.FilePath
|
|
||||||
import Data.Time
|
|
||||||
|
|
||||||
import Data.CryptoID.Poly
|
import Data.CryptoID.Poly
|
||||||
import qualified Data.CryptoID.Class.ImplicitNamespace as Implicit
|
import qualified Data.CryptoID.Class.ImplicitNamespace as Implicit
|
||||||
|
|
||||||
@ -34,11 +31,6 @@ import Control.Monad.Catch.Pure (Catch, runCatch)
|
|||||||
|
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
|
|
||||||
import Data.Conduit
|
|
||||||
import qualified Data.Conduit.Combinators as C
|
|
||||||
|
|
||||||
import Data.Ratio ((%))
|
|
||||||
|
|
||||||
import Data.Universe
|
import Data.Universe
|
||||||
|
|
||||||
|
|
||||||
@ -144,31 +136,6 @@ instance Arbitrary User where
|
|||||||
return User{..}
|
return User{..}
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
instance (LazySequence lazy strict, Arbitrary lazy, Monad m) => Arbitrary (ConduitT () strict m ()) where
|
|
||||||
arbitrary = C.sourceLazy <$> arbitrary
|
|
||||||
|
|
||||||
scaleRatio :: Rational -> Int -> Int
|
|
||||||
scaleRatio r = ceiling . (* r) . fromIntegral
|
|
||||||
|
|
||||||
instance Monad m => Arbitrary (File m) where
|
|
||||||
arbitrary = do
|
|
||||||
fileTitle <- scale (scaleRatio $ 1 % 8) $ (joinPath <$> arbitrary) `suchThat` (any $ not . isPathSeparator)
|
|
||||||
date <- addDays <$> arbitrary <*> pure (fromGregorian 2043 7 2)
|
|
||||||
fileModified <- (addUTCTime <$> arbitrary <*> pure (UTCTime date 0)) `suchThat` inZipRange
|
|
||||||
fileContent <- oneof
|
|
||||||
[ pure Nothing
|
|
||||||
, Just <$> scale (scaleRatio $ 7 % 8) arbitrary
|
|
||||||
]
|
|
||||||
return File{..}
|
|
||||||
where
|
|
||||||
inZipRange :: UTCTime -> Bool
|
|
||||||
inZipRange time
|
|
||||||
| time > UTCTime (fromGregorian 1980 1 1) 0
|
|
||||||
, time < UTCTime (fromGregorian 2107 1 1) 0
|
|
||||||
= True
|
|
||||||
| otherwise
|
|
||||||
= False
|
|
||||||
|
|
||||||
instance Arbitrary ExamModePredicate where
|
instance Arbitrary ExamModePredicate where
|
||||||
arbitrary = elements universeF
|
arbitrary = elements universeF
|
||||||
|
|
||||||
@ -208,8 +175,6 @@ spec = do
|
|||||||
parallel $ do
|
parallel $ do
|
||||||
lawsCheckHspec (Proxy @User)
|
lawsCheckHspec (Proxy @User)
|
||||||
[ eqLaws, jsonLaws ]
|
[ eqLaws, jsonLaws ]
|
||||||
lawsCheckHspec (Proxy @PureFile)
|
|
||||||
[ eqLaws, ordLaws ]
|
|
||||||
lawsCheckHspec (Proxy @School)
|
lawsCheckHspec (Proxy @School)
|
||||||
[ eqLaws ]
|
[ eqLaws ]
|
||||||
lawsCheckHspec (Proxy @Term)
|
lawsCheckHspec (Proxy @Term)
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
module Test.QuickCheck.Classes.JSON
|
module Test.QuickCheck.Classes.JSON
|
||||||
( jsonKeyLaws
|
( jsonLaws
|
||||||
|
, jsonKeyLaws
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Test.QuickCheck
|
import Test.QuickCheck
|
||||||
import Test.QuickCheck.Property (failed)
|
import Test.QuickCheck.Property (failed, Property(..))
|
||||||
import Test.QuickCheck.Classes
|
import Test.QuickCheck.Classes hiding (jsonLaws)
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.Aeson.Encoding.Internal
|
import Data.Aeson.Encoding.Internal
|
||||||
import Data.Aeson.Types (parseMaybe)
|
import Data.Aeson.Types (parseEither)
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
import Data.Coerce
|
import Data.Coerce
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ jsonKeyLaws _ = Laws "ToJSONKey/FromJSONKey"
|
|||||||
-> let (toVal, toEnc) = case toJSONKey of
|
-> let (toVal, toEnc) = case toJSONKey of
|
||||||
ToJSONKeyText toVal' toEnc' -> (String . toVal', retagEncoding . toEnc')
|
ToJSONKeyText toVal' toEnc' -> (String . toVal', retagEncoding . toEnc')
|
||||||
ToJSONKeyValue toVal' toEnc' -> (toVal', toEnc')
|
ToJSONKeyValue toVal' toEnc' -> (toVal', toEnc')
|
||||||
in decode (encodingToLazyByteString $ toEnc a) == Just (toVal a)
|
in eitherDecode (encodingToLazyByteString $ toEnc a) == Right (toVal a)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
@ -31,8 +32,40 @@ jsonKeyLaws _ = Laws "ToJSONKey/FromJSONKey"
|
|||||||
(ToJSONKeyText toVal _, FromJSONKeyText fromVal)
|
(ToJSONKeyText toVal _, FromJSONKeyText fromVal)
|
||||||
-> property $ fromVal (toVal a) == a
|
-> property $ fromVal (toVal a) == a
|
||||||
(ToJSONKeyText toVal _, FromJSONKeyTextParser parser)
|
(ToJSONKeyText toVal _, FromJSONKeyTextParser parser)
|
||||||
-> property $ parseMaybe parser (toVal a) == Just a
|
-> property $ parseEither parser (toVal a) == Right a
|
||||||
(ToJSONKeyValue toVal _, FromJSONKeyValue parser)
|
(ToJSONKeyValue toVal _, FromJSONKeyValue parser)
|
||||||
-> property $ parseMaybe parser (toVal a) == Just a
|
-> property $ parseEither parser (toVal a) == Right a
|
||||||
(_, _)
|
(_, _)
|
||||||
-> property failed
|
-> property failed
|
||||||
|
|
||||||
|
jsonLaws :: (ToJSON a, FromJSON a, Show a, Arbitrary a, Eq a) => Proxy a -> Laws
|
||||||
|
jsonLaws p = Laws "ToJSON/FromJSON"
|
||||||
|
[ ("Partial Isomorphism", jsonEncodingPartialIsomorphism p)
|
||||||
|
, ("Encoding Equals Value", jsonEncodingEqualsValue p)
|
||||||
|
]
|
||||||
|
|
||||||
|
-- TODO: improve the quality of the error message if
|
||||||
|
-- something does not pass this test.
|
||||||
|
jsonEncodingEqualsValue :: forall a. (ToJSON a, Show a, Arbitrary a) => Proxy a -> Property
|
||||||
|
jsonEncodingEqualsValue _ = property $ \(a :: a) ->
|
||||||
|
case decode (encode a) of
|
||||||
|
Nothing -> False
|
||||||
|
Just (v :: Value) -> v == toJSON a
|
||||||
|
|
||||||
|
jsonEncodingPartialIsomorphism :: forall a. (ToJSON a, FromJSON a, Show a, Eq a, Arbitrary a) => Proxy a -> Property
|
||||||
|
jsonEncodingPartialIsomorphism _ = again $
|
||||||
|
MkProperty $
|
||||||
|
arbitrary >>= \(x :: a) ->
|
||||||
|
unProperty $
|
||||||
|
shrinking shrink x $ \x' ->
|
||||||
|
let desc1 = "Right"
|
||||||
|
desc2 = "Data.Aeson.eitherDecode . Data.Aeson.encode"
|
||||||
|
name1 = "Data.Aeson.encode a"
|
||||||
|
name2 = "Data.Aeson.eitherDecode (Data.Aeson.encode a)"
|
||||||
|
b1 = encode x'
|
||||||
|
b2 = eitherDecode b1
|
||||||
|
sb1 = show b1
|
||||||
|
sb2 = show b2
|
||||||
|
description = " Description: " ++ desc1 ++ " == " ++ desc2
|
||||||
|
err = description ++ "\n" ++ unlines (map (" " ++) (["a = " ++ show x'])) ++ " " ++ name1 ++ " = " ++ sb1 ++ "\n " ++ name2 ++ " = " ++ sb2
|
||||||
|
in counterexample err (Right x' == b2)
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import Test.QuickCheck.Gen as X
|
|||||||
import Data.Default as X
|
import Data.Default as X
|
||||||
import Test.QuickCheck.Instances as X ()
|
import Test.QuickCheck.Instances as X ()
|
||||||
import Test.QuickCheck.Arbitrary.Generic as X
|
import Test.QuickCheck.Arbitrary.Generic as X
|
||||||
import Test.QuickCheck.Classes as X
|
import Test.QuickCheck.Classes as X hiding (jsonLaws)
|
||||||
import Test.QuickCheck.Classes.PathPiece as X
|
import Test.QuickCheck.Classes.PathPiece as X
|
||||||
import Test.QuickCheck.Classes.PersistField as X
|
import Test.QuickCheck.Classes.PersistField as X
|
||||||
import Test.QuickCheck.Classes.Hashable as X
|
import Test.QuickCheck.Classes.Hashable as X
|
||||||
|
|||||||
7
test/TestInstances.hs
Normal file
7
test/TestInstances.hs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module TestInstances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Text.Blaze.TestInstances as TestInstances ()
|
||||||
|
import Database.Persist.Sql.Types.TestInstances as TestInstances ()
|
||||||
|
import Data.NonNull.TestInstances as TestInstances ()
|
||||||
13
test/Text/Blaze/TestInstances.hs
Normal file
13
test/Text/Blaze/TestInstances.hs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module Text.Blaze.TestInstances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
|
||||||
|
import Text.Blaze.Html
|
||||||
|
import Text.Blaze.Renderer.Text
|
||||||
|
|
||||||
|
|
||||||
|
instance Arbitrary Html where
|
||||||
|
arbitrary = (preEscapedToHtml :: String -> Html) . getPrintableString <$> arbitrary
|
||||||
|
shrink = map preEscapedToHtml . shrink . renderMarkup
|
||||||
18
test/Utils/I18nSpec.hs
Normal file
18
test/Utils/I18nSpec.hs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Utils.I18nSpec where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
|
||||||
|
import Utils.I18n
|
||||||
|
|
||||||
|
|
||||||
|
instance Arbitrary a => Arbitrary (I18n a) where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
parallel $ do
|
||||||
|
lawsCheckHspec (Proxy @I18nText)
|
||||||
|
[ eqLaws, ordLaws, showLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @I18n)
|
||||||
|
[ foldableLaws, functorLaws, traversableLaws ]
|
||||||
30
testdata/exam-rooms.yaml
vendored
30
testdata/exam-rooms.yaml
vendored
@ -1,30 +0,0 @@
|
|||||||
nodes:
|
|
||||||
"beantragt":
|
|
||||||
display-label: "Beantragt"
|
|
||||||
final: false
|
|
||||||
viewers:
|
|
||||||
- tag: initiator
|
|
||||||
- tag: authorized
|
|
||||||
authorized: { "dnf-terms": [[{"val": "variable", "var": "exam-office"}]] }
|
|
||||||
edges:
|
|
||||||
"beantragen":
|
|
||||||
mode: initial
|
|
||||||
display-label: "Beantragen"
|
|
||||||
actors:
|
|
||||||
- tag: authorized
|
|
||||||
authorized: { "dnf-terms": [[{"val": "variable", "var": "lecturer"}]] }
|
|
||||||
form:
|
|
||||||
"raumbeschreibung":
|
|
||||||
- tag: text
|
|
||||||
label: "Raumbeschreibung"
|
|
||||||
placeholder: null
|
|
||||||
tooltip: null
|
|
||||||
default: null
|
|
||||||
optional: false
|
|
||||||
payload-view:
|
|
||||||
"raumbeschreibung":
|
|
||||||
display-label: "Raumbeschreibung"
|
|
||||||
viewers:
|
|
||||||
- tag: initiator
|
|
||||||
- tag: authorized
|
|
||||||
authorized: { "dnf-terms": [[{"val": "variable", "var": "exam-office"}]] }
|
|
||||||
549
testdata/theses.yaml
vendored
549
testdata/theses.yaml
vendored
@ -1,112 +1,293 @@
|
|||||||
nodes:
|
nodes:
|
||||||
"antrag":
|
"antrag":
|
||||||
display-label: "Antrag angelegt"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- &pruefungsamt
|
display-label: "Antrag angelegt"
|
||||||
tag: authorized
|
viewers:
|
||||||
authorized: { "dnf-terms": [[{"val": "variable", "var": "exam-office"}]] }
|
- &pruefungsamt
|
||||||
- &hochschullehrer
|
tag: authorized
|
||||||
tag: payload-reference
|
authorized: { "dnf-terms": [[{"tag": "variable", "var": "exam-office"}]] }
|
||||||
payload-label: "hochschullehrer"
|
- &hochschullehrer
|
||||||
- &betreuer
|
tag: payload-reference
|
||||||
tag: payload-reference
|
payload-label: "hochschullehrer"
|
||||||
payload-label: "betreuer"
|
- &betreuer
|
||||||
- &student
|
tag: payload-reference
|
||||||
tag: payload-reference
|
payload-label: "betreuer"
|
||||||
payload-label: "student"
|
- &student
|
||||||
|
tag: payload-reference
|
||||||
|
payload-label: "student"
|
||||||
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag als pruefungsamt":
|
"antrag als pruefungsamt":
|
||||||
mode: initial
|
mode: initial
|
||||||
display-label: "Antrag anlegen (als Prüfungsverwaltung)"
|
display-label: "Antrag anlegen (als Prüfungsverwaltung)"
|
||||||
actors:
|
actors:
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
form:
|
view-actor:
|
||||||
|
- *pruefungsamt
|
||||||
|
form: &antrag-forms-pruefungsamt
|
||||||
"hochschullehrer": &hochschullehrer-form
|
"hochschullehrer": &hochschullehrer-form
|
||||||
- tag: multiple
|
- "1":
|
||||||
label: "Verantwortliche Hochschullehrer"
|
tag: multiple
|
||||||
tooltip: null
|
label: "Verantwortliche Hochschullehrer"
|
||||||
default: null
|
|
||||||
min: 1
|
|
||||||
range: null
|
|
||||||
sub:
|
|
||||||
tag: user
|
|
||||||
label: "Verantwortlicher Hochschullehrer"
|
|
||||||
tooltip: null
|
tooltip: null
|
||||||
default: null
|
default: null
|
||||||
optional: false
|
min: 1
|
||||||
|
range: null
|
||||||
|
sub:
|
||||||
|
tag: user
|
||||||
|
label: "Verantwortlicher Hochschullehrer"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: false
|
||||||
"betreuer": &betreuer-form
|
"betreuer": &betreuer-form
|
||||||
- tag: multiple
|
- "2":
|
||||||
label: "Betreuer"
|
tag: multiple
|
||||||
tooltip: null
|
|
||||||
default: null
|
|
||||||
min: 0
|
|
||||||
range: null
|
|
||||||
sub:
|
|
||||||
tag: user
|
|
||||||
label: "Betreuer"
|
label: "Betreuer"
|
||||||
tooltip: null
|
tooltip: null
|
||||||
default: null
|
default: null
|
||||||
optional: false
|
min: 0
|
||||||
|
range: null
|
||||||
|
sub:
|
||||||
|
tag: user
|
||||||
|
label: "Betreuer"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: false
|
||||||
"student": &student-form
|
"student": &student-form
|
||||||
- tag: user
|
- "3":
|
||||||
label: "Student"
|
tag: user
|
||||||
tooltip: null
|
label: "Student"
|
||||||
default: null
|
tooltip: null
|
||||||
optional: false
|
default: null
|
||||||
|
optional: false
|
||||||
|
"anmeldetag": &anmeldetag-form-optional
|
||||||
|
- "4": &anmeldetag-field-optional
|
||||||
|
tag: day
|
||||||
|
label: "Tag der Anmeldung"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: true
|
||||||
|
"sprache": &sprache-form-optional
|
||||||
|
- "5": &sprache-field-optional
|
||||||
|
tag: text
|
||||||
|
label: "Sprache der Arbeit"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: true
|
||||||
|
"titel": &titel-form-optional
|
||||||
|
- "6": &titel-field-optional
|
||||||
|
tag: text
|
||||||
|
label: "Titel, in Sprache der Arbeit"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: true
|
||||||
|
"titel, englisch": &entitel-form-optional
|
||||||
|
- "7": &entitel-field-optional
|
||||||
|
tag: text
|
||||||
|
label: "Titel, Englisch"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: true
|
||||||
|
"aufgabenstellung": &aufgabenstellung-form
|
||||||
|
- "8":
|
||||||
|
tag: text
|
||||||
|
large: true
|
||||||
|
label: "Aufgabenstellung"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: true
|
||||||
|
"notizen": ¬izen-form
|
||||||
|
- "9":
|
||||||
|
tag: text
|
||||||
|
large: true
|
||||||
|
label: "Notizen"
|
||||||
|
tooltip: "Einsehbar für alle Beteiligten, außer den Studenten"
|
||||||
|
default: null
|
||||||
|
optional: true
|
||||||
|
"korrektur als pruefungsamt": &korrektur-pruefungsamt
|
||||||
|
mode: manual
|
||||||
|
display-label: "Antrag anpassen"
|
||||||
|
source: "antrag"
|
||||||
|
actors:
|
||||||
|
- *pruefungsamt
|
||||||
|
view-actor:
|
||||||
|
- *pruefungsamt
|
||||||
|
form: *antrag-forms-pruefungsamt
|
||||||
|
"korrektur als pruefungsamt, hochschullehrer":
|
||||||
|
<<: *korrektur-pruefungsamt
|
||||||
|
source: "antrag, hochschullehrer"
|
||||||
|
"korrektur als pruefungsamt, student":
|
||||||
|
<<: *korrektur-pruefungsamt
|
||||||
|
source: "antrag, student"
|
||||||
|
"korrektur als pruefungsamt, student&hochschullehrer":
|
||||||
|
<<: *korrektur-pruefungsamt
|
||||||
|
source: "antrag, student&hochschullehrer"
|
||||||
|
"korrektur als pruefungsamt, student&hochschullehrer&anmeldetag":
|
||||||
|
<<: *korrektur-pruefungsamt
|
||||||
|
source: "antrag, student&hochschullehrer, anmeldetag"
|
||||||
"antrag als hochschullehrer":
|
"antrag als hochschullehrer":
|
||||||
mode: initial
|
mode: initial
|
||||||
display-label: "Antrag anlegen (als verantwortlicher Hochschullehrer)"
|
display-label: "Antrag anlegen (als verantwortlicher Hochschullehrer)"
|
||||||
actors:
|
actors:
|
||||||
- tag: authorized
|
- tag: authorized
|
||||||
authorized: { "dnf-terms": [[{"val": "variable", "var": "lecturer" }]] }
|
authorized: { "dnf-terms": [[{"tag": "variable", "var": "lecturer" }]] }
|
||||||
form:
|
view-actor: &view-actor-all
|
||||||
|
- *pruefungsamt
|
||||||
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
- *student
|
||||||
|
form: &antrag-forms-hochschullehrer
|
||||||
"hochschullehrer":
|
"hochschullehrer":
|
||||||
- tag: capture-user
|
- "1":
|
||||||
- tag: multiple
|
tag: capture-user
|
||||||
label: "Zusätzliche verantwortliche Hochschullehrer"
|
- "1.1":
|
||||||
tooltip: null
|
tag: multiple
|
||||||
default: null
|
label: "Zusätzliche verantwortliche Hochschullehrer"
|
||||||
min: 0
|
|
||||||
range: null
|
|
||||||
sub:
|
|
||||||
tag: user
|
|
||||||
label: "Verantwortlicher Hochschullehrer"
|
|
||||||
tooltip: null
|
tooltip: null
|
||||||
default: null
|
default: null
|
||||||
optional: false
|
min: 0
|
||||||
|
range: null
|
||||||
|
sub:
|
||||||
|
tag: user
|
||||||
|
label: "Verantwortlicher Hochschullehrer"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: false
|
||||||
"betreuer": *betreuer-form
|
"betreuer": *betreuer-form
|
||||||
"student": *student-form
|
"student": *student-form
|
||||||
|
"anmeldetag": *anmeldetag-form-optional
|
||||||
|
"sprache": *sprache-form-optional
|
||||||
|
"titel": *titel-form-optional
|
||||||
|
"titel, englisch": *entitel-form-optional
|
||||||
|
"aufgabenstellung": *aufgabenstellung-form
|
||||||
|
"notizen": *notizen-form
|
||||||
|
"korrektur als hochschullehrer": &korrektur-hochschullehrer
|
||||||
|
mode: manual
|
||||||
|
display-label: "Antrag anpassen"
|
||||||
|
source: "antrag"
|
||||||
|
actors:
|
||||||
|
- *hochschullehrer
|
||||||
|
view-actor: *view-actor-all
|
||||||
|
form: *antrag-forms-hochschullehrer
|
||||||
|
"korrektur als hochschullehrer, student":
|
||||||
|
<<: *korrektur-hochschullehrer
|
||||||
|
source: "antrag, student"
|
||||||
"antrag als betreuer":
|
"antrag als betreuer":
|
||||||
mode: initial
|
mode: initial
|
||||||
display-label: "Antrag anlegen (als Betreuer)"
|
display-label: "Antrag anlegen (als Betreuer)"
|
||||||
actors:
|
actors:
|
||||||
- tag: authorized
|
- tag: authorized
|
||||||
authorized: { "dnf-terms": [[{"val": "variable", "var": "lecturer" }]] }
|
authorized: { "dnf-terms": [[{"tag": "variable", "var": "lecturer" }]] }
|
||||||
form:
|
view-actor: *view-actor-all
|
||||||
|
form: &antrag-forms-betreuer
|
||||||
"betreuer":
|
"betreuer":
|
||||||
- tag: capture-user
|
- "2":
|
||||||
- tag: multiple
|
tag: capture-user
|
||||||
label: "Zusätzliche Betreuer"
|
- "2.1":
|
||||||
tooltip: null
|
tag: multiple
|
||||||
default: null
|
label: "Zusätzliche Betreuer"
|
||||||
min: 0
|
|
||||||
range: null
|
|
||||||
sub:
|
|
||||||
tag: user
|
|
||||||
label: "Betreuer"
|
|
||||||
tooltip: null
|
tooltip: null
|
||||||
default: null
|
default: null
|
||||||
optional: false
|
min: 0
|
||||||
|
range: null
|
||||||
|
sub:
|
||||||
|
tag: user
|
||||||
|
label: "Betreuer"
|
||||||
|
tooltip: null
|
||||||
|
default: null
|
||||||
|
optional: false
|
||||||
"hochschullehrer": *hochschullehrer-form
|
"hochschullehrer": *hochschullehrer-form
|
||||||
"student": *student-form
|
"student": *student-form
|
||||||
|
"anmeldetag": *anmeldetag-form-optional
|
||||||
|
"sprache": *sprache-form-optional
|
||||||
|
"titel": *titel-form-optional
|
||||||
|
"titel, englisch": *entitel-form-optional
|
||||||
|
"aufgabenstellung": *aufgabenstellung-form
|
||||||
|
"notizen": *notizen-form
|
||||||
|
"betreuer als hochschullehrer": &betreuer-hochschullehrer
|
||||||
|
mode: manual
|
||||||
|
display-label: "Eigene Rolle zu Betreuer wechseln"
|
||||||
|
source: "antrag"
|
||||||
|
actors:
|
||||||
|
- *hochschullehrer
|
||||||
|
view-actor: *view-actor-all
|
||||||
|
form: *antrag-forms-betreuer
|
||||||
|
"betreuer als hochschullehrer, student":
|
||||||
|
<<: *betreuer-hochschullehrer
|
||||||
|
source: "antrag, student"
|
||||||
|
"betreuer als hochschullehrer, hochschullehrer":
|
||||||
|
<<: *betreuer-hochschullehrer
|
||||||
|
source: "antrag, hochschullehrer"
|
||||||
|
"betreuer als hochschullehrer, student&hochschullehrer":
|
||||||
|
<<: *betreuer-hochschullehrer
|
||||||
|
source: "antrag, student&hochschullehrer"
|
||||||
|
"betreuer als hochschullehrer, student&hochschullehrer&anmeldetag":
|
||||||
|
<<: *betreuer-hochschullehrer
|
||||||
|
source: "antrag, student&hochschullehrer, anmeldetag"
|
||||||
|
"hochschullehrer als betreuer": &hochschullehrer-betreuer
|
||||||
|
mode: manual
|
||||||
|
display-label: "Eigene Rolle zu Hochschullehrer wechseln"
|
||||||
|
source: "antrag"
|
||||||
|
actors:
|
||||||
|
- *betreuer
|
||||||
|
view-actor: *view-actor-all
|
||||||
|
form: *antrag-forms-hochschullehrer
|
||||||
|
"hochschullehrer als betreuer, hochschullehrer":
|
||||||
|
<<: *hochschullehrer-betreuer
|
||||||
|
source: "antrag, hochschullehrer"
|
||||||
|
"hochschullehrer als betreuer, student":
|
||||||
|
<<: *hochschullehrer-betreuer
|
||||||
|
source: "antrag, student"
|
||||||
|
"hochschullehrer als betreuer, student&hochschullehrer":
|
||||||
|
<<: *hochschullehrer-betreuer
|
||||||
|
source: "antrag, student&hochschullehrer"
|
||||||
|
"hochschullehrer als betreuer, student&hochschullehrer&anmeldetag":
|
||||||
|
<<: *hochschullehrer-betreuer
|
||||||
|
source: "antrag, student&hochschullehrer, anmeldetag"
|
||||||
|
"korrektur als betreuer": &korrektur-betreuer
|
||||||
|
mode: manual
|
||||||
|
display-label: "Antrag anpassen"
|
||||||
|
source: "antrag"
|
||||||
|
actors:
|
||||||
|
- *betreuer
|
||||||
|
view-actor: *view-actor-all
|
||||||
|
form: *antrag-forms-betreuer
|
||||||
|
"korrektur als betreuer, student":
|
||||||
|
<<: *korrektur-betreuer
|
||||||
|
source: "antrag, student"
|
||||||
|
"korrektur als betreuer, hochschullehrer":
|
||||||
|
<<: *korrektur-betreuer
|
||||||
|
source: "antrag, hochschullehrer"
|
||||||
|
"korrektur als betreuer, student&hochschullehrer":
|
||||||
|
<<: *korrektur-betreuer
|
||||||
|
source: "antrag, student&hochschullehrer"
|
||||||
|
"korrektur als betreuer, student&hochschullehrer&anmeldetag":
|
||||||
|
<<: *korrektur-betreuer
|
||||||
|
source: "antrag, student&hochschullehrer, anmeldetag"
|
||||||
|
"korrektur als student": &korrektur-student
|
||||||
|
mode: manual
|
||||||
|
display-label: "Antrag anpassen"
|
||||||
|
source: "antrag"
|
||||||
|
actors:
|
||||||
|
- *student
|
||||||
|
view-actor: *view-actor-all
|
||||||
|
form:
|
||||||
|
"sprache": *sprache-form-optional
|
||||||
|
"titel": *titel-form-optional
|
||||||
|
"titel, englisch": *entitel-form-optional
|
||||||
|
"aufgabenstellung": *aufgabenstellung-form
|
||||||
|
"korrektur als student, hochschullehrer":
|
||||||
|
<<: *korrektur-student
|
||||||
|
source: "antrag, hochschullehrer"
|
||||||
|
|
||||||
"antrag, hochschullehrer":
|
"antrag, hochschullehrer":
|
||||||
display-label: "Antrag angelegt und vom Hochschullehrer bestätigt"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
display-label: "Antrag angelegt und vom Hochschullehrer bestätigt"
|
||||||
- *hochschullehrer
|
viewers:
|
||||||
- *betreuer
|
- *pruefungsamt
|
||||||
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag bestaetigen als hochschullehrer":
|
"antrag bestaetigen als hochschullehrer":
|
||||||
mode: manual
|
mode: manual
|
||||||
@ -115,15 +296,26 @@ nodes:
|
|||||||
actors:
|
actors:
|
||||||
- *hochschullehrer
|
- *hochschullehrer
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
|
view-actor: *view-actor-all
|
||||||
form: {}
|
form: {}
|
||||||
|
"korrektur als hochschullehrer":
|
||||||
|
<<: *korrektur-hochschullehrer
|
||||||
|
source: "antrag, hochschullehrer"
|
||||||
|
"korrektur als hochschullehrer, student":
|
||||||
|
<<: *korrektur-hochschullehrer
|
||||||
|
source: "antrag, student&hochschullehrer"
|
||||||
|
"korrektur als hochschullehrer, student&anmeldetag":
|
||||||
|
<<: *korrektur-hochschullehrer
|
||||||
|
source: "antrag, student&hochschullehrer, anmeldetag"
|
||||||
"antrag, student":
|
"antrag, student":
|
||||||
display-label: "Antrag angelegt und vom Student bestätigt"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
display-label: "Antrag angelegt und vom Student bestätigt"
|
||||||
- *student
|
viewers:
|
||||||
- *hochschullehrer
|
- *pruefungsamt
|
||||||
- *betreuer
|
- *student
|
||||||
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag bestaetigen als student":
|
"antrag bestaetigen als student":
|
||||||
mode: manual
|
mode: manual
|
||||||
@ -132,14 +324,25 @@ nodes:
|
|||||||
actors:
|
actors:
|
||||||
- *student
|
- *student
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
|
view-actor: *view-actor-all
|
||||||
form: {}
|
form: {}
|
||||||
|
"korrektur als student":
|
||||||
|
<<: *korrektur-student
|
||||||
|
source: "antrag, student"
|
||||||
|
"korrektur als student, hochschullehrer":
|
||||||
|
<<: *korrektur-student
|
||||||
|
source: "antrag, student&hochschullehrer"
|
||||||
|
"korrektur als student, hochschullehrer&anmeldetag":
|
||||||
|
<<: *korrektur-student
|
||||||
|
source: "antrag, student&hochschullehrer, anmeldetag"
|
||||||
"antrag, student&hochschullehrer":
|
"antrag, student&hochschullehrer":
|
||||||
display-label: "Antrag angelegt und von Student und Hochschullehrer bestätigt"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
display-label: "Antrag angelegt und von Student und Hochschullehrer bestätigt"
|
||||||
- *hochschullehrer
|
viewers:
|
||||||
- *betreuer
|
- *pruefungsamt
|
||||||
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag bestaetigen als student":
|
"antrag bestaetigen als student":
|
||||||
mode: manual
|
mode: manual
|
||||||
@ -148,6 +351,7 @@ nodes:
|
|||||||
actors:
|
actors:
|
||||||
- *student
|
- *student
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
|
view-actor: *view-actor-all
|
||||||
form: {}
|
form: {}
|
||||||
"antrag bestaetigen als hochschullehrer":
|
"antrag bestaetigen als hochschullehrer":
|
||||||
mode: manual
|
mode: manual
|
||||||
@ -156,86 +360,187 @@ nodes:
|
|||||||
actors:
|
actors:
|
||||||
- *hochschullehrer
|
- *hochschullehrer
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
|
view-actor: *view-actor-all
|
||||||
form: {}
|
form: {}
|
||||||
"angemeldet":
|
"antrag, student&hochschullehrer, anmeldetag":
|
||||||
display-label: "Angemeldet"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
display-label: "Antrag angelegt, von Student und Hochschullehrer bestätigt, Anmeldetag eingetragen"
|
||||||
- *hochschullehrer
|
viewers:
|
||||||
- *betreuer
|
- *pruefungsamt
|
||||||
- *student
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
final: false
|
||||||
edges:
|
edges:
|
||||||
|
"anmeldetag ist eingetragen":
|
||||||
|
mode: automatic
|
||||||
|
source: "antrag, student&hochschullehrer"
|
||||||
|
payload-restriction: { "dnf-terms": [[{"tag": "variable", "var": "anmeldetag"}]] }
|
||||||
|
"angemeldet":
|
||||||
|
viewers:
|
||||||
|
display-label: "Angemeldet"
|
||||||
|
viewers:
|
||||||
|
- *pruefungsamt
|
||||||
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
- *student
|
||||||
|
final: false
|
||||||
|
edges:
|
||||||
|
"anmelden, bestaetigt student&hochschullehrer, anmeldetag":
|
||||||
|
mode: manual
|
||||||
|
display-label: "Arbeit anmelden (bestätigt vom Student und verantwortlichem Hochschullehrer)"
|
||||||
|
source: "antrag, student&hochschullehrer, anmeldetag"
|
||||||
|
actors:
|
||||||
|
- *pruefungsamt
|
||||||
|
view-actor:
|
||||||
|
- *pruefungsamt
|
||||||
|
form: {}
|
||||||
"anmelden, bestaetigt student&hochschullehrer":
|
"anmelden, bestaetigt student&hochschullehrer":
|
||||||
mode: manual
|
mode: manual
|
||||||
display-label: "Arbeit anmelden (bestätigt vom Student und verantwortlichem Hochschullehrer)"
|
display-label: "Arbeit anmelden (bestätigt vom Student und verantwortlichem Hochschullehrer)"
|
||||||
source: "antrag, student&hochschullehrer"
|
source: "antrag, student&hochschullehrer"
|
||||||
actors:
|
actors:
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
|
view-actor:
|
||||||
|
- *pruefungsamt
|
||||||
form:
|
form:
|
||||||
"hochschullehrer": *hochschullehrer-form
|
"anmeldetag":
|
||||||
"betreuer": *betreuer-form
|
- "4":
|
||||||
"student": *student-form
|
<<: *anmeldetag-field-optional
|
||||||
|
optional: false
|
||||||
"anmelden, bestaetigt student":
|
"anmelden, bestaetigt student":
|
||||||
mode: manual
|
mode: manual
|
||||||
display-label: "Arbeit anmelden (bestätigt nur vom Student)"
|
display-label: "Arbeit anmelden (bestätigt nur vom Student)"
|
||||||
source: "antrag, student"
|
source: "antrag, student"
|
||||||
actors:
|
actors:
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
|
view-actor:
|
||||||
|
- *pruefungsamt
|
||||||
form:
|
form:
|
||||||
"hochschullehrer": *hochschullehrer-form
|
"anmeldetag":
|
||||||
"betreuer": *betreuer-form
|
- "4":
|
||||||
"student": *student-form
|
<<: *anmeldetag-field-optional
|
||||||
|
optional: false
|
||||||
"anmelden, bestaetigt hochschullehrer":
|
"anmelden, bestaetigt hochschullehrer":
|
||||||
mode: manual
|
mode: manual
|
||||||
display-label: "Arbeit anmelden (bestätigt nur vom Hochschullehrer)"
|
display-label: "Arbeit anmelden (bestätigt nur vom Hochschullehrer)"
|
||||||
source: "antrag, hochschullehrer"
|
source: "antrag, hochschullehrer"
|
||||||
actors:
|
actors:
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
|
view-actor:
|
||||||
|
- *pruefungsamt
|
||||||
form:
|
form:
|
||||||
"hochschullehrer": *hochschullehrer-form
|
"anmeldetag":
|
||||||
"betreuer": *betreuer-form
|
- "4":
|
||||||
"student": *student-form
|
<<: *anmeldetag-field-optional
|
||||||
|
optional: false
|
||||||
"datei":
|
"datei":
|
||||||
display-label: "Datei hochgeladen"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
display-label: "Datei hochgeladen"
|
||||||
- *hochschullehrer
|
viewers:
|
||||||
- *betreuer
|
- *pruefungsamt
|
||||||
- *student
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
- *student
|
||||||
|
final: false
|
||||||
edges: {}
|
edges: {}
|
||||||
"abgegeben":
|
"abgegeben":
|
||||||
display-label: "Abgabe akzeptiert"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
display-label: "Abgabe akzeptiert"
|
||||||
- *hochschullehrer
|
viewers:
|
||||||
- *betreuer
|
- *pruefungsamt
|
||||||
- *student
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
- *student
|
||||||
|
final: false
|
||||||
edges: {}
|
edges: {}
|
||||||
"benotet":
|
"benotet":
|
||||||
display-label: "Benotet"
|
|
||||||
final: false
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
display-label: "Benotet"
|
||||||
- *hochschullehrer
|
viewers:
|
||||||
- *betreuer
|
- *pruefungsamt
|
||||||
- *student
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
- *student
|
||||||
|
final: false
|
||||||
edges: {}
|
edges: {}
|
||||||
"abgebrochen":
|
"abgebrochen":
|
||||||
display-label: "Abgebrochen"
|
viewers:
|
||||||
|
display-label: "Abgebrochen"
|
||||||
|
viewers:
|
||||||
|
- *pruefungsamt
|
||||||
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
- *student
|
||||||
final: false
|
final: false
|
||||||
|
edges: {}
|
||||||
|
"fertig":
|
||||||
|
viewers:
|
||||||
|
display-label: "Fertig"
|
||||||
|
viewers:
|
||||||
|
- *pruefungsamt
|
||||||
|
final: true
|
||||||
|
edges: {}
|
||||||
|
payload-view:
|
||||||
|
"hochschullehrer":
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
- *hochschullehrer
|
- *hochschullehrer
|
||||||
- *betreuer
|
- *betreuer
|
||||||
- *student
|
- *student
|
||||||
edges: {}
|
- {"tag": "initiator"}
|
||||||
"fertig":
|
display-label: "Verantwortliche Hochschullehrer"
|
||||||
display-label: "Fertig"
|
"betreuer":
|
||||||
final: true
|
|
||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
edges: {}
|
- *hochschullehrer
|
||||||
payload-view: {}
|
- *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