diff --git a/ui/app/templates/components/placement-failure.hbs b/ui/app/templates/components/placement-failure.hbs
new file mode 100644
index 000000000..63f49a826
--- /dev/null
+++ b/ui/app/templates/components/placement-failure.hbs
@@ -0,0 +1,40 @@
+{{#if taskGroup.placementFailures}}
+ {{#with taskGroup.placementFailures as |failures|}}
+
{{#each model.taskGroups as |taskGroup|}}
- {{#if taskGroup.placementFailures}}
- {{#with taskGroup.placementFailures as |failures|}}
-
- {{taskGroup.name}}
- {{inc failures.coalescedFailures}} unplaced
-
-
- {{#if (eq failures.nodesEvaluated 0)}}
- - No nodes were eligible for evaluation
- {{/if}}
- {{#each-in failures.nodesAvailable as |datacenter available|}}
- {{#if (eq available 0)}}
- - No nodes are available in datacenter {{datacenter}}
- {{/if}}
- {{/each-in}}
- {{#each-in failures.classFiltered as |class count|}}
- - Class {{class}} filtered {{count}} {{pluralize "node" count}}
- {{/each-in}}
- {{#each-in failures.constraintFiltered as |constraint count|}}
- - Constraint
{{constraint}} filtered {{count}} {{pluralize "node" count}}
- {{/each-in}}
- {{#if failures.nodesExhausted}}
- - Resources exhausted on {{failures.nodesExhausted}} {{pluralize "node" failures.nodesExhausted}}
- {{/if}}
- {{#each-in failures.classExhausted as |class count|}}
- - Class {{class}} exhausted on {{count}} {{pluralize "node" count}}
- {{/each-in}}
- {{#each-in failures.dimensionExhausted as |dimension count|}}
- - Dimension {{dimension}} exhausted on {{count}} {{pluralize "node" count}}
- {{/each-in}}
- {{#each-in failures.quotaExhausted as |quota dimension|}}
- - Quota limit hit {{dimension}}
- {{/each-in}}
- {{#each-in failures.scores as |name score|}}
- - Score {{name}} = {{score}}
- {{/each-in}}
-
- {{/with}}
- {{/if}}
+ {{placement-failure taskGroup=taskGroup}}
{{/each}}
diff --git a/ui/tests/integration/job-diff-test.js b/ui/tests/integration/job-diff-test.js
index d1821e452..d311ca4f2 100644
--- a/ui/tests/integration/job-diff-test.js
+++ b/ui/tests/integration/job-diff-test.js
@@ -1,6 +1,7 @@
import { findAll, find } from 'ember-native-dom-helpers';
import { test, moduleForComponent } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
+import cleanWhitespace from '../utils/clean-whitespace';
moduleForComponent('job-diff', 'Integration | Component | job diff', {
integration: true,
@@ -192,10 +193,3 @@ function field(name, type, newVal, oldVal) {
Name: name,
};
}
-
-function cleanWhitespace(string) {
- return string
- .replace(/\n/g, '')
- .replace(/ +/g, ' ')
- .trim();
-}
diff --git a/ui/tests/integration/placement-failure-test.js b/ui/tests/integration/placement-failure-test.js
new file mode 100644
index 000000000..b43a1b553
--- /dev/null
+++ b/ui/tests/integration/placement-failure-test.js
@@ -0,0 +1,144 @@
+import { find, findAll } from 'ember-native-dom-helpers';
+import { test, moduleForComponent } from 'ember-qunit';
+import { assign } from '@ember/polyfills';
+import hbs from 'htmlbars-inline-precompile';
+import cleanWhitespace from '../utils/clean-whitespace';
+
+moduleForComponent('placement-failure', 'Integration | Component | placement failures', {
+ integration: true,
+});
+
+const commonTemplate = hbs`
+ {{placement-failure taskGroup=taskGroup}}
+`;
+
+test('should render the placement failure (basic render)', function(assert) {
+ const name = 'Placement Failure';
+ const failures = 11;
+ this.set(
+ 'taskGroup',
+ createFixture(
+ {
+ coalescedFailures: failures - 1
+ },
+ name
+ )
+ );
+
+ this.render(commonTemplate);
+
+ assert.equal(
+ cleanWhitespace(find('[data-test-placement-failure-task-group]').firstChild.wholeText),
+ name,
+ 'Title is rendered with the name of the placement failure'
+ );
+ assert.equal(
+ parseInt(find('[data-test-placement-failure-coalesced-failures]').textContent),
+ failures,
+ 'Title is rendered correctly with a count of unplaced'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-no-evaluated-nodes]').length,
+ 1,
+ 'No evaluated nodes message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-no-nodes-available]').length,
+ 1,
+ 'No nodes in datacenter message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-class-filtered]').length,
+ 1,
+ 'Class filtered message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-constraint-filtered]').length,
+ 1,
+ 'Constraint filtered message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-nodes-exhausted]').length,
+ 1,
+ 'Node exhausted message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-class-exhausted]').length,
+ 1,
+ 'Class exhausted message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-dimension-exhausted]').length,
+ 1,
+ 'Dimension exhausted message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-quota-exhausted]').length,
+ 1,
+ 'Quota exhausted message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-scores]').length,
+ 1,
+ 'Scores message shown'
+ );
+});
+
+test('should render correctly when a node is not evaluated', function(assert) {
+ this.set(
+ 'taskGroup',
+ createFixture(
+ {
+ nodesEvaluated: 1,
+ nodesExhausted: 0
+ }
+ )
+ );
+
+ this.render(commonTemplate);
+
+ assert.equal(
+ findAll('[data-test-placement-failure-no-evaluated-nodes]').length,
+ 0,
+ 'No evaluated nodes message shown'
+ );
+ assert.equal(
+ findAll('[data-test-placement-failure-nodes-exhausted]').length,
+ 0,
+ 'Nodes exhausted message NOT shown when there are no nodes exausted'
+ );
+});
+
+function createFixture(obj = {}, name = 'Placement Failure') {
+ return {
+ name: name,
+ placementFailures: assign({
+ coalescedFailures: 10,
+ nodesEvaluated: 0,
+ nodesAvailable: {
+ datacenter: 0,
+ },
+ classFiltered: {
+ filtered: 1,
+ },
+ constraintFiltered: {
+ 'prop = val': 1,
+ },
+ nodesExhausted: 3,
+ classExhausted: {
+ class: 3,
+ },
+ dimensionExhausted: {
+ iops: 3,
+ },
+ quotaExhausted: {
+ quota: 'dimension',
+ },
+ scores: {
+ name: 3,
+ },
+ },
+ obj
+ )
+ };
+}
diff --git a/ui/tests/utils/clean-whitespace.js b/ui/tests/utils/clean-whitespace.js
new file mode 100644
index 000000000..cc3d5282e
--- /dev/null
+++ b/ui/tests/utils/clean-whitespace.js
@@ -0,0 +1,8 @@
+// cleans whitespace from a string, for example for cleaning
+// textContent in DOM nodes with indentation
+export default function cleanWhitespace(string) {
+ return string
+ .replace(/\n/g, '')
+ .replace(/ +/g, ' ')
+ .trim();
+}