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