0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-08 02:52:39 -05:00

Added data-cache service for managing limited-lifetime cached data

refs https://github.com/TryGhost/Team/issues/1277

- `data-cache` service has a `.set(key, data, lifetime)` method that will store the data under the key and sets a timeout that will remove the data when the lifetime expires
  - data can be retrieved with `.get(key)`
  - allows for components to cache data for use when re-rendering without having to worry about keeping track of their state and it's expiration manually somewhere else
- moved caching concern out of the `members-activity` service and into the latest-member-activity dashboard component which is the one that cares about it's data and cache lifetime
  - frees the `members-activity` service up to be more generic as it's no longer tied to the dashboard component's concerns
  - component switched to using a task rather than a promise so it is automatically cancelled if it's destroyed before data fetching is complete
This commit is contained in:
Kevin Ansfield 2022-01-21 12:56:15 +00:00
parent 3c2a322d79
commit ec6a5637e8
3 changed files with 61 additions and 26 deletions

View file

@ -3,6 +3,8 @@ import {inject as service} from '@ember/service';
import {tracked} from '@glimmer/tracking';
export default class DashboardLatestMemberActivityComponent extends Component {
@service dataCache;
@service feature;
@service membersActivity;
@service session;
@service settings;
@ -31,10 +33,25 @@ export default class DashboardLatestMemberActivityComponent extends Component {
}
async loadEvents() {
const limit = 5;
const filter = this.feature.membersActivity ?
'type:-[email_delivered_event,email_opened_event,email_failed_event]' :
'';
const dataKey = `dashboard-member-activity::${JSON.stringify({limit, filter})}`;
if (this.dataCache.get(dataKey)) {
this.eventsData = this.dataCache.get(dataKey);
return;
}
try {
this.eventsLoading = true;
const {events} = await this.membersActivity.fetchTimeline({limit: 5});
const {events} = await this.membersActivity.fetchTask.perform({limit, filter});
this.eventsData = events;
const ONE_MINUTE = 1 * 60 * 1000;
this.dataCache.set(dataKey, events, ONE_MINUTE);
} catch (error) {
this.eventsError = error;
} finally {

View file

@ -0,0 +1,37 @@
import Service from '@ember/service';
const ONE_MINUTE = 1 * 60 * 1000;
export default class DataCacheService extends Service {
cache = {};
timeouts = {};
get(key) {
return this.cache[key];
}
set(key, data, lifetime = ONE_MINUTE) {
this.cache[key] = data;
this.timeouts[key] = window.setTimeout(() => {
delete this.cache[key];
delete this.timeouts[key];
}, lifetime);
return this.cache[key];
}
clear() {
this._clearAllTimeouts();
this.cache = {};
this.timeouts = {};
}
willDestroy() {
this._clearAllTimeouts();
}
_clearAllTimeouts() {
Object.keys(this.timeouts).forEach(key => window.clearTimeout(this.timeouts[key]));
}
}

View file

@ -1,38 +1,19 @@
import Service from '@ember/service';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
const ONE_MINUTE = 1 * 60 * 1000;
export default class MembersActivityService extends Service {
@service ajax;
@service ghostPaths;
_lastFetchedTimeline = null;
_lastFetchedTimelineLimit = null;
async fetchTimeline(options = {}) {
let staleData = this._lastFetchedTimeline && (new Date() - this._lastFetchedTimeline) > ONE_MINUTE;
let differentLimit = this._lastFetchedTimelineLimit && this._lastFetchedTimelineLimit !== options.limit;
if (this._fetchTimelineTask.isRunning) {
return this._fetchTimelineTask.last;
}
if (this.events && !staleData && !differentLimit) {
return this.events;
}
return this._fetchTimelineTask.perform(options.limit);
async fetch(options = {}) {
return this._fetchTask.perform(options);
}
@task
*_fetchTimelineTask(limit) {
this._lastFetchedTimeline = new Date();
this._lastFetchedTimelineLimit = limit;
let eventsUrl = this.ghostPaths.url.api('members/events');
let events = yield this.ajax.request(eventsUrl, {data: {limit}});
this.events = events;
*fetchTask({limit, filter}) {
const eventsUrl = this.ghostPaths.url.api('members/events');
const events = yield this.ajax.request(eventsUrl, {data: {limit, filter}});
return events;
}
}