From f4069dd04dd9f0d8ed2718b6d3e0075bac764db6 Mon Sep 17 00:00:00 2001 From: andreeapescar Date: Sat, 1 Oct 2022 16:02:40 +0300 Subject: [PATCH] feat: integrate complex-rubric; deprecate rubric and multi-trait-rubric BREAKING CHANGE: addRubric and addMultiTraitRubric methods can not be used anymore; rubric item type and multi-trait-rubric item type can not be used anymore. Instead, complex-rubric will be used, which contains both rubric and multi-trait-rubric. --- src/components.d.ts | 10 +- src/components/pie-author/pie-author.tsx | 218 ++++++++++++++++------- src/demo/author-preview-add-rubric.html | 3 +- src/demo/multi-item-rubric.html | 33 +++- src/rubric-utils.ts | 18 ++ 5 files changed, 210 insertions(+), 72 deletions(-) diff --git a/src/components.d.ts b/src/components.d.ts index 10907d13..5553a943 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -13,7 +13,6 @@ import { import { ItemConfig, ItemSession, - PieContent, PieController, PieElement, PieModel, @@ -28,11 +27,12 @@ import { export namespace Components { interface PieAuthor { /** - * Utility method to add a `@pie-element/multi-trait-rubric` section to an item config when creating an item should be used before setting the config. * + * Utility method to add a `@pie-element/multi-trait-rubric` section to an item config when creating an item should be used before setting the config. + * @deprecated this method was for temporary use, was removed in the latest major release * * @param config the item config to mutate * @param multiTraitRubricModel */ - 'addMultiTraitRubricToConfig': (config: ItemConfig, multiTraitRubricModel?: any) => Promise; + 'addMultiTraitRubricToConfig': (config: ItemConfig, multiTraitRubricModel?: any) => Promise; /** * Adds a preview view which will render the content in another tab as it may appear to a student or instructor. */ @@ -43,11 +43,11 @@ export namespace Components { 'addRubric': boolean; /** * Utility method to add a `@pie-element/rubric` section to an item config when creating an item should be used before setting the config. - * @deprecated this method is for temporary use, will be removed at next major release + * @deprecated this method was for temporary use, was removed in the latest major release * @param config the item config to mutate * @param rubricModel */ - 'addRubricToConfig': (config: ItemConfig, rubricModel?: any) => Promise; + 'addRubricToConfig': (config: ItemConfig, rubricModel?: any) => Promise; /** * Provide this property override the default endpoints used by the player to retrieve JS bundles. Must be set before setting the config property. Most users will not need to use this property. */ diff --git a/src/components/pie-author/pie-author.tsx b/src/components/pie-author/pie-author.tsx index f9be6c22..16670335 100644 --- a/src/components/pie-author/pie-author.tsx +++ b/src/components/pie-author/pie-author.tsx @@ -22,7 +22,7 @@ import {pieContentFromConfig} from "../../utils/utils"; import parseNpm from "parse-package-name"; import _isEqual from "lodash/isEqual"; import _isEmpty from "lodash/isEmpty"; -import {addMultiTraitRubric, addPackageToContent, addRubric} from "../../rubric-utils"; +import {addComplexRubric, addPackageToContent} from "../../rubric-utils"; import { ModelUpdatedEvent, @@ -151,7 +151,7 @@ export class Author { if (!this.pieContentModel || !this.pieContentModel.models) { console.error('No pie content model'); - return { hasErrors: false, validatedModels: {} }; + return {hasErrors: false, validatedModels: {}}; } return (this.pieContentModel.models || []).reduce((acc: any, model) => { @@ -199,10 +199,9 @@ export class Author { } } return acc; - }, { hasErrors: false, validatedModels: {} }); + }, {hasErrors: false, validatedModels: {}}); } - constructor() { this.handleFileInputChange = (e: Event) => { const input = e.target; @@ -270,6 +269,138 @@ export class Author { } } + removeRubricFromMarkup(rubricElements) { + const tempDiv = this.doc.createElement("div"); + + tempDiv.innerHTML = this.pieContentModel.markup; + + const elsWithId = tempDiv.querySelectorAll("[id]"); + + elsWithId.forEach(el => { + const pieElName = el.tagName.toLowerCase().split("-config")[0]; + + if (rubricElements.includes(pieElName)) { + try { + tempDiv.querySelector(`#${el.id}`).remove(); + } catch (e) { + console.log(e.toString()); + } + } + }); + + const newMarkup = tempDiv.innerHTML; + + tempDiv.remove(); + + return newMarkup; + } + + removeRubricItemTypes(rubricElements) { + if (!rubricElements.length || !this.pieContentModel.models) { + return []; + } + + // save the rubric and multi-trait-rubric models that we're going to delete + const deletedModels = this.pieContentModel.models.filter(model => rubricElements.includes(model.element)); + + // delete the rubric and multi-trait-rubric elements + rubricElements.forEach(rubricElementKey => delete this.pieContentModel.elements[rubricElementKey]); + + // delete the rubric and multi-trait-rubric models + this.pieContentModel.models = this.pieContentModel.models.filter(model => !rubricElements.includes(model.element)); + + // delete the rubric and multi-trait-rubric nodes from markup + this.pieContentModel.markup = this.removeRubricFromMarkup(rubricElements); + + return deletedModels; + } + + addComplexRubric(complexRubricModel) { + // add complex-rubric + addPackageToContent( + this.pieContentModel, + "@pie-element/complex-rubric", + complexRubricModel as PieModel + ); + + this.pieContentModel = addComplexRubric(this.pieContentModel); + } + + /** + * The main flows for parsing an item config: + * + * When player gets initialized: + * 1. IF has rubric + * A. IF at least one model has withRubric = true => we replace rubric item with complex-rubric item (that will contain old rubric item data) + * B. IF no model has withRubric = true => we remove rubric item from item config + * 2. ELSE IF doesn't have rubric + * A. IF at least one model has withRubric = true => we add a complex-rubric item (that will be empty) + * B. IF no model has withRubric = true => we do nothing + * + * When a model gets updated, we check again: + * 1. IF there's at least one model that has withRubric = true + * A. IF there was a complex-rubric => we do nothing + * B. IF there was no complex-rubric => we add a complex-rubric item (that will be empty) + * 2. IF there's no model that has withRubric = true + * A. IF there was a complex-rubric => we remove complex-rubric + * B. IF there was no complex-rubric => we do nothing + */ + parseComplexRubric() { + // load all rubric and multi-trait-rubric items + const rubricElements = Object.keys(this.pieContentModel.elements).filter(key => this.pieContentModel.elements[key].indexOf('rubric') >= 0 && this.pieContentModel.elements[key].indexOf('complex-rubric') < 0); + + // delete all rubric items and store them for later conversion + const deletedModels = this.removeRubricItemTypes(rubricElements); + + // TODO replace teacherInstructionsEnabled -> withRubric + const shouldHaveComplexRubric = this.pieContentModel.models.filter(model => model.teacherInstructionsEnabled).length; + const hasComplexRubric = Object.keys(this.pieContentModel.elements).filter(key => this.pieContentModel.elements[key].indexOf('complex-rubric') >= 0).length; + + const baseComplexRubricModel = { + id: "complex-rubric", + element: "pie-complex-rubric", + }; + + if (shouldHaveComplexRubric) { + if (!deletedModels.length) { + // if should have complex rubric and there was no rubric, just add one from scratch + this.addComplexRubric(baseComplexRubricModel); + } else { + // if should have complex rubric and there were rubric items, use them to add the new complex-rubric + // TODO should we have support for multi rubrics (items/multi-items with more than one rubric item)? + const deletedModel = deletedModels[0]; + let type; + + // we check what type of rubric item we removed in order to set the type for the new complex-rubric + if (deletedModel.element.indexOf('multi-trait') >= 0) { + type = 'multiTraitRubric'; + } else if (deletedModel.element.indexOf('rubric') >= 0) { + type = 'simpleRubric'; + } + + // TODO I think there's no need for id and element + delete deletedModel.id; + delete deletedModel.element; + + this.addComplexRubric({ + ...baseComplexRubricModel, + type, + rubrics: { + [type]: deletedModel + } + }); + } + } else if (hasComplexRubric) { + // if should not have complex-rubric, but it has, then delete complex-rubric + // load all complex-rubric items + const rubricElements = Object.keys(this.pieContentModel.elements).filter(key => this.pieContentModel.elements[key].indexOf('complex-rubric') >= 0); + + this.removeRubricItemTypes(rubricElements); + } + + this.pieContentModel = pieContentFromConfig(this.pieContentModel); + } + @Watch("config") async watchConfig(newValue, oldValue) { if (newValue && !_isEqual(newValue, oldValue)) { @@ -277,6 +408,7 @@ export class Author { this.elementsLoaded = false; this._modelLoadedState = false; this.pieContentModel = pieContentFromConfig(newValue); + this.parseComplexRubric(); this.addConfigTags(this.pieContentModel); this.loadPieElements(); } catch (error) { @@ -379,9 +511,22 @@ export class Author { } }); } + if (this._modelLoadedState) { this.modelUpdated.emit(this.pieContentModel); } + + // TODO replace teacherInstructionsEnabled -> withRubric + const shouldHaveComplexRubric = this.pieContentModel.models.filter(model => model.teacherInstructionsEnabled).length; + const hasComplexRubric = Object.keys(this.pieContentModel.elements).filter(key => this.pieContentModel.elements[key].indexOf('complex-rubric') >= 0).length; + + if ((shouldHaveComplexRubric && !hasComplexRubric) + || (!shouldHaveComplexRubric && hasComplexRubric)) { + this.parseComplexRubric(); + + // TODO this causes reloading the entire element; can we load only the new item type? + this.watchConfig(this.pieContentModel, {}); + } }); this.el.addEventListener(InsertImageEvent.TYPE, this.handleInsertImage); @@ -455,78 +600,29 @@ export class Author { /** * Utility method to add a `@pie-element/rubric` section to an item config when creating an item should be used before setting the config. - * - * @deprecated this method is for temporary use, will be removed at next major release - * + * @deprecated this method was for temporary use, was removed in the latest major release * @param config the item config to mutate * @param rubricModel */ @Method() async addRubricToConfig(config: ItemConfig, rubricModel?) { - if (!rubricModel) { - rubricModel = { - id: "rubric", - element: "pie-rubric", - points: ["", "", "", ""], - maxPoints: 4, - excludeZero: false - }; - } - const configPieContent = pieContentFromConfig(config); - addPackageToContent( - configPieContent, - "@pie-element/rubric", - rubricModel as PieModel - ); - return addRubric(configPieContent); + console.error('addRubricToConfig method was for temporary use, so it was removed in the latest major release'); + + return config; } /** * Utility method to add a `@pie-element/multi-trait-rubric` section to an item config when creating an item should be used before setting the config. + * @deprecated this method was for temporary use, was removed in the latest major release ** * @param config the item config to mutate * @param multiTraitRubricModel */ @Method() async addMultiTraitRubricToConfig(config: ItemConfig, multiTraitRubricModel?) { - if (!multiTraitRubricModel) { - multiTraitRubricModel = { - id: "multi-trait-rubric", - element: "pie-multi-trait-rubric", - visibleToStudent: true, - halfScoring: false, - excludeZero: true, - pointLabels: true, - description: false, - standards: false, - scales: [ - { - maxPoints: 4, - scorePointsLabels: ['', '', '', ''], - traitLabel: 'Trait', - traits: [ - { - name: '', - standards: [], - description: '', - scorePointsDescriptors: [ - '', - '', - '', - '', - '', - ], - },] - }] - } - } - const configPieContent = pieContentFromConfig(config); - addPackageToContent( - configPieContent, - "@pie-element/multi-trait-rubric", - multiTraitRubricModel as PieModel - ); - return addMultiTraitRubric(configPieContent); + console.error('addMultiTraitRubricToConfig method was for temporary use, so it was removed in the latest major release'); + + return config; } render() { diff --git a/src/demo/author-preview-add-rubric.html b/src/demo/author-preview-add-rubric.html index 2e308398..ea201971 100644 --- a/src/demo/author-preview-add-rubric.html +++ b/src/demo/author-preview-add-rubric.html @@ -21,7 +21,8 @@ elements: { 'pie-multiple-choice': '@pie-element/multiple-choice@latest' }, - models: [MC_model], + // TODO replace teacherInstructionsEnabled -> withRubric + models: [{ ...MC_model, teacherInstructionsEnabled: true }], markup: "" }; diff --git a/src/demo/multi-item-rubric.html b/src/demo/multi-item-rubric.html index fe9259a1..6c58840c 100644 --- a/src/demo/multi-item-rubric.html +++ b/src/demo/multi-item-rubric.html @@ -25,7 +25,8 @@ "height": "200px", "showMathInput": false, "width": "500px", - "prompt": "

A bag contains 7 red cubes, 8 green cubes, 3 yellow cubes, 5 blue cubes, and 2 white cubes. All of the cubes are exactly the same size.

\r\n

A. Create a table that represents the contents of the bag and shows the probability of choosing each color when a cube is drawn from the bag without looking. Express the probabilities as fractions.

\r\n

" + "prompt": "

A bag contains 7 red cubes, 8 green cubes, 3 yellow cubes, 5 blue cubes, and 2 white cubes. All of the cubes are exactly the same size.

\r\n

A. Create a table that represents the contents of the bag and shows the probability of choosing each color when a cube is drawn from the bag without looking. Express the probabilities as fractions.

\r\n

", + teacherInstructionsEnabled: false }, { "id": "4028e4a23e1a7e7c013e28b6d228132d", @@ -33,7 +34,8 @@ "height": "200px", "showMathInput": false, "width": "500px", - "prompt": "

B. Determine the probability of randomly drawing a green cube, returning it to the bag, and then randomly drawing a blue cube.

\r\n
    \r\n
  • Explain how you can use your table to find the probability.
  • \r\n
  • Write and solve an equation that represents the calculation of the probability.
  • \r\n
\r\n

" + "prompt": "

B. Determine the probability of randomly drawing a green cube, returning it to the bag, and then randomly drawing a blue cube.

\r\n
    \r\n
  • Explain how you can use your table to find the probability.
  • \r\n
  • Write and solve an equation that represents the calculation of the probability.
  • \r\n
\r\n

", + teacherInstructionsEnabled: true }, { "width": "500px", @@ -41,7 +43,8 @@ "id": "4028e4a23e1a7e7c013e28bca5c91332", "element": "extended-text-entry", "height": "200px", - "showMathInput": false + "showMathInput": false, + teacherInstructionsEnabled: false }, { "points": [ @@ -55,12 +58,32 @@ "maxPoints": 4, "excludeZero": false, "element": "pie-rubric" - } + }, + // { + // "rubrics": { + // "simpleRubric": { + // "points": [ + // "

The response is completely incorrect, there is no response, or the response is off topic.

", + // "

The response demonstrates minimal understanding. A level 1 response is characterized by:

\r\n
    \r\n
  • A table in part A that exhibits up to 3 incorrect probabilities;
  • \r\n
  • An explanation and an equation for part B that are incorrect, incomplete, or missing;
  • \r\n
  • An explanation for part C that is incorrect, incomplete, or missing.
  • \r\n
", + // "

The response demonstrates a basic but incomplete understanding. A level 2 response is characterized by:

\r\n
    \r\n
  • A table in part A that exhibits 1–2 incorrect probabilities;
  • \r\n
  • An explanation for part B that is vague, incomplete, or missing;
  • \r\n
  • An expression or equation for part B that is correctly derived from the table in part A but may be incorrectly solved;
  • \r\n
  • An explanation for part C that is incomplete.
  • \r\n
", + // "

The response demonstrates a strong understanding, but the work contains minor errors. A level 3 response is characterized by:

\r\n
    \r\n
  • A correctly drawn table in part A that may exhibit minor errors in labeling or organization;
  • \r\n
  • An explanation for part B that contains minor flaws or is incomplete but correct;
  • \r\n
  • An equation for part B that is solvable but may contain a minor flaw leading to an incorrect result;
  • \r\n
  • An explanation in part C that indicates the event is more likely without replacement but may be incomplete or show inadequate supporting details OR leads to a well-supported conclusion that is consistent with calculation errors made in part B.
  • \r\n
", + // "

The response demonstrates a high level of understanding. A level 4 response is characterized by:

\r\n
    \r\n
  • A correctly created and labeled table in part A, with probabilities expressed as fractions, similar to:
  • \r\n
\r\n

\"image

\r\n

(Accept an unsimplified fraction (5/25) for the probability of Blue);

\r\n
    \r\n
  • A correct explanation for part B indicating that the compound probability can be calculated as the product of the individual probabilities, similar to \"For the green cube you use the fraction 8/25 from the table, and for the blue cube you use the fraction 1/5 from the table. Then you multiply them together to get 8/125\";
  • \r\n
  • A correct equation in part B, similar to 8/25 × 1/5 = p, solved as 8/125;
  • \r\n
  • A correct explanation with supporting work for part C indicating that the event is more likely when the green cube is not replaced. Justification should specify that without replacing the green cube the probability for the blue cube to be drawn second increases from 1/5 to 5/24; that the fraction 5/24 is greater than 1/5; and that there are fewer cubes in the box after the green cube is removed, so all cubes have a greater likelihood of being chosen on the second draw, or that the compound probability without replacing the green cube is 1/15, which is greater than 8/125. Accept correct models, such as tree diagrams or tables that clearly illustrate the increased likelihood and/or a clear comparison of equations and solutions representing the two events.
  • \r\n
\r\n

" + // ], + // "maxPoints": 4, + // "excludeZero": false + // }, + // "multiTraitRubric": {} + // }, + // "id": "p-4006dac7", + // "element": "pp-pie-element-complex-rubric", + // "type": "simpleRubric" + // } ], "markup": "", "elements": { "pie-rubric": "@pie-element/rubric@latest", - "extended-text-entry": "@pie-element/extended-text-entry@latest" + "extended-text-entry": "@pie-element/extended-text-entry@latest", + // "pp-pie-element-complex-rubric": "@pie-element/complex-rubric@latest" } }; diff --git a/src/rubric-utils.ts b/src/rubric-utils.ts index 60ba1fbd..6c31d8ec 100644 --- a/src/rubric-utils.ts +++ b/src/rubric-utils.ts @@ -33,6 +33,24 @@ export const addMarkupForPackage = ( return out; }; +/** + * Adds complex-rubric html to markup. + * @param content + */ +export const addComplexRubric = (content: PieContent): PieContent => { + return addMarkupForPackage( + cloneDeep(content), + "@pie-element/complex-rubric", + (id, tag, markup) => { + return ` + ${markup} +
+ <${tag} id="${id}"> +
+ `; + } + ); +}; /** * Adds rubric html to markup. * @param content