mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
UI: Update custom message list filters (#26653)
* use SegmentedGroup for custom messages list filter * Update test coverage * cleanup * add changelog * address pr comments
This commit is contained in:
3
changelog/26653.txt
Normal file
3
changelog/26653.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui (enterprise): Update filters on the custom messages list view.
|
||||
```
|
||||
@@ -136,3 +136,7 @@ a.disabled.toolbar-link {
|
||||
margin: 0 $spacing-8;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.segment-filter {
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
@@ -9,40 +9,64 @@
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
>
|
||||
<:toolbarFilters>
|
||||
<FilterInput
|
||||
aria-label="Search by message title"
|
||||
placeholder="Search by message title"
|
||||
id="message-filter"
|
||||
value={{@params.pageFilter}}
|
||||
@autofocus={{true}}
|
||||
@onInput={{this.onFilterInputChange}}
|
||||
/>
|
||||
<div>
|
||||
<SearchSelect
|
||||
@id="filter-by-message-status"
|
||||
class="has-left-margin-s"
|
||||
@options={{this.statusFilterOptions}}
|
||||
@selectLimit="1"
|
||||
@searchEnabled={{false}}
|
||||
@fallbackComponent="select"
|
||||
@onChange={{fn this.onFilterChange "status"}}
|
||||
@placeholder="Filter by message status"
|
||||
@inputValue={{if @params.status (array @params.status)}}
|
||||
data-test-filter-by-message-status
|
||||
/>
|
||||
</div>
|
||||
<SearchSelect
|
||||
@id="filter-by-message-type"
|
||||
class="has-left-margin-s"
|
||||
@options={{this.typeFilterOptions}}
|
||||
@selectLimit="1"
|
||||
@searchEnabled={{false}}
|
||||
@fallbackComponent="select"
|
||||
@onChange={{fn this.onFilterChange "type"}}
|
||||
@placeholder="Filter by message type"
|
||||
@inputValue={{if @params.type (array @params.type)}}
|
||||
data-test-filter-by-message-type
|
||||
/>
|
||||
<form {{on "submit" (perform this.handleSearch)}} aria-label="Filter custom message list">
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.Button
|
||||
@color="secondary"
|
||||
@text="Clear filters"
|
||||
@icon="trash"
|
||||
@isIconOnly={{true}}
|
||||
type="reset"
|
||||
{{on "click" this.resetFilters}}
|
||||
data-test-filter-reset
|
||||
/>
|
||||
<S.TextInput
|
||||
@value={{@params.pageFilter}}
|
||||
@type="search"
|
||||
name="pageFilter"
|
||||
class="segment-filter"
|
||||
placeholder="Search by message title"
|
||||
aria-label="Search by message title"
|
||||
size="32"
|
||||
data-test-filter-by="pageFilter"
|
||||
/>
|
||||
<S.Select
|
||||
aria-label="Filter by message status"
|
||||
name="status"
|
||||
class="segment-filter {{unless @params.status 'has-text-grey'}}"
|
||||
data-test-filter-by="status"
|
||||
as |S|
|
||||
>
|
||||
<S.Options>
|
||||
<option value="" class="default-option">Message status</option>
|
||||
{{#each (array "active" "inactive") as |status|}}
|
||||
<option value={{status}} selected={{eq @params.status status}}>{{status}}</option>
|
||||
{{/each}}
|
||||
</S.Options>
|
||||
</S.Select>
|
||||
<S.Select
|
||||
aria-label="Filter by type"
|
||||
name="type"
|
||||
class="segment-filter {{unless @params.status 'has-text-grey'}}"
|
||||
data-test-filter-by="type"
|
||||
as |S|
|
||||
>
|
||||
<S.Options>
|
||||
<option value="" class="has-text-grey">Message type</option>
|
||||
{{#each (array "modal" "banner") as |type|}}
|
||||
<option value={{type}} selected={{eq @params.type type}}>{{type}}</option>
|
||||
{{/each}}
|
||||
</S.Options>
|
||||
</S.Select>
|
||||
<S.Button
|
||||
@color="secondary"
|
||||
@text="Apply filters"
|
||||
@icon={{if this.handleSearch.isRunning "loading" "filter"}}
|
||||
type="submit"
|
||||
data-test-filter-submit
|
||||
/>
|
||||
</Hds::SegmentedGroup>
|
||||
</form>
|
||||
</:toolbarFilters>
|
||||
<:toolbarActions>
|
||||
<Hds::Button
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Ember from 'ember';
|
||||
import Component from '@glimmer/component';
|
||||
import { service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { task, timeout } from 'ember-concurrency';
|
||||
import { dateFormat } from 'core/helpers/date-format';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
import { next } from '@ember/runloop';
|
||||
|
||||
/**
|
||||
* @module Page::MessagesList
|
||||
@@ -32,26 +32,6 @@ export default class MessagesList extends Component {
|
||||
@tracked showMaxMessageModal = false;
|
||||
@tracked messageToDelete = null;
|
||||
|
||||
// This follows the pattern in sync/addon/components/secrets/page/destinations for FilterInput.
|
||||
// Currently, FilterInput doesn't do a full page refresh causing it to lose focus.
|
||||
// The work around is to verify that a transition from this route was completed and then focus the input.
|
||||
constructor(owner, args) {
|
||||
super(owner, args);
|
||||
this.router.on('routeDidChange', this.focusNameFilter);
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy();
|
||||
this.router.off('routeDidChange', this.focusNameFilter);
|
||||
}
|
||||
|
||||
focusNameFilter(transition) {
|
||||
const route = 'vault.cluster.config-ui.messages.index';
|
||||
if (transition?.from?.name === route && transition?.to?.name === route) {
|
||||
next(() => document.getElementById('message-filter')?.focus());
|
||||
}
|
||||
}
|
||||
|
||||
get formattedMessages() {
|
||||
return this.args.messages.map((message) => {
|
||||
let badgeDisplayText = '';
|
||||
@@ -91,20 +71,6 @@ export default class MessagesList extends Component {
|
||||
return [{ label: 'Messages' }, { label }];
|
||||
}
|
||||
|
||||
get statusFilterOptions() {
|
||||
return [
|
||||
{ id: 'active', name: 'active' },
|
||||
{ id: 'inactive', name: 'inactive' },
|
||||
];
|
||||
}
|
||||
|
||||
get typeFilterOptions() {
|
||||
return [
|
||||
{ id: 'modal', name: 'modal' },
|
||||
{ id: 'banner', name: 'banner' },
|
||||
];
|
||||
}
|
||||
|
||||
// callback from HDS pagination to set the queryParams page
|
||||
get paginationQueryParams() {
|
||||
return (page) => {
|
||||
@@ -116,7 +82,8 @@ export default class MessagesList extends Component {
|
||||
|
||||
transitionToMessagesWithParams(queryParams) {
|
||||
this.router.transitionTo('vault.cluster.config-ui.messages', {
|
||||
queryParams,
|
||||
// always reset back to page 1 when changing filters
|
||||
queryParams: { ...queryParams, page: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,17 +103,24 @@ export default class MessagesList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onFilterInputChange(pageFilter) {
|
||||
this.transitionToMessagesWithParams({ pageFilter });
|
||||
@task
|
||||
*handleSearch(evt) {
|
||||
evt.preventDefault();
|
||||
const formData = new FormData(evt.target);
|
||||
// shows loader to indicate that the search was executed
|
||||
yield timeout(Ember.testing ? 0 : 250);
|
||||
const params = {};
|
||||
for (const key of formData.keys()) {
|
||||
const valDefault = key === 'pageFilter' ? '' : null;
|
||||
const val = formData.get(key) || valDefault;
|
||||
params[key] = val;
|
||||
}
|
||||
this.transitionToMessagesWithParams(params);
|
||||
}
|
||||
|
||||
@action
|
||||
onFilterChange(filterType, [filterOption]) {
|
||||
const param = {};
|
||||
param[filterType] = filterOption;
|
||||
param.page = 1;
|
||||
this.transitionToMessagesWithParams(param);
|
||||
resetFilters() {
|
||||
this.transitionToMessagesWithParams({ pageFilter: '', status: null, type: null });
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@@ -12,34 +12,22 @@ import logout from 'vault/tests/pages/logout';
|
||||
import { format, addDays, startOfDay } from 'date-fns';
|
||||
import { datetimeLocalStringFormat } from 'core/utils/date-formatters';
|
||||
import { CUSTOM_MESSAGES } from 'vault/tests/helpers/config-ui/message-selectors';
|
||||
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
const MESSAGES_LIST = {
|
||||
listItem: '.linked-block',
|
||||
filterBy: (name) => `[data-test-filter-by="${name}"]`,
|
||||
filterSubmit: '[data-test-filter-submit]',
|
||||
filterReset: '[data-test-filter-reset]',
|
||||
};
|
||||
|
||||
module('Acceptance | Community | config-ui/messages', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
this.server.get('/sys/health', function () {
|
||||
return {
|
||||
enterprise: false,
|
||||
initialized: true,
|
||||
sealed: false,
|
||||
standby: false,
|
||||
license: {
|
||||
expiry: '2024-01-12T23:20:50.52Z',
|
||||
state: 'stored',
|
||||
},
|
||||
performance_standby: false,
|
||||
replication_performance_mode: 'disabled',
|
||||
replication_dr_mode: 'disabled',
|
||||
server_time_utc: 1622562585,
|
||||
version: '1.16.0',
|
||||
cluster_name: 'vault-cluster-e779cd7c',
|
||||
cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
|
||||
last_wal: 121,
|
||||
};
|
||||
});
|
||||
const version = this.owner.lookup('service:version');
|
||||
version.type = 'community';
|
||||
await authPage.login();
|
||||
});
|
||||
|
||||
@@ -58,6 +46,8 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
const version = this.owner.lookup('service:version');
|
||||
version.type = 'enterprise';
|
||||
this.messageDetailId = () => {
|
||||
return currentURL().match(/messages\/(.*)\/details/)[1];
|
||||
};
|
||||
@@ -97,26 +87,6 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
await click(CUSTOM_MESSAGES.confirmActionButton('Delete message'));
|
||||
await click(GENERAL.confirmButton);
|
||||
};
|
||||
this.server.get('/sys/health', function () {
|
||||
return {
|
||||
enterprise: true,
|
||||
initialized: true,
|
||||
sealed: false,
|
||||
standby: false,
|
||||
license: {
|
||||
expiry: '2024-01-12T23:20:50.52Z',
|
||||
state: 'stored',
|
||||
},
|
||||
performance_standby: false,
|
||||
replication_performance_mode: 'disabled',
|
||||
replication_dr_mode: 'disabled',
|
||||
server_time_utc: 1622562585,
|
||||
version: '1.16.0+ent',
|
||||
cluster_name: 'vault-cluster-e779cd7c',
|
||||
cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
|
||||
last_wal: 121,
|
||||
};
|
||||
});
|
||||
await authPage.login();
|
||||
});
|
||||
|
||||
@@ -126,10 +96,10 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
test('it should show an empty state when no messages are created', async function (assert) {
|
||||
assert.expect(4);
|
||||
await click(CUSTOM_MESSAGES.navLink);
|
||||
assert.dom('[data-test-component="empty-state"]').exists();
|
||||
assert.dom(GENERAL.emptyStateTitle).exists();
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet');
|
||||
await click(CUSTOM_MESSAGES.tab('On login page'));
|
||||
assert.dom('[data-test-component="empty-state"]').exists();
|
||||
assert.dom(GENERAL.emptyStateTitle).exists();
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet');
|
||||
});
|
||||
|
||||
@@ -165,37 +135,41 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
await click(CUSTOM_MESSAGES.listItem('Awesome custom message title'));
|
||||
await click(CUSTOM_MESSAGES.confirmActionButton('Delete message'));
|
||||
await click(GENERAL.confirmButton);
|
||||
assert.dom('[data-test-component="empty-state"]').exists('Message was deleted');
|
||||
assert.dom(GENERAL.emptyStateTitle).exists('Message was deleted');
|
||||
});
|
||||
test('it should filter by type and status', async function (assert) {
|
||||
await this.createMessage('banner', null);
|
||||
const msg1 = this.messageDetailId();
|
||||
await this.createMessage('banner');
|
||||
const msg2 = this.messageDetailId();
|
||||
await visit('vault/config-ui/messages');
|
||||
await visit('vault/config-ui/messages?pageFilter=foobar&status=inactive&type=banner');
|
||||
// check that filters inherit param values
|
||||
assert.dom(MESSAGES_LIST.filterBy('pageFilter')).hasValue('foobar');
|
||||
assert.dom(MESSAGES_LIST.filterBy('status')).hasValue('inactive');
|
||||
assert.dom(MESSAGES_LIST.filterBy('type')).hasValue('banner');
|
||||
assert.dom(GENERAL.emptyStateTitle).exists();
|
||||
|
||||
// clear filters works
|
||||
await click(MESSAGES_LIST.filterReset);
|
||||
assert.dom(MESSAGES_LIST.listItem).exists({ count: 2 });
|
||||
|
||||
// check number of messages with status filters
|
||||
await clickTrigger('#filter-by-message-status');
|
||||
await click('.ember-power-select-options [data-option-index="0"]');
|
||||
assert.dom('.linked-block').exists({ count: 1 }, 'filtered by active');
|
||||
await click('[data-test-selected-list-button="delete"]');
|
||||
await clickTrigger('#filter-by-message-status');
|
||||
await click('.ember-power-select-options [data-option-index="1"]');
|
||||
assert.dom('.linked-block').exists({ count: 1 }, 'filtered by inactive');
|
||||
await click('[data-test-selected-list-button="delete"]');
|
||||
await fillIn(MESSAGES_LIST.filterBy('status'), 'active');
|
||||
assert.dom(MESSAGES_LIST.listItem).exists({ count: 2 }, 'list does not filter before clicking submit');
|
||||
await click(MESSAGES_LIST.filterSubmit);
|
||||
assert.dom(MESSAGES_LIST.listItem).exists({ count: 1 });
|
||||
|
||||
// check number of messages with type filters
|
||||
await clickTrigger('#filter-by-message-type');
|
||||
await click('.ember-power-select-options [data-option-index="0"]');
|
||||
assert.dom('.linked-block').exists({ count: 0 }, 'filtered by modal');
|
||||
await click('[data-test-selected-list-button="delete"]');
|
||||
await clickTrigger('#filter-by-message-type');
|
||||
await click('.ember-power-select-options [data-option-index="1"]');
|
||||
assert.dom('.linked-block').exists({ count: 2 }, 'filtered by banner');
|
||||
await click('[data-test-selected-list-button="delete"]');
|
||||
await click(MESSAGES_LIST.filterReset);
|
||||
await fillIn(MESSAGES_LIST.filterBy('type'), 'modal');
|
||||
await click(MESSAGES_LIST.filterSubmit);
|
||||
assert.dom(GENERAL.emptyStateTitle).exists();
|
||||
|
||||
// check number of messages with no filters
|
||||
assert.dom('.linked-block').exists({ count: 2 }, 'no filters selected');
|
||||
// unsetting a filter will reset that item in the query
|
||||
await fillIn(MESSAGES_LIST.filterBy('type'), '');
|
||||
await fillIn(MESSAGES_LIST.filterBy('status'), 'inactive');
|
||||
await click(MESSAGES_LIST.filterSubmit);
|
||||
assert.dom(MESSAGES_LIST.listItem).exists({ count: 1 });
|
||||
|
||||
// clean up custom messages
|
||||
await this.deleteMessage(msg1);
|
||||
@@ -270,7 +244,7 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
await click(CUSTOM_MESSAGES.listItem('Awesome custom message title'));
|
||||
await click(CUSTOM_MESSAGES.confirmActionButton('Delete message'));
|
||||
await click(GENERAL.confirmButton);
|
||||
assert.dom('[data-test-component="empty-state"]').exists('Message was deleted');
|
||||
assert.dom(GENERAL.emptyStateTitle).exists('Message was deleted');
|
||||
});
|
||||
test('it should show info message on create and edit form', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
Reference in New Issue
Block a user