add severity support for issues (#18761)

* add severity support for issues

* vbump STS

* pr comments
This commit is contained in:
Alan Ren
2022-03-17 14:09:02 -07:00
committed by GitHub
parent aeb4e87c1f
commit 9f2940e8f8
8 changed files with 195 additions and 160 deletions

View File

@@ -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 {

View 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';
}
}

View File

@@ -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';
}
}

View File

@@ -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 {

View File

@@ -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 {