blob: 2ac93942d8236100372760111c895660c0a2b56a [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';
5import {IXosFormCfg, IXosFormInput, IXosFormInputValidator} from '../../form/form';
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -08006import {IXosAuthService} from '../../../datasources/rest/auth.rest';
Matteo Scandolo47860fe2017-02-02 12:05:55 -08007import {IXosModelStoreService} from '../../../datasources/stores/model.store';
Matteo Scandolo1aee1982017-02-17 08:33:23 -08008import {IXosState} from '../runtime-states';
9
10export interface IXosModelDefsFieldValidators {
11 name: string;
12 bool_value?: boolean;
13 int_value?: number;
14}
Matteo Scandolod58d5042016-12-16 16:59:21 -080015
16export interface IXosModelDefsField {
17 name: string;
18 type: string;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080019 validators?: IXosModelDefsFieldValidators[];
Matteo Scandolo04964232017-01-07 12:53:46 -080020 hint?: string;
21 relation?: {
22 model: string;
23 type: string;
24 };
Matteo Scandolod58d5042016-12-16 16:59:21 -080025}
26
27export interface IXosConfigHelpersService {
Matteo Scandoloee655a12016-12-19 15:38:43 -080028 excluded_fields: string[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080029 modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[];
30 modelToTableCfg(model: IXosModeldef, modelName: string): IXosTableCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -080031 modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080032 modelToFormCfg(model: IXosModeldef): IXosFormCfg;
Matteo Scandolod58d5042016-12-16 16:59:21 -080033 pluralize(string: string, quantity?: number, count?: boolean): string;
34 toLabel(string: string, pluralize?: boolean): string;
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080035 toLabels(string: string[], pluralize?: boolean): string[];
Matteo Scandoloa242c872017-01-12 15:13:00 -080036 stateFromCoreModel(name: string): string;
37 stateWithParams(name: string, model: any): string;
Matteo Scandolo86bc26a2017-01-18 11:06:47 -080038 stateWithParamsForJs(name: string, model: any): any;
Matteo Scandolod58d5042016-12-16 16:59:21 -080039}
40
Matteo Scandolo8b2370c2017-02-02 17:19:07 -080041export class ConfigHelpers implements IXosConfigHelpersService {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080042 static $inject = [
43 '$state',
44 'toastr',
45 'AuthService',
46 '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 Scandolo04964232017-01-07 12:53:46 -080070 private AuthService: IXosAuthService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080071 private XosModelStore: IXosModelStoreService
Matteo Scandolocb466ed2017-01-04 17:16:24 -080072 ) {
Matteo Scandolo08464e52017-01-17 13:35:27 -080073 pluralize.addIrregularRule('xos', 'xoses');
Matteo Scandolod58d5042016-12-16 16:59:21 -080074 pluralize.addPluralRule(/slice$/i, 'slices');
Matteo Scandolo80c3a652017-01-06 10:48:31 -080075 pluralize.addSingularRule(/slice$/i, 'slice');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080076 pluralize.addPluralRule(/library$/i, 'librarys');
77 pluralize.addPluralRule(/imagedeployments/i, 'imagedeploymentses');
78 pluralize.addPluralRule(/controllerimages/i, 'controllerimageses');
79
Matteo Scandolod58d5042016-12-16 16:59:21 -080080 }
81
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080082 public pluralize(string: string, quantity?: number, count?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080083 return pluralize(string, quantity, count);
84 }
85
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080086 public toLabels(strings: string[], pluralize?: boolean): string[] {
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080087 if (angular.isArray(strings)) {
88 return _.map(strings, s => {
Matteo Scandolod58d5042016-12-16 16:59:21 -080089 return this.toLabel(s, pluralize);
90 });
91 }
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080092 }
93
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080094 public toLabel(string: string, pluralize?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080095
96 if (pluralize) {
97 string = this.pluralize(string);
98 }
99
100 string = this.fromCamelCase(string);
101 string = this.fromSnakeCase(string);
102 string = this.fromKebabCase(string);
103
104 return this.capitalizeFirst(string);
105 }
106
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800107 public modelToTableCfg(model: IXosModeldef, baseUrl: string): IXosTableCfg {
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800108 const cfg = {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800109 columns: this.modelFieldsToColumnsCfg(model),
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800110 filter: 'fulltext',
111 order: {field: 'id', reverse: false},
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800112 pagination: {
113 pageSize: 10
114 },
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800115 actions: [
116 {
117 label: 'delete',
118 icon: 'remove',
119 color: 'red',
120 cb: (item) => {
121 let obj = angular.copy(item);
122
123 item.$delete()
124 .then((res) => {
125 if (res.status === 404) {
126 // TODO understand why it does not go directly in catch
127 throw new Error();
128 }
129 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
130 })
131 .catch(() => {
132 this.toastr.error(`Error while deleting ${obj.name}`);
133 });
134 }
135 }
136 ]
137 };
138 return cfg;
139 }
140
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800141 public modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[] {
142 const fields: IXosModelDefsField[] = model.fields;
143 const modelName: string = model.name;
Matteo Scandolo231de262017-01-04 16:33:14 -0800144 const columns = _.map(fields, (f) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800145 if (!angular.isDefined(f) || this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800146 return;
147 }
148 const col: IXosTableColumn = {
149 label: this.toLabel(f.name),
150 prop: f.name
151 };
152
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800153 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800154 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandoloee655a12016-12-19 15:38:43 -0800155 }
156
Matteo Scandolo04964232017-01-07 12:53:46 -0800157 // if the field identify a relation, create a link
158 if (f.relation && f.relation.type === 'many_to_one') {
Matteo Scandolo04964232017-01-07 12:53:46 -0800159 col.type = 'custom';
160 col.formatter = item => {
161 this.populateRelated(item, item[f.name], f);
162 return item[f.name];
163 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800164 col.link = item => {
165 return this.stateWithParams(f.relation.model, item);
166 };
Matteo Scandolo04964232017-01-07 12:53:46 -0800167 }
168
Matteo Scandolod58d5042016-12-16 16:59:21 -0800169 if (f.name === 'backend_status') {
170 col.type = 'icon';
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800171 col.hover = (item) => {
172 return item[f.name];
173 };
Matteo Scandolod58d5042016-12-16 16:59:21 -0800174 col.formatter = (item) => {
175 if (item.backend_status.indexOf('1') > -1) {
176 return 'check';
177 }
178 if (item.backend_status.indexOf('2') > -1) {
179 return 'exclamation-circle';
180 }
181 if (item.backend_status.indexOf('0') > -1) {
182 return 'clock-o';
183 }
184 };
185 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800186
Matteo Scandolod58d5042016-12-16 16:59:21 -0800187 return col;
188 })
189 .filter(v => angular.isDefined(v));
190
Matteo Scandolo231de262017-01-04 16:33:14 -0800191 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800192 };
193
Matteo Scandoloa242c872017-01-12 15:13:00 -0800194 public stateFromCoreModel(name: string): string {
195 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
196 if (s.data) {
197 return s.data.model === name;
198 }
199 return false;
200 });
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800201 return state ? state.name : null;
Matteo Scandoloa242c872017-01-12 15:13:00 -0800202 }
203
204 public stateWithParams(name: string, model: any): string {
205 const state = this.stateFromCoreModel(name);
206 return `${state}({id: ${model['id']}})`;
207 }
208
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800209 public stateWithParamsForJs(name: string, model: any): any {
210 // TODO test and interface
211 const state = this.stateFromCoreModel(name);
212 return {name: state, params: {id: model.id}};
213 }
214
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800215 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
216
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800217 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800218 const input: IXosFormInput = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800219 name: f.name,
220 label: this.toLabel(f.name),
221 type: f.type,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800222 validators: this.formatValidators(f.validators),
223 hint: f.hint
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800224 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800225 if (f.relation) {
226 input.type = 'select';
227 this.populateSelectField(f, input);
228 return input;
229 }
230 return input;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800231 })
232 .filter(f => this.excluded_fields.indexOf(f.name) === -1);
233 }
234
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800235 public modelToFormCfg(model: IXosModeldef): IXosFormCfg {
236 const formCfg: IXosFormCfg = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800237 formName: `${model.name}Form`,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800238 exclude: ['backend_status', 'creator', 'id'],
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800239 actions: [{
240 label: 'Save',
241 class: 'success',
242 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800243 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800244 }],
245 inputs: this.modelFieldToInputCfg(model.fields)
246 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800247
248 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
249
250 if (!form.$valid) {
251 formCfg.feedback = {
252 show: true,
253 message: 'Form is invalid',
254 type: 'danger',
255 closeBtn: true
256 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800257
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800258 return;
259 }
260
261 const model = angular.copy(item);
262
263 // TODO remove ManyToMany relations and save them separately (how??)
264 delete item.networks;
265
266 // adding userId as creator
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800267 // item.creator = this.AuthService.getUser().id;
268
269 // remove field added by xosTable
270 _.forEach(Object.keys(item), prop => {
271 if (prop.indexOf('-formatted') > -1) {
272 delete item[prop];
273 }
274 });
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800275
276 item.$save()
277 .then((res) => {
278 if (res.status === 403 || res.status === 405 || res.status === 500) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800279 // TODO understand why 405 does not go directly in catch (it may be related to ng-rest-gw)
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800280 throw new Error();
281 }
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 => {
291 // TODO keep the edited model
292 this.toastr.error(`Error while saving ${model.name}`);
293 });
294 };
295
296 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800297 }
298
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800299 private formatValidators(validators: IXosModelDefsFieldValidators[]): IXosFormInputValidator {
300 // convert validators as expressed from modelDefs,
301 // to the object required by xosForm
302 return _.reduce(validators, (formValidators: IXosFormInputValidator, v: IXosModelDefsFieldValidators) => {
303 formValidators[v.name] = v.bool_value ? v.bool_value : v.int_value;
304 return formValidators;
305 }, {});
306 }
307
Matteo Scandolod58d5042016-12-16 16:59:21 -0800308 private fromCamelCase(string: string): string {
309 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
310 }
311
312 private fromSnakeCase(string: string): string {
313 return string.split('_').join(' ').trim();
314 }
315
316 private fromKebabCase(string: string): string {
317 return string.split('-').join(' ').trim();
318 }
319
320 private capitalizeFirst(string: string): string {
321 return string.slice(0, 1).toUpperCase() + string.slice(1);
322 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800323
324 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
325 // if the relation is not defined return
326 if (!fk || angular.isUndefined(fk) || fk === null) {
327 return;
328 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800329 this.XosModelStore.query(field.relation.model)
Matteo Scandolo04964232017-01-07 12:53:46 -0800330 .subscribe(res => {
331 if (angular.isDefined(res) && angular.isDefined(fk)) {
332 let ri = _.find(res, {id: fk});
333 if (angular.isDefined(ri)) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800334 if (angular.isDefined(ri.name)) {
335 item[`${field.name}-formatted`] = ri.name;
336 }
337 else if (angular.isDefined(ri.humanReadableName)) {
338 item[`${field.name}-formatted`] = ri.humanReadableName;
339 }
340 else {
341 item[`${field.name}-formatted`] = ri.id;
342 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800343 }
344 }
345 });
346 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800347
348 // augment a select field with related model informations
349 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800350 this.XosModelStore.query(field.relation.model)
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800351 .subscribe(res => {
352 input.options = _.map(res, item => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800353 let opt = {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
354 if (!angular.isDefined(item.humanReadableName) && !angular.isDefined(item.name)) {
355 opt.label = item.id;
356 }
357 return opt;
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800358 });
359 });
360 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800361}