Просмотр исходного кода

Explicitly create tags and get their id

James Allen 10 лет назад
Родитель
Сommit
b32178182d

+ 14 - 3
services/web/app/coffee/Features/Tags/TagsController.coffee

@@ -2,9 +2,20 @@ TagsHandler = require("./TagsHandler")
 logger = require("logger-sharelatex")
 logger = require("logger-sharelatex")
 
 
 module.exports =
 module.exports =
-	getAllTags: (req, res)->
-		TagsHandler.getAllTags req.session.user._id, (err, allTags)->
-			res.send(allTags)
+	getAllTags: (req, res, next)->
+		user_id = req.session.user._id
+		logger.log {user_id}, "getting tags"
+		TagsHandler.getAllTags user_id, (error, allTags)->
+			return next(error) if error?
+			res.json(allTags)
+	
+	createTag: (req, res, next) ->
+		user_id = req.session.user._id
+		name = req.body.name
+		logger.log {user_id, name}, "creating tag"
+		TagsHandler.createTag user_id, name, (error, tag) ->
+			return next(error) if error?
+			res.json(tag)
 	
 	
 	addProjectToTag: (req, res, next) ->
 	addProjectToTag: (req, res, next) ->
 		user_id = req.session.user._id
 		user_id = req.session.user._id

+ 46 - 39
services/web/app/coffee/Features/Tags/TagsHandler.coffee

@@ -5,18 +5,24 @@ logger = require("logger-sharelatex")
 
 
 TIMEOUT = 1000
 TIMEOUT = 1000
 module.exports = TagsHandler =
 module.exports = TagsHandler =
-	_handleResponse: (res, params, callback) ->
-		if err?
-			params.err = err
-			logger.err params, "error in tag api"
-			return callback(err)
-		else if res? and res.statusCode >= 200 and res.statusCode < 300
-			return callback(null)
-		else
-			err = new Error("tags api returned a failure status code: #{res?.statusCode}")
-			params.err = err
-			logger.err params, "tags api returned failure status code: #{res?.statusCode}"
-			return callback(err)
+	getAllTags: (user_id, callback)->
+		@_requestTags user_id, (err, allTags)=>
+			if !allTags?
+				allTags = []
+			@_groupTagsByProject allTags, (err, groupedByProject)->
+				logger.log allTags:allTags, user_id:user_id, groupedByProject:groupedByProject, "got all tags from tags api"
+				callback err, allTags, groupedByProject
+	
+	createTag: (user_id, name, callback = (error, tag) ->) ->
+		opts = 
+			url: "#{settings.apis.tags.url}/user/#{user_id}/tag"
+			json:
+				name: name
+			timeout: TIMEOUT
+		request.post opts, (err, res, body)->
+			TagsHandler._handleResponse err, res, {user_id}, (error) ->
+				return callback(error) if error?
+				callback(null, body or {})
 
 
 	renameTag: (user_id, tag_id, name, callback = (error) ->) ->
 	renameTag: (user_id, tag_id, name, callback = (error) ->) ->
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/rename"
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/rename"
@@ -26,50 +32,55 @@ module.exports = TagsHandler =
 				name: name
 				name: name
 			timeout: TIMEOUT
 			timeout: TIMEOUT
 		}, (err, res, body) ->
 		}, (err, res, body) ->
-			TagsHandler._handleResponse res, {url, user_id, tag_id, name}, callback
+			TagsHandler._handleResponse err, res, {url, user_id, tag_id, name}, callback
 
 
 	deleteTag: (user_id, tag_id, callback = (error) ->) ->
 	deleteTag: (user_id, tag_id, callback = (error) ->) ->
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}"
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}"
 		request.del {url, timeout: TIMEOUT}, (err, res, body) ->
 		request.del {url, timeout: TIMEOUT}, (err, res, body) ->
-			TagsHandler._handleResponse res, {url, user_id, tag_id}, callback
+			TagsHandler._handleResponse err, res, {url, user_id, tag_id}, callback
 
 
 	removeProjectFromTag: (user_id, tag_id, project_id, callback)->
 	removeProjectFromTag: (user_id, tag_id, project_id, callback)->
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/project/#{project_id}"
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/project/#{project_id}"
 		request.del {url, timeout: TIMEOUT}, (err, res, body) ->
 		request.del {url, timeout: TIMEOUT}, (err, res, body) ->
-			TagsHandler._handleResponse res, {url, user_id, tag_id, project_id}, callback
+			TagsHandler._handleResponse err, res, {url, user_id, tag_id, project_id}, callback
 
 
 	addProjectToTag: (user_id, tag_id, project_id, callback)->
 	addProjectToTag: (user_id, tag_id, project_id, callback)->
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/project/#{project_id}"
 		url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/project/#{project_id}"
 		request.post {url, timeout: TIMEOUT}, (err, res, body) ->
 		request.post {url, timeout: TIMEOUT}, (err, res, body) ->
-			TagsHandler._handleResponse res, {url, user_id, tag_id, project_id}, callback
+			TagsHandler._handleResponse err, res, {url, user_id, tag_id, project_id}, callback
+
+	removeProjectFromAllTags: (user_id, project_id, callback)->
+		url = "#{settings.apis.tags.url}/user/#{user_id}/project/#{project_id}"
+		opts =
+			url: url
+			timeout:TIMEOUT
+		request.del opts, (err, res, body) ->
+			TagsHandler._handleResponse err, res, {url, user_id, project_id}, callback
+
+	_handleResponse: (err, res, params, callback) ->
+		if err?
+			params.err = err
+			logger.err params, "error in tag api"
+			return callback(err)
+		else if res? and res.statusCode >= 200 and res.statusCode < 300
+			return callback(null)
+		else
+			err = new Error("tags api returned a failure status code: #{res?.statusCode}")
+			params.err = err
+			logger.err params, "tags api returned failure status code: #{res?.statusCode}"
+			return callback(err)
 
 
-	requestTags: (user_id, callback)->
+	_requestTags: (user_id, callback)->
 		opts = 
 		opts = 
 			url: "#{settings.apis.tags.url}/user/#{user_id}/tag"
 			url: "#{settings.apis.tags.url}/user/#{user_id}/tag"
 			json: true
 			json: true
 			timeout: TIMEOUT
 			timeout: TIMEOUT
 		request.get opts, (err, res, body)->
 		request.get opts, (err, res, body)->
-			TagsHandler._handleResponse res, {user_id}, (error) ->
+			TagsHandler._handleResponse err, res, {user_id}, (error) ->
 				return callback(error, []) if error?
 				return callback(error, []) if error?
 				callback(null, body or [])
 				callback(null, body or [])
 
 
-	getAllTags: (user_id, callback)->
-		@requestTags user_id, (err, allTags)=>
-			if !allTags?
-				allTags = []
-			@groupTagsByProject allTags, (err, groupedByProject)->
-				logger.log allTags:allTags, user_id:user_id, groupedByProject:groupedByProject, "getting all tags from tags api"
-				callback err, allTags, groupedByProject
-
-	removeProjectFromAllTags: (user_id, project_id, callback)->
-		uri = buildUri(user_id, project_id)
-		opts =
-			uri:"#{settings.apis.tags.url}/user/#{user_id}/project/#{project_id}"
-			timeout:TIMEOUT
-		logger.log user_id:user_id, project_id:project_id, "removing project_id from tags"
-		request.del opts, callback
-
-	groupTagsByProject: (tags, callback)->
+	_groupTagsByProject: (tags, callback)->
 		result = {}
 		result = {}
 		_.each tags, (tag)->
 		_.each tags, (tag)->
 			_.each tag.project_ids, (project_id)->
 			_.each tag.project_ids, (project_id)->
@@ -81,7 +92,3 @@ module.exports = TagsHandler =
 				delete clonedTag.project_ids
 				delete clonedTag.project_ids
 				result[project_id].push(clonedTag)
 				result[project_id].push(clonedTag)
 		callback null, result
 		callback null, result
-
-
-buildUri = (user_id, project_id)->
-	uri = "#{settings.apis.tags.url}/user/#{user_id}/project/#{project_id}/tag"

+ 4 - 3
services/web/app/coffee/router.coffee

@@ -131,11 +131,12 @@ module.exports = class Router
 		webRouter.get  '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
 		webRouter.get  '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
 		webRouter.get  '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
 		webRouter.get  '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
 
 
-		webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
+		webRouter.get    '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
+		webRouter.post   '/tag', AuthenticationController.requireLogin(), TagsController.createTag
+		webRouter.post   '/tag/:tag_id/rename', AuthenticationController.requireLogin(), TagsController.renameTag
+		webRouter.delete '/tag/:tag_id', AuthenticationController.requireLogin(), TagsController.deleteTag
 		webRouter.post   '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.addProjectToTag
 		webRouter.post   '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.addProjectToTag
 		webRouter.delete '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.removeProjectFromTag
 		webRouter.delete '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.removeProjectFromTag
-		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
 		# Deprecated in favour of /internal/project/:project_id but still used by versioning
 		apiRouter.get  '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
 		apiRouter.get  '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails

+ 6 - 2
services/web/app/views/project/list/modals.jade

@@ -18,6 +18,8 @@ script(type='text/ng-template', id='newTagModalTemplate')
 				stop-propagation="click"
 				stop-propagation="click"
 			)
 			)
 	.modal-footer
 	.modal-footer
+		.modal-footer-left
+			span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
 		//- We stop propagation to stop the clicks from closing the
 		//- We stop propagation to stop the clicks from closing the
 		//- 'move to folder' menu.
 		//- 'move to folder' menu.
 		button.btn.btn-default(
 		button.btn.btn-default(
@@ -25,10 +27,12 @@ script(type='text/ng-template', id='newTagModalTemplate')
 			stop-propagation="click"
 			stop-propagation="click"
 		) #{translate("cancel")}
 		) #{translate("cancel")}
 		button.btn.btn-primary(
 		button.btn.btn-primary(
-			ng-disabled="newTagForm.$invalid"
+			ng-disabled="newTagForm.$invalid || state.inflight"
 			ng-click="create()"
 			ng-click="create()"
 			stop-propagation="click"
 			stop-propagation="click"
-		) #{translate("create")}
+		)
+			span(ng-show="!state.inflight") #{translate("create")}
+			span(ng-show="state.inflight") #{translate("creating")}...
 
 
 script(type='text/ng-template', id='deleteTagModalTemplate')
 script(type='text/ng-template', id='deleteTagModalTemplate')
 	.modal-header
 	.modal-header

+ 4 - 8
services/web/public/coffee/main/project-list/project-list.coffee

@@ -203,12 +203,6 @@ define [
 				}
 				}
 
 
 		$scope.createTag = (name) ->
 		$scope.createTag = (name) ->
-			event_tracking.send 'project-list-page-interaction', 'project action', 'createTag'
-			$scope.tags.push tag = {
-				name: name
-				project_ids: []
-				showWhenEmpty: true
-			}
 			return tag
 			return tag
 
 
 		$scope.openNewTagModal = (e) ->
 		$scope.openNewTagModal = (e) ->
@@ -218,8 +212,9 @@ define [
 			)
 			)
 
 
 			modalInstance.result.then(
 			modalInstance.result.then(
-				(newTagName) ->
-					tag = $scope.createTag(newTagName)
+				(tag) ->
+					console.log "Created tag", tag
+					$scope.tags.push tag
 					$scope.addSelectedProjectsToTag(tag)
 					$scope.addSelectedProjectsToTag(tag)
 			)
 			)
 
 
@@ -343,6 +338,7 @@ define [
 				$scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
 				$scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
 
 
 			for project in selected_projects
 			for project in selected_projects
+				project.tags = []
 				if project.accessLevel == "owner"
 				if project.accessLevel == "owner"
 					project.archived = true
 					project.archived = true
 					queuedHttp {
 					queuedHttp {

+ 19 - 2
services/web/public/coffee/main/project-list/tag-controllers.coffee

@@ -61,9 +61,13 @@ define [
 			$scope.recalculateProjectsInTag()
 			$scope.recalculateProjectsInTag()
 		$scope.recalculateProjectsInTag()
 		$scope.recalculateProjectsInTag()
 	
 	
-	App.controller 'NewTagModalController', ($scope, $modalInstance, $timeout) ->
+	App.controller 'NewTagModalController', ($scope, $modalInstance, $timeout, $http) ->
 		$scope.inputs = 
 		$scope.inputs = 
 			newTagName: ""
 			newTagName: ""
+		
+		$scope.state =
+			inflight: false
+			error: false
 
 
 		$modalInstance.opened.then () ->
 		$modalInstance.opened.then () ->
 			$timeout () ->
 			$timeout () ->
@@ -71,7 +75,20 @@ define [
 			, 200
 			, 200
 
 
 		$scope.create = () ->
 		$scope.create = () ->
-			$modalInstance.close($scope.inputs.newTagName)
+			name = $scope.inputs.newTagName
+			$scope.state.inflight = true
+			$scope.state.error = false
+			$http
+				.post "/tag", {
+					_csrf: window.csrfToken,
+					name: name
+				}
+				.success (data, status, headers, config) ->
+					$scope.state.inflight = false
+					$modalInstance.close(data)
+				.error () ->
+					$scope.state.inflight = false
+					$scope.state.error = true
 
 
 		$scope.cancel = () ->
 		$scope.cancel = () ->
 			$modalInstance.dismiss('cancel')
 			$modalInstance.dismiss('cancel')

+ 18 - 1
services/web/test/UnitTests/coffee/Tags/TagsControllerTests.coffee

@@ -16,6 +16,7 @@ describe 'TagsController', ->
 			removeProjectFromTag: sinon.stub().callsArgWith(3)
 			removeProjectFromTag: sinon.stub().callsArgWith(3)
 			deleteTag: sinon.stub().callsArg(2)
 			deleteTag: sinon.stub().callsArg(2)
 			renameTag: sinon.stub().callsArg(3)
 			renameTag: sinon.stub().callsArg(3)
+			createTag: sinon.stub()
 		@controller = SandboxedModule.require modulePath, requires:
 		@controller = SandboxedModule.require modulePath, requires:
 			"./TagsHandler":@handler
 			"./TagsHandler":@handler
 			'logger-sharelatex':
 			'logger-sharelatex':
@@ -31,15 +32,31 @@ describe 'TagsController', ->
 		@res = {}
 		@res = {}
 		@res.status = sinon.stub().returns @res
 		@res.status = sinon.stub().returns @res
 		@res.end = sinon.stub()
 		@res.end = sinon.stub()
+		@res.json = sinon.stub()
 
 
 	describe "getAllTags", ->
 	describe "getAllTags", ->
 		it 'should ask the handler for all tags', (done)->
 		it 'should ask the handler for all tags', (done)->
 			allTags = [{name:"tag", projects:["123423","423423"]}]
 			allTags = [{name:"tag", projects:["123423","423423"]}]
 			@handler.getAllTags = sinon.stub().callsArgWith(1, null, allTags)
 			@handler.getAllTags = sinon.stub().callsArgWith(1, null, allTags)
-			@controller.getAllTags @req, send:(body)=>
+			@controller.getAllTags @req, json:(body)=>
 				body.should.equal allTags
 				body.should.equal allTags
 				@handler.getAllTags.calledWith(user_id).should.equal true
 				@handler.getAllTags.calledWith(user_id).should.equal true
 				done()
 				done()
+
+	describe "createTag", ->
+		beforeEach ->
+			@handler.createTag.callsArgWith(2, null, @tag = {"mock": "tag"})
+			@req.session.user._id = @user_id = "user-id-123"
+			@req.body = name: @name = "tag-name"
+			@controller.createTag @req, @res
+			
+		it "should create the tag in the backend", ->
+			@handler.createTag
+				.calledWith(@user_id, @name)
+				.should.equal true
+		
+		it "should return the tag", ->
+			@res.json.calledWith(@tag).should.equal true
 	
 	
 	describe "deleteTag", ->
 	describe "deleteTag", ->
 		beforeEach ->
 		beforeEach ->

+ 26 - 8
services/web/test/UnitTests/coffee/Tags/TagsHandlerTests.coffee

@@ -29,10 +29,10 @@ describe 'TagsHandler', ->
 	describe "removeProjectFromAllTags", ->
 	describe "removeProjectFromAllTags", ->
 		it 'should tell the tags api to remove the project_id from all the users tags', (done)->
 		it 'should tell the tags api to remove the project_id from all the users tags', (done)->
 			@handler.removeProjectFromAllTags user_id, project_id, =>
 			@handler.removeProjectFromAllTags user_id, project_id, =>
-				@request.del.calledWith({uri:"#{tagsUrl}/user/#{user_id}/project/#{project_id}", timeout:1000}).should.equal true
+				@request.del.calledWith({url:"#{tagsUrl}/user/#{user_id}/project/#{project_id}", timeout:1000}).should.equal true
 				done()
 				done()
 
 
-	describe "groupTagsByProject", ->
+	describe "_groupTagsByProject", ->
 		it 'should 	group the tags by project_id', (done)->
 		it 'should 	group the tags by project_id', (done)->
 			rawTags = [
 			rawTags = [
 				{name:"class101", project_ids:["1234", "51db33e31a55afd212000007"]}
 				{name:"class101", project_ids:["1234", "51db33e31a55afd212000007"]}
@@ -41,35 +41,35 @@ describe 'TagsHandler', ->
 				{name:"different", project_ids:["1234", "e2c39a2f09000100"]}
 				{name:"different", project_ids:["1234", "e2c39a2f09000100"]}
 			]
 			]
 
 
-			@handler.groupTagsByProject rawTags, (err, tags)->
+			@handler._groupTagsByProject rawTags, (err, tags)->
 				_.size(tags).should.equal 7
 				_.size(tags).should.equal 7
 				done()
 				done()
 
 
-	describe "requestTags", ->
+	describe "_requestTags", ->
 		it 'should return an err and empty array on error', (done)->
 		it 'should return an err and empty array on error', (done)->
 			@request.get.callsArgWith(1, {something:"wrong"}, {statusCode:200}, [])
 			@request.get.callsArgWith(1, {something:"wrong"}, {statusCode:200}, [])
-			@handler.requestTags user_id, (err, allTags)=>
+			@handler._requestTags user_id, (err, allTags)=>
 				allTags.length.should.equal 0
 				allTags.length.should.equal 0
 				assert.isDefined err
 				assert.isDefined err
 				done()
 				done()
 
 
 		it 'should return an err and empty array on no body', (done)->
 		it 'should return an err and empty array on no body', (done)->
 			@request.get.callsArgWith(1, {something:"wrong"}, {statusCode:200}, undefined)
 			@request.get.callsArgWith(1, {something:"wrong"}, {statusCode:200}, undefined)
-			@handler.requestTags user_id, (err, allTags)=>
+			@handler._requestTags user_id, (err, allTags)=>
 				allTags.length.should.equal 0
 				allTags.length.should.equal 0
 				assert.isDefined err
 				assert.isDefined err
 				done()
 				done()
 
 
 		it 'should return an err and empty array on non 200 response', (done)->
 		it 'should return an err and empty array on non 200 response', (done)->
 			@request.get.callsArgWith(1, null, {statusCode:201}, [])
 			@request.get.callsArgWith(1, null, {statusCode:201}, [])
-			@handler.requestTags user_id, (err, allTags)=>
+			@handler._requestTags user_id, (err, allTags)=>
 				allTags.length.should.equal 0
 				allTags.length.should.equal 0
 				assert.isDefined err
 				assert.isDefined err
 				done()
 				done()
 
 
 		it 'should return an err and empty array on no body and no response', (done)->
 		it 'should return an err and empty array on no body and no response', (done)->
 			@request.get.callsArgWith(1, {something:"wrong"}, undefined, undefined)
 			@request.get.callsArgWith(1, {something:"wrong"}, undefined, undefined)
-			@handler.requestTags user_id, (err, allTags)=>
+			@handler._requestTags user_id, (err, allTags)=>
 				allTags.length.should.equal 0
 				allTags.length.should.equal 0
 				assert.isDefined err
 				assert.isDefined err
 				done()
 				done()
@@ -93,6 +93,24 @@ describe 'TagsHandler', ->
 				allTags.length.should.equal 0
 				allTags.length.should.equal 0
 				_.size(projectGroupedTags).should.equal 0
 				_.size(projectGroupedTags).should.equal 0
 	
 	
+	describe "createTag", ->
+		beforeEach ->
+			@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "")
+			@handler.createTag user_id, @name = "tag_name", @callback
+		
+		it "should send a request to the tag backend", ->
+			@request.post
+				.calledWith({
+					url: "#{tagsUrl}/user/#{user_id}/tag"
+					json:
+						name: @name
+					timeout: 1000
+				})
+				.should.equal true
+		
+		it "should call the callback with no error", ->
+			@callback.calledWith(null).should.equal true
+	
 	describe "deleteTag", ->
 	describe "deleteTag", ->
 		describe "successfully", ->
 		describe "successfully", ->
 			beforeEach ->
 			beforeEach ->