-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathServiceInsightController.java
More file actions
230 lines (197 loc) · 10.2 KB
/
Copy pathServiceInsightController.java
File metadata and controls
230 lines (197 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package apu.saerok_admin.web;
import apu.saerok_admin.web.serviceinsight.ServiceInsightAjaxResponse;
import apu.saerok_admin.web.serviceinsight.ServiceInsightQuery;
import apu.saerok_admin.web.serviceinsight.ServiceInsightRangePreset;
import apu.saerok_admin.web.serviceinsight.ServiceInsightService;
import apu.saerok_admin.web.view.Breadcrumb;
import apu.saerok_admin.web.view.ServiceInsightViewModel;
import apu.saerok_admin.web.view.ToastMessage;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
@Controller
public class ServiceInsightController {
private static final Logger log = LoggerFactory.getLogger(ServiceInsightController.class);
private static final String ERROR_TOAST_ID = "toastServiceInsightError";
private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Seoul");
private static final ServiceInsightRangePreset DEFAULT_PRESET = ServiceInsightRangePreset.LAST_14_DAYS;
private final ServiceInsightService serviceInsightService;
private final ObjectMapper objectMapper;
public ServiceInsightController(ServiceInsightService serviceInsightService, ObjectMapper objectMapper) {
this.serviceInsightService = serviceInsightService;
// ObjectMapper 설정 강화
this.objectMapper = objectMapper.copy()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
@GetMapping(value = "/service-insight", produces = MediaType.TEXT_HTML_VALUE)
public String serviceInsight(
Model model,
@RequestParam(value = "range", required = false) String rangeParam,
@RequestParam(value = "startDate", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam(value = "endDate", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate
) {
model.addAttribute("pageTitle", "서비스 인사이트");
model.addAttribute("activeMenu", "serviceInsight");
model.addAttribute("breadcrumbs", List.of(
Breadcrumb.of("대시보드", "/"),
Breadcrumb.active("서비스 인사이트")
));
ensureToastMessages(model);
PageData pageData = loadPageData(rangeParam, startDate, endDate);
ServiceInsightViewModel viewModel = pageData.viewModel();
RangeSelection rangeSelection = pageData.rangeSelection();
if (pageData.hadError()) {
attachErrorToast(model);
}
model.addAttribute("serviceInsight", viewModel);
String chartDataJson = toJson(viewModel);
model.addAttribute("chartDataJson", chartDataJson);
model.addAttribute("rangeQuickOptions", ServiceInsightRangePreset.quickSelections());
model.addAttribute("selectedRange", rangeSelection.preset().paramValue());
model.addAttribute("customRangeActive", rangeSelection.preset() == ServiceInsightRangePreset.CUSTOM);
model.addAttribute("selectedStartDate", rangeSelection.query().startDate());
model.addAttribute("selectedEndDate", rangeSelection.query().endDate());
log.debug("Chart data JSON length: {}", chartDataJson.length());
return "service-insight/index";
}
@GetMapping(value = "/service-insight", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ServiceInsightAjaxResponse> serviceInsightData(
@RequestParam(value = "range", required = false) String rangeParam,
@RequestParam(value = "startDate", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam(value = "endDate", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate
) {
PageData pageData = loadPageData(rangeParam, startDate, endDate);
RangeSelection rangeSelection = pageData.rangeSelection();
ServiceInsightAjaxResponse response = new ServiceInsightAjaxResponse(
pageData.viewModel(),
rangeSelection.preset().paramValue(),
rangeSelection.preset() == ServiceInsightRangePreset.CUSTOM,
rangeSelection.query().startDate(),
rangeSelection.query().endDate(),
pageData.hadError()
);
return ResponseEntity.ok(response);
}
private void ensureToastMessages(Model model) {
if (!model.containsAttribute("toastMessages")) {
model.addAttribute("toastMessages", List.of());
}
}
private PageData loadPageData(String rangeParam, LocalDate startDate, LocalDate endDate) {
RangeSelection rangeSelection = resolveRange(rangeParam, startDate, endDate);
try {
ServiceInsightViewModel viewModel = serviceInsightService.loadViewModel(rangeSelection.query());
log.info("Successfully loaded service insight view model with {} metrics (range: {} - {}, preset: {})",
viewModel.metricOptions().size(),
rangeSelection.query().startDate(),
rangeSelection.query().endDate(),
rangeSelection.preset().name());
return new PageData(rangeSelection, viewModel, false);
} catch (RestClientResponseException exception) {
log.warn(
"Failed to load service insight stats. status={}, body={}",
exception.getStatusCode(),
exception.getResponseBodyAsString(),
exception
);
} catch (RestClientException | IllegalStateException exception) {
log.warn("Failed to load service insight stats.", exception);
}
ServiceInsightViewModel fallback = serviceInsightService.defaultViewModel();
return new PageData(rangeSelection, fallback, true);
}
private RangeSelection resolveRange(String rangeParam, LocalDate startDate, LocalDate endDate) {
LocalDate today = LocalDate.now(DEFAULT_ZONE);
ServiceInsightRangePreset requestedPreset = ServiceInsightRangePreset.fromParameter(rangeParam)
.orElse(DEFAULT_PRESET);
if (startDate != null || endDate != null) {
requestedPreset = ServiceInsightRangePreset.CUSTOM;
}
if (requestedPreset == ServiceInsightRangePreset.ALL) {
return new RangeSelection(ServiceInsightRangePreset.ALL, ServiceInsightQuery.all());
}
if (requestedPreset == ServiceInsightRangePreset.CUSTOM) {
if (startDate == null || endDate == null) {
log.debug("Incomplete custom range supplied (start={}, end={}), falling back to default preset {}",
startDate,
endDate,
DEFAULT_PRESET.name());
return buildPresetSelection(DEFAULT_PRESET, today);
}
LocalDate effectiveStart = startDate;
LocalDate effectiveEnd = endDate;
if (effectiveEnd.isBefore(effectiveStart)) {
effectiveStart = endDate;
effectiveEnd = startDate;
}
if (effectiveEnd.isAfter(today)) {
effectiveEnd = today;
}
if (effectiveStart.isAfter(effectiveEnd)) {
effectiveStart = effectiveEnd;
}
return new RangeSelection(ServiceInsightRangePreset.CUSTOM, new ServiceInsightQuery(effectiveStart, effectiveEnd));
}
return buildPresetSelection(requestedPreset, today);
}
private RangeSelection buildPresetSelection(ServiceInsightRangePreset preset, LocalDate today) {
return preset.toWindow(today)
.map(window -> new RangeSelection(preset, new ServiceInsightQuery(window.startDate(), window.endDate())))
.orElseGet(() -> new RangeSelection(ServiceInsightRangePreset.ALL, ServiceInsightQuery.all()));
}
private void attachErrorToast(Model model) {
ToastMessage errorToast = new ToastMessage(
ERROR_TOAST_ID,
"데이터 로드 실패",
"통계 데이터를 불러오지 못했습니다. 잠시 후 다시 시도해주세요.",
"danger",
false
);
List<ToastMessage> messages = new ArrayList<>();
Object existing = model.getAttribute("toastMessages");
if (existing instanceof List<?> list) {
for (Object item : list) {
if (item instanceof ToastMessage toastMessage && !ERROR_TOAST_ID.equals(toastMessage.id())) {
messages.add(toastMessage);
}
}
}
messages.add(errorToast);
model.addAttribute("toastMessages", List.copyOf(messages));
}
private String toJson(ServiceInsightViewModel viewModel) {
try {
String json = objectMapper.writeValueAsString(viewModel);
log.debug("Serialized view model to JSON successfully");
return json;
} catch (JsonProcessingException exception) {
log.error("Failed to serialize service insight payload.", exception);
return "{\"metricOptions\":[],\"series\":[],\"componentLabels\":{}}";
}
}
private record RangeSelection(ServiceInsightRangePreset preset, ServiceInsightQuery query) {
}
private record PageData(RangeSelection rangeSelection, ServiceInsightViewModel viewModel, boolean hadError) {
}
}