diff --git a/.vscode/launch.json b/.vscode/launch.json index eb1e198..cec2a22 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -71,7 +71,7 @@ "CYGNUS_LOGIN_PUBLIC_KEY": "file:${workspaceFolder}/config/keys/login-public.pem", "CYGNUS_PAYLOAD_PRIVATE_KEY": "file:${workspaceFolder}/config/keys/case-save-private.pem", "CYGNUS_PAYLOAD_PUBLIC_KEY": "file:${workspaceFolder}/config/keys/case-save-public.pem", - "CYGNUS_CLOUD_REQUEST_TIMEOUT": "PT10S", + "CYGNUS_CLOUD_REQUEST_TIMEOUT": "PT30S", "CYGNUS_TOKEN_REFRESH_SKEW": "PT30S" }, "vmArgs": "-Dserver.port=8080 -Djava.awt.headless=true -Dmatrix.webapp=${workspaceFolder}/cygnus-onprem-app/build/WebContent -Dmatrix.classes=${workspaceFolder}/cygnus-onprem-app/target/classes", diff --git a/cygnus-cloud-client/src/main/java/com/cygnus/client/CloudIdentityClient.java b/cygnus-cloud-client/src/main/java/com/cygnus/client/CloudIdentityClient.java index 8f2076a..b320cb0 100644 --- a/cygnus-cloud-client/src/main/java/com/cygnus/client/CloudIdentityClient.java +++ b/cygnus-cloud-client/src/main/java/com/cygnus/client/CloudIdentityClient.java @@ -11,6 +11,7 @@ import java.time.Clock; import java.util.List; import java.util.Map; import java.util.UUID; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; @@ -18,6 +19,9 @@ import reactor.core.publisher.Mono; public class CloudIdentityClient { + private static final ParameterizedTypeReference> CLOUD_DATA_LIST = + new ParameterizedTypeReference<>() { }; + private final WebClient webClient; private final MachineTokenProvider tokenProvider; private final LoginEnvelopeEncryptor encryptor; @@ -87,8 +91,7 @@ public class CloudIdentityClient { .accept(MediaType.APPLICATION_JSON) .bodyValue(new CloudDataRequest(scope, Map.copyOf(data))) .retrieve() - .bodyToFlux(CloudDataItem.class) - .collectList()) + .bodyToMono(CLOUD_DATA_LIST)) .timeout(properties.requestTimeout()); } } diff --git a/cygnus-cloud-client/src/main/java/com/cygnus/client/model/CloudDataItem.java b/cygnus-cloud-client/src/main/java/com/cygnus/client/model/CloudDataItem.java index 67821d1..d24e73a 100644 --- a/cygnus-cloud-client/src/main/java/com/cygnus/client/model/CloudDataItem.java +++ b/cygnus-cloud-client/src/main/java/com/cygnus/client/model/CloudDataItem.java @@ -1,4 +1,6 @@ package com.cygnus.client.model; -public record CloudDataItem(Object value, String label, String group) { +import java.util.Map; + +public record CloudDataItem(Object value, String label, String group, Map data) { } diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/api/ScopedDataItem.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/api/ScopedDataItem.java index d6a0296..4afc41d 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/api/ScopedDataItem.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/api/ScopedDataItem.java @@ -1,7 +1,10 @@ package com.cygnus.cloud.platform.api; +import java.util.Map; + public record ScopedDataItem( Object value, String label, - String group) { + String group, + Map data) { } diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/repository/ScopedDataRepository.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/repository/ScopedDataRepository.java index 8b49942..c27476f 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/repository/ScopedDataRepository.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/repository/ScopedDataRepository.java @@ -5,6 +5,7 @@ import com.cygnus.cloud.platform.api.ScopedDataItem; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.Tuple; import java.util.List; +import java.util.Map; import java.util.UUID; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; @@ -46,6 +47,16 @@ public class ScopedDataRepository { AND o.description = ANY($5::text[]) ORDER BY o.description, ov.val_description """; + private static final String REPORT_DEFINITION = """ + SELECT r.report_key, r.report_title, r.hide_leading_zero_columns, + c.column_key, c.column_label, c.display_order, c.alignment, + c.column_width, c.is_visible, c.is_totalled + FROM platform.report_definition r + JOIN platform.report_column_definition c ON c.report_key = r.report_key + WHERE r.report_key = $1 + AND r.enabled = true + ORDER BY c.display_order + """; private final ReactiveDatabaseClient database; @@ -66,6 +77,22 @@ public class ScopedDataRepository { descriptions.toArray(String[]::new))); } + public Flux reportDefinition(String reportKey) { + return database.preparedQuery(REPORT_DEFINITION, Tuple.of(reportKey)) + .flatMapMany(result -> Flux.fromIterable(result)) + .map(row -> new ScopedDataItem(null, null, null, Map.of( + "reportKey", row.getString("report_key"), + "reportTitle", row.getString("report_title"), + "hideLeadingZeroColumns", row.getBoolean("hide_leading_zero_columns"), + "columnKey", row.getString("column_key"), + "columnLabel", row.getString("column_label"), + "displayOrder", row.getInteger("display_order"), + "alignment", row.getString("alignment"), + "columnWidth", row.getInteger("column_width"), + "visible", row.getBoolean("is_visible"), + "totalled", row.getBoolean("is_totalled")))); + } + private Flux rows(String sql, Tuple parameters) { return database.preparedQuery(sql, parameters) .flatMapMany(result -> Flux.fromIterable(result)) @@ -74,6 +101,6 @@ public class ScopedDataRepository { private ScopedDataItem item(Row row) { return new ScopedDataItem( - row.getValue("value"), row.getString("label"), row.getString("group_name")); + row.getValue("value"), row.getString("label"), row.getString("group_name"), Map.of()); } } diff --git a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/service/ScopedDataService.java b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/service/ScopedDataService.java index df542cc..39cb929 100644 --- a/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/service/ScopedDataService.java +++ b/cygnus-cloud-service/src/main/java/com/cygnus/cloud/platform/service/ScopedDataService.java @@ -14,6 +14,7 @@ import reactor.core.publisher.Flux; public class ScopedDataService { public static final String COMPANY_OPTIONS = "company-options"; public static final String PORTFOLIO_OPTIONS = "portfolio-options"; + public static final String REPORT_DEFINITION = "report-definition"; private final ScopedDataRepository repository; @@ -23,9 +24,13 @@ public class ScopedDataService { public Flux fetch(UUID tenantId, ScopedDataRequest request) { Map data = request.data(); + String scope = request.scope().trim().toLowerCase(Locale.ROOT); + if (REPORT_DEFINITION.equals(scope)) { + return repository.reportDefinition(textValue(data, "reportKey")); + } short companyId = shortValue(data, "companyId"); List descriptions = descriptions(data); - return switch (request.scope().trim().toLowerCase(Locale.ROOT)) { + return switch (scope) { case COMPANY_OPTIONS -> repository.companyOptions(tenantId, companyId, descriptions); case PORTFOLIO_OPTIONS -> repository.portfolioOptions( tenantId, companyId, shortValue(data, "branchId"), @@ -34,6 +39,14 @@ public class ScopedDataService { }; } + private String textValue(Map data, String name) { + String value = String.valueOf(data.getOrDefault(name, "")).trim(); + if (value.isEmpty() || value.length() > 100 || !value.matches("[a-z0-9-]+")) { + throw new ScopedDataException(name + " is invalid"); + } + return value; + } + private UUID uuidValue(Map data, String name) { try { return UUID.fromString(String.valueOf(data.get(name))); diff --git a/cygnus-onprem-db/src/main/resources/db/022_report_definitions.sql b/cygnus-cloud-service/src/main/resources/db/platform/004_report_definitions.sql similarity index 62% rename from cygnus-onprem-db/src/main/resources/db/022_report_definitions.sql rename to cygnus-cloud-service/src/main/resources/db/platform/004_report_definitions.sql index e461048..69473e6 100644 --- a/cygnus-onprem-db/src/main/resources/db/022_report_definitions.sql +++ b/cygnus-cloud-service/src/main/resources/db/platform/004_report_definitions.sql @@ -34,11 +34,5 @@ SELECT 'hourly-mis', 'hour'||hour, hour::text, hour+1, 'center', 55, true, true FROM generate_series(0,23) hour ON CONFLICT (report_key,column_key) DO NOTHING; -UPDATE platform.application_query -SET query_text='select!C0L!SELECT r.report_title,r.hide_leading_zero_columns,c.column_key,c.column_label,c.display_order,c.alignment,c.column_width,c.is_visible,c.is_totalled FROM platform.report_definition r JOIN platform.report_column_definition c ON c.report_key=r.report_key WHERE r.report_key=? AND r.enabled=true ORDER BY c.display_order', - enabled=true, updated_at=current_timestamp -WHERE query_key='report-definition'; - -INSERT INTO platform.application_query(query_key, query_text, enabled) -SELECT 'report-definition', 'select!C0L!SELECT r.report_title,r.hide_leading_zero_columns,c.column_key,c.column_label,c.display_order,c.alignment,c.column_width,c.is_visible,c.is_totalled FROM platform.report_definition r JOIN platform.report_column_definition c ON c.report_key=r.report_key WHERE r.report_key=? AND r.enabled=true ORDER BY c.display_order', true -WHERE NOT EXISTS (SELECT 1 FROM platform.application_query WHERE query_key='report-definition'); +DELETE FROM platform.application_query +WHERE query_key = 'report-definition'; diff --git a/cygnus-onprem-app/build/WebContent/WEB-INF/app/edp/punching/initcase.jsp b/cygnus-onprem-app/build/WebContent/WEB-INF/app/edp/punching/initcase.jsp index a4f86d9..b056864 100644 --- a/cygnus-onprem-app/build/WebContent/WEB-INF/app/edp/punching/initcase.jsp +++ b/cygnus-onprem-app/build/WebContent/WEB-INF/app/edp/punching/initcase.jsp @@ -40,12 +40,14 @@ - + + - + + diff --git a/cygnus-onprem-app/build/WebContent/WEB-INF/app/mis/filter.jsp b/cygnus-onprem-app/build/WebContent/WEB-INF/app/mis/filter.jsp index b649d6d..98ac680 100644 --- a/cygnus-onprem-app/build/WebContent/WEB-INF/app/mis/filter.jsp +++ b/cygnus-onprem-app/build/WebContent/WEB-INF/app/mis/filter.jsp @@ -26,14 +26,14 @@ - - + + - + diff --git a/cygnus-onprem-app/build/WebContent/css/date-time-input-v1.css b/cygnus-onprem-app/build/WebContent/css/date-time-input-v1.css new file mode 100644 index 0000000..b810314 --- /dev/null +++ b/cygnus-onprem-app/build/WebContent/css/date-time-input-v1.css @@ -0,0 +1,50 @@ +.matrix-date-control { + position: relative; + display: flex; + width: 100%; + align-items: stretch; +} + +.matrix-date-control > .matrix-date-input { + min-width: 0; + flex: 1 1 auto; + padding-right: 38px !important; +} + +.matrix-date-control__button { + position: absolute; + top: 1px; + right: 1px; + bottom: 1px; + display: grid; + width: 34px; + padding: 0; + place-items: center; + border: 0; + border-left: 1px solid #c4d0da; + border-radius: 0 3px 3px 0; + background: #edf3f7; + color: #285f8f; + cursor: pointer; +} + +.matrix-date-control__button:hover, +.matrix-date-control__button:focus-visible { + background: #dfeaf3; + outline: 0; +} + +.matrix-date-control__picker { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + opacity: 0; + pointer-events: none; +} + +.matrix-mis-filter .FormPanel .matrix-date-field { + width: 100%; + min-width: 0; + flex: 0 0 auto; +} diff --git a/cygnus-onprem-app/build/WebContent/css/mis-filter-v4.css b/cygnus-onprem-app/build/WebContent/css/mis-filter-v4.css index 43f4bba..996dd79 100644 --- a/cygnus-onprem-app/build/WebContent/css/mis-filter-v4.css +++ b/cygnus-onprem-app/build/WebContent/css/mis-filter-v4.css @@ -64,25 +64,43 @@ } .matrix-mis-filter .FormPanel { - padding: 20px 18px; + height: auto !important; + min-height: 0 !important; + padding: 16px 18px; border: 1px solid #c9d4de; border-radius: 0 0 5px 5px; background: #fff; } .matrix-mis-filter .FormPanel table { + display: block; width: 100%; margin: 0; } +.matrix-mis-filter .FormPanel table tbody, +.matrix-mis-filter .FormPanel table tr, +.matrix-mis-filter .FormPanel table tr td { + display: block; + width: 100%; + height: auto !important; +} + .matrix-mis-filter .FormPanel table tr td { - display: flex; - flex-wrap: wrap; - align-items: end; - gap: 14px 18px; padding: 0; } +.matrix-mis-filter .FormPanel table tr:first-child td { + display: grid; + gap: 12px; +} + +.matrix-mis-filter .FormPanel table tr:last-child td { + display: flex; + justify-content: flex-end; + padding-top: 14px; +} + .matrix-mis-filter .FormPanel .widget { display: flex; min-width: 230px; @@ -124,12 +142,15 @@ } .matrix-mis-filter .FormPanel input.button { + width: auto !important; min-width: 96px; min-height: 34px; padding: 6px 14px 6px 30px !important; border-color: #7f8b96; border-radius: 4px; background-color: #e5e8eb; + float: none !important; + margin: 0 !important; } .matrix-mis-filter .FormPanel br { diff --git a/cygnus-onprem-app/build/WebContent/js/lib/date-time-input-v1.js b/cygnus-onprem-app/build/WebContent/js/lib/date-time-input-v1.js new file mode 100644 index 0000000..6e7db74 --- /dev/null +++ b/cygnus-onprem-app/build/WebContent/js/lib/date-time-input-v1.js @@ -0,0 +1,81 @@ +(() => { + "use strict"; + + const pad = (value) => String(value).padStart(2, "0"); + + const parseDisplayValue = (value, mode) => { + const pattern = mode === "datetime" + ? /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/ + : /^(\d{2})\/(\d{2})\/(\d{4})$/; + const match = value.trim().match(pattern); + if (!match) return ""; + + const [, day, month, year, hour = "00", minute = "00", second = "00"] = match; + const date = new Date(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second)); + if (date.getFullYear() !== Number(year) || date.getMonth() !== Number(month) - 1 + || date.getDate() !== Number(day) || date.getHours() !== Number(hour) + || date.getMinutes() !== Number(minute) || date.getSeconds() !== Number(second)) return ""; + + const isoDate = `${year}-${month}-${day}`; + return mode === "datetime" ? `${isoDate}T${hour}:${minute}:${second}` : isoDate; + }; + + const formatPickerValue = (value, mode) => { + if (!value) return ""; + const [datePart, timePart = "00:00:00"] = value.split("T"); + const [year, month, day] = datePart.split("-"); + if (!year || !month || !day) return ""; + if (mode === "date") return `${day}/${month}/${year}`; + const [hour = "00", minute = "00", second = "00"] = timePart.split(":"); + return `${day}/${month}/${year} ${pad(hour)}:${pad(minute)}:${pad(second)}`; + }; + + const initialise = (input) => { + if (input.dataset.datePickerReady === "true") return; + input.dataset.datePickerReady = "true"; + + const mode = input.dataset.dateMode === "date" ? "date" : "datetime"; + const wrapper = document.createElement("span"); + wrapper.className = "matrix-date-control"; + input.parentNode.insertBefore(wrapper, input); + wrapper.appendChild(input); + + const picker = document.createElement("input"); + picker.type = mode === "datetime" ? "datetime-local" : "date"; + picker.className = "matrix-date-control__picker"; + picker.tabIndex = -1; + picker.setAttribute("aria-hidden", "true"); + if (mode === "datetime") picker.step = "1"; + + const button = document.createElement("button"); + button.type = "button"; + button.className = "matrix-date-control__button"; + button.setAttribute("aria-label", mode === "datetime" ? "Choose date and time" : "Choose date"); + button.innerHTML = "📅"; + + wrapper.append(picker, button); + input.closest(".widget")?.classList.add("matrix-date-field"); + + const syncPicker = () => { + const parsed = parseDisplayValue(input.value, mode); + if (parsed) picker.value = parsed; + }; + syncPicker(); + + picker.addEventListener("change", () => { + input.value = formatPickerValue(picker.value, mode); + input.dispatchEvent(new Event("change", { bubbles: true })); + input.focus(); + }); + input.addEventListener("change", syncPicker); + button.addEventListener("click", () => { + syncPicker(); + if (typeof picker.showPicker === "function") picker.showPicker(); + else picker.click(); + }); + }; + + const initialiseAll = (root = document) => root.querySelectorAll(".matrix-date-input").forEach(initialise); + document.addEventListener("DOMContentLoaded", () => initialiseAll()); + window.CygnusDateTimeInputs = { initialise: initialiseAll }; +})(); diff --git a/cygnus-onprem-app/src/main/java/matrix/nimble/mis/controller/MIS.java b/cygnus-onprem-app/src/main/java/matrix/nimble/mis/controller/MIS.java index 6aac7c8..cbe496f 100644 --- a/cygnus-onprem-app/src/main/java/matrix/nimble/mis/controller/MIS.java +++ b/cygnus-onprem-app/src/main/java/matrix/nimble/mis/controller/MIS.java @@ -6,10 +6,12 @@ import java.util.Arrays; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; -import matrix.nimble.mis.model.Filters; -import matrix.nimble.mis.model.MISHandler; -import matrix.nimble.model.Session; +import matrix.nimble.mis.model.Filters; +import matrix.nimble.mis.model.MISHandler; +import matrix.nimble.cloud.identity.CloudSessionMapper; +import matrix.nimble.model.Session; import matrix.nimble.utilities.GlobalClass; +import matrix.services.commons.CommonService; import matrix.services.mis.HourlyMisService; import org.springframework.stereotype.Controller; @@ -27,9 +29,31 @@ import org.springframework.http.ResponseEntity; @SessionAttributes({"Sessvals"}) public class MIS { private final HourlyMisService hourlyMisService; + private final CommonService commonService; + private final CloudSessionMapper cloudSessionMapper; - public MIS(HourlyMisService hourlyMisService) { + public MIS( + HourlyMisService hourlyMisService, + CommonService commonService, + CloudSessionMapper cloudSessionMapper) { this.hourlyMisService = hourlyMisService; + this.commonService = commonService; + this.cloudSessionMapper = cloudSessionMapper; + } + + @ModelAttribute("Sessvals") + public Session legacySession(HttpSession httpSession) { + Session legacySession = commonService.getSession(httpSession); + if (legacySession != null) { + return legacySession; + } + var userSession = commonService.getUserSession(httpSession); + if (userSession == null) { + return null; + } + legacySession = cloudSessionMapper.toLegacy(userSession); + commonService.storeSession(httpSession, legacySession); + return legacySession; } @RequestMapping(value="datetimerange",method=RequestMethod.POST ) public String DateRangeFilter(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,HttpSession session) diff --git a/cygnus-onprem-app/src/main/java/matrix/nimble/query/CloudQuerySource.java b/cygnus-onprem-app/src/main/java/matrix/nimble/query/CloudQuerySource.java index 6ebeb7a..39b660e 100644 --- a/cygnus-onprem-app/src/main/java/matrix/nimble/query/CloudQuerySource.java +++ b/cygnus-onprem-app/src/main/java/matrix/nimble/query/CloudQuerySource.java @@ -12,18 +12,33 @@ public final class CloudQuerySource implements QuerySource { @Override public String fetch(int queryId) { - return client.fetchQuery(queryId) - .map(response -> response.query()) - .blockOptional() - .filter(query -> !query.isBlank()) - .orElseThrow(() -> new QueryProviderException( - "Cloud query was empty: " + queryId)); + try { + return client.fetchQuery(queryId) + .map(response -> response.query()) + .blockOptional() + .filter(query -> !query.isBlank()) + .orElseThrow(() -> new QueryProviderException( + "Cloud query was empty: " + queryId)); + } catch (QueryProviderException exception) { + throw exception; + } catch (RuntimeException exception) { + throw new QueryProviderException( + "Cloud query request failed for query " + queryId, exception); + } } @Override public String fetch(String queryKey) { - return client.fetchQuery(queryKey).map(response -> response.query()).blockOptional() - .filter(query -> !query.isBlank()) - .orElseThrow(() -> new QueryProviderException("Cloud query was empty: " + queryKey)); + try { + return client.fetchQuery(queryKey).map(response -> response.query()).blockOptional() + .filter(query -> !query.isBlank()) + .orElseThrow(() -> new QueryProviderException( + "Cloud query was empty: " + queryKey)); + } catch (QueryProviderException exception) { + throw exception; + } catch (RuntimeException exception) { + throw new QueryProviderException( + "Cloud query request failed for query " + queryKey, exception); + } } } diff --git a/cygnus-onprem-app/src/main/java/matrix/services/commons/CommonService.java b/cygnus-onprem-app/src/main/java/matrix/services/commons/CommonService.java index 4dc5a64..e080cda 100644 --- a/cygnus-onprem-app/src/main/java/matrix/services/commons/CommonService.java +++ b/cygnus-onprem-app/src/main/java/matrix/services/commons/CommonService.java @@ -9,6 +9,7 @@ import lib.models.UserSession; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -151,6 +152,9 @@ public class CommonService { } return cloudIdentityClient.fetchData(scope, data) .map(items -> items.stream().map(this::option).toList()) + .onErrorMap(TimeoutException.class, exception -> new IllegalStateException( + "Cloud platform-data request timed out for scope: " + scope, + exception)) .blockOptional() .orElseGet(List::of); } diff --git a/cygnus-onprem-app/src/main/java/matrix/services/mis/ReportDefinitionService.java b/cygnus-onprem-app/src/main/java/matrix/services/mis/ReportDefinitionService.java index 73f78af..589a2fe 100644 --- a/cygnus-onprem-app/src/main/java/matrix/services/mis/ReportDefinitionService.java +++ b/cygnus-onprem-app/src/main/java/matrix/services/mis/ReportDefinitionService.java @@ -1,38 +1,45 @@ package matrix.services.mis; -import com.cygnus.db.CygnusDbExecutor; -import com.cygnus.db.RowView; +import com.cygnus.client.CloudIdentityClient; +import com.cygnus.client.model.CloudDataItem; import lib.models.ReportColumnDefinition; import lib.models.ReportDefinition; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; @Service public class ReportDefinitionService { - private static final String DEFINITION_QUERY = "report-definition"; - private final CygnusDbExecutor dbExecutor; + private static final String DEFINITION_SCOPE = "report-definition"; + private final CloudIdentityClient cloudIdentityClient; - public ReportDefinitionService(CygnusDbExecutor dbExecutor) { - this.dbExecutor = dbExecutor; + public ReportDefinitionService(CloudIdentityClient cloudIdentityClient) { + this.cloudIdentityClient = cloudIdentityClient; } public ReportDefinition hourlyMis(String fallbackTitle) { try { - List rows = dbExecutor.query(DEFINITION_QUERY, new Object[] { "hourly-mis" }); + List rows = cloudIdentityClient + .fetchData(DEFINITION_SCOPE, Map.of("reportKey", "hourly-mis")) + .onErrorMap(TimeoutException.class, + exception -> new IllegalStateException("Cloud report definition request timed out", exception)) + .blockOptional().orElseGet(List::of); if (rows.isEmpty()) return defaults(fallbackTitle); - RowView first = rows.getFirst(); + Map first = rows.getFirst().data(); List columns = new ArrayList<>(rows.size()); - for (RowView row : rows) { + for (CloudDataItem item : rows) { + Map row = item.data(); columns.add(new ReportColumnDefinition( - text(row, "column_key"), text(row, "column_label"), - number(row, "display_order"), text(row, "alignment"), - number(row, "column_width"), bool(row, "is_visible"), - bool(row, "is_totalled"))); + text(row, "columnKey"), text(row, "columnLabel"), + number(row, "displayOrder"), text(row, "alignment"), + number(row, "columnWidth"), bool(row, "visible"), + bool(row, "totalled"))); } - return new ReportDefinition("hourly-mis", text(first, "report_title"), - bool(first, "hide_leading_zero_columns"), List.copyOf(columns)); + return new ReportDefinition("hourly-mis", text(first, "reportTitle"), + bool(first, "hideLeadingZeroColumns"), List.copyOf(columns)); } catch (RuntimeException unavailable) { return defaults(fallbackTitle); } @@ -49,7 +56,7 @@ public class ReportDefinitionService { return new ReportDefinition("hourly-mis", title, true, List.copyOf(columns)); } - private String text(RowView row, String column) { return String.valueOf(row.get(column)); } - private int number(RowView row, String column) { return ((Number) row.get(column)).intValue(); } - private boolean bool(RowView row, String column) { return Boolean.TRUE.equals(row.get(column)); } + private String text(Map row, String column) { return String.valueOf(row.get(column)); } + private int number(Map row, String column) { return ((Number) row.get(column)).intValue(); } + private boolean bool(Map row, String column) { return Boolean.TRUE.equals(row.get(column)); } }