-
Notifications
You must be signed in to change notification settings - Fork 45
WIP Apply minor patches #456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
name: Build | ||
|
||
on: | ||
workflow_dispatch: {} | ||
push: | ||
branches: | ||
- '**' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,11 @@ Stay in the flow by using Atlassian for VSCode to start work on a JIRA issue, ra | |
|
||
[**Download now**](https://marketplace.visualstudio.com/items?itemName=Atlassian.atlascode&ssr=false#overview) | ||
|
||
## [Devsphere specific changelog](/DEVSPHERE_CHANGELOG.md) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
We're adding certain features & updates to this fork which may or may not be suitable to push upstream. Refer to the [Devsphere specific changelog](/DEVSPHERE_CHANGELOG.md) to get more context. | ||
|
||
## Usage | ||
|
||
|
||
### Getting Started | ||
|
||
- Make sure you have VS Code version 1.77.0 or above | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,8 +38,7 @@ | |
"onStartupFinished" | ||
], | ||
"extensionDependencies": [ | ||
"vscode.git", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
"redhat.vscode-yaml" | ||
"vscode.git" | ||
], | ||
"extensionKind": [ | ||
"workspace" | ||
|
@@ -98,6 +97,16 @@ | |
}, | ||
"category": "Atlassian" | ||
}, | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
"command": "atlascode.devsphere.review.initialise", | ||
"title": "Initialise Devsphere Review Settings", | ||
"category": "Atlassian" | ||
}, | ||
{ | ||
"command": "atlascode.devsphere.custom.reset", | ||
"title": "Reset Devsphere Custom Configuration", | ||
"category": "Atlassian" | ||
}, | ||
{ | ||
"command": "atlascode.jira.searchIssues", | ||
"title": "Search Jira Issue Results", | ||
|
@@ -402,6 +411,14 @@ | |
"title": "Disable Help Explorer", | ||
"category": "Atlassian" | ||
}, | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
"command": "atlascode.bitbucket.pullRequestsOverview.refresh", | ||
"title": "Refresh", | ||
"icon": { | ||
"light": "resources/light/refresh.svg", | ||
"dark": "resources/dark/refresh.svg" | ||
} | ||
}, | ||
{ | ||
"command": "atlascode.showOnboardingFlow", | ||
"title": "Show Onboarding Flow", | ||
|
@@ -432,7 +449,12 @@ | |
{ | ||
"id": "atlascode.views.bb.pullrequestsTreeView", | ||
"name": "Bitbucket pull requests", | ||
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled" | ||
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled && config.atlascode.bitbucket.explorer.repositoryBasedPullRequestView.enabled" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
}, | ||
{ | ||
"id": "atlascode.views.bb.pullRequestsOverviewTreeView", | ||
"name": "Bitbucket pull requests overview", | ||
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled && config.atlascode.bitbucket.explorer.pullRequestsOverview.enabled" | ||
}, | ||
{ | ||
"id": "atlascode.views.bb.pipelinesTreeView", | ||
|
@@ -579,6 +601,11 @@ | |
{ | ||
"command": "atlascode.disableHelpExplorer", | ||
"when": "view == atlascode.views.helpTreeView" | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
{ | ||
"command": "atlascode.bitbucket.pullRequestsOverview.refresh", | ||
"when": "view == atlascode.views.bb.pullRequestsOverviewTreeView", | ||
"group": "navigation@1" | ||
} | ||
], | ||
"view/item/context": [ | ||
|
@@ -825,7 +852,7 @@ | |
"properties": { | ||
"atlascode.outputLevel": { | ||
"type": "string", | ||
"default": "silent", | ||
"default": "debug", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
"enum": [ | ||
"silent", | ||
"errors", | ||
|
@@ -1105,6 +1132,18 @@ | |
"description": "Enables the Bitbucket Pull Request Explorer", | ||
"scope": "window" | ||
}, | ||
"atlascode.bitbucket.explorer.repositoryBasedPullRequestView.enabled": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
"type": "boolean", | ||
"default": true, | ||
"description": "Enable repository based pull requests tree view", | ||
"scope": "window" | ||
}, | ||
"atlascode.bitbucket.explorer.pullRequestsOverview.enabled": { | ||
"type": "boolean", | ||
"default": true, | ||
"description": "Enable pull requests overview tree view", | ||
"scope": "window" | ||
}, | ||
"atlascode.bitbucket.explorer.nestFilesEnabled": { | ||
"type": "boolean", | ||
"default": true, | ||
|
@@ -1237,6 +1276,12 @@ | |
"default": true, | ||
"description": "Shows the help explorer treeview", | ||
"scope": "window" | ||
}, | ||
"atlascode.disableOnboarding": { | ||
"type": "boolean", | ||
"default": false, | ||
"description": "Hide initial onboarding and help screens", | ||
"scope": "window" | ||
} | ||
} | ||
} | ||
|
@@ -1284,6 +1329,7 @@ | |
"@material-ui/styles": "^4.11.5", | ||
"@segment/analytics-node": "^2.1.3", | ||
"@vscode/webview-ui-toolkit": "^1.4.0", | ||
"async-mutex": "^0.5.0", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
"awesome-debounce-promise": "^2.1.0", | ||
"axios": "1.9.0", | ||
"axios-curlirize": "^2.0.0", | ||
|
@@ -1408,4 +1454,4 @@ | |
"webpack-node-externals": "^3.0.0" | ||
}, | ||
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,7 +62,7 @@ export interface AuthInfo { | |
|
||
export interface OAuthInfo extends AuthInfo { | ||
access: string; | ||
refresh: string; | ||
refresh?: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
expirationDate?: number; | ||
iat?: number; | ||
recievedAt: number; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -223,21 +223,6 @@ describe('CredentialManager', () => { | |
site: mockJiraSite, | ||
}); | ||
}); | ||
|
||
it("should not save to secret storage if auth info hasn't changed", async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
// Setup existing auth info | ||
const memStore = (credentialManager as any)._memStore; | ||
const jiraStore = memStore.get(ProductJira.key); | ||
jiraStore.set(mockJiraSite.credentialId, mockAuthInfo); | ||
|
||
// Mock getAuthInfo to return the existing info | ||
jest.spyOn(credentialManager, 'getAuthInfo').mockResolvedValue(mockAuthInfo); | ||
|
||
await credentialManager.saveAuthInfo(mockJiraSite, mockAuthInfo); | ||
|
||
expect(Container.context.secrets.store).not.toHaveBeenCalled(); | ||
expect(mockFireEvent).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
describe('refreshAccessToken', () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { Mutex } from 'async-mutex'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an older PR |
||
import crypto from 'crypto'; | ||
import PQueue from 'p-queue'; | ||
import { Disposable, Event, EventEmitter, version, window } from 'vscode'; | ||
|
@@ -34,10 +35,15 @@ enum Priority { | |
Write, | ||
} | ||
|
||
function getKeyForSecrets(productKey: string, credentialId: string): string { | ||
return `${productKey}-${credentialId}`; | ||
} | ||
|
||
export class CredentialManager implements Disposable { | ||
private _memStore: Map<string, Map<string, AuthInfo>> = new Map<string, Map<string, AuthInfo>>(); | ||
private _queue = new PQueue({ concurrency: 1 }); | ||
private _refresher = new OAuthRefesher(); | ||
private mutex = new Mutex(); | ||
|
||
constructor(private _analyticsClient: AnalyticsClient) { | ||
this._memStore.set(ProductJira.key, new Map<string, AuthInfo>()); | ||
|
@@ -59,21 +65,40 @@ export class CredentialManager implements Disposable { | |
* it's available, otherwise will return the value in the secretstorage. | ||
*/ | ||
public async getAuthInfo(site: DetailedSiteInfo, allowCache = true): Promise<AuthInfo | undefined> { | ||
return this.getAuthInfoForProductAndCredentialId(site, allowCache); | ||
return this.mutex.runExclusive(() => { | ||
return this.getAuthInfoForProductAndCredentialId(site, allowCache); | ||
}); | ||
} | ||
|
||
/** | ||
* Saves the auth info to both the in-memory store and the secretstorage. | ||
*/ | ||
public async saveAuthInfo(site: DetailedSiteInfo, info: AuthInfo): Promise<void> { | ||
Logger.debug(`Saving auth info for site: ${site.baseApiUrl} credentialID: ${site.credentialId}`); | ||
return this.mutex.runExclusive(() => { | ||
return this.saveAuthInfoForProductAndCredentialId(site, info); | ||
}); | ||
} | ||
|
||
/** | ||
* Removes an auth item from both the in-memory store and the secretstorage. | ||
*/ | ||
public async removeAuthInfo(site: DetailedSiteInfo): Promise<boolean> { | ||
return this.mutex.runExclusive(() => { | ||
return this.removeAuthInfoForProductAndCredentialId(site); | ||
}); | ||
} | ||
|
||
/** | ||
* Saves the auth info to both the in-memory store and the secretstorage. | ||
*/ | ||
public async saveAuthInfoForProductAndCredentialId(site: DetailedSiteInfo, info: AuthInfo): Promise<void> { | ||
let productAuths = this._memStore.get(site.product.key); | ||
|
||
if (!productAuths) { | ||
productAuths = new Map<string, AuthInfo>(); | ||
} | ||
|
||
const existingInfo = await this.getAuthInfo(site, false); | ||
const existingInfo = await this.getAuthInfoForProductAndCredentialId(site, false); | ||
|
||
this._memStore.set(site.product.key, productAuths.set(site.credentialId, info)); | ||
|
||
|
@@ -100,6 +125,29 @@ export class CredentialManager implements Disposable { | |
} | ||
} | ||
|
||
/** | ||
* Saves the auth info to both the in-memory store and the secretstorage for Bitbucket API key login | ||
*/ | ||
public async saveAuthInfoBBToken(site: DetailedSiteInfo, info: AuthInfo, id: number): Promise<void> { | ||
Logger.debug( | ||
`[id=${id}] Saving auth info for site: ${site.baseApiUrl} credentialID: ${site.credentialId} using BB token`, | ||
); | ||
const productAuths = this._memStore.get(site.product.key); | ||
Logger.debug(`[id=${id}] productAuths: ${productAuths}`); | ||
if (!productAuths) { | ||
this._memStore.set(site.product.key, new Map<string, AuthInfo>().set(site.credentialId, info)); | ||
Logger.debug(`[id=${id}] productAuths is empty so setting it to the new auth info in memstore`); | ||
} else { | ||
productAuths.set(site.credentialId, info); | ||
this._memStore.set(site.product.key, productAuths); | ||
Logger.debug( | ||
`[id=${id}] productAuths is not empty so setting it to productAuth: ${productAuths}in memstore`, | ||
); | ||
} | ||
Logger.debug(`[id=${id}] Calling saveAuthInfo for site: ${site.baseApiUrl} credentialID: ${site.credentialId}`); | ||
await this.saveAuthInfo(site, info); | ||
} | ||
|
||
private async getAuthInfoForProductAndCredentialId( | ||
site: DetailedSiteInfo, | ||
allowCache: boolean, | ||
|
@@ -202,7 +250,10 @@ export class CredentialManager implements Disposable { | |
await this._queue.add( | ||
async () => { | ||
try { | ||
await Container.context.secrets.store(`${productKey}-${credentialId}`, JSON.stringify(info)); | ||
await Container.context.secrets.store( | ||
getKeyForSecrets(productKey, credentialId), | ||
JSON.stringify(info), | ||
); | ||
} catch (e) { | ||
Logger.error(e, `Error writing to secretstorage`); | ||
} | ||
|
@@ -217,7 +268,7 @@ export class CredentialManager implements Disposable { | |
let info: string | undefined = undefined; | ||
await this._queue.add( | ||
async () => { | ||
info = await Container.context.secrets.get(`${productKey}-${credentialId}`); | ||
info = await Container.context.secrets.get(getKeyForSecrets(productKey, credentialId)); | ||
}, | ||
{ priority: Priority.Read }, | ||
); | ||
|
@@ -227,9 +278,9 @@ export class CredentialManager implements Disposable { | |
let wasKeyDeleted = false; | ||
await this._queue.add( | ||
async () => { | ||
const storedInfo = await Container.context.secrets.get(`${productKey}-${credentialId}`); | ||
const storedInfo = await Container.context.secrets.get(getKeyForSecrets(productKey, credentialId)); | ||
if (storedInfo) { | ||
await Container.context.secrets.delete(`${productKey}-${credentialId}`); | ||
await Container.context.secrets.delete(getKeyForSecrets(productKey, credentialId)); | ||
wasKeyDeleted = true; | ||
} | ||
}, | ||
|
@@ -244,7 +295,7 @@ export class CredentialManager implements Disposable { | |
if (keychain) { | ||
wasKeyDeleted = await keychain.deletePassword( | ||
keychainServiceNameV3, | ||
`${productKey}-${credentialId}`, | ||
getKeyForSecrets(productKey, credentialId), | ||
); | ||
} | ||
}, | ||
|
@@ -256,9 +307,7 @@ export class CredentialManager implements Disposable { | |
private async getAuthInfoFromSecretStorage( | ||
productKey: string, | ||
credentialId: string, | ||
serviceName?: string, | ||
): Promise<AuthInfo | undefined> { | ||
Logger.debug(`Retrieving secretstorage info for product: ${productKey} credentialID: ${credentialId}`); | ||
let authInfo: string | undefined = undefined; | ||
authInfo = await this.getSiteInformationFromSecretStorage(productKey, credentialId); | ||
if (!authInfo) { | ||
|
@@ -288,7 +337,7 @@ export class CredentialManager implements Disposable { | |
await this._queue.add( | ||
async () => { | ||
if (keychain) { | ||
authInfo = await keychain.getPassword(svcName, `${productKey}-${credentialId}`); | ||
authInfo = await keychain.getPassword(svcName, getKeyForSecrets(productKey, credentialId)); | ||
} | ||
}, | ||
{ priority: Priority.Read }, | ||
|
@@ -320,7 +369,7 @@ export class CredentialManager implements Disposable { | |
|
||
const provider: OAuthProvider | undefined = oauthProviderForSite(site); | ||
const newTokens = undefined; | ||
if (provider && credentials) { | ||
if (provider && credentials && credentials.refresh) { | ||
const tokenResponse = await this._refresher.getNewTokens(provider, credentials.refresh); | ||
if (tokenResponse.tokens) { | ||
const newTokens = tokenResponse.tokens; | ||
|
@@ -344,7 +393,7 @@ export class CredentialManager implements Disposable { | |
/** | ||
* Removes an auth item from both the in-memory store and the secretstorage. | ||
*/ | ||
public async removeAuthInfo(site: DetailedSiteInfo): Promise<boolean> { | ||
public async removeAuthInfoForProductAndCredentialId(site: DetailedSiteInfo): Promise<boolean> { | ||
const productAuths = this._memStore.get(site.product.key); | ||
let wasKeyDeleted = false; | ||
let wasMemDeleted = false; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In an older PR