diff --git a/src/main/java/no/nibio/vips/logic/entity/Observation.java b/src/main/java/no/nibio/vips/logic/entity/Observation.java index 241e0c14ad16f334804c251a2f47dc698315c2f1..7d971682885caa3fe572f0df23933e2762e477ef 100755 --- a/src/main/java/no/nibio/vips/logic/entity/Observation.java +++ b/src/main/java/no/nibio/vips/logic/entity/Observation.java @@ -691,6 +691,8 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse } return new ObservationListItem( this.getObservationId(), + this.userId, + this.user != null ? this.user.getFullName() : null, this.getObservationTimeSeriesId(), this.getTimeOfObservation(), this.getOrganismId(), @@ -701,8 +703,11 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse // Specific geoInfo trumps location. This is to be interpreted // as that the observation has been geographically masked by // choice of the observer + this.location != null ? this.location.getPointOfInterestId() : null, + this.location != null ? this.location.getName() : null, this.location != null && this.geoinfo == null ? this.location.getGeoJSON() : this.getGeoinfo(), this.getObservationHeading(), + this.getObservationText(), this.getBroadcastMessage(), this.getLocationIsPrivate(), this.getIsPositive(), diff --git a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java index c8d96e9477c78f3cac0cb482c7168e210f084eb1..0d9c0f09189b5ed1e42c857df8748a611b141114 100755 --- a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java +++ b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java @@ -143,6 +143,12 @@ public class VipsLogicUser implements Serializable, Comparable{ this.lastName = lastName; } + @JsonIgnore + @Transient + public String getFullName() { + return firstName + " " + lastName; + } + @OneToMany(cascade = CascadeType.ALL, mappedBy = "vipsLogicUser", fetch=FetchType.EAGER) @XmlTransient @JsonIgnore diff --git a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java index a4dd12f901526bfda95fb6c5edadc1c793218772..5082c7089796662fdd85982ab4ff6529e0e41b90 100644 --- a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java +++ b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java @@ -28,11 +28,16 @@ import no.nibio.vips.observationdata.ObservationDataSchema; */ public class ObservationListItem implements Comparable{ private Integer observationId, observationTimeSeriesId, organismId, cropOrganismId; + private Integer observerId; + private String observerName; private Date timeOfObservation; private String organismName, cropOrganismName; private String observationTimeSeriesLabel; + private Integer locationPointOfInterestId; + private String locationPointOfInterestName; private String geoInfo; private String observationHeading; + private String observationText; private String observationData; private ObservationDataSchema observationDataSchema; private Boolean broadcastMessage; @@ -41,6 +46,8 @@ public class ObservationListItem implements Comparable{ public ObservationListItem( Integer observationId, + Integer observerId, + String observerName, Integer observationTimeSeriesId, Date timeOfObservation, Integer organismId, @@ -48,15 +55,19 @@ public class ObservationListItem implements Comparable{ Integer cropOrganismId, String cropOrganismName, String observationTimeSeriesLabel, + Integer poiId, + String poiName, String geoinfo, String observationHeading, + String observationText, Boolean broadcastMessage, Boolean locationIsPrivate, Boolean isPositive, String observationData, - ObservationDataSchema observationDataSchema - ){ + ObservationDataSchema observationDataSchema){ this.observationId = observationId; + this.observerId = observerId; + this.observerName = observerName; this.observationTimeSeriesId = observationTimeSeriesId; this.timeOfObservation = timeOfObservation; this.organismId = organismId; @@ -64,8 +75,11 @@ public class ObservationListItem implements Comparable{ this.cropOrganismId = cropOrganismId; this.cropOrganismName = cropOrganismName; this.observationTimeSeriesLabel = observationTimeSeriesLabel; + this.locationPointOfInterestId = poiId; + this.locationPointOfInterestName = poiName; this.geoInfo = geoinfo; this.observationHeading = observationHeading; + this.observationText = observationText; this.broadcastMessage = broadcastMessage; this.locationIsPrivate = locationIsPrivate; this.isPositive = isPositive; @@ -98,6 +112,34 @@ public class ObservationListItem implements Comparable{ this.observationId = observationId; } + /** + * @return The ID of the observer + */ + public Integer getObserverId() { + return observerId; + } + + /** + * @param observerId The ID to set + */ + public void setObserverId(Integer observerId) { + this.observerId = observerId; + } + + /** + * @return The full name of the observer + */ + public String getObserverName() { + return observerName; + } + + /** + * @param observerName The observer name to set + */ + public void setObserverName(String observerName) { + this.observerName = observerName; + } + /** * @return the observationTimeSeriesId */ @@ -162,6 +204,22 @@ public class ObservationListItem implements Comparable{ this.observationTimeSeriesLabel = observationTimeSeriesLabel; } + public Integer getLocationPointOfInterestId() { + return locationPointOfInterestId; + } + + public void setLocationPointOfInterestId(Integer locationPointOfInterestId) { + this.locationPointOfInterestId = locationPointOfInterestId; + } + + public String getLocationPointOfInterestName() { + return locationPointOfInterestName; + } + + public void setLocationPointOfInterestName(String locationPointOfInterestName) { + this.locationPointOfInterestName = locationPointOfInterestName; + } + /** * @return the geoInfo */ @@ -190,6 +248,20 @@ public class ObservationListItem implements Comparable{ this.observationHeading = observationHeading; } + /** + * @return the observation text + */ + public String getObservationText() { + return observationText; + } + + /** + * @param observationText The observation text to set + */ + public void setObservationText(String observationText) { + this.observationText = observationText; + } + /** * @return the organismId */ diff --git a/src/main/java/no/nibio/vips/logic/service/ObservationService.java b/src/main/java/no/nibio/vips/logic/service/ObservationService.java index 128a5bf9c241cb2103a9bc04d34486a1d313def7..12aa050d36c928713615101745cb370c492db080 100755 --- a/src/main/java/no/nibio/vips/logic/service/ObservationService.java +++ b/src/main/java/no/nibio/vips/logic/service/ObservationService.java @@ -192,7 +192,7 @@ public class ObservationService { ULocale locale = new ULocale(localeStr != null ? localeStr : user != null ? user.getOrganizationId().getDefaultLocale() : userBean.getOrganization(organizationId).getDefaultLocale()); - LOGGER.info("Generate xlsx file for observations for user {}", user != null ? user.getUserId() : "unregistered"); + LOGGER.info("Generate xlsx file for observations for user {} from {} to {}", user != null ? user.getUserId() : "unregistered", fromStr, toStr); LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); @@ -200,7 +200,7 @@ public class ObservationService { try { List<ObservationListItem> observations = getFilteredObservationListItems(organizationId, observationTimeSeriesId, pestId, cropId, cropCategoryId, fromStr, toStr, userUUID, localeStr, isPositive); - byte[] excelFile = ExcelFileGenerator.generateExcel(observations, locale); + byte[] excelFile = ExcelFileGenerator.generateExcel(user, locale, now, fromStr, toStr, observations); return Response .ok(excelFile) diff --git a/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java index 0e927d28cfd17357b6d7d3ad2633cd6ff51cc045..0a2ad0d909e4cf108ff1a1d8de324e8d8fe5beaa 100644 --- a/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java +++ b/src/main/java/no/nibio/vips/logic/util/ExcelFileGenerator.java @@ -4,46 +4,119 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.ibm.icu.util.ULocale; +import no.nibio.vips.logic.entity.VipsLogicUser; import no.nibio.vips.logic.entity.rest.ObservationListItem; import no.nibio.vips.observationdata.ObservationDataSchema; +import org.apache.poi.common.usermodel.HyperlinkType; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.ss.usermodel.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; public final class ExcelFileGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(ExcelFileGenerator.class); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final int COL_INDEX_DATE = 0; - private static final int COL_INDEX_LOCATION = 1; - private static final int COL_INDEX_ORGANISM = 2; - private static final int COL_INDEX_CROP_ORGANISM = 3; - private static final int COL_INDEX_HEADING = 4; - private static final int COL_START_INDEX_DATA = 5; + private enum ColumnIndex { + ID(false, 0, 0, "observationId"), + DATE(false, 1, 1, "timeOfObservation"), + POI_ID(false, 2, 2, "locationPointOfInterestId"), + POI_NAME(false, 3, 3, "location"), + OBSERVER_ID(true, null, 4, "observerId"), + OBSERVER_NAME(true, null, 5, "observer"), + OBSERVATION_TIME_SERIES_ID(false, 4, 6, "observationTimeSeriesId"), + OBSERVATION_TIME_SERIES_LABEL(false, 5, 7, "observationTimeSeriesLabel"), + ORGANISM(false, 6, 8, "organism"), + CROP_ORGANISM(false, 7, 9, "cropOrganismId"), + HEADING(false, 8, 10, "observationHeading"), + DESCRIPTION(false, 9, 11, "observationText"), + BROADCAST(false, 10, 12, "isBroadcast"), + POSITIVE(false, 11, 13, "isPositiveRegistration"), + INDEX_DATA(false, 12, 14, null); - public static byte[] generateExcel(List<ObservationListItem> observations, ULocale locale) throws IOException { - ResourceBundle rb = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts", locale.toLocale()); + private final boolean isSensitive; + private final Integer openIndex; + private final Integer adminIndex; + private final String rbKey; + + ColumnIndex(boolean isSensitive, Integer openIndex, Integer adminIndex, String rbKey) { + this.isSensitive = isSensitive; + this.openIndex = openIndex; + this.adminIndex = adminIndex; + this.rbKey = rbKey; + } + + public static List<ColumnIndex> forUser(boolean isAdmin) { + if (!isAdmin) { + return Arrays.stream(ColumnIndex.values()).filter(columnIndex -> !columnIndex.isSensitive).toList(); + } + return Arrays.stream(ColumnIndex.values()).toList(); + } + + public String getColumnHeading(ResourceBundle rb) { + return rbKey != null && !rbKey.isBlank() ? rb.getString(rbKey) : ""; + } + public Integer getIndex(boolean admin) { + if (admin) { + return adminIndex; + } + return openIndex; + } + } + + public static byte[] generateExcel(VipsLogicUser user, ULocale locale, LocalDateTime now, String fromStr, String toStr, List<ObservationListItem> observations) throws IOException { + ResourceBundle rb = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts", locale.toLocale()); + boolean isAdmin = user != null && (user.isSuperUser() || user.isOrganizationAdmin()); + LOGGER.info("Create Excel file containing {} observations for {} user", observations.size(), isAdmin ? "admin" : "regular"); try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { - // Create main mainSheet for all observations, with header row + Font font = workbook.createFont(); + font.setBold(true); + CellStyle headerStyle = workbook.createCellStyle(); + headerStyle.setFont(font); + + // Create main sheet for all observations, with header row Sheet mainSheet = workbook.createSheet(rb.getString("allObservations")); - createHeaderRow(mainSheet, rb); + createHeaderRow(isAdmin, mainSheet, headerStyle, rb); int mainSheetRowIndex = 1; // Add one row for each observation in list of all observations for (ObservationListItem item : observations) { - createItemRow(mainSheet, mainSheetRowIndex++, item); + createItemRow(isAdmin, mainSheet, mainSheetRowIndex++, item, rb); } - autoSizeColumns(mainSheet, 0, COL_INDEX_HEADING); + autoSizeColumns(mainSheet, 0, ColumnIndex.INDEX_DATA.getIndex(isAdmin) - 1); + + // Create meta sheet for information about the download + Sheet metaSheet = workbook.createSheet(rb.getString("downloadInfo")); + Row userRow = metaSheet.createRow(0); + userRow.createCell(0).setCellValue(rb.getString("downloadedBy")); + userRow.createCell(1).setCellValue(user != null ? user.getFullName() : rb.getString("unregisteredUser")); + Row timeRow = metaSheet.createRow(1); + timeRow.createCell(0).setCellValue(rb.getString("downloadedTime")); + timeRow.createCell(1).setCellValue(DATE_TIME_FORMATTER.format(now)); + Row fromRow = metaSheet.createRow(2); + fromRow.createCell(0).setCellValue(rb.getString("dateStart")); + fromRow.createCell(1).setCellValue(fromStr); + Row toRow = metaSheet.createRow(3); + toRow.createCell(0).setCellValue(rb.getString("dateEnd")); + toRow.createCell(1).setCellValue(toStr); + Row countRow = metaSheet.createRow(4); + countRow.createCell(0).setCellValue(rb.getString("observationCount")); + countRow.createCell(1).setCellValue(observations.size()); + autoSizeColumns(metaSheet, 0, 1); // Prepare list of observations for each type of pest Map<Integer, List<ObservationListItem>> pestObservations = getObservationsForEachPest(observations); @@ -53,36 +126,33 @@ public final class ExcelFileGenerator { List<ObservationListItem> observationsForPest = pestObservations.get(pestId); ObservationListItem firstObservationForPest = observationsForPest.get(0); String pestName = firstObservationForPest.getOrganismName(); - Sheet pestSheet = workbook.createSheet(pestName); - Row headerRow = createHeaderRow(pestSheet, rb); + Sheet pestSheet = workbook.createSheet(sanitizeSheetName(pestId, pestName)); + Row headerRow = createHeaderRow(isAdmin, pestSheet, headerStyle, rb); // Add column titles for observation data Map<String, String> dataColumnTitles = getObservationDataColumnTitles(firstObservationForPest.getObservationDataSchema()); - int pestSheetColIndex = COL_START_INDEX_DATA; + int pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin); for (String key : dataColumnTitles.keySet()) { - headerRow.createCell(pestSheetColIndex++).setCellValue(dataColumnTitles.get(key)); + Cell cell = headerRow.createCell(pestSheetColIndex++); + cell.setCellStyle(headerStyle); + cell.setCellValue(dataColumnTitles.get(key)); } int pestSheetRowIndex = 1; for (ObservationListItem item : observationsForPest) { - Row row = createItemRow(pestSheet, pestSheetRowIndex++, item); + Row row = createItemRow(isAdmin, pestSheet, pestSheetRowIndex++, item, rb); if (item.getObservationData() != null) { Map<String, Object> observationDataMap = objectMapper.readValue(item.getObservationData(), HashMap.class); if (observationDataMap != null) { - pestSheetColIndex = COL_START_INDEX_DATA; + pestSheetColIndex = ColumnIndex.INDEX_DATA.getIndex(isAdmin); for (String key : dataColumnTitles.keySet()) { - Object value = observationDataMap.get(key); - if (value instanceof Number) { - row.createCell(pestSheetColIndex++).setCellValue(((Number) value).intValue()); - } else { - row.createCell(pestSheetColIndex++).setCellValue(value != null ? value.toString() : ""); - } + pestSheetColIndex = addValueToCell(row, pestSheetColIndex, observationDataMap.get(key)); } } } } - autoSizeColumns(pestSheet, COL_INDEX_DATE, COL_START_INDEX_DATA + dataColumnTitles.size()); + autoSizeColumns(pestSheet, ColumnIndex.ID.getIndex(isAdmin), ColumnIndex.INDEX_DATA.getIndex(isAdmin) + dataColumnTitles.size()); } workbook.write(out); @@ -90,6 +160,21 @@ public final class ExcelFileGenerator { } } + /** + * Create sheet name without invalid characters and within size limits + * + * @param pestId The id of the pest + * @param pestName The name of the pest + * @return a sanitized string to be used as sheet name + */ + private static String sanitizeSheetName(Integer pestId, String pestName) { + if (pestName == null || pestName.isBlank()) { + return "Id=" + pestId; + } + return pestName.replaceAll("[\\[\\]\\*/:?\\\\]", "_").substring(0, Math.min(pestName.length(), 31)); + } + + /** * Auto-size columns of given sheet, from startIndex to endIndex, to ensure that content fits * @@ -123,23 +208,6 @@ public final class ExcelFileGenerator { return resultMap; } - /** - * Find the name of the point of interest in given geoInfo string - * - * @param geoInfo The geoInfo which might contain the name of the point of interest - * @return the point of interest name, or empty string - */ - private static String getPointOfInterestName(String geoInfo) throws JsonProcessingException { - JsonNode rootNode = objectMapper.readTree(geoInfo); - JsonNode featuresNode = rootNode.path("features"); - if (featuresNode.isArray() && !featuresNode.isEmpty()) { - JsonNode firstFeature = featuresNode.get(0); - JsonNode propertiesNode = firstFeature.path("properties"); - return propertiesNode.path("pointOfInterestName").asText(); - } - return ""; - } - /** * Create map with pestId as key, and list of corresponding observations as value * @@ -161,41 +229,129 @@ public final class ExcelFileGenerator { return pestObservations; } + /** + * Create first row of given sheet, with column titles dependent on user privileges + * + * @param isAdmin Whether the user is a logged in admin + * @param sheet The sheet to which a row will be added + * @param headerStyle The style to be applied to each cell in the header row + * @param rb A resource bundle enabling localized messages + * @return the newly created header row + */ + public static Row createHeaderRow(boolean isAdmin, Sheet sheet, CellStyle headerStyle, ResourceBundle rb) { + Row headerRow = sheet.createRow(0); + for (ColumnIndex columnIndex : ColumnIndex.forUser(isAdmin)) { + Cell cell = headerRow.createCell(columnIndex.getIndex(isAdmin)); + cell.setCellStyle(headerStyle); + cell.setCellValue(columnIndex.getColumnHeading(rb)); + } + return headerRow; + } + /** * Create row with given index, for given observation list item * + * @param isAdmin Whether the user is a logged in admin * @param sheet The sheet to which a row will be added * @param rowIndex The index of the row * @param item The item of which to add data * @return the newly created row */ - private static Row createItemRow(Sheet sheet, int rowIndex, ObservationListItem item) throws JsonProcessingException { + private static Row createItemRow(boolean isAdmin, Sheet sheet, int rowIndex, ObservationListItem item, ResourceBundle rb) throws JsonProcessingException { LocalDate localDateOfObservation = item.getTimeOfObservation().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); - String pointOfInterestName = getPointOfInterestName(item.getGeoInfo()); - Row row = sheet.createRow(rowIndex); - row.createCell(COL_INDEX_DATE).setCellValue(localDateOfObservation.format(DATE_FORMATTER)); - row.createCell(COL_INDEX_LOCATION).setCellValue(pointOfInterestName); - row.createCell(COL_INDEX_ORGANISM).setCellValue(item.getOrganismName()); - row.createCell(COL_INDEX_CROP_ORGANISM).setCellValue(item.getCropOrganismName()); - row.createCell(COL_INDEX_HEADING).setCellValue(item.getObservationHeading()); + addObservationLinkToIdCol(isAdmin, row, item.getObservationId()); + addValueToCell(row, ColumnIndex.DATE.getIndex(isAdmin), localDateOfObservation.format(DATE_FORMATTER)); + + if (item.getLocationPointOfInterestId() != null) { + addValueToCell(row, ColumnIndex.POI_ID.getIndex(isAdmin), item.getLocationPointOfInterestId()); + addValueToCell(row, ColumnIndex.POI_NAME.getIndex(isAdmin), item.getLocationPointOfInterestName()); + } + + if (isAdmin) { + addValueToCell(row, ColumnIndex.OBSERVER_ID.getIndex(isAdmin), item.getObserverId()); + addValueToCell(row, ColumnIndex.OBSERVER_NAME.getIndex(isAdmin), item.getObserverName()); + } + if (item.getObservationTimeSeriesId() != null) { + addValueToCell(row, ColumnIndex.OBSERVATION_TIME_SERIES_ID.getIndex(isAdmin), item.getObservationTimeSeriesId()); + addValueToCell(row, ColumnIndex.OBSERVATION_TIME_SERIES_LABEL.getIndex(isAdmin), item.getObservationTimeSeriesLabel()); + } + addValueToCell(row, ColumnIndex.ORGANISM.getIndex(isAdmin), item.getOrganismName()); + addValueToCell(row, ColumnIndex.CROP_ORGANISM.getIndex(isAdmin), item.getCropOrganismName()); + addValueToCell(row, ColumnIndex.HEADING.getIndex(isAdmin), item.getObservationHeading()); + addValueToCell(row, ColumnIndex.DESCRIPTION.getIndex(isAdmin), item.getObservationText()); + + addValueToCell(row, ColumnIndex.BROADCAST.getIndex(isAdmin), getBooleanStringValue(rb, item.getBroadcastMessage())); + addValueToCell(row, ColumnIndex.POSITIVE.getIndex(isAdmin), getBooleanStringValue(rb, item.getIsPositive())); return row; } /** - * Create first row of given sheet, with standard set of column titles + * Add value to cell * - * @param sheet The sheet to which a row will be added - * @param rb A resource bundle enabling localized messages - * @return the newly created header row + * @param row The row to which the value should be added + * @param colIndex The index of the column to add the value to + * @param value The value + * @return The next index value */ - public static Row createHeaderRow(Sheet sheet, ResourceBundle rb) { - Row headerRow = sheet.createRow(0); - headerRow.createCell(COL_INDEX_DATE).setCellValue(rb.getString("timeOfObservation")); - headerRow.createCell(COL_INDEX_LOCATION).setCellValue(rb.getString("location")); - headerRow.createCell(COL_INDEX_ORGANISM).setCellValue(rb.getString("organism")); - headerRow.createCell(COL_INDEX_CROP_ORGANISM).setCellValue(rb.getString("cropOrganismId")); - headerRow.createCell(COL_INDEX_HEADING).setCellValue(rb.getString("observationHeading")); - return headerRow; + private static Integer addValueToCell(Row row, Integer colIndex, Object value) { + Integer index = colIndex; + if (value == null) { + row.createCell(index++).setCellValue(""); + } else if (value instanceof Number) { + row.createCell(index++).setCellValue(((Number) value).intValue()); + } else { + try { + int intValue = Integer.parseInt(value.toString()); + row.createCell(index++).setCellValue(intValue); + } catch (NumberFormatException e) { + row.createCell(index++).setCellValue(value.toString()); + } + } + return index; + } + + /** + * Get a localized String representing either true or false + * + * @param rb The resource bundle to get the localized string from + * @param value Either true or false + * @return A localized String + */ + private static String getBooleanStringValue(ResourceBundle rb, Boolean value) { + if (value == null) { + return null; + } else if (value) { + return rb.getString("yes"); + } + return rb.getString("no"); } + + /** + * Add link to observation details in column containing the observation Id + * + * @param isAdmin Whether or not the user is admin + * @param row A reference to the current row + * @param observationId The id of the observation + */ + private static void addObservationLinkToIdCol(boolean isAdmin, Row row, Integer observationId) { + Cell cell = row.createCell(ColumnIndex.ID.getIndex(isAdmin)); + cell.setCellValue(observationId); + + Workbook workbook = row.getSheet().getWorkbook(); + CreationHelper creationHelper = workbook.getCreationHelper(); + Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL); + //hyperlink.setAddress("https://www.vips-landbruk.no/observations/" + observationId); + // TODO Dette må fikses før deploy til prod + hyperlink.setAddress("https://testvips.nibio.no/observations/" + observationId); + cell.setHyperlink(hyperlink); + + CellStyle hlinkStyle = workbook.createCellStyle(); + Font hlinkFont = workbook.createFont(); + hlinkFont.setUnderline(Font.U_SINGLE); + hlinkFont.setColor(IndexedColors.BLUE.getIndex()); + hlinkStyle.setFont(hlinkFont); + cell.setCellStyle(hlinkStyle); + } + } diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties index 03e0245fe58ee6f99153ddd76e9281ba5d3d982a..bde7a3eda0709993ca8d40955b378b0adbc3d5f1 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties @@ -596,7 +596,7 @@ observationSiteStored = Observation site was successfully updated observationStored = Observation was stored -observationText = Observation text +observationText = Description observations = Observations @@ -604,6 +604,8 @@ observedDateOfFirstCatch = Observed date of first catch observedValue = Observed value +observerId = Observer ID + observer = Observer older = Older @@ -1067,3 +1069,15 @@ infoUriExpression=Template for request for station information isGridWeatherDataSource=This is a grid based weather data source weatherStationDataSourceStored=Weather (station) data source was successfully stored weatherStationDataSourceDeleted=The weather (station) data source was successfully deleted +observationTimeSeriesId=Timeseries +observationTimeSeriesLabel=Timeseries label +observationId=Observation +isBroadcast=Is broadcast +yes=Yes +no=No +downloadInfo=About download +downloadedBy=Downloaded by +unregisteredUser=Unregistered user +downloadedTime=Time of download +observationCount=Observation count + diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties index bc7b59b08ae5e993f8616d0b36d54d476f6b67e5..c50e7a8c6066d6b3d030196e525b4e3fc360b70e 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties @@ -420,13 +420,13 @@ listSelectedCropCategoryOnTop = List kulturer fra valgt gruppe \u00f8verst localName = Lokalt navn -location = Plassering +location=Sted -locationIsPrivate = Lokalitet skal ikke offentliggj\u00f8res +locationIsPrivate = Sted skal ikke offentliggj\u00f8res -locationIsPublic = Lokaliteten kan vises offentlig +locationIsPublic = Sted kan vises offentlig -locationPointOfInterestId = Lokalitet +locationPointOfInterestId=Sted-Id logInterval = M\u00e5leintervall @@ -560,7 +560,7 @@ allObservations = Alle observasjoner observationDeleted = Observasjonen ble slettet -observationHeading = Observasjonstittel +observationHeading = Tittel observationMap = Observasjonskart @@ -594,7 +594,7 @@ observationSiteStored = Rogneb\u00e6rm\u00f8llstasjonen ble oppdatert observationStored = Observasjonen ble lagret -observationText = Observasjonstekst +observationText = Beskrivelse observations = Observasjoner/f\u00f8rstefunn @@ -602,6 +602,8 @@ observedDateOfFirstCatch = Observert dato for f\u00f8rste fellefangst observedValue = Observert verdi +observerId = Observat\u00f8r-Id + observer = Observat\u00f8r older = Eldre @@ -1065,3 +1067,15 @@ infoUriExpression=Mal for henvendelse om stasjonsinformasjon isGridWeatherDataSource=Er en grid-basert v\u00e6rdatakilde weatherStationDataSourceStored=V\u00e6r(stasjons)datakilden ble lagret weatherStationDataSourceDeleted=V\u00e6r(stasjons)datakilden ble slettet +observationTimeSeriesId=Tidsserie-Id +observationTimeSeriesLabel=Tidsserie +observationId=Observasjon-Id +isBroadcast=Er kringkastet +yes=Ja +no=Nei +downloadInfo=Om nedlastingen +downloadedBy=Lastet ned av +unregisteredUser=Uregistrert bruker +downloadedTime=Tidspunkt for nedlasting +observationCount=Antall observasjoner + diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties index 731d0896dc4fb0df8f637c2d89571c44a58c00a9..a914181f176776529e06f8cd633d7bdfced87c07 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties @@ -1055,6 +1055,9 @@ privacyStatement=Privacy statement privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation +isBroadcast=Is broadcast +yes=Yes +no=No useGridWeatherData=Use grid weather data doNotUse=Do not use defaultGridWeatherStationDataSource=Gridded weather data source