Skip to content

Commit

Permalink
feat(misc): add onboarding a/b testing
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz committed Jul 30, 2024
1 parent efa59fa commit 59d155e
Show file tree
Hide file tree
Showing 18 changed files with 320 additions and 177 deletions.
3 changes: 1 addition & 2 deletions packages/create-nx-plugin/bin/create-nx-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { output } from 'create-nx-workspace/src/utils/output';
import { NxCloud } from 'create-nx-workspace/src/utils/nx/nx-cloud';
import type { PackageManager } from 'create-nx-workspace/src/utils/package-manager';
import { showNxWarning } from 'create-nx-workspace/src/utils/nx/show-nx-warning';
import { printNxCloudSuccessMessage } from 'create-nx-workspace/src/utils/nx/nx-cloud';
import {
messages,
recordStat,
Expand Down Expand Up @@ -164,7 +163,7 @@ async function main(parsedArgs: yargs.Arguments<CreateNxPluginArguments>) {
});

if (parsedArgs.nxCloud && workspaceInfo.nxCloudInfo) {
printNxCloudSuccessMessage(workspaceInfo.nxCloudInfo);
console.log(workspaceInfo.nxCloudInfo);
}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/create-nx-workspace/bin/create-nx-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
withPackageManager,
} from '../src/internal-utils/yargs-options';
import { showNxWarning } from '../src/utils/nx/show-nx-warning';
import { printNxCloudSuccessMessage } from '../src/utils/nx/nx-cloud';
import { messages, recordStat } from '../src/utils/nx/ab-testing';
import { mapErrorToBodyLines } from '../src/utils/error-utils';
import { existsSync } from 'fs';
Expand Down Expand Up @@ -233,7 +232,7 @@ async function main(parsedArgs: yargs.Arguments<Arguments>) {
});

if (parsedArgs.nxCloud && workspaceInfo.nxCloudInfo) {
printNxCloudSuccessMessage(workspaceInfo.nxCloudInfo);
console.log(workspaceInfo.nxCloudInfo);
}

if (isKnownPreset(parsedArgs.preset)) {
Expand Down
34 changes: 15 additions & 19 deletions packages/create-nx-workspace/src/create-workspace.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CreateWorkspaceOptions } from './create-workspace-options';
import { output } from './utils/output';
import { setupNxCloud } from './utils/nx/nx-cloud';
import { getOnboardingInfo, setupNxCloud } from './utils/nx/nx-cloud';
import { createSandbox } from './create-sandbox';
import { createEmptyWorkspace } from './create-empty-workspace';
import { createPreset } from './create-preset';
Expand Down Expand Up @@ -51,31 +51,27 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
);
}

let nxCloudInstallRes;
let connectUrl: string | undefined;
let nxCloudInfo: string | undefined;
if (nxCloud !== 'skip') {
nxCloudInstallRes = await setupNxCloud(
directory,
packageManager,
nxCloud,
useGitHub
);
const token = await setupNxCloud(directory, nxCloud, useGitHub);

if (nxCloud !== 'yes') {
await setupCI(
directory,
nxCloud,
packageManager,
nxCloudInstallRes?.code === 0
);
await setupCI(directory, nxCloud, packageManager);
}

const { connectCloudUrl, output } = await getOnboardingInfo(
nxCloud,
token,
directory,
useGitHub
);
connectUrl = connectCloudUrl;
nxCloudInfo = output;
}

if (!skipGit) {
try {
let connectUrl;
if (nxCloudInstallRes?.code === 0) {
connectUrl = extractConnectUrl(nxCloudInstallRes?.stdout);
}
await initializeGitRepo(directory, { defaultBase, commit, connectUrl });
} catch (e) {
if (e instanceof Error) {
Expand All @@ -90,7 +86,7 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
}

return {
nxCloudInfo: nxCloudInstallRes?.stdout,
nxCloudInfo,
directory,
};
}
Expand Down
12 changes: 1 addition & 11 deletions packages/create-nx-workspace/src/utils/ci/setup-ci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,8 @@ import { getPackageManagerCommand, PackageManager } from '../package-manager';
export async function setupCI(
directory: string,
ci: string,
packageManager: PackageManager,
nxCloudSuccessfullyInstalled: boolean
packageManager: PackageManager
) {
if (!nxCloudSuccessfullyInstalled) {
output.error({
title: `CI workflow generation skipped`,
bodyLines: [
`Nx Cloud was not installed`,
`The autogenerated CI workflow requires Nx Cloud to be set-up.`,
],
});
}
const ciSpinner = ora(`Generating CI workflow`).start();
try {
const pmc = getPackageManagerCommand(packageManager);
Expand Down
122 changes: 122 additions & 0 deletions packages/create-nx-workspace/src/utils/nx/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const outputMessages = {
'create-nx-workspace-success-ci-setup': [
{
code: 'nx-cloud-workspace-push-goto',
createMessage: (url: string) => ({
title: `Your Nx Cloud workspace is ready.`,
type: 'success',
bodyLines: [
`To claim it, connect it to your Nx Cloud account:`,
`- Push your repository to your git hosting provider.`,
`- Go to the following URL to connect your workspace to Nx Cloud:`,
'',
`${url}`,
],
}),
},
{
code: 'nx-cloud-powered-ci-setup-visit',
createMessage: (url: string) => ({
title: `Your CI setup powered by Nx Cloud is almost complete.`,
type: 'success',
bodyLines: [`Finish it by visiting: ${url}`],
}),
},
{
code: 'nx-cloud-powered-ci-setup-connect',
createMessage: (url: string) => ({
title: `Your CI setup powered by Nx Cloud is almost complete.`,
type: 'success',
bodyLines: [`Connect your repository: ${url}`],
}),
},
{
code: 'ci-setup-visit',
createMessage: (url: string) => ({
title: `Your CI setup is almost complete.`,
type: 'success',
bodyLines: [`Finish it by visiting: ${url}`],
}),
},
{
code: 'ci-setup-connect',
createMessage: (url: string) => ({
title: `Your CI setup is almost complete.`,
type: 'success',
bodyLines: [`Connect your repository: ${url}`],
}),
},
],
'create-nx-workspace-success-cache-setup': [
{
code: 'nx-cloud-workspace-push-goto',
createMessage: (url: string) => ({
title: `Your Nx Cloud workspace is ready.`,
type: 'success',
bodyLines: [
`To claim it, connect it to your Nx Cloud account:`,
`- Push your repository to your git hosting provider.`,
`- Go to the following URL to connect your workspace to Nx Cloud:`,
'',
`${url}`,
],
}),
},
{
code: 'nx-cloud-remote-cache-setup-finish',
createMessage: (url: string) => ({
title: `Your Nx Cloud remote cache setup is almost complete.`,
type: 'success',
bodyLines: [`Finish it by visiting: ${url}`],
}),
},
{
code: 'nx-cloud-remote-cache-setup-connect',
createMessage: (url: string) => ({
title: `Your Nx Cloud remote cache setup is almost complete.`,
type: 'success',
bodyLines: [`Connect your repository: ${url}`],
}),
},
{
code: 'remote-cache-visit',
createMessage: (url: string) => ({
title: `Your remote cache setup is almost complete.`,
type: 'success',
bodyLines: [`Finish it by visiting: ${url}`],
}),
},
{
code: 'remote-cache-connect',
createMessage: (url: string) => ({
title: `Your remote cache setup is almost complete.`,
type: 'success',
bodyLines: [`Connect your repository: ${url}`],
}),
},
],
} as const;
type OutputMessageKey = keyof typeof outputMessages;

class ABTestingMessages {
private selectedMessages: Record<string, number> = {};
getMessageFactory(key: OutputMessageKey) {
console.log({ key, outputMessages });
if (this.selectedMessages[key] === undefined) {
if (process.env.NX_GENERATE_DOCS_PROCESS === 'true') {
this.selectedMessages[key] = 0;
} else {
this.selectedMessages[key] = Math.floor(
Math.random() * outputMessages[key].length
);
}
}
return outputMessages[key][this.selectedMessages[key]!];
}
}

const messages = new ABTestingMessages();

export function getMessageFactory(key: OutputMessageKey) {
return messages.getMessageFactory(key);
}
78 changes: 49 additions & 29 deletions packages/create-nx-workspace/src/utils/nx/nx-cloud.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import * as ora from 'ora';
import { execAndWait } from '../child-process-utils';
import { output } from '../output';
import { getPackageManagerCommand, PackageManager } from '../package-manager';
import { CLIOutput, output } from '../output';
import { mapErrorToBodyLines } from '../error-utils';
import { getMessageFactory } from './messages';

export type NxCloud = 'yes' | 'github' | 'circleci' | 'skip';

export async function setupNxCloud(
directory: string,
packageManager: PackageManager,
nxCloud: NxCloud,
useGitHub?: boolean
) {
const nxCloudSpinner = ora(`Setting up Nx Cloud`).start();
try {
const pmc = getPackageManagerCommand(packageManager);
const res = await execAndWait(
`${
pmc.exec
} nx g nx:connect-to-nx-cloud --installationSource=create-nx-workspace --directory=${directory} ${
useGitHub ? '--github' : ''
} --no-interactive`,
directory
);
const { connectWorkspaceToCloud } = require(require.resolve(
'nx/src/command-line/connect/connect-to-nx-cloud',
{
paths: [directory],
}
)) as typeof import('nx/src/command-line/connect/connect-to-nx-cloud');

const accessToken = await connectWorkspaceToCloud({
installationSource: 'create-nx-workspace',
directory,
github: useGitHub,
sourceInfo: nxCloud,
});

if (nxCloud !== 'yes') {
nxCloudSpinner.succeed(
Expand All @@ -31,7 +33,7 @@ export async function setupNxCloud(
} else {
nxCloudSpinner.succeed('Nx Cloud has been set up successfully');
}
return res;
return accessToken;
} catch (e) {
nxCloudSpinner.fail();

Expand All @@ -50,19 +52,37 @@ export async function setupNxCloud(
}
}

export function printNxCloudSuccessMessage(nxCloudOut: string) {
// remove leading Nx carret and any new lines
const logContent = nxCloudOut.split('NX ')[1];
const indexOfTitleEnd = logContent.indexOf('\n');
const title = logContent.slice(0, logContent.indexOf('\n')).trim();
const bodyLines = logContent
.slice(indexOfTitleEnd)
.replace(/^\n*/, '') // remove leading new lines
.replace(/\n*$/, '') // remove trailing new lines
.split('\n')
.map((r) => r.trim());
output.warn({
title,
bodyLines,
});
export async function getOnboardingInfo(
nxCloud: NxCloud,
token: string,
directory: string,
useGithub?: boolean
) {
const { createNxCloudOnboardingURL } = require(require.resolve(
'nx/src/nx-cloud/utilities/url-shorten',
{
paths: [directory],
}
)) as typeof import('nx/src/nx-cloud/utilities/url-shorten');

const source =
nxCloud === 'yes'
? 'create-nx-workspace-success-cache-setup'
: 'create-nx-workspace-success-ci-setup';
const { code, createMessage } = getMessageFactory(source);
const connectCloudUrl = await createNxCloudOnboardingURL(
source,
token,
useGithub ??
(nxCloud === 'yes' || nxCloud === 'github' || nxCloud === 'circleci'),
code
);
const out = new CLIOutput(false);
const message = createMessage(connectCloudUrl);
if (message.type === 'success') {
out.success(message);
} else {
out.warn(message);
}
return { output: out.getOutput(), connectCloudUrl };
}
21 changes: 19 additions & 2 deletions packages/create-nx-workspace/src/utils/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ if (isCI()) {
(chalk as any).level = 0;
}

class CLIOutput {
export class CLIOutput {
outstream = this.real ? process.stdout : new FakeStdout();
constructor(private real = true) {}
/**
* Longer dash character which forms more of a continuous line when place side to side
* with itself, unlike the standard dash character
Expand Down Expand Up @@ -64,7 +66,7 @@ class CLIOutput {
dim = chalk.dim;

private writeToStdOut(str: string) {
process.stdout.write(str);
this.outstream.write(str);
}

private writeOutputTitle({
Expand Down Expand Up @@ -192,6 +194,21 @@ class CLIOutput {

this.addNewline();
}

getOutput() {
return this.outstream.toString();
}
}

export const output = new CLIOutput();

class FakeStdout {
private content = '';
write(str: string) {
this.content += str;
}

toString() {
return this.content;
}
}
Loading

0 comments on commit 59d155e

Please sign in to comment.