Skip to content
Open
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ have notable changes.

Changes since v2.39.1

### Additions
- Added applicant UI for catalogue item hierarchy (see previous release note), and a configuration option to switch the feature on. (#3460, #3473). See (manual/owner.md)[manual/owner.md] and

### Fixes
- Fixed an issue where the application page would get stuck on a loop, displaying the "loading" indicator, when there was an error while creating the application (#3460).

Expand Down
1 change: 1 addition & 0 deletions dev-config.edn
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
:enable-doi true
:enable-duo true
:enable-catalogue-tree true
:enable-catalogue-hierarchy true
:enable-save-compaction true
:enable-autosave true
:enable-extended-logging true
Expand Down
4 changes: 3 additions & 1 deletion manual/owner.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ This will **disable and archive the old catalogue item** and create a new catalo

Catalogue items can be grouped under **Categories** that display as folding sub-headers on the catalogue page, also referred to as the **catalogue tree**. Categories can have subcategories and they can be nested in a tree-like structure.

Additionally, catalogue items may specify **complementary items** that must be applied for together with the **top-level catalogue item**. A top-level catalogue item may have many complementary items, and there can only be one top-level item for a given complementary item. A catalogue item that is already referenced by a top-level item may not have complementary items of it's own.
Additionally, catalogue items may specify **complementary items** that must be applied for together with the **top-level catalogue item**. To use this feature, configure `:enable-catalogue-hierarchy` to `true`.

A top-level catalogue item may have many complementary items, and there can only be one top-level item for a given complementary item. A catalogue item that is already referenced by a top-level item may not have complementary items of it's own. Once the applicant has been given entitlement to the resource of a top-level item, it's complementary items become applicable on their own as ordinary catalogue items.

## Disabling, and archiving items

Expand Down
3 changes: 3 additions & 0 deletions resources/config-defaults.edn
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,9 @@
:enable-save-compaction false ; whether to merge consecutive saves into one event (EXPERIMENTAL)
:enable-autosave false ; whether to automatically save the application as the applicant types (EXPERIMENTAL)

;; Enable catalogue hierarchy. See `manual/owner.md` and `docs/architecture/023-catalogue-item-hierarchy.md`
:enable-catalogue-hierarchy false

;; Extended logging for routes that should additionally log content of entity mutation.
;; logging is at INFO level and the log message is prefixed with "extended-logging (:uri request)"
:enable-extended-logging false
Expand Down
1 change: 1 addition & 0 deletions src/clj/rems/api/public.clj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
(s/optional-key :enable-catalogue-tree) s/Bool
(s/optional-key :catalogue-tree-show-matching-parents) s/Bool
(s/optional-key :enable-cart) s/Bool
(s/optional-key :enable-catalogue-hierarchy) s/Bool
(s/optional-key :application-list-hidden-columns) [s/Keyword]
(s/optional-key :enable-autosave) s/Bool
(s/optional-key :show-resources-section) s/Bool
Expand Down
48 changes: 25 additions & 23 deletions src/clj/rems/application/commands.clj
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,21 @@

(defn- invalid-catalogue-item-hierarchy-error
"For such an item in `catalogue-item-ids` that has a parent (dependent), check that the parent item is also included in `catalogue-item-ids`, or `actor` has an entitlement to the parent item's resource."
[catalogue-item-ids actor {:keys [get-catalogue-item get-dependents get-entitlements]}]
(let [entitled-to-resids (into #{} (map :resourceid) (get-entitlements actor))
missing (into []
(comp (mapcat (fn [id] (get-dependents {:catalogue-item/id id})))
(map :catalogue-item/id)
(remove (set catalogue-item-ids))
(map get-catalogue-item)
(filter (complement (comp entitled-to-resids :resource-id)))
(map :id))
catalogue-item-ids)]
(when (seq missing)
{:errors [{:type :t.applications.errors/missing-top-level-item
:catalogue-item-ids missing}]})))
[catalogue-item-ids actor {:keys [get-catalogue-item get-dependents get-entitlements get-config]}]
(when (:enable-catalogue-hierarchy (get-config))
(let [entitled-to-resids (into #{} (map :resourceid) (get-entitlements actor))
missing (into []
(comp (mapcat (fn [id] (get-dependents {:catalogue-item/id id})))
(map :catalogue-item/id)
(remove (set catalogue-item-ids))
(map get-catalogue-item)
(filter (complement (comp entitled-to-resids :resource-id)))
(map :id)
(distinct))
catalogue-item-ids)]
(when (seq missing)
{:errors [{:type :t.applications.errors/missing-top-level-item
:catalogue-item-ids missing}]}))))

(defn- licenses-not-accepted-error [application userid]
(when-not (application-util/accepted-licenses? application userid)
Expand Down Expand Up @@ -449,21 +451,21 @@
:application/licenses (mapv (fn [id] {:license/id id}) (:licenses cmd))})))

(defmethod command-handler :application.command/change-resources
[cmd application {:keys [get-catalogue-item get-workflow] :as injections}]
(let [cat-ids (:catalogue-item-ids cmd)
workflow (when (seq cat-ids)
(get-workflow (-> (first cat-ids)
[{:keys [catalogue-item-ids actor] :as cmd} application {:keys [get-catalogue-item get-workflow] :as injections}]
(let [workflow (when (seq catalogue-item-ids)
(get-workflow (-> (first catalogue-item-ids)
get-catalogue-item
:wfid)))]
(or (must-not-be-empty cmd :catalogue-item-ids)
(invalid-catalogue-items cat-ids injections)
(unbundlable-catalogue-items-for-actor application cat-ids (:actor cmd) injections)
(changes-original-workflow application cat-ids (:actor cmd) injections)
(invalid-catalogue-items catalogue-item-ids injections)
(unbundlable-catalogue-items-for-actor application catalogue-item-ids actor injections)
(changes-original-workflow application catalogue-item-ids actor injections)
(invalid-catalogue-item-hierarchy-error catalogue-item-ids actor injections)
(add-comment-and-attachments cmd application injections
{:event/type :application.event/resources-changed
:application/forms (build-forms-list workflow cat-ids injections)
:application/resources (build-resources-list cat-ids injections)
:application/licenses (build-licenses-list cat-ids injections)}))))
:application/forms (build-forms-list workflow catalogue-item-ids injections)
:application/resources (build-resources-list catalogue-item-ids injections)
:application/licenses (build-licenses-list catalogue-item-ids injections)}))))

(defmethod command-handler :application.command/add-member
[cmd application injections]
Expand Down
1 change: 1 addition & 0 deletions src/clj/rems/service/public.clj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
:enable-catalogue-tree
:catalogue-tree-show-matching-parents
:enable-cart
:enable-catalogue-hierarchy
:application-list-hidden-columns
:enable-autosave
:show-resources-section
Expand Down
74 changes: 55 additions & 19 deletions src/cljs/rems/actions/change_resources.cljs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
(ns rems.actions.change-resources
(:require [re-frame.core :as rf]
[rems.actions.components :refer [action-button action-form-view comment-field collapse-action-form perform-action-button]]
[rems.globals]
(:require [clojure.set :as set]
[medley.core :refer [distinct-by]]
[re-frame.core :as rf]
[rems.actions.components :refer [action-button action-form-view
collapse-action-form
comment-field
perform-action-button]]
[rems.dropdown :as dropdown]
[rems.flash-message :as flash-message]
[medley.core :refer [distinct-by]]
[rems.globals]
[rems.spinner :as spinner]
[rems.text :refer [text get-localized-title]]
[rems.text :refer [get-localized-title text]]
[rems.util :refer [post!]]))

(def ^:private action-form-id "change-resources")
Expand All @@ -18,10 +22,14 @@
(merge
{:db (assoc db
::initial-resources (into #{} (map :catalogue-item/id initial-resources))
::selected-resources (into #{} (map :catalogue-item/id initial-resources)))
:dispatch-n (concat [[:rems.actions.components/set-comment action-form-id ""]]
(when-not (:rems.catalogue/catalogue db)
[[:rems.catalogue/full-catalogue]]))})))
::selected-resources (into #{} (map :catalogue-item/id initial-resources))
::error nil)
:dispatch-n (cond-> [[:rems.actions.components/set-comment action-form-id ""]]
(not (:rems.catalogue/catalogue db))
(conj [:rems.catalogue/full-catalogue])

(:enable-catalogue-hierarchy @rems.globals/config)
(conj [:rems.catalogue/entitlements->catalogue-item-ids]))})))

(rf/reg-sub
::catalogue
Expand All @@ -44,6 +52,9 @@
(rf/reg-sub ::selected-resources (fn [db _] (::selected-resources db)))
(rf/reg-event-db ::set-selected-resources (fn [db [_ resources]] (assoc db ::selected-resources (set (map :id resources)))))

(rf/reg-sub ::command-error (fn [db _] some? (::command-error db)))
(rf/reg-event-db ::set-command-error (fn [db [_ error]] (assoc db ::command-error error)))

(def ^:private dropdown-id "change-resources-dropdown")

;; The API allows us to add attachments to this command
Expand All @@ -57,12 +68,25 @@
:catalogue-item-ids (vec resources)}
(when comment
{:comment comment}))
:handler (flash-message/default-success-handler
:change-resources
description
(fn [_]
(collapse-action-form action-form-id)
(on-finished)))
:handler (fn [{:keys [success errors] :as response}]
(cond success
(do
((flash-message/default-success-handler
:change-resources
description
(fn [_]
(collapse-action-form action-form-id)
(on-finished)))
response)
(rf/dispatch [::set-command-error nil]))
errors
(do
(flash-message/show-error!
:change-resources
(->> errors
(mapv (flash-message/argumentize-some-key :catalogue-item-id :catalogue-item-ids))
flash-message/format-errors))
(rf/dispatch [::set-command-error errors]))))
:error-handler (flash-message/default-error-handler :change-resources description)}))
{}))

Expand All @@ -74,21 +98,31 @@
(defn compatible-item? [item original-workflow-id]
(= original-workflow-id (:wfid item)))

(defn compatible-hierarchy? [catalogue-item selected-resources entitlements]
(if-let [top-level-id (-> catalogue-item :part-of :catalogue-item/id)]
(contains? (set/union entitlements selected-resources) top-level-id)
true))

(defn change-resources-view
[{:keys [application initial-resources selected-resources catalogue can-comment? on-set-resources on-send]}]
[{:keys [application initial-resources selected-resources catalogue entitlements can-comment? on-set-resources on-send]}]
(let [original-workflow-id (get-in application [:application/workflow :workflow/id])
compatible-first-sort-fn #(if (compatible-item? % original-workflow-id) -1 1)
sorted-selected-catalogue (->> catalogue
(sort-by #(get-localized-title %))
(sort-by compatible-first-sort-fn))
enable-cart? (:enable-cart @rems.globals/config)]
enable-cart? (:enable-cart @rems.globals/config)
enable-hierarchy? (:enable-catalogue-hierarchy @rems.globals/config)
item-disabled? #(or (not (compatible-item? % original-workflow-id))
(and enable-hierarchy?
(not (compatible-hierarchy? % selected-resources entitlements))))]
[action-form-view action-form-id
(text :t.actions/change-resources)
[[perform-action-button {:id "change-resources"
:text (text :t.actions/change-resources)
:class "btn-primary"
:disabled (or (empty? selected-resources)
(= selected-resources initial-resources))
(and (not @(rf/subscribe [::command-error]))
(= selected-resources initial-resources)))
:on-click on-send}]]
(if (empty? catalogue)
[spinner/big]
Expand All @@ -106,7 +140,7 @@
{:id dropdown-id
:items (->> sorted-selected-catalogue
(mapv #(assoc % ::label (get-localized-title %))))
:item-disabled? #(not (compatible-item? % original-workflow-id))
:item-disabled? item-disabled?
:item-key :id
:item-label ::label
:item-selected? #(contains? (set selected-resources) (% :id))
Expand All @@ -121,11 +155,13 @@
(let [initial-resources @(rf/subscribe [::initial-resources])
selected-resources @(rf/subscribe [::selected-resources])
catalogue @(rf/subscribe [::catalogue])
entitlements @(rf/subscribe [:rems.catalogue/entitlements->catalogue-item-ids])
comment @(rf/subscribe [:rems.actions.components/comment action-form-id])]
[change-resources-view {:application application
:initial-resources initial-resources
:selected-resources selected-resources
:catalogue catalogue
:entitlements entitlements
:can-comment? can-comment?
:on-set-resources #(rf/dispatch [::set-selected-resources %])
:on-send #(rf/dispatch [::send-change-resources {:application-id (:application/id application)
Expand Down
31 changes: 16 additions & 15 deletions src/cljs/rems/administration/catalogue_item.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
[rems.administration.status-flags :as status-flags]
[rems.atoms :as atoms :refer [document-title readonly-checkbox]]
[rems.collapsible :as collapsible]
[rems.flash-message :as flash-message]
[rems.common.roles :as roles]
[rems.flash-message :as flash-message]
[rems.globals]
[rems.spinner :as spinner]
[rems.text :refer [get-localized-title localize-time localized text]]
[rems.util :refer [fetch]]))
Expand Down Expand Up @@ -95,22 +96,22 @@
^{:key (:category/id cat)}
[atoms/link nil
(str "/administration/categories/" (:category/id cat))
(localized (:category/title cat))])))]
[inline-info-field (text :t.administration/catalogue-item-hierarchy-parent)
(let [{id :catalogue-item/id} (:part-of catalogue-item)]
(when id
(localized (:category/title cat))])))]]
(when (:enable-catalogue-hierarchy @rems.globals/config)
[[inline-info-field (text :t.administration/catalogue-item-hierarchy-parent)
(when-let [id (:catalogue-item/id (:part-of catalogue-item))]
[atoms/link {:href (str "/administration/catalogue-items/" id)
:label id
:target :_blank}]))]
[inline-info-field (text :t.administration/catalogue-item-hierarchy-children)
(when-let [children (:children catalogue-item)]
(doall
(for [{id :catalogue-item/id} children]
^{:key id}
[atoms/link {:href (str "/administration/catalogue-items/" id)
:label id
:target :_blank}])))]
[inline-info-field (text :t.administration/start) (localize-time (:start catalogue-item))]
:target :_blank}])]
[inline-info-field (text :t.administration/catalogue-item-hierarchy-children)
(when-let [children (:children catalogue-item)]
(doall
(for [{id :catalogue-item/id} children]
^{:key id}
[atoms/link {:href (str "/administration/catalogue-items/" id)
:label id
:target :_blank}])))]])
[[inline-info-field (text :t.administration/start) (localize-time (:start catalogue-item))]
[inline-info-field (text :t.administration/end) (localize-time (:end catalogue-item))]
[inline-info-field (text :t.administration/active) [readonly-checkbox {:value (status-flags/active? catalogue-item)}]]]))}]
(let [id (:id catalogue-item)]
Expand Down
7 changes: 4 additions & 3 deletions src/cljs/rems/administration/create_catalogue_item.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,10 @@
[catalogue-item-resource-field]
[catalogue-item-form-field]
[catalogue-item-categories-field]
(if parent-item
[catalogue-item-parent-field parent-item]
[catalogue-item-children-field])
(when (:enable-catalogue-hierarchy @rems.globals/config)
(if parent-item
[catalogue-item-parent-field parent-item]
[catalogue-item-children-field]))

[:div.col.commands
[cancel-button catalogue-item-id]
Expand Down
Loading