Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] Deployment History search #16608

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions ui/app/components/job-status/deployment-history.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<div class="deployment-history">
<h4 class="title is-5">Deployment History</h4>
<header>
<h4 class="title is-5">Deployment History</h4>
<SearchBox
data-test-history-search
@searchTerm={{mut this.searchTerm}}
@placeholder="Search events..."
/>
</header>
<ol class="timeline">
{{#each this.history as |deployment-log|}}
<li class="timeline-object {{if (eq deployment-log.exitCode 1) "error"}}">
Expand All @@ -19,12 +26,20 @@
</div>
</li>
{{else}}
{{#if this.deploymentAllocations}}
<li class="timeline-object">
<div class="boxed-section-head is-light">
<span>No deployment events yet</span>
</div>
</li>
{{#if this.deploymentAllocations.length}}
{{#if this.searchTerm}}
<li class="timeline-object" data-test-history-search-no-match>
<div class="boxed-section-head is-light">
<span>No events match {{this.searchTerm}}</span>
</div>
</li>
{{else}}
<li class="timeline-object">
<div class="boxed-section-head is-light">
<span>No deployment events yet</span>
</div>
</li>
{{/if}}
{{else}}
<li class="timeline-object">
<div class="boxed-section-head is-light">
Expand Down
22 changes: 22 additions & 0 deletions ui/app/components/job-status/deployment-history.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default class JobStatusDeploymentHistoryComponent extends Component {
.flat()
)
.flat()
.filter((a) => this.containsSearchTerm(a))
.sort((a, b) => a.get('time') - b.get('time'))
.reverse();
} catch (e) {
Expand All @@ -71,4 +72,25 @@ export default class JobStatusDeploymentHistoryComponent extends Component {
color: 'critical',
});
}

// #region search

/**
* @type { string }
*/
@tracked searchTerm = '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I noticed that this is not associated to a query parameter, as you've requested in previous PRs. Any color about why we're not using an associated query parameter here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! In this case, this is a conditional component — it only exists when a deployment is active (a much smaller % of the time than when it's in steady state).

If I wanted to add a queryParam that controlled this from the job index route, there would be a majority of time where that queryParam did nothing.

There's an argument to be made for including it anyway, but it seems pretty outweighed by the argument that each deployment is separate and search terms aren't necessarily something you'd want to maintain between all of them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha!


/**
* @param { import('../../models/task-event').default } taskEvent
* @returns { boolean }
*/
containsSearchTerm(taskEvent) {
return (
taskEvent.message.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
taskEvent.type.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
taskEvent.state.allocation.shortId.includes(this.searchTerm.toLowerCase())
);
}

// #endregion search
}
48 changes: 47 additions & 1 deletion ui/app/styles/components/job-status-panel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,41 @@
display: grid;
grid-template-columns: 70% auto;
gap: 1rem;
margin-top: 1rem; // TODO: grid-ify the deployment panel generally and just use gap for this
margin-top: 2rem; // TODO: grid-ify the deployment panel generally and just use gap for this
}

.deployment-history {
& > header {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
margin-bottom: 1rem;
align-items: end;
& > .search-box {
max-width: unset;
}
}
& > ol {
max-height: 300px;
overflow-y: auto;
}
& > ol > li {
@for $i from 1 through 50 {
&:nth-child(#{$i}) {
animation-name: historyItemSlide;
animation-duration: 0.2s;
animation-fill-mode: both;
animation-delay: 0.1s + (0.05 * $i);
}

&:nth-child(#{$i}) > div {
animation-name: historyItemShine;
animation-duration: 1s;
animation-fill-mode: both;
animation-delay: 0.1s + (0.05 * $i);
}
}
Comment on lines +195 to +209
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SCSS iterator format; this lets us do things like animations that look like they roll in one at a time. New to our codebase, but a proven pattern.


& > div {
gap: 0.5rem;
}
Expand Down Expand Up @@ -218,3 +244,23 @@
}
}
}

@keyframes historyItemSlide {
from {
opacity: 0;
top: 40px;
}
to {
opacity: 1;
top: 0px;
}
}

@keyframes historyItemShine {
from {
box-shadow: inset 0 0 0 100px rgba(255, 200, 0, 0.2);
}
to {
box-shadow: inset 0 0 0 100px rgba(255, 200, 0, 0);
}
}
2 changes: 1 addition & 1 deletion ui/mirage/factories/task-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export default Factory.extend({
exitCode: () => null,
time: () => faker.date.past(2 / 365, REF_TIME) * 1000000,

displayMessage: () => faker.lorem.sentence(),
message: () => faker.lorem.sentence(),
});
2 changes: 1 addition & 1 deletion ui/tests/acceptance/allocation-detail-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ module('Acceptance | allocation detail', function (hooks) {

assert.equal(taskRow.name, task.name, 'Name');
assert.equal(taskRow.state, task.state, 'State');
assert.equal(taskRow.message, event.displayMessage, 'Event Message');
assert.equal(taskRow.message, event.message, 'Event Message');
assert.equal(
taskRow.time,
moment(event.time / 1000000).format("MMM DD, 'YY HH:mm:ss ZZ"),
Expand Down
72 changes: 71 additions & 1 deletion ui/tests/acceptance/job-status-panel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';

import { click, visit, find, triggerEvent } from '@ember/test-helpers';
import {
click,
visit,
find,
findAll,
fillIn,
triggerEvent,
} from '@ember/test-helpers';

import { setupMirage } from 'ember-cli-mirage/test-support';
import faker from 'nomad-ui/mirage/faker';
Expand Down Expand Up @@ -400,4 +407,67 @@ module('Acceptance | job status panel', function (hooks) {
'Summary block has the correct number of grouped running allocs'
);
});

module('deployment history', function () {
test('Deployment history can be searched', async function (assert) {
faker.seed(1);

let groupTaskCount = 10;

let job = server.create('job', {
status: 'running',
datacenters: ['*'],
type: 'service',
resourceSpec: ['M: 256, C: 500'], // a single group
createAllocations: true,
allocStatusDistribution: {
running: 1,
failed: 0,
unknown: 0,
lost: 0,
},
groupTaskCount,
shallow: true,
activeDeployment: true,
version: 0,
});

let state = server.create('task-state');
state.events = server.schema.taskEvents.where({ taskStateId: state.id });

server.schema.allocations.where({ jobId: job.id }).update({
taskStateIds: [state.id],
jobVersion: 0,
});

await visit(`/jobs/${job.id}`);
assert.dom('.job-status-panel').exists();

const serverEvents = server.schema.taskEvents.where({
taskStateId: state.id,
});
const shownEvents = findAll('.timeline-object');
const jobAllocations = server.db.allocations.where({ jobId: job.id });
assert.equal(
shownEvents.length,
serverEvents.length * jobAllocations.length,
'All events are shown'
);

await fillIn(
'[data-test-history-search] input',
serverEvents.models[0].message
);
assert.equal(
findAll('.timeline-object').length,
jobAllocations.length,
'Only events matching the search are shown'
);

await fillIn('[data-test-history-search] input', 'foo bar baz');
assert
.dom('[data-test-history-search-no-match]')
.exists('No match message is shown');
});
});
});
2 changes: 1 addition & 1 deletion ui/tests/acceptance/task-detail-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ module('Acceptance | task detail', function (hooks) {
'Event timestamp'
);
assert.equal(recentEvent.type, event.type, 'Event type');
assert.equal(recentEvent.message, event.displayMessage, 'Event message');
assert.equal(recentEvent.message, event.message, 'Event message');
});

test('when the allocation is not found, the application errors', async function (assert) {
Expand Down
12 changes: 10 additions & 2 deletions ui/tests/integration/components/job-page/service-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,11 @@ module('Integration | Component | job-page/service', function (hooks) {
'The error message mentions ACLs'
);

await componentA11yAudit(this.element, assert);
await componentA11yAudit(
this.element,
assert,
'scrollable-region-focusable'
); //keyframe animation fades from opacity 0

await click('[data-test-job-error-close]');

Expand Down Expand Up @@ -335,7 +339,11 @@ module('Integration | Component | job-page/service', function (hooks) {
'The error message mentions ACLs'
);

await componentA11yAudit(this.element, assert);
await componentA11yAudit(
this.element,
assert,
'scrollable-region-focusable'
); //keyframe animation fades from opacity 0

await click('[data-test-job-error-close]');

Expand Down
7 changes: 5 additions & 2 deletions ui/tests/integration/components/job-status-panel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,11 @@ module(
// deployment.get('statusDescription'),
// 'Status description is in the metrics block'
// );

await componentA11yAudit(this.element, assert);
await componentA11yAudit(
this.element,
assert,
'scrollable-region-focusable'
); //keyframe animation fades from opacity 0
});

test('when there is no running deployment, the latest deployment section shows up for the last deployment', async function (assert) {
Expand Down