From d1ffde776d6d4477bd8d78b349551cb543a132e7 Mon Sep 17 00:00:00 2001
From: Allan Otodi Opeto <103313919+AllanOXDi@users.noreply.github.com>
Date: Fri, 13 Sep 2024 19:01:55 +0300
Subject: [PATCH] Revert "Fixed field is required' validation"
---
.../QuickEditModal/EditBooleanMapModal.vue | 44 ++++++-------------
.../__tests__/EditBooleanMapModal.spec.js | 27 +++++++++++-
.../contentNodeFields/CategoryOptions.vue | 36 ++++++++++++++-
.../shared/views/form/ExpandableSelect.vue | 9 +++-
4 files changed, 81 insertions(+), 35 deletions(-)
diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue
index 2cbdded597..d949cc576b 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue
+++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue
@@ -1,10 +1,8 @@
-
{{ resourcesSelectedText }}
-
- {{ hasMixedCategoriesMessage }}
-
+
+ {{ error }}
+
@@ -47,7 +45,6 @@
import { mapGetters, mapActions } from 'vuex';
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
import { getInvalidText } from 'shared/utils/validation';
- import commonStrings from 'shared/translator';
export default {
name: 'EditBooleanMapModal',
@@ -97,31 +94,13 @@
};
},
computed: {
- ...mapGetters('contentNode', ['getContentNodes', 'getContentNode']),
+ ...mapGetters('contentNode', ['getContentNodes']),
nodes() {
return this.getContentNodes(this.nodeIds);
},
isTopicSelected() {
return this.nodes.some(node => node.kind === ContentKindsNames.TOPIC);
},
- canSave() {
- return Object.values(this.selectedValues).some(value => value.length > 0);
- },
- hasMixedCategories() {
- const allSelectedCategories = new Set(Object.keys(this.selectedValues));
- for (const categoryId of allSelectedCategories) {
- const nodesWithThisCategory = this.selectedValues[categoryId].length;
-
- if (nodesWithThisCategory < this.nodes.length) {
- return true;
- }
- }
- return false;
- },
- hasMixedCategoriesMessage() {
- // eslint-disable-next-line kolibri/vue-no-undefined-string-uses
- return commonStrings.$tr('addAdditionalCatgoriesDescription');
- },
},
watch: {
selectedValues(newValue, oldValue) {
@@ -168,16 +147,19 @@
}
},
async handleSave() {
+ this.validate();
+ if (this.error) {
+ return;
+ }
+
await Promise.all(
- this.nodes.map(async node => {
+ this.nodes.map(node => {
const fieldValue = {};
- const currentNode = this.getContentNode(node.id);
-
Object.entries(this.selectedValues).forEach(([key, value]) => {
- const existingValue = currentNode[this.field]?.[key] || false;
- fieldValue[key] = existingValue || value.includes(node.id);
+ if (value.includes(node.id)) {
+ fieldValue[key] = true;
+ }
});
-
if (this.updateDescendants && node.kind === ContentKindsNames.TOPIC) {
return this.updateContentNodeDescendants({
id: node.id,
diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js
index 428d41cad1..e8a48355e1 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js
@@ -16,6 +16,7 @@ let generalActions;
const CheckboxValue = {
UNCHECKED: 'UNCHECKED',
CHECKED: 'CHECKED',
+ INDETERMINATE: 'INDETERMINATE',
};
const { translateMetadataString } = metadataTranslationMixin.methods;
@@ -30,9 +31,11 @@ const getOptionsValues = wrapper => {
const categories = {};
const checkboxes = wrapper.findAll('[data-test="option-checkbox"]');
checkboxes.wrappers.forEach(checkbox => {
- const { label, checked } = checkbox.vm.$props || {};
+ const { label, checked, indeterminate } = checkbox.vm.$props || {};
let value;
- if (checked) {
+ if (indeterminate) {
+ value = CheckboxValue.INDETERMINATE;
+ } else if (checked) {
value = CheckboxValue.CHECKED;
} else {
value = CheckboxValue.UNCHECKED;
@@ -179,6 +182,26 @@ describe('EditBooleanMapModal', () => {
expect(dailyLifeValue).toBe(CheckboxValue.CHECKED);
expect(foundationsValue).toBe(CheckboxValue.CHECKED);
});
+
+ test('checkbox option should be indeterminate if not all nodes have the same options set', () => {
+ nodes['node1'].categories = {
+ [Categories.DAILY_LIFE]: true,
+ [Categories.FOUNDATIONS]: true,
+ };
+ nodes['node2'].categories = {
+ [Categories.DAILY_LIFE]: true,
+ };
+
+ const wrapper = makeWrapper({ nodeIds: ['node1', 'node2'] });
+
+ const optionsValues = getOptionsValues(wrapper);
+ const {
+ [Categories.DAILY_LIFE]: dailyLifeValue,
+ [Categories.FOUNDATIONS]: foundationsValue,
+ } = optionsValues;
+ expect(dailyLifeValue).toBe(CheckboxValue.CHECKED);
+ expect(foundationsValue).toBe(CheckboxValue.INDETERMINATE);
+ });
});
});
diff --git a/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue b/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue
index f6170a8b9a..032a720873 100644
--- a/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue
@@ -5,7 +5,7 @@
entry[1].length === this.nodeIds.length)
.map(([key]) => key);
+ if (
+ this.expanded &&
+ Object.values(this.selected).some(value => value.length < this.nodeIds.length)
+ ) {
+ selectedValues.push(MIXED);
+ }
return selectedValues;
},
nested() {
@@ -195,6 +216,9 @@
this.selected = {};
},
tooltipText(optionId) {
+ if (optionId === MIXED) {
+ return this.$tr('mixedLabel');
+ }
const option = this.categoriesList.find(option => option.value === optionId);
if (!option) {
return '';
@@ -258,6 +282,15 @@
});
return nodeIds.size === this.nodeIds.length;
},
+ isCheckboxIndeterminate(optionId) {
+ if (this.selected[optionId] && this.selected[optionId].length < this.nodeIds.length) {
+ return true;
+ }
+ return (
+ Object.keys(this.selected).some(selectedValue => selectedValue.startsWith(optionId)) &&
+ !this.isSelected(optionId)
+ );
+ },
onChange(optionId) {
if (this.isSelected(optionId)) {
this.remove(optionId);
@@ -268,6 +301,7 @@
},
$trs: {
noCategoryFoundText: 'Category not found',
+ mixedLabel: 'Mixed',
},
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/form/ExpandableSelect.vue b/contentcuration/contentcuration/frontend/shared/views/form/ExpandableSelect.vue
index cdeea8ee22..127eeabd7a 100644
--- a/contentcuration/contentcuration/frontend/shared/views/form/ExpandableSelect.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/form/ExpandableSelect.vue
@@ -50,6 +50,7 @@
:key="option.value"
:label="option.text"
:checked="isSelected(option.value)"
+ :indeterminate="isIndeterminate(option.value)"
data-test="option-checkbox"
@change="value => setOption(option.value, value)"
/>
@@ -187,6 +188,12 @@
}
return this.valueModel[value].length === this.availableItems.length;
},
+ isIndeterminate(value) {
+ if (!this.valueModel[value]) {
+ return false;
+ }
+ return this.valueModel[value].length < this.availableItems.length;
+ },
setOption(optionId, value) {
if (value) {
this.valueModel = {
@@ -209,4 +216,4 @@
pointer-events: none;
opacity: 0.5;
}
-
+
\ No newline at end of file