Allow to add and remove all repositories to/from team. (#8867)
* Allow to add and remove all repositories to team. * Change style, buttons on same row. * Apply suggestions from code review Grammar Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Move set num repos to lower function. * Make general language sentences
This commit is contained in:
		
							parent
							
								
									d2aee2a3e2
								
							
						
					
					
						commit
						9ae4c17cb1
					
				
					 7 changed files with 175 additions and 45 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddRepository adds new repository to team of organization.
 | 
			
		||||
func (t *Team) AddRepository(repo *Repository) (err error) {
 | 
			
		||||
	if repo.OwnerID != t.OrgID {
 | 
			
		||||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
				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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,6 +68,10 @@ pull_requests = Pull Requests
 | 
			
		|||
issues = Issues
 | 
			
		||||
 | 
			
		||||
cancel = Cancel
 | 
			
		||||
add = Add
 | 
			
		||||
add_all = Add All
 | 
			
		||||
remove = Remove
 | 
			
		||||
remove_all = Remove All
 | 
			
		||||
 | 
			
		||||
write = Write
 | 
			
		||||
preview = Preview
 | 
			
		||||
| 
						 | 
				
			
			@ -1583,8 +1587,10 @@ 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.remove_repo = Remove
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -945,8 +945,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}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2263,6 +2263,7 @@ $(document).ready(function () {
 | 
			
		|||
 | 
			
		||||
    // Helpers.
 | 
			
		||||
    $('.delete-button').click(showDeletePopup);
 | 
			
		||||
    $('.add-all-button').click(showAddAllPopup);
 | 
			
		||||
 | 
			
		||||
    $('.delete-branch-button').click(showDeletePopup);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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 = ['${', '}'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -152,6 +152,7 @@
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        #add-repo-form,
 | 
			
		||||
        #repo-multiple-form,
 | 
			
		||||
        #add-member-form {
 | 
			
		||||
            input {
 | 
			
		||||
                margin-left: 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -162,5 +163,9 @@
 | 
			
		|||
                margin-top: -3px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #repo-top-segment {
 | 
			
		||||
            height: 60px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -155,6 +155,10 @@ 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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +166,10 @@ func TeamsRepoAction(ctx *context.Context) {
 | 
			
		|||
		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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,25 +9,33 @@
 | 
			
		|||
				{{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 "add"}}</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 "remove_all"}}</button>
 | 
			
		||||
								<button class="ui green button add-all-button right" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/addall">{{.i18n.Tr "add_all"}}</button>
 | 
			
		||||
							</form>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				<div class="ui bottom attached table segment repositories">
 | 
			
		||||
					{{range .Team.Repos}}
 | 
			
		||||
						<div class="item">
 | 
			
		||||
							{{if $canAddRemove}}
 | 
			
		||||
								<a class="ui red small button right" href="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/remove?repoid={{.ID}}">{{$.i18n.Tr "org.teams.remove_repo"}}</a>
 | 
			
		||||
								<a class="ui red small button right" href="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/remove?repoid={{.ID}}">{{$.i18n.Tr "remove"}}</a>
 | 
			
		||||
							{{end}}
 | 
			
		||||
							<a class="member" href="{{AppSubUrl}}/{{$.Org.Name}}/{{.Name}}">
 | 
			
		||||
								<i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
 | 
			
		||||
| 
						 | 
				
			
			@ -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" .}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue