blob: 28a16a3d3682008e39b7c71493e73abf5886c54d [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
Matteo Scandolo8b2370c2017-02-02 17:19:07 -080036export class ConfigHelpers implements IXosConfigHelpersService {
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},
Matteo Scandolo8b2370c2017-02-02 17:19:07 -080099 pagination: {
100 pageSize: 10
101 },
Matteo Scandolocb466ed2017-01-04 17:16:24 -0800102 actions: [
103 {
104 label: 'delete',
105 icon: 'remove',
106 color: 'red',
107 cb: (item) => {
108 let obj = angular.copy(item);
109
110 item.$delete()
111 .then((res) => {
112 if (res.status === 404) {
113 // TODO understand why it does not go directly in catch
114 throw new Error();
115 }
116 this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
117 })
118 .catch(() => {
119 this.toastr.error(`Error while deleting ${obj.name}`);
120 });
121 }
122 }
123 ]
124 };
125 return cfg;
126 }
127
Matteo Scandoloa242c872017-01-12 15:13:00 -0800128 public modelFieldsToColumnsCfg(fields: IXosModelDefsField[], modelName: string): IXosTableColumn[] {
Matteo Scandoloee655a12016-12-19 15:38:43 -0800129
Matteo Scandolo231de262017-01-04 16:33:14 -0800130 const columns = _.map(fields, (f) => {
Matteo Scandoloee655a12016-12-19 15:38:43 -0800131 if (this.excluded_fields.indexOf(f.name) > -1) {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800132 return;
133 }
134 const col: IXosTableColumn = {
135 label: this.toLabel(f.name),
136 prop: f.name
137 };
138
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800139 if (f.name === 'id' || f.name === 'name') {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800140 col.link = item => this.stateWithParams(modelName, item);
Matteo Scandoloee655a12016-12-19 15:38:43 -0800141 }
142
Matteo Scandolo04964232017-01-07 12:53:46 -0800143 // if the field identify a relation, create a link
144 if (f.relation && f.relation.type === 'many_to_one') {
Matteo Scandolo04964232017-01-07 12:53:46 -0800145 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);
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';
Matteo Scandolo8b2370c2017-02-02 17:19:07 -0800155 col.hover = (item) => {
156 return item[f.name];
157 };
Matteo Scandolod58d5042016-12-16 16:59:21 -0800158 col.formatter = (item) => {
159 if (item.backend_status.indexOf('1') > -1) {
160 return 'check';
161 }
162 if (item.backend_status.indexOf('2') > -1) {
163 return 'exclamation-circle';
164 }
165 if (item.backend_status.indexOf('0') > -1) {
166 return 'clock-o';
167 }
168 };
169 }
Matteo Scandoloa8a6fbb2016-12-21 16:59:08 -0800170
Matteo Scandolod58d5042016-12-16 16:59:21 -0800171 return col;
172 })
173 .filter(v => angular.isDefined(v));
174
Matteo Scandolo231de262017-01-04 16:33:14 -0800175 return columns;
Matteo Scandolod58d5042016-12-16 16:59:21 -0800176 };
177
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800178 public urlFromCoreModel(name: string): string {
Matteo Scandoloa242c872017-01-12 15:13:00 -0800179
Matteo Scandolo1c5905f2017-01-04 17:41:15 -0800180 return `/core/${this.pluralize(name.toLowerCase())}`;
181 }
182
Matteo Scandoloa242c872017-01-12 15:13:00 -0800183 public stateFromCoreModel(name: string): string {
184 const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
185 if (s.data) {
186 return s.data.model === name;
187 }
188 return false;
189 });
190 return state.name;
191 }
192
193 public stateWithParams(name: string, model: any): string {
194 const state = this.stateFromCoreModel(name);
195 return `${state}({id: ${model['id']}})`;
196 }
197
Matteo Scandolo86bc26a2017-01-18 11:06:47 -0800198 public stateWithParamsForJs(name: string, model: any): any {
199 // TODO test and interface
200 const state = this.stateFromCoreModel(name);
201 return {name: state, params: {id: model.id}};
202 }
203
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800204 public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
205
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800206 return _.map(fields, (f: IXosModelDefsField) => {
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800207 if (f.relation) {
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800208 const input: IXosFormInput = {
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800209 name: f.name,
210 label: this.toLabel(f.name),
211 type: 'select',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800212 validators: f.validators,
213 hint: f.hint
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800214 };
215 this.populateSelectField(f, input);
216 return input;
217 }
218
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800219 return {
220 name: f.name,
221 label: this.toLabel(f.name),
222 type: f.type,
223 validators: f.validators
224 };
225 })
226 .filter(f => this.excluded_fields.indexOf(f.name) === -1);
227 }
228
229 public modelToFormCfg(model: IModeldef): IXosFormConfig {
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800230 const formCfg: IXosFormConfig = {
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800231 formName: `${model.name}Form`,
Matteo Scandolo0a8b02e2017-01-06 14:43:36 -0800232 exclude: ['backend_status', 'creator'],
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800233 actions: [{
234 label: 'Save',
235 class: 'success',
236 icon: 'ok',
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800237 cb: null
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800238 }],
239 inputs: this.modelFieldToInputCfg(model.fields)
240 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800241
242 formCfg.actions[0].cb = (item, form: angular.IFormController) => {
243
244 if (!form.$valid) {
245 formCfg.feedback = {
246 show: true,
247 message: 'Form is invalid',
248 type: 'danger',
249 closeBtn: true
250 };
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800251
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800252 return;
253 }
254
255 const model = angular.copy(item);
256
257 // TODO remove ManyToMany relations and save them separately (how??)
258 delete item.networks;
259
260 // adding userId as creator
261 item.creator = this.AuthService.getUser().id;
262
263 item.$save()
264 .then((res) => {
265 if (res.status === 403 || res.status === 405 || res.status === 500) {
266 // TODO understand why 405 does not go directly in catch (it may be realted to ng-rest-gw)
267 throw new Error();
268 }
Matteo Scandoloac8c8c22017-01-09 15:04:32 -0800269 formCfg.feedback = {
270 show: true,
271 message: `${model.name} succesfully saved`,
272 type: 'success',
273 closeBtn: true
274 };
Matteo Scandolo6f45e262017-01-09 14:47:26 -0800275 this.toastr.success(`${model.name} succesfully saved`);
276 })
277 .catch(err => {
278 // TODO keep the edited model
279 this.toastr.error(`Error while saving ${model.name}`);
280 });
281 };
282
283 return formCfg;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800284 }
285
Matteo Scandolod58d5042016-12-16 16:59:21 -0800286 private fromCamelCase(string: string): string {
287 return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
288 }
289
290 private fromSnakeCase(string: string): string {
291 return string.split('_').join(' ').trim();
292 }
293
294 private fromKebabCase(string: string): string {
295 return string.split('-').join(' ').trim();
296 }
297
298 private capitalizeFirst(string: string): string {
299 return string.slice(0, 1).toUpperCase() + string.slice(1);
300 }
Matteo Scandolo04964232017-01-07 12:53:46 -0800301
302 private populateRelated(item: any, fk: string, field: IXosModelDefsField): any {
303 // if the relation is not defined return
304 if (!fk || angular.isUndefined(fk) || fk === null) {
305 return;
306 }
307 this.ModelStore.query(field.relation.model)
308 .subscribe(res => {
309 if (angular.isDefined(res) && angular.isDefined(fk)) {
310 let ri = _.find(res, {id: fk});
311 if (angular.isDefined(ri)) {
312 item[`${field.name}-formatted`] = angular.isDefined(ri.name) ? ri.name : ri.humanReadableName;
313 }
314 }
315 });
316 }
Matteo Scandolo07e2f622017-01-09 10:54:13 -0800317
318 // augment a select field with related model informations
319 private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
320 this.ModelStore.query(field.relation.model)
321 .subscribe(res => {
322 input.options = _.map(res, item => {
323 return {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
324 });
325 });
326 }
Matteo Scandolod58d5042016-12-16 16:59:21 -0800327}