UI: HDS adoption replace <ListPagination> component (#23169)

* change currentPage to page to be consistent

* replace pagination in listview and always show pagination

* wip

* fix query param issue

* access identity aliases index

* leases done and dusted

* policies and secrets backend

* remove list Pagination

* changelog
This commit is contained in:
Angel Garbarino
2023-09-26 10:27:14 -06:00
committed by GitHub
parent 88ed074287
commit 6db476da41
25 changed files with 128 additions and 408 deletions

3
changelog/23169.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: Implement Helios Design System pagination component
```

View File

@@ -7,6 +7,15 @@ import Controller from '@ember/controller';
import ListController from 'core/mixins/list-controller'; import ListController from 'core/mixins/list-controller';
export default Controller.extend(ListController, { export default Controller.extend(ListController, {
// callback from HDS pagination to set the queryParams page
get paginationQueryParams() {
return (page) => {
return {
page,
};
};
},
actions: { actions: {
onDelete() { onDelete() {
this.send('reload'); this.send('reload');

View File

@@ -10,6 +10,15 @@ import ListController from 'core/mixins/list-controller';
export default Controller.extend(ListController, { export default Controller.extend(ListController, {
flashMessages: service(), flashMessages: service(),
// callback from HDS pagination to set the queryParams page
get paginationQueryParams() {
return (page) => {
return {
page,
};
};
},
actions: { actions: {
delete(model) { delete(model) {
const type = model.get('identityType'); const type = model.get('identityType');

View File

@@ -14,6 +14,15 @@ export default Controller.extend(ListController, {
store: service(), store: service(),
clusterController: controller('vault.cluster'), clusterController: controller('vault.cluster'),
// callback from HDS pagination to set the queryParams page
get paginationQueryParams() {
return (page) => {
return {
page,
};
};
},
backendCrumb: computed('clusterController.model.name', function () { backendCrumb: computed('clusterController.model.name', function () {
return { return {
label: 'leases', label: 'leases',

View File

@@ -24,6 +24,15 @@ export default Controller.extend({
// set via the route `loading` action // set via the route `loading` action
isLoading: false, isLoading: false,
// callback from HDS pagination to set the queryParams page
get paginationQueryParams() {
return (page) => {
return {
page,
};
};
},
filterMatchesKey: computed('filter', 'model', 'model.[]', function () { filterMatchesKey: computed('filter', 'model', 'model.[]', function () {
var filter = this.filter; var filter = this.filter;
var content = this.model; var content = this.model;

View File

@@ -18,6 +18,15 @@ export default Controller.extend(ListController, BackendCrumbMixin, WithNavToNea
tab: '', tab: '',
// callback from HDS pagination to set the queryParams page
get paginationQueryParams() {
return (page) => {
return {
page,
};
};
},
filterIsFolder: computed('filter', function () { filterIsFolder: computed('filter', function () {
return !!keyIsFolder(this.filter); return !!keyIsFolder(this.filter);
}), }),

View File

@@ -1,200 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
// This file combines Bulma CSS with our own CSS that previously overrode Bulma. In the future we should adopt the HDS pagination.
.pagination-previous[disabled],
.pagination-next[disabled],
.pagination-link[disabled],
.pagination-ellipsis[disabled] {
cursor: not-allowed;
}
.pagination-previous,
.pagination-next,
.pagination-link,
.pagination-ellipsis,
.tabs {
user-select: none;
}
.pagination-previous,
.pagination-next,
.pagination-link,
.pagination-ellipsis {
align-items: center;
box-shadow: none;
display: inline-flex;
font-size: $size-6;
justify-content: flex-start;
line-height: 1.5;
margin: $size-11;
padding-bottom: calc(0.5em - 1px);
padding-left: calc(0.75em - 1px);
padding-right: calc(0.75em - 1px);
padding-top: calc(0.5em - 1px);
position: relative;
vertical-align: top;
}
.pagination-link.is-current {
color: $white;
}
.pagination-list {
flex-grow: 1;
flex-shrink: 1;
justify-content: flex-start;
order: 1;
}
.list-pagination {
@extend .has-slim-padding;
position: relative;
top: 1px;
background-color: $grey-lightest;
margin-bottom: $size-4;
a {
text-decoration: none;
height: 1.5rem;
min-width: 1.5rem;
border: none;
}
a.pagination-link {
width: 3ch;
}
a:not(.is-current):hover {
text-decoration: underline;
color: $blue;
}
a.is-current {
background-color: $grey;
}
.pagination {
justify-content: center;
}
.pagination-list {
flex-grow: 0;
flex-wrap: wrap;
li {
list-style: none;
}
}
.pagination,
.pagination-list {
align-items: center;
display: flex;
justify-content: center;
text-align: center;
}
.pagination-ellipsis {
margin: 0;
padding-left: 0;
padding-right: 0;
}
}
.list-pagination .pagination-previous,
.list-pagination .pagination-next {
@extend .button;
@extend .is-primary;
@extend .is-outlined;
@extend .is-compact;
background: $white;
border-color: $blue-500;
color: $blue-500;
max-width: 8rem;
@include until($mobile) {
max-width: 2rem;
padding-left: 0;
padding-right: 0;
}
.pagination-next-label,
.pagination-previous-label {
@include until($mobile) {
display: none;
}
}
.icon {
height: 1em;
width: 1em;
vertical-align: middle;
&:last-child:not(:first-child),
&:first-child:not(:last-child) {
margin: -0.1em 0 0;
}
}
.button .icon {
margin: 0;
}
}
.pagination-previous {
order: 1;
}
.pagination-next {
order: 3;
}
.pagination.is-centered {
&.pagination-previous {
order: 1;
}
&.pagination-list {
justify-content: center;
order: 2;
}
&.pagination-next {
order: 3;
}
}
.pagination.is-right {
&.pagination-previous {
order: 1;
}
&.pagination-next {
order: 2;
}
&.pagination-list {
justify-content: flex-end;
order: 3;
}
}
// responsive css
@media screen and (max-width: 768px) {
.pagination {
flex-wrap: wrap;
}
.pagination-previous,
.pagination-next {
flex-grow: 1;
flex-shrink: 1;
}
.pagination-list li {
flex-grow: 1;
flex-shrink: 1;
}
}
.list-pagination .pagination-next {
@include until($mobile) {
order: 3;
}
}

View File

@@ -77,7 +77,6 @@
@import './components/license-banners'; @import './components/license-banners';
@import './components/linked-block'; @import './components/linked-block';
@import './components/list-item-row'; @import './components/list-item-row';
@import './components/list-pagination';
@import './components/loader'; @import './components/loader';
@import './components/login-form'; @import './components/login-form';
@import './components/masked-input'; @import './components/masked-input';

View File

@@ -36,13 +36,16 @@
</div> </div>
</LinkedBlock> </LinkedBlock>
{{/each}} {{/each}}
{{#if (gt this.model.meta.lastPage 1)}}
<ListPagination <Hds::Pagination::Numbered
@page={{this.model.meta.currentPage}} @currentPage={{this.model.meta.currentPage}}
@lastPage={{this.model.meta.lastPage}} @currentPageSize={{this.model.meta.pageSize}}
@link="vault.cluster.access.identity.aliases.index" @route="vault.cluster.access.identity.aliases.index"
/> @showSizeSelector={{false}}
{{/if}} @totalItems={{this.model.meta.total}}
@queryFunction={{this.paginationQueryParams}}
/>
{{else}} {{else}}
<EmptyState <EmptyState
@title="No {{this.identityType}} aliases yet" @title="No {{this.identityType}} aliases yet"

View File

@@ -96,13 +96,16 @@
</div> </div>
</LinkedBlock> </LinkedBlock>
{{/each}} {{/each}}
{{#if (gt this.model.meta.lastPage 1)}}
<ListPagination <Hds::Pagination::Numbered
@page={{this.model.meta.currentPage}} @currentPage={{this.model.meta.currentPage}}
@lastPage={{this.model.meta.lastPage}} @currentPageSize={{this.model.meta.pageSize}}
@link="vault.cluster.access.identity.index" @route="vault.cluster.access.identity.index"
/> @showSizeSelector={{false}}
{{/if}} @totalItems={{this.model.meta.total}}
@queryFunction={{this.paginationQueryParams}}
/>
{{else}} {{else}}
<EmptyState <EmptyState
@title="No {{pluralize this.identityType}} yet" @title="No {{pluralize this.identityType}} yet"

View File

@@ -102,11 +102,12 @@
{{else}} {{else}}
<EmptyState @title={{this.emptyTitle}} /> <EmptyState @title={{this.emptyTitle}} />
{{/if}} {{/if}}
{{#if (gt this.model.meta.lastPage 1)}}
<ListPagination <Hds::Pagination::Numbered
@page={{this.model.meta.currentPage}} @currentPage={{this.model.meta.currentPage}}
@lastPage={{this.model.meta.lastPage}} @currentPageSize={{this.model.meta.pageSize}}
@link={{concat "vault.cluster.access.leases.list" (unless this.baseKey.id "-root")}} @route={{concat "vault.cluster.access.leases.list" (unless this.baseKey.id "-root")}}
@models={{compact (array (if this.baseKey.id this.baseKey.id))}} @showSizeSelector={{false}}
/> @totalItems={{this.model.meta.total}}
{{/if}} @queryFunction={{this.paginationQueryParams}}
/>

View File

@@ -151,13 +151,16 @@
{{else}} {{else}}
<EmptyState @title="No policies matching &quot;{{this.pageFilter}}&quot;" /> <EmptyState @title="No policies matching &quot;{{this.pageFilter}}&quot;" />
{{/each}} {{/each}}
{{#if (gt this.model.meta.lastPage 1)}}
<ListPagination <Hds::Pagination::Numbered
@page={{this.model.meta.currentPage}} @currentPage={{this.model.meta.currentPage}}
@lastPage={{this.model.meta.lastPage}} @currentPageSize={{this.model.meta.pageSize}}
@link="vault.cluster.policies.index" @route="vault.cluster.policies.index"
/> @showSizeSelector={{false}}
{{/if}} @totalItems={{this.model.meta.total}}
@queryFunction={{this.paginationQueryParams}}
/>
{{else}} {{else}}
<EmptyState <EmptyState
@title="No {{uppercase this.policyType}} policies yet" @title="No {{uppercase this.policyType}} policies yet"

View File

@@ -94,14 +94,16 @@
{{/if}} {{/if}}
</div> </div>
{{/each}} {{/each}}
{{#if (gt this.model.meta.lastPage 1)}}
<ListPagination <Hds::Pagination::Numbered
@page={{this.model.meta.currentPage}} @currentPage={{this.model.meta.currentPage}}
@lastPage={{this.model.meta.lastPage}} @currentPageSize={{this.model.meta.pageSize}}
@link={{concat "vault.cluster.secrets.backend.list" (unless this.baseKey.id "-root")}} @route={{concat "vault.cluster.secrets.backend.list" (unless this.baseKey.id "-root")}}
@models={{compact (array this.backend (if this.baseKey.id this.baseKey.id))}} @showSizeSelector={{false}}
/> @totalItems={{this.model.meta.total}}
{{/if}} @queryFunction={{this.paginationQueryParams}}
/>
{{else}} {{else}}
{{#if (eq this.baseKey.id "")}} {{#if (eq this.baseKey.id "")}}
{{#if (and options.firstStep (not this.tab))}} {{#if (and options.firstStep (not this.tab))}}

View File

@@ -1,45 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { gt } from '@ember/object/computed';
import Component from '@ember/component';
import { computed } from '@ember/object';
import { range } from 'ember-composable-helpers/helpers/range';
import { A } from '@ember/array';
import layout from '../templates/components/list-pagination';
// In non-dev mode, the pagination defaults to the config/environment variable. Set to 100.
export default Component.extend({
layout,
classNames: ['box', 'is-shadowless', 'list-pagination'],
page: null,
lastPage: null,
link: null,
models: A(),
// number of links to show on each side of page
spread: 2,
hasNext: computed('page', 'lastPage', function () {
return this.page < this.lastPage;
}),
hasPrevious: gt('page', 1),
segmentLinks: gt('lastPage', 10),
pageRange: computed('lastPage', 'page', 'spread', function () {
const { spread, page, lastPage } = this;
let lower = Math.max(2, page - spread);
const upper = Math.min(lastPage - 1, lower + spread * 2);
// we're closer to lastPage than the spread
if (upper - lower < 5) {
lower = upper - 4;
}
if (lastPage <= 10) {
return range([1, lastPage, true]);
}
return range([lower, upper, true]);
}),
});

View File

@@ -10,12 +10,14 @@
{{else}} {{else}}
{{yield}} {{yield}}
{{/each}} {{/each}}
{{#if this.showPagination}} {{#if @paginationRouteName}}
<ListPagination <Hds::Pagination::Numbered
@page={{@items.meta.currentPage}} @currentPage={{@items.meta.currentPage}}
@lastPage={{@items.meta.lastPage}} @currentPageSize={{@items.meta.pageSize}}
@link={{@paginationRouteName}} @route={{@paginationRouteName}}
data-test-list-view-pagination @showSizeSelector={{false}}
@totalItems={{@items.meta.total}}
@queryFunction={{this.paginationQueryParams}}
/> />
{{/if}} {{/if}}
</div> </div>

View File

@@ -40,11 +40,6 @@ export default class ListView extends Component {
return this.args.itemNoun || 'item'; return this.args.itemNoun || 'item';
} }
get showPagination() {
const meta = this.args.items.meta;
return this.args.paginationRouteName && meta && meta.lastPage > 1 && meta.total > 0;
}
get emptyTitle() { get emptyTitle() {
const items = pluralize(this.itemNoun); const items = pluralize(this.itemNoun);
return `No ${items} yet`; return `No ${items} yet`;
@@ -54,4 +49,13 @@ export default class ListView extends Component {
const items = pluralize(this.itemNoun); const items = pluralize(this.itemNoun);
return `Your ${items} will be listed here. Add your first ${this.itemNoun} to get started.`; return `Your ${items} will be listed here. Add your first ${this.itemNoun} to get started.`;
} }
// callback from HDS pagination to set the queryParams page
get paginationQueryParams() {
return (page) => {
return {
page,
};
};
}
} }

View File

@@ -1,94 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<nav class="pagination is-centered" aria-label="pagination">
{{#if this.hasPrevious}}
<LinkTo @route={{this.link}} @models={{this.models}} @query={{hash page=(dec this.page)}} class="pagination-previous">
<Chevron @direction="left" />
<span class="pagination-previous-label">
Previous
</span>
</LinkTo>
{{else}}
<button type="button" disabled={{true}} class="pagination-previous is-invisible" aria-hidden={{true}}>
<Chevron @direction="left" />
<span class="pagination-previous-label">
Previous
</span>
</button>
{{/if}}
{{#if this.hasNext}}
<LinkTo @route={{this.link}} @models={{this.models}} @query={{hash page=(inc this.page)}} class="pagination-next">
<span class="pagination-next-label">
Next
</span>
<Chevron />
</LinkTo>
{{else}}
<button type="button" disabled={{true}} class="pagination-next is-invisible" aria-hidden={{true}}>
<span class="pagination-next-label">
Next
</span>
<Chevron />
</button>
{{/if}}
{{#if this.segmentLinks}}
<ul class="pagination-list">
<li>
<LinkTo
@route={{this.link}}
@models={{this.models}}
@query={{hash page=1}}
class={{concat (if (eq this.page 1) "is-current ") "pagination-link"}}
aria-label="Go to page 1"
>
1
</LinkTo>
</li>
<li><span class="pagination-ellipsis">&hellip;</span></li>
{{#each this.pageRange as |p|}}
<li>
<LinkTo
@route={{this.link}}
@models={{this.models}}
@query={{hash page=p}}
class={{concat (if (eq this.page 1) "is-current ") "pagination-link"}}
aria-label={{concat "Go to page " p}}
>
{{p}}
</LinkTo>
</li>
{{/each}}
<li><span class="pagination-ellipsis">&hellip;</span></li>
<li>
<LinkTo
@route={{this.link}}
@models={{this.models}}
@query={{hash page=this.lastPage}}
class={{concat (if (eq this.page this.lastPage) "is-current ") "pagination-link"}}
aria-label={{concat "Go to page " this.lastPage}}
>
{{this.lastPage}}
</LinkTo>
</li>
</ul>
{{else}}
<ul class="pagination-list">
{{#each this.pageRange as |p|}}
<li>
<LinkTo
@route={{this.link}}
@models={{this.models}}
@query={{hash page=p}}
class={{concat (if (eq this.page p) "is-current ") "pagination-link"}}
aria-label={{concat "Go to page " p}}
>
{{p}}
</LinkTo>
</li>
{{/each}}
</ul>
{{/if}}
</nav>

View File

@@ -1,6 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/components/list-pagination';

View File

@@ -42,11 +42,11 @@ export default class KvListPageComponent extends Component {
return pathIsDirectory(path) ? 'View list' : 'View secret'; return pathIsDirectory(path) ? 'View list' : 'View secret';
} }
// callback from HDS pagination to set the queryParams currentPage // callback from HDS pagination to set the queryParams page
get paginationQueryParams() { get paginationQueryParams() {
return (page) => { return (page) => {
return { return {
currentPage: page, page,
}; };
}; };
} }

View File

@@ -6,5 +6,5 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
export default class KvListController extends Controller { export default class KvListController extends Controller {
queryParams = ['pageFilter', 'currentPage']; queryParams = ['pageFilter', 'page'];
} }

View File

@@ -19,7 +19,7 @@ export default class KvSecretsListRoute extends Route {
pageFilter: { pageFilter: {
refreshModel: true, refreshModel: true,
}, },
currentPage: { page: {
refreshModel: true, refreshModel: true,
}, },
}; };
@@ -29,7 +29,7 @@ export default class KvSecretsListRoute extends Route {
.lazyPaginatedQuery('kv/metadata', { .lazyPaginatedQuery('kv/metadata', {
backend, backend,
responsePath: 'data.keys', responsePath: 'data.keys',
page: Number(params.currentPage) || 1, page: Number(params.page) || 1,
size: Number(params.currentPageSize), size: Number(params.currentPageSize),
pageFilter: params.pageFilter, pageFilter: params.pageFilter,
pathToSecret, pathToSecret,
@@ -90,7 +90,7 @@ export default class KvSecretsListRoute extends Route {
resetController(controller, isExiting) { resetController(controller, isExiting) {
if (isExiting) { if (isExiting) {
controller.set('pageFilter', null); controller.set('pageFilter', null);
controller.set('currentPage', null); controller.set('page', null);
} }
} }
} }

View File

@@ -35,7 +35,7 @@ export default class LdapRolesPageComponent extends Component<Args> {
} }
get paginationQueryParams() { get paginationQueryParams() {
return (page: number) => ({ currentPage: page }); return (page: number) => ({ page });
} }
@action @action

View File

@@ -6,5 +6,5 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
export default class LdapRolesController extends Controller { export default class LdapRolesController extends Controller {
queryParams = ['pageFilter', 'currentPage']; queryParams = ['pageFilter', 'page'];
} }

View File

@@ -25,11 +25,11 @@ interface LdapRolesController extends Controller {
breadcrumbs: Array<Breadcrumb>; breadcrumbs: Array<Breadcrumb>;
model: LdapRolesRouteModel; model: LdapRolesRouteModel;
pageFilter: string | undefined; pageFilter: string | undefined;
currentPage: number | undefined; page: number | undefined;
} }
interface LdapRolesRouteParams { interface LdapRolesRouteParams {
currentPage?: string; page?: string;
pageFilter: string; pageFilter: string;
} }
@@ -44,7 +44,7 @@ export default class LdapRolesRoute extends Route {
pageFilter: { pageFilter: {
refreshModel: true, refreshModel: true,
}, },
currentPage: { page: {
refreshModel: true, refreshModel: true,
}, },
}; };
@@ -58,7 +58,7 @@ export default class LdapRolesRoute extends Route {
'ldap/role', 'ldap/role',
{ {
backend: backendModel.id, backend: backendModel.id,
page: Number(params.currentPage) || 1, page: Number(params.page) || 1,
pageFilter: params.pageFilter, pageFilter: params.pageFilter,
responsePath: 'data.keys', responsePath: 'data.keys',
}, },
@@ -83,7 +83,7 @@ export default class LdapRolesRoute extends Route {
resetController(controller: LdapRolesController, isExiting: boolean) { resetController(controller: LdapRolesController, isExiting: boolean) {
if (isExiting) { if (isExiting) {
controller.set('pageFilter', undefined); controller.set('pageFilter', undefined);
controller.set('currentPage', undefined); controller.set('page', undefined);
} }
} }
} }

View File

@@ -35,6 +35,6 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
// Default page size is 15 // Default page size is 15
assert.strictEqual(store.peekAll('namespace').length, 15, 'Store has 15 namespaces records'); assert.strictEqual(store.peekAll('namespace').length, 15, 'Store has 15 namespaces records');
assert.dom('.list-item-row').exists({ count: 15 }); assert.dom('.list-item-row').exists({ count: 15 });
assert.dom('[data-test-list-view-pagination]').exists(); assert.dom('.hds-pagination').exists();
}); });
}); });