Skip to content

Commit e6658de

Browse files
fix(codecatalyst): exclude third-party repos for "Clone" #3252
Problem: CodeCatalyst API does not support cloning 3P repos. Solution: Do not show 3P repos when listing repos for "Clone CodeCatalyst Repository" command. Fixes IDE-10273 Signed-off-by: Nikolas Komonen <[email protected]>
1 parent e413835 commit e6658de

File tree

10 files changed

+198
-64
lines changed

10 files changed

+198
-64
lines changed

src/codecatalyst/commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as nls from 'vscode-nls'
99
const localize = nls.loadMessageBundle()
1010

1111
import * as vscode from 'vscode'
12-
import { selectCodeCatalystResource } from './wizards/selectResource'
12+
import { selectCodeCatalystRepository, selectCodeCatalystResource } from './wizards/selectResource'
1313
import { openCodeCatalystUrl, recordSource } from './utils'
1414
import { CodeCatalystAuthenticationProvider } from './auth'
1515
import { Commands } from '../shared/vscode/commands2'
@@ -33,7 +33,7 @@ export async function listCommands(): Promise<void> {
3333
export async function cloneCodeCatalystRepo(client: CodeCatalystClient, url?: vscode.Uri): Promise<void> {
3434
let resource: { name: string; project: string; org: string }
3535
if (!url) {
36-
const r = await selectCodeCatalystResource(client, 'repo')
36+
const r = await selectCodeCatalystRepository(client, false)
3737
if (!r) {
3838
throw new CancellationError('user')
3939
}

src/codecatalyst/wizards/selectResource.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,17 @@ export function createProjectPrompter(
124124

125125
export function createRepoPrompter(
126126
client: codecatalyst.CodeCatalystClient,
127-
proj?: codecatalyst.CodeCatalystProject
127+
proj?: codecatalyst.CodeCatalystProject,
128+
thirdParty?: boolean
128129
): QuickPickPrompter<codecatalyst.CodeCatalystRepo> {
129130
const helpUri = isCloud9() ? docs.cloud9.cloneRepo : docs.vscode.main
130131
const repos = proj
131-
? client.listSourceRepositories({ spaceName: proj.org.name, projectName: proj.name })
132-
: client.listResources('repo')
132+
? client.listSourceRepositories({ spaceName: proj.org.name, projectName: proj.name }, thirdParty)
133+
: client.listResources('repo', thirdParty)
133134

134135
return createResourcePrompter(repos, helpUri, {
135136
title: 'Select a CodeCatalyst Repository',
136-
placeholder: 'Search for a Repository',
137+
placeholder: 'Search for a CodeCatalyst Repository',
137138
})
138139
}
139140

@@ -190,6 +191,15 @@ export async function selectCodeCatalystResource<T extends ResourceType>(
190191
return isValidResponse(response) ? (response as codecatalyst.CodeCatalystResource & { type: T }) : undefined
191192
}
192193

194+
export async function selectCodeCatalystRepository(
195+
client: codecatalyst.CodeCatalystClient,
196+
includeThirdPartyRepos?: boolean
197+
): Promise<codecatalyst.CodeCatalystRepo | undefined> {
198+
const prompter = createRepoPrompter(client, undefined, includeThirdPartyRepos)
199+
const response = await prompter.prompt()
200+
return isValidResponse(response) ? response : undefined
201+
}
202+
193203
/**
194204
* Special-case of {@link createRepoPrompter} for creating a new devenv
195205
*/

src/shared/clients/codecatalystClient.ts

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import { CodeCatalyst } from 'aws-sdk'
2121
import { ToolkitError } from '../errors'
2222
import { SsoConnection } from '../../credentials/auth'
2323
import { TokenProvider } from '../../credentials/sdkV2Compat'
24+
import { Uri } from 'vscode'
25+
import {
26+
GetSourceRepositoryCloneUrlsRequest,
27+
ListSourceRepositoriesItem,
28+
ListSourceRepositoriesItems,
29+
} from 'aws-sdk/clients/codecatalyst'
2430

2531
// REMOVE ME SOON: only used for development
2632
interface CodeCatalystConfig {
@@ -411,12 +417,33 @@ class CodeCatalystClientInternal {
411417

412418
/**
413419
* Gets a flat list of all repos for the given CodeCatalyst user.
420+
* @param thirdParty If you want to include 3P (eg github) results in
421+
* your output.
414422
*/
415423
public listSourceRepositories(
416-
request: CodeCatalyst.ListSourceRepositoriesRequest
424+
request: CodeCatalyst.ListSourceRepositoriesRequest,
425+
thirdParty: boolean = true
417426
): AsyncCollection<CodeCatalystRepo[]> {
418-
const requester = async (request: CodeCatalyst.ListSourceRepositoriesRequest) =>
419-
this.call(this.sdkClient.listSourceRepositories(request), true, { items: [] })
427+
const requester = async (request: CodeCatalyst.ListSourceRepositoriesRequest) => {
428+
const allRepositories = this.call(this.sdkClient.listSourceRepositories(request), true, { items: [] })
429+
let finalRepositories = allRepositories
430+
431+
// Filter out 3P repos
432+
if (!thirdParty) {
433+
finalRepositories = allRepositories.then(async repos => {
434+
repos.items = await excludeThirdPartyRepos(
435+
this,
436+
request.spaceName,
437+
request.projectName,
438+
repos.items
439+
)
440+
return repos
441+
})
442+
}
443+
444+
return finalRepositories
445+
}
446+
420447
const collection = pageableToCollection(requester, request, 'nextToken', 'items')
421448
return collection.map(
422449
summaries =>
@@ -445,12 +472,17 @@ class CodeCatalystClientInternal {
445472

446473
/**
447474
* Lists ALL of the given resource in the current account
475+
*
476+
* @param thirdParty If you want 3P repos in the result.
448477
*/
449478
public listResources(resourceType: 'org'): AsyncCollection<CodeCatalystOrg[]>
450479
public listResources(resourceType: 'project'): AsyncCollection<CodeCatalystProject[]>
451-
public listResources(resourceType: 'repo'): AsyncCollection<CodeCatalystRepo[]>
480+
public listResources(resourceType: 'repo', thirdParty?: boolean): AsyncCollection<CodeCatalystRepo[]>
452481
public listResources(resourceType: 'devEnvironment'): AsyncCollection<DevEnvironment[]>
453-
public listResources(resourceType: CodeCatalystResource['type']): AsyncCollection<CodeCatalystResource[]> {
482+
public listResources(
483+
resourceType: CodeCatalystResource['type'],
484+
...args: any[]
485+
): AsyncCollection<CodeCatalystResource[]> {
454486
function mapInner<T, U>(
455487
collection: AsyncCollection<T[]>,
456488
fn: (element: T) => AsyncCollection<U[]>
@@ -465,7 +497,7 @@ class CodeCatalystClientInternal {
465497
return mapInner(this.listResources('org'), o => this.listProjects({ spaceName: o.name }))
466498
case 'repo':
467499
return mapInner(this.listResources('project'), p =>
468-
this.listSourceRepositories({ projectName: p.name, spaceName: p.org.name })
500+
this.listSourceRepositories({ projectName: p.name, spaceName: p.org.name }, ...args)
469501
)
470502
case 'branch':
471503
throw new Error('Listing branches is not currently supported')
@@ -481,13 +513,14 @@ class CodeCatalystClientInternal {
481513
}
482514

483515
/**
484-
* Gets the git source host URL for the given CodeCatalyst repo.
516+
* Gets the git source host URL for the given CodeCatalyst or third-party repo.
485517
*/
486518
public async getRepoCloneUrl(args: CodeCatalyst.GetSourceRepositoryCloneUrlsRequest): Promise<string> {
487519
const r = await this.call(this.sdkClient.getSourceRepositoryCloneUrls(args), false)
488520

489521
// The git extension skips over credential providers if the username is included in the authority
490-
return `https://${r.https.replace(/.*@/, '')}`
522+
const uri = Uri.parse(r.https)
523+
return uri.with({ authority: uri.authority.replace(/.*@/, '') }).toString()
491524
}
492525

493526
public async createDevEnvironment(args: CodeCatalyst.CreateDevEnvironmentRequest): Promise<DevEnvironment> {
@@ -676,3 +709,48 @@ class CodeCatalystClientInternal {
676709
return devenv
677710
}
678711
}
712+
713+
/**
714+
* Returns only the first-party repos from the given
715+
* list of repository items.
716+
*/
717+
export async function excludeThirdPartyRepos(
718+
client: CodeCatalystClient,
719+
spaceName: CodeCatalystOrg['name'],
720+
projectName: CodeCatalystProject['name'],
721+
items?: Pick<ListSourceRepositoriesItem, 'name'>[]
722+
): Promise<ListSourceRepositoriesItems | undefined> {
723+
if (items === undefined) {
724+
return items
725+
}
726+
727+
// Filter out 3P repos.
728+
return (
729+
await Promise.all(
730+
items.map(async item => {
731+
return (await isThirdPartyRepo(client, {
732+
spaceName,
733+
projectName,
734+
sourceRepositoryName: item.name,
735+
}))
736+
? undefined
737+
: item
738+
})
739+
)
740+
).filter(item => item !== undefined) as CodeCatalyst.ListSourceRepositoriesItem[]
741+
}
742+
743+
/**
744+
* Determines if a repo is third party (3P) compared to first party (1P).
745+
*
746+
* 1P is CodeCatalyst, 3P is something like Github.
747+
*/
748+
async function isThirdPartyRepo(
749+
client: CodeCatalystClient,
750+
codeCatalystRepo: GetSourceRepositoryCloneUrlsRequest
751+
): Promise<boolean> {
752+
const url = await client.getRepoCloneUrl(codeCatalystRepo)
753+
// TODO: Make more robust to work with gamma once getCodeCatalystConfig()
754+
// can provide a valid hostname
755+
return !Uri.parse(url).authority.endsWith('codecatalyst.aws')
756+
}

src/test/caws/codecatalystClient.test.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*!
2+
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as assert from 'assert'
7+
import sinon = require('sinon')
8+
import { toCodeCatalystUrl } from '../../codecatalyst/utils'
9+
import * as codecatalyst from '../../shared/clients/codecatalystClient'
10+
11+
describe('codeCatalystClient', function () {
12+
it('toCodeCatalystUrl()', async function () {
13+
const org: codecatalyst.CodeCatalystOrg = {
14+
type: 'org',
15+
name: 'org1',
16+
regionName: 'region',
17+
}
18+
const project: codecatalyst.CodeCatalystProject = {
19+
type: 'project',
20+
org: org,
21+
name: 'project1',
22+
}
23+
const repo: codecatalyst.CodeCatalystRepo = {
24+
type: 'repo',
25+
org: org,
26+
project: project,
27+
id: 'repoid1',
28+
name: 'repo1',
29+
lastUpdatedTime: new Date(),
30+
createdTime: new Date(),
31+
}
32+
const prefix = `https://${codecatalyst.getCodeCatalystConfig().hostname}/spaces`
33+
assert.deepStrictEqual(toCodeCatalystUrl(org), `${prefix}/org1/view`)
34+
assert.deepStrictEqual(toCodeCatalystUrl(project), `${prefix}/org1/projects/project1/view`)
35+
assert.deepStrictEqual(
36+
toCodeCatalystUrl(repo),
37+
`${prefix}/org1/projects/project1/source-repositories/repo1/view`
38+
)
39+
})
40+
})
41+
42+
describe('getFirstPartyRepos()', function () {
43+
let sandbox: sinon.SinonSandbox
44+
let codeCatalystClient: codecatalyst.CodeCatalystClient
45+
let getRepoCloneUrlStub: sinon.SinonStub
46+
47+
before(function () {
48+
sandbox = sinon.createSandbox()
49+
})
50+
51+
beforeEach(function () {
52+
codeCatalystClient = <codecatalyst.CodeCatalystClient>{}
53+
54+
getRepoCloneUrlStub = sandbox.stub()
55+
codeCatalystClient.getRepoCloneUrl = getRepoCloneUrlStub
56+
})
57+
58+
it('removes third party repos', async function () {
59+
getRepoCloneUrlStub.onCall(0).resolves('https://github.com/aws/not-code-catalyst-1.git')
60+
getRepoCloneUrlStub.onCall(1).resolves('https://codecatalyst.aws/code-catalyst-1')
61+
getRepoCloneUrlStub.onCall(2).resolves('https://github.com/aws/not-code-catalyst-2.git')
62+
getRepoCloneUrlStub.onCall(3).resolves('https://codecatalyst.aws/code-catalyst-1')
63+
64+
const allRepos = await codecatalyst.excludeThirdPartyRepos(codeCatalystClient, '', '', [
65+
{ name: 'not-code-catalyst-1' },
66+
{ name: 'code-catalyst-1' },
67+
{ name: 'not-code-catalyst-2' },
68+
{ name: 'code-catalyst-2' },
69+
])
70+
71+
assert.deepStrictEqual(allRepos, [{ name: 'code-catalyst-1' }, { name: 'code-catalyst-2' }])
72+
})
73+
74+
it('returns empty array if no first party repos', async function () {
75+
getRepoCloneUrlStub.onCall(0).resolves('https://github.com/aws/aws-cdk.git')
76+
getRepoCloneUrlStub.onCall(1).resolves('https://github.com/aws/vscode-toolkit.git')
77+
78+
const allRepos = await codecatalyst.excludeThirdPartyRepos(codeCatalystClient, '', '', [
79+
{ name: 'aws-cdk' },
80+
{ name: 'vscode-toolkit' },
81+
])
82+
83+
assert.deepStrictEqual(allRepos, [])
84+
})
85+
})
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)