Skip to content
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

allow issue reporter command to activate API #201905

Merged
merged 3 commits into from Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
127 changes: 83 additions & 44 deletions src/vs/code/electron-sandbox/issue/issueReporterService.ts
Expand Up @@ -71,7 +71,6 @@ export class IssueReporter extends Disposable {
});

//TODO: Handle case where extension is not activated

const issueReporterElement = this.getElementById('issue-reporter');
if (issueReporterElement) {
this.previewButton = new Button(issueReporterElement, unthemedButtonStyles);
Expand Down Expand Up @@ -133,6 +132,11 @@ export class IssueReporter extends Disposable {
this.updateExperimentsInfo(configuration.data.experiments);
this.updateRestrictedMode(configuration.data.restrictedMode);
this.updateUnsupportedMode(configuration.data.isUnsupported);

// Handle case where extension is pre-selected through the command
if (configuration.data.command && targetExtension) {
this.updateExtensionStatus(targetExtension);
}
}

render(): void {
Expand Down Expand Up @@ -772,7 +776,6 @@ export class IssueReporter extends Disposable {
hide(workspaceBlock);
hide(extensionsBlock);
hide(experimentsBlock);
hide(problemSource);
hide(extensionSelector);
hide(extensionDataTextArea);
hide(extensionDataBlock);
Expand All @@ -785,7 +788,7 @@ export class IssueReporter extends Disposable {
show(extensionSelector);
}

if (fileOnExtension && selectedExtension?.hasIssueUriRequestHandler) {
if (fileOnExtension && selectedExtension?.hasIssueUriRequestHandler && !selectedExtension.hasIssueDataProviders) {
hide(titleTextArea);
hide(descriptionTextArea);
reset(descriptionTitle, localize('handlesIssuesElsewhere', "This extension handles issues outside of VS Code"));
Expand Down Expand Up @@ -890,8 +893,10 @@ export class IssueReporter extends Disposable {
}

private async createIssue(): Promise<boolean> {
const hasUri = this.issueReporterModel.getData().selectedExtension?.hasIssueUriRequestHandler;
const hasData = this.issueReporterModel.getData().selectedExtension?.hasIssueDataProviders;
// Short circuit if the extension provides a custom issue handler
if (this.issueReporterModel.getData().selectedExtension?.hasIssueUriRequestHandler) {
if (hasUri && !hasData) {
const url = this.getExtensionBugsUrl();
if (url) {
this.hasBeenSubmitted = true;
Expand Down Expand Up @@ -934,7 +939,10 @@ export class IssueReporter extends Disposable {
const issueTitle = (<HTMLInputElement>this.getElementById('issue-title')).value;
const issueBody = this.issueReporterModel.serialize();

const issueUrl = this.getIssueUrl();
const issueUrl = hasUri ? this.getExtensionBugsUrl() : this.getIssueUrl();
if (!issueUrl) {
return false;
}
const gitHubDetails = this.parseGitHubUrl(issueUrl);
if (this.configuration.data.githubAccessToken && gitHubDetails) {
return this.submitToGitHub(issueTitle, issueBody, gitHubDetails);
Expand Down Expand Up @@ -1141,49 +1149,13 @@ export class IssueReporter extends Disposable {
const extensions = this.issueReporterModel.getData().allExtensions;
const matches = extensions.filter(extension => extension.id === selectedExtensionId);
if (matches.length) {
this.issueReporterModel.update({ selectedExtension: matches[0] });

// if extension does not have provider/handles, will check for either. If extension is already active, IPC will return [false, false] and will proceed as normal.
if (!matches[0].hasIssueDataProviders && !matches[0].hasIssueUriRequestHandler) {
const toActivate = await this.getReporterStatus(matches[0]);
matches[0].hasIssueDataProviders = toActivate[0];
matches[0].hasIssueUriRequestHandler = toActivate[1];
this.renderBlocks();
}

if (matches[0].hasIssueUriRequestHandler) {
this.updateIssueReporterUri(matches[0]);
} else if (matches[0].hasIssueDataProviders) {
const template = await this.getIssueTemplateFromExtension(matches[0]);
const descriptionTextArea = this.getElementById('description')!;
const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value;
if (descriptionText === '' || !descriptionText.includes(template)) {
const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template;
(descriptionTextArea as HTMLTextAreaElement).value = fullTextArea;
this.issueReporterModel.update({ issueDescription: fullTextArea });
}
const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!;
show(extensionDataBlock);

// Start loading for extension data.
const iconElement = document.createElement('span');
iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin');
this.setLoading(iconElement);
await this.getIssueDataFromExtension(matches[0]);
this.removeLoading(iconElement);
} else {
this.validateSelectedExtension();
this.issueReporterModel.update({ extensionData: undefined });
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
this.searchExtensionIssues(title);
}
this.updateExtensionStatus(matches[0]);
} else {
this.issueReporterModel.update({ selectedExtension: undefined });
this.clearSearchResults();
this.validateSelectedExtension();
}
this.updatePreviewButtonState();
this.renderBlocks();

});
}

Expand All @@ -1192,6 +1164,73 @@ export class IssueReporter extends Disposable {
});
}

private async updateExtensionStatus(extension: IssueReporterExtensionData) {
this.issueReporterModel.update({ selectedExtension: extension });

// if extension does not have provider/handles, will check for either. If extension is already active, IPC will return [false, false] and will proceed as normal.
if (!extension.hasIssueDataProviders && !extension.hasIssueUriRequestHandler) {
const toActivate = await this.getReporterStatus(extension);
extension.hasIssueDataProviders = toActivate[0];
extension.hasIssueUriRequestHandler = toActivate[1];
this.renderBlocks();
}

if (extension.hasIssueUriRequestHandler && extension.hasIssueDataProviders) {
// update this first
const template = await this.getIssueTemplateFromExtension(extension);
const descriptionTextArea = this.getElementById('description')!;
const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value;
if (descriptionText === '' || !descriptionText.includes(template)) {
const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template;
(descriptionTextArea as HTMLTextAreaElement).value = fullTextArea;
this.issueReporterModel.update({ issueDescription: fullTextArea });
}
const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!;
show(extensionDataBlock);

// Start loading for extension data.
const iconElement = document.createElement('span');
iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin');
this.setLoading(iconElement);
await this.getIssueDataFromExtension(extension);
this.removeLoading(iconElement);

// then update this
this.updateIssueReporterUri(extension);

// reset to false so issue url is updated, but won't be affected later.
// extension.hasIssueUriRequestHandler = false;
} else if (extension.hasIssueUriRequestHandler) {
this.updateIssueReporterUri(extension);
} else if (extension.hasIssueDataProviders) {
const template = await this.getIssueTemplateFromExtension(extension);
const descriptionTextArea = this.getElementById('description')!;
const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value;
if (descriptionText === '' || !descriptionText.includes(template)) {
const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template;
(descriptionTextArea as HTMLTextAreaElement).value = fullTextArea;
this.issueReporterModel.update({ issueDescription: fullTextArea });
}
const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!;
show(extensionDataBlock);

// Start loading for extension data.
const iconElement = document.createElement('span');
iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin');
this.setLoading(iconElement);
await this.getIssueDataFromExtension(extension);
this.removeLoading(iconElement);
} else {
this.validateSelectedExtension();
this.issueReporterModel.update({ extensionData: undefined });
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
this.searchExtensionIssues(title);
}

this.updatePreviewButtonState();
this.renderBlocks();
}

private validateSelectedExtension(): void {
const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!;
const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!;
Expand All @@ -1205,7 +1244,7 @@ export class IssueReporter extends Disposable {
}

const hasValidGitHubUrl = this.getExtensionGitHubUrl();
if (hasValidGitHubUrl || extension.hasIssueUriRequestHandler) {
if (hasValidGitHubUrl || (extension.hasIssueUriRequestHandler && !extension.hasIssueDataProviders)) {
this.previewButton.enabled = true;
} else {
this.setExtensionValidationMessage();
Expand Down
2 changes: 2 additions & 0 deletions src/vs/platform/issue/common/issue.ts
Expand Up @@ -57,6 +57,7 @@ export interface IssueReporterExtensionData {
extensionTemplate?: string;
hasIssueUriRequestHandler?: boolean;
hasIssueDataProviders?: boolean;
command?: boolean;
}

export interface IssueReporterData extends WindowData {
Expand All @@ -70,6 +71,7 @@ export interface IssueReporterData extends WindowData {
githubAccessToken: string;
readonly issueTitle?: string;
readonly issueBody?: string;
readonly command?: boolean;
}

export interface ISettingSearchResult {
Expand Down
11 changes: 6 additions & 5 deletions src/vs/workbench/api/browser/mainThreadIssueReporter.ts
Expand Up @@ -13,7 +13,8 @@ import { IIssueDataProvider, IIssueUriRequestHandler, IWorkbenchIssueService } f
@extHostNamedCustomer(MainContext.MainThreadIssueReporter)
export class MainThreadIssueReporter extends Disposable implements MainThreadIssueReporterShape {
private readonly _proxy: ExtHostIssueReporterShape;
private readonly _registrations = this._register(new DisposableMap<string>());
private readonly _registrationsUri = this._register(new DisposableMap<string>());
private readonly _registrationsData = this._register(new DisposableMap<string>());

constructor(
context: IExtHostContext,
Expand All @@ -30,11 +31,11 @@ export class MainThreadIssueReporter extends Disposable implements MainThreadIss
return URI.from(parts);
}
};
this._registrations.set(extensionId, this._issueService.registerIssueUriRequestHandler(extensionId, handler));
this._registrationsUri.set(extensionId, this._issueService.registerIssueUriRequestHandler(extensionId, handler));
}

$unregisterIssueUriRequestHandler(extensionId: string): void {
this._registrations.deleteAndDispose(extensionId);
this._registrationsUri.deleteAndDispose(extensionId);
}

$registerIssueDataProvider(extensionId: string): void {
Expand All @@ -48,10 +49,10 @@ export class MainThreadIssueReporter extends Disposable implements MainThreadIss
return parts;
}
};
this._registrations.set(extensionId, this._issueService.registerIssueDataProvider(extensionId, provider));
this._registrationsData.set(extensionId, this._issueService.registerIssueDataProvider(extensionId, provider));
}

$unregisterIssueDataProvider(extensionId: string): void {
this._registrations.deleteAndDispose(extensionId);
this._registrationsData.deleteAndDispose(extensionId);
}
}
2 changes: 1 addition & 1 deletion src/vs/workbench/services/issue/browser/issueService.ts
Expand Up @@ -73,7 +73,7 @@ export class WebIssueService implements IWorkbenchIssueService {

registerIssueDataProvider(extensionId: string, handler: IIssueDataProvider): IDisposable {
this._providers.set(extensionId, handler);
return toDisposable(() => this._handlers.delete(extensionId));
return toDisposable(() => this._providers.delete(extensionId));
}

private async getIssueUriFromHandler(extensionId: string, token: CancellationToken): Promise<string> {
Expand Down
Expand Up @@ -103,6 +103,7 @@ export class NativeIssueService implements IWorkbenchIssueService {
hasIssueDataProviders: this._providers.has(extension.identifier.id.toLowerCase()),
displayName: manifest.displayName,
id: extension.identifier.id,
command: dataOverrides.command,
isTheme,
isBuiltin,
extensionData: 'Extensions data loading',
Expand Down