blob: 3ee5a15e8e69053b7184b535c285c0e8d351a327 [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 Scandolo1aee1982017-02-17 08:33:23 -080050}
51
52export class XosModelDiscovererService implements IXosModelDiscovererService {
53 static $inject = [
54 '$log',
55 '$q',
56 'XosModelDefs',
57 'ConfigHelpers',
58 'XosRuntimeStates',
59 'XosNavigationService',
Matteo Scandolo042ea632017-03-01 19:02:34 -080060 'XosModelStore',
Matteo Scandolo0f3692e2017-07-10 14:06:41 -070061 'ngProgressFactory',
Matteo Scandolo63498472017-09-26 17:21:41 -070062 'AuthService',
63 'XosModeldefsCache'
Matteo Scandolo1aee1982017-02-17 08:33:23 -080064 ];
Matteo Scandolo63498472017-09-26 17:21:41 -070065
Matteo Scandolo1aee1982017-02-17 08:33:23 -080066 private xosServices: string[] = []; // list of loaded services
Matteo Scandolo042ea632017-03-01 19:02:34 -080067 private progressBar;
Matteo Scandolo9b460042017-04-14 16:24:45 -070068 private modelsLoaded: boolean = false;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080069
70 constructor (
71 private $log: ng.ILogService,
72 private $q: ng.IQService,
73 private XosModelDefs: IXosModeldefsService,
74 private ConfigHelpers: IXosConfigHelpersService,
75 private XosRuntimeStates: IXosRuntimeStatesService,
76 private XosNavigationService: IXosNavigationService,
Matteo Scandolo042ea632017-03-01 19:02:34 -080077 private XosModelStore: IXosModelStoreService,
Matteo Scandolo0f3692e2017-07-10 14:06:41 -070078 private ngProgressFactory: any, // check for type defs
Matteo Scandolo63498472017-09-26 17:21:41 -070079 private AuthService: IXosAuthService,
80 private XosModeldefsCache: IXosModeldefsCache
Matteo Scandolo1aee1982017-02-17 08:33:23 -080081 ) {
Matteo Scandolo042ea632017-03-01 19:02:34 -080082 this.progressBar = this.ngProgressFactory.createInstance();
83 this.progressBar.setColor('#f6a821');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080084 }
85
Matteo Scandolo9b460042017-04-14 16:24:45 -070086 public areModelsLoaded(): boolean {
87 return this.modelsLoaded;
88 }
89
Matteo Scandolo47c53fc2017-03-23 14:11:32 -070090 public getApiUrlFromModel(model: IXosModel): string {
91 if (model.app === 'core') {
92 return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
93 }
94 else {
Matteo Scandolo63498472017-09-26 17:21:41 -070095 const serviceName = this.XosModeldefsCache.serviceNameFromAppName(model.app);
Matteo Scandolo47c53fc2017-03-23 14:11:32 -070096 return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
97 }
98 }
99
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800100 public discover() {
101 const d = this.$q.defer();
Matteo Scandolo042ea632017-03-01 19:02:34 -0800102 this.progressBar.start();
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800103 this.XosModelDefs.get()
104 .then((modelsDef: IXosModeldef[]) => {
Matteo Scandolo63498472017-09-26 17:21:41 -0700105 // TODO store modeldefs and add a method to retrieve the model definition from the name
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800106 const pArray = [];
107 _.forEach(modelsDef, (model: IXosModeldef) => {
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800108 this.$log.debug(`[XosModelDiscovererService] Loading: ${model.name}`);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800109 let p = this.cacheModelEntries(model)
110 .then(model => {
111 return this.addState(model);
112 })
113 .then(model => {
114 return this.addNavItem(model);
115 })
116 .then(model => {
117 return this.getTableCfg(model);
118 })
119 .then(model => {
120 return this.getFormCfg(model);
121 })
122 .then(model => {
123 return this.storeModel(model);
124 })
125 .then(model => {
126 this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
Matteo Scandolo042ea632017-03-01 19:02:34 -0800127 return this.$q.resolve('true');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800128 })
129 .catch(err => {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200130 this.$log.warn(`[XosModelDiscovererService] Model ${model.name} NOT stored`, err);
131 const isAuthError = this.AuthService.isAuthError(err);
132 if (isAuthError) {
133 this.$log.warn(`[XosModelDiscovererService] User is not authentincated`);
134 return this.$q.reject(err);
135 }
Matteo Scandolo042ea632017-03-01 19:02:34 -0800136 return this.$q.resolve('false');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800137 });
138 pArray.push(p);
139 });
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200140
141
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800142 this.$q.all(pArray)
Matteo Scandolo042ea632017-03-01 19:02:34 -0800143 .then((res) => {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200144 // the ModelLoader promise won't ever be reject, in case it will be resolve with value false,
Matteo Scandolo042ea632017-03-01 19:02:34 -0800145 // that's because we want to wait anyway for all the models to be loaded
146 if (res.indexOf('false') > -1) {
Matteo Scandolo02229382017-04-18 11:52:23 -0700147 return d.resolve(false);
Matteo Scandolo042ea632017-03-01 19:02:34 -0800148 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800149 d.resolve(true);
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200150 this.modelsLoaded = true;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800151 })
Matteo Scandolo02229382017-04-18 11:52:23 -0700152 .catch((e) => {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200153 this.XosModelStore.clean(); // reset all the observable otherwise they'll store login errors
154 this.$log.warn(`[XosModelDiscovererService]`, e);
155 // the ModelLoader promise will be rejected in case of authentication error
156 d.reject(e);
Matteo Scandolo042ea632017-03-01 19:02:34 -0800157 })
158 .finally(() => {
159 this.progressBar.complete();
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800160 });
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200161 })
162 .catch(err => {
163 this.progressBar.complete();
164 this.$log.error(`[XosModelDiscovererService] Cannot load model defs`, err);
165 return d.resolve('chameleon');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800166 });
167 return d.promise;
168 }
169
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800170 private stateNameFromModel(model: IXosModel): string {
Matteo Scandolo63498472017-09-26 17:21:41 -0700171 return `xos.${this.XosModeldefsCache.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800172 }
173
174 private getParentStateFromModel(model: IXosModel): string {
175 let parentState: string;
176 if (model.app === 'core') {
177 parentState = 'xos.core';
178 }
179 else {
180 const serviceName = this.addService(model);
181 parentState = `xos.${serviceName}`;
182 }
183 return parentState;
184 }
185
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800186 // add a service state and navigation item if it is not already there
187 private addService(model: IXosModel): string {
Matteo Scandolo63498472017-09-26 17:21:41 -0700188 const serviceName: string = this.XosModeldefsCache.serviceNameFromAppName(model.app);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800189 if (!_.find(this.xosServices, n => n === serviceName)) {
190 const serviceState = {
191 url: serviceName,
192 parent: 'xos',
193 abstract: true,
194 template: '<div ui-view></div>'
195 };
196 this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
197
198 this.XosNavigationService.add({
199 label: this.ConfigHelpers.toLabel(serviceName),
200 state: `xos.${serviceName}`,
201 });
202 this.xosServices.push(serviceName);
203 }
204 return serviceName;
205 }
206
207 private addState(model: IXosModel): ng.IPromise<IXosModel> {
208 const d = this.$q.defer();
209 const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
210 const state: IXosState = {
211 parent: this.getParentStateFromModel(model),
212 url: clientUrl,
213 params: {
214 id: null
215 },
216 data: {
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700217 model: model.name,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800218 },
219 component: 'xosCrud',
220 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800221
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700222 if (angular.isDefined(model.relations)) {
223 state.data.relations = model.relations;
224 }
225
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800226 try {
227 this.XosRuntimeStates.addState(
228 this.stateNameFromModel(model),
229 state
230 );
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800231
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800232 // extend model
Matteo Scandolo63498472017-09-26 17:21:41 -0700233 model.clientUrl = `${this.XosModeldefsCache.serviceNameFromAppName(model.app)}${clientUrl}`;
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800234
235 d.resolve(model);
236 } catch (e) {
237 d.reject(e);
238 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800239 return d.promise;
240 }
241
242 private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
243 const d = this.$q.defer();
244
245 const stateName = this.stateNameFromModel(model);
246
247 const parentState: string = this.getParentStateFromModel(model);
248
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800249 try {
Matteo Scandolo580033a2017-08-17 11:16:39 -0700250 const name = model.verbose_name ? model.verbose_name : model.name;
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800251 this.XosNavigationService.add({
Matteo Scandolo580033a2017-08-17 11:16:39 -0700252 label: this.ConfigHelpers.pluralize(name),
Matteo Scandoloc80ccbd2017-02-27 16:24:33 -0800253 state: stateName,
254 parent: parentState
255 });
256 d.resolve(model);
257 } catch (e) {
258 d.reject(e);
259 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800260
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800261
262 return d.promise;
263 }
264
265 private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200266
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800267 const d = this.$q.defer();
268
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800269 const apiUrl = this.getApiUrlFromModel(model);
270 this.XosModelStore.query(model.name, apiUrl)
Matteo Scandolo29edc0f2018-04-26 17:19:10 +0200271 .skip(1) // NOTE observables returns as first an empty array, so skip it
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800272 .subscribe(
273 () => {
Matteo Scandolo38e94a82017-03-02 12:17:27 -0800274 return d.resolve(model);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800275 },
276 err => {
Matteo Scandolo042ea632017-03-01 19:02:34 -0800277 return d.reject(err);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800278 }
279 );
280
281 return d.promise;
282 }
283
284 private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
285
286 const d = this.$q.defer();
287
288 const stateUrl = this.stateNameFromModel(model);
289
290 model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
291
292 d.resolve(model);
293
294 return d.promise;
295 }
296
297 private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
298
299 const d = this.$q.defer();
300
301 model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
302
303 d.resolve(model);
304
305 return d.promise;
306 }
307
308 private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
309
310 const d = this.$q.defer();
311
Matteo Scandolo63498472017-09-26 17:21:41 -0700312 this.XosModeldefsCache.cache(model);
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800313
314 d.resolve(model);
315
316 return d.promise;
317 }
318}