Added access level filter to posts and pages lists in admin

no issue

- adds `visibility` query param to posts and pages controllers/routes that is tied to the `filter` query param used in API requests
- adds dropdown for selecting post/page visibility to `<GhContentFilter>`
This commit is contained in:
Kevin Ansfield 2020-06-09 12:19:25 +01:00
parent 1a69f391ea
commit 758f5285fb
12 changed files with 89 additions and 14 deletions

View File

@ -17,6 +17,24 @@
</div> </div>
{{/unless}} {{/unless}}
{{#if this.feature.members}}
<div class="gh-contentfilter-menu gh-contentfilter-visibility {{if @selectedVisibility.value "gh-contentfilter-selected"}}" data-test-visibility-select="true">
<PowerSelect
@selected={{@selectedVisibility}}
@options={{@availableVisibilities}}
@searchEnabled={{false}}
@onChange={{@onVisibilityChange}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@matchTriggerWidth={{false}}
as |visibility|
>
{{#if visibility.name}}{{visibility.name}}{{else}}<span class="red">Unknown visibility</span>{{/if}}
</PowerSelect>
</div>
{{/if}}
{{#unless @currentUser.isAuthorOrContributor}} {{#unless @currentUser.isAuthorOrContributor}}
<div class="gh-contentfilter-menu gh-contentfilter-author {{if @selectedAuthor.slug "gh-contentfilter-selected"}}" data-test-author-select="true"> <div class="gh-contentfilter-menu gh-contentfilter-author {{if @selectedAuthor.slug "gh-contentfilter-selected"}}" data-test-author-select="true">
<PowerSelect <PowerSelect

View File

@ -4,6 +4,7 @@ import {inject as service} from '@ember/service';
export default class GhContentfilterComponent extends Component { export default class GhContentfilterComponent extends Component {
@service customViews; @service customViews;
@service feature;
@service router; @service router;
get showCustomViewManagement() { get showCustomViewManagement() {
@ -11,6 +12,7 @@ export default class GhContentfilterComponent extends Component {
let onPostsScreen = this.router.currentRouteName === 'posts'; let onPostsScreen = this.router.currentRouteName === 'posts';
let isDefaultView = this.customViews?.activeView?.isDefault; let isDefaultView = this.customViews?.activeView?.isDefault;
let hasFilter = this.args.selectedType.value let hasFilter = this.args.selectedType.value
|| this.args.selectedVisibility.value
|| this.args.selectedAuthor.slug || this.args.selectedAuthor.slug
|| this.args.selectedTag.slug || this.args.selectedTag.slug
|| this.args.selectedOrder.value; || this.args.selectedOrder.value;

View File

@ -76,13 +76,11 @@
{{/unless}} {{/unless}}
{{#if this.feature.members}} {{#if (and this.feature.members this.showVisibilityInput)}}
{{#if this.showVisibilityInput}} <div class="form-group">
<div class="form-group"> <label for="visibility-input">Post access</label>
<label for="visibility-input">Post access</label> <GhPsmVisibilityInput @post={{this.post}} @triggerId="visibility-input" />
<GhPsmVisibilityInput @post={{this.post}} @triggerId="visibility-input" /> </div>
</div>
{{/if}}
{{/if}} {{/if}}

View File

@ -11,6 +11,8 @@ export default Controller.extend({
availableTypes: readOnly('postsController.availableTypes'), availableTypes: readOnly('postsController.availableTypes'),
selectedType: readOnly('postsController.selectedType'), selectedType: readOnly('postsController.selectedType'),
selectedVisibility: readOnly('postsController.selectedVisibility'),
availableVisibilities: readOnly('postsController.availableVisibilities'),
availableTags: readOnly('postsController.availableTags'), availableTags: readOnly('postsController.availableTags'),
selectedTag: readOnly('postsController.selectedTag'), selectedTag: readOnly('postsController.selectedTag'),
availableAuthors: readOnly('postsController.availableAuthors'), availableAuthors: readOnly('postsController.availableAuthors'),

View File

@ -22,6 +22,20 @@ const TYPES = [{
value: 'featured' value: 'featured'
}]; }];
const VISIBILITIES = [{
name: 'All access',
value: null
}, {
name: 'Public',
value: 'public'
}, {
name: 'Members-only',
value: 'members'
}, {
name: 'Paid members-only',
value: 'paid'
}];
const ORDERS = [{ const ORDERS = [{
name: 'Newest', name: 'Newest',
value: null value: null
@ -38,27 +52,29 @@ export default Controller.extend({
store: service(), store: service(),
// default values for these are set in `init` and defined in `helpers/reset-query-params` // default values for these are set in `init` and defined in `helpers/reset-query-params`
queryParams: ['type', 'author', 'tag', 'order'], queryParams: ['type', 'access', 'author', 'tag', 'order'],
_hasLoadedTags: false, _hasLoadedTags: false,
_hasLoadedAuthors: false, _hasLoadedAuthors: false,
availableTypes: null, availableTypes: null,
availableVisibilities: null,
availableOrders: null, availableOrders: null,
init() { init() {
this._super(...arguments); this._super(...arguments);
this.availableTypes = TYPES; this.availableTypes = TYPES;
this.availableOrders = ORDERS; this.availableOrders = ORDERS;
this.availableVisibilities = VISIBILITIES;
this.setProperties(DEFAULT_QUERY_PARAMS.posts); this.setProperties(DEFAULT_QUERY_PARAMS.posts);
}, },
postsInfinityModel: alias('model'), postsInfinityModel: alias('model'),
showingAll: computed('type', 'author', 'tag', function () { showingAll: computed('type', 'author', 'tag', function () {
let {type, author, tag} = this.getProperties(['type', 'author', 'tag']); let {type, author, tag, visibility} = this.getProperties(['type', 'visibility', 'author', 'tag']);
return !type && !author && !tag; return !type && !visibility && !author && !tag;
}), }),
selectedType: computed('type', function () { selectedType: computed('type', function () {
@ -66,6 +82,11 @@ export default Controller.extend({
return types.findBy('value', this.get('type')) || {value: '!unknown'}; return types.findBy('value', this.get('type')) || {value: '!unknown'};
}), }),
selectedVisibility: computed('visibility', function () {
let visibilities = this.get('availableVisibilities');
return visibilities.findBy('value', this.get('visibility')) || {value: '!unknown'};
}),
selectedOrder: computed('order', function () { selectedOrder: computed('order', function () {
let orders = this.get('availableOrders'); let orders = this.get('availableOrders');
return orders.findBy('value', this.get('order')) || {value: '!unknown'}; return orders.findBy('value', this.get('order')) || {value: '!unknown'};
@ -117,6 +138,10 @@ export default Controller.extend({
this.set('type', get(type, 'value')); this.set('type', get(type, 'value'));
}, },
changeVisibility(visibility) {
this.set('visibility', get(visibility, 'value'));
},
changeAuthor(author) { changeAuthor(author) {
this.set('author', get(author, 'slug')); this.set('author', get(author, 'slug'));
}, },

View File

@ -3,12 +3,14 @@ import {helper} from '@ember/component/helper';
export const DEFAULT_QUERY_PARAMS = { export const DEFAULT_QUERY_PARAMS = {
posts: { posts: {
type: null, type: null,
visibility: null,
author: null, author: null,
tag: null, tag: null,
order: null order: null
}, },
pages: { pages: {
type: null, type: null,
visibility: null,
author: null, author: null,
tag: null, tag: null,
order: null order: null

View File

@ -9,6 +9,8 @@ export default AuthenticatedRoute.extend({
queryParams: { queryParams: {
type: {refreshModel: true}, type: {refreshModel: true},
visibility: {refreshModel: true},
access: {refreshModel: true},
author: {refreshModel: true}, author: {refreshModel: true},
tag: {refreshModel: true}, tag: {refreshModel: true},
order: {refreshModel: true} order: {refreshModel: true}
@ -38,7 +40,7 @@ export default AuthenticatedRoute.extend({
model(params) { model(params) {
return this.session.user.then((user) => { return this.session.user.then((user) => {
let queryParams = {}; let queryParams = {};
let filterParams = {tag: params.tag}; let filterParams = {tag: params.tag, visibility: params.visibility};
let paginationParams = { let paginationParams = {
perPageParam: 'limit', perPageParam: 'limit',
totalPagesParam: 'meta.pagination.pages' totalPagesParam: 'meta.pagination.pages'

View File

@ -7,6 +7,9 @@
@selectedType={{this.selectedType}} @selectedType={{this.selectedType}}
@availableTypes={{this.availableTypes}} @availableTypes={{this.availableTypes}}
@onTypeChange={{action (mut k)}} @onTypeChange={{action (mut k)}}
@selectedVisibility={{this.selectedVisibility}}
@availableVisibilities={{this.availableVisibilities}}
@onVisibilityChange={{action (mut k)}}
@selectedAuthor={{this.selectedAuthor}} @selectedAuthor={{this.selectedAuthor}}
@availableAuthors={{this.availableAuthors}} @availableAuthors={{this.availableAuthors}}
@onAuthorChange={{action (mut k)}} @onAuthorChange={{action (mut k)}}

View File

@ -8,6 +8,9 @@
@selectedType={{this.selectedType}} @selectedType={{this.selectedType}}
@availableTypes={{this.availableTypes}} @availableTypes={{this.availableTypes}}
@onTypeChange={{action "changeType"}} @onTypeChange={{action "changeType"}}
@selectedVisibility={{this.selectedVisibility}}
@availableVisibilities={{this.availableVisibilities}}
@onVisibilityChange={{action "changeVisibility"}}
@selectedAuthor={{this.selectedAuthor}} @selectedAuthor={{this.selectedAuthor}}
@availableAuthors={{this.availableAuthors}} @availableAuthors={{this.availableAuthors}}
@onAuthorChange={{action "changeAuthor"}} @onAuthorChange={{action "changeAuthor"}}

View File

@ -7,6 +7,9 @@
@selectedType={{this.selectedType}} @selectedType={{this.selectedType}}
@availableTypes={{this.availableTypes}} @availableTypes={{this.availableTypes}}
@onTypeChange={{action (mut k)}} @onTypeChange={{action (mut k)}}
@selectedVisibility={{this.selectedVisibility}}
@availableVisibilities={{this.availableVisibilities}}
@onVisibilityChange={{action (mut k)}}
@selectedAuthor={{this.selectedAuthor}} @selectedAuthor={{this.selectedAuthor}}
@availableAuthors={{this.availableAuthors}} @availableAuthors={{this.availableAuthors}}
@onAuthorChange={{action (mut k)}} @onAuthorChange={{action (mut k)}}

View File

@ -8,6 +8,9 @@
@selectedType={{this.selectedType}} @selectedType={{this.selectedType}}
@availableTypes={{this.availableTypes}} @availableTypes={{this.availableTypes}}
@onTypeChange={{action "changeType"}} @onTypeChange={{action "changeType"}}
@selectedVisibility={{this.selectedVisibility}}
@availableVisibilities={{this.availableVisibilities}}
@onVisibilityChange={{action "changeVisibility"}}
@selectedAuthor={{this.selectedAuthor}} @selectedAuthor={{this.selectedAuthor}}
@availableAuthors={{this.availableAuthors}} @availableAuthors={{this.availableAuthors}}
@onAuthorChange={{action "changeAuthor"}} @onAuthorChange={{action "changeAuthor"}}

View File

@ -1,6 +1,6 @@
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import {beforeEach, describe, it} from 'mocha'; import {beforeEach, describe, it} from 'mocha';
import {click, currentURL, fillIn, find, findAll, visit} from '@ember/test-helpers'; import {click, currentURL, fillIn, find, findAll, settled, visit} from '@ember/test-helpers';
import {clickTrigger, selectChoose} from 'ember-power-select/test-support/helpers'; import {clickTrigger, selectChoose} from 'ember-power-select/test-support/helpers';
import {expect} from 'chai'; import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; import {setupApplicationTest} from 'ember-mocha';
@ -86,8 +86,22 @@ describe('Acceptance: Content', function () {
// API request is correct // API request is correct
[lastRequest] = this.server.pretender.handledRequests.slice(-1); [lastRequest] = this.server.pretender.handledRequests.slice(-1);
expect(lastRequest.queryParams.filter, '"editor" request status filter').to.have.string('status:[draft,scheduled,published]'); expect(lastRequest.queryParams.filter, '"editor" request status filter')
expect(lastRequest.queryParams.filter, '"editor" request filter param').to.have.string(`authors:${editor.slug}`); .to.have.string('status:[draft,scheduled,published]');
expect(lastRequest.queryParams.filter, '"editor" request filter param')
.to.have.string(`authors:${editor.slug}`);
// Post status is only visible when members is enabled
expect(find('[data-test-visibility-select]'), 'access dropdown before members enabled').to.not.exist;
let featureService = this.owner.lookup('service:feature');
featureService.set('members', true);
await settled();
expect(find('[data-test-visibility-select]'), 'access dropdown after members enabled').to.exist;
await selectChoose('[data-test-visibility-select]', 'Paid members-only');
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
expect(lastRequest.queryParams.filter, '"visibility" request filter param')
.to.have.string('visibility:paid+status:[draft,scheduled,published]');
// Displays editor post // Displays editor post
// TODO: implement "filter" param support and fix mirage post->author association // TODO: implement "filter" param support and fix mirage post->author association