Merge "Fixing GUI docs"
diff --git a/src/app/core/confirm/confirm.service.spec.ts b/src/app/core/confirm/confirm.service.spec.ts
index 84a61de..d05899d 100644
--- a/src/app/core/confirm/confirm.service.spec.ts
+++ b/src/app/core/confirm/confirm.service.spec.ts
@@ -23,6 +23,8 @@
let service: IXosConfirm;
let modal;
let modalInstance;
+let q;
+let scope;
describe('The XosConfirm service', () => {
@@ -33,10 +35,14 @@
angular.mock.inject((
XosConfirm: IXosConfirm,
- $uibModal: any
+ $uibModal: any,
+ $q: ng.IQService,
+ $rootScope: ng.IScope
) => {
service = XosConfirm;
modal = $uibModal;
+ q = $q;
+ scope = $rootScope;
});
});
@@ -54,19 +60,31 @@
}]
};
- it('should open a modal', () => {
- spyOn(modal, 'open');
+ it('should open the modal', () => {
+ spyOn(modal, 'open').and.returnValue('fake');
modalInstance = service.open(test1);
expect(modal.open).toHaveBeenCalled();
+ expect(modalInstance).toEqual('fake');
+ expect(service.modalInstance).toEqual('fake');
});
- });
- // describe('the close method', () => {
- //
- // });
- //
- // describe('the dismiss method', () => {
- //
- // });
+ it('should close the modal', (done) => {
+ const p = q.defer();
+ const cb = jasmine.createSpy('cb').and.returnValue(p.promise);
+ service.modalInstance = {
+ close: jasmine.createSpy('close')
+ };
+
+ service.close(cb);
+ expect(cb).toHaveBeenCalled();
+ p.resolve();
+ scope.$apply();
+ expect(service.modalInstance.close).toHaveBeenCalled();
+ done();
+ });
+
+
+
+ });
});
diff --git a/src/app/core/confirm/confirm.service.ts b/src/app/core/confirm/confirm.service.ts
index a8e802e..39336e3 100644
--- a/src/app/core/confirm/confirm.service.ts
+++ b/src/app/core/confirm/confirm.service.ts
@@ -18,36 +18,44 @@
import {IXosConfirmConfig} from './confirm';
export interface IXosConfirm {
+ modalInstance: any;
open(config: IXosConfirmConfig) : void;
- close(cb: Function) : void;
+ close(cb: any) : void;
dismiss() : void;
}
export class XosConfirm implements IXosConfirm {
- static $inject = ['$uibModal'];
+ static $inject = ['$uibModal', '$log'];
public modalInstance;
constructor(
private $uibModal : any,
+ private $log: ng.ILogService,
) {
}
public open(config: IXosConfirmConfig) {
-
+ this.$log.debug('[XosConfirm] called open');
this.modalInstance = this.$uibModal.open({
- keyboard: false,
+ keyboard: true,
component: 'xosConfirm',
backdrop: 'static',
resolve: {
config: () => config
}
});
+
return this.modalInstance;
}
- public close(cb: Function) {
+ public close(cb: any) {
+ // check if model instance exists
+ if (angular.isUndefined(this.modalInstance)) {
+ this.$log.debug('[XosConfirm] called close without a modalInstance');
+ return;
+ }
cb()
.then(() => {
this.modalInstance.close();
diff --git a/src/app/core/header/header.spec.ts b/src/app/core/header/header.spec.ts
index 13f8b5b..eb236b1 100644
--- a/src/app/core/header/header.spec.ts
+++ b/src/app/core/header/header.spec.ts
@@ -51,11 +51,26 @@
const infoNotification = {
model: 'TestModel',
msg: {
- changed_fields: ['backend_status'],
+ changed_fields: ['backend_status', 'backend_code'],
pk: 1,
object: {
name: 'TestName',
- backend_status: '0 - In Progress'
+ backend_status: 'In Progress',
+ backend_code: 0
+ }
+ }
+};
+
+const noNotification = {
+ model: 'TestModel',
+ skip_notification: true,
+ msg: {
+ changed_fields: ['backend_status', 'backend_code'],
+ pk: 1,
+ object: {
+ name: 'TestName',
+ backend_status: 'In Progress',
+ backend_code: 0
}
}
};
@@ -74,6 +89,9 @@
.value('toastrConfig', MockToastrConfig)
.value('AuthService', MockAuth)
.value('XosNavigationService', {})
+ .value('ConfigHelpers', {
+ stateWithParamsForJs: () => null
+ })
.value('XosKeyboardShortcut', MockXosKeyboardShortcut)
.value('StyleConfig', {
logo: 'cord-logo.png',
@@ -92,6 +110,7 @@
// clear notifications
isolatedScope.notifications = [];
+ MockToastr.info.calls.reset();
}));
it('should render the appropriate logo', () => {
@@ -116,6 +135,8 @@
});
it('should configure toastr', () => {
+ delete MockToastrConfig['onTap'];
+
expect(MockToastrConfig).toEqual({
newestOnTop: false,
positionClass: 'toast-top-right',
@@ -129,7 +150,14 @@
sendEvent(infoNotification);
scope.$digest();
- expect(MockToastr.info).toHaveBeenCalledWith('Synchronization started for: TestName', 'TestModel');
+ expect(MockToastr.info).toHaveBeenCalledWith('Synchronization started for: TestName', 'TestModel', {extraData: {dest: null}});
+ });
+
+ it('should not display a toastr for a new event that use skip_notification', () => {
+ sendEvent(noNotification);
+ scope.$digest();
+
+ expect(MockToastr.info).not.toHaveBeenCalled();
});
// TODO test error and success toaster call
diff --git a/src/app/core/header/header.ts b/src/app/core/header/header.ts
index a89199d..a465f8f 100644
--- a/src/app/core/header/header.ts
+++ b/src/app/core/header/header.ts
@@ -26,13 +26,29 @@
import {IXosStyleConfig} from '../../../index';
import {IXosSearchService, IXosSearchResult} from '../../datasources/helpers/search.service';
import {IXosKeyboardShortcutService} from '../services/keyboard-shortcut';
+import {Subscription} from 'rxjs';
+import {IXosConfigHelpersService} from '../services/helpers/config.helpers';
export interface INotification extends IWSEvent {
viewed?: boolean;
}
class HeaderController {
- static $inject = ['$scope', '$rootScope', '$state', 'AuthService', 'SynchronizerStore', 'toastr', 'toastrConfig', 'XosNavigationService', 'StyleConfig', 'SearchService', 'XosKeyboardShortcut'];
+ static $inject = [
+ '$log',
+ '$scope',
+ '$rootScope',
+ '$state',
+ 'AuthService',
+ 'SynchronizerStore',
+ 'toastr',
+ 'toastrConfig',
+ 'XosNavigationService',
+ 'StyleConfig',
+ 'SearchService',
+ 'XosKeyboardShortcut',
+ 'ConfigHelpers'
+ ];
public notifications: INotification[] = [];
public newNotifications: INotification[] = [];
public version: string;
@@ -42,7 +58,10 @@
public query: string;
public search: (query: string) => any[];
+ private syncStoreSubscription: Subscription;
+
constructor(
+ private $log: ng.ILogService,
private $scope: angular.IScope,
private $rootScope: ng.IScope,
private $state: IStateService,
@@ -53,8 +72,14 @@
private NavigationService: IXosNavigationService,
private StyleConfig: IXosStyleConfig,
private SearchService: IXosSearchService,
- private XosKeyboardShortcut: IXosKeyboardShortcutService
+ private XosKeyboardShortcut: IXosKeyboardShortcutService,
+ private ConfigHelpers: IXosConfigHelpersService
) {
+
+ }
+
+ $onInit() {
+ this.$log.info('[XosHeader] Setup');
this.version = require('../../../../package.json').version;
angular.extend(this.toastrConfig, {
newestOnTop: false,
@@ -62,10 +87,9 @@
preventDuplicates: false,
preventOpenDuplicates: false,
progressBar: true,
- // autoDismiss: false,
- // closeButton: false,
- // timeOut: 0,
- // tapToDismiss: false
+ onTap: (toast) => {
+ this.$state.go(toast.scope.extraData.dest.name, toast.scope.extraData.dest.params);
+ }
});
this.search = (query: string) => {
@@ -94,27 +118,43 @@
this.userEmail = this.authService.getUser() ? this.authService.getUser().email : '';
- this.syncStore.query()
+ this.syncStoreSubscription = this.syncStore.query()
.subscribe(
(event: IWSEvent) => {
- $scope.$evalAsync(() => {
+ this.$scope.$evalAsync(() => {
+
+ if (event.model === 'Diag') {
+ // NOTE skip notifications for Diag model
+ return;
+ }
+
let toastrMsg: string;
let toastrLevel: string;
- if (event.msg.object.backend_status.indexOf('0') > -1) {
+ if (event.msg.object.backend_code === 0) {
toastrMsg = 'Synchronization started for:';
toastrLevel = 'info';
}
- else if (event.msg.object.backend_status.indexOf('1') > -1) {
+ else if (event.msg.object.backend_code === 1) {
toastrMsg = 'Synchronization succedeed for:';
toastrLevel = 'success';
}
- else if (event.msg.object.backend_status.indexOf('2') > -1) {
+ else if (event.msg.object.backend_code === 2) {
toastrMsg = 'Synchronization failed for:';
toastrLevel = 'error';
}
if (toastrLevel && toastrMsg) {
- this.toastr[toastrLevel](`${toastrMsg} ${event.msg.object.name}`, event.model);
+ let modelName = event.msg.object.name;
+ let modelClassName = event.model;
+ if (angular.isUndefined(event.msg.object.name) || event.msg.object.name === null) {
+ modelName = `${event.msg.object.leaf_model_name} [${event.msg.object.id}]`;
+ }
+
+ const dest = this.ConfigHelpers.stateWithParamsForJs(modelClassName, event.msg.object);
+
+ if (!event.skip_notification) {
+ this.toastr[toastrLevel](`${toastrMsg} ${modelName}`, modelClassName, {extraData: {dest: dest}});
+ }
}
// this.notifications.unshift(event);
// this.newNotifications = this.getNewNotifications(this.notifications);
@@ -123,6 +163,11 @@
);
}
+ $onDestroy() {
+ this.$log.info('[XosHeader] Teardown');
+ this.syncStoreSubscription.unsubscribe();
+ }
+
public getLogo(): string {
return require(`../../images/brand/${this.StyleConfig.logo}`);
}
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index e4a0a70..f566e7a 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -153,6 +153,7 @@
color: 'red',
cb: (item) => {
let obj = angular.copy(item);
+ const objName = (angular.isUndefined(obj.name)) ? 'instance' : obj.name;
item.$delete()
.then((res) => {
@@ -160,10 +161,10 @@
// TODO understand why it does not go directly in catch
throw new Error();
}
- this.toastr.info(`${model.name} ${obj.name} succesfully deleted`);
+ this.toastr.info(`${model.name} ${objName} successfully deleted`);
})
.catch(() => {
- this.toastr.error(`Error while deleting ${obj.name}`);
+ this.toastr.error(`Error while deleting ${objName}`);
});
}
}
@@ -303,7 +304,7 @@
return;
}
- const model = angular.copy(item);
+ const itemCopy = angular.copy(item);
// TODO remove ManyToMany relations and save them separately (how??)
delete item.networks;
@@ -315,24 +316,26 @@
}
});
+ const itemName = (angular.isUndefined(itemCopy.name)) ? model.name : itemCopy.name;
+
item.$save()
.then((res) => {
formCfg.feedback = {
show: true,
- message: `${model.name} succesfully saved`,
+ message: `${itemName} successfully saved`,
type: 'success',
closeBtn: true
};
- this.toastr.success(`${model.name} succesfully saved`);
+ this.toastr.success(`${itemName} successfully saved`);
})
.catch(err => {
formCfg.feedback = {
show: true,
- message: `Error while saving ${model.name}: ${err.error}. ${err.specific_error || ''}`,
+ message: `Error while saving ${itemName}: ${err.error}. ${err.specific_error || ''}`,
type: 'danger',
closeBtn: true
};
- this.toastr.error(err.specific_error || '', `Error while saving ${model.name}: ${err.error}`);
+ this.toastr.error(err.specific_error || '', `Error while saving ${itemName}: ${err.error}`);
});
};
diff --git a/src/app/core/services/keyboard-shortcut.ts b/src/app/core/services/keyboard-shortcut.ts
index b8678f7..9b98b45 100644
--- a/src/app/core/services/keyboard-shortcut.ts
+++ b/src/app/core/services/keyboard-shortcut.ts
@@ -106,8 +106,8 @@
return;
}
- if (this.allowedModifiers.indexOf(e.key) > -1) {
- this.addActiveModifierKey(e.key);
+ if (this.allowedModifiers.indexOf(e.key.toLowerCase()) > -1) {
+ this.addActiveModifierKey(e.key.toLowerCase());
return;
}
@@ -126,8 +126,8 @@
});
$('body').on('keyup', (e) => {
- if (this.allowedModifiers.indexOf(e.key) > -1) {
- this.removeActiveModifierKey(e.key);
+ if (this.allowedModifiers.indexOf(e.key.toLowerCase()) > -1) {
+ this.removeActiveModifierKey(e.key.toLowerCase());
return;
}
});
diff --git a/src/app/core/side-panel/side-panel.scss b/src/app/core/side-panel/side-panel.scss
index a6c718b..46e84ff 100644
--- a/src/app/core/side-panel/side-panel.scss
+++ b/src/app/core/side-panel/side-panel.scss
@@ -31,7 +31,7 @@
width: $side-panel-width;
height: 100%;
position: fixed;
- z-index: 9999;
+ z-index: 1049;
top: 0;
right: -$side-panel-width;
background: $background-dark-color;
diff --git a/src/app/core/side-panel/side-panel.ts b/src/app/core/side-panel/side-panel.ts
index 9b2b78f..ba1f5b0 100644
--- a/src/app/core/side-panel/side-panel.ts
+++ b/src/app/core/side-panel/side-panel.ts
@@ -39,5 +39,5 @@
export const xosSidePanel: angular.IComponentOptions = {
template: require('./side-panel.html'),
controllerAs: 'vm',
- controller: XosSidePanelController
+ controller: XosSidePanelController,
};
diff --git a/src/app/datasources/stores/synchronizer.store.ts b/src/app/datasources/stores/synchronizer.store.ts
index 635e6db..e1b26b3 100644
--- a/src/app/datasources/stores/synchronizer.store.ts
+++ b/src/app/datasources/stores/synchronizer.store.ts
@@ -36,7 +36,7 @@
if (!e.msg || !e.msg.changed_fields) {
return false;
}
- return e.msg.changed_fields.indexOf('backend_status') > -1;
+ return (e.msg.changed_fields.indexOf('backend_status') > -1 || e.msg.changed_fields.indexOf('backend_code') > -1) && !e.skip_notification;
})
.subscribe(
(event: IWSEvent) => {
diff --git a/src/app/datasources/websocket/global.ts b/src/app/datasources/websocket/global.ts
index 1367dc7..32f6f4a 100644
--- a/src/app/datasources/websocket/global.ts
+++ b/src/app/datasources/websocket/global.ts
@@ -23,6 +23,7 @@
export interface IWSEvent {
model: string;
+ skip_notification?: boolean;
msg: {
changed_fields: string[],
object?: any,
@@ -60,18 +61,20 @@
return;
}
- this.$log.info(`[WebSocket] Received Event for: ${data.model} [${data.msg.pk}]`);
+ this.$log.info(`[WebSocket] Received Event for: ${data.model} [${data.msg.pk}]`, data);
this._events.next(data);
// NOTE update observers of parent classes
if (data.msg.object.class_names && angular.isString(data.msg.object.class_names)) {
const models = data.msg.object.class_names.split(',');
+ let event: IWSEvent = angular.copy(data);
_.forEach(models, (m: string) => {
// send event only if the parent class is not the same as the model class
- if (data.model !== m) {
- data.model = m;
- this._events.next(data);
+ if (event.model !== m && m !== 'object') {
+ event.model = m;
+ event.skip_notification = true;
+ this._events.next(event);
}
});
}
diff --git a/src/decorators.ts b/src/decorators.ts
index 790293d..ab80a39 100644
--- a/src/decorators.ts
+++ b/src/decorators.ts
@@ -42,11 +42,12 @@
// eg: the first parameter is the group name
const msg = arguments[0];
- if (!isLogEnabled() && msg.indexOf('WebSocket') === 0) {
+
+ if (!isLogEnabled() && msg.indexOf('WebSocket') === -1) {
return;
}
- if (!isEventLogEnabled() && msg.indexOf('WebSocket') > 0) {
+ if (!isEventLogEnabled() && msg.indexOf('WebSocket') > -1) {
return;
}