Skip to content

Commit bbf7c64

Browse files
committed
Make the owner field auto-discoverable
1. `doFillRepoOwnerItems` method: * fetches all accessible repositories using `c.fetchCurrentUserRepositories()` * extracts unique owner names from the repositories * returns them as a ListBoxModel for the dropdown * maintains the current selection if it's valid 2. `owner` field in the UI is changed from `textbox` to `select` with the right dependencies. Signed-off-by: Nikita Vakula <[email protected]>
1 parent 371f87c commit bbf7c64

File tree

6 files changed

+288
-3
lines changed

6 files changed

+288
-3
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.jenkinsci.plugin.gitea;
2+
3+
import hudson.util.ListBoxModel;
4+
import java.util.HashSet;
5+
import java.util.List;
6+
import java.util.Set;
7+
import org.jenkinsci.plugin.gitea.client.api.GiteaRepository;
8+
9+
/**
10+
* Utility class for returning unique Gitea repository owners.
11+
*/
12+
public class GiteaOwnerListHelper {
13+
/**
14+
* Retrieves unique repository owners from the given list of repositories.
15+
*
16+
* @param currentUser the Gitea user used for fetching the repositories
17+
* @param repositories the list of repositories to extract owners from
18+
* @return a Set of unique owner names
19+
*/
20+
public static Set<String> getOwners(String currentUser,
21+
List<GiteaRepository> repositories) {
22+
Set<String> owners = new HashSet<>();
23+
24+
for (GiteaRepository repo : repositories) {
25+
String owner = repo.getOwner().getUsername();
26+
owners.add(owner); // Set will handle duplicates
27+
}
28+
29+
owners.add(currentUser); // Ensure current user is included
30+
31+
return owners;
32+
}
33+
34+
/**
35+
* Returns a ListBoxModel with repository owners.
36+
*
37+
* @param currentUser the Gitea user used for fetching the repositories
38+
* @param currentOwner the currently selected owner (will be preserved if valid)
39+
* @param repositories the list of repositories to extract owners from
40+
* @return the populated ListBoxModel
41+
*/
42+
public static ListBoxModel populateOwnerListBoxModel(String currentUser,
43+
String currentOwner,
44+
List<GiteaRepository> repositories) {
45+
ListBoxModel result = new ListBoxModel();
46+
Set<String> owners = getOwners(currentUser, repositories);
47+
// Add owners to the result, with current selection first if it exists
48+
if (owners.remove(currentOwner)) {
49+
result.add(currentOwner);
50+
}
51+
52+
for (String owner : owners) {
53+
result.add(owner);
54+
}
55+
56+
return result;
57+
}
58+
}

src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMNavigator.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
import java.util.Iterator;
5252
import java.util.List;
5353
import java.util.Set;
54+
import java.util.logging.Level;
55+
import java.util.logging.Logger;
5456
import jenkins.authentication.tokens.api.AuthenticationTokens;
5557
import jenkins.model.Jenkins;
5658
import jenkins.plugins.git.traits.GitBrowserSCMSourceTrait;
@@ -90,6 +92,7 @@
9092
public class GiteaSCMNavigator extends SCMNavigator {
9193
private final String serverUrl;
9294
private final String repoOwner;
95+
private static final Logger LOGGER = Logger.getLogger(GiteaSCMNavigator.class.getName());
9396
private String credentialsId;
9497
private List<SCMTrait<? extends SCMTrait<?>>> traits = new ArrayList<>();
9598
private GiteaOwner giteaOwner;
@@ -385,6 +388,61 @@ public FormValidation doCheckCredentialsId(@AncestorInPath SCMSourceOwner contex
385388
return FormValidation.ok();
386389
}
387390

391+
public ListBoxModel doFillRepoOwnerItems(@AncestorInPath SCMSourceOwner context,
392+
@QueryParameter String serverUrl,
393+
@QueryParameter String credentialsId,
394+
@QueryParameter String repoOwner) throws IOException,
395+
InterruptedException {
396+
ListBoxModel result = new ListBoxModel();
397+
if (context == null) {
398+
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
399+
result.add(repoOwner);
400+
return result;
401+
}
402+
} else {
403+
if (!context.hasPermission(Item.EXTENDED_READ)
404+
&& !context.hasPermission(CredentialsProvider.USE_ITEM)) {
405+
result.add(repoOwner);
406+
return result;
407+
}
408+
}
409+
GiteaServer server = GiteaServers.get().findServer(serverUrl);
410+
if (server == null) {
411+
result.add(repoOwner);
412+
return result;
413+
}
414+
StandardCredentials credentials = CredentialsMatchers.firstOrNull(
415+
CredentialsProvider.lookupCredentials(
416+
StandardCredentials.class,
417+
context,
418+
context instanceof Queue.Task
419+
? ((Queue.Task) context).getDefaultAuthentication()
420+
: ACL.SYSTEM,
421+
URIRequirementBuilder.fromUri(serverUrl).build()
422+
),
423+
CredentialsMatchers.allOf(
424+
AuthenticationTokens.matcher(GiteaAuth.class),
425+
CredentialsMatchers.withId(credentialsId)
426+
)
427+
);
428+
if (credentials == null) {
429+
result.add(repoOwner);
430+
return result;
431+
}
432+
433+
try (GiteaConnection c = Gitea.server(serverUrl)
434+
.as(AuthenticationTokens.convert(GiteaAuth.class, credentials))
435+
.open()) {
436+
List<GiteaRepository> repositories = c.fetchCurrentUserRepositories();
437+
String currentUser = c.fetchCurrentUser().getUsername();
438+
return GiteaOwnerListHelper.populateOwnerListBoxModel(currentUser, repoOwner, repositories);
439+
} catch (IOException e) {
440+
LOGGER.log(Level.FINE, "Could not populate owners", e);
441+
}
442+
443+
return result;
444+
}
445+
388446
@NonNull
389447
@Override
390448
public String getDescription() {

src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMSource.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,62 @@ public FormValidation doCheckCredentialsId(@AncestorInPath SCMSourceOwner contex
919919
return FormValidation.ok();
920920
}
921921

922+
public ListBoxModel doFillRepoOwnerItems(@AncestorInPath SCMSourceOwner context,
923+
@QueryParameter String serverUrl,
924+
@QueryParameter String credentialsId,
925+
@QueryParameter String repoOwner) throws IOException,
926+
InterruptedException {
927+
ListBoxModel result = new ListBoxModel();
928+
if (context == null) {
929+
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
930+
// must have admin if you want the list without a context
931+
result.add(repoOwner);
932+
return result;
933+
}
934+
} else {
935+
if (!context.hasPermission(Item.EXTENDED_READ)
936+
&& !context.hasPermission(CredentialsProvider.USE_ITEM)) {
937+
result.add(repoOwner);
938+
return result;
939+
}
940+
}
941+
GiteaServer server = GiteaServers.get().findServer(serverUrl);
942+
if (server == null) {
943+
result.add(repoOwner);
944+
return result;
945+
}
946+
StandardCredentials credentials = CredentialsMatchers.firstOrNull(
947+
CredentialsProvider.lookupCredentials(
948+
StandardCredentials.class,
949+
context,
950+
context instanceof Queue.Task
951+
? ((Queue.Task) context).getDefaultAuthentication()
952+
: ACL.SYSTEM,
953+
URIRequirementBuilder.fromUri(serverUrl).build()
954+
),
955+
CredentialsMatchers.allOf(
956+
AuthenticationTokens.matcher(GiteaAuth.class),
957+
CredentialsMatchers.withId(credentialsId)
958+
)
959+
);
960+
if (credentials == null) {
961+
result.add(repoOwner);
962+
return result;
963+
}
964+
965+
try (GiteaConnection c = Gitea.server(serverUrl)
966+
.as(AuthenticationTokens.convert(GiteaAuth.class, credentials))
967+
.open()) {
968+
List<GiteaRepository> repositories = c.fetchCurrentUserRepositories();
969+
String currentUser = c.fetchCurrentUser().getUsername();
970+
return GiteaOwnerListHelper.populateOwnerListBoxModel(currentUser, repoOwner, repositories);
971+
} catch (IOException e) {
972+
LOGGER.log(Level.FINE, "Could not populate owners", e);
973+
}
974+
975+
return result;
976+
}
977+
922978
public ListBoxModel doFillRepositoryItems(@AncestorInPath SCMSourceOwner context,
923979
@QueryParameter String serverUrl,
924980
@QueryParameter String credentialsId,

src/main/resources/org/jenkinsci/plugin/gitea/GiteaSCMNavigator/config.jelly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<c:select/>
99
</f:entry>
1010
<f:entry title="${%Owner}" field="repoOwner">
11-
<f:textbox/>
11+
<f:select fillDependsOn="serverUrl,credentialsId"/>
1212
</f:entry>
1313
<f:entry title="${%Behaviours}">
1414
<scm:traits field="traits"/>

src/main/resources/org/jenkinsci/plugin/gitea/GiteaSCMSource/config-detail.jelly

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
<c:select/>
99
</f:entry>
1010
<f:entry title="${%Owner}" field="repoOwner">
11-
<f:textbox/>
11+
<f:select fillDependsOn="serverUrl,credentialsId"/>
1212
</f:entry>
1313
<f:entry title="${%Repository}" field="repository">
14-
<f:select/>
14+
<f:select fillDependsOn="serverUrl,credentialsId,repoOwner"/>
1515
</f:entry>
1616
<f:entry title="${%Behaviours}">
1717
<scm:traits field="traits"/>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.jenkinsci.plugin.gitea;
2+
3+
import org.jenkinsci.plugin.gitea.client.api.GiteaOwner;
4+
import org.jenkinsci.plugin.gitea.client.api.GiteaRepository;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
import java.util.List;
10+
import java.util.Set;
11+
12+
import static org.hamcrest.MatcherAssert.assertThat;
13+
import static org.hamcrest.Matchers.anyOf;
14+
import static org.hamcrest.Matchers.containsInAnyOrder;
15+
import static org.hamcrest.Matchers.is;
16+
import static org.hamcrest.Matchers.not;
17+
import static org.mockito.Mockito.mock;
18+
import static org.mockito.Mockito.when;
19+
20+
class GiteaOwnerListHelperTest {
21+
22+
@Test
23+
void getOwners_withValidRepositories_returnsUniqueOwnersIncludingCurrentUser() {
24+
List<GiteaRepository> repositories = Arrays.asList(
25+
createMockRepository("alice"),
26+
createMockRepository("bob"),
27+
createMockRepository("alice") // duplicate
28+
);
29+
30+
Set<String> owners = GiteaOwnerListHelper.getOwners("charlie", repositories);
31+
32+
assertThat(owners, containsInAnyOrder("alice", "bob", "charlie"));
33+
}
34+
35+
@Test
36+
void getOwners_withEmptyRepositories_returnsOnlyCurrentUser() {
37+
List<GiteaRepository> repositories = Collections.emptyList();
38+
39+
Set<String> owners = GiteaOwnerListHelper.getOwners("charlie", repositories);
40+
41+
assertThat(owners.contains("charlie"), is(true));
42+
}
43+
44+
@Test
45+
void populateOwnerListBoxModel_withCurrentOwnerSelected_placesCurrentOwnerFirst() {
46+
List<GiteaRepository> repositories = Arrays.asList(
47+
createMockRepository("alice"),
48+
createMockRepository("bob"));
49+
50+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("charlie", "bob", repositories);
51+
52+
assertThat(result.size(), is(3));
53+
assertThat(result.get(0).name, is("bob"));
54+
assertThat(result.get(1).name, anyOf(is("alice"), is("charlie")));
55+
assertThat(result.get(2).name, anyOf(is("alice"), is("charlie")));
56+
}
57+
58+
@Test
59+
void populateOwnerListBoxModel_withCurrentOwnerNotInList_includesCurrentOwner() {
60+
List<GiteaRepository> repositories = Arrays.asList(
61+
createMockRepository("alice"));
62+
63+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("bob", "charlie", repositories);
64+
65+
assertThat(result.size(), is(2));
66+
assertThat(result.stream().anyMatch(opt -> "charlie".equals(opt.name)), is(false));
67+
assertThat(result.stream().anyMatch(opt -> "alice".equals(opt.name)), is(true));
68+
assertThat(result.stream().anyMatch(opt -> "bob".equals(opt.name)), is(true));
69+
}
70+
71+
@Test
72+
void populateOwnerListBoxModel_selectedOwnerIsFirst_restAreUnordered() {
73+
List<GiteaRepository> repositories = Arrays.asList(
74+
createMockRepository("alice"),
75+
createMockRepository("bob"),
76+
createMockRepository("charlie")
77+
);
78+
79+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("dave", "bob", repositories);
80+
81+
assertThat(result.size(), is(4));
82+
assertThat(result.get(0).name, is("bob")); // selected owner first
83+
84+
// The rest should be alice, charlie, dave in any order
85+
List<String> rest = Arrays.asList(result.get(1).name, result.get(2).name, result.get(3).name);
86+
assertThat(rest, containsInAnyOrder("alice", "charlie", "dave"));
87+
}
88+
89+
@Test
90+
void populateOwnerListBoxModel_selectedOwnerNotPresent() {
91+
List<GiteaRepository> repositories = Arrays.asList(
92+
createMockRepository("alice"),
93+
createMockRepository("charlie")
94+
);
95+
96+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("dave", "bob", repositories);
97+
98+
assertThat(result.size(), is(3));
99+
assertThat(result.get(0).name, not(is("bob"))); // selected owner first
100+
101+
// The rest should be alice, charlie, dave in any order
102+
List<String> rest = Arrays.asList(result.get(0).name, result.get(1).name, result.get(2).name);
103+
assertThat(rest, containsInAnyOrder("alice", "charlie", "dave"));
104+
}
105+
106+
private GiteaRepository createMockRepository(String ownerName) {
107+
GiteaRepository repo = mock(GiteaRepository.class);
108+
GiteaOwner owner = mock(GiteaOwner.class);
109+
when(owner.getUsername()).thenReturn(ownerName);
110+
when(repo.getOwner()).thenReturn(owner);
111+
return repo;
112+
}
113+
}

0 commit comments

Comments
 (0)