From 0b7a4f883912d7d589c66a93c28110ad31b8ef24 Mon Sep 17 00:00:00 2001 From: Mohit Yadav <105265192+mohityadav766@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:45:45 +0530 Subject: [PATCH] Add new Template and Graph building logic for Insights reporting (#15184) --- .../insights/DataInsightsReportApp.java | 205 ++- .../bundles/searchIndex/SearchIndexApp.java | 1 + .../bundles/test/NoOpTestApplication.java | 1 + ...ataInsightDescriptionAndOwnerTemplate.java | 16 +- .../DataInsightTotalAssetTemplate.java | 18 +- .../openmetadata/service/util/EmailUtil.java | 4 + .../openmetadata/service/util/Utilities.java | 50 + .../emailTemplates/dataInsightReport.ftl | 1181 +++++++++-------- 8 files changed, 824 insertions(+), 652 deletions(-) create mode 100644 openmetadata-service/src/main/java/org/openmetadata/service/util/Utilities.java diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java index 9f77e6a3bb5..4aa7445d58c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java @@ -11,21 +11,23 @@ import static org.openmetadata.service.Entity.TEAM; import static org.openmetadata.service.apps.scheduler.AppScheduler.APP_INFO_KEY; import static org.openmetadata.service.apps.scheduler.AppScheduler.SEARCH_CLIENT_KEY; import static org.openmetadata.service.util.SubscriptionUtil.getAdminsData; +import static org.openmetadata.service.util.Utilities.getMonthAndDateFromEpoch; import com.fasterxml.jackson.core.type.TypeReference; import java.io.IOException; import java.text.ParseException; import java.time.Instant; -import java.time.Period; -import java.time.ZoneId; -import java.util.Date; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.TreeMap; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.openmetadata.common.utils.CommonUtil; import org.openmetadata.schema.dataInsight.DataInsightChartResult; import org.openmetadata.schema.dataInsight.kpi.Kpi; @@ -35,7 +37,6 @@ import org.openmetadata.schema.dataInsight.type.PercentageOfEntitiesWithOwnerByT import org.openmetadata.schema.dataInsight.type.TotalEntitiesByTier; import org.openmetadata.schema.dataInsight.type.TotalEntitiesByType; import org.openmetadata.schema.entity.app.App; -import org.openmetadata.schema.entity.app.AppSchedule; import org.openmetadata.schema.entity.applications.configuration.internal.DataInsightsReportAppConfig; import org.openmetadata.schema.entity.teams.Team; import org.openmetadata.schema.entity.teams.User; @@ -54,15 +55,13 @@ import org.openmetadata.service.search.SearchRepository; import org.openmetadata.service.util.EmailUtil; import org.openmetadata.service.util.JsonUtils; import org.openmetadata.service.util.ResultList; +import org.openmetadata.service.util.Utilities; import org.openmetadata.service.workflows.searchIndex.PaginatedEntitiesSource; -import org.quartz.CronScheduleBuilder; import org.quartz.JobExecutionContext; -import org.quartz.Trigger; @Slf4j +@SuppressWarnings("unused") public class DataInsightsReportApp extends AbstractNativeApplication { - private static final String MISSING_DATA = - "Data Insight Report Data Unavailable or too short of a span for Reporting."; private static final String KPI_NOT_SET = "No Kpi Set"; @Override @@ -73,9 +72,8 @@ public class DataInsightsReportApp extends AbstractNativeApplication { App app = (App) jobExecutionContext.getJobDetail().getJobDataMap().get(APP_INFO_KEY); // Calculate time diff long currentTime = Instant.now().toEpochMilli(); - AppSchedule scheduleConfiguration = app.getAppSchedule(); long scheduleTime = currentTime - 604800000L; - int numberOfDaysChange = getNumberOfDays(scheduleConfiguration); + int numberOfDaysChange = 7; try { DataInsightsReportAppConfig insightAlertConfig = JsonUtils.convertValue(app.getAppConfiguration(), DataInsightsReportAppConfig.class); @@ -86,7 +84,7 @@ public class DataInsightsReportApp extends AbstractNativeApplication { } // Send to Teams - if (Boolean.TRUE.equals(insightAlertConfig.getSendToTeams())) { + if (Boolean.FALSE.equals(insightAlertConfig.getSendToTeams())) { sendReportsToTeams( searchRepository.getSearchClient(), scheduleTime, currentTime, numberOfDaysChange); } @@ -130,6 +128,8 @@ public class DataInsightsReportApp extends AbstractNativeApplication { searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); EmailUtil.sendDataInsightEmailNotificationToUser( emails, + getMonthAndDateFromEpoch(scheduleTime), + getMonthAndDateFromEpoch(currentTime), totalAssetTemplate, descriptionTemplate, ownershipTemplate, @@ -165,6 +165,8 @@ public class DataInsightsReportApp extends AbstractNativeApplication { createTierTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); EmailUtil.sendDataInsightEmailNotificationToUser( emailList, + getMonthAndDateFromEpoch(scheduleTime), + getMonthAndDateFromEpoch(currentTime), totalAssetTemplate, descriptionTemplate, ownershipTemplate, @@ -190,6 +192,9 @@ public class DataInsightsReportApp extends AbstractNativeApplication { private DataInsightTotalAssetTemplate createTotalAssetTemplate( SearchClient searchClient, String team, Long scheduleTime, Long currentTime, int numberOfDays) throws ParseException, IOException { + // Create A Date Map + Map dateMap = new LinkedHashMap<>(); + Utilities.getLastSevenDays(currentTime).forEach(day -> dateMap.put(day, 0)); // Get total Assets Data TreeMap> dateWithDataMap = searchClient.getSortedDate( @@ -199,7 +204,6 @@ public class DataInsightsReportApp extends AbstractNativeApplication { TOTAL_ENTITIES_BY_TYPE, ENTITY_REPORT_DATA_INDEX.value()); if (dateWithDataMap.firstEntry() != null && dateWithDataMap.lastEntry() != null) { - List first = JsonUtils.convertValue(dateWithDataMap.firstEntry().getValue(), new TypeReference<>() {}); List last = @@ -207,16 +211,29 @@ public class DataInsightsReportApp extends AbstractNativeApplication { Double previousCount = getCountOfEntitiesFromList(first); Double currentCount = getCountOfEntitiesFromList(last); + dateWithDataMap.forEach( + (key, value) -> { + List list = + JsonUtils.convertValue(value, new TypeReference<>() {}); + Double count = getCountOfEntitiesFromList(list); + dateMap.put(Utilities.getDateFromEpoch(key), count.intValue()); + }); + + processDateMapToNormalize(dateMap); + if (previousCount == 0D) { // it should be undefined - return new DataInsightTotalAssetTemplate(currentCount, 0D, numberOfDays); + return new DataInsightTotalAssetTemplate(currentCount, 0D, numberOfDays, dateMap); } else { return new DataInsightTotalAssetTemplate( - currentCount, ((currentCount - previousCount) / previousCount) * 100, numberOfDays); + currentCount, + ((currentCount - previousCount) / previousCount) * 100, + numberOfDays, + dateMap); } } - throw new IOException(MISSING_DATA); + return new DataInsightTotalAssetTemplate(0D, 0D, numberOfDays, dateMap); } private DataInsightDescriptionAndOwnerTemplate createDescriptionTemplate( @@ -226,6 +243,9 @@ public class DataInsightsReportApp extends AbstractNativeApplication { Long currentTime, int numberOfDaysChange) throws ParseException, IOException { + // Create A Date Map + Map dateMap = new LinkedHashMap<>(); + Utilities.getLastSevenDays(currentTime).forEach(day -> dateMap.put(day, 0)); // Get total Assets Data // This assumes that on a particular date the correct count per entities are given TreeMap> dateWithDataMap = @@ -246,6 +266,16 @@ public class DataInsightsReportApp extends AbstractNativeApplication { double currentCompletedDescription = getCompletedDescriptionCount(last); double currentTotalCount = getTotalEntityFromDescriptionList(last); + dateWithDataMap.forEach( + (key, value) -> { + List list = + JsonUtils.convertValue(value, new TypeReference<>() {}); + Double count = getCompletedDescriptionCount(list); + dateMap.put(Utilities.getDateFromEpoch(key), count.intValue()); + }); + + processDateMapToNormalize(dateMap); + // Previous Percent double previousPercentCompleted = 0D; if (previousTotalCount != 0) { @@ -262,10 +292,17 @@ public class DataInsightsReportApp extends AbstractNativeApplication { PERCENTAGE_OF_ENTITIES_WITH_DESCRIPTION_BY_TYPE, currentPercentCompleted, currentPercentCompleted - previousPercentCompleted, - numberOfDaysChange); + numberOfDaysChange, + dateMap); } - throw new IOException(MISSING_DATA); + return getTemplate( + DataInsightDescriptionAndOwnerTemplate.MetricType.DESCRIPTION, + PERCENTAGE_OF_ENTITIES_WITH_DESCRIPTION_BY_TYPE, + 0D, + 0D, + numberOfDaysChange, + dateMap); } private DataInsightDescriptionAndOwnerTemplate createOwnershipTemplate( @@ -275,6 +312,9 @@ public class DataInsightsReportApp extends AbstractNativeApplication { Long currentTime, int numberOfDaysChange) throws ParseException, IOException { + // Create A Date Map + Map dateMap = new LinkedHashMap<>(); + Utilities.getLastSevenDays(currentTime).forEach(day -> dateMap.put(day, 0)); // Get total Assets Data // This assumes that on a particular date the correct count per entities are given TreeMap> dateWithDataMap = @@ -305,16 +345,31 @@ public class DataInsightsReportApp extends AbstractNativeApplication { if (currentTotalCount != 0) { currentPercentCompleted = (currentHasOwner / currentTotalCount) * 100; } + dateWithDataMap.forEach( + (key, value) -> { + List list = + JsonUtils.convertValue(value, new TypeReference<>() {}); + Double count = getCompletedOwnershipCount(list); + dateMap.put(Utilities.getDateFromEpoch(key), count.intValue()); + }); + + processDateMapToNormalize(dateMap); return getTemplate( DataInsightDescriptionAndOwnerTemplate.MetricType.OWNER, PERCENTAGE_OF_ENTITIES_WITH_OWNER_BY_TYPE, currentPercentCompleted, currentPercentCompleted - previousPercentCompleted, - numberOfDaysChange); + numberOfDaysChange, + dateMap); } - - throw new IOException(MISSING_DATA); + return getTemplate( + DataInsightDescriptionAndOwnerTemplate.MetricType.OWNER, + PERCENTAGE_OF_ENTITIES_WITH_OWNER_BY_TYPE, + 0D, + 0D, + numberOfDaysChange, + dateMap); } private DataInsightDescriptionAndOwnerTemplate createTierTemplate( @@ -324,6 +379,9 @@ public class DataInsightsReportApp extends AbstractNativeApplication { Long currentTime, int numberOfDaysChange) throws ParseException, IOException { + // Create A Date Map + Map dateMap = new LinkedHashMap<>(); + Utilities.getLastSevenDays(currentTime).forEach(day -> dateMap.put(day, 0)); // Get total Assets Data // This assumes that on a particular date the correct count per entities are given TreeMap> dateWithDataMap = @@ -336,6 +394,14 @@ public class DataInsightsReportApp extends AbstractNativeApplication { if (dateWithDataMap.lastEntry() != null) { List last = JsonUtils.convertValue(dateWithDataMap.lastEntry().getValue(), new TypeReference<>() {}); + dateWithDataMap.forEach( + (key, value) -> { + List list = + JsonUtils.convertValue(value, new TypeReference<>() {}); + Double count = getCountOfTieredEntities(list); + dateMap.put(Utilities.getDateFromEpoch(key), count.intValue()); + }); + processDateMapToNormalize(dateMap); Map tierData = getTierData(last); return new DataInsightDescriptionAndOwnerTemplate( DataInsightDescriptionAndOwnerTemplate.MetricType.TIER, @@ -346,10 +412,21 @@ public class DataInsightsReportApp extends AbstractNativeApplication { false, "", numberOfDaysChange, - tierData); + tierData, + dateMap); } - throw new IOException(MISSING_DATA); + return new DataInsightDescriptionAndOwnerTemplate( + DataInsightDescriptionAndOwnerTemplate.MetricType.TIER, + null, + 0D, + KPI_NOT_SET, + 0D, + false, + "", + numberOfDaysChange, + new HashMap<>(), + dateMap); } private Double getCountOfEntitiesFromList(List entitiesByTypeList) { @@ -361,6 +438,15 @@ public class DataInsightsReportApp extends AbstractNativeApplication { return totalCount; } + private Double getCountOfTieredEntities(List entitiesByTierList) { + // If there are multiple entries for same entities then this can yield invalid results + double totalCount = 0D; + for (TotalEntitiesByTier obj : entitiesByTierList) { + totalCount += obj.getEntityCountFraction() * 100; + } + return totalCount; + } + private Map getTierData(List entitiesByTypeList) { // If there are multiple entries for same entities then this can yield invalid results Map data = new TreeMap<>(); @@ -415,7 +501,8 @@ public class DataInsightsReportApp extends AbstractNativeApplication { DataInsightChartResult.DataInsightChartType chartType, Double percentCompleted, Double percentChange, - int numberOfDaysChange) { + int numberOfDaysChange, + Map dateMap) { List kpiList = getAvailableKpi(); Kpi validKpi = null; @@ -463,60 +550,32 @@ public class DataInsightsReportApp extends AbstractNativeApplication { isKpiAvailable, totalDaysLeft, numberOfDaysChange, - null); + null, + dateMap); } - private long getTimeFromSchedule( - AppSchedule appSchedule, JobExecutionContext jobExecutionContext) { - AppSchedule.ScheduleTimeline timeline = appSchedule.getScheduleType(); - return switch (timeline) { - case HOURLY -> 3600000L; - case DAILY -> 86400000L; - case WEEKLY -> 604800000L; - case MONTHLY -> 2592000000L; - case CUSTOM -> { - if (jobExecutionContext.getTrigger() != null) { - Trigger triggerQrz = jobExecutionContext.getTrigger(); - Date previousFire = - triggerQrz.getPreviousFireTime() == null - ? triggerQrz.getStartTime() - : triggerQrz.getPreviousFireTime(); - yield previousFire.toInstant().toEpochMilli(); - } - yield 86400000L; - } - }; + private void processDateMapToNormalize(Map dateMap) { + Pair maxIn = getMinAndMax(dateMap.values().stream().toList()); + dateMap.replaceAll( + (k, v) -> + getNormalizedValue( + v.doubleValue(), maxIn.getRight().doubleValue(), maxIn.getLeft().doubleValue()) + .intValue()); } - public static int getNumberOfDays(AppSchedule appSchedule) { - AppSchedule.ScheduleTimeline timeline = appSchedule.getScheduleType(); - switch (timeline) { - case HOURLY: - return 0; - case DAILY: - return 1; - case WEEKLY: - return 7; - case MONTHLY: - return 30; - case CUSTOM: - if (!CommonUtil.nullOrEmpty(appSchedule.getCronExpression())) { - Trigger triggerQrz = - CronScheduleBuilder.cronSchedule(appSchedule.getCronExpression()).build(); - Date previousFire = - triggerQrz.getPreviousFireTime() == null - ? triggerQrz.getStartTime() - : triggerQrz.getPreviousFireTime(); - Date nextFire = triggerQrz.getFireTimeAfter(previousFire); - Period period = - Period.between( - previousFire.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), - nextFire.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); - return period.getDays(); - } else { - throw new IllegalArgumentException("Missing Cron Expression for Custom Schedule."); - } + private Pair getMinAndMax(List integers) { + Optional minOptional = integers.stream().min(Integer::compareTo); + Optional maxOptional = integers.stream().max(Integer::compareTo); + int min = minOptional.orElseThrow(() -> new IllegalArgumentException("List is empty")); + int max = maxOptional.orElseThrow(() -> new IllegalArgumentException("List is empty")); + + return Pair.of(min, max); + } + + private Double getNormalizedValue(Double value, Double max, Double min) { + if (max - min == 0) { + return 0d; } - throw new IllegalArgumentException("Invalid Trigger Type, Can only be Scheduled."); + return ((value - min) / (max - min) * 50); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java index ffffee5f4f3..df29fe882f6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java @@ -48,6 +48,7 @@ import org.openmetadata.service.workflows.searchIndex.PaginatedEntitiesSource; import org.quartz.JobExecutionContext; @Slf4j +@SuppressWarnings("unused") public class SearchIndexApp extends AbstractNativeApplication { private static final String ALL = "all"; diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/test/NoOpTestApplication.java b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/test/NoOpTestApplication.java index 94a0c0e8a7a..c8230036609 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/test/NoOpTestApplication.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/test/NoOpTestApplication.java @@ -7,6 +7,7 @@ import org.openmetadata.service.jdbi3.CollectionDAO; import org.openmetadata.service.search.SearchRepository; @Slf4j +@SuppressWarnings("unused") public class NoOpTestApplication extends AbstractNativeApplication { @Override diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightDescriptionAndOwnerTemplate.java b/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightDescriptionAndOwnerTemplate.java index 3bfbf770e83..3925de0895d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightDescriptionAndOwnerTemplate.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightDescriptionAndOwnerTemplate.java @@ -15,6 +15,7 @@ package org.openmetadata.service.events.scheduled.template; import java.util.Map; +@SuppressWarnings("unused") public class DataInsightDescriptionAndOwnerTemplate { public enum MetricType { DESCRIPTION, @@ -36,6 +37,7 @@ public class DataInsightDescriptionAndOwnerTemplate { private String completeMessage; private int numberOfDaysChange; private Map tierMap; + private Map dateMap; public DataInsightDescriptionAndOwnerTemplate( MetricType metricType, @@ -46,7 +48,8 @@ public class DataInsightDescriptionAndOwnerTemplate { boolean isKpiAvailable, String numberOfDaysLeft, int numberOfDaysChange, - Map tierMap) { + Map tierMap, + Map dateMap) { this.percentCompleted = String.format("%.2f", percentCompleted); this.targetKpi = targetKpi; this.percentChange = String.format("%.2f", percentChange); @@ -54,6 +57,7 @@ public class DataInsightDescriptionAndOwnerTemplate { this.numberOfDaysLeft = numberOfDaysLeft; this.tierMap = tierMap; this.numberOfDaysChange = numberOfDaysChange; + this.dateMap = dateMap; String color = "#BF0000"; if (percentChange > 0) { color = "#008510"; @@ -86,7 +90,7 @@ public class DataInsightDescriptionAndOwnerTemplate { case NOT_MET -> "The Target set for KPIs was not met it’s time to restructure your goals and progress faster."; }; } - return "You have not set any KPIS yet, it’s time to restructure your goals, set KPIs and progress faster."; + return "You have not set any KPIs yet, it’s time to restructure your goals, set KPIs and progress faster."; } return ""; } @@ -150,4 +154,12 @@ public class DataInsightDescriptionAndOwnerTemplate { public void setNumberOfDaysChange(int numberOfDaysChange) { this.numberOfDaysChange = numberOfDaysChange; } + + public Map getDateMap() { + return dateMap; + } + + public void setDateMap(Map dateMap) { + this.dateMap = dateMap; + } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightTotalAssetTemplate.java b/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightTotalAssetTemplate.java index 69ca1c85092..6e194fa7ea5 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightTotalAssetTemplate.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/events/scheduled/template/DataInsightTotalAssetTemplate.java @@ -13,17 +13,25 @@ package org.openmetadata.service.events.scheduled.template; +import java.util.Map; + +@SuppressWarnings("unused") public class DataInsightTotalAssetTemplate { private String totalDataAssets; private String percentChangeTotalAssets; private String completeMessage; private int numberOfDaysChange; + private Map dateMap; public DataInsightTotalAssetTemplate( - Double totalDataAssets, Double percentChangeTotalAssets, int numberOfDaysChange) { + Double totalDataAssets, + Double percentChangeTotalAssets, + int numberOfDaysChange, + Map dateMap) { this.totalDataAssets = String.format("%.2f", totalDataAssets); this.percentChangeTotalAssets = String.format("%.2f", percentChangeTotalAssets); this.numberOfDaysChange = numberOfDaysChange; + this.dateMap = dateMap; String color = "#BF0000"; if (percentChangeTotalAssets > 0) { color = "#008510"; @@ -65,4 +73,12 @@ public class DataInsightTotalAssetTemplate { public void setNumberOfDaysChange(int numberOfDaysChange) { this.numberOfDaysChange = numberOfDaysChange; } + + public Map getDateMap() { + return dateMap; + } + + public void setDateMap(Map dateMap) { + this.dateMap = dateMap; + } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/EmailUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/EmailUtil.java index 2914fb0e466..3eebeaeb7f8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/util/EmailUtil.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/EmailUtil.java @@ -337,6 +337,8 @@ public class EmailUtil { public static void sendDataInsightEmailNotificationToUser( Set emails, + String startDate, + String endDate, DataInsightTotalAssetTemplate totalAssetObj, DataInsightDescriptionAndOwnerTemplate descriptionObj, DataInsightDescriptionAndOwnerTemplate ownerShipObj, @@ -346,6 +348,8 @@ public class EmailUtil { throws IOException, TemplateException { if (Boolean.TRUE.equals(getSmtpSettings().getEnableSmtpServer())) { Map templatePopulator = new HashMap<>(); + templatePopulator.put("startDate", startDate); + templatePopulator.put("endDate", endDate); templatePopulator.put("totalAssetObj", totalAssetObj); templatePopulator.put("descriptionObj", descriptionObj); templatePopulator.put("ownershipObj", ownerShipObj); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/Utilities.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/Utilities.java new file mode 100644 index 00000000000..0a9b0143156 --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/Utilities.java @@ -0,0 +1,50 @@ +package org.openmetadata.service.util; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class Utilities { + private Utilities() {} + + public static List getLastSevenDays(long currentEpochTimestampInMilli) { + List lastSevenDays = new ArrayList<>(); + + // Create a formatter for the date + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d"); + + // Calculate and add the dates for the last seven days + for (int i = 6; i >= 0; i--) { + long dayEpochTimestamp = + currentEpochTimestampInMilli + - ((long) i * 24 * 60 * 60 * 1000); // Subtracting seconds for each day + LocalDateTime dateTime = + LocalDateTime.ofInstant( + Instant.ofEpochMilli(dayEpochTimestamp), java.time.ZoneId.systemDefault()); + lastSevenDays.add(dateTime.format(formatter)); + } + + return lastSevenDays; + } + + public static String getMonthAndDateFromEpoch(long epochTimestamp) { + return getFormattedDateFromEpoch(epochTimestamp, "MMM d"); + } + + public static String getDateFromEpoch(long epochTimestampInMilli) { + return getFormattedDateFromEpoch(epochTimestampInMilli, "d"); + } + + private static String getFormattedDateFromEpoch(long epochTimestamp, String format) { + Instant instant = Instant.ofEpochMilli(epochTimestamp); + LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + + // Define a custom date formatter + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(format); + + return dateTime.format(dateFormat); + } +} diff --git a/openmetadata-service/src/main/resources/emailTemplates/dataInsightReport.ftl b/openmetadata-service/src/main/resources/emailTemplates/dataInsightReport.ftl index 870e302edfe..7d5398121cf 100644 --- a/openmetadata-service/src/main/resources/emailTemplates/dataInsightReport.ftl +++ b/openmetadata-service/src/main/resources/emailTemplates/dataInsightReport.ftl @@ -1,608 +1,637 @@ - - + + - - - - - - - - - - + + + Weekly Update - -
- - - +
-
- -
- -
- -
-
- - - - -
- -
- -
-
-
- - + +
+ + + +
+ + + + -
+ + - + -
-
- - - - -
-
- OpenMetadata Data Insights Report -
-
-
-
 
-
- - - - -
-
- These weekly reports are intended to give you a single pane view of how your data is evolving. Reachout to us if you need anything else or have any questions. -
-
-
-
 
- - -
 
-
- - - +
- - - - + -
-
- - - - -
-
- - - - - -
-
- - - - -
-
- Total Data Assets -
-
 
-
- ${totalAssetObj.totalDataAssets} -
-
 
-
- - ${totalAssetObj.percentChangeTotalAssets} [${totalAssetObj.numberOfDaysChange} Days Change] -
-
-
-
- -
-
-
- -
- - - - -
-
- - - - - -
-
- - - - -
-
-
- Completed Desciption -
-
 
-
- - Current: ${descriptionObj.percentCompleted}% | Target KPI: ${descriptionObj.targetKpi}% - -
-
-
-
- - ${descriptionObj.percentChange}% [${descriptionObj.numberOfDaysChange} Days Change] -
-
-
-
- -
-
-
-
- - - - -
-
- - - - - -
-
- - - - -
-
-
- Assigned Ownership -
-
 
-
- - Current: ${ownershipObj.percentCompleted}% | Target KPI: ${ownershipObj.targetKpi}% - -
-
-
- - ${ownershipObj.percentChange}% [${ownershipObj.numberOfDaysChange} Days Change] -
-
-
-
- -
-
-
- -
- - - - -
-
- - - - - -
-
- - - - -
-
-
- Assigned Tier -
-
 
-
- - Current: ${tierObj.percentCompleted}% - -
-
-
- - ${tierObj.percentChange}% [${tierObj.numberOfDaysChange} Days Change] -
-
-
-
- -
-
-
-
+ + + + + + +
- - -
- -
 
- - -
-
- + + + +
 
+ + + + + + + +
 
+ + - + -
${startDate} - ${endDate}
- +
- -
-
 
-
- - - - -
-
- ${totalAssetObj.completeMessage} -
-
- -
-
 
-
- - - - -
-
- + + + + + + + + + + + + + +
+ + - + -
 
- +
Your Weekly Update
 
Our weekly reports are designed to give you a clear and complete view of how your data is changing over time.
+ + + + + + + + + +
 
+ + + + + + +
+ + + + + + +
View Details
+
+
+
- -
-
 
-
- - + +
+ + - -
-
- ${descriptionObj.completeMessage} -
+
+ + + + + + + + + + + + + + + +
 
+ + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
 
 
Total Data Assets
${totalAssetObj.completeMessage}
${totalAssetObj.totalDataAssets}
+
+
+
 
 
- -
-
 
-
- - + +
+ + - + -
-
- ${ownershipObj.completeMessage} -
-
 
- -
-
 
-
- - + +
+ + - + -
-
- ${tierObj.completeMessage}. -
+
+ + + + + + + + + + + + + + + + + <#if descriptionObj.kpiAvailable> + + + + + + +
+ + + + + + +
Completed Description
+
 
${descriptionObj.completeMessage}
 
Target KPI: ${descriptionObj.targetKpi}% | Current KPI: ${descriptionObj.percentCompleted}%
+
+ + + + + + + + + + + + + + + + + + + + +
${descriptionObj.percentCompleted}
Completed Description
 
+ + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + <#list descriptionObj.dateMap?keys as key> + + + + + +
+ + + + + + + + + +
 
 
+
+
 
+
+ + + + + + +
 
+
+ + + + + <#list descriptionObj.dateMap?keys as key> + + + + + +
${key}
+
+
 
- <#if tierObj.tierMap?size gt 0> -
 
- - - - - - <#list tierObj.tierMap?keys as key> - - - - - -
Tier TypePercentage
${key} - ${tierObj.tierMap[key]} -
- -
 
- View Detailed Report in OpenMetadata - -
+
+ + + + + + +
 
+ + + + + + + +
+ + + + + + + + + + + + + + + + + <#if ownershipObj.kpiAvailable> + + + + + + +
+ + + + + + +
Assigned Ownership
+
 
${ownershipObj.completeMessage}
 
Target KPI: ${ownershipObj.targetKpi}% | Current KPI: ${ownershipObj.percentCompleted}%
+
+ + + + + + + + + + + + + + + + + + + + +
${ownershipObj.percentCompleted}
Assigned Ownership
 
+ + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + <#list ownershipObj.dateMap?keys as key> + + + + + +
+ + + + + + + + + +
 
 
+
+
 
+
+ + + + + + +
 
+
+ + + + + <#list ownershipObj.dateMap?keys as key> + + + + + +
${key}
+
+
 
+
+ + + + + + +
 
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + +
Assigned Tier
+
 
${tierObj.completeMessage}
 
+
+ + + + + + + + + + + + + + + + + + + + +
${tierObj.percentCompleted}
Assigned Tier
 
+ + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + <#list tierObj.dateMap?keys as key> + + + + + +
+ + + + + + + + + +
 
 
+
+
 
+
+ + + + + + +
 
+
+ + + + + <#list ownershipObj.dateMap?keys as key> + + + + + +
${key}
+
+
 
+
+ + + + + + +
 
+ + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
 
© 2024 Collate, All Rights Reserved
 
+ + + + + + + + +
+
 
+
+
- - +
-
+
\ No newline at end of file