blob: 434f74ee8e6f710675097caa438904ac64cb4c7c [file] [log] [blame]
Matteo Scandolo1aee1982017-02-17 08:33:23 -08001// TODO test me hard!!!
2
3import * as _ from 'lodash';
4import {IXosModeldefsService, IXosModeldef, IXosModelDefsField, IXosModelDefsRelation} from '../rest/modeldefs.rest';
5import {IXosTableCfg} from '../../core/table/table';
6import {IXosFormCfg} from '../../core/form/form';
7import {IXosNavigationService} from '../../core/services/navigation';
8import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
9import {IXosRuntimeStatesService, IXosState} from '../../core/services/runtime-states';
10import {IXosModelStoreService} from '../stores/model.store';
11
12export interface IXosModel {
13 name: string; // the model name
14 app: string; // the service to wich it belong
15 fields: IXosModelDefsField[];
16 relations?: IXosModelDefsRelation[];
17 backendUrl?: string; // the api endpoint
18 clientUrl?: string; // the view url
19 tableCfg?: IXosTableCfg;
20 formCfg?: IXosFormCfg;
21}
22
23// Service
24export interface IXosModelDiscovererService {
25 discover(): ng.IPromise<boolean>;
26 get(modelName: string): IXosModel;
27}
28
29export class XosModelDiscovererService implements IXosModelDiscovererService {
30 static $inject = [
31 '$log',
32 '$q',
33 'XosModelDefs',
34 'ConfigHelpers',
35 'XosRuntimeStates',
36 'XosNavigationService',
Matteo Scandolo042ea632017-03-01 19:02:34 -080037 'XosModelStore',
38 'ngProgressFactory'
Matteo Scandolo1aee1982017-02-17 08:33:23 -080039 ];
40 private xosModels: IXosModel[] = []; // list of augmented model definitions;
41 private xosServices: string[] = []; // list of loaded services
Matteo Scandolo042ea632017-03-01 19:02:34 -080042 private progressBar;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080043
44 constructor (
45 private $log: ng.ILogService,
46 private $q: ng.IQService,
47 private XosModelDefs: IXosModeldefsService,
48 private ConfigHelpers: IXosConfigHelpersService,
49 private XosRuntimeStates: IXosRuntimeStatesService,
50 private XosNavigationService: IXosNavigationService,
Matteo Scandolo042ea632017-03-01 19:02:34 -080051 private XosModelStore: IXosModelStoreService,
52 private ngProgressFactory: any // check for type defs
Matteo Scandolo1aee1982017-02-17 08:33:23 -080053 ) {
Matteo Scandolo042ea632017-03-01 19:02:34 -080054 this.progressBar = this.ngProgressFactory.createInstance();
55 this.progressBar.setColor('#f6a821');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080056 }
57
58 public get(modelName: string): IXosModel|null {
59 return _.find(this.xosModels, m => m.name === modelName);
60 }
61
62 public discover() {
63 const d = this.$q.defer();
Matteo Scandolo042ea632017-03-01 19:02:34 -080064 this.progressBar.start();
Matteo Scandolo1aee1982017-02-17 08:33:23 -080065 this.XosModelDefs.get()
66 .then((modelsDef: IXosModeldef[]) => {
67
68 const pArray = [];
69 _.forEach(modelsDef, (model: IXosModeldef) => {
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -080070 this.$log.debug(`[XosModelDiscovererService] Loading: ${model.name}`);
Matteo Scandolo1aee1982017-02-17 08:33:23 -080071 let p = this.cacheModelEntries(model)
72 .then(model => {
73 return this.addState(model);
74 })
75 .then(model => {
76 return this.addNavItem(model);
77 })
78 .then(model => {
79 return this.getTableCfg(model);
80 })
81 .then(model => {
82 return this.getFormCfg(model);
83 })
84 .then(model => {
85 return this.storeModel(model);
86 })
87 .then(model => {
88 this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
Matteo Scandolo042ea632017-03-01 19:02:34 -080089 return this.$q.resolve('true');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080090 })
91 .catch(err => {
92 this.$log.error(`[XosModelDiscovererService] Model ${model.name} NOT stored`);
Matteo Scandolo042ea632017-03-01 19:02:34 -080093 return this.$q.resolve('false');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080094 });
95 pArray.push(p);
96 });
97 this.$q.all(pArray)
Matteo Scandolo042ea632017-03-01 19:02:34 -080098 .then((res) => {
99 // the Model Loader promise won't ever be reject, in case it will be resolve with value false,
100 // that's because we want to wait anyway for all the models to be loaded
101 if (res.indexOf('false') > -1) {
102 d.resolve(false);
103 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800104 d.resolve(true);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800105 })
106 .catch(() => {
107 d.resolve(false);
Matteo Scandolo042ea632017-03-01 19:02:34 -0800108 })
109 .finally(() => {
110 this.progressBar.complete();
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800111 });
112 });
113 return d.promise;
114 }
115
116 private serviceNameFromAppName(appName: string): string {
117 return appName.replace('services.', '');
118 }
119
120 private stateNameFromModel(model: IXosModel): string {
121 return `xos.${this.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
122 }
123
124 private getParentStateFromModel(model: IXosModel): string {
125 let parentState: string;
126 if (model.app === 'core') {
127 parentState = 'xos.core';
128 }
129 else {
130 const serviceName = this.addService(model);
131 parentState = `xos.${serviceName}`;
132 }
133 return parentState;
134 }
135
136 private getApiUrlFromModel(model: IXosModel): string {
137 if (model.app === 'core') {
138 return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
139 }
140 else {
141 const serviceName = this.serviceNameFromAppName(model.app);
142 return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
143 }
144 }
145
146 // add a service state and navigation item if it is not already there
147 private addService(model: IXosModel): string {
148 const serviceName: string = this.serviceNameFromAppName(model.app);
149 if (!_.find(this.xosServices, n => n === serviceName)) {
150 const serviceState = {
151 url: serviceName,
152 parent: 'xos',
153 abstract: true,
154 template: '<div ui-view></div>'
155 };
156 this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
157
158 this.XosNavigationService.add({
159 label: this.ConfigHelpers.toLabel(serviceName),
160 state: `xos.${serviceName}`,
161 });
162 this.xosServices.push(serviceName);
163 }
164 return serviceName;
165 }
166
167 private addState(model: IXosModel): ng.IPromise<IXosModel> {
168 const d = this.$q.defer();
169 const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
170 const state: IXosState = {
171 parent: this.getParentStateFromModel(model),
172 url: clientUrl,
173 params: {
174 id: null
175 },
176 data: {
177 model: model.name
178 },
179 component: 'xosCrud',
180 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800181
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800182 try {
183 this.XosRuntimeStates.addState(
184 this.stateNameFromModel(model),
185 state
186 );
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800187
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800188 // extend model
189 model.clientUrl = `${this.serviceNameFromAppName(model.app)}${clientUrl}`;
190
191 d.resolve(model);
192 } catch (e) {
193 d.reject(e);
194 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800195 return d.promise;
196 }
197
198 private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
199 const d = this.$q.defer();
200
201 const stateName = this.stateNameFromModel(model);
202
203 const parentState: string = this.getParentStateFromModel(model);
204
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800205 try {
206 this.XosNavigationService.add({
207 label: this.ConfigHelpers.pluralize(model.name),
208 state: stateName,
209 parent: parentState
210 });
211 d.resolve(model);
212 } catch (e) {
213 d.reject(e);
214 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800215
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800216
217 return d.promise;
218 }
219
220 private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
221 const d = this.$q.defer();
222
Matteo Scandolo042ea632017-03-01 19:02:34 -0800223 let populated = false;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800224 const apiUrl = this.getApiUrlFromModel(model);
225 this.XosModelStore.query(model.name, apiUrl)
226 .subscribe(
227 () => {
228 // skipping the first response as the observable gets created as an empty array
Matteo Scandolo042ea632017-03-01 19:02:34 -0800229 if (populated) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800230 return d.resolve(model);
231 }
Matteo Scandolo042ea632017-03-01 19:02:34 -0800232 populated = true;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800233 },
234 err => {
Matteo Scandolo042ea632017-03-01 19:02:34 -0800235 return d.reject(err);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800236 }
237 );
238
239 return d.promise;
240 }
241
242 private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
243
244 const d = this.$q.defer();
245
246 const stateUrl = this.stateNameFromModel(model);
247
248 model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
249
250 d.resolve(model);
251
252 return d.promise;
253 }
254
255 private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
256
257 const d = this.$q.defer();
258
259 model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
260
261 d.resolve(model);
262
263 return d.promise;
264 }
265
266 private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
267
268 const d = this.$q.defer();
269
270 if (!_.find(this.xosModels, m => m.name === model.name)) {
271 this.xosModels.push(model);
272 }
273
274 d.resolve(model);
275
276 return d.promise;
277 }
278}