|
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 top attached inverted pointing menu"> |
41 | | - <div class="active submission-modal item" data-tab="{admin_: submission.admin}log_ing_out"> |
42 | | - Ingestion output |
43 | | - </div> |
44 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}log_ing_err"> |
45 | | - Ingestion errors |
46 | | - </div> |
47 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}log_score_out"> |
48 | | - Scoring output |
49 | | - </div> |
50 | | - <div class="submission-modal item" data-tab="{admin_: submission.admin}log_score_err"> |
51 | | - Scoring errors |
52 | | - </div> |
53 | | - </div> |
54 | | - <div class="ui active bottom attached inverted segment tab log" |
55 | | - data-tab="{admin_: submission.admin}log_ing_out"> |
56 | | - <pre class="{empty: isEmpty(logs.prediction_ingestion_stdout)}">{ showLog(logs.prediction_ingestion_stdout) }</pre> |
57 | | - </div> |
58 | | - <div class="ui bottom attached inverted segment tab log" |
59 | | - data-tab="{admin_: submission.admin}log_ing_err"> |
60 | | - <pre class="{empty: isEmpty(logs.prediction_ingestion_stderr)}">{ showLog(logs.prediction_ingestion_stderr) }</pre> |
| 42 | + <div class="ui top attached inverted pointing menu" if="{logTabs.length > 0}"> |
| 43 | + <div each="{tab in logTabs}" |
| 44 | + class="submission-modal item {active: tab.fullTabId === activeLogTabId}" |
| 45 | + data-tab="{tab.fullTabId}"> |
| 46 | + {tab.label} |
| 47 | + </div> |
61 | 48 | </div> |
62 | | - <div class="ui bottom attached inverted segment tab log" |
63 | | - data-tab="{admin_: submission.admin}log_score_out"> |
64 | | - <pre class="{empty: isEmpty(logs.scoring_stdout)}">{ showLog(logs.scoring_stdout) }</pre> |
| 49 | + <!-- If no logs --> |
| 50 | + <div class="ui bottom attached inverted segment tab log active" if="{logTabs.length === 0}"> |
| 51 | + <pre class="empty">No logs available for this submission.</pre> |
65 | 52 | </div> |
66 | | - <div class="ui bottom attached inverted segment tab log" |
67 | | - data-tab="{admin_: submission.admin}log_score_err"> |
68 | | - <pre class="{empty: isEmpty(logs.scoring_stderr)}">{ showLog(logs.scoring_stderr) }</pre> |
| 53 | + <!-- Dynamic tabs --> |
| 54 | + <div each="{tab in logTabs}" |
| 55 | + class="ui bottom attached inverted segment tab log {active: tab.fullTabId === activeLogTabId}" |
| 56 | + data-tab="{tab.fullTabId}"> |
| 57 | + <pre class="{empty: isEmpty(tab.content)}">{ showLog(tab.content) }</pre> |
69 | 58 | </div> |
70 | 59 | </div> |
| 60 | + <!-- Fact sheet --> |
71 | 61 | <div class="ui tab modal-tab" data-tab="{admin_: submission.admin}fact_sheet"> |
72 | 62 | <div class="ui inverted segment log"> |
73 | 63 | <textarea name="fact-sheet" id="fact_sheet" ref="fact_sheet_text_area">{ JSON.stringify(fact_sheet_answers, null, 2) }</textarea> |
74 | 64 | </div> |
75 | 65 | <div class="ui button green" onclick="{update_fact_sheet.bind(this)}">Save</div> |
76 | 66 | </div> |
| 67 | + <!-- Visualization --> |
77 | 68 | <div class="ui tab modal-tab" data-tab="{admin_: submission.admin}graph" show="{opts.show_visualization && (!opts.hide_output || submission.admin)}"> |
78 | 69 | <iframe src="{detailed_result}" class="graph-frame" show="{detailed_result}"></iframe> |
79 | 70 | </div> |
| 71 | + <!-- Admin --> |
80 | 72 | <div class="ui tab leaderboard-tab" data-tab="admin" if="{submission.admin}"> |
81 | 73 | <submission-scores leaderboards="{leaderboards}"></submission-scores> |
82 | 74 | </div> |
| 75 | + |
83 | 76 | <script> |
84 | 77 | var self = this |
85 | 78 | self.submission = {} |
86 | 79 | self.logs = {} |
87 | 80 | self.leaderboards = [] |
88 | 81 | self.columns = [] |
89 | 82 |
|
90 | | - // Check if logs are empty |
91 | | - self.isEmpty = (v) => v == null || (typeof v === "string" && v.trim().length === 0) |
| 83 | + // OLD helpers - Check if logs are empty |
| 84 | + //self.isEmpty = (v) => v == null || (typeof v === "string" && v.trim().length === 0) |
| 85 | + //self.nonEmpty = (v) => !self.isEmpty(v) |
| 86 | + //self.showLog = (v) => self.nonEmpty(v) ? v : "No logs for this tab." |
| 87 | + |
| 88 | + // Logs helpers |
| 89 | + self.normalizeLog = (v) => { |
| 90 | + if (v == null) return v |
| 91 | + if (Array.isArray(v)) return v.join('\n') |
| 92 | + if (typeof v === "object") { |
| 93 | + try { return JSON.stringify(v, null, 2) } catch { return String(v) } |
| 94 | + } |
| 95 | + return String(v) |
| 96 | + } |
| 97 | + self.isEmpty = (v) => { |
| 98 | + v = self.normalizeLog(v) |
| 99 | + return v == null || (typeof v === "string" && v.trim().length === 0) |
| 100 | + } |
92 | 101 | self.nonEmpty = (v) => !self.isEmpty(v) |
93 | | - self.showLog = (v) => self.nonEmpty(v) ? v : "No logs for this tab." |
| 102 | + self.showLog = (v) => self.nonEmpty(v) ? self.normalizeLog(v) : "No logs for this tab." |
| 103 | + self.getLog = (...keys) => { |
| 104 | + for (const k of keys) { |
| 105 | + const v = self.normalizeLog(self.logs[k]) |
| 106 | + if (!self.isEmpty(v)) return v |
| 107 | + } |
| 108 | + return null |
| 109 | + } |
| 110 | + |
| 111 | + // Dynamic tabs state |
| 112 | + self.logTabs = [] |
| 113 | + self.activeLogTabId = null |
| 114 | + |
| 115 | + self.rebuildLogTabs = () => { |
| 116 | + const prefix = self.submission && self.submission.admin ? 'admin_' : '' |
| 117 | + const candidates = [ |
| 118 | + { |
| 119 | + key: 'log_ing_out', |
| 120 | + label: 'Ingestion output', |
| 121 | + content: self.getLog('prediction_ingestion_stdout', 'prediction_stdout') |
| 122 | + }, |
| 123 | + { |
| 124 | + key: 'log_ing_err', |
| 125 | + label: 'Ingestion errors', |
| 126 | + content: self.getLog('prediction_ingestion_stderr', 'prediction_stderr') |
| 127 | + }, |
| 128 | + { |
| 129 | + key: 'log_score_out', |
| 130 | + label: 'Scoring output', |
| 131 | + content: self.getLog('scoring_stdout', 'scoring_ingestion_stdout') |
| 132 | + }, |
| 133 | + { |
| 134 | + key: 'log_score_err', |
| 135 | + label: 'Scoring errors', |
| 136 | + content: self.getLog('scoring_stderr', 'scoring_ingestion_stderr') |
| 137 | + }, |
| 138 | + ] |
| 139 | + |
| 140 | + // Keep only non empty tabs |
| 141 | + self.logTabs = candidates |
| 142 | + .filter(t => !self.isEmpty(t.content)) |
| 143 | + .map(t => ({ |
| 144 | + ...t, |
| 145 | + fullTabId: `${prefix}${t.key}` |
| 146 | + })) |
| 147 | + // Select a valid tab as active |
| 148 | + const stillValid = self.logTabs.find(t => t.fullTabId === self.activeLogTabId) |
| 149 | + if (!stillValid) { |
| 150 | + self.activeLogTabId = self.logTabs.length ? self.logTabs[0].fullTabId : null |
| 151 | + } |
| 152 | + } |
94 | 153 |
|
95 | 154 | self.get_score_details = function (column) { |
96 | 155 | try { |
|
116 | 175 | $.get(item.data_file) |
117 | 176 | .done(function (content) { |
118 | 177 | self.logs[item.name] = content |
| 178 | + self.rebuildLogTabs() |
119 | 179 | self.update() |
| 180 | + // Rebind Semantic UI tabs after DOM update |
| 181 | + setTimeout(() => { |
| 182 | + // init tabs inside the LOGS menu |
| 183 | + $('.ui.top.attached.menu .item').tab() |
| 184 | + if (self.activeLogTabId) { |
| 185 | + $('.ui.top.attached.menu .item').tab('change tab', self.activeLogTabId) |
| 186 | + } |
| 187 | + }, 0) |
120 | 188 | }) |
121 | 189 | }) |
| 190 | + self.rebuildLogTabs() |
| 191 | + self.update() |
122 | 192 | if (self.submission.admin) { |
123 | 193 | _.forEach(data.leaderboards, (leaderboard) => { |
124 | 194 | _.map(leaderboard.columns, (column) => { |
|
0 commit comments