[Port] Improved behavior for accepting EULA. (#12453) (#12749)

* Improved behavior for accepting EULA. (#12453)

* working version of overloading "select" button

* promptForEula to use showErrorMessage

* make parameter optional in promptForEula

* remove test code

* PR feedback

* eula to EULA

* minor fix

* Fix compile error

Co-authored-by: Arvind Ranasaria <ranasaria@outlook.com>
This commit is contained in:
Charles Gagnon
2020-10-05 18:52:16 -07:00
committed by GitHub
parent 4c6b606c82
commit 867faae14f
8 changed files with 81 additions and 34 deletions

View File

@@ -377,14 +377,14 @@ async function promptToUpdateAzdata(newVersion: string, userRequested: boolean =
}
/**
* Prompts user to accept EULA it if was not previously accepted. Stores and returns the user response to EULA prompt.
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
* @param memento - memento where the user response is stored.
* @param userRequested - if true this operation was requested in response to a user issued command, if false it was issued at startup by system
* @param requireUserAction - if the prompt is required to be acted upon by the user. This is typically 'true' when this method is called to address an Error when the EULA needs to be accepted to proceed.
* pre-requisite, the calling code has to ensure that the eula has not yet been previously accepted by the user.
* returns true if the user accepted the EULA.
*/
export async function promptForEula(memento: vscode.Memento, userRequested: boolean = false): Promise<boolean> {
export async function promptForEula(memento: vscode.Memento, userRequested: boolean = false, requireUserAction: boolean = false): Promise<boolean> {
let response: string | undefined = loc.no;
const config = <AzdataDeployOption>getConfig(azdataAcceptEulaKey);
if (userRequested) {
@@ -397,7 +397,9 @@ export async function promptForEula(memento: vscode.Memento, userRequested: bool
if (config === AzdataDeployOption.prompt || userRequested) {
Logger.show();
Logger.log(loc.promptForEulaLog(microsoftPrivacyStatementUrl, eulaUrl));
response = await vscode.window.showInformationMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), ...responses);
response = requireUserAction
? await vscode.window.showErrorMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), ...responses)
: await vscode.window.showInformationMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), ...responses);
Logger.log(loc.userResponseToEulaPrompt(response));
}
if (response === loc.doNotAskAgain) {

View File

@@ -59,6 +59,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
return {
isEulaAccepted: () => !!context.globalState.get<boolean>(constants.eulaAccepted),
promptForEula: (onError: boolean = true): Promise<boolean> => promptForEula(context.globalState, true /* userRequested */, onError),
azdata: {
arc: {
dc: {

View File

@@ -240,6 +240,19 @@ declare module 'azdata-ext' {
export interface IExtension {
azdata: IAzdataApi;
/**
* returns true if AZDATA CLI EULA has been previously accepted by the user.
*/
isEulaAccepted(): boolean;
/**
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
* @param requireUserAction - if the prompt is required to be acted upon by the user. This is typically 'true' when this method is called to address an Error when the EULA needs to be accepted to proceed.
*
* pre-requisite, the calling code has to ensure that the EULA has not yet been previously accepted by the user. The code can use @see isEulaAccepted() call to ascertain this.
* returns true if the user accepted the EULA.
*/
promptForEula(requireUserAction?: boolean): Promise<boolean>
}
}

View File

@@ -374,7 +374,8 @@ export interface ITool {
finishInitialization(): Promise<void>;
install(): Promise<void>;
isSameOrNewerThan(version: string): boolean;
validateEula(): boolean;
isEulaAccepted(): boolean;
promptForEula(): Promise<boolean>;
}
export const enum BdcDeploymentType {

View File

@@ -15,7 +15,7 @@ export const subscriptionDescription = localize('azure.account.subscriptionDescr
export const resourceGroup = localize('azure.account.resourceGroup', "Resource Group");
export const location = localize('azure.account.location', "Azure Location");
export const browse = localize('filePicker.browse', "Browse");
export const select = localize('filePicker.select', "Select");
export const select = localize('button.label', "Select");
export const kubeConfigFilePath = localize('kubeConfigClusterPicker.kubeConfigFilePath', "Kube config file path");
export const clusterContextNotFound = localize('kubeConfigClusterPicker.clusterContextNotFound', "No cluster context information found");
export const signIn = localize('azure.signin', "Sign in…");
@@ -35,4 +35,6 @@ export const optionsNotDefined = (fieldType: FieldType) => localize('optionsNotD
export const optionsNotObjectOrArray = localize('optionsNotObjectOrArray', "FieldInfo.options must be an object if it is not an array");
export const optionsTypeNotFound = localize('optionsTypeNotFound', "When FieldInfo.options is an object it must have 'optionsType' property");
export const optionsTypeRadioOrDropdown = localize('optionsTypeRadioOrDropdown', "When optionsType is not {0} then it must be {1}", OptionsType.Radio, OptionsType.Dropdown);
export const azdataEulaNotAccepted = localize('azdataEulaNotAccepted', "Deployment cannot continue. Azure Data CLI license terms have not been accepted. Execute the command: [Azure Data CLI: Accept EULA] to accept EULA to enable the features that requires Azure Data CLI.");
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");

View File

@@ -45,7 +45,7 @@ export class AzdataTool extends ToolBase {
return 'https://docs.microsoft.com/sql/big-data-cluster/deploy-install-azdata';
}
public validateEula(): boolean {
public isEulaAccepted(): boolean {
if (apiService.azdataApi.isEulaAccepted()) {
return true;
} else {
@@ -54,6 +54,14 @@ export class AzdataTool extends ToolBase {
}
}
public async promptForEula(): Promise<boolean> {
const eulaAccepted = await apiService.azdataApi.promptForEula();
if (!eulaAccepted) {
this.setStatusDescription(loc.azdataEulaDeclined);
}
return eulaAccepted;
}
/* unused */
protected get versionCommand(): Command {
return {

View File

@@ -58,7 +58,9 @@ export abstract class ToolBase implements ITool {
protected abstract readonly versionCommand: Command;
public validateEula(): boolean { return true; }
public isEulaAccepted(): boolean { return true; }
public promptForEula(): Promise<boolean> { return Promise.resolve(true); }
public get dependencyMessages(): string[] {
return (this.dependenciesByOsType.get(this.osDistribution) || []).map((msgType: dependencyType) => messageByDependencyType.get(msgType)!);

View File

@@ -9,6 +9,7 @@ import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } fr
import { IResourceTypeService } from '../services/resourceTypeService';
import { IToolsService } from '../services/toolsService';
import { getErrorMessage } from '../utils';
import * as loc from './../localizedConstants';
import { DialogBase } from './dialogBase';
import { createFlexContainer } from './modelViewUtils';
@@ -26,9 +27,9 @@ export class ResourceTypePickerDialog extends DialogBase {
private _agreementContainer!: azdata.DivContainer;
private _agreementCheckboxChecked: boolean = false;
private _installToolButton: azdata.window.Button;
private _recheckEulaButton: azdata.window.Button;
private _installationInProgress: boolean = false;
private _tools: ITool[] = [];
private _eulaValidationSucceeded: boolean = false;
constructor(
private toolsService: IToolsService,
@@ -38,32 +39,30 @@ export class ResourceTypePickerDialog extends DialogBase {
super(localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
this._selectedResourceType = defaultResourceType;
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
this._recheckEulaButton = azdata.window.createButton(localize('deploymentDialog.RecheckEulaButton', "Validate EULA"));
this._recheckEulaButton.hidden = true;
this._toDispose.push(this._installToolButton.onClick(() => {
this.installTools().catch(error => console.log(error));
}));
this._toDispose.push(this._recheckEulaButton.onClick(() => {
this._dialogObject.message = { text: '' }; // clear any previous message.
this._dialogObject.okButton.enabled = this.validateToolsEula(); // re-enable the okButton if validation succeeds.
}));
this._dialogObject.customButtons = [this._installToolButton, this._recheckEulaButton];
this._dialogObject.customButtons = [this._installToolButton];
this._installToolButton.hidden = true;
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', "Select");
this._dialogObject.okButton.label = loc.select;
this._dialogObject.okButton.enabled = false; // this is enabled after all tools are discovered.
}
initialize() {
let tab = azdata.window.createTab('');
this._dialogObject.registerCloseValidator(() => {
this._dialogObject.registerCloseValidator(async () => {
const isValid = this._selectedResourceType && (this._selectedResourceType.agreement === undefined || this._agreementCheckboxChecked);
if (!isValid) {
this._dialogObject.message = {
text: localize('deploymentDialog.AcceptAgreements', "You must agree to the license agreements in order to proceed."),
level: azdata.window.MessageLevel.Error
};
return false;
}
return isValid;
if (!this._eulaValidationSucceeded && !(await this.acquireEulaAndProceed())) {
return false; // we return false so that the workflow does not proceed and user gets to either click acceptEulaAndSelect again or cancel
}
return true;
});
tab.registerContent((view: azdata.ModelView) => {
const tableWidth = 1126;
@@ -103,8 +102,8 @@ export class ResourceTypePickerDialog extends DialogBase {
iconPosition: 'left'
}).component();
this._toDispose.push(this._cardGroup.onSelectionChanged(({ cardId }) => {
this._recheckEulaButton.hidden = true;
this._dialogObject.okButton.enabled = true;
this._dialogObject.message = { text: '' };
this._dialogObject.okButton.label = loc.select;
const resourceType = resourceTypes.find(rt => { return rt.name === cardId; });
if (resourceType) {
this.selectResourceType(resourceType);
@@ -114,7 +113,7 @@ export class ResourceTypePickerDialog extends DialogBase {
this._agreementContainer = view.modelBuilder.divContainer().component();
const toolColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolNameColumnHeader', "Tool"),
width: 80
width: 105
};
const descriptionColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolDescriptionColumnHeader', "Description"),
@@ -130,7 +129,7 @@ export class ResourceTypePickerDialog extends DialogBase {
};
const minVersionColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolMinimumVersionColumnHeader', "Required Version"),
width: 95
width: 105
};
const installedPathColumn: azdata.TableColumn = {
value: localize('deploymentDialog.toolDiscoveredPathColumnHeader', "Discovered Path or Additional Information"),
@@ -288,7 +287,7 @@ export class ResourceTypePickerDialog extends DialogBase {
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || ''];
});
this._installToolButton.hidden = erroredOrFailedTool || minVersionCheckFailed || (toolsToAutoInstall.length === 0);
this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0) && this.validateToolsEula();
this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
if (messages.length !== 0) {
if (messages.length > 1) {
messages = messages.map(message => `${message}`);
@@ -320,22 +319,41 @@ export class ResourceTypePickerDialog extends DialogBase {
text: infoText.join(EOL)
};
}
if (!this.areToolsEulaAccepted()) {
this._dialogObject.okButton.label = loc.acceptEulaAndSelect;
}
this._toolsLoadingComponent.loading = false;
}
private validateToolsEula(): boolean {
const validationSucceeded = this._tools.every(tool => {
const eulaValidated = tool.validateEula();
if (!eulaValidated) {
private areToolsEulaAccepted(): boolean {
// we run 'map' on each tool before doing 'every' so that we collect eula messages for all tools (instead of bailing out after 1st failure)
this._eulaValidationSucceeded = this._tools.map(tool => {
const eulaAccepted = tool.isEulaAccepted();
if (!eulaAccepted) {
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: tool.statusDescription!
text: [tool.statusDescription!, this._dialogObject.message.text].join(EOL)
};
}
return eulaValidated;
});
this._recheckEulaButton.hidden = validationSucceeded;
return validationSucceeded;
return eulaAccepted;
}).every(isEulaAccepted => isEulaAccepted);
return this._eulaValidationSucceeded;
}
private async acquireEulaAndProceed(): Promise<boolean> {
this._dialogObject.message = { text: '' };
let eulaAccepted = true;
for (const tool of this._tools) {
eulaAccepted = tool.isEulaAccepted() || await tool.promptForEula();
if (!eulaAccepted) {
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: [tool.statusDescription!, this._dialogObject.message.text].join(EOL)
};
break;
}
}
return eulaAccepted;
}
private get toolRequirements() {