Skip to content

Commit 499ce65

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 499ce65

File tree

6 files changed

+287
-3
lines changed

6 files changed

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

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: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.*;
14+
import static org.mockito.Mockito.*;
15+
16+
class GiteaOwnerListHelperTest {
17+
18+
@Test
19+
void getOwners_withValidRepositories_returnsUniqueOwnersIncludingCurrentUser() {
20+
List<GiteaRepository> repositories = Arrays.asList(
21+
createMockRepository("alice"),
22+
createMockRepository("bob"),
23+
createMockRepository("alice") // duplicate
24+
);
25+
26+
Set<String> owners = GiteaOwnerListHelper.getOwners("charlie", repositories);
27+
28+
assertThat(owners, containsInAnyOrder("alice", "bob", "charlie"));
29+
}
30+
31+
@Test
32+
void getOwners_withEmptyRepositories_returnsOnlyCurrentUser() {
33+
List<GiteaRepository> repositories = Collections.emptyList();
34+
35+
Set<String> owners = GiteaOwnerListHelper.getOwners("charlie", repositories);
36+
37+
assertThat(owners.contains("charlie"), is(true));
38+
}
39+
40+
@Test
41+
void populateOwnerListBoxModel_withCurrentOwnerSelected_placesCurrentOwnerFirst() {
42+
List<GiteaRepository> repositories = Arrays.asList(
43+
createMockRepository("alice"),
44+
createMockRepository("bob"));
45+
46+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("charlie", "bob", repositories);
47+
48+
assertThat(result.size(), is(3));
49+
assertThat(result.get(0).name, is("bob"));
50+
assertThat(result.get(1).name, anyOf(is("alice"), is("charlie")));
51+
assertThat(result.get(2).name, anyOf(is("alice"), is("charlie")));
52+
}
53+
54+
@Test
55+
void populateOwnerListBoxModel_withCurrentOwnerNotInList_includesCurrentOwner() {
56+
List<GiteaRepository> repositories = Arrays.asList(
57+
createMockRepository("alice"));
58+
59+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("bob", "charlie", repositories);
60+
61+
assertThat(result.size(), is(2));
62+
assertThat(result.stream().anyMatch(opt -> "charlie".equals(opt.name)), is(false));
63+
assertThat(result.stream().anyMatch(opt -> "alice".equals(opt.name)), is(true));
64+
assertThat(result.stream().anyMatch(opt -> "bob".equals(opt.name)), is(true));
65+
}
66+
67+
@Test
68+
void populateOwnerListBoxModel_selectedOwnerIsFirst_restAreUnordered() {
69+
List<GiteaRepository> repositories = Arrays.asList(
70+
createMockRepository("alice"),
71+
createMockRepository("bob"),
72+
createMockRepository("charlie")
73+
);
74+
75+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("dave", "bob", repositories);
76+
77+
assertThat(result.size(), is(4));
78+
assertThat(result.get(0).name, is("bob")); // selected owner first
79+
80+
// The rest should be alice, charlie, dave in any order
81+
List<String> rest = Arrays.asList(result.get(1).name, result.get(2).name, result.get(3).name);
82+
assertThat(rest, containsInAnyOrder("alice", "charlie", "dave"));
83+
}
84+
85+
@Test
86+
void populateOwnerListBoxModel_selectedOwnerNotPresent() {
87+
List<GiteaRepository> repositories = Arrays.asList(
88+
createMockRepository("alice"),
89+
createMockRepository("charlie")
90+
);
91+
92+
var result = GiteaOwnerListHelper.populateOwnerListBoxModel("dave", "bob", repositories);
93+
94+
assertThat(result.size(), is(3));
95+
assertThat(result.get(0).name, not(is("bob"))); // selected owner first
96+
97+
// The rest should be alice, charlie, dave in any order
98+
List<String> rest = Arrays.asList(result.get(0).name, result.get(1).name, result.get(2).name);
99+
assertThat(rest, containsInAnyOrder("alice", "charlie", "dave"));
100+
}
101+
102+
private GiteaRepository createMockRepository(String ownerName) {
103+
GiteaRepository repo = mock(GiteaRepository.class);
104+
GiteaOwner owner = mock(GiteaOwner.class);
105+
when(owner.getUsername()).thenReturn(ownerName);
106+
when(repo.getOwner()).thenReturn(owner);
107+
return repo;
108+
}
109+
}

0 commit comments

Comments
 (0)