mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
* [ui] Service job status panel (#16134) * it begins * Hacky demo enabled * Still very hacky but seems deece * Floor of at least 3 must be shown * Width from on-high * Other statuses considered * More sensible allocTypes listing * Beginnings of a legend * Total number of allocs running now maps over job.groups * Lintfix * base the number of slots to hold open on actual tallies, which should never exceed totalAllocs * Versions get yer versions here * Versions lookin like versions * Mirage fixup * Adds Remaining as an alloc chart status and adds historical status option * Get tests passing again by making job status static for a sec * Historical status panel click actions moved into their own component class * job detail tests plz chill * Testing if percy is fickle * Hyper-specfic on summary distribution bar identifier * Perhaps the 2nd allocSummary item no longer exists with the more accurate afterCreate data * UI Test eschewing the page pattern * Bones of a new acceptance test * Track width changes explicitly with window-resize * testlintfix * Alloc counting tests * Alloc grouping test * Alloc grouping with complex resizing * Refined the list of showable statuses * PR feedback addressed * renamed allocation-row to allocation-status-row * [ui, job status] Make panel status mode a queryParam (#16345) * queryParam changing * Test for QP in panel * Adding @tracked to legacy controller * Move the job of switching to Historical out to larger context * integration test mock passed func * [ui] Service job deployment status panel (#16383) * 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 * changelog
361 lines
10 KiB
JavaScript
361 lines
10 KiB
JavaScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*/
|
|
|
|
import { assign } from '@ember/polyfills';
|
|
import { module, test } from 'qunit';
|
|
import { setupRenderingTest } from 'ember-qunit';
|
|
import { click, find, render } from '@ember/test-helpers';
|
|
import hbs from 'htmlbars-inline-precompile';
|
|
import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
|
|
import {
|
|
startJob,
|
|
stopJob,
|
|
purgeJob,
|
|
expectError,
|
|
expectDeleteRequest,
|
|
expectStartRequest,
|
|
expectPurgeRequest,
|
|
} from './helpers';
|
|
import Job from 'nomad-ui/tests/pages/jobs/detail';
|
|
import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer';
|
|
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
|
|
|
|
module('Integration | Component | job-page/service', function (hooks) {
|
|
setupRenderingTest(hooks);
|
|
|
|
hooks.beforeEach(function () {
|
|
fragmentSerializerInitializer(this.owner);
|
|
window.localStorage.clear();
|
|
this.store = this.owner.lookup('service:store');
|
|
this.server = startMirage();
|
|
this.server.create('namespace');
|
|
});
|
|
|
|
hooks.afterEach(function () {
|
|
this.server.shutdown();
|
|
window.localStorage.clear();
|
|
});
|
|
|
|
const commonTemplate = hbs`
|
|
<JobPage::Service
|
|
@job={{job}}
|
|
@sortProperty={{sortProperty}}
|
|
@sortDescending={{sortDescending}}
|
|
@currentPage={{currentPage}}
|
|
@gotoJob={{gotoJob}}
|
|
@statusMode={{statusMode}}
|
|
@setStatusMode={{setStatusMode}}
|
|
/>
|
|
`;
|
|
|
|
const commonProperties = (job) => ({
|
|
job,
|
|
sortProperty: 'name',
|
|
sortDescending: true,
|
|
currentPage: 1,
|
|
gotoJob() {},
|
|
statusMode: 'current',
|
|
setStatusMode() {},
|
|
});
|
|
|
|
const makeMirageJob = (server, props = {}) =>
|
|
server.create(
|
|
'job',
|
|
assign(
|
|
{
|
|
type: 'service',
|
|
createAllocations: false,
|
|
status: 'running',
|
|
},
|
|
props
|
|
)
|
|
);
|
|
|
|
test('Stopping a job sends a delete request for the job', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
const mirageJob = makeMirageJob(this.server);
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await stopJob();
|
|
expectDeleteRequest(assert, this.server, job);
|
|
});
|
|
|
|
test('Stopping a job without proper permissions shows an error message', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
this.server.pretender.delete('/v1/job/:id', () => [403, {}, '']);
|
|
|
|
const mirageJob = makeMirageJob(this.server);
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await stopJob();
|
|
expectError(assert, 'Could Not Stop Job');
|
|
|
|
await componentA11yAudit(this.element, assert);
|
|
});
|
|
|
|
test('Starting a job sends a post request for the job using the current definition', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
const mirageJob = makeMirageJob(this.server, { status: 'dead' });
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await startJob();
|
|
expectStartRequest(assert, this.server, job);
|
|
});
|
|
|
|
test('Starting a job without proper permissions shows an error message', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
this.server.pretender.post('/v1/job/:id', () => [403, {}, '']);
|
|
|
|
const mirageJob = makeMirageJob(this.server, { status: 'dead' });
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await startJob();
|
|
|
|
await expectError(assert, 'Could Not Start Job');
|
|
});
|
|
|
|
test('Purging a job sends a purge request for the job', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
const mirageJob = makeMirageJob(this.server, { status: 'dead' });
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await purgeJob();
|
|
expectPurgeRequest(assert, this.server, job);
|
|
});
|
|
|
|
test('Recent allocations shows allocations in the job context', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
this.server.create('node');
|
|
const mirageJob = makeMirageJob(this.server, { createAllocations: true });
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
const allocation = this.server.db.allocations
|
|
.sortBy('modifyIndex')
|
|
.reverse()[0];
|
|
const allocationRow = Job.allocations.objectAt(0);
|
|
|
|
assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'ID');
|
|
assert.equal(
|
|
allocationRow.taskGroup,
|
|
allocation.taskGroup,
|
|
'Task Group name'
|
|
);
|
|
|
|
await componentA11yAudit(this.element, assert);
|
|
});
|
|
|
|
test('Recent allocations caps out at five', async function (assert) {
|
|
this.server.create('node');
|
|
const mirageJob = makeMirageJob(this.server);
|
|
this.server.createList('allocation', 10);
|
|
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
assert.equal(Job.allocations.length, 5, 'Capped at 5 allocations');
|
|
assert.ok(
|
|
Job.viewAllAllocations.includes(job.get('allocations.length') + ''),
|
|
`View link mentions ${job.get('allocations.length')} allocations`
|
|
);
|
|
});
|
|
|
|
test('Recent allocations shows an empty message when the job has no allocations', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
this.server.create('node');
|
|
const mirageJob = makeMirageJob(this.server);
|
|
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
assert.ok(
|
|
Job.recentAllocationsEmptyState.headline.includes('No Allocations'),
|
|
'No allocations empty message'
|
|
);
|
|
|
|
await componentA11yAudit(this.element, assert);
|
|
});
|
|
|
|
test('Active deployment can be promoted', async function (assert) {
|
|
this.server.create('node');
|
|
const mirageJob = makeMirageJob(this.server, { activeDeployment: true });
|
|
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
const deployment = await job.get('latestDeployment');
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await click('[data-test-promote-canary]');
|
|
|
|
const requests = this.server.pretender.handledRequests;
|
|
|
|
assert.ok(
|
|
requests
|
|
.filterBy('method', 'POST')
|
|
.findBy('url', `/v1/deployment/promote/${deployment.get('id')}`),
|
|
'A promote POST request was made'
|
|
);
|
|
});
|
|
|
|
test('When promoting the active deployment fails, an error is shown', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
this.server.pretender.post('/v1/deployment/promote/:id', () => [
|
|
403,
|
|
{},
|
|
'',
|
|
]);
|
|
|
|
this.server.create('node');
|
|
const mirageJob = makeMirageJob(this.server, { activeDeployment: true });
|
|
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await click('[data-test-promote-canary]');
|
|
|
|
assert.equal(
|
|
find('[data-test-job-error-title]').textContent,
|
|
'Could Not Promote Deployment',
|
|
'Appropriate error is shown'
|
|
);
|
|
assert.ok(
|
|
find('[data-test-job-error-body]').textContent.includes('ACL'),
|
|
'The error message mentions ACLs'
|
|
);
|
|
|
|
await componentA11yAudit(
|
|
this.element,
|
|
assert,
|
|
'scrollable-region-focusable'
|
|
); //keyframe animation fades from opacity 0
|
|
|
|
await click('[data-test-job-error-close]');
|
|
|
|
assert.notOk(
|
|
find('[data-test-job-error-title]'),
|
|
'Error message is dismissable'
|
|
);
|
|
});
|
|
|
|
test('Active deployment can be failed', async function (assert) {
|
|
this.server.create('node');
|
|
const mirageJob = makeMirageJob(this.server, { activeDeployment: true });
|
|
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
const deployment = await job.get('latestDeployment');
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await click('[data-test-active-deployment] [data-test-idle-button]');
|
|
await click('[data-test-active-deployment] [data-test-confirm-button]');
|
|
|
|
const requests = this.server.pretender.handledRequests;
|
|
|
|
assert.ok(
|
|
requests
|
|
.filterBy('method', 'POST')
|
|
.findBy('url', `/v1/deployment/fail/${deployment.get('id')}`),
|
|
'A fail POST request was made'
|
|
);
|
|
});
|
|
|
|
test('When failing the active deployment fails, an error is shown', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
this.server.pretender.post('/v1/deployment/fail/:id', () => [403, {}, '']);
|
|
|
|
this.server.create('node');
|
|
const mirageJob = makeMirageJob(this.server, { activeDeployment: true });
|
|
|
|
await this.store.findAll('job');
|
|
|
|
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
|
|
|
this.setProperties(commonProperties(job));
|
|
await render(commonTemplate);
|
|
|
|
await click('[data-test-active-deployment] [data-test-idle-button]');
|
|
await click('[data-test-active-deployment] [data-test-confirm-button]');
|
|
|
|
assert.equal(
|
|
find('[data-test-job-error-title]').textContent,
|
|
'Could Not Fail Deployment',
|
|
'Appropriate error is shown'
|
|
);
|
|
assert.ok(
|
|
find('[data-test-job-error-body]').textContent.includes('ACL'),
|
|
'The error message mentions ACLs'
|
|
);
|
|
|
|
await componentA11yAudit(
|
|
this.element,
|
|
assert,
|
|
'scrollable-region-focusable'
|
|
); //keyframe animation fades from opacity 0
|
|
|
|
await click('[data-test-job-error-close]');
|
|
|
|
assert.notOk(
|
|
find('[data-test-job-error-title]'),
|
|
'Error message is dismissable'
|
|
);
|
|
});
|
|
});
|