Introducing Visualizer to SQL Query Editor (#6422)

* extension recommendation on application launch

* Introducing Visualizer (SandDance) to the SQL Query Editor. (#6347)

* Created Visualizer icon in the results grid. Utilized a context key so that the icon only shows if Visualizer extensions (currently, just SandDance) is installed. Visualizer icon open up SandDance in a top-level document.

* When the user clicks on Charts, visualizer recommendation popup appears. User can click on "Install Extensions" to download the visualizer extensions.

* Enabled SQL Query Editor to pass query data to SandDance extension.

* Introducing Visualizer (SandDance) to the SQL Query Editor. (#6347)

* Created Visualizer icon in the results grid. Utilized a context key so that the icon only shows if Visualizer extensions (currently, just SandDance) is installed. Visualizer icon open up SandDance in a top-level document.

* When the user clicks on Charts, visualizer recommendation popup appears. User can click on "Install Extensions" to download the visualizer extensions.

* Enabled SQL Query Editor to pass query data to SandDance extension.

* Cleaned code; made changes according to PR comments

* removed the test service for extensions gallary

* Cleaned up code according to PR changes

* unid changes to build/azure-piplines

* Removed all the build/azure-pipelines changes

* removed changes on media/language.svg

* refactored extension recommendation system to allow it to be generic

* updated extensionsViews to support generic extension query search; added localized constants for visualizer extensions

* Made syntax and error message changes acccording to PR comments.

* Updated recommendation message according to scenario type
This commit is contained in:
Rebecca Runxin Wang
2019-07-29 13:54:32 -07:00
committed by Rachel Kim
parent 720a7fbfa2
commit 2c8a22bb0d
28 changed files with 337 additions and 22 deletions

View File

@@ -71,6 +71,16 @@ export class SimpleExtensionTipsService implements IExtensionTipsService {
getAllIgnoredRecommendations(): { global: string[]; workspace: string[]; } {
return { global: [], workspace: [] };
}
// {{SQL CARBON EDIT}}
getRecommendedExtensionsByScenario(scenarioType: string): Promise<IExtensionRecommendation[]> {
return Promise.resolve([]);
}
promptRecommendedExtensionsByScenario(scenarioType: string): void {
return;
}
// {{SQL CARBON EDIT}} - End
}
registerSingleton(IExtensionTipsService, SimpleExtensionTipsService, true);

View File

@@ -47,6 +47,9 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IProductService } from 'vs/platform/product/common/product';
import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon';
// {{SQL CARBON EDIT}}
import product from 'vs/platform/product/node/product';
class ExtensionsViewState extends Disposable implements IExtensionsViewState {
@@ -424,6 +427,19 @@ export class ExtensionsListView extends ViewletPanel {
options.sortBy = SortBy.InstallCount;
}
// {{SQL CARBON EDIT}}
let promiseRecommendedExtensionsByScenario;
Object.keys(product.recommendedExtensionsByScenario).forEach(scenarioType => {
let re = new RegExp('@' + scenarioType, 'i');
if (re.test(query.value)) {
promiseRecommendedExtensionsByScenario = this.getRecommendedExtensionsByScenario(token, scenarioType);
}
});
if (promiseRecommendedExtensionsByScenario) {
return promiseRecommendedExtensionsByScenario;
}
// {{SQL CARBON EDIT}} - End
if (ExtensionsListView.isWorkspaceRecommendedExtensionsQuery(query.value)) {
return this.getWorkspaceRecommendationsModel(query, options, token);
} else if (ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value)) {
@@ -650,6 +666,31 @@ export class ExtensionsListView extends ViewletPanel {
});
}
// {{SQL CARBON EDIT}}
private getRecommendedExtensionsByScenario(token: CancellationToken, scenarioType: string): Promise<IPagedModel<IExtension>> {
if (!scenarioType) {
return Promise.reject(new Error(localize('scenarioTypeUndefined', 'The scenario type for extension recommendations must be provided.')));
}
return this.extensionsWorkbenchService.queryLocal()
.then(result => result.filter(e => e.type === ExtensionType.User))
.then(local => {
return this.tipsService.getRecommendedExtensionsByScenario(scenarioType).then((recommmended) => {
const installedExtensions = local.map(x => `${x.publisher}.${x.name}`);
return this.extensionsWorkbenchService.queryGallery(token).then((pager) => {
// filter out installed extensions and the extensions not in the recommended list
pager.firstPage = pager.firstPage.filter((p) => {
const extensionId = `${p.publisher}.${p.name}`;
return installedExtensions.indexOf(extensionId) === -1 && recommmended.findIndex(ext => ext.extensionId === extensionId) !== -1;
});
pager.total = pager.firstPage.length;
pager.pageSize = pager.firstPage.length;
return this.getPagedModel(pager);
});
});
});
}
// {{SQL CARBON EDIT}} - End
// Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions
private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, fileBasedRecommendations: IExtensionRecommendation[], otherRecommendations: IExtensionRecommendation[], workpsaceRecommendations: IExtensionRecommendation[]): string[] {
const totalCount = 8;

View File

@@ -16,7 +16,11 @@ import { ITextModel } from 'vs/editor/common/model';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import product from 'vs/platform/product/node/product';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
// {{SQL CARBON EDIT}}
import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { ShowRecommendedExtensionsByScenarioAction, InstallRecommendedExtensionsByScenarioAction } from 'sql/workbench/contrib/extensions/extensionsActions';
import * as Constants from 'sql/workbench/contrib/extensions/constants';
// {{SQL CARBON EDIT}} - End
import Severity from 'vs/base/common/severity';
import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileService } from 'vs/platform/files/common/files';
@@ -1148,4 +1152,79 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
private isExtensionAllowedToBeRecommended(id: string): boolean {
return this._allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1;
}
// {{SQL CARBON EDIT}}
promptRecommendedExtensionsByScenario(scenarioType: string): void {
const storageKey = 'extensionAssistant/RecommendationsIgnore/' + scenarioType;
if (this.storageService.getBoolean(storageKey, StorageScope.GLOBAL, false)) {
return;
}
let recommendations: IExtensionRecommendation[];
let localExtensions: ILocalExtension[];
const getRecommendationPromise = this.getRecommendedExtensionsByScenario(scenarioType).then(recs => { recommendations = recs; });
const getLocalExtensionPromise = this.extensionsService.getInstalled(ExtensionType.User).then(local => { localExtensions = local; });
let recommendationMessage = localize('ExtensionsRecommended', "Azure Data Studio has extension recommendations.");
if (scenarioType === Constants.visualizerExtensions) {
recommendationMessage = localize('VisualizerExtensionsRecommended', "Azure Data Studio has extension recommendations for data visualization.\nOnce installed, you can select the Visualizer icon to visualize your query results.");
}
Promise.all([getRecommendationPromise, getLocalExtensionPromise]).then(() => {
if (!recommendations.every(rec => { return localExtensions.findIndex(local => local.identifier.id.toLocaleLowerCase() === rec.extensionId.toLocaleLowerCase()) !== -1; })) {
return new Promise<void>(c => {
this.notificationService.prompt(
Severity.Info,
recommendationMessage,
[{
label: localize('installAll', "Install All"),
run: () => {
this.telemetryService.publicLog(scenarioType + 'Recommendations:popup', { userReaction: 'install' });
const installAllAction = this.instantiationService.createInstance(InstallRecommendedExtensionsByScenarioAction, scenarioType, recommendations);
installAllAction.run();
installAllAction.dispose();
}
}, {
label: localize('showRecommendations', "Show Recommendations"),
run: () => {
this.telemetryService.publicLog(scenarioType + 'Recommendations:popup', { userReaction: 'show' });
const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsByScenarioAction, scenarioType);
showAction.run();
showAction.dispose();
c(undefined);
}
}, {
label: choiceNever,
isSecondary: true,
run: () => {
this.telemetryService.publicLog(scenarioType + 'Recommendations:popup', { userReaction: 'neverShowAgain' });
this.storageService.store(storageKey, true, StorageScope.GLOBAL);
c(undefined);
}
}],
{
sticky: true,
onCancel: () => {
this.telemetryService.publicLog(scenarioType + 'Recommendations:popup', { userReaction: 'cancelled' });
c(undefined);
}
}
);
});
} else {
return Promise.resolve();
}
});
}
getRecommendedExtensionsByScenario(scenarioType: string): Promise<IExtensionRecommendation[]> {
if (!scenarioType) {
return Promise.reject(new Error(localize('scenarioTypeUndefined', 'The scenario type for extension recommendations must be provided.')));
}
return Promise.resolve((product.recommendedExtensionsByScenario[scenarioType] || [])
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId))
.map(extensionId => (<IExtensionRecommendation>{ extensionId, sources: ['application'] })));
}
// {{SQL CARBON EDIT}} - End
}

View File

@@ -106,6 +106,10 @@ export interface IExtensionTipsService {
toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void;
getAllIgnoredRecommendations(): { global: string[], workspace: string[] };
onRecommendationChange: Event<RecommendationChangeNotification>;
// {{SQL CARBON EDIT}}
getRecommendedExtensionsByScenario(scenarioType: string): Promise<IExtensionRecommendation[]>;
promptRecommendedExtensionsByScenario(scenarioType: string): void;
// {{SQL CARBON EDIT}} - End
}
export const enum ExtensionRecommendationReason {