Skip to content

Commit e2bdb8f

Browse files
authored
Fix display empty string when json cell is null (#132)
* Fix display empty when json cell is null * Decrease output cells complexity * Imp coverage
1 parent d2104a6 commit e2bdb8f

File tree

6 files changed

+158
-50
lines changed

6 files changed

+158
-50
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.michelin.kafkactl.model.format;
2+
3+
import static io.micronaut.core.util.StringUtils.EMPTY_STRING;
4+
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.util.StdDateFormat;
7+
import java.text.ParseException;
8+
import java.util.Date;
9+
import lombok.AllArgsConstructor;
10+
import org.ocpsoft.prettytime.PrettyTime;
11+
12+
/**
13+
* Ago format.
14+
*/
15+
@AllArgsConstructor
16+
public class AgoFormat implements OutputFormatStrategy {
17+
private String jsonPointer;
18+
19+
@Override
20+
public String display(JsonNode node) {
21+
String output;
22+
JsonNode cell = node.at(this.jsonPointer);
23+
24+
try {
25+
StdDateFormat sdf = new StdDateFormat();
26+
Date d = sdf.parse(cell.asText());
27+
output = new PrettyTime().format(d);
28+
} catch (ParseException e) {
29+
output = EMPTY_STRING;
30+
}
31+
32+
return output;
33+
}
34+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.michelin.kafkactl.model.format;
2+
3+
import static io.micronaut.core.util.StringUtils.EMPTY_STRING;
4+
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.node.JsonNodeType;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import lombok.AllArgsConstructor;
10+
11+
/**
12+
* Default format.
13+
*/
14+
@AllArgsConstructor
15+
public class DefaultFormat implements OutputFormatStrategy {
16+
private String jsonPointer;
17+
18+
@Override
19+
public String display(JsonNode node) {
20+
String output;
21+
JsonNode cell = node.at(this.jsonPointer);
22+
23+
if (cell.isArray()) {
24+
List<String> children = new ArrayList<>();
25+
cell.elements().forEachRemaining(jsonNode -> children.add(jsonNode.asText()));
26+
output = String.join(",", children);
27+
} else {
28+
output = cell.getNodeType().equals(JsonNodeType.NULL) ? EMPTY_STRING : cell.asText();
29+
}
30+
31+
return output;
32+
}
33+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.michelin.kafkactl.model.format;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
5+
/**
6+
* Output format strategy.
7+
*/
8+
public interface OutputFormatStrategy {
9+
String display(JsonNode node);
10+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.michelin.kafkactl.model.format;
2+
3+
import static io.micronaut.core.util.StringUtils.EMPTY_STRING;
4+
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import java.util.concurrent.TimeUnit;
7+
import lombok.AllArgsConstructor;
8+
9+
/**
10+
* Period format.
11+
*/
12+
@AllArgsConstructor
13+
public class PeriodFormat implements OutputFormatStrategy {
14+
private String jsonPointer;
15+
16+
@Override
17+
public String display(JsonNode node) {
18+
String output;
19+
JsonNode cell = node.at(this.jsonPointer);
20+
21+
try {
22+
long ms = Long.parseLong(cell.asText());
23+
long days = TimeUnit.MILLISECONDS.toDays(ms);
24+
long hours = TimeUnit.MILLISECONDS.toHours(ms - TimeUnit.DAYS.toMillis(days));
25+
long minutes = TimeUnit.MILLISECONDS.toMinutes(
26+
ms - TimeUnit.DAYS.toMillis(days) - TimeUnit.HOURS.toMillis(hours));
27+
output = days > 0 ? (days + "d") : "";
28+
output += hours > 0 ? (hours + "h") : "";
29+
output += minutes > 0 ? (minutes + "m") : "";
30+
} catch (NumberFormatException e) {
31+
output = EMPTY_STRING;
32+
}
33+
34+
return output;
35+
}
36+
}

src/main/java/com/michelin/kafkactl/service/FormatService.java

Lines changed: 20 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
package com.michelin.kafkactl.service;
22

3-
import static io.micronaut.core.util.StringUtils.EMPTY_STRING;
4-
53
import com.fasterxml.jackson.databind.JsonNode;
64
import com.fasterxml.jackson.databind.ObjectMapper;
7-
import com.fasterxml.jackson.databind.util.StdDateFormat;
85
import com.michelin.kafkactl.config.KafkactlConfig;
96
import com.michelin.kafkactl.model.Resource;
107
import com.michelin.kafkactl.model.Status;
8+
import com.michelin.kafkactl.model.format.AgoFormat;
9+
import com.michelin.kafkactl.model.format.DefaultFormat;
10+
import com.michelin.kafkactl.model.format.OutputFormatStrategy;
11+
import com.michelin.kafkactl.model.format.PeriodFormat;
1112
import io.micronaut.core.annotation.ReflectiveAccess;
1213
import io.micronaut.core.naming.conventions.StringConvention;
1314
import io.micronaut.http.client.exceptions.HttpClientResponseException;
1415
import jakarta.inject.Inject;
1516
import jakarta.inject.Singleton;
16-
import java.text.ParseException;
1717
import java.util.ArrayList;
1818
import java.util.Arrays;
19-
import java.util.Date;
2019
import java.util.List;
2120
import java.util.Optional;
22-
import java.util.concurrent.TimeUnit;
23-
import org.ocpsoft.prettytime.PrettyTime;
2421
import org.yaml.snakeyaml.DumperOptions;
2522
import org.yaml.snakeyaml.Yaml;
2623
import org.yaml.snakeyaml.nodes.Tag;
@@ -234,63 +231,36 @@ public String toString() {
234231

235232
static class PrettyTextTableColumn {
236233
private final String header;
237-
private final String jsonPointer;
238-
private final String transform;
239234
private final int indent;
240235
private int size = -1;
236+
private OutputFormatStrategy outputFormat;
241237

242238
public PrettyTextTableColumn(int indent, String... elements) {
243239
this.header = elements[0];
244240
this.indent = indent;
245241

246-
if (elements[1].contains("%")) {
247-
this.jsonPointer = elements[1].split("%")[0];
248-
this.transform = elements[1].split("%")[1];
242+
String[] field = elements[1].split("%");
243+
if (field.length > 1) {
244+
switch (field[1]) {
245+
case "AGO":
246+
this.outputFormat = new AgoFormat(field[0]);
247+
break;
248+
case "PERIOD":
249+
this.outputFormat = new PeriodFormat(field[0]);
250+
break;
251+
default:
252+
break;
253+
}
249254
} else {
250-
this.jsonPointer = elements[1];
251-
this.transform = "NONE";
255+
this.outputFormat = new DefaultFormat(field[0]);
252256
}
257+
253258
// Size should consider headers
254259
this.size = Math.max(this.size, this.header.length() + indent);
255260
}
256261

257262
public String transform(JsonNode node) {
258-
String output;
259-
JsonNode cell = node.at(this.jsonPointer);
260-
switch (this.transform) {
261-
case "AGO" -> {
262-
try {
263-
StdDateFormat sdf = new StdDateFormat();
264-
Date d = sdf.parse(cell.asText());
265-
output = new PrettyTime().format(d);
266-
} catch (ParseException e) {
267-
output = EMPTY_STRING;
268-
}
269-
}
270-
case "PERIOD" -> {
271-
try {
272-
long ms = Long.parseLong(cell.asText());
273-
long days = TimeUnit.MILLISECONDS.toDays(ms);
274-
long hours = TimeUnit.MILLISECONDS.toHours(ms - TimeUnit.DAYS.toMillis(days));
275-
long minutes = TimeUnit.MILLISECONDS.toMinutes(
276-
ms - TimeUnit.DAYS.toMillis(days) - TimeUnit.HOURS.toMillis(hours));
277-
output = days > 0 ? (days + "d") : "";
278-
output += hours > 0 ? (hours + "h") : "";
279-
output += minutes > 0 ? (minutes + "m") : "";
280-
} catch (NumberFormatException e) {
281-
output = EMPTY_STRING;
282-
}
283-
}
284-
default -> {
285-
if (cell.isArray()) {
286-
List<String> children = new ArrayList<>();
287-
cell.elements().forEachRemaining(jsonNode -> children.add(jsonNode.asText()));
288-
output = String.join(",", children);
289-
} else {
290-
output = cell.asText();
291-
}
292-
}
293-
}
263+
String output = this.outputFormat.display(node);
294264
// Check size for later
295265
size = Math.max(size, output.length() + indent);
296266
return output;

src/test/java/com/michelin/kafkactl/service/FormatServiceTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,31 @@ void shouldDisplayListTable() {
5353
assertTrue(sw.toString().contains("prefix.topic 1m delete"));
5454
}
5555

56+
@Test
57+
void shouldDisplayEmptyInsteadOfNull() {
58+
Resource resource = Resource.builder()
59+
.kind("Topic")
60+
.apiVersion("v1")
61+
.metadata(Metadata.builder()
62+
.name(null)
63+
.creationTimestamp(Date.from(Instant.parse("2000-01-01T01:00:00.00Z")))
64+
.build())
65+
.spec(Map.of("configs",
66+
Map.of("retention.ms", "60000",
67+
"cleanup.policy", "delete"
68+
)))
69+
.build();
70+
71+
CommandLine cmd = new CommandLine(new Kafkactl());
72+
StringWriter sw = new StringWriter();
73+
cmd.setOut(new PrintWriter(sw));
74+
75+
formatService.displayList("Topic", Collections.singletonList(resource), TABLE, cmd.getCommandSpec());
76+
77+
assertTrue(sw.toString().contains("TOPIC RETENTION POLICY AGE"));
78+
assertTrue(sw.toString().contains(" 1m delete"));
79+
}
80+
5681
@Test
5782
void shouldDisplayListTableEmptySpecs() {
5883
Resource resource = Resource.builder()

0 commit comments

Comments
 (0)