Tobias Jeger pushed to branch feature/cmng-psp1 at cms-community / 
hippo-addon-channel-manager


Commits:
84490f44 by Tobias Jeger at 2016-03-21T12:33:06+01:00
CHANNELMGR-500: Add support for publishing and discarding changes.

- - - - -


11 changed files:

- frontend-ng/src/angularjs/api/channel.service.js
- frontend-ng/src/angularjs/api/channel.service.spec.js
- frontend-ng/src/angularjs/channel/channel.controller.js
- frontend-ng/src/angularjs/channel/channel.controller.spec.js
- frontend-ng/src/angularjs/channel/channel.html
- frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.js
- frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.spec.js
- frontend-ng/src/angularjs/channel/page/pageStructure.service.js
- frontend-ng/src/angularjs/channel/page/pageStructure.service.spec.js
- frontend-ng/src/i18n/hippo-cm.en.json
- frontend-ng/src/i18n/hippo-cm.nl.json


Changes:

=====================================
frontend-ng/src/angularjs/api/channel.service.js
=====================================
--- a/frontend-ng/src/angularjs/api/channel.service.js
+++ b/frontend-ng/src/angularjs/api/channel.service.js
@@ -103,7 +103,35 @@ export class ChannelService {
     return this.CatalogService.getComponents();
   }
 
+  recordOwnChange() {
+    const user = this.ConfigService.cmsUser;
+
+    if (this.channel.changedBySet.indexOf(user) === -1) {
+      this.channel.changedBySet.push(user);
+    }
+  }
+
+  publishOwnChanges() {
+    return this.HstService.doPost(null, this._getMountId(), 'publish')
+      .then((response) => {
+        this._resetOwnChange();
+        return response;
+      });
+  }
+
+  discardOwnChanges() {
+    return this.HstService.doPost(null, this._getMountId(), 'discard')
+      .then((response) => {
+        this._resetOwnChange();
+        return response;
+      });
+  }
+
   _getMountId() {
     return this.channel.mountId;
   }
+
+  _resetOwnChange() {
+    
this.channel.changedBySet.splice(this.channel.changedBySet.indexOf(this.ConfigService.cmsUser),
 1);
+  }
 }


=====================================
frontend-ng/src/angularjs/api/channel.service.spec.js
=====================================
--- a/frontend-ng/src/angularjs/api/channel.service.spec.js
+++ b/frontend-ng/src/angularjs/api/channel.service.spec.js
@@ -32,6 +32,7 @@ describe('ChannelService', () => {
     channelMock = {
       contextPath: '/testContextPath',
       hostname: 'test.host.name',
+      mountId: 'mountId',
     };
 
     SessionServiceMock = {
@@ -46,8 +47,9 @@ describe('ChannelService', () => {
     ConfigServiceMock = jasmine.createSpyObj('ConfigService', 
['setContextPath']);
     ConfigServiceMock.apiUrlPrefix = '/testApiUrlPrefix';
     ConfigServiceMock.rootUuid = 'testRootUuid';
+    ConfigServiceMock.cmsUser = 'testUser';
 
-    HstServiceMock = jasmine.createSpyObj('HstService', ['setContextPath', 
'getChannel']);
+    HstServiceMock = jasmine.createSpyObj('HstService', ['setContextPath', 
'getChannel', 'doPost']);
 
     module(($provide) => {
       $provide.value('SessionService', SessionServiceMock);
@@ -185,4 +187,60 @@ describe('ChannelService', () => {
     CatalogServiceMock.getComponents.and.returnValue(mockCatalog);
     expect(ChannelService.getCatalog()).toEqual(mockCatalog);
   });
+
+  it('should publish own changes', () => {
+    channelMock.changedBySet = ['testUser'];
+    ChannelService.load(channelMock);
+    $rootScope.$digest();
+
+    const deferred = $q.defer();
+    HstServiceMock.doPost.and.returnValue(deferred.promise);
+    ChannelService.publishOwnChanges().then((response) => {
+      expect(response).toBe('pass-through');
+    });
+
+    deferred.resolve('pass-through');
+    $rootScope.$digest();
+
+    expect(HstServiceMock.doPost).toHaveBeenCalledWith(null, 'mountId', 
'publish');
+    expect(channelMock.changedBySet).toEqual([]);
+  });
+
+  it('should discard own changes', () => {
+    channelMock.changedBySet = ['testUser'];
+    ChannelService.load(channelMock);
+    $rootScope.$digest();
+
+    const deferred = $q.defer();
+    HstServiceMock.doPost.and.returnValue(deferred.promise);
+    ChannelService.discardOwnChanges().then((response) => {
+      expect(response).toBe('pass-through');
+    });
+
+    deferred.resolve('pass-through');
+    $rootScope.$digest();
+
+    expect(HstServiceMock.doPost).toHaveBeenCalledWith(null, 'mountId', 
'discard');
+    expect(channelMock.changedBySet).toEqual([]);
+  });
+
+  it('records own changes', () => {
+    channelMock.changedBySet = ['tobi', 'obiwan'];
+    ChannelService.load(channelMock);
+    $rootScope.$digest();
+
+    ChannelService.recordOwnChange();
+
+    expect(channelMock.changedBySet).toEqual(['tobi', 'obiwan', 'testUser']);
+  });
+
+  it('recognizes changes already pending', () => {
+    channelMock.changedBySet = ['tobi', 'testUser', 'obiwan'];
+    ChannelService.load(channelMock);
+    $rootScope.$digest();
+
+    ChannelService.recordOwnChange();
+
+    expect(channelMock.changedBySet).toEqual(['tobi', 'testUser', 'obiwan']);
+  });
 });


=====================================
frontend-ng/src/angularjs/channel/channel.controller.js
=====================================
--- a/frontend-ng/src/angularjs/channel/channel.controller.js
+++ b/frontend-ng/src/angularjs/channel/channel.controller.js
@@ -18,15 +18,18 @@ const SIDENAVS = ['components'];
 
 export class ChannelCtrl {
 
-  constructor($log, $scope, $mdSidenav, ChannelService, ScalingService, 
SessionService, ComponentAdderService) {
+  constructor($log, $scope, $translate, $mdDialog, $mdSidenav, ChannelService, 
ScalingService, SessionService, ComponentAdderService, ConfigService) {
     'ngInject';
 
     this.$log = $log;
     this.$scope = $scope;
+    this.$translate = $translate;
+    this.$mdDialog = $mdDialog;
     this.$mdSidenav = $mdSidenav;
     this.ChannelService = ChannelService;
     this.ScalingService = ScalingService;
     this.SessionService = SessionService;
+    this.ConfigService = ConfigService;
 
     this.iframeUrl = ChannelService.getUrl();
     this.isEditMode = false;
@@ -65,6 +68,9 @@ export class ChannelCtrl {
     this.isCreatingPreview = true;
     this.ChannelService.createPreviewConfiguration()
       .then(() => {
+//        this._reloadPage(); // reload page to keep UUIDs in sync with 
preview config
+        // TODO: this is first stab at reloading a page. I guess we need a 
better way.
+        // reloading the page here works in the app, but once we tell Ext 
which component to render, ext doesn't seem to "get it" yet.
         this.isEditMode = true;
       })
       // TODO: handle error response
@@ -87,7 +93,44 @@ export class ChannelCtrl {
     return this.ChannelService.getCatalog();
   }
 
+  hasChanges() {
+    return 
this.ChannelService.getChannel().changedBySet.indexOf(this.ConfigService.cmsUser)
 !== -1;
+  }
+
+  publish() {
+    this.ChannelService.publishOwnChanges();
+    // TODO: what if this fails?
+  }
+
+  discard() {
+    this._confirmDiscard().then(() => {
+      this.ChannelService.discardOwnChanges().then(() => this._reloadPage());
+      // TODO: what if this fails?
+    });
+  }
+
   _isSidenavOpen(name) {
     return this.$mdSidenav(name).isOpen();
   }
+
+  _confirmDiscard() {
+    const confirm = this.$mdDialog
+      .confirm()
+      .title(this.$translate.instant('CONFIRM_DISCARD_OWN_CHANGES_TITLE'))
+      
.textContent(this.$translate.instant('CONFIRM_DISCARD_OWN_CHANGES_MESSAGE'))
+      .ok(this.$translate.instant('BUTTON_YES'))
+      .cancel(this.$translate.instant('BUTTON_NO'));
+
+    return this.$mdDialog.show(confirm);
+  }
+
+  _reloadPage() {
+    // TODO: this should probably go into the hippoIframe.
+    const iframe = $('iframe');
+    if (iframe.length > 0) {
+      const currentPage = iframe[0].contentWindow.location.pathname;
+
+      iframe.attr('src', currentPage);
+    }
+  }
 }


=====================================
frontend-ng/src/angularjs/channel/channel.controller.spec.js
=====================================
--- a/frontend-ng/src/angularjs/channel/channel.controller.spec.js
+++ b/frontend-ng/src/angularjs/channel/channel.controller.spec.js
@@ -22,23 +22,30 @@ describe('ChannelCtrl', () => {
   let ChannelService;
   let ComponentsService;
   let ScalingService;
+  let ConfigService;
   let ChannelCtrl;
   let $rootScope;
   let $q;
+  let $mdDialog;
 
   beforeEach(() => {
     module('hippo-cm');
 
-    inject(($controller, _$rootScope_, _$q_) => {
+    inject(($controller, _$rootScope_, _$q_, _$mdDialog_, _ConfigService_) => {
       const resolvedPromise = _$q_.when();
 
       $rootScope = _$rootScope_;
       $q = _$q_;
+      $mdDialog = _$mdDialog_;
+      ConfigService = _ConfigService_;
 
       ChannelService = jasmine.createSpyObj('ChannelService', [
         'getUrl',
         'hasPreviewConfiguration',
         'createPreviewConfiguration',
+        'getChannel',
+        'publishOwnChanges',
+        'discardOwnChanges',
       ]);
       ChannelService.getUrl.and.returnValue('/test/url');
       
ChannelService.createPreviewConfiguration.and.returnValue(resolvedPromise);
@@ -116,4 +123,49 @@ describe('ChannelCtrl', () => {
     ChannelCtrl.toggleEditMode();
     expect(ChannelService.createPreviewConfiguration).not.toHaveBeenCalled();
   });
+
+  it('detects that the current user has pending changes', () => {
+    ConfigService.cmsUser = 'testUser';
+    ChannelService.getChannel.and.returnValue({ changedBySet: ['tobi', 
'testUser', 'obiwan'] });
+
+    expect(ChannelCtrl.hasChanges()).toBe(true);
+  });
+
+  it('detects that the current user has no pending changes', () => {
+    ConfigService.cmsUser = 'testUser';
+    ChannelService.getChannel.and.returnValue({ changedBySet: ['tobi', 
'obiwan'] });
+
+    expect(ChannelCtrl.hasChanges()).toBe(false);
+  });
+
+  it('publishes changes', () => {
+    ChannelCtrl.publish();
+
+    expect(ChannelService.publishOwnChanges).toHaveBeenCalled();
+  });
+
+  it('discards changes', () => {
+    ChannelService.discardOwnChanges.and.returnValue($q.resolve());
+    spyOn($mdDialog, 'show').and.returnValue($q.resolve());
+    spyOn($mdDialog, 'confirm').and.callThrough();
+
+    ChannelCtrl.discard();
+    $rootScope.$digest();
+
+    expect($mdDialog.confirm).toHaveBeenCalled();
+    expect($mdDialog.show).toHaveBeenCalled();
+    expect(ChannelService.discardOwnChanges).toHaveBeenCalled();
+  });
+
+  it('does not discard changes if not confirmed', () => {
+    spyOn($mdDialog, 'show').and.returnValue($q.reject());
+    spyOn($mdDialog, 'confirm').and.callThrough();
+
+    ChannelCtrl.discard();
+    $rootScope.$digest();
+
+    expect($mdDialog.confirm).toHaveBeenCalled();
+    expect($mdDialog.show).toHaveBeenCalled();
+    expect(ChannelService.discardOwnChanges).not.toHaveBeenCalled();
+  });
 });


=====================================
frontend-ng/src/angularjs/channel/channel.html
=====================================
--- a/frontend-ng/src/angularjs/channel/channel.html
+++ b/frontend-ng/src/angularjs/channel/channel.html
@@ -22,6 +22,28 @@
       {{ 'TOOLBAR_BUTTON_COMPONENTS' | translate }}
     </md-button>
     <span flex></span>
+    <md-menu ng-if="channelCtrl.hasChanges()">
+      <md-button aria-label="{{ 'TOOLBAR_BUTTON_CHANGES' | translate }}"
+                 ng-click="$mdOpenMenu($event)">
+        <md-icon class="material-icons">change_history</md-icon>
+        {{ 'TOOLBAR_BUTTON_CHANGES' | translate }}
+      </md-button>
+      <md-menu-content width="4">
+        <md-menu-item>
+          <md-button aria-label="{{ 'TOOLBAR_MENU_CHANGES_PUBLISH' | translate 
}}"
+                     ng-click="channelCtrl.publish()">
+            {{ 'TOOLBAR_MENU_CHANGES_PUBLISH' | translate }}
+          </md-button>
+        </md-menu-item>
+        <md-menu-item>
+          <md-button aria-label="{{ 'TOOLBAR_MENU_CHANGES_DISCARD' | translate 
}}"
+                     ng-click="channelCtrl.discard()">
+            {{ 'TOOLBAR_MENU_CHANGES_DISCARD' | translate }}
+          </md-button>
+        </md-menu-item>
+      </md-menu-content>
+    </md-menu>
+
     <md-button ng-if="channelCtrl.isEditable()"
                ng-disabled="channelCtrl.isCreatingPreview"
                ng-click="channelCtrl.toggleEditMode()">


=====================================
frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.js
=====================================
--- a/frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.js
+++ b/frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.js
@@ -16,7 +16,7 @@
 
 export class DragDropService {
 
-  constructor($rootScope, $q, DomService, HstService, PageStructureService, 
ScalingService) {
+  constructor($rootScope, $q, DomService, HstService, PageStructureService, 
ScalingService, ChannelService) {
     'ngInject';
 
     this.$rootScope = $rootScope;
@@ -25,6 +25,7 @@ export class DragDropService {
     this.HstService = HstService;
     this.PageStructureService = PageStructureService;
     this.ScalingService = ScalingService;
+    this.ChannelService = ChannelService;
 
     this.dragging = false;
   }
@@ -129,6 +130,8 @@ export class DragDropService {
     if (sourceContainer.getId() !== targetContainer.getId()) {
       this._updateContainer(targetContainer);
     }
+
+    this.ChannelService.recordOwnChange();
   }
 
   _updateContainer(container) {


=====================================
frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.spec.js
=====================================
--- a/frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.spec.js
+++ b/frontend-ng/src/angularjs/channel/hippoIframe/dragDrop.service.spec.js
@@ -21,6 +21,7 @@ describe('DragDropService', () => {
   let ScalingService;
   let PageStructureService;
   let HstService;
+  let ChannelService;
 
   let iframe;
   let base;
@@ -33,13 +34,15 @@ describe('DragDropService', () => {
   beforeEach(() => {
     module('hippo-cm.channel.hippoIframe');
 
-    inject((_DragDropService_, _ScalingService_, _PageStructureService_, 
_HstService_) => {
+    inject((_DragDropService_, _ScalingService_, _PageStructureService_, 
_HstService_, _ChannelService_) => {
       DragDropService = _DragDropService_;
       ScalingService = _ScalingService_;
       PageStructureService = _PageStructureService_;
       HstService = _HstService_;
+      ChannelService = _ChannelService_;
     });
 
+    spyOn(ChannelService, 'recordOwnChange');
     
jasmine.getFixtures().load('channel/hippoIframe/dragDrop.service.fixture.html');
 
     iframe = $j('#testIframe');


=====================================
frontend-ng/src/angularjs/channel/page/pageStructure.service.js
=====================================
--- a/frontend-ng/src/angularjs/channel/page/pageStructure.service.js
+++ b/frontend-ng/src/angularjs/channel/page/pageStructure.service.js
@@ -110,7 +110,10 @@ export class PageStructureService {
     if (component) {
       const container = component.getContainer();
       this.HstService.removeHstComponent(container.getId(), componentId)
-        .then(() => this._renderContainer(container));
+        .then(() => {
+          this.ChannelService.recordOwnChange();
+          this._renderContainer(container);
+        });
       // TODO handle error
     } else {
       this.$log.debug(`Was asked to remove component with ID '${componentId}', 
but couldn't find it in the page structure.`);
@@ -192,7 +195,10 @@ export class PageStructureService {
 
     if (container) {
       this.HstService.addHstComponent(catalogComponent, container.getId())
-        .then(() => this._renderContainer(container));
+        .then(() => {
+          this.ChannelService.recordOwnChange();
+          this._renderContainer(container);
+        });
       // TODO: handle error
     } else {
       console.log('container not found');


=====================================
frontend-ng/src/angularjs/channel/page/pageStructure.service.spec.js
=====================================
--- a/frontend-ng/src/angularjs/channel/page/pageStructure.service.spec.js
+++ b/frontend-ng/src/angularjs/channel/page/pageStructure.service.spec.js
@@ -44,6 +44,8 @@ describe('PageStructureService', () => {
       HstService = _HstService_;
       RenderingService = _RenderingService_;
     });
+
+    spyOn(ChannelService, 'recordOwnChange');
   });
 
   beforeEach(() => {
@@ -299,6 +301,7 @@ describe('PageStructureService', () => {
     $rootScope.$digest();
 
     
expect(HstService.removeHstComponent).toHaveBeenCalledWith('container-vbox', 
'aaaa');
+    expect(ChannelService.recordOwnChange).toHaveBeenCalled();
   });
 
   it('removes a valid component but fails to call HST', () => {


=====================================
frontend-ng/src/i18n/hippo-cm.en.json
=====================================
--- a/frontend-ng/src/i18n/hippo-cm.en.json
+++ b/frontend-ng/src/i18n/hippo-cm.en.json
@@ -1,11 +1,16 @@
 {
   "TOOLBAR_BUTTON_COMPONENTS": "Components",
+  "TOOLBAR_BUTTON_CHANGES": "Changes",
+  "TOOLBAR_MENU_CHANGES_PUBLISH": "Publish",
+  "TOOLBAR_MENU_CHANGES_DISCARD": "Discard",
   "TOOLBAR_SWITCH_VIEWER_MODE": "Viewer mode",
   "TOOLBAR_SWITCH_VIEWER_MODE_EDIT": "Edit",
   "TOOLBAR_SWITCH_VIEWER_MODE_VIEW": "View",
   "CONFIRM_OPEN_EXTERNAL_LINK": "This external link will be opened in a new 
tab.\nDo you want to continue?",
   "CONFIRM_DELETE_COMPONENT_TITLE": "Confirm remove",
   "CONFIRM_DELETE_COMPONENT_MESSAGE": "Are you sure you want to remove the 
component '{{component}}' from this template?",
+  "CONFIRM_DISCARD_OWN_CHANGES_TITLE": "Confirm discard changes",
+  "CONFIRM_DISCARD_OWN_CHANGES_MESSAGE": "Are you sure you want to discard all 
your unpublished changes to this channel?",
   "BUTTON_YES": "Yes",
   "BUTTON_NO": "No"
 }


=====================================
frontend-ng/src/i18n/hippo-cm.nl.json
=====================================
--- a/frontend-ng/src/i18n/hippo-cm.nl.json
+++ b/frontend-ng/src/i18n/hippo-cm.nl.json
@@ -1,11 +1,16 @@
 {
   "TOOLBAR_BUTTON_COMPONENTS": "Componenten",
+  "TOOLBAR_BUTTON_CHANGES": "Wijzigingen",
+  "TOOLBAR_MENU_CHANGES_PUBLISH": "Publiceer",
+  "TOOLBAR_MENU_CHANGES_DISCARD": "Maak ongedaan",
   "TOOLBAR_SWITCH_VIEWER_MODE": "Viewer modus",
   "TOOLBAR_SWITCH_VIEWER_MODE_EDIT": "Bewerk",
   "TOOLBAR_SWITCH_VIEWER_MODE_VIEW": "Bekijk",
   "CONFIRM_OPEN_EXTERNAL_LINK": "Deze externe link zal in een nieuwe tab 
worden geopened.\nWilt u doorgaan?",
   "CONFIRM_DELETE_COMPONENT_TITLE": "Bevestig het verwijderen",
   "CONFIRM_DELETE_COMPONENT_MESSAGE": "Weet u zeker dat u {0} wilt 
verwijderen?",
+  "CONFIRM_DISCARD_OWN_CHANGES_TITLE": "Bevestig ongedaan maken",
+  "CONFIRM_DISCARD_OWN_CHANGES_MESSAGE": "Weet u zeker dat u al uw 
ongepubliceerde veranderingen in deze channel ongedaan wilt maken?",
   "BUTTON_YES": "Ja",
   "BUTTON_NO": "Nee"
 }



View it on GitLab: 
https://code.onehippo.org/cms-community/hippo-addon-channel-manager/commit/84490f446f4051220fd4e1e51144cf08a12bf9bb
_______________________________________________
Hippocms-svn mailing list
Hippocms-svn@lists.onehippo.org
https://lists.onehippo.org/mailman/listinfo/hippocms-svn

Reply via email to