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…
	
		Reference in a new issue