Skip to content

Commit 9456a3c

Browse files
committed
Create service for exporting skill information to CSV
1 parent 79bc8a9 commit 9456a3c

File tree

10 files changed

+283
-0
lines changed

10 files changed

+283
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.objectcomputing.checkins.services.skill_record;
2+
3+
import com.objectcomputing.checkins.services.skillcategory_skill.SkillCategorySkillId;
4+
import io.micronaut.core.annotation.Introspected;
5+
import io.micronaut.data.annotation.MappedEntity;
6+
7+
import javax.persistence.Column;
8+
import javax.persistence.EmbeddedId;
9+
import javax.persistence.Table;
10+
11+
@MappedEntity
12+
@Introspected
13+
@Table(name = "skill_record")
14+
@SuppressWarnings("unused")
15+
public class SkillRecord {
16+
17+
@EmbeddedId
18+
private SkillCategorySkillId skillCategorySkillId;
19+
20+
@Column(name = "name")
21+
private String name;
22+
23+
@Column(name = "description")
24+
private String description;
25+
26+
@Column(name = "extraneous")
27+
private boolean extraneous;
28+
29+
@Column(name = "pending")
30+
private boolean pending;
31+
32+
@Column(name = "category_name")
33+
private String categoryName;
34+
35+
public SkillRecord() {}
36+
37+
public SkillCategorySkillId getSkillCategorySkillId() {
38+
return skillCategorySkillId;
39+
}
40+
41+
public void setSkillCategorySkillId(SkillCategorySkillId skillCategorySkillId) {
42+
this.skillCategorySkillId = skillCategorySkillId;
43+
}
44+
45+
public String getName() {
46+
return name;
47+
}
48+
49+
public void setName(String name) {
50+
this.name = name;
51+
}
52+
53+
public String getDescription() {
54+
return description;
55+
}
56+
57+
public void setDescription(String description) {
58+
this.description = description;
59+
}
60+
61+
public boolean isExtraneous() {
62+
return extraneous;
63+
}
64+
65+
public void setExtraneous(boolean extraneous) {
66+
this.extraneous = extraneous;
67+
}
68+
69+
public boolean isPending() {
70+
return pending;
71+
}
72+
73+
public void setPending(boolean pending) {
74+
this.pending = pending;
75+
}
76+
77+
public String getCategoryName() {
78+
return categoryName;
79+
}
80+
81+
public void setCategoryName(String categoryName) {
82+
this.categoryName = categoryName;
83+
}
84+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.objectcomputing.checkins.services.skill_record;
2+
3+
import com.objectcomputing.checkins.security.permissions.Permissions;
4+
import com.objectcomputing.checkins.services.permissions.RequiredPermission;
5+
import io.micronaut.http.HttpResponse;
6+
import io.micronaut.http.MediaType;
7+
import io.micronaut.http.MutableHttpResponse;
8+
import io.micronaut.http.annotation.Controller;
9+
import io.micronaut.http.annotation.Get;
10+
import io.micronaut.scheduling.TaskExecutors;
11+
import io.micronaut.security.annotation.Secured;
12+
import io.micronaut.security.rules.SecurityRule;
13+
import jakarta.inject.Named;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
import reactor.core.publisher.Mono;
17+
import reactor.core.scheduler.Scheduler;
18+
import reactor.core.scheduler.Schedulers;
19+
20+
import java.io.File;
21+
import java.util.concurrent.ExecutorService;
22+
23+
@Controller("/services/skills/records")
24+
@Secured(SecurityRule.IS_AUTHENTICATED)
25+
public class SkillRecordController {
26+
27+
private static final Logger LOG = LoggerFactory.getLogger(SkillRecordController.class);
28+
private final SkillRecordServices skillRecordServices;
29+
private final Scheduler ioScheduler;
30+
31+
public SkillRecordController(
32+
SkillRecordServices skillRecordServices,
33+
@Named(TaskExecutors.IO) ExecutorService ioExecutorService) {
34+
this.skillRecordServices = skillRecordServices;
35+
this.ioScheduler = Schedulers.fromExecutor(ioExecutorService);
36+
}
37+
38+
@RequiredPermission(Permissions.CAN_VIEW_SKILL_CATEGORIES)
39+
@Get(value = "/csv", produces = MediaType.TEXT_CSV)
40+
public Mono<MutableHttpResponse<File>> generateCsv() {
41+
return Mono.defer(() -> Mono.just(skillRecordServices.generateFile()))
42+
.subscribeOn(ioScheduler)
43+
.map(file -> HttpResponse
44+
.ok(file)
45+
.header("Content-Disposition", String.format("attachment; filename=%s", file.getName())))
46+
.onErrorResume(error -> {
47+
LOG.error("Something went terribly wrong during export... ", error);
48+
return Mono.just(HttpResponse.serverError());
49+
});
50+
}
51+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.objectcomputing.checkins.services.skill_record;
2+
3+
import com.objectcomputing.checkins.services.skillcategory_skill.SkillCategorySkillId;
4+
import io.micronaut.core.annotation.NonNull;
5+
import io.micronaut.data.jdbc.annotation.JdbcRepository;
6+
import io.micronaut.data.model.query.builder.sql.Dialect;
7+
import io.micronaut.data.repository.CrudRepository;
8+
9+
import java.util.List;
10+
11+
@JdbcRepository(dialect = Dialect.POSTGRES)
12+
public interface SkillRecordRepository extends CrudRepository<SkillRecord, SkillCategorySkillId> {
13+
14+
@NonNull
15+
List<SkillRecord> findAll();
16+
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.objectcomputing.checkins.services.skill_record;
2+
3+
import java.io.File;
4+
5+
public interface SkillRecordServices {
6+
7+
File generateFile();
8+
9+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.objectcomputing.checkins.services.skill_record;
2+
3+
import jakarta.inject.Singleton;
4+
import org.apache.commons.csv.CSVFormat;
5+
import org.apache.commons.csv.CSVPrinter;
6+
7+
import java.io.File;
8+
import java.io.FileWriter;
9+
import java.io.IOException;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.List;
12+
13+
@Singleton
14+
public class SkillRecordServicesImpl implements SkillRecordServices {
15+
16+
private final SkillRecordRepository skillRecordRepository;
17+
18+
public SkillRecordServicesImpl(SkillRecordRepository skillRecordRepository) {
19+
this.skillRecordRepository = skillRecordRepository;
20+
}
21+
22+
@Override
23+
public File generateFile() {
24+
List<SkillRecord> skillRecords = skillRecordRepository.findAll();
25+
26+
String[] headers = { "name", "description", "extraneous", "pending", "category_name" };
27+
CSVFormat csvFormat = CSVFormat.DEFAULT.withHeader(headers).withQuote('"');
28+
29+
File csvFile = new File("skill_records.csv");
30+
try (final CSVPrinter printer = new CSVPrinter(new FileWriter(csvFile, StandardCharsets.UTF_8), csvFormat)) {
31+
for (SkillRecord record : skillRecords) {
32+
printer.printRecord(record.getName(), record.getDescription(), record.isExtraneous(), record.isPending(), record.getCategoryName());
33+
}
34+
} catch (IOException e) {
35+
throw new RuntimeException(e);
36+
}
37+
38+
return csvFile;
39+
}
40+
}

server/src/main/java/com/objectcomputing/checkins/services/skillcategory_skill/SkillCategorySkillId.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
@Introspected
1818
public class SkillCategorySkillId {
1919

20+
private static final UUID MARKER = UUID.fromString("6ad7baca-3741-4ae8-b45a-4b82ade40d1f");
21+
2022
@TypeDef(type = DataType.STRING)
2123
@Column(name = "skillcategory_id")
2224
@Schema(description = "The id of the skill category", required = true)
@@ -39,13 +41,17 @@ public SkillCategorySkillId(UUID skillCategoryId, UUID skillId) {
3941
public SkillCategorySkillId() {}
4042

4143
public UUID getSkillCategoryId() {
44+
if (Objects.equals(skillCategoryId, MARKER)) {
45+
return null;
46+
}
4247
return skillCategoryId;
4348
}
4449

4550
public UUID getSkillId() {
4651
return skillId;
4752
}
4853

54+
4955
@Override
5056
public boolean equals(Object o) {
5157
if (this == o) return true;

server/src/main/resources/db/common/V94__create_skill_category_table.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CREATE TABLE skillcategories
55
name varchar UNIQUE,
66
description varchar
77
);
8+
89
DROP TABLE if exists skillcategory_skills;
910
CREATE TABLE skillcategory_skills
1011
(
@@ -16,3 +17,12 @@ CREATE TABLE skillcategory_skills
1617
constraint fk_skill_skillcategoryskills
1718
foreign key (skill_id) references skills(id)
1819
);
20+
21+
DROP VIEW IF EXISTS skill_record;
22+
CREATE VIEW skill_record AS
23+
SELECT s.id AS skill_id,
24+
COALESCE(sc.id, '6ad7baca-3741-4ae8-b45a-4b82ade40d1f') AS skillcategory_id,
25+
s.name, s.description, s.extraneous, s.pending, sc.name AS category_name
26+
FROM skills s
27+
LEFT JOIN skillcategory_skills ss ON s.id = ss.skill_id
28+
LEFT JOIN skillcategories sc ON ss.skillcategory_id = sc.id;

server/src/main/resources/db/dev/R__Load_testing_data.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,4 +1284,9 @@ INSERT INTO skillcategory_skills -- Tools GIT
12841284
values
12851285
('0778a8e7-21d8-4ca3-a0dc-cad676aac417', '84682de9-85a7-4bf7-b74b-e9054311a61a');
12861286

1287+
INSERT INTO skillcategory_skills -- Tools CSS
1288+
(skillcategory_id, skill_id)
1289+
values
1290+
('0778a8e7-21d8-4ca3-a0dc-cad676aac417', '6b56f0aa-09aa-4b09-bb81-03481af7e49f');
1291+
12871292

server/src/test/java/com/objectcomputing/checkins/services/fixture/RepositoryFixture.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.objectcomputing.checkins.services.role.member_roles.MemberRoleRepository;
2727
import com.objectcomputing.checkins.services.role.role_permissions.RolePermissionRepository;
2828
import com.objectcomputing.checkins.services.settings.SettingsRepository;
29+
import com.objectcomputing.checkins.services.skill_record.SkillRecordRepository;
2930
import com.objectcomputing.checkins.services.skillcategory.SkillCategoryRepository;
3031
import com.objectcomputing.checkins.services.skillcategory_skill.SkillCategorySkillRepository;
3132
import com.objectcomputing.checkins.services.skills.SkillRepository;
@@ -188,4 +189,8 @@ default SkillCategoryRepository getSkillCategoryRepository() {
188189
default SkillCategorySkillRepository getSkillCategorySkillRepository() {
189190
return getEmbeddedServer().getApplicationContext().getBean(SkillCategorySkillRepository.class);
190191
}
192+
193+
default SkillRecordRepository getSkillRecordRepository() {
194+
return getEmbeddedServer().getApplicationContext().getBean(SkillRecordRepository.class);
195+
}
191196
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.objectcomputing.checkins.services.skill_record;
2+
3+
import com.objectcomputing.checkins.services.TestContainersSuite;
4+
import com.objectcomputing.checkins.services.fixture.RoleFixture;
5+
import com.objectcomputing.checkins.services.fixture.SkillFixture;
6+
import io.micronaut.http.HttpRequest;
7+
import io.micronaut.http.HttpResponse;
8+
import io.micronaut.http.HttpStatus;
9+
import io.micronaut.http.client.HttpClient;
10+
import io.micronaut.http.client.annotation.Client;
11+
import io.micronaut.http.client.exceptions.HttpClientResponseException;
12+
import jakarta.inject.Inject;
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.io.File;
17+
18+
import static com.objectcomputing.checkins.services.role.RoleType.Constants.ADMIN_ROLE;
19+
import static com.objectcomputing.checkins.services.role.RoleType.Constants.MEMBER_ROLE;
20+
import static org.junit.jupiter.api.Assertions.*;
21+
22+
class SkillRecordControllerTest extends TestContainersSuite implements RoleFixture, SkillFixture {
23+
24+
@Inject
25+
@Client("/services/skills/records")
26+
private HttpClient client;
27+
28+
@BeforeEach
29+
void createRolesAndPermissions() {
30+
createAndAssignRoles();
31+
}
32+
33+
@Test
34+
public void testGetSuccess() {
35+
HttpRequest<?> request = HttpRequest
36+
.GET("/csv")
37+
.basicAuth(ADMIN_ROLE, ADMIN_ROLE);
38+
HttpResponse<File> response = client.toBlocking().exchange(request, File.class);
39+
40+
assertEquals(HttpStatus.OK, response.getStatus());
41+
assertTrue(response.getBody().isPresent());
42+
}
43+
44+
@Test
45+
public void testGetNotAllowed() {
46+
HttpRequest<?> request = HttpRequest
47+
.GET("/csv")
48+
.basicAuth(MEMBER_ROLE, MEMBER_ROLE);
49+
HttpClientResponseException responseException = assertThrows(HttpClientResponseException.class, () ->
50+
client.toBlocking().exchange(request, File.class)
51+
);
52+
53+
assertEquals(HttpStatus.FORBIDDEN, responseException.getStatus());
54+
}
55+
56+
}

0 commit comments

Comments
 (0)