Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to add and remove all repositories to/from team. #8867

Merged
merged 6 commits into from
Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 79 additions & 30 deletions models/org_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ func (t *Team) addAllRepositories(e Engine) error {
return nil
}

// AddAllRepositories adds all repositories to the team
func (t *Team) AddAllRepositories() (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

if err = t.addAllRepositories(sess); err != nil {
return err
}

lafriks marked this conversation as resolved.
Show resolved Hide resolved
return sess.Commit()
}

// AddRepository adds new repository to team of organization.
func (t *Team) AddRepository(repo *Repository) (err error) {
if repo.OwnerID != t.OrgID {
Expand All @@ -264,6 +279,69 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
return sess.Commit()
}

// RemoveAllRepositories removes all repositories from team and recalculates access
func (t *Team) RemoveAllRepositories() (err error) {
if t.IncludesAllRepositories {
return nil
}

sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

if err = t.removeAllRepositories(sess); err != nil {
return err
}

return sess.Commit()
}

// removeAllRepositories removes all repositories from team and recalculates access
// Note: Shall not be called if team includes all repositories
func (t *Team) removeAllRepositories(e Engine) (err error) {
// Delete all accesses.
for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(e, t.ID); err != nil {
return err
}

// Remove watches from all users and now unaccessible repos
for _, user := range t.Members {
has, err := hasAccess(e, user.ID, repo)
if err != nil {
return err
} else if has {
continue
}

if err = watchRepo(e, user.ID, repo.ID, false); err != nil {
return err
}

// Remove all IssueWatches a user has subscribed to in the repositories
if err = removeIssueWatchersByRepoID(e, user.ID, repo.ID); err != nil {
davidsvantesson marked this conversation as resolved.
Show resolved Hide resolved
return err
}
}
}

// Delete team-repo
if _, err := e.
Where("team_id=?", t.ID).
Delete(new(TeamRepo)); err != nil {
return err
}

t.NumRepos = 0
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
return err
}

return nil
}

// removeRepository removes a repository from a team and recalculates access
// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
Expand Down Expand Up @@ -577,36 +655,7 @@ func DeleteTeam(t *Team) error {
return err
}

// Delete all accesses.
for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
return err
}

// Remove watches from all users and now unaccessible repos
for _, user := range t.Members {
has, err := hasAccess(sess, user.ID, repo)
if err != nil {
return err
} else if has {
continue
}

if err = watchRepo(sess, user.ID, repo.ID, false); err != nil {
return err
}

// Remove all IssueWatches a user has subscribed to in the repositories
if err = removeIssueWatchersByRepoID(sess, user.ID, repo.ID); err != nil {
return err
}
}
}

// Delete team-repo
if _, err := sess.
Where("team_id=?", t.ID).
Delete(new(TeamRepo)); err != nil {
if err := t.removeAllRepositories(sess); err != nil {
return err
}

Expand Down
8 changes: 7 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1583,8 +1583,14 @@ teams.write_permission_desc = This team grants <strong>Write</strong> access: me
teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to and add collaborators to team repositories.
teams.repositories = Team Repositories
teams.search_repo_placeholder = Search repository…
teams.add_team_repository = Add Team Repository
teams.add_team_repository = Add
teams.remove_repo = Remove
teams.add_all_repos = Add All
teams.remove_all_repos = Remove All
techknowlogick marked this conversation as resolved.
Show resolved Hide resolved
teams.remove_all_repos_title = Remove all team repositories
teams.remove_all_repos_desc = This will remove all repositories from the team.
teams.add_all_repos_title = Add all repositories
teams.add_all_repos_desc = This will add all the organization's repositories to the team.
teams.add_nonexistent_repo = "The repository you're trying to add does not exist; please create it first."
teams.add_duplicate_users = User is already a team member.
teams.repos.none = No repositories could be accessed by this team.
Expand Down
5 changes: 3 additions & 2 deletions public/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -944,8 +944,9 @@ tbody.commit-list{vertical-align:baseline}
.organization.teams .members .item,.organization.teams .repositories .item{padding:10px 20px;line-height:32px}
.organization.teams .members .item:not(:last-child),.organization.teams .repositories .item:not(:last-child){border-bottom:1px solid #ddd}
.organization.teams .members .item .button,.organization.teams .repositories .item .button{padding:9px 10px}
.organization.teams #add-member-form input,.organization.teams #add-repo-form input{margin-left:0}
.organization.teams #add-member-form .ui.button,.organization.teams #add-repo-form .ui.button{margin-left:5px;margin-top:-3px}
.organization.teams #add-member-form input,.organization.teams #add-repo-form input,.organization.teams #repo-multiple-form input{margin-left:0}
.organization.teams #add-member-form .ui.button,.organization.teams #add-repo-form .ui.button,.organization.teams #repo-multiple-form .ui.button{margin-left:5px;margin-top:-3px}
.organization.teams #repo-top-segment{height:60px}
.user:not(.icon){padding-top:15px}
.user.profile .ui.card .username{display:block}
.user.profile .ui.card .extra.content{padding:0}
Expand Down
30 changes: 30 additions & 0 deletions public/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,7 @@ $(document).ready(function () {

// Helpers.
$('.delete-button').click(showDeletePopup);
$('.add-all-button').click(showAddAllPopup);

$('.delete-branch-button').click(showDeletePopup);

Expand Down Expand Up @@ -2501,6 +2502,35 @@ function showDeletePopup() {
return false;
}

function showAddAllPopup() {
const $this = $(this);
let filter = "";
if ($this.attr("id")) {
filter += "#" + $this.attr("id")
}

const dialog = $('.addall.modal' + filter);
dialog.find('.name').text($this.data('name'));

dialog.modal({
closable: false,
onApprove: function() {
if ($this.data('type') == "form") {
$($this.data('form')).submit();
return;
}

$.post($this.data('url'), {
"_csrf": csrf,
"id": $this.data("id")
}).done(function(data) {
window.location.href = data.redirect;
});
}
}).modal('show');
return false;
}

function initVueComponents(){
const vueDelimeters = ['${', '}'];

Expand Down
5 changes: 5 additions & 0 deletions public/less/_organization.less
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
}

#add-repo-form,
#repo-multiple-form,
#add-member-form {
input {
margin-left: 0;
Expand All @@ -162,5 +163,9 @@
margin-top: -3px;
}
}

#repo-top-segment {
height: 60px;
}
}
}
8 changes: 8 additions & 0 deletions routers/org/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,21 @@ func TeamsRepoAction(ctx *context.Context) {
err = ctx.Org.Team.AddRepository(repo)
case "remove":
err = ctx.Org.Team.RemoveRepository(com.StrTo(ctx.Query("repoid")).MustInt64())
case "addall":
err = ctx.Org.Team.AddAllRepositories()
case "removeall":
err = ctx.Org.Team.RemoveAllRepositories()
}

if err != nil {
log.Error("Action(%s): '%s' %v", ctx.Params(":action"), ctx.Org.Team.Name, err)
ctx.ServerError("TeamsRepoAction", err)
return
}

ctx.JSON(200, map[string]interface{}{
"redirect": ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName + "/repositories",
})
ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName + "/repositories")
}

Expand Down
51 changes: 41 additions & 10 deletions templates/org/team/repositories.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@
{{template "org/team/navbar" .}}
{{$canAddRemove := and $.IsOrganizationOwner (not $.Team.IncludesAllRepositories)}}
{{if $canAddRemove}}
<div class="ui attached segment">
<form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/add" method="post">
{{.CsrfTokenHtml}}
<div class="inline field ui left">
<div id="search-repo-box" data-uid="{{.Org.ID}}" class="ui search">
<div class="ui input">
<input class="prompt" name="repo_name" placeholder="{{.i18n.Tr "org.teams.search_repo_placeholder"}}" autocomplete="off" required>
<div class="ui attached segment" id="repo-top-segment">
<div class="inline ui field left">
<form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/add" method="post">
{{.CsrfTokenHtml}}
<div class="inline field ui left">
<div id="search-repo-box" data-uid="{{.Org.ID}}" class="ui search">
<div class="ui input">
<input class="prompt" name="repo_name" placeholder="{{.i18n.Tr "org.teams.search_repo_placeholder"}}" autocomplete="off" required>
</div>
</div>
</div>
</div>
<button class="ui green button">{{.i18n.Tr "org.teams.add_team_repository"}}</button>
</form>
<button class="ui green button">{{.i18n.Tr "org.teams.add_team_repository"}}</button>
</form>
</div>
<div class="inline ui field right">
<form class="ui form" id="repo-multiple-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/repositories" method="post">
<button class="ui red button delete-button right" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/removeall">{{.i18n.Tr "org.teams.remove_all_repos"}}</button>
<button class="ui green button add-all-button right" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/addall">{{.i18n.Tr "org.teams.add_all_repos"}}</button>
</form>
</div>
</div>
{{end}}
<div class="ui bottom attached table segment repositories">
Expand All @@ -44,4 +52,27 @@
</div>
</div>
</div>

<div class="ui small basic delete modal">
<div class="ui icon header">
<i class="trash icon"></i>
{{.i18n.Tr "org.teams.remove_all_repos_title"}}
</div>
<div class="content">
<p>{{.i18n.Tr "org.teams.remove_all_repos_desc"}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

<div class="ui small basic addall modal">
<div class="ui icon header">
<i class="globe icon"></i>
{{.i18n.Tr "org.teams.add_all_repos_title"}}
</div>
<div class="content">
<p>{{.i18n.Tr "org.teams.add_all_repos_desc"}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

{{template "base/footer" .}}