Skip to content

Commit cd3f130

Browse files
committed
Support grouping guides into categories
Closes gh-1
1 parent d40f7ef commit cd3f130

File tree

9 files changed

+259
-46
lines changed

9 files changed

+259
-46
lines changed

src/main/java/io/spring/renderer/RendererProperties.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package io.spring.renderer;
1818

1919
import java.util.HashMap;
20+
import java.util.LinkedHashSet;
2021
import java.util.Map;
22+
import java.util.Set;
2123

24+
import io.spring.renderer.RendererProperties.Webhook.Category;
2225
import jakarta.validation.constraints.Pattern;
2326

2427
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -38,6 +41,8 @@ public class RendererProperties {
3841
*/
3942
private final Map<String, String> academy = new HashMap<>();
4043

44+
private final Map<String, Category> category = new HashMap<>();
45+
4146
public Github getGithub() {
4247
return this.github;
4348
}
@@ -46,6 +51,10 @@ public Map<String, String> getAcademy() {
4651
return this.academy;
4752
}
4853

54+
public Map<String, Category> getCategory() {
55+
return this.category;
56+
}
57+
4958
public static class Github {
5059

5160
/**
@@ -138,6 +147,26 @@ public void setDispatchToken(String dispatchToken) {
138147
this.dispatchToken = dispatchToken;
139148
}
140149

150+
public static class Category {
151+
152+
private String displayName;
153+
154+
private final Set<String> guide = new LinkedHashSet<>();
155+
156+
public String getDisplayName() {
157+
return this.displayName;
158+
}
159+
160+
public void setDisplayName(String displayName) {
161+
this.displayName = displayName;
162+
}
163+
164+
public Set<String> getGuide() {
165+
return this.guide;
166+
}
167+
168+
}
169+
141170
}
142171

143172
}

src/main/java/io/spring/renderer/guides/GuideMetadata.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package io.spring.renderer.guides;
1818

19+
import java.util.Set;
20+
1921
import io.spring.renderer.github.Repository;
2022

2123
/**
@@ -29,9 +31,12 @@ class GuideMetadata {
2931

3032
private final String academyUrl;
3133

32-
GuideMetadata(Repository repository, String academyUrl) {
34+
private final Set<String> category;
35+
36+
GuideMetadata(Repository repository, String academyUrl, Set<String> category) {
3337
this.repository = repository;
3438
this.academyUrl = academyUrl;
39+
this.category = category;
3540
}
3641

3742
public Repository getRepository() {
@@ -42,4 +47,8 @@ public String getAcademyUrl() {
4247
return this.academyUrl;
4348
}
4449

50+
public Set<String> getCategory() {
51+
return this.category;
52+
}
53+
4554
}

src/main/java/io/spring/renderer/guides/GuideModel.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class GuideModel extends RepresentationModel<GuideModel> {
5050

5151
private String academyUrl;
5252

53+
private String[] category;
54+
5355
GuideModel(GuideMetadata guideMetadata) {
5456
Repository repository = guideMetadata.getRepository();
5557
this.type = GuideType.fromRepositoryName(repository.getName());
@@ -76,6 +78,7 @@ class GuideModel extends RepresentationModel<GuideModel> {
7678
this.projects = new String[0];
7779
}
7880
this.academyUrl = guideMetadata.getAcademyUrl();
81+
this.category = guideMetadata.getCategory().toArray(new String[0]);
7982
}
8083

8184
public String getName() {
@@ -122,4 +125,8 @@ public String getAcademyUrl() {
122125
return this.academyUrl;
123126
}
124127

128+
public String[] getCategory() {
129+
return this.category;
130+
}
131+
125132
}

src/main/java/io/spring/renderer/guides/GuidesController.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,22 @@
1616

1717
package io.spring.renderer.guides;
1818

19+
import java.util.Collections;
1920
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Set;
2023
import java.util.stream.Collectors;
2124

2225
import io.spring.renderer.RendererProperties;
26+
import io.spring.renderer.RendererProperties.Webhook.Category;
2327
import io.spring.renderer.github.GithubClient;
2428
import io.spring.renderer.github.GithubResourceNotFoundException;
2529
import io.spring.renderer.github.Repository;
2630

2731
import org.springframework.hateoas.CollectionModel;
2832
import org.springframework.hateoas.MediaTypes;
2933
import org.springframework.http.ResponseEntity;
34+
import org.springframework.util.CollectionUtils;
3035
import org.springframework.web.bind.annotation.ExceptionHandler;
3136
import org.springframework.web.bind.annotation.GetMapping;
3237
import org.springframework.web.bind.annotation.PathVariable;
@@ -51,6 +56,8 @@ public class GuidesController {
5156

5257
private final GuideModelAssembler guideAssembler = new GuideModelAssembler();
5358

59+
private Set<String> DEFAULT_CATEGORY = Collections.singleton("Misc");
60+
5461
public GuidesController(GuideRenderer guideRenderer, GithubClient github, RendererProperties properties) {
5562
this.guideRenderer = guideRenderer;
5663
this.githubClient = github;
@@ -67,7 +74,7 @@ public CollectionModel<GuideModel> listGuides() {
6774
List<Repository> repositories = this.githubClient
6875
.fetchOrgRepositories(this.properties.getGithub().getOrganization());
6976
List<GuideMetadata> guideMetadataList = repositories.stream()
70-
.map((repository) -> new GuideMetadata(repository, getAcademyUrl(repository)))
77+
.map((repository) -> new GuideMetadata(repository, getAcademyUrl(repository), getCategory(repository)))
7178
.toList();
7279
List<GuideModel> guideModels = this.guideAssembler.toCollectionModel(guideMetadataList)
7380
.getContent()
@@ -84,6 +91,16 @@ public CollectionModel<GuideModel> listGuides() {
8491
return resources;
8592
}
8693

94+
private Set<String> getCategory(Repository repository) {
95+
Map<String, Category> category = this.properties.getCategory();
96+
Set<String> values = category.values()
97+
.stream()
98+
.filter((v) -> v.getGuide().contains(repository.getName()))
99+
.map(Category::getDisplayName)
100+
.collect(Collectors.toSet());
101+
return (!CollectionUtils.isEmpty(values)) ? values : DEFAULT_CATEGORY;
102+
}
103+
87104
private String getAcademyUrl(Repository repository) {
88105
return this.properties.getAcademy().get(repository.getName());
89106
}
@@ -97,7 +114,7 @@ public ResponseEntity<GuideModel> showGuide(@PathVariable String type, @PathVari
97114
Repository repository = this.githubClient.fetchOrgRepository(this.properties.getGithub().getOrganization(),
98115
guideType.getPrefix() + guide);
99116
String academyUrl = this.properties.getAcademy().get(repository.getName());
100-
GuideMetadata guideMetadata = new GuideMetadata(repository, academyUrl);
117+
GuideMetadata guideMetadata = new GuideMetadata(repository, academyUrl, getCategory(repository));
101118
GuideModel guideModel = this.guideAssembler.toModel(guideMetadata);
102119
if (guideModel.getType().equals(GuideType.UNKNOWN)) {
103120
return ResponseEntity.notFound().build();

src/main/resources/application.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
spring.config.import: optional:secret.properties
1+
spring.config.import:
2+
- optional:secret.properties
3+
- categories.yml
24
renderer:
35
github:
46
organization: spring-guides
@@ -20,6 +22,7 @@ renderer:
2022
gs-rest-service: https://spring.academy/guides/rest-service
2123
gs-spring-boot: https://spring.academy/guides/building-an-application-with-spring-boot
2224
gs-messaging-rabbitmq: https://spring.academy/guides/messaging-with-rabbitmq
25+
2326
spring:
2427
security:
2528
user:

src/main/resources/categories.yml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
renderer:
2+
category:
3+
batch:
4+
display-name: Batch Processing
5+
guide:
6+
- gs-batch-processing
7+
- gs-spring-cloud-task
8+
caching:
9+
display-name: Caching
10+
guide:
11+
- gs-caching
12+
cloud-platform:
13+
display-name: Cloud Platform
14+
guide:
15+
- gs-spring-boot-for-azure
16+
- gs-spring-boot-kubernetes
17+
data:
18+
display-name: Data Access
19+
guide:
20+
- gs-accessing-data-mysql
21+
- gs-accessing-data-jpa
22+
- gs-accessing-data-mongodb
23+
- gs-relational-data-access
24+
- gs-accessing-data-rest
25+
- gs-managing-transactions
26+
- gs-accessing-data-r2dbc
27+
- gs-accessing-mongodb-data-rest
28+
- gs-accessing-data-cassandra
29+
- gs-spring-data-reactive-redis
30+
- gs-accessing-data-neo4j
31+
- gs-accessing-data-gemfire
32+
io:
33+
display-name: IO
34+
guide:
35+
- gs-uploading-files
36+
- gs-async-method
37+
- gs-validating-form-input
38+
- gs-handling-form-submission
39+
- gs-scheduling-tasks
40+
messaging:
41+
display-name: Messaging
42+
guide:
43+
- gs-messaging-stomp-websocket
44+
- gs-messaging-rabbitmq
45+
- gs-messaging-jms
46+
- gs-messaging-gcp-pubsub
47+
- gs-messaging-gcp-pubsub
48+
- gs-integration
49+
- gs-spring-cloud-stream
50+
microservices:
51+
display-name: Microservices
52+
guide:
53+
- gs-gateway
54+
- gs-service-registration-and-discovery
55+
- gs-spring-cloud-loadbalancer
56+
- gs-cloud-circuit-breaker
57+
- gs-centralized-configuration
58+
rest:
59+
display-name: REST APIs
60+
guide:
61+
- gs-accessing-neo4j-data-rest
62+
- gs-accessing-gemfire-data-rest
63+
- gs-rest-hateoas
64+
- gs-reactive-rest-service
65+
- gs-consuming-rest
66+
- gs-rest-service
67+
- gs-spring-boot
68+
security:
69+
display-name: Security
70+
guide:
71+
- gs-accessing-vault
72+
- gs-vault-config
73+
- gs-authenticating-ldap
74+
- gs-rest-service-cors
75+
- gs-securing-web
76+
testing:
77+
display-name: Testing
78+
guide:
79+
- gs-contract-rest
80+
- gs-testing-web
81+
documentation:
82+
display-name: Documentation
83+
guide:
84+
- gs-testing-restdocs
85+
web:
86+
display-name: Web
87+
guide:
88+
- gs-serving-web-content
89+
- gs-producing-web-service
90+
- gs-consuming-web-service
91+
packaging:
92+
display-name: Packaging
93+
guide:
94+
ops:
95+
display-name: Ops
96+
guide:
97+
- gs-actuator-service
98+
streaming:
99+
display-name: Streaming
100+
guide:
101+
- gs-spring-cloud-dataflow
102+
graphql:
103+
display-name: GraphQL
104+
guide:
105+
- gs-graphql-server
106+

src/test/java/io/spring/renderer/guides/GuideModelTests.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.spring.renderer.guides;
1818

1919
import java.util.Arrays;
20+
import java.util.Collections;
2021

2122
import io.spring.renderer.github.Repository;
2223
import org.junit.jupiter.api.Test;
@@ -35,7 +36,7 @@ public void nullRepositoryDescription() {
3536
"git://example.org/spring-guides/gs-sample-guide.git",
3637
"[email protected]:spring-guides/gs-sample-guide.git",
3738
"https://example.org/spring-guides/gs-sample-guide.git", null);
38-
GuideMetadata guideMetadata = new GuideMetadata(repository, null);
39+
GuideMetadata guideMetadata = new GuideMetadata(repository, null, Collections.singleton("test"));
3940
GuideModel guideModel = new GuideModel(guideMetadata);
4041
assertThat(guideModel.getName()).isEqualTo("sample-guide");
4142
assertThat(guideModel.getRepositoryName()).isEqualTo("spring-guides/gs-sample-guide");
@@ -47,6 +48,7 @@ public void nullRepositoryDescription() {
4748
assertThat(guideModel.getGitUrl()).isEqualTo("git://example.org/spring-guides/gs-sample-guide.git");
4849
assertThat(guideModel.getSshUrl()).isEqualTo("[email protected]:spring-guides/gs-sample-guide.git");
4950
assertThat(guideModel.getProjects()).isEmpty();
51+
assertThat(guideModel.getCategory()).containsExactly("test");
5052
}
5153

5254
@Test
@@ -56,7 +58,7 @@ public void noGuideProjects() {
5658
"git://example.org/spring-guides/tut-sample-guide.git",
5759
"[email protected]:spring-guides/tut-sample-guide.git",
5860
"https://example.org/spring-guides/tut-sample-guide.git", null);
59-
GuideMetadata guideMetadata = new GuideMetadata(repository, "http://test.academy");
61+
GuideMetadata guideMetadata = new GuideMetadata(repository, "http://test.academy", Collections.emptySet());
6062
GuideModel guideModel = new GuideModel(guideMetadata);
6163
assertThat(guideModel.getName()).isEqualTo("sample-guide");
6264
assertThat(guideModel.getRepositoryName()).isEqualTo("spring-guides/tut-sample-guide");
@@ -75,7 +77,7 @@ public void withGuideProjects() {
7577
"[email protected]:spring-guides/top-sample-guide.git",
7678
"https://example.org/spring-guides/top-sample-guide.git",
7779
Arrays.asList("spring-framework", "spring-boot"));
78-
GuideMetadata guideMetadata = new GuideMetadata(repository, null);
80+
GuideMetadata guideMetadata = new GuideMetadata(repository, null, Collections.emptySet());
7981
GuideModel guideModel = new GuideModel(guideMetadata);
8082
assertThat(guideModel.getName()).isEqualTo("sample-guide");
8183
assertThat(guideModel.getRepositoryName()).isEqualTo("spring-guides/top-sample-guide");
@@ -94,7 +96,7 @@ public void deprecatedGuide() {
9496
"[email protected]:spring-guides/deprecated-gs-sample-guide.git",
9597
"https://example.org/spring-guides/deprecated-gs-sample-guide.git",
9698
Arrays.asList("spring-framework", "spring-boot"));
97-
GuideMetadata guideMetadata = new GuideMetadata(repository, null);
99+
GuideMetadata guideMetadata = new GuideMetadata(repository, null, Collections.emptySet());
98100
GuideModel guideModel = new GuideModel(guideMetadata);
99101
assertThat(guideModel.getName()).isEqualTo("deprecated-gs-sample-guide");
100102
assertThat(guideModel.getTitle()).isEqualTo("Title");

0 commit comments

Comments
 (0)