Skip to content

Commit

Permalink
Upgrade analytics.js to analytics-next (browser SDK) (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
silesky authored Apr 25, 2023
1 parent ff7ba5f commit 8477826
Show file tree
Hide file tree
Showing 13 changed files with 1,192 additions and 287 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,7 @@ test-env/**

# intelliJ
.idea


# all build artifacts
**/build/**
6 changes: 2 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
module.exports = {
preset: 'ts-jest',
testPathIgnorePatterns: [
'./src/__tests__/__helpers__/',
'./src/__tests__/__data__/'
],
testMatch: ['**/?(*.)+(test).[jt]s?(x)'],
modulePathIgnorePatterns: ['/dist/'],
setupFilesAfterEnv: ['./jest.setup.js'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json'
Expand Down
11 changes: 11 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// eslint-disable-next-line no-undef
const supressLog = (...args) => {
// avoid Warning: No API token found at test-env/build
if (typeof args[0] === 'string' && args[0].includes('API token')) {
return undefined
} else {
console.error(...args)
}
}

console.error = supressLog
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
"devDependencies": {
"@oclif/test": "^2.1",
"@segment/analytics-next": "^1.51.6",
"@segment/analytics-react-native": "^2.2.1",
"@types/analytics-node": "^3.1.9",
"@types/chai": "^4",
Expand Down Expand Up @@ -99,7 +100,8 @@
"postpack": "shx rm -f oclif.manifest.json",
"posttest": "yarn lint",
"prepack": "yarn build && oclif manifest && oclif readme",
"test": "jest --runInBand",
"test": "yarn test:typedef && jest --runInBand",
"test:typedef": "npx ts-node src/__tests__/ts-typedef-tests/run.ts",
"version": "oclif readme && git add README.md"
},
"engines": {
Expand Down
103 changes: 10 additions & 93 deletions src/__tests__/commands/__snapshots__/build.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5174,102 +5174,19 @@ import Ajv, { ErrorObject } from 'ajv'
* You can install it by following instructions at: https://segment.com/docs/sources/website/analytics.js/quickstart/
* Make sure to also include the TypeScript declarations with: \`npm install --dev @types/segment-analytics\`
*/
declare global {
import type { AnalyticsSnippet, Analytics, AnalyticsBrowser, Options } from '@segment/analytics-next'

declare global {
interface Window {
analytics: SegmentAnalytics.AnalyticsJS
analytics: AnalyticsSnippet;
}
}

type AnyAnalytics = AnalyticsSnippet | Analytics | AnalyticsBrowser

/** The callback exposed by analytics.js. */
export type Callback = () => void

/** A dictionary of options. For example, enable or disable specific destinations for the call. */
export interface Options {
/**
* Selectivly filter destinations. By default all destinations are enabled.
* https://segment.com/docs/sources/website/analytics.js/#selecting-destinations
*/
integrations?: {
[key: string]: boolean | { [key: string]: any }
}
/**
* A dictionary of extra context to attach to the call.
* https://segment.com/docs/spec/common/#context
*/
context?: Context
}

/**
* Context is a dictionary of extra information that provides useful context about a datapoint.
* @see {@link https://segment.com/docs/spec/common/#context}
*/
export interface Context extends Record<string, any> {
active?: boolean
app?: {
name?: string
version?: string
build?: string
}
campaign?: {
name?: string
source?: string
medium?: string
term?: string
content?: string
}
device?: {
id?: string
manufacturer?: string
model?: string
name?: string
type?: string
version?: string
}
ip?: string
locale?: string
location?: {
city?: string
country?: string
latitude?: string
longitude?: string
region?: string
speed?: string
}
network?: {
bluetooth?: string
carrier?: string
cellular?: string
wifi?: string
}
os?: {
name?: string
version?: string
}
page?: {
hash?: string
path?: string
referrer?: string
search?: string
title?: string
url?: string
}
referrer?: {
type?: string
name?: string
url?: string
link?: string
}
screen?: {
density?: string
height?: string
width?: string
}
timezone?: string
groupId?: string
traits?: Record<string, any>
userAgent?: string
}

export type ViolationHandler = (
message: Record<string, any>,
violations: ErrorObject[]
Expand Down Expand Up @@ -5297,7 +5214,7 @@ export const defaultValidationErrorHandler: ViolationHandler = (message, violati

let onViolation = defaultValidationErrorHandler

let analytics: () => SegmentAnalytics.AnalyticsJS | undefined = () => {
let analytics: () => AnyAnalytics | undefined = () => {
return window.analytics;
};

Expand All @@ -5307,7 +5224,7 @@ export interface TypewriterOptions {
* Underlying analytics instance where analytics calls are forwarded on to.
* Defaults to window.analytics.
*/
analytics?: SegmentAnalytics.AnalyticsJS;
analytics?: AnyAnalytics;
/**
* Handler fired when if an event does not match its spec. This handler
* does not fire in production mode, because it requires inlining the full
Expand All @@ -5325,7 +5242,7 @@ export interface TypewriterOptions {
* @param {TypewriterOptions} options - the options to upsert
*
* @typedef {Object} TypewriterOptions
* @property {AnalyticsJS} [analytics] - Underlying analytics instance where analytics
* @property {AnyAnalytics} [analytics] - Underlying analytics instance where analytics
* calls are forwarded on to. Defaults to window.analytics.
* @property {Function} [onViolation] - Handler fired when if an event does not match its spec. This handler does not fire in
* production mode, because it requires inlining the full JSON Schema spec for each event in your Tracking Plan. By default,
Expand Down Expand Up @@ -5785,7 +5702,7 @@ const clientAPI = {
* @param {TypewriterOptions} options - the options to upsert
*
* @typedef {Object} TypewriterOptions
* @property {AnalyticsJS} [analytics] - Underlying analytics instance where analytics
* @property {AnyAnalytics} [analytics] - Underlying analytics instance where analytics
* calls are forwarded on to. Defaults to window.analytics.
* @property {Function} [onViolation] - Handler fired when if an event does not match its spec. This handler does not fire in
* production mode, because it requires inlining the full JSON Schema spec for each event in your Tracking Plan. By default,
Expand Down
103 changes: 10 additions & 93 deletions src/__tests__/commands/__snapshots__/production.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -679,102 +679,19 @@ export interface NoIDType {
* You can install it by following instructions at: https://segment.com/docs/sources/website/analytics.js/quickstart/
* Make sure to also include the TypeScript declarations with: \`npm install --dev @types/segment-analytics\`
*/
declare global {
import type { AnalyticsSnippet, Analytics, AnalyticsBrowser, Options } from '@segment/analytics-next'
declare global {
interface Window {
analytics: SegmentAnalytics.AnalyticsJS
analytics: AnalyticsSnippet;
}
}
type AnyAnalytics = AnalyticsSnippet | Analytics | AnalyticsBrowser
/** The callback exposed by analytics.js. */
export type Callback = () => void
/** A dictionary of options. For example, enable or disable specific destinations for the call. */
export interface Options {
/**
* Selectivly filter destinations. By default all destinations are enabled.
* https://segment.com/docs/sources/website/analytics.js/#selecting-destinations
*/
integrations?: {
[key: string]: boolean | { [key: string]: any }
}
/**
* A dictionary of extra context to attach to the call.
* https://segment.com/docs/spec/common/#context
*/
context?: Context
}
/**
* Context is a dictionary of extra information that provides useful context about a datapoint.
* @see {@link https://segment.com/docs/spec/common/#context}
*/
export interface Context extends Record<string, any> {
active?: boolean
app?: {
name?: string
version?: string
build?: string
}
campaign?: {
name?: string
source?: string
medium?: string
term?: string
content?: string
}
device?: {
id?: string
manufacturer?: string
model?: string
name?: string
type?: string
version?: string
}
ip?: string
locale?: string
location?: {
city?: string
country?: string
latitude?: string
longitude?: string
region?: string
speed?: string
}
network?: {
bluetooth?: string
carrier?: string
cellular?: string
wifi?: string
}
os?: {
name?: string
version?: string
}
page?: {
hash?: string
path?: string
referrer?: string
search?: string
title?: string
url?: string
}
referrer?: {
type?: string
name?: string
url?: string
link?: string
}
screen?: {
density?: string
height?: string
width?: string
}
timezone?: string
groupId?: string
traits?: Record<string, any>
userAgent?: string
}
export type ViolationHandler = (
message: Record<string, any>,
violations: any[]
Expand All @@ -801,7 +718,7 @@ export const defaultValidationErrorHandler: ViolationHandler = (message, violati
};
let analytics: () => SegmentAnalytics.AnalyticsJS | undefined = () => {
let analytics: () => AnyAnalytics | undefined = () => {
return window.analytics;
};
Expand All @@ -811,7 +728,7 @@ export interface TypewriterOptions {
* Underlying analytics instance where analytics calls are forwarded on to.
* Defaults to window.analytics.
*/
analytics?: SegmentAnalytics.AnalyticsJS;
analytics?: AnyAnalytics;
/**
* Handler fired when if an event does not match its spec. This handler
* does not fire in production mode, because it requires inlining the full
Expand All @@ -829,7 +746,7 @@ export interface TypewriterOptions {
* @param {TypewriterOptions} options - the options to upsert
*
* @typedef {Object} TypewriterOptions
* @property {AnalyticsJS} [analytics] - Underlying analytics instance where analytics
* @property {AnyAnalytics} [analytics] - Underlying analytics instance where analytics
* calls are forwarded on to. Defaults to window.analytics.
* @property {Function} [onViolation] - Handler fired when if an event does not match its spec. This handler does not fire in
* production mode, because it requires inlining the full JSON Schema spec for each event in your Tracking Plan. By default,
Expand Down Expand Up @@ -1240,7 +1157,7 @@ const clientAPI = {
* @param {TypewriterOptions} options - the options to upsert
*
* @typedef {Object} TypewriterOptions
* @property {AnalyticsJS} [analytics] - Underlying analytics instance where analytics
* @property {AnyAnalytics} [analytics] - Underlying analytics instance where analytics
* calls are forwarded on to. Defaults to window.analytics.
* @property {Function} [onViolation] - Handler fired when if an event does not match its spec. This handler does not fire in
* production mode, because it requires inlining the full JSON Schema spec for each event in your Tracking Plan. By default,
Expand Down
13 changes: 13 additions & 0 deletions src/__tests__/ts-typedef-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Type definition "integration" tests for the typescript SDKs

```
npx ts-node src/__tests__/ts-typedef-tests/run.ts
```
These tests confirm:
- That the typescript clients have no type errors
- That the typescript clients typings behaves as expected

Running the tests...
1. Build typescript client using cli
2. Imports that client into a type definition test that asserts that it's typed correctly
3. Type checks both the client (artifact) and the type definition
44 changes: 44 additions & 0 deletions src/__tests__/ts-typedef-tests/build-sdks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import fs from "node:fs";
import path from "node:path";
import { setupEnv } from "../__helpers__/environment";
import { run } from "../__helpers__/oclif-runner";

const TEST_ENV_PATH = "production";

const configurations = [
["typescript", "analytics-node", "segment.ts"],
["typescript", "analytics-js", "segment.ts"],
];

export const buildSDKs = async () => {
for (const config of configurations) {
const [language, sdk, filename, plan, id, outputPath, legacyId] = config;
const testPath = await setupEnv(
TEST_ENV_PATH,
language,
sdk,
plan,
id,
outputPath,
legacyId
);
await run(["production", "-c", testPath]);
// basically, we write the test files to a new build path relative to this script.
// typechecking ./test-env directly is a little complicated because of .tsconfig configuration + hashed directory name
const output = fs
.readFileSync(path.join(testPath, filename), {
encoding: "utf-8",
})
.replace(/version:.*\d.*/g, "");

const BUILD_PATH = path.resolve(__dirname, "build");
if (!fs.existsSync(BUILD_PATH)) {
fs.mkdirSync(BUILD_PATH);
}
fs.writeFileSync(
path.resolve(BUILD_PATH, `${language}-${sdk}.ts`),
output,
{ encoding: "utf-8" }
);
}
};
Loading

0 comments on commit 8477826

Please sign in to comment.