From af6d7e4f65564d4e1863d1a7f2209eff9a665899 Mon Sep 17 00:00:00 2001 From: Andrew Telnov Date: Sun, 27 Aug 2023 00:13:32 +0300 Subject: [PATCH] Rating Question - The getPlainData function result doesn't include a selected rate item and it custom 'score' property value fix #6804 --- src/base-interfaces.ts | 8 +++ src/entries/chunks/model.ts | 1 + src/question.ts | 19 ++----- src/question_baseselect.ts | 16 ++---- src/question_file.ts | 12 ++-- src/question_matrix.ts | 12 ++-- src/question_matrixdropdownbase.ts | 15 +---- src/question_paneldynamic.ts | 14 +---- src/question_rating.ts | 8 ++- src/survey.ts | 12 +--- tests/surveytests.ts | 91 +++++++++++++++++++++++++++++- 11 files changed, 133 insertions(+), 75 deletions(-) diff --git a/src/base-interfaces.ts b/src/base-interfaces.ts index e7249462ac..da5d97fb6e 100644 --- a/src/base-interfaces.ts +++ b/src/base-interfaces.ts @@ -369,3 +369,11 @@ export interface ISurveyLayoutElement { template?: string; data?: any; } +export interface IPlainDataOptions { + includeEmpty?: boolean; + includeQuestionTypes?: boolean; + includeValues?: boolean; + calculations?: Array<{ + propertyName: string, + }>; +} \ No newline at end of file diff --git a/src/entries/chunks/model.ts b/src/entries/chunks/model.ts index 336f80eb8c..41811ef991 100644 --- a/src/entries/chunks/model.ts +++ b/src/entries/chunks/model.ts @@ -72,6 +72,7 @@ export { ISurveyData, ITitleOwner, ISurveyLayoutElement, + IPlainDataOptions as IPlainData, IShortcutText } from "../../base-interfaces"; export { SurveyError } from "../../survey-error"; diff --git a/src/question.ts b/src/question.ts index f0d3b56bef..b7cc836268 100644 --- a/src/question.ts +++ b/src/question.ts @@ -1,7 +1,7 @@ import { HashTable, Helpers } from "./helpers"; import { JsonObject, Serializer, property } from "./jsonobject"; import { Base, EventBase } from "./base"; -import { IElement, IQuestion, IPanel, IConditionRunner, ISurveyImpl, IPage, ITitleOwner, IProgressInfo, ISurvey } from "./base-interfaces"; +import { IElement, IQuestion, IPanel, IConditionRunner, ISurveyImpl, IPage, ITitleOwner, IProgressInfo, ISurvey, IPlainDataOptions } from "./base-interfaces"; import { SurveyElement } from "./survey-element"; import { surveyLocalization } from "./surveyStrings"; import { AnswerRequiredError, CustomError } from "./error"; @@ -1517,15 +1517,7 @@ export class Question extends SurveyElement * * Pass an object with the `includeEmpty` property set to `false` if you want to skip empty answers. */ - public getPlainData( - options?: { - includeEmpty?: boolean, - includeQuestionTypes?: boolean, - calculations?: Array<{ - propertyName: string, - }>, - } - ): IQuestionPlainData { + public getPlainData(options?: IPlainDataOptions): IQuestionPlainData { if (!options) { options = { includeEmpty: true, includeQuestionTypes: false }; } @@ -1543,9 +1535,7 @@ export class Question extends SurveyElement questionPlainData.questionType = this.getType(); } (options.calculations || []).forEach((calculation) => { - questionPlainData[calculation.propertyName] = this[ - calculation.propertyName - ]; + questionPlainData[calculation.propertyName] = this.getPlainDataCalculatedValue(calculation.propertyName); }); if (this.hasComment) { questionPlainData.isNode = true; @@ -1566,6 +1556,9 @@ export class Question extends SurveyElement } return undefined; } + protected getPlainDataCalculatedValue(propName: string): any { + return this[propName]; + } /** * A correct answer to this question. Specify this property if you want to [create a quiz](https://surveyjs.io/form-library/documentation/design-survey-create-a-quiz). * @see SurveyModel.getCorrectAnswerCount diff --git a/src/question_baseselect.ts b/src/question_baseselect.ts index 4df988dbfe..94a27dfc2a 100644 --- a/src/question_baseselect.ts +++ b/src/question_baseselect.ts @@ -1,8 +1,8 @@ import { property, Serializer } from "./jsonobject"; import { SurveyError } from "./survey-error"; -import { ISurveyImpl, ISurvey, ISurveyData } from "./base-interfaces"; +import { ISurveyImpl, ISurvey, ISurveyData, IPlainDataOptions } from "./base-interfaces"; import { SurveyModel } from "./survey"; -import { Question } from "./question"; +import { IQuestionPlainData, Question } from "./question"; import { ItemValue } from "./itemvalue"; import { surveyLocalization } from "./surveyStrings"; import { OtherEmptyError } from "./error"; @@ -938,17 +938,11 @@ export class QuestionSelectBase extends Question { this.isDesignMode && !this.customWidget && !this.isContentElement; } public getPlainData( - options: { - includeEmpty?: boolean, - includeQuestionTypes?: boolean, - calculations?: Array<{ - propertyName: string, - }>, - } = { + options: IPlainDataOptions = { includeEmpty: true, includeQuestionTypes: false, } - ) { + ): IQuestionPlainData { var questionPlainData = super.getPlainData(options); if (!!questionPlainData) { var values = Array.isArray(this.value) ? this.value : [this.value]; @@ -990,7 +984,7 @@ export class QuestionSelectBase extends Question { protected getDisplayValueEmpty(): string { return ItemValue.getTextOrHtmlByValue(this.visibleChoices, undefined); } - protected getChoicesDisplayValue(items: ItemValue[], val: any): any { + private getChoicesDisplayValue(items: ItemValue[], val: any): any { if (val == this.otherItemValue.value) return this.otherValue ? this.otherValue : this.locOtherText.textOrHtml; const selItem = this.getSingleSelectedItem(); diff --git a/src/question_file.ts b/src/question_file.ts index 89d7fa584a..d236f2b009 100644 --- a/src/question_file.ts +++ b/src/question_file.ts @@ -1,4 +1,5 @@ -import { Question } from "./question"; +import { IPlainDataOptions } from "./base-interfaces"; +import { IQuestionPlainData, Question } from "./question"; import { property, propertyArray, Serializer } from "./jsonobject"; import { QuestionFactory } from "./questionfactory"; import { EventBase, ComputedUpdater } from "./base"; @@ -464,15 +465,10 @@ export class QuestionFileModel extends Question { return result; } public getPlainData( - options: { - includeEmpty?: boolean, - calculations?: Array<{ - propertyName: string, - }>, - } = { + options: IPlainDataOptions = { includeEmpty: true, } - ) { + ): IQuestionPlainData { var questionPlainData = super.getPlainData(options); if (!!questionPlainData && !this.isEmpty()) { questionPlainData.isNode = false; diff --git a/src/question_matrix.ts b/src/question_matrix.ts index e6401e697e..9dec95bbef 100644 --- a/src/question_matrix.ts +++ b/src/question_matrix.ts @@ -9,10 +9,11 @@ import { RequiredInAllRowsError } from "./error"; import { QuestionFactory } from "./questionfactory"; import { LocalizableString, ILocalizableOwner } from "./localizablestring"; import { QuestionDropdownModel } from "./question_dropdown"; -import { IConditionObject } from "./question"; +import { IConditionObject, IQuestionPlainData } from "./question"; import { settings } from "./settings"; import { SurveyModel } from "./survey"; import { CssClassBuilder } from "./utils/cssClassBuilder"; +import { IPlainDataOptions } from "./base-interfaces"; export interface IMatrixData { onMatrixRowChanged(row: MatrixRowModel): void; @@ -484,15 +485,10 @@ export class QuestionMatrixModel return res; } public getPlainData( - options: { - includeEmpty?: boolean, - calculations?: Array<{ - propertyName: string, - }>, - } = { + options: IPlainDataOptions = { includeEmpty: true, } - ) { + ): IQuestionPlainData { var questionPlainData = super.getPlainData(options); if (!!questionPlainData) { var values = this.createValueCopy(); diff --git a/src/question_matrixdropdownbase.ts b/src/question_matrixdropdownbase.ts index 31d948da8a..d1a42247cb 100644 --- a/src/question_matrixdropdownbase.ts +++ b/src/question_matrixdropdownbase.ts @@ -1,9 +1,9 @@ import { JsonObject, CustomPropertiesCollection, Serializer, property } from "./jsonobject"; import { QuestionMatrixBaseModel } from "./martixBase"; -import { Question, IConditionObject } from "./question"; +import { Question, IConditionObject, IQuestionPlainData } from "./question"; import { HashTable, Helpers } from "./helpers"; import { Base } from "./base"; -import { IElement, IQuestion, ISurveyData, ISurvey, ISurveyImpl, ITextProcessor, IProgressInfo, IPanel } from "./base-interfaces"; +import { IElement, IQuestion, ISurveyData, ISurvey, ISurveyImpl, ITextProcessor, IProgressInfo, IPanel, IPlainDataOptions } from "./base-interfaces"; import { SurveyElement } from "./survey-element"; import { TextPreProcessorValue, QuestionTextProcessor } from "./textPreProcessor"; import { ItemValue } from "./itemvalue"; @@ -1714,16 +1714,7 @@ export class QuestionMatrixDropdownModelBase extends QuestionMatrixBaseModel, - } = { - includeEmpty: true, - } - ) { + public getPlainData(options: IPlainDataOptions = { includeEmpty: true }): IQuestionPlainData { var questionPlainData = super.getPlainData(options); if (!!questionPlainData) { questionPlainData.isNode = true; diff --git a/src/question_paneldynamic.ts b/src/question_paneldynamic.ts index 007fd98952..8ea725c931 100644 --- a/src/question_paneldynamic.ts +++ b/src/question_paneldynamic.ts @@ -8,6 +8,7 @@ import { ISurveyImpl, ITextProcessor, IProgressInfo, + IPlainDataOptions, } from "./base-interfaces"; import { SurveyElement } from "./survey-element"; import { LocalizableString } from "./localizablestring"; @@ -15,7 +16,7 @@ import { TextPreProcessorValue, QuestionTextProcessor, } from "./textPreProcessor"; -import { Question, IConditionObject } from "./question"; +import { Question, IConditionObject, IQuestionPlainData } from "./question"; import { PanelModel } from "./panel"; import { JsonObject, property, Serializer } from "./jsonobject"; import { QuestionFactory } from "./questionfactory"; @@ -1954,16 +1955,7 @@ export class QuestionPanelDynamicModel extends Question getRootData(): ISurveyData { return this.data; } - public getPlainData( - options: { - includeEmpty?: boolean, - calculations?: Array<{ - propertyName: string, - }>, - } = { - includeEmpty: true, - } - ) { + public getPlainData(options: IPlainDataOptions = { includeEmpty: true }): IQuestionPlainData { var questionPlainData = super.getPlainData(options); if (!!questionPlainData) { questionPlainData.isNode = true; diff --git a/src/question_rating.ts b/src/question_rating.ts index 744cfc5e41..8d746bdff0 100644 --- a/src/question_rating.ts +++ b/src/question_rating.ts @@ -7,7 +7,6 @@ import { settings } from "./settings"; import { surveyLocalization } from "./surveyStrings"; import { CssClassBuilder } from "./utils/cssClassBuilder"; import { Base } from "./base"; -import { HtmlConditionItem } from "./expressionItems"; import { mergeValues } from "./utils/utils"; import { DropdownListModel } from "./dropdownListModel"; import { SurveyModel } from "./survey"; @@ -313,6 +312,7 @@ export class QuestionRatingModel extends Question { } protected getDisplayValueCore(keysAsText: boolean, value: any): any { + if(!this.useRateValues) return super.getDisplayValueCore(keysAsText, value); var res = ItemValue.getTextOrHtmlByValue(this.visibleRateValues, value); return !!res ? res : value; } @@ -390,6 +390,12 @@ export class QuestionRatingModel extends Question { public supportOther(): boolean { return false; } + protected getPlainDataCalculatedValue(propName: string): any { + const res = super.getPlainDataCalculatedValue(propName); + if(res !== undefined || !this.useRateValues || this.isEmpty()) return res; + const item = ItemValue.getItemByValue(this.visibleRateValues, this.value); + return item ? item[propName] : undefined; + } /** * Specifies a description for the minimum (first) rate value. * @see rateValues diff --git a/src/survey.ts b/src/survey.ts index 1c67fa14c9..98b831024c 100644 --- a/src/survey.ts +++ b/src/survey.ts @@ -15,6 +15,7 @@ import { IProgressInfo, IFindElement, ISurveyLayoutElement, + IPlainDataOptions, LayoutElementContainer } from "./base-interfaces"; import { SurveyElementCore, SurveyElement } from "./survey-element"; @@ -2752,16 +2753,7 @@ export class SurveyModel extends SurveyElementCore * * If you want to skip empty answers, pass an object with the `includeEmpty` property set to `false`. */ - public getPlainData( - options?: { - includeEmpty?: boolean, - includeQuestionTypes?: boolean, - includeValues?: boolean, - calculations?: Array<{ - propertyName: string, - }>, - } - ): Array { + public getPlainData(options?: IPlainDataOptions): Array { if (!options) { options = { includeEmpty: true, includeQuestionTypes: false, includeValues: false }; } diff --git a/tests/surveytests.ts b/tests/surveytests.ts index 2b41454b36..a5b7126c7b 100644 --- a/tests/surveytests.ts +++ b/tests/surveytests.ts @@ -10188,6 +10188,95 @@ QUnit.test( Serializer.removeProperty("itemvalue", "score"); } ); +QUnit.test("getPlainData - calculate itemvalue.score in rate question, Bug#6804", + function (assert) { + Serializer.addProperty("itemvalue", { name: "score:number" }); + + var survey = new SurveyModel({ + questions: [ + { + type: "rating", + name: "q1", + rateValues: [{ value: 1, score: 2, text: "Score 2" }, { value: 2, score: 4, text: "Score 4" }] + }, + { + type: "dropdown", + name: "q2", + choices: [ + { + value: "item1", + score: 1, + }, + { + value: "item2", + score: 2, + }, + { + value: "item3", + score: 3, + }, + ], + }, + { + type: "checkbox", + name: "q3", + choices: [ + { + value: "item1", + score: 1, + }, + { + value: "item2", + score: 2, + }, + { + value: "item3", + score: 3, + }, + ], + } + ] + }); + survey.data = { + q1: 2, + q2: "item2", + q3: ["item2", "item3"] + }; + const q1 = survey.getQuestionByName("q1"); + const q1PlainData = q1.getPlainData({ calculations: [{ propertyName: "score" }] }); + assert.equal(q1PlainData.displayValue, "Score 4", "display value is correct"); + assert.equal(q1PlainData.isNode, false, "it is not a node"); + assert.equal((q1PlainData).score, 4, "score is correct"); + + const plainData = survey.getPlainData({ + includeEmpty: false, + calculations: [{ propertyName: "score" }], + }); + const calculate = ( + plainData: Array<{ + isNode: boolean, + score?: number, + data?: Array, + }> = [] + ): number => { + return plainData.reduce((result, current) => { + var currentScore = current.score; + if (current.isNode) { + currentScore = calculate(current.data); + } + if (currentScore) { + return result + currentScore; + } + return result; + }, 0); + }; + + const surveyScore = calculate(plainData); + assert.equal(surveyScore, 11, "overall survey score for answered questions"); + + Serializer.removeProperty("itemvalue", "score"); + } +); QUnit.test( "question.getPlainData - select base - multiple select - other", @@ -10219,7 +10308,7 @@ QUnit.test( question.value = ["other", "giraffe"]; question.comment = "Other value text"; - var plainData = question.getPlainData(); + const plainData = question.getPlainData(); assert.deepEqual(plainData.value, ["other", "giraffe"]); assert.equal(plainData.isNode, true); assert.deepEqual(plainData.data.length, 2);