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 +1 -0
- package/lib/commands/telemetry/info.js +1 -1
- package/lib/commands/telemetry/update.js +2 -2
- package/lib/commands/usage/addons.d.ts +14 -0
- package/lib/commands/usage/addons.js +118 -0
- package/lib/lib/addons/util.js +1 -1
- package/lib/lib/telemetry/util.d.ts +2 -1
- package/lib/lib/telemetry/util.js +24 -7
- package/oclif.manifest.json +1297 -1263
- package/package.json +2 -2
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 =
|
|
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
|
+
};
|
package/lib/lib/addons/util.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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;
|