Skip to content

Commit 87bfe82

Browse files
authored
feat: add new command 'data:pg:levels' (W-21411625) (#3578)
* Adds command 'data:pg:levels' * Addressing PR review feedback
1 parent d38a8a5 commit 87bfe82

File tree

6 files changed

+118
-2
lines changed

6 files changed

+118
-2
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@oclif/plugin-version": "^2.2.37",
2727
"@oclif/plugin-warn-if-update-available": "^3.1.55",
2828
"@oclif/plugin-which": "^3.2.44",
29+
"@oclif/table": "^0.5.2",
2930
"@opentelemetry/api": "^1.9.0",
3031
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
3132
"@opentelemetry/instrumentation": "^0.52.1",

src/commands/data/pg/levels.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {Command} from '@heroku-cli/command'
2+
import {hux} from '@heroku/heroku-cli-util'
3+
import {printTable, TableOptions} from '@oclif/table'
4+
5+
import BaseCommand from '../../../lib/data/baseCommand.js'
6+
import {PostgresLevelInfo, PostgresLevelsResponse} from '../../../lib/data/types.js'
7+
8+
export default class DataPgLevels extends BaseCommand {
9+
static baseFlags = Command.baseFlagsWithoutPrompt()
10+
static description = 'show available levels for Heroku Postgres Advanced databases'
11+
static promptFlagActive = false
12+
13+
async run(): Promise<void> {
14+
const {body: {items: levels}} = await this.dataApi.get<PostgresLevelsResponse>('/data/postgres/v1/levels/advanced')
15+
16+
hux.styledHeader('Available levels for Heroku Postgres Advanced databases')
17+
18+
const defaultStyle = {
19+
borderColor: 'whiteBright',
20+
borderStyle: 'headers-only-with-underline',
21+
headerOptions: {
22+
bold: true,
23+
color: 'white',
24+
},
25+
} as Partial<TableOptions<PostgresLevelInfo>>
26+
27+
printTable<PostgresLevelInfo>({
28+
columns: [
29+
{key: 'name', name: 'Name'},
30+
{horizontalAlignment: 'right', key: 'vcpu', name: 'vCPU'},
31+
{horizontalAlignment: 'right', key: 'memory_in_gb', name: 'Memory (GB)'},
32+
{horizontalAlignment: 'right', key: 'connection_limit', name: 'Max Connections'},
33+
],
34+
data: levels,
35+
...defaultStyle,
36+
})
37+
}
38+
}

src/lib/data/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ type StorageLimitInGb = {
8585
export type PlanLimit = ConnectionLimit | StorageLimitInGb | TableLimit
8686

8787
export type PostgresLevelInfo = {
88+
connection_limit: number
8889
memory_in_gb: number
8990
name: string
9091
vcpu: number

test/fixtures/data/pg/fixtures.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,18 @@ export const essentialAddon: DeepRequired<Heroku.AddOn> = {
109109

110110
export const levelsResponse: PostgresLevelsResponse = {
111111
items: [
112-
{memory_in_gb: 4, name: '4G-Performance', vcpu: 2},
113-
{memory_in_gb: 8, name: '8G-Performance', vcpu: 4},
112+
{
113+
connection_limit: 400,
114+
memory_in_gb: 4,
115+
name: '4G-Performance',
116+
vcpu: 2,
117+
},
118+
{
119+
connection_limit: 800,
120+
memory_in_gb: 8,
121+
name: '8G-Performance',
122+
vcpu: 4,
123+
},
114124
],
115125
}
116126

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {expect} from 'chai'
2+
import nock from 'nock'
3+
import {stderr, stdout} from 'stdout-stderr'
4+
5+
import DataPgLevels from '../../../../../src/commands/data/pg/levels.js'
6+
import {levelsResponse} from '../../../../fixtures/data/pg/fixtures.js'
7+
import runCommand from '../../../../helpers/runCommand.js'
8+
9+
describe('data:pg:levels', () => {
10+
let dataApi: nock.Scope
11+
12+
beforeEach(() => {
13+
dataApi = nock('https://api.data.heroku.com')
14+
})
15+
16+
afterEach(() => {
17+
dataApi.done()
18+
})
19+
20+
it('displays available levels for advanced databases', async () => {
21+
dataApi
22+
.get('/data/postgres/v1/levels/advanced')
23+
.reply(200, levelsResponse)
24+
25+
await runCommand(DataPgLevels, [])
26+
27+
expect(stderr.output).to.equal('')
28+
expect(stdout.output).to.include('Name')
29+
expect(stdout.output).to.include('vCPU')
30+
expect(stdout.output).to.include('Memory (GB)')
31+
expect(stdout.output).to.include('Max Connections')
32+
expect(stdout.output).to.include('4G-Performance')
33+
expect(stdout.output).to.include('8G-Performance')
34+
})
35+
36+
it('displays an empty table when no levels are available', async () => {
37+
dataApi
38+
.get('/data/postgres/v1/levels/advanced')
39+
.reply(200, {items: []})
40+
41+
await runCommand(DataPgLevels, [])
42+
43+
expect(stderr.output).to.equal('')
44+
expect(stdout.output).to.include('Name')
45+
expect(stdout.output).to.include('vCPU')
46+
expect(stdout.output).to.include('Memory (GB)')
47+
expect(stdout.output).to.include('Max Connections')
48+
expect(stdout.output).not.to.include('4G-Performance')
49+
expect(stdout.output).not.to.include('8G-Performance')
50+
})
51+
52+
it('handles API errors gracefully', async () => {
53+
dataApi
54+
.get('/data/postgres/v1/levels/advanced')
55+
.reply(500, {id: 'server_error', message: 'Internal Server Error'})
56+
57+
try {
58+
await runCommand(DataPgLevels, [])
59+
expect.fail('Expected command to throw an error')
60+
} catch (error: unknown) {
61+
const err = error as Error
62+
expect(err.message).to.include('Internal Server Error')
63+
}
64+
})
65+
})

0 commit comments

Comments
 (0)