heroku 10.4.1-beta.1 → 10.5.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,7 @@ For other issues, [submit a support ticket](https://help.heroku.com/).
73
73
  * [`heroku teams`](docs/teams.md) - manage teams
74
74
  * [`heroku telemetry`](docs/telemetry.md) - list telemetry drains
75
75
  * [`heroku update`](docs/update.md) - update the Heroku CLI
76
+ * [`heroku usage`](docs/usage.md) - list usage values for metered addons associated with a given app or team
76
77
  * [`heroku version`](docs/version.md)
77
78
  * [`heroku webhooks`](docs/webhooks.md) - list webhooks on an app
78
79
 
@@ -12,7 +12,7 @@ class Info extends command_1.Command {
12
12
  Accept: 'application/vnd.heroku+json; version=3.sdk',
13
13
  },
14
14
  });
15
- (0, util_1.displayTelemetryDrain)(telemetryDrain);
15
+ await (0, util_1.displayTelemetryDrain)(telemetryDrain, this.heroku);
16
16
  }
17
17
  }
18
18
  exports.default = Info;
@@ -28,7 +28,7 @@ class Update extends command_1.Command {
28
28
  exporter.endpoint = endpoint;
29
29
  }
30
30
  if (transport) {
31
- exporter.type = `otlp${transport}`;
31
+ exporter.type = (transport === 'grpc') ? 'otlp' : 'otlphttp';
32
32
  }
33
33
  drainConfig.exporter = exporter;
34
34
  }
@@ -40,7 +40,7 @@ class Update extends command_1.Command {
40
40
  body: drainConfig,
41
41
  });
42
42
  core_1.ux.action.stop();
43
- (0, util_1.displayTelemetryDrain)(telemetryDrain);
43
+ await (0, util_1.displayTelemetryDrain)(telemetryDrain, this.heroku);
44
44
  }
45
45
  }
46
46
  exports.default = Update;
@@ -0,0 +1,14 @@
1
+ import { Command } from '@heroku-cli/command';
2
+ export default class UsageAddons extends Command {
3
+ static topic: string;
4
+ static description: string;
5
+ static flags: {
6
+ app: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
7
+ team: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
+ };
9
+ private displayAppUsage;
10
+ private fetchAndDisplayAppUsageData;
11
+ private fetchAndDisplayTeamUsageData;
12
+ private getAppInfoFromTeamAddons;
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("@heroku-cli/command");
4
+ const core_1 = require("@oclif/core");
5
+ const color_1 = require("@heroku-cli/color");
6
+ class UsageAddons extends command_1.Command {
7
+ displayAppUsage(app, usageAddons, appAddons) {
8
+ const metersArray = usageAddons.flatMap(addon => Object.entries(addon.meters).map(([label, data]) => ({
9
+ label,
10
+ quantity: data.quantity,
11
+ addonId: addon.id,
12
+ })));
13
+ core_1.ux.styledHeader(`Usage for ${color_1.default.app(app)}`);
14
+ core_1.ux.table(metersArray, {
15
+ Addon: {
16
+ get: row => {
17
+ const matchingAddon = appAddons.find(a => a.id === row.addonId);
18
+ return (matchingAddon === null || matchingAddon === void 0 ? void 0 : matchingAddon.name) || row.addonId;
19
+ },
20
+ },
21
+ Meter: {
22
+ get: row => row.label,
23
+ },
24
+ Quantity: {
25
+ get: row => row.quantity,
26
+ },
27
+ });
28
+ }
29
+ async fetchAndDisplayAppUsageData(app, team) {
30
+ let usageData;
31
+ let appAddons;
32
+ core_1.ux.action.start('Gathering usage data');
33
+ if (team) {
34
+ [{ body: usageData }, { body: appAddons }] = await Promise.all([
35
+ this.heroku.get(`/teams/${team}/apps/${app}/usage`, {
36
+ headers: {
37
+ Accept: 'application/vnd.heroku+json; version=3.sdk',
38
+ },
39
+ }),
40
+ this.heroku.get(`/apps/${app}/addons`),
41
+ ]);
42
+ }
43
+ else {
44
+ [{ body: usageData }, { body: appAddons }] = await Promise.all([
45
+ this.heroku.get(`/apps/${app}/usage`, {
46
+ headers: {
47
+ Accept: 'application/vnd.heroku+json; version=3.sdk',
48
+ },
49
+ }),
50
+ this.heroku.get(`/apps/${app}/addons`),
51
+ ]);
52
+ }
53
+ core_1.ux.action.stop();
54
+ core_1.ux.log();
55
+ const usageAddons = usageData.addons;
56
+ if (usageAddons.length === 0) {
57
+ core_1.ux.log(`No usage found for app ${color_1.default.app(app)}`);
58
+ return;
59
+ }
60
+ this.displayAppUsage(app, usageAddons, appAddons);
61
+ }
62
+ async fetchAndDisplayTeamUsageData(team) {
63
+ core_1.ux.action.start(`Gathering usage data for ${color_1.default.magenta(team)}`);
64
+ const [{ body: usageData }, { body: teamAddons }] = await Promise.all([
65
+ this.heroku.get(`/teams/${team}/usage`, {
66
+ headers: {
67
+ Accept: 'application/vnd.heroku+json; version=3.sdk',
68
+ },
69
+ }),
70
+ this.heroku.get(`/teams/${team}/addons`),
71
+ ]);
72
+ core_1.ux.action.stop();
73
+ core_1.ux.log();
74
+ if (!usageData.apps || usageData.apps.length === 0) {
75
+ core_1.ux.log(`No usage found for team ${color_1.default.magenta(team)}`);
76
+ return;
77
+ }
78
+ const appInfoArray = this.getAppInfoFromTeamAddons(teamAddons);
79
+ // Display usage for each app
80
+ usageData.apps.forEach((app) => {
81
+ const appInfo = appInfoArray.find(info => info.id === app.id);
82
+ this.displayAppUsage((appInfo === null || appInfo === void 0 ? void 0 : appInfo.name) || app.id, app.addons, teamAddons);
83
+ core_1.ux.log();
84
+ });
85
+ }
86
+ getAppInfoFromTeamAddons(teamAddons) {
87
+ const appInfoMap = new Map();
88
+ teamAddons.forEach(addon => {
89
+ if (addon.app && addon.app.id && addon.app.name) {
90
+ appInfoMap.set(addon.app.id, addon.app.name);
91
+ }
92
+ });
93
+ return Array.from(appInfoMap.entries()).map(([id, name]) => ({
94
+ id,
95
+ name,
96
+ }));
97
+ }
98
+ async run() {
99
+ const { flags } = await this.parse(UsageAddons);
100
+ const { app, team } = flags;
101
+ if (!app && !team) {
102
+ core_1.ux.error('Specify an app with --app or a team with --team');
103
+ }
104
+ if (app) {
105
+ await this.fetchAndDisplayAppUsageData(app, team);
106
+ }
107
+ else if (team) {
108
+ await this.fetchAndDisplayTeamUsageData(team);
109
+ }
110
+ }
111
+ }
112
+ exports.default = UsageAddons;
113
+ UsageAddons.topic = 'usage';
114
+ UsageAddons.description = 'list usage values for metered addons associated with a given app or team';
115
+ UsageAddons.flags = {
116
+ app: command_1.flags.string(),
117
+ team: command_1.flags.string(),
118
+ };
@@ -45,7 +45,7 @@ const formatPriceText = function (price) {
45
45
  const priceMonthly = (0, exports.formatPrice)({ price, hourly: false });
46
46
  if (!priceHourly)
47
47
  return '';
48
- if (priceHourly === 'free' || priceHourly === 'contract')
48
+ if (priceHourly === 'free' || priceHourly === 'contract' || priceHourly === 'metered')
49
49
  return `${color_1.default.green(priceHourly)}`;
50
50
  return `${color_1.default.green(priceHourly)} (max ${priceMonthly})`;
51
51
  };
@@ -1,3 +1,4 @@
1
1
  import { TelemetryDrain } from '../types/telemetry';
2
+ import { APIClient } from '@heroku-cli/command';
2
3
  export declare function validateAndFormatSignals(signalInput: string | undefined): string[];
3
- export declare function displayTelemetryDrain(telemetryDrain: TelemetryDrain): void;
4
+ export declare function displayTelemetryDrain(telemetryDrain: TelemetryDrain, heroku: APIClient): Promise<void>;
@@ -15,15 +15,32 @@ function validateAndFormatSignals(signalInput) {
15
15
  return signalArray;
16
16
  }
17
17
  exports.validateAndFormatSignals = validateAndFormatSignals;
18
- function displayTelemetryDrain(telemetryDrain) {
18
+ async function displayTelemetryDrain(telemetryDrain, heroku) {
19
19
  core_1.ux.styledHeader(telemetryDrain.id);
20
- const drainType = telemetryDrain.owner.type.charAt(0).toUpperCase() + telemetryDrain.owner.type.slice(1);
21
- core_1.ux.styledObject({
22
- [drainType]: telemetryDrain.owner.name,
20
+ const displayObject = {
23
21
  Signals: telemetryDrain.signals.join(', '),
24
22
  Endpoint: telemetryDrain.exporter.endpoint,
25
- Kind: telemetryDrain.exporter.type,
26
- Headers: telemetryDrain.exporter.headers,
27
- }, ['App', 'Space', 'Signals', 'Endpoint', 'Kind', 'Headers']);
23
+ Transport: (telemetryDrain.exporter.type === 'otlp' ? 'gRPC' : 'HTTP'),
24
+ };
25
+ if (telemetryDrain.owner.type === 'space') {
26
+ const { body: space } = await heroku.get(`/spaces/${telemetryDrain.owner.id}`, {
27
+ headers: {
28
+ Accept: 'application/vnd.heroku+json; version=3.sdk',
29
+ },
30
+ });
31
+ displayObject.Space = space.name;
32
+ }
33
+ else {
34
+ const { body: app } = await heroku.get(`/apps/${telemetryDrain.owner.id}`, {
35
+ headers: {
36
+ Accept: 'application/vnd.heroku+json; version=3.sdk',
37
+ },
38
+ });
39
+ displayObject.App = app.name;
40
+ }
41
+ if (telemetryDrain.exporter.headers) {
42
+ displayObject.Headers = JSON.stringify(telemetryDrain.exporter.headers);
43
+ }
44
+ core_1.ux.styledObject(displayObject, ['App', 'Space', 'Signals', 'Endpoint', 'Transport', 'Headers']);
28
45
  }
29
46
  exports.displayTelemetryDrain = displayTelemetryDrain;