@windward/core 0.25.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/components/Content/Blocks/Accordion.vue +8 -14
- package/components/Content/Blocks/BlockQuote.vue +29 -4
- package/components/Content/Blocks/ClickableIcons.vue +18 -6
- package/components/Content/Blocks/Email.vue +10 -3
- package/components/Content/Blocks/Feedback/FeedbackAnalytics.vue +179 -0
- package/components/Content/Blocks/Feedback.vue +115 -111
- package/components/Content/Blocks/Image.vue +4 -0
- package/components/Content/Blocks/OpenResponse.vue +0 -9
- package/components/Content/Blocks/OpenResponseCollate.vue +27 -0
- package/components/Content/Blocks/ScenarioChoice.vue +10 -1
- package/components/Content/Blocks/Tab.vue +15 -28
- package/components/Content/Blocks/UserUpload.vue +65 -37
- package/components/Content/Blocks/Video.vue +17 -7
- package/components/Settings/AccordionSettings.vue +3 -15
- package/components/Settings/BlockQuoteSettings.vue +6 -4
- package/components/Settings/ClickableIconsSettings.vue +21 -7
- package/components/Settings/EmailSettings.vue +3 -11
- package/components/Settings/FileDownloadSettings.vue +8 -2
- package/components/Settings/OpenResponseCollateSettings.vue +19 -1
- package/components/Settings/OpenResponseSettings.vue +8 -7
- package/components/Settings/ScenarioChoiceSettings.vue +11 -5
- package/components/Settings/TabSettings.vue +3 -18
- package/components/Settings/UserUploadSettings.vue +16 -8
- package/components/Settings/VideoSettings/SourcePicker.vue +34 -8
- package/components/Settings/VideoSettings.vue +18 -2
- package/components/utils/glossary/GlossaryToolTip.vue +4 -23
- package/helpers/GlossaryHelper.ts +4 -7
- package/i18n/en-US/components/content/blocks/accordion.ts +3 -0
- package/i18n/en-US/components/content/blocks/block_quote.ts +3 -1
- package/i18n/en-US/components/content/blocks/feedback.ts +2 -0
- package/i18n/en-US/components/content/blocks/file_download.ts +2 -1
- package/i18n/en-US/components/content/blocks/index.ts +2 -0
- package/i18n/en-US/components/content/blocks/open_response.ts +1 -1
- package/i18n/en-US/components/content/blocks/open_response_collate.ts +1 -1
- package/i18n/en-US/components/content/blocks/scenario_choice.ts +2 -0
- package/i18n/en-US/components/content/blocks/user_upload.ts +2 -1
- package/i18n/en-US/components/settings/accordion.ts +2 -1
- package/i18n/en-US/components/settings/block_quote.ts +1 -1
- package/i18n/en-US/components/settings/clickable_icon.ts +5 -0
- package/i18n/en-US/components/settings/email.ts +2 -1
- package/i18n/en-US/components/settings/file_download.ts +2 -2
- package/i18n/en-US/components/settings/image.ts +1 -0
- package/i18n/en-US/components/settings/open_response.ts +3 -0
- package/i18n/en-US/components/settings/open_response_collate.ts +3 -0
- package/i18n/en-US/components/settings/scenario_choice.ts +3 -1
- package/i18n/en-US/components/settings/tab.ts +4 -3
- package/i18n/en-US/components/settings/user_upload.ts +1 -0
- package/i18n/en-US/components/settings/video.ts +3 -1
- package/i18n/en-US/shared/content_blocks.ts +1 -1
- package/i18n/en-US/shared/settings.ts +1 -18
- package/i18n/es-ES/components/content/blocks/accordion.ts +3 -0
- package/i18n/es-ES/components/content/blocks/block_quote.ts +3 -1
- package/i18n/es-ES/components/content/blocks/feedback.ts +2 -0
- package/i18n/es-ES/components/content/blocks/file_download.ts +2 -1
- package/i18n/es-ES/components/content/blocks/index.ts +2 -0
- package/i18n/es-ES/components/content/blocks/open_response.ts +1 -2
- package/i18n/es-ES/components/content/blocks/open_response_collate.ts +1 -1
- package/i18n/es-ES/components/content/blocks/scenario_choice.ts +2 -0
- package/i18n/es-ES/components/content/blocks/user_upload.ts +2 -1
- package/i18n/es-ES/components/settings/accordion.ts +4 -2
- package/i18n/es-ES/components/settings/block_quote.ts +1 -1
- package/i18n/es-ES/components/settings/clickable_icon.ts +7 -0
- package/i18n/es-ES/components/settings/email.ts +2 -1
- package/i18n/es-ES/components/settings/image.ts +1 -0
- package/i18n/es-ES/components/settings/open_response.ts +3 -0
- package/i18n/es-ES/components/settings/open_response_collate.ts +3 -0
- package/i18n/es-ES/components/settings/scenario_choice.ts +3 -1
- package/i18n/es-ES/components/settings/tab.ts +3 -2
- package/i18n/es-ES/components/settings/user_upload.ts +1 -0
- package/i18n/es-ES/components/settings/video.ts +3 -1
- package/i18n/es-ES/shared/content_blocks.ts +1 -1
- package/i18n/es-ES/shared/settings.ts +1 -19
- package/i18n/sv-SE/components/content/blocks/accordion.ts +3 -0
- package/i18n/sv-SE/components/content/blocks/block_quote.ts +3 -1
- package/i18n/sv-SE/components/content/blocks/feedback.ts +2 -0
- package/i18n/sv-SE/components/content/blocks/file_download.ts +2 -1
- package/i18n/sv-SE/components/content/blocks/index.ts +2 -0
- package/i18n/sv-SE/components/content/blocks/open_response.ts +1 -2
- package/i18n/sv-SE/components/content/blocks/open_response_collate.ts +1 -1
- package/i18n/sv-SE/components/content/blocks/scenario_choice.ts +2 -0
- package/i18n/sv-SE/components/content/blocks/user_upload.ts +2 -1
- package/i18n/sv-SE/components/settings/accordion.ts +2 -1
- package/i18n/sv-SE/components/settings/block_quote.ts +1 -1
- package/i18n/sv-SE/components/settings/clickable_icon.ts +6 -0
- package/i18n/sv-SE/components/settings/email.ts +2 -1
- package/i18n/sv-SE/components/settings/image.ts +1 -0
- package/i18n/sv-SE/components/settings/open_response.ts +3 -0
- package/i18n/sv-SE/components/settings/open_response_collate.ts +3 -0
- package/i18n/sv-SE/components/settings/scenario_choice.ts +3 -1
- package/i18n/sv-SE/components/settings/tab.ts +5 -3
- package/i18n/sv-SE/components/settings/user_upload.ts +1 -0
- package/i18n/sv-SE/components/settings/video.ts +3 -1
- package/i18n/sv-SE/shared/content_blocks.ts +1 -1
- package/i18n/sv-SE/shared/settings.ts +1 -18
- package/models/SurveyResultMetric.ts +8 -0
- package/package.json +1 -1
- package/plugin.js +27 -19
- package/test/Components/Content/Blocks/Feedback/FeedbackTemplates/FeedbackAnalytics.spec.js +23 -0
- package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.spec.js +1 -1
- package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.spec.js +1 -1
- package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.spec.js +1 -1
- package/test/Components/Settings/AccordionSettings.spec.js +0 -13
- package/test/Components/Settings/ClickableIconsSettings.spec.js +0 -11
- package/test/Components/Settings/EmailSettings.spec.js +0 -9
- package/test/Components/Settings/TabSettings.spec.js +0 -13
- package/test/helpers/GlossaryHelper.spec.js +8 -8
- package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.vue +1 -1
- package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.vue +1 -1
- /package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.vue +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release [0.27.0] - 2026-01-15
|
|
4
|
+
|
|
5
|
+
* Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #476)
|
|
6
|
+
* Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
|
|
7
|
+
* Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #474)
|
|
8
|
+
* Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
|
|
9
|
+
* Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #473)
|
|
10
|
+
* Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
|
|
11
|
+
* Merged in LE-1915-block-quote-design-updates (pull request #468)
|
|
12
|
+
* Merge remote-tracking branch 'origin/release/0.27.0' into LE-1915-block-quote-design-updates
|
|
13
|
+
* Merged in LE-2185-feedback-block-add-container-bac (pull request #469)
|
|
14
|
+
* Merge remote-tracking branch 'origin/release/0.27.0' into LE-2185-feedback-block-add-container-bac
|
|
15
|
+
* Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #472)
|
|
16
|
+
* Merged in feature/LE-1788-transcript-generation (pull request #465)
|
|
17
|
+
* Merged release/0.27.0 into feature/LE-2236-bar-charts-for-non-open-ended-it
|
|
18
|
+
* Merged in LE-2186-file-upload-block-add-container- (pull request #470)
|
|
19
|
+
* Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #471)
|
|
20
|
+
* Merged in LE-1897-glossary-pop-up-displayy (pull request #467)
|
|
21
|
+
* Add localized "item header" message for ClickableIconsSettings and update "initial setup" text for OpenResponse block
|
|
22
|
+
* Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #466)
|
|
23
|
+
* Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
|
|
24
|
+
* Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #464)
|
|
25
|
+
* Merge remote-tracking branch 'origin/feature/LE-2155-consistent-ui-for-adding-new-blo' into feature/LE-2155-consistent-ui-for-adding-new-blo
|
|
26
|
+
* Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
|
|
27
|
+
* add localized messages for OpenResponseCollate and update default settings handling
|
|
28
|
+
* Merged in release/0.26.0 (pull request #455)
|
|
29
|
+
* Merged in hotfix/0.25.1 (pull request #457)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Release [0.26.0] - 2025-12-05
|
|
33
|
+
|
|
34
|
+
* Merged in feature/LE-2154-open-response-download-add-a-tit (pull request #461)
|
|
35
|
+
* Merged in feature/LE-2156-block-settings-label (pull request #458)
|
|
36
|
+
* Merged in LE-2124-move-some-blocks-to-activities-s (pull request #459)
|
|
37
|
+
* Merged in bugfix/LE-2205-open-response-field-rendering-sw (pull request #460)
|
|
38
|
+
* Merged release/0.26.0 into bugfix/LE-2205-open-response-field-rendering-sw
|
|
39
|
+
* hotfix: add starting text support for OpenResponse blocks and enhance content insertion logic in TinyMCE
|
|
40
|
+
* Merged in hotfix/0.25.1 (pull request #456)
|
|
41
|
+
* Merged in feature/LE-2108/revise-text-fix (pull request #453)
|
|
42
|
+
* Merged in feature/LE-2123/open-res-gen-fix (pull request #452)
|
|
43
|
+
* Merged in release/0.25.0 (pull request #448)
|
|
44
|
+
* Merged in release/0.24.0 (pull request #442)
|
|
45
|
+
|
|
46
|
+
|
|
3
47
|
## Hotfix [0.25.1] - 2025-11-07
|
|
4
48
|
|
|
5
49
|
* Merged in feature/LE-2108/revise-text-fix (pull request #453)
|
|
@@ -25,7 +25,15 @@
|
|
|
25
25
|
{{ block.metadata.config.instructions }}
|
|
26
26
|
</p>
|
|
27
27
|
</v-container>
|
|
28
|
+
<v-alert v-if="block.metadata.config.items.length === 0" type="warning">
|
|
29
|
+
{{
|
|
30
|
+
$t(
|
|
31
|
+
'windward.core.components.content.blocks.accordion.no_accordion_items'
|
|
32
|
+
)
|
|
33
|
+
}}
|
|
34
|
+
</v-alert>
|
|
28
35
|
<v-expansion-panels
|
|
36
|
+
v-else
|
|
29
37
|
:value="selectedPanels"
|
|
30
38
|
flat
|
|
31
39
|
accordion
|
|
@@ -174,21 +182,7 @@ export default {
|
|
|
174
182
|
this.$set(this.block.metadata.config, 'display_title', true)
|
|
175
183
|
}
|
|
176
184
|
if (_.isEmpty(this.block.metadata.config.items)) {
|
|
177
|
-
const defaultObject = {
|
|
178
|
-
header: '',
|
|
179
|
-
expand: false,
|
|
180
|
-
content: '',
|
|
181
|
-
fileConfig: {
|
|
182
|
-
display: {
|
|
183
|
-
width: 100,
|
|
184
|
-
margin: '',
|
|
185
|
-
padding: '',
|
|
186
|
-
},
|
|
187
|
-
hideBackground: true,
|
|
188
|
-
},
|
|
189
|
-
}
|
|
190
185
|
this.block.metadata.config.items = []
|
|
191
|
-
this.block.metadata.config.items.push(defaultObject)
|
|
192
186
|
}
|
|
193
187
|
this.block.body = this.$t(
|
|
194
188
|
'windward.core.shared.content_blocks.title.accordion'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-container class="
|
|
2
|
+
<v-container :class="containerClasses">
|
|
3
3
|
<v-row
|
|
4
4
|
v-if="
|
|
5
5
|
block.metadata.config.title ||
|
|
@@ -25,7 +25,16 @@
|
|
|
25
25
|
</p>
|
|
26
26
|
</v-col>
|
|
27
27
|
</v-row>
|
|
28
|
-
<v-row>
|
|
28
|
+
<v-row v-if="!block.metadata.config.block_quote.quote" class="pa-2">
|
|
29
|
+
<v-alert type="warning" width="100%">
|
|
30
|
+
{{
|
|
31
|
+
$t(
|
|
32
|
+
'windward.core.components.content.blocks.block_quote.no_items'
|
|
33
|
+
)
|
|
34
|
+
}}
|
|
35
|
+
</v-alert>
|
|
36
|
+
</v-row>
|
|
37
|
+
<v-row v-else>
|
|
29
38
|
<v-col cols="1">
|
|
30
39
|
<v-icon color="primary">mdi-format-quote-open</v-icon>
|
|
31
40
|
</v-col>
|
|
@@ -67,6 +76,19 @@ export default {
|
|
|
67
76
|
return {}
|
|
68
77
|
},
|
|
69
78
|
computed: {
|
|
79
|
+
containerClasses() {
|
|
80
|
+
const classes = ['container-left-border']
|
|
81
|
+
|
|
82
|
+
// Add surface class when highlight is applied
|
|
83
|
+
if (
|
|
84
|
+
!_.isEmpty(this.block.metadata.display) &&
|
|
85
|
+
!_.isEmpty(this.block.metadata.display.highlight)
|
|
86
|
+
) {
|
|
87
|
+
classes.push('surface')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return classes.join(' ')
|
|
91
|
+
},
|
|
70
92
|
citation() {
|
|
71
93
|
const parts = []
|
|
72
94
|
|
|
@@ -152,7 +174,9 @@ export default {
|
|
|
152
174
|
this.block.metadata.config = {}
|
|
153
175
|
}
|
|
154
176
|
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
155
|
-
this.block.metadata.config.title =
|
|
177
|
+
this.block.metadata.config.title = this.$t(
|
|
178
|
+
'windward.core.components.content.blocks.block_quote.title'
|
|
179
|
+
)
|
|
156
180
|
}
|
|
157
181
|
if (!_.isBoolean(this.block.metadata.config.display_title)) {
|
|
158
182
|
this.$set(this.block.metadata.config, 'display_title', true)
|
|
@@ -183,10 +207,11 @@ export default {
|
|
|
183
207
|
<style scoped>
|
|
184
208
|
.container-left-border {
|
|
185
209
|
border-style: solid;
|
|
186
|
-
border-color: var(--v-secondary-base);
|
|
187
210
|
border-width: 2px 2px 2px 16px;
|
|
188
211
|
border-radius: 4px;
|
|
212
|
+
border-color: var(--v-primary-base);
|
|
189
213
|
}
|
|
214
|
+
|
|
190
215
|
.span-title {
|
|
191
216
|
font-style: italic;
|
|
192
217
|
}
|
|
@@ -18,7 +18,14 @@
|
|
|
18
18
|
{{ block.metadata.config.instructions }}
|
|
19
19
|
</p>
|
|
20
20
|
</v-container>
|
|
21
|
-
<v-
|
|
21
|
+
<v-alert v-if="block.metadata.config.items.length === 0" type="warning">
|
|
22
|
+
{{
|
|
23
|
+
$t(
|
|
24
|
+
'windward.core.components.settings.clickable_icon.no_clickable_items'
|
|
25
|
+
)
|
|
26
|
+
}}
|
|
27
|
+
</v-alert>
|
|
28
|
+
<v-container class="pa-0" v-else>
|
|
22
29
|
<v-container
|
|
23
30
|
v-for="(item, itemIndex) in block.metadata.config.items"
|
|
24
31
|
:key="itemIndex"
|
|
@@ -57,9 +64,11 @@
|
|
|
57
64
|
class="clickable--icon dark-text--text"
|
|
58
65
|
>{{ item.icon }}
|
|
59
66
|
</v-icon>
|
|
60
|
-
<span
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
<span
|
|
68
|
+
v-else
|
|
69
|
+
:class="iconClass + ' dark-text--text'"
|
|
70
|
+
>{{ decode(item.icon) }}</span
|
|
71
|
+
>
|
|
63
72
|
</button>
|
|
64
73
|
</v-col>
|
|
65
74
|
<v-col style="min-width: 0" class="ma-3 mt-5">
|
|
@@ -142,7 +151,8 @@ export default {
|
|
|
142
151
|
) &&
|
|
143
152
|
this.itemColor(itemIndex)
|
|
144
153
|
) {
|
|
145
|
-
classes +=
|
|
154
|
+
classes +=
|
|
155
|
+
' ' + this.itemColor(itemIndex) + ' dark-text--text'
|
|
146
156
|
}
|
|
147
157
|
return classes
|
|
148
158
|
}
|
|
@@ -232,7 +242,9 @@ export default {
|
|
|
232
242
|
)
|
|
233
243
|
}
|
|
234
244
|
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
235
|
-
this.block.metadata.config.title =
|
|
245
|
+
this.block.metadata.config.title = this.$t(
|
|
246
|
+
'windward.core.components.settings.clickable_icon.clickable_icon_title'
|
|
247
|
+
)
|
|
236
248
|
}
|
|
237
249
|
if (!_.isBoolean(this.block.metadata.config.display_title)) {
|
|
238
250
|
this.$set(this.block.metadata.config, 'display_title', true)
|
|
@@ -25,10 +25,15 @@
|
|
|
25
25
|
</p>
|
|
26
26
|
</v-col>
|
|
27
27
|
</v-row>
|
|
28
|
-
<v-row>
|
|
28
|
+
<v-row v-if="block.metadata.config.emails.length === 0">
|
|
29
|
+
<v-alert type="warning" width="100%">
|
|
30
|
+
{{ $t('windward.core.components.settings.email.no_emails') }}
|
|
31
|
+
</v-alert>
|
|
32
|
+
</v-row>
|
|
33
|
+
<v-row v-else>
|
|
29
34
|
<v-expansion-panels
|
|
30
|
-
v-model="selectedPanels"
|
|
31
35
|
:key="expansionPanelKey"
|
|
36
|
+
v-model="selectedPanels"
|
|
32
37
|
accordion
|
|
33
38
|
>
|
|
34
39
|
<v-container
|
|
@@ -256,7 +261,9 @@ export default {
|
|
|
256
261
|
this.block.metadata.config = {}
|
|
257
262
|
}
|
|
258
263
|
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
259
|
-
this.block.metadata.config.title =
|
|
264
|
+
this.block.metadata.config.title = this.$t(
|
|
265
|
+
'windward.core.components.content.blocks.email.email'
|
|
266
|
+
)
|
|
260
267
|
}
|
|
261
268
|
if (!_.isBoolean(this.block.metadata.config.display_title)) {
|
|
262
269
|
this.$set(this.block.metadata.config, 'display_title', true)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="questionCharts.length === 0">No Responses Available</div>
|
|
4
|
+
<div v-else>
|
|
5
|
+
<v-row dense>
|
|
6
|
+
<v-col
|
|
7
|
+
v-for="(opts, idx) in questionCharts"
|
|
8
|
+
:key="idx"
|
|
9
|
+
cols="12"
|
|
10
|
+
sm="12"
|
|
11
|
+
md="6"
|
|
12
|
+
lg="4"
|
|
13
|
+
class="mb-6"
|
|
14
|
+
>
|
|
15
|
+
<highcharts :options="opts"></highcharts>
|
|
16
|
+
</v-col>
|
|
17
|
+
</v-row>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
import SurveyResultMetric from '../../../../models/SurveyResultMetric'
|
|
24
|
+
import Course from '~/models/Course'
|
|
25
|
+
import ContentBlock from '~/models/ContentBlock'
|
|
26
|
+
export default {
|
|
27
|
+
name: 'FeedbackAnalytics',
|
|
28
|
+
components: {},
|
|
29
|
+
props: {
|
|
30
|
+
loading: { type: Boolean, required: false, default: false },
|
|
31
|
+
filters: {
|
|
32
|
+
type: Object,
|
|
33
|
+
required: false,
|
|
34
|
+
default() {
|
|
35
|
+
return { sections: [], roles: [], enrollments: [] }
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
courseContent: {
|
|
39
|
+
type: Object,
|
|
40
|
+
required: false,
|
|
41
|
+
default() {
|
|
42
|
+
return {}
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
block: {
|
|
46
|
+
type: Object,
|
|
47
|
+
required: true,
|
|
48
|
+
default() {
|
|
49
|
+
return {}
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
data() {
|
|
54
|
+
return {
|
|
55
|
+
metrics: [],
|
|
56
|
+
questionCharts: [],
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
async fetch() {
|
|
60
|
+
await this.loadMetrics()
|
|
61
|
+
},
|
|
62
|
+
watch: {
|
|
63
|
+
filters: {
|
|
64
|
+
deep: true,
|
|
65
|
+
handler() {
|
|
66
|
+
// Reload the metrics if this filter changes
|
|
67
|
+
this.loadMetrics()
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
methods: {
|
|
72
|
+
async loadMetrics() {
|
|
73
|
+
this.metrics = await SurveyResultMetric.custom(
|
|
74
|
+
new Course({ id: this.courseContent.course_id }),
|
|
75
|
+
new ContentBlock({ id: this.block.id }),
|
|
76
|
+
new SurveyResultMetric()
|
|
77
|
+
)
|
|
78
|
+
.whereIn('course_user_id', this.filters.enrollments)
|
|
79
|
+
.whereIn('course_section_id', this.filters.sections)
|
|
80
|
+
.whereIn('user.role_id', this.filters.roles)
|
|
81
|
+
.get()
|
|
82
|
+
this.createTables(this.metrics)
|
|
83
|
+
},
|
|
84
|
+
createTables(metrics) {
|
|
85
|
+
const charts = []
|
|
86
|
+
|
|
87
|
+
for (const m of metrics) {
|
|
88
|
+
if (this.isLikertMetric(m)) {
|
|
89
|
+
const categories = [
|
|
90
|
+
'strongly agree',
|
|
91
|
+
'agree',
|
|
92
|
+
'neutral',
|
|
93
|
+
'disagree',
|
|
94
|
+
'strongly disagree',
|
|
95
|
+
]
|
|
96
|
+
const counts = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }
|
|
97
|
+
for (const r of m.responses || []) {
|
|
98
|
+
const v =
|
|
99
|
+
typeof r.response_text === 'string'
|
|
100
|
+
? parseInt(r.response_text, 10)
|
|
101
|
+
: r.response_text
|
|
102
|
+
if (Number.isInteger(v) && v >= 1 && v <= 5) {
|
|
103
|
+
counts[v] =
|
|
104
|
+
(counts[v] || 0) + (r.response_count || 0)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
charts.push({
|
|
108
|
+
chart: { type: 'column' },
|
|
109
|
+
title: { text: m.survey_question_body || 'Question' },
|
|
110
|
+
xAxis: { categories },
|
|
111
|
+
yAxis: { min: 0 },
|
|
112
|
+
plotOptions: {
|
|
113
|
+
column: { pointPadding: 0.2, borderWidth: 0 },
|
|
114
|
+
},
|
|
115
|
+
series: [
|
|
116
|
+
{
|
|
117
|
+
name: 'Responses',
|
|
118
|
+
data: [
|
|
119
|
+
counts[5],
|
|
120
|
+
counts[4],
|
|
121
|
+
counts[3],
|
|
122
|
+
counts[2],
|
|
123
|
+
counts[1],
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
} else if (this.isBooleanMetric(m)) {
|
|
129
|
+
const categories = ['true', 'false']
|
|
130
|
+
let trueCount = 0
|
|
131
|
+
let falseCount = 0
|
|
132
|
+
for (const r of m.responses || []) {
|
|
133
|
+
if (r.response_text === 'true') {
|
|
134
|
+
trueCount += r.response_count || 0
|
|
135
|
+
} else if (r.response_text === 'false') {
|
|
136
|
+
falseCount += r.response_count || 0
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
charts.push({
|
|
140
|
+
chart: { type: 'column' },
|
|
141
|
+
title: { text: m.survey_question_body || 'Question' },
|
|
142
|
+
xAxis: { categories },
|
|
143
|
+
yAxis: { min: 0 },
|
|
144
|
+
plotOptions: {
|
|
145
|
+
column: { pointPadding: 0.2, borderWidth: 0 },
|
|
146
|
+
},
|
|
147
|
+
series: [
|
|
148
|
+
{
|
|
149
|
+
name: 'Responses',
|
|
150
|
+
data: [trueCount, falseCount],
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
})
|
|
154
|
+
} else {
|
|
155
|
+
// Unsupported (e.g., open-text); skip for now
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.questionCharts = charts
|
|
160
|
+
},
|
|
161
|
+
isLikertMetric(metric) {
|
|
162
|
+
if (!metric || !Array.isArray(metric.responses)) return false
|
|
163
|
+
|
|
164
|
+
return metric.responses.every((r) => {
|
|
165
|
+
const v = r && r.response_text
|
|
166
|
+
const n = typeof v === 'string' ? parseInt(v, 10) : v
|
|
167
|
+
return Number.isInteger(n) && n >= 1 && n <= 5
|
|
168
|
+
})
|
|
169
|
+
},
|
|
170
|
+
isBooleanMetric(metric) {
|
|
171
|
+
if (!metric || !Array.isArray(metric.responses)) return false
|
|
172
|
+
return metric.responses.every((r) => {
|
|
173
|
+
const v = r && r.response_text
|
|
174
|
+
return v === 'true' || v === 'false'
|
|
175
|
+
})
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
</script>
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<v-
|
|
2
|
+
<div>
|
|
3
|
+
<v-alert v-if="!chosenPreset" type="warning">{{
|
|
4
|
+
$t('windward.core.components.content.blocks.feedback.no_feedback')
|
|
5
|
+
}}</v-alert>
|
|
6
|
+
<v-container v-if="!doesFeedbackExist && chosenPreset" class="pa-0">
|
|
4
7
|
<v-row>
|
|
5
8
|
<h2>
|
|
6
|
-
{{
|
|
7
|
-
block.metadata.config.definition.name
|
|
8
|
-
? block.metadata.config.definition.name
|
|
9
|
-
: $t(
|
|
10
|
-
'windward.core.components.content.blocks.feedback.feedback'
|
|
11
|
-
)
|
|
12
|
-
}}
|
|
9
|
+
{{ block.metadata.config.definition.name }}
|
|
13
10
|
</h2>
|
|
14
11
|
</v-row>
|
|
15
12
|
<v-row class="pt-2">
|
|
@@ -45,126 +42,133 @@
|
|
|
45
42
|
</p>
|
|
46
43
|
</v-row>
|
|
47
44
|
</v-container>
|
|
48
|
-
<v-divider class="mt-4 mb-6"></v-divider>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
v-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
<v-divider v-if="chosenPreset" class="mt-4 mb-6"></v-divider>
|
|
46
|
+
<!-- Survey Container with theme-based background -->
|
|
47
|
+
<v-card v-if="chosenPreset" class="pa-6 surface" flat>
|
|
48
|
+
<v-form
|
|
49
|
+
ref="form"
|
|
50
|
+
v-model="valid"
|
|
51
|
+
lazy-validation
|
|
52
|
+
v-if="!doesFeedbackExist"
|
|
53
|
+
>
|
|
54
|
+
<div v-if="block.metadata.config.definition.metadata">
|
|
55
|
+
<v-container
|
|
56
|
+
v-for="(question, questionIndex) in chosenPreset"
|
|
57
|
+
:key="questionIndex"
|
|
58
|
+
fluid
|
|
59
|
+
>
|
|
60
|
+
<v-container
|
|
61
|
+
v-if="question.type === 'likert'"
|
|
62
|
+
class="pa-0 ma-0"
|
|
63
|
+
>
|
|
64
|
+
<FeedbackQuestionLikert
|
|
65
|
+
v-model="chosenPreset[questionIndex]"
|
|
66
|
+
></FeedbackQuestionLikert>
|
|
67
|
+
</v-container>
|
|
68
|
+
<v-container
|
|
69
|
+
v-if="question.type === 'true_false'"
|
|
70
|
+
class="pa-0 ma-0"
|
|
71
|
+
>
|
|
72
|
+
<FeedbackQuestionTrueFalse
|
|
73
|
+
v-model="chosenPreset[questionIndex]"
|
|
74
|
+
></FeedbackQuestionTrueFalse>
|
|
75
|
+
</v-container>
|
|
76
|
+
<v-container
|
|
77
|
+
v-if="question.type === 'open_response'"
|
|
78
|
+
class="pa-0 ma-0"
|
|
79
|
+
>
|
|
80
|
+
<FeedbackQuestionOpenResponse
|
|
81
|
+
v-model="chosenPreset[questionIndex]"
|
|
82
|
+
></FeedbackQuestionOpenResponse>
|
|
83
|
+
</v-container>
|
|
84
|
+
</v-container>
|
|
85
|
+
</div>
|
|
86
|
+
</v-form>
|
|
87
|
+
<v-row
|
|
88
|
+
class="d-flex justify-end align-center"
|
|
89
|
+
v-if="render && !doesFeedbackExist"
|
|
90
|
+
>
|
|
91
|
+
<v-btn
|
|
92
|
+
color="primary"
|
|
93
|
+
outlined
|
|
94
|
+
elevation="0"
|
|
95
|
+
class="text-center mt-4 mr-2"
|
|
96
|
+
@click="onReset()"
|
|
97
|
+
>{{
|
|
98
|
+
$t(
|
|
99
|
+
'windward.core.components.content.blocks.feedback.reset'
|
|
100
|
+
)
|
|
101
|
+
}}</v-btn
|
|
102
|
+
>
|
|
103
|
+
<v-btn
|
|
104
|
+
elevation="0"
|
|
105
|
+
color="success"
|
|
106
|
+
class="text-center mt-4"
|
|
107
|
+
@click="validate"
|
|
108
|
+
>{{
|
|
109
|
+
$t(
|
|
110
|
+
'windward.core.components.content.blocks.feedback.save'
|
|
111
|
+
)
|
|
112
|
+
}}</v-btn
|
|
113
|
+
>
|
|
114
|
+
</v-row>
|
|
115
|
+
<v-form
|
|
116
|
+
ref="form"
|
|
117
|
+
v-model="valid"
|
|
118
|
+
disabled
|
|
119
|
+
lazy-validation
|
|
120
|
+
v-if="doesFeedbackExist"
|
|
121
|
+
>
|
|
56
122
|
<v-container
|
|
57
|
-
v-for="(
|
|
123
|
+
v-for="(
|
|
124
|
+
question, questionIndex
|
|
125
|
+
) in existingFeedback.survey_question_answers"
|
|
58
126
|
:key="questionIndex"
|
|
59
127
|
fluid
|
|
60
128
|
>
|
|
61
|
-
<v-
|
|
62
|
-
v-if="question.type === 'likert'"
|
|
63
|
-
class="pa-0 ma-0"
|
|
64
|
-
>
|
|
129
|
+
<div v-if="question.type === 'likert'">
|
|
65
130
|
<FeedbackQuestionLikert
|
|
66
|
-
v-model="
|
|
131
|
+
v-model="
|
|
132
|
+
existingFeedback.survey_question_answers[
|
|
133
|
+
questionIndex
|
|
134
|
+
]
|
|
135
|
+
"
|
|
67
136
|
></FeedbackQuestionLikert>
|
|
68
|
-
</
|
|
69
|
-
<v-
|
|
70
|
-
v-if="question.type === 'true_false'"
|
|
71
|
-
class="pa-0 ma-0"
|
|
72
|
-
>
|
|
137
|
+
</div>
|
|
138
|
+
<div v-if="question.type === 'true_false'">
|
|
73
139
|
<FeedbackQuestionTrueFalse
|
|
74
|
-
v-model="
|
|
140
|
+
v-model="
|
|
141
|
+
existingFeedback.survey_question_answers[
|
|
142
|
+
questionIndex
|
|
143
|
+
]
|
|
144
|
+
"
|
|
75
145
|
></FeedbackQuestionTrueFalse>
|
|
76
|
-
</
|
|
77
|
-
<v-
|
|
78
|
-
v-if="question.type === 'open_response'"
|
|
79
|
-
class="pa-0 ma-0"
|
|
80
|
-
>
|
|
146
|
+
</div>
|
|
147
|
+
<div v-if="question.type === 'open_response'">
|
|
81
148
|
<FeedbackQuestionOpenResponse
|
|
82
|
-
v-model="
|
|
149
|
+
v-model="
|
|
150
|
+
existingFeedback.survey_question_answers[
|
|
151
|
+
questionIndex
|
|
152
|
+
]
|
|
153
|
+
"
|
|
154
|
+
:feedbackExist="true"
|
|
83
155
|
></FeedbackQuestionOpenResponse>
|
|
84
|
-
</
|
|
156
|
+
</div>
|
|
85
157
|
</v-container>
|
|
86
|
-
</
|
|
87
|
-
</v-
|
|
88
|
-
|
|
89
|
-
class="d-flex justify-end align-center"
|
|
90
|
-
v-if="render && !doesFeedbackExist"
|
|
91
|
-
>
|
|
92
|
-
<v-btn
|
|
93
|
-
color="primary"
|
|
94
|
-
outlined
|
|
95
|
-
elevation="0"
|
|
96
|
-
class="text-center mt-4 mr-2"
|
|
97
|
-
@click="onReset()"
|
|
98
|
-
>{{
|
|
99
|
-
$t('windward.core.components.content.blocks.feedback.reset')
|
|
100
|
-
}}</v-btn
|
|
101
|
-
>
|
|
102
|
-
<v-btn
|
|
103
|
-
elevation="0"
|
|
104
|
-
color="success"
|
|
105
|
-
class="text-center mt-4"
|
|
106
|
-
@click="validate"
|
|
107
|
-
>{{
|
|
108
|
-
$t('windward.core.components.content.blocks.feedback.save')
|
|
109
|
-
}}</v-btn
|
|
110
|
-
>
|
|
111
|
-
</v-row>
|
|
112
|
-
<v-form
|
|
113
|
-
ref="form"
|
|
114
|
-
v-model="valid"
|
|
115
|
-
disabled
|
|
116
|
-
lazy-validation
|
|
117
|
-
v-if="doesFeedbackExist"
|
|
118
|
-
>
|
|
119
|
-
<v-container
|
|
120
|
-
v-for="(
|
|
121
|
-
question, questionIndex
|
|
122
|
-
) in existingFeedback.survey_question_answers"
|
|
123
|
-
:key="questionIndex"
|
|
124
|
-
fluid
|
|
125
|
-
>
|
|
126
|
-
<div v-if="question.type === 'likert'">
|
|
127
|
-
<FeedbackQuestionLikert
|
|
128
|
-
v-model="
|
|
129
|
-
existingFeedback.survey_question_answers[
|
|
130
|
-
questionIndex
|
|
131
|
-
]
|
|
132
|
-
"
|
|
133
|
-
></FeedbackQuestionLikert>
|
|
134
|
-
</div>
|
|
135
|
-
<div v-if="question.type === 'true_false'">
|
|
136
|
-
<FeedbackQuestionTrueFalse
|
|
137
|
-
v-model="
|
|
138
|
-
existingFeedback.survey_question_answers[
|
|
139
|
-
questionIndex
|
|
140
|
-
]
|
|
141
|
-
"
|
|
142
|
-
></FeedbackQuestionTrueFalse>
|
|
143
|
-
</div>
|
|
144
|
-
<div v-if="question.type === 'open_response'">
|
|
145
|
-
<FeedbackQuestionOpenResponse
|
|
146
|
-
v-model="
|
|
147
|
-
existingFeedback.survey_question_answers[
|
|
148
|
-
questionIndex
|
|
149
|
-
]
|
|
150
|
-
"
|
|
151
|
-
:feedbackExist="true"
|
|
152
|
-
></FeedbackQuestionOpenResponse>
|
|
153
|
-
</div>
|
|
154
|
-
</v-container>
|
|
155
|
-
</v-form>
|
|
156
|
-
</v-container>
|
|
158
|
+
</v-form>
|
|
159
|
+
</v-card>
|
|
160
|
+
</div>
|
|
157
161
|
</template>
|
|
158
162
|
|
|
159
163
|
<script>
|
|
160
164
|
import _ from 'lodash'
|
|
161
165
|
import { mapGetters } from 'vuex'
|
|
162
|
-
import
|
|
163
|
-
import
|
|
164
|
-
import
|
|
166
|
+
import SurveyResult from '../../../models/SurveyResult'
|
|
167
|
+
import FeedbackQuestionLikert from './Feedback/FeedbackTemplates/FeedbackQuestionLikert.vue'
|
|
168
|
+
import FeedbackQuestionOpenResponse from './Feedback/FeedbackTemplates/FeedbackQuestionOpenResponse.vue'
|
|
169
|
+
import FeedbackQuestionTrueFalse from './Feedback/FeedbackTemplates/FeedbackQuestionTrueFalse.vue'
|
|
165
170
|
import Uuid from '~/helpers/Uuid'
|
|
166
171
|
|
|
167
|
-
import SurveyResult from '../../../models/SurveyResult'
|
|
168
172
|
import Enrollment from '~/models/Enrollment'
|
|
169
173
|
import ContentBlock from '~/models/ContentBlock'
|
|
170
174
|
import Crypto from '~/helpers/Crypto'
|