mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-04 17:23:45 -05:00
[Notebook] Run Parameters Action UI Component (#14889)
* Run Parameters Action UI Component * Update UX discussion - accept empty string
This commit is contained in:
@@ -28,7 +28,7 @@ import { INotebookService, INotebookParams, INotebookEditor, INotebookSection, I
|
||||
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { AddCellAction, KernelsDropdown, AttachToDropdown, TrustedAction, RunAllCellsAction, ClearAllOutputsAction, CollapseCellsAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
|
||||
import { AddCellAction, KernelsDropdown, AttachToDropdown, TrustedAction, RunAllCellsAction, ClearAllOutputsAction, CollapseCellsAction, RunParametersAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
|
||||
import { DropdownMenuActionViewItem } from 'sql/base/browser/ui/buttonMenu/buttonMenu';
|
||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||
@@ -129,7 +129,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
this.notebookService.removeNotebookEditor(this);
|
||||
}
|
||||
}
|
||||
|
||||
public get model(): NotebookModel | null {
|
||||
return this._model;
|
||||
}
|
||||
@@ -387,6 +386,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted', true);
|
||||
this._trustedAction.enabled = false;
|
||||
|
||||
let runParametersAction = this.instantiationService.createInstance(RunParametersAction, 'notebook.runParameters', true, this._notebookParams.notebookUri);
|
||||
|
||||
let taskbar = <HTMLElement>this.toolbar.nativeElement;
|
||||
this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) });
|
||||
this._actionBar.context = this._notebookParams.notebookUri;
|
||||
@@ -418,6 +419,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
{ action: collapseCellsAction },
|
||||
{ action: clearResultsButton },
|
||||
{ action: this._trustedAction },
|
||||
{ action: runParametersAction },
|
||||
]);
|
||||
} else {
|
||||
let kernelContainer = document.createElement('div');
|
||||
@@ -458,10 +460,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
{ action: this._trustedAction },
|
||||
{ action: this._runAllCellsAction },
|
||||
{ action: clearResultsButton },
|
||||
{ action: collapseCellsAction }
|
||||
{ action: collapseCellsAction },
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected initNavSection(): void {
|
||||
|
||||
@@ -79,6 +79,16 @@
|
||||
padding-left: 18px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.notebookEditor
|
||||
.in-preview
|
||||
.actions-container
|
||||
.action-item
|
||||
.codicon.icon-run-with-parameters:before {
|
||||
padding-left: 20px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.notebookEditor .in-preview .actions-container .action-item:last-child {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { INotebookService } from 'sql/workbench/services/notebook/browser/notebo
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
const msgLoading = localize('loading', "Loading kernels...");
|
||||
export const msgChanging = localize('changing', "Changing kernel...");
|
||||
@@ -39,6 +40,8 @@ const msgSelectConnection = localize('selectConnection', "Select Connection");
|
||||
const msgLocalHost = localize('localhost', "localhost");
|
||||
|
||||
export const noKernel: string = localize('noKernel', "No Kernel");
|
||||
const baseIconClass = 'codicon';
|
||||
const maskedIconClass = 'masked-icon';
|
||||
|
||||
// Action to add a cell to notebook based on cell type(code/markdown).
|
||||
export class AddCellAction extends Action {
|
||||
@@ -101,17 +104,15 @@ export abstract class TooltipFromLabelAction extends Action {
|
||||
// Action to clear outputs of all code cells.
|
||||
export class ClearAllOutputsAction extends TooltipFromLabelAction {
|
||||
private static readonly label = localize('clearResults', "Clear Results");
|
||||
private static readonly baseClass = 'codicon';
|
||||
private static readonly iconClass = 'icon-clear-results';
|
||||
private static readonly maskedIconClass = 'masked-icon';
|
||||
|
||||
constructor(id: string, toggleTooltip: boolean,
|
||||
@INotebookService private _notebookService: INotebookService) {
|
||||
super(id, {
|
||||
label: ClearAllOutputsAction.label,
|
||||
baseClass: ClearAllOutputsAction.baseClass,
|
||||
baseClass: baseIconClass,
|
||||
iconClass: ClearAllOutputsAction.iconClass,
|
||||
maskedIconClass: ClearAllOutputsAction.maskedIconClass,
|
||||
maskedIconClass: maskedIconClass,
|
||||
shouldToggleTooltip: toggleTooltip
|
||||
});
|
||||
}
|
||||
@@ -170,24 +171,22 @@ export class TrustedAction extends ToggleableAction {
|
||||
// Constants
|
||||
private static readonly trustedLabel = localize('trustLabel', "Trusted");
|
||||
private static readonly notTrustedLabel = localize('untrustLabel', "Not Trusted");
|
||||
private static readonly baseClass = 'codicon';
|
||||
private static readonly previewTrustedCssClass = 'icon-shield';
|
||||
private static readonly trustedCssClass = 'icon-trusted';
|
||||
private static readonly previewNotTrustedCssClass = 'icon-shield-x';
|
||||
private static readonly notTrustedCssClass = 'icon-notTrusted';
|
||||
private static readonly maskedIconClass = 'masked-icon';
|
||||
|
||||
constructor(
|
||||
id: string, toggleTooltip: boolean,
|
||||
@INotebookService private _notebookService: INotebookService
|
||||
) {
|
||||
super(id, {
|
||||
baseClass: TrustedAction.baseClass,
|
||||
baseClass: baseIconClass,
|
||||
toggleOnLabel: TrustedAction.trustedLabel,
|
||||
toggleOnClass: toggleTooltip === true ? TrustedAction.previewTrustedCssClass : TrustedAction.trustedCssClass,
|
||||
toggleOffLabel: TrustedAction.notTrustedLabel,
|
||||
toggleOffClass: toggleTooltip === true ? TrustedAction.previewNotTrustedCssClass : TrustedAction.notTrustedCssClass,
|
||||
maskedIconClass: TrustedAction.maskedIconClass,
|
||||
maskedIconClass: maskedIconClass,
|
||||
shouldToggleTooltip: toggleTooltip,
|
||||
isOn: false
|
||||
});
|
||||
@@ -232,22 +231,20 @@ export class RunAllCellsAction extends Action {
|
||||
export class CollapseCellsAction extends ToggleableAction {
|
||||
private static readonly collapseCells = localize('collapseAllCells', "Collapse Cells");
|
||||
private static readonly expandCells = localize('expandAllCells', "Expand Cells");
|
||||
private static readonly baseClass = 'codicon';
|
||||
private static readonly previewCollapseCssClass = 'icon-collapse-cells';
|
||||
private static readonly collapseCssClass = 'icon-hide-cells';
|
||||
private static readonly previewExpandCssClass = 'icon-expand-cells';
|
||||
private static readonly expandCssClass = 'icon-show-cells';
|
||||
private static readonly maskedIconClass = 'masked-icon';
|
||||
|
||||
constructor(id: string, toggleTooltip: boolean,
|
||||
@INotebookService private _notebookService: INotebookService) {
|
||||
super(id, {
|
||||
baseClass: CollapseCellsAction.baseClass,
|
||||
baseClass: baseIconClass,
|
||||
toggleOnLabel: CollapseCellsAction.expandCells,
|
||||
toggleOnClass: toggleTooltip === true ? CollapseCellsAction.previewExpandCssClass : CollapseCellsAction.expandCssClass,
|
||||
toggleOffLabel: CollapseCellsAction.collapseCells,
|
||||
toggleOffClass: toggleTooltip === true ? CollapseCellsAction.previewCollapseCssClass : CollapseCellsAction.collapseCssClass,
|
||||
maskedIconClass: CollapseCellsAction.maskedIconClass,
|
||||
maskedIconClass: maskedIconClass,
|
||||
shouldToggleTooltip: toggleTooltip,
|
||||
isOn: false
|
||||
});
|
||||
@@ -272,6 +269,101 @@ export class CollapseCellsAction extends ToggleableAction {
|
||||
}
|
||||
}
|
||||
|
||||
export class RunParametersAction extends TooltipFromLabelAction {
|
||||
private static readonly label = localize('runParameters', "Run with Parameters");
|
||||
private static readonly iconClass = 'icon-run-with-parameters';
|
||||
|
||||
constructor(id: string,
|
||||
toggleTooltip: boolean,
|
||||
context: URI,
|
||||
@IQuickInputService private quickInputService: IQuickInputService,
|
||||
@INotebookService private _notebookService: INotebookService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
) {
|
||||
super(id, {
|
||||
label: RunParametersAction.label,
|
||||
baseClass: baseIconClass,
|
||||
iconClass: RunParametersAction.iconClass,
|
||||
maskedIconClass: maskedIconClass,
|
||||
shouldToggleTooltip: toggleTooltip
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Default Parameters in Notebook from Parameter Cell
|
||||
* Uses that as Placeholder values for user to inject new values for
|
||||
* Once user enters all values it will open the new parameterized notebook
|
||||
* with injected parameters value from the QuickInput
|
||||
*/
|
||||
public async run(context: URI): Promise<void> {
|
||||
const editor = this._notebookService.findNotebookEditor(context);
|
||||
// Set defaultParameters to the parameter values in parameter cell
|
||||
let defaultParameters = new Map<string, string>();
|
||||
editor.cells.forEach(cell => {
|
||||
if (cell.isParameter) {
|
||||
for (let parameter of cell.source) {
|
||||
let param = parameter.split('=', 2);
|
||||
defaultParameters.set(param[0].trim(), param[1].trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Store new parameters values the user inputs
|
||||
let inputParameters = new Map<string, string>();
|
||||
let uriParams = new URLSearchParams();
|
||||
// Store new parameter values to map based off defaultParameters
|
||||
if (defaultParameters.size === 0) {
|
||||
// If there is no parameter cell indicate to user to create one
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message: localize('noParametersCell', "This notebook cannot run with parameters until a parameter cell is added. [Learn more](https://docs.microsoft.com/sql/azure-data-studio/notebooks/notebooks-parameterization)."),
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
for (let key of defaultParameters.keys()) {
|
||||
let newParameterValue = await this.quickInputService.input({ prompt: key, value: defaultParameters.get(key), ignoreFocusLost: true });
|
||||
// If user cancels or escapes then it stops the action entirely
|
||||
if (newParameterValue === undefined) {
|
||||
return;
|
||||
}
|
||||
inputParameters.set(key, newParameterValue);
|
||||
}
|
||||
// Format the new parameters to be append to the URI
|
||||
for (let key of inputParameters.keys()) {
|
||||
// Will only add new injected parameters when the value is not the same as the defaultParameters values
|
||||
if (inputParameters.get(key) !== defaultParameters.get(key)) {
|
||||
// For empty strings we need to escape the value
|
||||
// so that it is kept when adding uriParams.toString() to filePath
|
||||
if (inputParameters.get(key) === '') {
|
||||
uriParams.append(key, '\'\'');
|
||||
} else {
|
||||
uriParams.append(key, inputParameters.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
let stringParams = unescape(uriParams.toString());
|
||||
context = context.with({ query: stringParams });
|
||||
return this.openParameterizedNotebook(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will be used once the showNotebookDocument can be used
|
||||
* TODO - Call Extensibility API for ShowNotebook
|
||||
* (showNotebookDocument to be utilized in Notebook Service)
|
||||
**/
|
||||
public async openParameterizedNotebook(uri: URI): Promise<void> {
|
||||
// const editor = this._notebookService.findNotebookEditor(uri);
|
||||
// let modelContents = editor.model.toJSON();
|
||||
// let basename = path.basename(uri.fsPath);
|
||||
// let untitledUri = uri.with({ authority: '', scheme: 'untitled', path: basename });
|
||||
// this._notebookService.showNotebookDocument(untitledUri, {
|
||||
// initialContent: modelContents,
|
||||
// preserveFocus: true
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
const showAllKernelsConfigName = 'notebook.showAllKernels';
|
||||
const workbenchPreviewConfigName = 'workbench.enablePreviewFeatures';
|
||||
export const noKernelName = localize('noKernel', "No Kernel");
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as assert from 'assert';
|
||||
import * as azdata from 'azdata';
|
||||
import * as sinon from 'sinon';
|
||||
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
|
||||
import { AddCellAction, ClearAllOutputsAction, CollapseCellsAction, KernelsDropdown, msgChanging, NewNotebookAction, noKernelName, RunAllCellsAction, TrustedAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
|
||||
import { AddCellAction, ClearAllOutputsAction, CollapseCellsAction, KernelsDropdown, msgChanging, NewNotebookAction, noKernelName, RunAllCellsAction, RunParametersAction, TrustedAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
|
||||
import { ClientSessionStub, ContextViewProviderStub, NotebookComponentStub, NotebookModelStub, NotebookServiceStub } from 'sql/workbench/contrib/notebook/test/stubs';
|
||||
import { NotebookEditorStub } from 'sql/workbench/contrib/notebook/test/testCommon';
|
||||
import { ICellModel, INotebookModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
@@ -24,6 +24,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MockQuickInputService } from 'sql/workbench/contrib/notebook/test/common/quickInputServiceMock';
|
||||
|
||||
class TestClientSession extends ClientSessionStub {
|
||||
private _errorState: boolean = false;
|
||||
@@ -257,6 +258,24 @@ suite('Notebook Actions', function (): void {
|
||||
assert.strictEqual(actualCmdId, NewNotebookAction.INTERNAL_NEW_NOTEBOOK_CMD_ID);
|
||||
});
|
||||
|
||||
test.skip('Run with Parameters Action', async function (): Promise<void> {
|
||||
let mockNotification = TypeMoq.Mock.ofType<INotificationService>(TestNotificationService);
|
||||
mockNotification.setup(n => n.notify(TypeMoq.It.isAny()));
|
||||
let quickInputService = new MockQuickInputService;
|
||||
|
||||
let action = new RunParametersAction('TestId', true, testUri, quickInputService, mockNotebookService.object, mockNotification.object);
|
||||
|
||||
// Normal use case
|
||||
const testCells = [<ICellModel>{
|
||||
isParameter: true,
|
||||
source: ['x=2.0\n', 'y=5.0']
|
||||
}];
|
||||
|
||||
mockNotebookEditor.setup(x => x.cells).returns(() => testCells);
|
||||
|
||||
assert.doesNotThrow(() => action.run(testUri));
|
||||
});
|
||||
|
||||
suite('Kernels dropdown', async () => {
|
||||
let kernelsDropdown: KernelsDropdown;
|
||||
let contextViewProvider: ContextViewProviderStub;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as Types from 'vs/base/common/types';
|
||||
|
||||
import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
|
||||
export class MockQuickInputService implements IQuickInputService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
readonly onShow = Event.None;
|
||||
readonly onHide = Event.None;
|
||||
|
||||
readonly quickAccess = undefined!;
|
||||
|
||||
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
|
||||
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
|
||||
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T | undefined> {
|
||||
if (Types.isArray(picks)) {
|
||||
return Promise.resolve(<any>{ label: 'selectedPick', description: 'pick description', value: 'selectedPick' });
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public input(options?: IInputOptions, token?: CancellationToken): Promise<string> {
|
||||
return Promise.resolve(options ? 'resolved' + options.prompt : 'resolved');
|
||||
}
|
||||
|
||||
backButton!: IQuickInputButton;
|
||||
|
||||
createQuickPick<T extends IQuickPickItem>(): IQuickPick<T> {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
createInputBox(): IInputBox {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
accept(): Promise<void> {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
back(): Promise<void> {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
|
||||
cancel(): Promise<void> {
|
||||
throw new Error('not implemented.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user