Added component to view all entities and mock data

This commit is contained in:
Yossi Kolesnicov 2017-09-22 19:22:33 +03:00
Родитель 3784e1935c
Коммит 3391a3d32f
78 изменённых файлов: 554 добавлений и 245 удалений

Просмотреть файл

@ -6,6 +6,8 @@
"title": "Something bad happened",
"status": 1,
"machine": "yossi-pc",
"date": 1505710515089,
"tags": ["Cyber", "Automation"],
"host": {
"id": "yossi_comp",
"name": "Yossi's comp",

Просмотреть файл

@ -1,11 +0,0 @@
{
"name": "mockAPI",
"version": "0.0.0",
"dependencies": {
"body-parser": "^1.14.1",
"connect-busboy": "0.0.2",
"express": "5.0.0-alpha.5",
"express-ws": "^3.0.0",
"http2": "latest"
}
}

Просмотреть файл

@ -1,161 +1,164 @@
{
"name": "Paris",
"version": "1.0.0",
"description": "Library for the implementation of Domain Driven Design in Angular/TypeScript apps",
"repository": {
"url": "https://github.com/microsoft/paris.ts"
},
"scripts": {
"build.dev": "gulp build.dev --color --env-config dev",
"build.dev.watch": "gulp build.dev.watch --color",
"build.e2e": "gulp build.e2e --color",
"build.prod": "gulp build.prod --color --env-config prod --build-type prod",
"build.prod.exp": "gulp build.prod.aot --color --env-config prod --build-type prod",
"build.prod.rollup.aot": "gulp build.prod.rollup.aot --color --env-config prod --build-type prod",
"build.prod.aot": "gulp build.prod.aot --color --env-config prod --build-type prod",
"build.test": "gulp build.test --color",
"test.watch": "gulp test.watch --color",
"generate.manifest": "gulp generate.manifest --color",
"e2e": "protractor",
"e2e.live": "protractor --elementExplorer",
"gulp": "gulp",
"i18n": "gulp i18n.build --build-type prod && ng-xi18n -p dist/tmp/tsconfig.json --i18nFormat xlf && gulp i18n.merge",
"lint": "gulp tslint",
"karma": "karma",
"karma.start": "karma start",
"postinstall": "gulp check.versions && gulp build.bundle.rxjs && gulp webdriver && gulp print.banner",
"reinstall": "npm cache clean && npm install",
"serve.coverage": "gulp serve.coverage --color",
"serve.dev": "gulp serve.dev --color --env-config dev",
"serve.e2e": "gulp serve.e2e --color",
"serve.prod": "gulp serve.prod --color --env-config prod --build-type prod",
"serve.prod.aot": "gulp serve.prod.aot --color --env-config prod --build-type prod",
"serve.prod.exp": "gulp serve.prod.aot --color --env-config prod --build-type prod",
"serve.prod.rollup.aot": "gulp serve.prod.rollup.aot --color --env-config prod --build-type prod",
"sme.prod": "gulp sme.prod --color --env-config prod --build-type prod --preserve-source-maps",
"sme.prod.aot": "gulp sme.prod.aot --color --env-config prod --build-type prod --preserve-source-maps",
"sme.prod.rollup.aot": "gulp sme.prod.rollup.aot --color --env-config prod --build-type prod --preserve-source-maps",
"start": "gulp serve.dev --color",
"start.deving": "gulp start.deving --color",
"tasks.list": "gulp --tasks-simple --color",
"test": "gulp test --color",
"e2e.ci": "gulp build.prod.rollup.aot --color && gulp build.e2e --color && gulp e2e --color",
"tests.all": "npm test && npm run e2e.ci",
"webdriver-start": "node ./node_modules/protractor/bin/webdriver-manager start",
"webdriver-update": "node ./node_modules/protractor/bin/webdriver-manager update",
"compodoc": "./node_modules/.bin/compodoc -p src/client/tsconfig.json",
"serve.compodoc": "./node_modules/.bin/compodoc -s"
},
"author": "Yossi Kolesnicov",
"license": "MIT",
"devDependencies": {
"@angular/compiler-cli": "^5.0.0-beta.6",
"@angular/platform-server": "^5.0.0-beta.6",
"@compodoc/compodoc": "^1.0.0-beta.7",
"@types/async": "^2.0.32",
"@types/browser-sync": "^0.0.36",
"@types/express": "^4.0.33",
"@types/gulp": "^4.0.0",
"@types/gulp-filter": "^3.0.29",
"@types/gulp-htmlmin": "^1.3.30",
"@types/gulp-load-plugins": "^0.0.30",
"@types/gulp-protractor": "^1.0.30",
"@types/gulp-sass": "^0.0.30",
"@types/gulp-util": "^3.0.29",
"@types/jasmine": "^2.5.52",
"@types/node": "^8.0.25",
"@types/rimraf": "2.0.2",
"@types/run-sequence": "^0.0.29",
"@types/selenium-webdriver": "^3.0.3",
"@types/systemjs": "^0.20.2",
"@types/yargs": "^8.0.2",
"async": "^2.1.1",
"autoprefixer": "^7.0.1",
"browser-sync": "^2.17.3",
"codelyzer": "^3.1.2",
"connect-history-api-fallback": "^1.3.0",
"cssnano": "^3.7.7",
"deep-extend": "^0.5.0",
"event-stream": "^3.3.4",
"express": "~4.15.2",
"express-history-api-fallback": "^2.0.0",
"gulp": "^3.9.1",
"gulp-cached": "^1.1.0",
"gulp-cheerio": "^0.6.2",
"gulp-concat": "^2.6.0",
"gulp-concat-css": "^2.3.0",
"gulp-filter": "^5.0.0",
"gulp-htmlmin": "^3.0.0",
"gulp-inject": "^4.1.0",
"gulp-inline-ng2-template": "^4.0.0",
"gulp-load-plugins": "^1.3.0",
"gulp-plumber": "~1.1.0",
"gulp-postcss": "^7.0.0",
"gulp-progeny": "^0.4.0",
"gulp-protractor": "^3.0.0",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.6.1",
"gulp-sass": "^3.0.0",
"gulp-sourcemaps": "2.6.0",
"gulp-template": "^4.0.0",
"gulp-tslint": "^8.0.0",
"gulp-typescript": "~3.2.2",
"gulp-uglify": "^3.0.0",
"gulp-util": "^3.0.7",
"gulp-watch": "^4.3.10",
"http-proxy-middleware": "^0.17.4",
"is-ci": "^1.0.9",
"isstream": "^0.1.2",
"jasmine-core": "~2.6.1",
"jasmine-spec-reporter": "^4.1.0",
"karma": "~1.7.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage": "^1.1.1",
"karma-jasmine": "~1.1.0",
"karma-mocha-reporter": "^2.2.0",
"karma-remap-istanbul": "^0.6.0",
"merge-stream": "^1.0.0",
"minimatch": "^3.0.3",
"open": "0.0.5",
"protractor": "^4.0.14",
"remap-istanbul": "^0.9.5",
"rimraf": "^2.5.4",
"rollup": "^0.43.0",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-includepaths": "0.2.2",
"rollup-plugin-node-resolve": "^3.0.0",
"run-sequence": "^1.2.2",
"semver": "^5.3.0",
"serve-static": "^1.11.1",
"slash": "~1.0.0",
"source-map-explorer": "^1.4.0",
"supports-color": "^3.1.2",
"systemjs-builder": "0.16.9",
"temp": "^0.8.3",
"tildify": "^1.2.0",
"traceur": "^0.0.111",
"ts-node": "^3.0.4",
"tslint": "^5.0.0",
"tslib": "latest",
"typescript": "2.4.2",
"walk": "^2.3.9",
"yargs": "^8.0.1"
},
"dependencies": {
"@angular/animations": "^5.0.0-beta.6",
"@angular/common": "^5.0.0-beta.6",
"@angular/compiler": "^5.0.0-beta.6",
"@angular/core": "^5.0.0-beta.6",
"@angular/forms": "^5.0.0-beta.6",
"@angular/http": "^5.0.0-beta.6",
"@angular/platform-browser": "^5.0.0-beta.6",
"@angular/platform-browser-dynamic": "^5.0.0-beta.6",
"@angular/router": "^5.0.0-beta.6",
"@angular/service-worker": "^1.0.0-beta.16",
"core-js": "^2.4.1",
"intl": "^1.2.5",
"rxjs": "^5.4.2",
"systemjs": "0.20.14",
"zone.js": "0.8.12"
}
"name": "Paris",
"version": "1.0.0",
"description": "Library for the implementation of Domain Driven Design in Angular/TypeScript apps",
"repository": {
"url": "https://github.com/microsoft/paris.ts"
},
"scripts": {
"build.dev": "gulp build.dev --color --env-config dev",
"build.dev.watch": "gulp build.dev.watch --color",
"build.e2e": "gulp build.e2e --color",
"build.prod": "gulp build.prod --color --env-config prod --build-type prod",
"build.prod.exp": "gulp build.prod.aot --color --env-config prod --build-type prod",
"build.prod.rollup.aot": "gulp build.prod.rollup.aot --color --env-config prod --build-type prod",
"build.prod.aot": "gulp build.prod.aot --color --env-config prod --build-type prod",
"build.test": "gulp build.test --color",
"test.watch": "gulp test.watch --color",
"generate.manifest": "gulp generate.manifest --color",
"e2e": "protractor",
"e2e.live": "protractor --elementExplorer",
"gulp": "gulp",
"i18n": "gulp i18n.build --build-type prod && ng-xi18n -p dist/tmp/tsconfig.json --i18nFormat xlf && gulp i18n.merge",
"lint": "gulp tslint",
"karma": "karma",
"karma.start": "karma start",
"mock-data-server.start": "node ./src/mock_data_server/mock_data_server",
"postinstall": "gulp check.versions && gulp build.bundle.rxjs && gulp webdriver && gulp print.banner",
"reinstall": "npm cache clean && npm install",
"serve.coverage": "gulp serve.coverage --color",
"serve.dev": "gulp serve.dev --color --env-config dev",
"serve.e2e": "gulp serve.e2e --color",
"serve.prod": "gulp serve.prod --color --env-config prod --build-type prod",
"serve.prod.aot": "gulp serve.prod.aot --color --env-config prod --build-type prod",
"serve.prod.exp": "gulp serve.prod.aot --color --env-config prod --build-type prod",
"serve.prod.rollup.aot": "gulp serve.prod.rollup.aot --color --env-config prod --build-type prod",
"sme.prod": "gulp sme.prod --color --env-config prod --build-type prod --preserve-source-maps",
"sme.prod.aot": "gulp sme.prod.aot --color --env-config prod --build-type prod --preserve-source-maps",
"sme.prod.rollup.aot": "gulp sme.prod.rollup.aot --color --env-config prod --build-type prod --preserve-source-maps",
"start": "gulp serve.dev --color",
"start.deving": "gulp start.deving --color",
"tasks.list": "gulp --tasks-simple --color",
"test": "gulp test --color",
"e2e.ci": "gulp build.prod.rollup.aot --color && gulp build.e2e --color && gulp e2e --color",
"tests.all": "npm test && npm run e2e.ci",
"webdriver-start": "node ./node_modules/protractor/bin/webdriver-manager start",
"webdriver-update": "node ./node_modules/protractor/bin/webdriver-manager update",
"compodoc": "./node_modules/.bin/compodoc -p src/client/tsconfig.json",
"serve.compodoc": "./node_modules/.bin/compodoc -s"
},
"author": "Yossi Kolesnicov",
"license": "MIT",
"devDependencies": {
"@angular/compiler-cli": "^5.0.0-beta.6",
"@angular/platform-server": "^5.0.0-beta.6",
"@compodoc/compodoc": "^1.0.0-beta.7",
"@types/async": "^2.0.32",
"@types/browser-sync": "^0.0.36",
"@types/express": "^4.0.33",
"@types/gulp": "^4.0.0",
"@types/gulp-filter": "^3.0.29",
"@types/gulp-htmlmin": "^1.3.30",
"@types/gulp-load-plugins": "^0.0.30",
"@types/gulp-protractor": "^1.0.30",
"@types/gulp-sass": "^0.0.30",
"@types/gulp-util": "^3.0.29",
"@types/jasmine": "^2.5.52",
"@types/node": "^8.0.25",
"@types/rimraf": "2.0.2",
"@types/run-sequence": "^0.0.29",
"@types/selenium-webdriver": "^3.0.3",
"@types/systemjs": "^0.20.2",
"@types/yargs": "^8.0.2",
"async": "^2.1.1",
"autoprefixer": "^7.0.1",
"browser-sync": "^2.17.3",
"codelyzer": "^3.1.2",
"connect-history-api-fallback": "^1.3.0",
"cssnano": "^3.7.7",
"deep-extend": "^0.5.0",
"event-stream": "^3.3.4",
"body-parser": "^1.14.1",
"connect-busboy": "0.0.2",
"express": "^4.15.4",
"express-history-api-fallback": "^2.0.0",
"gulp": "^3.9.1",
"gulp-cached": "^1.1.0",
"gulp-cheerio": "^0.6.2",
"gulp-concat": "^2.6.0",
"gulp-concat-css": "^2.3.0",
"gulp-filter": "^5.0.0",
"gulp-htmlmin": "^3.0.0",
"gulp-inject": "^4.1.0",
"gulp-inline-ng2-template": "^4.0.0",
"gulp-load-plugins": "^1.3.0",
"gulp-plumber": "~1.1.0",
"gulp-postcss": "^7.0.0",
"gulp-progeny": "^0.4.0",
"gulp-protractor": "^3.0.0",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.6.1",
"gulp-sass": "^3.0.0",
"gulp-sourcemaps": "2.6.0",
"gulp-template": "^4.0.0",
"gulp-tslint": "^8.0.0",
"gulp-typescript": "~3.2.2",
"gulp-uglify": "^3.0.0",
"gulp-util": "^3.0.7",
"gulp-watch": "^4.3.10",
"http-proxy-middleware": "^0.17.4",
"is-ci": "^1.0.9",
"isstream": "^0.1.2",
"jasmine-core": "~2.6.1",
"jasmine-spec-reporter": "^4.1.0",
"karma": "~1.7.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage": "^1.1.1",
"karma-jasmine": "~1.1.0",
"karma-mocha-reporter": "^2.2.0",
"karma-remap-istanbul": "^0.6.0",
"merge-stream": "^1.0.0",
"minimatch": "^3.0.3",
"open": "0.0.5",
"protractor": "^4.0.14",
"remap-istanbul": "^0.9.5",
"rimraf": "^2.5.4",
"rollup": "^0.43.0",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-includepaths": "0.2.2",
"rollup-plugin-node-resolve": "^3.0.0",
"run-sequence": "^1.2.2",
"semver": "^5.3.0",
"serve-static": "^1.11.1",
"slash": "~1.0.0",
"source-map-explorer": "^1.4.0",
"supports-color": "^3.1.2",
"systemjs-builder": "0.16.9",
"temp": "^0.8.3",
"tildify": "^1.2.0",
"traceur": "^0.0.111",
"ts-node": "^3.0.4",
"tslib": "latest",
"tslint": "^5.0.0",
"typescript": "2.4.2",
"walk": "^2.3.9",
"yargs": "^8.0.1"
},
"dependencies": {
"@angular/animations": "^5.0.0-beta.6",
"@angular/common": "^5.0.0-beta.6",
"@angular/compiler": "^5.0.0-beta.6",
"@angular/core": "^5.0.0-beta.6",
"@angular/forms": "^5.0.0-beta.6",
"@angular/http": "^5.0.0-beta.6",
"@angular/platform-browser": "^5.0.0-beta.6",
"@angular/platform-browser-dynamic": "^5.0.0-beta.6",
"@angular/router": "^5.0.0-beta.6",
"@angular/service-worker": "^1.0.0-beta.16",
"core-js": "^2.4.1",
"intl": "^1.2.5",
"rxjs": "^5.4.2",
"systemjs": "0.20.14",
"zone.js": "0.8.12"
}
}

Просмотреть файл

@ -5,7 +5,11 @@ import {EntityField} from "../paris/entity/entity-field.decorator";
@Entity({
singularName: "Machine",
pluralName: "Machines",
endpoint: "machines"
endpoint: "machines",
cache: {
time: 10 * 1000,
max: 10
}
})
export class MachineModel extends Identifiable<string> {
@EntityField({

Просмотреть файл

@ -1,12 +1,18 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import {EntityComponent} from "./components/entity.component";
import {EntityResolver} from "./entity-resolver";
@NgModule({
imports: [
RouterModule.forRoot([
/* define app module routes here, e.g., to lazily load a module
(do not place feature module routes here, use an own -routing.module.ts in the feature instead)
*/
{
path: 'entity/:entityPluralName',
component: EntityComponent,
resolve: {
entity: EntityResolver
}
}
])
],
exports: [RouterModule]

Просмотреть файл

@ -5,22 +5,4 @@
padding: 1rem;
}
table{
border: solid 1px #ddd;
}
table td, table th{
text-align: left;
padding: 6px 10px;
}
thead tr{
background: #106cc8;
color: White;
}
tr + tr{
border-top: solid 1px #eaeaea;
}

Просмотреть файл

@ -1,3 +1,6 @@
<entities-nav></entities-nav>
<router-outlet></router-outlet>
<hr />
<div *ngIf="alert">
<h2>Alert</h2>
<table>
@ -24,28 +27,6 @@
</td>
</tr>
</table>
<button (click)="loadAlert()">Refresh Alert</button>
</div>
<hr />
<div *ngIf="alerts">
<h2>Alerts DataSet</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
<th>Machine</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let alert of alerts">
<td>{{alert.id}}</td>
<td>{{alert.name}}</td>
<td>{{alert.status.name}}</td>
<td>{{alert.machine.name}} ({{alert.machine.domain || 'No Domain'}})</td>
</tr>
</tbody>
</table>
<button (click)="loadAlerts()">Refresh Alert</button>
<button (click)="loadAlert()">Refresh</button>
<button (click)="saveAlert()">Update</button>
</div>

Просмотреть файл

@ -5,6 +5,7 @@ import {AlertModel} from "./@model/alert.model";
import {MachineModel} from "./@model/machine.model";
import {Repository} from "./paris/repository/repository";
import {DataSet} from "./paris/dataset/dataset";
import {Immutability} from "./paris/services/immutability";
/**
* This class represents the main application component.
@ -17,7 +18,6 @@ import {DataSet} from "./paris/dataset/dataset";
})
export class AppComponent {
alert:AlertModel;
machine:MachineModel;
alerts:Array<AlertModel>;
private alertsRepo:Repository<AlertModel>;
@ -30,7 +30,6 @@ export class AppComponent {
loadAll(){
this.loadAlert();
this.loadAlerts();
this.loadMachine();
}
@ -50,14 +49,11 @@ export class AppComponent {
}
saveAlert(){
this.alert.name = "Updated Alert!";
this.alertsRepo.save(this.alert).subscribe((savedAlert:AlertModel) => console.log("SAVED", savedAlert))
}
loadAlerts(){
this.alertsRepo.getItemsDataSet({ page: 1, pageSize: 15 }).subscribe((alerts:DataSet<AlertModel>) => {
console.log("Alerts: ", alerts);
this.alerts = alerts.items;
});
let editedAlert:AlertModel = Immutability.unfreeze(this.alert);
editedAlert.name = "Updated Alert!";
this.alertsRepo.save(editedAlert).subscribe((savedAlert:AlertModel) => {
console.log("SAVED", savedAlert);
this.alert = savedAlert;
})
}
}

Просмотреть файл

@ -7,6 +7,10 @@ import {ParisModule} from "./paris/paris.module";
import {HttpClientModule} from "@angular/common/http";
import {ParisConfig} from "./paris/config/paris-config";
import {TagComponent} from "./components/tag.component";
import {EntitiesNavComponent} from "./components/entities-nav.component";
import {EntityComponent} from "./components/entity.component";
import {RouterModule} from "@angular/router";
import {EntityResolver} from "./entity-resolver";
const parisConfig:ParisConfig = {
apiRoot: "api",
@ -18,17 +22,21 @@ const parisConfig:ParisConfig = {
BrowserModule,
HttpClientModule,
AppRoutingModule,
ParisModule.forRoot(parisConfig)
ParisModule.forRoot(parisConfig),
RouterModule
],
declarations: [
AppComponent,
TagComponent
TagComponent,
EntitiesNavComponent,
EntityComponent
],
providers: [
{
provide: APP_BASE_HREF,
useValue: '<%= APP_BASE %>'
}
},
EntityResolver
],
bootstrap: [AppComponent]

Просмотреть файл

@ -0,0 +1,20 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
import {entitiesService} from "../paris/services/entities.service";
import {DataEntityType} from "../paris/entity/data-entity.base";
import {ModelEntity} from "../paris/entity/entity.config";
@Component({
selector: "entities-nav",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nav>
<a *ngFor="let entity of entities"
[routerLink]="['entity', entity.pluralName]">
{{entity.pluralName}}
</a>
</nav>
`
})
export class EntitiesNavComponent{
entities:Array<ModelEntity> = entitiesService.allEntities;
}

Просмотреть файл

@ -0,0 +1,69 @@
import {Component} from "@angular/core";
import {DataEntityType} from "../paris/entity/data-entity.base";
import {ActivatedRoute} from "@angular/router";
import {IRepository} from "../paris/repository/repository.interface";
import {RepositoryManagerService} from "../paris/repository/repository-manager.service";
import {DataSet} from "../paris/dataset/dataset";
import {Index} from "../paris/models/index";
import {Field} from "../paris/entity/entity-field";
import {HttpErrorResponse} from "@angular/common/http";
@Component({
selector: "entity",
template: `
<h1>{{entity.entityConfig.pluralName}}</h1>
<strong class="error" *ngIf="error; else items" style="color: Red">{{error.message}}</strong>
<ng-template #items>
<table *ngIf="allItems">
<thead>
<tr>
<th *ngFor="let field of entity.entityConfig.fieldsArray">{{field.name}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of allItems.items">
<td *ngFor="let field of entity.entityConfig.fieldsArray">{{getFieldDisplay(item, field)}}</td>
</tr>
</tbody>
</table>
</ng-template>
`
})
export class EntityComponent{
entity:DataEntityType;
allItems:DataSet<any>;
error:any;
private repo:IRepository;
constructor(route: ActivatedRoute, private repositoriesManagerService: RepositoryManagerService){
route.params.subscribe(() => {
if (this.entity = route.snapshot.data['entity']) {
this.repo = repositoriesManagerService.getRepository(this.entity);
this.updateAll();
}
});
}
private updateAll(){
this.repo.getItemsDataSet({ page: 1, pageSize: 15 }).subscribe((allItemsDataSet:DataSet<any>) => {
this.allItems = allItemsDataSet;
this.error = null;
}, (error:HttpErrorResponse) => {
console.error(error);
this.error = error
});
}
getFieldDisplay(item:Index, field:Field):string{
let itemFieldValue = item[field.id];
if (itemFieldValue !== undefined && itemFieldValue !== null) {
if (itemFieldValue instanceof Array)
return itemFieldValue.map(member => member && member.name || member).join(", ");
return itemFieldValue.name || itemFieldValue;
}
return itemFieldValue || "N/A";
}
}

Просмотреть файл

@ -0,0 +1,14 @@
import {DataEntityType} from "./paris/entity/data-entity.base";
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from "@angular/router";
import {Injectable} from "@angular/core";
import {entitiesService} from "./paris/services/entities.service";
@Injectable()
export class EntityResolver implements Resolve<DataEntityType> {
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): DataEntityType {
return entitiesService.getEntityByPluralName(route.params.entityPluralName);
}
}

Просмотреть файл

@ -4,6 +4,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/share';

Просмотреть файл

@ -1,9 +1,11 @@
import {IIdentifiable} from "../models/identifiable.model";
import {ModelEntity} from "./entity.config";
export interface DataEntityConstructor<T> extends DataEntityType{
new(data:IIdentifiable): T
}
export interface DataEntityType{
new(data:IIdentifiable):any
new(data:IIdentifiable):any,
entityConfig?:ModelEntity
}

Просмотреть файл

@ -1,4 +1,5 @@
import {EntityFields} from "./entity-fields";
import {Field} from "./entity-field";
export class ModelEntity{
singularName:string;
@ -7,6 +8,11 @@ export class ModelEntity{
fields?:EntityFields;
loadAll?:boolean = false;
listOf?:any;
cache?:ModelEntityCacheConfig;
get fieldsArray():Array<Field>{
return this.fields ? Array.from(this.fields.values()) : [];
}
constructor(config:ModelEntityConfig){
Object.assign(this, config);
@ -18,5 +24,11 @@ export interface ModelEntityConfig{
pluralName:string,
endpoint:string,
loadAll?:boolean,
listOf?:any
listOf?:any,
cache?: ModelEntityCacheConfig
}
interface ModelEntityCacheConfig{
time?: number,
max?: number
}

Просмотреть файл

@ -4,6 +4,8 @@ import {entitiesService} from "../services/entities.service";
export function Entity(config:ModelEntityConfig){
return (target:DataEntityType) => {
entitiesService.addEntity(target, new ModelEntity(config));
let entity:ModelEntity = new ModelEntity(config);
target.entityConfig = entity;
entitiesService.addEntity(target, entity);
}
}

Просмотреть файл

@ -1,8 +1,11 @@
import {Observable} from "rxjs/Observable";
import {IIdentifiable} from "../models/identifiable.model";
import {DataSetOptions} from "../dataset/dataset-options";
import {DataSet} from "../dataset/dataset";
export interface IRepository{
createItem:(itemData:any) => Observable<any>,
getItemById:(id:any) => Observable<any>,
getItemsDataSet:(options?:DataSetOptions) => Observable<DataSet<any>>,
getItemSaveData:(item:IIdentifiable) => Object
}

Просмотреть файл

@ -12,12 +12,27 @@ import {DataSetOptions} from "../dataset/dataset-options";
import {DataSet} from "../dataset/dataset";
import {Index} from "../models/index";
import {DataTransformersService} from "../services/data-transformers.service";
import {Immutability} from "../services/immutability";
import {DataCache, DataCacheSettings} from "../services/cache";
export class Repository<T extends IIdentifiable>{
save$:Subject<T> = new Subject<T>();
private _allValues:Array<T>;
private _allValuesMap:Map<any, T>;
private _cache:DataCache<T>;
private get cache():DataCache<T>{
if (!this._cache) {
let cacheSettings:DataCacheSettings<T> = Object.assign({
getter: (itemId:string | number) => this.getItemById(itemId, false)
}, this.entity.cache);
this._cache = new DataCache<T>(cacheSettings);
}
return this._cache;
}
constructor(public readonly entity:ModelEntity,
private config:ParisConfig,
@ -27,7 +42,7 @@ export class Repository<T extends IIdentifiable>{
createItem(itemData:any):Observable<T>{
return this.getModelData(itemData)
.map((modelData:ModelData) => new this.entityConstructor(modelData));
.map((modelData:ModelData) => Immutability.freeze(new this.entityConstructor(modelData)));
}
/**
@ -107,15 +122,18 @@ export class Repository<T extends IIdentifiable>{
let itemCreators:Array<Observable<T>> = dataSet.items.map((itemData:any) => this.createItem(itemData));
return Observable.combineLatest.apply(this, itemCreators).map((items:Array<T>) => {
return {
return Object.freeze({
count: dataSet.count,
items: items
};
items: Object.freeze(items)
});
})
});
}
getItemById(itemId:string|number):Observable<T>{
getItemById(itemId:string|number, allowCache:boolean = true):Observable<T>{
if (allowCache !== false && this.entity.cache)
return this.cache.get(itemId);
if (this.entity.loadAll){
if (!this._allValues){
return this.getItemsDataSet()

Просмотреть файл

@ -0,0 +1,137 @@
import {Observable} from "rxjs/Observable";
export class DataCache<T>{
time:number;
obj:number;
getter:(_:any) => Promise<T> | Observable<T>;
private _keys:Array<string>;
private _values:Map<string, T>;
private _timeouts:{ [index:string]:any };
private _getObservable:{ [index:string]:Observable<T> };
constructor(settings?:DataCacheSettings<T>){
DataCache.validateSettings<T>(settings);
this.time = settings.time || null; // milliseconds
this.obj = settings.max;
this.getter = settings.getter;
this._keys = [];
this._values = new Map<string, T>();
this._timeouts = {};
this._getObservable = {};
Object.seal(this);
}
/**
* Gets a value from the cache collection.
* If getter is specified, uses it to get the data.
* @param key
* @returns {Observable<T>}
*/
get(key:any):Observable<T>{
if (!key && key !== 0)
throw new Error("Can't get DataCache item, key not specified.");
key = key.toString();
if (this.getter){
let getObservable = this._getObservable[key];
if (getObservable)
return getObservable;
let cachedItem = this._values.get(key);
if (cachedItem)
return Observable.of(cachedItem);
return this._getObservable[key] = Observable.from(this.getter(key))
.do((value:T) => {
this.add(key, value);
delete this._getObservable[key];
});
}
else
return Observable.of(this._values.get(key));
}
/**
* Adds an item to the Cached collection. If DataCache.time was specified, the item will expire after this time (in milliseconds), and will be deleted.
* @param key {String}
* @param value
* @returns {Cache}
*/
add(key:any, value:T):DataCache<T>{
key = key.toString();
let isNew = !this._values.has(key),
valueTimeout = this._timeouts[key];
if (valueTimeout)
clearTimeout(valueTimeout);
this._values.set(key, value);
if (isNew){
this._keys.push(key);
if (this._keys.length > this.obj)
this._values.delete(this._keys.shift());
}
if (this.time){
this._timeouts[key] = setTimeout(() => {
this.remove(key);
delete this._timeouts[key];
}, this.time);
}
return this;
}
/**
* Removes an item from the cache collection.
* @param key
* @returns {*}
*/
remove(key:any){
key = key.toString();
let valueTimeout = this._timeouts[key];
if (valueTimeout) {
clearTimeout(valueTimeout);
delete this._timeouts[key];
}
delete this._getObservable[key];
let keyIndex = this._keys.indexOf(key);
if (~keyIndex){
this._keys.splice(keyIndex, 1);
let value = this._values.get(key);
this._values.delete(key);
return value;
}
return null;
}
clearGetters(){
for (let getter in this._getObservable) delete this._getObservable[getter];
}
private static validateSettings<T>(config?:DataCacheSettings<T>){
if (!config)
return;
if (config.max < 1)
throw new Error("Invalid max for DataCache, should be at least 2.");
};
}
export interface DataCacheSettings<T>{
max?:number,
time?:number,
getter?:(_:any) => Observable<T>
}

Просмотреть файл

@ -4,16 +4,20 @@ import {Field} from "../entity/entity-field";
import {EntityFields} from "../entity/entity-fields";
class EntitiesService{
private allEntities:Map<DataEntityType, ModelEntity> = new Map;
private _allEntities:Map<DataEntityType, ModelEntity> = new Map;
private tempEntityFields:Map<DataEntityType, EntityFields> = new Map;
get allEntities():Array<ModelEntity>{
return Array.from(this._allEntities.values());
}
getEntityByType(dataEntityType:DataEntityType):ModelEntity{
return this.allEntities.get(dataEntityType) || this.allEntities.get(dataEntityType.prototype);
return this._allEntities.get(dataEntityType) || this._allEntities.get(dataEntityType.prototype);
}
addEntity(dataEntityType:DataEntityType, entity:ModelEntity):ModelEntity{
if (!this.allEntities.has(dataEntityType.prototype))
this.allEntities.set(dataEntityType.prototype, entity);
if (!this._allEntities.has(dataEntityType))
this._allEntities.set(dataEntityType, entity);
entity.fields = this.getDataEntityTypeFields(dataEntityType);
@ -32,12 +36,24 @@ class EntitiesService{
dataTypeFields.set(field.id, field);
}
getEntityByPluralName(pluralName:string):DataEntityType{
let allEntities:Array<DataEntityType> = Array.from(this._allEntities.keys()),
pluralNameLowerCase = pluralName.toLowerCase();
for(let i=0, entity:DataEntityType; entity = allEntities[i]; i++){
if (entity.entityConfig.pluralName.toLowerCase() === pluralNameLowerCase)
return entity;
}
return null;
}
private getDataEntityTypeFields(dataEntityType:DataEntityType):EntityFields{
if (!dataEntityType)
return null;
let parentEntityDataType:DataEntityType = Object.getPrototypeOf(dataEntityType).prototype,
parentEntity:ModelEntity = this.allEntities.get(parentEntityDataType),
parentEntity:ModelEntity = this._allEntities.get(parentEntityDataType),
parentDataTypeFields:EntityFields = parentEntity && parentEntity.fields || this.getDataEntityTypeFields(parentEntityDataType) || null;
let fullDataEntityTypeFields:EntityFields = new Map;

Просмотреть файл

@ -0,0 +1,27 @@
import {Index} from "../models/index";
export class Immutability{
static freeze<T>(obj:T):Readonly<T> {
if (!Object.isFrozen(obj))
Object.freeze(obj);
if (Object(obj) === obj)
Object.getOwnPropertyNames(obj).forEach((prop: string) => Immutability.freeze((<Index>obj)[prop]));
return obj;
}
static unfreeze<T>(obj:Readonly<T>):T{
if (Object(obj) !== obj || obj instanceof Date || obj instanceof RegExp || obj instanceof Function)
return obj;
let unfrozenObj:T = Object.create(obj.constructor.prototype);
Object.assign(unfrozenObj, obj);
Object.getOwnPropertyNames(obj).forEach((prop: string) => {
(<Index>unfrozenObj)[prop] = Immutability.unfreeze((<Index>unfrozenObj)[prop]);
});
return unfrozenObj;
}
}

Просмотреть файл

@ -69,3 +69,21 @@ button {
button:hover {
background-color: #28739e;
}
table{
border: solid 1px #ddd;
}
table td, table th{
text-align: left;
padding: 6px 10px;
}
thead tr{
background: #106cc8;
color: White;
}
tr + tr{
border-top: solid 1px #eaeaea;
}

Просмотреть файл

@ -3,7 +3,6 @@ const fs = require("fs");
const bodyParser = require('body-parser');
const https = require("https");
var routeModules = [
//require("./modules/investigations.routes"),
//require("./modules/alerts.routes"),

Просмотреть файл

@ -1,6 +1,6 @@
fs = require("fs");
var MOCK_DATA_FOLDER = "./data/";
var MOCK_DATA_FOLDER = "./mock_data/";
var exports = module.exports = {
getFileData: getFileData,

Просмотреть файл

Просмотреть файл