Skip to content

Commit

Permalink
feat(deriveEntry): n to n (#138)
Browse files Browse the repository at this point in the history
Fixes #127.

Link derived entry based on the type of the reference field (either as first item in Array or single entry)
  • Loading branch information
Phoebe Schmidt authored Sep 21, 2018
1 parent 282d892 commit 7186467
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// In this example, we want to turn the dog's owner field into its own entry
// and link it back to the dog. To do this, we create an "owner"
// content type and a link field on the "dog" content type.
// The link field is a singular Entry link field. (See example 19 if you want to create an Array link field.)
// In the identity function, we define the criterion for when a new owner should
// be created: If the name joined by a hyphen is the same, then the same owner entry is
// linked.
Expand Down
43 changes: 43 additions & 0 deletions examples/20-derive-entry-n-to-n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// In this example, we want to turn the dog's owner field into its own entry
// and link it back to the dog. To do this, we create an "owner"
// content type and a link field on the "dog" content type. The link field will be of
// type 'Array', and the derived entry will be the first item in that Array field.
// In the identity function, we define the criterion for when a new owner should
// be created: If the name joined by a hyphen is the same, then the same owner entry is
// linked.
// In the deriveLinkedEntries function, we define what values should go into the new
// owner entries. We don't create any values for the locale 'en-US' on the derived entries.

module.exports = function (migration) {
const owner = migration.createContentType('owner').name('Owner').description('An owner of a dog');
owner.createField('firstName').type('Symbol').name('First Name');
owner.createField('lastName').type('Symbol').name('Last Name');
owner.displayField('firstName');

const dog = migration.editContentType('dog');
dog.createField('ownersRef').type('Array').items({ type: 'Link', linkType: 'Entry' }).name('The Owner');
migration.deriveLinkedEntries({
contentType: 'dog',
derivedContentType: 'owner',
from: ['owner'],
toReferenceField: 'ownersRef',
derivedFields: ['firstName', 'lastName'],
identityKey: async (fromFields) => {
return fromFields.owner['en-US'].toLowerCase().replace(' ', '-');
},
shouldPublish: true,
deriveEntryForLocale: async (inputFields, locale) => {
if (locale !== 'en-US') {
return;
}
const [firstName, lastName] = inputFields.owner[locale].split(' ');

return {
firstName,
lastName
};
}
});

dog.deleteField('owner');
};
19 changes: 10 additions & 9 deletions src/lib/action/entry-derive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import EntryDerive from '../interfaces/entry-derive'
import { APIAction } from './action'
import { OfflineAPI } from '../offline-api'
import { ContentType } from '../entities/content-type'
import Entry from '../entities/entry'
import * as _ from 'lodash'

Expand All @@ -27,6 +28,7 @@ class EntryDeriveAction extends APIAction {
async applyTo (api: OfflineAPI) {
const entries: Entry[] = await api.getEntriesForContentType(this.contentTypeId)
const locales: string[] = await api.getLocalesForSpace()
const sourceContentType: ContentType = await api.getContentType(this.contentTypeId)

for (const entry of entries) {
const inputs = _.pick(entry.fields, this.fromFields)
Expand Down Expand Up @@ -74,7 +76,6 @@ class EntryDeriveAction extends APIAction {
// Usually you would not want to derive the contents again
// But what if the previous round may not have been complete
// for example one optional field was missing in the previous iteration

const targetEntry = await api.createEntry(this.derivedContentType, newEntryId)

// we are not skipping this source entry and the target entry does not yet exist,
Expand All @@ -94,16 +95,16 @@ class EntryDeriveAction extends APIAction {
await api.publishEntry(targetEntry.id)
}
}

const field = sourceContentType.fields.getField(this.referenceField)
entry.setField(this.referenceField, {})
for (const locale of locales) {
entry.setFieldForLocale(this.referenceField, locale, {
sys: {
type: 'Link',
linkType: 'Entry',
id: newEntryId
}
})
const sys = {
type: 'Link',
linkType: 'Entry',
id: newEntryId
}
const fieldValue = (field.type === 'Array') ? [{sys}] : {sys}
entry.setFieldForLocale(this.referenceField, locale, fieldValue)
}

await api.saveEntry(entry.id)
Expand Down
151 changes: 151 additions & 0 deletions test/unit/lib/actions/entry-derive.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
'use strict';

import { expect } from 'chai';

import { EntryDeriveAction } from '../../../../src/lib/action/entry-derive';
import OfflineApi from '../../../../src/lib/offline-api/index';
import { Entry } from '../../../../src/lib/entities/entry';
import makeApiEntry from '../../../helpers/make-api-entry';
import ContentType from '../../../../src/lib/entities/content-type';

describe('Entry Derive', function () {
it('derives an entry from n to 1', async function () {
const action = new EntryDeriveAction('dog', {
derivedContentType: 'owner',
from: ['owner'],
toReferenceField: 'ownerRef',
derivedFields: ['firstName', 'lastName'],
identityKey: async (fromFields) => {
return fromFields.owner['en-US'].toLowerCase().replace(' ', '-');
},
shouldPublish: true,
deriveEntryForLocale: async (inputFields, locale) => {
if (locale !== 'en-US') {
return;
}
const [firstName, lastName] = inputFields.owner[locale].split(' ');
return {
firstName,
lastName
};
}
});

const contentTypes = new Map();
contentTypes.set('dog', new ContentType({
sys: {
id: 'dog'
},
fields: [{
name: 'ownerRef',
id: 'ownerRef',
type: 'Symbol'
}]
})
);

const entries = [
new Entry(makeApiEntry({
id: '246',
contentTypeId: 'dog',
version: 1,
fields: {
owner: {
'en-US': 'john doe'
}
}
}))
];

const api = new OfflineApi(contentTypes, entries, ['en-US']);
api.startRecordingRequests(null);
await action.applyTo(api);
api.stopRecordingRequests();
const batches = await api.getRequestBatches();
expect(batches[0].requests.length).to.eq(4);
const createTargetEntryFields = batches[0].requests[0].data.fields;
const updateEntryWithLinkFields = batches[0].requests[2].data.fields;
expect(createTargetEntryFields.firstName['en-US']).to.eq('john'); // target entry has first and last name
expect(createTargetEntryFields.lastName['en-US']).to.eq('doe');
expect(typeof updateEntryWithLinkFields.ownerRef['en-US'].sys).to.eq('object'); // request to update entry is n to 1 link
expect(updateEntryWithLinkFields.ownerRef['en-US'].sys.type).to.eq('Link');
expect(updateEntryWithLinkFields.ownerRef['en-US'].sys.id).to.eq(batches[0].requests[0].data.sys.id); // id of linked object is same as id of target object
});

it('derives an entry from n to n', async function () {
const action = new EntryDeriveAction('dog', {
derivedContentType: 'owner',
from: ['owner'],
toReferenceField: 'owners',
derivedFields: ['firstName', 'lastName'],
identityKey: async (fromFields) => {
return fromFields.owner['en-US'].toLowerCase().replace(' ', '-');
},
shouldPublish: true,
deriveEntryForLocale: async (inputFields, locale) => {
if (locale !== 'en-US') {
return;
}
const [firstName, lastName] = inputFields.owner[locale].split(' ');

return {
firstName,
lastName
};
}
});

const contentTypes = new Map();
contentTypes.set('dog', new ContentType(
{
sys: {
id: 'dog'
},
name: 'dog content type',
fields: [{
name: 'owners',
id: 'owners',
type: 'Array',
items: {
type: 'Link',
linkType: 'Entry'
}
},
{
id: 'owner',
name: 'owner',
type: 'Symbol',
localized: true
}
]
}));

const entries = [
new Entry(makeApiEntry({
id: '246',
contentTypeId: 'dog',
version: 1,
fields: {
owner: {
'en-US': 'johnny depp'
}
}
}))
];

const api = new OfflineApi(contentTypes, entries, ['en-US']);
api.startRecordingRequests(null);
await action.applyTo(api);
api.stopRecordingRequests();
const batches = await api.getRequestBatches();

expect(batches[0].requests.length).to.eq(4);
const createTargetEntryFields = batches[0].requests[0].data.fields;
const updateEntryWithLinkFields = batches[0].requests[2].data.fields;
expect(createTargetEntryFields.firstName['en-US']).to.eq('johnny'); // target entry has first and last name
expect(createTargetEntryFields.lastName['en-US']).to.eq('depp');
expect(typeof updateEntryWithLinkFields.owners['en-US'][0].sys).to.eq('object'); // request to update entry is n to n link
expect(updateEntryWithLinkFields.owners['en-US'][0].sys.type).to.eq('Link');
expect(updateEntryWithLinkFields.owners['en-US'][0].sys.id).to.eq(batches[0].requests[0].data.sys.id); // id of linked object is same as id of target object
});
});

0 comments on commit 7186467

Please sign in to comment.