diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js index 23890bfdd1..3d04240b13 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js @@ -3,7 +3,7 @@ import { shallowMount, mount } from '@vue/test-utils'; import { AssessmentItemToolbarActions } from '../../constants'; import { assessmentItemKey } from '../../utils'; import AssessmentEditor from './AssessmentEditor'; -import { AssessmentItemTypes, ValidationErrors } from 'shared/constants'; +import { AssessmentItemTypes, ValidationErrors, DELAYED_VALIDATION } from 'shared/constants'; jest.mock('shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue'); jest.mock('shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue'); @@ -306,7 +306,7 @@ describe('AssessmentEditor', () => { answers: [], hints: [], order: 1, - isNew: true, + [DELAYED_VALIDATION]: true, }); }); @@ -348,7 +348,7 @@ describe('AssessmentEditor', () => { answers: [], hints: [], order: 2, - isNew: true, + [DELAYED_VALIDATION]: true, }); expect(listeners.addItem).toBeCalledTimes(1); }); @@ -433,7 +433,7 @@ describe('AssessmentEditor', () => { answers: [], hints: [], order: 4, - isNew: true, + [DELAYED_VALIDATION]: true, }); }); }); diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue index 89ee79dc04..a4fc50ccae 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue @@ -132,7 +132,7 @@ import AssessmentItemEditor from '../AssessmentItemEditor/AssessmentItemEditor'; import AssessmentItemPreview from '../AssessmentItemPreview/AssessmentItemPreview'; import Checkbox from 'shared/views/form/Checkbox'; - import { AssessmentItemTypes } from 'shared/constants'; + import { AssessmentItemTypes, DELAYED_VALIDATION } from 'shared/constants'; function areItemsEqual(item1, item2) { if (!item1 || !item2) { @@ -245,7 +245,7 @@ } this.$emit('updateItem', { ...assessmentItemKey(this.activeItem), - isNew: false, + [DELAYED_VALIDATION]: false, }); this.activeItem = null; }, @@ -313,8 +313,8 @@ type: AssessmentItemTypes.SINGLE_SELECTION, answers: [], hints: [], - isNew: true, order: newItemOrder, + [DELAYED_VALIDATION]: true, }; let reorderedItems = [...this.sortedItems]; diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentTab/AssessmentTab.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentTab/AssessmentTab.vue index aad1c17a5a..aff8a9594b 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentTab/AssessmentTab.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentTab/AssessmentTab.vue @@ -91,15 +91,15 @@ return this.getAssessmentItems(this.nodeId); }, areAssessmentItemsValid() { - return this.getAssessmentItemsAreValid({ contentNodeId: this.nodeId, ignoreNew: true }); + return this.getAssessmentItemsAreValid({ contentNodeId: this.nodeId, ignoreDelayed: true }); }, assessmentItemsErrors() { - return this.getAssessmentItemsErrors({ contentNodeId: this.nodeId, ignoreNew: true }); + return this.getAssessmentItemsErrors({ contentNodeId: this.nodeId, ignoreDelayed: true }); }, invalidItemsErrorMessage() { const invalidItemsCount = this.getInvalidAssessmentItemsCount({ contentNodeId: this.nodeId, - ignoreNew: true, + ignoreDelayed: true, }); if (!invalidItemsCount) { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue b/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue index fbd3c06062..29dc6cdea9 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/ContentNodeListItem/index.vue @@ -47,7 +47,7 @@

@@ -194,7 +194,7 @@ import ContentNodeValidator from '../ContentNodeValidator'; import ContentNodeChangedIcon from '../ContentNodeChangedIcon'; import TaskProgress from '../../views/progress/TaskProgress'; - import { ContentLevel, Categories } from '../../../shared/constants'; + import { ContentLevel, Categories, NEW_OBJECT } from 'shared/constants'; import { ContentKindsNames } from 'shared/leUtils/ContentKinds'; import { RolesNames } from 'shared/leUtils/Roles'; import ImageOnlyThumbnail from 'shared/views/files/ImageOnlyThumbnail'; @@ -262,6 +262,9 @@ isTopic() { return this.node.kind === ContentKindsNames.TOPIC; }, + isNew() { + return this.node[NEW_OBJECT]; + }, thumbnailAttrs() { const { title, kind, thumbnail_src: src, thumbnail_encoding: encoding } = this.node; return { title, kind, src, encoding }; diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue index 0a8391a5fc..24e43bf2c6 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditModal.vue @@ -196,6 +196,7 @@ import BottomBar from 'shared/views/BottomBar'; import FileDropzone from 'shared/views/files/FileDropzone'; import { isNodeComplete } from 'shared/utils/validation'; + import { DELAYED_VALIDATION } from 'shared/constants'; const CHECK_STORAGE_INTERVAL = 10000; @@ -422,7 +423,7 @@ // X button action this.enableValidation(this.nodeIds); let assessmentItems = this.getAssessmentItems(this.nodeIds); - assessmentItems.forEach(item => (item.question ? (item.isNew = false) : '')); + assessmentItems.forEach(item => (item.question ? (item[DELAYED_VALIDATION] = false) : '')); this.updateAssessmentItems(assessmentItems); // reaches into Details Tab to run save of diffTracker // before the validation pop up is executed diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue index 4fbe0d2ab3..f3ce855b2e 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue @@ -187,7 +187,7 @@ areAssessmentItemsValid() { return ( !this.oneSelected || - this.getAssessmentItemsAreValid({ contentNodeId: this.nodeIds[0], ignoreNew: true }) + this.getAssessmentItemsAreValid({ contentNodeId: this.nodeIds[0], ignoreDelayed: true }) ); }, areFilesValid() { diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/__tests__/getters.spec.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/__tests__/getters.spec.js index 58381ce3ee..57878d72b3 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/__tests__/getters.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/__tests__/getters.spec.js @@ -5,7 +5,7 @@ import { getInvalidAssessmentItemsCount, getAssessmentItemsAreValid, } from '../getters'; -import { AssessmentItemTypes, ValidationErrors } from 'shared/constants'; +import { AssessmentItemTypes, DELAYED_VALIDATION, ValidationErrors } from 'shared/constants'; describe('assessmentItem getters', () => { let state; @@ -45,7 +45,7 @@ describe('assessmentItem getters', () => { 'assessment-id-3': { assessment_id: 'assessment-id-3', contentnode: 'content-node-id-2', - isNew: true, + [DELAYED_VALIDATION]: true, type: AssessmentItemTypes.SINGLE_SELECTION, question: 'What color are minions?', answers: [ @@ -67,7 +67,7 @@ describe('assessmentItem getters', () => { 'assessment-id-4': { assessment_id: 'assessment-id-4', contentnode: 'content-node-id-3', - isNew: true, + [DELAYED_VALIDATION]: true, type: AssessmentItemTypes.SINGLE_SELECTION, question: '', answers: [], @@ -75,7 +75,7 @@ describe('assessmentItem getters', () => { 'assessment-id-5': { assessment_id: 'assessment-id-5', contentnode: 'content-node-id-3', - isNew: true, + [DELAYED_VALIDATION]: true, type: AssessmentItemTypes.SINGLE_SELECTION, question: '', answers: [], @@ -103,7 +103,7 @@ describe('assessmentItem getters', () => { { assessment_id: 'assessment-id-3', contentnode: 'content-node-id-2', - isNew: true, + [DELAYED_VALIDATION]: true, type: AssessmentItemTypes.SINGLE_SELECTION, question: 'What color are minions?', answers: [ @@ -145,9 +145,9 @@ describe('assessmentItem getters', () => { }); }); - it("doesn't include invalid nodes errors that are new if `ignoreNew` set to true", () => { + it("doesn't include invalid nodes errors that are new if `ignoreDelayed` set to true", () => { expect( - getAssessmentItemsErrors(state)({ contentNodeId: 'content-node-id-2', ignoreNew: true }) + getAssessmentItemsErrors(state)({ contentNodeId: 'content-node-id-2', ignoreDelayed: true }) ).toEqual({ 'assessment-id-2': [ ValidationErrors.QUESTION_REQUIRED, @@ -163,11 +163,11 @@ describe('assessmentItem getters', () => { expect(getInvalidAssessmentItemsCount(state)({ contentNodeId: 'content-node-id-2' })).toBe(2); }); - it("doesn't count invalid nodes that are new if `ignoreNew` set to true", () => { + it("doesn't count invalid nodes that are new if `ignoreDelayed` set to true", () => { expect( getInvalidAssessmentItemsCount(state)({ contentNodeId: 'content-node-id-2', - ignoreNew: true, + ignoreDelayed: true, }) ).toBe(1); }); @@ -182,9 +182,12 @@ describe('assessmentItem getters', () => { expect(getAssessmentItemsAreValid(state)({ contentNodeId: 'content-node-id-2' })).toBe(false); }); - it('returns true if all assessment items are not valid and marked as new if `ignoreNew` set to true', () => { + it('returns true if all assessment items are not valid and marked as new if `ignoreDelayed` set to true', () => { expect( - getAssessmentItemsAreValid(state)({ contentNodeId: 'content-node-id-4', ignoreNew: true }) + getAssessmentItemsAreValid(state)({ + contentNodeId: 'content-node-id-4', + ignoreDelayed: true, + }) ).toBe(true); }); }); diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/getters.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/getters.js index 01750ee804..6b40af15fe 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/getters.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/getters.js @@ -1,4 +1,5 @@ import { getAssessmentItemErrors } from 'shared/utils/validation'; +import { DELAYED_VALIDATION } from 'shared/constants'; /** * Get assessment items of a node. @@ -25,17 +26,17 @@ export function getAssessmentItemsCount(state) { /** * Get a map of assessment items errors where keys are assessment ids. - * Consider new assessment items as valid if `ignoreNew` is true. + * Consider new assessment items as valid if `ignoreDelayed` is true. */ export function getAssessmentItemsErrors(state) { - return function({ contentNodeId, ignoreNew = false }) { + return function({ contentNodeId, ignoreDelayed = false }) { const assessmentItemsErrors = {}; if (!state.assessmentItemsMap || !state.assessmentItemsMap[contentNodeId]) { return assessmentItemsErrors; } Object.keys(state.assessmentItemsMap[contentNodeId]).forEach(assessmentItemId => { const assessmentItem = state.assessmentItemsMap[contentNodeId][assessmentItemId]; - if (ignoreNew && assessmentItem.isNew) { + if (ignoreDelayed && assessmentItem[DELAYED_VALIDATION]) { assessmentItemsErrors[assessmentItemId] = []; } else { assessmentItemsErrors[assessmentItemId] = getAssessmentItemErrors(assessmentItem); @@ -47,12 +48,12 @@ export function getAssessmentItemsErrors(state) { /** * Get total number of invalid assessment items of a node. - * Consider new assessment items as valid if `ignoreNew` is true. + * Consider new assessment items as valid if `ignoreDelayed` is true. */ export function getInvalidAssessmentItemsCount(state) { - return function({ contentNodeId, ignoreNew = false }) { + return function({ contentNodeId, ignoreDelayed = false }) { let count = 0; - const assessmentItemsErrors = getAssessmentItemsErrors(state)({ contentNodeId, ignoreNew }); + const assessmentItemsErrors = getAssessmentItemsErrors(state)({ contentNodeId, ignoreDelayed }); for (const assessmentItemId in assessmentItemsErrors) { if (assessmentItemsErrors[assessmentItemId].length) { @@ -66,10 +67,10 @@ export function getInvalidAssessmentItemsCount(state) { /** * Are all assessment items of a node valid? - * Consider new assessment items as valid if `ignoreNew` is true. + * Consider new assessment items as valid if `ignoreDelayed` is true. */ export function getAssessmentItemsAreValid(state) { - return function({ contentNodeId, ignoreNew = false }) { - return getInvalidAssessmentItemsCount(state)({ contentNodeId, ignoreNew }) === 0; + return function({ contentNodeId, ignoreDelayed = false }) { + return getInvalidAssessmentItemsCount(state)({ contentNodeId, ignoreDelayed }) === 0; }; } diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js index 11ed17cab6..53754a0da1 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js @@ -116,7 +116,7 @@ export function getContentNodeIsValid(state, getters, rootState, rootGetters) { // and it is not used within a form to run field validations, // it's okay to set this to false. This also accounts for // any async delays with the node creation - ignoreNew: false, + ignoreDelayed: false, }))) ); }; diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js index a24dabb28c..bc212b92ec 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js @@ -1,7 +1,6 @@ import find from 'lodash/find'; import { ContentKindsNames } from 'shared/leUtils/ContentKinds'; import { RolesNames } from 'shared/leUtils/Roles'; -import { NEW_OBJECT } from 'shared/constants'; export function parseNode(node, children) { const thumbnail_encoding = JSON.parse(node.thumbnail_encoding || '{}'); @@ -35,7 +34,6 @@ export function parseNode(node, children) { ...aggregateValues, thumbnail_encoding, tags, - isNew: node[NEW_OBJECT], }; } diff --git a/contentcuration/contentcuration/frontend/shared/constants.js b/contentcuration/contentcuration/frontend/shared/constants.js index 8203c1eff4..31c1c0d571 100644 --- a/contentcuration/contentcuration/frontend/shared/constants.js +++ b/contentcuration/contentcuration/frontend/shared/constants.js @@ -48,6 +48,10 @@ export const NOVALUE = Symbol('No value default'); // that they have not yet been committed to our IndexedDB layer. export const NEW_OBJECT = Symbol('New object'); +// This symbol is used as a key on new objects used to denote when +// validation should be delayed +export const DELAYED_VALIDATION = Symbol('Delayed validation'); + export const kindToIconMap = { audio: 'headset', channel: 'apps', diff --git a/contentcuration/contentcuration/frontend/shared/data/constants.js b/contentcuration/contentcuration/frontend/shared/data/constants.js index caaeea30c5..2a6204c2c2 100644 --- a/contentcuration/contentcuration/frontend/shared/data/constants.js +++ b/contentcuration/contentcuration/frontend/shared/data/constants.js @@ -48,9 +48,10 @@ export const RELATIVE_TREE_POSITIONS = { RIGHT: 'right', }; -// Special fields used for copying and other async tasks +// Special fields used for frontend specific handling export const COPYING_FLAG = '__COPYING'; export const TASK_ID = '__TASK_ID'; +export const LAST_FETCHED = '__last_fetch'; // This constant is used for saving/retrieving a current // user object from the session table diff --git a/contentcuration/contentcuration/frontend/shared/data/resources.js b/contentcuration/contentcuration/frontend/shared/data/resources.js index d268ed27ac..33ac48b156 100644 --- a/contentcuration/contentcuration/frontend/shared/data/resources.js +++ b/contentcuration/contentcuration/frontend/shared/data/resources.js @@ -26,12 +26,13 @@ import { ACTIVE_CHANNELS, CHANNEL_SYNC_KEEP_ALIVE_INTERVAL, MAX_REV_KEY, + LAST_FETCHED, } from './constants'; import applyChanges, { applyMods, collectChanges } from './applyRemoteChanges'; import mergeAllChanges from './mergeChanges'; import db, { channelScope, CLIENTID, Collection } from './db'; import { API_RESOURCES, INDEXEDDB_RESOURCES } from './registry'; -import { fileErrors, NEW_OBJECT } from 'shared/constants'; +import { DELAYED_VALIDATION, fileErrors, NEW_OBJECT } from 'shared/constants'; import client, { paramsSerializer } from 'shared/client'; import { currentLanguage } from 'shared/i18n'; import urls from 'shared/urls'; @@ -39,8 +40,6 @@ import urls from 'shared/urls'; // Number of seconds after which data is considered stale. const REFRESH_INTERVAL = 5; -const LAST_FETCHED = '__last_fetch'; - const QUERY_SUFFIXES = { IN: 'in', GT: 'gt', @@ -495,7 +494,7 @@ class IndexedDBResource { } /** - * Method to remove the NEW_OBJECT + * Method to remove the NEW_OBJECT and DELAYED_VALIDATION symbols * property so we don't commit it to IndexedDB * @param {Object} obj * @return {Object} @@ -505,6 +504,7 @@ class IndexedDBResource { ...obj, }; delete out[NEW_OBJECT]; + delete out[DELAYED_VALIDATION]; return out; } diff --git a/contentcuration/contentcuration/frontend/shared/data/serverSync.js b/contentcuration/contentcuration/frontend/shared/data/serverSync.js index b022d6154f..47eb294e80 100644 --- a/contentcuration/contentcuration/frontend/shared/data/serverSync.js +++ b/contentcuration/contentcuration/frontend/shared/data/serverSync.js @@ -2,6 +2,7 @@ import debounce from 'lodash/debounce'; import findLastIndex from 'lodash/findLastIndex'; import get from 'lodash/get'; import pick from 'lodash/pick'; +import omit from 'lodash/omit'; import orderBy from 'lodash/orderBy'; import uniq from 'lodash/uniq'; import applyChanges from './applyRemoteChanges'; @@ -12,6 +13,9 @@ import { CHANNEL_SYNC_KEEP_ALIVE_INTERVAL, ACTIVE_CHANNELS, MAX_REV_KEY, + LAST_FETCHED, + COPYING_FLAG, + TASK_ID, } from './constants'; import db from './db'; import mergeAllChanges from './mergeChanges'; @@ -31,13 +35,36 @@ const SYNC_POLL_INTERVAL = 5; // Flag to check if a sync is currently active. let syncActive = false; +const commonFields = ['type', 'key', 'table', 'rev', 'channel_id', 'user_id']; +const objectFields = ['objs', 'mods']; +const ignoredSubFields = [COPYING_FLAG, LAST_FETCHED, TASK_ID]; + +const ChangeTypeMapFields = { + [CHANGE_TYPES.CREATED]: commonFields.concat(['obj']), + [CHANGE_TYPES.UPDATED]: commonFields.concat(['mods']), + [CHANGE_TYPES.DELETED]: commonFields, + [CHANGE_TYPES.MOVED]: commonFields.concat(['target', 'position']), + [CHANGE_TYPES.COPIED]: commonFields.concat([ + 'from_key', + 'mods', + 'target', + 'position', + 'excluded_descendants', + ]), + [CHANGE_TYPES.PUBLISHED]: commonFields.concat(['version_notes', 'language']), + [CHANGE_TYPES.SYNCED]: commonFields.concat(['attributes', 'tags', 'files', 'assessment_items']), +}; + function isSyncableChange(change) { const src = change.source || ''; return ( !src.match(IGNORED_SOURCE) && INDEXEDDB_RESOURCES[change.table] && - INDEXEDDB_RESOURCES[change.table].syncable + INDEXEDDB_RESOURCES[change.table].syncable && + // don't create changes when it's an update to only ignored fields + (change.type !== CHANGE_TYPES.UPDATED || + Object.keys(change.mods).some(key => !ignoredSubFields.includes(key))) ); } @@ -48,36 +75,23 @@ function applyResourceListener(change) { } } -const commonFields = ['type', 'key', 'table', 'rev', 'channel_id', 'user_id']; -const createFields = commonFields.concat(['obj']); -const updateFields = commonFields.concat(['mods']); -const movedFields = commonFields.concat(['target', 'position']); -const copiedFields = commonFields.concat([ - 'from_key', - 'mods', - 'target', - 'position', - 'excluded_descendants', -]); -const publishedFields = commonFields.concat(['version_notes', 'language']); -const syncedFields = commonFields.concat(['attributes', 'tags', 'files', 'assessment_items']); - +/** + * Reduces a change to only the fields that are needed for sending it to the backend + * + * @param change + * @return {null|Object} + */ function trimChangeForSync(change) { - if (change.type === CHANGE_TYPES.CREATED) { - return pick(change, createFields); - } else if (change.type === CHANGE_TYPES.UPDATED) { - return pick(change, updateFields); - } else if (change.type === CHANGE_TYPES.DELETED) { - return pick(change, commonFields); - } else if (change.type === CHANGE_TYPES.MOVED) { - return pick(change, movedFields); - } else if (change.type === CHANGE_TYPES.COPIED) { - return pick(change, copiedFields); - } else if (change.type === CHANGE_TYPES.PUBLISHED) { - return pick(change, publishedFields); - } else if (change.type === CHANGE_TYPES.SYNCED) { - return pick(change, syncedFields); + // Extract the syncable fields + const payload = pick(change, ChangeTypeMapFields[change.type]); + + // for any field that has an object as a value, remove ignored fields from those objects + for (let field of objectFields) { + if (payload[field]) { + payload[field] = omit(payload[field], ignoredSubFields); + } } + return payload; } function handleDisallowed(response) { @@ -256,7 +270,7 @@ async function syncChanges() { // can now trim it down to only what is needed to transmit over the wire. // TODO: remove moves when a delete change is present for an object, // because a delete will wipe out the move. - const changes = changesToSync.map(trimChangeForSync); + const changes = changesToSync.map(trimChangeForSync).filter(Boolean); // Create a promise for the sync - if there is nothing to sync just resolve immediately, // in order to still call our change cleanup code. if (changes.length) { diff --git a/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js b/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js index dc20375632..52a1475260 100644 --- a/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js +++ b/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js @@ -1,5 +1,6 @@ import { EventEmitter } from 'events'; -import { CHANGE_TYPES } from 'shared/data'; +import omit from 'lodash/omit'; +import { CHANGE_TYPES, LAST_FETCHED } from 'shared/data/constants'; function getEventName(table, type) { return `${table}/${type}`; @@ -116,7 +117,8 @@ export default function IndexedDBPlugin(db, listeners = []) { events.emit(getEventName(change.table, change.type), { // we ensure we invoke the listeners with an object that has the PK [db[change.table].schema.primKey.keyPath]: change.key, - ...obj, + // spread the object, omitting the last fetched attribute used only in resource layer + ...omit(obj, [LAST_FETCHED]), }); }); });