blob: 16a7b71ce6fd1718f0d8d042da322bcf2d17e1df [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';
4import {IModeldef} from '../../../datasources/rest/modeldefs.rest';
Matteo Scandolo80c3a652017-01-06 10:48:31 -08005import {IXosFormConfig, IXosFormInput} from '../../form/form';
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -08006import {IXosAuthService} from '../../../datasources/rest/auth.rest';
Matteo Scandolo04964232017-01-07 12:53:46 -08007import {IModelStoreService} from '../../../datasources/stores/model.store';
Matteo Scandoloa242c872017-01-12 15:13:00 -08008import {IXosState} from '../../../../index';
Matteo Scandolod58d5042016-12-16 16:59:21 -08009
10export interface IXosModelDefsField {
11 name: string;
12 type: string;
Matteo Scandolocb466ed2017-01-04 17:16:24 -080013 validators?: any;
Matteo Scandolo04964232017-01-07 12:53:46 -080014 hint?: string;
15 relation?: {
16 model: string;
17 type: string;
18 };
Matteo Scandolod58d5042016-12-16 16:59:21 -080019}
20
21export interface IXosConfigHelpersService {
Matteo Scandoloee655a12016-12-19 15:38:43 -080022 excluded_fields: string[];
Matteo Scandolocb466ed2017-01-04 17:16:24 -080023 modelFieldsToColumnsCfg(fields: IXosModelDefsField[], baseUrl: string): IXosTableColumn[]; // TODO use a proper interface
Matteo Scandoloa242c872017-01-12 15:13:00 -080024 modelToTableCfg(model: IModeldef, modelName: string): IXosTableCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -080025 modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
26 modelToFormCfg(model: IModeldef): IXosFormConfig;
Matteo Scandolod58d5042016-12-16 16:59:21 -080027 pluralize(string: string, quantity?: number, count?: boolean): string;
28 toLabel(string: string, pluralize?: boolean): string;
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080029 toLabels(string: string[], pluralize?: boolean): string[];
Matteo Scandoloa242c872017-01-12 15:13:00 -080030 urlFromCoreModel(model: string): string;
31 stateFromCoreModel(name: string): string;
32 stateWithParams(name: string, model: any): string;
Matteo Scandolod58d5042016-12-16 16:59:21 -080033}
34
35export class ConfigHelpers {
Matteo Scandoloa242c872017-01-12 15:13:00 -080036 static $inject = ['$state', 'toastr', 'AuthService', 'ModelStore'];
Matteo Scandolod58d5042016-12-16 16:59:21 -080037
Matteo Scandoloee655a12016-12-19 15:38:43 -080038 excluded_fields = [
39 'created',
40 'updated',
41 'enacted',
42 'policed',
43 'backend_register',
44 'deleted',
45 'write_protect',
46 'lazy_blocked',
47 'no_sync',
48 'no_policy',
49 'omf_friendly',
50 'enabled',
Matteo Scandolod62ea792016-12-22 14:02:28 -080051 'validators',
Matteo Scandolo80c3a652017-01-06 10:48:31 -080052 'password',
53 'backend_need_delete',
54 'backend_need_reap'
Matteo Scandoloee655a12016-12-19 15:38:43 -080055 ];
56
Matteo Scandolocb466ed2017-01-04 17:16:24 -080057 constructor(
Matteo Scandoloa242c872017-01-12 15:13:00 -080058 private $state: ng.ui.IStateService,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -080059 private toastr: ng.toastr.IToastrService,
Matteo Scandolo04964232017-01-07 12:53:46 -080060 private AuthService: IXosAuthService,
61 private ModelStore: IModelStoreService
Matteo Scandolocb466ed2017-01-04 17:16:24 -080062 ) {
Matteo Scandolo08464e52017-01-17 13:35:27 -080063 pluralize.addIrregularRule('xos', 'xoses');
Matteo Scandolod58d5042016-12-16 16:59:21 -080064 pluralize.addPluralRule(/slice$/i, 'slices');
Matteo Scandolo80c3a652017-01-06 10:48:31 -080065 pluralize.addSingularRule(/slice$/i, 'slice');
Matteo Scandolod58d5042016-12-16 16:59:21 -080066 }
67
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080068 public pluralize(string: string, quantity?: number, count?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080069 return pluralize(string, quantity, count);
70 }
71
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080072 public toLabels(strings: string[], pluralize?: boolean): string[] {
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080073 if (angular.isArray(strings)) {
74 return _.map(strings, s => {
Matteo Scandolod58d5042016-12-16 16:59:21 -080075 return this.toLabel(s, pluralize);
76 });
77 }
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080078 }
79
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080080 public toLabel(string: string, pluralize?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080081
82 if (pluralize) {
83 string = this.pluralize(string);
84 }
85
86 string = this.fromCamelCase(string);
87 string = this.fromSnakeCase(string);
88 string = this.fromKebabCase(string);
89
90 return this.capitalizeFirst(string);
91 }
92
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080093 public modelToTableCfg(model: IModeldef, baseUrl: string): IXosTableCfg {
Matteo Scandolocb466ed2017-01-04 17:16:24 -080094 const cfg = {
Matteo Scandoloa242c872017-01-12 15:13:00 -080095 columns: this.modelFieldsToColumnsCfg(model.fields, model.name),
Matteo Scandolocb466ed2017-01-04 17:16:24 -080096 filter: 'fulltext',
97 order: {field: 'id', reverse: false},
98 actions: [
99 {
100 label: 'delete',
101 icon: 'remove',
102 color: 'red',
103 cb: (item) => {
104 let obj = angular.copy(item);
105
106 item.$delete()
107 .then((res) => {
108 if (res.status === 404) {
109 // TODO understand why it does not go directly in catch
110 throw new Error();
111 }
112 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
113 })
114 .catch(() => {
115 this.toastr.error(`Error while deleting ${obj.name}`);
116 });
117 }
118 }
119 ]
120 };
121 return cfg;
122 }
123
Matteo Scandoloa242c872017-01-12 15:13:00 -0800124 public modelFieldsToColumnsCfg(fields: IXosModelDefsField[], modelName: string): IXosTableColumn[] {
Matteo Scandoloee655a12016-12-19 15:38:43 -0800125
Matteo Scandolo231de262017-01-04 16:33:14 -0800126 const columns = _.map(fields, (f) => {
Matteo Scandoloee655a12016-12-19 15:38:43 -0800127 if (this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800128 return;
129 }
130 const col: IXosTableColumn = {
131 label: this.toLabel(f.name),
132 prop: f.name
133 };
134
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800135 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800136 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandolo99ac9d92017-01-03 13:58:19 -0800137 // NOTE can we find a better method to generalize the route?
Matteo Scandoloa242c872017-01-12 15:13:00 -0800138 // col.link = item => `#/core${baseUrl.replace(':id?', item.id)}`;
Matteo Scandoloee655a12016-12-19 15:38:43 -0800139 }
140
Matteo Scandolo04964232017-01-07 12:53:46 -0800141 // if the field identify a relation, create a link
142 if (f.relation && f.relation.type === 'many_to_one') {
143 // TODO read the related model name and replace the value, use the xosTable format method?
144 col.type = 'custom';
145 col.formatter = item => {
146 this.populateRelated(item, item[f.name], f);
147 return item[f.name];
148 };
Matteo Scandoloa242c872017-01-12 15:13:00 -0800149 col.link = item => this.stateWithParams(f.relation.model, item);
150 // col.link = item => `#${this.urlFromCoreModel(f.relation.model)}/${item[f.name]}`;
Matteo Scandolo04964232017-01-07 12:53:46 -0800151 }
152
Matteo Scandolod58d5042016-12-16 16:59:21 -0800153 if (f.name === 'backend_status') {
154 col.type = 'icon';
155 col.formatter = (item) => {
156 if (item.backend_status.indexOf('1') > -1) {
157 return 'check';
158 }
159 if (item.backend_status.indexOf('2') > -1) {
160 return 'exclamation-circle';
161 }
162 if (item.backend_status.indexOf('0') > -1) {
163 return 'clock-o';
164 }
165 };
166 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800167
Matteo Scandolod58d5042016-12-16 16:59:21 -0800168 return col;
169 })
170 .filter(v => angular.isDefined(v));
171
Matteo Scandolo231de262017-01-04 16:33:14 -0800172 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800173 };
174
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800175 public urlFromCoreModel(name: string): string {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800176
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800177 return `/core/${this.pluralize(name.toLowerCase())}`;
178 }
179
Matteo Scandoloa242c872017-01-12 15:13:00 -0800180 public stateFromCoreModel(name: string): string {
181 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
182 if (s.data) {
183 return s.data.model === name;
184 }
185 return false;
186 });
187 return state.name;
188 }
189
190 public stateWithParams(name: string, model: any): string {
191 const state = this.stateFromCoreModel(name);
192 return `${state}({id: ${model['id']}})`;
193 }
194
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800195 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
196
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800197 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800198 if (f.relation) {
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800199 const input: IXosFormInput = {
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800200 name: f.name,
201 label: this.toLabel(f.name),
202 type: 'select',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800203 validators: f.validators,
204 hint: f.hint
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800205 };
206 this.populateSelectField(f, input);
207 return input;
208 }
209
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800210 return {
211 name: f.name,
212 label: this.toLabel(f.name),
213 type: f.type,
214 validators: f.validators
215 };
216 })
217 .filter(f => this.excluded_fields.indexOf(f.name) === -1);
218 }
219
220 public modelToFormCfg(model: IModeldef): IXosFormConfig {
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800221 const formCfg: IXosFormConfig = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800222 formName: `${model.name}Form`,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -0800223 exclude: ['backend_status', 'creator'],
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800224 actions: [{
225 label: 'Save',
226 class: 'success',
227 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800228 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800229 }],
230 inputs: this.modelFieldToInputCfg(model.fields)
231 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800232
233 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
234
235 if (!form.$valid) {
236 formCfg.feedback = {
237 show: true,
238 message: 'Form is invalid',
239 type: 'danger',
240 closeBtn: true
241 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800242
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800243 return;
244 }
245
246 const model = angular.copy(item);
247
248 // TODO remove ManyToMany relations and save them separately (how??)
249 delete item.networks;
250
251 // adding userId as creator
252 item.creator = this.AuthService.getUser().id;
253
254 item.$save()
255 .then((res) => {
256 if (res.status === 403 || res.status === 405 || res.status === 500) {
257 // TODO understand why 405 does not go directly in catch (it may be realted to ng-rest-gw)
258 throw new Error();
259 }
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800260 formCfg.feedback = {
261 show: true,
262 message: `${model.name} succesfully saved`,
263 type: 'success',
264 closeBtn: true
265 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800266 this.toastr.success(`${model.name} succesfully saved`);
267 })
268 .catch(err => {
269 // TODO keep the edited model
270 this.toastr.error(`Error while saving ${model.name}`);
271 });
272 };
273
274 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800275 }
276
Matteo Scandolod58d5042016-12-16 16:59:21 -0800277 private fromCamelCase(string: string): string {
278 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
279 }
280
281 private fromSnakeCase(string: string): string {
282 return string.split('_').join(' ').trim();
283 }
284
285 private fromKebabCase(string: string): string {
286 return string.split('-').join(' ').trim();
287 }
288
289 private capitalizeFirst(string: string): string {
290 return string.slice(0, 1).toUpperCase() + string.slice(1);
291 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800292
293 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
294 // if the relation is not defined return
295 if (!fk || angular.isUndefined(fk) || fk === null) {
296 return;
297 }
298 this.ModelStore.query(field.relation.model)
299 .subscribe(res => {
300 if (angular.isDefined(res) && angular.isDefined(fk)) {
301 let ri = _.find(res, {id: fk});
302 if (angular.isDefined(ri)) {
303 item[`${field.name}-formatted`] = angular.isDefined(ri.name) ? ri.name : ri.humanReadableName;
304 }
305 }
306 });
307 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800308
309 // augment a select field with related model informations
310 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
311 this.ModelStore.query(field.relation.model)
312 .subscribe(res => {
313 input.options = _.map(res, item => {
314 return {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
315 });
316 });
317 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800318}