mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-26 01:25:38 -05:00
add severity support for issues (#18761)
* add severity support for issues * vbump STS * pr comments
This commit is contained in:
@@ -36,7 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { DesignerMessagesTabPanelView } from 'sql/workbench/browser/designer/designerMessagesTabPanelView';
|
||||
import { DesignerIssuesTabPanelView } from 'sql/workbench/browser/designer/designerIssuesTabPanelView';
|
||||
import { DesignerScriptEditorTabPanelView } from 'sql/workbench/browser/designer/designerScriptEditorTabPanelView';
|
||||
import { DesignerPropertyPathValidator } from 'sql/workbench/browser/designer/designerPropertyPathValidator';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -70,7 +70,7 @@ interface DesignerTableCellContext {
|
||||
}
|
||||
|
||||
const ScriptTabId = 'scripts';
|
||||
const MessagesTabId = 'messages';
|
||||
const IssuesTabId = 'issues';
|
||||
|
||||
export class Designer extends Disposable implements IThemable {
|
||||
private _loadingSpinner: LoadingSpinner;
|
||||
@@ -95,7 +95,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
private _inputDisposable: DisposableStore;
|
||||
private _loadingTimeoutHandle: any;
|
||||
private _groupHeaders: HTMLElement[] = [];
|
||||
private _messagesView: DesignerMessagesTabPanelView;
|
||||
private _issuesView: DesignerIssuesTabPanelView;
|
||||
private _scriptEditorView: DesignerScriptEditorTabPanelView;
|
||||
private _onStyleChangeEventEmitter = new Emitter<void>();
|
||||
|
||||
@@ -152,8 +152,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
this._scriptTabbedPannel = new TabbedPanel(this._editorContainer);
|
||||
this._messagesView = this._instantiationService.createInstance(DesignerMessagesTabPanelView);
|
||||
this._register(this._messagesView.onMessageSelected((path) => {
|
||||
this._issuesView = this._instantiationService.createInstance(DesignerIssuesTabPanelView);
|
||||
this._register(this._issuesView.onIssueSelected((path) => {
|
||||
if (path && path.length > 0) {
|
||||
this.selectProperty(path);
|
||||
}
|
||||
@@ -332,14 +332,14 @@ export class Designer extends Disposable implements IThemable {
|
||||
private handleEditProcessedEvent(args: DesignerEditProcessedEventArgs): void {
|
||||
const edit = args.edit;
|
||||
this._supressEditProcessing = true;
|
||||
if (!args.result.isValid) {
|
||||
alert(localize('designer.errorCountAlert', "{0} validation errors found.", args.result.errors.length));
|
||||
if (args.result.issues?.length > 0) {
|
||||
alert(localize('designer.issueCountAlert', "{0} validation issues found.", args.result.issues.length));
|
||||
}
|
||||
try {
|
||||
if (args.result.refreshView) {
|
||||
this.refresh();
|
||||
if (!args.result.isValid) {
|
||||
this._scriptTabbedPannel.showTab(MessagesTabId);
|
||||
this._scriptTabbedPannel.showTab(IssuesTabId);
|
||||
}
|
||||
} else {
|
||||
this.updateComponentValues();
|
||||
@@ -466,7 +466,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
private updateComponentValues(): void {
|
||||
this.updateMessagesTab();
|
||||
this.updateIssuesTab();
|
||||
const viewModel = this._input.viewModel;
|
||||
const scriptProperty = viewModel[ScriptProperty] as InputBoxProperties;
|
||||
if (scriptProperty) {
|
||||
@@ -477,23 +477,24 @@ export class Designer extends Disposable implements IThemable {
|
||||
});
|
||||
}
|
||||
|
||||
private updateMessagesTab(): void {
|
||||
private updateIssuesTab(): void {
|
||||
if (!this._input) {
|
||||
return;
|
||||
}
|
||||
if (this._scriptTabbedPannel.contains(MessagesTabId)) {
|
||||
this._scriptTabbedPannel.removeTab(MessagesTabId);
|
||||
if (this._scriptTabbedPannel.contains(IssuesTabId)) {
|
||||
this._scriptTabbedPannel.removeTab(IssuesTabId);
|
||||
}
|
||||
if (this._input.validationErrors === undefined || this._input.validationErrors.length === 0) {
|
||||
|
||||
if (this._input.issues === undefined || this._input.issues.length === 0) {
|
||||
return;
|
||||
}
|
||||
this._scriptTabbedPannel.pushTab({
|
||||
title: localize('designer.messagesTabTitle', "Errors ({0})", this._input.validationErrors.length),
|
||||
identifier: MessagesTabId,
|
||||
view: this._messagesView
|
||||
title: localize('designer.issuesTabTitle', "Issues ({0})", this._input.issues.length),
|
||||
identifier: IssuesTabId,
|
||||
view: this._issuesView
|
||||
});
|
||||
this._scriptTabbedPannel.showTab(MessagesTabId);
|
||||
this._messagesView.updateMessages(this._input.validationErrors);
|
||||
this._scriptTabbedPannel.showTab(IssuesTabId);
|
||||
this._issuesView.updateIssues(this._input.issues);
|
||||
}
|
||||
|
||||
private selectProperty(path: DesignerPropertyPath, view?: DesignerUIArea, highlight: boolean = true): void {
|
||||
|
||||
143
src/sql/workbench/browser/designer/designerIssuesTabPanelView.ts
Normal file
143
src/sql/workbench/browser/designer/designerIssuesTabPanelView.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { DesignerPropertyPath, DesignerIssue } from 'sql/workbench/browser/designer/interfaces';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IListAccessibilityProvider, List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export class DesignerIssuesTabPanelView extends Disposable implements IPanelView {
|
||||
private _container: HTMLElement;
|
||||
private _onIssueSelected = new Emitter<DesignerPropertyPath>();
|
||||
private _issueList: List<DesignerIssue>;
|
||||
|
||||
public readonly onIssueSelected: Event<DesignerPropertyPath> = this._onIssueSelected.event;
|
||||
|
||||
constructor(@IThemeService private _themeService: IThemeService) {
|
||||
super();
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
this._container = container.appendChild(DOM.$('.issues-container'));
|
||||
this._issueList = new List<DesignerIssue>('designerIssueList', this._container, new DesignerIssueListDelegate(), [new TableFilterListRenderer()], {
|
||||
multipleSelectionSupport: false,
|
||||
keyboardSupport: true,
|
||||
mouseSupport: true,
|
||||
accessibilityProvider: new DesignerIssueListAccessibilityProvider()
|
||||
});
|
||||
this._register(this._issueList.onDidChangeSelection((e) => {
|
||||
if (e.elements && e.elements.length === 1) {
|
||||
this._onIssueSelected.fire(e.elements[0].propertyPath);
|
||||
}
|
||||
}));
|
||||
this._register(attachListStyler(this._issueList, this._themeService));
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
this._issueList.layout(dimension.height, dimension.width);
|
||||
}
|
||||
|
||||
updateIssues(errors: DesignerIssue[]) {
|
||||
if (this._issueList) {
|
||||
this._issueList.splice(0, this._issueList.length, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const errorForegroundColor = theme.getColor(problemsErrorIconForeground);
|
||||
const warningForegroundColor = theme.getColor(problemsWarningIconForeground);
|
||||
const informationalForegroundColor = theme.getColor(problemsInfoIconForeground);
|
||||
if (errorForegroundColor) {
|
||||
collector.addRule(`
|
||||
.designer-component .issues-container .issue-item .issue-icon.codicon-error {
|
||||
color: ${errorForegroundColor};
|
||||
}
|
||||
.designer-component .issues-container .issue-item .issue-icon.codicon-warning {
|
||||
color: ${warningForegroundColor};
|
||||
}
|
||||
.designer-component .issues-container .issue-item .issue-icon.codicon-info {
|
||||
color: ${informationalForegroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
const DesignerIssueListTemplateId = 'DesignerIssueListTemplate';
|
||||
class DesignerIssueListDelegate implements IListVirtualDelegate<DesignerIssue> {
|
||||
getHeight(element: DesignerIssue): number {
|
||||
return 25;
|
||||
}
|
||||
|
||||
getTemplateId(element: DesignerIssue): string {
|
||||
return DesignerIssueListTemplateId;
|
||||
}
|
||||
}
|
||||
|
||||
interface DesignerIssueListItemTemplate {
|
||||
issueText: HTMLDivElement;
|
||||
issueIcon: HTMLDivElement;
|
||||
}
|
||||
|
||||
class TableFilterListRenderer implements IListRenderer<DesignerIssue, DesignerIssueListItemTemplate> {
|
||||
renderTemplate(container: HTMLElement): DesignerIssueListItemTemplate {
|
||||
const data: DesignerIssueListItemTemplate = Object.create(null);
|
||||
const issueItem = container.appendChild(DOM.$('.issue-item'));
|
||||
data.issueIcon = issueItem.appendChild(DOM.$(''));
|
||||
data.issueText = issueItem.appendChild(DOM.$('.issue-text'));
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(element: DesignerIssue, index: number, templateData: DesignerIssueListItemTemplate, height: number): void {
|
||||
templateData.issueText.innerText = element.description;
|
||||
templateData.issueText.title = element.description;
|
||||
let iconClass;
|
||||
switch (element.severity) {
|
||||
case 'warning':
|
||||
iconClass = Codicon.warning.classNames;
|
||||
break;
|
||||
case 'information':
|
||||
iconClass = Codicon.info.classNames;
|
||||
break;
|
||||
default:
|
||||
iconClass = Codicon.error.classNames;
|
||||
break;
|
||||
}
|
||||
templateData.issueIcon.className = `issue-icon ${iconClass}`;
|
||||
}
|
||||
|
||||
public disposeTemplate(templateData: DesignerIssueListItemTemplate): void {
|
||||
}
|
||||
|
||||
public get templateId(): string {
|
||||
return DesignerIssueListTemplateId;
|
||||
}
|
||||
}
|
||||
|
||||
class DesignerIssueListAccessibilityProvider implements IListAccessibilityProvider<DesignerIssue> {
|
||||
getAriaLabel(element: DesignerIssue): string {
|
||||
return element.description;
|
||||
}
|
||||
|
||||
getWidgetAriaLabel(): string {
|
||||
return localize('designer.IssueListAriaLabel', "Issues");
|
||||
}
|
||||
|
||||
getWidgetRole() {
|
||||
return 'listbox';
|
||||
}
|
||||
|
||||
getRole(element: DesignerIssue): string {
|
||||
return 'option';
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { DesignerPropertyPath, DesignerValidationError } from 'sql/workbench/browser/designer/interfaces';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IListAccessibilityProvider, List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { problemsErrorIconForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export class DesignerMessagesTabPanelView extends Disposable implements IPanelView {
|
||||
private _container: HTMLElement;
|
||||
private _onMessageSelected = new Emitter<DesignerPropertyPath>();
|
||||
private _messageList: List<DesignerValidationError>;
|
||||
|
||||
public readonly onMessageSelected: Event<DesignerPropertyPath> = this._onMessageSelected.event;
|
||||
|
||||
constructor(@IThemeService private _themeService: IThemeService) {
|
||||
super();
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
this._container = container.appendChild(DOM.$('.messages-container'));
|
||||
this._messageList = new List<DesignerValidationError>('designerMessageList', this._container, new DesignerMessageListDelegate(), [new TableFilterListRenderer()], {
|
||||
multipleSelectionSupport: false,
|
||||
keyboardSupport: true,
|
||||
mouseSupport: true,
|
||||
accessibilityProvider: new DesignerMessagesListAccessibilityProvider()
|
||||
});
|
||||
this._register(this._messageList.onDidChangeSelection((e) => {
|
||||
if (e.elements && e.elements.length === 1) {
|
||||
this._onMessageSelected.fire(e.elements[0].propertyPath);
|
||||
}
|
||||
}));
|
||||
this._register(attachListStyler(this._messageList, this._themeService));
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
this._messageList.layout(dimension.height, dimension.width);
|
||||
}
|
||||
|
||||
updateMessages(errors: DesignerValidationError[]) {
|
||||
if (this._messageList) {
|
||||
this._messageList.splice(0, this._messageList.length, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const errorForegroundColor = theme.getColor(problemsErrorIconForeground);
|
||||
if (errorForegroundColor) {
|
||||
collector.addRule(`
|
||||
.designer-component .messages-container .message-item .message-icon {
|
||||
color: ${errorForegroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
const DesignerMessageListTemplateId = 'DesignerMessageListTemplate';
|
||||
class DesignerMessageListDelegate implements IListVirtualDelegate<DesignerValidationError> {
|
||||
getHeight(element: DesignerValidationError): number {
|
||||
return 25;
|
||||
}
|
||||
|
||||
getTemplateId(element: DesignerValidationError): string {
|
||||
return DesignerMessageListTemplateId;
|
||||
}
|
||||
}
|
||||
|
||||
interface DesignerMessageListItemTemplate {
|
||||
messageText: HTMLDivElement;
|
||||
}
|
||||
|
||||
class TableFilterListRenderer implements IListRenderer<DesignerValidationError, DesignerMessageListItemTemplate> {
|
||||
renderTemplate(container: HTMLElement): DesignerMessageListItemTemplate {
|
||||
const data: DesignerMessageListItemTemplate = Object.create(null);
|
||||
const messageItem = container.appendChild(DOM.$('.message-item'));
|
||||
messageItem.appendChild(DOM.$(`.message-icon${Codicon.error.cssSelector}`));
|
||||
data.messageText = messageItem.appendChild(DOM.$('.message-text'));
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(element: DesignerValidationError, index: number, templateData: DesignerMessageListItemTemplate, height: number): void {
|
||||
templateData.messageText.innerText = element.message;
|
||||
}
|
||||
|
||||
disposeElement?(element: DesignerValidationError, index: number, templateData: DesignerMessageListItemTemplate, height: number): void {
|
||||
}
|
||||
|
||||
public disposeTemplate(templateData: DesignerMessageListItemTemplate): void {
|
||||
}
|
||||
|
||||
public get templateId(): string {
|
||||
return DesignerMessageListTemplateId;
|
||||
}
|
||||
}
|
||||
|
||||
class DesignerMessagesListAccessibilityProvider implements IListAccessibilityProvider<DesignerValidationError> {
|
||||
getAriaLabel(element: DesignerValidationError): string {
|
||||
return element.message;
|
||||
}
|
||||
|
||||
getWidgetAriaLabel(): string {
|
||||
return localize('designer.MessageListAriaLabel', "Errors");
|
||||
}
|
||||
|
||||
getWidgetRole() {
|
||||
return 'listbox';
|
||||
}
|
||||
|
||||
getRole(element: DesignerValidationError): string {
|
||||
return 'option';
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,9 @@ export interface DesignerComponentInput {
|
||||
readonly viewModel: DesignerViewModel;
|
||||
|
||||
/**
|
||||
* Gets the validation errors.
|
||||
* Gets the issues.
|
||||
*/
|
||||
readonly validationErrors: DesignerValidationError[] | undefined;
|
||||
readonly issues: DesignerIssue[] | undefined;
|
||||
|
||||
/**
|
||||
* Start initilizing the designer input object.
|
||||
@@ -229,12 +229,13 @@ export type DesignerUIArea = 'PropertiesView' | 'ScriptView' | 'TopContentView'
|
||||
export type DesignerPropertyPath = (string | number)[];
|
||||
export const DesignerRootObjectPath: DesignerPropertyPath = [];
|
||||
|
||||
export type DesignerValidationError = { message: string, propertyPath?: DesignerPropertyPath };
|
||||
export type DesignerIssueSeverity = 'error' | 'warning' | 'information';
|
||||
export type DesignerIssue = { description: string, propertyPath?: DesignerPropertyPath, severity: DesignerIssueSeverity };
|
||||
|
||||
export interface DesignerEditResult {
|
||||
isValid: boolean;
|
||||
refreshView?: boolean;
|
||||
errors?: DesignerValidationError[];
|
||||
issues?: DesignerIssue[];
|
||||
}
|
||||
|
||||
export interface DesignerTextEditor {
|
||||
|
||||
@@ -29,24 +29,27 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.designer-component .messages-container {
|
||||
.designer-component .issues-container {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.designer-component .messages-container .message-item {
|
||||
.designer-component .issues-container .issue-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.designer-component .messages-container .message-item .message-icon {
|
||||
.designer-component .issues-container .issue-item .issue-icon {
|
||||
margin: 0px 6px;
|
||||
flex: 0 0 auto;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.designer-component .messages-container .message-item .message-text {
|
||||
.designer-component .issues-container .issue-item .issue-text {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.designer-component .tabbed-panel-container {
|
||||
|
||||
Reference in New Issue
Block a user