diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index fafe76a3e1eb1..6d339e8581544 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -441,7 +441,11 @@ export class IssueReporter extends Disposable { private updatePreviewButtonState() { if (this.isPreviewEnabled()) { - this.previewButton.label = localize('previewOnGitHub', "Preview on GitHub"); + if (this.configuration.data.githubAccessToken) { + this.previewButton.label = localize('createOnGitHub', "Create on GitHub"); + } else { + this.previewButton.label = localize('previewOnGitHub', "Preview on GitHub"); + } this.previewButton.enabled = true; } else { this.previewButton.enabled = false; @@ -776,6 +780,35 @@ export class IssueReporter extends Disposable { return isValid; } + private async submitToGitHub(issueTitle: string, issueBody: string, gitHubDetails: { owner: string, repositoryName: string }): Promise { + const url = `https://api.github.com/repos/${gitHubDetails.owner}/${gitHubDetails.repositoryName}/issues`; + const init = { + method: 'POST', + body: JSON.stringify({ + title: issueTitle, + body: issueBody + }), + headers: new Headers({ + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.configuration.data.githubAccessToken}` + }) + }; + + return new Promise((resolve, reject) => { + window.fetch(url, init).then((response) => { + if (response.ok) { + response.json().then(result => { + ipcRenderer.send('vscode:openExternal', result.html_url); + ipcRenderer.send('vscode:closeIssueReporter'); + resolve(true); + }); + } else { + resolve(false); + } + }); + }); + } + private async createIssue(): Promise { if (!this.validateInputs()) { // If inputs are invalid, set focus to the first one and add listeners on them @@ -808,8 +841,16 @@ export class IssueReporter extends Disposable { this.hasBeenSubmitted = true; - const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value); + const issueTitle = (this.getElementById('issue-title')).value; const issueBody = this.issueReporterModel.serialize(); + + const issueUrl = this.issueReporterModel.fileOnExtension() ? this.getExtensionGitHubUrl() : this.configuration.product.reportIssueUrl!; + const gitHubDetails = this.parseGitHubUrl(issueUrl); + if (this.configuration.data.githubAccessToken && gitHubDetails) { + return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); + } + + const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value); let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; if (url.length > MAX_URL_LENGTH) { @@ -839,6 +880,20 @@ export class IssueReporter extends Disposable { }); } + private parseGitHubUrl(url: string): undefined | { repositoryName: string, owner: string } { + // Assumes a GitHub url to a particular repo, https://github.com/repositoryName/owner. + // Repository name and owner cannot contain '/' + const match = /^https?:\/\/github\.com\/([^\/]*)\/([^\/]*).*/.exec(url); + if (match && match.length) { + return { + owner: match[1], + repositoryName: match[2] + }; + } + + return undefined; + } + private getExtensionGitHubUrl(): string { let repositoryUrl = ''; const bugsUrl = this.getExtensionBugsUrl(); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index ba0bae45c5a45..fc36369c68c91 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -56,6 +56,7 @@ export interface IssueReporterData extends WindowData { issueType?: IssueType; extensionId?: string; experiments?: string; + githubAccessToken: string; readonly issueTitle?: string; readonly issueBody?: string; } diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts index 47f72a58ba7d9..94beb1646aede 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts @@ -17,6 +17,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; export class WorkbenchIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -28,7 +29,8 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IProductService private readonly productService: IProductService, - @ITASExperimentService private readonly experimentService: ITASExperimentService + @ITASExperimentService private readonly experimentService: ITASExperimentService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService ) { } async openReporter(dataOverrides: Partial = {}): Promise { @@ -52,12 +54,15 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { }; }); const experiments = await this.experimentService.getCurrentExperiments(); + const githubSessions = await this.authenticationService.getSessions('github'); + const potentialSessions = githubSessions.filter(session => session.scopes.includes('repo')); const theme = this.themeService.getColorTheme(); const issueReporterData: IssueReporterData = Object.assign({ styles: getIssueReporterStyles(theme), zoomLevel: getZoomLevel(), enabledExtensions: extensionData, - experiments: experiments?.join('\n') + experiments: experiments?.join('\n'), + githubAccessToken: potentialSessions[0]?.accessToken }, dataOverrides); return this.issueService.openReporter(issueReporterData); }