Skip to content

Commit e44af0a

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 e44af0a

File tree

6 files changed

+287
-3
lines changed

6 files changed

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

0 commit comments

Comments
 (0)