Skip to content

Commit edc4285

Browse files
fixes
1 parent 07510b7 commit edc4285

File tree

4 files changed

+218
-108
lines changed

4 files changed

+218
-108
lines changed

snakemake/report/html_reporter/template/components/abstract_results.js

Lines changed: 168 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,79 @@
1+
function arrayKey(array) {
2+
return array.join(",");
3+
}
4+
15
class AbstractResults extends React.Component {
6+
constructor(props) {
7+
super(props);
8+
this.state = { toggles: new Map(), data: this.getData() };
9+
this.toggleCallback = this.toggleCallback.bind(this);
10+
}
11+
212
render() {
13+
if (this.state.data.toggleLabels.size > 0) {
14+
this.initToggleStates(this.state.data.toggleLabels);
15+
return e(
16+
"div",
17+
{},
18+
e(
19+
"div",
20+
{ className: "flex gap-2 rounded bg-slate-800" },
21+
this.getToggleControls(this.state.data.toggleLabels),
22+
),
23+
this.getResultsTable(this.state.data)
24+
)
25+
} else {
26+
return this.getResultsTable(this.state.data);
27+
}
28+
}
29+
30+
initToggleStates(toggleLabels) {
31+
let toggles = this.state.toggles;
32+
toggles.clear();
33+
toggleLabels.forEach(function(value, key) {
34+
toggles.set(key, value[0]);
35+
})
36+
}
37+
38+
getToggleControls(toggleLabels) {
39+
let toggleCallback = this.toggleCallback;
40+
return toggleLabels.entries().map(function(entry) {
41+
let [name, values] = entry;
42+
return e(
43+
Toggle,
44+
{
45+
label: name,
46+
values: values,
47+
callback: function(selected) {
48+
toggleCallback(name, selected);
49+
}
50+
}
51+
)
52+
})
53+
}
54+
55+
toggleCallback(name, selected) {
56+
let data = this.state.data;
57+
this.setState(function(prevState) {
58+
let toggles = new Map(prevState.toggles);
59+
toggles.set(name, selected);
60+
return { data: data, toggles };
61+
});
62+
}
63+
64+
getResultsTable(data) {
365
return e(
466
"table",
567
{ className: "table-auto text-white text-sm w-full" },
668
e(
769
"thead",
870
{},
9-
this.renderHeader(),
71+
this.renderHeader(data),
1072
),
1173
e(
1274
"tbody",
1375
{},
14-
this.renderEntries()
76+
this.renderEntries(data),
1577
)
1678
)
1779
}
@@ -50,106 +112,142 @@ class AbstractResults extends React.Component {
50112
});
51113
}
52114

53-
isLabelled() {
54-
return this.getResults().every(function ([path, result]) {
55-
return result.labels;
56-
});
57-
}
58-
59-
renderHeader() {
115+
getData() {
116+
let labels;
60117
if (this.isLabelled()) {
61-
return e(
62-
"tr",
63-
{},
64-
this.getLabels().map(function (label) {
65-
return e(
66-
"th",
67-
{ className: "text-left p-1 uppercase" },
68-
label
69-
)
70-
}),
71-
e(
72-
"th",
73-
{ className: "text-right p-1 w-fit" },
74-
)
75-
)
76-
} else {
77-
return e(
78-
"tr",
79-
{},
80-
e(
81-
"th",
82-
{ className: "text-left p-1" },
83-
"Filename"
84-
),
85-
e(
86-
"th",
87-
{ className: "text-right p-1 w-fit" },
88-
)
89-
)
118+
labels = this.getLabels();
90119
}
91-
}
92120

93-
renderEntries() {
94-
AbstractResults.propTypes = {
95-
app: PropTypes.object.isRequired,
96-
};
121+
let results = this.getResults();
97122

98-
let app = this.props.app;
99-
let labels;
100-
if (this.isLabelled()) {
101-
labels = this.getLabels();
123+
let toggleLabels = new Map();
124+
if (labels !== undefined) {
125+
labels.forEach(function (label) {
126+
let values = results.map(function ([path, entry]) {
127+
return entry.labels[label];
128+
}).filter(function (value) {
129+
return value !== undefined;
130+
});
131+
132+
let uniqueValues = [...new Set(values)];
133+
if (
134+
uniqueValues.length == 2 &&
135+
uniqueValues.every(value => values.filter(v => v === value).length === results.length / 2)
136+
) {
137+
uniqueValues.sort();
138+
toggleLabels.set(label, uniqueValues);
139+
}
140+
});
102141
}
103-
let entries = this.getResults().map(function ([path, entry]) {
104-
let entryLabels;
105-
let key;
142+
143+
let entries = new Map();
144+
let entryLabelValues = [];
145+
146+
results.forEach(function ([path, entry]) {
147+
let entryLabels = [];
148+
let entryToggleLabels = [];
106149
if (labels !== undefined) {
107-
entryLabels = labels.map(function (label) {
108-
return entry.labels[label] || "";
150+
labels.forEach(function (label) {
151+
if (!toggleLabels.has(label)) {
152+
entryLabels.push(entry.labels[label] || "");
153+
} else {
154+
entryToggleLabels.push(entry.labels[label]);
155+
}
109156
});
110-
key = labels.join();
111157
} else {
112158
entryLabels = [path];
113-
key = path;
114159
}
115160

116-
return {
117-
key: key,
118-
path: path,
119-
labels: entryLabels
120-
};
161+
let key = arrayKey(entryLabels);
162+
163+
if (!entries.has(key)) {
164+
entries.set(key, new Map());
165+
entryLabelValues.push(entryLabels);
166+
}
167+
168+
entries.get(key).set(arrayKey(entryToggleLabels), path);
121169
});
122170

123-
entries = entries.sort(function (a, b) {
171+
entryLabelValues = entryLabelValues.sort(function (aLabels, bLabels) {
124172
// sort labels lexicographically, first element is the most important
125-
for (let i = 0; i < a.labels.length; i++) {
126-
let comparison = a.labels[i].localeCompare(b.labels[i]);
173+
for (let i = 0; i < aLabels.length; i++) {
174+
let comparison = aLabels[i].localeCompare(bLabels[i]);
127175
if (comparison !== 0) {
128176
return comparison;
129177
}
130178
}
131179
return 0;
132180
});
133181

134-
return entries.map(function (entry) {
182+
if (labels === undefined) {
183+
labels = ["File"];
184+
}
185+
186+
return {
187+
entryLabels: labels.filter((label) => toggleLabels.has(label)),
188+
entryLabelValues: entryLabelValues,
189+
toggleLabels: toggleLabels,
190+
entries: entries,
191+
}
192+
}
193+
194+
isLabelled() {
195+
return this.getResults().every(function ([path, result]) {
196+
return result.labels;
197+
});
198+
}
199+
200+
renderHeader(data) {
201+
return e(
202+
"tr",
203+
{},
204+
data.entryLabels.map(function (label) {
205+
return e(
206+
"th",
207+
{ className: "text-left p-1 uppercase" },
208+
label
209+
)
210+
}),
211+
e(
212+
"th",
213+
{ className: "text-right p-1 w-fit" },
214+
)
215+
)
216+
}
217+
218+
renderEntries(data) {
219+
AbstractResults.propTypes = {
220+
app: PropTypes.object.isRequired,
221+
};
222+
223+
let app = this.props.app;
224+
let state = this.state;
225+
226+
return data.entryLabelValues.map(function (entryLabels) {
227+
let toggleLabels = Array.from(data.toggleLabels.keys().map((label) => state.toggles.get(label)));
228+
let entryPath = data.entries.get(arrayKey(entryLabels)).get(arrayKey(toggleLabels));
229+
console.log({
230+
toggleLabels,
231+
entryPath,
232+
entryLabels,
233+
});
234+
235+
135236
let actions = e(
136237
"td",
137238
{ className: "p-1 text-right" },
138239
e(
139240
"div",
140241
{ className: "inline-flex gap-1", role: "group" },
141-
e(
142-
Toggle,
143-
),
144242
e(
145243
ResultViewButton,
146-
{ resultPath: entry.path, app: app }
244+
{ resultPath: entryPath, app: app }
147245
),
148246
e(
149247
Button,
150248
{
151249
href: "#",
152-
onClick: () => app.showResultInfo(path),
250+
onClick: () => app.showResultInfo(entryPath),
153251
iconName: "information-circle"
154252
}
155253
)
@@ -159,8 +257,8 @@ class AbstractResults extends React.Component {
159257
return [
160258
e(
161259
"tr",
162-
{ key: entry.key },
163-
entry.labels.map(function (labelValue) {
260+
{ key: entryLabels.join(",") },
261+
entryLabels.map(function (labelValue) {
164262
return e(
165263
"td",
166264
{ className: "p-1" },

snakemake/report/html_reporter/template/components/result_view_button.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
class ResultViewButton extends React.Component {
44
render() {
55
let result = results[this.props.resultPath];
6-
76
return this.getViewButton(this.props.resultPath, result);
87
}
98

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
class Toggle extends React.Component {
4+
constructor(props) {
5+
super(props);
6+
this.state = { value: this.props.values[0], checked: false };
7+
this.handleToggle = this.handleToggle.bind(this);
8+
}
9+
10+
handleToggle() {
11+
// swap the value
12+
let value = this.props.values.find((value) => value !== this.state.value);
13+
this.setState({ value: value, checked: !this.state.checked });
14+
this.props.callback(value);
15+
}
16+
17+
render() {
18+
return e(
19+
"label",
20+
{ for: this.props.label, className: "p-1 relative inline-flex cursor-pointer items-center"},
21+
e(
22+
"span",
23+
{},
24+
this.props.label
25+
),
26+
e(
27+
"div",
28+
{className: "relative inline-flex cursor-pointer items-center"},
29+
e(
30+
"input",
31+
{ id: this.props.label, type: "checkbox", className: "peer sr-only", checked: this.state.checked, onChange: this.handleToggle }
32+
),
33+
e(
34+
"div",
35+
{ className: "peer flex h-8 items-center gap-4 rounded-full bg-emerald-500 px-3 after:absolute after:left-1 after: after:h-6 after:w-8 after:rounded-full after:bg-white/40 after:transition-all after:content-[''] peer-checked:bg-stone-600 peer-checked:after:translate-x-full peer-focus:outline-none text-sm text-white" },
36+
e(
37+
"span",
38+
{},
39+
this.props.values[0]
40+
),
41+
e(
42+
"span",
43+
{},
44+
this.props.values[1]
45+
)
46+
)
47+
)
48+
)
49+
}
50+
}

0 commit comments

Comments
 (0)