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

Show V1 (OL) projects in project list
This commit is contained in:
Alasdair Smith 2017-11-20 15:04:10 +00:00 committed by GitHub
commit 51ec0ab4c7
10 changed files with 172 additions and 44 deletions

View file

@ -24,6 +24,7 @@ AnalyticsManager = require "../Analytics/AnalyticsManager"
Sources = require "../Authorization/Sources"
TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
Modules = require '../../infrastructure/Modules'
crypto = require 'crypto'
module.exports = ProjectController =
@ -148,6 +149,11 @@ module.exports = ProjectController =
NotificationsHandler.getUserNotifications user_id, cb
projects: (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)->
LimitationsManager.userHasSubscriptionOrIsGroupMember currentUser, cb
user: (cb) ->
@ -157,11 +163,12 @@ module.exports = ProjectController =
logger.err err:err, "error getting data for project list page"
return next(err)
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)->
notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
return notification
projects = ProjectController._buildProjectList results.projects
projects = ProjectController._buildProjectList results.projects, results.v1Projects?.projects
user = results.user
ProjectController._injectProjectOwners projects, (error, projects) ->
return next(error) if error?
@ -173,6 +180,8 @@ module.exports = ProjectController =
notifications: notifications or []
user: user
hasSubscription: results.hasSubscription[0]
isShowingV1Projects: results.v1Projects?
noV1Connection: results.v1Projects?.noConnection
}
if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key?
@ -390,7 +399,7 @@ module.exports = ProjectController =
showLinkSharingOnboarding: showLinkSharingOnboarding
timer.done()
_buildProjectList: (allProjects)->
_buildProjectList: (allProjects, v1Projects = [])->
{owned, readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly} = allProjects
projects = []
for project in owned
@ -400,6 +409,8 @@ module.exports = ProjectController =
projects.push ProjectController._buildProjectViewModel(project, "readWrite", Sources.INVITE)
for project in readOnly
projects.push ProjectController._buildProjectViewModel(project, "readOnly", Sources.INVITE)
for project in v1Projects
projects.push ProjectController._buildV1ProjectViewModel(project)
# Token-access
# Only add these projects if they're not already present, this gives us cascading access
# from 'owner' => 'token-read-only'
@ -424,9 +435,20 @@ module.exports = ProjectController =
archived: !!project.archived
owner_ref: project.owner_ref
tokens: project.tokens
isV1Project: false
}
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) ->) ->
users = {}
for project in projects

View file

@ -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}}

View file

@ -114,6 +114,10 @@
) #{translate("delete_forever")}
.row.row-spaced
if noV1Connection
.col-xs-12
.alert.alert-warning No V1 Connection
.col-xs-12
.card.card-thin.project-list-card
ul.list-unstyled.project-list.structured-list(
@ -142,47 +146,15 @@
ng-repeat="project in visibleProjects | orderBy:predicate:reverse",
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(
ng-if="visibleProjects.length == 0",
ng-cloak

View file

@ -45,6 +45,9 @@
a(href) #{translate("shared_with_you")}
li(ng-class="{active: (filter == 'archived')}", ng-click="filterProjects('archived')")
a(href) #{translate("deleted_projects")}
if isShowingV1Projects
li(ng-class="{active: (filter == 'v1')}", ng-click="filterProjects('v1')")
a(href) #{translate("v1_projects")}
li.separator
h2 #{translate("folders")}
li.tag(
@ -62,6 +65,11 @@
)
span.name {{tag.name}}
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)
a.dropdown-toggle(
href="#",

View file

@ -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}}

View file

@ -116,6 +116,10 @@ define [
if $scope.filter == "shared" and project.accessLevel == "owner"
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
if $scope.filter == "owned" and project.accessLevel != "owner"
visible = false
@ -129,6 +133,9 @@ define [
if project.archived
visible = false
if $scope.filter == "v1" and !project.isV1Project
visible = false
if visible
$scope.visibleProjects.push project
else

View file

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

View file

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

View file

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

View file

@ -66,6 +66,9 @@ describe "ProjectController", ->
protectTokens: sinon.stub()
@CollaboratorsHandler =
userIsTokenMember: sinon.stub().callsArgWith(2, null, false)
@Modules =
hooks:
fire: sinon.stub()
@ProjectController = SandboxedModule.require modulePath, requires:
"settings-sharelatex":@settings
"logger-sharelatex":
@ -93,6 +96,7 @@ describe "ProjectController", ->
"../Analytics/AnalyticsManager": @AnalyticsManager
"../TokenAccess/TokenAccessHandler": @TokenAccessHandler
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler
"../../infrastructure/Modules": @Modules
@projectName = "£12321jkj9ujkljds"
@req =
@ -263,6 +267,7 @@ describe "ProjectController", ->
@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
@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)->
@res.render = (pageName, opts)=>
@ -295,6 +300,53 @@ describe "ProjectController", ->
done()
@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", ->
beforeEach ->
@ -338,6 +390,7 @@ describe "ProjectController", ->
@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
@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)->
@res.render = (pageName, opts)=>