Skip to content

Commit cd70492

Browse files
authored
Merge pull request #4404 from FidalMathew/issue-2924
Improve errors reporting in edit modal
2 parents fb7ccbb + 6ba65d1 commit cd70492

File tree

2 files changed

+91
-15
lines changed

2 files changed

+91
-15
lines changed

contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@
1313
<ToolBar v-if="showTabs" :flat="!tabsElevated" class="tabs" color="white">
1414
<Tabs v-model="currentTab" slider-color="primary" height="64px">
1515
<!-- Details tab -->
16-
<VTab
17-
ref="detailstab"
18-
:href="`#${tabs.DETAILS}`"
19-
@click="trackTab('Details')"
20-
>
16+
<VTab ref="detailstab" :href="`#${tabs.DETAILS}`" @click="trackTab('Details')">
2117
{{ $tr(tabs.DETAILS) }}
2218
<VTooltip v-if="!areDetailsValid || !areFilesValid" top lazy>
2319
<template #activator="{ on }">
@@ -72,21 +68,22 @@
7268
</VAlert>
7369
<VAlert v-else-if="!areDetailsValid" :value="true" type="error" outline icon="error">
7470
{{ $tr('errorBannerText') }}
71+
<ul>
72+
<li
73+
v-for="(error, index) in errorsList"
74+
:key="index"
75+
@click="handleErrorClick(error)"
76+
>
77+
<a class="error-ref"> {{ $tr(error) }} </a>
78+
</li>
79+
</ul>
7580
</VAlert>
76-
<DetailsTabView
77-
:key="nodeIds.join('-')"
78-
ref="detailsTab"
79-
:nodeIds="nodeIds"
80-
/>
81+
<DetailsTabView :key="nodeIds.join('-')" ref="detailsTab" :nodeIds="nodeIds" />
8182
</VTabItem>
8283
<VTabItem :key="tabs.QUESTIONS" ref="questionwindow" :value="tabs.QUESTIONS" lazy>
8384
<AssessmentTab :nodeId="nodeIds[0]" />
8485
</VTabItem>
85-
<VTabItem
86-
:key="tabs.RELATED"
87-
:value="tabs.RELATED"
88-
lazy
89-
>
86+
<VTabItem :key="tabs.RELATED" :value="tabs.RELATED" lazy>
9087
<RelatedResourcesTab :nodeId="nodeIds[0]" />
9188
</VTabItem>
9289
</VTabsItems>
@@ -110,6 +107,14 @@
110107
import ToolBar from 'shared/views/ToolBar';
111108
import Tabs from 'shared/views/Tabs';
112109
110+
const EditFields = {
111+
TITLE: 'titleLabel',
112+
LICENSE: 'licenseLabel',
113+
COPYRIGHT_HOLDER: 'copyrightHolderLabel',
114+
COMPLETION: 'completionLabel',
115+
LEARNING_ACTIVITY: 'learningActivityLabel',
116+
};
117+
113118
export default {
114119
name: 'EditView',
115120
components: {
@@ -133,6 +138,7 @@
133138
return {
134139
currentTab: null,
135140
tabsElevated: false,
141+
errorsList: [],
136142
};
137143
},
138144
computed: {
@@ -141,6 +147,7 @@
141147
'getContentNodeDetailsAreValid',
142148
'getContentNodeFilesAreValid',
143149
'getImmediateRelatedResourcesCount',
150+
'getNodeDetailsErrorsList',
144151
]),
145152
...mapGetters('assessmentItem', ['getAssessmentItemsAreValid', 'getAssessmentItemsCount']),
146153
firstNode() {
@@ -182,6 +189,7 @@
182189
return this.$tr('editingMultipleCount', totals);
183190
},
184191
areDetailsValid() {
192+
this.setErrors(this.nodeIds[0]);
185193
return !this.oneSelected || this.getContentNodeDetailsAreValid(this.nodeIds[0]);
186194
},
187195
areAssessmentItemsValid() {
@@ -243,6 +251,57 @@
243251
trackTab(name) {
244252
this.$analytics.trackClick('channel_editor_modal', name);
245253
},
254+
handleErrorClick(error) {
255+
const errorRefs = {
256+
[EditFields.TITLE]: 'title',
257+
[EditFields.LICENSE]: 'license',
258+
[EditFields.COPYRIGHT_HOLDER]: 'copyright_holder',
259+
[EditFields.COMPLETION]: 'randomize',
260+
[EditFields.LEARNING_ACTIVITY]: 'learning_activities',
261+
};
262+
263+
const errorRef = errorRefs[error];
264+
const targetElement = this.$refs.detailsTab.$refs[errorRef];
265+
266+
if (!targetElement) {
267+
console.error(`Target element ref not found for error: ${error}`);
268+
return;
269+
}
270+
271+
const nativeElement = targetElement.$el;
272+
const containerElement = this.$refs.editview;
273+
const position = nativeElement.getBoundingClientRect();
274+
const offsetY = position.top - nativeElement.clientHeight - 50; // additional padding of 50 to adjust position.
275+
containerElement.scrollTo({ top: offsetY, behavior: 'smooth' });
276+
},
277+
setErrors(nodeId) {
278+
const errorsTagList = this.getNodeDetailsErrorsList(nodeId);
279+
// fetch unique errors
280+
// set that to errorsList
281+
const errorRefs = {
282+
TITLE_REQUIRED: EditFields.TITLE,
283+
LICENSE_REQUIRED: EditFields.LICENSE,
284+
COPYRIGHT_HOLDER_REQUIRED: EditFields.COPYRIGHT_HOLDER,
285+
MASTERY_MODEL_REQUIRED: EditFields.COMPLETION,
286+
MASTERY_MODEL_M_REQUIRED: EditFields.COMPLETION,
287+
MASTERY_MODEL_M_WHOLE_NUMBER: EditFields.COMPLETION,
288+
MASTERY_MODEL_M_GT_ZERO: EditFields.COMPLETION,
289+
MASTERY_MODEL_M_LTE_N: EditFields.COMPLETION,
290+
MASTERY_MODEL_N_REQUIRED: EditFields.COMPLETION,
291+
MASTERY_MODEL_N_WHOLE_NUMBER: EditFields.COMPLETION,
292+
MASTERY_MODEL_N_GT_ZERO: EditFields.COMPLETION,
293+
LEARNING_ACTIVITY_REQUIRED: EditFields.LEARNING_ACTIVITY,
294+
};
295+
296+
const uniqueErrorsSet = new Set();
297+
for (const error of errorsTagList) {
298+
if (errorRefs[error]) {
299+
uniqueErrorsSet.add(errorRefs[error]);
300+
}
301+
}
302+
const arr = [...uniqueErrorsSet];
303+
this.errorsList = arr;
304+
},
246305
/*
247306
* @public
248307
*/
@@ -266,6 +325,11 @@
266325
errorBannerText: 'Please provide the required information',
267326
editingMultipleCount:
268327
'Editing details for {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}',
328+
titleLabel: 'Title',
329+
licenseLabel: 'License',
330+
copyrightHolderLabel: 'Copyright holder',
331+
completionLabel: 'Completion',
332+
learningActivityLabel: 'Learning activity',
269333
},
270334
};
271335
@@ -315,4 +379,9 @@
315379
overflow: auto;
316380
}
317381
382+
.error-ref {
383+
color: inherit;
384+
text-decoration: underline;
385+
}
386+
318387
</style>

contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ export function getContentNodeDetailsAreValid(state) {
155155
};
156156
}
157157

158+
export function getNodeDetailsErrorsList(state) {
159+
return function(contentNodeId) {
160+
const contentNode = state.contentNodesMap[contentNodeId];
161+
return getNodeDetailsErrors(contentNode);
162+
};
163+
}
164+
158165
export function getContentNodeFilesAreValid(state, getters, rootState, rootGetters) {
159166
return function(contentNodeId) {
160167
const contentNode = state.contentNodesMap[contentNodeId];

0 commit comments

Comments
 (0)