Repository: CSCfi/rems
Pull request #1322 (2019-06-06 09:15:55)

Changes to email translations and corresponding changes to code


Patch:
diff --git a/resources/translations/en.edn b/resources/translations/en.edn
index a46be48aa..a57bb3a2f 100644
--- a/resources/translations/en.edn
+++ b/resources/translations/en.edn
@@ -192,27 +192,27 @@
                     :round-n "Round %1"
                     :rounds-workflow "Round-based workflow"
                     :title "Title"}
-  :email {:application-approved {:subject "Your application has been approved"
-                                 :message "Dear %1,\nYour application %2 has been approved.\nView your application: %3"}
-          :application-rejected {:subject "Your application has been rejected"
-                                 :message "Dear %1,\nYour application %2 has been rejected.\nView your application: %3"}
-          :application-closed {:subject "Your application has been closed"
-                               :message "Dear %1,\nYour application %2 has been closed.\nView your application: %3"}
-          :application-submitted {:subject "A new application has been submitted"
-                                  :message "Dear %1,\nUser %2 has submitted a new application %3 for the resource(s) %4.\nView the application: %5"}
-          :comment-requested {:subject "Comment request"
-                              :message "Dear %1,\nUser %2 has requested your comment on application %3.\nComment here: %4"}
-          :commented {:subject "New comment notification"
-                      :message "Dear %1,\nUser %2 has posted a comment on application %3.\nView the application: %4"}
-          :decided {:subject "New decision notification"
-                    :message "Dear %1,\nUser %2 has sent a decision on application %3.\nView the application: %4"}
-          :decision-requested {:subject "Decision request"
-                               :message "Dear %1,\nUser %2 has requested your decision on application %3.\nView the application: %4"}
-          :footer "\nPlease do not reply to this automatically generated message."
-          :member-added {:subject "You've been added as a member to an application"
-                         :message "Dear %1,\nYou've been added as a member to application %2.\nView the application: %3"}
+  :email {:application-approved {:subject "Application approved (%2)"
+                                 :message "Dear %1,\n\nYour application %2 has been approved.\n\nView application: %3"}
+          :application-rejected {:subject "Application rejected (%2)"
+                                 :message "Dear %1,\n\nYour application %2 has been rejected.\n\nView application: %3"}
+          :application-closed {:subject "Application closed (%2)"
+                               :message "Dear %1,\n\nYour application %2 has been closed.\n\nView application: %3"}
+          :application-submitted {:subject "Application submitted (%2: %3)"
+                                  :message "Dear %1,\n\n%2 has submitted an application (%3): %4.\n\nView application: %5"}
+          :comment-requested {:subject "Comment requested (%2: %3)"
+                              :message "Dear %1,\n\n%2 has requested a comment on application %3.\n\nComment: %4"}
+          :commented {:subject "Comment added (%2: %3)"
+                      :message "Dear %1,\n\n%2 has commented application %3.\n\nView application: %4"}
+          :decided {:subject "Decision made (%2: %3)"
+                    :message "Dear %1,\n\n%2 has made a decision on application %3.\n\nView application: %4"}
+          :decision-requested {:subject "Decision requested (%2: %3)"
+                               :message "Dear %1,\n\n%2 has requested your decision on application %3.\n\nView application: %4"}
+          :footer "\n\nPlease don't reply to this automatically generated message."
+          :member-added {:subject "Added as a member of an application (%2)"
+                         :message "Dear %1,\n\nYou've been added as a member of application %2.\n\nView application: %3"}
           :member-invited {:subject "Invitation to participate in an application"
-                           :message "Hello,\nThis email address (%1) has been invited to participate in an application.\nParticipate with this link: %2"}}
+                           :message "Hello,\n\nThis email address (%1) has been invited to participate in an application.\n\nParticipate: %2"}}
   :footer "Powered by CSC - IT Center for Science"
   :form {:accepted-licenses "Accepted terms of use"
          :actions "Actions"
@@ -246,7 +246,6 @@
          :user "User"
          :validation {:errors "There were problems. Check the following fields."
                       :invalid-user "Invalid user."
-                      :invalid-value "Invalid value."
                       :required "Field \"%1\" is required."
                       :toolong "Answer to field \"%1\" is too long."}}
   :header {:title "Welcome to REMS"}
diff --git a/resources/translations/fi.edn b/resources/translations/fi.edn
index 0186354a5..55d35f544 100644
--- a/resources/translations/fi.edn
+++ b/resources/translations/fi.edn
@@ -192,27 +192,27 @@
                     :round-n "Kierros %1"
                     :rounds-workflow "Kierrospohjainen työvuo"
                     :title "Työvuon nimi"}
-  :email {:application-approved {:subject "Hakemuksesi on hyväksytty"
-                                 :message "Hyvä %1,\nHakemuksesi %2 on hyväksytty.\nKatsele hakemusta: %3"}
-          :application-rejected {:subject "Hakemuksesi on hylätty"
-                                 :message "Hyvä %1,\nHakemuksesi %2 on hylätty.\nKatsele hakemusta: %3"}
-          :application-closed {:subject "Hakemuksesi on suljettu"
-                               :message "Hyvä %1,\nHakemuksesi %2 on suljettu.\nKatsele hakemusta: %3"}
-          :application-submitted {:subject "Uusi hakemus"
-                                  :message "Hyvä %1,\nKäyttäjä %2 on lähettänyt uuden hakemuksen %3 resurss(e)ille %4.\nKatsele hakemusta: %5"}
-          :comment-requested {:subject "Kommenttipyyntö"
-                              :message "Hyvä %1,\nKäyttäjä %2 on pyytänyt sinulta kommenttia hakemukseen %3.\nKommentoi täällä: %4"}
-          :commented {:subject "Uusi kommentti"
-                      :message "Hyvä %1,\nKäyttäjä %2 on kommentoinut hakemusta %3.\nKatsele hakemusta: %4"}
-          :decided {:subject "Uusi päätös"
-                    :message "Hyvä %1,\nKäyttäjä %2 on tehnyt päätöksen hakemuksesta %3.\nKatsele hakemusta: %4"}
-          :decision-requested {:subject "Päätöspyyntö"
-                               :message "Hyvä %1,\nKäyttäjä %2 on pyytänyt sinulta päätöstä hakemukseen %3.\nKatsele hakemusta: %4"}
-          :footer "\nTämä on automaattinen viesti. Älä vastaa tähän."
-          :member-added {:subject "Sinut on lisätty hakemukseen"
-                         :message "Hyvä %1,\nSinut on lisätty jäseneksi hakemukseen %2.\nKatsele hakemusta: %3"}
+  :email {:application-approved {:subject "Hakemus hyväksytty (%2)"
+                                 :message "Hyvä %1,\n\nHakemuksesi %2 on hyväksytty.\n\nTarkastele hakemusta: %3"}
+          :application-rejected {:subject "Hakemus hylätty (%2)"
+                                 :message "Hyvä %1,\n\nHakemuksesi %2 on hylätty.\n\nTarkastele hakemusta: %3"}
+          :application-closed {:subject "Hakemus suljettu (%2)"
+                               :message "Hyvä %1,\n\nHakemuksesi %2 on suljettu.\n\nTarkastele hakemusta: %3"}
+          :application-submitted {:subject "Hakemus lähetetty (%2: %3)"
+                                  :message "Hyvä %1,\n\n%2 on lähettänyt käyttöoikeushakemuksen (%3): %4.\n\nTarkastele hakemusta: %5"}
+          :comment-requested {:subject "Kommenttipyyntö (%2: %3)"
+                              :message "Hyvä %1,\n\n%2 on pyytänyt kommentoimaan hakemusta %3.\n\nKommentoi: %4"}
+          :commented {:subject "Hakemusta kommentoitu (%2: %3)"
+                      :message "Hyvä %1,\n\n%2 on kommentoinut hakemusta %3.\n\nTarkastele hakemusta: %4"}
+          :decided {:subject "Päätös tehty (%2: %3)"
+                    :message "Hyvä %1,\n\n%2 on tehnyt päätöksen hakemuksesta %3.\n\nTarkastele hakemusta: %4"}
+          :decision-requested {:subject "Päätöspyyntö (%2: %3)"
+                               :message "Hyvä %1,\n\n%2 on pyytänyt sinua tekemään päätöksen hakemuksesta %3.\n\nTarkastele hakemusta: %4"}
+          :footer "\n\nTämä on automaattinen viesti. Älä vastaa."
+          :member-added {:subject "Lisätty hakemuksen jäseneksi (%2)"
+                         :message "Hyvä %1,\n\nSinut on lisätty hakemuksen %2 jäseneksi.\n\nTarkastele hakemusta: %3"}
           :member-invited {:subject "Kutsu osallistumaan hakemukseen"
-                           :message "Hei,\nTämä sähköpostiosoite (%1) on kutsuttu osallistumaan hakemukseen.\nOsallistumislinkki: %2"}}
+                           :message "Hei,\n\nTämä sähköpostiosoite (%1) on kutsuttu osallistumaan kielivarojen käyttöoikeushakemukseen.\n\nOsallistumislinkki: %2"}}
   :footer "CSC - IT Center for Science"
   :form {:accepted-licenses "Hyväksynyt käyttöehdot"
          :actions "Toiminnot"
@@ -246,7 +246,6 @@
          :user "Käyttäjä"
          :validation {:errors "Lähetys epäonnistui. Tarkista seuraavat kentät."
                       :invalid-user "Käyttäjä ei kelpaa."
-                      :invalid-value "Arvo ei kelpaa."
                       :required "Kenttä \"%1\" on pakollinen."
                       :toolong "Kentän \"%1\" vastaus on liian pitkä."}}
   :header {:title "Tervetuloa REMSiin"}
diff --git a/src/clj/rems/poller/email.clj b/src/clj/rems/poller/email.clj
index 68ee94291..87c29e41b 100644
--- a/src/clj/rems/poller/email.clj
+++ b/src/clj/rems/poller/email.clj
@@ -48,7 +48,12 @@
   (vec
    (for [handler (get-in application [:application/workflow :workflow.dynamic/handlers])]
      {:to-user handler
-      :subject (text :t.email.application-submitted/subject)
+      :subject (text-format :t.email.application-submitted/subject
+                            handler
+                            (:application/applicant application)
+                            (application-id-for-email application)
+                            (resources-for-email application)
+                            (link-to-application (:application/id application)))
       :body (text-format :t.email.application-submitted/message
                          handler
                          (:application/applicant application)
@@ -67,7 +72,10 @@
   (vec
    (for [member (applicant-and-members application)]
      {:to-user (:userid member)
-      :subject (text :t.email.application-approved/subject)
+      :subject (text-format :t.email.application-approved/subject
+                            (:userid member)
+                            (application-id-for-email application)
+                            (link-to-application (:application/id event)))
       :body (text-format :t.email.application-approved/message
                          (:userid member)
                          (application-id-for-email application)
@@ -77,7 +85,10 @@
   (vec
    (for [member (applicant-and-members application)]
      {:to-user (:userid member)
-      :subject (text :t.email.application-rejected/subject)
+      :subject (text-format :t.email.application-rejected/subject
+                            (:userid member)
+                            (application-id-for-email application)
+                            (link-to-application (:application/id event)))
       :body (text-format :t.email.application-rejected/message
                          (:userid member)
                          (application-id-for-email application)
@@ -87,7 +98,10 @@
   (vec
    (for [member (applicant-and-members application)]
      {:to-user (:userid member)
-      :subject (text :t.email.application-closed/subject)
+      :subject (text-format :t.email.application-closed/subject
+                            (:userid member)
+                            (application-id-for-email application)
+                            (link-to-application (:application/id event)))
       :body (text-format :t.email.application-closed/message
                          (:userid member)
                          (application-id-for-email application)
@@ -97,7 +111,11 @@
   (vec
    (for [commenter (:application/commenters event)]
      {:to-user commenter
-      :subject (text :t.email.comment-requested/subject)
+      :subject (text-format :t.email.comment-requested/subject
+                            commenter
+                            (:event/actor event)
+                            (application-id-for-email application)
+                            (link-to-application (:application/id event)))
       :body (text-format :t.email.comment-requested/message
                          commenter
                          (:event/actor event)
@@ -108,7 +126,11 @@
   (vec
    (for [decider (:application/deciders event)]
      {:to-user decider
-      :subject (text :t.email.decision-requested/subject)
+      :subject (text-format :t.email.decision-requested/subject
+                            decider
+                            (:event/actor event)
+                            (application-id-for-email application)
+                            (link-to-application (:application/id event)))
       :body (text-format :t.email.decision-requested/message
                          decider
                          (:event/actor event)
@@ -119,7 +141,11 @@
   (vec
    (for [handler (get-in application [:application/workflow :workflow.dynamic/handlers])]
      {:to-user handler
-      :subject (text :t.email.commented/subject)
+      :subject (text-format :t.email.commented/subject
+                            handler
+                            (:event/actor event)
+                            (application-id-for-email application)
+                            (link-to-application (:application/id event)))
       :body (text-format :t.email.commented/message
                          handler
                          (:event/actor event)
@@ -130,7 +156,11 @@
   (vec
    (for [handler (get-in application [:application/workflow :workflow.dynamic/handlers])]
      {:to-user handler
-      :subject (text :t.email.decided/subject)
+      :subject (text-format :t.email.decided/subject
+                            handler
+                            (:event/actor event)
+                            (application-id-for-email application)
+                            (link-to-application (:application/id event)))
       :body (text-format :t.email.decided/message
                          handler
                          (:event/actor event)
@@ -140,7 +170,10 @@
 (defmethod event-to-emails-impl :application.event/member-added [event application]
   ;; TODO email to applicant? email to handler?
   [{:to-user (:userid (:application/member event))
-    :subject (text :t.email.member-added/subject)
+    :subject (text-format :t.email.member-added/subject
+                          (:userid (:application/member event))
+                          (application-id-for-email application)
+                          (link-to-application (:application/id event)))
     :body (text-format :t.email.member-added/message
                        (:userid (:application/member event))
                        (application-id-for-email application)
@@ -148,7 +181,9 @@
 
 (defmethod event-to-emails-impl :application.event/member-invited [event _application]
   [{:to (:email (:application/member event))
-    :subject (text :t.email.member-invited/subject)
+    :subject (text-format :t.email.member-invited/subject
+                          (:email (:application/member event))
+                          (invitation-link (:invitation/token event)))
     :body (text-format :t.email.member-invited/message
                        (:email (:application/member event))
                        (invitation-link (:invitation/token event)))}])
@@ -191,10 +226,7 @@
                                    (:to-user email-spec)))))]
         ;; TODO check that :to is set
         (log/info "sending email:" (pr-str email))
-        (try
-          (postal/send-message {:host host :port port} email)
-          (catch com.sun.mail.smtp.SMTPAddressFailedException e ; email address does not exist
-            (log/warn e "failed sending email, skipping:" (pr-str email))))))))
+        (postal/send-message {:host host :port port} email)))))
 
 (defn run []
   (common/run-event-poller ::poller (fn [event]
diff --git a/src/cljs/rems/administration/components.cljs b/src/cljs/rems/administration/components.cljs
index 8677c0595..81bcbda18 100644
--- a/src/cljs/rems/administration/components.cljs
+++ b/src/cljs/rems/administration/components.cljs
@@ -15,8 +15,7 @@
     :label  - String, shown to the user as-is."
   (:require [clojure.string :as str]
             [re-frame.core :as rf]
-            [rems.atoms :refer [info-field textarea]]
-            [rems.text :refer [text-format]]))
+            [rems.atoms :refer [info-field textarea]]))
 
 (defn- key-to-id [key]
   (if (number? key)
@@ -28,29 +27,20 @@
        (map key-to-id)
        (str/join "-")))
 
-(defn- field-validation-message [error label]
-  [:div {:class "invalid-feedback"}
-   (when error (text-format error label))])
-
 (defn input-field [{:keys [keys label placeholder context type normalizer readonly]}]
   (let [form @(rf/subscribe [(:get-form context)])
-        form-errors (when (:get-form-errors context)
-                      @(rf/subscribe [(:get-form-errors context)]))
         id (keys-to-id keys)
-        normalizer (or normalizer identity)
-        error (get-in form-errors keys)]
+        normalizer (or normalizer identity)]
     [:div.form-group.field
      [:label {:for id} label]
      [:input.form-control {:type type
                            :id id
                            :disabled readonly
                            :placeholder placeholder
-                           :class (when error "is-invalid")
                            :value (get-in form keys)
                            :on-change #(rf/dispatch [(:update-form context)
                                                      keys
-                                                     (normalizer (.. % -target -value))])}]
-     [field-validation-message error label]]))
+                                                     (normalizer (.. % -target -value))])}]]))
 
 (defn text-field
   "A basic text field, full page width."
@@ -61,28 +51,20 @@
   "A basic textarea, full page width."
   [context {:keys [keys label placeholder]}]
   (let [form @(rf/subscribe [(:get-form context)])
-        form-errors (when (:get-form-errors context)
-                      @(rf/subscribe [(:get-form-errors context)]))
-        id (keys-to-id keys)
-        error (get-in form-errors keys)]
+        id (keys-to-id keys)]
     [:div.form-group.field
      [:label {:for id} label]
      [textarea {:id id
                 :placeholder placeholder
                 :value (get-in form keys)
-                :class (when error "is-invalid")
                 :on-change #(rf/dispatch [(:update-form context)
                                           keys
-                                          (.. % -target -value)])}]
-     [field-validation-message error label]]))
+                                          (.. % -target -value)])}]]))
 
-(defn- localized-text-field-lang [context {:keys [keys-prefix label lang]}]
+(defn- localized-text-field-lang [context {:keys [keys-prefix lang]}]
   (let [form @(rf/subscribe [(:get-form context)])
-        form-errors (when (:get-form-errors context)
-                      @(rf/subscribe [(:get-form-errors context)]))
         keys (conj keys-prefix lang)
-        id (keys-to-id keys)
-        error (get-in form-errors keys)]
+        id (keys-to-id keys)]
     [:div.form-group.row
      [:label.col-sm-1.col-form-label {:for id}
       (str/upper-case (name lang))]
@@ -90,11 +72,9 @@
       [textarea {:id id
                  :min-rows 1
                  :value (get-in form keys)
-                 :class (when error "is-invalid")
                  :on-change #(rf/dispatch [(:update-form context)
                                            keys
-                                           (.. % -target -value)])}]
-      [field-validation-message error label]]]))
+                                           (.. % -target -value)])}]]]))
 
 (defn localized-text-field
   "A text field for inputting text in all supported languages.
@@ -106,7 +86,6 @@
            [:label label]]
           (for [lang languages]
             [localized-text-field-lang context {:keys-prefix keys
-                                                :label label
                                                 :lang lang}]))))
 
 (defn checkbox
@@ -127,18 +106,14 @@
 
 (defn- radio-button [context {:keys [keys value label orientation readonly]}]
   (let [form @(rf/subscribe [(:get-form context)])
-        form-errors (when (:get-form-errors context)
-                      @(rf/subscribe [(:get-form-errors context)]))
         name (keys-to-id keys)
-        id (keys-to-id (conj keys value))
-        error (get-in form-errors keys)]
+        id (keys-to-id (conj keys value))]
     [(case orientation
        :vertical :div.form-check
        :horizontal :div.form-check.form-check-inline)
      [:input.form-check-input {:id id
                                :type "radio"
                                :disabled readonly
-                               :class (when error "is-invalid")
                                :name name
                                :value value
                                :checked (= value (get-in form keys))
diff --git a/src/cljs/rems/administration/create_catalogue_item.cljs b/src/cljs/rems/administration/create_catalogue_item.cljs
index e7e7788bc..62b184f10 100644
--- a/src/cljs/rems/administration/create_catalogue_item.cljs
+++ b/src/cljs/rems/administration/create_catalogue_item.cljs
@@ -66,7 +66,8 @@
 
 
 (defn- fetch-workflows []
-  (fetch "/api/workflows" {:handler #(rf/dispatch [::fetch-workflows-result %])}))
+  (fetch "/api/workflows" {:url-params {:active true}
+                           :handler #(rf/dispatch [::fetch-workflows-result %])}))
 
 (rf/reg-fx ::fetch-workflows fetch-workflows)
 
@@ -79,7 +80,8 @@
 (rf/reg-sub ::workflows (fn [db _] (::workflows db)))
 
 (defn- fetch-resources []
-  (fetch "/api/resources" {:handler #(rf/dispatch [::fetch-resources-result %])}))
+  (fetch "/api/resources" {:url-params {:active true}
+                           :handler #(rf/dispatch [::fetch-resources-result %])}))
 
 (rf/reg-fx ::fetch-resources fetch-resources)
 
@@ -93,7 +95,8 @@
 
 
 (defn- fetch-forms []
-  (fetch "/api/forms" {:handler #(rf/dispatch [::fetch-forms-result %])}))
+  (fetch "/api/forms" {:url-params {:active true}
+                       :handler #(rf/dispatch [::fetch-forms-result %])}))
 
 (rf/reg-fx ::fetch-forms fetch-forms)
 
diff --git a/src/cljs/rems/administration/create_form.cljs b/src/cljs/rems/administration/create_form.cljs
index c3aa31bca..1085be1c8 100644
--- a/src/cljs/rems/administration/create_form.cljs
+++ b/src/cljs/rems/administration/create_form.cljs
@@ -10,7 +10,7 @@
             [rems.fields :as fields]
             [rems.status-modal :as status-modal]
             [rems.text :refer [text text-format]]
-            [rems.util :refer [dispatch! post! normalize-option-key parse-int remove-empty-keys]]))
+            [rems.util :refer [dispatch! post! normalize-option-key parse-int]]))
 
 (rf/reg-event-fx
  ::enter-page
@@ -21,7 +21,6 @@
 
 ;; TODO rename item->field
 (rf/reg-sub ::form (fn [db _] (::form db)))
-(rf/reg-sub ::form-errors (fn [db _] (::form-errors db)))
 (rf/reg-event-db ::set-form-field (fn [db [_ keys value]] (assoc-in db (concat [::form] keys) value)))
 
 (rf/reg-event-db ::add-form-field (fn [db [_]] (update-in db [::form :fields] items/add {:type "text"})))
@@ -49,6 +48,7 @@
  (fn [db [_ field-index option-index]]
    (update-in db [::form :fields field-index :options] items/move-down option-index)))
 
+
 ;;;; form submit
 
 (defn- supports-optional? [field]
@@ -63,6 +63,47 @@
 (defn- supports-options? [field]
   (contains? #{"option" "multiselect"} (:type field)))
 
+(defn- localized-string? [lstr languages]
+  (and (= (set (keys lstr))
+          (set languages))
+       (every? string? (vals lstr))))
+
+(defn- valid-required-localized-string? [lstr languages]
+  (and (localized-string? lstr languages)
+       (every? #(not (str/blank? %))
+               (vals lstr))))
+
+(defn- valid-optional-localized-string? [lstr languages]
+  (and (localized-string? lstr languages)
+       ;; partial translations are not allowed
+       (or (every? #(not (str/blank? %))
+                   (vals lstr))
+           (every? str/blank?
+                   (vals lstr)))))
+
+(defn- valid-option? [option languages]
+  (and (not (str/blank? (:key option)))
+       (valid-required-localized-string? (:label option) languages)))
+
+(defn- valid-request-field? [field languages]
+  (and (valid-required-localized-string? (:title field) languages)
+       (not (str/blank? (:type field)))
+       (boolean? (:optional field))
+       (if (supports-input-prompt? field)
+         (valid-optional-localized-string? (:input-prompt field) languages)
+         (nil? (:input-prompt field)))
+       (if (supports-maxlength? field)
+         (not (neg? (:maxlength field)))
+         (nil? (:maxlength field)))
+       (if (supports-options? field)
+         (every? #(valid-option? % languages) (:options field))
+         (nil? (:options field)))))
+
+(defn- valid-request? [request languages]
+  (and (not (str/blank? (:organization request)))
+       (not (str/blank? (:title request)))
+       (every? #(valid-request-field? % languages) (:fields request))))
+
 (defn build-localized-string [lstr languages]
   (into {} (for [language languages]
              [language (get lstr language "")])))
@@ -83,70 +124,26 @@
                         :label (build-localized-string label languages)})})))
 
 (defn build-request [form languages]
-  {:organization (:organization form)
-   :title (:title form)
-   :fields (mapv #(build-request-field % languages) (:fields form))})
-
-;;;; form validation
-
-(defn- validate-text-field [m key]
-  (when (str/blank? (get m key))
-    {key :t.form.validation/required}))
-
-(defn- validate-localized-text-field [m key languages]
-  {key (apply merge (mapv #(validate-text-field (get m key) %) languages))})
-
-(defn- validate-optional-localized-field [m key languages]
-  (let [validated (mapv #(validate-text-field (get m key) %) languages)]
-    ;; partial translations are not allowed
-    (when (not-empty (remove identity validated))
-      {key (apply merge validated)})))
-
-(def ^:private maxlength-range [0 32767])
-
-(defn- validate-maxlength [maxlength]
-  (when-not (str/blank? maxlength)
-    (let [parsed (parse-int maxlength)]
-      (when (or (nil? parsed)
-                (not (<= (first maxlength-range) parsed (second maxlength-range))))
-        {:maxlength :t.form.validation/invalid-value}))))
-
-(defn- validate-option [option id languages]
-  {id (merge (validate-text-field option :key)
-             (validate-localized-text-field option :label languages))})
-
-(defn- validate-options [options languages]
-  {:options (apply merge (mapv #(validate-option %1 %2 languages) options (range)))})
-
-(defn- validate-field [field id languages]
-  {id (merge (validate-text-field field :type)
-             (validate-localized-text-field field :title languages)
-             (validate-optional-localized-field field :input-prompt languages)
-             (validate-maxlength (:maxlength field))
-             (validate-options (:options field) languages))})
-
-(defn validate-form [form languages]
-  (-> (merge (validate-text-field form :organization)
-             (validate-text-field form :title)
-             {:fields (apply merge (mapv #(validate-field %1 %2 languages) (form :fields) (range)))})
-      remove-empty-keys))
+  (let [request {:organization (:organization form)
+                 :title (:title form)
+                 :fields (mapv #(build-request-field % languages) (:fields form))}]
+    (when (valid-request? request languages)
+      request)))
 
 (rf/reg-event-fx
  ::create-form
- (fn [{:keys [db]} [_ request]]
-   (let [form-errors (validate-form (db ::form) (db :languages))]
-     (when (empty? form-errors)
-       (status-modal/common-pending-handler! (text :t.administration/create-form))
-       (post! "/api/forms/create" {:params request
-                                   :handler (partial status-modal/common-success-handler! #(dispatch! (str "#/administration/forms/" (:id %))))
-                                   :error-handler status-modal/common-error-handler!}))
-     {:db (assoc db ::form-errors form-errors)})))
+ (fn [_ [_ request]]
+   (status-modal/common-pending-handler! (text :t.administration/create-form))
+   (post! "/api/forms/create" {:params request
+                               :handler (partial status-modal/common-success-handler! #(dispatch! (str "#/administration/forms/" (:id %))))
+                               :error-handler status-modal/common-error-handler!})
+   {}))
+
 
 ;;;; UI
 
 (def ^:private context
   {:get-form ::form
-   :get-form-errors ::form-errors
    :update-form ::set-form-field})
 
 (defn- form-organization-field []
@@ -247,7 +244,8 @@
         request (build-request form languages)]
     [:button.btn.btn-primary
      {:type :button
-      :on-click #(on-click request)}
+      :on-click #(on-click request)
+      :disabled (nil? request)}
      (text :t.administration/save)]))
 
 (defn- cancel-button []
diff --git a/src/cljs/rems/util.cljs b/src/cljs/rems/util.cljs
index 975923ef2..236d8464f 100644
--- a/src/cljs/rems/util.cljs
+++ b/src/cljs/rems/util.cljs
@@ -19,17 +19,6 @@
   [m ks]
   (reduce getx m ks))
 
-(defn remove-empty-keys
-  "Given a map, recursively remove keys with empty map or nil values.
-
-  E.g., given {:a {:b {:c nil} :d {:e :f}}}, return {:a {:d {:e :f}}}."
-  [m]
-  (into {} (filter (fn [[_ v]] (not ((if (map? v) empty? nil?) v)))
-                   (mapv (fn [[k v]] [k (if (map? v)
-                                          (remove-empty-keys v)
-                                          v)])
-                         m))))
-
 (defn dispatch!
   "Dispatches to the given url.
 


Test patch:
diff --git a/test/clj/rems/poller/test_email.clj b/test/clj/rems/poller/test_email.clj
index 5f097742f..703fc7e1c 100644
--- a/test/clj/rems/poller/test_email.clj
+++ b/test/clj/rems/poller/test_email.clj
@@ -102,57 +102,57 @@
                     :event/actor "assistant"}])]
       (is (= [[]
               [{:to-user "assistant",
-                :subject "A new application has been submitted",
-                :body "Dear assistant,\nUser applicant has submitted a new application 2001/3 for the resource(s) en title 11, en title 21.\nView the application: http://example.com/#/application/7"}
+                :subject "Application submitted (applicant: 2001/3)",
+                :body "Dear assistant,\n\napplicant has submitted an application (2001/3): en title 11, en title 21.\n\nView application: http://example.com/#/application/7"}
                {:to-user "handler",
-                :subject "A new application has been submitted",
-                :body "Dear handler,\nUser applicant has submitted a new application 2001/3 for the resource(s) en title 11, en title 21.\nView the application: http://example.com/#/application/7"}]
+                :subject "Application submitted (applicant: 2001/3)",
+                :body "Dear handler,\n\napplicant has submitted an application (2001/3): en title 11, en title 21.\n\nView application: http://example.com/#/application/7"}]
               [{:to "somebody@example.com",
                 :subject "Invitation to participate in an application",
-                :body "Hello,\nThis email address (somebody@example.com) has been invited to participate in an application.\nParticipate with this link: http://example.com/accept-invitation?token=abc"}]
+                :body "Hello,\n\nThis email address (somebody@example.com) has been invited to participate in an application.\n\nParticipate: http://example.com/accept-invitation?token=abc"}]
               [{:to-user "commenter1",
-                :subject "Comment request",
-                :body "Dear commenter1,\nUser handler has requested your comment on application 2001/3.\nComment here: http://example.com/#/application/7"}
+                :subject "Comment requested (handler: 2001/3)",
+                :body "Dear commenter1,\n\nhandler has requested a comment on application 2001/3.\n\nComment: http://example.com/#/application/7"}
                {:to-user "commenter2",
-                :subject "Comment request",
-                :body "Dear commenter2,\nUser handler has requested your comment on application 2001/3.\nComment here: http://example.com/#/application/7"}]
+                :subject "Comment requested (handler: 2001/3)",
+                :body "Dear commenter2,\n\nhandler has requested a comment on application 2001/3.\n\nComment: http://example.com/#/application/7"}]
               []
               [{:to-user "assistant",
-                :subject "New comment notification",
-                :body "Dear assistant,\nUser commenter2 has posted a comment on application 2001/3.\nView the application: http://example.com/#/application/7"}
+                :subject "Comment added (commenter2: 2001/3)",
+                :body "Dear assistant,\n\ncommenter2 has commented application 2001/3.\n\nView application: http://example.com/#/application/7"}
                {:to-user "handler",
-                :subject "New comment notification",
-                :body "Dear handler,\nUser commenter2 has posted a comment on application 2001/3.\nView the application: http://example.com/#/application/7"}]
+                :subject "Comment added (commenter2: 2001/3)",
+                :body "Dear handler,\n\ncommenter2 has commented application 2001/3.\n\nView application: http://example.com/#/application/7"}]
               [{:to-user "member",
-                :subject "You've been added as a member to an application",
-                :body "Dear member,\nYou've been added as a member to application 2001/3.\nView the application: http://example.com/#/application/7"}]
+                :subject "Added as a member of an application (2001/3)",
+                :body "Dear member,\n\nYou've been added as a member of application 2001/3.\n\nView application: http://example.com/#/application/7"}]
               [{:to-user "decider",
-                :subject "Decision request",
-                :body "Dear decider,\nUser assistant has requested your decision on application 2001/3.\nView the application: http://example.com/#/application/7"}]
+                :subject "Decision requested (assistant: 2001/3)",
+                :body "Dear decider,\n\nassistant has requested your decision on application 2001/3.\n\nView application: http://example.com/#/application/7"}]
               [{:to-user "assistant",
-                :subject "New decision notification",
-                :body "Dear assistant,\nUser decider has sent a decision on application 2001/3.\nView the application: http://example.com/#/application/7"}
+                :subject "Decision made (decider: 2001/3)",
+                :body "Dear assistant,\n\ndecider has made a decision on application 2001/3.\n\nView application: http://example.com/#/application/7"}
                {:to-user "handler",
-                :subject "New decision notification",
-                :body "Dear handler,\nUser decider has sent a decision on application 2001/3.\nView the application: http://example.com/#/application/7"}]
+                :subject "Decision made (decider: 2001/3)",
+                :body "Dear handler,\n\ndecider has made a decision on application 2001/3.\n\nView application: http://example.com/#/application/7"}]
               [{:to-user "applicant",
-                :subject "Your application has been approved",
-                :body "Dear applicant,\nYour application 2001/3 has been approved.\nView your application: http://example.com/#/application/7"}
+                :subject "Application approved (2001/3)",
+                :body "Dear applicant,\n\nYour application 2001/3 has been approved.\n\nView application: http://example.com/#/application/7"}
                {:to-user "member",
-                :subject "Your application has been approved",
-                :body "Dear member,\nYour application 2001/3 has been approved.\nView your application: http://example.com/#/application/7"}
+                :subject "Application approved (2001/3)",
+                :body "Dear member,\n\nYour application 2001/3 has been approved.\n\nView application: http://example.com/#/application/7"}
                {:to-user "somebody",
-                :subject "Your application has been approved",
-                :body "Dear somebody,\nYour application 2001/3 has been approved.\nView your application: http://example.com/#/application/7"}]
+                :subject "Application approved (2001/3)",
+                :body "Dear somebody,\n\nYour application 2001/3 has been approved.\n\nView application: http://example.com/#/application/7"}]
               [{:to-user "applicant",
-                :subject "Your application has been closed",
-                :body "Dear applicant,\nYour application 2001/3 has been closed.\nView your application: http://example.com/#/application/7"}
+                :subject "Application closed (2001/3)",
+                :body "Dear applicant,\n\nYour application 2001/3 has been closed.\n\nView application: http://example.com/#/application/7"}
                {:to-user "member",
-                :subject "Your application has been closed",
-                :body "Dear member,\nYour application 2001/3 has been closed.\nView your application: http://example.com/#/application/7"}
+                :subject "Application closed (2001/3)",
+                :body "Dear member,\n\nYour application 2001/3 has been closed.\n\nView application: http://example.com/#/application/7"}
                {:to-user "somebody",
-                :subject "Your application has been closed",
-                :body "Dear somebody,\nYour application 2001/3 has been closed.\nView your application: http://example.com/#/application/7"}]]
+                :subject "Application closed (2001/3)",
+                :body "Dear somebody,\n\nYour application 2001/3 has been closed.\n\nView application: http://example.com/#/application/7"}]]
              (events-to-emails events))))
     (let [events (conj base-events
                        {:application/id 7
@@ -160,22 +160,22 @@
                         :event/actor "handler"})]
       (is (= [[]
               [{:to-user "assistant",
-                :subject "A new application has been submitted",
-                :body "Dear assistant,\nUser applicant has submitted a new application 2001/3 for the resource(s) en title 11, en title 21.\nView the application: http://example.com/#/application/7"}
+                :subject "Application submitted (applicant: 2001/3)",
+                :body "Dear assistant,\n\napplicant has submitted an application (2001/3): en title 11, en title 21.\n\nView application: http://example.com/#/application/7"}
                {:to-user "handler",
-                :subject "A new application has been submitted",
-                :body "Dear handler,\nUser applicant has submitted a new application 2001/3 for the resource(s) en title 11, en title 21.\nView the application: http://example.com/#/application/7"}]
-              [{:subject "Your application has been rejected",
-                :body "Dear applicant,\nYour application 2001/3 has been rejected.\nView your application: http://example.com/#/application/7",
+                :subject "Application submitted (applicant: 2001/3)",
+                :body "Dear handler,\n\napplicant has submitted an application (2001/3): en title 11, en title 21.\n\nView application: http://example.com/#/application/7"}]
+              [{:subject "Application rejected (2001/3)",
+                :body "Dear applicant,\n\nYour application 2001/3 has been rejected.\n\nView application: http://example.com/#/application/7",
                 :to-user "applicant"}]]
              (events-to-emails events))))
     (testing "id field can be overrided"
       (with-redefs [rems.config/env (assoc rems.config/env :application-id-column :id)]
         (is (= [[]
                 [{:to-user "assistant"
-                  :subject "A new application has been submitted"
-                  :body "Dear assistant,\nUser applicant has submitted a new application 7 for the resource(s) en title 11, en title 21.\nView the application: http://example.com/#/application/7"}
+                  :subject "Application submitted (applicant: 7)"
+                  :body "Dear assistant,\n\napplicant has submitted an application (7): en title 11, en title 21.\n\nView application: http://example.com/#/application/7"}
                  {:to-user "handler"
-                  :subject "A new application has been submitted"
-                  :body "Dear handler,\nUser applicant has submitted a new application 7 for the resource(s) en title 11, en title 21.\nView the application: http://example.com/#/application/7"}]]
+                  :subject "Application submitted (applicant: 7)"
+                  :body "Dear handler,\n\napplicant has submitted an application (7): en title 11, en title 21.\n\nView application: http://example.com/#/application/7"}]]
                (events-to-emails base-events)))))))
diff --git a/test/cljs/rems/administration/test_create_form.cljs b/test/cljs/rems/administration/test_create_form.cljs
index eb57bea49..f77747ded 100644
--- a/test/cljs/rems/administration/test_create_form.cljs
+++ b/test/cljs/rems/administration/test_create_form.cljs
@@ -1,7 +1,7 @@
 (ns rems.administration.test-create-form
   (:require [cljs.test :refer-macros [deftest is testing use-fixtures]]
             [re-frame.core :as rf]
-            [rems.administration.create-form :as f :refer [build-request build-localized-string validate-form]]
+            [rems.administration.create-form :as f :refer [build-request build-localized-string]]
             [rems.testing :refer [isolate-re-frame-state stub-re-frame-effect]]
             [rems.util :refer [getx-in]]))
 
@@ -160,46 +160,50 @@
   (let [form {:organization "abc"
               :title "the title"
               :fields [{:title {:en "en title"
-                                :fi "fi title"}
+                               :fi "fi title"}
                        :optional true
                        :type "text"
                        :maxlength "12"
                        :input-prompt {:en "en prompt"
                                       :fi "fi prompt"}}]}
         languages [:en :fi]]
-
-    (testing "basic form"
+    (testing "valid form"
       (is (= {:organization "abc"
               :title "the title"
               :fields [{:title {:en "en title"
-                                :fi "fi title"}
-                        :optional true
-                        :type "text"
-                        :maxlength 12
-                        :input-prompt {:en "en prompt"
-                                       :fi "fi prompt"}}]}
+                               :fi "fi title"}
+                       :optional true
+                       :type "text"
+                       :maxlength 12
+                       :input-prompt {:en "en prompt"
+                                      :fi "fi prompt"}}]}
              (build-request form languages))))
 
-    (testing "zero fields"
+    (testing "missing organization"
+      (is (nil? (build-request (assoc-in form [:organization] "") languages))))
+
+    (testing "missing title"
+      (is (nil? (build-request (assoc-in form [:title] "") languages))))
+
+    (testing "zero fields is ok"
       (is (= {:organization "abc"
               :title "the title"
               :fields []}
              (build-request (assoc-in form [:fields] []) languages))))
 
-    (testing "date fields"
-      (let [form (assoc-in form [:fields 0 :type] "date")]
-        (is (= {:organization "abc"
-                :title "the title"
-                :fields [{:title {:en "en title"
-                                  :fi "fi title"}
-                          :optional true
-                          :type "date"}]}
-               (build-request form languages)))))
+    (testing "missing field title"
+      (is (= nil
+             (build-request (assoc-in form [:fields 0 :title :en] "") languages)
+             (build-request (update-in form [:fields 0 :title] dissoc :en) languages)
+             (build-request (assoc-in form [:fields 0 :title] nil) languages))))
 
     (testing "missing optional implies false"
       (is (false? (getx-in (build-request (assoc-in form [:fields 0 :optional] nil) languages)
                            [:fields 0 :optional]))))
 
+    (testing "missing field type"
+      (is (nil? (build-request (assoc-in form [:fields 0 :type] nil) languages))))
+
     (testing "input prompt is optional"
       (is (= {:en "" :fi ""}
              (getx-in (build-request (assoc-in form [:fields 0 :input-prompt] nil) languages)
@@ -215,155 +219,92 @@
       (is (nil? (getx-in (build-request (assoc-in form [:fields 0 :maxlength] nil) languages)
                          [:fields 0 :maxlength]))))
 
-    (testing "option fields"
-      (let [form (-> form
-                     (assoc-in [:fields 0 :type] "option")
-                     (assoc-in [:fields 0 :options] [{:key "yes"
-                                                      :label {:en "en yes"
-                                                              :fi "fi yes"}}
-                                                     {:key "no"
-                                                      :label {:en "en no"
-                                                              :fi "fi no"}}]))]
-        (is (= {:organization "abc"
-                :title "the title"
-                :fields [{:title {:en "en title"
-                                  :fi "fi title"}
-                          :optional true
-                          :type "option"
-                          :options [{:key "yes"
-                                     :label {:en "en yes"
-                                             :fi "fi yes"}}
-                                    {:key "no"
-                                     :label {:en "en no"
-                                             :fi "fi no"}}]}]}
-               (build-request form languages)))))
-
-    (testing "multiselect fields"
-      (let [form (-> form
-                     (assoc-in [:fields 0 :type] "multiselect")
-                     (assoc-in [:fields 0 :options] [{:key "egg"
-                                                      :label {:en "Egg"
-                                                              :fi "Munaa"}}
-                                                     {:key "bacon"
-                                                      :label {:en "Bacon"
-                                                              :fi "Pekonia"}}]))]
-        (is (= {:organization "abc"
-                :title "the title"
-                :fields [{:title {:en "en title"
-                                  :fi "fi title"}
-                          :optional true
-                          :type "multiselect"
-                          :options [{:key "egg"
-                                     :label {:en "Egg"
-                                             :fi "Munaa"}}
-                                    {:key "bacon"
-                                     :label {:en "Bacon"
-                                             :fi "Pekonia"}}]}]}
-               (build-request form languages)))))))
-
-(deftest validate-form-test
-  (let [form {:organization "abc"
-              :title "the title"
-              :fields [{:title {:en "en title"
-                                :fi "fi title"}
-                       :optional true
-                       :type "text"
-                       :maxlength "12"
-                       :input-prompt {:en "en prompt"
-                                      :fi "fi prompt"}}]}
-        languages [:en :fi]]
-
-    (testing "valid form"
-      (is (empty? (validate-form form languages))))
-
-    (testing "missing organization"
-      (is (= (:organization (validate-form (assoc-in form [:organization] "") languages))
-             :t.form.validation/required)))
-
-    (testing "missing title"
-      (is (= (:title (validate-form (assoc-in form [:title] "") languages))
-             :t.form.validation/required)))
-
-    (testing "zero fields is ok"
-      (is (empty? (validate-form (assoc-in form [:fields] []) languages))))
-
-    (testing "missing field title"
-      (let [nil-title (validate-form (assoc-in form [:fields 0 :title] nil) languages)]
-        (is (= (get-in (validate-form (assoc-in form [:fields 0 :title :en] "") languages)
-                       [:fields 0 :title :en])
-               (get-in (validate-form (update-in form [:fields 0 :title] dissoc :en) languages)
-                       [:fields 0 :title :en])
-               (get-in nil-title [:fields 0 :title :en])
-               (get-in nil-title [:fields 0 :title :fi])
-               :t.form.validation/required))))
+    (testing "if you use input prompt, you must fill in all the languages"
+      (is (= nil
+             (build-request (assoc-in form [:fields 0 :input-prompt] {:en "en prompt" :fi ""}) languages)
+             (build-request (assoc-in form [:fields 0 :input-prompt] {:en "en prompt"}) languages))))
 
-    (testing "missing field type"
-      (is (get-in (validate-form (assoc-in form [:fields 0 :type] nil) language)
-                  [:fields 0 :type])
-          :t.form.validation/required))
+    (testing "date fields"
+      (let [form (assoc-in form [:fields 0 :type] "date")]
 
-    (testing "if you use input prompt, you must fill in all the languages"
-      (is (= (get-in (validate-form (assoc-in form [:fields 0 :input-prompt] {:en "en prompt" :fi ""}) languages)
-                     [:fields 0 :input-prompt :fi])
-             (get-in (validate-form (assoc-in form [:fields 0 :input-prompt] {:en "en prompt"}) languages)
-                     [:fields 0 :input-prompt :fi])
-             :t.form.validation/required)))
+        (testing "valid form"
+          (is (= {:organization "abc"
+                  :title "the title"
+                  :fields [{:title {:en "en title"
+                                   :fi "fi title"}
+                           :optional true
+                           :type "date"}]}
+                 (build-request form languages))))))
 
     (testing "option fields"
       (let [form (-> form
                      (assoc-in [:fields 0 :type] "option")
                      (assoc-in [:fields 0 :options] [{:key "yes"
-                                                      :label {:en "en yes"
-                                                              :fi "fi yes"}}
-                                                     {:key "no"
-                                                      :label {:en "en no"
-                                                              :fi "fi no"}}]))]
+                                                     :label {:en "en yes"
+                                                             :fi "fi yes"}}
+                                                    {:key "no"
+                                                     :label {:en "en no"
+                                                             :fi "fi no"}}]))]
+
         (testing "valid form"
-          (is (empty? (validate-form form languages))))
+          (is (= {:organization "abc"
+                  :title "the title"
+                  :fields [{:title {:en "en title"
+                                   :fi "fi title"}
+                           :optional true
+                           :type "option"
+                           :options [{:key "yes"
+                                      :label {:en "en yes"
+                                              :fi "fi yes"}}
+                                     {:key "no"
+                                      :label {:en "en no"
+                                              :fi "fi no"}}]}]}
+                 (build-request form languages))))
 
         (testing "missing option key"
-          (is (= (get-in (validate-form (assoc-in form [:fields 0 :options 0 :key] "") languages)
-                         [:fields 0 :options 0 :key])
-                 (get-in (validate-form (assoc-in form [:fields 0 :options 0 :key] nil) languages)
-                         [:fields 0 :options 0 :key])
-                 :t.form.validation/required)))
+          (is (= nil
+                 (build-request (assoc-in form [:fields 0 :options 0 :key] "") languages)
+                 (build-request (assoc-in form [:fields 0 :options 0 :key] nil) languages))))
 
         (testing "missing option label"
-          (let [empty-label (validate-form (assoc-in form [:fields 0 :options 0 :label] {:en "" :fi ""}) languages)
-                nil-label (validate-form (assoc-in form [:fields 0 :options 0 :label] nil) languages)]
-            (is (= (get-in empty-label [:fields 0 :options 0 :label :en])
-                   (get-in empty-label [:fields 0 :options 0 :label :fi])
-                   (get-in nil-label [:fields 0 :options 0 :label :en])
-                   (get-in nil-label [:fields 0 :options 0 :label :fi])
-                   :t.form.validation/required))))))
+          (is (= nil
+                 (build-request (assoc-in form [:fields 0 :options 0 :label] {:en "" :fi ""}) languages)
+                 (build-request (assoc-in form [:fields 0 :options 0 :label] nil) languages))))))
 
     (testing "multiselect fields"
       (let [form (-> form
                      (assoc-in [:fields 0 :type] "multiselect")
                      (assoc-in [:fields 0 :options] [{:key "egg"
-                                                      :label {:en "Egg"
-                                                              :fi "Munaa"}}
-                                                     {:key "bacon"
-                                                      :label {:en "Bacon"
-                                                              :fi "Pekonia"}}]))]
+                                                     :label {:en "Egg"
+                                                             :fi "Munaa"}}
+                                                    {:key "bacon"
+                                                     :label {:en "Bacon"
+                                                             :fi "Pekonia"}}]))]
+
         (testing "valid form"
-          (is (empty? (validate-form form languages))))
+          (is (= {:organization "abc"
+                  :title "the title"
+                  :fields [{:title {:en "en title"
+                                   :fi "fi title"}
+                           :optional true
+                           :type "multiselect"
+                           :options [{:key "egg"
+                                      :label {:en "Egg"
+                                              :fi "Munaa"}}
+                                     {:key "bacon"
+                                      :label {:en "Bacon"
+                                              :fi "Pekonia"}}]}]}
+                 (build-request form languages))))
 
         (testing "missing option key"
-          (is (= (get-in (validate-form (assoc-in form [:fields 0 :options 0 :key] "") languages)
-                         [:fields 0 :options 0 :key])
-                 (get-in (validate-form (assoc-in form [:fields 0 :options 0 :key] nil) languages)
-                         [:fields 0 :options 0 :key])
-                 :t.form.validation/required)))
+          (is (= nil
+                 (build-request (assoc-in form [:fields 0 :options 0 :key] "") languages)
+                 (build-request (assoc-in form [:fields 0 :options 0 :key] nil) languages))))
 
         (testing "missing option label"
-          (let [empty-label (validate-form (assoc-in form [:fields 0 :options 0 :label] {:en "" :fi ""}) languages)
-                nil-label (validate-form (assoc-in form [:fields 0 :options 0 :label] nil) languages)]
-            (is (= (get-in empty-label [:fields 0 :options 0 :label :en])
-                   (get-in empty-label [:fields 0 :options 0 :label :fi])
-                   (get-in nil-label [:fields 0 :options 0 :label :en])
-                   (get-in nil-label [:fields 0 :options 0 :label :fi])
-                   :t.form.validation/required))))))))
+          (is (= nil
+                 (build-request (assoc-in form [:fields 0 :options 0 :label] {:en "" :fi ""}) languages)
+                 (build-request (assoc-in form [:fields 0 :options 0 :label] nil) languages))))))))
 
 (deftest build-localized-string-test
   (let [languages [:en :fi]]
diff --git a/test/cljs/rems/test_util.cljs b/test/cljs/rems/test_util.cljs
index 7ffeb59e3..b0bbee44f 100644
--- a/test/cljs/rems/test_util.cljs
+++ b/test/cljs/rems/test_util.cljs
@@ -3,7 +3,7 @@
             [cljs-time.core :as time]
             [cljs-time.format :as format]
             [rems.text :refer [time-format localize-time]]
-            [rems.util :refer [decode-option-keys encode-option-keys normalize-option-key linkify parse-int remove-empty-keys]]))
+            [rems.util :refer [decode-option-keys encode-option-keys normalize-option-key linkify parse-int]]))
 
 (def test-time #inst "1980-01-02T13:45:00.000Z")
 
@@ -49,9 +49,3 @@
   (testing "change link strings to hiccup links"
     (is (= (linkify "a http://www.abc.com c")
         ["a" " " [:a {:href "http://www.abc.com"} "http://www.abc.com"] " " "c"]))))
-
-(deftest test-remove-empty-keys
-  (is (= (remove-empty-keys {}) {}))
-  (is (= (remove-empty-keys {:a :b}) {:a :b}))
-  (is (= (remove-empty-keys {:a nil}) {}))
-  (is (= (remove-empty-keys {:a {:b {:c nil} :d {:e :f}}}) {:a {:d {:e :f}}})))