Skip to content

Commit ee8f871

Browse files
committed
Merge branch 'develop' into CLAP-84
2 parents 07fc216 + 9265b4b commit ee8f871

31 files changed

+785
-6
lines changed

.github/image/gyudong.jpg

605 KB
Loading

.github/workflows/dev-cd.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# github repository actions 페이지에 나타날 이름
2+
name: CD to dev using github actions
3+
4+
# event trigger
5+
# develop 브랜치에 pull_request가 닫혔을 때 실행
6+
on:
7+
pull_request:
8+
types: [ closed ]
9+
branches: [ "develop" ]
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
DEV-CD:
16+
if: github.event.pull_request.merged == true
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: 'Set up jdk'
21+
uses: actions/setup-java@v3
22+
with:
23+
java-version: '17'
24+
distribution: 'temurin' # https://github.com/actions/setup-java
25+
26+
- run: touch ./Dockerfile
27+
- run: echo "${{ secrets.DEV_DOCKERFILE }}" > ./Dockerfile
28+
29+
# gradle caching - 빌드 시간 향상
30+
- name: Gradle Caching
31+
uses: actions/cache@v4
32+
with:
33+
path: |
34+
~/.gradle/caches
35+
~/.gradle/wrapper
36+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
37+
restore-keys: |
38+
${{ runner.os }}-gradle-
39+
40+
## gradle build
41+
- name: Build with Gradle
42+
run: |
43+
chmod +x ./gradlew
44+
./gradlew build -x test
45+
46+
## docker build & push to production
47+
- name: Docker build & push to dev
48+
run: |
49+
docker login clap.kr-central-2.kcr.dev -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
50+
docker build -t ${{ secrets.DOCKER_REPO }} .
51+
docker push ${{ secrets.DOCKER_REPO }}
52+
53+
## deploy to dev
54+
- name: Deploy to prod
55+
uses: appleboy/ssh-action@master
56+
id: deploy
57+
with:
58+
host: ${{ secrets.DEV_HOST }}
59+
username: ${{ secrets.DEV_HOST_USERNAME }}
60+
key: ${{ secrets.DEV_HOST_KEY }}
61+
port: ${{ secrets.DEV_HOST_PORT }}
62+
script: |
63+
docker rm -f taskflow
64+
docker image rm ${{ secrets.DOCKER_REPO }} -f
65+
docker run --name taskflow --network host -d -p 9090:9090 ${{ secrets.DOCKER_REPO }} --restart on-failure

.github/workflows/dev-ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# github repository actions 페이지에 나타날 이름
2+
name: CI for dev using github actions
3+
4+
# event trigger
5+
# develop 브랜치에 pull_request가 열렸을 때 실행
6+
on:
7+
pull_request:
8+
types: [opened, synchronize]
9+
branches: [ "develop" ]
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
DEV_CI:
16+
if: github.event.action == 'opened' || github.event.action == 'synchronize'
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v3
20+
- name: 'Set up jdk'
21+
uses: actions/setup-java@v3
22+
with:
23+
java-version: '17'
24+
distribution: 'temurin' # https://github.com/actions/setup-java
25+
26+
# gradle caching - 빌드 시간 향상
27+
- name: Gradle Caching
28+
uses: actions/cache@v4
29+
with:
30+
path: |
31+
~/.gradle/caches
32+
~/.gradle/wrapper
33+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
34+
restore-keys: |
35+
${{ runner.os }}-gradle-
36+
37+
## gradle build
38+
- name: Build with Gradle
39+
run: |
40+
chmod +x ./gradlew
41+
./gradlew build

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Coming Soon...
1212
## 💁‍♂️ Team Member
1313
| 서주원 (PL) | 나은비(BE) | 박재학(BE) | 양시훈(BE) | 이규동(BE) | 최효성(BE & INFRA) |
1414
|:---------:|:--------:|:--------:|:--------:|:--------:|:--------:|
15-
| <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/joowojr.JPG" width="150px" > | <img src="https://github.com/user-attachments/assets/5c59f742-8f2b-4472-bff4-d8dff350481b" width="150px"> | <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/jaehak.jpg" width="150px" > | <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/Sihun23.jpeg" width="150px" > | ![](사진) | <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/hyoseong.jpg" width="150px" > |
15+
| <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/joowojr.JPG" width="150px" > | <img src="https://github.com/user-attachments/assets/5c59f742-8f2b-4472-bff4-d8dff350481b" width="150px"> | <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/jaehak.jpg" width="150px" > | <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/Sihun23.jpeg" width="150px" > | <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/gyudong.jpg" width="150px" > | <img src="https://github.com/TaskFlow-CLAP/TaskFlow-Server/blob/develop/.github/image/hyoseong.jpg" width="150px" > |
1616
| [Github](https://github.com/joowojr) | [Github](https://github.com/nano-mm) | [Github](https://github.com/parkjaehak) | [Github](https://github.com/Sihun23) | [Github](https://github.com/starboxxxx) | [Github](https://github.com/hyoseong-Choi) |
1717

1818
## ⚒️ Stack

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ repositories {
2626
dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-web'
2828

29+
//ElasticSearch
30+
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
31+
2932
// validator
3033
implementation 'org.springframework.boot:spring-boot-starter-validation:3.4.1'
3134

@@ -71,6 +74,10 @@ dependencies {
7174

7275
// Junit
7376
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
77+
testImplementation 'org.testcontainers:junit-jupiter'
78+
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
79+
testImplementation 'org.testcontainers:elasticsearch:1.20.4'
80+
testImplementation 'com.redis:testcontainers-redis:2.2.2'
7481

7582
// Json
7683
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.1'

src/main/java/clap/server/TaskflowApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.scheduling.annotation.EnableScheduling;
56

67
@SpringBootApplication
8+
@EnableScheduling
79
public class TaskflowApplication {
810

911
public static void main(String[] args) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package clap.server.adapter.inbound.web.statistics;
2+
3+
import clap.server.application.port.inbound.statistics.FindCategoryTaskRequestUsecase;
4+
import clap.server.application.port.inbound.statistics.FindPeriodTaskProcessUsecase;
5+
import clap.server.application.port.inbound.statistics.FindPeriodTaskRequestUsecase;
6+
import clap.server.application.port.inbound.statistics.FindSubCategoryTaskRequestUsecase;
7+
import clap.server.common.annotation.architecture.WebAdapter;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
12+
13+
import java.util.Map;
14+
15+
@WebAdapter
16+
@RequiredArgsConstructor
17+
public class FindStatisticsController {
18+
private final FindPeriodTaskRequestUsecase findPeriodTaskRequestUsecase;
19+
private final FindPeriodTaskProcessUsecase findPeriodTaskProcessUsecase;
20+
private final FindCategoryTaskRequestUsecase findCategoryTaskRequestUsecase;
21+
private final FindSubCategoryTaskRequestUsecase findSubCategoryTaskRequestUsecase;
22+
// private final ManagerTaskProcessUsecase managerTaskProcessUsecase;
23+
24+
@GetMapping(value = "/task/statistics/task-requests-by-period")
25+
public ResponseEntity<Map<String, Long>> aggregatePeriodTaskRequest(@RequestParam String period) {
26+
return ResponseEntity.ok(findPeriodTaskRequestUsecase.aggregatePeriodTaskRequest(period));
27+
}
28+
29+
@GetMapping("/task/statistics/task-processed-by-period")
30+
public ResponseEntity<Map<String, Long>> aggregatePeriodTaskProcess(@RequestParam String period) {
31+
return ResponseEntity.ok(findPeriodTaskProcessUsecase.aggregatePeriodTaskProcess(period));
32+
}
33+
@GetMapping("/task/statistics/task-requests-by-category")
34+
public ResponseEntity<Map<String, Long>> aggregateCategoryTaskRequest(@RequestParam String period) {
35+
return ResponseEntity.ok(findCategoryTaskRequestUsecase.aggregateCategoryTaskRequest(period));
36+
}
37+
38+
@GetMapping("/task/statistics/task-requests-by-subcategory")
39+
public ResponseEntity<Map<String, Long>> aggregateSubCategoryTaskRequest(@RequestParam String period, @RequestParam String mainCategory) {
40+
return ResponseEntity.ok(findSubCategoryTaskRequestUsecase.aggregateSubCategoryTaskRequest(period, mainCategory));
41+
}
42+
// @GetMapping("/task/statistics/tasks-processed-by-manager")
43+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package clap.server.adapter.outbound.infrastructure.elastic;
2+
3+
import co.elastic.clients.elasticsearch._types.aggregations.CalendarInterval;
4+
import lombok.Getter;
5+
import lombok.RequiredArgsConstructor;
6+
7+
@Getter
8+
@RequiredArgsConstructor
9+
public enum PeriodConfig {
10+
DAY(1, CalendarInterval.Hour, 11, 19),
11+
WEEK(14, CalendarInterval.Day, 0, 10);
12+
13+
private final long daysToSubtract;
14+
private final CalendarInterval calendarInterval;
15+
private final int substringStart;
16+
private final int substringEnd;
17+
18+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package clap.server.adapter.outbound.infrastructure.elastic;
2+
3+
import clap.server.adapter.outbound.infrastructure.elastic.document.TaskDocument;
4+
import clap.server.adapter.outbound.infrastructure.elastic.repository.TaskElasticRepository;
5+
import clap.server.application.port.outbound.task.TaskDocumentPort;
6+
import clap.server.common.annotation.architecture.InfrastructureAdapter;
7+
import clap.server.domain.model.task.Task;
8+
import co.elastic.clients.elasticsearch._types.aggregations.AggregationBuilders;
9+
import co.elastic.clients.elasticsearch._types.aggregations.MultiBucketBase;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregations;
12+
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
13+
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
14+
15+
import java.time.LocalDate;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.TreeMap;
19+
import java.util.stream.Collectors;
20+
21+
@InfrastructureAdapter
22+
@RequiredArgsConstructor
23+
public class TaskDocumentAdapter implements TaskDocumentPort {
24+
private final TaskElasticRepository taskElasticRepository;
25+
private final ElasticsearchOperations elasticsearchOperations;
26+
27+
@Override
28+
public void saveStatistics(List<Task> statistics) {
29+
taskElasticRepository.saveAll(statistics.stream().map(TaskDocument::new).toList());
30+
}
31+
32+
@Override
33+
public Map<String, Long> findPeriodTaskRequestByPeriod(String period) {
34+
PeriodConfig periodConfig = PeriodConfig.valueOf(period.toUpperCase());
35+
36+
NativeQuery query = buildPeriodTaskRequestQuery(periodConfig);
37+
return getPeriodTaskResults(executeQuery(query), periodConfig);
38+
}
39+
40+
@Override
41+
public Map<String, Long> findPeriodTaskProcessByPeriod(String period) {
42+
PeriodConfig periodConfig = PeriodConfig.valueOf(period.toUpperCase());
43+
44+
NativeQuery query = buildPeriodTaskProcessQuery(periodConfig);
45+
return getPeriodTaskResults(executeQuery(query), periodConfig);
46+
}
47+
48+
@Override
49+
public Map<String, Long> findCategoryTaskRequestByPeriod(String period) {
50+
PeriodConfig periodConfig = PeriodConfig.valueOf(period.toUpperCase());
51+
52+
NativeQuery query = buildCategoryTaskRequestQuery(periodConfig);
53+
return getCategoryTaskResults(executeQuery(query));
54+
}
55+
56+
@Override
57+
public Map<String, Long> findSubCategoryTaskRequestByPeriod(String period, String mainCategory) {
58+
PeriodConfig periodConfig = PeriodConfig.valueOf(period.toUpperCase());
59+
60+
NativeQuery query = buildSubCategoryTaskRequestQuery(periodConfig, mainCategory);
61+
return getCategoryTaskResults(executeQuery(query));
62+
}
63+
64+
private NativeQuery buildPeriodTaskRequestQuery(PeriodConfig config) {
65+
return NativeQuery.builder()
66+
.withQuery(q -> q
67+
.range(r -> r
68+
.date(d -> d
69+
.field("created_at")
70+
.gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract()))))))
71+
.withAggregation("period_task", AggregationBuilders.dateHistogram()
72+
.field("created_at")
73+
.calendarInterval(config.getCalendarInterval())
74+
.build()._toAggregation())
75+
.withMaxResults(0)
76+
.build();
77+
}
78+
79+
private NativeQuery buildPeriodTaskProcessQuery(PeriodConfig config) {
80+
NativeQuery rangeQuery = NativeQuery.builder()
81+
.withQuery(q -> q
82+
.range(r -> r
83+
.date(d -> d
84+
.field("created_at")
85+
.gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract())))))).build();
86+
NativeQuery statusQuery = NativeQuery.builder()
87+
.withQuery(q -> q
88+
.term(v -> v
89+
.field("status")
90+
.value("completed"))).build();
91+
92+
return NativeQuery.builder()
93+
.withQuery(q -> q
94+
.bool(b -> b
95+
.must(rangeQuery.getQuery(), statusQuery.getQuery()))
96+
)
97+
.withAggregation("period_task", AggregationBuilders.dateHistogram()
98+
.field("created_at")
99+
.calendarInterval(config.getCalendarInterval())
100+
.build()._toAggregation())
101+
.withMaxResults(0)
102+
.build();
103+
}
104+
105+
private NativeQuery buildCategoryTaskRequestQuery(PeriodConfig config) {
106+
return NativeQuery.builder()
107+
.withQuery(q -> q
108+
.range(r -> r
109+
.date(d -> d
110+
.field("created_at")
111+
.gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract()))))))
112+
.withAggregation("category_task", AggregationBuilders.terms()
113+
.field("main_category")
114+
.build()._toAggregation())
115+
.withMaxResults(0)
116+
.build();
117+
}
118+
119+
private NativeQuery buildSubCategoryTaskRequestQuery(PeriodConfig config, String mainCategory) {
120+
NativeQuery rangeQuery = NativeQuery.builder()
121+
.withQuery(q -> q
122+
.range(r -> r
123+
.date(d -> d
124+
.field("created_at")
125+
.gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract())))))).build();
126+
NativeQuery categoryQuery = NativeQuery.builder()
127+
.withQuery(q -> q
128+
.term(v -> v
129+
.field("main_category")
130+
.value(mainCategory))).build();
131+
132+
return NativeQuery.builder()
133+
.withQuery(q -> q
134+
.bool(b -> b
135+
.must(rangeQuery.getQuery(), categoryQuery.getQuery()))
136+
)
137+
.withAggregation("category_task", AggregationBuilders.terms()
138+
.field("sub_category")
139+
.build()._toAggregation())
140+
.withMaxResults(0)
141+
.build();
142+
}
143+
144+
private ElasticsearchAggregations executeQuery(NativeQuery query) {
145+
return (ElasticsearchAggregations) elasticsearchOperations
146+
.search(query, TaskDocument.class)
147+
.getAggregations();
148+
}
149+
150+
private Map<String, Long> getPeriodTaskResults(ElasticsearchAggregations aggregations, PeriodConfig config) {
151+
return new TreeMap<>(
152+
aggregations.get("period_task")
153+
.aggregation()
154+
.getAggregate()
155+
.dateHistogram()
156+
.buckets()
157+
.array()
158+
.stream()
159+
.collect(Collectors.toMap(
160+
bucket -> bucket.keyAsString().substring(
161+
config.getSubstringStart(),
162+
config.getSubstringEnd()
163+
),
164+
MultiBucketBase::docCount
165+
))
166+
);
167+
}
168+
169+
private Map<String, Long> getCategoryTaskResults(ElasticsearchAggregations aggregations) {
170+
return new TreeMap<>(
171+
aggregations.get("category_task")
172+
.aggregation()
173+
.getAggregate()
174+
.sterms()
175+
.buckets()
176+
.array()
177+
.stream()
178+
.collect(Collectors.toMap(
179+
bucket -> bucket.key().stringValue(),
180+
MultiBucketBase::docCount
181+
))
182+
);
183+
}
184+
}

0 commit comments

Comments
 (0)