blob: 2dc8e46602bc92b821902cf600be6e091b219714 [file] [log] [blame]
Matteo Scandolod58d5042016-12-16 16:59:21 -08001import * as _ from 'lodash';
2import * as pluralize from 'pluralize';
Matteo Scandolocb466ed2017-01-04 17:16:24 -08003import {IXosTableColumn, IXosTableCfg} from '../../table/table';
Matteo Scandolo1aee1982017-02-17 08:33:23 -08004import {IXosModeldef} from '../../../datasources/rest/modeldefs.rest';
Matteo Scandoloe7e052d2017-07-31 19:54:31 -07005import {IXosFormCfg, IXosFormInput, IXosFormInputValidator, IXosFormInputOptions} from '../../form/form';
Matteo Scandolo47860fe2017-02-02 12:05:55 -08006import {IXosModelStoreService} from '../../../datasources/stores/model.store';
Matteo Scandolo1aee1982017-02-17 08:33:23 -08007import {IXosState} from '../runtime-states';
8
9export interface IXosModelDefsFieldValidators {
10 name: string;
11 bool_value?: boolean;
12 int_value?: number;
13}
Matteo Scandolod58d5042016-12-16 16:59:21 -080014
15export interface IXosModelDefsField {
16 name: string;
17 type: string;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080018 validators?: IXosModelDefsFieldValidators[];
Matteo Scandolo04964232017-01-07 12:53:46 -080019 hint?: string;
20 relation?: {
21 model: string;
22 type: string;
23 };
Matteo Scandoloe7e052d2017-07-31 19:54:31 -070024 options?: IXosFormInputOptions[];
25 default?: any | null;
Matteo Scandolod58d5042016-12-16 16:59:21 -080026}
27
28export interface IXosConfigHelpersService {
Matteo Scandoloee655a12016-12-19 15:38:43 -080029 excluded_fields: string[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080030 modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[];
31 modelToTableCfg(model: IXosModeldef, modelName: string): IXosTableCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -080032 modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080033 modelToFormCfg(model: IXosModeldef): IXosFormCfg;
Matteo Scandolod58d5042016-12-16 16:59:21 -080034 pluralize(string: string, quantity?: number, count?: boolean): string;
35 toLabel(string: string, pluralize?: boolean): string;
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080036 toLabels(string: string[], pluralize?: boolean): string[];
Matteo Scandoloa242c872017-01-12 15:13:00 -080037 stateFromCoreModel(name: string): string;
38 stateWithParams(name: string, model: any): string;
Matteo Scandolo8248bca2017-08-09 13:46:04 -070039 relatedStateWithParams(name: string, id: string): string;
Matteo Scandolo86bc26a2017-01-18 11:06:47 -080040 stateWithParamsForJs(name: string, model: any): any;
Matteo Scandolod58d5042016-12-16 16:59:21 -080041}
42
Matteo Scandolo8b2370c2017-02-02 17:19:07 -080043export class ConfigHelpers implements IXosConfigHelpersService {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080044 static $inject = [
45 '$state',
46 'toastr',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080047 'XosModelStore'];
Matteo Scandolod58d5042016-12-16 16:59:21 -080048
Matteo Scandolo1aee1982017-02-17 08:33:23 -080049 public excluded_fields = [
Matteo Scandoloee655a12016-12-19 15:38:43 -080050 'created',
51 'updated',
52 'enacted',
53 'policed',
54 'backend_register',
55 'deleted',
56 'write_protect',
57 'lazy_blocked',
58 'no_sync',
59 'no_policy',
60 'omf_friendly',
61 'enabled',
Matteo Scandolod62ea792016-12-22 14:02:28 -080062 'validators',
Matteo Scandolo80c3a652017-01-06 10:48:31 -080063 'password',
64 'backend_need_delete',
Matteo Scandolod53ac1d2017-08-01 15:06:09 -070065 'backend_need_reap',
66 'leaf_model_name'
Matteo Scandoloee655a12016-12-19 15:38:43 -080067 ];
68
Matteo Scandolod53ac1d2017-08-01 15:06:09 -070069 public form_excluded_fields = this.excluded_fields.concat([
70 'id',
71 'policy_status',
72 'backend_status',
73 ]);
74
Matteo Scandolocb466ed2017-01-04 17:16:24 -080075 constructor(
Matteo Scandoloa242c872017-01-12 15:13:00 -080076 private $state: ng.ui.IStateService,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -080077 private toastr: ng.toastr.IToastrService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080078 private XosModelStore: IXosModelStoreService
Matteo Scandolocb466ed2017-01-04 17:16:24 -080079 ) {
Matteo Scandolo08464e52017-01-17 13:35:27 -080080 pluralize.addIrregularRule('xos', 'xoses');
Matteo Scandolod58d5042016-12-16 16:59:21 -080081 pluralize.addPluralRule(/slice$/i, 'slices');
Matteo Scandolo80c3a652017-01-06 10:48:31 -080082 pluralize.addSingularRule(/slice$/i, 'slice');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080083 pluralize.addPluralRule(/library$/i, 'librarys');
Matteo Scandolo5d962a32017-08-01 18:16:14 -070084 pluralize.addPluralRule(/imagedeployments/i, 'imagedeploymentss');
85 pluralize.addPluralRule(/controllerimages/i, 'controllerimagess');
86 pluralize.addPluralRule(/servicedependency/i, 'servicedependencys');
87 pluralize.addPluralRule(/servicemonitoringagentinfo/i, 'servicemonitoringagentinfoes');
Matteo Scandolod58d5042016-12-16 16:59:21 -080088 }
89
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080090 public pluralize(string: string, quantity?: number, count?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080091 return pluralize(string, quantity, count);
92 }
93
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080094 public toLabels(strings: string[], pluralize?: boolean): string[] {
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080095 if (angular.isArray(strings)) {
96 return _.map(strings, s => {
Matteo Scandolod58d5042016-12-16 16:59:21 -080097 return this.toLabel(s, pluralize);
98 });
99 }
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -0800100 }
101
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800102 public toLabel(string: string, pluralize?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800103
104 if (pluralize) {
105 string = this.pluralize(string);
106 }
107
108 string = this.fromCamelCase(string);
109 string = this.fromSnakeCase(string);
110 string = this.fromKebabCase(string);
111
112 return this.capitalizeFirst(string);
113 }
114
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800115 public modelToTableCfg(model: IXosModeldef, baseUrl: string): IXosTableCfg {
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800116 const cfg = {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800117 columns: this.modelFieldsToColumnsCfg(model),
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800118 filter: 'fulltext',
119 order: {field: 'id', reverse: false},
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800120 pagination: {
121 pageSize: 10
122 },
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800123 actions: [
124 {
Matteo Scandolocc4bce82017-08-07 13:11:47 -0700125 label: 'details',
126 icon: 'search',
127 cb: (item) => {
128 this.$state.go(this.$state.current.name, {id: item.id});
129 }
130 },
131 {
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800132 label: 'delete',
133 icon: 'remove',
134 color: 'red',
135 cb: (item) => {
136 let obj = angular.copy(item);
137
138 item.$delete()
139 .then((res) => {
140 if (res.status === 404) {
141 // TODO understand why it does not go directly in catch
142 throw new Error();
143 }
144 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
145 })
146 .catch(() => {
147 this.toastr.error(`Error while deleting ${obj.name}`);
148 });
149 }
150 }
151 ]
152 };
153 return cfg;
154 }
155
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800156 public modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[] {
157 const fields: IXosModelDefsField[] = model.fields;
158 const modelName: string = model.name;
Matteo Scandolo231de262017-01-04 16:33:14 -0800159 const columns = _.map(fields, (f) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800160 if (!angular.isDefined(f) || this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800161 return;
162 }
163 const col: IXosTableColumn = {
164 label: this.toLabel(f.name),
165 prop: f.name
166 };
167
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800168 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800169 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandoloee655a12016-12-19 15:38:43 -0800170 }
171
Matteo Scandolo04964232017-01-07 12:53:46 -0800172 // if the field identify a relation, create a link
Matteo Scandolo18975142017-08-01 14:48:04 -0700173 if (f.relation && f.relation.type === 'manytoone') {
Matteo Scandolo04964232017-01-07 12:53:46 -0800174 col.type = 'custom';
175 col.formatter = item => {
176 this.populateRelated(item, item[f.name], f);
177 return item[f.name];
178 };
Matteo Scandolo8248bca2017-08-09 13:46:04 -0700179 col.link = item => this.relatedStateWithParams(f.relation.model, item[col.prop]);
Matteo Scandolo04964232017-01-07 12:53:46 -0800180 }
181
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700182 if (f.name === 'backend_status' || f.name === 'policy_status') {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800183 col.type = 'icon';
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800184 col.hover = (item) => {
185 return item[f.name];
186 };
Matteo Scandolod58d5042016-12-16 16:59:21 -0800187 col.formatter = (item) => {
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700188 if (item[f.name].indexOf('1') > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800189 return 'check';
190 }
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700191 if (item[f.name].indexOf('2') > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800192 return 'exclamation-circle';
193 }
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700194 if (item[f.name].indexOf('0') > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800195 return 'clock-o';
196 }
197 };
198 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800199
Matteo Scandolod58d5042016-12-16 16:59:21 -0800200 return col;
201 })
202 .filter(v => angular.isDefined(v));
203
Matteo Scandolo231de262017-01-04 16:33:14 -0800204 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800205 };
206
Matteo Scandoloa242c872017-01-12 15:13:00 -0800207 public stateFromCoreModel(name: string): string {
208 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
209 if (s.data) {
210 return s.data.model === name;
211 }
212 return false;
213 });
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800214 return state ? state.name : null;
Matteo Scandoloa242c872017-01-12 15:13:00 -0800215 }
216
217 public stateWithParams(name: string, model: any): string {
218 const state = this.stateFromCoreModel(name);
219 return `${state}({id: ${model['id']}})`;
220 }
221
Matteo Scandolo8248bca2017-08-09 13:46:04 -0700222 public relatedStateWithParams(name: string, id: string): string {
223 const state = this.stateFromCoreModel(name);
224 return `${state}({id: ${id}})`;
225 }
226
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800227 public stateWithParamsForJs(name: string, model: any): any {
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800228 const state = this.stateFromCoreModel(name);
229 return {name: state, params: {id: model.id}};
230 }
231
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800232 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
233
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800234 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800235 const input: IXosFormInput = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800236 name: f.name,
237 label: this.toLabel(f.name),
238 type: f.type,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800239 validators: this.formatValidators(f.validators),
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700240 hint: f.hint,
241 default: f.default || null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800242 };
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700243
244 // NOTE populate drop-downs based on relation
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800245 if (f.relation) {
246 input.type = 'select';
247 this.populateSelectField(f, input);
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700248 }
249 // NOTE if static options are defined in modeldefs
250 // the f.options field is already populated,
251 // we just need to move it to the input
252 else if (f.options && f.options.length > 0) {
253 input.options = f.options;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800254 }
255 return input;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800256 })
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700257 .filter(f => this.form_excluded_fields.indexOf(f.name) === -1);
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800258 }
259
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800260 public modelToFormCfg(model: IXosModeldef): IXosFormCfg {
261 const formCfg: IXosFormCfg = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800262 formName: `${model.name}Form`,
Matteo Scandolod53ac1d2017-08-01 15:06:09 -0700263 exclude: this.form_excluded_fields,
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800264 actions: [{
265 label: 'Save',
266 class: 'success',
267 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800268 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800269 }],
270 inputs: this.modelFieldToInputCfg(model.fields)
271 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800272
273 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
274
275 if (!form.$valid) {
276 formCfg.feedback = {
277 show: true,
278 message: 'Form is invalid',
279 type: 'danger',
280 closeBtn: true
281 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800282
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800283 return;
284 }
285
286 const model = angular.copy(item);
287
288 // TODO remove ManyToMany relations and save them separately (how??)
289 delete item.networks;
290
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800291 // remove field added by xosTable
292 _.forEach(Object.keys(item), prop => {
293 if (prop.indexOf('-formatted') > -1) {
294 delete item[prop];
295 }
296 });
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800297
298 item.$save()
299 .then((res) => {
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800300 formCfg.feedback = {
301 show: true,
302 message: `${model.name} succesfully saved`,
303 type: 'success',
304 closeBtn: true
305 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800306 this.toastr.success(`${model.name} succesfully saved`);
307 })
308 .catch(err => {
Matteo Scandolo42c66922017-05-01 17:24:59 -0700309 formCfg.feedback = {
310 show: true,
311 message: `Error while saving ${model.name}: ${err.error}. ${err.specific_error || ''}`,
312 type: 'danger',
313 closeBtn: true
314 };
315 this.toastr.error(err.specific_error || '', `Error while saving ${model.name}: ${err.error}`);
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800316 });
317 };
318
319 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800320 }
321
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800322 private formatValidators(validators: IXosModelDefsFieldValidators[]): IXosFormInputValidator {
323 // convert validators as expressed from modelDefs,
324 // to the object required by xosForm
325 return _.reduce(validators, (formValidators: IXosFormInputValidator, v: IXosModelDefsFieldValidators) => {
326 formValidators[v.name] = v.bool_value ? v.bool_value : v.int_value;
327 return formValidators;
328 }, {});
329 }
330
Matteo Scandolod58d5042016-12-16 16:59:21 -0800331 private fromCamelCase(string: string): string {
332 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
333 }
334
335 private fromSnakeCase(string: string): string {
336 return string.split('_').join(' ').trim();
337 }
338
339 private fromKebabCase(string: string): string {
340 return string.split('-').join(' ').trim();
341 }
342
343 private capitalizeFirst(string: string): string {
344 return string.slice(0, 1).toUpperCase() + string.slice(1);
345 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800346
347 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
348 // if the relation is not defined return
349 if (!fk || angular.isUndefined(fk) || fk === null) {
350 return;
351 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800352 this.XosModelStore.query(field.relation.model)
Matteo Scandolo04964232017-01-07 12:53:46 -0800353 .subscribe(res => {
354 if (angular.isDefined(res) && angular.isDefined(fk)) {
355 let ri = _.find(res, {id: fk});
356 if (angular.isDefined(ri)) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800357 if (angular.isDefined(ri.name)) {
358 item[`${field.name}-formatted`] = ri.name;
359 }
360 else if (angular.isDefined(ri.humanReadableName)) {
361 item[`${field.name}-formatted`] = ri.humanReadableName;
362 }
363 else {
364 item[`${field.name}-formatted`] = ri.id;
365 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800366 }
367 }
368 });
369 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800370
371 // augment a select field with related model informations
372 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800373 this.XosModelStore.query(field.relation.model)
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800374 .subscribe(res => {
375 input.options = _.map(res, item => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800376 let opt = {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
377 if (!angular.isDefined(item.humanReadableName) && !angular.isDefined(item.name)) {
378 opt.label = item.id;
379 }
380 return opt;
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800381 });
382 });
383 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800384}