blob: 1cd9e60922f82c96a6dc09c2298d4f87c2b255a0 [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 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 Scandolod58d5042016-12-16 16:59:21 -080024}
25
26export interface IXosConfigHelpersService {
Matteo Scandoloee655a12016-12-19 15:38:43 -080027 excluded_fields: string[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080028 modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[];
29 modelToTableCfg(model: IXosModeldef, modelName: string): IXosTableCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -080030 modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
Matteo Scandolo1aee1982017-02-17 08:33:23 -080031 modelToFormCfg(model: IXosModeldef): IXosFormCfg;
Matteo Scandolod58d5042016-12-16 16:59:21 -080032 pluralize(string: string, quantity?: number, count?: boolean): string;
33 toLabel(string: string, pluralize?: boolean): string;
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080034 toLabels(string: string[], pluralize?: boolean): string[];
Matteo Scandoloa242c872017-01-12 15:13:00 -080035 stateFromCoreModel(name: string): string;
36 stateWithParams(name: string, model: any): string;
Matteo Scandolo86bc26a2017-01-18 11:06:47 -080037 stateWithParamsForJs(name: string, model: any): any;
Matteo Scandolod58d5042016-12-16 16:59:21 -080038}
39
Matteo Scandolo8b2370c2017-02-02 17:19:07 -080040export class ConfigHelpers implements IXosConfigHelpersService {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080041 static $inject = [
42 '$state',
43 'toastr',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080044 'XosModelStore'];
Matteo Scandolod58d5042016-12-16 16:59:21 -080045
Matteo Scandolo1aee1982017-02-17 08:33:23 -080046 public excluded_fields = [
Matteo Scandoloee655a12016-12-19 15:38:43 -080047 'created',
48 'updated',
49 'enacted',
50 'policed',
51 'backend_register',
52 'deleted',
53 'write_protect',
54 'lazy_blocked',
55 'no_sync',
56 'no_policy',
57 'omf_friendly',
58 'enabled',
Matteo Scandolod62ea792016-12-22 14:02:28 -080059 'validators',
Matteo Scandolo80c3a652017-01-06 10:48:31 -080060 'password',
61 'backend_need_delete',
62 'backend_need_reap'
Matteo Scandoloee655a12016-12-19 15:38:43 -080063 ];
64
Matteo Scandolocb466ed2017-01-04 17:16:24 -080065 constructor(
Matteo Scandoloa242c872017-01-12 15:13:00 -080066 private $state: ng.ui.IStateService,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -080067 private toastr: ng.toastr.IToastrService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080068 private XosModelStore: IXosModelStoreService
Matteo Scandolocb466ed2017-01-04 17:16:24 -080069 ) {
Matteo Scandolo08464e52017-01-17 13:35:27 -080070 pluralize.addIrregularRule('xos', 'xoses');
Matteo Scandolod58d5042016-12-16 16:59:21 -080071 pluralize.addPluralRule(/slice$/i, 'slices');
Matteo Scandolo80c3a652017-01-06 10:48:31 -080072 pluralize.addSingularRule(/slice$/i, 'slice');
Matteo Scandolo1aee1982017-02-17 08:33:23 -080073 pluralize.addPluralRule(/library$/i, 'librarys');
74 pluralize.addPluralRule(/imagedeployments/i, 'imagedeploymentses');
75 pluralize.addPluralRule(/controllerimages/i, 'controllerimageses');
Matteo Scandolod4878532017-03-20 17:39:55 -070076 pluralize.addPluralRule(/servicedependency/i, 'servicedependencys');
Matteo Scandolod58d5042016-12-16 16:59:21 -080077 }
78
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080079 public pluralize(string: string, quantity?: number, count?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080080 return pluralize(string, quantity, count);
81 }
82
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080083 public toLabels(strings: string[], pluralize?: boolean): string[] {
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080084 if (angular.isArray(strings)) {
85 return _.map(strings, s => {
Matteo Scandolod58d5042016-12-16 16:59:21 -080086 return this.toLabel(s, pluralize);
87 });
88 }
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080089 }
90
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080091 public toLabel(string: string, pluralize?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080092
93 if (pluralize) {
94 string = this.pluralize(string);
95 }
96
97 string = this.fromCamelCase(string);
98 string = this.fromSnakeCase(string);
99 string = this.fromKebabCase(string);
100
101 return this.capitalizeFirst(string);
102 }
103
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800104 public modelToTableCfg(model: IXosModeldef, baseUrl: string): IXosTableCfg {
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800105 const cfg = {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800106 columns: this.modelFieldsToColumnsCfg(model),
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800107 filter: 'fulltext',
108 order: {field: 'id', reverse: false},
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800109 pagination: {
110 pageSize: 10
111 },
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800112 actions: [
113 {
114 label: 'delete',
115 icon: 'remove',
116 color: 'red',
117 cb: (item) => {
118 let obj = angular.copy(item);
119
120 item.$delete()
121 .then((res) => {
122 if (res.status === 404) {
123 // TODO understand why it does not go directly in catch
124 throw new Error();
125 }
126 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
127 })
128 .catch(() => {
129 this.toastr.error(`Error while deleting ${obj.name}`);
130 });
131 }
132 }
133 ]
134 };
135 return cfg;
136 }
137
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800138 public modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[] {
139 const fields: IXosModelDefsField[] = model.fields;
140 const modelName: string = model.name;
Matteo Scandolo231de262017-01-04 16:33:14 -0800141 const columns = _.map(fields, (f) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800142 if (!angular.isDefined(f) || this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800143 return;
144 }
145 const col: IXosTableColumn = {
146 label: this.toLabel(f.name),
147 prop: f.name
148 };
149
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800150 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800151 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandoloee655a12016-12-19 15:38:43 -0800152 }
153
Matteo Scandolo04964232017-01-07 12:53:46 -0800154 // if the field identify a relation, create a link
155 if (f.relation && f.relation.type === 'many_to_one') {
Matteo Scandolo04964232017-01-07 12:53:46 -0800156 col.type = 'custom';
157 col.formatter = item => {
158 this.populateRelated(item, item[f.name], f);
159 return item[f.name];
160 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800161 col.link = item => {
162 return this.stateWithParams(f.relation.model, item);
163 };
Matteo Scandolo04964232017-01-07 12:53:46 -0800164 }
165
Matteo Scandolod58d5042016-12-16 16:59:21 -0800166 if (f.name === 'backend_status') {
167 col.type = 'icon';
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800168 col.hover = (item) => {
169 return item[f.name];
170 };
Matteo Scandolod58d5042016-12-16 16:59:21 -0800171 col.formatter = (item) => {
172 if (item.backend_status.indexOf('1') > -1) {
173 return 'check';
174 }
175 if (item.backend_status.indexOf('2') > -1) {
176 return 'exclamation-circle';
177 }
178 if (item.backend_status.indexOf('0') > -1) {
179 return 'clock-o';
180 }
181 };
182 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800183
Matteo Scandolod58d5042016-12-16 16:59:21 -0800184 return col;
185 })
186 .filter(v => angular.isDefined(v));
187
Matteo Scandolo231de262017-01-04 16:33:14 -0800188 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800189 };
190
Matteo Scandoloa242c872017-01-12 15:13:00 -0800191 public stateFromCoreModel(name: string): string {
192 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
193 if (s.data) {
194 return s.data.model === name;
195 }
196 return false;
197 });
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800198 return state ? state.name : null;
Matteo Scandoloa242c872017-01-12 15:13:00 -0800199 }
200
201 public stateWithParams(name: string, model: any): string {
202 const state = this.stateFromCoreModel(name);
203 return `${state}({id: ${model['id']}})`;
204 }
205
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800206 public stateWithParamsForJs(name: string, model: any): any {
207 // TODO test and interface
208 const state = this.stateFromCoreModel(name);
209 return {name: state, params: {id: model.id}};
210 }
211
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800212 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
213
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800214 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800215 const input: IXosFormInput = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800216 name: f.name,
217 label: this.toLabel(f.name),
218 type: f.type,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800219 validators: this.formatValidators(f.validators),
220 hint: f.hint
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800221 };
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800222 if (f.relation) {
223 input.type = 'select';
224 this.populateSelectField(f, input);
225 return input;
226 }
227 return input;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800228 })
229 .filter(f => this.excluded_fields.indexOf(f.name) === -1);
230 }
231
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800232 public modelToFormCfg(model: IXosModeldef): IXosFormCfg {
233 const formCfg: IXosFormCfg = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800234 formName: `${model.name}Form`,
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800235 exclude: ['backend_status', 'creator', 'id'],
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800236 actions: [{
237 label: 'Save',
238 class: 'success',
239 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800240 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800241 }],
242 inputs: this.modelFieldToInputCfg(model.fields)
243 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800244
245 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
246
247 if (!form.$valid) {
248 formCfg.feedback = {
249 show: true,
250 message: 'Form is invalid',
251 type: 'danger',
252 closeBtn: true
253 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800254
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800255 return;
256 }
257
258 const model = angular.copy(item);
259
260 // TODO remove ManyToMany relations and save them separately (how??)
261 delete item.networks;
262
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800263 // remove field added by xosTable
264 _.forEach(Object.keys(item), prop => {
265 if (prop.indexOf('-formatted') > -1) {
266 delete item[prop];
267 }
268 });
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800269
270 item.$save()
271 .then((res) => {
Matteo Scandolo47c53fc2017-03-23 14:11:32 -0700272 if (res.status === 403 || res.status === 405 || res.status === 404 || res.status === 500) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800273 // 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 -0800274 throw new Error();
275 }
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800276 formCfg.feedback = {
277 show: true,
278 message: `${model.name} succesfully saved`,
279 type: 'success',
280 closeBtn: true
281 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800282 this.toastr.success(`${model.name} succesfully saved`);
283 })
284 .catch(err => {
285 // TODO keep the edited model
286 this.toastr.error(`Error while saving ${model.name}`);
287 });
288 };
289
290 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800291 }
292
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800293 private formatValidators(validators: IXosModelDefsFieldValidators[]): IXosFormInputValidator {
294 // convert validators as expressed from modelDefs,
295 // to the object required by xosForm
296 return _.reduce(validators, (formValidators: IXosFormInputValidator, v: IXosModelDefsFieldValidators) => {
297 formValidators[v.name] = v.bool_value ? v.bool_value : v.int_value;
298 return formValidators;
299 }, {});
300 }
301
Matteo Scandolod58d5042016-12-16 16:59:21 -0800302 private fromCamelCase(string: string): string {
303 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
304 }
305
306 private fromSnakeCase(string: string): string {
307 return string.split('_').join(' ').trim();
308 }
309
310 private fromKebabCase(string: string): string {
311 return string.split('-').join(' ').trim();
312 }
313
314 private capitalizeFirst(string: string): string {
315 return string.slice(0, 1).toUpperCase() + string.slice(1);
316 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800317
318 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
319 // if the relation is not defined return
320 if (!fk || angular.isUndefined(fk) || fk === null) {
321 return;
322 }
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800323 this.XosModelStore.query(field.relation.model)
Matteo Scandolo04964232017-01-07 12:53:46 -0800324 .subscribe(res => {
325 if (angular.isDefined(res) && angular.isDefined(fk)) {
326 let ri = _.find(res, {id: fk});
327 if (angular.isDefined(ri)) {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800328 if (angular.isDefined(ri.name)) {
329 item[`${field.name}-formatted`] = ri.name;
330 }
331 else if (angular.isDefined(ri.humanReadableName)) {
332 item[`${field.name}-formatted`] = ri.humanReadableName;
333 }
334 else {
335 item[`${field.name}-formatted`] = ri.id;
336 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800337 }
338 }
339 });
340 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800341
342 // augment a select field with related model informations
343 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800344 this.XosModelStore.query(field.relation.model)
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800345 .subscribe(res => {
346 input.options = _.map(res, item => {
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800347 let opt = {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
348 if (!angular.isDefined(item.humanReadableName) && !angular.isDefined(item.name)) {
349 opt.label = item.id;
350 }
351 return opt;
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800352 });
353 });
354 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800355}