Skip to content

Commit

Permalink
Merge pull request #7895 from hashicorp/f-ui/csi-search-and-filter
Browse files Browse the repository at this point in the history
UI: CSI Searchable volumes and plugins lists
  • Loading branch information
DingoEatingFuzz authored May 9, 2020
2 parents a334ba3 + 79327c9 commit d6986b7
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 54 deletions.
65 changes: 37 additions & 28 deletions ui/app/controllers/csi/plugins/index.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { alias, readOnly } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import SortableFactory from 'nomad-ui/mixins/sortable-factory';
import Searchable from 'nomad-ui/mixins/searchable';
import { lazyClick } from 'nomad-ui/helpers/lazy-click';

export default Controller.extend(SortableFactory([]), {
userSettings: service(),
pluginsController: controller('csi/plugins'),

isForbidden: alias('pluginsController.isForbidden'),

queryParams: {
currentPage: 'page',
sortProperty: 'sort',
sortDescending: 'desc',
},
export default Controller.extend(
SortableFactory([
'plainId',
'controllersHealthyProportion',
'nodesHealthyProportion',
'provider',
]),
Searchable,
{
userSettings: service(),
pluginsController: controller('csi/plugins'),

isForbidden: alias('pluginsController.isForbidden'),

queryParams: {
currentPage: 'page',
searchTerm: 'search',
sortProperty: 'sort',
sortDescending: 'desc',
},

currentPage: 1,
pageSize: readOnly('userSettings.pageSize'),
currentPage: 1,
pageSize: readOnly('userSettings.pageSize'),

sortProperty: 'id',
sortDescending: false,
searchProps: computed(() => ['id']),
fuzzySearchProps: computed(() => ['id']),

listToSort: alias('model'),
sortedPlugins: alias('listSorted'),
sortProperty: 'id',
sortDescending: false,

// TODO: Remove once this page gets search capability
resetPagination() {
if (this.currentPage != null) {
this.set('currentPage', 1);
}
},
listToSort: alias('model'),
listToSearch: alias('listSorted'),
sortedPlugins: alias('listSearched'),

actions: {
gotoPlugin(plugin, event) {
lazyClick([() => this.transitionToRoute('csi.plugins.plugin', plugin.plainId), event]);
actions: {
gotoPlugin(plugin, event) {
lazyClick([() => this.transitionToRoute('csi.plugins.plugin', plugin.plainId), event]);
},
},
},
});
}
);
17 changes: 9 additions & 8 deletions ui/app/controllers/csi/volumes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { computed } from '@ember/object';
import { alias, readOnly } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import SortableFactory from 'nomad-ui/mixins/sortable-factory';
import Searchable from 'nomad-ui/mixins/searchable';
import { lazyClick } from 'nomad-ui/helpers/lazy-click';

export default Controller.extend(
Expand All @@ -13,6 +14,7 @@ export default Controller.extend(
'nodesHealthyProportion',
'provider',
]),
Searchable,
{
system: service(),
userSettings: service(),
Expand All @@ -22,6 +24,7 @@ export default Controller.extend(

queryParams: {
currentPage: 'page',
searchTerm: 'search',
sortProperty: 'sort',
sortDescending: 'desc',
},
Expand All @@ -32,6 +35,10 @@ export default Controller.extend(
sortProperty: 'id',
sortDescending: false,

searchProps: computed(() => ['name']),
fuzzySearchProps: computed(() => ['name']),
fuzzySearchEnabled: true,

/**
Visible volumes are those that match the selected namespace
*/
Expand All @@ -49,14 +56,8 @@ export default Controller.extend(
}),

listToSort: alias('visibleVolumes'),
sortedVolumes: alias('listSorted'),

// TODO: Remove once this page gets search capability
resetPagination() {
if (this.currentPage != null) {
this.set('currentPage', 1);
}
},
listToSearch: alias('listSorted'),
sortedVolumes: alias('listSearched'),

actions: {
gotoVolume(volume, event) {
Expand Down
26 changes: 22 additions & 4 deletions ui/app/templates/csi/plugins/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
{{#if isForbidden}}
{{partial "partials/forbidden-message"}}
{{else}}
<div class="toolbar">
<div class="toolbar-item">
{{#if model.length}}
{{search-box
data-test-plugins-search
searchTerm=(mut searchTerm)
onChange=(action resetPagination)
placeholder="Search plugins..."}}
{{/if}}
</div>
</div>
{{#if sortedPlugins}}
{{#list-pagination
source=sortedPlugins
Expand Down Expand Up @@ -56,10 +67,17 @@
{{/list-pagination}}
{{else}}
<div data-test-empty-plugins-list class="empty-message">
<h3 data-test-empty-plugins-list-headline class="empty-message-headline">No Plugins</h3>
<p class="empty-message-body">
The cluster currently has no registered CSI Plugins.
</p>
{{#if (eq model.length 0)}}
<h3 data-test-empty-plugins-list-headline class="empty-message-headline">No Plugins</h3>
<p class="empty-message-body">
The cluster currently has no registered CSI Plugins.
</p>
{{else if searchTerm}}
<h3 data-test-empty-plugins-list-headline class="empty-message-headline">No Matches</h3>
<p class="empty-message-body">
No plugins match the term <strong>{{searchTerm}}</strong>
</p>
{{/if}}
</div>
{{/if}}
{{/if}}
Expand Down
26 changes: 22 additions & 4 deletions ui/app/templates/csi/volumes/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
{{#if isForbidden}}
{{partial "partials/forbidden-message"}}
{{else}}
<div class="toolbar">
<div class="toolbar-item">
{{#if model.length}}
{{search-box
data-test-volumes-search
searchTerm=(mut searchTerm)
onChange=(action resetPagination)
placeholder="Search volumes..."}}
{{/if}}
</div>
</div>
{{#if sortedVolumes}}
{{#list-pagination
source=sortedVolumes
Expand Down Expand Up @@ -60,10 +71,17 @@
{{/list-pagination}}
{{else}}
<div data-test-empty-volumes-list class="empty-message">
<h3 data-test-empty-volumes-list-headline class="empty-message-headline">No Volumes</h3>
<p class="empty-message-body">
The cluster currently has no CSI Volumes.
</p>
{{#if (eq model.length 0)}}
<h3 data-test-empty-volumes-list-headline class="empty-message-headline">No Volumes</h3>
<p class="empty-message-body">
The cluster currently has no CSI Volumes.
</p>
{{else if searchTerm}}
<h3 data-test-empty-volumes-list-headline class="empty-message-headline">No Matches</h3>
<p class="empty-message-body">
No volumes match the term <strong>{{searchTerm}}</strong>
</p>
{{/if}}
</div>
{{/if}}
{{/if}}
Expand Down
27 changes: 23 additions & 4 deletions ui/mirage/factories/csi-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,42 @@ export default Factory.extend({
// When false, the plugin will not make its own volumes
createVolumes: true,

// When true, doesn't create any resources, state, or events for associated allocations
shallow: false,

afterCreate(plugin, server) {
let storageNodes;
let storageControllers;

if (plugin.isMonolith) {
const pluginJob = server.create('job', { type: 'service', createAllocations: false });
const count = plugin.nodesExpected;
storageNodes = server.createList('storage-node', count, { job: pluginJob });
storageControllers = server.createList('storage-controller', count, { job: pluginJob });
storageNodes = server.createList('storage-node', count, {
job: pluginJob,
shallow: plugin.shallow,
});
storageControllers = server.createList('storage-controller', count, {
job: pluginJob,
shallow: plugin.shallow,
});
} else {
const controllerJob = server.create('job', { type: 'service', createAllocations: false });
const nodeJob = server.create('job', { type: 'service', createAllocations: false });
const controllerJob = server.create('job', {
type: 'service',
createAllocations: false,
shallow: plugin.shallow,
});
const nodeJob = server.create('job', {
type: 'service',
createAllocations: false,
shallow: plugin.shallow,
});
storageNodes = server.createList('storage-node', plugin.nodesExpected, {
job: nodeJob,
shallow: plugin.shallow,
});
storageControllers = server.createList('storage-controller', plugin.controllersExpected, {
job: controllerJob,
shallow: plugin.shallow,
});
}

Expand Down
3 changes: 3 additions & 0 deletions ui/mirage/factories/storage-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export default Factory.extend({
requiresControllerPlugin: true,
requiresTopologies: true,

shallow: false,

controllerInfo: () => ({
SupportsReadOnlyAttach: true,
SupportsAttachDetach: true,
Expand All @@ -29,6 +31,7 @@ export default Factory.extend({
jobId: storageController.job.id,
forceRunningClientStatus: true,
modifyTime: storageController.updateTime * 1000000,
shallow: storageController.shallow,
});

storageController.update({
Expand Down
3 changes: 3 additions & 0 deletions ui/mirage/factories/storage-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export default Factory.extend({
requiresControllerPlugin: true,
requiresTopologies: true,

shallow: false,

nodeInfo: () => ({
MaxVolumes: 51,
AccessibleTopology: {
Expand All @@ -29,6 +31,7 @@ export default Factory.extend({
const alloc = server.create('allocation', {
jobId: storageNode.job.id,
modifyTime: storageNode.updateTime * 1000000,
shallow: storageNode.shallow,
});

storageNode.update({
Expand Down
32 changes: 28 additions & 4 deletions ui/tests/acceptance/plugins-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module('Acceptance | plugins list', function(hooks) {

test('/csi/plugins should list the first page of plugins sorted by id', async function(assert) {
const pluginCount = PluginsList.pageSize + 1;
server.createList('csi-plugin', pluginCount);
server.createList('csi-plugin', pluginCount, { shallow: true });

await PluginsList.visit();

Expand All @@ -35,7 +35,7 @@ module('Acceptance | plugins list', function(hooks) {
});

test('each plugin row should contain information about the plugin', async function(assert) {
const plugin = server.create('csi-plugin');
const plugin = server.create('csi-plugin', { shallow: true });

await PluginsList.visit();

Expand All @@ -56,7 +56,7 @@ module('Acceptance | plugins list', function(hooks) {
});

test('each plugin row should link to the corresponding plugin', async function(assert) {
const plugin = server.create('csi-plugin');
const plugin = server.create('csi-plugin', { shallow: true });

await PluginsList.visit();

Expand All @@ -77,6 +77,30 @@ module('Acceptance | plugins list', function(hooks) {
assert.equal(PluginsList.emptyState.headline, 'No Plugins');
});

test('when there are plugins, but no matches for a search, there is an empty message', async function(assert) {
server.create('csi-plugin', { id: 'cat 1', shallow: true });
server.create('csi-plugin', { id: 'cat 2', shallow: true });

await PluginsList.visit();

await PluginsList.search('dog');
assert.ok(PluginsList.isEmpty);
assert.equal(PluginsList.emptyState.headline, 'No Matches');
});

test('search resets the current page', async function(assert) {
server.createList('csi-plugin', PluginsList.pageSize + 1, { shallow: true });

await PluginsList.visit();
await PluginsList.nextPage();

assert.equal(currentURL(), '/csi/plugins?page=2');

await PluginsList.search('foobar');

assert.equal(currentURL(), '/csi/plugins?search=foobar');
});

test('when accessing plugins is forbidden, a message is shown with a link to the tokens page', async function(assert) {
server.pretender.get('/v1/plugins', () => [403, {}, null]);

Expand All @@ -92,7 +116,7 @@ module('Acceptance | plugins list', function(hooks) {
pageObject: PluginsList,
pageObjectList: PluginsList.plugins,
async setup() {
server.createList('csi-plugin', PluginsList.pageSize);
server.createList('csi-plugin', PluginsList.pageSize, { shallow: true });
await PluginsList.visit();
},
});
Expand Down
24 changes: 24 additions & 0 deletions ui/tests/acceptance/volumes-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,30 @@ module('Acceptance | volumes list', function(hooks) {
assert.equal(VolumesList.emptyState.headline, 'No Volumes');
});

test('when there are volumes, but no matches for a search, there is an empty message', async function(assert) {
server.create('csi-volume', { id: 'cat 1' });
server.create('csi-volume', { id: 'cat 2' });

await VolumesList.visit();

await VolumesList.search('dog');
assert.ok(VolumesList.isEmpty);
assert.equal(VolumesList.emptyState.headline, 'No Matches');
});

test('searching resets the current page', async function(assert) {
server.createList('csi-volume', VolumesList.pageSize + 1);

await VolumesList.visit();
await VolumesList.nextPage();

assert.equal(currentURL(), '/csi/volumes?page=2');

await VolumesList.search('foobar');

assert.equal(currentURL(), '/csi/volumes?search=foobar');
});

test('when the namespace query param is set, only matching volumes are shown and the namespace value is forwarded to app state', async function(assert) {
server.createList('namespace', 2);
const volume1 = server.create('csi-volume', { namespaceId: server.db.namespaces[0].id });
Expand Down
Loading

0 comments on commit d6986b7

Please sign in to comment.