diff --git a/src/index.module.js b/src/index.module.js index b7a2e4921c..f64f5c1d10 100644 --- a/src/index.module.js +++ b/src/index.module.js @@ -14,7 +14,8 @@ 'ui.bootstrap', 'ui.router', 'smart-table', - 'http-etag' + 'http-etag', + 'ig.linkHeaderParser' ]; var pluginModules = _.chain(env.plugins).map('moduleName').value(); @@ -28,10 +29,11 @@ config.$inject = [ '$compileProvider', + '$logProvider', 'httpEtagProvider' ]; - function config($compileProvider, httpEtagProvider) { + function config($compileProvider, $logProvider, httpEtagProvider) { /** * Disabling Debug Data @@ -49,6 +51,8 @@ // Use Etags to resolve caching issues on application upgrade httpEtagProvider.cache('default'); + + $logProvider.debugEnabled(false); } })(); diff --git a/src/plugins/cloud-foundry/api/github/github.api.js b/src/plugins/cloud-foundry/api/github/github.api.js index 6a81a96b4b..3129c159e2 100644 --- a/src/plugins/cloud-foundry/api/github/github.api.js +++ b/src/plugins/cloud-foundry/api/github/github.api.js @@ -46,13 +46,13 @@ } angular.extend(GithubApi.prototype, { - /** - * @function repos - * @memberof cloud-foundry.api.github.GithubApi - * @description Get repos user owns or has admin rights to - * @param {object} params - additional params to send - * @returns {promise} - * @public + /** + * @function repos + * @memberof cloud-foundry.api.github.GithubApi + * @description Get repos user owns or has admin rights to + * @param {object} params - additional params to send + * @returns {promise} A promise object + * @public */ repos: function (params) { var url = this.githubApiUrl + 'user/repos'; @@ -67,14 +67,14 @@ }, /** - * @function branches - * @memberof cloud-foundry.api.github.GithubApi - * @description Get branches a repo - * @param {string} repo - the repo full name - * @param {object} params - additional params to send - * @returns {promise} - * @public - */ + * @function branches + * @memberof cloud-foundry.api.github.GithubApi + * @description Get branches a repo + * @param {string} repo - the repo full name + * @param {object} params - additional params to send + * @returns {promise} A promise object + * @public + */ branches: function (repo, params) { var url = this.githubApiUrl + 'repos/' + repo + '/branches'; var config = { @@ -94,7 +94,7 @@ * @param {string} repo - the repo full name * @param {string} branch - the branch name * @param {object} params - additional params to send - * @returns {promise} + * @returns {promise} A promise object * @public */ getBranch: function (repo, branch, params) { @@ -110,14 +110,14 @@ }, /** - * @function commits - * @memberof cloud-foundry.api.github.GithubApi - * @description Get commits for a repo - * @param {string} repo - the repo full name - * @param {object} params - additional params to send - * @returns {promise} - * @public - */ + * @function commits + * @memberof cloud-foundry.api.github.GithubApi + * @description Get commits for a repo + * @param {string} repo - the repo full name + * @param {object} params - additional params to send + * @returns {promise} A promise object + * @public + */ commits: function (repo, params) { var url = this.githubApiUrl + 'repos/' + repo + '/commits'; var config = { diff --git a/src/plugins/cloud-foundry/model/github/github.model.js b/src/plugins/cloud-foundry/model/github/github.model.js index 21a54ce0be..067f9f1b04 100644 --- a/src/plugins/cloud-foundry/model/github/github.model.js +++ b/src/plugins/cloud-foundry/model/github/github.model.js @@ -13,25 +13,45 @@ registerGithubModel.$inject = [ 'app.model.modelManager', - 'app.api.apiManager' + 'app.api.apiManager', + '$q', + '$filter', + 'linkHeaderParser' ]; - function registerGithubModel(modelManager, apiManager) { - modelManager.register('cloud-foundry.model.github', new GithubModel(apiManager)); + function registerGithubModel(modelManager, apiManager, $q, $filter, linkHeaderParser) { + modelManager.register('cloud-foundry.model.github', new GithubModel(apiManager, $q, $filter, linkHeaderParser)); } /** * @memberof cloud-foundry.model.github * @name GithubModel * @param {app.api.apiManager} apiManager - the application API manager + * @param {object} $q - the Angular $q service + * @param {object} $filter - the Angular $filter service + * @param {linkHeaderParser} linkHeaderParser - the linkHeaderParser service * @property {app.api.apiManager} apiManager - the application API manager + * @property {object} $q - the Angular $q service + * @property {object} $filter - the Angular $filter service + * @property {linkHeaderParser} linkHeaderParser - the linkHeaderParser service * @property {object} data - the Github data * @class */ - function GithubModel(apiManager) { + function GithubModel(apiManager, $q, $filter, linkHeaderParser) { this.apiManager = apiManager; + this.$q = $q; + this.$filter = $filter; + this.linkHeaderParser = linkHeaderParser; + this.repo = { + filterTerm: null, + page: 0, + pageSize: 50, + links: {} + }; this.data = { + repoLinks: {}, repos: [], + filteredRepos: [], branches: [], commits: [] }; @@ -42,7 +62,7 @@ * @function isAuthenticated * @memberof cloud-foundry.model.github.GithubModel * @description Whether the user has authenticated against Github - * @returns {boolean} True if user has authenticated against Github * + * @returns {boolean} True if user has authenticated against Github */ isAuthenticated: function () { return _.get(this.apiManager.retrieve('cloud-foundry.api.github'), 'authenticated'); @@ -52,22 +72,113 @@ * @function repos * @memberof cloud-foundry.model.github.GithubModel * @description Get repos user owns or has admin rights to + * @param {boolean} ignoreReset - flag to ignore reset of repos array * @returns {promise} A promise object * @public */ - repos: function () { + repos: function (ignoreReset) { var that = this; - var githubApi = this.apiManager.retrieve('cloud-foundry.api.github'); - return githubApi.repos({per_page: 100}) + if (!ignoreReset) { + this._resetRepos(); + } + + return this.nextRepos() .then(function (response) { - that.onRepos(response); - }) - .catch(function (err) { + return response; + }, function (err) { that.onReposError(); - throw err; + return err; }); }, + filterRepos: function (filterTerm) { + this.repo.filterTerm = filterTerm || null; + if (this.repo.filterTerm !== null) { + this.data.filteredRepos = this.$filter('filter')(this.data.repos, this.repo.filterTerm); + var filteredCnt = this.data.filteredRepos.length; + var maxCnt = this.repo.page * this.repo.pageSize; + if (filteredCnt < maxCnt) { + return this.repos(true); + } + } else { + this.data.filteredRepos.length = 0; + } + }, + + /** + * @function nextRepos + * @memberof cloud-foundry.model.github.GithubModel + * @description Get next set of repos + * @returns {promise} A promise object + * @public + */ + nextRepos: function () { + var that = this; + var deferred = this.$q.defer(); + var config = {per_page: 50}; + var numFetched = 0; + var fetchedRepos = []; + + function next() { + if (that.repo.links.next) { + config.page = _.toInteger(that.repo.links.next.page); + } + + that.apiManager.retrieve('cloud-foundry.api.github') + .repos(config) + .then(function (response) { + // Parse out the Link header + var linkHeaderText = response.headers('Link'); + var links = linkHeaderText ? that.linkHeaderParser.parse(linkHeaderText) : {}; + that.repo.links = links; + + var repos = response.data || []; + var adminRepos = _.filter(repos, function (o) { return o.permissions.admin; }); + + if (that.repo.filterTerm) { + var filteredAdminRepos = that.$filter('filter')(adminRepos, that.repo.filterTerm); + numFetched += filteredAdminRepos.length; + [].push.apply(that.data.filteredRepos, filteredAdminRepos); + } else { + numFetched += adminRepos.length; + } + + [].push.apply(fetchedRepos, adminRepos); + [].push.apply(that.data.repos, adminRepos); + + if (numFetched < that.repo.pageSize && links.next) { + next(); // eslint-disable-line callback-return + } else { + that.repo.page++; + deferred.resolve({newRepos: fetchedRepos, repos: that.data.repos, links: that.repo.links, page: that.repo.page}); + } + }, function (err) { + deferred.reject(err); + }); + } + + if (this.repo.page === 0 || this.repo.links.next) { + next(); // eslint-disable-line callback-return + } else { + deferred.resolve({newRepos: fetchedRepos, repos: this.data.repos, links: this.repo.links, page: this.repo.page}); + } + + return deferred.promise; + }, + + /** + * @function _resetRepos + * @memberof cloud-foundry.model.github.GithubModel + * @description Reset repos + * @returns {void} + * @private + */ + _resetRepos: function () { + this.data.repos.length = 0; + this.repo.page = 0; + this.repo.links = {}; + }, + /** * @function branches * @memberof cloud-foundry.model.github.GithubModel @@ -133,18 +244,6 @@ }); }, - /** - * @function onRepos - * @memberof cloud-foundry.model.github.GithubModel - * @description onRepos handler - * @param {string} response - the JSON response from API call - * @private - */ - onRepos: function (response) { - this.data.repos.length = 0; - [].push.apply(this.data.repos, response.data || []); - }, - /** * @function onBranches * @memberof cloud-foundry.model.github.GithubModel @@ -176,7 +275,7 @@ * @private */ onReposError: function () { - this.data.repos.length = 0; + this._resetRepos(); }, /** diff --git a/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/add-app-workflow.directive.js b/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/add-app-workflow.directive.js index 15edde4a38..266c8a1f3c 100644 --- a/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/add-app-workflow.directive.js +++ b/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/add-app-workflow.directive.js @@ -26,7 +26,8 @@ 'app.event.eventService', 'github.view.githubOauthService', '$scope', - '$q' + '$q', + '$timeout' ]; /** @@ -36,10 +37,12 @@ * @param {app.model.modelManager} modelManager - the Model management service * @param {app.event.eventService} eventService - the Event management service * @param {object} githubOauthService - github oauth service - * @param {object} $scope - angular $scope - * @param {object} $q - angular $q service + * @param {object} $scope - Angular $scope + * @param {object} $q - Angular $q service + * @param {object} $timeout - the Angular $timeout service * @property {object} $scope - angular $scope * @property {object} $q - angular $q service + * @property {object} $timeout - the Angular $timeout service * @property {boolean} addingApplication - flag for adding app * @property {app.event.eventService} eventService - the Event management service * @property {github.view.githubOauthService} githubOauthService - github oauth service @@ -56,11 +59,12 @@ * @property {object} userInput - user's input about new application * @property {object} options - workflow options */ - function AddAppWorkflowController(modelManager, eventService, githubOauthService, $scope, $q) { + function AddAppWorkflowController(modelManager, eventService, githubOauthService, $scope, $q, $timeout) { var that = this; this.$scope = $scope; this.$q = $q; + this.$timeout = $timeout; this.addingApplication = false; this.eventService = eventService; this.githubOauthService = githubOauthService; @@ -80,6 +84,7 @@ this.userInput = {}; this.options = {}; + this.filterTimeout = null; $scope.$watch(function () { return that.userInput.serviceInstance; @@ -123,6 +128,22 @@ that.appendSubflow(that.data.subflows[subflow]); } }); + + $scope.$watch(function () { + return that.userInput.repoFilterTerm; + }, function (newFilterTerm) { + if (that.filterTimeout !== null) { + that.$timeout.cancel(that.filterTimeout); + } + + that.filterTimeout = that.$timeout(function () { + return that.filterRepos(newFilterTerm); + }, 500); + }); + + this.eventService.$on('cf.events.LOAD_MORE_REPOS', function () { + that.loadMoreRepos(); + }); } angular.extend(AddAppWorkflowController.prototype, { @@ -147,6 +168,7 @@ hceCnsi: null, source: null, repo: null, + repoFilterTerm: null, branch: null, buildContainer: null, imageRegistry: null, @@ -252,11 +274,7 @@ return oauth .then(function () { - return that.githubModel.repos(); - }) - .then(function () { - var repos = _.filter(that.githubModel.data.repos || [], function (o) { return o.permissions.admin; }); - [].push.apply(that.options.repos, repos); + return that.getRepos(); }); } }, @@ -350,6 +368,7 @@ this.options = { workflow: that.data.workflow, userInput: this.userInput, + eventService: this.eventService, errors: this.errors, subflow: null, serviceInstances: [], @@ -380,7 +399,10 @@ } ], sources: [], + displayedRepos: [], repos: [], + hasMoreRepos: false, + loadingRepos: false, branches: [], buildContainers: [], imageRegistries: [] @@ -580,6 +602,46 @@ }); }, + getRepos: function () { + var that = this; + this.options.loadingRepos = true; + return this.githubModel.repos() + .then(function (response) { + that.options.hasMoreRepos = angular.isDefined(response.links.next); + [].push.apply(that.options.repos, response.repos); + }) + .finally(function () { + that.options.loadingRepos = false; + }); + }, + + loadMoreRepos: function () { + var that = this; + this.options.loadingRepos = true; + return this.githubModel.nextRepos() + .then(function (response) { + that.options.hasMoreRepos = angular.isDefined(response.links.next); + [].push.apply(that.options.repos, response.newRepos); + }) + .finally(function () { + that.options.loadingRepos = false; + }); + }, + + filterRepos: function (newFilterTerm) { + var that = this; + this.options.loadingRepos = true; + return this.$q.when(this.githubModel.filterRepos(newFilterTerm)) + .then(function (response) { + if (angular.isDefined(response)) { + that.options.hasMoreRepos = angular.isDefined(response.links.next); + [].push.apply(that.options.repos, response.newRepos); + } + }).finally(function () { + that.options.loadingRepos = false; + }); + }, + getPipelineDetailsData: function () { var that = this; diff --git a/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/pipeline-subflow.scss b/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/pipeline-subflow.scss index 795c527c1a..d4ca06801d 100644 --- a/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/pipeline-subflow.scss +++ b/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/pipeline-subflow.scss @@ -4,44 +4,17 @@ add-app-workflow { border: 1px solid $table-border-color; } - // TODO: From here onwards, code will be moved to helion-ui-theme https://jira.hpcloud.net/browse/TEAMFOUR-624 - > thead > tr > th.text-left, - > tbody > tr:not([table-inline-message]) > td.text-left { - &:last-child:not(.text-center) { - text-align: left; + thead > tr > th:last-child:not(.text-center) { + &.filter-header { + padding: $hpe-unit-space/2 0; - &.filter-header { - padding: $hpe-unit-space 0; + .repo-count { + flex-shrink: 1; + width: auto; } - } - } - - .filter-header { - .form-group { - margin: 0; - - &:first-child { - @include flex-align(center, flex-start); - flex-direction: column; - border: 1px solid $input-border; - padding: 0 $padding-large-horizontal; - margin: 0; - - label { - color: $hpe-input-label-fg; - font-weight: normal; - font-size: $font-size-small; - margin-bottom: $hpe-unit-space / 4; - padding-top: 0; - } - input[type="text"] { - border: none; - background-color: transparent; - padding: 0; - height: $line-height-computed; - line-height: $line-height-computed; // Required for bug in Chrome - } + .repo-filter { + flex-grow: 1; } } } diff --git a/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/select-repository.html b/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/select-repository.html index 0d2f22f500..9dceb315be 100644 --- a/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/select-repository.html +++ b/src/plugins/cloud-foundry/view/applications/workflows/add-app-workflow/pipeline-subflow/select-repository.html @@ -8,19 +8,21 @@

Select Repository

st-safe-src="wizardCtrl.options.repos"> - +
-
+
+ ng-value="wizardCtrl.options.displayedRepos.length" readonly/>
-
+
- +
@@ -29,7 +31,6 @@

Select Repository

Repository Name - Description @@ -39,7 +40,17 @@

Select Repository

{{ ::repo.full_name }} - {{ ::repo.description }} + + + + + Load More Repositories + + + diff --git a/tools/bower.json b/tools/bower.json index 8da290daf7..bf0e811935 100644 --- a/tools/bower.json +++ b/tools/bower.json @@ -26,7 +26,8 @@ "ngSmoothScroll": "~2.0.0", "angular-cookies": "1.4", "angular-utf8-base64": "~0.0.5", - "angular-http-etag": "~1.1.3" + "angular-http-etag": "~1.1.3", + "angular-link-header-parser": "~1.0.2" }, "devDependencies": { "angular-mocks": "~1.4.9", diff --git a/tools/gulp.config.js b/tools/gulp.config.js index 4b30626cc5..c7107aaee9 100644 --- a/tools/gulp.config.js +++ b/tools/gulp.config.js @@ -16,22 +16,25 @@ module.exports = function () { directory: '../src/lib/', ignorePath: '../src/', overrides: { + "angular-link-header-parser": { + "main": [ "release/angular-link-header-parser.min.js" ] + }, "angular-toastr": { - "main": [ "./dist/angular-toastr.tpls.js" ] + "main": [ "./dist/angular-toastr.tpls.js" ] } - } + } }, - bowerDev: { - bowerJson: require('./bower.json'), - directory: '../src/lib/', - ignorePath: '../src/', - devDependencies: false - }, + bowerDev: { + bowerJson: require('./bower.json'), + directory: '../src/lib/', + ignorePath: '../src/', + devDependencies: false + }, - assetFiles: [ - paths.src + 'plugins/**/assets/**/*' - ], + assetFiles: [ + paths.src + 'plugins/**/assets/**/*' + ], cssFiles: [ paths.dist + 'index.css' diff --git a/tools/karma.conf.js b/tools/karma.conf.js index 1986e786ae..6d059d9335 100644 --- a/tools/karma.conf.js +++ b/tools/karma.conf.js @@ -22,6 +22,7 @@ module.exports = function (config) { files: [ 'lib/jquery/dist/jquery.js', 'lib/angular-mocks/angular-mocks.js', + 'lib/angular-link-header-parser/release/angular-link-header-parser.min.js', 'config.js', 'plugins/*/plugin.config.js',