github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/ui/tests/acceptance/job-deployments-test.js (about) 1 import { click, findAll, find, visit } from 'ember-native-dom-helpers'; 2 import Ember from 'ember'; 3 import { test } from 'qunit'; 4 import moment from 'moment'; 5 import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; 6 7 const { get, $ } = Ember; 8 const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0); 9 10 let job; 11 let deployments; 12 let sortedDeployments; 13 14 moduleForAcceptance('Acceptance | job deployments', { 15 beforeEach() { 16 server.create('node'); 17 job = server.create('job'); 18 deployments = server.schema.deployments.where({ jobId: job.id }); 19 sortedDeployments = deployments.sort((a, b) => { 20 const aVersion = server.db.jobVersions.findBy({ jobId: a.jobId, version: a.versionNumber }); 21 const bVersion = server.db.jobVersions.findBy({ jobId: b.jobId, version: b.versionNumber }); 22 if (aVersion.submitTime < bVersion.submitTime) { 23 return 1; 24 } else if (aVersion.submitTime > bVersion.submitTime) { 25 return -1; 26 } 27 return 0; 28 }); 29 }, 30 }); 31 32 test('/jobs/:id/deployments should list all job deployments', function(assert) { 33 visit(`/jobs/${job.id}/deployments`); 34 andThen(() => { 35 assert.ok( 36 findAll('.timeline-object').length, 37 deployments.length, 38 'Each deployment gets a row in the timeline' 39 ); 40 }); 41 }); 42 43 test('each deployment mentions the deployment shortId, status, version, and time since it was submitted', function( 44 assert 45 ) { 46 visit(`/jobs/${job.id}/deployments`); 47 48 andThen(() => { 49 const deployment = sortedDeployments.models[0]; 50 const version = server.db.jobVersions.findBy({ 51 jobId: deployment.jobId, 52 version: deployment.versionNumber, 53 }); 54 const deploymentRow = $(findAll('.timeline-object')[0]); 55 56 assert.ok(deploymentRow.text().includes(deployment.id.split('-')[0]), 'Short ID'); 57 assert.equal(deploymentRow.find('.tag').text(), deployment.status, 'Status'); 58 assert.ok( 59 deploymentRow.find('.tag').hasClass(classForStatus(deployment.status)), 60 'Status Class' 61 ); 62 assert.ok(deploymentRow.text().includes(deployment.versionNumber), 'Version #'); 63 assert.ok( 64 deploymentRow.text().includes(moment(version.submitTime / 1000000).fromNow()), 65 'Submit time ago' 66 ); 67 }); 68 }); 69 70 test('when the deployment is running and needs promotion, the deployment item says so', function( 71 assert 72 ) { 73 // Ensure the deployment needs deployment 74 const deployment = sortedDeployments.models[0]; 75 const taskGroupSummary = deployment.deploymentTaskGroupSummaryIds.map(id => 76 server.schema.deploymentTaskGroupSummaries.find(id) 77 )[0]; 78 79 deployment.update('status', 'running'); 80 deployment.save(); 81 82 taskGroupSummary.update({ 83 desiredCanaries: 1, 84 placedCanaries: 0, 85 promoted: false, 86 }); 87 88 taskGroupSummary.save(); 89 90 visit(`/jobs/${job.id}/deployments`); 91 92 andThen(() => { 93 const deploymentRow = $(findAll('.timeline-object')[0]); 94 assert.ok( 95 deploymentRow.find('.badge:contains("Requires Promotion")').length, 96 'Requires Promotion badge found' 97 ); 98 }); 99 }); 100 101 test('each deployment item can be opened to show details', function(assert) { 102 let deploymentRow; 103 104 visit(`/jobs/${job.id}/deployments`); 105 106 andThen(() => { 107 deploymentRow = $(findAll('.timeline-object')[0]); 108 109 assert.ok(deploymentRow.find('.boxed-section-body').length === 0, 'No deployment body'); 110 111 click(deploymentRow.find('button').get(0)); 112 113 andThen(() => { 114 assert.ok(deploymentRow.find('.boxed-section-body').length, 'Deployment body found'); 115 }); 116 }); 117 }); 118 119 test('when open, a deployment shows the deployment metrics', function(assert) { 120 visit(`/jobs/${job.id}/deployments`); 121 122 andThen(() => { 123 const deployment = sortedDeployments.models[0]; 124 const deploymentRow = $(findAll('.timeline-object')[0]); 125 const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => 126 server.db.deploymentTaskGroupSummaries.find(id) 127 ); 128 129 click(deploymentRow.find('button').get(0)); 130 131 andThen(() => { 132 assert.equal( 133 $('.deployment-metrics .label:contains("Canaries") + .value') 134 .get(0) 135 .textContent.trim(), 136 `${sum(taskGroupSummaries, 'placedCanaries')} / ${sum( 137 taskGroupSummaries, 138 'desiredCanaries' 139 )}`, 140 'Canaries, both places and desired, are in the metrics' 141 ); 142 143 assert.equal( 144 $('.deployment-metrics .label:contains("Placed") + .value') 145 .get(0) 146 .textContent.trim(), 147 sum(taskGroupSummaries, 'placedAllocs'), 148 'Placed allocs aggregates across task groups' 149 ); 150 151 assert.equal( 152 $('.deployment-metrics .label:contains("Desired") + .value') 153 .get(0) 154 .textContent.trim(), 155 sum(taskGroupSummaries, 'desiredTotal'), 156 'Desired allocs aggregates across task groups' 157 ); 158 159 assert.equal( 160 $('.deployment-metrics .label:contains("Healthy") + .value') 161 .get(0) 162 .textContent.trim(), 163 sum(taskGroupSummaries, 'healthyAllocs'), 164 'Healthy allocs aggregates across task groups' 165 ); 166 167 assert.equal( 168 $('.deployment-metrics .label:contains("Unhealthy") + .value') 169 .get(0) 170 .textContent.trim(), 171 sum(taskGroupSummaries, 'unhealthyAllocs'), 172 'Unhealthy allocs aggregates across task groups' 173 ); 174 175 assert.equal( 176 find('.deployment-metrics .notification').textContent.trim(), 177 deployment.statusDescription, 178 'Status description is in the metrics block' 179 ); 180 }); 181 }); 182 }); 183 184 test('when open, a deployment shows a list of all task groups and their respective stats', function( 185 assert 186 ) { 187 visit(`/jobs/${job.id}/deployments`); 188 189 andThen(() => { 190 const deployment = sortedDeployments.models[0]; 191 const deploymentRow = $(findAll('.timeline-object')[0]); 192 const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => 193 server.db.deploymentTaskGroupSummaries.find(id) 194 ); 195 196 click(deploymentRow.find('button').get(0)); 197 198 andThen(() => { 199 assert.ok( 200 deploymentRow.find('.boxed-section-head:contains("Task Groups")').length, 201 'Task groups found' 202 ); 203 204 const taskGroupTable = deploymentRow.find( 205 '.boxed-section-head:contains("Task Groups") + .boxed-section-body tbody' 206 ); 207 208 assert.equal( 209 taskGroupTable.find('tr').length, 210 taskGroupSummaries.length, 211 'One row per task group' 212 ); 213 214 const taskGroup = taskGroupSummaries[0]; 215 const taskGroupRow = taskGroupTable.find('tr:eq(0)'); 216 217 assert.equal( 218 taskGroupRow 219 .find('td:eq(0)') 220 .text() 221 .trim(), 222 taskGroup.name, 223 'Name' 224 ); 225 assert.equal( 226 taskGroupRow 227 .find('td:eq(1)') 228 .text() 229 .trim(), 230 promotionTestForTaskGroup(taskGroup), 231 'Needs Promotion' 232 ); 233 assert.equal( 234 taskGroupRow 235 .find('td:eq(2)') 236 .text() 237 .trim(), 238 taskGroup.autoRevert ? 'Yes' : 'No', 239 'Auto Revert' 240 ); 241 assert.equal( 242 taskGroupRow 243 .find('td:eq(3)') 244 .text() 245 .trim(), 246 `${taskGroup.placedCanaries} / ${taskGroup.desiredCanaries}`, 247 'Canaries' 248 ); 249 assert.equal( 250 taskGroupRow 251 .find('td:eq(4)') 252 .text() 253 .trim(), 254 `${taskGroup.placedAllocs} / ${taskGroup.desiredTotal}`, 255 'Allocs' 256 ); 257 assert.equal( 258 taskGroupRow 259 .find('td:eq(5)') 260 .text() 261 .trim(), 262 taskGroup.healthyAllocs, 263 'Healthy Allocs' 264 ); 265 assert.equal( 266 taskGroupRow 267 .find('td:eq(6)') 268 .text() 269 .trim(), 270 taskGroup.unhealthyAllocs, 271 'Unhealthy Allocs' 272 ); 273 }); 274 }); 275 }); 276 277 test('when open, a deployment shows a list of all allocations for the deployment', function( 278 assert 279 ) { 280 visit(`/jobs/${job.id}/deployments`); 281 282 andThen(() => { 283 const deployment = sortedDeployments.models[0]; 284 const deploymentRow = $(findAll('.timeline-object')[0]); 285 286 // TODO: Make this less brittle. This logic is copied from the mirage config, 287 // since there is no reference to allocations on the deployment model. 288 const allocations = server.db.allocations.where({ jobId: deployment.jobId }).slice(0, 3); 289 290 click(deploymentRow.find('button').get(0)); 291 292 andThen(() => { 293 assert.ok( 294 deploymentRow.find('.boxed-section-head:contains("Allocations")').length, 295 'Allocations found' 296 ); 297 298 const allocationsTable = deploymentRow.find( 299 '.boxed-section-head:contains("Allocations") + .boxed-section-body tbody' 300 ); 301 302 assert.equal( 303 allocationsTable.find('tr').length, 304 allocations.length, 305 'One row per allocation' 306 ); 307 308 const allocation = allocations[0]; 309 const allocationRow = allocationsTable.find('tr:eq(0)'); 310 311 assert.equal( 312 allocationRow 313 .find('td:eq(0)') 314 .text() 315 .trim(), 316 allocation.id.split('-')[0], 317 'Allocation is as expected' 318 ); 319 }); 320 }); 321 }); 322 323 function classForStatus(status) { 324 const classMap = { 325 running: 'is-running', 326 successful: 'is-primary', 327 paused: 'is-light', 328 failed: 'is-error', 329 cancelled: 'is-cancelled', 330 }; 331 332 return classMap[status] || 'is-dark'; 333 } 334 335 function promotionTestForTaskGroup(taskGroup) { 336 if (taskGroup.desiredCanaries > 0 && taskGroup.promoted === false) { 337 return 'Yes'; 338 } else if (taskGroup.desiredCanaries > 0) { 339 return 'No'; 340 } 341 return 'N/A'; 342 }