mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-28 05:14:12 +03:00
Updated "Created" member filter to work against site timezone instead of UTC
no issue - updated NQL generation to adjust dates in filter string so they are the UTC equivalent - eg, in UTC-5 "created on or after 2022-02-22" becomes `created_at:>='2022-02-22 05:00:00'` - updated NQL parsing to take a UTC date filter, convert to a date in the site timezone, then convert to a local date in a way that the datepicker input value matches the respective site timezone date - eg, in UTC-5 `created_at:<='2022-02-22 04:59:59` becomes "created on or before 2022-02-21"
This commit is contained in:
parent
443689ffb3
commit
7470f887cb
@ -136,10 +136,15 @@ class Filter {
|
|||||||
this.type = options.type;
|
this.type = options.type;
|
||||||
this.relation = options.relation;
|
this.relation = options.relation;
|
||||||
this.relationOptions = options.relationOptions;
|
this.relationOptions = options.relationOptions;
|
||||||
|
this.timezone = options.timezone || 'Etc/UTC';
|
||||||
|
|
||||||
const filterProperty = FILTER_PROPERTIES.find(prop => this.type === prop.name);
|
const filterProperty = FILTER_PROPERTIES.find(prop => this.type === prop.name);
|
||||||
const value = filterProperty.valueType === 'date'
|
|
||||||
? moment(options.value).toDate()
|
// date string values are passed in as UTC strings
|
||||||
|
// we need to convert them to the site timezone and make a local date that matches
|
||||||
|
// so the date string output in the filter inputs is correct
|
||||||
|
const value = filterProperty.valueType === 'date' && typeof options.value === 'string'
|
||||||
|
? moment(moment.tz(moment.utc(options.value), this.timezone).format('YYYY-MM-DD')).toDate()
|
||||||
: options.value;
|
: options.value;
|
||||||
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@ -263,19 +268,23 @@ export default class MembersFilter extends Component {
|
|||||||
|
|
||||||
if (operator === '>') {
|
if (operator === '>') {
|
||||||
relationStr = '>';
|
relationStr = '>';
|
||||||
filterValue = `'${moment(filter.value).set({hour: 23, minute: 59, second: 59}).format(nqlDateFormat)}'`;
|
const tzMoment = moment.tz(moment(filter.value).format('YYYY-MM-DD'), this.settings.get('timezone')).set({hour: 23, minute: 59, second: 59});
|
||||||
|
filterValue = `'${tzMoment.utc().format(nqlDateFormat)}'`;
|
||||||
}
|
}
|
||||||
if (operator === '>=') {
|
if (operator === '>=') {
|
||||||
relationStr = '>=';
|
relationStr = '>=';
|
||||||
filterValue = `'${moment(filter.value).set({hour: 0, minute: 0, second: 0}).format(nqlDateFormat)}'`;
|
const tzMoment = moment.tz(moment(filter.value).format('YYYY-MM-DD'), this.settings.get('timezone')).set({hour: 0, minute: 0, second: 0});
|
||||||
|
filterValue = `'${tzMoment.utc().format(nqlDateFormat)}'`;
|
||||||
}
|
}
|
||||||
if (operator === '<') {
|
if (operator === '<') {
|
||||||
relationStr = '<';
|
relationStr = '<';
|
||||||
filterValue = `'${moment(filter.value).set({hour: 0, minute: 0, second: 0}).format(nqlDateFormat)}'`;
|
const tzMoment = moment.tz(moment(filter.value).format('YYYY-MM-DD'), this.settings.get('timezone')).set({hour: 0, minute: 0, second: 0});
|
||||||
|
filterValue = `'${tzMoment.utc().format(nqlDateFormat)}'`;
|
||||||
}
|
}
|
||||||
if (operator === '<=') {
|
if (operator === '<=') {
|
||||||
relationStr = '<=';
|
relationStr = '<=';
|
||||||
filterValue = `'${moment(filter.value).set({hour: 23, minute: 59, second: 59}).format(nqlDateFormat)}'`;
|
const tzMoment = moment.tz(moment(filter.value).format('YYYY-MM-DD'), this.settings.get('timeone')).set({hour: 23, minute: 59, second: 59});
|
||||||
|
filterValue = `'${tzMoment.utc().format(nqlDateFormat)}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
query += `${filter.type}:${relationStr}${filterValue}+`;
|
query += `${filter.type}:${relationStr}${filterValue}+`;
|
||||||
@ -302,7 +311,8 @@ export default class MembersFilter extends Component {
|
|||||||
type: key,
|
type: key,
|
||||||
relation: 'is',
|
relation: 'is',
|
||||||
value: value.$in,
|
value: value.$in,
|
||||||
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
relationOptions: FILTER_RELATIONS_OPTIONS[key],
|
||||||
|
timezone: this.settings.get('timezone')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +323,8 @@ export default class MembersFilter extends Component {
|
|||||||
type: key,
|
type: key,
|
||||||
relation: 'is-not',
|
relation: 'is-not',
|
||||||
value: value.$nin,
|
value: value.$nin,
|
||||||
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
relationOptions: FILTER_RELATIONS_OPTIONS[key],
|
||||||
|
timezone: this.settings.get('timezone')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +335,8 @@ export default class MembersFilter extends Component {
|
|||||||
type: key,
|
type: key,
|
||||||
relation: 'is-not',
|
relation: 'is-not',
|
||||||
value: value.$ne,
|
value: value.$ne,
|
||||||
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
relationOptions: FILTER_RELATIONS_OPTIONS[key],
|
||||||
|
timezone: this.settings.get('timezone')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +348,8 @@ export default class MembersFilter extends Component {
|
|||||||
type: key,
|
type: key,
|
||||||
relation: 'is-greater',
|
relation: 'is-greater',
|
||||||
value: value.$gt,
|
value: value.$gt,
|
||||||
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
relationOptions: FILTER_RELATIONS_OPTIONS[key],
|
||||||
|
timezone: this.settings.get('timezone')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +361,8 @@ export default class MembersFilter extends Component {
|
|||||||
type: key,
|
type: key,
|
||||||
relation: 'is-or-greater',
|
relation: 'is-or-greater',
|
||||||
value: value.$gte,
|
value: value.$gte,
|
||||||
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
relationOptions: FILTER_RELATIONS_OPTIONS[key],
|
||||||
|
timezone: this.settings.get('timezone')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +373,8 @@ export default class MembersFilter extends Component {
|
|||||||
type: key,
|
type: key,
|
||||||
relation: 'is-less',
|
relation: 'is-less',
|
||||||
value: value.$lt,
|
value: value.$lt,
|
||||||
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
relationOptions: FILTER_RELATIONS_OPTIONS[key],
|
||||||
|
timezone: this.settings.get('timezone')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +385,8 @@ export default class MembersFilter extends Component {
|
|||||||
type: key,
|
type: key,
|
||||||
relation: 'is-or-less',
|
relation: 'is-or-less',
|
||||||
value: value.$lte,
|
value: value.$lte,
|
||||||
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
relationOptions: FILTER_RELATIONS_OPTIONS[key],
|
||||||
|
timezone: this.settings.get('timezone')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,7 +699,7 @@ describe('Acceptance: Members filtering', function () {
|
|||||||
// with a site timezone UTC-5 (Eastern Time Zone) we would expect date-based NQL filter strings
|
// with a site timezone UTC-5 (Eastern Time Zone) we would expect date-based NQL filter strings
|
||||||
// to be adjusted to UTC.
|
// to be adjusted to UTC.
|
||||||
//
|
//
|
||||||
// Eg. "created at on or after 2022-02-22" = `created_at:>='2022-02-21 19:00:00'
|
// Eg. "created on or after 2022-02-22" = `created_at:>='2022-02-22 05:00:00'
|
||||||
//
|
//
|
||||||
// we also need to convert back when parsing the NQL-based query param and make sure dates
|
// we also need to convert back when parsing the NQL-based query param and make sure dates
|
||||||
// shown in the members table match site timezone
|
// shown in the members table match site timezone
|
||||||
@ -721,6 +721,34 @@ describe('Acceptance: Members filtering', function () {
|
|||||||
const createdAtFields = findAll('[data-test-list="members-list-item"] [data-test-table-data="created-at"]');
|
const createdAtFields = findAll('[data-test-list="members-list-item"] [data-test-table-data="created-at"]');
|
||||||
expect(createdAtFields.filter(el => el.textContent.match(/21 Feb 2022/)).length).to.equal(3);
|
expect(createdAtFields.filter(el => el.textContent.match(/21 Feb 2022/)).length).to.equal(3);
|
||||||
expect(createdAtFields.filter(el => el.textContent.match(/22 Feb 2022/)).length).to.equal(4);
|
expect(createdAtFields.filter(el => el.textContent.match(/22 Feb 2022/)).length).to.equal(4);
|
||||||
|
|
||||||
|
const filterSelect = `[data-test-members-filter="0"]`;
|
||||||
|
const typeSelect = `${filterSelect} [data-test-select="members-filter"]`;
|
||||||
|
const operatorSelect = `${filterSelect} [data-test-select="members-filter-operator"]`;
|
||||||
|
const valueInput = `${filterSelect} [data-test-input="members-filter-value"] [data-test-date-picker-input]`;
|
||||||
|
|
||||||
|
// filter date is transformed to UTC equivalent timeframe when querying
|
||||||
|
await click('[data-test-button="members-filter-actions"]');
|
||||||
|
await fillIn(typeSelect, 'created_at');
|
||||||
|
await fillIn(operatorSelect, 'is-or-greater');
|
||||||
|
await fillIn(valueInput, '2022-02-22');
|
||||||
|
await blur(valueInput);
|
||||||
|
|
||||||
|
expect(findAll('[data-test-list="members-list-item"]').length, '# of member rows - post filter')
|
||||||
|
.to.equal(4);
|
||||||
|
|
||||||
|
// query param is transformed back to expected filter date value
|
||||||
|
await visit('/'); // TODO: remove once <Members::Filter> component reacts to filter updates
|
||||||
|
const filterQuery = encodeURIComponent(`created_at:<='2022-02-22 04:59:59'`);
|
||||||
|
await visit(`/members?filter=${filterQuery}`);
|
||||||
|
|
||||||
|
expect(findAll('[data-test-list="members-list-item"]').length, '# of member rows - post URL parse')
|
||||||
|
.to.equal(3);
|
||||||
|
|
||||||
|
await click('[data-test-button="members-filter-actions"]');
|
||||||
|
|
||||||
|
expect(find(operatorSelect)).to.have.value('is-or-less');
|
||||||
|
expect(find(valueInput)).to.have.value('2022-02-21');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle multiple filters', async function () {
|
it('can handle multiple filters', async function () {
|
||||||
|
Loading…
Reference in New Issue
Block a user