mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Open books from Github (#10670)
* Add open book option in UI * Add dropdowns option on dialog * Add flow logic * Fetch releases and validate URL * Add class for github books and shared file books * Change code structure * Unblock local copy + stack overflows * Download books from github * Remove unused files * Clean code and use the openNotebookFolder command to open remote book * Checkpoint * Refactor remote book dialog model to only hold data * Remove ApiWrapper and refactor createlocalcopy method * Use sinon js framework instead of typemoq for testing remotebookController * Remove api wrapper * Add some tests * Add more tests and address pr comments * Address PR comments * Fix remotebook broken tests * Add download location in output channel and use openBook command * Address PR comments * Fix typos, print error message and remove failing test * Print error message * Separate tests in different files * Declare controller variable inside extension.tst Co-authored-by: chlafreniere <hichise@gmail.com>
This commit is contained in:
292
extensions/notebook/src/dialog/remoteBookDialog.ts
Normal file
292
extensions/notebook/src/dialog/remoteBookDialog.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as loc from '../common/localizedConstants';
|
||||
import { RemoteBookController, IAsset } from '../book/remoteBookController';
|
||||
import * as utils from '../common/utils';
|
||||
|
||||
const tigerToolboxRepo = 'repos/microsoft/tigertoolbox';
|
||||
const urlGithubRE = /^(?:https:\/\/(?:github\.com|api\.github\.com\/repos)|(?:\/)?(?:\/)?repos)([\w-.?!=&%*+:@\/]*)/g;
|
||||
|
||||
function apiGitHub(url: string): string {
|
||||
return `https://api.github.com/${url}/releases`;
|
||||
}
|
||||
|
||||
function getRemoteLocationCategory(name: string): azdata.CategoryValue {
|
||||
if (name === loc.onGitHub) {
|
||||
return { name: name, displayName: loc.onGitHub };
|
||||
}
|
||||
return { name: name, displayName: loc.onSharedFile };
|
||||
}
|
||||
|
||||
export class RemoteBookDialog {
|
||||
|
||||
private dialog: azdata.window.Dialog;
|
||||
public view: azdata.ModelView;
|
||||
private formModel: azdata.FormContainer;
|
||||
private githubRepoDropdown: azdata.DropDownComponent;
|
||||
private remoteLocationDropdown: azdata.DropDownComponent;
|
||||
public releaseDropdown: azdata.DropDownComponent;
|
||||
private searchButton: azdata.ButtonComponent;
|
||||
public bookDropdown: azdata.DropDownComponent;
|
||||
public versionDropdown: azdata.DropDownComponent;
|
||||
public languageDropdown: azdata.DropDownComponent;
|
||||
private _remoteTypes: azdata.CategoryValue[];
|
||||
|
||||
constructor(public controller: RemoteBookController) {
|
||||
}
|
||||
|
||||
public async createDialog(): Promise<void> {
|
||||
this.dialog = azdata.window.createModelViewDialog(loc.addRemoteBook);
|
||||
this.dialog.registerContent(async view => {
|
||||
this.view = view;
|
||||
|
||||
this.remoteLocationDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
values: this.remoteLocationCategories,
|
||||
value: '',
|
||||
editable: false,
|
||||
}).component();
|
||||
|
||||
this.remoteLocationDropdown.onValueChanged(e => this.onRemoteLocationChanged());
|
||||
|
||||
this.githubRepoDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
values: [tigerToolboxRepo],
|
||||
value: '',
|
||||
editable: true,
|
||||
fireOnTextChange: true,
|
||||
}).component();
|
||||
|
||||
this.searchButton = this.view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
label: loc.search,
|
||||
title: loc.search,
|
||||
width: '200px'
|
||||
}).component();
|
||||
this.searchButton.onDidClick(async () => await this.validate());
|
||||
|
||||
this.releaseDropdown = this.view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
values: [],
|
||||
value: '',
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.releaseDropdown.onValueChanged(async () => await this.getAssets());
|
||||
|
||||
this.bookDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
values: [],
|
||||
value: '',
|
||||
editable: false,
|
||||
}).component();
|
||||
|
||||
this.bookDropdown.onValueChanged(async () => await this.fillVersionDropdown());
|
||||
|
||||
this.versionDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
values: [],
|
||||
value: '',
|
||||
editable: false,
|
||||
}).component();
|
||||
|
||||
this.versionDropdown.onValueChanged(async () => await this.fillLanguageDropdown());
|
||||
|
||||
this.languageDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
values: [],
|
||||
value: '',
|
||||
editable: false,
|
||||
}).component();
|
||||
|
||||
this.languageDropdown.onValueChanged(async () => this.checkValues());
|
||||
this.setFieldsToEmpty();
|
||||
|
||||
this.formModel = this.view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [
|
||||
{
|
||||
component: this.remoteLocationDropdown,
|
||||
title: loc.location,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: this.githubRepoDropdown,
|
||||
title: loc.repoUrl,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: this.searchButton,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.releaseDropdown,
|
||||
title: loc.releases,
|
||||
},
|
||||
{
|
||||
component: this.bookDropdown,
|
||||
title: loc.book,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: this.versionDropdown,
|
||||
title: loc.version,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: this.languageDropdown,
|
||||
title: loc.language,
|
||||
required: true
|
||||
},
|
||||
],
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
await this.view.initializeModel(this.formModel);
|
||||
});
|
||||
this.dialog.okButton.enabled = false;
|
||||
this.dialog.okButton.label = loc.add;
|
||||
this.dialog.cancelButton.label = loc.close;
|
||||
this.dialog.registerCloseValidator(async () => await this.download());
|
||||
azdata.window.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private async setFieldsToEmpty(): Promise<void> {
|
||||
await this.bookDropdown.updateProperties({
|
||||
values: [loc.invalidTextPlaceholder],
|
||||
value: loc.invalidTextPlaceholder
|
||||
});
|
||||
await this.versionDropdown.updateProperties({
|
||||
values: [loc.invalidTextPlaceholder],
|
||||
value: loc.invalidTextPlaceholder
|
||||
});
|
||||
await this.languageDropdown.updateProperties({
|
||||
values: [loc.invalidTextPlaceholder],
|
||||
value: loc.invalidTextPlaceholder
|
||||
});
|
||||
this.dialog.okButton.enabled = false;
|
||||
}
|
||||
|
||||
private get remoteLocationValue(): string {
|
||||
return (<azdata.CategoryValue>this.remoteLocationDropdown.value).name;
|
||||
}
|
||||
|
||||
public onRemoteLocationChanged(): void {
|
||||
if (this.controller.getReleases() !== undefined && this.remoteLocationValue === loc.onGitHub) {
|
||||
this.releaseDropdown.enabled = true;
|
||||
} else {
|
||||
this.releaseDropdown.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public async validate(): Promise<void> {
|
||||
try {
|
||||
let url = utils.getDropdownValue(this.githubRepoDropdown);
|
||||
url = url.trim().toLowerCase();
|
||||
if (this.remoteLocationValue === loc.onGitHub && url.length > 0) {
|
||||
//get the first group to extract /owner/repo/releases format
|
||||
let groupsRe = url.match(urlGithubRE);
|
||||
if (groupsRe?.length > 0) {
|
||||
url = apiGitHub(groupsRe[0]);
|
||||
let releases = await this.controller.getReleases(new URL(url));
|
||||
if (releases) {
|
||||
this.releaseDropdown.enabled = true;
|
||||
await this.fillReleasesDropdown();
|
||||
this.setFieldsToEmpty();
|
||||
}
|
||||
} else {
|
||||
throw new Error(loc.urlGithubError);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
await this.fillReleasesDropdown();
|
||||
this.setFieldsToEmpty();
|
||||
this.showErrorMessage(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async getAssets(): Promise<void> {
|
||||
try {
|
||||
if (this.remoteLocationValue === loc.onGitHub) {
|
||||
let releases = await this.controller.getReleases();
|
||||
let selected_release = releases.filter(release =>
|
||||
release.name === this.releaseDropdown.value);
|
||||
let assets = await this.controller.getAssets(selected_release[0]);
|
||||
if (assets?.length > 0) {
|
||||
this.bookDropdown.values = ['-'].concat([...new Set(assets.map(asset => asset.book))]);
|
||||
}
|
||||
this.checkValues();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.setFieldsToEmpty();
|
||||
this.showErrorMessage(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async download(): Promise<boolean> {
|
||||
try {
|
||||
if (this.remoteLocationValue === loc.onGitHub) {
|
||||
let selected_asset = await this.getSelectedAsset();
|
||||
if (!selected_asset) {
|
||||
throw new Error(loc.msgUndefinedAssetError);
|
||||
}
|
||||
await this.controller.setRemoteBook(selected_asset.url, this.remoteLocationValue, selected_asset);
|
||||
} else {
|
||||
let url = utils.getDropdownValue(this.githubRepoDropdown);
|
||||
let newUrl = new URL(url);
|
||||
await this.controller.setRemoteBook(newUrl, this.remoteLocationValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.showErrorMessage(error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async fillReleasesDropdown(): Promise<void> {
|
||||
this.releaseDropdown.values = ['-'].concat((await this.controller.getReleases()).map(release => release.name));
|
||||
}
|
||||
|
||||
public async fillVersionDropdown(): Promise<void> {
|
||||
let filtered_assets = (await this.controller.getAssets()).filter(asset => asset.book === this.bookDropdown.value);
|
||||
this.versionDropdown.values = ['-'].concat(filtered_assets.map(asset => asset.version));
|
||||
this.checkValues();
|
||||
}
|
||||
|
||||
public async fillLanguageDropdown(): Promise<void> {
|
||||
let filtered_assets = (await this.controller.getAssets()).filter(asset => asset.book === this.bookDropdown.value &&
|
||||
asset.version === this.versionDropdown.value);
|
||||
this.languageDropdown.values = ['-'].concat(filtered_assets.map(asset => asset.language));
|
||||
this.checkValues();
|
||||
}
|
||||
|
||||
public async getSelectedAsset(): Promise<IAsset> {
|
||||
let lang = this.languageDropdown.value;
|
||||
let book = this.bookDropdown.value;
|
||||
let version = this.versionDropdown.value;
|
||||
return (await this.controller.getAssets()).filter(asset => asset.book === book && asset.version === version && asset.language === lang)[0];
|
||||
}
|
||||
|
||||
public checkValues(): void {
|
||||
if (this.languageDropdown.value !== loc.invalidTextPlaceholder && this.versionDropdown.value !== loc.invalidTextPlaceholder &&
|
||||
this.bookDropdown.value !== loc.invalidTextPlaceholder) {
|
||||
this.dialog.okButton.enabled = true;
|
||||
} else {
|
||||
this.dialog.okButton.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public get remoteLocationCategories(): azdata.CategoryValue[] {
|
||||
if (!this._remoteTypes) {
|
||||
this._remoteTypes = [getRemoteLocationCategory(loc.onGitHub)];
|
||||
}
|
||||
return this._remoteTypes;
|
||||
}
|
||||
|
||||
public showErrorMessage(message: string): void {
|
||||
this.dialog.message = {
|
||||
text: message,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
}
|
||||
50
extensions/notebook/src/dialog/remoteBookDialogModel.ts
Normal file
50
extensions/notebook/src/dialog/remoteBookDialogModel.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RemoteBook } from '../book/remoteBook';
|
||||
import { IRelease, IAsset } from '../book/remoteBookController';
|
||||
|
||||
export class RemoteBookDialogModel {
|
||||
private _remoteLocation: string;
|
||||
private _releases: IRelease[];
|
||||
private _assets: IAsset[];
|
||||
private _book: RemoteBook;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public get remoteLocation(): string {
|
||||
return this._remoteLocation;
|
||||
}
|
||||
|
||||
public set remoteLocation(location: string) {
|
||||
this._remoteLocation = location;
|
||||
}
|
||||
|
||||
public get releases(): IRelease[] {
|
||||
return this._releases;
|
||||
}
|
||||
|
||||
public set releases(newReleases: IRelease[]) {
|
||||
this._releases = newReleases;
|
||||
}
|
||||
|
||||
public get assets(): IAsset[] {
|
||||
return this._assets;
|
||||
}
|
||||
|
||||
public set assets(newAssets: IAsset[]) {
|
||||
this._assets = newAssets;
|
||||
}
|
||||
|
||||
public get remoteBook(): RemoteBook {
|
||||
return this._book;
|
||||
}
|
||||
|
||||
public set remoteBook(newBook: RemoteBook) {
|
||||
this._book = newBook;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user