appcenter-cli
Version:
Command line tool for Visual Studio App Center
253 lines (209 loc) • 8.96 kB
text/typescript
import { AppCommand, CommandArgs, CommandResult, ErrorCodes, failure, hasArg, help, longName, shortName, success } from "../../util/commandline";
import { AppCenterClient, models, clientRequest } from "../../util/apis";
import { out, supportsCsv } from "../../util/interaction";
import { inspect } from "util";
import * as _ from "lodash";
import { DefaultApp } from "../../util/profile";
import { parseDate } from "./lib/date-parsing-helper";
import { startDateHelpMessage, endDateHelpMessage } from "./lib/analytics-constants";
const debug = require("debug")("appcenter-cli:commands:analytics:audience");
("Show audience statistics")
export default class AudienceCommand extends AppCommand {
constructor(args: CommandArgs) {
super(args);
supportsCsv(this.additionalSupportedOutputFormats);
}
(startDateHelpMessage)
("s")
("start")
public startDate: string;
(endDateHelpMessage)
("e")
("end")
public endDate: string;
("Specify app version to show statistics for")
("V")
("app-version")
public appVersion: string;
("Show devices statistics")
("devices")
public devices: boolean;
("Show country statistics")
("countries")
public countries: boolean;
("Show languages statistics")
("languages")
public languages: boolean;
("Show active users statistics")
("active-users")
public activeUsers: boolean;
("output")
("Format of output for this command: json, csv")
public format: string;
public async run(client: AppCenterClient): Promise<CommandResult> {
const app: DefaultApp = this.app;
const appVersion = this.getAppVersion();
const startDate = parseDate(this.startDate,
new Date(new Date().setHours(0, 0, 0, 0)),
`start date value ${this.startDate} is not a valid date string`);
const endDate = parseDate(this.endDate,
new Date(),
`end date value ${this.endDate} is not a valid date string`);
if (!this.devices && !this.countries && !this.languages && !this.activeUsers) {
// when no switches are specified, all the data should be shown
this.devices = this.countries = this.languages = this.activeUsers = true;
}
const promises: Array<Promise<void>> = [];
const statistics: IStatisticsObject = {};
if (this.devices) {
promises.push(this.loadDevicesStatistics(statistics, client, app, startDate, endDate, appVersion));
}
if (this.countries) {
promises.push(this.loadCountriesStatistics(statistics, client, app, startDate, endDate, appVersion));
}
if (this.languages) {
promises.push(this.loadLanguagesStatistics(statistics, client, app, startDate, endDate, appVersion));
}
if (this.activeUsers) {
promises.push(this.loadActiveUsersStatistics(statistics, client, app, startDate, endDate, appVersion));
}
await out.progress("Loading statistics...", Promise.all(promises));
this.outputStatistics(statistics);
return success();
}
private getAppVersion(): string[] {
return !_.isNil(this.appVersion) ? [this.appVersion] : undefined;
}
private async loadDevicesStatistics(statisticsObject: IStatisticsObject, client: AppCenterClient, app: DefaultApp, startDate: Date, endDate: Date, appVersion?: string[]): Promise<void> {
try {
const httpRequest = await clientRequest<models.AnalyticsModels>((cb) => client.analytics.modelCounts(startDate, app.ownerName, app.appName, {
end: endDate,
versions: appVersion
}, cb));
const result = httpRequest.result;
statisticsObject.devices = result.modelsProperty.map((model) => ({
count: model.count,
value: model.modelName,
percentage: calculatePercentage(model.count, result.total)
}));
} catch (error) {
debug(`Failed to get devices count statistics - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, "failed to get devices count statistics");
}
}
private async loadCountriesStatistics(statisticsObject: IStatisticsObject, client: AppCenterClient, app: DefaultApp, startDate: Date, endDate: Date, appVersion?: string[]): Promise<void> {
try {
const httpRequest = await clientRequest<models.Places>((cb) => client.analytics.placeCounts(startDate, app.ownerName, app.appName, {
end: endDate,
versions: appVersion
}, cb));
const result = httpRequest.result;
statisticsObject.countries = result.places.map((place) => ({
count: place.count,
value: place.code,
percentage: calculatePercentage(place.count, result.total)
}));
} catch (error) {
debug(`Failed to get countries statistics - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, "failed to get countries statistics");
}
}
private async loadLanguagesStatistics(statisticsObject: IStatisticsObject, client: AppCenterClient, app: DefaultApp, startDate: Date, endDate: Date, appVersion?: string[]): Promise<void> {
try {
const httpRequest = await clientRequest<models.Languages>((cb) => client.analytics.languageCounts(startDate, app.ownerName, app.appName, {
end: endDate,
versions: appVersion
}, cb));
const result = httpRequest.result;
statisticsObject.languages = result.languages.map((language) => ({
count: language.count,
value: language.languageName,
percentage: calculatePercentage(language.count, result.total)
}));
} catch (error) {
debug(`Failed to get languages statistics - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, "failed to get languages statistics");
}
}
private async loadActiveUsersStatistics(statisticsObject: IStatisticsObject, client: AppCenterClient, app: DefaultApp, startDate: Date, endDate: Date, appVersion?: string[]): Promise<void> {
try {
const httpRequest = await clientRequest<models.ActiveDeviceCounts>((cb) => client.analytics.deviceCounts(startDate, app.ownerName, app.appName, {
end: endDate,
versions: appVersion
}, cb));
const result = httpRequest.result;
statisticsObject.activeUsers = result.daily.map((dailyData, index) => ({
date: new Date(dailyData.datetime),
daily: dailyData.count,
weekly: result.weekly[index].count,
monthly: result.monthly[index].count
}));
} catch (error) {
debug(`Failed to get active users statistics - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, "failed to get active users statistics");
}
}
private outputStatistics(statisticsObject: IStatisticsObject): void {
out.reportObjectAsTitledTables((stats: IStatisticsObject, numberFormatter, dateFormatter, percentageFormatter) => {
const tableArray: out.NamedTables = [];
if (stats.devices) {
tableArray.push({
name: "Devices",
content: [["", "Count", "Change"]].concat(stats.devices.map((device) => toArray(device, numberFormatter, percentageFormatter)))
});
}
if (stats.countries) {
tableArray.push({
name: "Countries",
content: [["", "Count", "Change"]].concat(stats.countries.map((country) => toArray(country, numberFormatter, percentageFormatter)))
});
}
if (stats.languages) {
tableArray.push({
name: "Languages",
content: [["", "Count", "Change"]].concat(stats.languages.map((language) => toArray(language, numberFormatter, percentageFormatter)))
});
}
if (stats.activeUsers) {
tableArray.push({
name: "Active Users",
content: [["Date", "Monthly", "Weekly", "Daily"]]
.concat(stats.activeUsers.map((activeUsersStatistics) => [
dateFormatter(activeUsersStatistics.date),
numberFormatter(activeUsersStatistics.monthly),
numberFormatter(activeUsersStatistics.weekly),
numberFormatter(activeUsersStatistics.daily)
]))
});
}
return tableArray;
}, statisticsObject);
}
}
interface IStatisticsObject {
devices?: IStatisticsForValue[];
countries?: IStatisticsForValue[];
languages?: IStatisticsForValue[];
activeUsers?: IActiveUsersCount[];
}
interface IStatisticsForValue {
count: number;
value: string;
percentage: number;
}
function toArray(stats: IStatisticsForValue, numberFormatter: (num: number) => string, percentageFormatter: (percentage: number) => string): string[] {
return [stats.value, numberFormatter(stats.count), percentageFormatter(stats.percentage)];
}
function calculatePercentage(count: number, total: number): number {
return count / total * 100;
}
interface IActiveUsersCount {
date: Date;
daily: number;
weekly: number;
monthly: number;
}