Commit e4ce0543 authored by Haye Böhm's avatar Haye Böhm

Merge branch 'master' into fully-async-share-trees

parents f5674347 65877747
Pipeline #13720 failed with stage
in 1 minute and 16 seconds
......@@ -6,3 +6,5 @@
*.prp
*.exe
.sass-cache
BasicAPIExamples.icl
.ctest-results.json
test:
before_script:
- install_clean.sh bundle-complete && install_clean_nightly.sh test && apt-get update -qq && apt-get install -y -qq build-essential libsqlite3-dev libmariadbclient-dev-compat
image: "camilstaps/clean:nightly"
script:
- bash ci-tests.bash
Version: 1.0
Environments
Environment
EnvironmentName: iTasks git
EnvironmentPaths
......
Version: 1.0
Environments
Environment
EnvironmentName: iTasks Tonic
EnvironmentPaths
......
Version: 1.0
Environments
Environment
EnvironmentName: iTasks
EnvironmentPaths
......
Version: 1.0
Environments
Environment
EnvironmentName: iTasks git
EnvironmentPaths
......@@ -11,7 +12,7 @@ Version: 1.0
Path: {Application}/lib/Sapl
Path: {Application}/lib/GraphCopy
Path: {Application}/Development/iTasks-SDK/Libraries
EnvironmentCompiler: lib/exe/cocl:-dynamics -sapl
EnvironmentCompiler: lib/exe/cocl::-dynamics -sapl
EnvironmentCodeGen: lib/exe/cg
EnvironmentLinker: /usr/bin/gcc|lib/exe/sapl-collector-linker|lib/exe/itasks-web-collector
EnvironmentDynLink: /usr/bin/gcc
......
Version: 1.0
Environments
Environment
EnvironmentName: iTasks Tonic
EnvironmentPaths
......@@ -11,7 +12,7 @@ Version: 1.0
Path: {Application}/lib/Sapl
Path: {Application}/lib/GraphCopy
Path: {Application}/lib/iTasks
EnvironmentCompiler: lib/exe/cocl-tonic:-dynamics -sapl -tonic
EnvironmentCompiler: lib/exe/cocl-tonic::-dynamics -sapl -tonic
EnvironmentCodeGen: lib/exe/cg
EnvironmentLinker: /usr/bin/gcc|lib/exe/sapl-collector-linker|lib/exe/itasks-web-collector
EnvironmentDynLink: /usr/bin/gcc
......
Version: 1.0
Environments
Environment
EnvironmentName: iTasks
EnvironmentPaths
......@@ -11,7 +12,7 @@ Version: 1.0
Path: {Application}/lib/Sapl
Path: {Application}/lib/GraphCopy
Path: {Application}/lib/iTasks
EnvironmentCompiler: lib/exe/cocl:-dynamics -sapl
EnvironmentCompiler: lib/exe/cocl::-dynamics -sapl
EnvironmentCodeGen: lib/exe/cg
EnvironmentLinker: /usr/bin/gcc|lib/exe/sapl-collector-linker|lib/exe/itasks-web-collector
EnvironmentDynLink: /usr/bin/gcc
......
Version: 1.0
Environments
Environment
EnvironmentName: iTasks
EnvironmentPaths
Path: {Application}\Libraries\StdEnv
Path: {Application}\Libraries\Dynamics
Path: {Application}\Libraries\Dynamics\extension
Path: {Application}\Libraries\Dynamics\general
Path: {Application}\Libraries\Dynamics\implementation
Path: {Application}\Libraries\Dynamics\implementation\windows
Path: {Application}\Libraries\TCPIP
Path: {Application}\Libraries\Platform
Path: {Application}\Libraries\Platform\Deprecated\Generics
Path: {Application}\Libraries\Platform\Deprecated\StdLib
Path: {Application}\Libraries\Sapl
Path: {Application}\Libraries\GraphCopy
Path: {Application}\Libraries\iTasks
EnvironmentCompiler: Tools\Clean System 64\CleanCompiler64.exe : -h 64M : -sapl -dynamics
EnvironmentCodeGen: Tools\Clean System 64\CodeGenerator64.exe
EnvironmentLinker: Tools\Clean System 64\StaticLinker.exe : -h 64M | Tools\Clean System\SaplCollectorLinker.exe | Tools\Clean System\WebResourceCollector.exe
EnvironmentDynLink: Tools\Clean System 64\DynamicLinker.exe
EnvironmentVersion: 920
EnvironmentRedirect: False
EnvironmentCompileMethod: Pers
EnvironmentProcessor: I386
Environment64BitProcessor: True
......@@ -16,7 +16,7 @@ Environments
Path: {Application}\Libraries\Sapl
Path: {Application}\Libraries\GraphCopy
Path: {Application}\Libraries\iTasks
EnvironmentCompiler: Tools\Clean System\CleanCompiler.exe : -h 64M : -sapl -dynamics -generics
EnvironmentCompiler: Tools\Clean System\CleanCompiler.exe : -h 64M : -sapl -dynamics
EnvironmentCodeGen: Tools\Clean System\CodeGenerator.exe
EnvironmentLinker: Tools\Clean System\StaticLinker.exe : -h 64M | Tools\Clean System\SaplCollectorLinker.exe | Tools\Clean System\WebResourceCollector.exe
EnvironmentDynLink: Tools\Clean System\DynamicLinker.exe
......
##### Creating Custom Editors
# Creating Custom Editors #
## Introduction
## Introduction ##
In this guide we assume you are already familiar with writing programs with iTasks and have used the common task definitions for user interaction such as `updateInformation` and `viewInformation`, but find that the automagically generated GUI's are not working for your application.
In this guide we'll walk you through the process of creating an `Editor`. This is the construct that is used by tasks like `updateInformation` to render the GUI and handle user events. Editors are typed GUI building blocks that can be composed to create any GUI you like. Editors abstract away all UI concerns into a single opaque value. These building blocks define how the GUI is rendered, how events are handled and how the GUI is synchronized with the (task) value it represents.
The remainder of this document is structured as follows: First we'll look at how a custom editor can be applied in an interaction task. We'll then look at how we can configure the builtin editors of the iTask framework for basic types. We end by looking at how we can use editor combinators to build editors for more complex data structures such as records and Algebraic data types.
## Using custom editors in tasks
## Using custom editors in tasks ##
The first thing you need to know to work with custom editors is how you can use them in a task. Let's look at a very simple task we'll use as example:
......@@ -40,13 +40,13 @@ The GUI will now be rendered as a nice slider. Let's look a little closer at wha
So to use a custom task UI we need to do two things: We need to specify an editor of the right type, and then pass it in the option list of the interaction task we are using. In the next section we'll look at the builtin editors and how we can use them to replace the generic editor function.
## Using the builtin editors
## Using the builtin editors ##
The iTask framework provides a number of builtin UI components that it can render in a browser. These are the lowest level building blocks with which all iTasks GUI's are constructed.
In the example of the previous section we have seen the `slider` editor. This editor is one of the builtin componentens in the `iTasks.UI.Editor.Controls` module. All builtin editors have no arguments, but can dynamically be configured by setting attributes. For example if we wanted to set the maximum value of the slider, we would write `slider <<@ maxAttr 42`. The tuning combinators `<<@` or `@>>` are used to set attributes on editors. This pattern is used to make it easy to create editors without the need to specify all attributes in advance. In many cases, it is not necessary to deviate from the default values of the configurable attributes. Forcing a programmer to specify them all makes our GUI code too verbose. The price we pay for this convenience is that we lose some type safety. We dynamically set arbitrary attributes on editors, whether the UI rendering code uses them or not.
## Composing editors
## Composing editors ##
Creating editors for basic values is useful, but more often we want to construct editors for composite datastructures such as records. Let's expand the slider example to show how you can compose editors.
......@@ -100,7 +100,7 @@ where
This example is a little more complex, but uses only things we have already seen. By constructing editors from the basic building blocks and transforming the value domain of the editors, we can construct any kind of GUI we like.
## Creating dynamic editors
## Creating dynamic editors ##
One of the nice features of the generic editors is that they work for any type of data structure. You can easily create editors for recursive data types that have values of arbitrary size, not just static forms.
......@@ -129,7 +129,7 @@ The type of the list of possible editors is not `[Editor a]` but `[(a -> a, Edit
This example shows that with these combinators you can also make dynamic editors for recursive types. You can even plug in in the generic editors to create editors for higher order-types.
## Conclusion
## Conclusion ##
In this guide we have shown how you can fully customize the GUI of your iTask tasks by creating editors. We have not covered all builtin editors and combinators, but just enough to get you started. You can look at the documentation of `iTasks.UI.Editor.Controls`, `iTasks.UI.Editor.Containers` and `iTasks.UI.Editor.Modifiers` to find out all possibilities.
......
##### Layouting Task Compositions
# Laying out Task Compositions #
## Introduction
## Introduction ##
In this guide we introduce you to the different task layout modifiers.
We assume you are already familiar with writing programs with iTasks.
......@@ -21,7 +21,7 @@ In those cases you can use the layout directly with the `ApplyLayout` type as fo
task1 <<@ ApplyLayout somelayout
```
## Menu bars
## Menu bars ##
The function signature for the menubar decorator is as follows:
```clean
arrangeAsMenu :: [[Int]] -> Layout
......@@ -48,7 +48,7 @@ task = (programTask
) <<@ ArrangeAsMenu [[0,2]]
```
## Sidebar
## Sidebar ##
The function signature for the sidebar decorator is as follows:
```clean
arrangeWithSideBar :: !Int !UISide !Int !Bool -> Layout
......@@ -72,7 +72,7 @@ where
choices = ["string" +++ toString i\\i<-[0..20]]
```
## Panels
## Panels ##
In some cases, such as when you apply a title, the user interface is automatically lifted to a panel.
However, this can also be done manually with the option of making the panel fullscreenable.
......@@ -84,7 +84,7 @@ toPanel :: Bool -> Layout
If the boolean flag is set to true, the panel includes a small icon (![](Libraries/iTasks/UI/WebPublic/css/icons/fullscreen.png)) on the bottom right that, when clicked, makes the panel full screen.
If the small icon is clicked again, the panel shrinks back to the original size again.
## Conclusion
## Conclusion ##
In this guide we have shown how you how to decorate tasks with layout combinators so that you can change the way they appear in the client.
Not all combinators are covered, but they can be found in `iTasks.UI.Layout.Common`.
# Starting Tasks #
## Introduction ##
For many cases the "standard" way of starting tasks using `doTasks` is all you need.
For example, when you create an iTask program with a single simple task such as this:
```Clean
Start world = doTasks (viewInformation () [] "Hello, world") world
```
your task will make your task available to be started at the URL path `/` of the built-in web-server.
For each client that requests this URL, the server creates an instance of that task for that client.
While this is the common way of starting tasks, it is not the only way.
You can specify different tasks to be started through different URL paths, or you can specify that certain tasks should be started when the iTask program is (re)started.
In this guide we will show you how to do this.
## Starting different tasks in one application ##
If we want to start different tasks by accessing different URLs we need to specify which URLs correspond to which tasks. This also possible with `doTasks`.
The `doTasks` function is overloaded. It can be used with everything that is `Startable`.
There is an instance of `Startable` for `Task a` which is used in the simple case.
If we want to use `doTasks` with multiple tasks that can have different types, we have to create a value that
wraps those tasks and groups them together.
We do this as follows:
```Clean
taska = viewInformation () [] "Hello, world"
taskb = viewInformation () [] 42
Start world = doTasks
[onRequest "/ta" taska
,onRequest "/tb" taskb
] world
```
The `onRequest` function takes a task and wraps it together with the URL path as a `StartableTask` value.
If you need the information from the HTTP request to compute the task, you can use `onRequestFromRequest`, which takes a function of type `HTTPRequest -> Task a` instead.
There is an instance of `Startable` for `[StartableTask]`.
This makes it possible to specify a heterogeneous list of tasks with different types that can be started by requesting different URLs. In most cases you don't need the HTTP request, but it can be useful.
## Starting tasks when the application starts ##
Some applications contain tasks that don't need any user interaction and should be started immediately.
For those cases there is the `onStartup` function which also wraps a task as `StartableTask` value.
Tasks that are wrapped this way are created when the iTask program starts and are not directly bound to any user or client.
The following example shows a combination of an initialization task that is started when the application is started and another that can be started through the web.
```Clean
Start world = doTasks
[onStartup initDatabase
,onRequest "/" browseDatabase
] world
```
These tasks are not directly attached to a client when they are created. For a database initialization this may not be necessary, but sometimes you may want to attach them later.
In those cases you can use `onStartupWithAttributes`. This version takes another argument of type `TaskAttributes`. It lets you set the initial meta-data of the tasks, so you can identify the task instance.
This makes it possible to find and attach the task later.
## Advanced patterns ##
If you create frameworks for similar types of applications you can also create an instance of `Startable` for a type of your own. This lets you abstract from tasks that are always available in a certain type of application.
For example, the `iTasks.Extensions.Admin.WorkflowAdmin` extension defines the type `WorkflowCollection` which is `Startable`. Values of this type contain a set of tasks with names and descriptions.
This way, when you create an application using `WorkflowCollection` it initializes a database with those wrapped tasks, called workflows, on startup. It also adds a generic task that lets you login and choose which of those workflows to work on.
......@@ -33,7 +33,7 @@ import Incidone.OP.Concepts
}
:: ActionTasks
= E.c: ActionTasks
= E.c: ActionTasks
([ContactNo] [IncidentNo] -> Task (c,ActionStatus)) //Configuration task
(c (Shared ActionStatus) -> Task ()) & iTask c //An action item that needs to be configured before it can be deployed
......@@ -97,7 +97,7 @@ import Incidone.OP.Concepts
:: CommunicationMeanSuggestion
= CommunicateUsingPhone
| CommunicateUsingVHF
| CommunicateUsingP2000
| CommunicateUsingP2000
| CommunicateUsingEmail
:: UserActionListDefinition =
......
......@@ -166,7 +166,7 @@ contactOptions (Just group) = sdsFocus group contactsWithGroupShort
forIncident :: IncidentNo (ActionDefinition (ContactNo,IncidentNo)) -> ActionDefinition ContactNo
forIncident incidentNo item=:{ActionDefinition|task} = {ActionDefinition|item & task = task`}
where
task` contactNo status = task (contactNo,incidentNo) status
task` contactNo status = task (contactNo,incidentNo) status
addDefaultStatus :: (Task c) -> ([ContactNo] [IncidentNo] -> Task (c,ActionStatus)) | iTask c
addDefaultStatus task = \initContacts initIncidents -> task @ \c -> (c,{ActionStatus|defaultValue & contacts = initContacts, incidents = initIncidents})
......@@ -228,7 +228,7 @@ gEq{|ActionTasks|} x y = True
gDefault{|ActionTasks|} = ActionTasks (\_ _ -> return ((),defaultValue)) (\_ _ -> return ())
gText{|ActionTasks|} _ _ = ["Action item task definition"]
gEditor{|ActionTasks|} = emptyEditor
gEditor{|ActionTasks|} = emptyEditor
instance toString ActionProgress
where
......@@ -280,7 +280,7 @@ predefinedTodoItem :: String ItemMeta -> CatalogAction
predefinedTodoItem identity meta=:{ItemMeta|title,description}
= {CatalogAction|identity=identity,meta=meta,tasks=ActionTasks configer todoItemTask}
where
configer initContacts initIncidents
configer initContacts initIncidents
= return ((),{ActionStatus|title=title,description=description,progress=ActionActive,contacts=initContacts,incidents=initIncidents})
userTodoItem :: String ItemMeta -> CatalogAction
......@@ -293,14 +293,14 @@ alertItemTask contactNo status = communicationItemTask contactNo status
configureAlertItemTask :: [ContactNo] [IncidentNo] -> Task ((ContactNo, Maybe P2000Message),ActionStatus)
configureAlertItemTask initContacts initIncidents = configureCommunicationItemTask "Alert" initContacts initIncidents
configureUserAlertItemTask :: ItemMeta CommunicationActionDefinition [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureUserAlertItemTask :: ItemMeta CommunicationActionDefinition [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureUserAlertItemTask meta def initContacts initIncidents = configureUserCommunicationItemTask "Alert" meta def initContacts initIncidents
blankAlertItem :: CatalogAction
blankAlertItem = {CatalogAction|identity="blank-alert",meta={ItemMeta|title="Alert item",description=Nothing},tasks=ActionTasks configureAlertItemTask alertItemTask}
predefinedAlertItem :: String ItemMeta (ContactNo,Maybe P2000Message) -> CatalogAction
predefinedAlertItem identity meta=:{ItemMeta|title,description} config
predefinedAlertItem :: String ItemMeta (ContactNo,Maybe P2000Message) -> CatalogAction
predefinedAlertItem identity meta=:{ItemMeta|title,description} config
= {CatalogAction|identity=identity,meta=meta,tasks=ActionTasks configer alertItemTask}
where
configer initContacts initIncidents
......@@ -312,19 +312,19 @@ userAlertItem identity meta def
//Inform items
informItemTask :: (ContactNo,Maybe P2000Message) (Shared ActionStatus) -> Task ()
informItemTask contactNo status = communicationItemTask contactNo status
informItemTask contactNo status = communicationItemTask contactNo status
configureInformItemTask :: [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureInformItemTask initContacts initIncidents = configureCommunicationItemTask "Inform" initContacts initIncidents
configureInformItemTask initContacts initIncidents = configureCommunicationItemTask "Inform" initContacts initIncidents
configureUserInformItemTask :: ItemMeta CommunicationActionDefinition [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureUserInformItemTask :: ItemMeta CommunicationActionDefinition [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureUserInformItemTask meta def initContacts initIncidents = configureUserCommunicationItemTask "Inform" meta def initContacts initIncidents
blankInformItem :: CatalogAction
blankInformItem = {CatalogAction|identity="blank-inform",meta={ItemMeta|title="Inform item",description=Nothing}
,tasks=ActionTasks configureInformItemTask informItemTask}
predefinedInformItem :: String ItemMeta (ContactNo,Maybe P2000Message) -> CatalogAction
predefinedInformItem :: String ItemMeta (ContactNo,Maybe P2000Message) -> CatalogAction
predefinedInformItem identity meta=:{ItemMeta|title,description} config
= {CatalogAction|identity=identity,meta=meta,tasks=ActionTasks configer informItemTask}
where
......@@ -335,7 +335,7 @@ userInformItem :: String ItemMeta CommunicationActionDefinition -> CatalogAction
userInformItem identity meta def
= {CatalogAction|identity=identity,meta=meta,tasks=ActionTasks (configureUserInformItemTask meta def) informItemTask}
//List items
//List items
listItemTask :: (String,ActionPlan) (Shared ActionStatus) -> Task ()
listItemTask (title,plan) status
= upd (\s -> {ActionStatus|s & title = title}) status
......@@ -351,7 +351,7 @@ where
where
configureDelayed configer task list
= configer initContacts initIncidents
>>= \(config,status) ->
>>= \(config,status) ->
set status (selfActionStatus list)
>>| task config (selfActionStatus list)
initStatus {ItemMeta|title,description}
......@@ -361,11 +361,11 @@ where
/*
= [(Detached (initAttributes identity (initStatus [] [])) True, \list -> task (selfActionStatus list)) //TODO: Maybe inherit contact+incident from parent
\\ item=:{CatalogAction|identity,task=ConfigurableAction configer task} <- init]
*/
*/
configureListItemTask :: [ContactNo] [IncidentNo] -> Task ((String,ActionPlan),ActionStatus)
configureListItemTask initContacts initIncidents
= enterActionStatus initContacts initIncidents
= enterActionStatus initContacts initIncidents
@ \s -> ((s.ActionStatus.title, {ActionPlan|immediateActions=return [],suggestedActions=return []}),s)
blankListItem :: CatalogAction
......@@ -375,7 +375,7 @@ predefinedListItem :: String ItemMeta ActionPlan -> CatalogAction
predefinedListItem identity meta=:{ItemMeta|title,description} plan
= {CatalogAction|identity=identity,meta=meta,tasks=ActionTasks configer listItemTask}
where
configer initContacts initIncidents
configer initContacts initIncidents
= updateInitialActionStatus meta initContacts initIncidents
@ \s -> ((s.ActionStatus.title, plan),s)
......@@ -386,7 +386,7 @@ where
task c status = listItemTask (title,plan c) status
//TODO: Filter actions by their rules and watch data changes
userListItem :: String ItemMeta UserActionListDefinition -> CatalogAction
userListItem :: String ItemMeta UserActionListDefinition -> CatalogAction
userListItem identity meta=:{ItemMeta|title,description} {immediateActions,additionalActions}
= {CatalogAction|identity=identity,meta=meta,tasks=ActionTasks configer listItemTask}
where
......@@ -406,7 +406,7 @@ where
//These tasks are used both by alert and inform actions
configureCommunicationItemTask :: String [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureCommunicationItemTask type initContacts initIncidents
configureCommunicationItemTask type initContacts initIncidents
= (enterChoiceWithShared ("Select the contact to "+++ toLowerCase type) [] allContactsShort
-&&-
enterChoiceWithShared ("Select the incident to "+++toLowerCase type +++" about") [] openIncidentsShort
......@@ -416,28 +416,28 @@ configureCommunicationItemTask type initContacts initIncidents
,contacts=removeDup [contactIdentity c:initContacts]
,incidents=removeDup [incidentIdentity i:initIncidents]})
configureUserCommunicationItemTask :: String ItemMeta CommunicationActionDefinition [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureUserCommunicationItemTask type meta {CommunicationActionDefinition|contactReference=Just ref,p2000Template} initContacts initIncidents
= findReferencedContacts ref
>>- \contacts ->
configureUserCommunicationItemTask :: String ItemMeta CommunicationActionDefinition [ContactNo] [IncidentNo] -> Task ((ContactNo,Maybe P2000Message),ActionStatus)
configureUserCommunicationItemTask type meta {CommunicationActionDefinition|contactReference=Just ref,p2000Template} initContacts initIncidents
= findReferencedContacts ref
>>- \contacts ->
(enterChoiceAs ("Select the contact to " +++ toLowerCase type) [] contacts contactIdentity @ (\c -> (c,p2000Template)))
-&&-
updateInitialActionStatus meta initContacts initIncidents
configureUserCommunicationItemTask type _ _ initContacts initIncidents
configureUserCommunicationItemTask type _ _ initContacts initIncidents
= configureCommunicationItemTask type initContacts initIncidents
communicationItemTask :: (ContactNo,Maybe P2000Message) (Shared ActionStatus) -> Task ()
communicationItemTask (contactNo,mbP2000Template) status
//View action description
= viewSharedInformation () [ViewAs (\{ActionStatus|description} -> description)] status
= viewSharedInformation () [ViewAs (\{ActionStatus|description} -> description)] status
-&&- ((
//View contact communication means
(viewContactCommunicationMeans contactNo
>^* [OnAction ActionEdit (always (doOrClose (manageContactCommunicationMeans True contactNo) <<@ InWindow))]
)
//Manage list of communication attempts and initiate communications
-&&- attemptCommunication contactNo
-&&- attemptCommunication contactNo
) <<@ ArrangeWithSideBar 0 LeftSide 200 True) <<@ ArrangeWithSideBar 0 TopSide 50 True
@! ()
where
......@@ -464,7 +464,7 @@ where
addP2000Message status attempts
= createCommunication P2000Message Out (Just contactNo)
>>- \communicationNo ->