blob: 32e0b16d6a159118defdaf70b664bc38deaf79df [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 Scandolo86bc26a2017-01-18 11:06:47 -080039 stateWithParamsForJs(name: string, model: any): any;
Matteo Scandolod58d5042016-12-16 16:59:21 -080040}
41
Matteo Scandolo8b2370c2017-02-02 17:19:07 -080042export class ConfigHelpers implements IXosConfigHelpersService {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080043 static $inject = [
44 '$state',
45 'toastr',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080046 'XosModelStore'];
Matteo Scandolod58d5042016-12-16 16:59:21 -080047
Matteo Scandolo1aee1982017-02-17 08:33:23 -080048 public excluded_fields = [
Matteo Scandoloee655a12016-12-19 15:38:43 -080049 'created',
50 'updated',
51 'enacted',
52 'policed',
53 'backend_register',
54 'deleted',
55 'write_protect',
56 'lazy_blocked',
57 'no_sync',
58 'no_policy',
59 'omf_friendly',
60 'enabled',
Matteo Scandolod62ea792016-12-22 14:02:28 -080061 'validators',
Matteo Scandolo80c3a652017-01-06 10:48:31 -080062 'password',
63 'backend_need_delete',
64 'backend_need_reap'
Matteo Scandoloee655a12016-12-19 15:38:43 -080065 ];
66
Matteo Scandolocb466ed2017-01-04 17:16:24 -080067 constructor(
Matteo Scandoloa242c872017-01-12 15:13:00 -080068 private $state: ng.ui.IStateService,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -080069 private toastr: ng.toastr.IToastrService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080070 private XosModelStore: IXosModelStoreService
Matteo Scandolocb466ed2017-01-04 17:16:24 -080071 ) {
Matteo Scandolo08464e52017-01-17 13:35:27 -080072 pluralize.addIrregularRule('xos', 'xoses');
Matteo Scandolod58d5042016-12-16 16:59:21 -080073 pluralize.addPluralRule(/slice$/i, 'slices');
Matteo Scandolo80c3a652017-01-06 10:48:31 -080074 pluralize.addSingularRule(/slice$/i, 'slice');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080075 pluralize.addPluralRule(/library$/i, 'librarys');
76 pluralize.addPluralRule(/imagedeployments/i, 'imagedeploymentses');
77 pluralize.addPluralRule(/controllerimages/i, 'controllerimageses');
Matteo Scandolo72181592017-07-25 14:49:40 -070078 pluralize.addPluralRule(/servicedependency/i, 'servicedependencies');
Matteo Scandolod58d5042016-12-16 16:59:21 -080079 }
80
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080081 public pluralize(string: string, quantity?: number, count?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080082 return pluralize(string, quantity, count);
83 }
84
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080085 public toLabels(strings: string[], pluralize?: boolean): string[] {
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080086 if (angular.isArray(strings)) {
87 return _.map(strings, s => {
Matteo Scandolod58d5042016-12-16 16:59:21 -080088 return this.toLabel(s, pluralize);
89 });
90 }
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080091 }
92
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080093 public toLabel(string: string, pluralize?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080094
95 if (pluralize) {
96 string = this.pluralize(string);
97 }
98
99 string = this.fromCamelCase(string);
100 string = this.fromSnakeCase(string);
101 string = this.fromKebabCase(string);
102
103 return this.capitalizeFirst(string);
104 }
105
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800106 public modelToTableCfg(model: IXosModeldef, baseUrl: string): IXosTableCfg {
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800107 const cfg = {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800108 columns: this.modelFieldsToColumnsCfg(model),
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800109 filter: 'fulltext',
110 order: {field: 'id', reverse: false},
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800111 pagination: {
112 pageSize: 10
113 },
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800114 actions: [
115 {
116 label: 'delete',
117 icon: 'remove',
118 color: 'red',
119 cb: (item) => {
120 let obj = angular.copy(item);
121
122 item.$delete()
123 .then((res) => {
124 if (res.status === 404) {
125 // TODO understand why it does not go directly in catch
126 throw new Error();
127 }
128 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
129 })
130 .catch(() => {
131 this.toastr.error(`Error while deleting ${obj.name}`);
132 });
133 }
134 }
135 ]
136 };
137 return cfg;
138 }
139
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800140 public modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[] {
141 const fields: IXosModelDefsField[] = model.fields;
142 const modelName: string = model.name;
Matteo Scandolo231de262017-01-04 16:33:14 -0800143 const columns = _.map(fields, (f) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800144 if (!angular.isDefined(f) || this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800145 return;
146 }
147 const col: IXosTableColumn = {
148 label: this.toLabel(f.name),
149 prop: f.name
150 };
151
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800152 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800153 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandoloee655a12016-12-19 15:38:43 -0800154 }
155
Matteo Scandolo04964232017-01-07 12:53:46 -0800156 // if the field identify a relation, create a link
Matteo Scandolo18975142017-08-01 14:48:04 -0700157 if (f.relation && f.relation.type === 'manytoone') {
Matteo Scandolo04964232017-01-07 12:53:46 -0800158 col.type = 'custom';
159 col.formatter = item => {
160 this.populateRelated(item, item[f.name], f);
161 return item[f.name];
162 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800163 col.link = item => {
164 return this.stateWithParams(f.relation.model, item);
165 };
Matteo Scandolo04964232017-01-07 12:53:46 -0800166 }
167
Matteo Scandolod58d5042016-12-16 16:59:21 -0800168 if (f.name === 'backend_status') {
169 col.type = 'icon';
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800170 col.hover = (item) => {
171 return item[f.name];
172 };
Matteo Scandolod58d5042016-12-16 16:59:21 -0800173 col.formatter = (item) => {
174 if (item.backend_status.indexOf('1') > -1) {
175 return 'check';
176 }
177 if (item.backend_status.indexOf('2') > -1) {
178 return 'exclamation-circle';
179 }
180 if (item.backend_status.indexOf('0') > -1) {
181 return 'clock-o';
182 }
183 };
184 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800185
Matteo Scandolod58d5042016-12-16 16:59:21 -0800186 return col;
187 })
188 .filter(v => angular.isDefined(v));
189
Matteo Scandolo231de262017-01-04 16:33:14 -0800190 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800191 };
192
Matteo Scandoloa242c872017-01-12 15:13:00 -0800193 public stateFromCoreModel(name: string): string {
194 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
195 if (s.data) {
196 return s.data.model === name;
197 }
198 return false;
199 });
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800200 return state ? state.name : null;
Matteo Scandoloa242c872017-01-12 15:13:00 -0800201 }
202
203 public stateWithParams(name: string, model: any): string {
204 const state = this.stateFromCoreModel(name);
205 return `${state}({id: ${model['id']}})`;
206 }
207
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800208 public stateWithParamsForJs(name: string, model: any): any {
209 // TODO test and interface
210 const state = this.stateFromCoreModel(name);
211 return {name: state, params: {id: model.id}};
212 }
213
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800214 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
215
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800216 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800217 const input: IXosFormInput = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800218 name: f.name,
219 label: this.toLabel(f.name),
220 type: f.type,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800221 validators: this.formatValidators(f.validators),
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700222 hint: f.hint,
223 default: f.default || null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800224 };
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700225
226 // NOTE populate drop-downs based on relation
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800227 if (f.relation) {
228 input.type = 'select';
229 this.populateSelectField(f, input);
Matteo Scandoloe7e052d2017-07-31 19:54:31 -0700230 }
231 // NOTE if static options are defined in modeldefs
232 // the f.options field is already populated,
233 // we just need to move it to the input
234 else if (f.options && f.options.length > 0) {
235 input.options = f.options;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800236 }
237 return input;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800238 })
239 .filter(f => this.excluded_fields.indexOf(f.name) === -1);
240 }
241
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800242 public modelToFormCfg(model: IXosModeldef): IXosFormCfg {
243 const formCfg: IXosFormCfg = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800244 formName: `${model.name}Form`,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800245 exclude: ['backend_status', 'creator', 'id'],
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800246 actions: [{
247 label: 'Save',
248 class: 'success',
249 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800250 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800251 }],
252 inputs: this.modelFieldToInputCfg(model.fields)
253 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800254
255 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
256
257 if (!form.$valid) {
258 formCfg.feedback = {
259 show: true,
260 message: 'Form is invalid',
261 type: 'danger',
262 closeBtn: true
263 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800264
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800265 return;
266 }
267
268 const model = angular.copy(item);
269
270 // TODO remove ManyToMany relations and save them separately (how??)
271 delete item.networks;
272
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800273 // remove field added by xosTable
274 _.forEach(Object.keys(item), prop => {
275 if (prop.indexOf('-formatted') > -1) {
276 delete item[prop];
277 }
278 });
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800279
280 item.$save()
281 .then((res) => {
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800282 formCfg.feedback = {
283 show: true,
284 message: `${model.name} succesfully saved`,
285 type: 'success',
286 closeBtn: true
287 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800288 this.toastr.success(`${model.name} succesfully saved`);
289 })
290 .catch(err => {
Matteo Scandolo42c66922017-05-01 17:24:59 -0700291 formCfg.feedback = {
292 show: true,
293 message: `Error while saving ${model.name}: ${err.error}. ${err.specific_error || ''}`,
294 type: 'danger',
295 closeBtn: true
296 };
297 this.toastr.error(err.specific_error || '', `Error while saving ${model.name}: ${err.error}`);
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800298 });
299 };
300
301 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800302 }
303
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800304 private formatValidators(validators: IXosModelDefsFieldValidators[]): IXosFormInputValidator {
305 // convert validators as expressed from modelDefs,
306 // to the object required by xosForm
307 return _.reduce(validators, (formValidators: IXosFormInputValidator, v: IXosModelDefsFieldValidators) => {
308 formValidators[v.name] = v.bool_value ? v.bool_value : v.int_value;
309 return formValidators;
310 }, {});
311 }
312
Matteo Scandolod58d5042016-12-16 16:59:21 -0800313 private fromCamelCase(string: string): string {
314 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
315 }
316
317 private fromSnakeCase(string: string): string {
318 return string.split('_').join(' ').trim();
319 }
320
321 private fromKebabCase(string: string): string {
322 return string.split('-').join(' ').trim();
323 }
324
325 private capitalizeFirst(string: string): string {
326 return string.slice(0, 1).toUpperCase() + string.slice(1);
327 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800328
329 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
330 // if the relation is not defined return
331 if (!fk || angular.isUndefined(fk) || fk === null) {
332 return;
333 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800334 this.XosModelStore.query(field.relation.model)
Matteo Scandolo04964232017-01-07 12:53:46 -0800335 .subscribe(res => {
336 if (angular.isDefined(res) && angular.isDefined(fk)) {
337 let ri = _.find(res, {id: fk});
338 if (angular.isDefined(ri)) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800339 if (angular.isDefined(ri.name)) {
340 item[`${field.name}-formatted`] = ri.name;
341 }
342 else if (angular.isDefined(ri.humanReadableName)) {
343 item[`${field.name}-formatted`] = ri.humanReadableName;
344 }
345 else {
346 item[`${field.name}-formatted`] = ri.id;
347 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800348 }
349 }
350 });
351 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800352
353 // augment a select field with related model informations
354 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800355 this.XosModelStore.query(field.relation.model)
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800356 .subscribe(res => {
357 input.options = _.map(res, item => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800358 let opt = {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
359 if (!angular.isDefined(item.humanReadableName) && !angular.isDefined(item.name)) {
360 opt.label = item.id;
361 }
362 return opt;
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800363 });
364 });
365 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800366}