@ai-qa/workflow 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,188 +1,261 @@
1
- <% layout = 'layouts/main' %>
2
- <% title = project ? 'Analytics - ' + project.name : 'Analytics Dashboard' %>
1
+ <% layout='layouts/main' %>
2
+ <% title=project ? 'Analytics - ' + project.name : 'Analytics Dashboard' %>
3
3
 
4
4
  <div class="page-header">
5
5
  <h1>Analytics Dashboard</h1>
6
6
  <div class="header-actions">
7
- <% if (typeof projects !== 'undefined' && projects.length > 1) { %>
8
- <select id="project-select" class="form-input" style="width:auto;" onchange="window.location='/analytics?project='+this.value">
9
- <% projects.forEach(p => { %>
10
- <option value="<%= p.id %>" <%= project && project.id === p.id ? 'selected' : '' %>><%= p.name %></option>
11
- <% }) %>
12
- </select>
7
+ <% if (typeof projects !=='undefined' && projects.length> 1) { %>
8
+ <select id="project-select" class="form-input" style="width:auto;"
9
+ onchange="window.location='/analytics?project='+this.value">
10
+ <% projects.forEach(p=> { %>
11
+ <option value="<%= p.id %>" <%=project && project.id===p.id ? 'selected' : '' %>><%= p.name %></option>
12
+ <% }) %>
13
+ </select>
13
14
  <% } %>
14
15
  <% if (project) { %>
15
- <a href="/projects/<%= project.id %>" class="btn">Back to Project</a>
16
+ <a href="/projects/<%= project.id %>" class="btn">Back to Project</a>
16
17
  <% } %>
17
18
  </div>
18
19
  </div>
19
20
 
20
21
  <% if (!project) { %>
21
- <div class="empty-state">
22
- <h3>No project selected</h3>
23
- <p>Add a project first or select one from the dropdown</p>
24
- </div>
25
- <% } else if (stats.totalRuns === 0) { %>
26
- <div class="empty-state">
27
- <h3>No runs for <%= project.name %></h3>
28
- <p>Execute the pipeline to see analytics</p>
29
- <a href="/projects/<%= project.id %>" class="btn btn-primary">Go to Project</a>
30
- </div>
22
+ <div class="empty-state">
23
+ <h3>No project selected</h3>
24
+ <p>Add a project first or select one from the dropdown menu to see analytics.</p>
25
+ </div>
26
+ <% } else if (stats.totalRuns===0) { %>
27
+ <div class="empty-state">
28
+ <h3>No runs for <%= project.name %></h3>
29
+ <p>Execute the pipeline from the Stories page to view analytics data.</p>
30
+ <a href="/projects/<%= project.id %>" class="btn btn-primary">Go to Project</a>
31
+ </div>
31
32
  <% } else { %>
32
33
 
33
- <div class="row">
34
- <div class="col">
35
- <div class="card stat-card">
36
- <div class="stat-number"><%= stats.totalRuns %></div>
37
- <div class="stat-label">Total Runs</div>
34
+ <!-- Real Stats Cards aligned with Clay palette -->
35
+ <div class="row" style="margin-bottom:2rem;">
36
+ <div class="col">
37
+ <div class="card stat-card">
38
+ <div class="stat-number"><%= stats.totalRuns %></div>
39
+ <div class="stat-label">Total Runs</div>
40
+ </div>
38
41
  </div>
39
- </div>
40
- <div class="col">
41
- <div class="card stat-card">
42
- <div class="stat-number <%= stats.passRate > 80 ? 'text-success' : stats.passRate > 50 ? 'text-warning' : 'text-danger' %>"><%= stats.passRate %>%</div>
43
- <div class="stat-label">Pass Rate</div>
42
+ <div class="col">
43
+ <div class="card stat-card card-lavender">
44
+ <div class="stat-number"><%= stats.passRate %>%</div>
45
+ <div class="stat-label">Pass Rate</div>
46
+ </div>
44
47
  </div>
45
- </div>
46
- <div class="col">
47
- <div class="card stat-card">
48
- <div class="stat-number"><%= stats.totalHealed %></div>
49
- <div class="stat-label">Tests Healed</div>
48
+ <div class="col">
49
+ <div class="card stat-card card-peach">
50
+ <div class="stat-number"><%= stats.totalHealed %></div>
51
+ <div class="stat-label">Tests Healed</div>
52
+ </div>
50
53
  </div>
51
- </div>
52
- <div class="col">
53
- <div class="card stat-card">
54
- <div class="stat-number"><%= stats.healRate %>%</div>
55
- <div class="stat-label">Heal Rate</div>
54
+ <div class="col">
55
+ <div class="card stat-card card-ochre">
56
+ <div class="stat-number"><%= stats.healRate %>%</div>
57
+ <div class="stat-label">Heal Rate</div>
58
+ </div>
56
59
  </div>
57
- </div>
58
- <div class="col">
59
- <div class="card stat-card">
60
- <div class="stat-number"><%= stats.totalDefects %></div>
61
- <div class="stat-label">Defects Found</div>
60
+ <div class="col">
61
+ <div class="card stat-card" style="background-color:var(--danger-bg-soft);border-color:var(--danger);">
62
+ <div class="stat-number" style="color:var(--danger);"><%= stats.totalDefects %></div>
63
+ <div class="stat-label" style="color:var(--danger);">Defects Found</div>
64
+ </div>
62
65
  </div>
63
- </div>
64
- <div class="col">
65
- <div class="card stat-card">
66
- <div class="stat-number"><%= stats.avgDuration > 0 ? (stats.avgDuration / 1000).toFixed(1) + 's' : 'N/A' %></div>
67
- <div class="stat-label">Avg Duration</div>
66
+ <div class="col">
67
+ <div class="card stat-card">
68
+ <div class="stat-number"><%= stats.avgDuration > 0 ? (stats.avgDuration / 1000).toFixed(1) + 's' : '—' %></div>
69
+ <div class="stat-label">Avg Duration</div>
70
+ </div>
68
71
  </div>
69
72
  </div>
70
- </div>
71
73
 
72
- <div class="row">
73
- <div class="col-60">
74
- <div class="card">
75
- <h3>Pass Rate Trend</h3>
76
- <canvas id="passRateChart" height="200"></canvas>
74
+ <!-- Charts layout -->
75
+ <div class="row" style="margin-bottom:2rem;">
76
+ <div class="col-60">
77
+ <div class="card">
78
+ <h3>📈 Pass Rate Trend</h3>
79
+ <canvas id="passRateChart" height="200"></canvas>
80
+ </div>
77
81
  </div>
78
- </div>
79
- <div class="col-40">
80
- <div class="card">
81
- <h3>Failure Breakdown</h3>
82
- <canvas id="failureChart" height="200"></canvas>
82
+ <div class="col-40">
83
+ <div class="card">
84
+ <h3>🍕 Failure Breakdown</h3>
85
+ <canvas id="failureChart" height="200"></canvas>
86
+ </div>
83
87
  </div>
84
88
  </div>
85
- </div>
86
89
 
87
- <div class="row">
88
- <div class="col-50">
89
- <div class="card">
90
- <h3>Duration Trend</h3>
91
- <canvas id="durationChart" height="150"></canvas>
90
+ <div class="row" style="margin-bottom:2rem;">
91
+ <div class="col-50">
92
+ <div class="card">
93
+ <h3>⏳ Duration Trend</h3>
94
+ <canvas id="durationChart" height="150"></canvas>
95
+ </div>
92
96
  </div>
93
- </div>
94
- <div class="col-50">
95
- <div class="card">
96
- <h3>Healing vs Defects</h3>
97
- <canvas id="healingChart" height="150"></canvas>
97
+ <div class="col-50">
98
+ <div class="card">
99
+ <h3>🛠️ Healing vs Defects</h3>
100
+ <canvas id="healingChart" height="150"></canvas>
101
+ </div>
98
102
  </div>
99
103
  </div>
100
- </div>
101
104
 
102
- <div class="card">
103
- <h3>Run History</h3>
104
- <table class="info-table">
105
- <tr>
106
- <th>Run</th><th>Test</th><th>Status</th><th>Duration</th><th>Failures</th><th>Healed</th><th>Defects</th><th>Export</th>
107
- </tr>
108
- <% runs.forEach(r => { %>
109
- <tr>
110
- <td><code><%= r.id.substring(0, 20) %>...</code></td>
111
- <td><%= r.testName %></td>
112
- <td><span class="badge <%= r.success ? 'badge-pass' : 'badge-fail' %>"><%= r.success ? 'PASS' : 'FAIL' %></span></td>
113
- <td><%= r.duration ? (r.duration / 1000).toFixed(1) + 's' : 'N/A' %></td>
114
- <td><%= r.failures %></td>
115
- <td><%= r.healed %></td>
116
- <td><%= r.defects %></td>
117
- <td>
118
- <a href="/export/report/<%= r.id %>?project=<%= project.id %>" class="btn btn-sm">HTML</a>
119
- <a href="/export/pdf/<%= r.id %>?project=<%= project.id %>" class="btn btn-sm">TXT</a>
120
- </td>
121
- </tr>
122
- <% }) %>
123
- </table>
124
- </div>
105
+ <!-- Run History Table -->
106
+ <div class="card" style="overflow-x:auto;">
107
+ <h3 style="margin-bottom:1.5rem;">📜 Run History</h3>
108
+ <table class="info-table">
109
+ <thead>
110
+ <tr>
111
+ <th>Run ID</th>
112
+ <th>Test Suite</th>
113
+ <th>Status</th>
114
+ <th>Duration</th>
115
+ <th>Failures</th>
116
+ <th>Healed</th>
117
+ <th>Defects</th>
118
+ <th>Export</th>
119
+ </tr>
120
+ </thead>
121
+ <tbody>
122
+ <% runs.forEach(r=> { %>
123
+ <tr>
124
+ <td><code style="font-family:var(--font-mono);font-size:0.8rem;"><%= r.id.substring(0, 16) %>...</code></td>
125
+ <td><%= r.testName %></td>
126
+ <td><span class="badge <%= r.success ? 'badge-pass' : 'badge-fail' %>"><%= r.success ? 'PASS' : 'FAIL' %></span></td>
127
+ <td><%= r.duration ? (r.duration / 1000).toFixed(1) + 's' : 'N/A' %></td>
128
+ <td><%= r.failures %></td>
129
+ <td><%= r.healed %></td>
130
+ <td><%= r.defects %></td>
131
+ <td>
132
+ <a href="/export/report/<%= r.id %>?project=<%= project.id %>" class="btn btn-sm" target="_blank">HTML</a>
133
+ <a href="/export/pdf/<%= r.id %>?project=<%= project.id %>" class="btn btn-sm">TXT</a>
134
+ </td>
135
+ </tr>
136
+ <% }) %>
137
+ </tbody>
138
+ </table>
139
+ </div>
140
+
141
+ <!-- Client-side data storage to prevent server EJS tags in script blocks -->
142
+ <div id="chart-data"
143
+ style="display: none;"
144
+ data-labels='<%- JSON.stringify(runs.map(r => r.id.substring(0, 10)).reverse()) %>'
145
+ data-passrates='<%- JSON.stringify(runs.map(r => r.total > 0 ? parseFloat(((r.total - r.failures) / r.total * 100).toFixed(1)) : 0).reverse()) %>'
146
+ data-failures='<%- JSON.stringify(runs.map(r => r.failures).reverse()) %>'
147
+ data-durations='<%- JSON.stringify(runs.map(r => r.duration ? parseFloat((r.duration / 1000).toFixed(1)) : 0).reverse()) %>'
148
+ data-healed='<%- JSON.stringify(runs.map(r => r.healed).reverse()) %>'
149
+ data-defects='<%- JSON.stringify(runs.map(r => r.defects).reverse()) %>'
150
+ data-passedruns="<%= stats.passedRuns %>"
151
+ data-totalhealed="<%= stats.totalHealed %>"
152
+ data-totaldefects="<%= stats.totalDefects %>">
153
+ </div>
125
154
 
126
- <script>
127
- const runLabels = <%= JSON.stringify(runs.map(r => r.id.substring(0, 10))) %>;
128
- const passRates = <%= JSON.stringify(runs.map(r => r.total > 0 ? ((r.total - r.failures) / r.total * 100).toFixed(1) : 0)) %>;
129
- const failures = <%= JSON.stringify(runs.map(r => r.failures)) %>;
130
- const durations = <%= JSON.stringify(runs.map(r => r.duration ? (r.duration / 1000).toFixed(1) : 0)) %>;
131
- const healed = <%= JSON.stringify(runs.map(r => r.healed)) %>;
132
- const defects = <%= JSON.stringify(runs.map(r => r.defects)) %>;
155
+ <script>
156
+ const chartDataEl = document.getElementById('chart-data');
157
+ const runLabels = JSON.parse(chartDataEl.dataset.labels || '[]');
158
+ const passRates = JSON.parse(chartDataEl.dataset.passrates || '[]');
159
+ const failures = JSON.parse(chartDataEl.dataset.failures || '[]');
160
+ const durations = JSON.parse(chartDataEl.dataset.durations || '[]');
161
+ const healed = JSON.parse(chartDataEl.dataset.healed || '[]');
162
+ const defects = JSON.parse(chartDataEl.dataset.defects || '[]');
163
+ const passedRuns = parseInt(chartDataEl.dataset.passedruns || '0', 10);
164
+ const totalHealed = parseInt(chartDataEl.dataset.totalhealed || '0', 10);
165
+ const totalDefects = parseInt(chartDataEl.dataset.totaldefects || '0', 10);
133
166
 
134
- new Chart(document.getElementById('passRateChart'), {
135
- type: 'line',
136
- data: {
137
- labels: runLabels,
138
- datasets: [{
139
- label: 'Pass Rate %',
140
- data: passRates,
141
- borderColor: '#22c55e',
142
- backgroundColor: 'rgba(34,197,94,0.1)',
143
- fill: true,
144
- tension: 0.4,
145
- }]
146
- },
147
- options: { responsive: true, plugins: { legend: { labels: { color: '#e4e6f0' } } }, scales: { x: { ticks: { color: '#8b8fa3' } }, y: { ticks: { color: '#8b8fa3' }, min: 0, max: 100 } } }
148
- });
167
+ // 1. Pass Rate Trend Chart (Vibrant Pink & Teal fill)
168
+ new Chart(document.getElementById('passRateChart'), {
169
+ type: 'line',
170
+ data: {
171
+ labels: runLabels,
172
+ datasets: [{
173
+ label: 'Pass Rate %',
174
+ data: passRates,
175
+ borderColor: '#ff4d8b',
176
+ backgroundColor: 'rgba(255, 77, 139, 0.08)',
177
+ fill: true,
178
+ tension: 0.4,
179
+ borderWidth: 3
180
+ }]
181
+ },
182
+ options: {
183
+ responsive: true,
184
+ plugins: {
185
+ legend: { labels: { color: '#0a0a0a', font: { family: 'Inter', weight: 600 } } }
186
+ },
187
+ scales: {
188
+ x: { grid: { display: false }, ticks: { color: '#3a3a3a' } },
189
+ y: { min: 0, max: 100, ticks: { color: '#3a3a3a' } }
190
+ }
191
+ }
192
+ });
149
193
 
150
- new Chart(document.getElementById('failureChart'), {
151
- type: 'pie',
152
- data: {
153
- labels: ['Passed', 'Healed', 'Defects'],
154
- datasets: [{
155
- data: [<%= stats.passedRuns %>, <%= stats.totalHealed %>, <%= stats.totalDefects %>],
156
- backgroundColor: ['#22c55e', '#f59e0b', '#ef4444'],
157
- }]
158
- },
159
- options: { responsive: true, plugins: { legend: { labels: { color: '#e4e6f0' } } } }
160
- });
194
+ // 2. Failure Breakdown Pie Chart (Clay Brand Colors)
195
+ new Chart(document.getElementById('failureChart'), {
196
+ type: 'pie',
197
+ data: {
198
+ labels: ['Passed', 'Healed', 'Defects'],
199
+ datasets: [{
200
+ data: [passedRuns, totalHealed, totalDefects],
201
+ backgroundColor: ['#22c55e', '#ffb084', '#ff4d8b'],
202
+ borderColor: '#fffaf0',
203
+ borderWidth: 2
204
+ }]
205
+ },
206
+ options: {
207
+ responsive: true,
208
+ plugins: {
209
+ legend: { labels: { color: '#0a0a0a', font: { family: 'Inter', weight: 600 } } }
210
+ }
211
+ }
212
+ });
161
213
 
162
- new Chart(document.getElementById('durationChart'), {
163
- type: 'bar',
164
- data: {
165
- labels: runLabels,
166
- datasets: [{
167
- label: 'Duration (s)',
168
- data: durations,
169
- backgroundColor: '#6366f1',
170
- }]
171
- },
172
- options: { responsive: true, plugins: { legend: { labels: { color: '#e4e6f0' } } }, scales: { x: { ticks: { color: '#8b8fa3' } }, y: { ticks: { color: '#8b8fa3' } } } }
173
- });
214
+ // 3. Duration Trend Bar Chart (Deep Teal)
215
+ new Chart(document.getElementById('durationChart'), {
216
+ type: 'bar',
217
+ data: {
218
+ labels: runLabels,
219
+ datasets: [{
220
+ label: 'Duration (s)',
221
+ data: durations,
222
+ backgroundColor: '#1a3a3a',
223
+ borderRadius: 6
224
+ }]
225
+ },
226
+ options: {
227
+ responsive: true,
228
+ plugins: {
229
+ legend: { labels: { color: '#0a0a0a', font: { family: 'Inter', weight: 600 } } }
230
+ },
231
+ scales: {
232
+ x: { grid: { display: false }, ticks: { color: '#3a3a3a' } },
233
+ y: { ticks: { color: '#3a3a3a' } }
234
+ }
235
+ }
236
+ });
174
237
 
175
- new Chart(document.getElementById('healingChart'), {
176
- type: 'bar',
177
- data: {
178
- labels: runLabels,
179
- datasets: [
180
- { label: 'Healed', data: healed, backgroundColor: '#f59e0b' },
181
- { label: 'Defects', data: defects, backgroundColor: '#ef4444' }
182
- ]
183
- },
184
- options: { responsive: true, plugins: { legend: { labels: { color: '#e4e6f0' } } }, scales: { x: { ticks: { color: '#8b8fa3' } }, y: { ticks: { color: '#8b8fa3' } } } }
185
- });
186
- </script>
238
+ // 4. Healing vs Defects Double Bar Chart (Peach & Pink)
239
+ new Chart(document.getElementById('healingChart'), {
240
+ type: 'bar',
241
+ data: {
242
+ labels: runLabels,
243
+ datasets: [
244
+ { label: 'Healed', data: healed, backgroundColor: '#ffb084', borderRadius: 4 },
245
+ { label: 'Defects', data: defects, backgroundColor: '#ff4d8b', borderRadius: 4 }
246
+ ]
247
+ },
248
+ options: {
249
+ responsive: true,
250
+ plugins: {
251
+ legend: { labels: { color: '#0a0a0a', font: { family: 'Inter', weight: 600 } } }
252
+ },
253
+ scales: {
254
+ x: { grid: { display: false }, ticks: { color: '#3a3a3a' } },
255
+ y: { ticks: { color: '#3a3a3a' } }
256
+ }
257
+ }
258
+ });
259
+ </script>
187
260
 
188
- <% } %>
261
+ <% } %>