Repository: CSCfi/rems
Pull request #2937 (2022-05-18 15:09:25)

DUO in application pdf render
relates #2857 

Data Use Ontology codes are now included in rendered pdf, both under resources that declare DUOs as well as applicant DUOs. Rendered pdf includes some refactoring and UI tuning so that fields look more consistent.

# Checklist for author

Remove items that aren't applicable, check items that are done.

## Reviewability
- [x] Link to issue
- [x] Consider adding screenshots for ease of review

## Backwards compatibility
- [x] Feature appears correctly in PDF print

## Documentation
- [x] Update changelog if necessary

## Testing
- [x] Valuable features are integration / browser / acceptance tested automatically

## Follow-up
- [ ] New tasks are created for pending or remaining tasks

---
## Screenshots

### Before 
<img src="https://user-images.githubusercontent.com/794087/190620886-7cd38a0c-99b5-476d-a8e9-ac85192c246f.png" width="400px"><img src="https://user-images.githubusercontent.com/794087/190620882-277ef9a5-b569-4915-95e0-ce19c7935e25.png" width="400px">

### After
<img src="https://user-images.githubusercontent.com/794087/192512780-068d48f0-c730-421f-b550-707030bd3b6d.png" width="400px"><img src="https://user-images.githubusercontent.com/794087/190620239-9ddadb83-435f-46f9-b39d-3dc2c76528d2.png" width="400px">


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4e03bfdd..f06e253da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@ Changes since v2.29
   - In the future, autosave may become the default mode. (#2767)
   - Applications don't yet automatically get reloaded, should another person be viewing the same application.
     This is a potential future feature. (see #2622, #2244)
+- Data Use Ontology (DUO) codes are now rendered in application pdf. Rendered pdf styles have also been tuned for more consistent look. (#2857)
 
 ### Fixes
 - License, create/edit license and create/edit catalogue item administrator views have been updated to display localized fields the same way other administrator views do. (#1334)
diff --git a/resources/translations/en.edn b/resources/translations/en.edn
index 43b983494..565526abf 100644
--- a/resources/translations/en.edn
+++ b/resources/translations/en.edn
@@ -509,7 +509,8 @@
                :remove "Remove"}
   :label {:default "%1: %2"
           :dash "%1 – %2"
-          :long "%1: %2 – %3"}
+          :long "%1: %2 – %3"
+          :parens "%1 (%2)"}
   :link {:download-file "Download file"
          :opens-in-new-window "Opens in a new window"}
   :login {:alternative "Other login options"
diff --git a/resources/translations/fi.edn b/resources/translations/fi.edn
index 66692f0cc..d22e5098c 100644
--- a/resources/translations/fi.edn
+++ b/resources/translations/fi.edn
@@ -466,7 +466,8 @@
                :remove "Poista"}
   :label {:default "%1: %2"
           :dash "%1 – %2"
-          :long "%1: %2 – %3"}
+          :long "%1: %2 – %3"
+          :parens "%1 (%2)"}
   :link {:download-file "Lataa tiedosto"
          :opens-in-new-window "Aukeaa uuteen ikkunaan"}
   :login {:alternative "Muut kirjautumisvaihtoehdot"
diff --git a/resources/translations/sv.edn b/resources/translations/sv.edn
index fe177f1a3..fb013ce10 100644
--- a/resources/translations/sv.edn
+++ b/resources/translations/sv.edn
@@ -466,7 +466,8 @@
                :remove "Ta bort"}
   :label {:default "%1: %2"
           :dash "%1 – %2"
-          :long "%1: %2 – %3"}
+          :long "%1: %2 – %3"
+          :parens "%1 (%2)"}
   :link {:download-file "Ladda ner fil"
          :opens-in-new-window "Öppnas i nytt fönster"}
   :login {:alternative "Andra alternativ för inloggning"
diff --git a/src/clj/rems/pdf.clj b/src/clj/rems/pdf.clj
index b353a6a7e..e524e77da 100644
--- a/src/clj/rems/pdf.clj
+++ b/src/clj/rems/pdf.clj
@@ -7,27 +7,16 @@
             [rems.common.form :as form]
             [rems.common.util :refer [build-index]]
             [rems.context :as context]
-            [rems.text :refer [localized localize-decision localize-event localize-state localize-time text with-language]]
+            [rems.text :refer [localized localize-decision localize-event localize-state localize-time text text-format with-language]]
             [rems.util :refer [getx]])
   (:import [java.io ByteArrayOutputStream]))
 
-(defn- render-user [application user]
-  (str (or (:name user)
-           (:userid user))
-       " (" (:userid user) ")"
-       " <" (:email user) ">. "
-       (when-let [userid (:userid user)]
-         (str (text :t.form/accepted-licenses)
-              ": "
-              (if (application-util/accepted-licenses? application userid)
-                (text :t.form/checkbox-checked)
-                (text :t.form/checkbox-unchecked))))))
-
 (def heading-style {:spacing-before 20})
+(def field-heading-style {:spacing-before 8 :style :bold})
+(def field-style {:spacing-before 8})
 
 (defn- render-header [application]
-  (let [state (getx application :application/state)
-        resources (getx application :application/resources)]
+  (let [state (getx application :application/state)]
     (list
      [:heading heading-style
       (str (text :t.applications/application)
@@ -36,63 +25,114 @@
                 (getx application :application/id))
            (when-let [description (get application :application/description)]
              (str ": " description)))]
-     [:paragraph
+     [:paragraph field-style
       (text :t.pdf/generated)
       " "
       (localize-time (time/now))]
      [:paragraph
       (text :t.applications/state)
-      (when state [:phrase ": " (localize-state state)])]
-     [:heading heading-style (text :t.applicant-info/applicants)]
-     [:paragraph (text :t.applicant-info/applicant) ": " (render-user application (getx application :application/applicant))]
-     (doall
-      (for [member (getx application :application/members)]
-        [:paragraph (text :t.applicant-info/member) ": " (render-user application member)]))
-     [:heading heading-style (text :t.form/resources)]
+      (when state [:phrase ": " (localize-state state)])])))
+
+(defn- render-user [application user label]
+  (let [userid (:userid user)
+        email (:email user)]
+    (list
+     [:paragraph field-style
+      [:phrase {:style :bold} label] ": " (if-let [name (:name user)]
+                                            (str name " (" userid ") <" email ">")
+                                            (str userid " <" email ">"))]
+     (when (some? userid)
+       [:paragraph
+        (text-format :t.label/default
+                     (text :t.form/accepted-licenses)
+                     (if (application-util/accepted-licenses? application userid)
+                       (text :t.form/checkbox-checked)
+                       (text :t.form/checkbox-unchecked)))]))))
+
+(defn- render-applicants [application]
+  (list
+   [:heading heading-style (text :t.applicant-info/applicants)]
+   (render-user application
+                (getx application :application/applicant)
+                (text :t.applicant-info/applicant))
+   (doall
+    (for [member (getx application :application/members)]
+      (render-user application member (text :t.applicant-info/member))))))
+
+(defn- render-duo [duo & [opts]]
+  (let [label (text-format :t.label/dash
+                           (:shorthand duo)
+                           (str/capitalize (localized (:label duo))))]
+    [:paragraph (:field-style opts)
+     [:paragraph (:label-style opts) label]
      [:list
       (doall
-       (for [resource resources]
-         [:phrase
-          (localized (:catalogue-item/title resource))
-          " (" (:resource/ext-id resource) ")"]))])))
+       (for [restriction (:restrictions duo)
+             value (:values restriction)]
+         [:phrase (case (:type restriction)
+                    :mondo (text-format :t.label/dash
+                                        (:id value)
+                                        (str/capitalize (:label value)))
+                    (:value value))]))]]))
+
+(defn- render-resources [application]
+  (let [resources (getx application :application/resources)]
+    (list
+     [:heading heading-style (text :t.form/resources)]
+     (doall
+      (for [resource resources
+            :let [title (localized (:catalogue-item/title resource))
+                  ext-id (:resource/ext-id resource)
+                  duos (get-in resource [:resource/duo :duo/codes])]]
+        (list
+         [:paragraph field-heading-style
+          (text-format :t.label/parens title ext-id)]
+         (when (seq duos)
+           (list
+            [:paragraph field-style (text :t.duo/title)]
+            (doall
+             (for [duo duos]
+               (render-duo duo {:field-style {:spacing-before 8}})))))))))))
+
+(defn- render-duos [application]
+  (when-some [duos (seq (get-in application [:application/duo :duo/codes]))]
+    (concat
+     (list [:heading heading-style (text :t.duo/title)]
+           [:paragraph field-style] ; for margin
+           (render-duo (first duos) {:label-style {:style :bold}}))
+     (doall
+      (for [duo (rest duos)]
+        (render-duo duo {:field-style {:spacing-before 8}
+                         :label-style {:style :bold}}))))))
 
-(defn- attachment-filenames [application]
-  (build-index {:keys [:attachment/id] :value-fn :attachment/filename} (:application/attachments application)))
+(defn- render-license [license]
+  (list [:paragraph field-heading-style (localized (:license/title license))]
+        (case (:license/type license)
+          :text
+          [:paragraph (localized (:license/text license))]
+          :link
+          [:paragraph (localized (:license/link license))]
+          :attachment
+          [:paragraph (localized (:license/attachment-filename license))])))
 
-(defn- render-events [application]
-  (let [filenames (attachment-filenames application)
-        events (getx application :application/events)]
-    (list
-     [:heading heading-style (text :t.form/events)]
-     (if (empty? events)
-       [:paragraph "–"]
-       [:list
+(defn- render-licenses [application]
+  (list [:heading heading-style (text :t.form/licenses)]
         (doall
-         (for [event events
-               :when (not (#{:application.event/draft-saved} (:event/type event)))]
-           [:phrase
-            (localize-time (:event/time event))
-            " "
-            (localize-event event)
-            (when-let [decision (localize-decision event)]
-              (str "\n" decision))
-            (let [comment (get event :application/comment)]
-              (when-not (empty? comment)
-                (str "\n"
-                     (text :t.form/comment)
-                     ": "
-                     comment)))
-            (when-let [attachments (seq (get event :event/attachments))]
-              (str "\n"
-                   (text :t.form/attachments)
-                   ": "
-                   (str/join ", " (map (comp filenames :attachment/id) attachments))))]))]))))
+         (for [license (getx application :application/licenses)]
+           (render-license license)))))
+
+(defn- attachment-filenames [application]
+  (->> (:application/attachments application)
+       (build-index {:keys [:attachment/id]
+                     :value-fn :attachment/filename})))
 
 (defn- field-value [filenames field]
   (let [value (:field/value field)]
     (case (:field/type field)
       (:option :multiselect)
-      (localized (get (build-index {:keys [:key] :value-fn :label} (:field/options field)) value))
+      (localized (-> (build-index {:keys [:key] :value-fn :label}
+                                  (:field/options field))
+                     (get value)))
 
       :attachment
       (if (empty? value)
@@ -114,17 +154,12 @@
 
       (:field/value field))))
 
-(def label-field-style {:spacing-before 8})
-(def header-field-style {:spacing-before 8 :style :bold :size 15})
-(def field-style {:spacing-before 8 :style :bold})
-
 (defn- render-field [filenames field]
   (when (:field/visible field)
     (list
      [:paragraph (case (:field/type field)
-                   :label label-field-style
-                   :header header-field-style
-                   field-style)
+                   :header (merge field-heading-style {:size 15})
+                   field-heading-style)
       (localized (:field/title field))]
      [:paragraph (field-value filenames field)])))
 
@@ -135,32 +170,45 @@
            :let [fields (->> (getx form :form/fields)
                              (remove :field/private))]
            :when (seq fields)]
-       (list [:heading heading-style (or (get-in form [:form/external-title context/*lang*]) (text :t.form/application))]
+       (list [:heading heading-style (or (get-in form [:form/external-title context/*lang*])
+                                         (text :t.form/application))]
              (doall (for [field fields]
                       (render-field filenames field))))))))
 
-(def license-title-style {:style :bold})
-
-(defn- render-license [license]
-  (list [:paragraph license-title-style (localized (:license/title license))]
-        (case (:license/type license)
-          :text
-          [:paragraph (localized (:license/text license))]
-          :link
-          [:paragraph (localized (:license/link license))]
-          :attachment
-          [:paragraph (localized (:license/attachment-filename license))])))
-
-(defn- render-licenses [application]
-  (let [licenses (getx application :application/licenses)]
-    (list [:heading heading-style (text :t.form/licenses)]
-          (doall
-           (for [license (sort-by #(-> % :license/title localized) licenses)]
-             (render-license license))))))
+(defn- render-events [application]
+  (let [filenames (attachment-filenames application)
+        events (getx application :application/events)]
+    (list
+     [:heading heading-style (text :t.form/events)]
+     [:paragraph field-style
+      (if (empty? events)
+        ""
+        [:list
+         (doall
+          (for [event events
+                :when (not (#{:application.event/draft-saved}
+                            (:event/type event)))]
+            [:phrase
+             (localize-time (:event/time event)) " " (localize-event event)
+             (when-let [decision (localize-decision event)]
+               (str "\n" decision))
+             (when-some [comment (not-empty (:application/comment event))]
+               (str "\n" (text-format :t.label/default
+                                      (text :t.form/comment)
+                                      comment)))
+             (when-some [attachments (seq (:event/attachments event))]
+               (str "\n" (text-format :t.label/default
+                                      (text :t.form/attachments)
+                                      (->> attachments
+                                           (map (comp filenames :attachment/id))
+                                           (str/join ", ")))))]))])])))
 
 (defn- render-application [application]
   [{}
    (render-header application)
+   (render-applicants application)
+   (render-resources application)
+   (render-duos application)
    (render-licenses application)
    (render-fields application)
    (render-events application)])
diff --git a/src/cljs/rems/application.cljs b/src/cljs/rems/application.cljs
index b7f15fd4a..c8a3ca36f 100644
--- a/src/cljs/rems/application.cljs
+++ b/src/cljs/rems/application.cljs
@@ -1011,10 +1011,11 @@
                (when-let [missing-duos (seq (unmatched-duos application-duo-matches))]
                  [:div.alert.alert-warning
                   [:p (text :t.applications.duos.validation/missing-duo-codes)]
-                  (into [:ul]
-                        (for [duo missing-duos]
-                          ^{:key (:duo/id duo)}
-                          [:li (str (:duo/shorthand duo) " - " (localized (:duo/label duo)))]))])
+                  [:ul (for [duo missing-duos
+                             :let [label (localized (:duo/label duo))]]
+                         ^{:key (:duo/id duo)}
+                         [:li (text-format :t.label/dash
+                                           (:duo/shorthand duo) label)])]])
                [:div.mb-3
                 [:label.administration-field-label {:for "duos-dropdown"} (text :t.duo/title)]
                 [dropdown/dropdown
@@ -1090,11 +1091,11 @@
     [:div.col-lg-8
      [application-state application config highlight-request-id userid]
      [:div.mt-3 [applicants-info application userid]]
+     [:div.mt-3 [applied-resources application userid language]]
      (when (:enable-duo config)
        (if (= userid (-> application :application/applicant :userid))
          [:div.mt-3 [edit-application-duo-codes]]
          [:div.mt-3 [application-duo-codes]]))
-     [:div.mt-3 [applied-resources application userid language]]
      (when (contains? (:application/permissions application) :see-everything)
        [:div.mt-3 [previous-applications (get-in application [:application/applicant :userid])]])
      [:div.my-3 [application-licenses application userid]]
diff --git a/src/cljs/rems/util.cljs b/src/cljs/rems/util.cljs
index c35b6e5e3..bc5edd6cc 100644
--- a/src/cljs/rems/util.cljs
+++ b/src/cljs/rems/util.cljs
@@ -127,7 +127,7 @@
                                 s
                                 (str "http://" s)))]
       (map #(if (link? %)
-              [:a {:target :_blank :href (text-to-url %)} %]
+              ^{:key %} [:a {:target :_blank :href (text-to-url %)} %]
               %)
            splitted))))
 


Test patch:
diff --git a/src/clj/rems/db/test_data_helpers.clj b/src/clj/rems/db/test_data_helpers.clj
index 3cdbd28d3..57242e70e 100644
--- a/src/clj/rems/db/test_data_helpers.clj
+++ b/src/clj/rems/db/test_data_helpers.clj
@@ -239,6 +239,18 @@
                                                (:option :multiselect) (:key (first (:field/options field)))
                                                (or field-value "x"))})))))
 
+(defn fill-duo-codes! [{:keys [application-id actor duos] :as command}]
+  (let [app (applications/get-application-for-user actor application-id)]
+    (command! (assoc (base-command command)
+                     :type :application.command/save-draft
+                     ;; copy existing forms so as to not override
+                     :field-values (for [form (:application/forms app)
+                                         field (:form/fields form)]
+                                     {:form (:form/id form)
+                                      :field (:field/id field)
+                                      :value (:field/value field)})
+                     :duo-codes duos))))
+
 (defn accept-licenses! [{:keys [application-id actor] :as command}]
   (let [app (applications/get-application-for-user actor application-id)]
     (command! (assoc (base-command command)
diff --git a/test/clj/rems/test_pdf.clj b/test/clj/rems/test_pdf.clj
index c82aef026..c49e91768 100644
--- a/test/clj/rems/test_pdf.clj
+++ b/test/clj/rems/test_pdf.clj
@@ -122,28 +122,37 @@
 
     (testing "alice should not see reviewer and decider actions"
       (is (= [{}
-              [[:heading {:spacing-before 20} "Ansökan 2000/1: "]
-               [:paragraph "Denna pdf skapades" " " "2010-01-01 00:00"]
-               [:paragraph "Status" [:phrase ": " "Inlämnad"]]
-               [:heading {:spacing-before 20} "Sökande"]
-               [:paragraph "Sökande" ": " "Alice Applicant (alice) <alice@example.com>. Licenserna har accepterats: Ja"]
-               []
-               [:heading {:spacing-before 20} "Resurser"]
-               [:list [[:phrase "Resurs" " (" "pdf-resource-ext" ")"]
-                       [:phrase "Resurs 2" " (" "pdf-resource-2-ext" ")"]]]]
-              [[:heading {:spacing-before 20} "Licenser"] []]
-              [[[:heading {:spacing-before 20} "Blankett"]
-                [[[:paragraph {:spacing-before 8, :style :bold} "Offentligt textfält"]
+              [[:heading pdf/heading-style "Ansökan 2000/1: "]
+               [:paragraph pdf/field-style "Denna pdf skapades" " " "2010-01-01 00:00"]
+               [:paragraph "Status" [:phrase ": " "Inlämnad"]]]
+              [[:heading pdf/heading-style "Sökande"]
+               [[:paragraph pdf/field-style
+                 [:phrase {:style :bold} "Sökande"]
+                 ": "
+                 "Alice Applicant (alice) <alice@example.com>"]
+                [:paragraph "Licenserna har accepterats: Ja"]]
+               []] ; no members
+              [[:heading pdf/heading-style "Resurser"]
+               [[[:paragraph pdf/field-heading-style "Resurs (pdf-resource-ext)"]
+                 nil] ; no resource duos
+                [[:paragraph pdf/field-heading-style "Resurs 2 (pdf-resource-2-ext)"]
+                 nil]]] ; no resource duos
+              nil ; no application duos
+              [[:heading pdf/heading-style "Licenser"]
+               []]
+              [[[:heading pdf/heading-style "Blankett"]
+                [[[:paragraph pdf/field-heading-style "Offentligt textfält"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph {:spacing-before 8, :style :bold} "Privat textfält"]
+                 [[:paragraph pdf/field-heading-style "Privat textfält"]
                   [:paragraph "pdf test"]]]]
-               [[:heading {:spacing-before 20} "Privat blankett"]
-                [[[:paragraph {:spacing-before 8, :style :bold} "Privat textfält"]
+               [[:heading pdf/heading-style "Privat blankett"]
+                [[[:paragraph pdf/field-heading-style "Privat textfält"]
                   [:paragraph "pdf test"]]]]]
-              [[:heading {:spacing-before 20} "Händelser"]
-               [:list
-                [[:phrase "2000-01-01 00:00" " " "Alice Applicant skapade ansökan 2000/1." nil nil nil]
-                 [:phrase "2001-01-01 00:00" " " "Alice Applicant lämnade in ansökan." nil nil nil]]]]]
+              [[:heading pdf/heading-style "Händelser"]
+               [:paragraph pdf/field-style
+                [:list
+                 [[:phrase "2000-01-01 00:00" " " "Alice Applicant skapade ansökan 2000/1." nil nil nil]
+                  [:phrase "2001-01-01 00:00" " " "Alice Applicant lämnade in ansökan." nil nil nil]]]]]]
              (with-language :sv
                (fn []
                  (with-fixed-time (time/date-time 2010)
@@ -151,31 +160,64 @@
                      (#'pdf/render-application (applications/get-application-for-user "alice" application-id)))))))))
     (testing "handler should see complete application"
       (is (= [{}
-              [[:heading {:spacing-before 20} "Application 2000/1: "]
-               [:paragraph "This PDF generated at" " " "2010-01-01 00:00"]
-               [:paragraph "State" [:phrase ": " "Applied"]]
-               [:heading {:spacing-before 20} "Applicants"]
-               [:paragraph "Applicant" ": " "Alice Applicant (alice) <alice@example.com>. Accepted terms of use: Yes"]
-               []
-               [:heading {:spacing-before 20} "Resources"]
-               [:list [[:phrase "Resource" " (" "pdf-resource-ext" ")"]
-                       [:phrase "Resource 2" " (" "pdf-resource-2-ext" ")"]]]]
-              [[:heading {:spacing-before 20} "Terms of use"] []]
-              [[[:heading {:spacing-before 20} "Form"]
-                [[[:paragraph {:spacing-before 8, :style :bold} "Public text field"]
+              [[:heading pdf/heading-style "Application 2000/1: "]
+               [:paragraph pdf/field-style "This PDF generated at" " " "2010-01-01 00:00"]
+               [:paragraph "State" [:phrase ": " "Applied"]]]
+              [[:heading pdf/heading-style "Applicants"]
+               [[:paragraph pdf/field-style
+                 [:phrase {:style :bold} "Applicant"]
+                 ": "
+                 "Alice Applicant (alice) <alice@example.com>"]
+                [:paragraph "Accepted terms of use: Yes"]]
+               []] ; no members
+              [[:heading pdf/heading-style "Resources"]
+               [[[:paragraph pdf/field-heading-style "Resource (pdf-resource-ext)"]
+                 nil] ; no resource duos
+                [[:paragraph pdf/field-heading-style "Resource 2 (pdf-resource-2-ext)"]
+                 nil]]] ; no resource duos
+              nil ; no application duos
+              [[:heading pdf/heading-style "Terms of use"]
+               []]
+              [[[:heading pdf/heading-style "Form"]
+                [[[:paragraph pdf/field-heading-style "Public text field"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph {:spacing-before 8, :style :bold} "Private text field"]
+                 [[:paragraph pdf/field-heading-style "Private text field"]
                   [:paragraph "pdf test"]]]]
-               [[:heading {:spacing-before 20} "Private form"]
-                [[[:paragraph {:spacing-before 8, :style :bold} "Private text field"]
+               [[:heading pdf/heading-style "Private form"]
+                [[[:paragraph pdf/field-heading-style "Private text field"]
                   [:paragraph "pdf test"]]]]]
-              [[:heading {:spacing-before 20} "Events"]
-               [:list
-                [[:phrase "2000-01-01 00:00" " " "Alice Applicant created application 2000/1." nil nil nil]
-                 [:phrase "2001-01-01 00:00" " " "Alice Applicant submitted the application for review." nil nil nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer requested a review from Carl Reviewer." nil "\nComment: please have a look" nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer requested a decision from David Decider." nil "\nComment: please decide" nil]
-                 [:phrase "2003-01-01 00:00" " " "David Decider filed a decision for the application." "\nDavid Decider approved the application." "\nComment: I have decided" nil]]]]]
+              [[:heading pdf/heading-style "Events"]
+               [:paragraph pdf/field-style
+                [:list
+                 [[:phrase "2000-01-01 00:00" " " "Alice Applicant created application 2000/1." nil nil nil]
+                  [:phrase
+                   "2001-01-01 00:00"
+                   " "
+                   "Alice Applicant submitted the application for review."
+                   nil
+                   nil
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "Developer requested a review from Carl Reviewer."
+                   nil
+                   "\nComment: please have a look"
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "Developer requested a decision from David Decider."
+                   nil
+                   "\nComment: please decide"
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "David Decider filed a decision for the application."
+                   "\nDavid Decider approved the application."
+                   "\nComment: I have decided"
+                   nil]]]]]]
              (with-language :en
                (fn []
                  (with-fixed-time (time/date-time 2010)
@@ -183,31 +225,64 @@
                      (#'pdf/render-application (applications/get-application-for-user "developer" application-id)))))))))
     (testing "decider should see complete application"
       (is (= [{}
-              [[:heading {:spacing-before 20} "Hakemus 2000/1: "]
-               [:paragraph "Tämä PDF luotu" " " "2010-01-01 00:00"]
-               [:paragraph "Tila" [:phrase ": " "Haettu"]]
-               [:heading {:spacing-before 20} "Hakijat"]
-               [:paragraph "Hakija" ": " "Alice Applicant (alice) <alice@example.com>. Käyttöehdot hyväksytty: Kyllä"]
-               []
-               [:heading {:spacing-before 20} "Resurssit"]
-               [:list [[:phrase "Resurssi" " (" "pdf-resource-ext" ")"]
-                       [:phrase "Resurssi 2" " (" "pdf-resource-2-ext" ")"]]]]
-              [[:heading {:spacing-before 20} "Käyttöehdot"] []]
-              [[[:heading {:spacing-before 20} "Lomake"]
-                [[[:paragraph {:spacing-before 8, :style :bold} "Julkinen tekstikenttä"]
+              [[:heading pdf/heading-style "Hakemus 2000/1: "]
+               [:paragraph pdf/field-style "Tämä PDF luotu" " " "2010-01-01 00:00"]
+               [:paragraph "Tila" [:phrase ": " "Haettu"]]]
+              [[:heading pdf/heading-style "Hakijat"]
+               [[:paragraph pdf/field-style
+                 [:phrase {:style :bold} "Hakija"]
+                 ": "
+                 "Alice Applicant (alice) <alice@example.com>"]
+                [:paragraph "Käyttöehdot hyväksytty: Kyllä"]]
+               []]
+              [[:heading pdf/heading-style "Resurssit"]
+               [[[:paragraph pdf/field-heading-style "Resurssi (pdf-resource-ext)"]
+                 nil] ; no resource duos
+                [[:paragraph pdf/field-heading-style "Resurssi 2 (pdf-resource-2-ext)"]
+                 nil]]] ; no resource duos
+              nil ; no application duos
+              [[:heading pdf/heading-style "Käyttöehdot"]
+               []]
+              [[[:heading pdf/heading-style "Lomake"]
+                [[[:paragraph pdf/field-heading-style "Julkinen tekstikenttä"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph {:spacing-before 8, :style :bold} "Yksityinen tekstikenttä"]
+                 [[:paragraph pdf/field-heading-style "Yksityinen tekstikenttä"]
                   [:paragraph "pdf test"]]]]
-               [[:heading {:spacing-before 20} "Yksityinen lomake"]
-                [[[:paragraph {:spacing-before 8, :style :bold} "Yksityinen tekstikenttä"]
+               [[:heading pdf/heading-style "Yksityinen lomake"]
+                [[[:paragraph pdf/field-heading-style "Yksityinen tekstikenttä"]
                   [:paragraph "pdf test"]]]]]
-              [[:heading {:spacing-before 20} "Tapahtumat"]
-               [:list
-                [[:phrase "2000-01-01 00:00" " " "Alice Applicant loi hakemuksen 2000/1." nil nil nil]
-                 [:phrase "2001-01-01 00:00" " " "Alice Applicant lähetti hakemuksen käsiteltäväksi." nil nil nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer pyysi katselmointia käyttäjältä Carl Reviewer." nil "\nKommentti: please have a look" nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer pyysi päätöstä käyttäjältä David Decider." nil "\nKommentti: please decide" nil]
-                 [:phrase "2003-01-01 00:00" " " "David Decider teki päätöksen hakemukselle." "\nDavid Decider hyväksyi hakemuksen." "\nKommentti: I have decided" nil]]]]]
+              [[:heading pdf/heading-style "Tapahtumat"]
+               [:paragraph pdf/field-style
+                [:list
+                 [[:phrase "2000-01-01 00:00" " " "Alice Applicant loi hakemuksen 2000/1." nil nil nil]
+                  [:phrase
+                   "2001-01-01 00:00"
+                   " "
+                   "Alice Applicant lähetti hakemuksen käsiteltäväksi."
+                   nil
+                   nil
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "Developer pyysi katselmointia käyttäjältä Carl Reviewer."
+                   nil
+                   "\nKommentti: please have a look"
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "Developer pyysi päätöstä käyttäjältä David Decider."
+                   nil
+                   "\nKommentti: please decide"
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "David Decider teki päätöksen hakemukselle."
+                   "\nDavid Decider hyväksyi hakemuksen."
+                   "\nKommentti: I have decided"
+                   nil]]]]]]
              (with-language :fi
                (fn []
                  (with-fixed-time (time/date-time 2010)
@@ -215,26 +290,53 @@
                      (#'pdf/render-application (applications/get-application-for-user "david" application-id)))))))))
     (testing "reviewer should not see private fields"
       (is (= [{}
-              [[:heading {:spacing-before 20} "Ansökan 2000/1: "]
-               [:paragraph "Denna pdf skapades" " " "2010-01-01 00:00"]
-               [:paragraph "Status" [:phrase ": " "Inlämnad"]]
-               [:heading {:spacing-before 20} "Sökande"]
-               [:paragraph "Sökande" ": " "Alice Applicant (alice) <alice@example.com>. Licenserna har accepterats: Ja"]
-               []
-               [:heading {:spacing-before 20} "Resurser"]
-               [:list [[:phrase "Resurs" " (" "pdf-resource-ext" ")"]
-                       [:phrase "Resurs 2" " (" "pdf-resource-2-ext" ")"]]]]
-              [[:heading {:spacing-before 20} "Licenser"] []]
-              [[[:heading {:spacing-before 20} "Blankett"]
-                [[[:paragraph {:spacing-before 8, :style :bold} "Offentligt textfält"]
+              [[:heading pdf/heading-style "Ansökan 2000/1: "]
+               [:paragraph pdf/field-style "Denna pdf skapades" " " "2010-01-01 00:00"]
+               [:paragraph "Status" [:phrase ": " "Inlämnad"]]]
+              [[:heading pdf/heading-style "Sökande"]
+               [[:paragraph pdf/field-style
+                 [:phrase {:style :bold} "Sökande"]
+                 ": "
+                 "Alice Applicant (alice) <alice@example.com>"]
+                [:paragraph "Licenserna har accepterats: Ja"]]
+               []] ; no members
+              [[:heading pdf/heading-style "Resurser"]
+               [[[:paragraph pdf/field-heading-style "Resurs (pdf-resource-ext)"]
+                 nil] ; no resource duos
+                [[:paragraph pdf/field-heading-style "Resurs 2 (pdf-resource-2-ext)"]
+                 nil]]] ; no resource duos
+              nil ; no application duos
+              [[:heading pdf/heading-style "Licenser"]
+               []]
+              [[[:heading pdf/heading-style "Blankett"]
+                [[[:paragraph pdf/field-heading-style "Offentligt textfält"]
                   [:paragraph "pdf test"]]]]]
-              [[:heading {:spacing-before 20} "Händelser"]
-               [:list
-                [[:phrase "2000-01-01 00:00" " " "Alice Applicant skapade ansökan 2000/1." nil nil nil]
-                 [:phrase "2001-01-01 00:00" " " "Alice Applicant lämnade in ansökan." nil nil nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer begärde granskning av Carl Reviewer." nil "\nKommentar: please have a look" nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer begärde beslut från användare David Decider." nil "\nKommentar: please decide" nil]
-                 [:phrase "2003-01-01 00:00" " " "David Decider behandlade ansökan." "\nDavid Decider godkände ansökan." "\nKommentar: I have decided" nil]]]]]
+              [[:heading pdf/heading-style "Händelser"]
+               [:paragraph pdf/field-style
+                [:list
+                 [[:phrase "2000-01-01 00:00" " " "Alice Applicant skapade ansökan 2000/1." nil nil nil]
+                  [:phrase "2001-01-01 00:00" " " "Alice Applicant lämnade in ansökan." nil nil nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "Developer begärde granskning av Carl Reviewer."
+                   nil
+                   "\nKommentar: please have a look"
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "Developer begärde beslut från användare David Decider."
+                   nil
+                   "\nKommentar: please decide"
+                   nil]
+                  [:phrase
+                   "2003-01-01 00:00"
+                   " "
+                   "David Decider behandlade ansökan."
+                   "\nDavid Decider godkände ansökan."
+                   "\nKommentar: I have decided"
+                   nil]]]]]]
              (with-language :sv
                (fn []
                  (with-fixed-time (time/date-time 2010)
@@ -257,7 +359,14 @@
                                                            :fi "Tekstiä"}})
         ;; TODO attachment license
         resource (test-helpers/create-resource! {:resource-ext-id "pdf-resource-ext"
-                                                 :license-ids [lic1 lic2]})
+                                                 :license-ids [lic1 lic2]
+                                                 :resource/duo {:duo/codes [{:id "DUO:0000007" :restrictions [{:type :mondo
+                                                                                                               :values [{:id "MONDO:0000928"}]}]}
+                                                                            {:id "DUO:0000015"}
+                                                                            {:id "DUO:0000027"
+                                                                             :restrictions [{:type :project
+                                                                                             :values [{:value "project name here"}]}]
+                                                                             :more-info {:en "List of approved projects can be found at http://www.google.fi"}}]}})
         form (test-helpers/create-form! {:form/internal-name  "Form"
                                          :form/external-title {:en "Form"
                                                                :fi  "Lomake"
@@ -296,6 +405,13 @@
                                   :field-value "pdf test"
                                   :attachment (str attachment-1 "," attachment-2)
                                   :optional-fields true}))
+      ;; third draft-saved event
+      (test-helpers/fill-duo-codes! {:time (time/date-time 2000)
+                                     :actor applicant
+                                     :application-id application-id
+                                     :duos [{:id "DUO:0000007" :restrictions [{:type :mondo :values [{:id "MONDO:0000928"}]}]}
+                                            {:id "DUO:0000015"}
+                                            {:id "DUO:0000027" :restrictions [{:type :project :values [{:value "my project"}]}]}]})
       (test-helpers/accept-licenses! {:time (time/date-time 2000)
                                       :actor applicant
                                       :application-id application-id})
@@ -342,66 +458,98 @@
     (testing "pdf contents"
       (is (= [{}
               [[:heading pdf/heading-style "Application 2000/1: pdf test"]
-               [:paragraph "This PDF generated at" " " "2010-01-01 00:00"]
-               [:paragraph "State" [:phrase ": " "Approved"]]
-               [:heading pdf/heading-style "Applicants"]
-               [:paragraph "Applicant" ": " "Alice Applicant (alice) <alice@example.com>. Accepted terms of use: Yes"]
-               [[:paragraph "Member" ": " "Beth Applicant (beth) <beth@example.com>. Accepted terms of use: No"]]
-               [:heading pdf/heading-style "Resources"]
-               [:list [[:phrase "Catalogue item" " (" "pdf-resource-ext" ")"]]]]
+               [:paragraph pdf/field-style "This PDF generated at" " " "2010-01-01 00:00"]
+               [:paragraph "State" [:phrase ": " "Approved"]]]
+              [[:heading pdf/heading-style "Applicants"]
+               [[:paragraph pdf/field-style
+                 [:phrase {:style :bold} "Applicant"]
+                 ": "
+                 "Alice Applicant (alice) <alice@example.com>"]
+                [:paragraph "Accepted terms of use: Yes"]]
+               [[[:paragraph pdf/field-style
+                  [:phrase {:style :bold} "Member"]
+                  ": "
+                  "Beth Applicant (beth) <beth@example.com>"]
+                 [:paragraph "Accepted terms of use: No"]]]]
+              [[:heading pdf/heading-style "Resources"]
+               [[[:paragraph pdf/field-heading-style "Catalogue item (pdf-resource-ext)"]
+                 [[:paragraph pdf/field-style "Data Use Ontology"]
+                  [[:paragraph {:spacing-before 8}
+                    [:paragraph nil "DS – Disease specific research"]
+                    [:list
+                     [[:phrase "MONDO:0000928 – Eyelid melanoma"]]]]
+                   [:paragraph {:spacing-before 8}
+                    [:paragraph nil "NMDS – No general methods research"]
+                    [:list []]]
+                   [:paragraph {:spacing-before 8}
+                    [:paragraph nil "PS – Project specific restriction"]
+                    [:list [[:phrase "project name here"]]]]]]]]]
+              [[:heading pdf/heading-style "Data Use Ontology"]
+               [:paragraph {:spacing-before 8}]
+               [:paragraph nil
+                [:paragraph {:style :bold} "DS – Disease specific research"]
+                [:list
+                 [[:phrase "MONDO:0000928 – Eyelid melanoma"]]]]
+               [:paragraph {:spacing-before 8}
+                [:paragraph {:style :bold} "NMDS – No general methods research"]
+                [:list []]]
+               [:paragraph {:spacing-before 8}
+                [:paragraph {:style :bold} "PS – Project specific restriction"]
+                [:list [[:phrase "my project"]]]]]
               [[:heading pdf/heading-style "Terms of use"]
-               [[[:paragraph pdf/license-title-style "Google license"]
+               [[[:paragraph pdf/field-heading-style "Google license"]
                  [:paragraph "http://google.com"]]
-                [[:paragraph pdf/license-title-style "Text license"]
+                [[:paragraph pdf/field-heading-style "Text license"]
                  [:paragraph "Some text"]]]]
               [[[:heading pdf/heading-style "Form"]
-                [[[:paragraph pdf/label-field-style
+                [[[:paragraph pdf/field-heading-style
                    "This form demonstrates all possible field types. (This text itself is a label field.)"]
                   [:paragraph ""]]
-                 [[:paragraph pdf/field-style "Application title field"]
+                 [[:paragraph pdf/field-heading-style "Application title field"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph pdf/field-style "Text field"]
+                 [[:paragraph pdf/field-heading-style "Text field"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph pdf/field-style "Text area"]
+                 [[:paragraph pdf/field-heading-style "Text area"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph pdf/header-field-style "Header"]
+                 [[:paragraph (merge pdf/field-heading-style {:size 15}) "Header"]
                   [:paragraph ""]]
-                 [[:paragraph pdf/field-style "Date field"]
+                 [[:paragraph pdf/field-heading-style "Date field"]
                   [:paragraph "2002-03-04"]]
-                 [[:paragraph pdf/field-style "Email field"]
+                 [[:paragraph pdf/field-heading-style "Email field"]
                   [:paragraph "user@example.com"]]
-                 [[:paragraph pdf/field-style "Attachment"]
+                 [[:paragraph pdf/field-heading-style "Attachment"]
                   [:paragraph "attachment.pdf, picture.png"]]
-                 [[:paragraph pdf/field-style "Option list. Choose the first option to reveal a new field."]
+                 [[:paragraph pdf/field-heading-style "Option list. Choose the first option to reveal a new field."]
                   [:paragraph "First option"]]
-                 [[:paragraph pdf/field-style "Conditional field. Shown only if first option is selected above."]
+                 [[:paragraph pdf/field-heading-style "Conditional field. Shown only if first option is selected above."]
                   [:paragraph "pdf test"]]
-                 [[:paragraph pdf/field-style "Multi-select list"]
+                 [[:paragraph pdf/field-heading-style "Multi-select list"]
                   [:paragraph "First option"]]
-                 [[:paragraph pdf/field-style "Table"]
+                 [[:paragraph pdf/field-heading-style "Table"]
                   [:paragraph
                    [:table {:header ["First" "Second"]}
                     ["pdf test" "pdf test"]
                     ["pdf test" "pdf test"]]]]
-                 [[:paragraph pdf/label-field-style "The following field types can have a max length."]
+                 [[:paragraph pdf/field-heading-style "The following field types can have a max length."]
                   [:paragraph ""]]
-                 [[:paragraph pdf/field-style "Text field with max length"]
+                 [[:paragraph pdf/field-heading-style "Text field with max length"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph pdf/field-style "Text area with max length"]
+                 [[:paragraph pdf/field-heading-style "Text area with max length"]
                   [:paragraph "pdf test"]]
-                 [[:paragraph pdf/field-style "Phone number"]
+                 [[:paragraph pdf/field-heading-style "Phone number"]
                   [:paragraph "+358451110000"]]
-                 [[:paragraph pdf/field-style "IP address"]
+                 [[:paragraph pdf/field-heading-style "IP address"]
                   [:paragraph "142.250.74.110"]]]]]
               [[:heading pdf/heading-style "Events"]
-               [:list
-                [[:phrase "2000-01-01 00:00" " " "Alice Applicant created application 2000/1." nil nil nil]
-                 [:phrase "2000-01-01 00:00" " " "Alice Applicant accepted the terms of use." nil nil nil]
-                 [:phrase "2001-01-01 00:00" " " "Alice Applicant submitted the application for review." nil nil nil]
-                 [:phrase "2002-01-01 00:00" " " "Developer added Beth Applicant to the application." nil nil nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer requested a decision from David Decider." nil "\nComment: please decide" nil]
-                 [:phrase "2003-01-01 00:00" " " "David Decider filed a decision for the application." "\nDavid Decider approved the application." "\nComment: I have decided" nil]
-                 [:phrase "2003-01-01 00:00" " " "Developer approved the application." nil "\nComment: approved" "\nAttachments: file1.txt, file2.pdf"]]]]]
+               [:paragraph pdf/field-style
+                [:list
+                 [[:phrase "2000-01-01 00:00" " " "Alice Applicant created application 2000/1." nil nil nil]
+                  [:phrase "2000-01-01 00:00" " " "Alice Applicant accepted the terms of use." nil nil nil]
+                  [:phrase "2001-01-01 00:00" " " "Alice Applicant submitted the application for review." nil nil nil]
+                  [:phrase "2002-01-01 00:00" " " "Developer added Beth Applicant to the application." nil nil nil]
+                  [:phrase "2003-01-01 00:00" " " "Developer requested a decision from David Decider." nil "\nComment: please decide" nil]
+                  [:phrase "2003-01-01 00:00" " " "David Decider filed a decision for the application." "\nDavid Decider approved the application." "\nComment: I have decided" nil]
+                  [:phrase "2003-01-01 00:00" " " "Developer approved the application." nil "\nComment: approved" "\nAttachments: file1.txt, file2.pdf"]]]]]]
              (with-language :en
                (fn []
                  (with-fixed-time (time/date-time 2010)