blob: 7cd899153d60b0b99bfc0a7b51ed3e63423eecda [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 Scandolod58d5042016-12-16 16:59:21 -080019import * as _ from 'lodash';
20import * as pluralize from 'pluralize';
Matteo Scandolocb466ed2017-01-04 17:16:24 -080021import {IXosTableColumn, IXosTableCfg} from '../../table/table';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080022import {IXosModeldef} from '../../../datasources/rest/modeldefs.rest';
Matteo Scandoloe7e052d2017-07-31 19:54:31 -070023import {IXosFormCfg, IXosFormInput, IXosFormInputValidator, IXosFormInputOptions} from '../../form/form';
Matteo Scandolo47860fe2017-02-02 12:05:55 -080024import {IXosModelStoreService} from '../../../datasources/stores/model.store';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080025import {IXosState} from '../runtime-states';
26
27export interface IXosModelDefsFieldValidators {
28 name: string;
29 bool_value?: boolean;
30 int_value?: number;
31}
Matteo Scandolod58d5042016-12-16 16:59:21 -080032
33export interface IXosModelDefsField {
34 name: string;
35 type: string;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080036 validators?: IXosModelDefsFieldValidators[];
Matteo Scandolo04964232017-01-07 12:53:46 -080037 hint?: string;
38 relation?: {
39 model: string;
40 type: string;
41 };
Matteo Scandoloe7e052d2017-07-31 19:54:31 -070042 options?: IXosFormInputOptions[];
43 default?: any | null;
Matteo Scandolod58d5042016-12-16 16:59:21 -080044}
45
46export interface IXosConfigHelpersService {
Matteo Scandoloee655a12016-12-19 15:38:43 -080047 excluded_fields: string[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080048 modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[];
49 modelToTableCfg(model: IXosModeldef, modelName: string): IXosTableCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -080050 modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080051 modelToFormCfg(model: IXosModeldef): IXosFormCfg;
Matteo Scandolod58d5042016-12-16 16:59:21 -080052 pluralize(string: string, quantity?: number, count?: boolean): string;
53 toLabel(string: string, pluralize?: boolean): string;
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080054 toLabels(string: string[], pluralize?: boolean): string[];
Matteo Scandoloa242c872017-01-12 15:13:00 -080055 stateFromCoreModel(name: string): string;
56 stateWithParams(name: string, model: any): string;
Matteo Scandolo86bc26a2017-01-18 11:06:47 -080057 stateWithParamsForJs(name: string, model: any): any;
Matteo Scandolod58d5042016-12-16 16:59:21 -080058}
59
Matteo Scandolo8b2370c2017-02-02 17:19:07 -080060export class ConfigHelpers implements IXosConfigHelpersService {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080061 static $inject = [
62 '$state',
63 'toastr',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080064 'XosModelStore'];
Matteo Scandolod58d5042016-12-16 16:59:21 -080065
Matteo Scandolo1aee1982017-02-17 08:33:23 -080066 public excluded_fields = [
Matteo Scandoloee655a12016-12-19 15:38:43 -080067 'created',
68 'updated',
69 'enacted',
70 'policed',
71 'backend_register',
72 'deleted',
73 'write_protect',
74 'lazy_blocked',
75 'no_sync',
76 'no_policy',
77 'omf_friendly',
78 'enabled',
Matteo Scandolod62ea792016-12-22 14:02:28 -080079 'validators',
Matteo Scandolo80c3a652017-01-06 10:48:31 -080080 'password',
81 'backend_need_delete',
Matteo Scandolod53ac1d2017-08-01 15:06:09 -070082 'backend_need_reap',
83 'leaf_model_name'
Matteo Scandoloee655a12016-12-19 15:38:43 -080084 ];
85
Matteo Scandolod53ac1d2017-08-01 15:06:09 -070086 public form_excluded_fields = this.excluded_fields.concat([
87 'id',
88 'policy_status',
89 'backend_status',
90 ]);
91
Matteo Scandolocb466ed2017-01-04 17:16:24 -080092 constructor(
Matteo Scandoloa242c872017-01-12 15:13:00 -080093 private $state: ng.ui.IStateService,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -080094 private toastr: ng.toastr.IToastrService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080095 private XosModelStore: IXosModelStoreService
Matteo Scandolocb466ed2017-01-04 17:16:24 -080096 ) {
Matteo Scandolo08464e52017-01-17 13:35:27 -080097 pluralize.addIrregularRule('xos', 'xoses');
Matteo Scandolod58d5042016-12-16 16:59:21 -080098 pluralize.addPluralRule(/slice$/i, 'slices');
Matteo Scandolo80c3a652017-01-06 10:48:31 -080099 pluralize.addSingularRule(/slice$/i, 'slice');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800100 pluralize.addPluralRule(/library$/i, 'librarys');
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700101 pluralize.addPluralRule(/imagedeployments/i, 'imagedeploymentss');
102 pluralize.addPluralRule(/controllerimages/i, 'controllerimagess');
103 pluralize.addPluralRule(/servicedependency/i, 'servicedependencys');
104 pluralize.addPluralRule(/servicemonitoringagentinfo/i, 'servicemonitoringagentinfoes');
Matteo Scandolod58d5042016-12-16 16:59:21 -0800105 }
106
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800107 public pluralize(string: string, quantity?: number, count?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800108 return pluralize(string, quantity, count);
109 }
110
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800111 public toLabels(strings: string[], pluralize?: boolean): string[] {
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -0800112 if (angular.isArray(strings)) {
113 return _.map(strings, s => {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800114 return this.toLabel(s, pluralize);
115 });
116 }
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -0800117 }
118
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800119 public toLabel(string: string, pluralize?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800120
121 if (pluralize) {
122 string = this.pluralize(string);
123 }
124
125 string = this.fromCamelCase(string);
126 string = this.fromSnakeCase(string);
127 string = this.fromKebabCase(string);
128
129 return this.capitalizeFirst(string);
130 }
131
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800132 public modelToTableCfg(model: IXosModeldef, baseUrl: string): IXosTableCfg {
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800133 const cfg = {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800134 columns: this.modelFieldsToColumnsCfg(model),
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800135 filter: 'fulltext',
136 order: {field: 'id', reverse: false},
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800137 pagination: {
138 pageSize: 10
139 },
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800140 actions: [
141 {
142 label: 'delete',
143 icon: 'remove',
144 color: 'red',
145 cb: (item) => {
146 let obj = angular.copy(item);
147
148 item.$delete()
149 .then((res) => {
150 if (res.status === 404) {
151 // TODO understand why it does not go directly in catch
152 throw new Error();
153 }
154 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
155 })
156 .catch(() => {
157 this.toastr.error(`Error while deleting ${obj.name}`);
158 });
159 }
160 }
161 ]
162 };
163 return cfg;
164 }
165
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800166 public modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[] {
167 const fields: IXosModelDefsField[] = model.fields;
168 const modelName: string = model.name;
Matteo Scandolo231de262017-01-04 16:33:14 -0800169 const columns = _.map(fields, (f) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800170 if (!angular.isDefined(f) || this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800171 return;
172 }
173 const col: IXosTableColumn = {
174 label: this.toLabel(f.name),
175 prop: f.name
176 };
177
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800178 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800179 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandoloee655a12016-12-19 15:38:43 -0800180 }
181
Matteo Scandolo04964232017-01-07 12:53:46 -0800182 // if the field identify a relation, create a link
Matteo Scandolo18975142017-08-01 14:48:04 -0700183 if (f.relation && f.relation.type === 'manytoone') {
Matteo Scandolo04964232017-01-07 12:53:46 -0800184 col.type = 'custom';
185 col.formatter = item => {
186 this.populateRelated(item, item[f.name], f);
187 return item[f.name];
188 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800189 col.link = item => {
190 return this.stateWithParams(f.relation.model, item);
191 };
Matteo Scandolo04964232017-01-07 12:53:46 -0800192 }
193
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700194 if (f.name === 'backend_status' || f.name === 'policy_status') {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800195 col.type = 'icon';
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800196 col.hover = (item) => {
197 return item[f.name];
198 };
Matteo Scandolod58d5042016-12-16 16:59:21 -0800199 col.formatter = (item) => {
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700200 if (item[f.name].indexOf('1') > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800201 return 'check';
202 }
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700203 if (item[f.name].indexOf('2') > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800204 return 'exclamation-circle';
205 }
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700206 if (item[f.name].indexOf('0') > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800207 return 'clock-o';
208 }
209 };
210 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800211
Matteo Scandolod58d5042016-12-16 16:59:21 -0800212 return col;
213 })
214 .filter(v => angular.isDefined(v));
215
Matteo Scandolo231de262017-01-04 16:33:14 -0800216 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800217 };
218
Matteo Scandoloa242c872017-01-12 15:13:00 -0800219 public stateFromCoreModel(name: string): string {
220 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
221 if (s.data) {
222 return s.data.model === name;
223 }
224 return false;
225 });
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800226 return state ? state.name : null;
Matteo Scandoloa242c872017-01-12 15:13:00 -0800227 }
228
229 public stateWithParams(name: string, model: any): string {
230 const state = this.stateFromCoreModel(name);
231 return `${state}({id: ${model['id']}})`;
232 }
233
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800234 public stateWithParamsForJs(name: string, model: any): any {
235 // TODO test and interface
236 const state = this.stateFromCoreModel(name);
237 return {name: state, params: {id: model.id}};
238 }
239
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800240 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
241
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800242 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800243 const input: IXosFormInput = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800244 name: f.name,
245 label: this.toLabel(f.name),
246 type: f.type,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800247 validators: this.formatValidators(f.validators),
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700248 hint: f.hint,
249 default: f.default || null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800250 };
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700251
252 // NOTE populate drop-downs based on relation
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800253 if (f.relation) {
254 input.type = 'select';
255 this.populateSelectField(f, input);
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700256 }
257 // NOTE if static options are defined in modeldefs
258 // the f.options field is already populated,
259 // we just need to move it to the input
260 else if (f.options && f.options.length > 0) {
261 input.options = f.options;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800262 }
263 return input;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800264 })
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700265 .filter(f => this.form_excluded_fields.indexOf(f.name) === -1);
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800266 }
267
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800268 public modelToFormCfg(model: IXosModeldef): IXosFormCfg {
269 const formCfg: IXosFormCfg = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800270 formName: `${model.name}Form`,
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700271 exclude: this.form_excluded_fields,
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800272 actions: [{
273 label: 'Save',
274 class: 'success',
275 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800276 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800277 }],
278 inputs: this.modelFieldToInputCfg(model.fields)
279 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800280
281 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
282
283 if (!form.$valid) {
284 formCfg.feedback = {
285 show: true,
286 message: 'Form is invalid',
287 type: 'danger',
288 closeBtn: true
289 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800290
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800291 return;
292 }
293
294 const model = angular.copy(item);
295
296 // TODO remove ManyToMany relations and save them separately (how??)
297 delete item.networks;
298
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800299 // remove field added by xosTable
300 _.forEach(Object.keys(item), prop => {
301 if (prop.indexOf('-formatted') > -1) {
302 delete item[prop];
303 }
304 });
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800305
306 item.$save()
307 .then((res) => {
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800308 formCfg.feedback = {
309 show: true,
310 message: `${model.name} succesfully saved`,
311 type: 'success',
312 closeBtn: true
313 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800314 this.toastr.success(`${model.name} succesfully saved`);
315 })
316 .catch(err => {
Matteo Scandolo42c66922017-05-01 17:24:59 -0700317 formCfg.feedback = {
318 show: true,
319 message: `Error while saving ${model.name}: ${err.error}. ${err.specific_error || ''}`,
320 type: 'danger',
321 closeBtn: true
322 };
323 this.toastr.error(err.specific_error || '', `Error while saving ${model.name}: ${err.error}`);
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800324 });
325 };
326
327 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800328 }
329
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800330 private formatValidators(validators: IXosModelDefsFieldValidators[]): IXosFormInputValidator {
331 // convert validators as expressed from modelDefs,
332 // to the object required by xosForm
333 return _.reduce(validators, (formValidators: IXosFormInputValidator, v: IXosModelDefsFieldValidators) => {
334 formValidators[v.name] = v.bool_value ? v.bool_value : v.int_value;
335 return formValidators;
336 }, {});
337 }
338
Matteo Scandolod58d5042016-12-16 16:59:21 -0800339 private fromCamelCase(string: string): string {
340 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
341 }
342
343 private fromSnakeCase(string: string): string {
344 return string.split('_').join(' ').trim();
345 }
346
347 private fromKebabCase(string: string): string {
348 return string.split('-').join(' ').trim();
349 }
350
351 private capitalizeFirst(string: string): string {
352 return string.slice(0, 1).toUpperCase() + string.slice(1);
353 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800354
355 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
356 // if the relation is not defined return
357 if (!fk || angular.isUndefined(fk) || fk === null) {
358 return;
359 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800360 this.XosModelStore.query(field.relation.model)
Matteo Scandolo04964232017-01-07 12:53:46 -0800361 .subscribe(res => {
362 if (angular.isDefined(res) && angular.isDefined(fk)) {
363 let ri = _.find(res, {id: fk});
364 if (angular.isDefined(ri)) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800365 if (angular.isDefined(ri.name)) {
366 item[`${field.name}-formatted`] = ri.name;
367 }
368 else if (angular.isDefined(ri.humanReadableName)) {
369 item[`${field.name}-formatted`] = ri.humanReadableName;
370 }
371 else {
372 item[`${field.name}-formatted`] = ri.id;
373 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800374 }
375 }
376 });
377 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800378
379 // augment a select field with related model informations
380 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800381 this.XosModelStore.query(field.relation.model)
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800382 .subscribe(res => {
383 input.options = _.map(res, item => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800384 let opt = {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
385 if (!angular.isDefined(item.humanReadableName) && !angular.isDefined(item.name)) {
386 opt.label = item.id;
387 }
388 return opt;
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800389 });
390 });
391 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800392}