From 9102247d5fbc3ac8144326548c9bd3af9fc2b013 Mon Sep 17 00:00:00 2001 From: Phil Renaud Date: Thu, 23 Mar 2023 20:15:29 -0400 Subject: [PATCH] [ui] Deployment History search (#16608) * Functioning searchbox * Some nice animations for history items * History search test * Fixing up some old mirage conventions * some a11y rule override to account for scss keyframes --- .../job-status/deployment-history.hbs | 29 ++++++-- .../job-status/deployment-history.js | 22 ++++++ .../styles/components/job-status-panel.scss | 48 ++++++++++++- ui/mirage/factories/task-event.js | 2 +- ui/tests/acceptance/allocation-detail-test.js | 2 +- ui/tests/acceptance/job-status-panel-test.js | 72 ++++++++++++++++++- ui/tests/acceptance/task-detail-test.js | 2 +- .../components/job-page/service-test.js | 12 +++- .../components/job-status-panel-test.js | 7 +- 9 files changed, 180 insertions(+), 16 deletions(-) diff --git a/ui/app/components/job-status/deployment-history.hbs b/ui/app/components/job-status/deployment-history.hbs index dc2fe778718..ec0efacc3d6 100644 --- a/ui/app/components/job-status/deployment-history.hbs +++ b/ui/app/components/job-status/deployment-history.hbs @@ -1,5 +1,12 @@
-

Deployment History

+
+

Deployment History

+ +
    {{#each this.history as |deployment-log|}}
  1. @@ -19,12 +26,20 @@
{{else}} - {{#if this.deploymentAllocations}} -
  • -
    - No deployment events yet -
    -
  • + {{#if this.deploymentAllocations.length}} + {{#if this.searchTerm}} +
  • +
    + No events match {{this.searchTerm}} +
    +
  • + {{else}} +
  • +
    + No deployment events yet +
    +
  • + {{/if}} {{else}}
  • diff --git a/ui/app/components/job-status/deployment-history.js b/ui/app/components/job-status/deployment-history.js index 8fc539bd966..61d76b2bab7 100644 --- a/ui/app/components/job-status/deployment-history.js +++ b/ui/app/components/job-status/deployment-history.js @@ -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) { @@ -71,4 +72,25 @@ export default class JobStatusDeploymentHistoryComponent extends Component { color: 'critical', }); } + + // #region search + + /** + * @type { string } + */ + @tracked searchTerm = ''; + + /** + * @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 } diff --git a/ui/app/styles/components/job-status-panel.scss b/ui/app/styles/components/job-status-panel.scss index 7b04a34b4f1..17d2602f323 100644 --- a/ui/app/styles/components/job-status-panel.scss +++ b/ui/app/styles/components/job-status-panel.scss @@ -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); + } + } + & > div { gap: 0.5rem; } @@ -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); + } +} diff --git a/ui/mirage/factories/task-event.js b/ui/mirage/factories/task-event.js index 0bbd8ae8a7a..bb426cec56a 100644 --- a/ui/mirage/factories/task-event.js +++ b/ui/mirage/factories/task-event.js @@ -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(), }); diff --git a/ui/tests/acceptance/allocation-detail-test.js b/ui/tests/acceptance/allocation-detail-test.js index cf31ddccbcd..564c8cb3eac 100644 --- a/ui/tests/acceptance/allocation-detail-test.js +++ b/ui/tests/acceptance/allocation-detail-test.js @@ -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"), diff --git a/ui/tests/acceptance/job-status-panel-test.js b/ui/tests/acceptance/job-status-panel-test.js index e27b3f4fdca..9fa1b94a7d3 100644 --- a/ui/tests/acceptance/job-status-panel-test.js +++ b/ui/tests/acceptance/job-status-panel-test.js @@ -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'; @@ -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'); + }); + }); }); diff --git a/ui/tests/acceptance/task-detail-test.js b/ui/tests/acceptance/task-detail-test.js index 6e4a22d09ba..b9418fc8617 100644 --- a/ui/tests/acceptance/task-detail-test.js +++ b/ui/tests/acceptance/task-detail-test.js @@ -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) { diff --git a/ui/tests/integration/components/job-page/service-test.js b/ui/tests/integration/components/job-page/service-test.js index 03edf640238..f90709e5b92 100644 --- a/ui/tests/integration/components/job-page/service-test.js +++ b/ui/tests/integration/components/job-page/service-test.js @@ -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]'); @@ -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]'); diff --git a/ui/tests/integration/components/job-status-panel-test.js b/ui/tests/integration/components/job-status-panel-test.js index f321083d1ba..da930f277c9 100644 --- a/ui/tests/integration/components/job-status-panel-test.js +++ b/ui/tests/integration/components/job-status-panel-test.js @@ -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) {