11package apu .saerok_admin .web ;
22
3+ import apu .saerok_admin .web .serviceinsight .ServiceInsightAjaxResponse ;
4+ import apu .saerok_admin .web .serviceinsight .ServiceInsightQuery ;
5+ import apu .saerok_admin .web .serviceinsight .ServiceInsightRangePreset ;
36import apu .saerok_admin .web .serviceinsight .ServiceInsightService ;
47import apu .saerok_admin .web .view .Breadcrumb ;
58import apu .saerok_admin .web .view .ServiceInsightViewModel ;
811import com .fasterxml .jackson .databind .ObjectMapper ;
912import com .fasterxml .jackson .databind .SerializationFeature ;
1013import com .fasterxml .jackson .datatype .jsr310 .JavaTimeModule ;
14+ import java .time .LocalDate ;
15+ import java .time .ZoneId ;
1116import java .util .ArrayList ;
1217import java .util .List ;
1318import org .slf4j .Logger ;
1419import org .slf4j .LoggerFactory ;
20+ import org .springframework .format .annotation .DateTimeFormat ;
21+ import org .springframework .http .MediaType ;
22+ import org .springframework .http .ResponseEntity ;
1523import org .springframework .stereotype .Controller ;
1624import org .springframework .ui .Model ;
1725import org .springframework .web .bind .annotation .GetMapping ;
26+ import org .springframework .web .bind .annotation .RequestParam ;
1827import org .springframework .web .client .RestClientException ;
1928import org .springframework .web .client .RestClientResponseException ;
2029
@@ -23,6 +32,8 @@ public class ServiceInsightController {
2332
2433 private static final Logger log = LoggerFactory .getLogger (ServiceInsightController .class );
2534 private static final String ERROR_TOAST_ID = "toastServiceInsightError" ;
35+ private static final ZoneId DEFAULT_ZONE = ZoneId .of ("Asia/Seoul" );
36+ private static final ServiceInsightRangePreset DEFAULT_PRESET = ServiceInsightRangePreset .LAST_14_DAYS ;
2637
2738 private final ServiceInsightService serviceInsightService ;
2839 private final ObjectMapper objectMapper ;
@@ -35,8 +46,15 @@ public ServiceInsightController(ServiceInsightService serviceInsightService, Obj
3546 .disable (SerializationFeature .WRITE_DATES_AS_TIMESTAMPS );
3647 }
3748
38- @ GetMapping ("/service-insight" )
39- public String serviceInsight (Model model ) {
49+ @ GetMapping (value = "/service-insight" , produces = MediaType .TEXT_HTML_VALUE )
50+ public String serviceInsight (
51+ Model model ,
52+ @ RequestParam (value = "range" , required = false ) String rangeParam ,
53+ @ RequestParam (value = "startDate" , required = false )
54+ @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate startDate ,
55+ @ RequestParam (value = "endDate" , required = false )
56+ @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate endDate
57+ ) {
4058 model .addAttribute ("pageTitle" , "서비스 인사이트" );
4159 model .addAttribute ("activeMenu" , "serviceInsight" );
4260 model .addAttribute ("breadcrumbs" , List .of (
@@ -45,41 +63,133 @@ public String serviceInsight(Model model) {
4563 ));
4664 ensureToastMessages (model );
4765
48- ServiceInsightViewModel viewModel ;
49- try {
50- viewModel = serviceInsightService .loadViewModel ();
51- log .info ("Successfully loaded service insight view model with {} metrics" ,
52- viewModel .metricOptions ().size ());
53- } catch (RestClientResponseException exception ) {
54- log .warn (
55- "Failed to load service insight stats. status={}, body={}" ,
56- exception .getStatusCode (),
57- exception .getResponseBodyAsString (),
58- exception
59- );
60- viewModel = serviceInsightService .defaultViewModel ();
61- attachErrorToast (model );
62- } catch (RestClientException | IllegalStateException exception ) {
63- log .warn ("Failed to load service insight stats." , exception );
64- viewModel = serviceInsightService .defaultViewModel ();
66+ PageData pageData = loadPageData (rangeParam , startDate , endDate );
67+ ServiceInsightViewModel viewModel = pageData .viewModel ();
68+ RangeSelection rangeSelection = pageData .rangeSelection ();
69+ if (pageData .hadError ()) {
6570 attachErrorToast (model );
6671 }
6772
6873 model .addAttribute ("serviceInsight" , viewModel );
6974 String chartDataJson = toJson (viewModel );
7075 model .addAttribute ("chartDataJson" , chartDataJson );
76+ model .addAttribute ("rangeQuickOptions" , ServiceInsightRangePreset .quickSelections ());
77+ model .addAttribute ("selectedRange" , rangeSelection .preset ().paramValue ());
78+ model .addAttribute ("customRangeActive" , rangeSelection .preset () == ServiceInsightRangePreset .CUSTOM );
79+ model .addAttribute ("selectedStartDate" , rangeSelection .query ().startDate ());
80+ model .addAttribute ("selectedEndDate" , rangeSelection .query ().endDate ());
7181
7282 log .debug ("Chart data JSON length: {}" , chartDataJson .length ());
7383
7484 return "service-insight/index" ;
7585 }
7686
87+ @ GetMapping (value = "/service-insight" , produces = MediaType .APPLICATION_JSON_VALUE )
88+ public ResponseEntity <ServiceInsightAjaxResponse > serviceInsightData (
89+ @ RequestParam (value = "range" , required = false ) String rangeParam ,
90+ @ RequestParam (value = "startDate" , required = false )
91+ @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate startDate ,
92+ @ RequestParam (value = "endDate" , required = false )
93+ @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate endDate
94+ ) {
95+ PageData pageData = loadPageData (rangeParam , startDate , endDate );
96+ RangeSelection rangeSelection = pageData .rangeSelection ();
97+
98+ ServiceInsightAjaxResponse response = new ServiceInsightAjaxResponse (
99+ pageData .viewModel (),
100+ rangeSelection .preset ().paramValue (),
101+ rangeSelection .preset () == ServiceInsightRangePreset .CUSTOM ,
102+ rangeSelection .query ().startDate (),
103+ rangeSelection .query ().endDate (),
104+ pageData .hadError ()
105+ );
106+
107+ return ResponseEntity .ok (response );
108+ }
109+
77110 private void ensureToastMessages (Model model ) {
78111 if (!model .containsAttribute ("toastMessages" )) {
79112 model .addAttribute ("toastMessages" , List .of ());
80113 }
81114 }
82115
116+ private PageData loadPageData (String rangeParam , LocalDate startDate , LocalDate endDate ) {
117+ RangeSelection rangeSelection = resolveRange (rangeParam , startDate , endDate );
118+
119+ try {
120+ ServiceInsightViewModel viewModel = serviceInsightService .loadViewModel (rangeSelection .query ());
121+ log .info ("Successfully loaded service insight view model with {} metrics (range: {} - {}, preset: {})" ,
122+ viewModel .metricOptions ().size (),
123+ rangeSelection .query ().startDate (),
124+ rangeSelection .query ().endDate (),
125+ rangeSelection .preset ().name ());
126+ return new PageData (rangeSelection , viewModel , false );
127+ } catch (RestClientResponseException exception ) {
128+ log .warn (
129+ "Failed to load service insight stats. status={}, body={}" ,
130+ exception .getStatusCode (),
131+ exception .getResponseBodyAsString (),
132+ exception
133+ );
134+ } catch (RestClientException | IllegalStateException exception ) {
135+ log .warn ("Failed to load service insight stats." , exception );
136+ }
137+
138+ ServiceInsightViewModel fallback = serviceInsightService .defaultViewModel ();
139+ return new PageData (rangeSelection , fallback , true );
140+ }
141+
142+ private RangeSelection resolveRange (String rangeParam , LocalDate startDate , LocalDate endDate ) {
143+ LocalDate today = LocalDate .now (DEFAULT_ZONE );
144+
145+ ServiceInsightRangePreset requestedPreset = ServiceInsightRangePreset .fromParameter (rangeParam )
146+ .orElse (DEFAULT_PRESET );
147+
148+ if (startDate != null || endDate != null ) {
149+ requestedPreset = ServiceInsightRangePreset .CUSTOM ;
150+ }
151+
152+ if (requestedPreset == ServiceInsightRangePreset .ALL ) {
153+ return new RangeSelection (ServiceInsightRangePreset .ALL , ServiceInsightQuery .all ());
154+ }
155+
156+ if (requestedPreset == ServiceInsightRangePreset .CUSTOM ) {
157+ if (startDate == null || endDate == null ) {
158+ log .debug ("Incomplete custom range supplied (start={}, end={}), falling back to default preset {}" ,
159+ startDate ,
160+ endDate ,
161+ DEFAULT_PRESET .name ());
162+ return buildPresetSelection (DEFAULT_PRESET , today );
163+ }
164+
165+ LocalDate effectiveStart = startDate ;
166+ LocalDate effectiveEnd = endDate ;
167+
168+ if (effectiveEnd .isBefore (effectiveStart )) {
169+ effectiveStart = endDate ;
170+ effectiveEnd = startDate ;
171+ }
172+
173+ if (effectiveEnd .isAfter (today )) {
174+ effectiveEnd = today ;
175+ }
176+
177+ if (effectiveStart .isAfter (effectiveEnd )) {
178+ effectiveStart = effectiveEnd ;
179+ }
180+
181+ return new RangeSelection (ServiceInsightRangePreset .CUSTOM , new ServiceInsightQuery (effectiveStart , effectiveEnd ));
182+ }
183+
184+ return buildPresetSelection (requestedPreset , today );
185+ }
186+
187+ private RangeSelection buildPresetSelection (ServiceInsightRangePreset preset , LocalDate today ) {
188+ return preset .toWindow (today )
189+ .map (window -> new RangeSelection (preset , new ServiceInsightQuery (window .startDate (), window .endDate ())))
190+ .orElseGet (() -> new RangeSelection (ServiceInsightRangePreset .ALL , ServiceInsightQuery .all ()));
191+ }
192+
83193 private void attachErrorToast (Model model ) {
84194 ToastMessage errorToast = new ToastMessage (
85195 ERROR_TOAST_ID ,
@@ -111,4 +221,10 @@ private String toJson(ServiceInsightViewModel viewModel) {
111221 return "{\" metricOptions\" :[],\" series\" :[],\" componentLabels\" :{}}" ;
112222 }
113223 }
114- }
224+
225+ private record RangeSelection (ServiceInsightRangePreset preset , ServiceInsightQuery query ) {
226+ }
227+
228+ private record PageData (RangeSelection rangeSelection , ServiceInsightViewModel viewModel , boolean hadError ) {
229+ }
230+ }
0 commit comments