diff --git a/ui/app/serializers/allocation.js b/ui/app/serializers/allocation.js index 2aa91d39f073..08da6e6c44c4 100644 --- a/ui/app/serializers/allocation.js +++ b/ui/app/serializers/allocation.js @@ -13,8 +13,9 @@ export default ApplicationSerializer.extend({ normalize(typeHash, hash) { // Transform the map-based TaskStates object into an array-based // TaskState fragment list - hash.TaskStates = Object.keys(get(hash, 'TaskStates') || {}).map(key => { - const state = get(hash, `TaskStates.${key}`); + const states = hash.TaskStates || {}; + hash.TaskStates = Object.keys(states).map(key => { + const state = states[key] || {}; const summary = { Name: key }; Object.keys(state).forEach(stateKey => (summary[stateKey] = state[stateKey])); summary.Resources = hash.TaskResources && hash.TaskResources[key]; diff --git a/ui/app/serializers/deployment.js b/ui/app/serializers/deployment.js index 0e39f953652c..456eb1b250e3 100644 --- a/ui/app/serializers/deployment.js +++ b/ui/app/serializers/deployment.js @@ -9,8 +9,9 @@ export default ApplicationSerializer.extend({ normalize(typeHash, hash) { if (hash) { - hash.TaskGroupSummaries = Object.keys(get(hash, 'TaskGroups') || {}).map(key => { - const deploymentStats = get(hash, `TaskGroups.${key}`); + const taskGroups = hash.TaskGroups || {}; + hash.TaskGroupSummaries = Object.keys(taskGroups).map(key => { + const deploymentStats = taskGroups[key]; return assign({ Name: key }, deploymentStats); }); diff --git a/ui/app/serializers/evaluation.js b/ui/app/serializers/evaluation.js index 4330bdeeac0d..b81c257ace2b 100644 --- a/ui/app/serializers/evaluation.js +++ b/ui/app/serializers/evaluation.js @@ -7,8 +7,10 @@ export default ApplicationSerializer.extend({ system: service(), normalize(typeHash, hash) { - hash.FailedTGAllocs = Object.keys(hash.FailedTGAllocs || {}).map(key => { - return assign({ Name: key }, get(hash, `FailedTGAllocs.${key}`) || {}); + const failures = hash.FailedTGAllocs || {}; + hash.FailedTGAllocs = Object.keys(failures).map(key => { + const propertiesForKey = failures[key] || {}; + return assign({ Name: key }, propertiesForKey); }); hash.PlainJobId = hash.JobID; diff --git a/ui/app/serializers/job-plan.js b/ui/app/serializers/job-plan.js index 0280734383df..19f9556d304b 100644 --- a/ui/app/serializers/job-plan.js +++ b/ui/app/serializers/job-plan.js @@ -1,11 +1,11 @@ -import { get } from '@ember/object'; import { assign } from '@ember/polyfills'; import ApplicationSerializer from './application'; export default ApplicationSerializer.extend({ normalize(typeHash, hash) { - hash.FailedTGAllocs = Object.keys(hash.FailedTGAllocs || {}).map(key => { - return assign({ Name: key }, get(hash, `FailedTGAllocs.${key}`) || {}); + const failures = hash.FailedTGAllocs || {}; + hash.FailedTGAllocs = Object.keys(failures).map(key => { + return assign({ Name: key }, failures[key] || {}); }); return this._super(...arguments); }, diff --git a/ui/app/serializers/job-summary.js b/ui/app/serializers/job-summary.js index 87badef4559d..ed464e819b24 100644 --- a/ui/app/serializers/job-summary.js +++ b/ui/app/serializers/job-summary.js @@ -9,8 +9,9 @@ export default ApplicationSerializer.extend({ hash.ID = JSON.stringify([hash.JobID, hash.Namespace || 'default']); hash.JobID = hash.ID; - hash.TaskGroupSummaries = Object.keys(get(hash, 'Summary') || {}).map(key => { - const allocStats = get(hash, `Summary.${key}`) || {}; + const fullSummary = hash.Summary || {}; + hash.TaskGroupSummaries = Object.keys(fullSummary).map(key => { + const allocStats = fullSummary[key] || {}; const summary = { Name: key }; Object.keys(allocStats).forEach( diff --git a/ui/app/serializers/node.js b/ui/app/serializers/node.js index e6a1f2c22cbf..7eb259f26e51 100644 --- a/ui/app/serializers/node.js +++ b/ui/app/serializers/node.js @@ -1,4 +1,3 @@ -import { get } from '@ember/object'; import { assign } from '@ember/polyfills'; import { inject as service } from '@ember/service'; import ApplicationSerializer from './application'; @@ -13,8 +12,9 @@ export default ApplicationSerializer.extend({ normalize(modelClass, hash) { // Transform the map-based Drivers object into an array-based NodeDriver fragment list - hash.Drivers = Object.keys(get(hash, 'Drivers') || {}).map(key => { - return assign({}, get(hash, `Drivers.${key}`), { Name: key }); + const drivers = hash.Drivers || {}; + hash.Drivers = Object.keys(drivers).map(key => { + return assign({}, drivers[key], { Name: key }); }); return this._super(modelClass, hash); diff --git a/ui/tests/unit/serializers/allocation-test.js b/ui/tests/unit/serializers/allocation-test.js new file mode 100644 index 000000000000..a4c0941ec589 --- /dev/null +++ b/ui/tests/unit/serializers/allocation-test.js @@ -0,0 +1,149 @@ +import { test } from 'ember-qunit'; +import AllocationModel from 'nomad-ui/models/allocation'; +import moduleForSerializer from '../../helpers/module-for-serializer'; + +moduleForSerializer('allocation', 'Unit | Serializer | Allocation', { + needs: [ + 'service:token', + 'service:system', + 'serializer:allocation', + 'transform:fragment', + 'transform:fragment-array', + 'model:job', + 'model:node', + 'model:namespace', + 'model:evaluation', + 'model:allocation', + 'model:resources', + 'model:task-state', + 'model:reschedule-event', + ], +}); + +const sampleDate = new Date('2018-12-12T00:00:00'); +const normalizationTestCases = [ + { + name: 'Normal', + in: { + ID: 'test-allocation', + JobID: 'test-summary', + Name: 'test-summary[1]', + Namespace: 'test-namespace', + TaskGroup: 'test-group', + CreateTime: +sampleDate * 1000000, + ModifyTime: +sampleDate * 1000000, + TaskStates: { + testTask: { + State: 'running', + Failed: false, + }, + }, + }, + out: { + data: { + id: 'test-allocation', + type: 'allocation', + attributes: { + taskGroupName: 'test-group', + name: 'test-summary[1]', + modifyTime: sampleDate, + createTime: sampleDate, + states: [ + { + name: 'testTask', + state: 'running', + failed: false, + }, + ], + }, + relationships: { + followUpEvaluation: { + data: null, + }, + nextAllocation: { + data: null, + }, + previousAllocation: { + data: null, + }, + job: { + data: { + id: '["test-summary","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, + + { + name: 'Dots in task names', + in: { + ID: 'test-allocation', + JobID: 'test-summary', + Name: 'test-summary[1]', + Namespace: 'test-namespace', + TaskGroup: 'test-group', + CreateTime: +sampleDate * 1000000, + ModifyTime: +sampleDate * 1000000, + TaskStates: { + 'one.two': { + State: 'running', + Failed: false, + }, + 'three.four': { + State: 'pending', + Failed: true, + }, + }, + }, + out: { + data: { + id: 'test-allocation', + type: 'allocation', + attributes: { + taskGroupName: 'test-group', + name: 'test-summary[1]', + modifyTime: sampleDate, + createTime: sampleDate, + states: [ + { + name: 'one.two', + state: 'running', + failed: false, + }, + { + name: 'three.four', + state: 'pending', + failed: true, + }, + ], + }, + relationships: { + followUpEvaluation: { + data: null, + }, + nextAllocation: { + data: null, + }, + previousAllocation: { + data: null, + }, + job: { + data: { + id: '["test-summary","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, +]; + +normalizationTestCases.forEach(testCase => { + test(`normalization: ${testCase.name}`, function(assert) { + assert.deepEqual(this.subject().normalize(AllocationModel, testCase.in), testCase.out); + }); +}); diff --git a/ui/tests/unit/serializers/deployment-test.js b/ui/tests/unit/serializers/deployment-test.js new file mode 100644 index 000000000000..04605160f7b7 --- /dev/null +++ b/ui/tests/unit/serializers/deployment-test.js @@ -0,0 +1,129 @@ +import { test } from 'ember-qunit'; +import DeploymentModel from 'nomad-ui/models/deployment'; +import moduleForSerializer from '../../helpers/module-for-serializer'; + +moduleForSerializer('deployment', 'Unit | Serializer | Deployment', { + needs: [ + 'adapter:application', + 'serializer:deployment', + 'service:system', + 'service:token', + 'transform:fragment-array', + 'model:allocation', + 'model:job', + 'model:task-group-deployment-summary', + ], +}); + +const normalizationTestCases = [ + { + name: 'Normal', + in: { + ID: 'test-deployment', + JobID: 'test-job', + Namespace: 'test-namespace', + Status: 'canceled', + TaskGroups: { + taskGroup: { + DesiredCanaries: 2, + }, + }, + }, + out: { + data: { + id: 'test-deployment', + type: 'deployment', + attributes: { + status: 'canceled', + taskGroupSummaries: [ + { + name: 'taskGroup', + desiredCanaries: 2, + }, + ], + }, + relationships: { + allocations: { + links: { + related: '/v1/deployment/allocations/test-deployment', + }, + }, + job: { + data: { + id: '["test-job","test-namespace"]', + type: 'job', + }, + }, + jobForLatest: { + data: { + id: '["test-job","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, + + { + name: 'Dots in task group names', + in: { + ID: 'test-deployment', + JobID: 'test-job', + Namespace: 'test-namespace', + Status: 'canceled', + TaskGroups: { + 'one.two': { + DesiredCanaries: 2, + }, + 'three.four': { + DesiredCanaries: 3, + }, + }, + }, + out: { + data: { + id: 'test-deployment', + type: 'deployment', + attributes: { + status: 'canceled', + taskGroupSummaries: [ + { + name: 'one.two', + desiredCanaries: 2, + }, + { + name: 'three.four', + desiredCanaries: 3, + }, + ], + }, + relationships: { + allocations: { + links: { + related: '/v1/deployment/allocations/test-deployment', + }, + }, + job: { + data: { + id: '["test-job","test-namespace"]', + type: 'job', + }, + }, + jobForLatest: { + data: { + id: '["test-job","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, +]; + +normalizationTestCases.forEach(testCase => { + test(`normalization: ${testCase.name}`, function(assert) { + assert.deepEqual(this.subject().normalize(DeploymentModel, testCase.in), testCase.out); + }); +}); diff --git a/ui/tests/unit/serializers/evaluation-test.js b/ui/tests/unit/serializers/evaluation-test.js new file mode 100644 index 000000000000..fc1f39db7d65 --- /dev/null +++ b/ui/tests/unit/serializers/evaluation-test.js @@ -0,0 +1,104 @@ +import { test } from 'ember-qunit'; +import EvaluationModel from 'nomad-ui/models/evaluation'; +import moduleForSerializer from '../../helpers/module-for-serializer'; + +moduleForSerializer('evaluation', 'Unit | Serializer | Evaluation', { + needs: [ + 'serializer:evaluation', + 'service:system', + 'transform:fragment-array', + 'model:job', + 'model:placement-failure', + ], +}); + +const normalizationTestCases = [ + { + name: 'Normal', + in: { + ID: 'test-eval', + FailedTGAllocs: { + taskGroup: { + NodesAvailable: 10, + }, + }, + JobID: 'some-job-id', + Job: { + Namespace: 'test-namespace', + }, + }, + out: { + data: { + id: 'test-eval', + type: 'evaluation', + attributes: { + failedTGAllocs: [ + { + name: 'taskGroup', + nodesAvailable: 10, + }, + ], + }, + relationships: { + job: { + data: { + id: '["some-job-id","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, + + { + name: 'Dots in task group names', + in: { + ID: 'test-eval', + FailedTGAllocs: { + 'one.two': { + NodesAvailable: 10, + }, + 'three.four': { + NodesAvailable: 25, + }, + }, + JobID: 'some-job-id', + Job: { + Namespace: 'test-namespace', + }, + }, + out: { + data: { + id: 'test-eval', + type: 'evaluation', + attributes: { + failedTGAllocs: [ + { + name: 'one.two', + nodesAvailable: 10, + }, + { + name: 'three.four', + nodesAvailable: 25, + }, + ], + }, + relationships: { + job: { + data: { + id: '["some-job-id","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, +]; + +normalizationTestCases.forEach(testCase => { + test(`normalization: ${testCase.name}`, function(assert) { + assert.deepEqual(this.subject().normalize(EvaluationModel, testCase.in), testCase.out); + }); +}); diff --git a/ui/tests/unit/serializers/job-plan-test.js b/ui/tests/unit/serializers/job-plan-test.js new file mode 100644 index 000000000000..ccc1cdf149b8 --- /dev/null +++ b/ui/tests/unit/serializers/job-plan-test.js @@ -0,0 +1,94 @@ +import { test } from 'ember-qunit'; +import JobPlanModel from 'nomad-ui/models/job-plan'; +import moduleForSerializer from '../../helpers/module-for-serializer'; + +moduleForSerializer('job-plan', 'Unit | Serializer | JobPlan', { + needs: [ + 'service:token', + 'service:system', + 'serializer:job-plan', + 'transform:fragment-array', + 'model:placement-failure', + ], +}); + +const normalizationTestCases = [ + { + name: 'Normal', + in: { + ID: 'test-plan', + Diff: { + Arbitrary: 'Value', + }, + FailedTGAllocs: { + taskGroup: { + NodesAvailable: 10, + }, + }, + }, + out: { + data: { + id: 'test-plan', + type: 'job-plan', + attributes: { + diff: { + Arbitrary: 'Value', + }, + failedTGAllocs: [ + { + name: 'taskGroup', + nodesAvailable: 10, + }, + ], + }, + relationships: {}, + }, + }, + }, + + { + name: 'Dots in task names', + in: { + ID: 'test-plan', + Diff: { + Arbitrary: 'Value', + }, + FailedTGAllocs: { + 'one.two': { + NodesAvailable: 10, + }, + 'three.four': { + NodesAvailable: 25, + }, + }, + }, + out: { + data: { + id: 'test-plan', + type: 'job-plan', + attributes: { + diff: { + Arbitrary: 'Value', + }, + failedTGAllocs: [ + { + name: 'one.two', + nodesAvailable: 10, + }, + { + name: 'three.four', + nodesAvailable: 25, + }, + ], + }, + relationships: {}, + }, + }, + }, +]; + +normalizationTestCases.forEach(testCase => { + test(`normalization: ${testCase.name}`, function(assert) { + assert.deepEqual(this.subject().normalize(JobPlanModel, testCase.in), testCase.out); + }); +}); diff --git a/ui/tests/unit/serializers/job-summary-test.js b/ui/tests/unit/serializers/job-summary-test.js new file mode 100644 index 000000000000..8272cf2a5b59 --- /dev/null +++ b/ui/tests/unit/serializers/job-summary-test.js @@ -0,0 +1,103 @@ +import { test } from 'ember-qunit'; +import JobSummaryModel from 'nomad-ui/models/job-summary'; +import moduleForSerializer from '../../helpers/module-for-serializer'; + +moduleForSerializer('job-summary', 'Unit | Serializer | JobSummary', { + needs: [ + 'serializer:job-summary', + 'transform:fragment-array', + 'model:job', + 'model:task-group-summary', + ], +}); + +const normalizationTestCases = [ + { + name: 'Normal', + in: { + JobID: 'test-summary', + Namespace: 'test-namespace', + Summary: { + taskGroup: { + Complete: 0, + Running: 1, + }, + }, + }, + out: { + data: { + id: '["test-summary","test-namespace"]', + type: 'job-summary', + attributes: { + taskGroupSummaries: [ + { + name: 'taskGroup', + completeAllocs: 0, + runningAllocs: 1, + }, + ], + }, + relationships: { + job: { + data: { + id: '["test-summary","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, + + { + name: 'Dots in task group names', + in: { + JobID: 'test-summary', + Namespace: 'test-namespace', + Summary: { + 'one.two': { + Complete: 0, + Running: 1, + }, + 'three.four': { + Failed: 2, + Lost: 3, + }, + }, + }, + out: { + data: { + id: '["test-summary","test-namespace"]', + type: 'job-summary', + attributes: { + taskGroupSummaries: [ + { + name: 'one.two', + completeAllocs: 0, + runningAllocs: 1, + }, + { + name: 'three.four', + failedAllocs: 2, + lostAllocs: 3, + }, + ], + }, + relationships: { + job: { + data: { + id: '["test-summary","test-namespace"]', + type: 'job', + }, + }, + }, + }, + }, + }, +]; + +normalizationTestCases.forEach(testCase => { + test(`normalization: ${testCase.name}`, function(assert) { + assert.deepEqual(this.subject().normalize(JobSummaryModel, testCase.in), testCase.out); + }); +}); diff --git a/ui/tests/unit/serializers/node-test.js b/ui/tests/unit/serializers/node-test.js index a77d59af3c82..15cf914b381a 100644 --- a/ui/tests/unit/serializers/node-test.js +++ b/ui/tests/unit/serializers/node-test.js @@ -7,11 +7,20 @@ import pushPayloadToStore from '../../utils/push-payload-to-store'; moduleForSerializer('node', 'Unit | Serializer | Node', { needs: [ - 'serializer:node', + 'adapter:application', 'service:config', + 'serializer:node', + 'service:system', + 'service:token', 'transform:fragment', 'transform:fragment-array', + 'model:node-attributes', + 'model:resources', + 'model:drain-strategy', + 'model:node-driver', + 'model:node-event', 'model:allocation', + 'model:job', ], }); @@ -75,3 +84,98 @@ test('local store is culled to reflect the state of findAll requests', function( function makeNode(id, name, ip) { return { ID: id, Name: name, HTTPAddr: ip }; } + +const normalizationTestCases = [ + { + name: 'Normal', + in: { + ID: 'test-node', + HTTPAddr: '867.53.0.9:4646', + Drain: false, + Drivers: { + docker: { + Detected: true, + Healthy: false, + }, + }, + }, + out: { + data: { + id: 'test-node', + type: 'node', + attributes: { + isDraining: false, + httpAddr: '867.53.0.9:4646', + drivers: [ + { + name: 'docker', + detected: true, + healthy: false, + }, + ], + }, + relationships: { + allocations: { + links: { + related: '/v1/node/test-node/allocations', + }, + }, + }, + }, + }, + }, + + { + name: 'Dots in driver names', + in: { + ID: 'test-node', + HTTPAddr: '867.53.0.9:4646', + Drain: false, + Drivers: { + 'my.driver': { + Detected: true, + Healthy: false, + }, + 'my.other.driver': { + Detected: false, + Healthy: false, + }, + }, + }, + out: { + data: { + id: 'test-node', + type: 'node', + attributes: { + isDraining: false, + httpAddr: '867.53.0.9:4646', + drivers: [ + { + name: 'my.driver', + detected: true, + healthy: false, + }, + { + name: 'my.other.driver', + detected: false, + healthy: false, + }, + ], + }, + relationships: { + allocations: { + links: { + related: '/v1/node/test-node/allocations', + }, + }, + }, + }, + }, + }, +]; + +normalizationTestCases.forEach(testCase => { + test(`normalization: ${testCase.name}`, function(assert) { + assert.deepEqual(this.subject().normalize(NodeModel, testCase.in), testCase.out); + }); +});