From 77fd2f612f173f13cfa19a2cc94e7e1961e9a510 Mon Sep 17 00:00:00 2001 From: Karsten Amrhein Date: Tue, 8 Oct 2019 19:16:16 +0200 Subject: [PATCH 1/3] Load github project list only when needed in add projects dialog --- src/dashboard-client/src/components/AddProject.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dashboard-client/src/components/AddProject.vue b/src/dashboard-client/src/components/AddProject.vue index d360b9923..947126db8 100644 --- a/src/dashboard-client/src/components/AddProject.vue +++ b/src/dashboard-client/src/components/AddProject.vue @@ -9,7 +9,7 @@ - +

Select project type

@@ -113,7 +113,7 @@ export default { selectRepo: false }), created () { - UserService.loadRepos() + // UserService.loadRepos() }, watch: { projName () { @@ -141,6 +141,11 @@ export default { ProjectService.addProject(this.projName, this.priv, this.type, repoName) }, + stepperChange (value) { + if (value === 1 && this.type === 'github') { + UserService.loadRepos() + } + }, selectGithubRepo (r) { this.githubRepo = r }, From 30f761429a49e347f30ab4edce5d52e0723fb441 Mon Sep 17 00:00:00 2001 From: Karsten Amrhein Date: Fri, 18 Oct 2019 15:09:29 +0200 Subject: [PATCH 2/3] GitHub repository pagination Previsously the UI for creating a new Project became unusable when the user had access to too many GitHub repositories. To avoid that the project list is not loaded all in once but instead page by page. To further improve performance, the pagination api endpoint only sends fields actually needed for display instead of the full GitHub api result. --- src/api/handlers/account/github.py | 55 +++++++++++- .../src/components/AddProject.vue | 28 ++++-- .../components/utils/ApiTablePagination.vue | 89 +++++++++++++++++++ .../src/services/UserService.js | 4 +- 4 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 src/dashboard-client/src/components/utils/ApiTablePagination.vue diff --git a/src/api/handlers/account/github.py b/src/api/handlers/account/github.py index 63e22cb67..d00ae4874 100644 --- a/src/api/handlers/account/github.py +++ b/src/api/handlers/account/github.py @@ -1,5 +1,6 @@ import uuid import os +import re import requests from flask import g, request, abort, redirect @@ -42,7 +43,14 @@ def get_next_page(r): n3 = link.find('>;', n2) return link[n2:n3] -def get_github_api(url, token): +def parse_link_header(link): + reg = r"<.+?page=(?P\d+)>; rel=\"(?Pprev|next|first|last)\"" + res = {} + for match in re.finditer(reg, link): + res[match.group("direction")] = match.group("page") + return res + +def get_github_api(url, token, raw_result=False): headers = { "Authorization": "token " + token, "User-Agent": "InfraBox" @@ -51,6 +59,8 @@ def get_github_api(url, token): # TODO(ib-steffen): allow custom ca bundles r = requests.get(url, headers=headers, verify=False) + if raw_result: + return r result = [] result.extend(r.json()) @@ -126,6 +136,49 @@ def get(self): return github_repos +@api.route('/api/v1/github/paginated_repos', doc=False) +class V2Repos(Resource): + + def get(self): + user_id = g.token['user']['id'] + + user = g.db.execute_one_dict(''' + SELECT github_api_token + FROM "user" + WHERE id = %s + ''', [user_id]) + + if not user: + abort(404) + + token = user['github_api_token'] + + page = request.args.get('page', 1) + per_page = request.args.get('page', 50) + github_repos_response = get_github_api('/user/repos?visibility=all&page={page}&per_page={per_page}' + .format(page=page, per_page=per_page), + token, raw_result=True) + github_repos = github_repos_response.json() + filtered_repos = [] + for github_repo in github_repos: + filtered_repos.append({ + "name": github_repo["name"], + "owner_login": github_repo["owner"]["login"], + "private": github_repo["private"], + "open_issues_count": github_repo["open_issues_count"], + "forks_count": github_repo["forks_count"], + }) + + nav = {} + for direction, page in parse_link_header(github_repos_response.headers.get('Link', "")).items(): + nav[direction] = page + result = { + "nav": nav, + "items": filtered_repos + } + return result + + @api.route('/github/auth', doc=False) class Auth(Resource): diff --git a/src/dashboard-client/src/components/AddProject.vue b/src/dashboard-client/src/components/AddProject.vue index 947126db8..7cf74b873 100644 --- a/src/dashboard-client/src/components/AddProject.vue +++ b/src/dashboard-client/src/components/AddProject.vue @@ -56,9 +56,9 @@ - + {{ r.name }} - {{ r.owner.login }} + {{ r.owner_login }} @@ -72,6 +72,13 @@ +
@@ -99,10 +106,14 @@ import ProjectService from '../services/ProjectService' import UserService from '../services/UserService' import store from '../store' +import ApiTablePagination from './utils/ApiTablePagination' export default { name: 'AddProject', store, + components: { + 'ib-api-table-pagination': ApiTablePagination + }, data: () => ({ projName: '', nameValid: false, @@ -110,11 +121,10 @@ export default { priv: true, githubRepo: null, invalidMessage: 'Name required', - selectRepo: false + selectRepo: false, + page: 1, + size: 50 }), - created () { - // UserService.loadRepos() - }, watch: { projName () { if (this.projName.length < 3) { @@ -143,7 +153,7 @@ export default { }, stepperChange (value) { if (value === 1 && this.type === 'github') { - UserService.loadRepos() + UserService.loadRepos(1, this.size) } }, selectGithubRepo (r) { @@ -151,6 +161,10 @@ export default { }, connectGithubAccount () { window.location.href = '/github/auth/connect' + }, + onPagination (page) { + UserService.loadRepos(page, this.size) + this.page = parseInt(page) } } } diff --git a/src/dashboard-client/src/components/utils/ApiTablePagination.vue b/src/dashboard-client/src/components/utils/ApiTablePagination.vue new file mode 100644 index 000000000..fa0ec8092 --- /dev/null +++ b/src/dashboard-client/src/components/utils/ApiTablePagination.vue @@ -0,0 +1,89 @@ + + + \ No newline at end of file diff --git a/src/dashboard-client/src/services/UserService.js b/src/dashboard-client/src/services/UserService.js index 0813c2c3c..52087dbbe 100644 --- a/src/dashboard-client/src/services/UserService.js +++ b/src/dashboard-client/src/services/UserService.js @@ -70,9 +70,9 @@ class UserService { }) } - loadRepos () { + loadRepos (page, perPage) { if (store.state.user.hasGithubAccount() && store.state.settings.INFRABOX_GITHUB_ENABLED) { - return NewAPIService.get('github/repos/') + return NewAPIService.get('github/paginated_repos?page=' + page + '&per_page=' + perPage) .then((d) => { if (d) { store.commit('setGithubRepos', d) From 2324cd48c5c4ea5ecbfaa5d86310f3d2ad45b465 Mon Sep 17 00:00:00 2001 From: Karsten Amrhein Date: Wed, 22 Jan 2020 16:14:41 +0100 Subject: [PATCH 3/3] Fix api endpoint and add rule to opa --- src/api/handlers/account/github.py | 2 +- src/openpolicyagent/policies/github.rego | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/api/handlers/account/github.py b/src/api/handlers/account/github.py index d00ae4874..b19734e34 100644 --- a/src/api/handlers/account/github.py +++ b/src/api/handlers/account/github.py @@ -154,7 +154,7 @@ def get(self): token = user['github_api_token'] page = request.args.get('page', 1) - per_page = request.args.get('page', 50) + per_page = request.args.get('per_page', 50) github_repos_response = get_github_api('/user/repos?visibility=all&page={page}&per_page={per_page}' .format(page=page, per_page=per_page), token, raw_result=True) diff --git a/src/openpolicyagent/policies/github.rego b/src/openpolicyagent/policies/github.rego index 999016184..b32b066a9 100644 --- a/src/openpolicyagent/policies/github.rego +++ b/src/openpolicyagent/policies/github.rego @@ -19,6 +19,12 @@ allow { api.token.type = "user" } +allow { + api.method = "GET" + api.path = ["api", "v1", "github", "paginated_repos"] + api.token.type = "user" +} + allow { api.method = "GET" api.path = ["github", "auth"]