Skip to content

Commit

Permalink
show contributed issues in quick access (#206303)
Browse files Browse the repository at this point in the history
* show contributed issues in quick accesss

* moved some files around

* cleanup unused import

* quick access overhaul: add all extensions, filter no duplicates, contributed at bottom, add button to open extension page, add experimental setting

* fix to turn on by default

* switch to info button icon instad

* added issue file on product and marketplace

* differentiate btween insiders and stable

* some fixes for now

* address comments

* review fixes
  • Loading branch information
justschen committed Mar 7, 2024
1 parent 6a371bb commit d6fb91c
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/vs/code/electron-sandbox/issue/issueReporterModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface IssueReporterData {
extensionsDisabled?: boolean;
fileOnExtension?: boolean;
fileOnMarketplace?: boolean;
fileOnProduct?: boolean;
selectedExtension?: IssueReporterExtensionData;
actualSearchResults?: ISettingSearchResult[];
query?: string;
Expand Down
10 changes: 9 additions & 1 deletion src/vs/code/electron-sandbox/issue/issueReporterService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export class IssueReporter extends Disposable {
selectedExtension: targetExtension
});

const fileOnMarketplace = configuration.data.issueSource === IssueSource.Marketplace;
const fileOnProduct = configuration.data.issueSource === IssueSource.VSCode;
this.issueReporterModel.update({ fileOnMarketplace, fileOnProduct });

//TODO: Handle case where extension is not activated
const issueReporterElement = this.getElementById('issue-reporter');
if (issueReporterElement) {
Expand Down Expand Up @@ -772,13 +776,17 @@ export class IssueReporter extends Disposable {

private setSourceOptions(): void {
const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement;
const { issueType, fileOnExtension, selectedExtension } = this.issueReporterModel.getData();
const { issueType, fileOnExtension, selectedExtension, fileOnMarketplace, fileOnProduct } = this.issueReporterModel.getData();
let selected = sourceSelect.selectedIndex;
if (selected === -1) {
if (fileOnExtension !== undefined) {
selected = fileOnExtension ? 2 : 1;
} else if (selectedExtension?.isBuiltin) {
selected = 1;
} else if (fileOnMarketplace) {
selected = 3;
} else if (fileOnProduct) {
selected = 1;
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/vs/platform/issue/common/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export const enum IssueType {
FeatureRequest
}

export enum IssueSource {
VSCode = 'vscode',
Extension = 'extension',
Marketplace = 'marketplace'
}

export interface IssueReporterStyles extends WindowStyles {
textLinkColor?: string;
textLinkActiveForeground?: string;
Expand Down Expand Up @@ -65,6 +71,7 @@ export interface IssueReporterData extends WindowData {
styles: IssueReporterStyles;
enabledExtensions: IssueReporterExtensionData[];
issueType?: IssueType;
issueSource?: IssueSource;
extensionId?: string;
experiments?: string;
restrictedMode: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
type: 'boolean',
description: localize('extensionsDeferredStartupFinishedActivation', "When enabled, extensions which declare the `onStartupFinished` activation event will be activated after a timeout."),
default: false
},
'extensions.experimental.issueQuickAccess': {
type: 'boolean',
description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."),
default: true
}
}
});
Expand Down
6 changes: 4 additions & 2 deletions src/vs/workbench/contrib/issue/browser/issue.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common
import { WebIssueService } from 'vs/workbench/services/issue/browser/issueService';
import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue';
import { BaseIssueContribution } from 'vs/workbench/contrib/issue/common/issue.contribution';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';


class WebIssueContribution extends BaseIssueContribution {
constructor(@IProductService productService: IProductService) {
super(productService);
constructor(@IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService) {
super(productService, configurationService);
}
}

Expand Down
146 changes: 146 additions & 0 deletions src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { PickerQuickAccessProvider, IPickerQuickAccessItem, FastAndSlowPicks, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { matchesFuzzy } from 'vs/base/common/filters';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ThemeIcon } from 'vs/base/common/themables';
import { Codicon } from 'vs/base/common/codicons';
import { IssueSource } from 'vs/platform/issue/common/issue';
import { IProductService } from 'vs/platform/product/common/productService';

export class IssueQuickAccess extends PickerQuickAccessProvider<IPickerQuickAccessItem> {

static PREFIX = 'issue ';

constructor(
@IMenuService private readonly menuService: IMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@ICommandService private readonly commandService: ICommandService,
@IExtensionService private readonly extensionService: IExtensionService,
@IProductService private readonly productService: IProductService
) {
super(IssueQuickAccess.PREFIX, { canAcceptInBackground: true });
}

protected override _getPicks(filter: string): Picks<IPickerQuickAccessItem> | FastAndSlowPicks<IPickerQuickAccessItem> | Promise<Picks<IPickerQuickAccessItem> | FastAndSlowPicks<IPickerQuickAccessItem>> | null {
const issuePicks = new Array<IPickerQuickAccessItem | IQuickPickSeparator>();
const extensionIdSet = new Set<string>();

// add regular open issue reporter button
const productLabel = this.productService.nameLong;
issuePicks.push({
label: productLabel,
ariaLabel: productLabel,
accept: () => this.commandService.executeCommand('workbench.action.openIssueReporter', { issueSource: IssueSource.VSCode })
});

issuePicks.push({ type: 'separator' });

const marketPlaceLabel = localize("workbench.action.openIssueReporter2", "Extension Marketplace");
issuePicks.push({
label: marketPlaceLabel,
ariaLabel: marketPlaceLabel,
accept: () => this.commandService.executeCommand('workbench.action.openIssueReporter', { issueSource: IssueSource.Marketplace })
});

issuePicks.push({ type: 'separator', label: localize('extensions', "Extensions: Custom Reporting") });

// creates menu from contributed
const menu = this.menuService.createMenu(MenuId.IssueReporter, this.contextKeyService);

// render menu and dispose
const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]);

// create picks from contributed menu
actions.forEach(action => {
if ('source' in action.item && action.item.source) {
extensionIdSet.add(action.item.source.id);
}

const pick = this._createPick(filter, action);
if (pick) {
issuePicks.push(pick);
}
});

menu.dispose();

issuePicks.push({ type: 'separator', label: localize('otherExtensions', "Other Extensions") });

// create picks from extensions
this.extensionService.extensions.forEach(extension => {
if (!extension.isBuiltin) {
const pick = this._createPick(filter, undefined, extension);
const id = extension.identifier.value;
if (pick) {
if (extensionIdSet.has(id)) {
return;
}
else {
issuePicks.push(pick);
}
}
extensionIdSet.add(id);
}
});

return issuePicks;
}

private _createPick(filter: string, action?: MenuItemAction | SubmenuItemAction | undefined, extension?: IRelaxedExtensionDescription): IPickerQuickAccessItem | undefined {
if (action && 'source' in action.item && action.item.source) {
const label = action.item.source?.title;
const highlights = matchesFuzzy(filter, label, true);
if (highlights) {
return {
label,
highlights: { label: highlights },
buttons: [{
iconClass: ThemeIcon.asClassName(Codicon.info),
tooltip: localize('contributedIssuePage', "Open Extension Page")
}],
trigger: () => {
if ('source' in action.item && action.item.source) {
this.commandService.executeCommand('extension.open', action.item.source.id);
}
return TriggerAction.CLOSE_PICKER;
},
accept: (keyMod, event) => {
action.run();
}
};
}
} else if (extension) {
const label = extension.displayName ?? extension.name;
const highlights = matchesFuzzy(filter, label, true);
if (highlights) {
return {
label: label,
highlights: { label: highlights },
buttons: [{
iconClass: ThemeIcon.asClassName(Codicon.info),
tooltip: localize('contributedIssuePage', "Open Extension Page")
}],
trigger: () => {
this.commandService.executeCommand('extension.open', extension.identifier.value);
return TriggerAction.CLOSE_PICKER;
},
accept: (keyMod, event) => {
this.commandService.executeCommand('workbench.action.openIssueReporter', extension.identifier.value);
}

};
}
}
return undefined;
}
}
4 changes: 3 additions & 1 deletion src/vs/workbench/contrib/issue/common/issue.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IssueReporterData } from 'vs/platform/issue/common/issue';
import { IProductService } from 'vs/platform/product/common/productService';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';

const OpenIssueReporterActionId = 'workbench.action.openIssueReporter';
const OpenIssueReporterApiId = 'vscode.openIssueReporter';
Expand Down Expand Up @@ -59,7 +60,8 @@ interface OpenIssueReporterArgs {

export class BaseIssueContribution implements IWorkbenchContribution {
constructor(
@IProductService productService: IProductService
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
) {
if (!productService.reportIssueUrl) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,53 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INativeHostService } from 'vs/platform/native/common/native';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IIssueMainService, IssueType } from 'vs/platform/issue/common/issue';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
import { IssueQuickAccess } from 'vs/workbench/contrib/issue/browser/issueQuickAccess';


//#region Issue Contribution

class NativeIssueContribution extends BaseIssueContribution {

constructor(
@IProductService productService: IProductService
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService
) {
super(productService);
super(productService, configurationService);

if (productService.reportIssueUrl) {
registerAction2(ReportPerformanceIssueUsingReporterAction);
}

let disposable: IDisposable | undefined;

const registerQuickAccessProvider = () => {
disposable = Registry.as<IQuickAccessRegistry>(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({
ctor: IssueQuickAccess,
prefix: IssueQuickAccess.PREFIX,
contextKey: 'inReportIssuePicker',
placeholder: localize('tasksQuickAccessPlaceholder', "Type the name of an extension to report on."),
helpEntries: [{
description: localize('openIssueReporter', "Open Issue Reporter"),
commandId: 'workbench.action.openIssueReporter'
}]
});
};

configurationService.onDidChangeConfiguration(e => {
if (!configurationService.getValue<boolean>('extensions.experimental.issueQuickAccess') && disposable) {
disposable.dispose();
disposable = undefined;
} else if (!disposable) {
registerQuickAccessProvider();
}
});

if (configurationService.getValue<boolean>('extensions.experimental.issueQuickAccess')) {
registerQuickAccessProvider();
}
}
}
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkbenchContribution(NativeIssueContribution, LifecyclePhase.Restored);
Expand Down Expand Up @@ -133,5 +167,4 @@ registerAction2(StopTracing);
CommandsRegistry.registerCommand('_issues.getSystemStatus', (accessor) => {
return accessor.get(IIssueMainService).getSystemStatus();
});

//#endregion

0 comments on commit d6fb91c

Please sign in to comment.