|
6 | 6 | <div class="submission-modal item" data-tab="admin" if="{submission.admin}">ADMIN</div> |
7 | 7 | <div class="submission-modal item" data-tab="{admin_: submission.admin}fact_sheet">FACT SHEET ANSWERS</div> |
8 | 8 | </div> |
| 9 | + <!-- Downloads --> |
9 | 10 | <div class="ui tab active modal-tab" data-tab="{admin_: submission.admin}downloads"> |
10 | 11 | <div class="ui relaxed centered grid"> |
11 | 12 | <div class="ui fifteen wide column"> |
|
36 | 37 | </div> |
37 | 38 | </div> |
38 | 39 | </div> |
| 40 | + <!-- Logs --> |
39 | 41 | <div class="ui tab modal-tab" data-tab="{admin_: submission.admin}logs" hide="{opts.hide_output}"> |
40 | | - <div class="ui grid"> |
41 | | - <div class="three wide column"> |
42 | | - <div class="ui fluid vertical secondary menu"> |
43 | | - <div class="active submission-modal item" data-tab="{admin_: submission.admin}prediction"> |
44 | | - Prediction Logs |
45 | | - </div> |
46 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}scoring"> |
47 | | - Scoring Logs |
48 | | - </div> |
49 | | - </div> |
50 | | - </div> |
51 | | - <div class="thirteen wide column"> |
52 | | - <div class="ui active tab" data-tab="{admin_: submission.admin}prediction"> |
53 | | - <div class="ui top attached inverted pointing menu"> |
54 | | - <div class="active submission-modal item" data-tab="{admin_: submission.admin}p_stdout"> |
55 | | - stdout |
56 | | - </div> |
57 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}p_stderr"> |
58 | | - stderr |
59 | | - </div> |
60 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}p_ingest_stdout"> |
61 | | - Ingestion stdout |
62 | | - </div> |
63 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}p_ingest_stderr"> |
64 | | - Ingestion stderr |
65 | | - </div> |
66 | | - </div> |
67 | | - |
68 | | - <div class="ui active bottom attached inverted segment tab log" |
69 | | - data-tab="{admin_: submission.admin}p_stdout"> |
70 | | - <!-- |
71 | | - todo: something like: |
72 | | - <pre>{ logs.prediction_stdout ? logs.prediction_stdout : "Empty Logs"}</pre> |
73 | | - so log files don't look empty |
74 | | - --> |
75 | | - <pre>{ logs.prediction_stdout }</pre> |
76 | | - </div> |
77 | | - |
78 | | - <div class="ui bottom attached inverted segment tab log" |
79 | | - data-tab="{admin_: submission.admin}p_stderr"> |
80 | | - <pre>{ logs.prediction_stderr }</pre> |
81 | | - </div> |
82 | | - |
83 | | - <div class="ui bottom attached inverted segment tab log" |
84 | | - data-tab="{admin_: submission.admin}p_ingest_stdout"> |
85 | | - <pre>{ logs.prediction_ingestion_stdout }</pre> |
86 | | - </div> |
87 | | - |
88 | | - <div class="ui bottom attached inverted segment tab log" |
89 | | - data-tab="{admin_: submission.admin}p_ingest_stderr"> |
90 | | - <pre>{ logs.prediction_ingestion_stderr }</pre> |
91 | | - </div> |
92 | | - </div> |
93 | | - <div class="ui tab" data-tab="{admin_: submission.admin}scoring"> |
94 | | - <div class="ui top attached inverted pointing menu"> |
95 | | - <div class="active submission-modal item" data-tab="{admin_: submission.admin}s_stdout"> |
96 | | - stdout |
97 | | - </div> |
98 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}s_stderr"> |
99 | | - stderr |
100 | | - </div> |
101 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}s_ingest_stdout"> |
102 | | - Ingestion stdout |
103 | | - </div> |
104 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}s_ingest_stderr"> |
105 | | - Ingestion stderr |
106 | | - </div> |
107 | | - </div> |
108 | | - |
109 | | - <div class="ui active bottom attached inverted segment tab log" |
110 | | - data-tab="{admin_: submission.admin}s_stdout"> |
111 | | - <pre>{ logs.scoring_stdout }</pre> |
112 | | - </div> |
113 | | - |
114 | | - <div class="ui bottom attached inverted segment tab log" |
115 | | - data-tab="{admin_: submission.admin}s_stderr"> |
116 | | - <pre>{ logs.scoring_stderr }</pre> |
117 | | - </div> |
118 | | - |
119 | | - <div class="ui bottom attached inverted segment tab log" |
120 | | - data-tab="{admin_: submission.admin}s_ingest_stdout"> |
121 | | - <pre>{ logs.scoring_ingestion_stdout }</pre> |
122 | | - </div> |
123 | | - |
124 | | - <div class="ui bottom attached inverted segment tab log" |
125 | | - data-tab="{admin_: submission.admin}s_ingest_stderr"> |
126 | | - <pre>{ logs.scoring_ingestion_stderr }</pre> |
127 | | - </div> |
128 | | - </div> |
| 42 | + <div class="ui top attached inverted pointing menu" if="{logTabs.length > 0}" ref="log_tabs_menu"> |
| 43 | + <div each="{tab, i in logTabs}" |
| 44 | + class="submission-modal item {active: tab.fullTabId === activeLogTabId || (!activeLogTabId && i === 0)}" |
| 45 | + data-tab="{tab.fullTabId}" |
| 46 | + onclick="{() => activeLogTabId = tab.fullTabId}"> |
| 47 | + {tab.label} |
129 | 48 | </div> |
130 | 49 | </div> |
| 50 | + <!-- If no logs --> |
| 51 | + <div class="ui bottom attached inverted segment tab log active" if="{logTabs.length === 0}"> |
| 52 | + <pre class="empty">No logs available for this submission.</pre> |
| 53 | + </div> |
| 54 | + <!-- Dynamic tabs --> |
| 55 | + <div each="{tab, i in logTabs}" |
| 56 | + class="ui bottom attached inverted segment tab log {active: tab.fullTabId === activeLogTabId || (!activeLogTabId && i === 0)}" |
| 57 | + data-tab="{tab.fullTabId}"> |
| 58 | + <pre class="{empty: is_empty(tab.content)}">{ show_log(tab.content) }</pre> |
| 59 | + </div> |
131 | 60 | </div> |
| 61 | + <!-- Fact sheet --> |
132 | 62 | <div class="ui tab modal-tab" data-tab="{admin_: submission.admin}fact_sheet"> |
133 | 63 | <div class="ui inverted segment log"> |
134 | 64 | <textarea name="fact-sheet" id="fact_sheet" ref="fact_sheet_text_area">{ JSON.stringify(fact_sheet_answers, null, 2) }</textarea> |
135 | 65 | </div> |
136 | 66 | <div class="ui button green" onclick="{update_fact_sheet.bind(this)}">Save</div> |
137 | 67 | </div> |
| 68 | + <!-- Visualization --> |
138 | 69 | <div class="ui tab modal-tab" data-tab="{admin_: submission.admin}graph" show="{opts.show_visualization && (!opts.hide_output || submission.admin)}"> |
139 | 70 | <iframe src="{detailed_result}" class="graph-frame" show="{detailed_result}"></iframe> |
140 | 71 | </div> |
| 72 | + <!-- Admin --> |
141 | 73 | <div class="ui tab leaderboard-tab" data-tab="admin" if="{submission.admin}"> |
142 | 74 | <submission-scores leaderboards="{leaderboards}"></submission-scores> |
143 | 75 | </div> |
| 76 | + |
144 | 77 | <script> |
145 | 78 | var self = this |
146 | 79 | self.submission = {} |
147 | 80 | self.logs = {} |
148 | 81 | self.leaderboards = [] |
149 | 82 | self.columns = [] |
150 | 83 |
|
| 84 | + // Logs helpers |
| 85 | + self.non_empty = (v) => !self.is_empty(v) |
| 86 | + self.show_log = (v) => self.non_empty(v) ? self.normalizeLog(v) : "No logs for this tab." |
| 87 | + self.normalizeLog = (v) => { |
| 88 | + if (v == null) return v |
| 89 | + if (Array.isArray(v)) return v.join('\n') |
| 90 | + if (typeof v === "object") { |
| 91 | + try { return JSON.stringify(v, null, 2) } catch { return String(v) } |
| 92 | + } |
| 93 | + return String(v) |
| 94 | + } |
| 95 | + self.is_empty = (v) => { |
| 96 | + v = self.normalizeLog(v) |
| 97 | + return v == null || (typeof v === "string" && v.trim().length === 0) |
| 98 | + } |
| 99 | + |
| 100 | + // Dynamic tabs state |
| 101 | + self.logTabs = [] |
| 102 | + |
| 103 | + self.rebuild_log_tabs = () => { |
| 104 | + const prefix = self.submission && self.submission.admin ? 'admin_' : '' |
| 105 | + const candidates = [ |
| 106 | + { key:'p_stdout', label:'Prediction output', content: self.logs.prediction_stdout }, |
| 107 | + { key:'p_stderr', label:'Prediction errors', content: self.logs.prediction_stderr }, |
| 108 | + { key:'p_ing_out', label:'Ingestion output', content: self.logs.prediction_ingestion_stdout }, |
| 109 | + { key:'p_ing_err', label:'Ingestion errors', content: self.logs.prediction_ingestion_stderr }, |
| 110 | + { key:'s_stdout', label:'Scoring output', content: self.logs.scoring_stdout }, |
| 111 | + { key:'s_stderr', label:'Scoring errors', content: self.logs.scoring_stderr }, |
| 112 | + { key:'s_ing_out', label:'Scoring ingestion output', content: self.logs.scoring_ingestion_stdout }, |
| 113 | + { key:'s_ing_err', label:'Scoring ingestion errors', content: self.logs.scoring_ingestion_stderr }, |
| 114 | + ] |
| 115 | + |
| 116 | + // Keep only non empty tabs |
| 117 | + self.logTabs = candidates |
| 118 | + .filter(t => !self.is_empty(t.content)) |
| 119 | + .map(t => ({ |
| 120 | + ...t, |
| 121 | + fullTabId: `${prefix}sub_${self.submission.id}_${t.key}` |
| 122 | + })) |
| 123 | + |
| 124 | + if (!self.activeLogTabId || !self.logTabs.find(t => t.fullTabId === self.activeLogTabId)) { |
| 125 | + self.activeLogTabId = self.logTabs.length ? self.logTabs[0].fullTabId : null |
| 126 | + } |
| 127 | + } |
| 128 | + |
151 | 129 | self.get_score_details = function (column) { |
152 | 130 | try { |
153 | 131 | let score = _.filter(self.submission.scores, (score) => { |
|
159 | 137 | } |
160 | 138 | } |
161 | 139 | self.update_submission_details = () => { |
| 140 | + self.logs = {} |
| 141 | + self.rebuild_log_tabs() |
| 142 | + self.update() |
162 | 143 | CODALAB.api.get_submission_details(self.submission.id) |
163 | 144 | .done(function (data) { |
164 | 145 | self.leaderboards = data.leaderboards |
|
168 | 149 | self.detailed_result = data.detailed_result |
169 | 150 | self.fact_sheet_answers = data.fact_sheet_answers |
170 | 151 |
|
171 | | - _.forEach(data.logs, (item) => { |
172 | | - $.get(item.data_file) |
173 | | - .done(function (content) { |
174 | | - self.logs[item.name] = content |
175 | | - self.update() |
176 | | - }) |
| 152 | + const requests = data.logs.map(item => |
| 153 | + $.get(item.data_file) |
| 154 | + .done(content => { self.logs[item.name] = content }) |
| 155 | + .fail(() => { self.logs[item.name] = "" }) |
| 156 | + ) |
| 157 | + // When all log files are done loading: |
| 158 | + $.when.apply($, requests).always(() => { |
| 159 | + self.rebuild_log_tabs() |
| 160 | + self.update() |
| 161 | + setTimeout(() => { |
| 162 | + if (!self.refs.log_tabs_menu) return |
| 163 | + const $items = $(self.refs.log_tabs_menu).find('.item') |
| 164 | + $items.tab() |
| 165 | + if (self.logTabs.length) { |
| 166 | + $items.tab('change tab', self.activeLogTabId || self.logTabs[0].fullTabId) |
| 167 | + } |
| 168 | + }, 0) |
177 | 169 | }) |
| 170 | + self.rebuild_log_tabs() |
| 171 | + self.update() |
178 | 172 | if (self.submission.admin) { |
179 | 173 | _.forEach(data.leaderboards, (leaderboard) => { |
180 | 174 | _.map(leaderboard.columns, (column) => { |
|
213 | 207 |
|
214 | 208 | CODALAB.events.on('submission_clicked', () => { |
215 | 209 | self.submission = opts.submission |
| 210 | + // reset per-submission state |
| 211 | + self.logs = {} |
| 212 | + self.logTabs = [] |
| 213 | + self.activeLogTabId = null |
| 214 | + // update |
216 | 215 | self.update() |
217 | 216 | self.update_submission_details() |
218 | 217 | let path = self.submission.admin ? 'admin_downloads' : 'downloads' |
219 | | - $('.menu .submission-modal.item').tab('change tab', path) |
| 218 | + $('.ui.large.green.pointing.menu .submission-modal.item').tab('change tab', path) |
220 | 219 | }) |
221 | 220 | </script> |
222 | 221 |
|
|
235 | 234 |
|
236 | 235 | .file-download |
237 | 236 | margin-top 25px !important |
238 | | - margin-botton 25px !important |
| 237 | + margin-bottom 25px !important |
239 | 238 |
|
240 | 239 | .graph-frame |
241 | 240 | height 100% |
|
246 | 245 | #downloads thead tr th, #downloads tbody tr td |
247 | 246 | font-size 16px !important |
248 | 247 |
|
249 | | - .inverted, textarea |
250 | | - color: white |
251 | | - background: #1b1c1d |
252 | | - width: 100% |
253 | | - height: 98% |
| 248 | + pre.empty |
| 249 | + opacity 0.7 |
| 250 | + |
| 251 | + .log |
| 252 | + color white |
| 253 | + background #1b1c1d |
| 254 | + |
| 255 | + .log textarea |
| 256 | + width 100% |
| 257 | + height 98% |
| 258 | + |
254 | 259 | </style> |
255 | 260 | </submission-modal> |
0 commit comments