blob: b8ff20879f0879828b2e8ae9e4953e1074ac9dd9 [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 Scandolo47860fe2017-02-02 12:05:55 -08007import {IXosModelStoreService} 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 Scandolo86bc26a2017-01-18 11:06:47 -080033 stateWithParamsForJs(name: string, model: any): any;
Matteo Scandolod58d5042016-12-16 16:59:21 -080034}
35
36export class ConfigHelpers {
Matteo Scandoloa242c872017-01-12 15:13:00 -080037 static $inject = ['$state', 'toastr', 'AuthService', 'ModelStore'];
Matteo Scandolod58d5042016-12-16 16:59:21 -080038
Matteo Scandoloee655a12016-12-19 15:38:43 -080039 excluded_fields = [
40 'created',
41 'updated',
42 'enacted',
43 'policed',
44 'backend_register',
45 'deleted',
46 'write_protect',
47 'lazy_blocked',
48 'no_sync',
49 'no_policy',
50 'omf_friendly',
51 'enabled',
Matteo Scandolod62ea792016-12-22 14:02:28 -080052 'validators',
Matteo Scandolo80c3a652017-01-06 10:48:31 -080053 'password',
54 'backend_need_delete',
55 'backend_need_reap'
Matteo Scandoloee655a12016-12-19 15:38:43 -080056 ];
57
Matteo Scandolocb466ed2017-01-04 17:16:24 -080058 constructor(
Matteo Scandoloa242c872017-01-12 15:13:00 -080059 private $state: ng.ui.IStateService,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -080060 private toastr: ng.toastr.IToastrService,
Matteo Scandolo04964232017-01-07 12:53:46 -080061 private AuthService: IXosAuthService,
Matteo Scandolo47860fe2017-02-02 12:05:55 -080062 private ModelStore: IXosModelStoreService
Matteo Scandolocb466ed2017-01-04 17:16:24 -080063 ) {
Matteo Scandolo08464e52017-01-17 13:35:27 -080064 pluralize.addIrregularRule('xos', 'xoses');
Matteo Scandolod58d5042016-12-16 16:59:21 -080065 pluralize.addPluralRule(/slice$/i, 'slices');
Matteo Scandolo80c3a652017-01-06 10:48:31 -080066 pluralize.addSingularRule(/slice$/i, 'slice');
Matteo Scandolod58d5042016-12-16 16:59:21 -080067 }
68
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080069 public pluralize(string: string, quantity?: number, count?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080070 return pluralize(string, quantity, count);
71 }
72
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080073 public toLabels(strings: string[], pluralize?: boolean): string[] {
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080074 if (angular.isArray(strings)) {
75 return _.map(strings, s => {
Matteo Scandolod58d5042016-12-16 16:59:21 -080076 return this.toLabel(s, pluralize);
77 });
78 }
Matteo Scandoloe0d71ea2016-12-19 11:56:12 -080079 }
80
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080081 public toLabel(string: string, pluralize?: boolean): string {
Matteo Scandolod58d5042016-12-16 16:59:21 -080082
83 if (pluralize) {
84 string = this.pluralize(string);
85 }
86
87 string = this.fromCamelCase(string);
88 string = this.fromSnakeCase(string);
89 string = this.fromKebabCase(string);
90
91 return this.capitalizeFirst(string);
92 }
93
Matteo Scandolo1c5905f2017-01-04 17:41:15 -080094 public modelToTableCfg(model: IModeldef, baseUrl: string): IXosTableCfg {
Matteo Scandolocb466ed2017-01-04 17:16:24 -080095 const cfg = {
Matteo Scandoloa242c872017-01-12 15:13:00 -080096 columns: this.modelFieldsToColumnsCfg(model.fields, model.name),
Matteo Scandolocb466ed2017-01-04 17:16:24 -080097 filter: 'fulltext',
98 order: {field: 'id', reverse: false},
99 actions: [
100 {
101 label: 'delete',
102 icon: 'remove',
103 color: 'red',
104 cb: (item) => {
105 let obj = angular.copy(item);
106
107 item.$delete()
108 .then((res) => {
109 if (res.status === 404) {
110 // TODO understand why it does not go directly in catch
111 throw new Error();
112 }
113 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
114 })
115 .catch(() => {
116 this.toastr.error(`Error while deleting ${obj.name}`);
117 });
118 }
119 }
120 ]
121 };
122 return cfg;
123 }
124
Matteo Scandoloa242c872017-01-12 15:13:00 -0800125 public modelFieldsToColumnsCfg(fields: IXosModelDefsField[], modelName: string): IXosTableColumn[] {
Matteo Scandoloee655a12016-12-19 15:38:43 -0800126
Matteo Scandolo231de262017-01-04 16:33:14 -0800127 const columns = _.map(fields, (f) => {
Matteo Scandoloee655a12016-12-19 15:38:43 -0800128 if (this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800129 return;
130 }
131 const col: IXosTableColumn = {
132 label: this.toLabel(f.name),
133 prop: f.name
134 };
135
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800136 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800137 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandolo99ac9d92017-01-03 13:58:19 -0800138 // NOTE can we find a better method to generalize the route?
Matteo Scandoloa242c872017-01-12 15:13:00 -0800139 // col.link = item => `#/core${baseUrl.replace(':id?', item.id)}`;
Matteo Scandoloee655a12016-12-19 15:38:43 -0800140 }
141
Matteo Scandolo04964232017-01-07 12:53:46 -0800142 // if the field identify a relation, create a link
143 if (f.relation && f.relation.type === 'many_to_one') {
144 // TODO read the related model name and replace the value, use the xosTable format method?
145 col.type = 'custom';
146 col.formatter = item => {
147 this.populateRelated(item, item[f.name], f);
148 return item[f.name];
149 };
Matteo Scandoloa242c872017-01-12 15:13:00 -0800150 col.link = item => this.stateWithParams(f.relation.model, item);
151 // col.link = item => `#${this.urlFromCoreModel(f.relation.model)}/${item[f.name]}`;
Matteo Scandolo04964232017-01-07 12:53:46 -0800152 }
153
Matteo Scandolod58d5042016-12-16 16:59:21 -0800154 if (f.name === 'backend_status') {
155 col.type = 'icon';
156 col.formatter = (item) => {
157 if (item.backend_status.indexOf('1') > -1) {
158 return 'check';
159 }
160 if (item.backend_status.indexOf('2') > -1) {
161 return 'exclamation-circle';
162 }
163 if (item.backend_status.indexOf('0') > -1) {
164 return 'clock-o';
165 }
166 };
167 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800168
Matteo Scandolod58d5042016-12-16 16:59:21 -0800169 return col;
170 })
171 .filter(v => angular.isDefined(v));
172
Matteo Scandolo231de262017-01-04 16:33:14 -0800173 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800174 };
175
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800176 public urlFromCoreModel(name: string): string {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800177
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800178 return `/core/${this.pluralize(name.toLowerCase())}`;
179 }
180
Matteo Scandoloa242c872017-01-12 15:13:00 -0800181 public stateFromCoreModel(name: string): string {
182 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
183 if (s.data) {
184 return s.data.model === name;
185 }
186 return false;
187 });
188 return state.name;
189 }
190
191 public stateWithParams(name: string, model: any): string {
192 const state = this.stateFromCoreModel(name);
193 return `${state}({id: ${model['id']}})`;
194 }
195
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800196 public stateWithParamsForJs(name: string, model: any): any {
197 // TODO test and interface
198 const state = this.stateFromCoreModel(name);
199 return {name: state, params: {id: model.id}};
200 }
201
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800202 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
203
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800204 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800205 if (f.relation) {
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800206 const input: IXosFormInput = {
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800207 name: f.name,
208 label: this.toLabel(f.name),
209 type: 'select',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800210 validators: f.validators,
211 hint: f.hint
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800212 };
213 this.populateSelectField(f, input);
214 return input;
215 }
216
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800217 return {
218 name: f.name,
219 label: this.toLabel(f.name),
220 type: f.type,
221 validators: f.validators
222 };
223 })
224 .filter(f => this.excluded_fields.indexOf(f.name) === -1);
225 }
226
227 public modelToFormCfg(model: IModeldef): IXosFormConfig {
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800228 const formCfg: IXosFormConfig = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800229 formName: `${model.name}Form`,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -0800230 exclude: ['backend_status', 'creator'],
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800231 actions: [{
232 label: 'Save',
233 class: 'success',
234 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800235 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800236 }],
237 inputs: this.modelFieldToInputCfg(model.fields)
238 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800239
240 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
241
242 if (!form.$valid) {
243 formCfg.feedback = {
244 show: true,
245 message: 'Form is invalid',
246 type: 'danger',
247 closeBtn: true
248 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800249
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800250 return;
251 }
252
253 const model = angular.copy(item);
254
255 // TODO remove ManyToMany relations and save them separately (how??)
256 delete item.networks;
257
258 // adding userId as creator
259 item.creator = this.AuthService.getUser().id;
260
261 item.$save()
262 .then((res) => {
263 if (res.status === 403 || res.status === 405 || res.status === 500) {
264 // TODO understand why 405 does not go directly in catch (it may be realted to ng-rest-gw)
265 throw new Error();
266 }
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800267 formCfg.feedback = {
268 show: true,
269 message: `${model.name} succesfully saved`,
270 type: 'success',
271 closeBtn: true
272 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800273 this.toastr.success(`${model.name} succesfully saved`);
274 })
275 .catch(err => {
276 // TODO keep the edited model
277 this.toastr.error(`Error while saving ${model.name}`);
278 });
279 };
280
281 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800282 }
283
Matteo Scandolod58d5042016-12-16 16:59:21 -0800284 private fromCamelCase(string: string): string {
285 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
286 }
287
288 private fromSnakeCase(string: string): string {
289 return string.split('_').join(' ').trim();
290 }
291
292 private fromKebabCase(string: string): string {
293 return string.split('-').join(' ').trim();
294 }
295
296 private capitalizeFirst(string: string): string {
297 return string.slice(0, 1).toUpperCase() + string.slice(1);
298 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800299
300 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
301 // if the relation is not defined return
302 if (!fk || angular.isUndefined(fk) || fk === null) {
303 return;
304 }
305 this.ModelStore.query(field.relation.model)
306 .subscribe(res => {
307 if (angular.isDefined(res) && angular.isDefined(fk)) {
308 let ri = _.find(res, {id: fk});
309 if (angular.isDefined(ri)) {
310 item[`${field.name}-formatted`] = angular.isDefined(ri.name) ? ri.name : ri.humanReadableName;
311 }
312 }
313 });
314 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800315
316 // augment a select field with related model informations
317 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
318 this.ModelStore.query(field.relation.model)
319 .subscribe(res => {
320 input.options = _.map(res, item => {
321 return {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
322 });
323 });
324 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800325}