Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: CSI Searchable volumes and plugins lists #7895

Merged
merged 3 commits into from
May 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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