0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-27 22:49:56 -05:00

Fixed members table filters and URL handling in admin

fixes https://github.com/TryGhost/Team/issues/1344
fixes https://github.com/TryGhost/Team/issues/1127

This fixes a couple of bugs with the filter menu on the members page in admin:

- When opening the members page, the filters property was passed back from the filter component to the members controller. This caused a bug that the filter columns where not visible on reload.
- Fixed handling invalid filter parameters
- When updating the URL, the members page now properly reloads
- Fixed a bug that 'falsy' values in the NQL filter were removed on reload:
    - Filtering on unsubscribed members was gone after a page reload
    - Filtering on 0 emails was gone after a page reload
    - This is fixed by converting numbers and booleans to strings after parsing the NQL-filter
- Fixed a bug where boolean values didn't match any value in the select menu, causing the default option to be visible
    - Filtering members by 'unsubscribed' -> parsed as false (boolean) -> select menu opened -> false value (boolean) didn't match 'false' (string) so the first option was shown instead (subscribed).
    - This is also fixed by converting numbers and booleans to strings after parsing the NQL-filter

The way this is currently handled is not great. The parsing happens in the filter component, but should happen on a different layer, maybe in a different helper.
This is tracked here: https://github.com/TryGhost/Team/issues/1849
This commit is contained in:
Simon Backx 2022-08-25 13:53:22 +02:00
parent 403d24a01b
commit 9dbb2785bb
4 changed files with 70 additions and 5 deletions

View file

@ -4,7 +4,7 @@
class="gh-btn gh-btn-icon gh-btn-action-icon"
data-test-button="members-filter-actions"
>
<span class="{{if @isFiltered "gh-btn-label-green"}}">
<span class="{{if @isFiltered "gh-btn-label-green"}}" {{did-update this.parseDefaultFilters @parseFilterParamCounter}}>
{{svg-jar "filter"}}
Filter
{{#if @isFiltered}}

View file

@ -190,11 +190,27 @@ export default class MembersFilter extends Component {
constructor(...args) {
super(...args);
this.parseDefaultFilters();
this.fetchTiers.perform();
}
/**
* This method is not super clean as it uses did-update, but for now this is required to make URL changes work
* properly.
* Problem: filter parameter is changed in the members controller by modifying the URL directly
* -> the filters property is not updated in the members controller because the new parameter is not parsed again
* -> we need to listen for changes in the property and parse it again
* -> better future proof solution: move the filter parsing logic elsewhere so it can be parsed in the members controller
*/
@action
parseDefaultFilters() {
if (this.args.defaultFilterParam) {
this.parseNqlFilter(this.args.defaultFilterParam);
}
this.fetchTiers.perform();
// Pass the parsed filter to the parent component
// this doesn't start a new network request, and doesn't update filterParam again
this.applyParsedFilter();
}
}
@action
@ -329,6 +345,12 @@ export default class MembersFilter extends Component {
value = nqlValue;
}
if (typeof value === 'boolean' || typeof value === 'number') {
// Transform it to a string, to keep it compatible with the internally used value in admin
// + make sure false and 0 are truthy
value = value.toString();
}
if (relation && value) {
return new Filter({
type: key,
@ -342,14 +364,25 @@ export default class MembersFilter extends Component {
parseNqlFilter(filterParam) {
const validKeys = Object.keys(FILTER_RELATIONS_OPTIONS);
const filters = nql.parse(filterParam);
let filters;
try {
filters = nql.parse(filterParam);
} catch (e) {
// Invalid nql filter
this.filters = new TrackedArray([]);
return;
}
const filterKeys = Object.keys(filters);
let filterData = [];
if (filterKeys?.length === 1 && validKeys.includes(filterKeys[0])) {
const filterObj = this.parseNqlFilterKey(filters);
filterData = [filterObj];
if (filterObj) {
filterData = [filterObj];
}
} else if (filters?.$and) {
const andFilters = filters?.$and || [];
filterData = andFilters.filter((nqlFilter) => {
@ -485,6 +518,18 @@ export default class MembersFilter extends Component {
this.args.onApplyFilter(query, validFilters);
}
@action
applyParsedFilter() {
const validFilters = this.filters.filter((filter) => {
if (['label', 'tier'].includes(filter.type)) {
return filter.value?.length;
}
return filter.value;
});
this.args.onApplyParsedFilter(validFilters);
}
@action
resetFilter() {
const filters = [];

View file

@ -61,6 +61,8 @@ export default class MembersController extends Controller {
@tracked _availableLabels = A([]);
@tracked parseFilterParamCounter = 0;
paidParams = PAID_PARAMS;
constructor() {
@ -243,6 +245,15 @@ export default class MembersController extends Controller {
this.filters = filters;
}
/**
* Called to set the filters after the url filterParam has been parsed again
*/
@action
applyParsedFilter(filters) {
this.softFilters = A([]);
this.filters = filters;
}
@action
applySoftFilter(filterStr, filters) {
this.softFilters = filters;
@ -432,6 +443,13 @@ export default class MembersController extends Controller {
this.filters = A([]);
this.softFilterParam = null;
this.softFilters = A([]);
} else {
this.filterParam = params.filterParam;
// Trigger a did-update call in the filter component, so we get freshly parsed filters
// This is temporary, and a ugly pattern, but essential to make it work for now, until we moved the filter parsing logic
// out of the component
this.parseFilterParamCounter += 1;
}
}

View file

@ -27,9 +27,11 @@
@onApplyFilter={{this.applyFilter}}
@defaultFilterParam={{this.filterParam}}
@onApplySoftFilter={{this.applySoftFilter}}
@onApplyParsedFilter={{this.applyParsedFilter}}
@onResetSoftFilter={{this.resetSoftFilter}}
@onResetFilter={{this.resetFilter}}
@onLabelEdit={{this.editLabel}}
@parseFilterParamCounter={{this.parseFilterParamCounter}}
/>
<span class="dropdown">
<GhDropdownButton