From 0b1331b5d49409ef2ff73fdf01047b1d8d43ff74 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 22 Mar 2017 15:41:53 -0700 Subject: [PATCH 01/10] Added base dtos --- app/models/dtos/index.ts | 8 ++++++ app/models/dtos/job-create.dto.ts | 26 +++++++++++++++++++ app/models/dtos/metadata.dto.ts | 9 +++++++ app/models/dtos/pool-create.dto.ts | 40 ++++++++++++++++++++++++++++++ app/models/dtos/task-create.dto.ts | 26 +++++++++++++++++++ app/services/job-service.ts | 3 ++- app/services/pool-service.ts | 3 ++- app/services/task-service.ts | 3 ++- 8 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 app/models/dtos/index.ts create mode 100644 app/models/dtos/job-create.dto.ts create mode 100644 app/models/dtos/metadata.dto.ts create mode 100644 app/models/dtos/pool-create.dto.ts create mode 100644 app/models/dtos/task-create.dto.ts diff --git a/app/models/dtos/index.ts b/app/models/dtos/index.ts new file mode 100644 index 0000000000..1ce65e709a --- /dev/null +++ b/app/models/dtos/index.ts @@ -0,0 +1,8 @@ +/** + * Dto(Data transfer object) + * This folder is used for interfaces used for creating/updating entities + */ +export * from "./pool-create.dto"; +export * from "./job-create.dto"; +export * from "./task-create.dto"; +export * from "./metadata.dto"; diff --git a/app/models/dtos/job-create.dto.ts b/app/models/dtos/job-create.dto.ts new file mode 100644 index 0000000000..4d8ac80f92 --- /dev/null +++ b/app/models/dtos/job-create.dto.ts @@ -0,0 +1,26 @@ +import { EnvironmentSetting, MetaDataDto } from "./metadata.dto"; +import { PoolCreateDto } from "./pool-create.dto"; + +export interface JobCreateDto { + id: string; + displayName?: string; + priority?: number; + constraints?: any; + jobManagerTask?: any; + jobPreparationTask?: any; + jobReleaseTask?: any; + commonEnvironmentSettings?: EnvironmentSetting[]; + poolInfo: { + poolId?: string; + autoPoolSpecification: { + autoPoolIdPrefix?: string; + poolLifetimeOption?: "job" | "jobschedule"; + keepAlive?: boolean; + pool?: PoolCreateDto; + }; + }; + usesTaskDependencies?: boolean; + onAllTasksComplete?: string; + onTaskFailure?: string; + metadata?: MetaDataDto[]; +} diff --git a/app/models/dtos/metadata.dto.ts b/app/models/dtos/metadata.dto.ts new file mode 100644 index 0000000000..44ea43eaab --- /dev/null +++ b/app/models/dtos/metadata.dto.ts @@ -0,0 +1,9 @@ +export interface MetaDataDto { + name: string; + value: string; +} + +export interface EnvironmentSetting { + name: string; + value: string; +} diff --git a/app/models/dtos/pool-create.dto.ts b/app/models/dtos/pool-create.dto.ts new file mode 100644 index 0000000000..b40f7b10df --- /dev/null +++ b/app/models/dtos/pool-create.dto.ts @@ -0,0 +1,40 @@ +import { MetaDataDto } from "./metadata.dto"; + +export interface PoolCreateDto { + id: string; + displayName?: string; + vmSize?: string; + cloudServiceConfiguration?: { + osFamily: string; + targetOSVersion?: string; + }; + virtualMachineConfiguration?: { + nodeAgentSKUId: string; + imageReference: { + publisher: string; + offer: string; + sku: string; + version?: string; + } + windowsConfiguration?: { + enableAutomaticUpdates?: boolean; + } + }; + networkConfiguration?: { + subnetId: string; + }; + resizeTimeout?: moment.Duration; + targetDedicated?: number; + maxTasksPerNode?: number; + taskSchedulingPolicy?: { + nodeFillType?: string; + }; + autoScaleFormula?: string; + autoScaleEvaluationInterval?: moment.Duration; + enableAutoScale?: boolean; + enableInterNodeCommunication?: boolean; + startTask?: any; + certificateReferences?: any[]; + applicationPackageReferences: any[]; + metadata: MetaDataDto[]; +} diff --git a/app/models/dtos/task-create.dto.ts b/app/models/dtos/task-create.dto.ts new file mode 100644 index 0000000000..801b577e0e --- /dev/null +++ b/app/models/dtos/task-create.dto.ts @@ -0,0 +1,26 @@ +import { EnvironmentSetting } from "./metadata.dto"; + +export interface TaskCreateDto { + id: string; + displayName?: string; + commandLine: string; + resourceFiles?: ResourceFileDto[]; + applicationPackageReferences?: ApplicationPackageReferenceDto[]; + environmentSettings?: EnvironmentSetting[]; + affinityInfo?: any; + runElevated?: boolean; + multiInstanceSettings?: any; + dependsOn?: any; + exitConditions?: any; +} + +export interface ResourceFileDto { + blobSource: string; + filePath: string; + fileMode?: string; +} + +export interface ApplicationPackageReferenceDto { + applicationId: string; + version?: string; +} diff --git a/app/services/job-service.ts b/app/services/job-service.ts index 46c992985b..51103408f0 100644 --- a/app/services/job-service.ts +++ b/app/services/job-service.ts @@ -6,6 +6,7 @@ import { log } from "app/utils"; import { BatchClientService } from "./batch-client.service"; import { DataCache, RxBatchEntityProxy, RxBatchListProxy, RxEntityProxy, RxListProxy, getOnceProxy } from "./core"; import { ServiceBase } from "./service-base"; +import { JobCreateDto } from "app/models/dtos"; export interface JobParams { id?: string; @@ -73,7 +74,7 @@ export class JobService extends ServiceBase { return this.callBatchClient((client) => client.job.enable(jobId, options)); } - public add(job: any, options: any = {}): Observable<{}> { + public add(job: JobCreateDto, options: any = {}): Observable<{}> { return this.callBatchClient((client) => client.job.add(job, options)); } } diff --git a/app/services/pool-service.ts b/app/services/pool-service.ts index 298df285e3..2ef26cab70 100644 --- a/app/services/pool-service.ts +++ b/app/services/pool-service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@angular/core"; import { Observable, Subject } from "rxjs"; import { Pool } from "app/models"; +import { PoolCreateDto } from "app/models/dtos"; import { log } from "app/utils"; import { BatchClientService } from "./batch-client.service"; import { DataCache, RxBatchEntityProxy, RxBatchListProxy, RxEntityProxy, RxListProxy, getOnceProxy } from "./core"; @@ -30,7 +31,7 @@ export class PoolService extends ServiceBase { return this._basicProperties; } - public add(pool: any, options: any = {}): Observable { + public add(pool: PoolCreateDto, options: any = {}): Observable { return this.callBatchClient((client) => client.pool.add(pool, options), (error) => { log.error("Error adding pool", Object.assign({}, error)); }); diff --git a/app/services/task-service.ts b/app/services/task-service.ts index 5f96f05101..1f8edf674d 100644 --- a/app/services/task-service.ts +++ b/app/services/task-service.ts @@ -16,6 +16,7 @@ import { getOnceProxy, } from "./core"; import { CommonListOptions, ServiceBase } from "./service-base"; +import { TaskCreateDto } from "app/models/dtos"; export interface TaskListParams { jobId?: string; @@ -136,7 +137,7 @@ export class TaskService extends ServiceBase { }); } - public add(jobId: string, task: any, options: any): Observable<{}> { + public add(jobId: string, task: TaskCreateDto, options: any): Observable<{}> { return this.callBatchClient((client) => client.task.add(jobId, task, options)); } From 980a0a0929e5d44007d319b5e43721b50dd67483 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 22 Mar 2017 17:30:06 -0700 Subject: [PATCH 02/10] DTO base class start --- .../add/pool-create-basic-dialog.component.ts | 19 ++- .../action/add/pool-create-basic-dialog.html | 4 +- .../pool/details/pool-details.component.ts | 2 +- app/core/dynamic-form.ts | 24 +++ app/core/index.ts | 1 + app/models/dtos/pool-create.dto.ts | 154 +++++++++++++++--- app/models/forms/create-pool-model.ts | 12 +- 7 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 app/core/dynamic-form.ts create mode 100644 app/core/index.ts diff --git a/app/components/pool/action/add/pool-create-basic-dialog.component.ts b/app/components/pool/action/add/pool-create-basic-dialog.component.ts index bb44c1dc8f..370df03be5 100644 --- a/app/components/pool/action/add/pool-create-basic-dialog.component.ts +++ b/app/components/pool/action/add/pool-create-basic-dialog.component.ts @@ -5,7 +5,9 @@ import { Observable } from "rxjs"; import { NotificationService } from "app/components/base/notifications"; import { SidebarRef } from "app/components/base/sidebar"; +import { DynamicForm } from "app/core"; import { Pool } from "app/models"; +import { PoolCreateDto } from "app/models/dtos"; import { createPoolToData, poolToFormModel } from "app/models/forms"; import { PoolService } from "app/services"; @@ -13,7 +15,7 @@ import { PoolService } from "app/services"; selector: "bl-pool-create-basic-dialog", templateUrl: "pool-create-basic-dialog.html", }) -export class PoolCreateBasicDialogComponent { +export class PoolCreateBasicDialogComponent extends DynamicForm { public selectedOsConfiguration: string; public createPoolForm: FormGroup; @@ -27,9 +29,10 @@ export class PoolCreateBasicDialogComponent { public sidebarRef: SidebarRef, private poolService: PoolService, private notificationService: NotificationService) { + super(PoolCreateDto); this.selectedOsConfiguration = this.OS_CONFIGURATION_TYPES.PaaS; - this.createPoolForm = this.formBuilder.group({ + this.form = this.formBuilder.group({ id: ["", [ Validators.required, Validators.maxLength(64), @@ -46,8 +49,8 @@ export class PoolCreateBasicDialogComponent { @autobind() public submit(): Observable { - const id = this.createPoolForm.value.id; - const data = createPoolToData(this.createPoolForm.value); + const id = this.form.value.id; + const data = this.getCurrentValue(); const obs = this.poolService.add(data); obs.do(() => { this.poolService.onPoolAdded.next(id); @@ -61,8 +64,12 @@ export class PoolCreateBasicDialogComponent { return this.getVmSizes(); } - public setValue(pool: Pool) { - this.createPoolForm.patchValue(poolToFormModel(pool)); + public dtoToForm(pool: PoolCreateDto) { + return poolToFormModel(pool); + } + + public formToDto(data: any): PoolCreateDto { + return createPoolToData(data); } // TODO: Make this into it's own component diff --git a/app/components/pool/action/add/pool-create-basic-dialog.html b/app/components/pool/action/add/pool-create-basic-dialog.html index 9c21ecd4d0..44ec1c1360 100644 --- a/app/components/pool/action/add/pool-create-basic-dialog.html +++ b/app/components/pool/action/add/pool-create-basic-dialog.html @@ -1,8 +1,8 @@

Add a pool to the account

Pools allow you to provision compute nodes to do work

- -
+ +
diff --git a/app/components/pool/details/pool-details.component.ts b/app/components/pool/details/pool-details.component.ts index a5fa76e787..7a5cb81654 100644 --- a/app/components/pool/details/pool-details.component.ts +++ b/app/components/pool/details/pool-details.component.ts @@ -88,7 +88,7 @@ export class PoolDetailsComponent implements OnInit, OnDestroy { public clonePool() { const ref = this.sidebarManager.open("add-basic-pool", PoolCreateBasicDialogComponent); - ref.component.setValue(this.pool); + ref.component.setValueFromEntity(this.pool); } public resizePool() { diff --git a/app/core/dynamic-form.ts b/app/core/dynamic-form.ts new file mode 100644 index 0000000000..c974881dc2 --- /dev/null +++ b/app/core/dynamic-form.ts @@ -0,0 +1,24 @@ +import { Type } from "@angular/core"; +import { FormGroup } from "@angular/forms"; + +export abstract class DynamicForm { + public originalData: TDto; + public form: FormGroup; + + constructor(private dtoType: Type) { } + public setValue(value: TDto) { + this.originalData = value; + this.form.patchValue(this.dtoToForm(value)); + } + + public setValueFromEntity(value: TEntity) { + this.setValue(new this.dtoType((value as any).toJS())); + } + + public getCurrentValue(): TDto { + return Object.assign({}, this.originalData, this.formToDto(this.form.value)); + } + + public abstract formToDto(value: any): TDto; + public abstract dtoToForm(dto: TDto): any; +} diff --git a/app/core/index.ts b/app/core/index.ts new file mode 100644 index 0000000000..eb2cf46edd --- /dev/null +++ b/app/core/index.ts @@ -0,0 +1 @@ +export * from "./dynamic-form" diff --git a/app/models/dtos/pool-create.dto.ts b/app/models/dtos/pool-create.dto.ts index b40f7b10df..703cc7dbb1 100644 --- a/app/models/dtos/pool-create.dto.ts +++ b/app/models/dtos/pool-create.dto.ts @@ -1,14 +1,94 @@ +import { Type } from "@angular/core"; + import { MetaDataDto } from "./metadata.dto"; -export interface PoolCreateDto { - id: string; - displayName?: string; - vmSize?: string; - cloudServiceConfiguration?: { - osFamily: string; - targetOSVersion?: string; +type AttrOf = { + [P in keyof T]: T[P]; +}; + +export class DtoBase { + constructor(data: AttrOf) { + const attrs = this._attrMetadata; + console.log("Assign data", data); + for (let key of Object.keys(attrs)) { + const type = attrs[key]; + if (!(key in data)) { + continue; + } + const value = (data as any)[key]; + console.log("Assign value for", key, value, type); + if (type) { + this[key] = new type(value); + } else { + this[key] = value; + } + } + console.log("this", this); + } + + public toJS(): AttrOf { + let output = {}; + const attrs = this._attrMetadata; + for (let key of Object.keys(attrs)) { + if (!(key in this)) { + continue; + } + const value = this[key]; + if (value.toJS) { + output[key] = value.toJS(); + } else { + output = value; + } + } + return output; + } + + private get _metadata() { + return (this.constructor as any)._metadata || {}; + } + + private get _attrMetadata() { + return this._metadata.attrs || {}; + } +} + +function DtoAttr(attrType: Type = null) { + return (cls, attr, other?) => { + // console.log(cls, attr, other); + const ctr = cls.constructor; + if (!ctr._metadata) { + ctr._metadata = {}; + } + if (!ctr._metadata.attrs) { + ctr._metadata.attrs = {}; + } + ctr._metadata.attrs[attr] = attrType; }; - virtualMachineConfiguration?: { +} + +export class CloudServiceConfiguration extends DtoBase { + @DtoAttr() + public osFamily: string; + + @DtoAttr() + public targetOSVersion?: string; +} + +export class PoolCreateDto extends DtoBase { + @DtoAttr() + public id: string; + + @DtoAttr() + public displayName?: string; + + @DtoAttr() + public vmSize?: string; + + @DtoAttr(CloudServiceConfiguration) + public cloudServiceConfiguration?: CloudServiceConfiguration; + + @DtoAttr() + public virtualMachineConfiguration?: { nodeAgentSKUId: string; imageReference: { publisher: string; @@ -20,21 +100,53 @@ export interface PoolCreateDto { enableAutomaticUpdates?: boolean; } }; - networkConfiguration?: { + + @DtoAttr() + public networkConfiguration?: { subnetId: string; }; - resizeTimeout?: moment.Duration; - targetDedicated?: number; - maxTasksPerNode?: number; - taskSchedulingPolicy?: { + + @DtoAttr() + public resizeTimeout?: moment.Duration; + + @DtoAttr() + public targetDedicated?: number; + + @DtoAttr() + public maxTasksPerNode?: number; + + @DtoAttr() + public taskSchedulingPolicy?: { nodeFillType?: string; }; - autoScaleFormula?: string; - autoScaleEvaluationInterval?: moment.Duration; - enableAutoScale?: boolean; - enableInterNodeCommunication?: boolean; - startTask?: any; - certificateReferences?: any[]; - applicationPackageReferences: any[]; - metadata: MetaDataDto[]; + + @DtoAttr() + public autoScaleFormula?: string; + + @DtoAttr() + public autoScaleEvaluationInterval?: moment.Duration; + + @DtoAttr() + public enableAutoScale?: boolean; + + @DtoAttr() + public enableInterNodeCommunication?: boolean; + + @DtoAttr() + public startTask?: any; + + @DtoAttr() + public certificateReferences?: any[]; + + @DtoAttr() + public applicationPackageReferences: any[]; + + @DtoAttr() + public metadata: MetaDataDto[]; +} + + + +export class VirtualMachineConfiguration extends DtoBase { + } diff --git a/app/models/forms/create-pool-model.ts b/app/models/forms/create-pool-model.ts index 334e8b47f5..7cba1c883e 100644 --- a/app/models/forms/create-pool-model.ts +++ b/app/models/forms/create-pool-model.ts @@ -1,4 +1,4 @@ -import { Pool } from "app/models"; +import { PoolCreateDto } from "app/models/dtos"; export enum PoolOsSources { PaaS, @@ -30,13 +30,13 @@ export interface CreatePoolModel { os: PoolOSPickerModel; } -export function createPoolToData(output: CreatePoolModel): any { +export function createPoolToData(output: CreatePoolModel): PoolCreateDto { let data: any = { id: output.id, displayName: output.displayName, vmSize: output.vmSize, - targetDedicated: output.targetDedicated, - maxTasksPerNode: output.maxTasksPerNode, + targetDedicated: Number(output.targetDedicated), + maxTasksPerNode: Number(output.maxTasksPerNode), enableInterNodeCommunication: output.enableInterNodeCommunication, }; @@ -56,7 +56,7 @@ export function createPoolToData(output: CreatePoolModel): any { * Take an existing pool and create the data for a new form with that pool data. * Used to clone a pool */ -export function poolToFormModel(pool: Pool): CreatePoolModel { +export function poolToFormModel(pool: PoolCreateDto): CreatePoolModel { return { id: pool.id, displayName: pool.displayName, @@ -66,7 +66,7 @@ export function poolToFormModel(pool: Pool): CreatePoolModel { enableInterNodeCommunication: pool.enableInterNodeCommunication, os: { source: pool.cloudServiceConfiguration ? PoolOsSources.PaaS : PoolOsSources.IaaS, - cloudServiceConfiguration: pool.cloudServiceConfiguration, + cloudServiceConfiguration: pool.cloudServiceConfiguration as any, virtualMachineConfiguration: pool.virtualMachineConfiguration, }, }; From bfed612462f39e0ac3bf78b4eb974cc596cf878d Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 23 Mar 2017 08:29:00 -0700 Subject: [PATCH 03/10] Use reflect --- app/models/dtos/pool-create.dto.ts | 44 ++++++++++++------------------ definitions/index.d.ts | 4 +++ 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/app/models/dtos/pool-create.dto.ts b/app/models/dtos/pool-create.dto.ts index 703cc7dbb1..a28906559a 100644 --- a/app/models/dtos/pool-create.dto.ts +++ b/app/models/dtos/pool-create.dto.ts @@ -1,23 +1,20 @@ -import { Type } from "@angular/core"; - import { MetaDataDto } from "./metadata.dto"; -type AttrOf = { - [P in keyof T]: T[P]; -}; +const primitives = new Set(["Array", "Number", "String", "Object", "Boolean"]); + export class DtoBase { constructor(data: AttrOf) { const attrs = this._attrMetadata; - console.log("Assign data", data); + console.log("Assign data", data, attrs); for (let key of Object.keys(attrs)) { const type = attrs[key]; if (!(key in data)) { continue; } const value = (data as any)[key]; - console.log("Assign value for", key, value, type); - if (type) { + console.log("Assign value for", key, value, type.name); + if (type && !primitives.has(type.name)) { this[key] = new type(value); } else { this[key] = value; @@ -27,7 +24,7 @@ export class DtoBase { } public toJS(): AttrOf { - let output = {}; + let output: any = {}; const attrs = this._attrMetadata; for (let key of Object.keys(attrs)) { if (!(key in this)) { @@ -43,26 +40,21 @@ export class DtoBase { return output; } - private get _metadata() { - return (this.constructor as any)._metadata || {}; - } - private get _attrMetadata() { - return this._metadata.attrs || {}; + return Reflect.getMetadata("dto:attrs", this.constructor) || {}; } } -function DtoAttr(attrType: Type = null) { - return (cls, attr, other?) => { - // console.log(cls, attr, other); - const ctr = cls.constructor; - if (!ctr._metadata) { - ctr._metadata = {}; - } - if (!ctr._metadata.attrs) { - ctr._metadata.attrs = {}; - } - ctr._metadata.attrs[attr] = attrType; + +function DtoAttr() { + return (target, attr, descriptor?: TypedPropertyDescriptor) => { + const ctr = target.constructor; + const type = Reflect.getMetadata("design:type", target, attr); + console.log(target, attr, type); + + const metadata = Reflect.getMetadata("dto:attrs", ctr) || {}; + metadata[attr] = type; + Reflect.defineMetadata("dto:attrs", metadata, ctr); }; } @@ -84,7 +76,7 @@ export class PoolCreateDto extends DtoBase { @DtoAttr() public vmSize?: string; - @DtoAttr(CloudServiceConfiguration) + @DtoAttr() public cloudServiceConfiguration?: CloudServiceConfiguration; @DtoAttr() diff --git a/definitions/index.d.ts b/definitions/index.d.ts index ad55986edf..4f82b2a410 100644 --- a/definitions/index.d.ts +++ b/definitions/index.d.ts @@ -32,3 +32,7 @@ declare const ENV: Environment; type StringMap = { [key: string]: V }; + +type AttrOf = { + [P in keyof T]: T[P]; +}; From 22fe13541a91c9bec1c813e2e4e8845b2472e08a Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 23 Mar 2017 09:32:48 -0700 Subject: [PATCH 04/10] Pool&Job dynamic form --- .../add/job-create-basic-dialog.component.ts | 16 ++-- .../job/details/job-details.component.ts | 2 +- app/core/dto.ts | 66 ++++++++++++++ app/core/dynamic-form.ts | 5 +- app/core/index.ts | 1 + .../dtos/cloud-service-configuration.dto.ts | 9 ++ app/models/dtos/job-create.dto.ts | 54 +++++++++--- app/models/dtos/pool-create.dto.ts | 87 ++----------------- app/models/dtos/task-create.dto.ts | 46 +++++++--- .../dtos/virtual-machine-configuration.dto.ts | 19 ++++ app/models/forms/create-job-model.ts | 18 ++-- app/services/job-service.ts | 2 +- app/services/pool-service.ts | 2 +- client/api/batch-client-proxy/poolProxy.ts | 4 - 14 files changed, 196 insertions(+), 135 deletions(-) create mode 100644 app/core/dto.ts create mode 100644 app/models/dtos/cloud-service-configuration.dto.ts create mode 100644 app/models/dtos/virtual-machine-configuration.dto.ts diff --git a/app/components/job/action/add/job-create-basic-dialog.component.ts b/app/components/job/action/add/job-create-basic-dialog.component.ts index 07d2958e09..bf908cd793 100644 --- a/app/components/job/action/add/job-create-basic-dialog.component.ts +++ b/app/components/job/action/add/job-create-basic-dialog.component.ts @@ -6,7 +6,9 @@ import { Observable } from "rxjs"; import { NotificationService } from "app/components/base/notifications"; import { SidebarRef } from "app/components/base/sidebar"; import { RangeValidatorDirective } from "app/components/base/validation"; +import { DynamicForm } from "app/core"; import { Job, Pool } from "app/models"; +import { JobCreateDto } from "app/models/dtos"; import { createJobFormToJsonData, jobToFormModel } from "app/models/forms"; import { JobService, PoolService } from "app/services"; import { RxListProxy } from "app/services/core"; @@ -16,7 +18,7 @@ import { Constants } from "app/utils"; selector: "bl-job-create-basic-dialog", templateUrl: "job-create-basic-dialog.html", }) -export class JobCreateBasicDialogComponent implements OnInit { +export class JobCreateBasicDialogComponent extends DynamicForm implements OnInit { public poolsData: RxListProxy<{}, Pool>; public createJobForm: FormGroup; public constraintsGroup: FormGroup; @@ -28,6 +30,7 @@ export class JobCreateBasicDialogComponent implements OnInit { private jobService: JobService, private poolService: PoolService, private notificationService: NotificationService) { + super(JobCreateDto); this.poolsData = this.poolService.list(); const validation = Constants.forms.validation; @@ -66,14 +69,17 @@ export class JobCreateBasicDialogComponent implements OnInit { }); } - public setValue(job: Job) { - this.createJobForm.patchValue(jobToFormModel(job)); + public dtoToForm(job: JobCreateDto) { + return jobToFormModel(job); + } + + public formToDto(data: any): JobCreateDto { + return createJobFormToJsonData(data); } @autobind() public submit(): Observable { - const jsonData = createJobFormToJsonData(this.createJobForm.value); - const observable = this.jobService.add(jsonData, {}); + const observable = this.jobService.add(this.getCurrentValue(), {}); observable.subscribe({ next: () => { const id = this.createJobForm.value.id; diff --git a/app/components/job/details/job-details.component.ts b/app/components/job/details/job-details.component.ts index 510376757b..05840a2fc3 100644 --- a/app/components/job/details/job-details.component.ts +++ b/app/components/job/details/job-details.component.ts @@ -119,7 +119,7 @@ export class JobDetailsComponent implements OnInit, OnDestroy { public cloneJob() { const ref = this.sidebarManager.open("add-basic-pool", JobCreateBasicDialogComponent); - ref.component.setValue(this.job); + ref.component.setValueFromEntity(this.job); } public enableJob() { diff --git a/app/core/dto.ts b/app/core/dto.ts new file mode 100644 index 0000000000..b2a05aa7f9 --- /dev/null +++ b/app/core/dto.ts @@ -0,0 +1,66 @@ +import { nil } from "app/utils"; + +const primitives = new Set(["Array", "Number", "String", "Object", "Boolean"]); + +const attrMetadataKey = "dto:attrs"; + +/** + * Dto should be the base class for all dto. + * It will only assign defined attributes. + */ +export class Dto { + constructor(data: AttrOf) { + const attrs = this._attrMetadata; + for (let key of Object.keys(attrs)) { + const type = attrs[key]; + if (!(key in data)) { + continue; + } + const value = (data as any)[key]; + if (nil(value)) { + continue; + } + + if (type && !primitives.has(type.name)) { + this[key] = new type(value); + } else { + this[key] = value; + } + } + } + + public toJS(): AttrOf { + let output: any = {}; + const attrs = this._attrMetadata; + for (let key of Object.keys(attrs)) { + if (!(key in this)) { + continue; + } + const value = this[key]; + if (nil(value)) { + continue; + } + if (value.toJS) { + output[key] = value.toJS(); + } else { + output[key] = value; + } + } + return output; + } + + private get _attrMetadata() { + return Reflect.getMetadata(attrMetadataKey, this.constructor) || {}; + } +} + +export function DtoAttr() { + return (target, attr, descriptor?: TypedPropertyDescriptor) => { + const ctr = target.constructor; + const type = Reflect.getMetadata("design:type", target, attr); + + const metadata = Reflect.getMetadata(attrMetadataKey, ctr) || {}; + metadata[attr] = type; + Reflect.defineMetadata(attrMetadataKey, metadata, ctr); + }; +} diff --git a/app/core/dynamic-form.ts b/app/core/dynamic-form.ts index c974881dc2..90b07093a2 100644 --- a/app/core/dynamic-form.ts +++ b/app/core/dynamic-form.ts @@ -1,7 +1,8 @@ import { Type } from "@angular/core"; import { FormGroup } from "@angular/forms"; +import { Dto } from "app/core"; -export abstract class DynamicForm { +export abstract class DynamicForm> { public originalData: TDto; public form: FormGroup; @@ -16,7 +17,7 @@ export abstract class DynamicForm { } public getCurrentValue(): TDto { - return Object.assign({}, this.originalData, this.formToDto(this.form.value)); + return new this.dtoType(Object.assign({}, this.originalData, this.formToDto(this.form.value))); } public abstract formToDto(value: any): TDto; diff --git a/app/core/index.ts b/app/core/index.ts index eb2cf46edd..a11cd7aab0 100644 --- a/app/core/index.ts +++ b/app/core/index.ts @@ -1 +1,2 @@ export * from "./dynamic-form" +export * from "./dto" diff --git a/app/models/dtos/cloud-service-configuration.dto.ts b/app/models/dtos/cloud-service-configuration.dto.ts new file mode 100644 index 0000000000..575f6af950 --- /dev/null +++ b/app/models/dtos/cloud-service-configuration.dto.ts @@ -0,0 +1,9 @@ +import { Dto, DtoAttr } from "app/core"; + +export class CloudServiceConfiguration extends Dto { + @DtoAttr() + public osFamily: string; + + @DtoAttr() + public targetOSVersion?: string; +} diff --git a/app/models/dtos/job-create.dto.ts b/app/models/dtos/job-create.dto.ts index 4d8ac80f92..a73046b356 100644 --- a/app/models/dtos/job-create.dto.ts +++ b/app/models/dtos/job-create.dto.ts @@ -1,16 +1,34 @@ +import { Dto, DtoAttr } from "app/core"; import { EnvironmentSetting, MetaDataDto } from "./metadata.dto"; import { PoolCreateDto } from "./pool-create.dto"; -export interface JobCreateDto { - id: string; - displayName?: string; - priority?: number; - constraints?: any; - jobManagerTask?: any; - jobPreparationTask?: any; - jobReleaseTask?: any; - commonEnvironmentSettings?: EnvironmentSetting[]; - poolInfo: { +export class JobCreateDto extends Dto { + @DtoAttr() + public id: string; + + @DtoAttr() + public displayName?: string; + + @DtoAttr() + public priority?: number; + + @DtoAttr() + public constraints?: any; + + @DtoAttr() + public jobManagerTask?: any; + + @DtoAttr() + public jobPreparationTask?: any; + + @DtoAttr() + public jobReleaseTask?: any; + + @DtoAttr() + public commonEnvironmentSettings?: EnvironmentSetting[]; + + @DtoAttr() + public poolInfo: { poolId?: string; autoPoolSpecification: { autoPoolIdPrefix?: string; @@ -19,8 +37,16 @@ export interface JobCreateDto { pool?: PoolCreateDto; }; }; - usesTaskDependencies?: boolean; - onAllTasksComplete?: string; - onTaskFailure?: string; - metadata?: MetaDataDto[]; + + @DtoAttr() + public usesTaskDependencies?: boolean; + + @DtoAttr() + public onAllTasksComplete?: string; + + @DtoAttr() + public onTaskFailure?: string; + + @DtoAttr() + public metadata?: MetaDataDto[]; } diff --git a/app/models/dtos/pool-create.dto.ts b/app/models/dtos/pool-create.dto.ts index a28906559a..4afc8074e8 100644 --- a/app/models/dtos/pool-create.dto.ts +++ b/app/models/dtos/pool-create.dto.ts @@ -1,72 +1,11 @@ +import { Dto, DtoAttr } from "app/core"; +import { CloudServiceConfiguration } from "./cloud-service-configuration.dto"; import { MetaDataDto } from "./metadata.dto"; +import { VirtualMachineConfiguration } from "./virtual-machine-configuration.dto"; -const primitives = new Set(["Array", "Number", "String", "Object", "Boolean"]); - - -export class DtoBase { - constructor(data: AttrOf) { - const attrs = this._attrMetadata; - console.log("Assign data", data, attrs); - for (let key of Object.keys(attrs)) { - const type = attrs[key]; - if (!(key in data)) { - continue; - } - const value = (data as any)[key]; - console.log("Assign value for", key, value, type.name); - if (type && !primitives.has(type.name)) { - this[key] = new type(value); - } else { - this[key] = value; - } - } - console.log("this", this); - } - - public toJS(): AttrOf { - let output: any = {}; - const attrs = this._attrMetadata; - for (let key of Object.keys(attrs)) { - if (!(key in this)) { - continue; - } - const value = this[key]; - if (value.toJS) { - output[key] = value.toJS(); - } else { - output = value; - } - } - return output; - } - - private get _attrMetadata() { - return Reflect.getMetadata("dto:attrs", this.constructor) || {}; - } -} - - -function DtoAttr() { - return (target, attr, descriptor?: TypedPropertyDescriptor) => { - const ctr = target.constructor; - const type = Reflect.getMetadata("design:type", target, attr); - console.log(target, attr, type); - - const metadata = Reflect.getMetadata("dto:attrs", ctr) || {}; - metadata[attr] = type; - Reflect.defineMetadata("dto:attrs", metadata, ctr); - }; -} -export class CloudServiceConfiguration extends DtoBase { - @DtoAttr() - public osFamily: string; - @DtoAttr() - public targetOSVersion?: string; -} - -export class PoolCreateDto extends DtoBase { +export class PoolCreateDto extends Dto { @DtoAttr() public id: string; @@ -80,18 +19,7 @@ export class PoolCreateDto extends DtoBase { public cloudServiceConfiguration?: CloudServiceConfiguration; @DtoAttr() - public virtualMachineConfiguration?: { - nodeAgentSKUId: string; - imageReference: { - publisher: string; - offer: string; - sku: string; - version?: string; - } - windowsConfiguration?: { - enableAutomaticUpdates?: boolean; - } - }; + public virtualMachineConfiguration?: VirtualMachineConfiguration; @DtoAttr() public networkConfiguration?: { @@ -137,8 +65,3 @@ export class PoolCreateDto extends DtoBase { public metadata: MetaDataDto[]; } - - -export class VirtualMachineConfiguration extends DtoBase { - -} diff --git a/app/models/dtos/task-create.dto.ts b/app/models/dtos/task-create.dto.ts index 801b577e0e..8b7f3989db 100644 --- a/app/models/dtos/task-create.dto.ts +++ b/app/models/dtos/task-create.dto.ts @@ -1,17 +1,39 @@ +import { Dto, DtoAttr } from "app/core"; import { EnvironmentSetting } from "./metadata.dto"; -export interface TaskCreateDto { - id: string; - displayName?: string; - commandLine: string; - resourceFiles?: ResourceFileDto[]; - applicationPackageReferences?: ApplicationPackageReferenceDto[]; - environmentSettings?: EnvironmentSetting[]; - affinityInfo?: any; - runElevated?: boolean; - multiInstanceSettings?: any; - dependsOn?: any; - exitConditions?: any; +export class TaskCreateDto extends Dto { + @DtoAttr() + public id: string; + + @DtoAttr() + public displayName?: string; + + @DtoAttr() + public commandLine: string; + + @DtoAttr() + public resourceFiles?: ResourceFileDto[]; + + @DtoAttr() + public applicationPackageReferences?: ApplicationPackageReferenceDto[]; + + @DtoAttr() + public environmentSettings?: EnvironmentSetting[]; + + @DtoAttr() + public affinityInfo?: any; + + @DtoAttr() + public runElevated?: boolean; + + @DtoAttr() + public multiInstanceSettings?: any; + + @DtoAttr() + public dependsOn?: any; + + @DtoAttr() + public exitConditions?: any; } export interface ResourceFileDto { diff --git a/app/models/dtos/virtual-machine-configuration.dto.ts b/app/models/dtos/virtual-machine-configuration.dto.ts new file mode 100644 index 0000000000..c39ee8cec5 --- /dev/null +++ b/app/models/dtos/virtual-machine-configuration.dto.ts @@ -0,0 +1,19 @@ +import { Dto, DtoAttr } from "app/core"; + +export class VirtualMachineConfiguration extends Dto { + @DtoAttr() + public nodeAgentSKUId: string; + + @DtoAttr() + public imageReference: { + publisher: string; + offer: string; + sku: string; + version?: string; + }; + + @DtoAttr() + public windowsConfiguration?: { + enableAutomaticUpdates?: boolean; + }; +} diff --git a/app/models/forms/create-job-model.ts b/app/models/forms/create-job-model.ts index adf32e9bbb..bb8cb7e22e 100644 --- a/app/models/forms/create-job-model.ts +++ b/app/models/forms/create-job-model.ts @@ -1,4 +1,5 @@ -import { Job } from "app/models"; + +import { JobCreateDto } from "app/models/dtos"; export interface JobConstraintsModel { maxWallClockTime: string; @@ -6,7 +7,7 @@ export interface JobConstraintsModel { } export interface PoolInfoModel { - poolId: string; + poolId?: string; } export interface CreateJobModel { @@ -18,7 +19,7 @@ export interface CreateJobModel { jobPreparationTask: any; jobReleaseTask: any; commonEnvironmentSettings: any; - poolInfo: PoolInfoModel; + poolInfo?: PoolInfoModel; onAllTasksComplete: string; onTaskFailure: string; metadata: any; @@ -35,24 +36,15 @@ export function createJobFormToJsonData(formData: CreateJobModel): any { maxWallClockTime: maxWallClockTime, maxTaskRetryCount: formData.constraints.maxTaskRetryCount, }, - jobManagerTask: null, - jobPreparationTask: null, - jobReleaseTask: null, - commonEnvironmentSettings: null, poolInfo: { poolId: formData.poolInfo.poolId, - autoPoolSpecification: null, }, - onAllTasksComplete: null, - onTaskFailure: null, - metadata: null, - usesTaskDependencies: false, }; return data; } -export function jobToFormModel(job: Job): CreateJobModel { +export function jobToFormModel(job: JobCreateDto): CreateJobModel { return { id: job.id, displayName: job.displayName, diff --git a/app/services/job-service.ts b/app/services/job-service.ts index 51103408f0..6853bd8fad 100644 --- a/app/services/job-service.ts +++ b/app/services/job-service.ts @@ -75,6 +75,6 @@ export class JobService extends ServiceBase { } public add(job: JobCreateDto, options: any = {}): Observable<{}> { - return this.callBatchClient((client) => client.job.add(job, options)); + return this.callBatchClient((client) => client.job.add(job.toJS(), options)); } } diff --git a/app/services/pool-service.ts b/app/services/pool-service.ts index 2ef26cab70..befd6665e6 100644 --- a/app/services/pool-service.ts +++ b/app/services/pool-service.ts @@ -32,7 +32,7 @@ export class PoolService extends ServiceBase { } public add(pool: PoolCreateDto, options: any = {}): Observable { - return this.callBatchClient((client) => client.pool.add(pool, options), (error) => { + return this.callBatchClient((client) => client.pool.add(pool.toJS(), options), (error) => { log.error("Error adding pool", Object.assign({}, error)); }); } diff --git a/client/api/batch-client-proxy/poolProxy.ts b/client/api/batch-client-proxy/poolProxy.ts index 2733291388..44d41e7bdc 100644 --- a/client/api/batch-client-proxy/poolProxy.ts +++ b/client/api/batch-client-proxy/poolProxy.ts @@ -84,10 +84,6 @@ export default class PoolProxy { * @param options: Optional Parameters. */ public add(pool: any, options?: any) { - pool.enableAutoScale = true; - pool.autoScaleFormula = "$targetDedicated=0;"; - delete pool.targetDedicated; - pool.autoScaleEvaluationInterval = moment.duration({ days: 1 }); return new Promise((resolve, reject) => { this.client.pool.add(pool, { poolAddOptions: options }, (error, result) => { if (error) { return reject(error); } From 9ed56a2aa2e693597d51fd2c1163c66f7ca1b6e9 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 23 Mar 2017 09:42:45 -0700 Subject: [PATCH 05/10] Task create dto --- .../base/form/form-base.component.ts | 40 ------------------- .../add/job-create-basic-dialog.component.ts | 12 +++--- .../add/task-create-basic-dialog.component.ts | 32 +++++++++------ .../task/details/task-details.component.ts | 2 +- app/models/forms/create-task-model.ts | 4 +- 5 files changed, 28 insertions(+), 62 deletions(-) delete mode 100644 app/components/base/form/form-base.component.ts diff --git a/app/components/base/form/form-base.component.ts b/app/components/base/form/form-base.component.ts deleted file mode 100644 index ba48786d2a..0000000000 --- a/app/components/base/form/form-base.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { FormGroup } from "@angular/forms"; -import { autobind } from "core-decorators"; -import { Observable, Subject } from "rxjs"; - -import { FormUtils } from "app/utils"; - -/** - * Base component forms should inherit which adds a basic structure for it - */ -export abstract class FormBaseComponent { - public form: FormGroup; - - public beforeSubmit = new Subject(); - public afterSubmit = new Subject(); - - @autobind() - public submit() { - this.beforeSubmit.next(true); - const obs = this.execute(); - obs.subscribe({ - next: () => { - this.afterSubmit.next(true); - }, - error: () => null, - }); - return obs; - } - - public patchValue(value: TEntity) { - this.form.patchValue(this.entityToForm(value)); - } - - public disable(path: string | string[]) { - const control = FormUtils.getControl(this.form, path); - control.disable(); - } - - protected abstract entityToForm(entity: TEntity): TFormModel; - protected abstract execute(): Observable; -} diff --git a/app/components/job/action/add/job-create-basic-dialog.component.ts b/app/components/job/action/add/job-create-basic-dialog.component.ts index bf908cd793..a7d241f901 100644 --- a/app/components/job/action/add/job-create-basic-dialog.component.ts +++ b/app/components/job/action/add/job-create-basic-dialog.component.ts @@ -20,7 +20,6 @@ import { Constants } from "app/utils"; }) export class JobCreateBasicDialogComponent extends DynamicForm implements OnInit { public poolsData: RxListProxy<{}, Pool>; - public createJobForm: FormGroup; public constraintsGroup: FormGroup; public poolInfoGroup: FormGroup; @@ -45,7 +44,7 @@ export class JobCreateBasicDialogComponent extends DynamicForm { if (result.data && result.data.length > 0) { - this.createJobForm.value.poolId = result.data[0].id; + this.form.value.poolId = result.data[0].id; } }); } @@ -79,10 +78,11 @@ export class JobCreateBasicDialogComponent extends DynamicForm { - const observable = this.jobService.add(this.getCurrentValue(), {}); + const job = this.getCurrentValue(); + const observable = this.jobService.add(job, {}); observable.subscribe({ next: () => { - const id = this.createJobForm.value.id; + const id = job.id; this.jobService.onJobAdded.next(id); this.notificationService.success("Job added!", `Job '${id}' was created successfully!`); }, @@ -93,6 +93,6 @@ export class JobCreateBasicDialogComponent extends DynamicForm { +export class TaskCreateBasicDialogComponent extends DynamicForm { public jobId: string; public constraintsGroup: FormGroup; public resourceFiles: FormArray; @@ -31,7 +32,7 @@ export class TaskCreateBasicDialogComponent extends FormBaseComponent, protected taskService: TaskService, private notificationService: NotificationService) { - super(); + super(TaskCreateDto); const validation = Constants.forms.validation; this.constraintsGroup = this.formBuilder.group({ @@ -55,13 +56,22 @@ export class TaskCreateBasicDialogComponent extends FormBaseComponent { - const value = this.form.getRawValue(); - const id = value.id; - const jsonData = createTaskFormToJsonData(value); + public dtoToForm(task: TaskCreateDto) { + return taskToFormModel(task); + } + + public formToDto(data: any): TaskCreateDto { + return createTaskFormToJsonData(data); + } + + @autobind() + public submit(): Observable { + const task = this.getCurrentValue(); + const id = task.id; + const onAddedParams = { jobId: this.jobId, id }; - const observable = this.taskService.add(this.jobId, jsonData, {}); + const observable = this.taskService.add(this.jobId, task, {}); observable.subscribe({ next: () => { this.notificationService.success("Task added!", `Task '${id}' was created successfully!`); @@ -72,8 +82,4 @@ export class TaskCreateBasicDialogComponent extends FormBaseComponent Date: Thu, 23 Mar 2017 10:29:46 -0700 Subject: [PATCH 06/10] fixes --- app/components/base/form/index.ts | 1 - .../action/add/job-create-basic-dialog.html | 4 +-- .../action/add/rerun-task-form.component.ts | 2 +- .../add/task-create-basic-dialog.component.ts | 2 +- .../details/task-error-display.component.ts | 2 +- app/core/dto.ts | 2 ++ app/core/dynamic-form.ts | 6 ++++ app/models/dtos/task-create.dto.ts | 9 ++++++ app/models/forms/create-task-model.ts | 32 +++++++++++++------ app/services/task-service.ts | 3 +- tsconfig.json | 3 +- 11 files changed, 47 insertions(+), 19 deletions(-) diff --git a/app/components/base/form/index.ts b/app/components/base/form/index.ts index 9ae9a6f1f4..27c63c7d2f 100644 --- a/app/components/base/form/index.ts +++ b/app/components/base/form/index.ts @@ -1,2 +1 @@ export * from "./form.module"; -export * from "./form-base.component"; diff --git a/app/components/job/action/add/job-create-basic-dialog.html b/app/components/job/action/add/job-create-basic-dialog.html index e1a13324ac..898a0c78fb 100644 --- a/app/components/job/action/add/job-create-basic-dialog.html +++ b/app/components/job/action/add/job-create-basic-dialog.html @@ -2,8 +2,8 @@

Create job

Adds a job to the selected account

- - + +
diff --git a/app/components/task/action/add/rerun-task-form.component.ts b/app/components/task/action/add/rerun-task-form.component.ts index 24535ead09..4030f1c947 100644 --- a/app/components/task/action/add/rerun-task-form.component.ts +++ b/app/components/task/action/add/rerun-task-form.component.ts @@ -32,7 +32,7 @@ export class RerunTaskFormComponent extends TaskCreateBasicDialogComponent { return ObservableUtils.queue( () => this.taskService.delete(this.jobId, id).catch(() => Observable.of({})), - () => super.execute(), + () => super.submit(), ); } } diff --git a/app/components/task/action/add/task-create-basic-dialog.component.ts b/app/components/task/action/add/task-create-basic-dialog.component.ts index 801b71de20..c69d64dd02 100644 --- a/app/components/task/action/add/task-create-basic-dialog.component.ts +++ b/app/components/task/action/add/task-create-basic-dialog.component.ts @@ -1,8 +1,8 @@ import { Component } from "@angular/core"; import { FormArray, FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { autobind } from "core-decorators"; import { Observable } from "rxjs"; -import { autobind } from "@types/core-decorators"; import { NotificationService } from "app/components/base/notifications"; import { SidebarRef } from "app/components/base/sidebar"; import { RangeValidatorDirective } from "app/components/base/validation"; diff --git a/app/components/task/details/task-error-display.component.ts b/app/components/task/details/task-error-display.component.ts index ae85e14c51..ac3b9cf1aa 100644 --- a/app/components/task/details/task-error-display.component.ts +++ b/app/components/task/details/task-error-display.component.ts @@ -57,6 +57,6 @@ export class TaskErrorDisplayComponent { public rerunDifferent() { const ref = this.sidebarManager.open("rerun-task", RerunTaskFormComponent); ref.component.jobId = this.jobId; - ref.component.patchValue(this.task); + ref.component.setValueFromEntity(this.task); } } diff --git a/app/core/dto.ts b/app/core/dto.ts index b2a05aa7f9..814b0f99a6 100644 --- a/app/core/dto.ts +++ b/app/core/dto.ts @@ -10,6 +10,7 @@ const attrMetadataKey = "dto:attrs"; */ export class Dto { constructor(data: AttrOf) { + console.log("Setting dto", data); const attrs = this._attrMetadata; for (let key of Object.keys(attrs)) { const type = attrs[key]; @@ -27,6 +28,7 @@ export class Dto { this[key] = value; } } + console.log("set dto", this); } public toJS(): AttrOf { diff --git a/app/core/dynamic-form.ts b/app/core/dynamic-form.ts index 90b07093a2..75a355a840 100644 --- a/app/core/dynamic-form.ts +++ b/app/core/dynamic-form.ts @@ -1,6 +1,7 @@ import { Type } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { Dto } from "app/core"; +import { FormUtils } from "app/utils"; export abstract class DynamicForm> { public originalData: TDto; @@ -12,6 +13,11 @@ export abstract class DynamicForm> { this.form.patchValue(this.dtoToForm(value)); } + public disable(path: string | string[]) { + const control = FormUtils.getControl(this.form, path); + control.disable(); + } + public setValueFromEntity(value: TEntity) { this.setValue(new this.dtoType((value as any).toJS())); } diff --git a/app/models/dtos/task-create.dto.ts b/app/models/dtos/task-create.dto.ts index 8b7f3989db..bd4cc4093e 100644 --- a/app/models/dtos/task-create.dto.ts +++ b/app/models/dtos/task-create.dto.ts @@ -23,6 +23,9 @@ export class TaskCreateDto extends Dto { @DtoAttr() public affinityInfo?: any; + @DtoAttr() + public constraints?: ConstraintDto; + @DtoAttr() public runElevated?: boolean; @@ -46,3 +49,9 @@ export interface ApplicationPackageReferenceDto { applicationId: string; version?: string; } + +export interface ConstraintDto { + maxWallClockTime?: moment.Duration; + maxTaskRetryCount?: number; + retentionTime?: moment.Duration; +} diff --git a/app/models/forms/create-task-model.ts b/app/models/forms/create-task-model.ts index 48ced751ab..cd891b0c9a 100644 --- a/app/models/forms/create-task-model.ts +++ b/app/models/forms/create-task-model.ts @@ -18,7 +18,6 @@ export interface CreateTaskModel { runElevated: boolean; multiInstanceSettings: any; applicationPackageReferences: any[]; - dependsOn: any; } export function createTaskFormToJsonData(formData: CreateTaskModel): any { @@ -37,17 +36,18 @@ export function createTaskFormToJsonData(formData: CreateTaskModel): any { maxTaskRetryCount: formData.constraints.maxTaskRetryCount, retentionTime: retentionTime, }, - runElevated: this.runElevated, + runElevated: formData.runElevated, multiInstanceSettings: null, applicationPackageReferences: null, - dependsOn: null, }; + console.log("Data here...", data.dependsOn); + return data; } export function taskToFormModel(task: TaskCreateDto): CreateTaskModel { - return { + const out: any = { id: task.id, displayName: task.displayName, commandLine: task.commandLine, @@ -55,14 +55,26 @@ export function taskToFormModel(task: TaskCreateDto): CreateTaskModel { resourceFiles: task.resourceFiles, environmentSettings: task.environmentSettings, affinityInfo: task.affinityInfo, - constraints: { - maxWallClockTime: task.constraints.maxWallClockTime.toISOString(), - maxTaskRetryCount: task.constraints.maxTaskRetryCount, - retentionTime: task.constraints.retentionTime.toISOString(), - }, + runElevated: task.runElevated, multiInstanceSettings: task.multiInstanceSettings, applicationPackageReferences: task.applicationPackageReferences, - dependsOn: task.dependsOn, }; + + if (task.constraints) { + out.constraints = { + maxWallClockTime: durationToString(task.constraints.maxWallClockTime), + maxTaskRetryCount: task.constraints.maxTaskRetryCount, + retentionTime: durationToString(task.constraints.retentionTime), + }; + } + return out; +} + +function durationToString(duration: moment.Duration) { + if (duration) { + return (duration as any).toISOString(); + } else { + return null; + } } diff --git a/app/services/task-service.ts b/app/services/task-service.ts index 1f8edf674d..b768578621 100644 --- a/app/services/task-service.ts +++ b/app/services/task-service.ts @@ -138,7 +138,8 @@ export class TaskService extends ServiceBase { } public add(jobId: string, task: TaskCreateDto, options: any): Observable<{}> { - return this.callBatchClient((client) => client.task.add(jobId, task, options)); + console.log("Add task", task.toJS()); + return this.callBatchClient((client) => client.task.add(jobId, task.toJS(), options)); } /** diff --git a/tsconfig.json b/tsconfig.json index e3a9489667..1af1ec7ac1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,8 +32,7 @@ "node", "electron", "jasmine", - "hammerjs", - "moment-duration-format" + "hammerjs" ], "typeRoots": [ "./node_modules/@types" From a5def4b8fe3d4bc744357f1959bcf8e31d963794 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 23 Mar 2017 10:56:42 -0700 Subject: [PATCH 07/10] added specs for dto --- app/core/dto.ts | 19 +++++++------ test/app/core/dto.spec.ts | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 test/app/core/dto.spec.ts diff --git a/app/core/dto.ts b/app/core/dto.ts index 814b0f99a6..0d7f8e01ad 100644 --- a/app/core/dto.ts +++ b/app/core/dto.ts @@ -4,6 +4,9 @@ const primitives = new Set(["Array", "Number", "String", "Object", "Boolean"]); const attrMetadataKey = "dto:attrs"; +function metadataForDto(dto: Dto) { + return Reflect.getMetadata(attrMetadataKey, dto.constructor) || {}; +} /** * Dto should be the base class for all dto. * It will only assign defined attributes. @@ -11,7 +14,7 @@ const attrMetadataKey = "dto:attrs"; export class Dto { constructor(data: AttrOf) { console.log("Setting dto", data); - const attrs = this._attrMetadata; + const attrs = metadataForDto(this); for (let key of Object.keys(attrs)) { const type = attrs[key]; if (!(key in data)) { @@ -23,6 +26,7 @@ export class Dto { } if (type && !primitives.has(type.name)) { + console.log("typs is") this[key] = new type(value); } else { this[key] = value; @@ -31,9 +35,9 @@ export class Dto { console.log("set dto", this); } - public toJS(): AttrOf { + public toJS?(): AttrOf { let output: any = {}; - const attrs = this._attrMetadata; + const attrs = metadataForDto(this); for (let key of Object.keys(attrs)) { if (!(key in this)) { continue; @@ -50,17 +54,16 @@ export class Dto { } return output; } - - private get _attrMetadata() { - return Reflect.getMetadata(attrMetadataKey, this.constructor) || {}; - } } export function DtoAttr() { return (target, attr, descriptor?: TypedPropertyDescriptor) => { const ctr = target.constructor; const type = Reflect.getMetadata("design:type", target, attr); - + if (!type) { + throw `Cannot retrieve the type for DtoAttr ${target.constructor.name}#${attr}` + + "Check your nested type is defined in another file or above this DtoAttr"; + } const metadata = Reflect.getMetadata(attrMetadataKey, ctr) || {}; metadata[attr] = type; Reflect.defineMetadata(attrMetadataKey, metadata, ctr); diff --git a/test/app/core/dto.spec.ts b/test/app/core/dto.spec.ts new file mode 100644 index 0000000000..1518c32041 --- /dev/null +++ b/test/app/core/dto.spec.ts @@ -0,0 +1,56 @@ + + +import { Dto, DtoAttr } from "app/core"; + + +class FakeNestedDto extends Dto { + @DtoAttr() + public foo: string; +} + +class FakeDto extends Dto { + @DtoAttr() + public id: string; + + @DtoAttr() + public num?: number; + + @DtoAttr() + public obj?: { a: string, b: string }; + + @DtoAttr() + public nested?: FakeNestedDto; +} + +fdescribe("Dto", () => { + it("should not set attributes not in the list", () => { + const dto = new FakeDto({ id: "foo", other: "wrong", other2: { nested: true } } as any); + expect(dto.id).toEqual("foo"); + expect((dto as any).other).toBeUndefined(); + expect((dto as any).other2).toBeUndefined(); + }); + + it("should assign primitive types correctly", () => { + const dto = new FakeDto({ id: "foo", num: 3, obj: { a: "1", b: "2" } }); + expect(dto.id).toEqual("foo"); + expect(dto.num).toEqual(3); + expect(dto.obj).toEqual({ a: "1", b: "2" }); + }); + + it("should assign nested dto types correctly", () => { + const dto = new FakeDto({ id: "foo", nested: { foo: "bar" } }); + expect(dto.id).toEqual("foo"); + const nested = dto.nested; + expect(nested).not.toBeUndefined(); + expect(nested instanceof FakeNestedDto).toBe(true); + expect(nested.foo).toEqual("bar"); + }); + + it("should not assign unknown nested dto types correctly", () => { + const dto = new FakeDto({ id: "foo", nested: { foo: "bar", other: "wrong" } } as any); + const nested = dto.nested; + expect(nested).not.toBeUndefined(); + expect(nested instanceof FakeNestedDto).toBe(true); + expect((nested as any).other).toBeUndefined("bar"); + }); +}); From 0feb3a9dee4a7f31530dc088fe136d9082ec9cfc Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 23 Mar 2017 10:57:47 -0700 Subject: [PATCH 08/10] Fix tslint --- .../job/action/add/job-create-basic-dialog.component.spec.ts | 2 +- test/app/core/dto.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/app/components/job/action/add/job-create-basic-dialog.component.spec.ts b/test/app/components/job/action/add/job-create-basic-dialog.component.spec.ts index 706012cea6..8d3d6c4036 100644 --- a/test/app/components/job/action/add/job-create-basic-dialog.component.spec.ts +++ b/test/app/components/job/action/add/job-create-basic-dialog.component.spec.ts @@ -87,7 +87,7 @@ describe("JobCreateBasicDialogComponent ", () => { debugElement = fixture.debugElement; fixture.detectChanges(); - baseForm = component.createJobForm; + baseForm = component.form; constraintsForm = component.constraintsGroup; poolForm = component.poolInfoGroup; }); diff --git a/test/app/core/dto.spec.ts b/test/app/core/dto.spec.ts index 1518c32041..a7b895d6f5 100644 --- a/test/app/core/dto.spec.ts +++ b/test/app/core/dto.spec.ts @@ -22,7 +22,7 @@ class FakeDto extends Dto { public nested?: FakeNestedDto; } -fdescribe("Dto", () => { +describe("Dto", () => { it("should not set attributes not in the list", () => { const dto = new FakeDto({ id: "foo", other: "wrong", other2: { nested: true } } as any); expect(dto.id).toEqual("foo"); From df58998872408275675a6596b582f78be364d26d Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 23 Mar 2017 11:07:17 -0700 Subject: [PATCH 09/10] fix tslint --- .../task/action/add/task-create-basic-dialog.component.ts | 1 - app/models/dtos/pool-create.dto.ts | 3 --- app/models/forms/create-task-model.ts | 2 -- app/services/job-service.ts | 2 +- app/services/task-service.ts | 3 +-- client/api/batch-client-proxy/poolProxy.ts | 1 - 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/app/components/task/action/add/task-create-basic-dialog.component.ts b/app/components/task/action/add/task-create-basic-dialog.component.ts index c69d64dd02..db6a440e14 100644 --- a/app/components/task/action/add/task-create-basic-dialog.component.ts +++ b/app/components/task/action/add/task-create-basic-dialog.component.ts @@ -56,7 +56,6 @@ export class TaskCreateBasicDialogComponent extends DynamicForm { @DtoAttr() public id: string; @@ -64,4 +62,3 @@ export class PoolCreateDto extends Dto { @DtoAttr() public metadata: MetaDataDto[]; } - diff --git a/app/models/forms/create-task-model.ts b/app/models/forms/create-task-model.ts index cd891b0c9a..a7198c9c51 100644 --- a/app/models/forms/create-task-model.ts +++ b/app/models/forms/create-task-model.ts @@ -41,8 +41,6 @@ export function createTaskFormToJsonData(formData: CreateTaskModel): any { applicationPackageReferences: null, }; - console.log("Data here...", data.dependsOn); - return data; } diff --git a/app/services/job-service.ts b/app/services/job-service.ts index 6853bd8fad..20cb662d2c 100644 --- a/app/services/job-service.ts +++ b/app/services/job-service.ts @@ -2,11 +2,11 @@ import { Injectable } from "@angular/core"; import { Observable, Subject } from "rxjs"; import { Job } from "app/models"; +import { JobCreateDto } from "app/models/dtos"; import { log } from "app/utils"; import { BatchClientService } from "./batch-client.service"; import { DataCache, RxBatchEntityProxy, RxBatchListProxy, RxEntityProxy, RxListProxy, getOnceProxy } from "./core"; import { ServiceBase } from "./service-base"; -import { JobCreateDto } from "app/models/dtos"; export interface JobParams { id?: string; diff --git a/app/services/task-service.ts b/app/services/task-service.ts index b768578621..1da8da381d 100644 --- a/app/services/task-service.ts +++ b/app/services/task-service.ts @@ -3,6 +3,7 @@ import { List } from "immutable"; import { Observable, Subject } from "rxjs"; import { SubtaskInformation, Task } from "app/models"; +import { TaskCreateDto } from "app/models/dtos"; import { log } from "app/utils"; import { FilterBuilder } from "app/utils/filter-builder"; import { BatchClientService } from "./batch-client.service"; @@ -16,7 +17,6 @@ import { getOnceProxy, } from "./core"; import { CommonListOptions, ServiceBase } from "./service-base"; -import { TaskCreateDto } from "app/models/dtos"; export interface TaskListParams { jobId?: string; @@ -138,7 +138,6 @@ export class TaskService extends ServiceBase { } public add(jobId: string, task: TaskCreateDto, options: any): Observable<{}> { - console.log("Add task", task.toJS()); return this.callBatchClient((client) => client.task.add(jobId, task.toJS(), options)); } diff --git a/client/api/batch-client-proxy/poolProxy.ts b/client/api/batch-client-proxy/poolProxy.ts index 44d41e7bdc..a2cbb02661 100644 --- a/client/api/batch-client-proxy/poolProxy.ts +++ b/client/api/batch-client-proxy/poolProxy.ts @@ -1,4 +1,3 @@ -import * as moment from "moment"; import { BatchRequestOptions } from "./models"; import { DeleteProxy, GetProxy, ListProxy } from "./shared"; From 042e6181947748a33eaff16b1405e8eaffe78103 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 23 Mar 2017 14:32:11 -0700 Subject: [PATCH 10/10] Fix tslint --- app/core/dto.ts | 3 --- test/app/core/dto.spec.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/app/core/dto.ts b/app/core/dto.ts index 0d7f8e01ad..3fba429da9 100644 --- a/app/core/dto.ts +++ b/app/core/dto.ts @@ -13,7 +13,6 @@ function metadataForDto(dto: Dto) { */ export class Dto { constructor(data: AttrOf) { - console.log("Setting dto", data); const attrs = metadataForDto(this); for (let key of Object.keys(attrs)) { const type = attrs[key]; @@ -26,13 +25,11 @@ export class Dto { } if (type && !primitives.has(type.name)) { - console.log("typs is") this[key] = new type(value); } else { this[key] = value; } } - console.log("set dto", this); } public toJS?(): AttrOf { diff --git a/test/app/core/dto.spec.ts b/test/app/core/dto.spec.ts index a7b895d6f5..a82d0798d7 100644 --- a/test/app/core/dto.spec.ts +++ b/test/app/core/dto.spec.ts @@ -1,8 +1,5 @@ - - import { Dto, DtoAttr } from "app/core"; - class FakeNestedDto extends Dto { @DtoAttr() public foo: string;