Skip to content

Commit

Permalink
Improve GitHub API handling (#592)
Browse files Browse the repository at this point in the history
* Improve GitHub API handling

* consolidate metadata files

* fixed import regex

* fix old cli metadata

* Fixed cachedFetch tests

* fixed shapes

* add changeset

* address PR feedback
  • Loading branch information
Andarist authored Jul 8, 2024
1 parent 9748e25 commit 134591c
Show file tree
Hide file tree
Showing 37 changed files with 418 additions and 2,093 deletions.
6 changes: 6 additions & 0 deletions .changeset/eight-rice-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@replayio/cypress": patch
"@replayio/playwright": patch
---

Improved resiliency to GitHub API errors when auto-populating PR-related information metadata
3 changes: 1 addition & 2 deletions packages/replay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@
"./src/bin.ts",
"./src/main.ts",
"./src/utils.ts",
"./src/metadata/*.ts",
"./src/metadata/test/index.ts"
"./src/metadata/*.ts"
]
}
}
15 changes: 9 additions & 6 deletions packages/replay/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { readToken } from "./auth";
import { ProtocolError } from "./client";
import { ensureBrowsersInstalled, getExecutablePath, updateBrowsers } from "./install";
import { getLaunchDarkly } from "./launchdarkly";
import { add, sanitize, source as sourceMetadata, test as testMetadata } from "./metadata";
export { sanitizeMetadata as sanitize } from "@replay-cli/shared/recording/metadata/sanitizeMetadata";
import * as sourceMetadata from "@replay-cli/shared/recording/metadata/legacy/source";
import * as testMetadata from "@replay-cli/shared/recording/metadata/legacy/test/index";
import { addMetadata } from "@replay-cli/shared/recording/metadata/addMetadata";
import {
addRecordingEvent,
readRecordings,
Expand All @@ -38,10 +41,11 @@ import {
UploadAllOptions,
UploadOptions,
type ExternalRecordingEntry,
type UnstructuredMetadata,
} from "./types";
export type { UnstructuredMetadata } from "@replay-cli/shared/recording/types";
import { ReplayClient } from "./upload";
import { getDirectory, maybeLog, openExecutable } from "./utils";
import { sanitizeMetadata } from "@replay-cli/shared/recording/metadata/sanitizeMetadata";
export type { BrowserName, RecordingEntry } from "./types";
export { updateStatus } from "./updateStatus";

Expand Down Expand Up @@ -686,7 +690,7 @@ function removeAllRecordings(opts: Options = {}) {
}

function addLocalRecordingMetadata(recordingId: string, metadata: Record<string, unknown>) {
add(recordingId, metadata);
addMetadata(recordingId, metadata);
}

async function updateMetadata({
Expand Down Expand Up @@ -727,15 +731,15 @@ async function updateMetadata({
});

const data = Object.assign(md, ...keyedObjects);
const sanitized = await sanitize(data);
const sanitized = await sanitizeMetadata(data);

debug("Sanitized metadata: %O", sanitized);

const recordings = listAllRecordings({ directory, filter, includeCrashes });

recordings.forEach(r => {
maybeLog(verbose, `Setting metadata for ${r.id}`);
add(r.id, sanitized);
addMetadata(r.id, sanitized);
});
}

Expand Down Expand Up @@ -829,7 +833,6 @@ async function version() {

export {
ExternalRecordingEntry,
UnstructuredMetadata,
addLocalRecordingMetadata,
getDirectory,
launchBrowser,
Expand Down
132 changes: 0 additions & 132 deletions packages/replay/src/metadata/env.test.ts

This file was deleted.

16 changes: 1 addition & 15 deletions packages/replay/src/metadata/env.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1 @@
import { defaulted, string } from "superstruct";

type Resolver = string | ((env: NodeJS.ProcessEnv) => string | undefined);

const firstEnvValueOf =
(...envKeys: Resolver[]) =>
() =>
envKeys.reduce<string | undefined>(
(a, k) => a || (typeof k === "function" ? k(process.env) : process.env[k]),
undefined
);

const envString = (...envKeys: Resolver[]) => defaulted(string(), firstEnvValueOf(...envKeys));

export { firstEnvValueOf, envString };
export * from "@replay-cli/shared/recording/metadata/legacy/env";
85 changes: 4 additions & 81 deletions packages/replay/src/metadata/index.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,5 @@
import { appendFileSync } from "fs";
import path from "path";

import { Options, UnstructuredMetadata } from "../types";
import { getDirectory, maybeLog } from "../utils";

import * as test from "./test";
export { sanitizeMetadata as sanitize } from "@replay-cli/shared/recording/metadata/sanitizeMetadata";
export { addMetadata as add } from "@replay-cli/shared/recording/metadata/addMetadata";
import * as source from "./source";

// Each known metadata block should have a sanitizer that will check the contents before the upload
const handlers = {
test: test.validate,
source: source.validate,
};

type AllowedKey = keyof typeof handlers;
const ALLOWED_KEYS = Object.keys(handlers);

function isAllowedKey(key: string): key is AllowedKey {
return ALLOWED_KEYS.includes(key);
}

// Sanitizing arbitrary recording metadata before uploading by removing any
// non-object values (allowing null) and limiting object values to known keys or
// userspace keys prefixed by `x-`.
async function sanitize(metadata: UnstructuredMetadata, opts: Options = {}) {
const updated: UnstructuredMetadata = {};
for (const key of Object.keys(metadata)) {
const value = metadata[key];

if (typeof value !== "object") {
maybeLog(
opts.verbose,
`Ignoring metadata key "${key}". Expected an object but received ${typeof value}`
);

continue;
}

if (value === null || key.startsWith("x-")) {
// passthrough null or userspace types
updated[key] = value;
} else if (isAllowedKey(key)) {
// validate known types
const validated = await handlers[key](metadata as any);
Object.assign(updated, validated);
} else {
// and warn when dropping all other types
maybeLog(
opts.verbose,
`Ignoring metadata key "${key}". Custom metadata blocks must be prefixed by "x-". Try "x-${key}" instead.`
);
}
}

return updated;
}

/**
* Adds unstructured metadata to the local recordings database.
*
* New metadata will be merged with existing data. If the same key is used by
* multiple entries, the most recent entry's value will be used.
*
* Metadata is not validated until the recording is uploaded so arbitrary keys
* may be used here to manage recordings before upload.
*
* @param recordingId UUID of the recording
* @param metadata Recording metadata
*/
function add(recordingId: string, metadata: UnstructuredMetadata) {
const entry = {
id: recordingId,
kind: "addMetadata",
metadata,
timestamp: Date.now(),
};

appendFileSync(path.join(getDirectory(), "recordings.log"), `\n${JSON.stringify(entry)}\n`);
}

export { add, sanitize, source, test };
import * as test from "./test";
export { source, test };
23 changes: 0 additions & 23 deletions packages/replay/src/metadata/source.test.ts

This file was deleted.

Loading

0 comments on commit 134591c

Please sign in to comment.