Skip to content

Commit ecb9d00

Browse files
committed
add top level schema page in explorer ui
Clicking on `Browse Top Level Schemas` will return a schema page with all of the top level schemas. A top level schema is not referenced by any other schema. As part of this change, the `BrowseSchemaPage` was updated to support displaying multiple types.
1 parent da9ef6c commit ecb9d00

File tree

7 files changed

+181
-24
lines changed

7 files changed

+181
-24
lines changed

hollow-explorer-ui/src/main/java/com/netflix/hollow/explorer/ui/pages/BrowseSchemaPage.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.netflix.hollow.explorer.ui.model.SchemaDisplayField;
2222
import com.netflix.hollow.ui.HollowUISession;
2323
import java.io.Writer;
24+
import java.util.ArrayList;
25+
import java.util.List;
2426
import javax.servlet.http.HttpServletRequest;
2527
import org.apache.velocity.VelocityContext;
2628

@@ -32,24 +34,29 @@ public BrowseSchemaPage(HollowExplorerUI ui) {
3234

3335
@Override
3436
protected void setUpContext(HttpServletRequest req, HollowUISession session, VelocityContext ctx) {
35-
String type = req.getParameter("type");
37+
String[] types = req.getParameterValues("type");
3638
String expand = req.getParameter("expand");
3739
String collapse = req.getParameter("collapse");
38-
39-
SchemaDisplay schemaDisplay = (SchemaDisplay) session.getAttribute("schema-display-" + type);
4040

41-
if(schemaDisplay == null) {
42-
schemaDisplay = new SchemaDisplay(ui.getStateEngine().getSchema(type));
43-
schemaDisplay.setExpanded(true);
41+
List<SchemaDisplay> schemaDisplays = new ArrayList<SchemaDisplay>();
42+
for (String type : types) {
43+
SchemaDisplay schemaDisplay = (SchemaDisplay) session.getAttribute("schema-display-" + type);
44+
45+
if(schemaDisplay == null) {
46+
schemaDisplay = new SchemaDisplay(ui.getStateEngine().getSchema(type));
47+
schemaDisplay.setExpanded(true);
48+
}
49+
50+
if(expand != null) expandOrCollapse(schemaDisplay, expand.split("\\."), 1, true);
51+
if(collapse != null) expandOrCollapse(schemaDisplay, collapse.split("\\."), 1, false);
52+
53+
session.setAttribute("schema-display-" + type, schemaDisplay);
54+
55+
schemaDisplays.add(schemaDisplay);
4456
}
45-
46-
if(expand != null) expandOrCollapse(schemaDisplay, expand.split("\\."), 1, true);
47-
if(collapse != null) expandOrCollapse(schemaDisplay, collapse.split("\\."), 1, false);
48-
49-
session.setAttribute("schema-display-" + type, schemaDisplay);
50-
51-
ctx.put("schemaDisplay", schemaDisplay);
52-
ctx.put("type", type);
57+
58+
ctx.put("schemaDisplays", schemaDisplays);
59+
ctx.put("type", types);
5360
}
5461

5562
@Override

hollow-explorer-ui/src/main/java/com/netflix/hollow/explorer/ui/pages/ShowAllTypesPage.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
import com.netflix.hollow.core.index.key.PrimaryKey;
2222
import com.netflix.hollow.core.read.engine.HollowTypeReadState;
2323
import com.netflix.hollow.core.schema.HollowObjectSchema;
24-
import com.netflix.hollow.core.schema.HollowSchema;
2524
import com.netflix.hollow.core.schema.HollowSchema.SchemaType;
25+
import com.netflix.hollow.core.schema.HollowSchema;
26+
import com.netflix.hollow.core.schema.HollowSchemaUtil;
2627
import com.netflix.hollow.explorer.ui.HollowExplorerUI;
2728
import com.netflix.hollow.explorer.ui.model.TypeOverview;
2829
import com.netflix.hollow.ui.HollowUISession;
@@ -47,7 +48,7 @@ protected void setUpContext(HttpServletRequest req, HollowUISession session, Vel
4748
String sort = req.getParameter("sort") == null ? "primaryKey" : req.getParameter("sort");
4849

4950
List<TypeOverview> typeOverviews = new ArrayList<TypeOverview>();
50-
51+
5152
for(HollowTypeReadState typeState : ui.getStateEngine().getTypeStates()) {
5253
String typeName = typeState.getSchema().getName();
5354
BitSet populatedOrdinals = typeState.getPopulatedOrdinals();
@@ -58,7 +59,7 @@ protected void setUpContext(HttpServletRequest req, HollowUISession session, Vel
5859
long approxHeapFootprint = typeState.getApproximateHeapFootprintInBytes();
5960
HollowSchema schema = typeState.getSchema();
6061
int numShards = typeState.numShards();
61-
62+
6263
typeOverviews.add(new TypeOverview(typeName, numRecords, numHoles, approxHoleFootprint, approxHeapFootprint, primaryKey, schema, numShards));
6364
}
6465

@@ -109,6 +110,7 @@ public int compare(TypeOverview o1, TypeOverview o2) {
109110
ctx.put("totalHoleFootprint", totalApproximateHoleFootprint(typeOverviews));
110111
ctx.put("totalHeapFootprint", totalApproximateHeapFootprint(typeOverviews));
111112
ctx.put("typeOverviews", typeOverviews);
113+
ctx.put("topLevelTypes", HollowSchemaUtil.getTopLevelTypes(ui.getStateEngine()));
112114
}
113115

114116
@Override

hollow-explorer-ui/src/main/resources/browse-schema.vm

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
* limitations under the License.
1515
*#
1616

17-
<b>TYPE:</b> &nbsp;&nbsp; <span style="font-family: monospace;">$esc.html($schemaDisplay.getSchema().getName())</span>
18-
19-
#showSchema($schemaDisplay "")
17+
#foreach($schemaDisplay in $schemaDisplays)
18+
<b>TYPE:</b> &nbsp;&nbsp; <span style="font-family: monospace;">$esc.html($schemaDisplay.getSchema().getName())</span>
19+
#showSchema($schemaDisplay)
20+
#end
2021

2122
#macro(showSchema $schemaDisplay)
2223

@@ -28,9 +29,11 @@
2829
<span style="font-family: monospace;">
2930
#if($field.getReferencedType())
3031
#if($field.getReferencedType().isExpanded())
31-
<a href="$basePath/schema?type=$esc.url($type)&collapse=$esc.url($field.getFieldPath())">$esc.html($field.getFieldName())</a>
32+
<a href="$basePath/schema?#foreach($t in $type)type=$esc.url($t)#if($foreach.hasNext)&amp;
33+
#end#end&collapse=$esc.url($field.getFieldPath())">$esc.html($field.getFieldName())</a>
3234
#else
33-
<a href="$basePath/schema?type=$esc.url($type)&expand=$esc.url($field.getFieldPath())">$esc.html($field.getFieldName())</a>
35+
<a href="$basePath/schema?#foreach($t in $type)type=$esc.url($t)#if($foreach.hasNext)&amp;
36+
#end#end&expand=$esc.url($field.getFieldPath())">$esc.html($field.getFieldName())</a>
3437
#end
3538

3639
($esc.html($field.getReferencedType().getTypeName()) - $esc.html($field.getReferencedType().getSchema().getSchemaType()))

hollow-explorer-ui/src/main/resources/explorer-header.vm

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,16 @@
3434
#end
3535
#if($stateVersion)
3636
<td class="nav">[ State Version: $stateVersion ]</td>
37-
#end
38-
#if($type)
37+
#end
38+
## When $type is defined and is not a list, display links to browse
39+
## data and schema for that type.
40+
#if($type && !($type.size() > 0))
3941
<td class="nav">[ Type: <span style="font-family: monospace;">$esc.html($type)</span> ]</td>
4042
<td class="nav">[ <a href="$basePath/type?type=$esc.url($type)">Browse Data</a> | <a href="$basePath/schema?type=$esc.url($type)">Browse Schema</a> ]</td>
4143
#end
44+
#if(!$type && $topLevelTypes && $topLevelTypes.size() > 0)
45+
<td class="nav">[ <a href="$basePath/schema?#foreach($t in $topLevelTypes)type=$esc.url($t)#if($foreach.hasNext)&amp;#end#end">Browse Top Level Schemas</a> ]</td>
46+
#end
4247
<td class="nav">[ <a href="$basePath/query">SEARCH</a> ]</td>
4348
</tr>
4449
</table>

hollow-ui-tools/src/main/java/com/netflix/hollow/ui/HttpHandlerWithServletSupport.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ public String getParameter(String name) {
163163
return arr != null ? (arr.length > 1 ? Arrays.toString(arr) : arr[0]) : null;
164164
}
165165

166+
@Override
167+
public String[] getParameterValues(String name) {
168+
return postData.get(name);
169+
}
170+
166171
@Override
167172
public Map<String, String[]> getParameterMap() {
168173
return postData;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.netflix.hollow.core.schema;
2+
3+
import com.netflix.hollow.core.read.engine.HollowReadStateEngine;
4+
import com.netflix.hollow.core.read.engine.HollowTypeReadState;
5+
6+
import java.util.HashSet;
7+
import java.util.List;
8+
import java.util.Set;
9+
10+
public class HollowSchemaUtil {
11+
12+
/* *
13+
* Finds the top-level types in the Hollow dataset. A top-level type is one that is not a
14+
* reference from another type, i.e., it is not an element of a list, set, or map, nor is it
15+
* a field in an object schema.
16+
*
17+
* @return a set of top-level type names
18+
*/
19+
public static Set<String> getTopLevelTypes(HollowReadStateEngine readState) {
20+
List<HollowSchema> schemas = readState.getSchemas();
21+
Set<String> topLevelTypes = new HashSet<>(readState.getAllTypes());
22+
for (HollowSchema schema : schemas) {
23+
switch (schema.getSchemaType()) {
24+
case LIST:
25+
HollowListSchema listSchema = (HollowListSchema) schema;
26+
topLevelTypes.remove(listSchema.getElementType());
27+
break;
28+
case SET:
29+
HollowSetSchema setSchema = (HollowSetSchema) schema;
30+
topLevelTypes.remove(setSchema.getElementType());
31+
break;
32+
case MAP:
33+
HollowMapSchema mapSchema = (HollowMapSchema) schema;
34+
topLevelTypes.remove(mapSchema.getKeyType());
35+
topLevelTypes.remove(mapSchema.getValueType());
36+
break;
37+
case OBJECT:
38+
HollowObjectSchema objectSchema = (HollowObjectSchema) schema;
39+
for (int fieldIdx=0; fieldIdx < objectSchema.numFields(); fieldIdx++) {
40+
if (objectSchema.getFieldType(fieldIdx) == HollowObjectSchema.FieldType.REFERENCE) {
41+
topLevelTypes.remove(objectSchema.getReferencedType(fieldIdx));
42+
}
43+
}
44+
}
45+
}
46+
return topLevelTypes;
47+
}
48+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.netflix.hollow.core.schema;
2+
3+
import com.netflix.hollow.api.producer.HollowProducer;
4+
import com.netflix.hollow.api.producer.fs.HollowInMemoryBlobStager;
5+
import com.netflix.hollow.core.read.engine.HollowReadStateEngine;
6+
import com.netflix.hollow.core.util.StateEngineRoundTripper;
7+
import com.netflix.hollow.test.InMemoryBlobStore;
8+
import org.junit.Assert;
9+
import org.junit.Test;
10+
11+
import java.io.IOException;
12+
import java.util.HashSet;
13+
14+
public class HollowTopLevelTypesTest {
15+
16+
@Test
17+
public void testHollowTopLevelTypes() throws IOException {
18+
HollowProducer p = HollowProducer.withPublisher(new InMemoryBlobStore())
19+
.withBlobStager(new HollowInMemoryBlobStager())
20+
.build();
21+
22+
TypeWithPrimitive primitive = new TypeWithPrimitive();
23+
TypeWithReference reference = new TypeWithReference();
24+
TypeWithSetOfReference setOfReference = new TypeWithSetOfReference();
25+
26+
p.runCycle(cycle -> {
27+
cycle.add(reference);
28+
});
29+
HollowReadStateEngine readStateEngine = new HollowReadStateEngine();
30+
StateEngineRoundTripper.roundTripSnapshot(p.getWriteEngine(), readStateEngine);
31+
Assert.assertEquals(
32+
new HashSet<String>() {{
33+
add("TypeWithPrimitive");
34+
add("TypeWithReference");
35+
}},
36+
readStateEngine.getAllTypes());
37+
Assert.assertEquals(
38+
new HashSet<String>() {{
39+
add("TypeWithReference");
40+
}},
41+
HollowSchemaUtil.getTopLevelTypes(readStateEngine));
42+
43+
// Add a new type with a set of TypeWithReference, now only
44+
// TypeWithSetOfReference should be a top-level type.
45+
p.runCycle(cycle -> {
46+
cycle.add(setOfReference);
47+
});
48+
StateEngineRoundTripper.roundTripSnapshot(p.getWriteEngine(), readStateEngine);
49+
Assert.assertEquals(
50+
new HashSet<String>() {{
51+
add("TypeWithPrimitive");
52+
add("TypeWithReference");
53+
add("TypeWithSetOfReference");
54+
add("SetOfTypeWithReference");
55+
}},
56+
readStateEngine.getAllTypes());
57+
Assert.assertEquals(
58+
new HashSet<String>() {{
59+
add("TypeWithSetOfReference");
60+
}},
61+
HollowSchemaUtil.getTopLevelTypes(readStateEngine));
62+
}
63+
64+
private static class TypeWithSetOfReference {
65+
private final HashSet<TypeWithReference> set;
66+
67+
public TypeWithSetOfReference() {
68+
this.set = new HashSet<>();
69+
}
70+
}
71+
72+
private static class TypeWithPrimitive {
73+
private final int value;
74+
75+
public TypeWithPrimitive() {
76+
this.value = 0;
77+
}
78+
}
79+
80+
private static class TypeWithReference {
81+
private final TypeWithPrimitive primitive ;
82+
83+
public TypeWithReference() {
84+
this.primitive = new TypeWithPrimitive();
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)