From 55597b9279fe2574d811f339dec4c62ca1460cfc Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 2 Aug 2018 16:19:23 +0100 Subject: [PATCH 01/15] inform v1 when confirming affiliation emails --- .../app/coffee/Features/Institutions/InstitutionsAPI.coffee | 4 ++-- services/web/app/coffee/Features/User/UserUpdater.coffee | 2 +- .../test/unit/coffee/Institutions/InstitutionsAPITests.coffee | 4 +++- services/web/test/unit/coffee/User/UserUpdaterTests.coffee | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index 8ce39d68e3..c285ba89ff 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -25,11 +25,11 @@ module.exports = InstitutionsAPI = callback = affiliationOptions affiliationOptions = {} - { university, department, role } = affiliationOptions + { university, department, role, confirmed } = affiliationOptions makeAffiliationRequest { method: 'POST' path: "/api/v2/users/#{userId.toString()}/affiliations" - body: { email, university, department, role } + body: { email, university, department, role, confirmed } defaultErrorMessage: "Couldn't create affiliation" }, callback diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 9ee81c1ca3..119bf07285 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -166,7 +166,7 @@ module.exports = UserUpdater = email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? logger.log {userId, email}, 'confirming user email' - addAffiliation userId, email, (error) => + addAffiliation userId, email, { confirmed: true }, (error) => if error? logger.err error: error, 'problem adding affiliation while confirming email' return callback(error) diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 7500e46e40..79859aa93d 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -74,6 +74,7 @@ describe "InstitutionsAPI", -> university: { id: 1 } role: 'Prof' department: 'Math' + confirmed: true @InstitutionsAPI.addAffiliation @stubbedUser._id, @newEmail, affiliationOptions, (err)=> should.not.exist(err) @request.calledOnce.should.equal true @@ -83,11 +84,12 @@ describe "InstitutionsAPI", -> requestOptions.method.should.equal 'POST' body = requestOptions.body - Object.keys(body).length.should.equal 4 + Object.keys(body).length.should.equal 5 body.email.should.equal @newEmail body.university.should.equal affiliationOptions.university body.department.should.equal affiliationOptions.department body.role.should.equal affiliationOptions.role + body.confirmed.should.equal affiliationOptions.confirmed done() it 'handle error', (done)-> diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index 17f691edba..5ce5e1fc6a 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -241,7 +241,7 @@ describe "UserUpdater", -> @UserUpdater.confirmEmail @stubbedUser._id, @newEmail, (err)=> should.not.exist(err) @addAffiliation.calledOnce.should.equal true - sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail) + sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail, { confirmed: true } ) done() it 'handle error', (done)-> @@ -264,7 +264,7 @@ describe "UserUpdater", -> done() it 'handle affiliation error', (done)-> - @addAffiliation.callsArgWith(2, new Error('nope')) + @addAffiliation.callsArgWith(3, new Error('nope')) @UserUpdater.confirmEmail @stubbedUser._id, @newEmail, (err)=> should.exist(err) @UserUpdater.updateUser.called.should.equal false From 96ffeef73d9ea4cecda6fc51770e2abc38f04d3b Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 9 Aug 2018 08:20:34 +0100 Subject: [PATCH 02/15] send fixed confirmed date to v1 for affiliations --- .../app/coffee/Features/Institutions/InstitutionsAPI.coffee | 4 ++-- services/web/app/coffee/Features/User/UserUpdater.coffee | 2 +- .../test/unit/coffee/Institutions/InstitutionsAPITests.coffee | 4 ++-- services/web/test/unit/coffee/User/UserUpdaterTests.coffee | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index c285ba89ff..0d30b925aa 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -25,11 +25,11 @@ module.exports = InstitutionsAPI = callback = affiliationOptions affiliationOptions = {} - { university, department, role, confirmed } = affiliationOptions + { university, department, role, confirmedAt } = affiliationOptions makeAffiliationRequest { method: 'POST' path: "/api/v2/users/#{userId.toString()}/affiliations" - body: { email, university, department, role, confirmed } + body: { email, university, department, role, confirmedAt } defaultErrorMessage: "Couldn't create affiliation" }, callback diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 119bf07285..3abffdf444 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -166,7 +166,7 @@ module.exports = UserUpdater = email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? logger.log {userId, email}, 'confirming user email' - addAffiliation userId, email, { confirmed: true }, (error) => + addAffiliation userId, email, {confirmedAt: confirmedAt}, (error) => if error? logger.err error: error, 'problem adding affiliation while confirming email' return callback(error) diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 79859aa93d..04f0b61e8e 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -74,7 +74,7 @@ describe "InstitutionsAPI", -> university: { id: 1 } role: 'Prof' department: 'Math' - confirmed: true + confirmedAt: new Date() @InstitutionsAPI.addAffiliation @stubbedUser._id, @newEmail, affiliationOptions, (err)=> should.not.exist(err) @request.calledOnce.should.equal true @@ -89,7 +89,7 @@ describe "InstitutionsAPI", -> body.university.should.equal affiliationOptions.university body.department.should.equal affiliationOptions.department body.role.should.equal affiliationOptions.role - body.confirmed.should.equal affiliationOptions.confirmed + body.confirmedAt.should.equal affiliationOptions.confirmedAt done() it 'handle error', (done)-> diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index 5ce5e1fc6a..3be7733153 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -241,7 +241,7 @@ describe "UserUpdater", -> @UserUpdater.confirmEmail @stubbedUser._id, @newEmail, (err)=> should.not.exist(err) @addAffiliation.calledOnce.should.equal true - sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail, { confirmed: true } ) + sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail, { confirmedAt: new Date() } ) done() it 'handle error', (done)-> From 27823d3e06f56cc0a14f3a5d8c4acc1d490b1141 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 15:31:15 +0100 Subject: [PATCH 03/15] Show history entries for the last 24 hours for free users. --- .../ide/history/HistoryV2Manager.coffee | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 3f231c046b..004221674f 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -82,6 +82,8 @@ define [ viewMode: null nextBeforeTimestamp: null atEnd: false + userHasFullFeature: @$scope.project?.features?.versioning or false + freeHistoryLimitHit: false selection: { label: null updates: [] @@ -232,9 +234,11 @@ define [ @$scope.history.labels = @_sortLabelsByVersionAndDate response.labels.data @_loadUpdates(updatesData.updates) @$scope.history.nextBeforeTimestamp = updatesData.nextBeforeTimestamp - if !updatesData.nextBeforeTimestamp? + if !updatesData.nextBeforeTimestamp? or @$scope.history.freeHistoryLimitHit @$scope.history.atEnd = true @$scope.history.loading = false + if @$scope.history.updates.length == 0 + @$scope.history.loadingFileTree = false .catch (error) => { status, statusText } = error @$scope.history.error = { status, statusText } @@ -387,23 +391,34 @@ define [ _loadUpdates: (updates = []) -> previousUpdate = @$scope.history.updates[@$scope.history.updates.length - 1] - - for update in updates or [] + dateTimeNow = new Date() + timestamp24hoursAgo = dateTimeNow.setDate(dateTimeNow.getDate() - 1) + cutOffIndex = null + + for update, i in updates or [] for user in update.meta.users or [] if user? user.hue = ColorManager.getHueForUserId(user.id) if !previousUpdate? or !moment(previousUpdate.meta.end_ts).isSame(update.meta.end_ts, "day") update.meta.first_in_day = true - + update.selectedFrom = false update.selectedTo = false update.inSelection = false previousUpdate = update + if !@$scope.history.userHasFullFeature and update.meta.end_ts < timestamp24hoursAgo + cutOffIndex = i + @$scope.history.freeHistoryLimitHit = true + break + firstLoad = @$scope.history.updates.length == 0 + if !@$scope.history.userHasFullFeature and cutOffIndex? + updates = updates.slice 0, cutOffIndex + @$scope.history.updates = @$scope.history.updates.concat(updates) From fc424aee9e3a2bc8cf0126db3684845bf14f50e6 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 15:31:59 +0100 Subject: [PATCH 04/15] Adapt styles to avoid layout breaking/showing options that do not make sense when no history entries are loaded. --- services/web/app/views/project/editor/history/toolbarV2.pug | 6 +++--- services/web/public/stylesheets/app/editor/history-v2.less | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index a683facba4..2acb93854b 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -6,7 +6,7 @@ i.fa.fa-spin.fa-refresh |    #{translate("loading")}... span.history-toolbar-selected-version( - ng-show="!history.loadingFileTree && !history.showOnlyLabels && !history.error" + ng-show="!history.loadingFileTree && !history.showOnlyLabels && history.selection.updates.length && !history.error" ) #{translate("browsing_project_as_of")}  time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }} span.history-toolbar-selected-version( @@ -19,13 +19,13 @@ button.history-toolbar-btn( ng-click="showAddLabelDialog();" ng-if="!history.showOnlyLabels" - ng-disabled="history.loadingFileTree" + ng-disabled="history.loadingFileTree || history.selection.updates.length == 0" ) i.fa.fa-tag |  #{translate("history_label_this_version")} button.history-toolbar-btn( ng-click="toggleHistoryViewMode();" - ng-disabled="history.loadingFileTree" + ng-disabled="history.loadingFileTree || history.selection.updates.length == 0" ) i.fa.fa-exchange |  #{translate("compare_to_another_version")} diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index cfefbb462c..8c944062b7 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -19,6 +19,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + margin-right: (@line-height-computed / 2); } .history-toolbar-time, .history-toolbar-selected-label { @@ -33,7 +34,7 @@ .btn-xs; padding-left: @padding-small-horizontal; padding-right: @padding-small-horizontal; - margin-left: (@line-height-computed / 2); + margin-right: (@line-height-computed / 2); } .history-toolbar-entries-list { flex: 0 0 @changesListWidth; From 96aa418b94a01bca4dab8b3e010a71179cd83c20 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 16:04:31 +0100 Subject: [PATCH 05/15] Show upgrade prompts when the free history limit is hit. --- .../web/app/views/project/editor/history.pug | 41 ----------------- .../project/editor/history/entriesListV2.pug | 44 +++++++++++++++++++ .../components/historyEntriesList.coffee | 2 + .../stylesheets/app/editor/history-v2.less | 6 +++ 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index 35de68957f..f968639cca 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -1,45 +1,4 @@ div#history(ng-show="ui.view == 'history'") - span - .upgrade-prompt(ng-if="project.features.versioning === false && ui.view === 'history'") - .message(ng-if="project.owner._id == user.id") - p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})} - p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")} - ul.list-unstyled - li - i.fa.fa-check   - | #{translate("unlimited_projects")} - - li - i.fa.fa-check   - | #{translate("collabs_per_proj", {collabcount:'Multiple'})} - - li - i.fa.fa-check   - | #{translate("full_doc_history")} - - li - i.fa.fa-check   - | #{translate("sync_to_dropbox")} - - li - i.fa.fa-check   - | #{translate("sync_to_github")} - - li - i.fa.fa-check   - |#{translate("compile_larger_projects")} - p.text-center(ng-controller="FreeTrialModalController") - a.btn.btn-success( - href - ng-class="buttonClass" - ng-click="startFreeTrial('history')" - ) #{translate("start_free_trial")} - - .message(ng-show="project.owner._id != user.id") - p #{translate("ask_proj_owner_to_upgrade_for_history")} - p - a.small(href, ng-click="toggleHistory()") #{translate("cancel")} - include ./history/entriesListV1 include ./history/entriesListV2 diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 5e4c4b66c6..640a761296 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -6,11 +6,13 @@ aside.change-list( ng-if="!history.showOnlyLabels && !history.error" entries="history.updates" current-user="user" + current-user-is-owner="project.owner._id === user.id" users="projectUsers" load-entries="loadMore()" load-disabled="history.loading || history.atEnd" load-initialize="ui.view == 'history'" is-loading="history.loading" + free-history-limit-hit="history.freeHistoryLimitHit" on-entry-select="handleEntrySelect(selectedEntry)" on-label-delete="handleLabelDelete(label)" ) @@ -134,6 +136,48 @@ script(type="text/ng-template", id="historyEntriesListTpl") .loading(ng-show="$ctrl.isLoading") i.fa.fa-spin.fa-refresh |    #{translate("loading")}... + .history-entries-list-upgrade-prompt( + ng-if="$ctrl.freeHistoryLimitHit && $ctrl.currentUserIsOwner" + ng-controller="FreeTrialModalController" + ) + p #{translate("currently_seeing_only_24_hrs_history")} + p: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})} + ul.list-unstyled + li + i.fa.fa-check   + | #{translate("unlimited_projects")} + + li + i.fa.fa-check   + | #{translate("collabs_per_proj", {collabcount:'Multiple'})} + + li + i.fa.fa-check   + | #{translate("full_doc_history")} + + li + i.fa.fa-check   + | #{translate("sync_to_dropbox")} + + li + i.fa.fa-check   + | #{translate("sync_to_github")} + + li + i.fa.fa-check   + |#{translate("compile_larger_projects")} + p.text-center + a.btn.btn-success( + href + ng-class="buttonClass" + ng-click="startFreeTrial('history')" + ) #{translate("start_free_trial")} + p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")} + .history-entries-list-upgrade-prompt( + ng-if="$ctrl.freeHistoryLimitHit && !$ctrl.currentUserIsOwner" + ) + p #{translate("currently_seeing_only_24_hrs_history")} + strong #{translate("ask_proj_owner_to_upgrade_for_full_history")} script(type="text/ng-template", id="historyEntryTpl") .history-entry( diff --git a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee index de4e4f1b92..7e97121868 100644 --- a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee @@ -30,6 +30,8 @@ define [ loadInitialize: "<" isLoading: "<" currentUser: "<" + freeHistoryLimitHit: "<" + currentUserIsOwner: "<" onEntrySelect: "&" onLabelDelete: "&" controller: historyEntriesListController diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 8c944062b7..35190cd662 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -174,6 +174,12 @@ } } +.history-entries-list-upgrade-prompt { + background-color: #FFF; + margin-bottom: 2px; + padding: 5px 10px; +} + .history-labels-list { .history-entries; overflow-y: auto; From 5974afc2e388614715853742f033d0ebe463d4ed Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 16:17:53 +0100 Subject: [PATCH 06/15] Make sure that at least the last update (i.e. the current state) is shown to free users (even if it happened more than 24 hours ago), to allow labelling. --- services/web/public/coffee/ide/history/HistoryV2Manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 004221674f..66b4375c8e 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -410,7 +410,7 @@ define [ previousUpdate = update if !@$scope.history.userHasFullFeature and update.meta.end_ts < timestamp24hoursAgo - cutOffIndex = i + cutOffIndex = i or 1 # Make sure that we show at least one entry (to allow labelling). @$scope.history.freeHistoryLimitHit = true break From f8b85cb8482a5fbe8b66444f3b3a4d95a97ac811 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Tue, 14 Aug 2018 15:29:03 -0500 Subject: [PATCH 07/15] Move link mixins to mixins folder --- services/web/app/views/{_mixins_links.pug => _mixins/links.pug} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/web/app/views/{_mixins_links.pug => _mixins/links.pug} (100%) diff --git a/services/web/app/views/_mixins_links.pug b/services/web/app/views/_mixins/links.pug similarity index 100% rename from services/web/app/views/_mixins_links.pug rename to services/web/app/views/_mixins/links.pug From 45cc278acbceaecc9c88a5fa3b6be9a8d6e57e72 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Tue, 14 Aug 2018 15:30:13 -0500 Subject: [PATCH 08/15] Add FAQ search mixin --- services/web/app/views/_mixins/faq_search.pug | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 services/web/app/views/_mixins/faq_search.pug diff --git a/services/web/app/views/_mixins/faq_search.pug b/services/web/app/views/_mixins/faq_search.pug new file mode 100644 index 0000000000..b5d00f9832 --- /dev/null +++ b/services/web/app/views/_mixins/faq_search.pug @@ -0,0 +1,21 @@ +mixin faq_search(headerText, headerClass) + - if(typeof(settings.algolia) != "undefined" && typeof(settings.algolia.indexes) != "undefined" && typeof(settings.algolia.indexes.wiki) != "undefined") + if headerText + div(class=headerClass) #{headerText} + .wiki(ng-controller="SearchWikiController") + form.project-search.form-horizontal(role="form") + .form-group.has-feedback.has-feedback-left + .col-sm-12 + input.form-control(type='text', ng-model='searchQueryText', ng-keyup='search()', placeholder="Search help library....") + i.fa.fa-search.form-control-feedback-left + i.fa.fa-times.form-control-feedback( + ng-click="clearSearchText()", + style="cursor: pointer;", + ng-show="searchQueryText.length > 0" + ) + + .row + .col-md-12(ng-cloak) + a(ng-href='{{hit.url}}',ng-repeat='hit in hits').search-result.card.card-thin + span(ng-bind-html='hit.name') + div.search-result-content(ng-show="hit.content != ''", ng-bind-html='hit.content') From 09efced3523988488c3a9370d98a017aa1a9ef84 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Fri, 27 Jul 2018 15:50:20 -0500 Subject: [PATCH 09/15] Add pagination mixin and color variables --- services/web/app/views/mixins/_pagination.pug | 74 +++++++++++++++++++ .../public/stylesheets/core/ol-variables.less | 15 ++++ services/web/public/stylesheets/ol-style.less | 1 + 3 files changed, 90 insertions(+) create mode 100644 services/web/app/views/mixins/_pagination.pug diff --git a/services/web/app/views/mixins/_pagination.pug b/services/web/app/views/mixins/_pagination.pug new file mode 100644 index 0000000000..4ac2d924e8 --- /dev/null +++ b/services/web/app/views/mixins/_pagination.pug @@ -0,0 +1,74 @@ +mixin paginate(pages, page_path, max_btns) + //- @param pages.current_page the current page viewed + //- @param pages.total_pages previously calculated, + //- based on total entries and entries per page + //- @param page_path the relative path, minus a trailing slash and page param + //- @param max_btns max number of buttons on either side of the current page + //- button and excludes first, prev, next, last + + if pages && pages.current_page && pages.total_pages + - var max_btns = max_btns || 4 + - var prev_page = Math.max(parseInt(pages.current_page, 10) - max_btns, 1) + - var next_page = parseInt(pages.current_page, 10) + 1 + - var next_index = 0; + - var full_page_path = page_path + "/page/" + + nav(role="navigation" aria-label="Pagination Navigation") + ul.pagination + if pages.current_page > 1 + li + a( + aria-label="Go to first page" + href=page_path + ) « First + li + a( + aria-label="Go to previous page" + href=full_page_path + (parseInt(pages.current_page, 10) - 1) + rel="prev" + ) ‹ Prev + + if pages.current_page - max_btns > 1 + li + span … + + while prev_page < pages.current_page + li + a( + aria-label="Go to page " + prev_page + href=full_page_path + prev_page + ) #{prev_page} + - prev_page++ + + li(class="active") + span( + aria-label="Current Page, Page " + pages.current_page + aria-current="true" + ) #{pages.current_page} + + if pages.current_page < pages.total_pages + while next_page <= pages.total_pages && next_index < max_btns + li + a( + aria-label="Go to page " + next_page + href=full_page_path + next_page + ) #{next_page} + - next_page++ + - next_index++ + + if next_page <= pages.total_pages + li + span … + + li + a( + aria-label="Go to next page" + href=full_page_path + (parseInt(pages.current_page, 10) + 1) + rel="next" + ) Next › + + li + a( + aria-label="Go to last page" + href=full_page_path + pages.total_pages + ) Last » diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index e6a20c6ad8..7c22b1f01c 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -277,6 +277,21 @@ @chat-new-message-textarea-bg : @ol-blue-gray-1; @chat-new-message-textarea-color : @ol-blue-gray-6; +// Pagination +@pagination-active-bg : @ol-dark-green; +@pagination-active-border : @gray-lighter; +@pagination-active-color : #FFF; +@pagination-bg : #FFF; +@pagination-border : @gray-lighter; +@pagination-color : @ol-dark-green; +@pagination-disabled-color : @gray-dark; +@pagination-disabled-bg : @gray-lightest; +@pagination-disabled-border : @gray-lighter; +@pagination-hover-color : @ol-dark-green; +@pagination-hover-bg : @gray-lightest; +@pagination-hover-border : @gray-lighter; + + // PDF @pdf-top-offset : @toolbar-small-height; @pdf-bg : @ol-blue-gray-1; diff --git a/services/web/public/stylesheets/ol-style.less b/services/web/public/stylesheets/ol-style.less index 774e70a2ab..2be148a497 100644 --- a/services/web/public/stylesheets/ol-style.less +++ b/services/web/public/stylesheets/ol-style.less @@ -8,6 +8,7 @@ @import "_ol_style_includes.less"; @import "components/embed-responsive.less"; @import "components/icons.less"; +@import "components/pagination.less"; // Pages @import "app/about.less"; From 4dee3fd5e1d9e35fabd5ce4b71c68d47c2df8dfa Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Aug 2018 17:02:55 +0100 Subject: [PATCH 10/15] Update frontend unit tests. --- .../coffee/ide/history/HistoryV2ManagerTests.coffee | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee index 2fc5c5b3b6..542ee3e8eb 100644 --- a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee +++ b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee @@ -4,16 +4,21 @@ define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) -> @scope = $watch: sinon.stub() $on: sinon.stub() + project: + features: + versioning: true @ide = {} @historyManager = new HistoryV2Manager(@ide, @scope) - it "should setup the history scope on intialization", -> + it "should setup the history scope on initialization", -> expect(@scope.history).to.deep.equal({ isV2: true updates: [] viewMode: null nextBeforeTimestamp: null atEnd: false + userHasFullFeature: true + freeHistoryLimitHit: false selection: { label: null updates: [] @@ -32,6 +37,12 @@ define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) -> selectedFile: null }) + + it "should setup history without full access to the feature if the project does not have versioning", -> + @scope.project.features.versioning = false + @historyManager = new HistoryV2Manager(@ide, @scope) + expect(@scope.history.userHasFullFeature).to.equal false + describe "_perDocSummaryOfUpdates", -> it "should return the range of updates for the docs", -> result = @historyManager._perDocSummaryOfUpdates([{ From ab1848d0aee4a6f5abac2791d038eba5dc93fa8c Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 22 Aug 2018 14:47:15 +0100 Subject: [PATCH 11/15] Add a trusted filter for iframe downloads (#836) * add trusted helper to iframe downloads --- services/web/app/views/project/editor/pdf.pug | 2 +- .../public/coffee/ide/pdf/controllers/PdfController.coffee | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/project/editor/pdf.pug b/services/web/app/views/project/editor/pdf.pug index 26de2f35b1..d485d24700 100644 --- a/services/web/app/views/project/editor/pdf.pug +++ b/services/web/app/views/project/editor/pdf.pug @@ -305,7 +305,7 @@ div.full-size.pdf(ng-controller="PdfController") dbl-click-callback="syncToCode" ) iframe( - ng-src="{{ pdf.url }}" + ng-src="{{ pdf.url | trusted }}" ng-if="settings.pdfViewer == 'native'" ) diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 40264e1be0..79b770780a 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -12,6 +12,10 @@ define [ # and then again on ack. AUTO_COMPILE_DEBOUNCE = 2000 + App.filter('trusted', ['$sce', ($sce)-> + return (url)-> return $sce.trustAsResourceUrl(url); + ]) + App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, logHintsFeedback, localStorage) -> # enable per-user containers by default perUserCompile = true From cfaa8444dbb39375e4abe57be3fdfe8e095b2325 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 22 Aug 2018 16:33:15 +0100 Subject: [PATCH 12/15] null check path when building a pdf download url if there is an error just the domain is currently returned, empty string is better for us --- .../web/public/coffee/ide/pdf/controllers/PdfController.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 79b770780a..b4968b7d8a 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -230,7 +230,7 @@ define [ buildPdfDownloadUrl = (pdfDownloadDomain, path)-> #we only download builds from compiles server for security reasons - if pdfDownloadDomain? and path.indexOf("build") != -1 + if pdfDownloadDomain? and path? and path.indexOf("build") != -1 return "#{pdfDownloadDomain}#{path}" else return path From 8d72fc78fcbbf151238d5546a98ed7c88d03e4cc Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Wed, 22 Aug 2018 18:31:29 +0100 Subject: [PATCH 13/15] send licences graph request to v1 for data instead of analytics --- .../Analytics/AnalyticsController.coffee | 10 ++++++++++ .../Features/Analytics/AnalyticsRouter.coffee | 4 +++- .../Institutions/InstitutionsAPI.coffee | 7 +++++++ .../Analytics/AnalyticsControllerTests.coffee | 20 +++++++++++++++++++ .../Institutions/InstitutionsAPITests.coffee | 20 +++++++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee index 38029219ee..7773064481 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee @@ -1,6 +1,7 @@ AnalyticsManager = require "./AnalyticsManager" Errors = require "../Errors/Errors" AuthenticationController = require("../Authentication/AuthenticationController") +InstitutionsAPI = require("../Institutions/InstitutionsAPI") GeoIpLookup = require '../../infrastructure/GeoIpLookup' module.exports = AnalyticsController = @@ -23,6 +24,15 @@ module.exports = AnalyticsController = AnalyticsManager.recordEvent user_id, req.params.event, req.body, (error) -> respondWith(error, res, next) + licences: (req, res, next) -> + AuthenticationController.getLoggedInUserId(req) or req.sessionID + {resource_id, start_date, end_date, lag} = req.query + InstitutionsAPI.getInstitutionLicences resource_id, start_date, end_date, lag, (error, licences) -> + if error? + res.send 503 + else + res.send licences + respondWith = (error, res, next) -> if error instanceof Errors.ServiceNotConfiguredError # ignore, no-op diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee index 06ca2bfa1c..fb0f890241 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee @@ -9,6 +9,8 @@ module.exports = webRouter.put '/editingSession/:projectId', AnalyticsController.updateEditingSession + webRouter.get '/graphs/licences', AnalyticsController.licences + publicApiRouter.use '/analytics/graphs', AuthenticationController.httpAuth, AnalyticsProxy.call('/graphs') @@ -23,4 +25,4 @@ module.exports = publicApiRouter.use '/analytics/uniExternalCollaboration', AuthenticationController.httpAuth, - AnalyticsProxy.call('/uniExternalCollaboration') \ No newline at end of file + AnalyticsProxy.call('/uniExternalCollaboration') diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index d1b2a69816..def1426a28 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -11,6 +11,13 @@ module.exports = InstitutionsAPI = defaultErrorMessage: "Couldn't get institution affiliations" }, callback + getInstitutionLicences: (institutionId, startDate, endDate, lag, callback = (error, body) ->) -> + makeAffiliationRequest { + method: 'GET' + path: "/api/v2/institutions/#{institutionId.toString()}/institution_licences" + body: {start_date: startDate, end_date: endDate, lag} + defaultErrorMessage: "Couldn't get institution affiliations" + }, callback getUserAffiliations: (userId, callback = (error, body) ->) -> makeAffiliationRequest { diff --git a/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee b/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee index c3e3802f37..0aa0517074 100644 --- a/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee +++ b/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee @@ -17,9 +17,13 @@ describe 'AnalyticsController', -> updateEditingSession: sinon.stub().callsArgWith(3) recordEvent: sinon.stub().callsArgWith(3) + @InstitutionsAPI = + getInstitutionLicences: sinon.stub().callsArgWith(4) + @controller = SandboxedModule.require modulePath, requires: "./AnalyticsManager":@AnalyticsManager "../Authentication/AuthenticationController":@AuthenticationController + "../Institutions/InstitutionsAPI":@InstitutionsAPI "logger-sharelatex": log:-> '../../infrastructure/GeoIpLookup': @GeoIpLookup = @@ -66,3 +70,19 @@ describe 'AnalyticsController', -> @controller.recordEvent @req, @res @AnalyticsManager.recordEvent.calledWith(@req.sessionID, @req.params["event"], @req.body).should.equal true done() + + describe "licences", -> + beforeEach -> + @req = + query: + resource_id:1 + start_date:'1514764800' + end_date:'1530662400' + resource_type:'institution' + sessionID: "sessionIDHere" + session: {} + + it "should trigger institutions api to fetch licences graph data", (done)-> + @controller.licences @req, @res + @InstitutionsAPI.getInstitutionLicences.calledWith(@req.query["resource_id"], @req.query["start_date"], @req.query["end_date"], @req.query["lag"]).should.equal true + done() diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 04f0b61e8e..759cb8bfa4 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -41,6 +41,26 @@ describe "InstitutionsAPI", -> body.should.equal responseBody done() + describe 'getInstitutionLicences', -> + it 'get licences', (done)-> + @institutionId = 123 + responseBody = {"lag":"monthly","data":[{"key":"users","values":[{"x":"2018-01-01","y":1}]}]} + @request.yields(null, { statusCode: 200 }, responseBody) + startDate = '1417392000' + endDate = '1420848000' + @InstitutionsAPI.getInstitutionLicences @institutionId, startDate, endDate, 'monthly', (err, body) => + should.not.exist(err) + @request.calledOnce.should.equal true + requestOptions = @request.lastCall.args[0] + expectedUrl = "v1.url/api/v2/institutions/#{@institutionId}/institution_licences" + requestOptions.url.should.equal expectedUrl + requestOptions.method.should.equal 'GET' + requestOptions.body['start_date'].should.equal startDate + requestOptions.body['end_date'].should.equal endDate + requestOptions.body.lag.should.equal 'monthly' + body.should.equal responseBody + done() + describe 'getUserAffiliations', -> it 'get affiliations', (done)-> responseBody = [{ foo: 'bar' }] From 753fb02c056884525a1067266ac43f5977b101da Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Mon, 20 Aug 2018 16:29:44 +0200 Subject: [PATCH 14/15] always return an array when getting affiliations --- .../Institutions/InstitutionsAPI.coffee | 4 ++-- .../Institutions/InstitutionsAPITests.coffee | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index d1b2a69816..3d8da2ee80 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -9,7 +9,7 @@ module.exports = InstitutionsAPI = method: 'GET' path: "/api/v2/institutions/#{institutionId.toString()}/affiliations" defaultErrorMessage: "Couldn't get institution affiliations" - }, callback + }, (error, body) -> callback(error, body or []) getUserAffiliations: (userId, callback = (error, body) ->) -> @@ -17,7 +17,7 @@ module.exports = InstitutionsAPI = method: 'GET' path: "/api/v2/users/#{userId.toString()}/affiliations" defaultErrorMessage: "Couldn't get user affiliations" - }, callback + }, (error, body) -> callback(error, body or []) addAffiliation: (userId, email, affiliationOptions, callback) -> diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 04f0b61e8e..9588ea32e7 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -11,12 +11,12 @@ describe "InstitutionsAPI", -> beforeEach -> @logger = err: sinon.stub(), log: -> - settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } } + @settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } } @request = sinon.stub() @InstitutionsAPI = SandboxedModule.require modulePath, requires: "logger-sharelatex": @logger "metrics-sharelatex": timeAsyncMethod: sinon.stub() - 'settings-sharelatex': settings + 'settings-sharelatex': @settings 'request': @request @stubbedUser = @@ -41,6 +41,14 @@ describe "InstitutionsAPI", -> body.should.equal responseBody done() + it 'handle empty response', (done)-> + @settings.apis = null + @InstitutionsAPI.getInstitutionAffiliations @institutionId, (err, body) => + should.not.exist(err) + expect(body).to.be.a 'Array' + body.length.should.equal 0 + done() + describe 'getUserAffiliations', -> it 'get affiliations', (done)-> responseBody = [{ foo: 'bar' }] @@ -65,6 +73,14 @@ describe "InstitutionsAPI", -> err.message.should.have.string body.errors done() + it 'handle empty response', (done)-> + @settings.apis = null + @InstitutionsAPI.getUserAffiliations @stubbedUser._id, (err, body) => + should.not.exist(err) + expect(body).to.be.a 'Array' + body.length.should.equal 0 + done() + describe 'addAffiliation', -> beforeEach -> @request.callsArgWith(1, null, { statusCode: 201 }) From bd721d52f4aaf820264d3e0d314b1487727106a4 Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 23 Aug 2018 14:39:48 +0100 Subject: [PATCH 15/15] review fixes and moving licences endpoint to module --- .../app/coffee/Features/Analytics/AnalyticsController.coffee | 3 +-- .../web/app/coffee/Features/Analytics/AnalyticsRouter.coffee | 2 -- .../app/coffee/Features/Institutions/InstitutionsAPI.coffee | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee index 7773064481..e407feb488 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee @@ -25,11 +25,10 @@ module.exports = AnalyticsController = respondWith(error, res, next) licences: (req, res, next) -> - AuthenticationController.getLoggedInUserId(req) or req.sessionID {resource_id, start_date, end_date, lag} = req.query InstitutionsAPI.getInstitutionLicences resource_id, start_date, end_date, lag, (error, licences) -> if error? - res.send 503 + next(error) else res.send licences diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee index fb0f890241..57b131326f 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee @@ -9,8 +9,6 @@ module.exports = webRouter.put '/editingSession/:projectId', AnalyticsController.updateEditingSession - webRouter.get '/graphs/licences', AnalyticsController.licences - publicApiRouter.use '/analytics/graphs', AuthenticationController.httpAuth, AnalyticsProxy.call('/graphs') diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index def1426a28..e2fdab68d7 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -16,7 +16,7 @@ module.exports = InstitutionsAPI = method: 'GET' path: "/api/v2/institutions/#{institutionId.toString()}/institution_licences" body: {start_date: startDate, end_date: endDate, lag} - defaultErrorMessage: "Couldn't get institution affiliations" + defaultErrorMessage: "Couldn't get institution licences" }, callback getUserAffiliations: (userId, callback = (error, body) ->) ->