mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 17:23:10 -05:00
291 lines
10 KiB
TypeScript
291 lines
10 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
import {
|
|
Component, Inject, ViewContainerRef, forwardRef, AfterContentInit,
|
|
ComponentFactoryResolver, ViewChild, ChangeDetectorRef
|
|
} from '@angular/core';
|
|
import { Observable } from 'rxjs/Observable';
|
|
|
|
import { DashboardWidget, IDashboardWidget, WIDGET_CONFIG, WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
|
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
|
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
|
|
import { InsightAction, InsightActionContext } from 'sql/workbench/common/actions';
|
|
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
|
|
import { IInsightsConfig, IInsightsView } from './interfaces';
|
|
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
|
import { insertValueRegex } from 'sql/parts/insights/common/interfaces';
|
|
import { RunInsightQueryAction } from './actions';
|
|
|
|
import { SimpleExecuteResult } from 'sqlops';
|
|
|
|
import { Action } from 'vs/base/common/actions';
|
|
import * as types from 'vs/base/common/types';
|
|
import * as pfs from 'vs/base/node/pfs';
|
|
import * as nls from 'vs/nls';
|
|
import { Registry } from 'vs/platform/registry/common/platform';
|
|
import { WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
|
|
|
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
|
|
|
|
interface IStorageResult {
|
|
date: string;
|
|
results: SimpleExecuteResult;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'insights-widget',
|
|
template: `
|
|
<div *ngIf="error" style="text-align: center; padding-top: 20px">{{error}}</div>
|
|
<div *ngIf="lastUpdated" style="font-style: italic; font-size: 80%; margin-left: 5px">{{lastUpdated}}</div>
|
|
<div style="margin: 10px; width: calc(100% - 20px); height: calc(100% - 20px)">
|
|
<ng-template component-host></ng-template>
|
|
</div>`,
|
|
styles: [':host { width: 100%; height: 100% }']
|
|
})
|
|
export class InsightsWidget extends DashboardWidget implements IDashboardWidget, AfterContentInit {
|
|
private insightConfig: IInsightsConfig;
|
|
private queryObv: Observable<SimpleExecuteResult>;
|
|
@ViewChild(ComponentHostDirective) private componentHost: ComponentHostDirective;
|
|
|
|
private _typeKey: string;
|
|
private _init: boolean = false;
|
|
|
|
public error: string;
|
|
public lastUpdated: string;
|
|
|
|
constructor(
|
|
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver,
|
|
@Inject(forwardRef(() => DashboardServiceInterface)) private dashboardService: DashboardServiceInterface,
|
|
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
|
|
@Inject(forwardRef(() => ViewContainerRef)) private viewContainerRef: ViewContainerRef,
|
|
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
|
|
) {
|
|
super();
|
|
this.insightConfig = <IInsightsConfig>this._config.widget['insights-widget'];
|
|
|
|
this._verifyConfig();
|
|
|
|
this._parseConfig().then(() => {
|
|
if (!this._checkStorage()) {
|
|
let promise = this._runQuery();
|
|
this.queryObv = Observable.fromPromise(promise);
|
|
promise.then(
|
|
result => {
|
|
if (this._init) {
|
|
this._updateChild(result);
|
|
} else {
|
|
this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(result));
|
|
}
|
|
},
|
|
error => {
|
|
if (this._init) {
|
|
this.showError(error);
|
|
} else {
|
|
this.queryObv = Observable.fromPromise(Promise.reject<SimpleExecuteResult>(error));
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}, error => {
|
|
this.showError(error);
|
|
});
|
|
}
|
|
|
|
ngAfterContentInit() {
|
|
this._init = true;
|
|
if (this.queryObv) {
|
|
this._register(toDisposableSubscription(this.queryObv.subscribe(
|
|
result => {
|
|
this._updateChild(result);
|
|
},
|
|
error => {
|
|
this.showError(error);
|
|
}
|
|
)));
|
|
}
|
|
}
|
|
|
|
private showError(error: string): void {
|
|
this.error = error;
|
|
this._cd.detectChanges();
|
|
}
|
|
|
|
get actions(): Array<Action> {
|
|
let actions: Array<Action> = [];
|
|
if (this.insightConfig.details && (this.insightConfig.details.query || this.insightConfig.details.queryFile)) {
|
|
actions.push(this.dashboardService.instantiationService.createInstance(InsightAction, InsightAction.ID, InsightAction.LABEL));
|
|
}
|
|
actions.push(this.dashboardService.instantiationService.createInstance(RunInsightQueryAction, RunInsightQueryAction.ID, RunInsightQueryAction.LABEL));
|
|
return actions;
|
|
}
|
|
|
|
get actionsContext(): InsightActionContext {
|
|
return <InsightActionContext>{
|
|
profile: this.dashboardService.connectionManagementService.connectionInfo.connectionProfile,
|
|
insight: this.insightConfig
|
|
};
|
|
}
|
|
|
|
private _storeResult(result: SimpleExecuteResult): SimpleExecuteResult {
|
|
if (this.insightConfig.cacheId) {
|
|
let currentTime = new Date();
|
|
let store: IStorageResult = {
|
|
date: currentTime.toString(),
|
|
results: result
|
|
};
|
|
this.lastUpdated = nls.localize('insights.lastUpdated', "Last Updated: {0} {1}", currentTime.toLocaleTimeString(), currentTime.toLocaleDateString());
|
|
this._cd.detectChanges();
|
|
this.dashboardService.storageService.store(this._getStorageKey(), JSON.stringify(store));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private _checkStorage(): boolean {
|
|
if (this.insightConfig.cacheId) {
|
|
let storage = this.dashboardService.storageService.get(this._getStorageKey());
|
|
if (storage) {
|
|
let storedResult: IStorageResult = JSON.parse(storage);
|
|
let date = new Date(storedResult.date);
|
|
this.lastUpdated = nls.localize('insights.lastUpdated', "Last Updated: {0} {1}", date.toLocaleTimeString(), date.toLocaleDateString());
|
|
if (this._init) {
|
|
this._updateChild(storedResult.results);
|
|
this._cd.detectChanges();
|
|
} else {
|
|
this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(JSON.parse(storage)));
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public refresh(): void {
|
|
this._runQuery().then(
|
|
result => this._updateChild(result),
|
|
error => this.showError(error)
|
|
);
|
|
}
|
|
|
|
private _getStorageKey(): string {
|
|
return `insights.${this.insightConfig.cacheId}.${this.dashboardService.connectionManagementService.connectionInfo.connectionProfile.getOptionsKey()}`;
|
|
}
|
|
|
|
private _runQuery(): Thenable<SimpleExecuteResult> {
|
|
return this.dashboardService.queryManagementService.runQueryAndReturn(this.insightConfig.query as string).then(
|
|
result => {
|
|
return this._storeResult(result);
|
|
},
|
|
error => {
|
|
throw error;
|
|
}
|
|
);
|
|
}
|
|
|
|
private _updateChild(result: SimpleExecuteResult): void {
|
|
if (result.rowCount === 0) {
|
|
this.showError(nls.localize('noResults', 'No results to show'));
|
|
return;
|
|
}
|
|
|
|
let componentFactory = this._componentFactoryResolver.resolveComponentFactory<IInsightsView>(insightRegistry.getCtorFromId(this._typeKey));
|
|
this.componentHost.viewContainerRef.clear();
|
|
|
|
let componentRef = this.componentHost.viewContainerRef.createComponent(componentFactory);
|
|
let componentInstance = componentRef.instance;
|
|
componentInstance.data = { columns: result.columnInfo.map(item => item.columnName), rows: result.rows.map(row => row.map(item => item.displayValue)) };
|
|
// check if the setter is defined
|
|
if (componentInstance.setConfig) {
|
|
componentInstance.setConfig(this.insightConfig.type[this._typeKey]);
|
|
}
|
|
|
|
if (componentInstance.init) {
|
|
componentInstance.init();
|
|
}
|
|
}
|
|
|
|
private _verifyConfig() {
|
|
if (types.isUndefinedOrNull(this.insightConfig)) {
|
|
throw new Error('Insight config must be defined');
|
|
}
|
|
|
|
if (types.isUndefinedOrNull(this.insightConfig.type)) {
|
|
throw new Error('An Insight type must be specified');
|
|
}
|
|
|
|
if (Object.keys(this.insightConfig.type).length !== 1) {
|
|
throw new Error('Exactly 1 insight type must be specified');
|
|
}
|
|
|
|
if (!insightRegistry.getAllIds().includes(Object.keys(this.insightConfig.type)[0])) {
|
|
throw new Error('The insight type must be a valid registered insight');
|
|
}
|
|
|
|
if (!this.insightConfig.query && !this.insightConfig.queryFile) {
|
|
throw new Error('No query was specified for this insight');
|
|
}
|
|
|
|
if (!types.isStringArray(this.insightConfig.query)
|
|
&& !types.isString(this.insightConfig.query)
|
|
&& !types.isString(this.insightConfig.queryFile)) {
|
|
throw new Error('Invalid query or queryfile specified');
|
|
}
|
|
}
|
|
|
|
private _parseConfig(): Thenable<void[]> {
|
|
let promises: Array<Promise<void>> = [];
|
|
|
|
this._typeKey = Object.keys(this.insightConfig.type)[0];
|
|
|
|
if (types.isStringArray(this.insightConfig.query)) {
|
|
this.insightConfig.query = this.insightConfig.query.join(' ');
|
|
} else if (this.insightConfig.queryFile) {
|
|
let filePath = this.insightConfig.queryFile;
|
|
// check for workspace relative path
|
|
let match = filePath.match(insertValueRegex);
|
|
if (match && match.length > 0 && match[1] === 'workspaceRoot') {
|
|
filePath = filePath.replace(match[0], '');
|
|
|
|
//filePath = this.dashboardService.workspaceContextService.toResource(filePath).fsPath;
|
|
switch (this.dashboardService.workspaceContextService.getWorkbenchState()) {
|
|
case WorkbenchState.FOLDER:
|
|
filePath = this.dashboardService.workspaceContextService.getWorkspace().folders[0].toResource(filePath).fsPath;
|
|
break;
|
|
case WorkbenchState.WORKSPACE:
|
|
let filePathArray = filePath.split('/');
|
|
// filter out empty sections
|
|
filePathArray = filePathArray.filter(i => !!i);
|
|
let folder = this.dashboardService.workspaceContextService.getWorkspace().folders.find(i => i.name === filePathArray[0]);
|
|
if (!folder) {
|
|
return Promise.reject<void[]>(new Error(`Could not find workspace folder ${filePathArray[0]}`));
|
|
}
|
|
// remove the folder name from the filepath
|
|
filePathArray.shift();
|
|
// rejoin the filepath after doing the work to find the right folder
|
|
filePath = '/' + filePathArray.join('/');
|
|
filePath = folder.toResource(filePath).fsPath;
|
|
break;
|
|
}
|
|
|
|
}
|
|
promises.push(new Promise((resolve, reject) => {
|
|
pfs.readFile(filePath).then(
|
|
buffer => {
|
|
this.insightConfig.query = buffer.toString();
|
|
resolve();
|
|
},
|
|
error => {
|
|
reject(error);
|
|
}
|
|
);
|
|
}));
|
|
}
|
|
|
|
return Promise.all(promises);
|
|
}
|
|
}
|