Skip to content

Commit

Permalink
feat: Change when hasBeenReviewed flag is set [PT-187220438]
Browse files Browse the repository at this point in the history
Changes:

1. Changed hasBeenReviewed flag from set when feedback is saved to set when feedback loaded from Firebase.
2. Updated feedback icons to also show when score is set and changed hasRubricFeedback to ignore 0 scores.
3. Changed getStudentFeedbacks() selector to not filter on hasBeenReviewed flag.
  • Loading branch information
dougmartin committed Mar 13, 2024
1 parent 512db31 commit 7c24ed5
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 20 deletions.
4 changes: 3 additions & 1 deletion cypress/integration/feedback.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ describe("Provide Feedback", function() {
cy.get("[data-cy=feedbackBox]").clear().type(" Your work was great!").blur();
cy.get(".feedback-complete input").check();
});
feedback.getScoredStudentsCount().should("be.visible").and("contain", "1");
// the hasBeenReviewed flag use to filter to get this count is not updated yet so this test is invalid
// this report is no longer used so this was just commented out
// feedback.getScoredStudentsCount().should("be.visible").and("contain", "1");
});

it("Shows dialog when clicked", function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const ActivityFeedbackScore: React.FC<IProps> = (props) => {
newScore = firebase.firestore.FieldValue.delete() as any;
}

updateActivityFeedback(activityId, activityIndex, studentId, {score: newScore, hasBeenReviewed: true});
updateActivityFeedback(activityId, activityIndex, studentId, {score: newScore });

Check warning on line 40 in js/components/portal-dashboard/feedback/activity-feedback-score.tsx

View check run for this annotation

Codecov / codecov/patch

js/components/portal-dashboard/feedback/activity-feedback-score.tsx#L40

Added line #L40 was not covered by tests
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ export const ActivityFeedbackTextarea: React.FC<IProps> = (props) => {

const updateFeedback = (logUpdate: boolean) => {
if (activityId && studentId && updateActivityFeedback && textareaRef.current?.value !== undefined) {
const hasBeenReviewed = textareaRef.current.value !== "" ? true : false;
if (logUpdate) {
trackEvent("Portal-Dashboard", "AddActivityLevelFeedback", { label: feedback, parameters: { activityId, studentId }});
}
props.setFeedbackSortRefreshEnabled(true);
updateActivityFeedback(activityId, activityIndex, studentId, {feedback: textareaRef.current?.value, hasBeenReviewed});
updateActivityFeedback(activityId, activityIndex, studentId, {feedback: textareaRef.current?.value});
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const ActivityLevelFeedbackStudentRows: React.FC<IProps> = (props) => {
const score = feedbackData.get("score");
const hasRubric = rubric;
const { rubricFeedback } = feedbackData.toJS();
const hasFeedbacks = feedback || (hasRubric && hasRubricFeedback(rubric, rubricFeedback));
const hasFeedbacks = feedback || (hasRubric && hasRubricFeedback(rubric, rubricFeedback)) || score !== undefined;
const feedbackBadge = hasFeedbacks ? <GivenFeedbackActivityBadgeIcon /> : <AwaitingFeedbackActivityBadgeIcon />;

return (
Expand Down
6 changes: 2 additions & 4 deletions js/components/portal-dashboard/feedback/rubric-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Map } from "immutable";
import Markdown from "markdown-to-jsx";
import ReactTooltip from "react-tooltip";
import LaunchIcon from "../../../../img/svg-icons/launch-icon.svg";
import { hasRubricFeedback } from "../../../util/activity-feedback-helper";
import { Rubric, getFeedbackColor } from "./rubric-utils";
import { ScoringSettings } from "../../../util/scoring";
import { RUBRIC_SCORE } from "../../../util/scoring-constants";
Expand Down Expand Up @@ -153,11 +152,10 @@ export class RubricTableContainer extends React.PureComponent<IProps> {
}

private rubricChange = (rubricFeedback: any, studentId: string) => {
const { rubric, activityId, activityIndex, updateActivityFeedback } = this.props;
const hasBeenReviewed = hasRubricFeedback(rubric, rubricFeedback);
const { activityId, activityIndex, updateActivityFeedback } = this.props;

if (activityId && studentId) {
updateActivityFeedback(activityId, activityIndex, studentId, { rubricFeedback, hasBeenReviewed });
updateActivityFeedback(activityId, activityIndex, studentId, { rubricFeedback });
}
};
}
5 changes: 3 additions & 2 deletions js/components/portal-dashboard/student-answers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,9 @@ export class StudentAnswers extends React.PureComponent<IProps> {
: progress > 0 ? css.progress : "";
const rubricFeedback = activityFeedbacks.size > 0 && activityStudentFeedback?.get("rubricFeedback")?.toJS();
const rubricFeedbackGiven = rubric && hasRubricFeedback(rubric, rubricFeedback);
const hasFeedbacks = (activityFeedbacks.size > 0 && activityStudentFeedback)
&& (activityStudentFeedback.get("feedback") !== "" || rubricFeedbackGiven);
const textFeedbackGiven = (activityStudentFeedback?.get("feedback") ?? "") !== "";
const scoreFeedbackGiven = activityStudentFeedback?.get("score") !== undefined;
const hasFeedbacks = activityFeedbacks.size > 0 && (textFeedbackGiven || rubricFeedbackGiven || scoreFeedbackGiven);

return (
<div className={`${css.activityProgress} ${progressClass}`} key={activity.get("id")}>
Expand Down
60 changes: 59 additions & 1 deletion js/reducers/feedback-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@ import { Map, fromJS } from "immutable";
import {
RECEIVE_QUESTION_FEEDBACKS,
RECEIVE_FEEDBACK_SETTINGS,
RECEIVE_ACTIVITY_FEEDBACKS
RECEIVE_ACTIVITY_FEEDBACKS,
RECEIVE_RESOURCE_STRUCTURE
} from "../actions";
import { RecordFactory } from "../util/record-factory";
import { normalizeResourceJSON } from "../core/transform-json-response";
import { getScoringSettings } from "../util/scoring";
import { MANUAL_SCORE, RUBRIC_SCORE } from "../util/scoring-constants";
import { Rubric } from "../components/portal-dashboard/feedback/rubric-utils";

export interface IFeedbackState {
settings: Map<any, any>;
questionFeedbacks: Map<any, any>;
activityFeedbacks: Map<any, any>;
hasScoredQuestions: Map<any, boolean>;
}

const INITIAL_FEEDBACK_STATE = RecordFactory<IFeedbackState>({
settings: Map({}),
questionFeedbacks: Map({}),
activityFeedbacks: Map({}),
hasScoredQuestions: Map({}),
});

export class FeedbackState extends INITIAL_FEEDBACK_STATE implements IFeedbackState {
Expand All @@ -25,9 +32,12 @@ export class FeedbackState extends INITIAL_FEEDBACK_STATE implements IFeedbackSt
settings: Map<any, any>;
questionFeedbacks: Map<any, any>;
activityFeedbacks: Map<any, any>;
hasScoredQuestions: Map<any, boolean>;
}

export default function feedback(state = new FeedbackState({}), action: any) {
let hasScoredQuestions: any;

switch (action.type) {
case RECEIVE_FEEDBACK_SETTINGS:
return state.set("settings", fromJS(action.response) as Map<any, any>);
Expand All @@ -37,8 +47,24 @@ export default function feedback(state = new FeedbackState({}), action: any) {
return map;
}, {});
return state.set("questionFeedbacks", fromJS(feedbacks) as Map<any, any>);
case RECEIVE_RESOURCE_STRUCTURE:
const data = normalizeResourceJSON(action.response);
const questions = data.entities.questions || {};
hasScoredQuestions = Object.values(questions).reduce<Record<string,boolean>>((acc, question) => {
if (question.activity) {
const isScoredQuestion = question.type === "multiple_choice" && question.scored === true;
acc[question.activity] = acc[question.activity] || isScoredQuestion;
}
return acc;
}, {});
return state.set("hasScoredQuestions", fromJS(hasScoredQuestions) as Map<any, any>);
case RECEIVE_ACTIVITY_FEEDBACKS:
const settings: any = state.get("settings")?.toJS();
hasScoredQuestions = state.get("hasScoredQuestions")?.toJS();
const actFeedbacks = action.response.reduce((map: any, feedback: any) => {
if (feedback.activityId) {
feedback.hasBeenReviewed = computeHasBeenReviewed({feedback, settings, hasScoredQuestions});
}
map[`${feedback.activityId}-${feedback.platformStudentId}`] = feedback;
return map;
}, {});
Expand All @@ -47,3 +73,35 @@ export default function feedback(state = new FeedbackState({}), action: any) {
return state;
}
}

export const computeHasBeenReviewed = (params: {feedback: any; settings: any; hasScoredQuestions: any}) => {
const {feedback, settings, hasScoredQuestions} = params;

const rubric: Rubric|undefined = settings?.rubric;
const initialScoringSettings = settings?.activitySettings?.[feedback.activityId];
const scoreType = getScoringSettings(initialScoringSettings, {
rubric,
hasScoredQuestions: hasScoredQuestions?.[feedback.activityId],
}).scoreType;

const hasScore = !isNaN(parseInt(feedback.score, 10));
const hasText = (feedback.feedback ?? "").length > 0;

let hasFilledRubric = false;
const rubricFeedback = feedback.rubricFeedback;
if (rubric && rubricFeedback) {
const scoredValues = Object.values(rubricFeedback).filter((v: any) => v.score > 0);
hasFilledRubric = scoredValues.length === rubric.criteria.length;
}

switch (scoreType) {
case MANUAL_SCORE:
return hasScore;

case RUBRIC_SCORE:
return hasFilledRubric;

default:
return hasText || hasFilledRubric;
}
};
7 changes: 2 additions & 5 deletions js/selectors/activity-feedback-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,13 @@ export const getStudentFeedbacks = (activity, students, activityFeedbacks, progr
const feedbacksNeedingReview = feedbacks.filter(fb => fb.get("activityStarted") && !fb.get("hasBeenReviewed"));
const numFeedbacksNeedingReview = feedbacksNeedingReview.size;

const reviewedFeedback = activityFeedbacks
.filter(f => f.get("hasBeenReviewed") === true);

const scores = reviewedFeedback
const scores = activityFeedbacks
.map(f => f.get("score"))
.filter(x => x)
.toList()
.toJS();

const rubricFeedbacks = reviewedFeedback
const rubricFeedbacks = activityFeedbacks
.map(f => f.get("rubricFeedback"))
.filter(x => x)
.toList()
Expand Down
2 changes: 1 addition & 1 deletion js/util/activity-feedback-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function hasRubricFeedback(rubric: any, rubricFeedback: any) {
let numFeedback = 0;
rubric.criteria.forEach((crit: any) => {
if (rubricFeedback && rubricFeedback[crit.id]) {
if (rubricFeedback[crit.id].id !== "") {
if (rubricFeedback[crit.id].score > 0 && rubricFeedback[crit.id].id !== "") {
numFeedback++;
}
}
Expand Down
21 changes: 19 additions & 2 deletions js/util/scoring.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { List, Map } from "immutable";
import { Rubric } from "../components/portal-dashboard/feedback/rubric-utils";
import { RUBRIC_SCORE, NO_SCORE, AUTOMATIC_SCORE, MANUAL_SCORE } from "./scoring-constants";
import { computeRubricMaxScore, getRubricScores } from "../selectors/activity-feedback-selectors";
import { computeRubricMaxScore } from "../selectors/activity-feedback-selectors";

// the scoring constants are defined in a JavaScript file so we create the sum type here
const scoreTypes = [MANUAL_SCORE, NO_SCORE, RUBRIC_SCORE, AUTOMATIC_SCORE] as const;
Expand Down Expand Up @@ -54,7 +54,7 @@ export const computeAvgScore = (scoringSettings: ScoringSettings, rubric: Rubric

switch (scoreType) {
case RUBRIC_SCORE:
const rubricScores = getRubricScores(rubric, feedbacks);
const rubricScores = getCompletedRubricScores(rubric, feedbacks);
const {totalScore, scoredQuestions} = rubricScores.reduce((acc, cur) => {
let {totalScore, scoredQuestions} = acc;
if (cur) {
Expand Down Expand Up @@ -83,6 +83,23 @@ export const computeAvgScore = (scoringSettings: ScoringSettings, rubric: Rubric
return {avgScore, avgScoreMax};
};

export const getCompletedRubricScores = (rubric: Rubric, feedbacks: any) => {
let scores: Map<any, any> = Map({});
const numCriteria = rubric.criteria.length;
feedbacks.feedbacks.forEach((feedback: any) => {
const key = feedback.get("platformStudentId");
const rubricFeedback = feedback.get("rubricFeedback");
let score = null;
const nonZeroScores = rubricFeedback?.map((v: any, k: any) => v.get("score")).filter((n: number) => n > 0);
if (nonZeroScores?.size === numCriteria) {
score = nonZeroScores.reduce((p: number, n: number) => p + n);
scores.set(key, score);

Check warning on line 96 in js/util/scoring.ts

View check run for this annotation

Codecov / codecov/patch

js/util/scoring.ts#L95-L96

Added lines #L95 - L96 were not covered by tests
}
scores = scores.set(key, score);
});
return scores;
};

export const getRubricDisplayScore = (rubric: Rubric, rubricFeedback: any, maxScore?: number) => {
let displayScore = "N/A";

Expand Down

0 comments on commit 7c24ed5

Please sign in to comment.