Skip to content

Commit

Permalink
[ui] Service job deployment status panel (#16383)
Browse files Browse the repository at this point in the history
* A very fast and loose deployment panel

* Removing Unknown status from the panel

* Set up oldAllocs list in constructor, rather than as a getter/tracked var

* Small amount of template cleanup

* Refactored latest-deployment new logic back into panel.js

* Revert now-unused latest-deployment component

* margin bottom when ungrouped also

* Basic integration tests for job deployment status panel

* Updates complete alloc colour to green for new visualizations only (#16618)

* Updates complete alloc colour to green for new visualizations only

* Pale green instead of dark green for viz in general

* [ui] Job Deployment Status: History and Update Props (#16518)

* Deployment history wooooooo

* Styled deployment history

* Update Params

* lintfix

* Types and groups for updateParams

* Live-updating history

* Harden with types, error states, and pending states

* Refactor updateParams to use trigger component

* [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

* Split panel into deploying and steady components

* HandleError passed from job index

* gridified panel elements

* TotalAllocs added to deploying.js

* Width perc to px

* [ui] Splitting deployment allocs by status, health, and canary status (#16766)

* Initial attempt with lots of scratchpad work

* Style mods per UI discussion

* Fix canary overflow bug

* Dont show canary or health for steady/prev-alloc blocks

* Steady state

* Thanks Julie

* Fixes steady-state versions

* Legen, wait for it...

* Test fixes now that we have a minimum block size

* PR prep

* Shimmer effect on pending and unplaced allocs (#16801)

* Shimmer effect on pending and unplaced

* Dont show animation in the legend

* [ui, deployments] Linking allocblocks and legends to allocation / allocations index routes (#16821)

* Conditional link-to component and basic linking to allocations and allocation routes

* Job versions filter added to allocations index page

* Steady state legends link

* Legend links

* Badge count links for versions

* Fix: faded class on steady-state legend items

* version link now wont show completed ones

* Fix a11y violations with link labels

* Combining some template conditional logic

* [ui, deployments] Conversions on long nanosecond update params (#16882)

* Conversions on long nanosecond nums

* Early return in updateParamGroups comp prop

* [ui, deployments] Mirage Actively Deploying Job and Deployment Integration Tests (#16888)

* Start of deployment alloc test scaffolding

* Bit of test cleanup and canary for ungrouped allocs

* Flakey but more robust integrations for deployment panel

* De-flake acceptance tests and add an actively deploying job to mirage

* Jitter-less alloc status distribution removes my bad math

* bugfix caused by summary.desiredTotal non-null

* More interesting mirage active deployment alloc breakdown

* Further tests for previous-allocs row

* Previous alloc legend tests

* Percy snapshots added to integration test
  • Loading branch information
philrenaud committed Apr 19, 2023
1 parent 45e9761 commit 5458697
Show file tree
Hide file tree
Showing 33 changed files with 1,871 additions and 250 deletions.
9 changes: 9 additions & 0 deletions ui/app/components/conditional-link-to.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{#if @condition}}
<LinkTo @route={{@route}} @model={{@model}} @query={{this.query}} class={{@class}} aria-label={{@label}}>
{{yield}}
</LinkTo>
{{else}}
<span class={{@class}}>
{{yield}}
</span>
{{/if}}
7 changes: 7 additions & 0 deletions ui/app/components/conditional-link-to.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Component from '@glimmer/component';

export default class ConditionalLinkToComponent extends Component {
get query() {
return this.args.query || {};
}
}
53 changes: 47 additions & 6 deletions ui/app/components/job-status/allocation-status-block.hbs
Original file line number Diff line number Diff line change
@@ -1,17 +1,58 @@
<div
class="allocation-status-block {{unless this.countToShow "rest-only"}}"
style={{html-safe (concat "width: " @width "%")}}
style={{html-safe (concat "width: " @width "px")}}
>
{{#if this.countToShow}}
<div class="ungrouped-allocs">
{{#each (range 0 this.countToShow)}}
<span class="represented-allocation {{@status}}" />
{{#each (range 0 this.countToShow) as |i|}}
<ConditionalLinkTo
@condition={{not (eq @status "unplaced")}}
@route="allocations.allocation"
@model={{get @allocs i}}
@class="represented-allocation {{@status}} {{@health}} {{@canary}}"
@label="View allocation"
>
{{#if (and (eq @status "running") (not @steady))}}
{{#if (eq @canary "canary")}}
<span class="alloc-canary-indicator" />
{{/if}}
<span class="alloc-health-indicator">
{{#if (eq @health "healthy")}}
<FlightIcon @name="check" @color="white" />
{{else}}
<FlightIcon @name="running" @color="white" />
{{/if}}
</span>
{{/if}}
</ConditionalLinkTo>
{{/each}}
</div>
{{/if}}
{{#if this.remaining}}
<span class="represented-allocation rest {{@status}}">
{{#if this.countToShow}}+{{/if}}{{this.remaining}}
</span>

<ConditionalLinkTo
@condition={{not (eq @status "unplaced")}}
@route="jobs.job.allocations"
@model={{@allocs.0.job}}
@query={{hash status=(concat '["' @status '"]') version=(concat '[' @allocs.0.jobVersion ']')}}
@class="represented-allocation rest {{@status}} {{@health}} {{@canary}}"
@label="View all {{@status}} allocations"
>
<span class="rest-count">{{#if this.countToShow}}+{{/if}}{{this.remaining}}</span>
{{#unless @steady}}
{{#if (eq @canary "canary")}}
<span class="alloc-canary-indicator" />
{{/if}}
{{#if (eq @status "running")}}
<span class="alloc-health-indicator">
{{#if (eq @health "healthy")}}
<FlightIcon @name="check" @color="#25ba81" />
{{else}}
<FlightIcon @name="running" @color="black" />
{{/if}}
</span>
{{/if}}
{{/unless}}
</ConditionalLinkTo>
{{/if}}
</div>
86 changes: 64 additions & 22 deletions ui/app/components/job-status/allocation-status-row.hbs
Original file line number Diff line number Diff line change
@@ -1,22 +1,64 @@
{{#if this.showSummaries}}
<div class="alloc-status-summaries"
{{did-insert this.captureElement}}
{{window-resize this.reflow}}
>
{{#each-in @allocBlocks as |status allocs|}}
{{#if (gt allocs.length 0)}}
<JobStatus::AllocationStatusBlock @status={{status}} @count={{allocs.length}} @width={{compute (action this.calcPerc) allocs.length}} />
{{/if}}
{{/each-in}}
</div>
{{else}}
<div class="ungrouped-allocs">
{{#each-in @allocBlocks as |status allocs|}}
{{#if (gt allocs.length 0)}}
{{#each (range 0 allocs.length)}}
<span class="represented-allocation {{status}}"></span>
{{/each}}
{{/if}}
{{/each-in}}
</div>
{{/if}}
<div class="allocation-status-row">
{{#if this.showSummaries}}
<div class="alloc-status-summaries"
{{did-insert this.captureElement}}
{{window-resize this.reflow}}
>
{{#each-in @allocBlocks as |status allocsByStatus|}}
{{#each-in allocsByStatus as |health allocsByHealth|}}
{{#each-in allocsByHealth as |canary allocsByCanary|}}
{{#if (gt allocsByCanary.length 0)}}
<JobStatus::AllocationStatusBlock
@status={{status}}
@health={{health}}
@canary={{canary}}
@steady={{@steady}}
@count={{allocsByCanary.length}}
@width={{compute (action this.calcPerc) allocsByCanary.length}}
@allocs={{allocsByCanary}} />
{{/if}}
{{/each-in}}
{{/each-in}}
{{/each-in}}
</div>
{{else}}
<div class="ungrouped-allocs"
{{did-insert this.captureElement}}
{{window-resize this.reflow}}
>
{{#each-in @allocBlocks as |status allocsByStatus|}}
{{#each-in allocsByStatus as |health allocsByHealth|}}
{{#each-in allocsByHealth as |canary allocsByCanary|}}
{{#if (gt allocsByCanary.length 0)}}
{{#each (range 0 allocsByCanary.length) as |i|}}
<ConditionalLinkTo
@condition={{not (eq status "unplaced")}}
@route="allocations.allocation"
@model={{get allocsByCanary i}}
@class="represented-allocation {{status}} {{health}} {{canary}}"
@label="View allocation"
>
{{#unless @steady}}
{{#if (eq canary "canary")}}
<span class="alloc-canary-indicator" />
{{/if}}
{{#if (eq status "running")}}
<span class="alloc-health-indicator">
{{#if (eq health "healthy")}}
<FlightIcon @name="check" @color="white" />
{{else}}
<FlightIcon @name="running" @color="white" />
{{/if}}
</span>
{{/if}}
{{/unless}}
</ConditionalLinkTo>
{{/each}}
{{/if}}
{{/each-in}}
{{/each-in}}
{{/each-in}}
</div>
{{/if}}
</div>

22 changes: 16 additions & 6 deletions ui/app/components/job-status/allocation-status-row.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
// @ts-check
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

const UNGROUPED_ALLOCS_THRESHOLD = 50;
const ALLOC_BLOCK_WIDTH = 32;
const ALLOC_BLOCK_GAP = 10;

export default class JobStatusAllocationStatusRowComponent extends Component {
@tracked width = 0;

get allocBlockSlots() {
return Object.values(this.args.allocBlocks).reduce(
(m, n) => m + n.length,
0
);
return Object.values(this.args.allocBlocks)
.flatMap((statusObj) => Object.values(statusObj))
.flatMap((healthObj) => Object.values(healthObj))
.reduce(
(totalSlots, allocsByCanary) =>
totalSlots + (allocsByCanary ? allocsByCanary.length : 0),
0
);
}

get showSummaries() {
return this.allocBlockSlots > UNGROUPED_ALLOCS_THRESHOLD;
return (
this.allocBlockSlots * (ALLOC_BLOCK_WIDTH + ALLOC_BLOCK_GAP) -
ALLOC_BLOCK_GAP >
this.width
);
}

calcPerc(count) {
Expand Down
53 changes: 53 additions & 0 deletions ui/app/components/job-status/deployment-history.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<div class="deployment-history">
<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"}}">
<div class="boxed-section-head is-light">
<LinkTo @route="allocations.allocation" @model={{deployment-log.state.allocation}} class="allocation-reference">{{deployment-log.state.allocation.shortId}}</LinkTo>
<span><strong>{{deployment-log.type}}:</strong> {{deployment-log.message}}</span>
<span class="pull-right">
{{format-ts deployment-log.time}}
</span>
</div>
</li>
{{else}}
{{#if this.errorState}}
<li class="timeline-object">
<div class="boxed-section-head is-light">
<span>Error loading deployment history</span>
</div>
</li>
{{else}}
{{#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">
<span>Loading deployment events</span>
</div>
</li>
{{/if}}
{{/if}}
{{/each}}
</ol>
</div>
96 changes: 96 additions & 0 deletions ui/app/components/job-status/deployment-history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @ts-check
import Component from '@glimmer/component';
import { alias } from '@ember/object/computed';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class JobStatusDeploymentHistoryComponent extends Component {
@service notifications;

/**
* @type { Error }
*/
@tracked errorState = null;

/**
* @type { import('../../models/job').default }
*/
@alias('args.deployment.job') job;

/**
* @type { number }
*/
@alias('args.deployment.versionNumber') deploymentVersion;

/**
* Get all allocations for the job
* @type { import('../../models/allocation').default[] }
*/
get jobAllocations() {
return this.job.get('allocations');
}

/**
* Filter the job's allocations to only those that are part of the deployment
* @type { import('../../models/allocation').default[] }
*/
get deploymentAllocations() {
return this.jobAllocations.filter(
(alloc) => alloc.jobVersion === this.deploymentVersion
);
}

/**
* Map the deployment's allocations to their task events, in reverse-chronological order
* @type { import('../../models/task-event').default[] }
*/
get history() {
try {
return this.deploymentAllocations
.map((a) =>
a
.get('states')
.map((s) => s.events.content)
.flat()
)
.flat()
.filter((a) => this.containsSearchTerm(a))
.sort((a, b) => a.get('time') - b.get('time'))
.reverse();
} catch (e) {
this.triggerError(e);
return [];
}
}

@action triggerError(error) {
this.errorState = error;
this.notifications.add({
title: 'Could not fetch deployment history',
message: error,
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
}

0 comments on commit 5458697

Please sign in to comment.