Skip to content

Commit f455105

Browse files
authored
feat(smus): List connections under compute node (aws#2193)
__Description:__ Add 2 folder nodes under compute node to list Redshit and Spark connections. Add tooltip for the listed connections to show critical information. ## Appearance: <img width="360" height="304" alt="image" src="https://github.com/user-attachments/assets/4cb3ca4c-14d2-4373-af1d-35147238895b" /> --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent f064a4f commit f455105

8 files changed

+521
-9
lines changed

packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { SageMakerUnifiedStudioSpacesParentNode } from './sageMakerUnifiedStudio
1010
import { SageMakerUnifiedStudioProjectNode } from './sageMakerUnifiedStudioProjectNode'
1111
import { SagemakerClient } from '../../../shared/clients/sagemaker'
1212
import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
13+
import { SageMakerUnifiedStudioConnectionParentNode } from './sageMakerUnifiedStudioConnectionParentNode'
14+
import { ConnectionType } from '@aws-sdk/client-datazone'
1315

1416
export class SageMakerUnifiedStudioComputeNode implements TreeNode {
1517
public readonly id = 'smusComputeNode'
@@ -19,7 +21,7 @@ export class SageMakerUnifiedStudioComputeNode implements TreeNode {
1921
constructor(
2022
public readonly parent: SageMakerUnifiedStudioProjectNode,
2123
private readonly extensionContext: vscode.ExtensionContext,
22-
private readonly authprovider: SmusAuthenticationProvider,
24+
public readonly authProvider: SmusAuthenticationProvider,
2325
private readonly sagemakerClient: SagemakerClient
2426
) {}
2527

@@ -35,11 +37,17 @@ export class SageMakerUnifiedStudioComputeNode implements TreeNode {
3537
const projectId = this.parent.getProject()?.id
3638

3739
if (projectId) {
40+
childrenNodes.push(
41+
new SageMakerUnifiedStudioConnectionParentNode(this, ConnectionType.REDSHIFT, 'Data warehouse')
42+
)
43+
childrenNodes.push(
44+
new SageMakerUnifiedStudioConnectionParentNode(this, ConnectionType.SPARK, 'Data processing')
45+
)
3846
this.spacesNode = new SageMakerUnifiedStudioSpacesParentNode(
3947
this,
4048
projectId,
4149
this.extensionContext,
42-
this.authprovider,
50+
this.authProvider,
4351
this.sagemakerClient
4452
)
4553
childrenNodes.push(this.spacesNode)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as vscode from 'vscode'
7+
import { getLogger } from '../../../shared/logger/logger'
8+
import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
9+
import { SageMakerUnifiedStudioConnectionParentNode } from './sageMakerUnifiedStudioConnectionParentNode'
10+
import { ConnectionSummary, ConnectionType } from '@aws-sdk/client-datazone'
11+
12+
export class SageMakerUnifiedStudioConnectionNode implements TreeNode {
13+
public resource: SageMakerUnifiedStudioConnectionNode
14+
contextValue: string
15+
private readonly logger = getLogger()
16+
id: string
17+
public constructor(
18+
private readonly parent: SageMakerUnifiedStudioConnectionParentNode,
19+
private readonly connection: ConnectionSummary
20+
) {
21+
this.id = connection.name ?? ''
22+
this.resource = this
23+
this.contextValue = this.getContext()
24+
this.logger.debug(`SageMaker Space Node created: ${this.id}`)
25+
}
26+
27+
public async getTreeItem(): Promise<vscode.TreeItem> {
28+
const item = new vscode.TreeItem(this.id, vscode.TreeItemCollapsibleState.None)
29+
item.contextValue = this.getContext()
30+
item.tooltip = new vscode.MarkdownString(this.buildTooltip())
31+
return item
32+
}
33+
private buildTooltip(): string {
34+
if (this.connection.type === ConnectionType.REDSHIFT) {
35+
const tooltip = ''.concat(
36+
'### Compute Details\n\n',
37+
`**Type** \n${this.connection.type}\n\n`,
38+
`**Environment ID** \n${this.connection.environmentId}\n\n`,
39+
`**JDBC URL** \n${this.connection.props?.redshiftProperties?.jdbcUrl}`
40+
)
41+
return tooltip
42+
} else if (this.connection.type === ConnectionType.SPARK) {
43+
const tooltip = ''.concat(
44+
'### Compute Details\n\n',
45+
`**Type** \n${this.connection.type}\n\n`,
46+
`**Glue version** \n${this.connection.props?.sparkGlueProperties?.glueVersion}\n\n`,
47+
`**Worker type** \n${this.connection.props?.sparkGlueProperties?.workerType}\n\n`,
48+
`**Number of workers** \n${this.connection.props?.sparkGlueProperties?.numberOfWorkers}\n\n`,
49+
`**Idle timeout (minutes)** \n${this.connection.props?.sparkGlueProperties?.idleTimeout}\n\n`
50+
)
51+
return tooltip
52+
} else {
53+
return ''
54+
}
55+
}
56+
private getContext(): string {
57+
return 'SageMakerUnifiedStudioConnectionNode'
58+
}
59+
60+
public getParent(): TreeNode | undefined {
61+
return this.parent
62+
}
63+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as vscode from 'vscode'
7+
import { SageMakerUnifiedStudioComputeNode } from './sageMakerUnifiedStudioComputeNode'
8+
import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
9+
import { ListConnectionsCommandOutput, ConnectionType } from '@aws-sdk/client-datazone'
10+
import { SageMakerUnifiedStudioConnectionNode } from './sageMakerUnifiedStudioConnectionNode'
11+
import { DataZoneClient } from '../../shared/client/datazoneClient'
12+
13+
// eslint-disable-next-line id-length
14+
export class SageMakerUnifiedStudioConnectionParentNode implements TreeNode {
15+
public resource: SageMakerUnifiedStudioConnectionParentNode
16+
contextValue: string
17+
public connections: ListConnectionsCommandOutput | undefined
18+
public constructor(
19+
private readonly parent: SageMakerUnifiedStudioComputeNode,
20+
private readonly connectionType: ConnectionType,
21+
public id: string
22+
) {
23+
this.resource = this
24+
this.contextValue = this.getContext()
25+
}
26+
27+
public async getTreeItem(): Promise<vscode.TreeItem> {
28+
const item = new vscode.TreeItem(this.id, vscode.TreeItemCollapsibleState.Collapsed)
29+
item.contextValue = this.getContext()
30+
return item
31+
}
32+
33+
public async getChildren(): Promise<TreeNode[]> {
34+
const client = await DataZoneClient.getInstance(this.parent.authProvider)
35+
this.connections = await client.fetchConnections(
36+
this.parent.parent.project?.domainId,
37+
this.parent.parent.project?.id,
38+
this.connectionType
39+
)
40+
const childrenNodes = []
41+
if (!this.connections?.items) {
42+
return []
43+
}
44+
for (const connection of this.connections.items) {
45+
childrenNodes.push(new SageMakerUnifiedStudioConnectionNode(this, connection))
46+
}
47+
return childrenNodes
48+
}
49+
50+
private getContext(): string {
51+
return 'SageMakerUnifiedStudioConnectionParentNode'
52+
}
53+
54+
public getParent(): TreeNode | undefined {
55+
return this.parent
56+
}
57+
}

packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class SageMakerUnifiedStudioProjectNode implements TreeNode {
2424
private readonly onDidChangeEmitter = new vscode.EventEmitter<void>()
2525
public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event
2626
public readonly onDidChangeChildren = this.onDidChangeEmitter.event
27-
private project?: DataZoneProject
27+
public project?: DataZoneProject
2828
private logger = getLogger()
2929
private sagemakerClient?: SagemakerClient
3030

packages/core/src/sagemakerunifiedstudio/shared/client/datazoneClient.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
PhysicalEndpoint,
1414
RedshiftPropertiesOutput,
1515
S3PropertiesOutput,
16+
ConnectionType,
1617
} from '@aws-sdk/client-datazone'
1718
import { getLogger } from '../../../shared/logger/logger'
1819
import type { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
@@ -476,6 +477,18 @@ export class DataZoneClient {
476477
}
477478
}
478479

480+
public async fetchConnections(
481+
domain: string | undefined,
482+
project: string | undefined,
483+
ConnectionType: ConnectionType
484+
): Promise<ListConnectionsCommandOutput> {
485+
const datazoneClient = await this.getDataZoneClient()
486+
return datazoneClient.listConnections({
487+
domainIdentifier: domain,
488+
projectIdentifier: project,
489+
type: ConnectionType,
490+
})
491+
}
479492
/**
480493
* Lists connections in a DataZone environment
481494
* @param domainId The DataZone domain identifier

packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,16 @@ describe('SageMakerUnifiedStudioComputeNode', function () {
7171
assert.deepStrictEqual(children, [])
7272
})
7373

74-
it('returns spaces node when project is selected', async function () {
74+
it('returns connection nodes and spaces node when project is selected', async function () {
7575
const mockProject = { id: 'project-123', name: 'Test Project' }
7676
;(mockParent.getProject as sinon.SinonStub).returns(mockProject)
7777

78-
// Stub the SpacesParentNode constructor to prevent actual instantiation
79-
sinon.stub(SageMakerUnifiedStudioSpacesParentNode.prototype, 'constructor' as any).returns({})
80-
8178
const children = await computeNode.getChildren()
8279

83-
assert.strictEqual(children.length, 1)
84-
assert.ok(children[0] instanceof SageMakerUnifiedStudioSpacesParentNode)
80+
assert.strictEqual(children.length, 3)
81+
assert.strictEqual(children[0].id, 'Data warehouse')
82+
assert.strictEqual(children[1].id, 'Data processing')
83+
assert.ok(children[2] instanceof SageMakerUnifiedStudioSpacesParentNode)
8584
})
8685
})
8786

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import sinon from 'sinon'
8+
import * as vscode from 'vscode'
9+
import { SageMakerUnifiedStudioConnectionNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode'
10+
import { SageMakerUnifiedStudioConnectionParentNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode'
11+
import { ConnectionType, ConnectionSummary } from '@aws-sdk/client-datazone'
12+
import { getLogger } from '../../../../shared/logger/logger'
13+
14+
describe('SageMakerUnifiedStudioConnectionNode', function () {
15+
let connectionNode: SageMakerUnifiedStudioConnectionNode
16+
let mockParent: sinon.SinonStubbedInstance<SageMakerUnifiedStudioConnectionParentNode>
17+
18+
const mockRedshiftConnection: ConnectionSummary = {
19+
connectionId: 'conn-1',
20+
name: 'Test Redshift Connection',
21+
type: ConnectionType.REDSHIFT,
22+
environmentId: 'env-1',
23+
domainId: 'domain-1',
24+
domainUnitId: 'unit-1',
25+
physicalEndpoints: [],
26+
props: {
27+
redshiftProperties: {
28+
jdbcUrl: 'jdbc:redshift://test-cluster:5439/testdb',
29+
},
30+
},
31+
}
32+
33+
const mockSparkConnection: ConnectionSummary = {
34+
connectionId: 'conn-2',
35+
name: 'Test Spark Connection',
36+
type: ConnectionType.SPARK,
37+
environmentId: 'env-2',
38+
domainId: 'domain-2',
39+
domainUnitId: 'unit-2',
40+
physicalEndpoints: [],
41+
props: {
42+
sparkGlueProperties: {
43+
glueVersion: '4.0',
44+
workerType: 'G.1X',
45+
numberOfWorkers: 2,
46+
idleTimeout: 30,
47+
},
48+
},
49+
}
50+
51+
beforeEach(function () {
52+
mockParent = {} as any
53+
sinon.stub(getLogger(), 'debug')
54+
})
55+
56+
afterEach(function () {
57+
sinon.restore()
58+
})
59+
60+
describe('constructor', function () {
61+
it('creates instance with correct properties for Redshift connection', function () {
62+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection)
63+
64+
assert.strictEqual(connectionNode.id, 'Test Redshift Connection')
65+
assert.strictEqual(connectionNode.resource, connectionNode)
66+
assert.strictEqual(connectionNode.contextValue, 'SageMakerUnifiedStudioConnectionNode')
67+
})
68+
69+
it('creates instance with empty id when connection name is undefined', function () {
70+
const connectionWithoutName = { ...mockRedshiftConnection, name: undefined }
71+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, connectionWithoutName)
72+
73+
assert.strictEqual(connectionNode.id, '')
74+
})
75+
})
76+
77+
describe('getTreeItem', function () {
78+
it('returns correct tree item for Redshift connection', async function () {
79+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection)
80+
81+
const treeItem = await connectionNode.getTreeItem()
82+
83+
assert.strictEqual(treeItem.label, 'Test Redshift Connection')
84+
assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None)
85+
assert.strictEqual(treeItem.contextValue, 'SageMakerUnifiedStudioConnectionNode')
86+
assert.ok(treeItem.tooltip instanceof vscode.MarkdownString)
87+
})
88+
89+
it('returns correct tree item for Spark connection', async function () {
90+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockSparkConnection)
91+
92+
const treeItem = await connectionNode.getTreeItem()
93+
94+
assert.strictEqual(treeItem.label, 'Test Spark Connection')
95+
assert.ok(treeItem.tooltip instanceof vscode.MarkdownString)
96+
})
97+
})
98+
99+
describe('tooltip generation', function () {
100+
it('generates correct tooltip for Redshift connection', async function () {
101+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection)
102+
103+
const treeItem = await connectionNode.getTreeItem()
104+
const tooltip = (treeItem.tooltip as vscode.MarkdownString).value
105+
106+
assert(tooltip.includes('REDSHIFT'))
107+
assert(tooltip.includes('env-1'))
108+
assert(tooltip.includes('jdbc:redshift://test-cluster:5439/testdb'))
109+
})
110+
111+
it('generates correct tooltip for Spark connection', async function () {
112+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockSparkConnection)
113+
114+
const treeItem = await connectionNode.getTreeItem()
115+
const tooltip = (treeItem.tooltip as vscode.MarkdownString).value
116+
117+
assert(tooltip.includes('SPARK'))
118+
assert(tooltip.includes('4.0'))
119+
assert(tooltip.includes('G.1X'))
120+
assert(tooltip.includes('2'))
121+
assert(tooltip.includes('30'))
122+
})
123+
124+
it('generates empty tooltip for unknown connection type', async function () {
125+
const unknownConnection = { ...mockRedshiftConnection, type: 'UNKNOWN' as ConnectionType }
126+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, unknownConnection)
127+
128+
const treeItem = await connectionNode.getTreeItem()
129+
const tooltip = (treeItem.tooltip as vscode.MarkdownString).value
130+
131+
assert.strictEqual(tooltip, '')
132+
})
133+
})
134+
135+
describe('getParent', function () {
136+
it('returns the parent node', function () {
137+
connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection)
138+
139+
const parent = connectionNode.getParent()
140+
141+
assert.strictEqual(parent, mockParent)
142+
})
143+
})
144+
})

0 commit comments

Comments
 (0)