From cae598d36c04f6213d0a9f5cb7bc551bb28b7279 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Thu, 17 Jun 2021 10:28:12 -0700 Subject: [PATCH] Correctly announce loading state for arc cluster context loading (#15747) * Correctly announce loading state for arc cluster context loading * localize * Fix compile * Fix test --- extensions/arc/src/localizedConstants.ts | 4 +++- .../arc/src/test/ui/components/filePicker.test.ts | 2 +- .../test/ui/components/radioOptionsGroup.test.ts | 6 +++--- extensions/arc/src/ui/components/filePicker.ts | 14 ++++++++++++-- .../arc/src/ui/components/radioOptionsGroup.ts | 13 ++++++++++--- .../arc/src/ui/dialogs/connectControllerDialog.ts | 5 +++-- 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts index fcb32957c5..744a0806b5 100644 --- a/extensions/arc/src/localizedConstants.ts +++ b/extensions/arc/src/localizedConstants.ts @@ -191,7 +191,7 @@ export const noPodIssuesDetected = localize('arc.noPodIssuesDetected', "There ar export const podIssuesDetected = localize('arc.podIssuesDetected', "The pods listed below are experiencing issues that may affect performance or availability."); export const containerReady = localize('arc.containerReady', "Pod containers are ready."); export const podScheduled = localize('arc.podScheduled', "Pod is schedulable."); - +export const loadingClusterContextCompleted = localize('arc.loadingClusterContextCompleted', "Loading cluster contexts completed"); export function rangeSetting(min: string, max: string): string { return localize('arc.rangeSetting', "Value is expected to be in the range {0} - {1}", min, max); } export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); } export function deletingInstance(name: string): string { return localize('arc.deletingInstance', "Deleting instance '{0}'...", name); } @@ -262,3 +262,5 @@ export const noCurrentContextFound = (configFile: string) => localize('noCurrent export const noNameInContext = (configFile: string) => localize('noNameInContext', "No name field was found in a cluster context in the config file: {0}", configFile); export const userCancelledError = localize('arc.userCancelledError', "User cancelled the dialog"); export const clusterContextConfigNoLongerValid = (configFile: string, clusterContext: string, error: any) => localize('clusterContextConfigNoLongerValid', "The cluster context information specified by config file: {0} and cluster context: {1} is no longer valid. Error is:\n\t{2}\n Do you want to update this information?", configFile, clusterContext, getErrorMessage(error)); +export const invalidConfigPath = localize('arc.invalidConfigPath', "Invalid config path"); +export const loadingClusterContextsError = (error: any): string => localize('arc.loadingClusterContextsError', "Error loading cluster contexts. {0}", getErrorMessage(error)); diff --git a/extensions/arc/src/test/ui/components/filePicker.test.ts b/extensions/arc/src/test/ui/components/filePicker.test.ts index 7810c5452a..141a64259c 100644 --- a/extensions/arc/src/test/ui/components/filePicker.test.ts +++ b/extensions/arc/src/test/ui/components/filePicker.test.ts @@ -18,7 +18,7 @@ const newFileUri = vscode.Uri.file(path.join('path', 'to', 'new', '.kube', 'conf describe('filePicker', function (): void { beforeEach(async () => { const { modelBuilderMock } = createModelViewMock(); - filePicker = new FilePicker(modelBuilderMock.object, initialPath, (_disposable) => { }, ''); + filePicker = new FilePicker(modelBuilderMock.object, initialPath, (_disposable) => { }, '', ''); }); afterEach(() => { diff --git a/extensions/arc/src/test/ui/components/radioOptionsGroup.test.ts b/extensions/arc/src/test/ui/components/radioOptionsGroup.test.ts index a45473f2c2..69297eeae1 100644 --- a/extensions/arc/src/test/ui/components/radioOptionsGroup.test.ts +++ b/extensions/arc/src/test/ui/components/radioOptionsGroup.test.ts @@ -5,10 +5,10 @@ import * as azdata from 'azdata'; import * as should from 'should'; -import { getErrorMessage } from '../../../common/utils'; import { RadioOptionsGroup, RadioOptionsInfo } from '../../../ui/components/radioOptionsGroup'; import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock'; import { StubRadioButton } from '@microsoft/azdata-test/out/stubs/modelView/stubRadioButton'; +import * as loc from '../../../localizedConstants'; const loadingError = new Error('Error loading options'); @@ -25,7 +25,7 @@ let radioOptionsGroup: RadioOptionsGroup; describe('radioOptionsGroup', function (): void { beforeEach(async () => { const { modelBuilderMock } = createModelViewMock(); - radioOptionsGroup = new RadioOptionsGroup(modelBuilderMock.object, (_disposable) => { }); + radioOptionsGroup = new RadioOptionsGroup(modelBuilderMock.object, (_disposable) => { }, undefined, '', () => ''); await radioOptionsGroup.load(async () => radioOptionsInfo); }); @@ -51,7 +51,7 @@ describe('radioOptionsGroup', function (): void { radioOptionsGroup.items.length.should.equal(1, 'There is should be only one element in the divContainer when loading error happens'); const label = radioOptionsGroup.items[0] as azdata.TextComponent; should(label.value).not.be.undefined(); - label.value!.should.deepEqual(getErrorMessage(loadingError)); + label.value!.should.deepEqual(loc.loadingClusterContextsError(loadingError)); should(label.CSSStyles).not.be.undefined(); should(label.CSSStyles!.color).not.be.undefined(); label.CSSStyles!.color.should.equal('Red'); diff --git a/extensions/arc/src/ui/components/filePicker.ts b/extensions/arc/src/ui/components/filePicker.ts index 9a5610d1b5..5a424be40e 100644 --- a/extensions/arc/src/ui/components/filePicker.ts +++ b/extensions/arc/src/ui/components/filePicker.ts @@ -6,7 +6,7 @@ import * as azdata from 'azdata'; import * as path from 'path'; import * as vscode from 'vscode'; import * as loc from '../../localizedConstants'; - +import { promises as fs } from 'fs'; export interface RadioOptionsInfo { values?: string[], defaultValue: string @@ -20,14 +20,24 @@ export class FilePicker { modelBuilder: azdata.ModelBuilder, initialPath: string, onNewDisposableCreated: (disposable: vscode.Disposable) => void, - ariaLabel: string + ariaLabel: string, + validationErrorMessage: string ) { const buttonWidth = 80; this.filePathInputBox = modelBuilder.inputBox() .withProperties({ value: initialPath, ariaLabel: ariaLabel, + validationErrorMessage: validationErrorMessage, width: 350 + }).withValidation(async () => { + try { + await fs.stat(this.filePathInputBox.value || ''); + } catch (err) { + console.log('Error checking config path ', err); + return false; + } + return true; }).component(); this.filePickerButton = modelBuilder.button() diff --git a/extensions/arc/src/ui/components/radioOptionsGroup.ts b/extensions/arc/src/ui/components/radioOptionsGroup.ts index 6f380329e2..a6b157415b 100644 --- a/extensions/arc/src/ui/components/radioOptionsGroup.ts +++ b/extensions/arc/src/ui/components/radioOptionsGroup.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { getErrorMessage } from '../../common/utils'; +import * as loc from '../../localizedConstants'; export interface RadioOptionsInfo { values?: string[], @@ -20,7 +20,12 @@ export class RadioOptionsGroup { private _onRadioOptionChanged: vscode.EventEmitter = new vscode.EventEmitter(); public onRadioOptionChanged: vscode.Event = this._onRadioOptionChanged.event; - constructor(private _modelBuilder: azdata.ModelBuilder, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`) { + constructor(private _modelBuilder: azdata.ModelBuilder, + private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, + private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`, + private _loadingCompleteMessage: string, + private _loadingCompleteErrorMessage: (error: any) => string + ) { this._divContainer = this._modelBuilder.divContainer().withProperties({ clickable: false }).component(); this._loadingBuilder = this._modelBuilder.loadingComponent().withItem(this._divContainer); } @@ -59,10 +64,12 @@ export class RadioOptionsGroup { })); this._divContainer.addItem(radioOption); }); + this.component().loadingCompletedText = this._loadingCompleteMessage; } catch (e) { - const errorLabel = this._modelBuilder.text().withProperties({ value: getErrorMessage(e), CSSStyles: { 'color': 'Red' } }).component(); + const errorLabel = this._modelBuilder.text().withProperties({ value: loc.loadingClusterContextsError(e), CSSStyles: { 'color': 'Red' } }).component(); this._divContainer.addItem(errorLabel); + this.component().loadingCompletedText = this._loadingCompleteErrorMessage(e); } this.component().loading = false; } diff --git a/extensions/arc/src/ui/dialogs/connectControllerDialog.ts b/extensions/arc/src/ui/dialogs/connectControllerDialog.ts index 0575737c17..45b17ec122 100644 --- a/extensions/arc/src/ui/dialogs/connectControllerDialog.ts +++ b/extensions/arc/src/ui/dialogs/connectControllerDialog.ts @@ -97,13 +97,14 @@ abstract class ControllerDialogBase extends InitializingComponent { this.modelBuilder, controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath(), (disposable) => this._toDispose.push(disposable), - loc.controllerKubeConfig + loc.controllerKubeConfig, + loc.invalidConfigPath ); this.modelBuilder.inputBox() .withProps({ value: controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath() }).component(); - this.clusterContextRadioGroup = new RadioOptionsGroup(this.modelBuilder, (disposable) => this._toDispose.push(disposable)); + this.clusterContextRadioGroup = new RadioOptionsGroup(this.modelBuilder, (disposable) => this._toDispose.push(disposable), undefined, loc.loadingClusterContextCompleted, loc.loadingClusterContextsError); this.loadRadioGroup(controllerInfo?.kubeClusterContext); this._toDispose.push(this.clusterContextRadioGroup.onRadioOptionChanged(newContext => this.updateNamespace(newContext))); this._toDispose.push(this.kubeConfigInputBox.onTextChanged(() => this.loadRadioGroup(controllerInfo?.kubeClusterContext)));