appcenter-cli
Version:
Command line tool for Visual Studio App Center
222 lines (184 loc) • 8.64 kB
text/typescript
import { AppCommand, CommandArgs, CommandResult, ErrorCodes, failure, hasArg, help, longName, shortName, success, defaultValue } from "../../../util/commandline";
import { AppCenterClient, models, clientRequest, ClientResponse } 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 { calculatePercentChange } from "../lib/percent-change-helper";
import { startDateHelpMessage, endDateHelpMessage } from "../lib/analytics-constants";
const debug = require("debug")("appcenter-cli:commands:analytics:events:show");
const pLimit = require("p-limit");
("Show statistics for events")
export default class ShowCommand extends AppCommand {
constructor(args: CommandArgs) {
super(args);
supportsCsv(this.additionalSupportedOutputFormats);
}
private static readonly numberOfParallelRequests = 10;
("Show statistics about event properties")
("properties")
public properties: boolean;
(startDateHelpMessage)
("s")
("start")
public startDate: string;
(endDateHelpMessage)
("e")
("end")
public endDate: string;
("Filter the list of events by app version")
("V")
("app-version")
public appVersion: string;
("Introduce the number of events that are being displayed. By default, all the events will be shown")
("c")
("number-of-events")
("200")
public eventCount: string;
("Filter the metrics to a specific event name")
("n")
("event-name")
public eventName: string;
("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.toArrayWithSingleElement(this.appVersion);
const eventName = this.toArrayWithSingleElement(this.eventName);
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`);
const eventCount = this.getEventCount();
const events: IEventStatistics[] = await out.progress(`Loading statistics...`, this.getEvents(client, app, startDate, endDate, eventCount, this.properties, appVersion, eventName));
this.outputStatistics(events);
return success();
}
private toArrayWithSingleElement(value: string): string[] {
return !_.isNil(value) ? [value] : undefined;
}
private getEventCount(): number {
const eventCount = Number(this.eventCount);
if (Number.isSafeInteger(eventCount) && eventCount >= 0) {
return eventCount;
} else {
throw failure(ErrorCodes.InvalidParameter, "--number-of-events should be non-negative integer");
}
}
private async getEvents(client: AppCenterClient, app: DefaultApp, startDate: Date, endDate: Date, eventCount: number, loadProperties: boolean, appVersions: string[] | undefined, eventNames: string[] | undefined): Promise<IEventStatistics[]> {
let eventsStatistics: IEventStatistics[];
try {
const httpContent = await clientRequest<models.Events>((cb) => client.analytics.eventsMethod(startDate, app.ownerName, app.appName, {
end: endDate,
versions: appVersions,
orderby: "count desc",
top: eventCount,
skip: 0,
eventName: eventNames,
inlinecount: "allpages"
}, cb));
eventsStatistics = httpContent.result.events.map((event) => ({
name: event.name,
count: event.count,
countChange: calculatePercentChange(event.count, event.previousCount),
users: event.deviceCount,
userChange: calculatePercentChange(event.deviceCount, event.previousDeviceCount),
perUser: event.countPerDevice,
perSession: event.countPerSession
}));
} catch (error) {
debug(`Failed to get events statistics - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, "failed to get events statistics");
}
if (loadProperties) {
const limit = pLimit(ShowCommand.numberOfParallelRequests);
const propertiesPromises = eventsStatistics.map((eventStats) => this.getProperties(client, app, eventStats.name, startDate, endDate, appVersions, limit));
(await Promise.all(propertiesPromises)).forEach((properties, index) => eventsStatistics[index].properties = properties);
}
return eventsStatistics;
}
private async getProperties(client: AppCenterClient, app: DefaultApp, eventName: string, startDate: Date, endDate: Date, appVersions: string[] | undefined, limit: any): Promise<IPropertyStatistics[]> {
let propertiesNames: string[];
try {
const httpContent = await (limit(() => clientRequest<models.EventProperties>((cb) => client.analytics.eventPropertiesMethod(eventName, app.ownerName, app.appName, cb))) as Promise<ClientResponse<models.EventProperties>>);
propertiesNames = httpContent.result.eventProperties;
} catch (error) {
debug(`Failed to get event properties of event ${eventName} - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, `failed to get event properties of event ${eventName}`);
}
const valuesStatsPromises = propertiesNames.map((propertyName) => this.getPropertyValueStatistics(client, app, eventName, propertyName, startDate, endDate, appVersions, limit));
return (await Promise.all(valuesStatsPromises)).map((valueStats, index) => ({
name: propertiesNames[index],
valuesStatistics: valueStats
}));
}
private async getPropertyValueStatistics(client: AppCenterClient, app: DefaultApp, eventName: string, eventPropertyName: string, startDate: Date, endDate: Date, appVersions: string[] | undefined, limit: any): Promise<IPropertyValueStatistics[]> {
try {
const httpContent = await (limit(() => clientRequest<models.EventPropertyValues>((cb) => client.analytics.eventPropertyCounts(eventName, eventPropertyName, startDate, app.ownerName, app.appName, {
end: endDate,
versions: appVersions
}, cb))) as Promise<ClientResponse<models.EventPropertyValues>>);
return httpContent.result.values.map((value) => ({
value: value.name,
count: value.count,
countChange: calculatePercentChange(value.count, value.previousCount),
}));
} catch (error) {
debug(`Failed to get values of property ${eventPropertyName} of event ${eventName} - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, `failed to get values of property ${eventPropertyName} of event ${eventName}`);
}
}
private outputStatistics(statistics: IEventStatistics[]) {
out.reportObjectAsTitledTables((events, numberFormatter, dateFormatter, percentageFormatter) => {
const table: out.NamedTables = [];
const eventsTable: out.INamedTable = {
name: "Events",
content: [["Name", "Count", "Count Change", "Users", "User Change", "Per User", "Per Session"]]
};
table.push(eventsTable);
for (const event of events) {
eventsTable.content.push([event.name, numberFormatter(event.count), percentageFormatter(event.countChange),
numberFormatter(event.users), percentageFormatter(event.userChange), numberFormatter(event.perUser), numberFormatter(event.perSession)]);
if (event.properties) {
for (const property of event.properties) {
eventsTable.content.push({
name: property.name,
content: [["Value", "Count", "Count Change"]].concat(
property.valuesStatistics.map((valueStats) => [valueStats.value, numberFormatter(valueStats.count), percentageFormatter(valueStats.countChange)]))
});
}
}
}
return table;
}, statistics);
}
}
interface IEventStatistics {
name: string;
count: number;
countChange: number;
users: number;
userChange: number;
perUser: number;
perSession: number;
properties?: IPropertyStatistics[];
}
interface IPropertyStatistics {
name: string;
valuesStatistics: IPropertyValueStatistics[];
}
interface IPropertyValueStatistics {
value: string;
count: number;
countChange: number;
}