Skip to content

Commit 2a1c640

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 2a1c640

File tree

7 files changed

+171
-23
lines changed

7 files changed

+171
-23
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: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020

2121
import com.netflix.hollow.core.index.key.PrimaryKey;
2222
import com.netflix.hollow.core.read.engine.HollowTypeReadState;
23+
import com.netflix.hollow.core.schema.HollowListSchema;
24+
import com.netflix.hollow.core.schema.HollowMapSchema;
2325
import com.netflix.hollow.core.schema.HollowObjectSchema;
2426
import com.netflix.hollow.core.schema.HollowSchema;
2527
import com.netflix.hollow.core.schema.HollowSchema.SchemaType;
28+
import com.netflix.hollow.core.schema.HollowSetSchema;
2629
import com.netflix.hollow.explorer.ui.HollowExplorerUI;
2730
import com.netflix.hollow.explorer.ui.model.TypeOverview;
2831
import com.netflix.hollow.ui.HollowUISession;
@@ -32,6 +35,8 @@
3235
import java.util.Collections;
3336
import java.util.Comparator;
3437
import java.util.List;
38+
import java.util.Set;
39+
import java.util.stream.Collectors;
3540
import javax.servlet.http.HttpServletRequest;
3641
import org.apache.velocity.VelocityContext;
3742

@@ -47,7 +52,7 @@ protected void setUpContext(HttpServletRequest req, HollowUISession session, Vel
4752
String sort = req.getParameter("sort") == null ? "primaryKey" : req.getParameter("sort");
4853

4954
List<TypeOverview> typeOverviews = new ArrayList<TypeOverview>();
50-
55+
5156
for(HollowTypeReadState typeState : ui.getStateEngine().getTypeStates()) {
5257
String typeName = typeState.getSchema().getName();
5358
BitSet populatedOrdinals = typeState.getPopulatedOrdinals();
@@ -58,7 +63,7 @@ protected void setUpContext(HttpServletRequest req, HollowUISession session, Vel
5863
long approxHeapFootprint = typeState.getApproximateHeapFootprintInBytes();
5964
HollowSchema schema = typeState.getSchema();
6065
int numShards = typeState.numShards();
61-
66+
6267
typeOverviews.add(new TypeOverview(typeName, numRecords, numHoles, approxHoleFootprint, approxHeapFootprint, primaryKey, schema, numShards));
6368
}
6469

@@ -109,6 +114,7 @@ public int compare(TypeOverview o1, TypeOverview o2) {
109114
ctx.put("totalHoleFootprint", totalApproximateHoleFootprint(typeOverviews));
110115
ctx.put("totalHeapFootprint", totalApproximateHeapFootprint(typeOverviews));
111116
ctx.put("typeOverviews", typeOverviews);
117+
ctx.put("topLevelTypes", ui.getStateEngine().topLevelTypes());
112118
}
113119

114120
@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: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@
3434
#end
3535
#if($stateVersion)
3636
<td class="nav">[ State Version: $stateVersion ]</td>
37-
#end
38-
#if($type)
37+
#end
38+
#if($type && !($type.size() > 0))
3939
<td class="nav">[ Type: <span style="font-family: monospace;">$esc.html($type)</span> ]</td>
4040
<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>
4141
#end
42+
#if(!$type)
43+
<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>
44+
#end
4245
<td class="nav">[ <a href="$basePath/query">SEARCH</a> ]</td>
4346
</tr>
4447
</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;

hollow/src/main/java/com/netflix/hollow/core/read/engine/HollowReadStateEngine.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.List;
4242
import java.util.Map;
4343
import java.util.Set;
44+
import java.util.stream.Collectors;
4445

4546
/**
4647
* A HollowReadStateEngine is our main handle to the current state of a Hollow dataset as a data consumer.
@@ -200,6 +201,43 @@ public Map<String, Long> calcApproxShardSizePerType() {
200201
return typeShardSizes;
201202
}
202203

204+
/* *
205+
* Finds the top-level types in the Hollow dataset. A top-level type is one that is not a
206+
* reference from another type, i.e., it is not an element of a list, set, or map, nor is it
207+
* a field in an object schema.
208+
*
209+
* @return a set of top-level type names
210+
*/
211+
public Set<String> topLevelTypes() {
212+
List<HollowSchema> schemas = getSchemas();
213+
Set<String> topLevelTypes = new HashSet<>(this.getAllTypes());
214+
for (HollowSchema schema : schemas) {
215+
switch (schema.getSchemaType()) {
216+
case LIST:
217+
HollowListSchema listSchema = (HollowListSchema) schema;
218+
topLevelTypes.remove(listSchema.getElementType());
219+
break;
220+
case SET:
221+
HollowSetSchema setSchema = (HollowSetSchema) schema;
222+
topLevelTypes.remove(setSchema.getElementType());
223+
break;
224+
case MAP:
225+
HollowMapSchema mapSchema = (HollowMapSchema) schema;
226+
topLevelTypes.remove(mapSchema.getKeyType());
227+
topLevelTypes.remove(mapSchema.getValueType());
228+
break;
229+
case OBJECT:
230+
HollowObjectSchema objectSchema = (HollowObjectSchema) schema;
231+
for (int fieldIdx=0; fieldIdx < objectSchema.numFields(); fieldIdx++) {
232+
if (objectSchema.getFieldType(fieldIdx) == HollowObjectSchema.FieldType.REFERENCE) {
233+
topLevelTypes.remove(objectSchema.getReferencedType(fieldIdx));
234+
}
235+
}
236+
}
237+
}
238+
return topLevelTypes;
239+
}
240+
203241
@Override
204242
public HollowTypeDataAccess getTypeDataAccess(String type) {
205243
return typeStates.get(type);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.netflix.hollow.core.read.engine;
2+
3+
import com.netflix.hollow.api.producer.HollowProducer;
4+
import com.netflix.hollow.api.producer.fs.HollowInMemoryBlobStager;
5+
import com.netflix.hollow.core.util.StateEngineRoundTripper;
6+
import com.netflix.hollow.test.InMemoryBlobStore;
7+
import org.junit.Assert;
8+
import org.junit.Test;
9+
10+
import java.io.IOException;
11+
import java.util.HashSet;
12+
13+
public class HollowTopLevelTypesTest {
14+
15+
@Test
16+
public void testHollowTopLevelTypes() throws IOException {
17+
HollowProducer p = HollowProducer.withPublisher(new InMemoryBlobStore())
18+
.withBlobStager(new HollowInMemoryBlobStager())
19+
.build();
20+
21+
TypeWithPrimitive primitive = new TypeWithPrimitive();
22+
TypeWithReference reference = new TypeWithReference();
23+
TypeWithSetOfReference setOfReference = new TypeWithSetOfReference();
24+
25+
p.runCycle(cycle -> {
26+
cycle.add(reference);
27+
});
28+
HollowReadStateEngine readStateEngine = new HollowReadStateEngine();
29+
StateEngineRoundTripper.roundTripSnapshot(p.getWriteEngine(), readStateEngine);
30+
Assert.assertEquals(
31+
new HashSet<String>() {{
32+
add("TypeWithPrimitive");
33+
add("TypeWithReference");
34+
}},
35+
readStateEngine.getAllTypes());
36+
Assert.assertEquals(
37+
new HashSet<String>() {{
38+
add("TypeWithReference");
39+
}},
40+
readStateEngine.topLevelTypes());
41+
42+
// Add a new type with a set of TypeWithReference, now only
43+
// TypeWithSetOfReference should be a top-level type.
44+
p.runCycle(cycle -> {
45+
cycle.add(setOfReference);
46+
});
47+
StateEngineRoundTripper.roundTripSnapshot(p.getWriteEngine(), readStateEngine);
48+
Assert.assertEquals(
49+
new HashSet<String>() {{
50+
add("TypeWithPrimitive");
51+
add("TypeWithReference");
52+
add("TypeWithSetOfReference");
53+
add("SetOfTypeWithReference");
54+
}},
55+
readStateEngine.getAllTypes());
56+
Assert.assertEquals(
57+
new HashSet<String>() {{
58+
add("TypeWithSetOfReference");
59+
}},
60+
readStateEngine.topLevelTypes());
61+
}
62+
63+
private static class TypeWithSetOfReference {
64+
private final HashSet<TypeWithReference> set;
65+
66+
public TypeWithSetOfReference() {
67+
this.set = new HashSet<>();
68+
}
69+
}
70+
71+
private static class TypeWithPrimitive {
72+
private final int value;
73+
74+
public TypeWithPrimitive() {
75+
this.value = 0;
76+
}
77+
}
78+
79+
private static class TypeWithReference {
80+
private final TypeWithPrimitive primitive ;
81+
82+
public TypeWithReference() {
83+
this.primitive = new TypeWithPrimitive();
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)