diff --git a/ui/app/models/node-attributes.js b/ui/app/models/node-attributes.js
index c893d8500..232dae160 100644
--- a/ui/app/models/node-attributes.js
+++ b/ui/app/models/node-attributes.js
@@ -9,8 +9,13 @@ export default Fragment.extend({
attributes: attr(),
attributesStructured: computed('attributes', function() {
- // `unflatten` doesn't sort keys before unflattening, so manual preprocessing is necessary.
const original = this.get('attributes');
+
+ if (!original) {
+ return;
+ }
+
+ // `unflatten` doesn't sort keys before unflattening, so manual preprocessing is necessary.
const attrs = Object.keys(original)
.sort()
.reduce((obj, key) => {
diff --git a/ui/app/models/node.js b/ui/app/models/node.js
index 56e489e1d..74feacb4e 100644
--- a/ui/app/models/node.js
+++ b/ui/app/models/node.js
@@ -20,6 +20,7 @@ export default Model.extend({
httpAddr: attr('string'),
tlsEnabled: attr('boolean'),
attributes: fragment('node-attributes'),
+ meta: fragment('node-attributes'),
resources: fragment('resources'),
reserved: fragment('resources'),
diff --git a/ui/app/templates/clients/client.hbs b/ui/app/templates/clients/client.hbs
index 09423d9d4..132762710 100644
--- a/ui/app/templates/clients/client.hbs
+++ b/ui/app/templates/clients/client.hbs
@@ -84,6 +84,24 @@
attributes=model.attributes.attributesStructured
class="attributes-table"}}
+
+ {{attributes-table
+ data-test-meta
+ attributes=model.meta.attributesStructured
+ class="attributes-table"}}
+
+ {{else}}
+
+
+
No Meta Attributes
+
This client is configured with no meta attributes.
+
+
+ {{/if}}
{{/gutter-menu}}
diff --git a/ui/mirage/factories/node.js b/ui/mirage/factories/node.js
index 3693171a6..ba4bd569e 100644
--- a/ui/mirage/factories/node.js
+++ b/ui/mirage/factories/node.js
@@ -58,6 +58,14 @@ export default Factory.extend({
};
},
+ withMeta: trait({
+ meta: {
+ just: 'some',
+ prop: 'erties',
+ 'over.here': 100,
+ },
+ }),
+
afterCreate(node, server) {
// Each node has a corresponding client stats resource that's queried via node IP.
// Create that record, even though it's not a relationship.
diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js
index 1b0788b00..0c733750b 100644
--- a/ui/tests/acceptance/client-detail-test.js
+++ b/ui/tests/acceptance/client-detail-test.js
@@ -197,9 +197,7 @@ test('each allocation should have high-level details for the allocation', functi
});
});
-test('each allocation should show job information even if the job is incomplete and already in the store', function(
- assert
-) {
+test('each allocation should show job information even if the job is incomplete and already in the store', function(assert) {
// First, visit clients to load the allocations for each visible node.
// Don't load the job belongsTo of the allocation! Leave it unfulfilled.
@@ -288,9 +286,39 @@ test('/clients/:id should list all attributes for the node', function(assert) {
});
});
-test('when the node is not found, an error message is shown, but the URL persists', function(
- assert
-) {
+test('/clients/:id lists all meta attributes', function(assert) {
+ node = server.create('node', 'forceIPv4', 'withMeta');
+
+ visit(`/clients/${node.id}`);
+
+ andThen(() => {
+ assert.ok(find('[data-test-meta]'), 'Meta attributes table is on the page');
+ assert.notOk(find('[data-test-empty-meta-message]'), 'Meta attributes is not empty');
+
+ const firstMetaKey = Object.keys(node.meta)[0];
+ assert.equal(
+ find('[data-test-meta] [data-test-key]').textContent.trim(),
+ firstMetaKey,
+ 'Meta attributes for the node are bound to the attributes table'
+ );
+ assert.equal(
+ find('[data-test-meta] [data-test-value]').textContent.trim(),
+ node.meta[firstMetaKey],
+ 'Meta attributes for the node are bound to the attributes table'
+ );
+ });
+});
+
+test('/clients/:id shows an empty message when there is no meta data', function(assert) {
+ visit(`/clients/${node.id}`);
+
+ andThen(() => {
+ assert.notOk(find('[data-test-meta]'), 'Meta attributes table is not on the page');
+ assert.ok(find('[data-test-empty-meta-message]'), 'Meta attributes is empty');
+ });
+});
+
+test('when the node is not found, an error message is shown, but the URL persists', function(assert) {
visit('/clients/not-a-real-node');
andThen(() => {