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

Merge pull request #87 from sharelatex/as-fetch-ol-projects

Show V1 (OL) projects in project list
Alasdair Smith 9 лет назад
Родитель
Сommit
51ec0ab4c7

+ 25 - 3
services/web/app/coffee/Features/Project/ProjectController.coffee

@@ -24,6 +24,7 @@ AnalyticsManager = require "../Analytics/AnalyticsManager"
 Sources = require "../Authorization/Sources"
 Sources = require "../Authorization/Sources"
 TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
 TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
 CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
 CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
+Modules = require '../../infrastructure/Modules'
 crypto = require 'crypto'
 crypto = require 'crypto'
 
 
 module.exports = ProjectController =
 module.exports = ProjectController =
@@ -148,6 +149,11 @@ module.exports = ProjectController =
 				NotificationsHandler.getUserNotifications user_id, cb
 				NotificationsHandler.getUserNotifications user_id, cb
 			projects: (cb)->
 			projects: (cb)->
 				ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref tokens', cb
 				ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref tokens', cb
+			v1Projects: (cb) ->
+				Modules.hooks.fire "findAllV1Projects", user_id, (error, projects = []) ->
+					if error? and error.message == 'No V1 connection'
+						return cb(null, projects: [], tags: [], noConnection: true)
+					return cb(error, projects[0]) # hooks.fire returns an array of results, only need first
 			hasSubscription: (cb)->
 			hasSubscription: (cb)->
 				LimitationsManager.userHasSubscriptionOrIsGroupMember currentUser, cb
 				LimitationsManager.userHasSubscriptionOrIsGroupMember currentUser, cb
 			user: (cb) ->
 			user: (cb) ->
@@ -157,11 +163,12 @@ module.exports = ProjectController =
 					logger.err err:err, "error getting data for project list page"
 					logger.err err:err, "error getting data for project list page"
 					return next(err)
 					return next(err)
 				logger.log results:results, user_id:user_id, "rendering project list"
 				logger.log results:results, user_id:user_id, "rendering project list"
-				tags = results.tags[0]
+				v1Tags = results.v1Projects?.tags or []
+				tags = results.tags[0].concat(v1Tags)
 				notifications = require("underscore").map results.notifications, (notification)->
 				notifications = require("underscore").map results.notifications, (notification)->
 					notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
 					notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
 					return notification
 					return notification
-				projects = ProjectController._buildProjectList results.projects
+				projects = ProjectController._buildProjectList results.projects, results.v1Projects?.projects
 				user = results.user
 				user = results.user
 				ProjectController._injectProjectOwners projects, (error, projects) ->
 				ProjectController._injectProjectOwners projects, (error, projects) ->
 					return next(error) if error?
 					return next(error) if error?
@@ -173,6 +180,8 @@ module.exports = ProjectController =
 						notifications: notifications or []
 						notifications: notifications or []
 						user: user
 						user: user
 						hasSubscription: results.hasSubscription[0]
 						hasSubscription: results.hasSubscription[0]
+						isShowingV1Projects: results.v1Projects?
+						noV1Connection: results.v1Projects?.noConnection
 					}
 					}
 
 
 					if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key?
 					if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key?
@@ -390,7 +399,7 @@ module.exports = ProjectController =
 					showLinkSharingOnboarding: showLinkSharingOnboarding
 					showLinkSharingOnboarding: showLinkSharingOnboarding
 				timer.done()
 				timer.done()
 
 
-	_buildProjectList: (allProjects)->
+	_buildProjectList: (allProjects, v1Projects = [])->
 		{owned, readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly} = allProjects
 		{owned, readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly} = allProjects
 		projects = []
 		projects = []
 		for project in owned
 		for project in owned
@@ -400,6 +409,8 @@ module.exports = ProjectController =
 			projects.push ProjectController._buildProjectViewModel(project, "readWrite", Sources.INVITE)
 			projects.push ProjectController._buildProjectViewModel(project, "readWrite", Sources.INVITE)
 		for project in readOnly
 		for project in readOnly
 			projects.push ProjectController._buildProjectViewModel(project, "readOnly", Sources.INVITE)
 			projects.push ProjectController._buildProjectViewModel(project, "readOnly", Sources.INVITE)
+		for project in v1Projects
+			projects.push ProjectController._buildV1ProjectViewModel(project)
 		# Token-access
 		# Token-access
 		#   Only add these projects if they're not already present, this gives us cascading access
 		#   Only add these projects if they're not already present, this gives us cascading access
 		#   from 'owner' => 'token-read-only'
 		#   from 'owner' => 'token-read-only'
@@ -424,9 +435,20 @@ module.exports = ProjectController =
 			archived: !!project.archived
 			archived: !!project.archived
 			owner_ref: project.owner_ref
 			owner_ref: project.owner_ref
 			tokens: project.tokens
 			tokens: project.tokens
+			isV1Project: false
 		}
 		}
 		return model
 		return model
 
 
+	_buildV1ProjectViewModel: (project) ->
+		{
+			id: project.id
+			name: project.title
+			lastUpdated: new Date(project.updated_at * 1000) # Convert from epoch
+			accessLevel: "readOnly",
+			archived: project.removed || project.archived
+			isV1Project: true
+		}
+
 	_injectProjectOwners: (projects, callback = (error, projects) ->) ->
 	_injectProjectOwners: (projects, callback = (error, projects) ->) ->
 		users = {}
 		users = {}
 		for project in projects
 		for project in projects

+ 40 - 0
services/web/app/views/project/list/item.pug

@@ -0,0 +1,40 @@
+.col-xs-6
+	input.select-item(
+		select-individual,
+		type="checkbox",
+		ng-model="project.selected"
+		stop-propagation="click"
+		aria-label=translate('select_project') + " '{{ project.name }}'"
+	)
+	span
+		a.projectName(
+			ng-href="{{projectLink(project)}}"
+			stop-propagation="click"
+		) {{project.name}}
+		span(
+			ng-controller="TagListController"
+		)
+			.tag-label(
+				ng-repeat='tag in project.tags'
+				stop-propagation="click"
+			)
+				a.label.label-default.tag-label-name(
+					href,
+					ng-click="selectTag(tag)"
+				) {{tag.name}}
+				a.label.label-default.tag-label-remove(
+					href
+					ng-click="removeProjectFromTag(project, tag)"
+				) ×
+
+.col-xs-2
+	span.owner {{ownerName()}}
+	span(ng-if="isLinkSharingProject(project)")
+		|  
+		i.fa.fa-link.small(
+			tooltip=translate("link_sharing")
+			tooltip-placement="right"
+			tooltip-append-to-body="true"
+		)
+.col-xs-4
+	span.last-modified {{project.lastUpdated | formatDate}}

+ 13 - 41
services/web/app/views/project/list/project-list.pug

@@ -114,6 +114,10 @@
 					) #{translate("delete_forever")}
 					) #{translate("delete_forever")}
 
 
 .row.row-spaced
 .row.row-spaced
+	if noV1Connection
+		.col-xs-12
+			.alert.alert-warning No V1 Connection
+
 	.col-xs-12
 	.col-xs-12
 		.card.card-thin.project-list-card
 		.card.card-thin.project-list-card
 			ul.list-unstyled.project-list.structured-list(
 			ul.list-unstyled.project-list.structured-list(
@@ -142,47 +146,15 @@
 					ng-repeat="project in visibleProjects | orderBy:predicate:reverse",
 					ng-repeat="project in visibleProjects | orderBy:predicate:reverse",
 					ng-controller="ProjectListItemController"
 					ng-controller="ProjectListItemController"
 				)
 				)
-					.row(select-row)
-						.col-xs-6
-							input.select-item(
-								select-individual,
-								type="checkbox",
-								ng-model="project.selected"
-								stop-propagation="click"
-								aria-label=translate('select_project') + " '{{ project.name }}'"
-							)
-							span
-								a.projectName(
-									ng-href="{{projectLink(project)}}"
-									stop-propagation="click"
-								) {{project.name}}
-								span(
-									ng-controller="TagListController"
-								)
-									.tag-label(
-										ng-repeat='tag in project.tags'
-										stop-propagation="click"
-									)
-										a.label.label-default.tag-label-name(
-											href,
-											ng-click="selectTag(tag)"
-										) {{tag.name}} 
-										a.label.label-default.tag-label-remove(
-											href
-											ng-click="removeProjectFromTag(project, tag)"
-										) ×
-
-						.col-xs-2
-							span.owner {{ownerName()}}
-							span(ng-if="isLinkSharingProject(project)")
-								|  
-								i.fa.fa-link.small(
-									tooltip=translate("link_sharing")
-									tooltip-placement="right"
-									tooltip-append-to-body="true"
-								)
-						.col-xs-4
-							span.last-modified {{project.lastUpdated | formatDate}}
+					.row(
+						ng-if="!project.isV1Project"
+						select-row
+					)
+						include ./item
+					.row(
+						ng-if="project.isV1Project"
+					)
+						include ./v1-item
 				li(
 				li(
 					ng-if="visibleProjects.length == 0",
 					ng-if="visibleProjects.length == 0",
 					ng-cloak
 					ng-cloak

+ 8 - 0
services/web/app/views/project/list/side-bar.pug

@@ -45,6 +45,9 @@
 			a(href) #{translate("shared_with_you")}
 			a(href) #{translate("shared_with_you")}
 		li(ng-class="{active: (filter == 'archived')}", ng-click="filterProjects('archived')")
 		li(ng-class="{active: (filter == 'archived')}", ng-click="filterProjects('archived')")
 			a(href) #{translate("deleted_projects")}
 			a(href) #{translate("deleted_projects")}
+		if isShowingV1Projects
+			li(ng-class="{active: (filter == 'v1')}", ng-click="filterProjects('v1')")
+				a(href) #{translate("v1_projects")}
 		li.separator
 		li.separator
 			h2 #{translate("folders")}
 			h2 #{translate("folders")}
 		li.tag(
 		li.tag(
@@ -62,6 +65,11 @@
 				)
 				)
 				span.name {{tag.name}} 
 				span.name {{tag.name}} 
 					span.subdued  ({{tag.project_ids.length}})
 					span.subdued  ({{tag.project_ids.length}})
+					span.v1-badge(
+						ng-if="tag.isV1",
+						ng-cloak,
+						aria-label=translate("v1_badge")
+					)
 			span.dropdown.tag-menu(dropdown)
 			span.dropdown.tag-menu(dropdown)
 				a.dropdown-toggle(
 				a.dropdown-toggle(
 					href="#",
 					href="#",

+ 10 - 0
services/web/app/views/project/list/v1-item.pug

@@ -0,0 +1,10 @@
+.col-xs-8
+	span.v1-badge(aria-label=translate("v1_badge"))
+	span
+		a.projectName(
+			href=settings.overleaf.host + "/{{project.id}}"
+			stop-propagation="click"
+		) {{project.name}}
+
+.col-xs-4
+	span.last-modified {{project.lastUpdated | formatDate}}

+ 7 - 0
services/web/public/coffee/main/project-list/project-list.coffee

@@ -116,6 +116,10 @@ define [
 				if $scope.filter == "shared" and project.accessLevel == "owner"
 				if $scope.filter == "shared" and project.accessLevel == "owner"
 					visible = false
 					visible = false
 
 
+				# Hide projects from V1 if we only want to see shared projects
+				if $scope.filter == "shared" and project.isV1Project
+					visible = false
+
 				# Hide projects we don't own if we only want to see owned projects
 				# Hide projects we don't own if we only want to see owned projects
 				if $scope.filter == "owned" and project.accessLevel != "owner"
 				if $scope.filter == "owned" and project.accessLevel != "owner"
 					visible = false
 					visible = false
@@ -129,6 +133,9 @@ define [
 					if project.archived
 					if project.archived
 						visible = false
 						visible = false
 
 
+				if $scope.filter == "v1" and !project.isV1Project
+					visible = false
+
 				if visible
 				if visible
 					$scope.visibleProjects.push project
 					$scope.visibleProjects.push project
 				else
 				else

+ 1 - 0
services/web/public/stylesheets/_style_includes.less

@@ -78,6 +78,7 @@
 @import "app/invite.less";
 @import "app/invite.less";
 @import "app/review-features-page.less";
 @import "app/review-features-page.less";
 @import "app/error-pages.less";
 @import "app/error-pages.less";
+@import "app/v1-badge.less";
 
 
 @import	"../js/libs/pdfListView/TextLayer.css";
 @import	"../js/libs/pdfListView/TextLayer.css";
 @import	"../js/libs/pdfListView/AnnotationsLayer.css";
 @import	"../js/libs/pdfListView/AnnotationsLayer.css";

+ 5 - 0
services/web/public/stylesheets/app/project-list.less

@@ -366,6 +366,11 @@ ul.project-list {
 				border-top-left-radius: 0;
 				border-top-left-radius: 0;
 				border-bottom-left-radius: 0;
 				border-bottom-left-radius: 0;
 			}
 			}
+
+		.v1-badge {
+			margin-right: 9px;
+			margin-left: 7px;
+		}
 	}
 	}
 	i.tablesort {
 	i.tablesort {
 		padding-left: 8px;
 		padding-left: 8px;

+ 10 - 0
services/web/public/stylesheets/app/v1-badge.less

@@ -0,0 +1,10 @@
+.v1-badge {
+	&:extend(.label);
+	&:extend(.label-default);
+	vertical-align: 11%;
+	padding: 1px 3px;
+	margin: 0 6px;
+	&:before {
+		content: "V1";
+	}
+}

+ 53 - 0
services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee

@@ -66,6 +66,9 @@ describe "ProjectController", ->
 			protectTokens: sinon.stub()
 			protectTokens: sinon.stub()
 		@CollaboratorsHandler =
 		@CollaboratorsHandler =
 			userIsTokenMember: sinon.stub().callsArgWith(2, null, false)
 			userIsTokenMember: sinon.stub().callsArgWith(2, null, false)
+		@Modules =
+			hooks:
+				fire: sinon.stub()
 		@ProjectController = SandboxedModule.require modulePath, requires:
 		@ProjectController = SandboxedModule.require modulePath, requires:
 			"settings-sharelatex":@settings
 			"settings-sharelatex":@settings
 			"logger-sharelatex":
 			"logger-sharelatex":
@@ -93,6 +96,7 @@ describe "ProjectController", ->
 			"../Analytics/AnalyticsManager": @AnalyticsManager
 			"../Analytics/AnalyticsManager": @AnalyticsManager
 			"../TokenAccess/TokenAccessHandler": @TokenAccessHandler
 			"../TokenAccess/TokenAccessHandler": @TokenAccessHandler
 			"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler
 			"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler
+			"../../infrastructure/Modules": @Modules
 
 
 		@projectName = "£12321jkj9ujkljds"
 		@projectName = "£12321jkj9ujkljds"
 		@req =
 		@req =
@@ -263,6 +267,7 @@ describe "ProjectController", ->
 			@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
 			@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
 			@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
 			@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
 			@ProjectGetter.findAllUsersProjects.callsArgWith(2, null, @allProjects)
 			@ProjectGetter.findAllUsersProjects.callsArgWith(2, null, @allProjects)
+			@Modules.hooks.fire.withArgs('findAllV1Projects', @user._id).yields(undefined) # Without integration module hook, cb returns undefined
 
 
 		it "should render the project/list page", (done)->
 		it "should render the project/list page", (done)->
 			@res.render = (pageName, opts)=>
 			@res.render = (pageName, opts)=>
@@ -295,6 +300,53 @@ describe "ProjectController", ->
 				done()
 				done()
 			@ProjectController.projectListPage @req, @res
 			@ProjectController.projectListPage @req, @res
 
 
+		describe 'with overleaf-integration-web-module hook', ->
+			beforeEach ->
+				@V1Response =
+					projects: [
+						{ id: '123mockV1Id', title: 'mock title', updated_at: 1509616411, removed: false, archived: false }
+						{ id: '456mockV1Id', title: 'mock title 2', updated_at: 1509616411, removed: true, archived: false }
+					],
+					tags: [
+						{ name: 'mock tag', project_ids: ['123mockV1Id'] }
+					]
+				@Modules.hooks.fire.withArgs('findAllV1Projects', @user._id).yields(null, [@V1Response]) # Need to wrap response in array, as multiple hooks could fire
+
+			it 'should include V1 projects', (done) ->
+				@res.render = (pageName, opts) =>
+					opts.projects.length.should.equal (
+						@projects.length +
+						@collabertions.length +
+						@readOnly.length +
+						@tokenReadAndWrite.length +
+						@tokenReadOnly.length +
+						@V1Response.projects.length
+					)
+					opts.projects.forEach (p) ->
+						# Check properties correctly mapped from V1
+						expect(p).to.have.property 'id'
+						expect(p).to.have.property 'name'
+						expect(p).to.have.property 'lastUpdated'
+						expect(p).to.have.property 'accessLevel'
+						expect(p).to.have.property 'archived'
+					done()
+				@ProjectController.projectListPage @req, @res
+
+			it 'should include V1 tags', (done) ->
+				@res.render = (pageName, opts) =>
+					opts.tags.length.should.equal (@tags.length + @V1Response.tags.length)
+					opts.tags.forEach (t) ->
+						expect(t).to.have.property 'name'
+						expect(t).to.have.property 'project_ids'
+					done()
+				@ProjectController.projectListPage @req, @res
+
+			it 'should have isShowingV1Projects flag', (done) ->
+				@res.render = (pageName, opts) =>
+					opts.isShowingV1Projects.should.equal true
+					done()
+				@ProjectController.projectListPage @req, @res
+
 	describe "projectListPage with duplicate projects", ->
 	describe "projectListPage with duplicate projects", ->
 
 
 		beforeEach ->
 		beforeEach ->
@@ -338,6 +390,7 @@ describe "ProjectController", ->
 			@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
 			@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
 			@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
 			@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
 			@ProjectGetter.findAllUsersProjects.callsArgWith(2, null, @allProjects)
 			@ProjectGetter.findAllUsersProjects.callsArgWith(2, null, @allProjects)
+			@Modules.hooks.fire.withArgs('findAllV1Projects', @user._id).yields(undefined) # Without integration module hook, cb returns undefined
 
 
 		it "should render the project/list page", (done)->
 		it "should render the project/list page", (done)->
 			@res.render = (pageName, opts)=>
 			@res.render = (pageName, opts)=>