blob: 2e5597ebddd48805f005a2a6d86da73f014d32f3 [file] [log] [blame]
Matteo Scandolofb46ae62017-08-08 09:10:50 -07001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
Matteo Scandolo1aee1982017-02-17 08:33:23 -080019// TODO test me hard!!!
20
21import * as _ from 'lodash';
Matteo Scandolod67adee2018-03-08 16:27:05 -080022import {IXosModeldefsService, IXosModeldef, IXosModelDefsRelation} from '../rest/modeldefs.rest';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080023import {IXosTableCfg} from '../../core/table/table';
24import {IXosFormCfg} from '../../core/form/form';
25import {IXosNavigationService} from '../../core/services/navigation';
Matteo Scandolod67adee2018-03-08 16:27:05 -080026import {IXosConfigHelpersService, IXosModelDefsField} from '../../core/services/helpers/config.helpers';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080027import {IXosRuntimeStatesService, IXosState} from '../../core/services/runtime-states';
28import {IXosModelStoreService} from '../stores/model.store';
Matteo Scandolo0f3692e2017-07-10 14:06:41 -070029import {IXosAuthService} from '../rest/auth.rest';
Matteo Scandolo63498472017-09-26 17:21:41 -070030import {IXosModeldefsCache} from './modeldefs.service';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080031
32export interface IXosModel {
33 name: string; // the model name
34 app: string; // the service to wich it belong
35 fields: IXosModelDefsField[];
36 relations?: IXosModelDefsRelation[];
37 backendUrl?: string; // the api endpoint
38 clientUrl?: string; // the view url
39 tableCfg?: IXosTableCfg;
40 formCfg?: IXosFormCfg;
Matteo Scandolo580033a2017-08-17 11:16:39 -070041 description: string;
42 verbose_name: string;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080043}
44
45// Service
46export interface IXosModelDiscovererService {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +020047 discover(): ng.IPromise<string>;
Matteo Scandolo47c53fc2017-03-23 14:11:32 -070048 getApiUrlFromModel(model: IXosModel): string;
Matteo Scandolo9b460042017-04-14 16:24:45 -070049 areModelsLoaded(): boolean;
Matteo Scandolobf4e8402019-06-24 12:22:49 -070050 getStatusMessage(): string;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080051}
52
53export class XosModelDiscovererService implements IXosModelDiscovererService {
54 static $inject = [
55 '$log',
56 '$q',
Matteo Scandolobf4e8402019-06-24 12:22:49 -070057 '$interval',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080058 'XosModelDefs',
59 'ConfigHelpers',
60 'XosRuntimeStates',
61 'XosNavigationService',
Matteo Scandolo042ea632017-03-01 19:02:34 -080062 'XosModelStore',
Matteo Scandolo0f3692e2017-07-10 14:06:41 -070063 'ngProgressFactory',
Matteo Scandolo63498472017-09-26 17:21:41 -070064 'AuthService',
65 'XosModeldefsCache'
Matteo Scandolo1aee1982017-02-17 08:33:23 -080066 ];
Matteo Scandolo63498472017-09-26 17:21:41 -070067
Matteo Scandolo1aee1982017-02-17 08:33:23 -080068 private xosServices: string[] = []; // list of loaded services
Matteo Scandolo042ea632017-03-01 19:02:34 -080069 private progressBar;
Matteo Scandolo9b460042017-04-14 16:24:45 -070070 private modelsLoaded: boolean = false;
Matteo Scandolobf4e8402019-06-24 12:22:49 -070071 private statusMessage: string = 'Loading models definition';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080072
73 constructor (
74 private $log: ng.ILogService,
75 private $q: ng.IQService,
Matteo Scandolobf4e8402019-06-24 12:22:49 -070076 private $interval: ng.IIntervalService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080077 private XosModelDefs: IXosModeldefsService,
78 private ConfigHelpers: IXosConfigHelpersService,
79 private XosRuntimeStates: IXosRuntimeStatesService,
80 private XosNavigationService: IXosNavigationService,
Matteo Scandolo042ea632017-03-01 19:02:34 -080081 private XosModelStore: IXosModelStoreService,
Matteo Scandolo0f3692e2017-07-10 14:06:41 -070082 private ngProgressFactory: any, // check for type defs
Matteo Scandolo63498472017-09-26 17:21:41 -070083 private AuthService: IXosAuthService,
84 private XosModeldefsCache: IXosModeldefsCache
Matteo Scandolo1aee1982017-02-17 08:33:23 -080085 ) {
Matteo Scandolo042ea632017-03-01 19:02:34 -080086 this.progressBar = this.ngProgressFactory.createInstance();
87 this.progressBar.setColor('#f6a821');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080088 }
89
Matteo Scandolo9b460042017-04-14 16:24:45 -070090 public areModelsLoaded(): boolean {
91 return this.modelsLoaded;
92 }
93
Matteo Scandolo47c53fc2017-03-23 14:11:32 -070094 public getApiUrlFromModel(model: IXosModel): string {
95 if (model.app === 'core') {
96 return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
97 }
98 else {
Matteo Scandolo63498472017-09-26 17:21:41 -070099 const serviceName = this.XosModeldefsCache.serviceNameFromAppName(model.app);
Matteo Scandolo47c53fc2017-03-23 14:11:32 -0700100 return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
101 }
102 }
103
Matteo Scandolobf4e8402019-06-24 12:22:49 -0700104 public getStatusMessage(): string {
105 return this.statusMessage;
106 }
107
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800108 public discover() {
109 const d = this.$q.defer();
Matteo Scandolobf4e8402019-06-24 12:22:49 -0700110 // loading stats
111 let loadingSince = 0;
112 const modelDefInerval = this.$interval(() => {
113 loadingSince += 1;
114 this.setModelDefTimeMsg(loadingSince);
115 }, 1000);
116 this.progressBar.set(1);
117
118 // start loading data
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800119 this.XosModelDefs.get()
120 .then((modelsDef: IXosModeldef[]) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800121 const pArray = [];
Matteo Scandolobf4e8402019-06-24 12:22:49 -0700122
123 // Setting up counters for the status message
124 this.$interval.cancel(modelDefInerval);
125 const modelsTotal = modelsDef.length;
126 let modelsLoaded = 0;
127 this.setModelsCountMsg(modelsLoaded, modelsTotal);
128
129 // Setting up counters for the loading bar
130 this.progressBar.set(10);
131 const progressBarStep = 90 / modelsTotal;
132
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800133 _.forEach(modelsDef, (model: IXosModeldef) => {
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800134 this.$log.debug(`[XosModelDiscovererService] Loading: ${model.name}`);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800135 let p = this.cacheModelEntries(model)
136 .then(model => {
137 return this.addState(model);
138 })
139 .then(model => {
140 return this.addNavItem(model);
141 })
142 .then(model => {
143 return this.getTableCfg(model);
144 })
145 .then(model => {
146 return this.getFormCfg(model);
147 })
148 .then(model => {
149 return this.storeModel(model);
150 })
151 .then(model => {
Matteo Scandolobf4e8402019-06-24 12:22:49 -0700152 // Updating the status message
153 modelsLoaded = modelsLoaded + 1;
154 this.setModelsCountMsg(modelsLoaded, modelsTotal);
155
156 // Updating the progress bar
157 this.progressBar.set(10 + (modelsLoaded * progressBarStep));
158
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800159 this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
Matteo Scandolo042ea632017-03-01 19:02:34 -0800160 return this.$q.resolve('true');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800161 })
162 .catch(err => {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200163 this.$log.warn(`[XosModelDiscovererService] Model ${model.name} NOT stored`, err);
164 const isAuthError = this.AuthService.isAuthError(err);
165 if (isAuthError) {
166 this.$log.warn(`[XosModelDiscovererService] User is not authentincated`);
167 return this.$q.reject(err);
168 }
Matteo Scandolo042ea632017-03-01 19:02:34 -0800169 return this.$q.resolve('false');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800170 });
171 pArray.push(p);
172 });
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200173
174
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800175 this.$q.all(pArray)
Matteo Scandolo042ea632017-03-01 19:02:34 -0800176 .then((res) => {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200177 // the ModelLoader promise won't ever be reject, in case it will be resolve with value false,
Matteo Scandolo042ea632017-03-01 19:02:34 -0800178 // that's because we want to wait anyway for all the models to be loaded
179 if (res.indexOf('false') > -1) {
Matteo Scandolo02229382017-04-18 11:52:23 -0700180 return d.resolve(false);
Matteo Scandolo042ea632017-03-01 19:02:34 -0800181 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800182 d.resolve(true);
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200183 this.modelsLoaded = true;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800184 })
Matteo Scandolo02229382017-04-18 11:52:23 -0700185 .catch((e) => {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200186 this.XosModelStore.clean(); // reset all the observable otherwise they'll store login errors
187 this.$log.warn(`[XosModelDiscovererService]`, e);
188 // the ModelLoader promise will be rejected in case of authentication error
189 d.reject(e);
Matteo Scandolo042ea632017-03-01 19:02:34 -0800190 })
191 .finally(() => {
192 this.progressBar.complete();
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800193 });
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200194 })
195 .catch(err => {
196 this.progressBar.complete();
197 this.$log.error(`[XosModelDiscovererService] Cannot load model defs`, err);
198 return d.resolve('chameleon');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800199 });
200 return d.promise;
201 }
202
Matteo Scandolobf4e8402019-06-24 12:22:49 -0700203 private setModelDefTimeMsg(seconds: number) {
204 this.statusMessage = `Loading models definition for ${seconds} seconds...`;
205 }
206
207 private setModelsCountMsg(loaded: number, modelsTotal: number) {
208 const percent = Math.round((100 * loaded) / modelsTotal);
209 this.statusMessage = `Loading data.... ${percent}% completed (${loaded} of ${modelsTotal} models)`;
210 }
211
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800212 private stateNameFromModel(model: IXosModel): string {
Matteo Scandolo63498472017-09-26 17:21:41 -0700213 return `xos.${this.XosModeldefsCache.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800214 }
215
216 private getParentStateFromModel(model: IXosModel): string {
217 let parentState: string;
218 if (model.app === 'core') {
219 parentState = 'xos.core';
220 }
221 else {
222 const serviceName = this.addService(model);
223 parentState = `xos.${serviceName}`;
224 }
225 return parentState;
226 }
227
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800228 // add a service state and navigation item if it is not already there
229 private addService(model: IXosModel): string {
Matteo Scandolo63498472017-09-26 17:21:41 -0700230 const serviceName: string = this.XosModeldefsCache.serviceNameFromAppName(model.app);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800231 if (!_.find(this.xosServices, n => n === serviceName)) {
232 const serviceState = {
233 url: serviceName,
234 parent: 'xos',
235 abstract: true,
236 template: '<div ui-view></div>'
237 };
238 this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
239
240 this.XosNavigationService.add({
241 label: this.ConfigHelpers.toLabel(serviceName),
242 state: `xos.${serviceName}`,
243 });
244 this.xosServices.push(serviceName);
245 }
246 return serviceName;
247 }
248
249 private addState(model: IXosModel): ng.IPromise<IXosModel> {
250 const d = this.$q.defer();
251 const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
252 const state: IXosState = {
253 parent: this.getParentStateFromModel(model),
254 url: clientUrl,
255 params: {
256 id: null
257 },
258 data: {
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700259 model: model.name,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800260 },
261 component: 'xosCrud',
262 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800263
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700264 if (angular.isDefined(model.relations)) {
265 state.data.relations = model.relations;
266 }
267
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800268 try {
269 this.XosRuntimeStates.addState(
270 this.stateNameFromModel(model),
271 state
272 );
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800273
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800274 // extend model
Matteo Scandolo63498472017-09-26 17:21:41 -0700275 model.clientUrl = `${this.XosModeldefsCache.serviceNameFromAppName(model.app)}${clientUrl}`;
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800276
277 d.resolve(model);
278 } catch (e) {
279 d.reject(e);
280 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800281 return d.promise;
282 }
283
284 private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
285 const d = this.$q.defer();
286
287 const stateName = this.stateNameFromModel(model);
288
289 const parentState: string = this.getParentStateFromModel(model);
290
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800291 try {
Matteo Scandolo580033a2017-08-17 11:16:39 -0700292 const name = model.verbose_name ? model.verbose_name : model.name;
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800293 this.XosNavigationService.add({
Matteo Scandolo580033a2017-08-17 11:16:39 -0700294 label: this.ConfigHelpers.pluralize(name),
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800295 state: stateName,
296 parent: parentState
297 });
298 d.resolve(model);
299 } catch (e) {
300 d.reject(e);
301 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800302
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800303
304 return d.promise;
305 }
306
307 private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200308
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800309 const d = this.$q.defer();
310
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800311 const apiUrl = this.getApiUrlFromModel(model);
312 this.XosModelStore.query(model.name, apiUrl)
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200313 .skip(1) // NOTE observables returns as first an empty array, so skip it
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800314 .subscribe(
315 () => {
Matteo Scandolo38e94a82017-03-02 12:17:27 -0800316 return d.resolve(model);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800317 },
318 err => {
Matteo Scandolo042ea632017-03-01 19:02:34 -0800319 return d.reject(err);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800320 }
321 );
322
323 return d.promise;
324 }
325
326 private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
327
328 const d = this.$q.defer();
329
330 const stateUrl = this.stateNameFromModel(model);
331
332 model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
333
334 d.resolve(model);
335
336 return d.promise;
337 }
338
339 private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
340
341 const d = this.$q.defer();
342
343 model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
344
345 d.resolve(model);
346
347 return d.promise;
348 }
349
350 private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
351
352 const d = this.$q.defer();
353
Matteo Scandolo63498472017-09-26 17:21:41 -0700354 this.XosModeldefsCache.cache(model);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800355
356 d.resolve(model);
357
358 return d.promise;
359 }
360}