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

Refactored async initialization logic for Mixpanel, Grafana, and Launch Darkly #624

Merged
merged 10 commits into from
Jul 15, 2024
9 changes: 9 additions & 0 deletions .changeset/dirty-humans-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@replayio/cypress": patch
"@replayio/jest": patch
"@replayio/playwright": patch
"@replayio/puppeteer": patch
"replayio": patch
---

Improve async initialization and teardown logic
18 changes: 7 additions & 11 deletions packages/cypress/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="cypress" />

import { logger } from "@replay-cli/shared/logger";
import { mixpanelAPI } from "@replay-cli/shared/mixpanel/mixpanelAPI";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { getRuntimePath } from "@replay-cli/shared/runtime/getRuntimePath";
import { setUserAgent } from "@replay-cli/shared/userAgent";
import { initializeSession } from "@replay-cli/shared/session/initializeSession";
import { RecordingEntry, initMetadataFile, warn } from "@replayio/test-utils";
import chalk from "chalk";
import path from "path";
Expand Down Expand Up @@ -159,7 +159,7 @@ async function onAfterRun() {

if (missingSteps) {
logger.error("OnAfterRun:AfterRunMissingSteps", { missingSteps });
mixpanelAPI.trackEvent("warning.missing-steps");
mixpanelClient.trackEvent("warning.missing-steps");
loudWarning(
"Your tests completed but our plugin did not receive any command events.",
"",
Expand Down Expand Up @@ -197,7 +197,7 @@ function onReplayTask(value: any) {
reporter.addStep(v);
} else {
logger.error("OnReplayTask:ReplayTaskUnexpectedPayload", { payload: v });
mixpanelAPI.trackEvent("error.replay-task-unexpected-payload", { payload: v });
mixpanelClient.trackEvent("error.replay-task-unexpected-payload", { payload: v });
}
});

Expand Down Expand Up @@ -261,11 +261,7 @@ const plugin = (
config: Cypress.PluginConfigOptions,
options: PluginOptions = {}
) => {
setUserAgent(`${packageName}/${packageVersion}`);

logger.initialize(packageName, packageVersion);
logger.identify(getAuthKey(config));
mixpanelAPI.initialize({
bvaughn marked this conversation as resolved.
Show resolved Hide resolved
initializeSession({
accessToken: getAuthKey(config),
packageName,
packageVersion,
Expand All @@ -283,7 +279,7 @@ const plugin = (

ws.on("error", error => {
logger.error("CypressPlugin:WebSocketError", { error });
mixpanelAPI.trackEvent("error.websocket-error", { error });
mixpanelClient.trackEvent("error.websocket-error", { error });
warn("WebSocket error", error);
});

Expand All @@ -294,7 +290,7 @@ const plugin = (
onReplayTask(obj.events);
} catch (error) {
logger.error("CypressPlugin:WebSocketMessageError", { error });
mixpanelAPI.trackEvent("error.websocket-message-error", { error });
mixpanelClient.trackEvent("error.websocket-message-error", { error });
warn("Error parsing message from test", error);
}
});
Expand Down
14 changes: 9 additions & 5 deletions packages/jest/src/runner.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type { JestEnvironment } from "@jest/environment";
import type { TestFileEvent, TestResult } from "@jest/test-result";
import type { Circus, Config } from "@jest/types";
import { logger } from "@replay-cli/shared/logger";
import { setUserAgent } from "@replay-cli/shared/userAgent";
import { initializeSession } from "@replay-cli/shared/session/initializeSession";
import {
ReplayReporter,
getAccessToken,
getMetadataFilePath as getMetadataFilePathBase,
initMetadataFile,
removeAnsiCodes,
} from "@replayio/test-utils";
import type Runtime from "jest-runtime";
import path from "path";
import * as pkgJson from "../package.json";
import { name as packageName, version as packageVersion } from "../package.json";

const runner = require("jest-circus/runner");
const pluginVersion = require("@replayio/jest/package.json").version;
Expand All @@ -20,6 +20,12 @@ export function getMetadataFilePath(workerIndex = 0) {
return getMetadataFilePathBase("JEST", workerIndex);
}

initializeSession({
accessToken: getAccessToken(),
packageName,
packageVersion,
});

let version: string | undefined;

type MatcherResult = {
Expand All @@ -38,8 +44,6 @@ const ReplayRunner = async (
testPath: string,
sendMessageToJest?: TestFileEvent
): Promise<TestResult> => {
setUserAgent(`${pkgJson.name}/${pkgJson.version}`);
logger.initialize(pkgJson.name, pkgJson.version);
if (!version) {
try {
version = require(require.resolve("jest/package.json", {
Expand Down
19 changes: 8 additions & 11 deletions packages/playwright/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import type {
TestResult,
} from "@playwright/test/reporter";
import { logger } from "@replay-cli/shared/logger";
import { mixpanelAPI } from "@replay-cli/shared/mixpanel/mixpanelAPI";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { waitForExitTasks } from "@replay-cli/shared/process/waitForExitTasks";
import { getRuntimePath } from "@replay-cli/shared/runtime/getRuntimePath";
import { initializeSession } from "@replay-cli/shared/session/initializeSession";
import { emphasize, highlight, link } from "@replay-cli/shared/theme";
import { setUserAgent } from "@replay-cli/shared/userAgent";
import {
ReplayReporter,
ReplayReporterConfig,
Expand Down Expand Up @@ -83,18 +84,14 @@ export default class ReplayPlaywrightReporter implements Reporter {
private _executedProjects: Record<string, { usesReplayBrowser: boolean }> = {};

constructor(config: ReplayPlaywrightConfig) {
setUserAgent(`${packageName}/${packageVersion}`);

logger.initialize(packageName, packageVersion);
logger.identify(getAccessToken(config));
mixpanelAPI.initialize({
initializeSession({
accessToken: getAccessToken(config),
packageName,
packageVersion,
});

if (!config || typeof config !== "object") {
mixpanelAPI.trackEvent("error.invalid-reporter-config", { config });
mixpanelClient.trackEvent("error.invalid-reporter-config", { config });

throw new Error(
`Expected an object for @replayio/playwright/reporter configuration but received: ${config}`
Expand Down Expand Up @@ -374,13 +371,13 @@ export default class ReplayPlaywrightReporter implements Reporter {
const output: string[] = [];

if (!didUseReplayBrowser) {
mixpanelAPI.trackEvent("warning.reporter-used-without-replay-project");
mixpanelClient.trackEvent("warning.reporter-used-without-replay-project");
output.push(emphasize("None of the configured projects ran using Replay Chromium."));
}

if (!isReplayBrowserInstalled) {
if (didUseReplayBrowser) {
mixpanelAPI.trackEvent("warning.replay-browser-not-installed");
mixpanelClient.trackEvent("warning.replay-browser-not-installed");
}

output.push(
Expand Down Expand Up @@ -418,7 +415,7 @@ export default class ReplayPlaywrightReporter implements Reporter {
// the issue is tracked here: https://github.com/microsoft/playwright/issues/23875
console.log("");
} finally {
await Promise.all([mixpanelAPI.close().catch(noop), logger.close().catch(noop)]);
await waitForExitTasks();
}
}

Expand Down
13 changes: 8 additions & 5 deletions packages/puppeteer/src/install.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { logger } from "@replay-cli/shared/logger";
import { installLatestRuntimeRelease } from "@replay-cli/shared/runtime/installLatestRuntimeRelease";
import { name, version } from "../package.json";
import { getAccessToken } from "@replay-cli/shared/authentication/getAccessToken";
import { initializeSession } from "@replay-cli/shared/session/initializeSession";
import { getAccessToken } from "@replayio/test-utils";
import { name as packageName, version as packageVersion } from "../package.json";

export default async function install() {
try {
logger.initialize(name, version);
const accessToken = await getAccessToken();
await logger.identify(accessToken.accessToken);
initializeSession({
bvaughn marked this conversation as resolved.
Show resolved Hide resolved
accessToken: getAccessToken(),
packageName,
packageVersion,
});
} catch (error) {
logger.error("Failed to identify for logger", { error });
}
Expand Down
15 changes: 10 additions & 5 deletions packages/replayio/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { logger } from "@replay-cli/shared/logger";
import { exitProcess } from "@replay-cli/shared/process/exitProcess";
import { setUserAgent } from "@replay-cli/shared/userAgent";
import { name, version } from "../package.json";
import { name as packageName, version as packageVersion } from "../package.json";
import { finalizeCommander } from "./utils/commander/finalizeCommander";

// Commands self-register with "commander"
import { getAccessToken } from "@replay-cli/shared/authentication/getAccessToken";
import { initializeSession } from "@replay-cli/shared/session/initializeSession";
import "./commands/info";
import "./commands/list";
import "./commands/login";
Expand All @@ -17,9 +18,13 @@ import "./commands/upload";
import "./commands/upload-source-maps";
import "./commands/whoami";

logger.initialize(name, version);

setUserAgent(`${name}/${version}`);
getAccessToken().then(({ accessToken }) => {
initializeSession({
accessToken,
packageName,
packageVersion,
});
});

finalizeCommander();

Expand Down
4 changes: 2 additions & 2 deletions packages/replayio/src/commands/record.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ProcessError } from "@replay-cli/shared/ProcessError";
import { mixpanelAPI } from "@replay-cli/shared/mixpanel/mixpanelAPI";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { exitProcess } from "@replay-cli/shared/process/exitProcess";
import { canUpload } from "@replay-cli/shared/recording/canUpload";
import { getRecordings } from "@replay-cli/shared/recording/getRecordings";
Expand Down Expand Up @@ -103,7 +103,7 @@ async function record(url: string = "about:blank") {
console.log(""); // Spacing for readability
}

mixpanelAPI.trackEvent("record.results", {
mixpanelClient.trackEvent("record.results", {
crashedCount: crashedRecordings.length,
successCountsByType: finishedRecordings.reduce(
(map, recording) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/replayio/src/commands/whoami.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getAccessToken } from "@replay-cli/shared/authentication/getAccessToken";
import { getAuthInfo } from "@replay-cli/shared/graphql/getAuthInfo";
import { getAuthInfo } from "@replay-cli/shared/authentication/getAuthInfo";
import { exitProcess } from "@replay-cli/shared/process/exitProcess";
import { dim, emphasize, highlight, link } from "@replay-cli/shared/theme";
import { name as packageName } from "../../package.json";
Expand Down
4 changes: 2 additions & 2 deletions packages/replayio/src/utils/browser/reportBrowserCrash.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getAccessToken } from "@replay-cli/shared/authentication/getAccessToken";
import { getReplayPath } from "@replay-cli/shared/getReplayPath";
import { logger } from "@replay-cli/shared/logger";
import { getUserAgent } from "@replay-cli/shared/userAgent";
import { getUserAgent } from "@replay-cli/shared/session/getUserAgent";
import { readFile, writeFileSync } from "fs-extra";
import { File, FormData, fetch } from "undici";
import { replayApiServer } from "../../config";
Expand All @@ -21,7 +21,7 @@ export async function reportBrowserCrash(stderr: string) {
};
}

const userAgent = getUserAgent();
const userAgent = await getUserAgent();

const formData = new FormData();

Expand Down
4 changes: 2 additions & 2 deletions packages/replayio/src/utils/commander/registerCommand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mixpanelAPI } from "@replay-cli/shared/mixpanel/mixpanelAPI";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { program } from "commander";
import { initialize } from "../initialization/initialize";

Expand All @@ -17,7 +17,7 @@ export function registerCommand(
} = config;

return program.command(commandName).hook("preAction", async () => {
mixpanelAPI.trackEvent("command", { commandName });
mixpanelClient.trackEvent("command", { commandName });

await initialize({
checkForNpmUpdate,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { logger } from "@replay-cli/shared/logger";
import { createAsyncFunctionWithTracking } from "@replay-cli/shared/mixpanel/createAsyncFunctionWithTracking";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { fetch } from "undici";
import { version as currentVersion, name as packageName } from "../../../package.json";
import { shouldPrompt } from "../prompt/shouldPrompt";
import { UpdateCheck } from "./types";

const PROMPT_ID = "npm-update";

export const checkForNpmUpdate = createAsyncFunctionWithTracking(
export const checkForNpmUpdate = mixpanelClient.createAsyncFunctionWithTracking(
async function checkForNpmUpdate(): Promise<UpdateCheck<string>> {
try {
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-metadata-format
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { logger } from "@replay-cli/shared/logger";
import { createAsyncFunctionWithTracking } from "@replay-cli/shared/mixpanel/createAsyncFunctionWithTracking";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { existsSync } from "fs-extra";
import { getBrowserPath } from "../browser/getBrowserPath";
import { getLatestRelease } from "../installation/getLatestReleases";
Expand All @@ -15,7 +15,7 @@ export type Version = {
version: Release["version"];
};

export const checkForRuntimeUpdate = createAsyncFunctionWithTracking(
export const checkForRuntimeUpdate = mixpanelClient.createAsyncFunctionWithTracking(
async function checkForRuntimeUpdate(): Promise<UpdateCheck<Version>> {
let latestRelease: Release;
let latestBuildId: string;
Expand Down
27 changes: 0 additions & 27 deletions packages/replayio/src/utils/initialization/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { raceWithTimeout } from "@replay-cli/shared/async/raceWithTimeout";
import { getAccessToken } from "@replay-cli/shared/authentication/getAccessToken";
import { initLaunchDarklyFromAccessToken } from "@replay-cli/shared/launch-darkly/initLaunchDarklyFromAccessToken";
import { mixpanelAPI } from "@replay-cli/shared/mixpanel/mixpanelAPI";
import { name as packageName, version as packageVersion } from "../../../package.json";
import { logPromise } from "../async/logPromise";
import { checkForNpmUpdate } from "./checkForNpmUpdate";
import { checkForRuntimeUpdate } from "./checkForRuntimeUpdate";
import { promptForAuthentication } from "./promptForAuthentication";
import { promptForNpmUpdate } from "./promptForNpmUpdate";
import { promptForRuntimeUpdate } from "./promptForRuntimeUpdate";
import { logger } from "@replay-cli/shared/logger";

export async function initialize({
checkForNpmUpdate: shouldCheckForNpmUpdate,
Expand Down Expand Up @@ -47,34 +43,11 @@ export async function initialize({
accessToken = await promptForAuthentication();
bvaughn marked this conversation as resolved.
Show resolved Hide resolved
}

// Initialize LaunchDarkly and Mixpanel for authenticated users
// These tasks don't print anything so they can be done in parallel with the upgrade prompts
// They also shouldn't block on failure, so we should only wait a couple of seconds before giving up
const abortController = new AbortController();

const loggerPromise = raceWithTimeout(logger.identify(accessToken), 2_500, abortController);

const launchDarklyPromise = accessToken
? raceWithTimeout(
initLaunchDarklyFromAccessToken(accessToken, abortController.signal),
2_500,
abortController
)
: Promise.resolve();

const mixpanelPromise = raceWithTimeout(
mixpanelAPI.initialize({ accessToken, packageName, packageVersion }),
2_500,
abortController
);

if (npmUpdateCheck.hasUpdate && npmUpdateCheck.shouldShowPrompt) {
await promptForNpmUpdate(npmUpdateCheck);
}

if (runtimeUpdateCheck.hasUpdate && runtimeUpdateCheck.shouldShowPrompt) {
await promptForRuntimeUpdate(runtimeUpdateCheck);
}

await Promise.all([loggerPromise, launchDarklyPromise, mixpanelPromise]);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mixpanelAPI } from "@replay-cli/shared/mixpanel/mixpanelAPI";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { emphasize } from "@replay-cli/shared/theme";
import { name as packageName } from "../../../package.json";
import { installLatestRelease } from "../installation/installLatestRelease";
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function promptForRuntimeUpdate(updateCheck: UpdateCheckResult<Vers
// A failed install will be handled later
}
} else {
mixpanelAPI.trackEvent("update.runtime.skipped", { newRuntimeVersion: toVersion });
mixpanelClient.trackEvent("update.runtime.skipped", { newRuntimeVersion: toVersion });
}

console.log("");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { writeToCache } from "@replay-cli/shared/cache";
import { getReplayPath } from "@replay-cli/shared/getReplayPath";
import { logger } from "@replay-cli/shared/logger";
import { createAsyncFunctionWithTracking } from "@replay-cli/shared/mixpanel/createAsyncFunctionWithTracking";
import { mixpanelClient } from "@replay-cli/shared/mixpanelClient";
import { dim, link } from "@replay-cli/shared/theme";
import { spawnSync } from "child_process";
import { ensureDirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "fs-extra";
Expand All @@ -21,7 +21,7 @@ type Result = {
forkedVersion: string | null;
};

export const installLatestRelease = createAsyncFunctionWithTracking(
export const installLatestRelease = mixpanelClient.createAsyncFunctionWithTracking(
async function installLatestRelease(): Promise<Result | undefined> {
logger.info("InstallLatestRelease:Start");
const runtimeBaseDir = getReplayPath("runtimes");
Expand Down
Loading