ui: no partition and peer in bucket-list at the same time (#13812)

* don't show partition / peer at the same time in bucket-list

* use bucket-list in intentions table

* add bucket-list tests

* Simplify bucket list - match old behavior

Refactor the bucket-list component to be easier to grok and match
how the old template based approach worked. I.e. do not surface
partition or namespace when it matches the passed nspace or partition
property.

* Update docs for bucket-list

* fix linting
This commit is contained in:
Michael Klein 2022-07-20 17:07:52 +02:00 committed by GitHub
parent 9176d3ed33
commit 59b044f96d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 344 additions and 90 deletions

View File

@ -11,6 +11,9 @@ the namespace will be displayed, whereas if the partition is different it will
show both the partition and namespace (as a namespace called 'team-1' in
`partition-1` is different to a namespace called 'team-1' in `partition-2`)
When the passed item contains a `PeerName`, this will be displayed in place of
the `Partition`.
Showing the service name is a tiny bit awkward (different boolean type,
doesn't care about difference) and could be improved but we only use it for
the read only view of intentions.
@ -89,7 +92,7 @@ At the time of writing, this is not currently used across the entire UI
| Argument/Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `item` | `array` | | A Consul object that could have both a `Partition` and a `Namespace` property |
| `item` | `object` | | A Consul object that could have a `Partition`, a `Namespace`, a `PeerName` and a `Service` property |
| `nspace` | `string` | | The name of the current namespace |
| `partition` | `string` | | The name of the current partition |
| `service` | `boolean` | | Whether to show the service name on the end or not. Please note you must also pass a item.Service for it to show. We flag this incase an API request has a Service property but you don't want to show it |

View File

@ -5,84 +5,105 @@ export default class ConsulBucketList extends Component {
@service abilities;
get itemsToDisplay() {
const { item, partition, nspace } = this.args;
const { abilities } = this;
const { peerOrPartitionPart, namespacePart, servicePart } = this;
let items = [];
return [...peerOrPartitionPart, ...namespacePart, ...servicePart];
}
get peerOrPartitionPart() {
const { peerPart, partitionPart } = this;
if (peerPart.length) {
return peerPart;
} else {
return partitionPart;
}
}
get partitionPart() {
const { item, partition } = this.args;
const { abilities } = this;
if (partition && abilities.can('use partitions')) {
if (item.Partition !== partition) {
this._addPeer(items);
this._addPartition(items);
this._addNamespace(items);
this._addService(items);
} else {
this._addPeerInfo(items);
return [
{
type: 'partition',
label: 'Admin Partition',
item: item.Partition,
},
];
}
} else if (nspace && abilities.can('use nspace')) {
if (item.Namespace !== nspace) {
this._addPeerInfo(items);
this._addService(items);
} else {
this._addPeerInfo(items);
}
} else {
this._addPeerInfo(items);
}
return items;
return [];
}
_addPeerInfo(items) {
get peerPart() {
const { item } = this.args;
if (item.PeerName) {
this._addPeer(items);
this._addNamespace(items);
return [
{
type: 'peer',
label: 'Peer',
item: item.PeerName,
},
];
}
return [];
}
_addPartition(items) {
const { item } = this.args;
get namespacePart() {
const { item, nspace } = this.args;
const { abilities, partitionPart } = this;
items.push({
type: 'partition',
label: 'Admin Partition',
item: item.Partition,
});
}
_addNamespace(items) {
const { item } = this.args;
items.push({
const nspaceItem = {
type: 'nspace',
label: 'Namespace',
item: item.Namespace,
});
};
// when we surface a partition - show a namespace with it
if (partitionPart.length) {
return [nspaceItem];
}
if (nspace && abilities.can('use nspaces')) {
if (item.Namespace !== nspace) {
return [
{
type: 'nspace',
label: 'Namespace',
item: item.Namespace,
},
];
}
}
return [];
}
_addService(items) {
const { service, item } = this.args;
get servicePart() {
const { item, service } = this.args;
if (service && item.Service) {
items.push({
type: 'service',
label: 'Service',
item: item.Service,
});
const { partitionPart, namespacePart } = this;
// when we show partitionPart or namespacePart -> consider service part
if (partitionPart.length || namespacePart.length) {
if (item.Service && service) {
return [
{
type: 'service',
label: 'Service',
item: item.Service,
},
];
}
}
}
_addPeer(items) {
const { item } = this.args;
if (item?.PeerName) {
items.push({
type: 'peer',
label: 'Peer',
item: item.PeerName,
});
}
return [];
}
}

View File

@ -24,43 +24,18 @@ as |item index|>
{{else}}
{{item.SourceName}}
{{/if}}
{{#if (or (can 'use nspaces') (can 'use partitions'))}}
{{! TODO: slugify }}
<em class="consul-intention-list-table__meta-info">
{{#if item.SourcePeer}}
<span class="consul-intention-list-table__meta-info__peer">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {{tooltip "Peer" }} class="-mr-1">
<path
d="M16 8C16 7.80109 15.921 7.61032 15.7803 7.46967L12.2803 3.96967C11.9874 3.67678 11.5126 3.67678 11.2197 3.96967C10.9268 4.26256 10.9268 4.73744 11.2197 5.03033L14.1893 8L11.2197 10.9697C10.9268 11.2626 10.9268 11.7374 11.2197 12.0303C11.5126 12.3232 11.9874 12.3232 12.2803 12.0303L15.7803 8.53033C15.921 8.38968 16 8.19891 16 8Z"
fill="#77838A" />
<path
d="M0.21967 8.53033C-0.0732233 8.23744 -0.0732233 7.76256 0.21967 7.46967L3.71967 3.96967C4.01256 3.67678 4.48744 3.67678 4.78033 3.96967C5.07322 4.26256 5.07322 4.73744 4.78033 5.03033L1.81066 8L4.78033 10.9697C5.07322 11.2626 5.07322 11.7374 4.78033 12.0303C4.48744 12.3232 4.01256 12.3232 3.71967 12.0303L0.21967 8.53033Z"
fill="#77838A" />
<path
d="M5 7C4.44772 7 4 7.44772 4 8C4 8.55229 4.44772 9 5 9H5.01C5.56228 9 6.01 8.55229 6.01 8C6.01 7.44772 5.56228 7 5.01 7H5Z"
fill="#77838A" />
<path
d="M7 8C7 7.44772 7.44772 7 8 7H8.01C8.56228 7 9.01 7.44772 9.01 8C9.01 8.55229 8.56228 9 8.01 9H8C7.44772 9 7 8.55229 7 8Z"
fill="#77838A" />
<path
d="M11 7C10.4477 7 10 7.44772 10 8C10 8.55229 10.4477 9 11 9H11.01C11.5623 9 12.01 8.55229 12.01 8C12.01 7.44772 11.5623 7 11.01 7H11Z"
fill="#77838A" />
</svg>
<span>{{item.SourcePeer}}</span>
</span>
{{else}}
<span
class={{concat 'partition-' (or item.SourcePartition 'default')}}
>
{{or item.SourcePartition 'default'}}
</span>
{{/if}}
/
<span
class={{concat 'nspace-' (or item.SourceNS 'default')}}
>{{or item.SourceNS 'default'}}</span>
<Consul::Bucket::List
@item={{hash
Namespace=item.SourceNS
Partition=item.SourcePartition
PeerName=item.SourcePeer
}}
@nspace="-"
@partition="-"
/>
</em>
{{/if}}
</a>
</td>
<td class="intent intent-{{slugify item.Action}}" data-test-intention-action={{item.Action}}>

View File

@ -0,0 +1,255 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { render } from '@ember/test-helpers';
import Service from '@ember/service';
module('Integration | Component | consul bucket list', function(hooks) {
setupRenderingTest(hooks);
module('without nspace or partition feature on', function(hooks) {
hooks.beforeEach(function() {
this.owner.register(
'service:abilities',
class Stub extends Service {
can(permission) {
if (permission === 'use partitions') {
return false;
}
if (permission === 'use nspaces') {
return false;
}
return false;
}
}
);
});
test('it displays a peer when the item passed has a peer name', async function(assert) {
const PEER_NAME = 'Tomster';
this.set('peerName', PEER_NAME);
await render(hbs`
<Consul::Bucket::List
@item={{hash
PeerName=this.peerName
Namespace="default"
Partition="default"
}}
/>
`);
assert.dom('[data-test-bucket-item="peer"]').hasText(PEER_NAME, 'Peer name is displayed');
assert.dom('[data-test-bucket-item="nspace"]').doesNotExist('namespace is not shown');
assert.dom('[data-test-bucket-item="partition"]').doesNotExist('partition is not shown');
});
test('it does not display a bucket list when item has no peer name', async function(assert) {
await render(hbs`
<Consul::Bucket::List
@item={{hash
PeerName=this.peerName
}}
/>
`);
assert.dom('[data-test-bucket-list]').doesNotExist('no bucket list displayed');
});
});
module('with partition feature on', function(hooks) {
hooks.beforeEach(function() {
this.owner.register(
'service:abilities',
class Stub extends Service {
can(permission) {
if (permission === 'use partitions') {
return true;
}
if (permission === 'use nspaces') {
return true;
}
return false;
}
}
);
});
test("it displays a peer and nspace and service and no partition when item.Partition and partition don't match", async function(assert) {
const PEER_NAME = 'Tomster';
const NAMESPACE_NAME = 'Mascot';
const SERVICE_NAME = 'Ember.js';
this.set('peerName', PEER_NAME);
this.set('namespace', NAMESPACE_NAME);
this.set('service', SERVICE_NAME);
await render(hbs`
<Consul::Bucket::List
@item={{hash
PeerName=this.peerName
Namespace=this.namespace
Service=this.service
Partition="default"
}}
@partition="-"
@nspace="-"
@service="default"
/>
`);
assert.dom('[data-test-bucket-item="peer"]').hasText(PEER_NAME, 'Peer is displayed');
assert
.dom('[data-test-bucket-item="nspace"]')
.hasText(NAMESPACE_NAME, 'namespace is displayed');
assert.dom('[data-test-bucket-item="service"]').hasText(SERVICE_NAME, 'service is displayed');
assert.dom('[data-test-bucket-item="partition"]').doesNotExist('partition is not displayed');
});
test("it displays partition and nspace and service when item.Partition and partition don't match and peer is not set", async function(assert) {
const PARTITION_NAME = 'Ember.js';
const NAMESPACE_NAME = 'Mascot';
const SERVICE_NAME = 'Consul';
this.set('partition', PARTITION_NAME);
this.set('namespace', NAMESPACE_NAME);
this.set('service', SERVICE_NAME);
await render(hbs`
<Consul::Bucket::List
@item={{hash
Namespace=this.namespace
Service=this.service
Partition=this.partition
}}
@partition="-"
@nspace="-"
@service="default"
/>
`);
assert.dom('[data-test-bucket-item="peer"]').doesNotExist('peer is not displayed');
assert
.dom('[data-test-bucket-item="nspace"]')
.hasText(NAMESPACE_NAME, 'namespace is displayed');
assert.dom('[data-test-bucket-item="service"]').hasText(SERVICE_NAME, 'service is displayed');
assert
.dom('[data-test-bucket-item="partition"]')
.hasText(PARTITION_NAME, 'partition is displayed');
});
test('it displays nspace and peer and service when item.Partition and partition match and peer is set', async function(assert) {
const PEER_NAME = 'Tomster';
const PARTITION_NAME = 'Ember.js';
const NAMESPACE_NAME = 'Mascot';
const SERVICE_NAME = 'Ember.js';
this.set('peerName', PEER_NAME);
this.set('partition', PARTITION_NAME);
this.set('namespace', NAMESPACE_NAME);
this.set('service', SERVICE_NAME);
await render(hbs`
<Consul::Bucket::List
@item={{hash
PeerName=this.peerName
Namespace=this.namespace
Service=this.service
Partition=this.partition
}}
@partition={{this.partition}}
@nspace="-"
@service="default"
/>
`);
assert.dom('[data-test-bucket-item="peer"]').hasText(PEER_NAME, 'peer is displayed');
assert
.dom('[data-test-bucket-item="nspace"]')
.hasText(NAMESPACE_NAME, 'namespace is displayed');
assert.dom('[data-test-bucket-item="service"]').hasText(SERVICE_NAME, 'service is displayed');
assert.dom('[data-test-bucket-item="partition"]').doesNotExist('partition is not displayed');
});
});
module('with nspace on but partition feature off', function(hooks) {
hooks.beforeEach(function() {
this.owner.register(
'service:abilities',
class Stub extends Service {
can(permission) {
if (permission === 'use partitions') {
return false;
}
if (permission === 'use nspaces') {
return true;
}
return false;
}
}
);
});
test("it displays a peer and nspace and service when item.namespace and nspace don't match", async function(assert) {
const PEER_NAME = 'Tomster';
const NAMESPACE_NAME = 'Mascot';
const SERVICE_NAME = 'Ember.js';
this.set('peerName', PEER_NAME);
this.set('namespace', NAMESPACE_NAME);
this.set('service', SERVICE_NAME);
await render(hbs`
<Consul::Bucket::List
@item={{hash
PeerName=this.peerName
Namespace=this.namespace
Service=this.service
Partition="default"
}}
@nspace="default"
@service="default"
/>
`);
assert.dom('[data-test-bucket-item="peer"]').hasText(PEER_NAME, 'Peer is displayed');
assert
.dom('[data-test-bucket-item="nspace"]')
.hasText(NAMESPACE_NAME, 'namespace is displayed');
assert.dom('[data-test-bucket-item="service"]').hasText(SERVICE_NAME, 'service is displayed');
assert.dom('[data-test-bucket-item="partition"]').doesNotExist('partition is not displayed');
});
test('it displays a peer and no nspace and no service when item.namespace and nspace match', async function(assert) {
const PEER_NAME = 'Tomster';
const NAMESPACE_NAME = 'Mascot';
const SERVICE_NAME = 'Ember.js';
this.set('peerName', PEER_NAME);
this.set('namespace', NAMESPACE_NAME);
this.set('service', SERVICE_NAME);
await render(hbs`
<Consul::Bucket::List
@item={{hash
PeerName=this.peerName
Namespace=this.namespace
Service=this.service
Partition="default"
}}
@nspace={{this.namespace}}
@service="default"
/>
`);
assert.dom('[data-test-bucket-item="peer"]').hasText(PEER_NAME, 'Peer is displayed');
assert.dom('[data-test-bucket-item="nspace"]').doesNotExist('namespace is not displayed');
assert.dom('[data-test-bucket-item="service"]').doesNotExist('service is not displayed');
assert.dom('[data-test-bucket-item="partition"]').doesNotExist('partition is not displayed');
});
});
});