Files
nomad/ui/tests/integration/components/job-page/service-test.js
Phil Renaud e1e59455e4 [ui, feature] Job Page Redesign (#16932)
* [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
2023-04-24 22:45:39 -04:00

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'
);
});
});