Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
27 changes: 14 additions & 13 deletions src/clj/rems/application/commands.clj
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,20 @@

(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))
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
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
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
79 changes: 42 additions & 37 deletions src/cljs/rems/cart.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[re-frame.core :as rf]
[rems.atoms :as atoms]
[rems.common.catalogue-util :refer [catalogue-items->ids]]
[rems.globals]
[rems.guide-util :refer [component-info example]]
[rems.text :refer [get-localized-title text text-format]]
[rems.util :refer [navigate!]]))
Expand All @@ -28,49 +29,53 @@
(update db ::cart #(remove (comp #{item-id} :id) %))))

(defn disable-remove-from-cart-button? [item cart-item-ids entitlement-catids]
(when-let [children-ids (seq (map :catalogue-item/id (:children item)))]
(and (not (contains? (set entitlement-catids) (:id item)))
(some (set cart-item-ids) children-ids))))
(when (:enable-catalogue-hierarchy @rems.globals/config)
(when-let [children-ids (seq (map :catalogue-item/id (:children item)))]
(and (not (contains? (set entitlement-catids) (:id item)))
(some (set cart-item-ids) children-ids)))))

(deftest test-disable-remove-from-cart-button
(testing "without hierarchy"
(is (nil? (disable-remove-from-cart-button? {:id 1} [1] nil))
"can remove item")
(is (nil? (disable-remove-from-cart-button? {:id 1} [1 2 3] nil))
"can remove item"))
(testing "with item and children in cart"
(is (disable-remove-from-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [2 3] nil)
"cannot remove parent")
(is (nil? (disable-remove-from-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [2 3] nil))
"can remove child"))
(testing "with parent in cart"
(is (nil? (disable-remove-from-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [2] nil))
"can remove item"))
(testing "with item and children in cart, and previous entitlement to item"
(is (false? (disable-remove-from-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [2 3] [2]))
"can remove item")))
(with-redefs [rems.globals/config (atom {:enable-catalogue-hierarchy true})]
(testing "without hierarchy"
(is (nil? (disable-remove-from-cart-button? {:id 1} [1] nil))
"can remove item")
(is (nil? (disable-remove-from-cart-button? {:id 1} [1 2 3] nil))
"can remove item"))
(testing "with item and children in cart"
(is (disable-remove-from-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [2 3] nil)
"cannot remove parent")
(is (nil? (disable-remove-from-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [2 3] nil))
"can remove child"))
(testing "with parent in cart"
(is (nil? (disable-remove-from-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [2] nil))
"can remove item"))
(testing "with item and children in cart, and previous entitlement to item"
(is (false? (disable-remove-from-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [2 3] [2]))
"can remove item"))))

(defn disable-add-to-cart-button? [item cart-item-ids entitlement-catids]
(when-let [parent-id (-> item :part-of :catalogue-item/id)]
(not (contains? (set/union (set cart-item-ids) (set entitlement-catids))
parent-id))))
(when (:enable-catalogue-hierarchy @rems.globals/config)
(when-let [parent-id (-> item :part-of :catalogue-item/id)]
(not (contains? (set/union (set cart-item-ids) (set entitlement-catids))
parent-id)))))

(deftest test-disable-add-to-cart-button
(testing "without hierarchy"
(is (nil? (disable-add-to-cart-button? {:id 1} nil nil))
"can add to cart"))
(testing "with parent, and unrelated item in cart"
(is (nil? (disable-add-to-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [1] nil))
"can add to cart"))
(testing "with child, without parent in cart"
(is (true? (disable-add-to-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [1] nil))
"cannot add to cart"))
(testing "with child, and parent in cart"
(is (false? (disable-add-to-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [2] nil))
"can add to cart"))
(testing "with child, without parent in cart, with entitlement to parent"
(is (false? (disable-add-to-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [1] [2]))
"can add to cart")))
(with-redefs [rems.globals/config (atom {:enable-catalogue-hierarchy true})]
(testing "without hierarchy"
(is (nil? (disable-add-to-cart-button? {:id 1} nil nil))
"can add to cart"))
(testing "with parent, and unrelated item in cart"
(is (nil? (disable-add-to-cart-button? {:id 2 :children [{:catalogue-item/id 3}]} [1] nil))
"can add to cart"))
(testing "with child, without parent in cart"
(is (true? (disable-add-to-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [1] nil))
"cannot add to cart"))
(testing "with child, and parent in cart"
(is (false? (disable-add-to-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [2] nil))
"can add to cart"))
(testing "with child, without parent in cart, with entitlement to parent"
(is (false? (disable-add-to-cart-button? {:id 3 :part-of {:catalogue-item/id 2}} [1] [2]))
"can add to cart"))))

(defn add-to-cart-button
"Hiccup fragment that contains a button that adds the given item to the cart"
Expand Down
4 changes: 2 additions & 2 deletions src/cljs/rems/flash_message.cljs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(ns rems.flash-message
(:require [clojure.string :as string]
(:require [clojure.string :as str]
[clojure.test :refer [deftest is are]]
[clojure.walk]
[re-frame.core :as rf]
Expand Down Expand Up @@ -130,7 +130,7 @@
((apply some-fn id-keys))
vector
flatten
(string/join ", "))])))
(str/join ", "))])))

(deftest test-argumentize-some-key
(are [expected input] (= expected ((argumentize-some-key :a :b) input))
Expand Down
1 change: 1 addition & 0 deletions test-config.edn
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@
:enable-doi true
:enable-duo true
:enable-catalogue-tree true
:enable-catalogue-hierarchy true
:enable-voting true
:enable-processing-states true}
7 changes: 6 additions & 1 deletion test/clj/rems/api/test_applications.clj
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,12 @@
(api-call :post "/api/applications/create" {:catalogue-item-ids [cat-id-child]} api-key user-id)))
(is (match? {:success true
:application-id number?}
(api-call :post "/api/applications/create" {:catalogue-item-ids [cat-id-parent cat-id-child]} api-key user-id)))))
(api-call :post "/api/applications/create" {:catalogue-item-ids [cat-id-parent cat-id-child]} api-key user-id)))
(testing "with hierarchy toggled off"
(with-redefs [rems.config/env (assoc rems.config/env :enable-catalogue-hierarchy false)]
(is (match? {:success true
:application-id number?}
(api-call :post "/api/applications/create" {:catalogue-item-ids [cat-id-child]} api-key user-id)))))))

(testing "no forms"
(let [no-form (test-helpers/create-catalogue-item! {:form-id nil})
Expand Down
Loading