Skip to content

Commit

Permalink
Merge pull request #2257 from concord-consortium/187269652-exemplar-a…
Browse files Browse the repository at this point in the history
…lert

exemplar alert
  • Loading branch information
bgoldowsky authored Apr 17, 2024
2 parents 3d07df1 + d226798 commit a769d5b
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 21 deletions.
22 changes: 19 additions & 3 deletions cypress/e2e/functional/document_tests/exemplar_test_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ let sortWork = new SortedWork,
drawToolTile = new DrawToolTile,
textToolTile = new TextToolTile;

// This unit has `initiallyHideExemplars` set, and an exemplar defined in curriculum
// The qaConfigSubtabs unit referenced here has `initiallyHideExemplars` set, and an exemplar defined in curriculum
const queryParams1 = `${Cypress.config("qaConfigSubtabsUnitStudent5")}`;
const exemplarName = "Ivan Idea: First Exemplar";
const exemplarName = "First Exemplar";
const exemplarInfo = "Ivan Idea: First Exemplar";

function beforeTest(params) {
cy.clearQAData('all');
Expand Down Expand Up @@ -43,6 +44,7 @@ context('Exemplar Documents', function () {
cy.openTopTab('sort-work');
sortWork.checkDocumentInGroup("No Group", exemplarName);
sortWork.getSortWorkItemByTitle(exemplarName).parents('.list-item').should("have.class", "private");
clueCanvas.getStickyNotePopup().should("not.exist");

cy.log("Create 3 drawing tiles with 3 events");
clueCanvas.addTile("drawing");
Expand Down Expand Up @@ -77,6 +79,17 @@ context('Exemplar Documents', function () {

// Now the exemplar should be revealed
sortWork.getSortWorkItemByTitle(exemplarName).parents('.list-item').should("not.have.class", "private");

clueCanvas.getStickyNotePopup().should("exist").should("be.visible")
.should("contain.text", "Nice work, you can now see a new example for this lesson")
.should("contain.text", exemplarName);

cy.log("Open exemplar");
sortWork.getFocusDocument().should('not.exist');
clueCanvas.getStickyNoteLink().should("be.visible").click();
sortWork.getFocusDocument().should('be.visible');
sortWork.getFocusDocumentTitle().should("contain.text", exemplarName);

});

it('Exemplar revealed by 3 drawings that include labels', function () {
Expand Down Expand Up @@ -104,6 +117,9 @@ context('Exemplar Documents', function () {
sortWork.getSortWorkItemByTitle(exemplarName).parents('.list-item').should("have.class", "private");
addText(300, 50, "one two three four five six seven eight nine ten");
// Now the exemplar should be revealed
sortWork.getSortWorkItemByTitle(exemplarName).parents('.list-item').should("not.have.class", "private");
sortWork.getSortWorkItemByTitle(exemplarInfo).parents('.list-item').should("not.have.class", "private");
clueCanvas.getStickyNotePopup().should("exist").should("be.visible")
.should("contain.text", "Nice work, you can now see a new example for this lesson")
.should("contain.text", exemplarName);
});
});
9 changes: 9 additions & 0 deletions cypress/support/elements/common/SortedWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ class SortedWork {
cy.get(".sort-work-view .sorted-sections .section-header-label").should("contain", label);
});
}

getFocusDocument() {
return cy.get('.sort-work-view .focus-document.sort-work');
}

getFocusDocumentTitle() {
return this.getFocusDocument().find('.document-title');
}

}

export default SortedWork;
8 changes: 8 additions & 0 deletions cypress/support/elements/common/cCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ class ClueCanvas {
this.getShareButton().find('.ball').invoke('attr', 'class').should('not.contain', 'toggle-on');
}

getStickyNotePopup() {
return cy.get('div.sticky-note-popup');
}

getStickyNoteLink() {
return this.getStickyNotePopup().find('a');
}

getToolPalette() {
return cy.get('.primary-workspace> .toolbar');
}
Expand Down
22 changes: 21 additions & 1 deletion src/components/document/document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MyWorkDocumentOrBrowser } from "./mywork-document-or-browser";
import { BaseComponent, IBaseProps } from "../base";
import { DocumentModelType } from "../../models/document/document";
import { LearningLogDocument, LearningLogPublication } from "../../models/document/document-types";
import { logDocumentEvent } from "../../models/document/log-document-event";
import { logDocumentEvent, logDocumentViewEvent } from "../../models/document/log-document-event";
import { IToolbarModel } from "../../models/stores/problem-configuration";
import { SupportType, TeacherSupportModelType, AudienceEnum } from "../../models/stores/supports";
import { WorkspaceModelType } from "../../models/stores/workspace";
Expand Down Expand Up @@ -304,6 +304,24 @@ export class DocumentComponent extends BaseComponent<IProps, IState> {
);
}

private openDocument(key: string) {
const doc = this.stores.documents.getDocument(key);
if (doc) {
this.stores.persistentUI.openResourceDocument(doc);
logDocumentViewEvent(doc);
}
}

private renderDocumentLink(key: string|undefined) {
if (!key) return null;
const title = this.stores.documents.getDocument(key)?.title;
if (title) {
return (<a onClick={() => this.openDocument(key)} href="#">{title}</a>);
} else {
return "[broken link!]";
}
}

private renderStickyNotesPopup() {
const { user } = this.stores;
const { stickyNotes, showNotes} = this.getStickyNoteData();
Expand Down Expand Up @@ -337,6 +355,8 @@ export class DocumentComponent extends BaseComponent<IProps, IState> {
</div>
<div className="sticky-note-popup-item-content">
{support.content}
{ ' ' }
{ this.renderDocumentLink(support.linkedDocumentKey) }
</div>
</div>
);
Expand Down
6 changes: 6 additions & 0 deletions src/components/document/sort-work-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import { DEBUG_DOC_LIST } from "../../lib/debug";
import { SortWorkDocumentArea } from "./sort-work-document-area";
import { ENavTab } from "../../models/view/nav-tabs";
import { DocListDebug } from "./doc-list-debug";
import { logDocumentViewEvent } from "../../models/document/log-document-event";

import "../thumbnail/document-type-collection.sass";
import "./sort-work-view.scss";

/**
* Resources pane view of class work and exemplars.
* Various options for sorting the display are available - by user, by group, by tools used, etc.
*/
export const SortWorkView: React.FC = observer(function SortWorkView() {
const { appConfig, persistentUI, sortedDocuments } = useStores();

Expand Down Expand Up @@ -56,6 +61,7 @@ export const SortWorkView: React.FC = observer(function SortWorkView() {
//******************************* Click to Open Document ***************************************
const handleSelectDocument = (document: DocumentModelType) => {
persistentUI.openSubTabDocument(ENavTab.kSortWork, ENavTab.kSortWork, document.key);
logDocumentViewEvent(document);
};

const tabState = persistentUI.tabs.get(ENavTab.kSortWork);
Expand Down
8 changes: 2 additions & 6 deletions src/components/navigation/document-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { useAppConfig, useLocalDocuments, useProblemStore, useStores,
import { useUserContext } from "../../hooks/use-user-context";
import { ISubTabSpec, NavTabModelType, kBookmarksTabTitle } from "../../models/view/nav-tabs";
import { DocumentType } from "../../models/document/document-types";
import { LogEventName } from "../../lib/logger-types";
import { logDocumentEvent } from "../../models/document/log-document-event";
import { logDocumentViewEvent } from "../../models/document/log-document-event";
import { DocumentModelType } from "../../models/document/document";
import { EditableDocumentContent } from "../document/editable-document-content";
import { getDocumentDisplayTitle } from "../../models/document/document-utils";
Expand Down Expand Up @@ -99,10 +98,7 @@ export const DocumentView = observer(function DocumentView({tabSpec, subTab}: IP
loadDocumentContent(document);
}
persistentUI.openSubTabDocument(tabSpec.tab, subTab.label, document.key);
const logEvent = document.isRemote
? LogEventName.VIEW_SHOW_TEACHER_NETWORK_COMPARISON_DOCUMENT
: LogEventName.VIEW_SHOW_COMPARISON_DOCUMENT;
logDocumentEvent(logEvent, { document });
logDocumentViewEvent(document);
}
};

Expand Down
7 changes: 2 additions & 5 deletions src/components/navigation/section-document-or-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { useQueryClient } from 'react-query';
import { DocumentModelType } from "../../models/document/document";
import { logDocumentEvent } from "../../models/document/log-document-event";
import { logDocumentViewEvent } from "../../models/document/log-document-event";
import { ISubTabSpec, NavTabModelType, kBookmarksTabTitle } from "../../models/view/nav-tabs";
import { useAppConfig, useClassStore, useProblemStore, useStores,
useUserStore, usePersistentUIStore } from "../../hooks/use-stores";
Expand Down Expand Up @@ -72,10 +72,7 @@ export const SectionDocumentOrBrowser: React.FC<IProps> = observer(function Sect
loadDocumentContent(document);
}
persistentUI.openSubTabDocument(tabSpec.tab, selectedSubTab.label, document.key);
const logEvent = document.isRemote
? LogEventName.VIEW_SHOW_TEACHER_NETWORK_COMPARISON_DOCUMENT
: LogEventName.VIEW_SHOW_COMPARISON_DOCUMENT;
logDocumentEvent(logEvent, { document });
logDocumentViewEvent(document);
}
};

Expand Down
5 changes: 4 additions & 1 deletion src/lib/logger-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum LogEventName {
VIEW_SHOW_DOCUMENT,
VIEW_SHOW_COMPARISON_DOCUMENT,
VIEW_SHOW_TEACHER_NETWORK_COMPARISON_DOCUMENT,
VIEW_SHOW_EXEMPLAR_DOCUMENT,
VIEW_ENTER_FOUR_UP,
VIEW_ENTER_ONE_UP,
VIEW_FOUR_UP_RESIZED,
Expand Down Expand Up @@ -90,5 +91,7 @@ export enum LogEventName {
SPARROW_TITLE_CHANGE,
SPARROW_SHOW_HIDE,

LOADING_MEASUREMENTS
LOADING_MEASUREMENTS,

EXEMPLAR_VISIBILITY_UPDATE
}
9 changes: 6 additions & 3 deletions src/models/curriculum/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,19 @@ export function createTextSupport(text: string) {
return SupportModel.create({ type: ESupportType.text, content: text });
}

export function createStickyNote(text: string) {
return SupportModel.create({ type: ESupportType.text, mode: ESupportMode.stickyNote, content: text });
export function createStickyNote(text: string, linkedDocumentKey?: string) {
return SupportModel.create({
type: ESupportType.text, mode: ESupportMode.stickyNote, content: text, linkedDocumentKey });
}

export const SupportModel = types
.model("Support", {
type: types.enumeration<ESupportType>("SupportType", Object.values(ESupportType)),
mode: types.maybe(types.enumeration<ESupportMode>("SupportMode", Object.values(ESupportMode))),
// text string or path to document
content: types.string
content: types.string,
// if set, sticky note will link to this
linkedDocumentKey: types.maybe(types.string)
})
.preProcessSnapshot(snapshot => {
const legacySupport = snapshot as any as LegacySupportSnapshot;
Expand Down
15 changes: 15 additions & 0 deletions src/models/document/log-document-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Logger } from "../../lib/logger";
import { LogEventMethod, LogEventName } from "../../lib/logger-types";
import { UserModelType } from "../stores/user";
import { DocumentModelType } from "./document";
import { ExemplarDocument } from "./document-types";

interface ITeacherNetworkInfo {
networkClassHash?: string;
Expand Down Expand Up @@ -44,3 +45,17 @@ export function logDocumentEvent(event: LogEventName, _params: IDocumentLogEvent
const params = processDocumentEventParams(_params, Logger.stores);
Logger.log(event, params, method);
}

/**
* Convenience function to log the appropriate type of VIEW_SHOW_*_DOCUMENT event for the document.
* @param document
*/
export function logDocumentViewEvent(document: DocumentModelType) {
const event =
document.type === ExemplarDocument
? LogEventName.VIEW_SHOW_EXEMPLAR_DOCUMENT
: document.isRemote
? LogEventName.VIEW_SHOW_TEACHER_NETWORK_COMPARISON_DOCUMENT
: LogEventName.VIEW_SHOW_COMPARISON_DOCUMENT;
logDocumentEvent(event, { document });
}
20 changes: 20 additions & 0 deletions src/models/document/log-exemplar-document-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Logger } from "../../lib/logger";
import { LogEventMethod, LogEventName } from "../../lib/logger-types";
import { IDocumentLogEvent } from "./log-document-event";

// Events that have to do with making Exemplar documents visible, or invisible, to users.

export interface IExemplarDocumentLogEvent extends IDocumentLogEvent {
// True if after this change the exemplar is visible to the user
visibleToUser: boolean,
// What caused the change? Manual action, or a rule in the system.
// In the future we might have other options, eg AI
changeSource: "rule" | "manual",
// If the change was based on a rule, this is the name of the rule.
rule?: string
}

export function logExemplarDocumentEvent (event: LogEventName.EXEMPLAR_VISIBILITY_UPDATE,
params: IExemplarDocumentLogEvent, method?: LogEventMethod) {
Logger.log(event, params, method);
}
21 changes: 20 additions & 1 deletion src/models/stores/exemplar-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { safeJsonParse } from "../../utilities/js-utils";
import { LogEventName } from "../../lib/logger-types";
import { DocumentsModelType } from "./documents";
import { Logger, LogMessage } from "../../lib/logger";
import { AudienceEnum, AudienceModel } from "./supports";
import { createStickyNote } from "../curriculum/support";
import { logExemplarDocumentEvent } from "../document/log-exemplar-document-event";
import { allExemplarControllerRules } from "./exemplar-controller-rules";
import { kDrawingTileType } from "../../plugins/drawing/model/drawing-types";
import { kTextTileType } from "../tiles/text/text-content";
Expand Down Expand Up @@ -48,6 +51,12 @@ export const BaseExemplarControllerModel = types
setExemplarVisibility(key: string, isVisible: boolean) {
if (self.db) {
self.db.firebase.ref(self.firebasePath).child(`${key}/visible`).set(isVisible);
if (isVisible) {
// Notify user with a sticky note
const audience = AudienceModel.create({type: AudienceEnum.user, identifier: self.db.stores.user.id});
const message = "Nice work, you can now see a new example for this lesson:";
self.db.createSupport(createStickyNote(message, key), "", audience);
}
}
}
}))
Expand All @@ -65,6 +74,7 @@ export const BaseExemplarControllerModel = types
if (chosen) {
self.setExemplarVisibility(chosen.key, true);
}
return chosen;
},
/**
* Moves our records of the tiles from the 'inProgress' map to the 'complete' map.
Expand All @@ -91,8 +101,17 @@ export const ExemplarControllerModel = BaseExemplarControllerModel
for (const rule of allExemplarControllerRules) {
const result = rule.test(self);
if (result) {
self.showRandomExemplar();
const chosen = self.showRandomExemplar();
rule.reset(self, result);
if (chosen) {
logExemplarDocumentEvent(LogEventName.EXEMPLAR_VISIBILITY_UPDATE,
{
document: chosen,
visibleToUser: true,
changeSource: "rule",
rule: rule.name
});
}
}
}
},
Expand Down
8 changes: 7 additions & 1 deletion src/models/stores/persistent-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isWorkspaceModelSnapshot, WorkspaceModel } from "./workspace";
import { DocumentModelType } from "../document/document";
import { ENavTab } from "../view/nav-tabs";
import { buildSectionPath, getCurriculumMetadata } from "../../../functions/src/shared";
import { LearningLogDocument, LearningLogPublication, PersonalDocument,
import { ExemplarDocument, LearningLogDocument, LearningLogPublication, PersonalDocument,
PersonalPublication, PlanningDocument, ProblemDocument,
ProblemPublication, SupportPublication } from "../document/document-types";
import { UserModelType } from "./user";
Expand Down Expand Up @@ -223,6 +223,9 @@ export const PersistentUIModel = types
subTab = groupId;
}
}
if (navTab === ENavTab.kSortWork) {
subTab = ENavTab.kSortWork;
}

if (!subTab) {
console.warn("Can't find subTab for doc", getSnapshot(doc));
Expand Down Expand Up @@ -293,6 +296,9 @@ const docTypeToNavTab: Record<string, ENavTab | undefined> = {
[LearningLogPublication]: ENavTab.kClassWork,
[PersonalPublication]: ENavTab.kClassWork,
[SupportPublication]: ENavTab.kClassWork,

// Other
[ExemplarDocument]: ENavTab.kSortWork,
};


Expand Down

0 comments on commit a769d5b

Please sign in to comment.