Skip to content

Commit

Permalink
Adding site service and generalizing conditional-http-client (#2266)
Browse files Browse the repository at this point in the history
* Adding site service and generalizing conditional-http-client

* Removing arm-preconditions

* Moving precondition.ts back to where it was originally
  • Loading branch information
ehamai authored Jan 30, 2018
1 parent be27e4f commit 6cafb29
Show file tree
Hide file tree
Showing 18 changed files with 363 additions and 186 deletions.
1 change: 1 addition & 0 deletions AzureFunctions.AngularClient/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first';
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/merge';
import 'rxjs/add/operator/mergeMap';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { FunctionAppContextComponent } from 'app/shared/components/function-app-
import { FunctionAppService } from 'app/shared/services/function-app.service';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import { FunctionAppHttpResult } from 'app/shared/models/function-app-http-result';
import { HttpResult } from 'app/shared/models/http-result';


@Component({
Expand Down Expand Up @@ -182,7 +182,7 @@ export class FileExplorerComponent extends FunctionAppContextComponent {
setTimeout(() => element.focus(), 50);
}

addFile(content?: string): Observable<FunctionAppHttpResult<VfsObject | string>> {
addFile(content?: string): Observable<HttpResult<VfsObject | string>> {
if (this.newFileName && this.files.find(f => f.name.toLocaleLowerCase() === this.newFileName.toLocaleLowerCase())) {
const error = {
message: this._translateService.instant(PortalResources.fileExplorer_fileAlreadyExists, { fileName: this.newFileName })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CdsFunctionDescriptor } from 'app/shared/resourceDescriptors';
import { errorIds } from 'app/shared/models/error-ids';
import { ErrorEvent } from 'app/shared/models/error-event';
import { FunctionAppHttpResult } from './../../../shared/models/function-app-http-result';
import { HttpResult } from './../../../shared/models/http-result';
import { Observable } from 'rxjs/Observable';
import { PortalResources } from './../../../shared/models/portal-resources';
import { TranslateService } from '@ngx-translate/core';
Expand Down Expand Up @@ -82,7 +82,7 @@ export class EmbeddedFunctionEditorComponent implements OnInit, AfterContentInit
});
}

private _getScriptContent(resourceId: string): Observable<FunctionAppHttpResult<string>> {
private _getScriptContent(resourceId: string): Observable<HttpResult<string>> {
this._busyManager.setBusy();
this.resourceId = resourceId;

Expand All @@ -98,15 +98,15 @@ export class EmbeddedFunctionEditorComponent implements OnInit, AfterContentInit
return this._cacheService.getArm(this._functionInfo.script_href, true);
})
.map(r => {
return <FunctionAppHttpResult<string>>{
return <HttpResult<string>>{
isSuccessful: true,
error: null,
result: r.text()
};
})
.catch(e => {
const descriptor = new CdsFunctionDescriptor(this.resourceId);
return Observable.of(<FunctionAppHttpResult<string>>{
return Observable.of(<HttpResult<string>>{
isSuccessful: false,
error: {
errorId: errorIds.embeddedEditorLoadError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TranslateService } from '@ngx-translate/core';
import { ErrorEvent } from 'app/shared/models/error-event';
import { errorIds } from 'app/shared/models/error-ids';
import { Observable } from 'rxjs/Observable';
import { FunctionAppHttpResult } from './../../../shared/models/function-app-http-result';
import { HttpResult } from './../../../shared/models/http-result';
import { BottomTabEvent } from './../../../controls/bottom-tabs/bottom-tab-event';
import { FunctionEditorEvent } from './../function-editor-event';
import { RightTabEvent } from './../../../controls/right-tabs/right-tab-event';
Expand Down Expand Up @@ -93,15 +93,15 @@ export class EmbeddedFunctionTestTabComponent implements OnInit, OnChanges, OnDe
private _getFunction(resourceId: string) {
return this._cacheService.getArm(resourceId, true)
.map(r => {
return <FunctionAppHttpResult<FunctionInfo>>{
return <HttpResult<FunctionInfo>>{
isSuccessful: true,
error: null,
result: r.json()
};
})
.catch(e => {
const descriptor = new CdsFunctionDescriptor(resourceId);
return Observable.of(<FunctionAppHttpResult<FunctionInfo>>{
return Observable.of(<HttpResult<FunctionInfo>>{
isSuccessful: false,
error: {
errorId: errorIds.embeddedEditorLoadError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { FunctionDescriptor } from 'app/shared/resourceDescriptors';
import { FunctionAppContext } from 'app/shared/function-app-context';
import { FunctionInfo } from 'app/shared/models/function-info';
import { FunctionAppService } from 'app/shared/services/function-app.service';
import { FunctionAppHttpResult } from 'app/shared/models/function-app-http-result';
import { HttpResult } from 'app/shared/models/http-result';
import { NavigableComponent } from './navigable-component';

type FunctionChangedEventsType = Observable<TreeViewInfo<SiteData> & {
siteDescriptor: ArmSiteDescriptor;
functionDescriptor: FunctionDescriptor;
context: FunctionAppContext;
functionInfo: FunctionAppHttpResult<FunctionInfo>;
functionInfo: HttpResult<FunctionInfo>;
}>;

export abstract class BaseFunctionComponent extends NavigableComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { OnDestroy, Input } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { TreeViewInfo } from 'app/tree-view/models/tree-view-info';
import { FunctionAppContext } from 'app/shared/function-app-context';
import { FunctionAppHttpResult } from 'app/shared/models/function-app-http-result';
import { HttpResult } from 'app/shared/models/http-result';
import { FunctionInfo } from 'app/shared/models/function-info';
import { ArmSiteDescriptor, ArmFunctionDescriptor, CdsEntityDescriptor } from 'app/shared/resourceDescriptors';
import { Subject } from 'rxjs/Subject';
Expand All @@ -21,7 +21,7 @@ export abstract class FunctionAppContextComponent extends ErrorableComponent imp
siteDescriptor: ArmSiteDescriptor | CdsEntityDescriptor | null;
functionDescriptor: ArmFunctionDescriptor | null;
context: FunctionAppContext | null;
functionInfo: FunctionAppHttpResult<FunctionInfo> | null;
functionInfo: HttpResult<FunctionInfo> | null;
}>;
protected ngUnsubscribe: Observable<void>;

Expand Down Expand Up @@ -70,7 +70,7 @@ export abstract class FunctionAppContextComponent extends ErrorableComponent imp
return Observable.zip(
tuple[1].functionDescriptor
? functionAppService.getFunction(tuple[0], tuple[1].functionDescriptor.name)
: Observable.of({ isSuccessful: false, error: { errorId: '' } } as FunctionAppHttpResult<FunctionInfo>),
: Observable.of({ isSuccessful: false, error: { errorId: '' } } as HttpResult<FunctionInfo>),
Observable.of(tuple[0]),
Observable.of(tuple[1])
);
Expand Down
111 changes: 80 additions & 31 deletions AzureFunctions.AngularClient/src/app/shared/conditional-http-client.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { FunctionAppContext } from './function-app-context';
import { ArmError, HttpError } from './models/http-result';
import { Preconditions as p } from './preconditions';
import { CacheService } from 'app/shared/services/cache.service';
import { Observable } from 'rxjs/Observable';
import { FunctionAppHttpResult } from 'app/shared/models/function-app-http-result';
import { LogService } from './services/log.service';
import 'rxjs/add/observable/forkJoin';
import { HttpResult, HttpErrorResponse } from 'app/shared/models/http-result';
import { Injector } from '@angular/core';
import { errorIds } from 'app/shared/models/error-ids';

type AuthenticatedQuery<T> = (t: AuthToken) => Observable<T>;
type Query<T> = Observable<T> | AuthenticatedQuery<T>;
type AuthToken = string;
type ErrorId = string;
type Milliseconds = number;
interface ExecuteOptions {
retryCount: number;
Expand All @@ -21,32 +19,38 @@ export class ConditionalHttpClient {
private readonly preconditionsMap: p.PreconditionMap = {} as p.PreconditionMap;
private readonly conditions: p.HttpPreconditions[];

constructor(cacheService: CacheService, logService: LogService, private getToken: (context: FunctionAppContext) => Observable<string>, ...defaultConditions: p.HttpPreconditions[]) {
constructor(injector: Injector, private getToken: (resourceId: string) => Observable<string>, ...defaultConditions: p.HttpPreconditions[]) {

this.conditions = defaultConditions;

this.preconditionsMap['NoClientCertificate'] = new p.NoClientCertificatePrecondition(cacheService, logService);
this.preconditionsMap['NotOverQuota'] = new p.NotOverQuotaPrecondition(cacheService, logService);
this.preconditionsMap['NotStopped'] = new p.NotStoppedPrecondition(cacheService, logService);
this.preconditionsMap['ReachableLoadballancer'] = new p.ReachableLoadballancerPrecondition(cacheService, logService);
this.preconditionsMap['RuntimeAvailable'] = new p.RuntimeAvailablePrecondition(cacheService, logService, getToken);
this.preconditionsMap['NoClientCertificate'] = new p.NoClientCertificatePrecondition(injector);
this.preconditionsMap['NotOverQuota'] = new p.NotOverQuotaPrecondition(injector);
this.preconditionsMap['NotStopped'] = new p.NotStoppedPrecondition(injector);
this.preconditionsMap['ReachableLoadballancer'] = new p.ReachableLoadballancerPrecondition(injector);
this.preconditionsMap['RuntimeAvailable'] = new p.RuntimeAvailablePrecondition(injector, getToken);
}

execute<T>(context: FunctionAppContext, query: Query<T>, executeOptions?: ExecuteOptions) {
return this.executeWithConditions(this.conditions, context, query, executeOptions);
execute<T>(input: p.PreconditionInput, query: Query<T>, executeOptions?: ExecuteOptions) {
return this.executeWithConditions(this.conditions, input, query, executeOptions);
}

executeWithConditions<T>(preconditions: p.HttpPreconditions[], context: FunctionAppContext, query: Query<T>, executeOptions?: ExecuteOptions): Observable<FunctionAppHttpResult<T>> {
executeWithConditions<T>(
preconditions: p.HttpPreconditions[],
input: p.PreconditionInput,
query: Query<T>,
executeOptions?: ExecuteOptions): Observable<HttpResult<T>> {

const errorMapper = (error: p.PreconditionResult) => Observable.of({
isSuccessful: false,
error: {
errorId: error.errorId
errorId: error.errorId,
result: error
},
result: null
});

const observableQuery = typeof query === 'function'
? this.getToken(context).take(1).concatMap(t => query(t))
? this.getToken(input.resourceId).take(1).concatMap(t => query(t))
: query;

const successMapper = () => observableQuery
Expand All @@ -55,20 +59,65 @@ export class ConditionalHttpClient {
error: null,
result: r
}))
.catch((e: ErrorId) => Observable.of({
isSuccessful: false,
error: {
errorId: e
},
result: null
}));

return preconditions.length > 0
? Observable.forkJoin(preconditions
.map(i => this.preconditionsMap[i])
.map(i => context ? i.check(context) : Observable.of({ conditionMet: true, errorId: null })))
.catch((e: any) => {

return Observable.of({
isSuccessful: false,
error: this._getErrorObj(e),
result: null
});
});

if (preconditions.length > 0) {
const checkPreconditions = Observable.forkJoin(
preconditions
.map(i => this.preconditionsMap[i])
.map(i => input ? i.check(input) : Observable.of({ conditionMet: true, errorId: null })));

return checkPreconditions
.map(preconditionResults => preconditionResults.find(r => !r.conditionMet))
.concatMap(maybeError => maybeError ? errorMapper(maybeError) : successMapper())
: successMapper();
.concatMap(failedPreconditionResult =>
failedPreconditionResult
? errorMapper(failedPreconditionResult)
: successMapper());
} else {
return successMapper();
}
}

// We have no idea what kind of observable will be failing, so we make a best
// effort to come up with some kind of useful error.
private _getErrorObj(e: any) {
let mesg: string;
let errorId = '/errors/unknown-error';

if (typeof e === 'string') {
mesg = e;
} else {
const httpError = e as HttpErrorResponse<ArmError>;
let body: ArmError;
if (httpError.json) {
body = httpError.json();
if (body && body.error) {
mesg = body.error.message;

if (httpError.status === 401) {
errorId = errorIds.armErrors.noAccess;
} else if (httpError.status === 409 && body.error.code === 'ScopeLocked') {
errorId = errorIds.armErrors.scopeLocked;
}
}
}

if (!mesg && httpError.statusText && httpError.url) {
mesg = `${httpError.statusText} - ${httpError.url}`;
}
}

return <HttpError> {
errorId: errorId,
message: mesg,
result: e
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export namespace errorIds {
export const unreachableInternalLoadBalancer = '/errors/preconditions/unreachableInternalLoadBalancer';
export const runtimeIsNotAvailable = '/errors/preconditions/runtimeIsNotAvailable';
export const runtimeHttpNotAvailable = '/errors/preconditions/runtimeHttpNotAvailable';
export const noFunctionAppContext = '/errors/preconditions/noFunctionAppContext';
}

export namespace armErrors {
export const noAccess = '/errors/arm/noaccess';
export const scopeLocked = '/errors/arm/scopelocked';
}

export const functionNotFound = '/errors/functionNotFound';
Expand Down

This file was deleted.

30 changes: 30 additions & 0 deletions AzureFunctions.AngularClient/src/app/shared/models/http-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export interface HttpError {
errorId: string;
message?: string;
result?: any;
}

export interface HttpResult<T> {
isSuccessful: boolean;
error: HttpError | null;
result: T | null;
}

export interface ArmError {
error: {
code: 'InvalidAuthenticationTokenTenant' | 'ScopeLocked';
message: string;
}
}

export interface HttpErrorResponse<T> {
message: string;
error: any | null;
ok: boolean;
headers: Headers;
status: number;
statusText: string;
url: string | null;
json(): T;
text(): string;
}
Loading

0 comments on commit 6cafb29

Please sign in to comment.