Skip to content

Commit fb1e6fe

Browse files
authored
feat(util): provide helper functions to deal with gridPos and rows (#155)
* feat(util): provide helper functions to deal with gridPos and rows * fix: correct infunc ref * feat(row): add sane defaults to row constructor and gridPos * test: add unit tests for various util functions * fix: small bugs that surfaced with unit tests
1 parent 62ec3a4 commit fb1e6fe

File tree

26 files changed

+2227
-405
lines changed

26 files changed

+2227
-405
lines changed

custom/row.libsonnet

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,20 @@ local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
77
),
88
new(title):
99
self.withTitle(title)
10-
+ self.withType(),
10+
+ self.withType()
11+
+ self.withCollapsed(false)
12+
+ self.gridPos.withX(0)
13+
+ self.gridPos.withH(1)
14+
+ self.gridPos.withW(24),
15+
16+
'#gridPos':: {}, // use withGridPos instead
17+
'#withGridPos':: d.func.new(
18+
'`withGridPos` sets the Y-axis on a row panel. x, width and height are fixed values.',
19+
args=[d.arg('y', d.T.number)]
20+
),
21+
withGridPos(y):
22+
self.gridPos.withX(0)
23+
+ self.gridPos.withY(y)
24+
+ self.gridPos.withH(1)
25+
+ self.gridPos.withW(24),
1126
}

custom/util/grid.libsonnet

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
2+
local xtd = import 'github.com/jsonnet-libs/xtd/main.libsonnet';
23

34
{
45
local root = self,
@@ -52,11 +53,12 @@ local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
5253
),
5354
makeGrid(panels, panelWidth=8, panelHeight=8, startY=0):
5455
// Get indexes for all Row panels
55-
local rowIndexes = [
56-
i
57-
for i in std.range(0, std.length(panels) - 1)
58-
if panels[i].type == 'row'
59-
];
56+
local rowIndexes =
57+
xtd.array.filterMapWithIndex(
58+
function(i, p) p.type == 'row',
59+
function(i, p) i,
60+
panels,
61+
);
6062

6163
// Group panels below each Row panel
6264
local rowGroups =

custom/util/panel.libsonnet

Lines changed: 233 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
2+
local xtd = import 'github.com/jsonnet-libs/xtd/main.libsonnet';
23

34
{
45
local this = self,
@@ -29,7 +30,8 @@ local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
2930
else 0),
3031

3132
panels+: [
32-
panel + { id: acc.index }
33+
panel
34+
+ { id: acc.index }
3335
+ (
3436
if panel.type == 'row'
3537
&& 'panels' in panel
@@ -48,4 +50,234 @@ local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
4850
{ index: start, panels: [] }
4951
).panels;
5052
infunc(panels),
53+
54+
'#sanitizePanel':: d.func.new(
55+
|||
56+
`sanitizePanel` ensures the panel has a valid `gridPos` and row panels have `collapsed` and `panels`. This function is recursively applied to panels inside row panels.
57+
58+
The default values for x,y,h,w are only applied if not already set.
59+
|||,
60+
[
61+
d.arg('panel', d.T.object),
62+
d.arg('defaultX', d.T.number, default=0),
63+
d.arg('defaultY', d.T.number, default=0),
64+
d.arg('defaultHeight', d.T.number, default=8),
65+
d.arg('defaultWidth', d.T.number, default=8),
66+
]
67+
),
68+
sanitizePanel(panel, defaultX=0, defaultY=0, defaultHeight=8, defaultWidth=8):
69+
local infunc(panel) =
70+
panel
71+
+ (
72+
local gridPos = std.get(panel, 'gridPos', {});
73+
if panel.type == 'row'
74+
then {
75+
collapsed: std.get(panel, 'collapsed', false),
76+
panels: std.map(infunc, std.get(panel, 'panels', [])),
77+
gridPos: { // x, h, w are fixed
78+
x: 0,
79+
y: std.get(gridPos, 'y', defaultY),
80+
h: 1,
81+
w: 24,
82+
},
83+
}
84+
else {
85+
gridPos: {
86+
x: std.get(gridPos, 'x', defaultX),
87+
y: std.get(gridPos, 'y', defaultY),
88+
h: std.get(gridPos, 'h', defaultHeight),
89+
w: std.get(gridPos, 'w', defaultWidth),
90+
},
91+
}
92+
);
93+
infunc(panel),
94+
95+
'#sortPanelsByXY':: d.func.new(
96+
|||
97+
`sortPanelsByXY` applies a simple sorting algorithm, first by x then again by y. This does not take width and height into account.
98+
|||,
99+
[
100+
d.arg('panels', d.T.array),
101+
]
102+
),
103+
sortPanelsByXY(panels):
104+
std.sort(
105+
std.sort(
106+
panels,
107+
function(panel)
108+
panel.gridPos.x
109+
),
110+
function(panel)
111+
panel.gridPos.y
112+
),
113+
114+
'#sortPanelsInRow':: d.func.new(
115+
|||
116+
`sortPanelsInRow` applies `sortPanelsByXY` on the panels in a rowPanel.
117+
|||,
118+
[
119+
d.arg('rowPanel', d.T.object),
120+
]
121+
),
122+
sortPanelsInRow(rowPanel):
123+
rowPanel + { panels: this.sortPanelsByXY(rowPanel.panels) },
124+
125+
'#groupPanelsInRows':: d.func.new(
126+
|||
127+
`groupPanelsInRows` ensures that panels that come after a row panel in an array are added to the `row.panels` attribute. This can be useful to apply intermediate functions to only the panels that belong to a row. Finally the panel array should get processed by `resolveCollapsedFlagOnRows` to "unfold" the rows that are not collapsed into the main array.
128+
|||,
129+
[
130+
d.arg('panels', d.T.array),
131+
]
132+
),
133+
groupPanelsInRows(panels):
134+
// Add panels that come after a row to row.panels
135+
local grouped =
136+
xtd.array.filterMapWithIndex(
137+
function(i, p) p.type == 'row',
138+
function(i, p)
139+
p + {
140+
panels+:
141+
this.getPanelsBeforeNextRow(panels[i + 1:]),
142+
},
143+
panels,
144+
);
145+
146+
// Get panels that come before the rowGroups
147+
local panelsBeforeRowGroups = this.getPanelsBeforeNextRow(panels);
148+
149+
panelsBeforeRowGroups + grouped,
150+
151+
'#getPanelsBeforeNextRow':: d.func.new(
152+
|||
153+
`getPanelsBeforeNextRow` returns all panels in an array up until a row has been found. Used in `groupPanelsInRows`.
154+
|||,
155+
[
156+
d.arg('panels', d.T.array),
157+
]
158+
),
159+
getPanelsBeforeNextRow(panels):
160+
local rowIndexes =
161+
xtd.array.filterMapWithIndex(
162+
function(i, p) p.type == 'row',
163+
function(i, p) i,
164+
panels,
165+
);
166+
if std.length(rowIndexes) != 0
167+
then panels[0:rowIndexes[0]]
168+
else panels[0:], // if no row panels found, return all remaining panels
169+
170+
'#resolveCollapsedFlagOnRows':: d.func.new(
171+
|||
172+
`resolveCollapsedFlagOnRows` should be applied to the final panel array to "unfold" the rows that are not collapsed into the main array.
173+
|||,
174+
[
175+
d.arg('panels', d.T.array),
176+
]
177+
),
178+
resolveCollapsedFlagOnRows(panels):
179+
std.foldl(
180+
function(acc, panel)
181+
acc + (
182+
if panel.type == 'row'
183+
&& !panel.collapsed
184+
then // If not collapsed, then move panels to main array below the row panel
185+
[panel + { panels: [] }]
186+
+ panel.panels
187+
else [panel]
188+
),
189+
panels,
190+
[],
191+
),
192+
193+
'#normalizeY':: d.func.new(
194+
|||
195+
`normalizeY` applies negative gravity on the inverted Y axis. This mimics the behavior of Grafana: when a panel is created without panel above it, then it'll float upward.
196+
197+
This is strictly not required as Grafana will do this on dashboard load, however it might be helpful when used when calculating the correct `gridPos`.
198+
|||,
199+
[
200+
d.arg('panels', d.T.array),
201+
]
202+
),
203+
normalizeY(panels):
204+
std.foldl(
205+
function(acc, i)
206+
acc + [
207+
panels[i] + {
208+
gridPos+: {
209+
y: this.calculateLowestYforPanel(panels[i], acc),
210+
},
211+
},
212+
],
213+
std.range(0, std.length(panels) - 1),
214+
[]
215+
),
216+
217+
'#calculateLowestYforPanel':: d.func.new(
218+
|||
219+
`calculateLowestYforPanel` calculates Y for a given `panel` from the `gridPos` of an array of `panels`. This function is used in `normalizeY`.
220+
|||,
221+
[
222+
d.arg('panel', d.T.object),
223+
d.arg('panels', d.T.array),
224+
]
225+
),
226+
calculateLowestYforPanel(panel, panels):
227+
local x1 = panel.gridPos.x;
228+
local x2 = panel.gridPos.x + panel.gridPos.w;
229+
xtd.number.maxInArray( // the new position is highest value (max) on the Y-scale
230+
std.filterMap(
231+
function(p) // find panels that overlap on X-scale
232+
p.gridPos.x == x1
233+
|| xtd.number.inRange(p.gridPos.x, x1, x2)
234+
|| xtd.number.inRange((p.gridPos.x + p.gridPos.w), x1, x2),
235+
function(p) // return new position on Y-scale
236+
p.gridPos.y + p.gridPos.h,
237+
panels,
238+
),
239+
),
240+
241+
'#normalizeYInRow':: d.func.new(
242+
|||
243+
`normalizeYInRow` applies `normalizeY` to the panels in a row panel.
244+
|||,
245+
[
246+
d.arg('rowPanel', d.T.object),
247+
]
248+
),
249+
normalizeYInRow(rowPanel):
250+
rowPanel + {
251+
panels:
252+
std.map(
253+
function(p)
254+
p + {
255+
gridPos+: {
256+
y: // Increase panel Y with the row Y to put them below the row when not collapsed.
257+
p.gridPos.y
258+
+ rowPanel.gridPos.y
259+
+ rowPanel.gridPos.h,
260+
},
261+
},
262+
this.normalizeY(rowPanel.panels)
263+
),
264+
},
265+
266+
'#mapToRows':: d.func.new(
267+
|||
268+
`mapToRows` is a little helper function that applies `func` to all row panels in an array. Other panels in that array are returned ad verbatim.
269+
|||,
270+
[
271+
d.arg('func', d.T.func),
272+
d.arg('panels', d.T.array),
273+
]
274+
),
275+
mapToRows(func, panels):
276+
std.map(
277+
function(p)
278+
if p.type == 'row'
279+
then func(p)
280+
else p,
281+
panels
282+
),
51283
}

0 commit comments

Comments
 (0)