diff --git a/services/web/app/coffee/Features/Tags/TagsController.coffee b/services/web/app/coffee/Features/Tags/TagsController.coffee index bb696380d1..168adf1352 100644 --- a/services/web/app/coffee/Features/Tags/TagsController.coffee +++ b/services/web/app/coffee/Features/Tags/TagsController.coffee @@ -27,3 +27,15 @@ module.exports = TagsHandler.deleteTag user_id, tag_id, (error) -> return next(error) if error? res.status(204).end() + + renameTag: (req, res, next) -> + user_id = req.session.user._id + tag_id = req.params.tag_id + name = req.body?.name + if !name? + return res.status(400).end() + else + logger.log {user_id, tag_id, name}, "renaming tag" + TagsHandler.renameTag user_id, tag_id, name, (error) -> + return next(error) if error? + res.status(204).end() diff --git a/services/web/app/coffee/Features/Tags/TagsHandler.coffee b/services/web/app/coffee/Features/Tags/TagsHandler.coffee index 68bafac306..d4593c9470 100644 --- a/services/web/app/coffee/Features/Tags/TagsHandler.coffee +++ b/services/web/app/coffee/Features/Tags/TagsHandler.coffee @@ -5,6 +5,23 @@ logger = require("logger-sharelatex") oneSecond = 1000 module.exports = + renameTag: (user_id, tag_id, name, callback = (error) ->) -> + url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/rename" + request.post { + url: url + json: + name: name + }, (err, res, body) -> + if err? + logger.err {err, user_id, tag_id, name}, "error renaming tag in tag api" + return callback(err) + else if res.statusCode >= 200 and res.statusCode < 300 + return callback(null) + else + err = new Error("tags api returned a failure status code: #{res.statusCode}") + logger.err {err, user_id, tag_id, name}, "tags api returned failure status code: #{res.statusCode}" + return callback(err) + deleteTag: (user_id, tag_id, callback = (error) ->) -> url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}" request.del url, (err, res, body) -> diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 8e9932b241..a5c35a627f 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -134,6 +134,7 @@ module.exports = class Router webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags webRouter.post '/project/:project_id/tag', AuthenticationController.requireLogin(), TagsController.processTagsUpdate webRouter.delete '/tag/:tag_id', AuthenticationController.requireLogin(), TagsController.deleteTag + webRouter.post '/tag/:tag_id/rename', AuthenticationController.requireLogin(), TagsController.renameTag # Deprecated in favour of /internal/project/:project_id but still used by versioning apiRouter.get '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails diff --git a/services/web/app/views/project/list/modals.jade b/services/web/app/views/project/list/modals.jade index 1a81ded748..78f0aae8fe 100644 --- a/services/web/app/views/project/list/modals.jade +++ b/services/web/app/views/project/list/modals.jade @@ -56,6 +56,35 @@ script(type='text/ng-template', id='deleteTagModalTemplate') span(ng-show="state.inflight") #{translate("deleting")}... span(ng-show="!state.inflight") #{translate("delete")} +script(type='text/ng-template', id='renameTagModalTemplate') + .modal-header + button.close( + type="button" + data-dismiss="modal" + ng-click="cancel()" + ) × + h3 #{translate("rename_tag")} + .modal-body + form(name="renameTagForm", novalidate) + input.form-control( + type="text", + placeholder="Tag Name", + ng-model="inputs.tagName", + required, + on-enter="rename()", + focus-on="open" + ) + .modal-footer + .modal-footer-left + span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")} + button.btn.btn-default(ng-click="cancel()") #{translate("cancel")} + button.btn.btn-primary( + ng-click="rename()", + ng-disabled="renameTagForm.$invalid || state.inflight" + ) + span(ng-show="!state.inflight") #{translate("rename")} + span(ng-show="state.inflight") #{translate("renaming")}... + script(type='text/ng-template', id='renameProjectModalTemplate') .modal-header button.close( diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index 032b0eef11..a30127840a 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -74,10 +74,10 @@ role="menu" ) li - a(href) + a(href, ng-click="renameTag(tag)", stop-propagation="click") | #{translate("rename")} li - a(href, ng-click="deleteTag(tag)") + a(href, ng-click="deleteTag(tag)", stop-propagation="click") | #{translate("delete")} li(ng-cloak) a.tag(href, ng-click="openNewTagModal()") diff --git a/services/web/public/coffee/main/project-list/modal-controllers.coffee b/services/web/public/coffee/main/project-list/modal-controllers.coffee index 911203e069..a841e369a8 100644 --- a/services/web/public/coffee/main/project-list/modal-controllers.coffee +++ b/services/web/public/coffee/main/project-list/modal-controllers.coffee @@ -1,23 +1,6 @@ define [ "base" ], (App) -> - - - App.controller 'NewTagModalController', ($scope, $modalInstance, $timeout) -> - $scope.inputs = - newTagName: "" - - $modalInstance.opened.then () -> - $timeout () -> - $scope.$broadcast "open" - , 200 - - $scope.create = () -> - $modalInstance.close($scope.inputs.newTagName) - - $scope.cancel = () -> - $modalInstance.dismiss('cancel') - App.controller 'RenameProjectModalController', ($scope, $modalInstance, $timeout, projectName) -> $scope.inputs = projectName: projectName @@ -102,28 +85,3 @@ define [ $scope.onComplete = (error, name, response) -> if response.project_id? window.location = '/project/' + response.project_id - - App.controller 'DeleteTagModalController', ($scope, $modalInstance, $http, tag) -> - $scope.tag = tag - $scope.state = - inflight: false - error: false - - $scope.delete = () -> - $scope.state.inflight = true - $scope.state.error = false - $http({ - method: "DELETE" - url: "/tag/#{tag._id}" - headers: - "X-CSRF-Token": window.csrfToken - }) - .success () -> - $scope.state.inflight = false - $modalInstance.close() - .error () -> - $scope.state.inflight = false - $scope.state.error = true - - $scope.cancel = () -> - $modalInstance.dismiss('cancel') \ No newline at end of file diff --git a/services/web/public/coffee/main/project-list/tag-controllers.coffee b/services/web/public/coffee/main/project-list/tag-controllers.coffee index c3c9ca0e35..1124449611 100644 --- a/services/web/public/coffee/main/project-list/tag-controllers.coffee +++ b/services/web/public/coffee/main/project-list/tag-controllers.coffee @@ -25,6 +25,17 @@ define [ ) modalInstance.result.then () -> $scope.tags = $scope.tags.filter (t) -> t != tag + + $scope.renameTag = (tag) -> + modalInstance = $modal.open( + templateUrl: "renameTagModalTemplate" + controller: "RenameTagModalController" + resolve: + tag: () -> tag + existing_tags: () -> $scope.tags + ) + modalInstance.result.then (new_name) -> + tag.name = new_name App.controller "TagDropdownItemController", ($scope) -> $scope.recalculateProjectsInTag = () -> @@ -49,3 +60,75 @@ define [ $scope.$watch "selectedProjects", () -> $scope.recalculateProjectsInTag() $scope.recalculateProjectsInTag() + + App.controller 'NewTagModalController', ($scope, $modalInstance, $timeout) -> + $scope.inputs = + newTagName: "" + + $modalInstance.opened.then () -> + $timeout () -> + $scope.$broadcast "open" + , 200 + + $scope.create = () -> + $modalInstance.close($scope.inputs.newTagName) + + $scope.cancel = () -> + $modalInstance.dismiss('cancel') + + App.controller 'RenameTagModalController', ($scope, $modalInstance, $timeout, $http, tag, existing_tags) -> + $scope.inputs = + tagName: tag.name + + $scope.state = + inflight: false + error: false + + $modalInstance.opened.then () -> + $timeout () -> + $scope.$broadcast "open" + , 200 + + $scope.rename = () -> + name = $scope.inputs.tagName + $scope.state.inflight = true + $scope.state.error = false + $http + .post "/tag/#{tag._id}/rename", { + _csrf: window.csrfToken, + name: name + } + .success () -> + $scope.state.inflight = false + $modalInstance.close(name) + .error () -> + $scope.state.inflight = false + $scope.state.error = true + + $scope.cancel = () -> + $modalInstance.dismiss('cancel') + + App.controller 'DeleteTagModalController', ($scope, $modalInstance, $http, tag) -> + $scope.tag = tag + $scope.state = + inflight: false + error: false + + $scope.delete = () -> + $scope.state.inflight = true + $scope.state.error = false + $http({ + method: "DELETE" + url: "/tag/#{tag._id}" + headers: + "X-CSRF-Token": window.csrfToken + }) + .success () -> + $scope.state.inflight = false + $modalInstance.close() + .error () -> + $scope.state.inflight = false + $scope.state.error = true + + $scope.cancel = () -> + $modalInstance.dismiss('cancel') diff --git a/services/web/test/UnitTests/coffee/Tags/TagsControllerTests.coffee b/services/web/test/UnitTests/coffee/Tags/TagsControllerTests.coffee index b7caae5c36..2d0c0c8653 100644 --- a/services/web/test/UnitTests/coffee/Tags/TagsControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Tags/TagsControllerTests.coffee @@ -15,6 +15,7 @@ describe 'Tags controller', -> addTag: sinon.stub().callsArgWith(3) removeProject: sinon.stub().callsArgWith(3) deleteTag: sinon.stub().callsArg(2) + renameTag: sinon.stub().callsArg(3) @controller = SandboxedModule.require modulePath, requires: "./TagsHandler":@handler 'logger-sharelatex': @@ -68,3 +69,34 @@ describe 'Tags controller', -> it "should return 204 status code", -> @res.status.calledWith(204).should.equal true @res.end.called.should.equal true + + describe "renameTag", -> + beforeEach -> + @req.params.tag_id = @tag_id = "tag-id-123" + @req.session.user._id = @user_id = "user-id-123" + + describe "with a name", -> + beforeEach -> + @req.body = name: @name = "new-name" + @controller.renameTag @req, @res + + it "should delete the tag in the backend", -> + @handler.renameTag + .calledWith(@user_id, @tag_id, @name) + .should.equal true + + it "should return 204 status code", -> + @res.status.calledWith(204).should.equal true + @res.end.called.should.equal true + + describe "without a name", -> + beforeEach -> + @controller.renameTag @req, @res + + it "should not call the backend", -> + @handler.renameTag.called.should.equal false + + it "should return 400 (bad request) status code", -> + @res.status.calledWith(400).should.equal true + @res.end.called.should.equal true + \ No newline at end of file diff --git a/services/web/test/UnitTests/coffee/Tags/TagsHandlerTests.coffee b/services/web/test/UnitTests/coffee/Tags/TagsHandlerTests.coffee index 33bbeaaddb..d19b780dfa 100644 --- a/services/web/test/UnitTests/coffee/Tags/TagsHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Tags/TagsHandlerTests.coffee @@ -126,4 +126,29 @@ describe 'TagsHandler', -> it "should call the callback with an Error", -> @callback.calledWith(new Error()).should.equal true + + describe "renameTag", -> + describe "successfully", -> + beforeEach -> + @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "") + @handler.renameTag user_id, tag_id, @name = "new-name", @callback + it "should send a request to the tag backend", -> + @request.post + .calledWith({ + url: "#{tagsUrl}/user/#{user_id}/tag/#{tag_id}/rename" + json: + name: @name + }) + .should.equal true + + it "should call the callback with no error", -> + @callback.calledWith(null).should.equal true + + describe "with error", -> + beforeEach -> + @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, "") + @handler.renameTag user_id, tag_id, "name", @callback + + it "should call the callback with an Error", -> + @callback.calledWith(new Error()).should.equal true