[ui, deployments] Show a "Latest Deployment Status" cell within the Job Status panel on steady service jobs (#17246)
* Failed or lost cell condensed * Latest Deployment cell * Stylistic changes and deploying state fixup * Rewritten tooltip message and updated lost/failed tests * failed-or-lost cell updates to job status panel acceptance tests
This commit is contained in:
parent
e04d8cf77b
commit
98aa88c739
|
@ -1,36 +1,44 @@
|
|||
<section class="failed-or-lost">
|
||||
<h4>
|
||||
{{@title}}
|
||||
<span
|
||||
class="tooltip multiline text-center"
|
||||
role="tooltip"
|
||||
aria-label={{@description}}
|
||||
>
|
||||
<FlightIcon @name="info" />
|
||||
<h4>Replaced Allocations</h4>
|
||||
<div class="failed-or-lost-links">
|
||||
{{#if @supportsRescheduling}}
|
||||
<span>
|
||||
<span
|
||||
class="tooltip multiline text-center"
|
||||
role="tooltip"
|
||||
aria-label="Allocations that have been rescheduled, on another node if possible, due to failure or manual restart"
|
||||
>
|
||||
<FlightIcon @name="info" />
|
||||
</span>
|
||||
<ConditionalLinkTo
|
||||
@condition={{@rescheduledAllocs.length}}
|
||||
@route="jobs.job.allocations"
|
||||
@model={{@job}}
|
||||
@query={{hash scheduling='["has-been-rescheduled"]' version=(concat '[' @job.latestDeployment.versionNumber ']')}}
|
||||
@label="View Allocations"
|
||||
>
|
||||
{{@rescheduledAllocs.length}} Rescheduled
|
||||
</ConditionalLinkTo>
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
<span>
|
||||
<span
|
||||
class="tooltip multiline text-center"
|
||||
role="tooltip"
|
||||
aria-label="Allocations that have been restarted in-place due to a task failure or manual restart"
|
||||
>
|
||||
<FlightIcon @name="info" />
|
||||
</span>
|
||||
<ConditionalLinkTo
|
||||
@condition={{@restartedAllocs.length}}
|
||||
@route="jobs.job.allocations"
|
||||
@model={{@job}}
|
||||
@query={{hash scheduling='["has-been-restarted"]' version=(concat '[' @job.latestDeployment.versionNumber ']')}}
|
||||
@label="View Allocations"
|
||||
>
|
||||
{{@restartedAllocs.length}} Restarted
|
||||
</ConditionalLinkTo>
|
||||
</span>
|
||||
</h4>
|
||||
{{#if (eq @title "Rescheduled")}}
|
||||
<ConditionalLinkTo
|
||||
@condition={{this.shouldLinkToAllocations}}
|
||||
@route="jobs.job.allocations"
|
||||
@model={{@job}}
|
||||
@query={{hash scheduling='["has-been-rescheduled"]' version=(concat '[' @job.latestDeployment.versionNumber ']')}}
|
||||
@label="View Allocations"
|
||||
@class="failed-or-lost-link"
|
||||
>
|
||||
{{@allocs.length}}
|
||||
</ConditionalLinkTo>
|
||||
{{/if}}
|
||||
{{#if (eq @title "Restarted")}}
|
||||
<ConditionalLinkTo
|
||||
@condition={{this.shouldLinkToAllocations}}
|
||||
@route="jobs.job.allocations"
|
||||
@model={{@job}}
|
||||
@query={{hash scheduling='["has-been-restarted"]' version=(concat '[' @job.latestDeployment.versionNumber ']')}}
|
||||
@label="View Allocations"
|
||||
@class="failed-or-lost-link"
|
||||
>
|
||||
{{@allocs.length}}
|
||||
</ConditionalLinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
|
@ -1,7 +0,0 @@
|
|||
import Component from '@glimmer/component';
|
||||
|
||||
export default class JobStatusFailedOrLostComponent extends Component {
|
||||
get shouldLinkToAllocations() {
|
||||
return this.args.allocs.length;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<section class="latest-deployment">
|
||||
<LinkTo @route="jobs.job.deployments" @model={{@job}}>
|
||||
<h4>
|
||||
Latest Deployment
|
||||
<FlightIcon @name="arrow-right" />
|
||||
</h4>
|
||||
</LinkTo>
|
||||
<Hds::Badge @text={{capitalize this.status}} @size="small" @color={{this.statusColor}} @type="filled" />
|
||||
<p>{{this.healthyAllocs}}/{{this.desiredTotal}} Healthy</p>
|
||||
</section>
|
|
@ -0,0 +1,31 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default class JobStatusLatestDeploymentComponent extends Component {
|
||||
@alias('args.job.latestDeployment') deployment;
|
||||
@alias('deployment.status') status;
|
||||
|
||||
get healthyAllocs() {
|
||||
return this.deployment
|
||||
.get('taskGroupSummaries')
|
||||
.mapBy('healthyAllocs')
|
||||
.reduce((sum, count) => sum + count, 0);
|
||||
}
|
||||
get desiredTotal() {
|
||||
return this.deployment
|
||||
.get('taskGroupSummaries')
|
||||
.mapBy('desiredTotal')
|
||||
.reduce((sum, count) => sum + count, 0);
|
||||
}
|
||||
|
||||
get statusColor() {
|
||||
switch (this.status) {
|
||||
case 'successful':
|
||||
return 'success';
|
||||
case 'failed':
|
||||
return 'critical';
|
||||
default:
|
||||
return 'neutral';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -104,17 +104,10 @@
|
|||
</legend>
|
||||
|
||||
<JobStatus::FailedOrLost
|
||||
@allocs={{this.rescheduledAllocs}}
|
||||
@rescheduledAllocs={{this.rescheduledAllocs}}
|
||||
@restartedAllocs={{this.restartedAllocs}}
|
||||
@job={{@job}}
|
||||
@title="Rescheduled"
|
||||
@description="Allocations that have been rescheduled, on another node if possible, due to failure during deployment"
|
||||
/>
|
||||
|
||||
<JobStatus::FailedOrLost
|
||||
@allocs={{this.restartedAllocs}}
|
||||
@job={{@job}}
|
||||
@title="Restarted"
|
||||
@description="Allocations that have been restarted in-place due to a task failure during deployment"
|
||||
@supportsRescheduling={{true}}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</h3>
|
||||
<JobStatus::AllocationStatusRow @allocBlocks={{this.allocBlocks}} @steady={{true}} />
|
||||
|
||||
<div class="legend-and-summary">
|
||||
<div class="legend-and-summary {{if @job.latestDeployment "has-latest-deployment"}}">
|
||||
<legend>
|
||||
{{#each this.allocTypes as |type|}}
|
||||
<ConditionalLinkTo
|
||||
|
@ -58,20 +58,12 @@
|
|||
{{/each}}
|
||||
</legend>
|
||||
|
||||
{{#if this.supportsRescheduling}}
|
||||
<JobStatus::FailedOrLost
|
||||
@allocs={{this.rescheduledAllocs}}
|
||||
@job={{@job}}
|
||||
@title="Rescheduled"
|
||||
@description="Allocations that have been rescheduled, on another node if possible, due to failure"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<JobStatus::FailedOrLost
|
||||
@allocs={{this.restartedAllocs}}
|
||||
@rescheduledAllocs={{this.rescheduledAllocs}}
|
||||
@restartedAllocs={{this.restartedAllocs}}
|
||||
@job={{@job}}
|
||||
@title="Restarted"
|
||||
@description="Allocations that have been restarted in-place due to a task failure"
|
||||
@supportsRescheduling={{this.supportsRescheduling}}
|
||||
/>
|
||||
|
||||
<section class="versions">
|
||||
|
@ -92,6 +84,10 @@
|
|||
</ul>
|
||||
</section>
|
||||
|
||||
{{#if @job.latestDeployment}}
|
||||
<JobStatus::LatestDeployment @job={{@job}} />
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="history-and-params">
|
||||
|
|
|
@ -48,11 +48,15 @@
|
|||
// grid-area: legend-and-summary;
|
||||
// TODO: may revisit this grid-area later, but is currently used in 2 competing ways
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 55% 15% 15% 15%;
|
||||
gap: 1rem;
|
||||
grid-template-columns: 3fr 1fr 1fr;
|
||||
&.has-latest-deployment {
|
||||
grid-template-columns: 3fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
& > section > h4,
|
||||
& > legend > h4 {
|
||||
& > legend > h4,
|
||||
& > section > a > h4 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
|
@ -62,8 +66,6 @@
|
|||
gap: 0.5rem;
|
||||
}
|
||||
.versions {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
& > ul {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 100px);
|
||||
|
@ -76,12 +78,18 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.latest-deployment {
|
||||
h4 svg {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.failed-or-lost {
|
||||
.failed-or-lost-link {
|
||||
display: block;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
.failed-or-lost > div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
.tooltip {
|
||||
top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -648,29 +648,29 @@ module('Acceptance | job status panel', function (hooks) {
|
|||
await visit(`/jobs/${job.id}`);
|
||||
assert.dom('.job-status-panel').exists();
|
||||
assert
|
||||
.dom('.failed-or-lost')
|
||||
.dom('.failed-or-lost-links > span')
|
||||
.exists({ count: 2 }, 'Restarted and Rescheduled cells are both present');
|
||||
|
||||
let rescheduledCell = [...findAll('.failed-or-lost')][0];
|
||||
let restartedCell = [...findAll('.failed-or-lost')][1];
|
||||
// await this.pauseTest();
|
||||
let rescheduledCell = [...findAll('.failed-or-lost-links > span')][0];
|
||||
let restartedCell = [...findAll('.failed-or-lost-links > span')][1];
|
||||
|
||||
// Check that the title in each cell has the right text
|
||||
assert.dom(rescheduledCell.querySelector('h4')).hasText('Rescheduled');
|
||||
assert.dom(restartedCell.querySelector('h4')).hasText('Restarted');
|
||||
assert.dom(rescheduledCell).hasText('0 Rescheduled');
|
||||
assert.dom(restartedCell).hasText('0 Restarted');
|
||||
|
||||
// Check that both values are zero and non-links
|
||||
assert
|
||||
.dom(rescheduledCell.querySelector('a'))
|
||||
.doesNotExist('Rescheduled cell is not a link');
|
||||
assert
|
||||
.dom(rescheduledCell.querySelector('.failed-or-lost-link'))
|
||||
.hasText('0', 'Rescheduled cell has zero value');
|
||||
.dom(rescheduledCell)
|
||||
.hasText('0 Rescheduled', 'Rescheduled cell has zero value');
|
||||
assert
|
||||
.dom(restartedCell.querySelector('a'))
|
||||
.doesNotExist('Restarted cell is not a link');
|
||||
assert
|
||||
.dom(restartedCell.querySelector('.failed-or-lost-link'))
|
||||
.hasText('0', 'Restarted cell has zero value');
|
||||
.dom(restartedCell)
|
||||
.hasText('0 Restarted', 'Restarted cell has zero value');
|
||||
|
||||
// A wild event appears! Change a recent task event to type "Restarting" in a task state:
|
||||
this.store
|
||||
|
@ -687,9 +687,9 @@ module('Acceptance | job status panel', function (hooks) {
|
|||
await settled();
|
||||
|
||||
assert
|
||||
.dom(restartedCell.querySelector('.failed-or-lost-link'))
|
||||
.dom(restartedCell)
|
||||
.hasText(
|
||||
'1',
|
||||
'1 Restarted',
|
||||
'Restarted cell updates when a task event with type "Restarting" is added'
|
||||
);
|
||||
|
||||
|
@ -708,9 +708,9 @@ module('Acceptance | job status panel', function (hooks) {
|
|||
|
||||
// Trigger a reschedule! Set up a desiredTransition object with a Reschedule property on one of the allocations.
|
||||
assert
|
||||
.dom(restartedCell.querySelector('.failed-or-lost-link'))
|
||||
.dom(restartedCell)
|
||||
.hasText(
|
||||
'2',
|
||||
'2 Restarted',
|
||||
'Restarted cell updates when a second task event with type "Restarting" is added'
|
||||
);
|
||||
|
||||
|
@ -725,8 +725,11 @@ module('Acceptance | job status panel', function (hooks) {
|
|||
await settled();
|
||||
|
||||
assert
|
||||
.dom(rescheduledCell.querySelector('.failed-or-lost-link'))
|
||||
.hasText('1', 'Rescheduled cell updates when desiredTransition is set');
|
||||
.dom(rescheduledCell)
|
||||
.hasText(
|
||||
'1 Rescheduled',
|
||||
'Rescheduled cell updates when desiredTransition is set'
|
||||
);
|
||||
assert
|
||||
.dom(rescheduledCell.querySelector('a'))
|
||||
.exists('Rescheduled cell with a non-zero number is now a link');
|
||||
|
@ -900,10 +903,10 @@ module('Acceptance | job status panel', function (hooks) {
|
|||
await visit(`/jobs/${job.id}`);
|
||||
assert.dom('.job-status-panel').exists();
|
||||
assert.dom('.failed-or-lost').exists({ count: 1 });
|
||||
assert.dom('.failed-or-lost h4').hasText('Restarted');
|
||||
assert.dom('.failed-or-lost h4').hasText('Replaced Allocations');
|
||||
assert
|
||||
.dom('.failed-or-lost-link')
|
||||
.hasText('0', 'Restarted cell at zero by default');
|
||||
.dom('.failed-or-lost-links > span')
|
||||
.hasText('0 Restarted', 'Restarted cell at zero by default');
|
||||
|
||||
// A wild event appears! Change a recent task event to type "Restarting" in a task state:
|
||||
this.store
|
||||
|
@ -920,9 +923,9 @@ module('Acceptance | job status panel', function (hooks) {
|
|||
await settled();
|
||||
|
||||
assert
|
||||
.dom('.failed-or-lost-link')
|
||||
.dom('.failed-or-lost-links > span')
|
||||
.hasText(
|
||||
'1',
|
||||
'1 Restarted',
|
||||
'Restarted cell updates when a task event with type "Restarting" is added'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -9,6 +9,11 @@ module('Integration | Component | job-status/failed-or-lost', function (hooks) {
|
|||
|
||||
test('it renders', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
let job = {
|
||||
id: 'job1',
|
||||
};
|
||||
|
||||
let allocs = [
|
||||
{
|
||||
id: 1,
|
||||
|
@ -21,20 +26,23 @@ module('Integration | Component | job-status/failed-or-lost', function (hooks) {
|
|||
];
|
||||
|
||||
this.set('allocs', allocs);
|
||||
this.set('job', job);
|
||||
|
||||
await render(hbs`<JobStatus::FailedOrLost
|
||||
@title="Rescheduled"
|
||||
@description="Rescheduled Allocations"
|
||||
@allocs={{this.allocs}}
|
||||
@job={{this.job}}
|
||||
@restartedAllocs={{this.allocs}}
|
||||
/>`);
|
||||
|
||||
assert.dom('h4').hasText('Rescheduled');
|
||||
assert.dom('.failed-or-lost-link').hasText('2');
|
||||
|
||||
assert.dom('h4').hasText('Replaced Allocations');
|
||||
assert.dom('.failed-or-lost-links').hasText('2 Restarted');
|
||||
await componentA11yAudit(this.element, assert);
|
||||
});
|
||||
|
||||
test('it links or does not link appropriately', async function (assert) {
|
||||
let job = {
|
||||
id: 'job1',
|
||||
};
|
||||
|
||||
let allocs = [
|
||||
{
|
||||
id: 1,
|
||||
|
@ -47,16 +55,67 @@ module('Integration | Component | job-status/failed-or-lost', function (hooks) {
|
|||
];
|
||||
|
||||
this.set('allocs', allocs);
|
||||
this.set('job', job);
|
||||
|
||||
await render(hbs`<JobStatus::FailedOrLost
|
||||
@title="Rescheduled"
|
||||
@description="Rescheduled Allocations"
|
||||
@allocs={{this.allocs}}
|
||||
@restartedAllocs={{this.allocs}}
|
||||
@job={{this.job}}
|
||||
/>`);
|
||||
|
||||
// Ensure it's of type a
|
||||
assert.dom('.failed-or-lost-link').hasTagName('a');
|
||||
assert.dom('.failed-or-lost-links > span > *:last-child').hasTagName('a');
|
||||
this.set('allocs', []);
|
||||
assert.dom('.failed-or-lost-link').doesNotHaveTagName('a');
|
||||
assert
|
||||
.dom('.failed-or-lost-links > span > *:last-child')
|
||||
.doesNotHaveTagName('a');
|
||||
});
|
||||
|
||||
test('it shows rescheduling as well', async function (assert) {
|
||||
let job = {
|
||||
id: 'job1',
|
||||
};
|
||||
|
||||
let restartedAllocs = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'alloc1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'alloc2',
|
||||
},
|
||||
];
|
||||
|
||||
let rescheduledAllocs = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'alloc1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'alloc2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'alloc3',
|
||||
},
|
||||
];
|
||||
|
||||
this.set('restartedAllocs', restartedAllocs);
|
||||
this.set('rescheduledAllocs', rescheduledAllocs);
|
||||
this.set('job', job);
|
||||
this.set('supportsRescheduling', true);
|
||||
|
||||
await render(hbs`<JobStatus::FailedOrLost
|
||||
@restartedAllocs={{this.restartedAllocs}}
|
||||
@rescheduledAllocs={{this.rescheduledAllocs}}
|
||||
@job={{this.job}}
|
||||
@supportsRescheduling={{this.supportsRescheduling}}
|
||||
/>`);
|
||||
|
||||
assert.dom('.failed-or-lost-links').containsText('2 Restarted');
|
||||
assert.dom('.failed-or-lost-links').containsText('3 Rescheduled');
|
||||
this.set('supportsRescheduling', false);
|
||||
assert.dom('.failed-or-lost-links').doesNotContainText('Rescheduled');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue