Repository: CSCfi/rems
Pull request #1417 (2019-07-18 14:14:52)

Minor changes to create form
- Move field type that sets the application title to top-most
  in the radio button list

- Add label ('Field type') to that radio-button-group

- Be more explicit in translations for the checkbox determining
  if the field is optional ('Optional' -> 'Optional field')


Patch:
diff --git a/resources/translations/en.edn b/resources/translations/en.edn
index 3a06c7009..0639f18f2 100644
--- a/resources/translations/en.edn
+++ b/resources/translations/en.edn
@@ -168,11 +168,12 @@
                 :add-option "Add option"
                 :field-n "Field %1"
                 :field-title "Field title"
+                :field-type "Field type"
                 :maxlength "Maximum length in characters (optional)"
                 :option-key "ID code"
                 :option-label "Label"
                 :option-n "Option %1"
-                :optional "Optional"
+                :optional "Optional field"
                 :placeholder "Placeholder text (optional)"
                 :title "Form name"
                 :type-attachment "Attachment"
diff --git a/resources/translations/fi.edn b/resources/translations/fi.edn
index 3e1a802a8..2523d6eac 100644
--- a/resources/translations/fi.edn
+++ b/resources/translations/fi.edn
@@ -168,11 +168,12 @@
                 :add-option "Lisää vaihtoehto"
                 :field-n "Kenttä %1"
                 :field-title "Kentän otsikko"
+                :field-type "Kentän tyyppi"
                 :maxlength "Enimmäispituus merkkeinä (ei pakollinen)"
                 :option-key "Tunniste"
                 :option-label "Otsikko"
                 :option-n "Vaihtoehto %1"
-                :optional "Ei pakollinen"
+                :optional "Vapaaehtoinen kenttä"
                 :placeholder "Esimerkkiteksti (ei pakollinen)"
                 :title "Lomakkeen nimi"
                 :type-attachment "Liitetiedosto"
diff --git a/src/clj/rems/api/catalogue.clj b/src/clj/rems/api/catalogue.clj
index e14166bfe..6238ce487 100644
--- a/src/clj/rems/api/catalogue.clj
+++ b/src/clj/rems/api/catalogue.clj
@@ -1,7 +1,9 @@
 (ns rems.api.catalogue
   (:require [compojure.api.sweet :refer :all]
             [rems.api.schema :refer :all]
+            [rems.api.util :refer [check-user]]
             [rems.db.catalogue :as catalogue]
+            [rems.db.core :as db]
             [ring.util.http-response :refer :all]
             [schema.core :as s]))
 
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index 42117c078..68e87859d 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -1,5 +1,6 @@
 (ns rems.application.model
-  (:require [clojure.test :refer [deftest is testing]]
+  (:require [clojure.set :as set]
+            [clojure.test :refer [deftest is testing]]
             [medley.core :refer [map-vals]]
             [rems.application.events :as events]
             [rems.permissions :as permissions]
diff --git a/src/clj/rems/db/applications.clj b/src/clj/rems/db/applications.clj
index 74c67770b..de90b755d 100644
--- a/src/clj/rems/db/applications.clj
+++ b/src/clj/rems/db/applications.clj
@@ -15,12 +15,15 @@
             [rems.db.attachments :as attachments]
             [rems.db.catalogue :as catalogue]
             [rems.db.core :as db]
+            [rems.db.entitlements :as entitlements]
             [rems.db.events :as events]
             [rems.db.form :as form]
             [rems.db.licenses :as licenses]
             [rems.db.users :as users]
             [rems.db.workflow :as workflow]
+            [rems.db.workflow-actors :as actors]
             [rems.form-validation :as form-validation]
+            [rems.json :as json]
             [rems.permissions :as permissions]
             [rems.scheduler :as scheduler]
             [rems.util :refer [atom? getx secure-token]])
diff --git a/src/clj/rems/db/applications/legacy.clj b/src/clj/rems/db/applications/legacy.clj
index 435196b55..0688874ad 100644
--- a/src/clj/rems/db/applications/legacy.clj
+++ b/src/clj/rems/db/applications/legacy.clj
@@ -14,7 +14,7 @@
             [rems.db.events :as events]
             [rems.db.licenses :as licenses]
             [rems.db.users :as users]
-            [rems.db.legacy-workflow-actors :as actors]
+            [rems.db.workflow-actors :as actors]
             [rems.json :as json]
             [rems.permissions :as permissions])
   (:import [rems InvalidRequestException]))
diff --git a/src/clj/rems/db/legacy_workflow_actors.clj b/src/clj/rems/db/workflow_actors.clj
similarity index 96%
rename from src/clj/rems/db/legacy_workflow_actors.clj
rename to src/clj/rems/db/workflow_actors.clj
index 65074381c..4e3573a12 100644
--- a/src/clj/rems/db/legacy_workflow_actors.clj
+++ b/src/clj/rems/db/workflow_actors.clj
@@ -1,4 +1,4 @@
-(ns rems.db.legacy-workflow-actors
+(ns rems.db.workflow-actors
   (:require [rems.db.core :as db]))
 
 (defn add-approver!
diff --git a/src/clj/rems/migrations/convert_to_dynamic_applications.clj b/src/clj/rems/migrations/convert_to_dynamic_applications.clj
new file mode 100644
index 000000000..4770cae0f
--- /dev/null
+++ b/src/clj/rems/migrations/convert_to_dynamic_applications.clj
@@ -0,0 +1,127 @@
+(ns rems.migrations.convert-to-dynamic-applications
+  (:require [clojure.java.jdbc :as jdbc]
+            [conman.core :as conman]
+            [rems.db.applications :as applications]
+            [rems.db.applications.legacy :as legacy]
+            [rems.db.core :refer [*db*] :as db]
+            [rems.db.events :as events]
+            [rems.db.workflow :as workflow]
+            [rems.db.workflow-actors :as actors]
+            [rems.poller.email :as email])
+  (:import [java.util UUID]))
+
+(defn migrate-catalogue-items! [workflow-id]
+  (jdbc/execute! *db* ["update catalogue_item set wfid = ?" workflow-id]))
+
+(defn migrate-application! [application-id workflow-id]
+  (let [read-user (or (first (actors/get-by-role application-id "approver"))
+                      ;; auto-approved workflows do not have an approver,
+                      ;; so the applicant is the only one who can see the application
+                      (:applicantuserid (legacy/get-application-state application-id)))
+        form (legacy/get-form-for read-user application-id)
+        application (:application form)
+        workflow (workflow/get-workflow workflow-id)
+        comment-requests-by-commenter (atom {})]
+    (assert (= "workflow/dynamic" (get-in workflow [:workflow :type])))
+
+    ;; use the dynamic workflow
+    (jdbc/execute! *db* ["update catalogue_item_application set wfid = ? where id = ?" (:id workflow) (:id application)])
+    ;; delete old events
+    (jdbc/execute! *db* ["delete from application_event where appid = ?" (:id application)])
+
+    (applications/add-application-created-event! {:application-id (:id application)
+                                                  :catalogue-item-ids (->> (:catalogue-items application)
+                                                                           (map :id))
+                                                  :time (:start application)
+                                                  :actor (:applicantuserid application)})
+    (events/add-event! {:event/type :application.event/draft-saved
+                        :event/time (:start application)
+                        :event/actor (:applicantuserid application)
+                        :application/id (:id application)
+                        :application/field-values (->> (:items form)
+                                                       (map (fn [item]
+                                                              [(:id item) (:value item)]))
+                                                       (into {}))})
+    (events/add-event! {:event/type :application.event/licenses-accepted
+                        :event/time (:start application)
+                        :event/actor (:applicantuserid application)
+                        :application/id (:id application)
+                        :application/accepted-licenses (->> (:licenses form)
+                                                            (filter :approved)
+                                                            (map :id)
+                                                            set)})
+    (doseq [event (:events application)]
+      (case (:event event)
+        "save" nil ; skip - the draft-saved event is produced separately and the legacy applications don't have form history
+        "apply" (events/add-event! {:event/type :application.event/submitted
+                                    :event/time (:time event)
+                                    :event/actor (:userid event)
+                                    :application/id (:id application)})
+        "reject" (events/add-event! {:event/type :application.event/rejected
+                                     :event/time (:time event)
+                                     :event/actor (:userid event)
+                                     :application/id (:id application)
+                                     :application/comment (or (:comment event) "")})
+        "approve" (events/add-event! {:event/type :application.event/approved
+                                      :event/time (:time event)
+                                      :event/actor (:userid event)
+                                      :application/id (:id application)
+                                      :application/comment (or (:comment event) "")})
+        "autoapprove" (events/add-event! {:event/type :application.event/approved
+                                          :event/time (:time event)
+                                          :event/actor (:userid event)
+                                          :application/id (:id application)
+                                          :application/comment ""})
+        "return" (events/add-event! {:event/type :application.event/returned
+                                     :event/time (:time event)
+                                     :event/actor (:userid event)
+                                     :application/id (:id application)
+                                     :application/comment (or (:comment event) "")})
+        "review" (events/add-event! {:event/type :application.event/commented
+                                     :event/time (:time event)
+                                     :event/actor (:userid event)
+                                     :application/id (:id application)
+                                     ;; TODO: request-id doesn't make much sense for these old applications - make it optional?
+                                     :application/request-id (UUID. 0 0)
+                                     :application/comment (or (:comment event) "")})
+        "review-request" (events/add-event! {:event/type :application.event/comment-requested
+                                             :event/time (:time event)
+                                             ;; TODO: the review request's actor is not known; can we guess the approver?
+                                             :event/actor (:userid event)
+                                             :application/id (:id application)
+                                             :application/request-id (let [request-id (UUID/randomUUID)]
+                                                                       (swap! comment-requests-by-commenter
+                                                                              assoc (:userid event) request-id)
+                                                                       request-id)
+                                             :application/commenters [(:userid event)]
+                                             :application/comment (or (:comment event) "")})
+        "third-party-review" (events/add-event! {:event/type :application.event/commented
+                                                 :event/time (:time event)
+                                                 :event/actor (:userid event)
+                                                 :application/id (:id application)
+                                                 :application/request-id (get @comment-requests-by-commenter (:userid event))
+                                                 :application/comment (or (:comment event) "")})
+        "withdraw" (events/add-event! {:event/type :application.event/returned
+                                       :event/time (:time event)
+                                       :event/actor (:userid event)
+                                       :application/id (:id application)
+                                       :application/comment (or (:comment event) "")})
+        "close" (events/add-event! {:event/type :application.event/closed
+                                    :event/time (:time event)
+                                    :event/actor (:userid event)
+                                    :application/id (:id application)
+                                    :application/comment (or (:comment event) "")})))))
+
+(defn migrate-all-applications! [new-workflow-id]
+  (conman/with-transaction [*db* {:isolation :serializable}]
+    (let [new-workflow (workflow/get-workflow new-workflow-id)]
+      (assert (= "workflow/dynamic" (get-in new-workflow [:workflow :type])))
+
+      (migrate-catalogue-items! (:id new-workflow))
+      (doseq [application (->> (db/get-applications {})
+                               (remove :workflow))] ;; remove dynamic applications
+        (println "Converting application" (:id application))
+        (migrate-application! (:id application) (:id new-workflow)))
+      (println "Marking all pending emails as sent")
+      (email/mark-all-emails-as-sent!)
+      (println "Done. Next go to the administration pages and archive all non-dynamic workflows."))))
diff --git a/src/clj/rems/read_gitlog.clj b/src/clj/rems/read_gitlog.clj
index 8497db3da..5d2cb2a8e 100644
--- a/src/clj/rems/read_gitlog.clj
+++ b/src/clj/rems/read_gitlog.clj
@@ -1,7 +1,7 @@
 (ns rems.read-gitlog
   (:require [clojure.java.io :as io]
-            [clojure.string :as str])
-  (:import [java.io IOException]))
+            [clojure.string :as str]
+            [clojure.java.shell :as sh]))
 
 (def ^:private version-description-file "git-describe.txt")
 (def ^:private version-revision-file "git-revision.txt")
@@ -21,4 +21,4 @@
         {:version version
          :revision revision
          :repo-url repo-url}))
-    (catch IOException _ nil)))
+    (catch java.io.IOException _ nil)))
diff --git a/src/clj/rems/standalone.clj b/src/clj/rems/standalone.clj
index 3cbd4d991..05308e1d1 100644
--- a/src/clj/rems/standalone.clj
+++ b/src/clj/rems/standalone.clj
@@ -3,6 +3,7 @@
   (:require [clojure.tools.cli :refer [parse-opts]]
             [clojure.tools.logging :as log]
             [luminus-migrations.core :as migrations]
+            [rems.migrations.convert-to-dynamic-applications :as convert-to-dynamic]
             [luminus.http-server :as http]
             [luminus.repl-server :as repl]
             [mount.core :as mount]
@@ -61,6 +62,7 @@
      \"migrate\" -- migrate database
      \"rollback\" -- roll back database migration
      \"reset\" -- empties database and runs migrations to empty db
+     \"convert-to-dynamic\" -- convert legacy applications to use dynamic workflow
      \"test-data\" -- insert test data into database
      \"demo-data\" -- insert data for demoing purposes into database
      \"validate\" -- validate data in db"
@@ -78,6 +80,14 @@
           (println "Running reset")
           (mount/start #'rems.config/env)
           (migrations/migrate args (select-keys env [:database-url])))))
+    (= "convert-to-dynamic" (first args))
+    (do
+      (when-not (= 2 (count args))
+        (println "Usage: convert-to-dynamic <new-workflow-id>")
+        (System/exit 1))
+      (let [new-workflow-id (Long/parseLong (second args))]
+        (mount/start #'rems.config/env #'rems.db.core/*db*)
+        (convert-to-dynamic/migrate-all-applications! new-workflow-id)))
     (= "test-data" (first args))
     (do
       (mount/start #'rems.config/env #'rems.db.core/*db*)
diff --git a/src/cljs/rems/administration/create_form.cljs b/src/cljs/rems/administration/create_form.cljs
index be82b200a..4add4836e 100644
--- a/src/cljs/rems/administration/create_form.cljs
+++ b/src/cljs/rems/administration/create_form.cljs
@@ -244,10 +244,11 @@
 (defn- form-field-type-radio-group [field-index]
   [radio-button-group context {:id (str "radio-group-" field-index)
                                :keys [:form/fields field-index :field/type]
+                               :label (text :t.create-form/field-type)
                                :orientation :vertical
-                               :options [{:value :text, :label (text :t.create-form/type-text)}
+                               :options [{:value :description, :label (text :t.create-form/type-description)}
+                                         {:value :text, :label (text :t.create-form/type-text)}
                                          {:value :texta, :label (text :t.create-form/type-texta)}
-                                         {:value :description, :label (text :t.create-form/type-description)}
                                          {:value :option, :label (text :t.create-form/type-option)}
                                          {:value :multiselect, :label (text :t.create-form/type-multiselect)}
                                          {:value :date, :label (text :t.create-form/type-date)}
diff --git a/src/cljs/rems/application.cljs b/src/cljs/rems/application.cljs
index 34028c401..4a3824d69 100644
--- a/src/cljs/rems/application.cljs
+++ b/src/cljs/rems/application.cljs
@@ -288,7 +288,7 @@
         form-fields-editable? (form-fields-editable? application)
         readonly? (not form-fields-editable?)]
     [collapsible/component
-     {:id "application-fields"
+     {:id "form"
       :title (text :t.form/application)
       :always
       [:div
@@ -318,7 +318,7 @@
           form-fields-editable? (form-fields-editable? application)
           readonly? (not form-fields-editable?)]
       [collapsible/component
-       {:id "application-licenses"
+       {:id "form"
         :title (text :t.form/licenses)
         :always
         [:div
@@ -607,8 +607,8 @@
      [application-state application config]
      [:div.mt-3 [applicants-info application]]
      [:div.mt-3 [applied-resources application userid]]
-     [:div.my-3 [application-licenses application edit-application userid]]
      [:div.my-3 [application-fields application edit-application]]
+     [:div.my-3 [application-licenses application edit-application userid]]
      [:div.mb-3 [actions-form application]]]))
 
 ;;;; Entrypoint
diff --git a/src/cljs/rems/status_modal.cljs b/src/cljs/rems/status_modal.cljs
index 0c5b932e0..96adfcb39 100644
--- a/src/cljs/rems/status_modal.cljs
+++ b/src/cljs/rems/status_modal.cljs
@@ -6,6 +6,7 @@
   Use the functions `set-pending!`, `set-success!` and `set-error!` to control its state.
   See `rems.status-modal/status-modal` for values to use in the calls."
   (:require [re-frame.core :as rf]
+            [reagent.core :as r]
             [rems.common-util :refer [deep-merge]]
             [rems.guide-functions]
             [rems.modal :as modal]


Test patch:
diff --git a/src/clj/rems/db/test_data.clj b/src/clj/rems/db/test_data.clj
index 721d748db..5621d5ae7 100644
--- a/src/clj/rems/db/test_data.clj
+++ b/src/clj/rems/db/test_data.clj
@@ -7,15 +7,21 @@
             [rems.db.catalogue :as catalogue]
             [rems.db.core :as db]
             [rems.db.form :as form]
-            [rems.db.legacy-workflow-actors :as actors]
             [rems.db.licenses :as licenses]
             [rems.db.resource :as resource]
             [rems.db.roles :as roles]
             [rems.db.users :as users]
             [rems.db.workflow :as workflow]
+            [rems.db.workflow-actors :as actors]
+            [rems.locales :as locales]
+            [rems.poller.email :as email]
             [rems.poller.entitlements :as entitlements-poller]
-            [ring.util.http-response :refer [bad-request!]])
-  (:import [java.util UUID]))
+            [ring.util.http-response :refer [bad-request!]]
+            [rems.poller.entitlements :as entitlements-poller])
+  (:import [java.util UUID]
+           [org.joda.time DateTimeUtils DateTime]))
+
+(def ^DateTime creation-time (time/now)) ; TODO: no more used, remove?
 
 (def +fake-users+
   {:applicant1 "alice"
@@ -742,59 +748,63 @@
            :comment "Looks fine."}))))))
 
 (defn create-test-data! []
-  (db/add-api-key! {:apikey 42 :comment "test data"})
-  (create-users-and-roles!)
-  (let [res1 (:id (db/create-resource! {:resid "urn:nbn:fi:lb-201403262" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner)}))
-        res2 (:id (db/create-resource! {:resid "Extra Data" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner)}))
-        res-with-extra-license (:id (db/create-resource! {:resid "urn:nbn:fi:lb-201403263" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner)}))
-        _ (:id (db/create-resource! {:resid "Expired Resource, should not be seen" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner) :end (time/minus (time/now) (time/years 1))}))
-        form (create-basic-form! +fake-users+)
-        _ (create-archived-form!)
-        workflows (create-workflows! +fake-users+)
-        _ (create-catalogue-item! res1 (:minimal workflows) form
-                                  {"en" "ELFA Corpus, direct approval"
-                                   "fi" "ELFA-korpus, suora hyväksyntä"})
-        simple (create-catalogue-item! res1 (:simple workflows) form
-                                       {"en" "ELFA Corpus, one approval"
-                                        "fi" "ELFA-korpus, yksi hyväksyntä"})
-        bundlable (create-catalogue-item! res2 (:simple workflows) form
-                                          {"en" "ELFA Corpus, one approval (extra data)"
-                                           "fi" "ELFA-korpus, yksi hyväksyntä (lisäpaketti)"})
-        with-review (create-catalogue-item! res1 (:with-review workflows) form
-                                            {"en" "ELFA Corpus, with review"
-                                             "fi" "ELFA-korpus, katselmoinnilla"})
-        _ (create-catalogue-item! res1 (:different workflows) form
-                                  {"en" "ELFA Corpus, two rounds of approval by different approvers"
-                                   "fi" "ELFA-korpus, kaksi hyväksyntäkierrosta eri hyväksyjillä"})
-        disabled (create-catalogue-item! res1 (:simple workflows) form
-                                         {"en" "ELFA Corpus, one approval (extra data, disabled)"
-                                          "fi" "ELFA-korpus, yksi hyväksyntä (lisäpaketti, pois käytöstä)"})]
-    (create-resource-license! res2 "Some test license" (+fake-users+ :owner))
-    (create-resource-license! res-with-extra-license "Extra license" (+fake-users+ :owner))
-    (db/set-catalogue-item-state! {:id disabled :enabled false})
-    (create-applications! simple (:simple workflows) (+fake-users+ :approver1) (+fake-users+ :approver1))
-    (create-bundled-application! simple bundlable (:simple workflows) (+fake-users+ :applicant1) (+fake-users+ :approver1))
-    (create-review-applications! with-review (:with-review workflows) +fake-users+)
-    (create-application-with-expired-resource-license! (:simple workflows) form +fake-users+)
-    (create-application-before-new-resource-license! (:simple workflows) form +fake-users+)
-    (create-expired-license!)
-    (let [dynamic (create-catalogue-item! res1 (:dynamic workflows) form
-                                          {"en" "Dynamic workflow" "fi" "Dynaaminen työvuo"})]
-      (create-dynamic-applications! dynamic (:dynamic workflows) +fake-users+))
-    (create-catalogue-item! res-with-extra-license (:dynamic workflows) form
-                            {"en" "Dynamic workflow with extra license" "fi" "Dynaaminen työvuo ylimääräisellä lisenssillä"})
-    (let [thlform (create-thl-demo-form! +fake-users+)
-          thl-catid (create-catalogue-item! res1 (:dynamic workflows) thlform {"en" "THL catalogue item" "fi" "THL katalogi-itemi"})]
-      (create-member-applications! thl-catid (:dynamic workflows) (+fake-users+ :applicant1) (+fake-users+ :approver1) [{:userid (+fake-users+ :applicant2)}]))
-    (let [dynamic-disabled (create-catalogue-item! res1 (:dynamic workflows) form
-                                                   {"en" "Dynamic workflow (disabled)"
-                                                    "fi" "Dynaaminen työvuo (pois käytöstä)"})]
-      (create-disabled-applications! dynamic-disabled (:dynamic workflows) (+fake-users+ :approver1) (+fake-users+ :approver1))
-      (db/set-catalogue-item-state! {:id dynamic-disabled :enabled false}))
-    (let [dynamic-expired (create-catalogue-item! res1 (:dynamic workflows) form
-                                                  {"en" "Dynamic workflow (expired)"
-                                                   "fi" "Dynaaminen työvuo (vanhentunut)"})]
-      (db/set-catalogue-item-state! {:id dynamic-expired :end (time/now)}))))
+  (DateTimeUtils/setCurrentMillisFixed (.getMillis creation-time))
+  (try
+    (db/add-api-key! {:apikey 42 :comment "test data"})
+    (create-users-and-roles!)
+    (let [res1 (:id (db/create-resource! {:resid "urn:nbn:fi:lb-201403262" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner)}))
+          res2 (:id (db/create-resource! {:resid "Extra Data" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner)}))
+          res-with-extra-license (:id (db/create-resource! {:resid "urn:nbn:fi:lb-201403263" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner)}))
+          _ (:id (db/create-resource! {:resid "Expired Resource, should not be seen" :organization "nbn" :owneruserid (+fake-users+ :owner) :modifieruserid (+fake-users+ :owner) :end (time/minus (time/now) (time/years 1))}))
+          form (create-basic-form! +fake-users+)
+          _ (create-archived-form!)
+          workflows (create-workflows! +fake-users+)
+          _ (create-catalogue-item! res1 (:minimal workflows) form
+                                    {"en" "ELFA Corpus, direct approval"
+                                     "fi" "ELFA-korpus, suora hyväksyntä"})
+          simple (create-catalogue-item! res1 (:simple workflows) form
+                                         {"en" "ELFA Corpus, one approval"
+                                          "fi" "ELFA-korpus, yksi hyväksyntä"})
+          bundlable (create-catalogue-item! res2 (:simple workflows) form
+                                            {"en" "ELFA Corpus, one approval (extra data)"
+                                             "fi" "ELFA-korpus, yksi hyväksyntä (lisäpaketti)"})
+          with-review (create-catalogue-item! res1 (:with-review workflows) form
+                                              {"en" "ELFA Corpus, with review"
+                                               "fi" "ELFA-korpus, katselmoinnilla"})
+          _ (create-catalogue-item! res1 (:different workflows) form
+                                    {"en" "ELFA Corpus, two rounds of approval by different approvers"
+                                     "fi" "ELFA-korpus, kaksi hyväksyntäkierrosta eri hyväksyjillä"})
+          disabled (create-catalogue-item! res1 (:simple workflows) form
+                                           {"en" "ELFA Corpus, one approval (extra data, disabled)"
+                                            "fi" "ELFA-korpus, yksi hyväksyntä (lisäpaketti, pois käytöstä)"})]
+      (create-resource-license! res2 "Some test license" (+fake-users+ :owner))
+      (create-resource-license! res-with-extra-license "Extra license" (+fake-users+ :owner))
+      (db/set-catalogue-item-state! {:id disabled :enabled false})
+      (create-applications! simple (:simple workflows) (+fake-users+ :approver1) (+fake-users+ :approver1))
+      (create-bundled-application! simple bundlable (:simple workflows) (+fake-users+ :applicant1) (+fake-users+ :approver1))
+      (create-review-applications! with-review (:with-review workflows) +fake-users+)
+      (create-application-with-expired-resource-license! (:simple workflows) form +fake-users+)
+      (create-application-before-new-resource-license! (:simple workflows) form +fake-users+)
+      (create-expired-license!)
+      (let [dynamic (create-catalogue-item! res1 (:dynamic workflows) form
+                                            {"en" "Dynamic workflow" "fi" "Dynaaminen työvuo"})]
+        (create-dynamic-applications! dynamic (:dynamic workflows) +fake-users+))
+      (create-catalogue-item! res-with-extra-license (:dynamic workflows) form
+                              {"en" "Dynamic workflow with extra license" "fi" "Dynaaminen työvuo ylimääräisellä lisenssillä"})
+      (let [thlform (create-thl-demo-form! +fake-users+)
+            thl-catid (create-catalogue-item! res1 (:dynamic workflows) thlform {"en" "THL catalogue item" "fi" "THL katalogi-itemi"})]
+        (create-member-applications! thl-catid (:dynamic workflows) (+fake-users+ :applicant1) (+fake-users+ :approver1) [{:userid (+fake-users+ :applicant2)}]))
+      (let [dynamic-disabled (create-catalogue-item! res1 (:dynamic workflows) form
+                                                     {"en" "Dynamic workflow (disabled)"
+                                                      "fi" "Dynaaminen työvuo (pois käytöstä)"})]
+        (create-disabled-applications! dynamic-disabled (:dynamic workflows) (+fake-users+ :approver1) (+fake-users+ :approver1))
+        (db/set-catalogue-item-state! {:id dynamic-disabled :enabled false}))
+      (let [dynamic-expired (create-catalogue-item! res1 (:dynamic workflows) form
+                                                    {"en" "Dynamic workflow (expired)"
+                                                     "fi" "Dynaaminen työvuo (vanhentunut)"})]
+        (db/set-catalogue-item-state! {:id dynamic-expired :end (time/now)})))
+    (finally
+      (DateTimeUtils/setCurrentMillisSystem))))
 
 (defn create-demo-data! []
   (db/add-api-key! {:apikey 55 :comment "Finna"})
diff --git a/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj b/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
new file mode 100644
index 000000000..231cd6961
--- /dev/null
+++ b/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
@@ -0,0 +1,765 @@
+(ns ^:integration rems.migrations.test-convert-to-dynamic-applications
+  (:require [clojure.java.jdbc :as jdbc]
+            [clojure.pprint :refer [pprint]]
+            [clojure.test :refer :all]
+            [conman.core :as conman]
+            [rems.api.testing :refer [api-fixture]]
+            [rems.db.applications.legacy :as legacy]
+            [rems.db.core :refer [*db*]]
+            [rems.db.test-data :as test-data]
+            [rems.db.workflow :as workflow]
+            [rems.migrations.convert-to-dynamic-applications :refer :all]
+            [rems.validate :as validate])
+  (:import [java.util UUID]))
+
+(use-fixtures
+  :once
+  api-fixture)
+
+;; dates in the past to avoid external-id conflicts with test-data
+(defn add-more-test-data! []
+  (jdbc/execute! *db* ["
+INSERT INTO catalogue_item_application (id, applicantuserid, start, endt, modifieruserid, wfid, description) VALUES (21, 'alice', '2019-03-05 11:56:19.353103', null, null, 1, 'direct approval');
+INSERT INTO catalogue_item_application (id, applicantuserid, start, endt, modifieruserid, wfid, description) VALUES (22, 'alice', '2019-03-05 11:56:59.596975', null, null, 2, 'third party review');
+INSERT INTO catalogue_item_application (id, applicantuserid, start, endt, modifieruserid, wfid, description) VALUES (23, 'alice', '2019-03-05 11:58:25.793817', null, null, 2, 'withdraw');
+INSERT INTO catalogue_item_application (id, applicantuserid, start, endt, modifieruserid, wfid, description) VALUES (24, 'alice', '2019-03-05 11:59:08.412314', null, null, 2, 'close');
+INSERT INTO catalogue_item_application_items (catappid, catitemid) VALUES (21, 1);
+INSERT INTO catalogue_item_application_items (catappid, catitemid) VALUES (22, 2);
+INSERT INTO catalogue_item_application_items (catappid, catitemid) VALUES (23, 2);
+INSERT INTO catalogue_item_application_items (catappid, catitemid) VALUES (24, 2);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (21, 'alice', 0, 'save', null, '2019-03-05 11:56:19.353103', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (21, 'alice', 0, 'save', null, '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (21, 'alice', 0, 'apply', null, '2019-03-05 11:56:32.397157', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (21, 'alice', 0, 'autoapprove', null, '2019-03-05 11:56:32.397157', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (22, 'alice', 0, 'save', null, '2019-03-05 11:56:59.596975', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (22, 'alice', 0, 'save', null, '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (22, 'alice', 0, 'apply', null, '2019-03-05 11:57:15.396081', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (22, 'carl', 0, 'review-request', 'plz comment', '2019-03-05 11:57:45.355694', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (22, 'carl', 0, 'third-party-review', 'lgtm', '2019-03-05 11:58:03.891460', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (23, 'alice', 0, 'save', null, '2019-03-05 11:58:25.793817', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (23, 'alice', 0, 'save', null, '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (23, 'alice', 0, 'apply', null, '2019-03-05 11:58:38.882900', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (23, 'alice', 0, 'withdraw', 'nope nope nope', '2019-03-05 11:58:47.882659', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (24, 'alice', 0, 'save', null, '2019-03-05 11:59:08.412314', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (24, 'alice', 0, 'save', null, '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (24, 'alice', 0, 'apply', null, '2019-03-05 11:59:17.559004', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (24, 'developer', 0, 'approve', '', '2019-03-05 11:59:42.328008', null);
+INSERT INTO application_event (appid, userid, round, event, comment, time, eventdata) VALUES (24, 'developer', 0, 'close', 'no more', '2019-03-05 11:59:54.974911', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (21, null, 1, 'alice', 'approved', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (21, null, 2, 'alice', 'approved', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (22, null, 1, 'alice', 'approved', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (22, null, 2, 'alice', 'approved', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (23, null, 1, 'alice', 'approved', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (23, null, 2, 'alice', 'approved', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (24, null, 1, 'alice', 'approved', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_license_approval_values (catappid, formmapid, licid, modifieruserid, state, start, endt) VALUES (24, null, 2, 'alice', 'approved', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 1, 'alice', 'direct approval', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 2, 'alice', 'direct approval', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 3, 'alice', '', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 4, 'alice', '', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 5, 'alice', '', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 6, 'alice', '', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 7, 'alice', '', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (21, 8, 'alice', '', '2019-03-05 11:56:32.083122', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 1, 'alice', 'third party review', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 2, 'alice', 'third party review', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 3, 'alice', '', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 4, 'alice', '', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 5, 'alice', '', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 6, 'alice', '', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 7, 'alice', '', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (22, 8, 'alice', '', '2019-03-05 11:57:15.083212', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 1, 'alice', 'withdraw', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 2, 'alice', 'withdraw', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 3, 'alice', '', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 4, 'alice', '', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 5, 'alice', '', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 6, 'alice', '', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 7, 'alice', '', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (23, 8, 'alice', '', '2019-03-05 11:58:38.616129', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 1, 'alice', 'close', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 2, 'alice', 'close', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 3, 'alice', '', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 4, 'alice', '', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 5, 'alice', '', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 6, 'alice', '', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 7, 'alice', '', '2019-03-05 11:59:17.267323', null);
+INSERT INTO application_text_values (catappid, formmapid, modifieruserid, value, start, endt) VALUES (24, 8, 'alice', '', '2019-03-05 11:59:17.267323', null);
+INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 21, 'alice', '2019-03-05 11:56:32.397157', null);
+INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 24, 'alice', '2019-03-05 11:59:42.328008', '2019-03-05 11:59:54.974911');
+"]))
+
+(deftest test-migration
+  (add-more-test-data!)
+  (let [event-id-seq (atom (:currval (first (jdbc/query *db* ["select currval('application_event_id_seq')"]))))
+        next-event-id #(swap! event-id-seq inc)]
+
+    (let [dynamic-workflows (->> (workflow/get-workflows {})
+                                 (filter #(= "workflow/dynamic" (get-in % [:workflow :type]))))
+          new-workflow (first dynamic-workflows)]
+      (assert (= 1 (count dynamic-workflows)))
+      (assert new-workflow)
+      (conman/with-transaction [*db* {:isolation :serializable}]
+        (migrate-catalogue-items! (:id new-workflow))
+        (migrate-application! 1 (:id new-workflow))
+        (migrate-application! 2 (:id new-workflow))
+        (migrate-application! 3 (:id new-workflow))
+        (migrate-application! 4 (:id new-workflow))
+        (migrate-application! 5 (:id new-workflow))
+        (migrate-application! 6 (:id new-workflow))
+        (migrate-application! 7 (:id new-workflow))
+        (migrate-application! 8 (:id new-workflow))
+        (migrate-application! 21 (:id new-workflow))
+        (migrate-application! 22 (:id new-workflow))
+        (migrate-application! 23 (:id new-workflow))
+        (migrate-application! 24 (:id new-workflow)))
+      ;; validation already happens when the events are written, but just in case...
+      (is (nil? (validate/validate))))
+
+    (let [app-id 1
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "draft application"
+              :applicantuserid "developer"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/11"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "draft application"
+                                                           2 "draft application"
+                                                           3 "draft application"
+                                                           4 "draft application"
+                                                           5 ""
+                                                           6 "draft application"
+                                                           7 "draft application"
+                                                           8 "draft appl"
+                                                           9 "draft application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id 1
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 2
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "applied application"
+              :applicantuserid "developer"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/12"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "applied application"
+                                                           2 "applied application"
+                                                           3 "applied application"
+                                                           4 "applied application"
+                                                           5 ""
+                                                           6 "applied application"
+                                                           7 "applied application"
+                                                           8 "applied ap"
+                                                           9 "applied application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 3
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "rejected application"
+              :applicantuserid "developer"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/13"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "rejected application"
+                                                           2 "rejected application"
+                                                           3 "rejected application"
+                                                           4 "rejected application"
+                                                           5 ""
+                                                           6 "rejected application"
+                                                           7 "rejected application"
+                                                           8 "rejected a"
+                                                           9 "rejected application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/rejected
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment "comment for rejection"}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 4
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "accepted application"
+              :applicantuserid "developer"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/14"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "accepted application"
+                                                           2 "accepted application"
+                                                           3 "accepted application"
+                                                           4 "accepted application"
+                                                           5 ""
+                                                           6 "accepted application"
+                                                           7 "accepted application"
+                                                           8 "accepted a"
+                                                           9 "accepted application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id 4
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/approved
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment "comment for approval"}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 5
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "returned application"
+              :applicantuserid "developer"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/15"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "developer"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "returned application"
+                                                           2 "returned application"
+                                                           3 "returned application"
+                                                           4 "returned application"
+                                                           5 ""
+                                                           6 "returned application"
+                                                           7 "returned application"
+                                                           8 "returned a"
+                                                           9 "returned application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/returned
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment "comment for return"}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 6
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "bundled application"
+              :applicantuserid "alice"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "alice"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/16"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}
+                                                        {:catalogue-item/id 3
+                                                         :resource/ext-id "Extra Data"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}
+                                                       {:license/id 3}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "alice"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "bundled application"
+                                                           2 "bundled application"
+                                                           3 "bundled application"
+                                                           4 "bundled application"
+                                                           5 ""
+                                                           6 "bundled application"
+                                                           7 "bundled application"
+                                                           8 "bundled ap"
+                                                           9 "bundled application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2 3}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/returned
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment "comment for return"}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 7
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "application with review"
+              :applicantuserid "alice"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "alice"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/17"
+                                :application/resources [{:catalogue-item/id 4
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "alice"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "application with review"
+                                                           2 "application with review"
+                                                           3 "application with review"
+                                                           4 "application with review"
+                                                           5 ""
+                                                           6 "application with review"
+                                                           7 "application with review"
+                                                           8 "applicatio"
+                                                           9 "application with review"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/commented
+                                :event/actor "carl"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/request-id (UUID/fromString "00000000-0000-0000-0000-000000000000")
+                                :application/comment "comment for review"}
+                               {:event/type :application.event/approved
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment "comment for approval"}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 8
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "application in review"
+              :applicantuserid "alice"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "alice"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/18"
+                                :application/resources [{:catalogue-item/id 4
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "alice"
+                                :event/time test-data/creation-time
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "application in review"
+                                                           2 "application in review"
+                                                           3 "application in review"
+                                                           4 "application in review"
+                                                           5 ""
+                                                           6 "application in review"
+                                                           7 "application in review"
+                                                           8 "applicatio"
+                                                           9 "application in review"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 21
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "direct approval"
+              :applicantuserid "alice"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 0) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/19"
+                                :application/resources [{:catalogue-item/id 1
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 1) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "direct approval"
+                                                           2 "direct approval"
+                                                           3 ""
+                                                           4 ""
+                                                           5 ""
+                                                           6 ""
+                                                           7 ""
+                                                           8 ""
+                                                           9 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/approved
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment ""}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 22
+          application (legacy/get-application-state app-id)
+          request-id (-> application :dynamic-events (nth 4) :application/request-id)]
+      (is request-id)
+      (is (= {:id app-id
+              :description "third party review"
+              :applicantuserid "alice"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 0) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/20"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 1) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "third party review"
+                                                           2 "third party review"
+                                                           3 ""
+                                                           4 ""
+                                                           5 ""
+                                                           6 ""
+                                                           7 ""
+                                                           8 ""
+                                                           9 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/comment-requested
+                                :event/actor "carl" ;; TODO: should really be "developer" but the old event doesn't tell it
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/request-id request-id
+                                :application/commenters ["carl"]
+                                :application/comment "plz comment"}
+                               {:event/type :application.event/commented
+                                :event/actor "carl"
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/request-id request-id
+                                :application/comment "lgtm"}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 23
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "withdraw"
+              :applicantuserid "alice"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 1) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/21"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 1) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "withdraw"
+                                                           2 "withdraw"
+                                                           3 ""
+                                                           4 ""
+                                                           5 ""
+                                                           6 ""
+                                                           7 ""
+                                                           8 ""
+                                                           9 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/returned
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment "nope nope nope"}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
+
+    (let [app-id 24
+          application (legacy/get-application-state app-id)]
+      (is (= {:id app-id
+              :description "close"
+              :applicantuserid "alice"
+              :dynamic-events [{:event/type :application.event/created
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 1) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/external-id "2019/22"
+                                :application/resources [{:catalogue-item/id 2
+                                                         :resource/ext-id "urn:nbn:fi:lb-201403262"}]
+                                :application/licenses [{:license/id 1}
+                                                       {:license/id 2}]
+                                :form/id 1
+                                :workflow/id 7
+                                :workflow/type :workflow/dynamic}
+                               {:event/type :application.event/draft-saved
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 1) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/field-values {1 "close"
+                                                           2 "close"
+                                                           3 ""
+                                                           4 ""
+                                                           5 ""
+                                                           6 ""
+                                                           7 ""
+                                                           8 ""
+                                                           9 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
+                               {:event/type :application.event/submitted
+                                :event/actor "alice"
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id}
+                               {:event/type :application.event/approved
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment ""}
+                               {:event/type :application.event/closed
+                                :event/actor "developer"
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
+                                :event/id (next-event-id)
+                                :application/id app-id
+                                :application/comment "no more"}]
+              :workflow {:type :workflow/dynamic
+                         :handlers ["developer"]}}
+             (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))))
+
+(comment
+  (user/run-tests 'rems.migrations.test-convert-to-dynamic-applications))
diff --git a/test/clj/rems/test_browser.clj b/test/clj/rems/test_browser.clj
index c8df288d7..a00aaa870 100644
--- a/test/clj/rems/test_browser.clj
+++ b/test/clj/rems/test_browser.clj
@@ -119,14 +119,14 @@
 ;;; application page
 
 (defn fill-form-field [label text]
-  (let [id (get-element-attr *driver* [:application-fields
+  (let [id (get-element-attr *driver* [:form
                                        {:tag :label, :fn/text label}]
                              :for)]
     ;; XXX: need to use `fill-human`, because `fill` is so quick that the form drops characters here and there
     (fill-human *driver* {:id id} text)))
 
 (defn set-date [label date]
-  (let [id (get-element-attr *driver* [:application-fields
+  (let [id (get-element-attr *driver* [:form
                                        {:tag :label, :fn/text label}]
                              :for)]
     ;; XXX: The date format depends on operating system settings and is unaffected by browser locale,
@@ -155,7 +155,7 @@
                 id date)))
 
 (defn select-option [label option]
-  (let [id (get-element-attr *driver* [:application-fields
+  (let [id (get-element-attr *driver* [:form
                                        {:tag :label, :fn/text label}]
                              :for)]
     (fill *driver* {:id id} option)))
diff --git a/test/clj/rems/test_db.clj b/test/clj/rems/test_db.clj
index 904100d64..1a775418e 100644
--- a/test/clj/rems/test_db.clj
+++ b/test/clj/rems/test_db.clj
@@ -16,7 +16,7 @@
             [rems.db.test-data :as test-data]
             [rems.db.testing :refer [test-db-fixture rollback-db-fixture]]
             [rems.db.users :as users]
-            [rems.db.legacy-workflow-actors :as actors]
+            [rems.db.workflow-actors :as actors]
             [rems.poller.entitlements :as entitlements-poller]
             [rems.testing-tempura :refer [fake-tempura-fixture]]
             [rems.util :refer [get-user-id]])