Added task links to various alloc tables (#14592)
* Added task links to various alloc tables * Lintfix * Border collapse and added to task group page * Logs icon temporarily removed and localStorage added * Mock task added to test * Delog * Two asserts in new test * Remove commented-out code * Changelog * Removing args.allocation deps
This commit is contained in:
parent
bf0e83e3a0
commit
d6c9676252
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: allow deep-dive clicks to tasks from client, job, and task group routes.
|
||||
```
|
|
@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
|
|||
import PromiseArray from 'nomad-ui/utils/classes/promise-array';
|
||||
import { classNames } from '@ember-decorators/component';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import localStorageProperty from 'nomad-ui/utils/properties/local-storage';
|
||||
|
||||
@classic
|
||||
@classNames('boxed-section')
|
||||
|
@ -13,6 +14,14 @@ export default class RecentAllocations extends Component {
|
|||
sortProperty = 'modifyIndex';
|
||||
sortDescending = true;
|
||||
|
||||
@localStorageProperty('nomadShowSubTasks', true) showSubTasks;
|
||||
|
||||
@action
|
||||
toggleShowSubTasks(e) {
|
||||
e.preventDefault();
|
||||
this.set('showSubTasks', !this.get('showSubTasks'));
|
||||
}
|
||||
|
||||
@computed('job.allocations.@each.modifyIndex')
|
||||
get sortedAllocations() {
|
||||
return PromiseArray.create({
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<tr class="task-sub-row"
|
||||
{{keyboard-shortcut
|
||||
enumerated=true
|
||||
action=(action "gotoTask" this.task.allocation this.task)
|
||||
}}
|
||||
>
|
||||
<td colspan={{@namespan}}>
|
||||
/
|
||||
<LinkTo @route="allocations.allocation.task" @models={{array this.task.allocation this.task}}>
|
||||
{{this.task.name}}
|
||||
</LinkTo>
|
||||
{{!-- TODO: in-page logs --}}
|
||||
{{!-- <FlightIcon @name="logs" /> --}}
|
||||
</td>
|
||||
<td data-test-cpu class="is-1 has-text-centered">
|
||||
{{#if this.task.isRunning}}
|
||||
{{#if (and (not this.cpu) this.fetchStats.isRunning)}}
|
||||
...
|
||||
{{else if this.statsError}}
|
||||
<span
|
||||
class="tooltip text-center"
|
||||
role="tooltip"
|
||||
aria-label="Couldn't collect stats"
|
||||
>
|
||||
{{x-icon "alert-triangle" class="is-warning"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<div
|
||||
class="inline-chart is-small tooltip"
|
||||
role="tooltip"
|
||||
aria-label="{{format-hertz this.cpu.used}}
|
||||
/
|
||||
{{format-hertz this.taskStats.reservedCPU}}"
|
||||
>
|
||||
<progress
|
||||
class="progress is-info is-small"
|
||||
value="{{this.cpu.percent}}"
|
||||
max="1"
|
||||
>
|
||||
{{this.cpu.percent}}
|
||||
</progress>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-test-mem class="is-1 has-text-centered">
|
||||
{{#if this.task.isRunning}}
|
||||
{{#if (and (not this.memory) this.fetchStats.isRunning)}}
|
||||
...
|
||||
{{else if this.statsError}}
|
||||
<span
|
||||
class="tooltip is-small text-center"
|
||||
role="tooltip"
|
||||
aria-label="Couldn't collect stats"
|
||||
>
|
||||
{{x-icon "alert-triangle" class="is-warning"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<div
|
||||
class="inline-chart tooltip"
|
||||
role="tooltip"
|
||||
aria-label="{{format-bytes this.memory.used}}
|
||||
/
|
||||
{{format-bytes this.taskStats.reservedMemory start="MiB"}}"
|
||||
>
|
||||
<progress
|
||||
class="progress is-danger is-small"
|
||||
value="{{this.memory.percent}}"
|
||||
max="1"
|
||||
>
|
||||
{{this.memory.percent}}
|
||||
</progress>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{yield}}
|
|
@ -0,0 +1,74 @@
|
|||
import Ember from 'ember';
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { task, timeout } from 'ember-concurrency';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class TaskSubRowComponent extends Component {
|
||||
@service store;
|
||||
@service router;
|
||||
@service('stats-trackers-registry') statsTrackersRegistry;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
// Kick off stats polling
|
||||
const allocation = this.task.allocation;
|
||||
if (allocation) {
|
||||
this.fetchStats.perform();
|
||||
} else {
|
||||
this.fetchStats.cancelAll();
|
||||
}
|
||||
}
|
||||
|
||||
@alias('args.taskState') task;
|
||||
|
||||
@action
|
||||
gotoTask(allocation, task) {
|
||||
this.router.transitionTo('allocations.allocation.task', allocation, task);
|
||||
}
|
||||
|
||||
// Since all tasks for an allocation share the same tracker, use the registry
|
||||
@computed('task.{allocation,isRunning}')
|
||||
get stats() {
|
||||
if (!this.task.isRunning) return undefined;
|
||||
|
||||
return this.statsTrackersRegistry.getTracker(this.task.allocation);
|
||||
}
|
||||
|
||||
// Internal state
|
||||
@tracked statsError = false;
|
||||
|
||||
@computed
|
||||
get enablePolling() {
|
||||
return !Ember.testing;
|
||||
}
|
||||
|
||||
@computed('task.name', 'stats.tasks.[]')
|
||||
get taskStats() {
|
||||
if (!this.stats) return undefined;
|
||||
|
||||
return this.stats.tasks.findBy('task', this.task.name);
|
||||
}
|
||||
|
||||
@alias('taskStats.cpu.lastObject') cpu;
|
||||
@alias('taskStats.memory.lastObject') memory;
|
||||
|
||||
@(task(function* () {
|
||||
do {
|
||||
if (this.stats) {
|
||||
try {
|
||||
yield this.stats.poll.linked().perform();
|
||||
this.statsError = false;
|
||||
} catch (error) {
|
||||
this.statsError = true;
|
||||
}
|
||||
}
|
||||
|
||||
yield timeout(500);
|
||||
} while (this.enablePolling);
|
||||
}).drop())
|
||||
fetchStats;
|
||||
}
|
|
@ -15,6 +15,7 @@ import {
|
|||
deserializedQueryParam as selection,
|
||||
} from 'nomad-ui/utils/qp-serialize';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import localStorageProperty from 'nomad-ui/utils/properties/local-storage';
|
||||
|
||||
@classic
|
||||
export default class ClientController extends Controller.extend(
|
||||
|
@ -60,6 +61,14 @@ export default class ClientController extends Controller.extend(
|
|||
sortProperty = 'modifyIndex';
|
||||
sortDescending = true;
|
||||
|
||||
@localStorageProperty('nomadShowSubTasks', false) showSubTasks;
|
||||
|
||||
@action
|
||||
toggleShowSubTasks(e) {
|
||||
e.preventDefault();
|
||||
this.set('showSubTasks', !this.get('showSubTasks'));
|
||||
}
|
||||
|
||||
@computed()
|
||||
get searchProps() {
|
||||
return ['shortId', 'name'];
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
deserializedQueryParam as selection,
|
||||
} from 'nomad-ui/utils/qp-serialize';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import localStorageProperty from 'nomad-ui/utils/properties/local-storage';
|
||||
|
||||
@classic
|
||||
export default class TaskGroupController extends Controller.extend(
|
||||
|
@ -57,6 +58,14 @@ export default class TaskGroupController extends Controller.extend(
|
|||
return ['shortId', 'name'];
|
||||
}
|
||||
|
||||
@localStorageProperty('nomadShowSubTasks', true) showSubTasks;
|
||||
|
||||
@action
|
||||
toggleShowSubTasks(e) {
|
||||
e.preventDefault();
|
||||
this.set('showSubTasks', !this.get('showSubTasks'));
|
||||
}
|
||||
|
||||
@computed('model.allocations.[]')
|
||||
get allocations() {
|
||||
return this.get('model.allocations') || [];
|
||||
|
|
|
@ -49,3 +49,4 @@
|
|||
@import './components/variables';
|
||||
@import './components/keyboard-shortcuts-modal';
|
||||
@import './components/services';
|
||||
@import './components/task-sub-row';
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
table tbody .task-sub-row {
|
||||
td {
|
||||
border-top: 2px solid white;
|
||||
}
|
||||
td:nth-child(1) {
|
||||
padding-left: 4rem;
|
||||
a {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
svg.flight-icon {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.with-collapsed-borders {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
&.is-darkened {
|
||||
tbody tr:not(.is-selected) {
|
||||
background-color: $white-bis;
|
||||
|
|
|
@ -496,6 +496,17 @@
|
|||
@inputClass="is-compact"
|
||||
@class="is-padded"
|
||||
/>
|
||||
|
||||
<span class="is-padded">
|
||||
<Toggle
|
||||
class="button is-borderless is-inline"
|
||||
@isActive={{this.showSubTasks}}
|
||||
@onToggle={{this.toggleShowSubTasks}}
|
||||
title="Show tasks of allocations"
|
||||
>
|
||||
Show Tasks
|
||||
</Toggle>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -512,7 +523,7 @@
|
|||
@source={{p.list}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|
|
||||
@class="with-foot {{if this.showSubTasks "with-collapsed-borders"}}" as |t|
|
||||
>
|
||||
<t.head>
|
||||
<th class="is-narrow"></th>
|
||||
|
@ -555,6 +566,11 @@
|
|||
@onClick={{action "gotoAllocation" row.model}}
|
||||
@data-test-allocation={{row.model.id}}
|
||||
/>
|
||||
{{#if this.showSubTasks}}
|
||||
{{#each row.model.states as |task|}}
|
||||
<TaskSubRow @namespan="8" @taskState={{task}} />
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</t.body>
|
||||
</ListTable>
|
||||
<div class="table-foot">
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
Recent Allocations
|
||||
<span class="pull-right is-padded">
|
||||
<Toggle
|
||||
class="button is-borderless is-inline"
|
||||
@isActive={{this.showSubTasks}}
|
||||
@onToggle={{this.toggleShowSubTasks}}
|
||||
title="Show tasks of allocations"
|
||||
>Show Tasks</Toggle>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="boxed-section-body
|
||||
|
@ -11,7 +19,7 @@
|
|||
@source={{this.sortedAllocations}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|
|
||||
@class="with-foot {{if this.showSubTasks "with-collapsed-borders"}}" as |t|
|
||||
>
|
||||
<t.head>
|
||||
<th class="is-narrow"></th>
|
||||
|
@ -52,11 +60,18 @@
|
|||
@allocation={{row.model}}
|
||||
@context="job"
|
||||
@onClick={{action "gotoAllocation" row.model}}
|
||||
@showSubTasks={{this.showSubTasks}}
|
||||
{{keyboard-shortcut
|
||||
enumerated=true
|
||||
action=(action "gotoAllocation" row.model)
|
||||
}}
|
||||
/>
|
||||
|
||||
{{#if this.showSubTasks}}
|
||||
{{#each row.model.states as |task|}}
|
||||
<TaskSubRow @namespan="9" @taskState={{task}} />
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</t.body>
|
||||
</ListTable>
|
||||
{{else}}
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
@source={{p.list}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|>
|
||||
@class="with-foot with-collapsed-borders" as |t|>
|
||||
<t.head>
|
||||
<th class="is-narrow"></th>
|
||||
<t.sort-by @prop="shortId">ID</t.sort-by>
|
||||
|
@ -70,6 +70,10 @@
|
|||
@allocation={{row.model}}
|
||||
@context="job"
|
||||
@onClick={{action "gotoAllocation" row.model}} />
|
||||
{{#each row.model.states as |task|}}
|
||||
<TaskSubRow @namespan="9" @taskState={{task}} />
|
||||
{{/each}}
|
||||
|
||||
</t.body>
|
||||
</ListTable>
|
||||
<div class="table-foot">
|
||||
|
|
|
@ -149,6 +149,16 @@
|
|||
@class="is-padded"
|
||||
@inputClass="is-compact"
|
||||
/>
|
||||
<span class="is-padded">
|
||||
<Toggle
|
||||
class="button is-borderless is-inline"
|
||||
@isActive={{this.showSubTasks}}
|
||||
@onToggle={{this.toggleShowSubTasks}}
|
||||
title="Show tasks of allocations"
|
||||
>
|
||||
Show Tasks
|
||||
</Toggle>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
|
@ -163,7 +173,7 @@
|
|||
@source={{p.list}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|
|
||||
@class="with-foot {{if this.showSubTasks "with-collapsed-borders"}}" as |t|
|
||||
>
|
||||
<t.head>
|
||||
<th class="is-narrow"></th>
|
||||
|
@ -206,6 +216,11 @@
|
|||
@context="taskGroup"
|
||||
@onClick={{action "gotoAllocation" row.model}}
|
||||
/>
|
||||
{{#if this.showSubTasks}}
|
||||
{{#each row.model.states as |task|}}
|
||||
<TaskSubRow @namespan="8" @taskState={{task}} />
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</t.body>
|
||||
</ListTable>
|
||||
<div class="table-foot">
|
||||
|
|
|
@ -133,8 +133,8 @@
|
|||
"jsonlint": "^1.6.3",
|
||||
"lint-staged": "^11.2.6",
|
||||
"loader.js": "^4.7.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.intersection": "^4.4.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"morgan": "^1.3.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"pretender": "^3.0.1",
|
||||
|
@ -174,7 +174,7 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@hashicorp/ember-flight-icons": "^2.0.5",
|
||||
"@hashicorp/ember-flight-icons": "^2.0.12",
|
||||
"@percy/cli": "^1.6.1",
|
||||
"@percy/ember": "^3.0.0",
|
||||
"curved-arrows": "^0.1.0",
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
|
||||
|
||||
const mockTask = {
|
||||
name: 'another-server',
|
||||
state: 'running',
|
||||
startedAt: '2022-09-14T17:19:12.351Z',
|
||||
finishedAt: null,
|
||||
failed: false,
|
||||
resources: null,
|
||||
events: [
|
||||
{
|
||||
Type: 'Received',
|
||||
Signal: 0,
|
||||
ExitCode: 0,
|
||||
Time: '2022-09-14T17:19:11.919Z',
|
||||
TimeNanos: 156992,
|
||||
DisplayMessage: 'Task received by client',
|
||||
},
|
||||
{
|
||||
Type: 'Task Setup',
|
||||
Signal: 0,
|
||||
ExitCode: 0,
|
||||
Time: '2022-09-14T17:19:11.920Z',
|
||||
TimeNanos: 793088,
|
||||
DisplayMessage: 'Building Task Directory',
|
||||
},
|
||||
{
|
||||
Type: 'Started',
|
||||
Signal: 0,
|
||||
ExitCode: 0,
|
||||
Time: '2022-09-14T17:19:12.351Z',
|
||||
TimeNanos: 258112,
|
||||
DisplayMessage: 'Task started by client',
|
||||
},
|
||||
{
|
||||
Type: 'Alloc Unhealthy',
|
||||
Signal: 0,
|
||||
ExitCode: 0,
|
||||
Time: '2022-09-14T17:24:11.919Z',
|
||||
TimeNanos: 589120,
|
||||
DisplayMessage:
|
||||
'Task not running for min_healthy_time of 10s by healthy_deadline of 5m0s',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
module('Integration | Component | task-sub-row', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
test('it renders', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.set('task', mockTask);
|
||||
await render(hbs`<TaskSubRow @taskState={{this.task}} />`);
|
||||
assert.dom(this.element).hasText(`/ ${mockTask.name}`);
|
||||
await componentA11yAudit(this.element, assert);
|
||||
});
|
||||
});
|
18
ui/yarn.lock
18
ui/yarn.lock
|
@ -3095,19 +3095,19 @@
|
|||
resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-1.1.0.tgz#d6dbc7574774b238114582410e8fee0dc3532bdf"
|
||||
integrity sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A==
|
||||
|
||||
"@hashicorp/ember-flight-icons@^2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/ember-flight-icons/-/ember-flight-icons-2.0.5.tgz#a1edfdd24475ecd0cf07cd1f944e2e5bdb5e97cc"
|
||||
integrity sha512-PXNk1aRBjYSGeoB4e2ovOBm6RhGKE554XjW8leYYK+y9yorHhJNNwWRkwjhDRLYWikLhNmfwp6nAYOJWl/IOgw==
|
||||
"@hashicorp/ember-flight-icons@^2.0.12":
|
||||
version "2.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/ember-flight-icons/-/ember-flight-icons-2.0.12.tgz#788adf7a4fedc468d612d35b604255df948f4012"
|
||||
integrity sha512-8fHPGaSpMkr5dLWaruwbq9INwZCi2EyTof/TR/dL8PN4UbCuY+KXNqG0lLIKNGFFTj09B1cO303m5GUfKKDGKQ==
|
||||
dependencies:
|
||||
"@hashicorp/flight-icons" "^2.3.1"
|
||||
"@hashicorp/flight-icons" "^2.10.0"
|
||||
ember-cli-babel "^7.26.11"
|
||||
ember-cli-htmlbars "^6.0.1"
|
||||
|
||||
"@hashicorp/flight-icons@^2.3.1":
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/flight-icons/-/flight-icons-2.3.1.tgz#0b0dc259c0a4255c5613174db7192ab48523ca6f"
|
||||
integrity sha512-WGCMMixkmYCP5Dyz4QW7XjW4zDhIc7njkVVucoj7Iv7abtfgQDWwm05Ja2aBJTxFHiP4jat9w9cbGNgC6QHmZQ==
|
||||
"@hashicorp/flight-icons@^2.10.0":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/flight-icons/-/flight-icons-2.10.0.tgz#24b03043bacda16e505200e6591dfef896ddacf1"
|
||||
integrity sha512-jYUA0M6Tz+4RAudil+GW/fHbhZPcKCiIZZAguBDviqbLneMkMgPOBgbXWCGWsEQ1fJzP2cXbUaio8L0aQZPWQw==
|
||||
|
||||
"@hashicorp/structure-icons@^1.3.0":
|
||||
version "1.9.2"
|
||||
|
|
Loading…
Reference in New Issue