ui: Reconcile ember-data store when records are deleted via blocking (#5745)

* ui: Reconciliate ember-data store when records are deleted via blocking

Currently we are barely using the ember-data store/cache, but it will
still cache records in the store even though technically we aren't using
it.

This adds a SyncTime to every record that uses blocking queries so we
can delete older records from the ember-data cache to prevent them
building up

* ui: Add basic timestamp method we can access from tests, fixup tests

Adds a timestamp method that we can access from within tests so we can
test that the SyncTime is being set.

There is probably a better way to do this, but this is also probably the
simplest approach - we are also likely to revisit this at a later date
This commit is contained in:
John Cowen 2019-05-15 14:44:57 +01:00 committed by John Cowen
parent fdd8aa5f2e
commit f9710c2c92
11 changed files with 58 additions and 8 deletions

View file

@ -10,4 +10,5 @@ export default Model.extend({
Coord: attr(), Coord: attr(),
Segment: attr('string'), Segment: attr('string'),
Datacenter: attr('string'), Datacenter: attr('string'),
SyncTime: attr('number'),
}); });

View file

@ -21,6 +21,7 @@ export default Model.extend({
Datacenter: attr('string'), Datacenter: attr('string'),
Segment: attr(), Segment: attr(),
Coord: attr(), Coord: attr(),
SyncTime: attr('number'),
meta: attr(), meta: attr(),
hasStatus: function(status) { hasStatus: function(status) {
return hasStatus(get(this, 'Checks'), status); return hasStatus(get(this, 'Checks'), status);

View file

@ -10,4 +10,5 @@ export default Model.extend({
ServiceID: attr('string'), ServiceID: attr('string'),
Node: attr('string'), Node: attr('string'),
ServiceProxy: attr(), ServiceProxy: attr(),
SyncTime: attr('number'),
}); });

View file

@ -31,6 +31,7 @@ export default Model.extend({
Node: attr(), Node: attr(),
Service: attr(), Service: attr(),
Checks: attr(), Checks: attr(),
SyncTime: attr('number'),
meta: attr(), meta: attr(),
passing: computed('ChecksPassing', 'Checks', function() { passing: computed('ChecksPassing', 'Checks', function() {
let num = 0; let num = 0;

View file

@ -20,4 +20,5 @@ export default Model.extend({
}, },
}), }),
Datacenter: attr('string'), Datacenter: attr('string'),
SyncTime: attr('number'),
}); });

View file

@ -1,6 +1,6 @@
import Serializer from 'ember-data/serializers/rest'; import Serializer from 'ember-data/serializers/rest';
import { get } from '@ember/object'; import { set } from '@ember/object';
import { import {
HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL, HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL,
HEADERS_INDEX as HTTP_HEADERS_INDEX, HEADERS_INDEX as HTTP_HEADERS_INDEX,
@ -44,14 +44,17 @@ export default Serializer.extend({
requestType requestType
); );
}, },
timestamp: function() {
return new Date().getTime();
},
normalizeMeta: function(store, primaryModelClass, headers, payload, id, requestType) { normalizeMeta: function(store, primaryModelClass, headers, payload, id, requestType) {
const meta = { const meta = {
cursor: headers[HTTP_HEADERS_INDEX], cursor: headers[HTTP_HEADERS_INDEX],
date: headers['date'],
}; };
if (requestType === 'query') { if (requestType === 'query') {
meta.ids = payload.map(item => { meta.date = this.timestamp();
return get(item, this.primaryKey); payload.forEach(function(item) {
set(item, 'SyncTime', meta.date);
}); });
} }
return meta; return meta;

View file

@ -6,8 +6,27 @@ import LazyProxyService from 'consul-ui/services/lazy-proxy';
import { cache as createCache, BlockingEventSource } from 'consul-ui/utils/dom/event-source'; import { cache as createCache, BlockingEventSource } from 'consul-ui/utils/dom/event-source';
const createProxy = function(repo, find, settings, cache, serialize = JSON.stringify) { const createProxy = function(repo, find, settings, cache, serialize = JSON.stringify) {
// proxied find*..(id, dc)
const client = get(this, 'client'); const client = get(this, 'client');
const store = get(this, 'store');
// custom createEvent, here used to reconcile the ember-data store for each tick
const createEvent = function(result, configuration) {
const event = {
type: 'message',
data: result,
};
const meta = get(event.data || {}, 'meta');
if (typeof meta.date !== 'undefined') {
// unload anything older than our current sync date/time
store.peekAll(repo.getModelName()).forEach(function(item) {
const date = get(item, 'SyncTime');
if (typeof date !== 'undefined' && date != meta.date) {
store.unloadRecord(item);
}
});
}
return event;
};
// proxied find*..(id, dc)
return function() { return function() {
const key = `${repo.getModelName()}.${find}.${serialize([...arguments])}`; const key = `${repo.getModelName()}.${find}.${serialize([...arguments])}`;
const _args = arguments; const _args = arguments;
@ -48,6 +67,7 @@ const createProxy = function(repo, find, settings, cache, serialize = JSON.strin
settings: { settings: {
enabled: settings.blocking, enabled: settings.blocking,
}, },
createEvent: createEvent,
} }
); );
}; };

View file

@ -1,13 +1,18 @@
import { moduleFor, test } from 'ember-qunit'; import { moduleFor, test } from 'ember-qunit';
import repo from 'consul-ui/tests/helpers/repo'; import repo from 'consul-ui/tests/helpers/repo';
import { get } from '@ember/object';
const NAME = 'coordinate'; const NAME = 'coordinate';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
// Specify the other units that are required for this test. // Specify the other units that are required for this test.
integration: true integration: true,
}); });
const dc = 'dc-1'; const dc = 'dc-1';
const now = new Date().getTime();
test('findAllByDatacenter returns the correct data for list endpoint', function(assert) { test('findAllByDatacenter returns the correct data for list endpoint', function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now;
};
return repo( return repo(
'Coordinate', 'Coordinate',
'findAllByDatacenter', 'findAllByDatacenter',
@ -26,6 +31,7 @@ test('findAllByDatacenter returns the correct data for list endpoint', function(
expected(function(payload) { expected(function(payload) {
return payload.map(item => return payload.map(item =>
Object.assign({}, item, { Object.assign({}, item, {
SyncTime: now,
Datacenter: dc, Datacenter: dc,
uid: `["${dc}","${item.Node}"]`, uid: `["${dc}","${item.Node}"]`,
}) })

View file

@ -1,5 +1,6 @@
import { moduleFor, test } from 'ember-qunit'; import { moduleFor, test } from 'ember-qunit';
import repo from 'consul-ui/tests/helpers/repo'; import repo from 'consul-ui/tests/helpers/repo';
import { get } from '@ember/object';
const NAME = 'node'; const NAME = 'node';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
// Specify the other units that are required for this test. // Specify the other units that are required for this test.
@ -8,7 +9,11 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
const dc = 'dc-1'; const dc = 'dc-1';
const id = 'token-name'; const id = 'token-name';
const now = new Date().getTime();
test('findByDatacenter returns the correct data for list endpoint', function(assert) { test('findByDatacenter returns the correct data for list endpoint', function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now;
};
return repo( return repo(
'Node', 'Node',
'findAllByDatacenter', 'findAllByDatacenter',
@ -27,6 +32,7 @@ test('findByDatacenter returns the correct data for list endpoint', function(ass
expected(function(payload) { expected(function(payload) {
return payload.map(item => return payload.map(item =>
Object.assign({}, item, { Object.assign({}, item, {
SyncTime: now,
Datacenter: dc, Datacenter: dc,
uid: `["${dc}","${item.ID}"]`, uid: `["${dc}","${item.ID}"]`,
}) })
@ -56,7 +62,6 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
Datacenter: dc, Datacenter: dc,
uid: `["${dc}","${item.ID}"]`, uid: `["${dc}","${item.ID}"]`,
meta: { meta: {
date: undefined,
cursor: undefined, cursor: undefined,
}, },
}); });

View file

@ -1,6 +1,7 @@
import { moduleFor, test } from 'ember-qunit'; import { moduleFor, test } from 'ember-qunit';
import { skip } from 'qunit'; import { skip } from 'qunit';
import repo from 'consul-ui/tests/helpers/repo'; import repo from 'consul-ui/tests/helpers/repo';
import { get } from '@ember/object';
const NAME = 'service'; const NAME = 'service';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
// Specify the other units that are required for this test. // Specify the other units that are required for this test.
@ -8,7 +9,11 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
}); });
const dc = 'dc-1'; const dc = 'dc-1';
const id = 'token-name'; const id = 'token-name';
const now = new Date().getTime();
test('findByDatacenter returns the correct data for list endpoint', function(assert) { test('findByDatacenter returns the correct data for list endpoint', function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now;
};
return repo( return repo(
'Service', 'Service',
'findAllByDatacenter', 'findAllByDatacenter',
@ -27,6 +32,7 @@ test('findByDatacenter returns the correct data for list endpoint', function(ass
expected(function(payload) { expected(function(payload) {
return payload.map(item => return payload.map(item =>
Object.assign({}, item, { Object.assign({}, item, {
SyncTime: now,
Datacenter: dc, Datacenter: dc,
uid: `["${dc}","${item.Name}"]`, uid: `["${dc}","${item.Name}"]`,
}) })
@ -69,7 +75,6 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
service.Nodes = nodes; service.Nodes = nodes;
service.Tags = payload.Nodes[0].Service.Tags; service.Tags = payload.Nodes[0].Service.Tags;
service.meta = { service.meta = {
date: undefined,
cursor: undefined, cursor: undefined,
}; };

View file

@ -1,5 +1,6 @@
import { moduleFor, test } from 'ember-qunit'; import { moduleFor, test } from 'ember-qunit';
import repo from 'consul-ui/tests/helpers/repo'; import repo from 'consul-ui/tests/helpers/repo';
import { get } from '@ember/object';
const NAME = 'session'; const NAME = 'session';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
// Specify the other units that are required for this test. // Specify the other units that are required for this test.
@ -8,7 +9,11 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
const dc = 'dc-1'; const dc = 'dc-1';
const id = 'node-name'; const id = 'node-name';
const now = new Date().getTime();
test('findByNode returns the correct data for list endpoint', function(assert) { test('findByNode returns the correct data for list endpoint', function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now;
};
return repo( return repo(
'Session', 'Session',
'findByNode', 'findByNode',
@ -27,6 +32,7 @@ test('findByNode returns the correct data for list endpoint', function(assert) {
expected(function(payload) { expected(function(payload) {
return payload.map(item => return payload.map(item =>
Object.assign({}, item, { Object.assign({}, item, {
SyncTime: now,
Datacenter: dc, Datacenter: dc,
uid: `["${dc}","${item.ID}"]`, uid: `["${dc}","${item.ID}"]`,
}) })