Skip to content

Commit

Permalink
Merge pull request #12 from vtex/abtester_version
Browse files Browse the repository at this point in the history
Fix / prohibit the use of old ABTester versions
  • Loading branch information
valentino-amadeus committed May 5, 2021
2 parents cfe6854 + 0c8b994 commit 9251b2c
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 87 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.4] - 2021-05-05

### Fixed
- [abtest] Prohibit the use of old versions of vtex.ab-tester

## [0.1.3] - 2021-04-20

### Fixed
Expand Down
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -17,7 +17,7 @@ $ npm install -g @vtex/cli-plugin-abtest
$ oclif-example COMMAND
running command...
$ oclif-example (-v|--version|version)
@vtex/cli-plugin-abtest/0.1.3 linux-x64 node-v12.22.1
@vtex/cli-plugin-abtest/0.1.4 linux-x64 node-v12.22.1
$ oclif-example --help [COMMAND]
USAGE
$ oclif-example COMMAND
Expand All @@ -44,7 +44,7 @@ OPTIONS
--trace Ensure all requests to VTEX IO are traced
```

_See code: [build/commands/workspace/abtest/finish.ts](https://github.com/vtex/cli-plugin-abtest/blob/v0.1.3/build/commands/workspace/abtest/finish.ts)_
_See code: [build/commands/workspace/abtest/finish.ts](https://github.com/vtex/cli-plugin-abtest/blob/v0.1.4/build/commands/workspace/abtest/finish.ts)_

## `oclif-example workspace:abtest:start`

Expand All @@ -60,7 +60,7 @@ OPTIONS
--trace Ensure all requests to VTEX IO are traced
```

_See code: [build/commands/workspace/abtest/start.ts](https://github.com/vtex/cli-plugin-abtest/blob/v0.1.3/build/commands/workspace/abtest/start.ts)_
_See code: [build/commands/workspace/abtest/start.ts](https://github.com/vtex/cli-plugin-abtest/blob/v0.1.4/build/commands/workspace/abtest/start.ts)_

## `oclif-example workspace:abtest:status`

Expand All @@ -76,5 +76,5 @@ OPTIONS
--trace Ensure all requests to VTEX IO are traced
```

_See code: [build/commands/workspace/abtest/status.ts](https://github.com/vtex/cli-plugin-abtest/blob/v0.1.3/build/commands/workspace/abtest/status.ts)_
_See code: [build/commands/workspace/abtest/status.ts](https://github.com/vtex/cli-plugin-abtest/blob/v0.1.4/build/commands/workspace/abtest/status.ts)_
<!-- commandsstop -->
2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "@vtex/cli-plugin-abtest",
"description": "vtex plugin abtest",
"version": "0.1.3",
"version": "0.1.4",
"bugs": "https://github.com/vtex/cli-plugin-abtest/issues",
"dependencies": {
"@oclif/command": "^1",
Expand Down
4 changes: 2 additions & 2 deletions src/modules/abtest/finish.ts
Expand Up @@ -5,7 +5,7 @@ import { map, prop, filter } from 'ramda'
import { logger, promptConfirm, SessionManager } from 'vtex'

import { default as abTestStatus } from './status'
import { abtester, installedABTester } from './utils'
import { abtester, checkABTester } from './utils'

const { account } = SessionManager.getSingleton()

Expand Down Expand Up @@ -33,7 +33,7 @@ const promptWorkspaceToFinishABTest = () =>
.then(prop('workspace'))

export default async () => {
await installedABTester()
await checkABTester()
const workspace = await promptWorkspaceToFinishABTest()
const promptAnswer = await promptContinue(workspace)

Expand Down
71 changes: 12 additions & 59 deletions src/modules/abtest/start.ts
@@ -1,45 +1,15 @@
import chalk from 'chalk'
import enquirer from 'enquirer'
import { compose, fromPairs, keys, map, mapObjIndexed, prop, values, zip } from 'ramda'
import semver from 'semver'

import { logger, promptConfirm } from 'vtex'

import {
abtester,
installedABTester,
formatDays,
checkABTester,
promptConstraintDuration,
promptProductionWorkspace,
promptProportionTrafic,
SIGNIFICANCE_LEVELS,
} from './utils'

const promptSignificanceLevel = async (): Promise<string> => {
const significanceTimePreviews = await Promise.all(
compose<any, number[], Array<Promise<number>>>(
map((value) => abtester.preview(value as number)),
values
)(SIGNIFICANCE_LEVELS)
)

const significanceTimePreviewMap = fromPairs(zip(keys(SIGNIFICANCE_LEVELS), significanceTimePreviews))

return enquirer
.prompt<{ level: string }>({
name: 'level',
message: 'Choose the significance level:',
type: 'select',
choices: values(
mapObjIndexed((value, key) => ({
message: `${key} (~ ${formatDays(value as number)})`,
value: key,
}))(significanceTimePreviewMap)
) as any,
})
.then(prop('level'))
}

const promptContinue = (workspace: string, significanceLevel?: string) => {
return significanceLevel
? promptConfirm(
Expand All @@ -56,41 +26,24 @@ ${chalk.green('master')} and ${chalk.green(workspace)}. Proceed?`,
}

export default async () => {
const abTesterManifest = await installedABTester()
await checkABTester()
const workspace = await promptProductionWorkspace('Choose production workspace to start A/B test:')

try {
const [version] = abTesterManifest.version.split('-')

if (semver.satisfies(version, '>=0.10.0')) {
logger.info(`Setting workspace ${chalk.green(workspace)} to A/B test`)
const promptAnswer = await promptContinue(workspace)

if (!promptAnswer) return
const proportion = Number(await promptProportionTrafic())
const timeLength = Number(await promptConstraintDuration())
logger.info(`Setting workspace ${chalk.green(workspace)} to A/B test`)
const promptAnswer = await promptContinue(workspace)

await abtester.customStart(workspace, timeLength, proportion)
logger.info(`Workspace ${chalk.green(String(workspace))} in A/B test`)
logger.info(`You can stop the test using ${chalk.blue('vtex workspace abtest finish')}`)
if (!promptAnswer) return
const proportion = Number(await promptProportionTrafic())
const timeLength = Number(await promptConstraintDuration())

return
}

const significanceLevel = await promptSignificanceLevel()
const promptAnswer = await promptContinue(workspace, significanceLevel)

if (!promptAnswer) return
const significanceLevelValue = SIGNIFICANCE_LEVELS[significanceLevel]

logger.info(`Setting workspace ${chalk.green(workspace)} to A/B test with \
${significanceLevel} significance level`)
await abtester.startLegacy(workspace, significanceLevelValue)
logger.info(`Workspace ${chalk.green(workspace)} in A/B test`)
logger.info(`You can stop the test using ${chalk.blue('vtex workspace abtest finish')}`)
try {
await abtester.customStart(workspace, timeLength, proportion)
} catch (err) {
if (err.message === 'Workspace not found') {
console.log(`Test not initialized due to workspace ${workspace} not found by ab-tester.`)
}
}

logger.info(`Workspace ${chalk.green(String(workspace))} in A/B test`)
logger.info(`You can stop the test using ${chalk.blue('vtex workspace abtest finish')}`)
}
4 changes: 2 additions & 2 deletions src/modules/abtest/status.ts
Expand Up @@ -4,7 +4,7 @@ import numbro from 'numbro'
import R from 'ramda'
import { SessionManager, logger, createTable } from 'vtex'

import { abtester, formatDuration, installedABTester } from './utils'
import { abtester, formatDuration, checkABTester } from './utils'

interface ABTestStatus {
ABTestBeginning: string
Expand Down Expand Up @@ -119,7 +119,7 @@ const printResultsTable = (testInfo: ABTestStatus) => {
export default async () => {
const { account } = SessionManager.getSingleton()

await installedABTester()
await checkABTester()
let abTestInfo = []

abTestInfo = await abtester.status()
Expand Down
40 changes: 21 additions & 19 deletions src/modules/abtest/utils.ts
@@ -1,19 +1,15 @@
import { AppManifest } from '@vtex/api'
import chalk from 'chalk'
import enquirer from 'enquirer'
import numbro from 'numbro'
import { compose, filter, map, prop } from 'ramda'
import * as env from 'vtex'
import { createFlowIssueError, createAppsClient, createWorkspacesClient, SessionManager } from 'vtex'
import { createFlowIssueError, createAppsClient, createWorkspacesClient, SessionManager, COLORS } from 'vtex'
import { ABTester } from '../../clients/apps/ABTester'
import semver from 'semver'

const DEFAULT_TIMEOUT = 15000
const VERSION_THRESHOLD = '0.12.0'

export const SIGNIFICANCE_LEVELS: Record<string, number> = {
low: 0.5,
mid: 0.7,
high: 0.9,
}
const DEFAULT_TIMEOUT = 15000

const { account } = SessionManager.getSingleton()

Expand All @@ -23,16 +19,6 @@ const options = { timeout: (env.envTimeout || DEFAULT_TIMEOUT) as number }
export const abtester = ABTester.createClient({ workspace: 'master' }, { ...options, retries: 3 })
export const apps = createAppsClient({ workspace: 'master' })

export const formatDays = (days: number) => {
let suffix = 'days'

if (days === 1) {
suffix = 'day'
}

return `${numbro(days).format('0,0')} ${suffix}`
}

export const formatDuration = (durationInMinutes: number) => {
const minutes = durationInMinutes % 60
const hours = Math.trunc(durationInMinutes / 60) % 24
Expand All @@ -41,7 +27,7 @@ export const formatDuration = (durationInMinutes: number) => {
return `${days} days, ${hours} hours and ${minutes} minutes`
}

export const installedABTester = async (): Promise<AppManifest> => {
const installedABTester = async (): Promise<AppManifest> => {
try {
return await apps.getApp('vtex.ab-tester@x')
} catch (e) {
Expand All @@ -56,6 +42,22 @@ testing functionality`)
}
}

const checkABTesterVersion = (version: string) => {
const [versionNumber] = version.split('-')

if (!semver.satisfies(versionNumber, `>${VERSION_THRESHOLD}`)) {
throw createFlowIssueError(`You are using ${chalk.yellow(`vtex.ab-tester@${version}`)}, \
which is of an excessively old version. Please, use a version newer than ${chalk.green(VERSION_THRESHOLD)} \
\nTo get the latest version, run ${chalk.hex(COLORS.PINK)('vtex install vtex.ab-tester')}`)
}
}

export const checkABTester = async () => {
const abTesterManifest = await installedABTester()

checkABTesterVersion(abTesterManifest.version)
}

export const promptProductionWorkspace = async (promptMessage: string): Promise<string> => {
const workspaces = createWorkspacesClient()
const productionWorkspaces = await workspaces.list(account).then(
Expand Down

0 comments on commit 9251b2c

Please sign in to comment.