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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"/oclif.manifest.json"
],
"dependencies": {
"@knocklabs/mgmt": "^0.16.1",
"@knocklabs/mgmt": "0.21.0",
"@oclif/core": "^3",
"@oclif/plugin-help": "^6",
"@prantlf/jsonlint": "^14.1.0",
Expand Down
69 changes: 69 additions & 0 deletions src/commands/audience/archive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Args, Flags, ux } from "@oclif/core";

import BaseCommand from "@/lib/base-command";
import { ApiError } from "@/lib/helpers/error";
import * as CustomFlags from "@/lib/helpers/flag";
import { promptToConfirm, spinner } from "@/lib/helpers/ux";

export default class AudienceArchive extends BaseCommand<
typeof AudienceArchive
> {
static summary = "Archive an audience (affects ALL environments).";

static description = `
WARNING: Archiving an audience affects ALL environments and cannot be undone.
Use this command with caution.
`;

static flags = {
environment: Flags.string({
required: true,
summary: "The environment to use.",
}),
branch: CustomFlags.branch,
force: Flags.boolean({
summary: "Skip confirmation prompt.",
}),
};

static args = {
audienceKey: Args.string({
required: true,
description: "The key of the audience to archive.",
}),
};

async run(): Promise<void> {
const { audienceKey } = this.props.args;
const { force, environment } = this.props.flags;

// Confirm before archiving since this affects all environments
if (!force) {
const confirmed = await promptToConfirm(
`WARNING: Archiving audience \`${audienceKey}\` will affect ALL environments.\n` +
`This action cannot be undone. Continue?`,
);
if (!confirmed) {
this.log("Archive cancelled.");
return;
}
}

spinner.start(`‣ Archiving audience \`${audienceKey}\``);

try {
await this.apiV1.mgmtClient.audiences.archive(audienceKey, {
environment,
});

spinner.stop();

this.log(
`‣ Successfully archived audience \`${audienceKey}\` across all environments.`,
);
} catch (error) {
spinner.stop();
ux.error(new ApiError((error as Error).message));
}
}
}
129 changes: 129 additions & 0 deletions src/commands/audience/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Audience } from "@knocklabs/mgmt/resources/audiences";
import { Args, Flags, ux } from "@oclif/core";

import BaseCommand from "@/lib/base-command";
import { formatCommandScope } from "@/lib/helpers/command";
import { formatDateTime } from "@/lib/helpers/date";
import { ApiError } from "@/lib/helpers/error";
import * as CustomFlags from "@/lib/helpers/flag";
import { spinner } from "@/lib/helpers/ux";

export default class AudienceGet extends BaseCommand<typeof AudienceGet> {
static summary = "Display a single audience from an environment.";

static flags = {
environment: Flags.string({
default: "development",
summary: "The environment to use.",
}),
branch: CustomFlags.branch,
"hide-uncommitted-changes": Flags.boolean({
summary: "Hide any uncommitted changes.",
}),
};

static args = {
audienceKey: Args.string({
required: true,
}),
};

static enableJsonFlag = true;

async run(): Promise<Audience | void> {
spinner.start("‣ Loading");

const { audience } = await this.loadAudience();

spinner.stop();

const { flags } = this.props;
if (flags.json) return audience;

this.render(audience);
}

private async loadAudience(): Promise<{
audience: Audience;
}> {
const { audienceKey } = this.props.args;
const { flags } = this.props;

try {
const audience = await this.apiV1.mgmtClient.audiences.retrieve(
audienceKey,
{
environment: flags.environment,
branch: flags.branch,
hide_uncommitted_changes: flags["hide-uncommitted-changes"],
},
);

return { audience };
} catch (error) {
ux.error(new ApiError((error as Error).message));
}
}

render(audience: Audience): void {
const { audienceKey } = this.props.args;
const { environment: env, "hide-uncommitted-changes": committedOnly } =
this.props.flags;

const qualifier =
env === "development" && !committedOnly ? "(including uncommitted)" : "";

const scope = formatCommandScope(this.props.flags);
this.log(
`‣ Showing audience \`${audienceKey}\` in ${scope} ${qualifier}\n`,
);

/*
* Audience table
*/

const rows = [
{
key: "Name",
value: audience.name,
},
{
key: "Key",
value: audience.key,
},
{
key: "Type",
value: audience.type,
},
{
key: "Description",
value: audience.description || "-",
},
{
key: "Created at",
value: formatDateTime(audience.created_at),
},
{
key: "Updated at",
value: formatDateTime(audience.updated_at),
},
];

ux.table(rows, {
key: {
header: "Audience",
minWidth: 24,
},
value: {
header: "",
minWidth: 24,
},
});

// Show segments for dynamic audiences
if (audience.type === "dynamic" && audience.segments) {
this.log("\nSegments:");
this.log(JSON.stringify(audience.segments, null, 2));
}
}
}
129 changes: 129 additions & 0 deletions src/commands/audience/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Audience } from "@knocklabs/mgmt/resources/audiences";
import { Flags, ux } from "@oclif/core";

import BaseCommand from "@/lib/base-command";
import { formatCommandScope } from "@/lib/helpers/command";
import { formatDate } from "@/lib/helpers/date";
import { ApiError } from "@/lib/helpers/error";
import * as CustomFlags from "@/lib/helpers/flag";
import {
maybePromptPageAction,
pageFlags,
PageInfo,
paramsForPageAction,
} from "@/lib/helpers/page";
import { spinner } from "@/lib/helpers/ux";

type ListAudienceData = {
entries: Audience[];
page_info: PageInfo;
};

export default class AudienceList extends BaseCommand<typeof AudienceList> {
static summary = "Display all audiences for an environment.";

static flags = {
environment: Flags.string({
default: "development",
summary: "The environment to use.",
}),
branch: CustomFlags.branch,
"hide-uncommitted-changes": Flags.boolean({
summary: "Hide any uncommitted changes.",
}),
...pageFlags,
};

static enableJsonFlag = true;

async run(): Promise<ListAudienceData | void> {
const data = await this.request();

const { flags } = this.props;
if (flags.json) return data;

return this.render(data);
}

async request(
pageParams: { after?: string; before?: string } = {},
): Promise<ListAudienceData> {
const { flags } = this.props;

spinner.start("‣ Loading");

try {
const page = await this.apiV1.mgmtClient.audiences.list({
environment: flags.environment,
branch: flags.branch,
hide_uncommitted_changes: flags["hide-uncommitted-changes"],
limit: flags.limit,
after: pageParams.after ?? flags.after,
before: pageParams.before ?? flags.before,
});

spinner.stop();

return {
entries: page.entries,
page_info: page.page_info,
};
} catch (error) {
spinner.stop();
throw new ApiError((error as Error).message);
}
}

async render(data: ListAudienceData): Promise<void> {
const { entries } = data;
const { environment: env, "hide-uncommitted-changes": committedOnly } =
this.props.flags;

const qualifier =
env === "development" && !committedOnly ? "(including uncommitted)" : "";

const scope = formatCommandScope(this.props.flags);
this.log(
`‣ Showing ${entries.length} audiences in ${scope} ${qualifier}\n`,
);

/*
* Audiences list table
*/

ux.table(entries as unknown as Record<string, unknown>[], {
key: {
header: "Key",
},
name: {
header: "Name",
},
description: {
header: "Description",
},
type: {
header: "Type",
},
updated_at: {
header: "Updated at",
get: (entry) => formatDate(entry.updated_at as string),
},
});

return this.prompt(data);
}

async prompt(data: ListAudienceData): Promise<void> {
const { page_info } = data;

const pageAction = await maybePromptPageAction(page_info);
const pageParams = pageAction && paramsForPageAction(pageAction, page_info);

if (pageParams) {
this.log("\n");

const nextData = await this.request(pageParams);
return this.render(nextData);
}
}
}
Loading