Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/api/audiences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import apiClient from './apiClient'
import { buildHeaders } from './common'

const BASE_URL = '/v1/projects/:project/audiences'

export const fetchAudiences = async (
token: string,
project_id: string
) => {
return apiClient.get(`${BASE_URL}`, {
headers: buildHeaders(token),
params: {
project: project_id,
}
})
}
29 changes: 15 additions & 14 deletions src/api/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type Variation = z.infer<typeof schemas.Variation>
export type Feature = z.infer<typeof schemas.Feature>
export type FeatureConfig = z.infer<typeof schemas.FeatureConfig>
export type Audience = z.infer<typeof schemas.Audience>
export type Target = z.infer<typeof schemas.Target>

export type CreateEnvironmentParams = z.infer<typeof schemas.CreateEnvironmentDto>
export const CreateEnvironmentDto = schemas.CreateEnvironmentDto
Expand Down Expand Up @@ -42,26 +43,26 @@ export const UpdateTargetDto = schemas.UpdateTargetDto
export type AudienceOperatorWithAudienceMatchFilter = z.infer<typeof schemas.AudienceOperatorWithAudienceMatchFilter>
export type Filters = z.infer<typeof schemas.AudienceOperatorWithAudienceMatchFilter.shape.filters>

type AllFilter = z.infer<typeof schemas.AllFilter>
export const AllFilter = schemas.AllFilter
export type AllFilter = z.infer<typeof schemas.AllFilter>
export const AllFilterSchema = schemas.AllFilter

type UserFilter = z.infer<typeof schemas.UserFilter>
export const UserFilter = schemas.UserFilter
export type UserFilter = z.infer<typeof schemas.UserFilter>
export const UserFilterSchema = schemas.UserFilter

type UserCountryFilter = z.infer<typeof schemas.UserCountryFilter>
export const UserCountryFilter = schemas.UserCountryFilter
export type UserCountryFilter = z.infer<typeof schemas.UserCountryFilter>
export const UserCountryFilterSchema = schemas.UserCountryFilter

type UserAppVersionFilter = z.infer<typeof schemas.UserAppVersionFilter>
export const UserAppVersionFilter = schemas.UserAppVersionFilter
export type UserAppVersionFilter = z.infer<typeof schemas.UserAppVersionFilter>
export const UserAppVersionFilterSchema = schemas.UserAppVersionFilter

type UserPlatformVersionFilter = z.infer<typeof schemas.UserPlatformVersionFilter>
export const UserPlatformVersionFilter = schemas.UserPlatformVersionFilter
export type UserPlatformVersionFilter = z.infer<typeof schemas.UserPlatformVersionFilter>
export const UserPlatformVersionFilterSchema = schemas.UserPlatformVersionFilter

type UserCustomFilter = z.infer<typeof schemas.UserCustomFilter>
export const UserCustomFilter = schemas.UserCustomFilter
export type UserCustomFilter = z.infer<typeof schemas.UserCustomFilter>
export const UserCustomFilterSchema = schemas.UserCustomFilter

type AudienceMatchFilter = z.infer<typeof schemas.AudienceMatchFilter>
export const AudienceMatchFilter = schemas.AudienceMatchFilter
export type AudienceMatchFilter = z.infer<typeof schemas.AudienceMatchFilter>
export const AudienceMatchFilterSchema = schemas.AudienceMatchFilter

export type Filter =
| AllFilter
Expand Down
7 changes: 5 additions & 2 deletions src/commands/targeting/disable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Variation } from '../../api/schemas'
import Base from '../base'
import { renderTargetingTree } from '../../ui/targetingTree'
import { getFeatureAndEnvironmentKeyFromArgs } from '../../utils/targeting'
import { fetchAudiences } from '../../api/audiences'

export default class DisableTargeting extends Base {
static hidden = false
Expand Down Expand Up @@ -45,10 +46,12 @@ export default class DisableTargeting extends Base {
if (flags.headless) {
this.writer.showResults(updatedTargeting)
} else {
const audiences = await fetchAudiences(this.authToken, this.projectKey)
renderTargetingTree(
[updatedTargeting],
[updatedTargeting],
[environment],
feature.variations as Variation[]
feature.variations as Variation[],
audiences
)
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/commands/targeting/enable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Variation } from '../../api/schemas'
import { renderTargetingTree } from '../../ui/targetingTree'
import Base from '../base'
import { getFeatureAndEnvironmentKeyFromArgs } from '../../utils/targeting'
import { fetchAudiences } from '../../api/audiences'

export default class EnableTargeting extends Base {
static hidden = false
Expand Down Expand Up @@ -45,10 +46,12 @@ export default class EnableTargeting extends Base {
if (flags.headless) {
this.writer.showResults(updatedTargeting)
Comment thread
nsalamad marked this conversation as resolved.
} else {
const audiences = await fetchAudiences(this.authToken, this.projectKey)
renderTargetingTree(
[updatedTargeting],
[updatedTargeting],
[environment],
feature.variations as Variation[]
feature.variations as Variation[],
audiences
)
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/commands/targeting/get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ describe('targeting get', () => {
.get(`/v1/projects/${projectKey}/environments`)
.reply(200, mockEnvironments)
)
.nock(BASE_URL, (api) => api
.get(`/v1/projects/${projectKey}/audiences`)
.reply(200, [])
)
.stdout()
.command(['targeting get', featureKey, ...authFlags])
.it('returns all targeting for a feature', (ctx) => {
Expand All @@ -146,6 +150,10 @@ describe('targeting get', () => {
.get(`/v1/projects/${projectKey}/environments`)
.reply(200, mockEnvironments)
)
.nock(BASE_URL, (api) => api
.get(`/v1/projects/${projectKey}/audiences`)
.reply(200, [])
)
.stdout()
.command(['targeting get', featureKey, 'development', ...authFlags])
.it('includes environment in query params',
Expand All @@ -166,6 +174,10 @@ describe('targeting get', () => {
.get(`/v1/projects/${projectKey}/environments`)
.reply(200, mockEnvironments)
)
.nock(BASE_URL, (api) => api
.get(`/v1/projects/${projectKey}/audiences`)
.reply(200, [])
)
.stub(inquirer, 'prompt', () => {
return { feature: { key: 'prompted-feature-id' } }
})
Expand Down
16 changes: 14 additions & 2 deletions src/commands/targeting/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import inquirer from '../../ui/autocomplete'
import { fetchEnvironments } from '../../api/environments'
import { fetchVariations } from '../../api/variations'
import { fetchTargetingForFeature } from '../../api/targeting'
import { environmentPrompt, EnvironmentPromptResult, featurePrompt, FeaturePromptResult } from '../../ui/prompts'
import {
environmentPrompt,
EnvironmentPromptResult,
featurePrompt,
FeaturePromptResult
} from '../../ui/prompts'
import { renderTargetingTree } from '../../ui/targetingTree'
import Base from '../base'
import { Feature, Environment } from '../../api/schemas'
import { fetchAudiences } from '../../api/audiences'

type Params = {
featureKey?: string,
Expand Down Expand Up @@ -83,7 +89,13 @@ export default class DetailedTargeting extends Base {
[params.environment] : await fetchEnvironments(this.authToken, this.projectKey)
const variations = params.feature?.variations
|| await fetchVariations(this.authToken, this.projectKey, params.featureKey)
renderTargetingTree(targeting, environments, variations)
const audiences = await fetchAudiences(this.authToken, this.projectKey)
renderTargetingTree(
targeting,
environments,
variations,
audiences
)
}
}
}
8 changes: 8 additions & 0 deletions src/commands/targeting/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ describe('targeting update', () => {
.query({ environment: envKey })
.reply(200, mockResponseHeadless)
)
.nock(BASE_URL, (api) => api
.get(`/v1/projects/${projectKey}/audiences`)
.reply(200, [])
)
.stdout()
.command([
'targeting update',
Expand Down Expand Up @@ -275,6 +279,10 @@ describe('targeting update', () => {
.query({ environment: envKey })
.reply(200, mockTargetingRules)
)
.nock(BASE_URL, (api) => api
.get(`/v1/projects/${projectKey}/audiences`)
.reply(200, [])
)
.nock(BASE_URL, (api) => api
.patch(
`/v1/projects/${projectKey}/features/${featureKey}/configurations`,
Expand Down
12 changes: 10 additions & 2 deletions src/commands/targeting/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { fetchVariations } from '../../api/variations'
import { FeatureConfig, UpdateFeatureConfigDto } from '../../api/schemas'
import { targetingStatusPrompt } from '../../ui/prompts/targetingPrompts'
import UpdateCommand from '../updateCommand'
import { fetchAudiences } from '../../api/audiences'

export default class UpdateTargeting extends UpdateCommand {
static hidden = false
Expand Down Expand Up @@ -86,6 +87,7 @@ export default class UpdateTargeting extends UpdateCommand {

const environment = await fetchEnvironmentByKey(this.authToken, this.projectKey, envKey)
const variations = await fetchVariations(this.authToken, this.projectKey, featureKey)
const audiences = await fetchAudiences(this.authToken, this.projectKey)

const status = this.convertStatusFlagValue(flags.status)
let targets: UpdateFeatureConfigDto['targets']
Expand All @@ -111,7 +113,12 @@ export default class UpdateTargeting extends UpdateCommand {
this.authToken, this.projectKey, featureKey, envKey
)
const targetingListPrompt = new TargetingListOptions(
featureTargetingRules.targets, this.writer, this.authToken, this.projectKey, featureKey
featureTargetingRules.targets,
audiences,
this.writer,
this.authToken,
this.projectKey,
featureKey
)
targetingListPrompt.variations = variations
this.prompts.push(targetingListPrompt.getTargetingListPrompt())
Expand All @@ -137,7 +144,8 @@ export default class UpdateTargeting extends UpdateCommand {
renderTargetingTree(
[result],
environment ? [environment] : [],
variations
variations,
audiences
)
this.showSuggestedCommand(featureKey, envKey, result)
}
Expand Down
12 changes: 10 additions & 2 deletions src/ui/prompts/listPrompts/filterListPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ import { Prompt } from '../types'
import { chooseFields } from '../../../utils/prompts'
import { UserSubType } from '../../../api/targeting'
import { renderDefinitionTree } from '../../targetingTree'
import { buildAudienceNameMap, replaceAudienceIdInFilter } from '../../../utils/audiences'
import Writer from '../../writer'

export class FilterListOptions extends ListOptionsPrompt<Filter> {
itemType = 'Filter'
messagePrompt = 'Manage your filters'

operator: Audience['filters']['operator'] = 'and'
audiences: Audience[]

constructor(list: Filter[], audiences: Audience[], writer: Writer) {
super(list, writer)
this.audiences = audiences
}

async promptAddItem(): Promise<ListOption<Filter>> {
const { type } = await inquirer.prompt([filterTypePrompt])
Expand Down Expand Up @@ -170,7 +178,7 @@ export class FilterListOptions extends ListOptionsPrompt<Filter> {
return []
}

async printListOptions(list?: ListOption<Filter>[]) {
printListOptions(list?: ListOption<Filter>[]) {
const listToPrint = list || this.list
if (listToPrint.length === 0) {
this.writer.infoMessage(`No existing ${this.itemType}s.`)
Expand All @@ -180,7 +188,7 @@ export class FilterListOptions extends ListOptionsPrompt<Filter> {
this.writer.title(this.messagePrompt)
this.writer.infoMessage(`Current ${this.itemType}s:`)
const values = listToPrint.map((item) => item.value.item)
renderDefinitionTree(values, this.operator)
renderDefinitionTree(values, this.operator, this.audiences)
this.writer.blankLine()
}
}
2 changes: 1 addition & 1 deletion src/ui/prompts/listPrompts/listOptionsPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export abstract class ListOptionsPrompt<T> {
* Prints the list of human-readable names of the list to the console
* @param ListOption<T>[]
*/
async printListOptions(list?: ListOption<T>[]) {
printListOptions(list?: ListOption<T>[]) {
const listToPrint = list || this.list
if (listToPrint.length === 0) {
this.writer.infoMessage(`No existing ${this.itemType}s.`)
Expand Down
25 changes: 19 additions & 6 deletions src/ui/prompts/listPrompts/targetingListPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ReorderItemPrompt
} from './promptOptions'
import { servePrompt } from '../targetingPrompts'
import { UpdateTargetParams, Variation } from '../../../api/schemas'
import { Audience, Filters, UpdateTargetParams, Variation } from '../../../api/schemas'
import { FilterListOptions } from './filterListPrompt'
import Writer from '../../writer'
import { renderRulesTree } from '../../targetingTree'
Expand All @@ -23,9 +23,18 @@ export class TargetingListOptions extends ListOptionsPrompt<UpdateTargetParams>
featureKey: string

variations: Variation[] = []
audiences: Audience[]

constructor(list: UpdateTargetParams[], writer: Writer, authToken: string, projectKey: string, featureKey: string) {
constructor(
list: UpdateTargetParams[],
audiences: Audience[],
writer: Writer,
authToken: string,
projectKey: string,
featureKey: string
) {
super(list, writer)
this.audiences = audiences
this.featureKey = featureKey
this.authToken = authToken
this.projectKey = projectKey
Expand Down Expand Up @@ -57,7 +66,7 @@ export class TargetingListOptions extends ListOptionsPrompt<UpdateTargetParams>
featureKey: this.featureKey
})
const operator = 'and'
const filterListOptions = new FilterListOptions([], this.writer)
const filterListOptions = new FilterListOptions([], this.audiences, this.writer)
filterListOptions.operator = operator
const filters = await filterListOptions.prompt()
const target = {
Expand Down Expand Up @@ -103,7 +112,11 @@ export class TargetingListOptions extends ListOptionsPrompt<UpdateTargetParams>
projectKey: this.projectKey,
featureKey: this.featureKey
})
const filterListOptions = new FilterListOptions(targetToEdit.audience.filters.filters, this.writer)
const filterListOptions = new FilterListOptions(
targetToEdit.audience.filters.filters,
this.audiences,
this.writer
)
filterListOptions.operator = targetToEdit.audience.filters.operator
const filters = await filterListOptions.prompt()
const target = {
Expand All @@ -125,7 +138,7 @@ export class TargetingListOptions extends ListOptionsPrompt<UpdateTargetParams>
}))
}

async printListOptions(list?: ListOption<UpdateTargetParams>[]) {
printListOptions(list?: ListOption<UpdateTargetParams>[]) {
const listToPrint = list || this.list
if (listToPrint.length === 0) {
this.writer.infoMessage(`No existing ${this.itemType}s.`)
Expand All @@ -135,7 +148,7 @@ export class TargetingListOptions extends ListOptionsPrompt<UpdateTargetParams>
this.writer.title(this.messagePrompt)
this.writer.infoMessage(`Current ${this.itemType}s:`)
const values = listToPrint.map((item) => item.value.item)
renderRulesTree(values, this.variations)
renderRulesTree(values, this.variations, this.audiences)
this.writer.blankLine()
}
}
Loading