Skip to content

Commit

Permalink
Merge branch 'main' into fix/no_live_openai
Browse files Browse the repository at this point in the history
  • Loading branch information
JBR90 committed Dec 20, 2024
2 parents d4b3d82 + 2f255b3 commit 392317f
Show file tree
Hide file tree
Showing 9 changed files with 952 additions and 344 deletions.
12 changes: 12 additions & 0 deletions CHANGE_LOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# [1.19.0](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.18.2...v1.19.0) (2024-12-18)


### Bug Fixes

* sonar duplication ([#397](https://github.com/oaknational/oak-ai-lesson-assistant/issues/397)) ([dea3106](https://github.com/oaknational/oak-ai-lesson-assistant/commit/dea3106d249119169e515d7ccebc827f324bdbf3))


### Features

* soft delete ([#383](https://github.com/oaknational/oak-ai-lesson-assistant/issues/383)) ([9dee4ad](https://github.com/oaknational/oak-ai-lesson-assistant/commit/9dee4adb840a0a5d96e43f02349ad04597862539))

## [1.18.2](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.18.1...v1.18.2) (2024-12-17)

## [1.18.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.18.0...v1.18.1) (2024-12-16)
Expand Down
1 change: 1 addition & 0 deletions apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"react-hot-toast": "^2.4.1",
"react-intersection-observer": "^9.6.0",
"react-markdown": "^9.0.0",
"react-scan": "^0.0.43",
"react-syntax-highlighter": "^15.5.0",
"react-textarea-autosize": "^8.5.3",
"remark-gfm": "^4.0.0",
Expand Down
5 changes: 5 additions & 0 deletions apps/nextjs/src/app/aila/page-contents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import React from "react";

import { useReactScan } from "hooks/useReactScan";

import { Chat } from "@/components/AppComponents/Chat/Chat/chat";
import LessonPlanDisplay from "@/components/AppComponents/Chat/chat-lessonPlanDisplay";
import Layout from "@/components/AppComponents/Layout";
import { ChatProvider } from "@/components/ContextProviders/ChatProvider";
import LessonPlanTrackingProvider from "@/lib/analytics/lessonPlanTrackingContext";

const ChatPageContents = ({ id }: { readonly id: string }) => {
useReactScan({ component: LessonPlanDisplay, interval: 10000 });

return (
<Layout>
<LessonPlanTrackingProvider chatId={id}>
Expand Down
13 changes: 13 additions & 0 deletions apps/nextjs/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { Toaster } from "react-hot-toast";
import { Monitoring } from "react-scan/dist/core/monitor/params/next";

import { ClerkProvider } from "@clerk/nextjs";
import "@fontsource/lexend";
Expand Down Expand Up @@ -38,6 +39,11 @@ const provided_vercel_url =

const vercel_url = `https://${provided_vercel_url}`;

const reactScanApiKey = process.env.NEXT_PUBLIC_REACT_SCAN_KEY;
const addReactScanMonitor =
process.env.NEXT_PUBLIC_RENDER_MONITOR === "true" &&
reactScanApiKey !== undefined;

const lexend = Lexend({
subsets: ["latin"],
display: "swap",
Expand Down Expand Up @@ -74,6 +80,7 @@ export default async function RootLayout({
children,
}: Readonly<RootLayoutProps>) {
const nonce = headers().get("x-nonce");

if (!nonce) {
// Our middleware path matching excludes static paths like /_next/static/...
// If a static path becomes a 404, CSP headers aren't set
Expand All @@ -93,6 +100,12 @@ export default async function RootLayout({
GeistMono.variable,
)}
>
{addReactScanMonitor && (
<Monitoring
apiKey={reactScanApiKey}
url={process.env.NEXT_PUBLIC_REACT_SCAN_URL}
/>
)}
<Theme
accentColor="blue"
grayColor="olive"
Expand Down
149 changes: 149 additions & 0 deletions apps/nextjs/src/hooks/useReactScan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { useEffect } from "react";
import { getReport, scan } from "react-scan";

import { aiLogger } from "@oakai/logger";

const log = aiLogger("ui:performance");

declare global {
interface Window {
NEXT_PUBLIC_ENABLE_RENDER_SCAN?: string;
}
}

const isRenderScanEnabled =
(typeof process !== "undefined" &&
process.env.NEXT_PUBLIC_ENABLE_RENDER_SCAN === "true") ||
(typeof window !== "undefined" &&
window.NEXT_PUBLIC_ENABLE_RENDER_SCAN === "true");

const getSingleReport = <T>(
report: RenderData,
component: React.ComponentType<T>,
) => {
return transformReport([component?.name, report]);
};

const getSortedAndFilteredReports = (reports: Map<string, RenderData>) => {
const reportsArray = Array.from(reports.entries())
.filter(([componentName]) => {
// Exclude styled components and other library-generated components
const isCustomComponent =
// Check if name exists and doesn't start with known library prefixes
componentName &&
!componentName.startsWith("Styled") &&
!componentName.includes("styled") &&
!componentName.startsWith("_") &&
!componentName.includes("$") &&
componentName !== "div" &&
componentName !== "span";

return isCustomComponent;
})
.map(transformReport);

const sortedReports = reportsArray.toSorted(
(a, b) => b.renderCount - a.renderCount,
);
return sortedReports;
};

function getWindowPropertyName<T>(component: React.ComponentType<T>): string {
return `reactScan${component.displayName ?? component.name ?? "UnknownComponent"}`;
}

function setWindowObjectForPlaywright<T>(
component: React.ComponentType<T> | undefined,
report: sortedReport,
) {
if (component) {
const windowPropertyName = getWindowPropertyName(component);
window[windowPropertyName] = report;
}
}

function transformReport([componentName, report]: [
string | undefined,
RenderData,
]) {
return {
name: componentName,
renderCount: report.renders.length,
totalRenderTime: report.time,
};
}

interface RenderData {
count: number;
time: number;
renders: Array<unknown>;
displayName: string | null;
type: React.ComponentType<unknown> | null;
}

type sortedReport = {
name: string | undefined;
renderCount: number;
totalRenderTime: number;
};

// Enable React Scan for performance monitoring - use with dev:react-scan
// Pass in component to get report for specific component
// Pass in interval to get reports at regular intervals
// - useReactScan({ component: LessonPlanDisplay, interval: 10000 });
// When a component is passed it will be added to window object for Playwright testing

export const useReactScan = <T extends object>({
component,
interval,
}: {
component?: React.ComponentType<T>;
interval?: number;
}) => {
useEffect(() => {
if (isRenderScanEnabled) {
try {
log.info("Initializing React Scan...");
if (typeof window !== "undefined") {
scan({
enabled: true,
log: true,
report: true,
renderCountThreshold: 0,
showToolbar: true,
});
}
log.info("React Scan initialized successfully");
} catch (error) {
log.error("React Scan initialization error:", error);
}

const getRenderReport = () => {
try {
log.info("Attempting to get render reports...");

const report = component ? getReport(component) : getReport();

if (report instanceof Map) {
const allComponentReport = getSortedAndFilteredReports(report);
log.table(allComponentReport);
} else if (report !== null && component) {
const singleComponentReport = getSingleReport(report, component);
setWindowObjectForPlaywright(component, singleComponentReport);
log.info("Single Report:,", singleComponentReport);
}
} catch (error) {
log.error("Performance Monitoring Error:", error);
}
};
// If interval is provided, get reports at regular intervals
if (interval) {
const performanceInterval = setInterval(getRenderReport, interval);

return () => clearInterval(performanceInterval);
} else {
getRenderReport();
}
}
}, [component, interval]);
};
66 changes: 66 additions & 0 deletions apps/nextjs/tests-e2e/tests/chat-performance.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect, test, type Page } from "@playwright/test";

import { TEST_BASE_URL } from "../config/config";
import { prepareUser } from "../helpers/auth";
import { cspSafeWaitForFunction } from "../helpers/auth/clerkHelpers";
import { bypassVercelProtection } from "../helpers/vercel";
import { isFinished } from "./aila-chat/helpers";

declare global {
interface Window {
reactScanLessonPlanDisplay: { renderCount: number };
NEXT_PUBLIC_ENABLE_RENDER_SCAN?: string;
}
}

test.describe("Component renders during lesson chat", () => {
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
window.NEXT_PUBLIC_ENABLE_RENDER_SCAN = "true";
});
await test.step("Setup", async () => {
await bypassVercelProtection(page);
const login = await prepareUser(page, "typical");

await page.goto(`${TEST_BASE_URL}/aila/${login.chatId}`);
await isFinished(page);
});
});

// this is disabled because react scan is not currently working in preview deplyments.
test.skip("There are no unnecessary rerenders across left and right side of chat", async ({
page,
}) => {
await test.step("Chat box keyboard input does not create rerenders in lesson plan", async () => {
await verifyChatInputRenders(page);
});
});

async function verifyChatInputRenders(page: Page) {
await page.waitForTimeout(10000);

await cspSafeWaitForFunction(
page,
() =>
window.reactScanLessonPlanDisplay &&
typeof window.reactScanLessonPlanDisplay.renderCount === "number",
);

const textbox = page.getByTestId("chat-input");
const message = "Create a KS1 lesson on the end of Roman Britain";
const initialRenderAmount: number = await page.evaluate(
() => window.reactScanLessonPlanDisplay.renderCount,
);
await page.keyboard.type(message);
await expect(textbox).toContainText(message);
await page.waitForTimeout(10000);
const finalRenderAmount: number = await page.evaluate(
() => window.reactScanLessonPlanDisplay.renderCount,
);

expect(initialRenderAmount).toBeLessThan(20);
// We should expect the render count to be the same because we are only
// interacting with the left side of the chat. This should be fixed and updated
expect(finalRenderAmount).toBeLessThan(400);
}
});
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"db-push": "turbo db-push",
"db-push-force": "turbo db-push-force",
"db-seed": "turbo db-seed",
"deps": "NODE_OPTIONS=\"--max-old-space-size=8192\" dependency-cruiser --progress --max-depth 10 --output-type err --config .dependency-cruiser.cjs apps packages",
"deps:graph": "NODE_OPTIONS=\"--max-old-space-size=8192\" dependency-cruiser --progress --max-depth 10 --config .dependency-cruiser.cjs --output-type dot apps packages | dot -T svg > dependency-graph.svg",
"dev": "FORCE_COLOR=1 turbo dev --parallel --ui=stream --log-prefix=none --filter=!@oakai/db",
"dev:react-scan": "NEXT_PUBLIC_ENABLE_RENDER_SCAN=true pnpm dev",
"dev:react-scan-monitor-cloud": "NEXT_PUBLIC_RENDER_MONITOR=true pnpm dev",
"doppler:pull:dev": "doppler secrets download --config dev --no-file --format env > .env",
"doppler:pull:stg": "doppler secrets download --config stg --no-file --format env > .env",
"doppler:run:stg": "doppler run -c stg --silent",
Expand Down Expand Up @@ -57,7 +57,6 @@
"@semantic-release/git": "^10.0.1",
"@types/jest": "^29.5.14",
"autoprefixer": "^10.4.16",
"dependency-cruiser": "^16.7.0",
"husky": "^8.0.3",
"lint-staged": "^15.2.0",
"next": "14.2.18",
Expand Down
13 changes: 10 additions & 3 deletions packages/logger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ type ChildKey =
| "aila:stream"
| "aila:rag"
| "aila:testing"
| "aila:chat"
| "aila:experimental-patches"
| "analytics"
| "app"
| "auth"
| "chat"
| "cloudinary"
| "consent"
| "cron"
| "db"
| "demo"
| "exports"
Expand All @@ -64,8 +64,8 @@ type ChildKey =
| "transcripts"
| "trpc"
| "ui"
| "webhooks"
| "cron";
| "ui:performance"
| "webhooks";

const errorLogger =
typeof window === "undefined"
Expand All @@ -88,10 +88,17 @@ const errorLogger =
*/
export function aiLogger(childKey: ChildKey) {
const debugLogger = debugBase.extend(childKey);

const tableLogger = (tabularData: unknown[], columns?: string[]) => {
if (typeof console !== "undefined" && console.table) {
console.table(tabularData, columns);
}
};
return {
info: debugLogger,
warn: debugLogger,
error: errorLogger.bind(structuredLogger),
table: tableLogger,
};
}

Expand Down
Loading

0 comments on commit 392317f

Please sign in to comment.