Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Analytics #606

Merged
merged 14 commits into from
Aug 21, 2023
Merged
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/fluffy-beans-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": minor
---

Switch `virtualKeypadVersion` on `perseus:expression-evaluated` event to be non-optional
7 changes: 7 additions & 0 deletions .changeset/lazy-coins-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@khanacademy/math-input": major
"@khanacademy/perseus": major
"@khanacademy/perseus-core": minor
---

Rename analytics prop from onEvent to onAnalyticsEvent
5 changes: 5 additions & 0 deletions .changeset/moody-birds-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

Fix APIOptions `trackInteraction` type for better Flow type generation
5 changes: 5 additions & 0 deletions .changeset/poor-gifts-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": major
---

Remove 'analytics' key from PerseusDependencies
2 changes: 2 additions & 0 deletions .changeset/sharp-mice-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
5 changes: 5 additions & 0 deletions .changeset/small-snails-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": minor
---

Introduce `dependencies` on ArticleRenderer, ItemRenderer, MultiRenderer, and ServerItemRenderer.
5 changes: 5 additions & 0 deletions .changeset/thin-islands-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/math-input": minor
---

Introduce `dependencies` on Keypad.
11 changes: 9 additions & 2 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import * as React from "react";

Check warning on line 1 in .storybook/preview.js

View workflow job for this annotation

GitHub Actions / Lint, Typecheck, Format, and Test (ubuntu-latest, 16.x)

File ignored by default.
import Color from "@khanacademy/wonder-blocks-color";
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
import {Dependencies} from "@khanacademy/perseus";
import {storybookTestDependencies} from "../testing/test-dependencies";

import {DependenciesContext} from "../packages/perseus/src/dependencies";
import {
storybookTestDependencies,
storybookDependenciesV2,
} from "../testing/test-dependencies";

// IMPORTANT: This code runs ONCE per story file, not per story within that file.
// If you want code to run once per story, see `StorybookWrapper`.
Expand All @@ -14,7 +19,9 @@
export const decorators = [
(Story) => (
<RenderStateRoot>
<Story />
<DependenciesContext.Provider value={storybookDependenciesV2}>
<Story />
</DependenciesContext.Provider>
</RenderStateRoot>
),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import Keypad from "../index";

import type Key from "../../../data/keys";
import type {MathFieldInterface} from "../../input/mathquill-types";
import type {PerseusAnalyticsEvent} from "@khanacademy/perseus-core";
import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";

type Props = {
onChangeMathInput: (mathInputTex: string) => void;
keypadClosed?: boolean;
sendEvent?: (event: PerseusAnalyticsEvent) => Promise<void>;
onAnalyticsEvent?: AnalyticsEventHandlerFn;
};

function V2KeypadWithMathquill(props: Props) {
const mathFieldWrapperRef = React.useRef<HTMLDivElement>(null);
const [mathField, setMathField] = React.useState<MathFieldInterface>();
const {onChangeMathInput, keypadClosed, sendEvent} = props;
const {onChangeMathInput, keypadClosed, onAnalyticsEvent} = props;
const [keypadOpen, setKeypadOpen] = React.useState<boolean>(!keypadClosed);

React.useEffect(() => {
Expand Down Expand Up @@ -75,7 +75,11 @@ function V2KeypadWithMathquill(props: Props) {
multiplicationDot
preAlgebra
trigonometry
sendEvent={sendEvent ? sendEvent : async () => {}}
onAnalyticsEvent={
onAnalyticsEvent
? onAnalyticsEvent
: async () => {}
}
showDismiss
/>
</div>
Expand Down Expand Up @@ -252,20 +256,20 @@ describe("Keypad v2 with MathQuill", () => {
// Keypad event tests
it("fires the keypad open event on open", () => {
// Arrange
const mockSendEvent = jest.fn();
const mockAnalyticsEventHandler = jest.fn();
render(
<V2KeypadWithMathquill
onChangeMathInput={() => {}}
keypadClosed={true}
sendEvent={mockSendEvent}
onAnalyticsEvent={mockAnalyticsEventHandler}
/>,
);

// Act
userEvent.click(screen.getByRole("button", {name: "Keypad toggle"}));

// Assert
expect(mockSendEvent).toHaveBeenLastCalledWith({
expect(mockAnalyticsEventHandler).toHaveBeenLastCalledWith({
type: "math-input:keypad-opened",
payload: {virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2"},
});
Expand All @@ -274,19 +278,19 @@ describe("Keypad v2 with MathQuill", () => {
// Keypad event tests
it("fires the keypad open event on close", () => {
// Arrange
const mockSendEvent = jest.fn();
const mockAnalyticsEventHandler = jest.fn();
render(
<V2KeypadWithMathquill
onChangeMathInput={() => {}}
sendEvent={mockSendEvent}
onAnalyticsEvent={mockAnalyticsEventHandler}
/>,
);

// Act
userEvent.click(screen.getByRole("button", {name: "Keypad toggle"}));

// Assert
expect(mockSendEvent).toHaveBeenLastCalledWith({
expect(mockAnalyticsEventHandler).toHaveBeenLastCalledWith({
type: "math-input:keypad-closed",
payload: {virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2"},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe("keypad", () => {
cursorContext={
context as typeof CursorContext[keyof typeof CursorContext]
}
sendEvent={async () => {
onAnalyticsEvent={async () => {
/* TODO: verify correct analytics event sent */
}}
/>,
Expand All @@ -52,7 +52,7 @@ describe("keypad", () => {
render(
<Keypad
onClickKey={() => {}}
sendEvent={async () => {}}
onAnalyticsEvent={async () => {}}
showDismiss
/>,
);
Expand All @@ -68,7 +68,9 @@ describe("keypad", () => {
it(`hides optional dismiss button`, () => {
// Arrange
// Act
render(<Keypad onClickKey={() => {}} sendEvent={async () => {}} />);
render(
<Keypad onClickKey={() => {}} onAnalyticsEvent={async () => {}} />,
);

// Assert
expect(
Expand All @@ -85,7 +87,7 @@ describe("keypad", () => {
<Keypad
onClickKey={() => {}}
fractionsOnly={true}
sendEvent={async () => {}}
onAnalyticsEvent={async () => {}}
/>,
);

Expand All @@ -104,7 +106,7 @@ describe("keypad", () => {
preAlgebra
trigonometry
extraKeys={["PI"]}
sendEvent={async () => {}}
onAnalyticsEvent={async () => {}}
/>,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function V2KeypadWithMathquill() {
multiplicationDot
preAlgebra
trigonometry
sendEvent={async (event) => {
onAnalyticsEvent={async (event) => {
// eslint-disable-next-line no-console
console.log("Send Event:", event);
}}
Expand Down
12 changes: 6 additions & 6 deletions packages/math-input/src/components/keypad/keypad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type Key from "../../data/keys";
import type {ClickKeyCallback} from "../../types";
import type {CursorContext} from "../input/cursor-contexts";
import type {TabbarItemType} from "../tabbar";
import type {SendEventFn} from "@khanacademy/perseus-core";
import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";

export type Props = {
extraKeys: ReadonlyArray<Key>;
Expand All @@ -35,7 +35,7 @@ export type Props = {
fractionsOnly?: boolean;

onClickKey: ClickKeyCallback;
sendEvent?: SendEventFn;
onAnalyticsEvent: AnalyticsEventHandlerFn;
};

const defaultProps = {
Expand Down Expand Up @@ -94,8 +94,8 @@ export default function Keypad(props: Props) {
basicRelations,
advancedRelations,
showDismiss,
onAnalyticsEvent,
fractionsOnly,
sendEvent,
} = props;

// Use a different grid for our fraction keypad
Expand All @@ -110,22 +110,22 @@ export default function Keypad(props: Props) {

useEffect(() => {
if (!isMounted) {
sendEvent?.({
onAnalyticsEvent({
type: "math-input:keypad-opened",
payload: {virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2"},
});
setIsMounted(true);
}
return () => {
if (isMounted) {
sendEvent?.({
onAnalyticsEvent({
type: "math-input:keypad-closed",
payload: {virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2"},
});
setIsMounted(false);
}
};
}, [sendEvent, isMounted]);
}, [onAnalyticsEvent, isMounted]);

return (
<View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
>
<Keypad
// TODO(jeremy)
sendEvent={async () => {}}
onAnalyticsEvent={async () => {}}
extraKeys={keypadConfig?.extraKeys}
onClickKey={(key) => this._handleClickKey(key)}
cursorContext={cursor?.context}
Expand Down
20 changes: 13 additions & 7 deletions packages/perseus-core/src/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
export type VirtualKeypadVersion =
| "PERSEUS_MATH_INPUT"
| "MATH_INPUT_KEYPAD_V1"
| "MATH_INPUT_KEYPAD_V2"
| "REACT_NATIVE_KEYPAD";

// A type union of all the events that any package in the Perseus ecosystem can
// send.
export type PerseusAnalyticsEvent =
| {
type: "perseus:expression-evaluated";
payload: {
// Keypad version can be supplied by Perseus sometimes, but not always.
// The host application will need to fill this in if it's not present.
virtualKeypadVersion?: string;
virtualKeypadVersion: VirtualKeypadVersion;
result: "correct" | "incorrect" | "invalid";
};
}
| {
type: "math-input:keypad-closed";
payload: {
virtualKeypadVersion: string;
virtualKeypadVersion: VirtualKeypadVersion;
};
}
| {
type: "math-input:keypad-opened";
payload: {
virtualKeypadVersion: string;
virtualKeypadVersion: VirtualKeypadVersion;
};
};
// Add more events here as needed. Note that each event should have a `type`
// key and a payload that varies by type.
// | {type: "b"; payload: {name: string}};

/** A function to send analytics events. */
export type SendEventFn = (event: PerseusAnalyticsEvent) => Promise<void>;
/** A function that is called when Perseus emits an analytics event. */
export type AnalyticsEventHandlerFn = (
event: PerseusAnalyticsEvent,
) => Promise<void>;
2 changes: 1 addition & 1 deletion packages/perseus-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export type {PerseusAnalyticsEvent, SendEventFn} from "./analytics";
export type {PerseusAnalyticsEvent, AnalyticsEventHandlerFn} from "./analytics";
export type {KEScore, RendererInterface} from "./types";
6 changes: 6 additions & 0 deletions packages/perseus-editor/src/multirenderer-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,12 @@ class MultiRendererEditor extends React.Component<MultiRendererEditorProps> {
item={item}
shape={itemShape}
apiOptions={apiOptions}
// Today, with analytics being the only thing in
// dependencies, we send in a dummy function as we don't
// want to gather analytics events from within the editor.
dependencies={{
analytics: {onAnalyticsEvent: async () => {}},
}}
>
{({renderers}) => (
<NodeContainer
Expand Down
37 changes: 35 additions & 2 deletions packages/perseus/src/__stories__/article-renderer.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React from "react";

import {passageArticle} from "../__testdata__/article-renderer.testdata";
import {storybookDependenciesV2} from "../../../../testing/test-dependencies";
import {
singleSectionArticle,
multiSectionArticle,
passageArticle,
} from "../__testdata__/article-renderer.testdata";
import ArticleRenderer from "../article-renderer";

export default {
Expand All @@ -12,6 +17,34 @@ export default {
},
};

export const ASingleSectionArticle = (args: {
useNewStyles;
}): React.ReactElement => {
return (
<ArticleRenderer
json={singleSectionArticle}
dependencies={storybookDependenciesV2}
useNewStyles={args.useNewStyles}
/>
);
};

export const BMultiSectionArticle = (args: {
useNewStyles;
}): React.ReactElement => {
return (
<ArticleRenderer
json={multiSectionArticle}
dependencies={storybookDependenciesV2}
useNewStyles={args.useNewStyles}
/>
);
};

export const PassageArticle = ({useNewStyles}): any => (
<ArticleRenderer json={passageArticle} useNewStyles={useNewStyles} />
<ArticleRenderer
json={passageArticle}
dependencies={storybookDependenciesV2}
useNewStyles={useNewStyles}
/>
);
Loading
Loading