mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Added categories and search based filtering to the resource dialog. (#12658)
* added filtering to the resource type along with a new component. * -Added caching of cards -Removed unused component props -localized tags -limited the scope of list items * Made some changes in the PR * - Added Iot Category to SQL edge - Moved category names to constants - Moved localization strings to localized constants - Made filtering logic more concise - Changed how category list is generated --Category list can now be ordered -Added back event generation for selectedCard * Fixed bugs, and some additional changes -Fixed radiogroup height to avoid the movement of options below it -Restoring the focus back to the search and listview components - Added focus behaviour for listview - Fixed a typo in comment * Made categories an Enum * Added localized string * localized category string converted categories to enum. * made the filtering logic more concise. * returning string if no localized string formed removed unnecessary returns * fixed the filtering tag logic resetting search when category is changed * removing the iot tag from sql edge deployment * made filtering logic more concise made enum const * added vscode list * some cleanup * Some PR changes - Made PR camelcase - added comments to SQL - removed unnecessary export * -Some PR related changes -Removing unsupported style property -scoping down css and removing unused ones. * Fixed a comment text * Fixed typings for listview event
This commit is contained in:
@@ -6,3 +6,13 @@
|
||||
export const DeploymentConfigurationKey: string = 'deployment';
|
||||
export const AzdataInstallLocationKey: string = 'azdataInstallLocation';
|
||||
export const ToolsInstallPath = 'AZDATA_NB_VAR_TOOLS_INSTALLATION_PATH';
|
||||
|
||||
export const enum ResourceTypeCategories {
|
||||
All = 'All',
|
||||
OnPrem = 'On-premises',
|
||||
SqlServer = 'SQL Server',
|
||||
Hybrid = 'Hybrid',
|
||||
PostgreSql = 'PostgreSQL',
|
||||
Cloud = 'Cloud',
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface ResourceType {
|
||||
displayIndex?: number;
|
||||
okButtonText?: string;
|
||||
getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface AgreementInfo {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ResourceTypeCategories } from './constants';
|
||||
import { FieldType, OptionsType } from './interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -34,5 +35,29 @@ export const optionsTypeRadioOrDropdown = localize('optionsTypeRadioOrDropdown',
|
||||
export const azdataEulaNotAccepted = localize('azdataEulaNotAccepted', "Deployment cannot continue. Azure Data CLI license terms have not yet been accepted. Please accept the EULA to enable the features that requires Azure Data CLI.");
|
||||
export const azdataEulaDeclined = localize('azdataEulaDeclined', "Deployment cannot continue. Azure Data CLI license terms were declined.You can either Accept EULA to continue or Cancel this operation");
|
||||
export const acceptEulaAndSelect = localize('deploymentDialog.RecheckEulaButton', "Accept EULA & Select");
|
||||
|
||||
export const resourceTypePickerDialogTitle = localize('resourceTypePickerDialog.title', "Select the deployment options");
|
||||
export const resourceTypeSearchBoxDescription = localize('resourceTypePickerDialog.resourceSearchPlaceholder', "Filter resources...");
|
||||
export const resoucrceTypeCategoryListViewTitle = localize('resourceTypePickerDialog.tagsListViewTitle', 'Categories');
|
||||
|
||||
export const scriptToNotebook = localize('ui.ScriptToNotebookButton', "Script");
|
||||
export const deployNotebook = localize('ui.DeployButton', "Run");
|
||||
|
||||
export function getResourceTypeCategoryLocalizedString(resourceTypeCategory: string): string {
|
||||
switch (resourceTypeCategory) {
|
||||
case ResourceTypeCategories.All:
|
||||
return localize('resourceTypePickerDialog.resourceTypeCategoryAll', "All");
|
||||
case ResourceTypeCategories.OnPrem:
|
||||
return localize('resourceTypePickerDialog.resourceTypeCategoryOnPrem', "On-premises");
|
||||
case ResourceTypeCategories.SqlServer:
|
||||
return localize('resourceTypePickerDialog.resourceTypeCategoriesSqlServer', "SQL Server");
|
||||
case ResourceTypeCategories.Hybrid:
|
||||
return localize('resourceTypePickerDialog.resourceTypeCategoryOnHybrid', "Hybrid");
|
||||
case ResourceTypeCategories.PostgreSql:
|
||||
return localize('resourceTypePickerDialog.resourceTypeCategoryOnPostgreSql', "PostgreSQL");
|
||||
case ResourceTypeCategories.Cloud:
|
||||
return localize('resourceTypePickerDialog.resourceTypeCategoryOnCloud', "Cloud");
|
||||
default:
|
||||
return resourceTypeCategory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,30 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import { EOL } from 'os';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } from '../interfaces';
|
||||
import { select } from '../localizedConstants';
|
||||
import { IResourceTypeService } from '../services/resourceTypeService';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { getErrorMessage } from '../common/utils';
|
||||
import * as loc from './../localizedConstants';
|
||||
import { DialogBase } from './dialogBase';
|
||||
import { createFlexContainer } from './modelViewUtils';
|
||||
import * as constants from '../constants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ResourceTypePickerDialog extends DialogBase {
|
||||
private toolRefreshTimestamp: number = 0;
|
||||
private _resourceTypes!: ResourceType[];
|
||||
private _selectedResourceType: ResourceType;
|
||||
private _view!: azdata.ModelView;
|
||||
private _optionsContainer!: azdata.FlexContainer;
|
||||
private _toolsTable!: azdata.TableComponent;
|
||||
private _resourceTagsListView!: azdata.ListViewComponent;
|
||||
private _resourceSearchBox!: azdata.InputBoxComponent;
|
||||
private _cardGroup!: azdata.RadioCardGroupComponent;
|
||||
private _optionDropDownMap: Map<string, azdata.DropDownComponent> = new Map();
|
||||
private _toolsLoadingComponent!: azdata.LoadingComponent;
|
||||
@@ -31,13 +35,16 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
private _installationInProgress: boolean = false;
|
||||
private _tools: ITool[] = [];
|
||||
private _eulaValidationSucceeded: boolean = false;
|
||||
// array to store listners that are specific to the selected resource. To be cleared after change in selected resource.
|
||||
private _currentResourceTypeDisposables: vscode.Disposable[] = [];
|
||||
private _cardsCache: Map<string, azdata.RadioCard> = new Map();
|
||||
|
||||
constructor(
|
||||
private toolsService: IToolsService,
|
||||
private resourceTypeService: IResourceTypeService,
|
||||
defaultResourceType: ResourceType,
|
||||
private _resourceTypeNameFilters?: string[]) {
|
||||
super(localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
|
||||
super(loc.resourceTypePickerDialogTitle, 'ResourceTypePickerDialog', true);
|
||||
this._selectedResourceType = defaultResourceType;
|
||||
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
|
||||
this._toDispose.push(this._installToolButton.onClick(() => {
|
||||
@@ -68,44 +75,29 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
const tableWidth = 1126;
|
||||
this._view = view;
|
||||
const resourceTypes = this.resourceTypeService
|
||||
this._resourceTypes = this.resourceTypeService
|
||||
.getResourceTypes()
|
||||
.filter(rt => !this._resourceTypeNameFilters || this._resourceTypeNameFilters.find(rtn => rt.name === rtn))
|
||||
.sort((a: ResourceType, b: ResourceType) => {
|
||||
return (a.displayIndex || Number.MAX_VALUE) - (b.displayIndex || Number.MAX_VALUE);
|
||||
});
|
||||
this._cardGroup = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
||||
cards: resourceTypes.map((resourceType) => {
|
||||
return <azdata.RadioCard>{
|
||||
id: resourceType.name,
|
||||
label: resourceType.displayName,
|
||||
icon: resourceType.icon,
|
||||
descriptions: [
|
||||
{
|
||||
textValue: resourceType.displayName,
|
||||
textStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold'
|
||||
}
|
||||
},
|
||||
{
|
||||
textValue: resourceType.description,
|
||||
}
|
||||
]
|
||||
};
|
||||
cards: this._resourceTypes.map((resourceType) => {
|
||||
return this.createOrGetCard(resourceType);
|
||||
}),
|
||||
iconHeight: '35px',
|
||||
iconWidth: '35px',
|
||||
cardWidth: '300px',
|
||||
cardHeight: '150px',
|
||||
ariaLabel: localize('deploymentDialog.deploymentOptions', "Deployment options"),
|
||||
width: '1100px',
|
||||
width: '1000px',
|
||||
height: '550px',
|
||||
iconPosition: 'left'
|
||||
}).component();
|
||||
this._toDispose.push(this._cardGroup.onSelectionChanged(({ cardId }) => {
|
||||
this._dialogObject.message = { text: '' };
|
||||
this._dialogObject.okButton.label = loc.select;
|
||||
const resourceType = resourceTypes.find(rt => { return rt.name === cardId; });
|
||||
const resourceType = this._resourceTypes.find(rt => { return rt.name === cardId; });
|
||||
if (resourceType) {
|
||||
this.selectResourceType(resourceType);
|
||||
}
|
||||
@@ -150,10 +142,35 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
loadingText: localize('deploymentDialog.loadingRequiredTools', "Loading required tools information"),
|
||||
showText: true
|
||||
}).component();
|
||||
|
||||
const resourceComponents: azdata.Component[] = [];
|
||||
if (this.getAllResourceTags().length !== 0) {
|
||||
this._resourceTagsListView = this.createTagsListView();
|
||||
resourceComponents.push(this._resourceTagsListView);
|
||||
}
|
||||
this._resourceSearchBox = view.modelBuilder.inputBox().withProperties({
|
||||
placeHolder: loc.resourceTypeSearchBoxDescription,
|
||||
ariaLabel: loc.resourceTypeSearchBoxDescription
|
||||
}).component();
|
||||
this._toDispose.push(this._resourceSearchBox.onTextChanged((value: string) => {
|
||||
this.filterResources();
|
||||
this._resourceSearchBox.focus();
|
||||
}));
|
||||
const searchContainer = view.modelBuilder.divContainer().withItems([this._resourceSearchBox]).withProps({
|
||||
CSSStyles: {
|
||||
'margin-left': '15px',
|
||||
'width': '300px'
|
||||
},
|
||||
}).component();
|
||||
const cardsContainer = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).withItems([searchContainer, this._cardGroup]).component();
|
||||
resourceComponents.push(cardsContainer);
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this._cardGroup,
|
||||
component: this._view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).withItems(resourceComponents).component(),
|
||||
title: ''
|
||||
}, {
|
||||
component: this._agreementContainer,
|
||||
@@ -175,18 +192,89 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
|
||||
return view.initializeModel(form).then(() => {
|
||||
this.selectResourceType(this._resourceTypes[0]);
|
||||
if (this._selectedResourceType) {
|
||||
this._cardGroup.selectedCardId = this._selectedResourceType.name;
|
||||
}
|
||||
this._resourceTagsListView.focus();
|
||||
});
|
||||
});
|
||||
this._dialogObject.content = [tab];
|
||||
|
||||
}
|
||||
|
||||
private createTagsListView(): azdata.ListViewComponent {
|
||||
|
||||
const tags = this.getAllResourceTags();
|
||||
if (!tags.includes('All')) {
|
||||
tags.splice(0, 0, 'All');
|
||||
}
|
||||
const items: azdata.ListViewOption[] = [];
|
||||
tags.forEach((t: string, idx: number) => {
|
||||
items.push({
|
||||
label: loc.getResourceTypeCategoryLocalizedString(t),
|
||||
id: t
|
||||
});
|
||||
});
|
||||
const listView = this._view.modelBuilder.listView().withProps({
|
||||
title: {
|
||||
text: loc.resoucrceTypeCategoryListViewTitle
|
||||
},
|
||||
CSSStyles: {
|
||||
'width': '140px',
|
||||
'margin-top': '35px'
|
||||
},
|
||||
options: items,
|
||||
selectedOptionId: items[0].id
|
||||
}).component();
|
||||
this._toDispose.push(listView.onDidClick((e) => {
|
||||
this._resourceSearchBox.value = '';
|
||||
this.filterResources();
|
||||
listView.focus();
|
||||
}));
|
||||
|
||||
return listView;
|
||||
}
|
||||
|
||||
private filterResources(): void {
|
||||
const tag = this._resourceTagsListView.selectedOptionId!;
|
||||
const search = this._resourceSearchBox.value?.toLowerCase() ?? '';
|
||||
|
||||
// Getting resourceType based on the selected tag
|
||||
let filteredResourceTypes = (tag !== 'All') ? this._resourceTypes.filter(element => element.tags?.includes(tag) ?? false) : this._resourceTypes;
|
||||
|
||||
// Filtering resourceTypes based on their names.
|
||||
const filteredResourceTypesOnSearch: ResourceType[] = filteredResourceTypes.filter((element) => element.displayName.toLowerCase().includes(search!));
|
||||
// Adding resourceTypes with descriptions matching the search text to the result at the end as they might be less relevant.
|
||||
filteredResourceTypesOnSearch.push(...filteredResourceTypes.filter((element) => !element.displayName.toLowerCase().includes(search!) && element.description.toLowerCase().includes(search!)));
|
||||
|
||||
const cards = filteredResourceTypesOnSearch.map((resourceType) => this.createOrGetCard(resourceType));
|
||||
|
||||
if (filteredResourceTypesOnSearch.length > 0) {
|
||||
this._cardGroup.updateProperties({
|
||||
selectedCardId: cards[0].id,
|
||||
cards: cards
|
||||
});
|
||||
|
||||
this.selectResourceType(filteredResourceTypesOnSearch[0]);
|
||||
}
|
||||
else {
|
||||
this._cardGroup.updateProperties({
|
||||
selectedCardId: '',
|
||||
cards: []
|
||||
});
|
||||
this._agreementCheckboxChecked = false;
|
||||
this._agreementContainer.clearItems();
|
||||
this._optionsContainer.clearItems();
|
||||
this.updateToolsDisplayTable();
|
||||
}
|
||||
}
|
||||
|
||||
private selectResourceType(resourceType: ResourceType): void {
|
||||
this._currentResourceTypeDisposables.forEach(disposable => disposable.dispose());
|
||||
this._selectedResourceType = resourceType;
|
||||
//handle special case when resource type has different OK button.
|
||||
this._dialogObject.okButton.label = this._selectedResourceType.okButtonText || select;
|
||||
this._dialogObject.okButton.label = this._selectedResourceType.okButtonText || loc.select;
|
||||
|
||||
this._agreementCheckboxChecked = false;
|
||||
this._agreementContainer.clearItems();
|
||||
@@ -210,7 +298,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
ariaLabel: option.displayName
|
||||
}).component();
|
||||
|
||||
this._toDispose.push(optionSelectBox.onValueChanged(() => { this.updateToolsDisplayTable(); }));
|
||||
this._currentResourceTypeDisposables.push(optionSelectBox.onValueChanged(() => { this.updateToolsDisplayTable(); }));
|
||||
this._optionDropDownMap.set(option.name, optionSelectBox);
|
||||
const row = this._view.modelBuilder.flexContainer().withItems([optionLabel, optionSelectBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
this._optionsContainer.addItem(row);
|
||||
@@ -262,7 +350,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
this._toolsTable.data = this.toolRequirements.map(toolRequirement => {
|
||||
const tool = this.toolsService.getToolByName(toolRequirement.name)!;
|
||||
// subscribe to onUpdateData event of the tool.
|
||||
this._toDispose.push(tool.onDidUpdateData((t: ITool) => {
|
||||
this._currentResourceTypeDisposables.push(tool.onDidUpdateData((t: ITool) => {
|
||||
this.updateToolsDisplayTableData(t);
|
||||
}));
|
||||
|
||||
@@ -361,7 +449,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
required: true
|
||||
}).component();
|
||||
checkbox.checked = false;
|
||||
this._toDispose.push(checkbox.onChanged(() => {
|
||||
this._currentResourceTypeDisposables.push(checkbox.onChanged(() => {
|
||||
this._agreementCheckboxChecked = !!checkbox.checked;
|
||||
}));
|
||||
const text = this._view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
@@ -476,4 +564,49 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
this._installationInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private getAllResourceTags(): string[] {
|
||||
const supportedTags = [
|
||||
constants.ResourceTypeCategories.All,
|
||||
constants.ResourceTypeCategories.OnPrem,
|
||||
constants.ResourceTypeCategories.Hybrid,
|
||||
constants.ResourceTypeCategories.Cloud,
|
||||
constants.ResourceTypeCategories.SqlServer,
|
||||
constants.ResourceTypeCategories.PostgreSql
|
||||
];
|
||||
|
||||
const tagsWithResourceTypes = supportedTags.filter(tag => {
|
||||
return (tag === constants.ResourceTypeCategories.All) || this._resourceTypes.find(resourceType => resourceType.tags?.includes(tag)) !== undefined;
|
||||
});
|
||||
|
||||
return tagsWithResourceTypes;
|
||||
}
|
||||
|
||||
private createOrGetCard(resourceType: ResourceType): azdata.RadioCard {
|
||||
if (this._cardsCache.has(resourceType.name)) {
|
||||
return this._cardsCache.get(resourceType.name)!;
|
||||
}
|
||||
|
||||
const newCard = <azdata.RadioCard>{
|
||||
id: resourceType.name,
|
||||
label: resourceType.displayName,
|
||||
icon: resourceType.icon,
|
||||
descriptions: [
|
||||
{
|
||||
textValue: resourceType.displayName,
|
||||
textStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold'
|
||||
}
|
||||
},
|
||||
{
|
||||
textValue: resourceType.description,
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this._cardsCache.set(resourceType.name, newCard);
|
||||
return newCard;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user