提交 5da1bc89 编写于 作者: P Piotr Bryk

Merge pull request #227 from bryk/card-menu

Implement replica set card menu with details and delete links
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {StateParams} from './replicasetdetail_state';
import {getReplicaSetDetailsResource} from './replicasetdetail_stateconfig';
/**
* Opens replica set delete dialog.
*
* @final
*/
export class DeleteReplicaSetService {
/**
* @param {!md.$dialog} $mdDialog
* @param {!angular.$resource} $resource
* @param {!angular.$q} $q
* @ngInject
*/
constructor($mdDialog, $resource, $q) {
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
/** @private {!angular.$resource} */
this.resource_ = $resource;
/** @private {!angular.$q} */
this.q_ = $q;
}
/**
* Opens a replica set delete dialog. Returns a promise that is resolved/rejeced when user wants
* to delete the replica. Nothing happens when user clicks cancel on the dialog.
*
* @param {string} namespace
* @param {string} replicaSet
* @return {!angular.$q.Promise}
*/
showDeleteDialog(namespace, replicaSet) {
let resource =
getReplicaSetDetailsResource(new StateParams(namespace, replicaSet), this.resource_);
let deferred = this.q_.defer();
this.mdDialog_
.show(
this.mdDialog_.confirm()
.title('Delete Replica Set')
.textContent(
`Delete replica set ${replicaSet} in namespace ${namespace}. Pods managed by ` +
`the replica set will be also deleted. Are you sure?`)
.ok('Ok')
.cancel('Cancel'))
.then(() => { resource.remove(deferred.resolve, deferred.reject); });
return deferred.promise;
}
}
......@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import showDeleteReplicaSetDialog from 'replicasetdetail/deletereplicaset_dialog';
import showUpdateReplicasDialog from 'replicasetdetail/updatereplicas_dialog';
import {UPWARDS, DOWNWARDS} from 'replicasetdetail/sortedheader_controller';
import {stateName as replicasets} from 'replicasetlist/replicasetlist_state';
......@@ -31,6 +30,7 @@ const EVENT_SOURCE_SYSTEM = 'System';
export default class ReplicaSetDetailController {
/**
* @param {!md.$dialog} $mdDialog
* @param {!./replicasetdetail_state.StateParams} $stateParams
* @param {!ui.router.$state} $state
* @param {!angular.$resource} $resource
* @param {!angular.$log} $log
......@@ -38,11 +38,12 @@ export default class ReplicaSetDetailController {
* @param {!backendApi.Events} replicaSetEvents
* @param {!angular.Resource<!backendApi.ReplicaSetDetail>} replicaSetDetailResource
* @param {!angular.Resource<!backendApi.ReplicaSetSpec>} replicaSetSpecPodsResource
* @param {!./deletereplicaset_service.DeleteReplicaSetService} kdDeleteReplicaSetService
* @ngInject
*/
constructor(
$mdDialog, $state, $resource, $log, replicaSetDetail, replicaSetEvents,
replicaSetDetailResource, replicaSetSpecPodsResource) {
$mdDialog, $stateParams, $state, $resource, $log, replicaSetDetail, replicaSetEvents,
replicaSetDetailResource, replicaSetSpecPodsResource, kdDeleteReplicaSetService) {
/** @export {!backendApi.ReplicaSetDetail} */
this.replicaSetDetail = replicaSetDetail;
......@@ -70,18 +71,24 @@ export default class ReplicaSetDetailController {
/** @export {string} */
this.eventSource = EVENT_ALL;
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
/** @private {!./replicasetdetail_state.StateParams} */
this.stateParams_ = $stateParams;
/** @private {!ui.router.$state} */
this.state_ = $state;
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
/** @private {!angular.$resource} */
this.resource_ = $resource;
/** @private {!angular.$log} */
this.log_ = $log;
/** @private {!./deletereplicaset_service.DeleteReplicaSetService} */
this.kdDeleteReplicaSetService_ = kdDeleteReplicaSetService;
/**
* Name of column, that will be used for pods sorting.
* @export {string}
......@@ -187,7 +194,9 @@ export default class ReplicaSetDetailController {
* @export
*/
handleDeleteReplicaSetDialog() {
showDeleteReplicaSetDialog(this.mdDialog_, this.deleteReplicaSet_.bind(this));
this.kdDeleteReplicaSetService_.showDeleteDialog(
this.stateParams_.namespace, this.stateParams_.replicaSet)
.then(this.onReplicaSetDeleteSuccess_.bind(this), this.onReplicaSetDeleteError_.bind(this));
}
/**
......@@ -212,15 +221,6 @@ export default class ReplicaSetDetailController {
// TODO(floreks): Think about refreshing data on this page after update.
}
/**
* Deletes replica set based on current replica set namespace and name.
* @private
*/
deleteReplicaSet_() {
this.replicaSetDetailResource_.remove(
this.onReplicaSetDeleteSuccess_.bind(this), this.onReplicaSetDeleteError_.bind(this));
}
/**
* Changes state back to replica set list after successful deletion of replica set.
* @private
......
......@@ -16,6 +16,7 @@ import filtersModule from 'common/filters/filters_module';
import serviceEndpointDirective from './serviceendpoint_directive';
import stateConfig from './replicasetdetail_stateconfig';
import sortedHeaderDirective from './sortedheader_directive';
import {DeleteReplicaSetService} from './deletereplicaset_service';
/**
* Angular module for the Replica Set details view.
......@@ -32,4 +33,5 @@ export default angular.module(
])
.config(stateConfig)
.directive('kdServiceEndpoint', serviceEndpointDirective)
.directive('kdSortedHeader', sortedHeaderDirective);
.directive('kdSortedHeader', sortedHeaderDirective)
.service('kdDeleteReplicaSetService', DeleteReplicaSetService);
......@@ -42,7 +42,7 @@ export default function stateConfig($stateProvider) {
* @return {!angular.Resource<!backendApi.ReplicaSetDetail>}
* @ngInject
*/
function getReplicaSetDetailsResource($stateParams, $resource) {
export function getReplicaSetDetailsResource($stateParams, $resource) {
return $resource('/api/replicasets/:namespace/:replicaSet', $stateParams);
}
......
......@@ -25,9 +25,7 @@ limitations under the License.
</a>
</h3>
<md-button class="md-icon-button kd-replicaset-card-menu-button">
<md-icon md-font-library="material-icons">more_vert</md-icon>
</md-button>
<kd-replica-set-card-menu replica-set="::ctrl.replicaSet"></kd-replica-set-card-menu>
</div>
<div flex class="md-caption">
<span ng-repeat="(label, value) in ::ctrl.replicaSet.labels"
......
<!--
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-menu>
<md-button ng-click="ctrl.openMenu($mdOpenMenu, $event)" class="md-icon-button kd-replicaset-card-menu-button">
<md-icon md-font-library="material-icons">more_vert</md-icon>
</md-button>
<md-menu-content width="3">
<md-menu-item>
<md-button ng-click="ctrl.viewDetails()">
View details
</md-button>
</md-menu-item>
<md-menu-item>
<md-button ng-click="ctrl.showDeleteDialog()">
Delete
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {StateParams} from 'replicasetdetail/replicasetdetail_state';
import {stateName} from 'replicasetdetail/replicasetdetail_state';
/**
* Controller for the replica set card menu
*
* @final
*/
export default class ReplicaSetCardMenuController {
/**
* @param {!ui.router.$state} $state
* @param {!angular.$log} $log
* @param {!./../replicasetdetail/deletereplicaset_service.DeleteReplicaSetService}
* kdDeleteReplicaSetService
* @ngInject
*/
constructor($state, $log, kdDeleteReplicaSetService) {
/**
* Initialized from the scope.
* @export {!backendApi.ReplicaSet}
*/
this.replicaSet;
/** @private {!ui.router.$state} */
this.state_ = $state;
/** @private {!angular.$log} */
this.log_ = $log;
/** @private {!./../replicasetdetail/deletereplicaset_service.DeleteReplicaSetService} */
this.kdDeleteReplicaSetService_ = kdDeleteReplicaSetService;
}
/**
* @param {!function(MouseEvent)} $mdOpenMenu
* @param {!MouseEvent} $event
* @export
*/
openMenu($mdOpenMenu, $event) { $mdOpenMenu($event); }
/**
* @export
*/
viewDetails() {
this.state_.go(stateName, new StateParams(this.replicaSet.namespace, this.replicaSet.name));
}
/**
* @export
*/
showDeleteDialog() {
this.kdDeleteReplicaSetService_.showDeleteDialog(
this.replicaSet.namespace, this.replicaSet.name)
.then(() => this.state_.reload(), () => this.log_.error('Error deleting replica set'));
}
}
......@@ -12,19 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import ReplicaSetCardMenuController from './replicasetcardmenu_controller';
/**
* Opens replica set delete dialog.
* @param {!md.$dialog} mdDialog
* @param {!function()} deleteReplicaSetFn - callback
* Returns directive definition object for logs menu.
* @return {!angular.Directive}
*/
export default function showDeleteReplicaSetDialog(mdDialog, deleteReplicaSetFn) {
mdDialog.show(
mdDialog.confirm()
.title('Delete Replica Set')
.textContent(
'All related pods will be deleted. Are you sure you want to delete this' +
' replica set? ')
.ok('Ok')
.cancel('Cancel'))
.then(deleteReplicaSetFn);
export default function replicaSetCardMenuDirective() {
return {
scope: {},
bindToController: {
'replicaSet': '=',
},
controller: ReplicaSetCardMenuController,
controllerAs: 'ctrl',
templateUrl: 'replicasetlist/replicasetcardmenu.html',
};
}
......@@ -16,6 +16,7 @@ import stateConfig from './replicasetlist_stateconfig';
import logsMenuDirective from './logsmenu_directive';
import filtersModule from 'common/filters/filters_module';
import replicaSetCardDirective from './replicasetcard_directive';
import replicaSetCardMenuDirective from './replicasetcardmenu_directive';
import replicaSetDetailModule from 'replicasetdetail/replicasetdetail_module';
import replicaSetListContainer from './replicasetlistcontainer_directive';
......@@ -36,4 +37,5 @@ export default angular.module(
.config(stateConfig)
.directive('logsMenu', logsMenuDirective)
.directive('kdReplicaSetListContainer', replicaSetListContainer)
.directive('kdReplicaSetCard', replicaSetCardDirective);
.directive('kdReplicaSetCard', replicaSetCardDirective)
.directive('kdReplicaSetCardMenu', replicaSetCardMenuDirective);
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import replicaSetDetailModule from 'replicasetdetail/replicasetdetail_module';
describe('Delete replica set service', () => {
/** @type {!DeleteReplicaSetService} */
let service;
/** @type {!md.$dialog} */
let mdDialog;
/** @type {!angular.$q} */
let q;
/** @type {!angular.$httpBackend} */
let httpBackend;
beforeEach(() => {
angular.mock.module(replicaSetDetailModule.name);
angular.mock.inject((kdDeleteReplicaSetService, $mdDialog, $q, $httpBackend) => {
service = kdDeleteReplicaSetService;
mdDialog = $mdDialog;
q = $q;
httpBackend = $httpBackend;
});
});
it('should show delete dialog and delete object', (doneFn) => {
// given
let deferred = q.defer();
spyOn(mdDialog, 'show').and.returnValue(deferred.promise);
httpBackend.when('DELETE', '/api/replicasets/foo-namespace/foo-name').respond({});
// when
let promise = service.showDeleteDialog('foo-namespace', 'foo-name');
// then
promise.then(doneFn, () => { doneFn(new Error()); });
deferred.resolve();
httpBackend.flush();
});
it('should show delete dialog and forward errors', (doneFn) => {
// given
let deferred = q.defer();
spyOn(mdDialog, 'show').and.returnValue(deferred.promise);
httpBackend.when('DELETE', '/api/replicasets/foo-namespace/foo-name').respond(404, 'Error');
// when
let promise = service.showDeleteDialog('foo-namespace', 'foo-name');
// then
promise.then(() => { doneFn(new Error()); }, doneFn);
deferred.resolve();
httpBackend.flush();
});
});
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {StateParams} from 'replicasetdetail/replicasetdetail_state';
import ReplicaSetCardMenuController from 'replicasetlist/replicasetcardmenu_controller';
import replicaSetListModule from 'replicasetlist/replicasetlist_module';
describe('Replica set card menu controller', () => {
/** @type {!ReplicaSetCardMenuController} */
let ctrl;
/** @type {!ui.router.$state} */
let state;
/** @type {!angular.Scope} */
let scope;
/** @type {!replicasetdetail/deletereplicaset_service.DeleteReplicaSetService} */
let kdDeleteReplicaSetService;
beforeEach(() => {
angular.mock.module(replicaSetListModule.name);
angular.mock.inject(($controller, $state, _kdDeleteReplicaSetService_, $rootScope) => {
state = $state;
kdDeleteReplicaSetService = _kdDeleteReplicaSetService_;
scope = $rootScope;
ctrl = $controller(
ReplicaSetCardMenuController, null,
{replicaSet: {name: 'foo-name', namespace: 'foo-namespace'}});
});
});
it('should view details', () => {
// given
spyOn(state, 'go');
// when
ctrl.viewDetails();
// then
expect(state.go)
.toHaveBeenCalledWith('replicasetdetail', new StateParams('foo-namespace', 'foo-name'));
});
it('should open the menu', () => {
// given
let openMenuFn = jasmine.createSpy();
let event = {};
// when
ctrl.openMenu(openMenuFn, event);
// then
expect(openMenuFn).toHaveBeenCalledWith(event);
});
it('should reload on successful delete', angular.mock.inject(($q) => {
// given
let deferred = $q.defer();
spyOn(state, 'reload');
spyOn(kdDeleteReplicaSetService, 'showDeleteDialog').and.returnValue(deferred.promise);
// when
ctrl.showDeleteDialog();
deferred.resolve();
// then
expect(state.reload).not.toHaveBeenCalled();
scope.$apply();
expect(state.reload).toHaveBeenCalled();
}));
it('should log on delete error', angular.mock.inject(($q, $log) => {
// given
let deferred = $q.defer();
spyOn($log, 'error');
spyOn(state, 'reload');
spyOn(kdDeleteReplicaSetService, 'showDeleteDialog').and.returnValue(deferred.promise);
// when
ctrl.showDeleteDialog();
deferred.reject();
// then
expect(state.reload).not.toHaveBeenCalled();
expect($log.error).not.toHaveBeenCalled();
scope.$apply();
expect(state.reload).not.toHaveBeenCalled();
expect($log.error).toHaveBeenCalled();
}));
});
......@@ -16,9 +16,7 @@ import ReplicaSetListController from 'replicasetlist/replicasetlist_controller';
import replicaSetListModule from 'replicasetlist/replicasetlist_module';
describe('Replica set list controller', () => {
/**
* @type {!ReplicaSetListController}
*/
/** @type {!ReplicaSetListController} */
let ctrl;
/** @type {!ui.router.$state} */
let state;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册