[CORD-873] CRUD for Core and Service model from Chameleon
Change-Id: I45c533feba6720b82de3681d862773047e7fd6f8
diff --git a/src/app/core/form/form.html b/src/app/core/form/form.html
index 4c7485a..5da8c4e 100644
--- a/src/app/core/form/form.html
+++ b/src/app/core/form/form.html
@@ -1,3 +1,4 @@
+<!--<pre>{{vm.config.inputs | json}}</pre>-->
<form name="vm.{{vm.config.formName || 'form'}}" novalidate>
<!--<div class="form-group" ng-repeat="(name, field) in vm.formField">-->
<!--<xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>-->
diff --git a/src/app/core/form/form.ts b/src/app/core/form/form.ts
index c0bd206..9de27ea 100644
--- a/src/app/core/form/form.ts
+++ b/src/app/core/form/form.ts
@@ -44,7 +44,7 @@
options?: IXosFormInputOptions[];
}
-export interface IXosFormConfig {
+export interface IXosFormCfg {
exclude?: string[];
actions: IXosFormAction[];
feedback?: IXosFeedback;
diff --git a/src/app/core/header/header.spec.ts b/src/app/core/header/header.spec.ts
index c1d6062..73ba587 100644
--- a/src/app/core/header/header.spec.ts
+++ b/src/app/core/header/header.spec.ts
@@ -55,7 +55,7 @@
.value('toastr', MockToastr)
.value('toastrConfig', MockToastrConfig)
.value('AuthService', MockAuth)
- .value('NavigationService', {})
+ .value('XosNavigationService', {})
.value('XosKeyboardShortcut', MockXosKeyboardShortcut)
.value('StyleConfig', {
logo: 'cord-logo.png',
diff --git a/src/app/core/header/header.ts b/src/app/core/header/header.ts
index 8926080..62e3ce9 100644
--- a/src/app/core/header/header.ts
+++ b/src/app/core/header/header.ts
@@ -14,7 +14,7 @@
}
class HeaderController {
- static $inject = ['$scope', '$rootScope', '$state', 'AuthService', 'SynchronizerStore', 'toastr', 'toastrConfig', 'NavigationService', 'StyleConfig', 'SearchService', 'XosKeyboardShortcut'];
+ static $inject = ['$scope', '$rootScope', '$state', 'AuthService', 'SynchronizerStore', 'toastr', 'toastrConfig', 'XosNavigationService', 'StyleConfig', 'SearchService', 'XosKeyboardShortcut'];
public notifications: INotification[] = [];
public newNotifications: INotification[] = [];
public version: string;
diff --git a/src/app/core/index.ts b/src/app/core/index.ts
index 318ffeb..d275a9f 100644
--- a/src/app/core/index.ts
+++ b/src/app/core/index.ts
@@ -4,8 +4,8 @@
import routesConfig from './routes';
import {xosLogin} from './login/login';
import {xosTable} from './table/table';
-import {RuntimeStates} from './services/runtime-states';
-import {NavigationService} from './services/navigation';
+import {XosRuntimeStates} from './services/runtime-states';
+import {IXosNavigationService} from './services/navigation';
import {PageTitle} from './services/page-title';
import {ConfigHelpers} from './services/helpers/config.helpers';
import {xosLinkWrapper} from './link-wrapper/link-wrapper';
@@ -15,7 +15,6 @@
import 'angular-toastr';
import {xosAlert} from './alert/alert';
import {xosValidation} from './validation/validation';
-import {ModelSetup} from './services/helpers/model-setup.helpers';
import {xosSidePanel} from './side-panel/side-panel';
import {XosSidePanel} from './side-panel/side-panel.service';
import {XosComponentInjector} from './services/helpers/component-injector.helpers';
@@ -33,12 +32,11 @@
'ui.bootstrap.typeahead'
])
.config(routesConfig)
- .provider('RuntimeStates', RuntimeStates)
- .service('NavigationService', NavigationService)
+ .provider('XosRuntimeStates', XosRuntimeStates)
+ .service('XosNavigationService', IXosNavigationService)
.service('PageTitle', PageTitle)
.service('XosFormHelpers', XosFormHelpers)
.service('ConfigHelpers', ConfigHelpers)
- .service('ModelSetup', ModelSetup)
.service('XosSidePanel', XosSidePanel)
.service('XosKeyboardShortcut', XosKeyboardShortcut)
.service('XosComponentInjector', XosComponentInjector)
diff --git a/src/app/core/login/login.ts b/src/app/core/login/login.ts
index 673d9a8..183980a 100644
--- a/src/app/core/login/login.ts
+++ b/src/app/core/login/login.ts
@@ -1,18 +1,19 @@
import {AuthService} from '../../datasources/rest/auth.rest';
import './login.scss';
-import {IXosModelSetupService} from '../services/helpers/model-setup.helpers';
+
import {IXosStyleConfig} from '../../../index';
+import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
class LoginCtrl {
- static $inject = ['$log', 'AuthService', '$state', 'ModelSetup', 'StyleConfig'];
+ static $inject = ['$log', 'AuthService', '$state', 'XosModelDiscoverer', 'StyleConfig'];
public loginStyle: any;
public img: string;
- /** @ngInject */
+
constructor(
private $log: ng.ILogService,
private authService: AuthService,
private $state: angular.ui.IStateService,
- private ModelSetup: IXosModelSetupService,
+ private XosModelDiscoverer: IXosModelDiscovererService,
private StyleConfig: IXosStyleConfig
) {
@@ -34,7 +35,7 @@
})
.then(res => {
// after login set up models
- return this.ModelSetup.setup();
+ return this.XosModelDiscoverer.discover();
})
.then(() => {
this.$state.go('xos.dashboard');
diff --git a/src/app/core/nav/nav.spec.ts b/src/app/core/nav/nav.spec.ts
index 5101ba8..1d41f4b 100644
--- a/src/app/core/nav/nav.spec.ts
+++ b/src/app/core/nav/nav.spec.ts
@@ -29,7 +29,7 @@
angular
.module('xosNav', ['app/core/nav/nav.html', 'ui.router'])
.component('xosNav', xosNav)
- .service('NavigationService', NavigationService)
+ .service('XosNavigationService', NavigationService)
.value('AuthService', AuthMock)
.value('StyleConfig', {})
.value('XosSidePanel', {})
diff --git a/src/app/core/nav/nav.ts b/src/app/core/nav/nav.ts
index 08ad48a..f537e5e 100644
--- a/src/app/core/nav/nav.ts
+++ b/src/app/core/nav/nav.ts
@@ -6,7 +6,7 @@
import {IXosComponentInjectorService} from '../services/helpers/component-injector.helpers';
class NavCtrl {
- static $inject = ['$scope', '$state', 'NavigationService', 'AuthService', 'StyleConfig', 'XosSidePanel', 'XosComponentInjector'];
+ static $inject = ['$scope', '$state', 'XosNavigationService', 'AuthService', 'StyleConfig', 'XosSidePanel', 'XosComponentInjector'];
public routes: IXosNavigationRoute[];
public navSelected: string;
public appName: string;
diff --git a/src/app/core/services/helpers/config.helpers.spec.ts b/src/app/core/services/helpers/config.helpers.spec.ts
index 3e90d33..ed41228 100644
--- a/src/app/core/services/helpers/config.helpers.spec.ts
+++ b/src/app/core/services/helpers/config.helpers.spec.ts
@@ -3,47 +3,60 @@
import 'angular-ui-router';
import {IXosConfigHelpersService, ConfigHelpers, IXosModelDefsField} from './config.helpers';
-import {IModeldef} from '../../../datasources/rest/modeldefs.rest';
+import {IXosModeldef} from '../../../datasources/rest/modeldefs.rest';
import {IXosTableCfg} from '../../table/table';
-import {IXosFormInput, IXosFormConfig} from '../../form/form';
+import {IXosFormInput, IXosFormCfg} from '../../form/form';
import {BehaviorSubject} from 'rxjs';
let service: IXosConfigHelpersService;
-const model: IModeldef = {
+const model: IXosModeldef = {
name: 'Test',
+ app: 'test',
fields: [
{
type: 'number',
name: 'id',
- validators: {}
+ validators: []
},
{
type: 'string',
name: 'name',
- validators: {
- required: true
- }
+ validators: [
+ {
+ bool_value: true,
+ name: 'required'
+ }
+ ]
},
{
type: 'string',
name: 'something',
- validators: {
- maxlength: 30
- }
+ validators: [
+ {
+ int_value: 30,
+ name: 'maxlength'
+ }
+ ]
},
{
type: 'number',
name: 'else',
- validators: {
- min: 20,
- max: 40
- }
+ validators: [
+ {
+ int_value: 20,
+ name: 'min'
+ },
+ {
+ int_value: 40,
+ name: 'max'
+ }
+ ]
},
{
type: 'date',
name: 'updated',
- validators: {}
+ validators: []
},
]
};
@@ -59,7 +72,7 @@
return {id: 1};
}
})
- .value('ModelStore', {
+ .value('XosModelStore', {
})
.value('$state', {
@@ -131,11 +144,6 @@
});
describe('the navigation methods', () => {
- describe('urlFromCoreModels', () => {
- it('should return the URL for a given model', () => {
- expect(service.urlFromCoreModel('Test')).toBe('/core/tests');
- });
- });
describe('stateFromCoreModels', () => {
let state: ng.ui.IStateService;
@@ -156,7 +164,7 @@
describe('the modelFieldsToColumnsCfg method', () => {
it('should return an array of columns', () => {
- const cols = service.modelFieldsToColumnsCfg(model.fields, 'testUrl/:id?');
+ const cols = service.modelFieldsToColumnsCfg({fields: model.fields, name: 'testUrl', app: 'test'});
expect(cols[0].label).toBe('Id');
expect(cols[0].prop).toBe('id');
expect(cols[0].link).toBeDefined();
@@ -214,7 +222,7 @@
describe('the modelToFormCfg method', () => {
it('should return a form config', () => {
- const config: IXosFormConfig = service.modelToFormCfg(model);
+ const config: IXosFormCfg = service.modelToFormCfg(model);
expect(config.formName).toBe('TestForm');
expect(config.actions.length).toBe(1);
expect(config.actions[0].label).toBe('Save');
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index 28a16a3..2ac9394 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -1,16 +1,22 @@
import * as _ from 'lodash';
import * as pluralize from 'pluralize';
import {IXosTableColumn, IXosTableCfg} from '../../table/table';
-import {IModeldef} from '../../../datasources/rest/modeldefs.rest';
-import {IXosFormConfig, IXosFormInput} from '../../form/form';
+import {IXosModeldef} from '../../../datasources/rest/modeldefs.rest';
+import {IXosFormCfg, IXosFormInput, IXosFormInputValidator} from '../../form/form';
import {IXosAuthService} from '../../../datasources/rest/auth.rest';
import {IXosModelStoreService} from '../../../datasources/stores/model.store';
-import {IXosState} from '../../../../index';
+import {IXosState} from '../runtime-states';
+
+export interface IXosModelDefsFieldValidators {
+ name: string;
+ bool_value?: boolean;
+ int_value?: number;
+}
export interface IXosModelDefsField {
name: string;
type: string;
- validators?: any;
+ validators?: IXosModelDefsFieldValidators[];
hint?: string;
relation?: {
model: string;
@@ -20,23 +26,26 @@
export interface IXosConfigHelpersService {
excluded_fields: string[];
- modelFieldsToColumnsCfg(fields: IXosModelDefsField[], baseUrl: string): IXosTableColumn[]; // TODO use a proper interface
- modelToTableCfg(model: IModeldef, modelName: string): IXosTableCfg;
+ modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[];
+ modelToTableCfg(model: IXosModeldef, modelName: string): IXosTableCfg;
modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
- modelToFormCfg(model: IModeldef): IXosFormConfig;
+ modelToFormCfg(model: IXosModeldef): IXosFormCfg;
pluralize(string: string, quantity?: number, count?: boolean): string;
toLabel(string: string, pluralize?: boolean): string;
toLabels(string: string[], pluralize?: boolean): string[];
- urlFromCoreModel(model: string): string;
stateFromCoreModel(name: string): string;
stateWithParams(name: string, model: any): string;
stateWithParamsForJs(name: string, model: any): any;
}
export class ConfigHelpers implements IXosConfigHelpersService {
- static $inject = ['$state', 'toastr', 'AuthService', 'ModelStore'];
+ static $inject = [
+ '$state',
+ 'toastr',
+ 'AuthService',
+ 'XosModelStore'];
- excluded_fields = [
+ public excluded_fields = [
'created',
'updated',
'enacted',
@@ -59,11 +68,15 @@
private $state: ng.ui.IStateService,
private toastr: ng.toastr.IToastrService,
private AuthService: IXosAuthService,
- private ModelStore: IXosModelStoreService
+ private XosModelStore: IXosModelStoreService
) {
pluralize.addIrregularRule('xos', 'xoses');
pluralize.addPluralRule(/slice$/i, 'slices');
pluralize.addSingularRule(/slice$/i, 'slice');
+ pluralize.addPluralRule(/library$/i, 'librarys');
+ pluralize.addPluralRule(/imagedeployments/i, 'imagedeploymentses');
+ pluralize.addPluralRule(/controllerimages/i, 'controllerimageses');
+
}
public pluralize(string: string, quantity?: number, count?: boolean): string {
@@ -91,9 +104,9 @@
return this.capitalizeFirst(string);
}
- public modelToTableCfg(model: IModeldef, baseUrl: string): IXosTableCfg {
+ public modelToTableCfg(model: IXosModeldef, baseUrl: string): IXosTableCfg {
const cfg = {
- columns: this.modelFieldsToColumnsCfg(model.fields, model.name),
+ columns: this.modelFieldsToColumnsCfg(model),
filter: 'fulltext',
order: {field: 'id', reverse: false},
pagination: {
@@ -125,10 +138,11 @@
return cfg;
}
- public modelFieldsToColumnsCfg(fields: IXosModelDefsField[], modelName: string): IXosTableColumn[] {
-
+ public modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[] {
+ const fields: IXosModelDefsField[] = model.fields;
+ const modelName: string = model.name;
const columns = _.map(fields, (f) => {
- if (this.excluded_fields.indexOf(f.name) > -1) {
+ if (!angular.isDefined(f) || this.excluded_fields.indexOf(f.name) > -1) {
return;
}
const col: IXosTableColumn = {
@@ -147,7 +161,9 @@
this.populateRelated(item, item[f.name], f);
return item[f.name];
};
- col.link = item => this.stateWithParams(f.relation.model, item);
+ col.link = item => {
+ return this.stateWithParams(f.relation.model, item);
+ };
}
if (f.name === 'backend_status') {
@@ -175,11 +191,6 @@
return columns;
};
- public urlFromCoreModel(name: string): string {
-
- return `/core/${this.pluralize(name.toLowerCase())}`;
- }
-
public stateFromCoreModel(name: string): string {
const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
if (s.data) {
@@ -187,7 +198,7 @@
}
return false;
});
- return state.name;
+ return state ? state.name : null;
}
public stateWithParams(name: string, model: any): string {
@@ -204,32 +215,27 @@
public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
return _.map(fields, (f: IXosModelDefsField) => {
- if (f.relation) {
- const input: IXosFormInput = {
- name: f.name,
- label: this.toLabel(f.name),
- type: 'select',
- validators: f.validators,
- hint: f.hint
- };
- this.populateSelectField(f, input);
- return input;
- }
-
- return {
+ const input: IXosFormInput = {
name: f.name,
label: this.toLabel(f.name),
type: f.type,
- validators: f.validators
+ validators: this.formatValidators(f.validators),
+ hint: f.hint
};
+ if (f.relation) {
+ input.type = 'select';
+ this.populateSelectField(f, input);
+ return input;
+ }
+ return input;
})
.filter(f => this.excluded_fields.indexOf(f.name) === -1);
}
- public modelToFormCfg(model: IModeldef): IXosFormConfig {
- const formCfg: IXosFormConfig = {
+ public modelToFormCfg(model: IXosModeldef): IXosFormCfg {
+ const formCfg: IXosFormCfg = {
formName: `${model.name}Form`,
- exclude: ['backend_status', 'creator'],
+ exclude: ['backend_status', 'creator', 'id'],
actions: [{
label: 'Save',
class: 'success',
@@ -258,12 +264,19 @@
delete item.networks;
// adding userId as creator
- item.creator = this.AuthService.getUser().id;
+ // item.creator = this.AuthService.getUser().id;
+
+ // remove field added by xosTable
+ _.forEach(Object.keys(item), prop => {
+ if (prop.indexOf('-formatted') > -1) {
+ delete item[prop];
+ }
+ });
item.$save()
.then((res) => {
if (res.status === 403 || res.status === 405 || res.status === 500) {
- // TODO understand why 405 does not go directly in catch (it may be realted to ng-rest-gw)
+ // TODO understand why 405 does not go directly in catch (it may be related to ng-rest-gw)
throw new Error();
}
formCfg.feedback = {
@@ -283,6 +296,15 @@
return formCfg;
}
+ private formatValidators(validators: IXosModelDefsFieldValidators[]): IXosFormInputValidator {
+ // convert validators as expressed from modelDefs,
+ // to the object required by xosForm
+ return _.reduce(validators, (formValidators: IXosFormInputValidator, v: IXosModelDefsFieldValidators) => {
+ formValidators[v.name] = v.bool_value ? v.bool_value : v.int_value;
+ return formValidators;
+ }, {});
+ }
+
private fromCamelCase(string: string): string {
return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
}
@@ -304,12 +326,20 @@
if (!fk || angular.isUndefined(fk) || fk === null) {
return;
}
- this.ModelStore.query(field.relation.model)
+ this.XosModelStore.query(field.relation.model)
.subscribe(res => {
if (angular.isDefined(res) && angular.isDefined(fk)) {
let ri = _.find(res, {id: fk});
if (angular.isDefined(ri)) {
- item[`${field.name}-formatted`] = angular.isDefined(ri.name) ? ri.name : ri.humanReadableName;
+ if (angular.isDefined(ri.name)) {
+ item[`${field.name}-formatted`] = ri.name;
+ }
+ else if (angular.isDefined(ri.humanReadableName)) {
+ item[`${field.name}-formatted`] = ri.humanReadableName;
+ }
+ else {
+ item[`${field.name}-formatted`] = ri.id;
+ }
}
}
});
@@ -317,10 +347,14 @@
// augment a select field with related model informations
private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
- this.ModelStore.query(field.relation.model)
+ this.XosModelStore.query(field.relation.model)
.subscribe(res => {
input.options = _.map(res, item => {
- return {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
+ let opt = {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
+ if (!angular.isDefined(item.humanReadableName) && !angular.isDefined(item.name)) {
+ opt.label = item.id;
+ }
+ return opt;
});
});
}
diff --git a/src/app/core/services/helpers/model-setup.helpers.ts b/src/app/core/services/helpers/model-setup.helpers.ts
deleted file mode 100644
index e79899d..0000000
--- a/src/app/core/services/helpers/model-setup.helpers.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import {ModeldefsService, IModeldef} from '../../../datasources/rest/modeldefs.rest';
-import {IXosConfigHelpersService} from './config.helpers';
-import {IRuntimeStatesService} from '../runtime-states';
-import {NavigationService} from '../navigation';
-import {IXosState} from '../../../../index';
-import * as _ from 'lodash';
-import IPromise = angular.IPromise;
-
-export interface IXosModelSetupService {
- setup(): IPromise<null>;
-}
-
-export class ModelSetup {
- static $inject = ['$rootScope', '$q', 'ModelDefs', 'ConfigHelpers', 'RuntimeStates', 'NavigationService'];
-
- constructor(
- private $rootScope: ng.IScope,
- private $q: ng.IQService,
- private ModelDefs: ModeldefsService,
- private ConfigHelpers: IXosConfigHelpersService,
- private RuntimeStates: IRuntimeStatesService,
- private NavigationService: NavigationService
- ) {
-
- }
-
- public setup(): IPromise<null> {
- const d = this.$q.defer();
- this.ModelDefs.get()
- .then((models: IModeldef[]) => {
- _.forEach(models, (m: IModeldef) => {
- const stateUrl = `/${this.ConfigHelpers.pluralize(m.name.toLowerCase())}/:id?`;
- const stateName = `xos.core.${this.ConfigHelpers.pluralize(m.name.toLowerCase())}`;
- const state: IXosState = {
- parent: 'core',
- url: stateUrl,
- component: 'xosCrud',
- params: {
- id: null
- },
- data: {
- model: m.name,
- related: m.relations,
- xosTableCfg: this.ConfigHelpers.modelToTableCfg(m, stateUrl),
- xosFormCfg: this.ConfigHelpers.modelToFormCfg(m)
- }
- };
-
- this.RuntimeStates.addState(stateName, state);
- this.NavigationService.add({
- label: this.ConfigHelpers.pluralize(m.name),
- state: stateName,
- parent: 'xos.core'
- });
- });
-
- d.resolve();
- })
- .catch(d.reject);
-
- return d.promise;
- }
-}
diff --git a/src/app/core/services/navigation.spec.ts b/src/app/core/services/navigation.spec.ts
index fbfb91c..82a235e 100644
--- a/src/app/core/services/navigation.spec.ts
+++ b/src/app/core/services/navigation.spec.ts
@@ -35,10 +35,10 @@
});
beforeEach(angular.mock.inject((
- NavigationService: IXosNavigationService,
+ XosNavigationService: IXosNavigationService,
_$log_: ng.ILogService
) => {
- service = NavigationService;
+ service = XosNavigationService;
$log = _$log_;
spyOn($log, 'warn');
defaultRoutes = [
diff --git a/src/app/core/services/navigation.ts b/src/app/core/services/navigation.ts
index e35c9f8..9869b5a 100644
--- a/src/app/core/services/navigation.ts
+++ b/src/app/core/services/navigation.ts
@@ -17,7 +17,9 @@
add(route: IXosNavigationRoute): void;
}
-export class NavigationService {
+// TODO support 3rd level to group service model under "Services"
+
+export class IXosNavigationService {
static $inject = ['$log', 'StyleConfig'];
private routes: IXosNavigationRoute[];
@@ -34,6 +36,10 @@
label: 'Core',
state: 'xos.core'
},
+ // {
+ // label: 'Service',
+ // state: 'xos.services'
+ // },
];
// adding configuration defined routes
// this.routes = StyleConfig.routes.concat(defaultRoutes).reverse();
@@ -64,16 +70,20 @@
return;
}
-
if (angular.isDefined(route.parent)) {
// route parent should be a state for now
const parentRoute = _.find(this.routes, {state: route.parent});
-
- if (angular.isArray(parentRoute.children)) {
- parentRoute.children.push(route);
+ if (angular.isDefined(parentRoute)) {
+ if (angular.isArray(parentRoute.children)) {
+ parentRoute.children.push(route);
+ }
+ else {
+ parentRoute.children = [route];
+ }
}
else {
- parentRoute.children = [route];
+ this.$log.warn(`[XosNavigation] Parent State (${route.parent}) for state: ${route.state} does not exists`);
+ return;
}
}
else {
diff --git a/src/app/core/services/runtime-states.spec.ts b/src/app/core/services/runtime-states.spec.ts
index 5dd44db..8b9015d 100644
--- a/src/app/core/services/runtime-states.spec.ts
+++ b/src/app/core/services/runtime-states.spec.ts
@@ -2,19 +2,19 @@
import 'angular-mocks';
import 'angular-ui-router';
import {xosCore} from '../index';
-import {IRuntimeStatesService} from './runtime-states';
+import {IXosRuntimeStatesService} from './runtime-states';
-let service: IRuntimeStatesService, $state: ng.ui.IStateService;
+let service: IXosRuntimeStatesService, $state: ng.ui.IStateService;
describe('The Navigation service', () => {
beforeEach(angular.mock.module(xosCore));
beforeEach(angular.mock.inject((
- RuntimeStates: IRuntimeStatesService,
+ XosRuntimeStates: IXosRuntimeStatesService,
_$state_: ng.ui.IStateService
) => {
- service = RuntimeStates;
+ service = XosRuntimeStates;
$state = _$state_;
}));
diff --git a/src/app/core/services/runtime-states.ts b/src/app/core/services/runtime-states.ts
index 652b554..1f495ba 100644
--- a/src/app/core/services/runtime-states.ts
+++ b/src/app/core/services/runtime-states.ts
@@ -1,9 +1,14 @@
-import {IXosState} from '../../../index';
-export interface IRuntimeStatesService {
+export interface IXosState extends angular.ui.IState {
+ data: {
+ model: string
+ };
+};
+
+export interface IXosRuntimeStatesService {
addState(name: string, state: ng.ui.IState): void;
}
-export function RuntimeStates($stateProvider: ng.ui.IStateProvider): ng.IServiceProvider {
+export function XosRuntimeStates($stateProvider: ng.ui.IStateProvider): ng.IServiceProvider {
this.$get = function($state: ng.ui.IStateService) {
return {
addState: function(name: string, state: IXosState) {
diff --git a/src/app/datasources/helpers/model-discoverer.service.ts b/src/app/datasources/helpers/model-discoverer.service.ts
new file mode 100644
index 0000000..deaed33
--- /dev/null
+++ b/src/app/datasources/helpers/model-discoverer.service.ts
@@ -0,0 +1,258 @@
+// TODO test me hard!!!
+
+import * as _ from 'lodash';
+import {IXosModeldefsService, IXosModeldef, IXosModelDefsField, IXosModelDefsRelation} from '../rest/modeldefs.rest';
+import {IXosTableCfg} from '../../core/table/table';
+import {IXosFormCfg} from '../../core/form/form';
+import {IXosNavigationService} from '../../core/services/navigation';
+import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
+import {IXosRuntimeStatesService, IXosState} from '../../core/services/runtime-states';
+import {IXosModelStoreService} from '../stores/model.store';
+
+export interface IXosModel {
+ name: string; // the model name
+ app: string; // the service to wich it belong
+ fields: IXosModelDefsField[];
+ relations?: IXosModelDefsRelation[];
+ backendUrl?: string; // the api endpoint
+ clientUrl?: string; // the view url
+ tableCfg?: IXosTableCfg;
+ formCfg?: IXosFormCfg;
+}
+
+// Service
+export interface IXosModelDiscovererService {
+ discover(): ng.IPromise<boolean>;
+ get(modelName: string): IXosModel;
+}
+
+export class XosModelDiscovererService implements IXosModelDiscovererService {
+ static $inject = [
+ '$log',
+ '$q',
+ 'XosModelDefs',
+ 'ConfigHelpers',
+ 'XosRuntimeStates',
+ 'XosNavigationService',
+ 'XosModelStore'
+ ];
+ private xosModels: IXosModel[] = []; // list of augmented model definitions;
+ private xosServices: string[] = []; // list of loaded services
+
+ constructor (
+ private $log: ng.ILogService,
+ private $q: ng.IQService,
+ private XosModelDefs: IXosModeldefsService,
+ private ConfigHelpers: IXosConfigHelpersService,
+ private XosRuntimeStates: IXosRuntimeStatesService,
+ private XosNavigationService: IXosNavigationService,
+ private XosModelStore: IXosModelStoreService
+ ) {
+ }
+
+ public get(modelName: string): IXosModel|null {
+ return _.find(this.xosModels, m => m.name === modelName);
+ }
+
+ public discover() {
+ const d = this.$q.defer();
+
+ this.XosModelDefs.get()
+ .then((modelsDef: IXosModeldef[]) => {
+
+ const pArray = [];
+ _.forEach(modelsDef, (model: IXosModeldef) => {
+ let p = this.cacheModelEntries(model)
+ .then(model => {
+ return this.addState(model);
+ })
+ .then(model => {
+ return this.addNavItem(model);
+ })
+ .then(model => {
+ return this.getTableCfg(model);
+ })
+ .then(model => {
+ return this.getFormCfg(model);
+ })
+ .then(model => {
+ return this.storeModel(model);
+ })
+ .then(model => {
+ this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
+ return this.$q.resolve();
+ })
+ .catch(err => {
+ this.$log.error(`[XosModelDiscovererService] Model ${model.name} NOT stored`);
+ // NOTE why this does not resolve?????
+ // return this.$q.resolve();
+ return this.$q.reject();
+ });
+ pArray.push(p);
+ });
+ this.$q.all(pArray)
+ .then(() => {
+ d.resolve(true);
+ this.$log.info('[XosModelDiscovererService] All models loaded!');
+ })
+ .catch(() => {
+ d.resolve(false);
+ });
+ });
+ return d.promise;
+ }
+
+ private serviceNameFromAppName(appName: string): string {
+ return appName.replace('services.', '');
+ }
+
+ private stateNameFromModel(model: IXosModel): string {
+ return `xos.${this.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
+ }
+
+ private getParentStateFromModel(model: IXosModel): string {
+ let parentState: string;
+ if (model.app === 'core') {
+ parentState = 'xos.core';
+ }
+ else {
+ const serviceName = this.addService(model);
+ parentState = `xos.${serviceName}`;
+ }
+ return parentState;
+ }
+
+ private getApiUrlFromModel(model: IXosModel): string {
+ if (model.app === 'core') {
+ return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+ }
+ else {
+ const serviceName = this.serviceNameFromAppName(model.app);
+ return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+ }
+ }
+
+ // add a service state and navigation item if it is not already there
+ private addService(model: IXosModel): string {
+ const serviceName: string = this.serviceNameFromAppName(model.app);
+ if (!_.find(this.xosServices, n => n === serviceName)) {
+ const serviceState = {
+ url: serviceName,
+ parent: 'xos',
+ abstract: true,
+ template: '<div ui-view></div>'
+ };
+ this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
+
+ this.XosNavigationService.add({
+ label: this.ConfigHelpers.toLabel(serviceName),
+ state: `xos.${serviceName}`,
+ });
+ this.xosServices.push(serviceName);
+ }
+ return serviceName;
+ }
+
+ private addState(model: IXosModel): ng.IPromise<IXosModel> {
+ const d = this.$q.defer();
+ const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
+ const state: IXosState = {
+ parent: this.getParentStateFromModel(model),
+ url: clientUrl,
+ params: {
+ id: null
+ },
+ data: {
+ model: model.name
+ },
+ component: 'xosCrud',
+ };
+ this.XosRuntimeStates.addState(
+ this.stateNameFromModel(model),
+ state
+ );
+
+ // extend model
+ model.clientUrl = `${this.serviceNameFromAppName(model.app)}${clientUrl}`;
+
+ d.resolve(model);
+ return d.promise;
+ }
+
+ private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
+ const d = this.$q.defer();
+
+ const stateName = this.stateNameFromModel(model);
+
+ const parentState: string = this.getParentStateFromModel(model);
+
+ this.XosNavigationService.add({
+ label: this.ConfigHelpers.pluralize(model.name),
+ state: stateName,
+ parent: parentState
+ });
+
+ d.resolve(model);
+
+ return d.promise;
+ }
+
+ private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
+ const d = this.$q.defer();
+
+ let called = false;
+ const apiUrl = this.getApiUrlFromModel(model);
+ this.XosModelStore.query(model.name, apiUrl)
+ .subscribe(
+ () => {
+ // skipping the first response as the observable gets created as an empty array
+ if (called) {
+ return d.resolve(model);
+ }
+ called = true;
+ },
+ err => {
+ d.reject(err);
+ }
+ );
+
+ return d.promise;
+ }
+
+ private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+ const d = this.$q.defer();
+
+ const stateUrl = this.stateNameFromModel(model);
+
+ model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
+
+ d.resolve(model);
+
+ return d.promise;
+ }
+
+ private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+ const d = this.$q.defer();
+
+ model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
+
+ d.resolve(model);
+
+ return d.promise;
+ }
+
+ private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
+
+ const d = this.$q.defer();
+
+ if (!_.find(this.xosModels, m => m.name === model.name)) {
+ this.xosModels.push(model);
+ }
+
+ d.resolve(model);
+
+ return d.promise;
+ }
+}
diff --git a/src/app/datasources/helpers/search.service.ts b/src/app/datasources/helpers/search.service.ts
index 5565b2c..8cee4d1 100644
--- a/src/app/datasources/helpers/search.service.ts
+++ b/src/app/datasources/helpers/search.service.ts
@@ -1,8 +1,8 @@
import * as _ from 'lodash';
import {IXosNavigationService} from '../../core/services/navigation';
-import {IXosState} from '../../../index';
import {IXosModelStoreService} from '../stores/model.store';
import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
+import {IXosState} from '../../core/services/runtime-states';
export interface IXosSearchResult {
label: string;
@@ -15,16 +15,18 @@
}
export class SearchService {
- static $inject = ['$rootScope', 'NavigationService', 'ModelStore', 'ConfigHelpers'];
+ static $inject = ['$rootScope', '$log', 'XosNavigationService', 'XosModelStore', 'ConfigHelpers'];
private states: IXosState[];
constructor (
private $rootScope: ng.IScope,
+ private $log: ng.ILogService,
private NavigationService: IXosNavigationService,
- private ModelStore: IXosModelStoreService,
+ private XosModelStore: IXosModelStoreService,
private ConfigHelpers: IXosConfigHelpersService
) {
this.$rootScope.$on('xos.core.modelSetup', () => {
+ this.$log.info(`[XosSearchService] Loading views`);
this.states = this.NavigationService.query().reduce((list, state) => {
// if it does not have child (otherwise it is abstract)
if (!state.children || state.children.length === 0) {
@@ -39,18 +41,21 @@
return list;
}, []);
this.states = _.uniqBy(this.states, 'state');
+ this.$log.debug(`[XosSearchService] Views Loaded: `, this.states);
});
}
public search(query: string): IXosSearchResult[] {
+ this.$log.info(`[XosSearchService] Searching for: ${query}`);
const routes: IXosSearchResult[] = _.filter(this.states, s => {
return s.label.toLowerCase().indexOf(query) > -1;
}).map(r => {
r.type = 'View';
return r;
});
-
- const models = _.map(this.ModelStore.search(query), m => {
+ // TODO XosModelStore.search throws an error,
+ // probably there is something wrong saved in the cache!!
+ const models = _.map(this.XosModelStore.search(query), m => {
return {
label: m.humanReadableName ? m.humanReadableName : m.name,
state: this.ConfigHelpers.stateWithParamsForJs(m.modelName, m),
diff --git a/src/app/datasources/helpers/store.helpers.spec.ts b/src/app/datasources/helpers/store.helpers.spec.ts
index 2cb1eaf..9f4370c 100644
--- a/src/app/datasources/helpers/store.helpers.spec.ts
+++ b/src/app/datasources/helpers/store.helpers.spec.ts
@@ -44,11 +44,9 @@
it('should convert a core model name in an URL', () => {
expect(service.urlFromCoreModel('Slice')).toBe('/core/slices');
expect(service.urlFromCoreModel('Xos')).toBe('/core/xoses');
-
- // handling exceptions
- expect(service.urlFromCoreModel('SiteRole')).toBe('/core/site_roles');
- expect(service.urlFromCoreModel('SliceRole')).toBe('/core/slice_roles');
- expect(service.urlFromCoreModel('SlicePrivilege')).toBe('/core/slice_privileges');
+ expect(service.urlFromCoreModel('SiteRole')).toBe('/core/siteroles');
+ expect(service.urlFromCoreModel('SliceRole')).toBe('/core/sliceroles');
+ expect(service.urlFromCoreModel('SlicePrivilege')).toBe('/core/sliceprivileges');
});
describe('when updating a collection', () => {
diff --git a/src/app/datasources/helpers/store.helpers.ts b/src/app/datasources/helpers/store.helpers.ts
index 7792590..c26e235 100644
--- a/src/app/datasources/helpers/store.helpers.ts
+++ b/src/app/datasources/helpers/store.helpers.ts
@@ -9,7 +9,7 @@
updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any>;
}
-export class StoreHelpers {
+export class StoreHelpers implements IStoreHelpersService {
static $inject = ['ModelRest'];
constructor (
@@ -18,15 +18,7 @@
}
public urlFromCoreModel(name: string): string {
- switch (name) {
- // FIXME handling exceptions, understand why these 3 endpoints are autogenerated with an _f
- case 'SiteRole':
- case 'SliceRole':
- case 'SlicePrivilege':
- return `/core/${pluralize(name.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join('_'))}`;
- default:
- return `/core/${pluralize(name.toLowerCase())}`;
- }
+ return `/core/${pluralize(name.toLowerCase())}`;
}
public updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any> {
diff --git a/src/app/datasources/index.ts b/src/app/datasources/index.ts
index 04c58db..80d348a 100644
--- a/src/app/datasources/index.ts
+++ b/src/app/datasources/index.ts
@@ -1,22 +1,27 @@
import {ModelRest} from './rest/model.rest';
import {AuthService} from './rest/auth.rest';
import {WebSocketEvent} from './websocket/global';
-import {ModelStore} from './stores/model.store';
+import {XosModelStore} from './stores/model.store';
import {StoreHelpers} from './helpers/store.helpers';
import {SynchronizerStore} from './stores/synchronizer.store';
-import {ModeldefsService} from './rest/modeldefs.rest';
+import {XosModeldefsService} from './rest/modeldefs.rest';
import {xosCore} from '../core/index';
import {SearchService} from './helpers/search.service';
+import {XosModelDiscovererService} from './helpers/model-discoverer.service';
export const xosDataSources = 'xosDataSources';
angular
- .module('xosDataSources', ['ngCookies', 'ngResource', xosCore])
+ .module(xosDataSources, ['ngCookies', 'ngResource', xosCore])
.service('ModelRest', ModelRest)
.service('AuthService', AuthService)
.service('WebSocket', WebSocketEvent)
.service('StoreHelpers', StoreHelpers)
.service('SynchronizerStore', SynchronizerStore)
- .service('ModelStore', ModelStore)
- .service('ModelDefs', ModeldefsService)
+ .service('XosModelStore', XosModelStore)
+ .service('XosModelDefs', XosModeldefsService)
.service('SearchService', SearchService);
+
+angular
+ .module(xosDataSources)
+ .service('XosModelDiscoverer', XosModelDiscovererService);
diff --git a/src/app/datasources/rest/auth.rest.ts b/src/app/datasources/rest/auth.rest.ts
index 2979bf1..edc2a65 100644
--- a/src/app/datasources/rest/auth.rest.ts
+++ b/src/app/datasources/rest/auth.rest.ts
@@ -7,9 +7,9 @@
export interface IAuthResponseData extends IHttpPromiseCallbackArg<any> {
data: {
- user: string;
- xoscsrftoken: string;
- xossessionid: string;
+ // user: string;
+ // xoscsrftoken: string;
+ sessionid: string;
};
}
@@ -37,12 +37,12 @@
public login(data: IAuthRequestData): Promise<any> {
const d = this.$q.defer();
- this.$http.post(`${this.AppConfig.apiEndpoint}/utility/login/`, data)
+ this.$http.post(`${this.AppConfig.apiEndpoint}/utility/login`, data)
.then((res: IAuthResponseData) => {
- this.$cookies.put('xoscsrftoken', res.data.xoscsrftoken, {path: '/'});
- this.$cookies.put('xossessionid', res.data.xossessionid, {path: '/'});
- this.$cookies.put('xosuser', res.data.user, {path: '/'});
- res.data.user = JSON.parse(res.data.user);
+ // this.$cookies.put('xoscsrftoken', res.data.xoscsrftoken, {path: '/'});
+ this.$cookies.put('sessionid', res.data.sessionid, {path: '/'});
+ // this.$cookies.put('xosuser', res.data.user, {path: '/'});
+ // res.data.user = JSON.parse(res.data.user);
d.resolve(res.data);
})
.catch(e => {
@@ -53,9 +53,9 @@
public logout(): Promise<any> {
const d = this.$q.defer();
- this.$http.post(`${this.AppConfig.apiEndpoint}/utility/logout/`, {
- xoscsrftoken: this.$cookies.get('xoscsrftoken'),
- xossessionid: this.$cookies.get('xossessionid')
+ this.$http.post(`${this.AppConfig.apiEndpoint}/utility/logout`, {
+ // xoscsrftoken: this.$cookies.get('xoscsrftoken'),
+ // sessionid: this.$cookies.get('sessionid')
})
.then(() => {
this.clearUser();
@@ -68,9 +68,9 @@
}
public clearUser(): void {
- this.$cookies.remove('xoscsrftoken', {path: '/'});
- this.$cookies.remove('xossessionid', {path: '/'});
- this.$cookies.remove('xosuser', {path: '/'});
+ // this.$cookies.remove('xoscsrftoken', {path: '/'});
+ this.$cookies.remove('sessionid', {path: '/'});
+ // this.$cookies.remove('xosuser', {path: '/'});
}
public getUser(): IXosUser {
@@ -82,8 +82,8 @@
}
public isAuthenticated(): boolean {
- const token = this.$cookies.get('xoscsrftoken');
- const session = this.$cookies.get('xossessionid');
- return angular.isDefined(token) && angular.isDefined(session);
+ // const token = this.$cookies.get('xoscsrftoken');
+ const session = this.$cookies.get('sessionid');
+ return angular.isDefined(session);
}
}
diff --git a/src/app/datasources/rest/model.rest.ts b/src/app/datasources/rest/model.rest.ts
index 12590af..4aa862c 100644
--- a/src/app/datasources/rest/model.rest.ts
+++ b/src/app/datasources/rest/model.rest.ts
@@ -16,7 +16,15 @@
public getResource(url: string): ng.resource.IResourceClass<ng.resource.IResource<any>> {
const resource: angular.resource.IResourceClass<any> = this.$resource(`${this.AppConfig.apiEndpoint}${url}/:id/`, {id: '@id'}, {
- update: { method: 'PUT' }
+ update: { method: 'PUT' },
+ query: {
+ method: 'GET',
+ isArray: true,
+ transformResponse: (res) => {
+ // FIXME chameleon return everything inside "items"
+ return res.items ? res.items : res;
+ }
+ }
});
resource.prototype.$save = function() {
diff --git a/src/app/datasources/rest/modeldefs.rest.spec.ts b/src/app/datasources/rest/modeldefs.rest.spec.ts
index 9dc4025..677463b 100644
--- a/src/app/datasources/rest/modeldefs.rest.spec.ts
+++ b/src/app/datasources/rest/modeldefs.rest.spec.ts
@@ -3,9 +3,9 @@
import 'angular-resource';
import 'angular-cookies';
import {xosDataSources} from '../index';
-import {IModeldefsService} from './modeldefs.rest';
+import {IXosModeldefsService} from './modeldefs.rest';
-let service: IModeldefsService;
+let service: IXosModeldefsService;
let httpBackend: ng.IHttpBackendService;
let $scope;
@@ -14,7 +14,7 @@
websocketClient: 'http://xos-test:3000'
};
-describe('The ModelDefs service', () => {
+describe('The XosModelDefs service', () => {
beforeEach(angular.mock.module(xosDataSources));
@@ -26,14 +26,13 @@
angular.mock.module(xosDataSources);
});
-
beforeEach(angular.mock.inject((
- ModelDefs: IModeldefsService,
+ XosModelDefs: IXosModeldefsService,
$httpBackend: ng.IHttpBackendService,
_$resource_: ng.resource.IResourceService,
_$rootScope_: ng.IRootScopeService
) => {
- service = ModelDefs;
+ service = XosModelDefs;
httpBackend = $httpBackend;
$scope = _$rootScope_;
}));
diff --git a/src/app/datasources/rest/modeldefs.rest.ts b/src/app/datasources/rest/modeldefs.rest.ts
index d927f8c..fdb4b99 100644
--- a/src/app/datasources/rest/modeldefs.rest.ts
+++ b/src/app/datasources/rest/modeldefs.rest.ts
@@ -1,17 +1,35 @@
import {IXosModelDefsField} from '../../core/services/helpers/config.helpers';
import {IXosAppConfig} from '../../../index';
-export interface IModeldef {
- fields: IXosModelDefsField[];
- relations?: string[];
+// Models interfaces
+export interface IXosModelDefsField {
name: string;
+ type: string;
+ validators?: any;
+ hint?: string;
+ relation?: {
+ model: string;
+ type: string;
+ };
}
-export interface IModeldefsService {
- get(): Promise<IModeldef[]>;
+export interface IXosModelDefsRelation {
+ model: string; // model name
+ type: string; // relation type
}
-export class ModeldefsService {
+export interface IXosModeldef {
+ fields: IXosModelDefsField[];
+ relations?: IXosModelDefsRelation[];
+ name: string;
+ app: string;
+}
+
+export interface IXosModeldefsService {
+ get(): Promise<IXosModeldef[]>;
+}
+
+export class XosModeldefsService implements IXosModeldefsService {
static $inject = ['$http', '$q', 'AppConfig'];
@@ -24,9 +42,9 @@
public get(): Promise<any> {
const d = this.$q.defer();
- this.$http.get(`${this.AppConfig.apiEndpoint}/utility/modeldefs/`)
- .then((res) => {
- d.resolve(res.data);
+ this.$http.get(`${this.AppConfig.apiEndpoint}/modeldefs`)
+ .then((res: any) => {
+ d.resolve(res.data.items);
})
.catch(e => {
d.reject(e);
diff --git a/src/app/datasources/stores/model.store.spec.ts b/src/app/datasources/stores/model.store.spec.ts
index 7173658..533a53d 100644
--- a/src/app/datasources/stores/model.store.spec.ts
+++ b/src/app/datasources/stores/model.store.spec.ts
@@ -1,7 +1,7 @@
import * as angular from 'angular';
import 'angular-mocks';
import 'angular-resource';
-import {IXosModelStoreService, ModelStore} from './model.store';
+import {IXosModelStoreService, XosModelStore} from './model.store';
import {Subject} from 'rxjs';
import {IWSEvent} from '../websocket/global';
import {StoreHelpers} from '../helpers/store.helpers';
@@ -46,7 +46,7 @@
.service('WebSocket', MockWs)
.service('StoreHelpers', StoreHelpers) // TODO mock
.service('ModelRest', ModelRest) // TODO mock
- .service('ModelStore', ModelStore)
+ .service('XosModelStore', XosModelStore)
.service('ConfigHelpers', ConfigHelpers) // TODO mock
.service('AuthService', AuthService)
.constant('AppConfig', MockAppCfg);
@@ -55,12 +55,12 @@
});
beforeEach(angular.mock.inject((
- ModelStore: IXosModelStoreService,
+ XosModelStore: IXosModelStoreService,
$httpBackend: ng.IHttpBackendService,
_$rootScope_: ng.IRootScopeService,
_WebSocket_: any
) => {
- service = ModelStore;
+ service = XosModelStore;
httpBackend = $httpBackend;
$scope = _$rootScope_;
WebSocket = _WebSocket_;
diff --git a/src/app/datasources/stores/model.store.ts b/src/app/datasources/stores/model.store.ts
index 291e7c0..4958015 100644
--- a/src/app/datasources/stores/model.store.ts
+++ b/src/app/datasources/stores/model.store.ts
@@ -6,11 +6,11 @@
import {IStoreHelpersService} from '../helpers/store.helpers';
export interface IXosModelStoreService {
- query(model: string): Observable<any>;
+ query(model: string, apiUrl?: string): Observable<any>;
search(modelName: string): any[];
}
-export class ModelStore implements IXosModelStoreService {
+export class XosModelStore implements IXosModelStoreService {
static $inject = ['$log', 'WebSocket', 'StoreHelpers', 'ModelRest'];
private _collections: any; // NOTE contains a map of {model: BehaviourSubject}
constructor(
@@ -22,59 +22,76 @@
this._collections = {};
}
- public query(model: string): Observable<any> {
+ public query(modelName: string, apiUrl: string): Observable<any> {
// if there isn't already an observable for that item
- if (!this._collections[model]) {
- this._collections[model] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
- this.loadInitialData(model);
+ if (!this._collections[modelName]) {
+ this._collections[modelName] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
+ this.loadInitialData(modelName, apiUrl);
}
this.webSocket.list()
- .filter((e: IWSEvent) => e.model === model)
+ .filter((e: IWSEvent) => e.model === modelName)
.subscribe(
(event: IWSEvent) => {
- this.storeHelpers.updateCollection(event, this._collections[model]);
+ this.storeHelpers.updateCollection(event, this._collections[modelName]);
},
err => console.error
);
- return this._collections[model].asObservable();
+ return this._collections[modelName].asObservable();
}
public search(modelName: string): any[] {
- return _.reduce(Object.keys(this._collections), (results, k) => {
- // console.log(k, this._collections[k].value)
- const partialRes = _.filter(this._collections[k].value, i => {
- if (i.humanReadableName) {
- return i.humanReadableName.toLowerCase().indexOf(modelName) > -1;
+ try {
+ const res = _.reduce(Object.keys(this._collections), (results, k) => {
+ let partialRes;
+ // NOTE wrapped in a try catch as some subject may be errored, due to not available REST endpoint
+ try {
+ partialRes = _.filter(this._collections[k].value, i => {
+ if (i && i.humanReadableName) {
+ return i.humanReadableName.toLowerCase().indexOf(modelName) > -1;
+ }
+ else if (i && i.name) {
+ return i.name.toLowerCase().indexOf(modelName) > -1;
+ }
+ return false;
+ });
+ } catch (e) {
+ partialRes = [];
}
- else if (i.name) {
- return i.name.toLowerCase().indexOf(modelName) > -1;
- }
- return false;
- })
- .map(m => {
+ partialRes.map(m => {
m.modelName = k;
return m;
});
- return results.concat(partialRes);
- }, []);
+ return results.concat(partialRes);
+ }, []);
+ return res;
+ } catch (e) {
+ return [];
+ }
}
public get(model: string, id: number) {
// TODO implement a get method
}
- private loadInitialData(model: string) {
- // NOTE check what is the correct pattern to pluralize this
- const endpoint = this.storeHelpers.urlFromCoreModel(model);
- this.ModelRest.getResource(endpoint).query().$promise
+ private loadInitialData(model: string, apiUrl?: string) {
+ // TODO provide alway the apiUrl togheter with the query() params
+ if (!angular.isDefined(apiUrl)) {
+ // NOTE check what is the correct pattern to pluralize this
+ apiUrl = this.storeHelpers.urlFromCoreModel(model);
+ }
+ this.ModelRest.getResource(apiUrl).query().$promise
.then(
res => {
this._collections[model].next(res);
})
.catch(
- err => this.$log.log(`Error retrieving ${model}`, err)
+ // TODO understand how to send an error to an observable
+ err => {
+ this._collections[model].error(err);
+ // this.$log.log(`Error retrieving ${model}`, err);
+ }
);
}
}
diff --git a/src/app/extender/services/onboard.service.spec.ts b/src/app/extender/services/onboard.service.spec.ts
index 375aadb..17a7543 100644
--- a/src/app/extender/services/onboard.service.spec.ts
+++ b/src/app/extender/services/onboard.service.spec.ts
@@ -50,7 +50,7 @@
.module('XosOnboarder', [])
.value('WebSocket', MockWs)
.value('$ocLazyLoad', MockLoad)
- .value('ModelStore', MockModelStore)
+ .value('XosModelStore', MockModelStore)
.service('XosOnboarder', XosOnboarder);
angular.mock.module('XosOnboarder');
diff --git a/src/app/extender/services/onboard.service.ts b/src/app/extender/services/onboard.service.ts
index 402557c..2f85028 100644
--- a/src/app/extender/services/onboard.service.ts
+++ b/src/app/extender/services/onboard.service.ts
@@ -8,7 +8,7 @@
}
export class XosOnboarder implements IXosOnboarder {
- static $inject = ['$timeout', '$log', '$q', 'WebSocket', '$ocLazyLoad', 'ModelStore'];
+ static $inject = ['$timeout', '$log', '$q', 'WebSocket', '$ocLazyLoad', 'XosModelStore'];
constructor(
private $timeout: ng.ITimeoutService,
@@ -16,7 +16,7 @@
private $q: ng.IQService,
private webSocket: IWSEventService,
private $ocLazyLoad: any, // TODO add definition
- private ModelStore: IXosModelStoreService
+ private XosModelStore: IXosModelStoreService
) {
this.$log.info('[XosOnboarder] Setup');
@@ -45,7 +45,7 @@
// Load previously onboarded app (containers are already running, so we don't need to wait)
let componentsLoaded = false;
- const ComponentObservable: Observable<any> = this.ModelStore.query('XOSComponent');
+ const ComponentObservable: Observable<any> = this.XosModelStore.query('XOSComponent');
ComponentObservable.subscribe(
(component) => {
if (componentsLoaded) {
diff --git a/src/app/views/crud/crud.html b/src/app/views/crud/crud.html
index 8d457e2..adb0500 100644
--- a/src/app/views/crud/crud.html
+++ b/src/app/views/crud/crud.html
@@ -26,11 +26,11 @@
</div>
<div class="col-lg-8 text-right">
<div class="btn-group">
- <a ng-if="vm.list" ng-repeat="r in vm.related" href="#/core/{{r.toLowerCase()}}s/" class="btn btn-default">
- {{r}}
+ <a ng-if="vm.list" ng-repeat="r in vm.related" href="#/core/{{r.model.toLowerCase()}}s/" class="btn btn-default">
+ {{r.model}}
</a>
- <a ng-if="!vm.list && vm.getRelatedItem(r, vm.model)" ng-repeat="r in vm.related" href="#/core/{{r.toLowerCase()}}s/{{vm.getRelatedItem(r, vm.model)}}" class="btn btn-default">
- {{r}}
+ <a ng-if="!vm.list && vm.getRelatedItem(r, vm.model)" ng-repeat="r in vm.related" href="#/core/{{r.model.toLowerCase()}}s/{{vm.getRelatedItem(r, vm.model)}}" class="btn btn-default">
+ {{r.model}}
</a>
</div>
</div>
diff --git a/src/app/views/crud/crud.ts b/src/app/views/crud/crud.ts
index 846a840..6b30edc 100644
--- a/src/app/views/crud/crud.ts
+++ b/src/app/views/crud/crud.ts
@@ -2,23 +2,38 @@
import {IXosModelStoreService} from '../../datasources/stores/model.store';
import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
import * as _ from 'lodash';
-import {IXosFormConfig} from '../../core/form/form';
+import {IXosFormCfg} from '../../core/form/form';
import {IXosResourceService} from '../../datasources/rest/model.rest';
import {IStoreHelpersService} from '../../datasources/helpers/store.helpers';
+import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
+
export interface IXosCrudData {
model: string;
- related: string[];
+ related: IXosModelRelation[];
xosTableCfg: IXosTableCfg;
- xosFormCfg: IXosFormConfig;
+ xosFormCfg: IXosFormCfg;
+}
+
+export interface IXosModelRelation {
+ model: string;
+ type: string;
}
class CrudController {
- static $inject = ['$scope', '$state', '$stateParams', 'ModelStore', 'ConfigHelpers', 'ModelRest', 'StoreHelpers'];
+ static $inject = [
+ '$scope',
+ '$state',
+ '$stateParams',
+ 'XosModelStore',
+ 'ConfigHelpers',
+ 'ModelRest',
+ 'StoreHelpers',
+ 'XosModelDiscoverer'
+ ];
- public data: IXosCrudData;
+ public data: {model: string};
public tableCfg: IXosTableCfg;
public formCfg: any;
- public stateName: string;
public baseUrl: string;
public list: boolean;
public title: string;
@@ -33,19 +48,23 @@
private store: IXosModelStoreService,
private ConfigHelpers: IXosConfigHelpersService,
private ModelRest: IXosResourceService,
- private StoreHelpers: IStoreHelpersService
+ private StoreHelpers: IStoreHelpersService,
+ private XosModelDiscovererService: IXosModelDiscovererService
) {
this.data = this.$state.current.data;
- this.tableCfg = this.data.xosTableCfg;
+ this.model = this.XosModelDiscovererService.get(this.data.model);
this.title = this.ConfigHelpers.pluralize(this.data.model);
this.list = true;
- this.stateName = $state.current.name;
- this.baseUrl = '#/core' + $state.current.url.toString().replace(':id?', '');
+
+ // TODO get the proper URL from model discoverer
+ this.baseUrl = '#/' + this.model.clientUrl.replace(':id?', '');
+
this.related = $state.current.data.related;
- this.formCfg = $state.current.data.xosFormCfg;
+ this.tableCfg = this.model.tableCfg;
+ this.formCfg = this.model.formCfg;
this.store.query(this.data.model)
.subscribe(
@@ -70,6 +89,7 @@
// if it is the create page
if ($stateParams['id'] === 'add') {
// generate a resource for an empty model
+ // TODO get the proper URL from model discoverer
const endpoint = this.StoreHelpers.urlFromCoreModel(this.data.model);
const resource = this.ModelRest.getResource(endpoint);
this.model = new resource({});
@@ -77,9 +97,9 @@
}
}
- public getRelatedItem(relation: string, item: any): number {
- if (item && angular.isDefined(item[relation.toLowerCase()])) {
- return item[relation.toLowerCase()];
+ public getRelatedItem(relation: IXosModelRelation, item: any): number {
+ if (item && angular.isDefined(item[`${relation.model.toLowerCase()}_id`])) {
+ return item[`${relation.model.toLowerCase()}_id`];
}
return 0;
}
diff --git a/src/app/views/dashboard/dashboard.ts b/src/app/views/dashboard/dashboard.ts
index 00fc1b4..21fbbde 100644
--- a/src/app/views/dashboard/dashboard.ts
+++ b/src/app/views/dashboard/dashboard.ts
@@ -1,7 +1,7 @@
import {IXosModelStoreService} from '../../datasources/stores/model.store';
import {IXosAuthService} from '../../datasources/rest/auth.rest';
class DashboardController {
- static $inject = ['$scope', '$state', 'ModelStore', 'AuthService'];
+ static $inject = ['$scope', '$state', 'XosModelStore', 'AuthService'];
public nodes: number;
public slices: number;
diff --git a/src/decorators.ts b/src/decorators.ts
index 6f2628d..c4543e2 100644
--- a/src/decorators.ts
+++ b/src/decorators.ts
@@ -1,7 +1,9 @@
export default function XosLogDecorator($provide: ng.auto.IProvideService) {
$provide.decorator('$log', function($delegate: any) {
const isLogEnabled = () => {
- return window.location.href.indexOf('debug=true') >= 0;
+ // NOTE to enable debug, in the broser console set: localStorage.debug = 'true'
+ // NOTE to disable debug, in the broser console set: localStorage.debug = 'false'
+ return window.localStorage.getItem('debug') === 'true';
};
// Save the original $log.debug()
let logFn = $delegate.log;
diff --git a/src/index.ts b/src/index.ts
index 74b0c04..de9d9bf 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -20,18 +20,13 @@
interceptorConfig, userStatusInterceptor, CredentialsInterceptor,
NoHyperlinksInterceptor
} from './interceptors';
-import {IXosCrudData} from './app/views/crud/crud';
import {IXosPageTitleService} from './app/core/services/page-title';
import {IXosAuthService} from './app/datasources/rest/auth.rest';
-import {IXosModelSetupService} from './app/core/services/helpers/model-setup.helpers';
import {IXosNavigationRoute} from './app/core/services/navigation';
import XosLogDecorator from './decorators';
import {xosExtender} from './app/extender/index';
import {IXosKeyboardShortcutService} from './app/core/services/keyboard-shortcut';
-
-export interface IXosState extends angular.ui.IState {
- data: IXosCrudData;
-};
+import {IXosModelDiscovererService} from './app/datasources/helpers/model-discoverer.service';
export interface IXosAppConfig {
apiEndpoint: string;
@@ -78,9 +73,10 @@
.run((
$rootScope: ng.IRootScopeService,
$transitions: any,
+ $log: ng.ILogService,
$location: ng.ILocationService,
$state: ng.ui.IStateService,
- ModelSetup: IXosModelSetupService,
+ XosModelDiscoverer: IXosModelDiscovererService,
AuthService: IXosAuthService,
XosKeyboardShortcut: IXosKeyboardShortcutService,
toastr: ng.toastr.IToastrService,
@@ -89,7 +85,7 @@
// check the user login
$transitions.onSuccess({ to: '**' }, (transtion) => {
- if (!AuthService.getUser()) {
+ if (!AuthService.isAuthenticated()) {
$state.go('login');
}
});
@@ -112,9 +108,17 @@
const lastQueryString = $location.search();
// if the user is authenticated
- if (AuthService.getUser()) {
- ModelSetup.setup()
- .then(() => {
+ if (AuthService.isAuthenticated()) {
+ // ModelSetup.setup()
+ XosModelDiscoverer.discover()
+ .then((res) => {
+ if (res) {
+ $log.info('[XOS] All models loaded');
+ }
+ else {
+ $log.info('[XOS] Failed to load some models, moving on.');
+ }
+
// after setting up dynamic routes, redirect to previous state
$location.path(lastRoute).search(lastQueryString);
$rootScope.$emit('xos.core.modelSetup');
@@ -124,5 +128,21 @@
// register keyboard shortcut
XosKeyboardShortcut.setup();
+ XosKeyboardShortcut.registerKeyBinding({
+ key: 'd',
+ // modifiers: ['Command'],
+ cb: () => {
+ if (window.localStorage.getItem('debug') === 'true') {
+ $log.info(`[XosKeyboardShortcut] Disabling debug`);
+ window.localStorage.setItem('debug', 'false');
+ }
+ else {
+ window.localStorage.setItem('debug', 'true');
+ $log.info(`[XosKeyboardShortcut] Enabling debug`);
+ }
+ },
+ description: 'Toggle debug messages in browser console'
+ }, 'global');
+
});
diff --git a/src/interceptors.ts b/src/interceptors.ts
index 9e3ba65..1ba6423 100644
--- a/src/interceptors.ts
+++ b/src/interceptors.ts
@@ -6,16 +6,13 @@
$httpProvider.interceptors.push('UserStatusInterceptor');
$httpProvider.interceptors.push('CredentialsInterceptor');
$httpProvider.interceptors.push('NoHyperlinksInterceptor');
- $resourceProvider.defaults.stripTrailingSlashes = false;
}
export function userStatusInterceptor($state: angular.ui.IStateService, $cookies: ng.cookies.ICookiesService) {
const checkLogin = (res) => {
if (res.status === 401 || res.status === -1) {
- $cookies.remove('xoscsrftoken', {path: '/'});
- $cookies.remove('xossessionid', {path: '/'});
- $cookies.remove('xosuser', {path: '/'});
+ $cookies.remove('sessionid', {path: '/'});
$state.go('login');
}
return res;
@@ -30,12 +27,11 @@
export function CredentialsInterceptor($cookies: angular.cookies.ICookiesService) {
return {
request: (req) => {
- if (!$cookies.get('xoscsrftoken') || !$cookies.get('xossessionid')) {
+ if (!$cookies.get('sessionid')) {
return req;
}
- // req.headers['X-CSRFToken'] = $cookies.get('xoscsrftoken');
- req.headers['x-csrftoken'] = $cookies.get('xoscsrftoken');
- req.headers['x-sessionid'] = $cookies.get('xossessionid');
+ req.headers['x-sessionid'] = $cookies.get('sessionid');
+ req.headers['x-xossession'] = $cookies.get('sessionid');
return req;
}
};
@@ -44,15 +40,26 @@
export function NoHyperlinksInterceptor() {
return {
request: (req) => {
- if (
- req.url.indexOf('.html') === -1
- // && req.url.indexOf('login') === -1
- // && req.url.indexOf('logout') === -1
- ) {
- // NOTE this may fail if there are already query params
- req.url += '?no_hyperlinks=1';
+ if (req.url.indexOf('.html') === -1) {
+ // NOTE force content type to be JSON
+ req.headers['Content-Type'] = 'application/json';
}
return req;
+ },
+ response: (res) => {
+ try {
+ // NOTE convert res.data from string to JSON
+ res.data = JSON.parse(res.data);
+ try {
+ // NOTE chameleon return everything inside an "items" field
+ res.data = res.data.items;
+ } catch (_e) {
+ res.data = res.data;
+ }
+ } catch (e) {
+ res.data = res.data;
+ }
+ return res;
}
};
}
diff --git a/src/routes.ts b/src/routes.ts
index 3e9274e..8685ce7 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -24,11 +24,6 @@
url: 'core',
parent: 'xos',
abstract: true,
- template: '<div ui-view=></div>'
- })
- .state('test', {
- url: '/test/:id?',
- parent: 'xos.core',
- template: '<h1>Child</h1>'
+ template: '<div ui-view></div>'
});
}