appcenter-cli
Version:
Command line tool for Visual Studio App Center
216 lines (176 loc) • 8.41 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 { calculatePercentChange } from "./lib/percent-change-helper";
import { startDateHelpMessage, endDateHelpMessage } from "./lib/analytics-constants";
const debug = require("debug")("appcenter-cli:commands:analytics:sessions");
const IsoDuration = require("iso8601-duration");
("Show statistics for sessions")
export default class SessionCommand 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 session durations")
("durations")
public duration: boolean;
("Show session statistics")
("statistics")
public statistics: 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.duration && !this.statistics) {
// when no switches are specified, all the data should be shown
this.duration = this.statistics = true;
}
const promises: Array<Promise<any>> = [];
const requestResult: IRequestsResult = {};
// durations statistics required for both "duration" and "statistics" switches
promises.push(this.loadSessionDurationsStatistics(client, app, startDate, endDate, appVersion)
.then((distributions) => requestResult.sessionDurationsDistribution = distributions));
if (this.statistics) {
promises.push(this.loadSessionCountsStatistics(client, app, startDate, endDate, appVersion)
.then((counts: models.DateTimeCounts[]) => requestResult.sessionCounts = counts));
// get session counts for the previous interval of the same length
const previousEndDate = startDate;
const previousStartDate = new Date(previousEndDate.valueOf() - (endDate.valueOf() - startDate.valueOf()));
promises.push(this.loadSessionCountsStatistics(client, app, previousStartDate, previousEndDate, appVersion)
.then((counts: models.DateTimeCounts[]) => requestResult.previousSessionCounts = counts));
}
await out.progress("Loading statistics...", Promise.all(promises));
const outputObject: IJsonOutput = this.toJsonOutput(requestResult);
this.outputStatistics(outputObject);
return success();
}
private getAppVersion(): string[] {
return !_.isNil(this.appVersion) ? [this.appVersion] : undefined;
}
private async loadSessionDurationsStatistics(client: AppCenterClient, app: DefaultApp, startDate: Date, endDate: Date, appVersion?: string[]): Promise<models.SessionDurationsDistribution> {
try {
return (await clientRequest<models.SessionDurationsDistribution>((cb) => client.analytics.sessionDurationsDistributionMethod(startDate, app.ownerName, app.appName, {
end: endDate,
versions: appVersion
}, cb))).result;
} catch (error) {
debug(`Failed to get sessions duration distributions - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, "failed to get sessions duration distributions");
}
}
private async loadSessionCountsStatistics(client: AppCenterClient, app: DefaultApp, startDate: Date, endDate: Date, appVersion?: string[]): Promise<models.DateTimeCounts[]> {
try {
const httpResponse = await clientRequest<models.DateTimeCounts[]>((cb) => client.analytics.sessionCounts(startDate, "P1D", app.ownerName, app.appName, {
end: endDate,
versions: appVersion
}, cb));
return httpResponse.result;
} catch (error) {
debug(`Failed to get session counts - ${inspect(error)}`);
throw failure(ErrorCodes.Exception, "failed to get session counts");
}
}
private toJsonOutput(requestsResult: IRequestsResult): IJsonOutput {
const jsonOutput: IJsonOutput = {};
if (this.duration) {
jsonOutput.sessions = requestsResult.sessionDurationsDistribution.distribution;
}
if (this.statistics) {
const totalSessionsCount = _.sumBy(requestsResult.sessionCounts, ((dateTimeCounts) => dateTimeCounts.count));
const previousTotalSessionsCount = _.sumBy(requestsResult.previousSessionCounts, ((dateTimeCounts) => dateTimeCounts.count));
const averageSessionsPerDay = totalSessionsCount / requestsResult.sessionCounts.length;
const previousAverageSessionsPerDay = previousTotalSessionsCount / requestsResult.previousSessionCounts.length;
const averageSessionDuration: number = IsoDuration.toSeconds(IsoDuration.parse(requestsResult.sessionDurationsDistribution.averageDuration));
const previousAverageSessionDuration: number = IsoDuration.toSeconds(IsoDuration.parse(requestsResult.sessionDurationsDistribution.previousAverageDuration));
jsonOutput.statistics = {
totalSessions: {
count: totalSessionsCount,
percentage: calculatePercentChange(totalSessionsCount, previousTotalSessionsCount)
},
averageSessionsPerDay: {
count: averageSessionsPerDay,
percentage: calculatePercentChange(averageSessionsPerDay, previousAverageSessionsPerDay)
},
averageSessionsLength: {
seconds: averageSessionDuration,
percentage: calculatePercentChange(averageSessionDuration, previousAverageSessionDuration)
}
};
}
return jsonOutput;
}
private outputStatistics(statisticsObject: IJsonOutput): void {
out.reportObjectAsTitledTables((stats: IJsonOutput, numberFormatter, dateFormatter, percentageFormatter) => {
const tableArray: out.NamedTables = [];
if (stats.sessions) {
tableArray.push({
name: "Session Durations",
content: [["", "Count"]].concat(stats.sessions.map((group) => [group.bucket, numberFormatter(group.count)]))
});
}
if (stats.statistics) {
tableArray.push({
name: "Session Statistics",
content: [
["", "Count", "Change"],
["Total Sessions"].concat(toArray(stats.statistics.totalSessions, numberFormatter, percentageFormatter)),
["Average Sessions Per Day"].concat(toArray(stats.statistics.averageSessionsPerDay, numberFormatter, percentageFormatter)),
["Average Session Length (sec)", numberFormatter(stats.statistics.averageSessionsLength.seconds), percentageFormatter(stats.statistics.averageSessionsLength.percentage)]
]
});
}
return tableArray;
}, statisticsObject);
}
}
interface IRequestsResult {
sessionDurationsDistribution?: models.SessionDurationsDistribution;
sessionCounts?: models.DateTimeCounts[];
previousSessionCounts?: models.DateTimeCounts[];
}
interface IJsonOutput {
sessions?: models.SessionDurationsDistributionDistributionItem[];
statistics?: {
totalSessions: IChangingCount;
averageSessionsPerDay: IChangingCount;
averageSessionsLength: {
seconds: number,
percentage: number;
}
};
}
interface IChangingCount {
count: number;
percentage: number;
}
function toArray(changingCount: IChangingCount, numberFormatter: (num: number) => string, percentageFormatter: (value: number) => string): string[] {
return [numberFormatter(changingCount.count), percentageFormatter(changingCount.percentage)];
}