Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/twelve-hornets-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

Adding rendering events to grapher, graded-group, graded-group-set, matrix, orderer, passage, and plotter
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {act, screen} from "@testing-library/react";
import {userEvent as userEventLib} from "@testing-library/user-event";

import {testDependencies} from "../../../../../testing/test-dependencies";
import {
testDependencies,
testDependenciesV2,
} from "../../../../../testing/test-dependencies";
import * as Dependencies from "../../dependencies";
import {renderQuestion} from "../__testutils__/renderQuestion";

Expand All @@ -10,6 +13,7 @@ import {
groupSetRadioRationaleQuestion,
} from "./graded-group-set.testdata";

import type {PerseusDependenciesV2} from "../../types";
import type {PerseusRenderer} from "@khanacademy/perseus-core";
import type {UserEvent} from "@testing-library/user-event";

Expand Down Expand Up @@ -41,6 +45,28 @@ describe("graded group set widget", () => {
expect(container).toMatchSnapshot();
});

it("should send analytics event when widget is rendered", () => {
// Arrange
const onAnalyticsEventSpy = jest.fn();
const depsV2: PerseusDependenciesV2 = {
...testDependenciesV2,
analytics: {onAnalyticsEvent: onAnalyticsEventSpy},
};

// Act
renderQuestion(article1, undefined, undefined, undefined, depsV2);

// Assert
expect(onAnalyticsEventSpy).toHaveBeenCalledWith({
type: "perseus:widget:rendered:ti",
payload: {
widgetSubType: "null",
widgetType: "graded-group-set",
widgetId: "graded-group-set 1",
},
});
});

it("should render error message when no current group", () => {
// Arrange
const articleWithNoGradedGroups: PerseusRenderer = {
Expand Down
27 changes: 24 additions & 3 deletions packages/perseus/src/widgets/graded-group-set/graded-group-set.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import classNames from "classnames";
import * as React from "react";

import {PerseusI18nContext} from "../../components/i18n-context";
import {withDependencies} from "../../components/with-dependencies";
import {getDependencies} from "../../dependencies";
import {
gray76,
Expand All @@ -19,7 +20,13 @@ import a11y from "../../util/a11y";
import {getPromptJSON} from "../../widget-ai-utils/graded-group-set/graded-group-set-ai-utils";
import {GradedGroup} from "../graded-group/graded-group";

import type {FocusPath, Widget, WidgetExports, WidgetProps} from "../../types";
import type {
FocusPath,
PerseusDependenciesV2,
Widget,
WidgetExports,
WidgetProps,
} from "../../types";
import type {GradedGroupSetPromptJSON} from "../../widget-ai-utils/graded-group-set/graded-group-set-ai-utils";
import type {
PerseusGradedGroupSetWidgetOptions,
Expand Down Expand Up @@ -90,6 +97,7 @@ class Indicators extends React.Component<IndicatorsProps> {

type Props = WidgetProps<PerseusGradedGroupSetWidgetOptions> & {
trackInteraction: () => void;
dependencies: PerseusDependenciesV2;
};

type DefaultProps = {
Expand All @@ -116,6 +124,17 @@ class GradedGroupSet extends React.Component<Props, State> implements Widget {
currentGroup: 0,
};

componentDidMount(): void {
this.props.dependencies.analytics.onAnalyticsEvent({
type: "perseus:widget:rendered:ti",
payload: {
widgetType: "graded-group-set",
widgetSubType: "null",
widgetId: this.props.widgetId,
},
});
}

shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
return nextProps !== this.props || nextState !== this.state;
}
Expand Down Expand Up @@ -228,15 +247,17 @@ class GradedGroupSet extends React.Component<Props, State> implements Widget {
}
}

const WrappedGradedGroupSet = withDependencies(GradedGroupSet);

export default {
name: "graded-group-set",
displayName: "Graded group set (articles only)",
widget: GradedGroupSet,
widget: WrappedGradedGroupSet,
// TODO(michaelpolyak): This widget should be available for articles only
hidden: false,
tracking: "all",
isLintable: true,
} satisfies WidgetExports<typeof GradedGroupSet>;
} satisfies WidgetExports<typeof WrappedGradedGroupSet>;

const styles = StyleSheet.create({
top: {
Expand Down
29 changes: 27 additions & 2 deletions packages/perseus/src/widgets/graded-group/graded-group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import {describe, beforeEach, it} from "@jest/globals";
import {act, screen} from "@testing-library/react";
import {userEvent as userEventLib} from "@testing-library/user-event";

import {testDependencies} from "../../../../../testing/test-dependencies";
import {
testDependencies,
testDependenciesV2,
} from "../../../../../testing/test-dependencies";
import {renderArticle} from "../../__tests__/article-renderer.test";
import * as Dependencies from "../../dependencies";
import {renderQuestion} from "../__testutils__/renderQuestion";
Expand All @@ -12,7 +15,7 @@ import {
groupedRadioRationaleQuestion,
} from "./graded-group.testdata";

import type {APIOptions} from "../../types";
import type {APIOptions, PerseusDependenciesV2} from "../../types";
import type {PerseusArticle} from "@khanacademy/perseus-core";
import type {UserEvent} from "@testing-library/user-event";

Expand Down Expand Up @@ -56,6 +59,28 @@ describe("graded-group", () => {
);
});

it("should send analytics event when widget is rendered", () => {
// Arrange
const onAnalyticsEventSpy = jest.fn();
const depsV2: PerseusDependenciesV2 = {
...testDependenciesV2,
analytics: {onAnalyticsEvent: onAnalyticsEventSpy},
};

// Act
renderQuestion(question1, undefined, undefined, undefined, depsV2);

// Assert
expect(onAnalyticsEventSpy).toHaveBeenCalledWith({
type: "perseus:widget:rendered:ti",
payload: {
widgetSubType: "null",
widgetType: "graded-group",
widgetId: "graded-group 1",
},
});
});

describe("on desktop", () => {
it("should be able to be answered correctly", async () => {
// Arrange
Expand Down
22 changes: 19 additions & 3 deletions packages/perseus/src/widgets/graded-group/graded-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import _ from "underscore";

import {PerseusI18nContext} from "../../components/i18n-context";
import InlineIcon from "../../components/inline-icon";
import {withDependencies} from "../../components/with-dependencies";
import {iconOk, iconRemove} from "../../icon-paths";
import {ApiOptions} from "../../perseus-api";
import Renderer from "../../renderer";
Expand All @@ -29,6 +30,7 @@ import GradedGroupAnswerBar from "./graded-group-answer-bar";
import type {ANSWER_BAR_STATES} from "./graded-group-answer-bar";
import type {
FocusPath,
PerseusDependenciesV2,
TrackingGradedGroupExtraArguments,
Widget,
WidgetExports,
Expand Down Expand Up @@ -75,6 +77,7 @@ type Props = WidgetProps<
> & {
inGradedGroupSet?: boolean; // Set by graded-group-set.jsx,
onNextQuestion?: () => unknown; // Set by graded-group-set.jsx
dependencies: PerseusDependenciesV2;
};

type DefaultProps = {
Expand Down Expand Up @@ -103,7 +106,7 @@ type State = {
0 as any as WidgetProps<
PerseusGradedGroupWidgetOptions,
Empty
> satisfies PropsFor<typeof GradedGroup>;
> satisfies PropsFor<typeof WrappedGradedGroup>;

// A Graded Group is more or less a Group widget that displays a check
// answer button below the rendered content. When clicked, the widget grades
Expand Down Expand Up @@ -136,6 +139,17 @@ export class GradedGroup
rendererRef = React.createRef<Renderer>();
hintRendererRef = React.createRef<Renderer>();

componentDidMount(): void {
this.props.dependencies.analytics.onAnalyticsEvent({
type: "perseus:widget:rendered:ti",
payload: {
widgetType: "graded-group",
widgetSubType: "null",
widgetId: this.props.widgetId,
},
});
}

shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
return nextProps !== this.props || nextState !== this.state;
}
Expand Down Expand Up @@ -496,12 +510,14 @@ const styles = StyleSheet.create({
},
});

const WrappedGradedGroup = withDependencies(GradedGroup);

export default {
name: "graded-group",
displayName: "Graded group (articles only)",
widget: GradedGroup,
widget: WrappedGradedGroup,
// TODO(aasmund): This widget should be available for articles only
hidden: false,
tracking: "all",
isLintable: true,
} satisfies WidgetExports<typeof GradedGroup>;
} satisfies WidgetExports<typeof WrappedGradedGroup>;
28 changes: 27 additions & 1 deletion packages/perseus/src/widgets/grapher/grapher.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {testDependencies} from "../../../../../testing/test-dependencies";
import {
testDependencies,
testDependenciesV2,
} from "../../../../../testing/test-dependencies";
import {waitForInitialGraphieRender} from "../../../../../testing/wait";
import * as Dependencies from "../../dependencies";
import {renderQuestion} from "../__testutils__/renderQuestion";
Expand All @@ -8,6 +11,8 @@ import {
multipleAvailableTypesQuestion,
} from "./grapher.testdata";

import type {PerseusDependenciesV2} from "../../types";

describe("grapher widget", () => {
beforeEach(() => {
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
Expand Down Expand Up @@ -40,4 +45,25 @@ describe("grapher widget", () => {
// Assert
expect(container).toMatchSnapshot("initial render");
});

it("should send analytics event when widget is rendered", () => {
// Arrange
const onAnalyticsEventSpy = jest.fn();
const depsV2: PerseusDependenciesV2 = {
...testDependenciesV2,
analytics: {onAnalyticsEvent: onAnalyticsEventSpy},
};

// Act
renderQuestion(linearQuestion, undefined, undefined, undefined, depsV2);
// Assert
expect(onAnalyticsEventSpy).toHaveBeenCalledWith({
type: "perseus:widget:rendered:ti",
payload: {
widgetSubType: "null",
widgetType: "grapher",
widgetId: "grapher 1",
},
});
});
});
33 changes: 27 additions & 6 deletions packages/perseus/src/widgets/grapher/grapher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as React from "react";
import ButtonGroup from "../../components/button-group";
import Graphie from "../../components/graphie";
import SvgImage from "../../components/svg-image";
import {withDependencies} from "../../components/with-dependencies";
import Interactive2 from "../../interactive2";
import WrappedLine from "../../interactive2/wrapped-line";
import {interactiveSizes} from "../../styles/constants";
Expand All @@ -30,7 +31,12 @@ import {
} from "./util";

import type {Coord, Line} from "../../interactive2/types";
import type {Widget, WidgetExports, WidgetProps} from "../../types";
import type {
PerseusDependenciesV2,
Widget,
WidgetExports,
WidgetProps,
} from "../../types";
import type {GridDimensions} from "../../util";
import type {GrapherPromptJSON} from "../../widget-ai-utils/grapher/grapher-ai-utils";
import type {
Expand Down Expand Up @@ -338,13 +344,26 @@ class FunctionGrapher extends React.Component<FunctionGrapherProps> {
}
}

type Props = WidgetProps<PerseusGrapherWidgetOptions, PerseusGrapherUserInput>;

type Props = WidgetProps<
PerseusGrapherWidgetOptions,
PerseusGrapherUserInput
> & {dependencies: PerseusDependenciesV2};
/* Widget and editor. */
class Grapher extends React.Component<Props> implements Widget {
horizHairline: any;
vertHairline: any;

componentDidMount(): void {
this.props.dependencies.analytics.onAnalyticsEvent({
type: "perseus:widget:rendered:ti",
payload: {
widgetType: "grapher",
widgetSubType: "null",
widgetId: this.props.widgetId,
},
});
}

handlePlotChanges: (arg1: any) => any = (newPlot) => {
const plot = {...this.props.userInput, ...newPlot};
this.props.handleUserInput(plot);
Expand Down Expand Up @@ -628,14 +647,16 @@ function getCorrectUserInput(
0 as any as WidgetProps<
PerseusGrapherWidgetOptions,
PerseusGrapherUserInput
> satisfies PropsFor<typeof Grapher>;
> satisfies PropsFor<typeof WrappedGrapher>;

const WrappedGrapher = withDependencies(Grapher);

export default {
name: "grapher",
displayName: "Grapher",
hidden: true,
widget: Grapher,
widget: WrappedGrapher,
getUserInputFromSerializedState,
getStartUserInput,
getCorrectUserInput,
} satisfies WidgetExports<typeof Grapher>;
} satisfies WidgetExports<typeof WrappedGrapher>;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
} from "@khanacademy/perseus-core";
import {act} from "@testing-library/react";

import {testDependencies} from "../../../../../testing/test-dependencies";
import {
testDependencies,
testDependenciesV2,
} from "../../../../../testing/test-dependencies";
import {renderQuestion} from "../../__tests__/test-utils";
import * as Dependencies from "../../dependencies";
import {registerAllWidgetsForTesting} from "../../util/register-all-widgets-for-testing";
Expand Down Expand Up @@ -98,6 +101,7 @@ describe("Grapher serialization", () => {
alignment: "default",
static: false,
availableTypes: ["linear"],
dependencies: testDependenciesV2,
graph: {
range: [
[-10, 10],
Expand Down
Loading
Loading