diff --git a/examples/54-create-experience-type.js b/examples/54-create-experience-type.js new file mode 100644 index 000000000..b3f1c0e34 --- /dev/null +++ b/examples/54-create-experience-type.js @@ -0,0 +1,90 @@ +module.exports = function (migration) { + const experienceType = migration.createContentType('experienceType').name('Experience') + + experienceType + .createField('title') + .name('Title') + .type('Symbol') + .localized(false) + .required(true) + .validations([]) + .disabled(false) + .omitted(false) + + experienceType.displayField('title') + + experienceType + .createField('slug') + .name('Slug') + .type('Symbol') + .localized(false) + .required(true) + .validations([{ unique: true }]) + .disabled(false) + .omitted(false) + + experienceType + .createField('componentTree') + .name('Component Tree') + .type('Object') + .localized(false) + .required(true) + .validations([]) + .disabled(false) + .omitted(false) + + experienceType + .createField('dataSource') + .name('Data Source') + .type('Object') + .localized(false) + .required(true) + .validations([]) + .disabled(false) + .omitted(false) + + experienceType + .createField('unboundValues') + .name('Unbound Values') + .type('Object') + .localized(false) + .required(true) + .validations([]) + .disabled(false) + .omitted(false) + + experienceType + .createField('componentSettings') + .name('Component Settings') + .type('Object') + .localized(false) + .required(false) + .validations([]) + .disabled(false) + .omitted(false) + + experienceType + .createField('usedComponents') + .name('Used Components') + .type('Array') + .localized(false) + .required(false) + .validations([]) + .disabled(false) + .omitted(false) + .items({ + type: 'Link', + validations: [{ linkContentType: ['experienceType'] }], + linkType: 'Entry' + }) + + experienceType.changeFieldControl('title', 'builtin', 'singleLine') + experienceType.changeFieldControl('slug', 'builtin', 'slugEditor') + experienceType.changeFieldControl('componentTree', 'builtin', 'objectEditor') + experienceType.changeFieldControl('dataSource', 'builtin', 'objectEditor') + experienceType.changeFieldControl('unboundValues', 'builtin', 'objectEditor') + experienceType.changeFieldControl('componentSettings', 'builtin', 'objectEditor') + experienceType.changeFieldControl('usedComponents', 'builtin', 'entryLinksEditor') + + experienceType.setAnnotations(['Contentful:ExperienceType']) +} diff --git a/src/lib/interfaces/annotation.ts b/src/lib/interfaces/annotation.ts index 498e72540..a9cda7837 100644 --- a/src/lib/interfaces/annotation.ts +++ b/src/lib/interfaces/annotation.ts @@ -58,6 +58,7 @@ export class Annotation { } export const availableAnnotations = { + // Targeting ContentType 'Contentful:AggregateRoot': new Annotation({ id: 'Contentful:AggregateRoot', targets: [ @@ -74,6 +75,15 @@ export const availableAnnotations = { } ] }), + 'Contentful:ExperienceType': new Annotation({ + id: 'Contentful:ExperienceType', + targets: [ + { + type: 'ContentType' + } + ] + }), + // Targeting ContentTypeField 'Contentful:AggregateComponent': new Annotation({ id: 'Contentful:AggregateComponent', targets: [ diff --git a/src/lib/offline-api/index.ts b/src/lib/offline-api/index.ts index b0c037a5e..e57c880d3 100644 --- a/src/lib/offline-api/index.ts +++ b/src/lib/offline-api/index.ts @@ -11,7 +11,7 @@ import { PayloadValidationError, InvalidActionError } from '../interfaces/errors import DisplayFieldValidator from './validator/display-field' import SchemaValidator from './validator/schema/index' import TypeChangeValidator from './validator/type-change' -import AnnotationValidator from './validator/annotations' +import AnnotationsValidator from './validator/annotations' import TagsOnEntryValidator from './validator/tags-on-entry' import { Intent } from '../interfaces/intent' import APIEntry from '../interfaces/api-entry' @@ -221,7 +221,7 @@ class OfflineAPI { new DisplayFieldValidator(), new SchemaValidator(), new TypeChangeValidator(), - new AnnotationValidator(), + new AnnotationsValidator(), new ResourceLinksValidator() ) diff --git a/test/end-to-end/assertions.js b/test/end-to-end/assertions.js index 4d96f83f3..f22b6d8e1 100644 --- a/test/end-to-end/assertions.js +++ b/test/end-to-end/assertions.js @@ -95,7 +95,7 @@ module.exports = { contentType: { create: function (id, params) { return (result) => { - expect(result.stdout).not.to.be.empty() + expect(result.stdout).not.to.be.empty(result.stderr) const withoutAnsiCodes = stripAnsi(result.stdout) expect(withoutAnsiCodes).to.include(`Create Content Type ${id}`) @@ -108,7 +108,7 @@ module.exports = { }, update: function (id, params) { return (result) => { - expect(result.stdout).not.to.be.empty() + expect(result.stdout).not.to.be.empty(result.stderr) const withoutAnsiCodes = stripAnsi(result.stdout) expect(withoutAnsiCodes).to.include(`Update Content Type ${id}`) @@ -121,7 +121,7 @@ module.exports = { }, delete: function (id) { return (result) => { - expect(result.stdout).not.to.be.empty() + expect(result.stdout).not.to.be.empty(result.stderr) const withoutAnsiCodes = stripAnsi(result.stdout) expect(withoutAnsiCodes).to.include(`Delete Content Type ${id}`) @@ -426,6 +426,17 @@ module.exports = { expect(withoutAnsiCodes).to.include(`Delete Tag ${id}`) } } + }, + annotation: { + assign: function (id) { + return (result) => { + expect(result.stdout).not.to.be.empty(result.stderr) + + const withoutAnsiCodes = stripAnsi(result.stdout) + + expect(withoutAnsiCodes).to.include(`Assign annotation ${id}`) + } + } } }, help: { diff --git a/test/end-to-end/content-type.spec.js b/test/end-to-end/content-type.spec.js index 667e00ea9..7c7005339 100644 --- a/test/end-to-end/content-type.spec.js +++ b/test/end-to-end/content-type.spec.js @@ -266,4 +266,39 @@ describe('apply content-type migration examples', function () { }) ) }) + + it('applies 54-create-experience-type', function (done) { + cli() + .run( + `--space-id ${SOURCE_TEST_SPACE} --environment-id ${environmentId} ./examples/54-create-experience-type.js` + ) + .on(/\? Do you want to apply the migration \(Y\/n\)/) + .respond('Y\n') + .expect(assert.plans.contentType.create('experienceType')) + .expect(assert.plans.annotation.assign('Contentful:ExperienceType')) + .expect(assert.plans.actions.apply()) + .end( + co(function* () { + const contentType = yield getDevContentType( + SOURCE_TEST_SPACE, + environmentId, + 'experienceType' + ) + expect(contentType.metadata).to.eql({ + annotations: { + ContentType: [ + { + sys: { + id: 'Contentful:ExperienceType', + type: 'Link', + linkType: 'Annotation' + } + } + ] + } + }) + done() + }) + ) + }) }) diff --git a/test/integration/migration.spec.js b/test/integration/migration.spec.js index f0ff09aab..5db55f806 100644 --- a/test/integration/migration.spec.js +++ b/test/integration/migration.spec.js @@ -42,6 +42,7 @@ const moveFieldOnContentTypeWithEditorLayout = require('../../examples/51-move-f const deleteFieldOnContentTypeWithEditorLayout = require('../../examples/52-delete-field-in-content-type-with-editor-layout') const renameFieldOnContentTypeWithEditorLayout = require('../../examples/53-rename-field-in-content-type-with-editor-layout') const createRichTextFieldWithValidation = require('../../examples/22-create-rich-text-field-with-validation') +const createExperienceType = require('../../examples/54-create-experience-type') const { createMigrationParser } = require('../../built/lib/migration-parser') const { DEFAULT_SIDEBAR_LIST } = require('../../built/lib/action/sidebarwidget') @@ -1378,4 +1379,27 @@ describe('the migration', function () { ] }) }) + + // Note: Requires space to be enabled for Studio Experiences via org settings + it('assigns experience type annotation', async function () { + await migrator(createExperienceType) + const ct = await request({ + method: 'GET', + url: '/content_types/experienceType' + }) + + expect(ct.metadata).to.eql({ + annotations: { + ContentType: [ + { + sys: { + id: 'Contentful:ExperienceType', + type: 'Link', + linkType: 'Annotation' + } + } + ] + } + }) + }) })