github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/ui/tests/acceptance/job-deployments-test.js (about) 1 import { get } from '@ember/object'; 2 import $ from 'jquery'; 3 import { click, findAll, find, visit } from 'ember-native-dom-helpers'; 4 import { test } from 'qunit'; 5 import moment from 'moment'; 6 import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; 7 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('[data-test-deployment]').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 = $(find('[data-test-deployment]')); 55 56 assert.ok(deploymentRow.text().includes(deployment.id.split('-')[0]), 'Short ID'); 57 assert.equal( 58 deploymentRow.find('[data-test-deployment-status]').text(), 59 deployment.status, 60 'Status' 61 ); 62 assert.ok( 63 deploymentRow 64 .find('[data-test-deployment-status]') 65 .hasClass(classForStatus(deployment.status)), 66 'Status Class' 67 ); 68 assert.ok( 69 deploymentRow 70 .find('[data-test-deployment-version]') 71 .text() 72 .includes(deployment.versionNumber), 73 'Version #' 74 ); 75 assert.ok( 76 deploymentRow 77 .find('[data-test-deployment-submit-time]') 78 .text() 79 .includes(moment(version.submitTime / 1000000).fromNow()), 80 'Submit time ago' 81 ); 82 }); 83 }); 84 85 test('when the deployment is running and needs promotion, the deployment item says so', function( 86 assert 87 ) { 88 // Ensure the deployment needs deployment 89 const deployment = sortedDeployments.models[0]; 90 const taskGroupSummary = deployment.deploymentTaskGroupSummaryIds.map(id => 91 server.schema.deploymentTaskGroupSummaries.find(id) 92 )[0]; 93 94 deployment.update('status', 'running'); 95 deployment.save(); 96 97 taskGroupSummary.update({ 98 desiredCanaries: 1, 99 placedCanaries: 0, 100 promoted: false, 101 }); 102 103 taskGroupSummary.save(); 104 105 visit(`/jobs/${job.id}/deployments`); 106 107 andThen(() => { 108 const deploymentRow = find('[data-test-deployment]'); 109 assert.ok( 110 deploymentRow.querySelector('[data-test-promotion-required]'), 111 'Requires Promotion badge found' 112 ); 113 }); 114 }); 115 116 test('each deployment item can be opened to show details', function(assert) { 117 let deploymentRow; 118 119 visit(`/jobs/${job.id}/deployments`); 120 121 andThen(() => { 122 deploymentRow = find('[data-test-deployment]'); 123 124 assert.notOk( 125 deploymentRow.querySelector('[data-test-deployment-details]'), 126 'No deployment body' 127 ); 128 129 click(deploymentRow.querySelector('[data-test-deployment-toggle-details]')); 130 131 andThen(() => { 132 assert.ok( 133 deploymentRow.querySelector('[data-test-deployment-details]'), 134 'Deployment body found' 135 ); 136 }); 137 }); 138 }); 139 140 test('when open, a deployment shows the deployment metrics', function(assert) { 141 visit(`/jobs/${job.id}/deployments`); 142 143 andThen(() => { 144 const deployment = sortedDeployments.models[0]; 145 const deploymentRow = find('[data-test-deployment]'); 146 const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => 147 server.db.deploymentTaskGroupSummaries.find(id) 148 ); 149 150 click(deploymentRow.querySelector('[data-test-deployment-toggle-details]')); 151 152 andThen(() => { 153 assert.equal( 154 find('[data-test-deployment-metric="canaries"]').textContent.trim(), 155 `${sum(taskGroupSummaries, 'placedCanaries')} / ${sum( 156 taskGroupSummaries, 157 'desiredCanaries' 158 )}`, 159 'Canaries, both places and desired, are in the metrics' 160 ); 161 162 assert.equal( 163 find('[data-test-deployment-metric="placed"]').textContent.trim(), 164 sum(taskGroupSummaries, 'placedAllocs'), 165 'Placed allocs aggregates across task groups' 166 ); 167 168 assert.equal( 169 find('[data-test-deployment-metric="desired"]').textContent.trim(), 170 sum(taskGroupSummaries, 'desiredTotal'), 171 'Desired allocs aggregates across task groups' 172 ); 173 174 assert.equal( 175 find('[data-test-deployment-metric="healthy"]').textContent.trim(), 176 sum(taskGroupSummaries, 'healthyAllocs'), 177 'Healthy allocs aggregates across task groups' 178 ); 179 180 assert.equal( 181 find('[data-test-deployment-metric="unhealthy"]').textContent.trim(), 182 sum(taskGroupSummaries, 'unhealthyAllocs'), 183 'Unhealthy allocs aggregates across task groups' 184 ); 185 186 assert.equal( 187 find('[data-test-deployment-notification]').textContent.trim(), 188 deployment.statusDescription, 189 'Status description is in the metrics block' 190 ); 191 }); 192 }); 193 }); 194 195 test('when open, a deployment shows a list of all task groups and their respective stats', function( 196 assert 197 ) { 198 visit(`/jobs/${job.id}/deployments`); 199 200 andThen(() => { 201 const deployment = sortedDeployments.models[0]; 202 const deploymentRow = find('[data-test-deployment]'); 203 const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => 204 server.db.deploymentTaskGroupSummaries.find(id) 205 ); 206 207 click(deploymentRow.querySelector('[data-test-deployment-toggle-details]')); 208 209 andThen(() => { 210 const taskGroupTable = deploymentRow.querySelector('[data-test-deployment-task-groups]'); 211 212 assert.ok(taskGroupTable, 'Task groups found'); 213 214 assert.equal( 215 taskGroupTable.querySelectorAll('[data-test-deployment-task-group]').length, 216 taskGroupSummaries.length, 217 'One row per task group' 218 ); 219 220 const taskGroup = taskGroupSummaries[0]; 221 const taskGroupRow = taskGroupTable.querySelector('[data-test-deployment-task-group]'); 222 223 assert.equal( 224 taskGroupRow.querySelector('[data-test-deployment-task-group-name]').textContent.trim(), 225 taskGroup.name, 226 'Name' 227 ); 228 assert.equal( 229 taskGroupRow 230 .querySelector('[data-test-deployment-task-group-promotion]') 231 .textContent.trim(), 232 promotionTestForTaskGroup(taskGroup), 233 'Needs Promotion' 234 ); 235 assert.equal( 236 taskGroupRow 237 .querySelector('[data-test-deployment-task-group-auto-revert]') 238 .textContent.trim(), 239 taskGroup.autoRevert ? 'Yes' : 'No', 240 'Auto Revert' 241 ); 242 assert.equal( 243 taskGroupRow.querySelector('[data-test-deployment-task-group-canaries]').textContent.trim(), 244 `${taskGroup.placedCanaries} / ${taskGroup.desiredCanaries}`, 245 'Canaries' 246 ); 247 assert.equal( 248 taskGroupRow.querySelector('[data-test-deployment-task-group-allocs]').textContent.trim(), 249 `${taskGroup.placedAllocs} / ${taskGroup.desiredTotal}`, 250 'Allocs' 251 ); 252 assert.equal( 253 taskGroupRow.querySelector('[data-test-deployment-task-group-healthy]').textContent.trim(), 254 taskGroup.healthyAllocs, 255 'Healthy Allocs' 256 ); 257 assert.equal( 258 taskGroupRow 259 .querySelector('[data-test-deployment-task-group-unhealthy]') 260 .textContent.trim(), 261 taskGroup.unhealthyAllocs, 262 'Unhealthy Allocs' 263 ); 264 }); 265 }); 266 }); 267 268 test('when open, a deployment shows a list of all allocations for the deployment', function( 269 assert 270 ) { 271 visit(`/jobs/${job.id}/deployments`); 272 273 andThen(() => { 274 const deployment = sortedDeployments.models[0]; 275 const deploymentRow = find('[data-test-deployment]'); 276 277 // TODO: Make this less brittle. This logic is copied from the mirage config, 278 // since there is no reference to allocations on the deployment model. 279 const allocations = server.db.allocations.where({ jobId: deployment.jobId }).slice(0, 3); 280 281 click(deploymentRow.querySelector('[data-test-deployment-toggle-details]')); 282 283 andThen(() => { 284 assert.ok( 285 deploymentRow.querySelector('[data-test-deployment-allocations]'), 286 'Allocations found' 287 ); 288 289 assert.equal( 290 deploymentRow.querySelectorAll('[data-test-deployment-allocation]').length, 291 allocations.length, 292 'One row per allocation' 293 ); 294 295 const allocation = allocations[0]; 296 const allocationRow = deploymentRow.querySelector('[data-test-deployment-allocation]'); 297 298 assert.equal( 299 allocationRow.querySelector('[data-test-short-id]').textContent.trim(), 300 allocation.id.split('-')[0], 301 'Allocation is as expected' 302 ); 303 }); 304 }); 305 }); 306 307 function classForStatus(status) { 308 const classMap = { 309 running: 'is-running', 310 successful: 'is-primary', 311 paused: 'is-light', 312 failed: 'is-error', 313 cancelled: 'is-cancelled', 314 }; 315 316 return classMap[status] || 'is-dark'; 317 } 318 319 function promotionTestForTaskGroup(taskGroup) { 320 if (taskGroup.desiredCanaries > 0 && taskGroup.promoted === false) { 321 return 'Yes'; 322 } else if (taskGroup.desiredCanaries > 0) { 323 return 'No'; 324 } 325 return 'N/A'; 326 }