Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Allow Custom Property Update in Glossary Bulk Import/export #17919

Merged
merged 66 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
1ec2981
fix import issue
chirag-madlani Sep 16, 2024
b90026b
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 18, 2024
0f25edd
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 18, 2024
4c0441a
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 19, 2024
564967a
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 19, 2024
8708738
Feat : Allow Custom Property Update in Bulk Upload
sonika-shah Sep 19, 2024
58fc964
Feat : Allow Custom Property Update in Bulk Upload
sonika-shah Sep 19, 2024
ac8a4cb
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 19, 2024
80c533d
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 20, 2024
65ed612
Merge branch 'main' into feature-import-export-custom-property
sonika-shah Sep 22, 2024
4bb8473
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 23, 2024
1d165d1
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 23, 2024
e46696b
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 24, 2024
04ea4bc
supported editable imports in glossary page
Ashish8689 Sep 24, 2024
4d918fb
added remaning localizaion keys
Ashish8689 Sep 24, 2024
290bbeb
Merge remote-tracking branch 'origin/main'
sonika-shah Sep 25, 2024
7584eec
Merge branch 'main' into feature-import-export-custom-property
sonika-shah Sep 25, 2024
c498c87
update logic of fieldToExtensionStrings to use csvparser
sonika-shah Sep 25, 2024
f2d35f5
Merge remote-tracking branch 'origin/feature-import-export-custom-pro…
sonika-shah Sep 25, 2024
410a067
update json and partialStatus condition
sonika-shah Sep 25, 2024
e87b156
Merge remote-tracking branch 'origin/feature-import-export-custom-pro…
sonika-shah Sep 25, 2024
c1793fd
fix tests for partialSuccess status change
sonika-shah Sep 26, 2024
a8ba567
Merge remote-tracking branch 'origin/feature-import-export-custom-pro…
sonika-shah Sep 26, 2024
4601f20
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 26, 2024
482e35f
supported customProperty editable field
Ashish8689 Sep 26, 2024
118f309
Merge remote-tracking branch 'origin/feature-import-export-custom-pro…
sonika-shah Sep 26, 2024
6ec8eb9
fix error in custom property edit modal on new line empty custom prop…
Ashish8689 Sep 26, 2024
e07f90d
Merge remote-tracking branch 'origin/feature-import-export-custom-pro…
sonika-shah Sep 26, 2024
b4a8f51
added entity type from root to support other bulk import entity as well
Ashish8689 Sep 26, 2024
de71b78
fix the quote removing due to the regex in the string type
Ashish8689 Sep 26, 2024
9fbde3c
Merge remote-tracking branch 'origin/feature-import-export-custom-pro…
sonika-shah Sep 27, 2024
e5176f9
Add backend tests , and error msg improvements
sonika-shah Sep 27, 2024
4751990
GlossaryStatus header change
mohityadav766 Sep 27, 2024
e1e66ab
Merge remote-tracking branch 'origin/feature-import-export-custom-pro…
mohityadav766 Sep 27, 2024
0c70baa
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 27, 2024
7d8cf84
fix unit test and dry run in case of synonyms having quotes in it
Ashish8689 Sep 27, 2024
dde2c0b
Remove extension column in CSVs for all entities except glossaryTerm
sonika-shah Sep 27, 2024
7a4500b
added editor for reviewers
Ashish8689 Sep 27, 2024
ce47405
unit test around csv utils
Ashish8689 Sep 27, 2024
998efe8
added escape for string too, in case of semicolon comes
Ashish8689 Sep 27, 2024
784f2b5
added playwright test without extension and supported relatedTerm as …
Ashish8689 Sep 27, 2024
70dd468
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 27, 2024
01ec59d
added unit test around csv util logic
Ashish8689 Sep 28, 2024
9228945
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 28, 2024
eaa9e86
resolve conflicts
sonika-shah Sep 29, 2024
bf56784
Merge branch 'main' into feature-import-export-custom-property
sonika-shah Sep 29, 2024
c2457bb
Backend - add support for enumWithDescriptions in bulk import
sonika-shah Sep 29, 2024
e4fb9d7
add tests and other error handling improvements related to enumWithDe…
sonika-shah Sep 29, 2024
1e25a5c
fix the custom property modal header and render the layout as per rig…
Ashish8689 Sep 29, 2024
4f4e26e
parese enumWithDescription for the customProperty modal while editable
Ashish8689 Sep 29, 2024
94b8515
fix description data in enumWithDescription one
Ashish8689 Sep 29, 2024
6ad168a
fix: Handle NullPointerException when adding custom properties to ens…
sonika-shah Sep 29, 2024
1986cf0
added extension playwrigth test and fix enumWithDescription object fa…
Ashish8689 Sep 29, 2024
4600bf0
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 29, 2024
354ffb3
descrease the size of extension modal
Ashish8689 Sep 29, 2024
24dba51
remove additional comments
sonika-shah Sep 30, 2024
addac1a
fix the escape in parent key
Ashish8689 Sep 30, 2024
9a9599b
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 30, 2024
779bed7
improve custom property layout
Sachin-chaurasiya Sep 30, 2024
2162217
improve ui for inline properties
Sachin-chaurasiya Sep 30, 2024
9734649
fix description, glossary and relatedTerm escape char issue
Ashish8689 Sep 30, 2024
eec45d4
fix some customProperty ui changes
Ashish8689 Sep 30, 2024
fd21170
fix sonar issue
Ashish8689 Sep 30, 2024
f6830cb
Merge branch 'main' into feature-import-export-custom-property
Ashish8689 Sep 30, 2024
305b92d
minor layout changes
Sachin-chaurasiya Sep 30, 2024
6da7ccb
minor label improvements for entity ref and list
Sachin-chaurasiya Sep 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions openmetadata-service/src/main/java/org/openmetadata/csv/CsvUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVFormat.Builder;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
import org.openmetadata.schema.type.EntityReference;
Expand All @@ -39,6 +45,8 @@ public final class CsvUtil {
public static final String ENTITY_TYPE_SEPARATOR = ":";
public static final String LINE_SEPARATOR = "\r\n";

public static final String INTERNAL_ARRAY_SEPARATOR = "|";

private CsvUtil() {
// Utility class hides the constructor
}
Expand Down Expand Up @@ -94,6 +102,62 @@ public static List<String> fieldToEntities(String field) {
return field == null ? null : listOf(field.split(ENTITY_TYPE_SEPARATOR));
}

public static List<String> fieldToInternalArray(String field) {
// Split a fieldValue that contains multiple elements of an array separated by
// INTERNAL_ARRAY_SEPARATOR
if (field == null || field.isBlank()) {
return Collections.emptyList();
}
return listOf(field.split(Pattern.quote(INTERNAL_ARRAY_SEPARATOR)));
}

/**
* Parses a field containing key-value pairs separated by semicolons, correctly handling quotes.
* Each key-value pair may also be enclosed in quotes, especially if it contains delimiter like (SEPARATOR , FIELD_SEPARATOR).
* Input Example:
* "key1:value1;key2:value2;\"key3:value;with;semicolon\""
* Output: [key1:value1, key2:value2, key3:value;with;semicolon]
*
*/
public static List<String> fieldToExtensionStrings(String field) throws IOException {
if (field == null || field.isBlank()) {
return List.of();
}

// Replace semicolons within quoted strings with a placeholder
String preprocessedField =
Pattern.compile("\"([^\"]*)\"") // Matches content inside double quotes
.matcher(field)
.replaceAll(mr -> "\"" + mr.group(1).replace(";", "__SEMICOLON__") + "\"");

preprocessedField = preprocessedField.replace("\n", "\\n").replace("\"", "\\\"");

CSVFormat format =
CSVFormat.DEFAULT
.withDelimiter(';')
.withQuote('"')
.withRecordSeparator(null)
.withIgnoreSurroundingSpaces(true)
.withIgnoreEmptyLines(true)
.withEscape('\\'); // Use backslash for escaping special characters

try (CSVParser parser = CSVParser.parse(new StringReader(preprocessedField), format)) {
return parser.getRecords().stream()
.flatMap(CSVRecord::stream)
.map(
value ->
value
.replace("__SEMICOLON__", ";")
.replace("\\n", "\n")) // Restore original semicolons and newlines
.map(
value ->
value.startsWith("\"") && value.endsWith("\"") // Remove outer quotes if present
? value.substring(1, value.length() - 1)
: value)
.toList();
}
}

public static String quote(String field) {
return String.format("\"%s\"", field);
}
Expand Down Expand Up @@ -205,4 +269,89 @@ private static String quoteCsvField(String str) {
}
return str;
}

public static List<String> addExtension(List<String> csvRecord, Object extension) {
if (extension == null) {
csvRecord.add(null);
return csvRecord;
}

ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> extensionMap = objectMapper.convertValue(extension, Map.class);

String extensionString =
extensionMap.entrySet().stream()
.map(
entry -> {
String key = entry.getKey();
Object value = entry.getValue();
return CsvUtil.quoteCsvField(key + ENTITY_TYPE_SEPARATOR + formatValue(value));
})
.collect(Collectors.joining(FIELD_SEPARATOR));

csvRecord.add(extensionString);
return csvRecord;
}

private static String formatValue(Object value) {
if (value instanceof Map) {
return formatMapValue((Map<String, Object>) value);
}

if (value instanceof List) {
return formatListValue((List<?>) value);
}

return value != null ? value.toString() : "";
}

private static String formatMapValue(Map<String, Object> valueMap) {
if (isEntityReference(valueMap)) {
return formatEntityReference(valueMap);
} else if (isTimeInterval(valueMap)) {
return formatTimeInterval(valueMap);
}

return valueMap.toString();
}

private static String formatListValue(List<?> list) {
if (list.isEmpty()) {
return "";
}

if (list.get(0) instanceof Map && isEnumWithDescriptions((Map<String, Object>) list.get(0))) {
return list.stream()
.map(item -> ((Map<String, Object>) item).get("key").toString())
.collect(Collectors.joining(INTERNAL_ARRAY_SEPARATOR));
} else if (list.get(0) instanceof Map) {
return list.stream()
.map(item -> formatMapValue((Map<String, Object>) item))
.collect(Collectors.joining(INTERNAL_ARRAY_SEPARATOR));
} else {
return list.stream()
.map(Object::toString)
.collect(Collectors.joining(INTERNAL_ARRAY_SEPARATOR));
}
}

private static boolean isEntityReference(Map<String, Object> valueMap) {
return valueMap.containsKey("type") && valueMap.containsKey("fullyQualifiedName");
}

private static boolean isTimeInterval(Map<String, Object> valueMap) {
return valueMap.containsKey("start") && valueMap.containsKey("end");
}

private static boolean isEnumWithDescriptions(Map<String, Object> valueMap) {
return valueMap.containsKey("key") && valueMap.containsKey("description");
}

private static String formatEntityReference(Map<String, Object> valueMap) {
return valueMap.get("type") + ENTITY_TYPE_SEPARATOR + valueMap.get("fullyQualifiedName");
}

private static String formatTimeInterval(Map<String, Object> valueMap) {
return valueMap.get("start") + ENTITY_TYPE_SEPARATOR + valueMap.get("end");
}
}
Loading
Loading