Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contentful/csv export #604

Merged
merged 8 commits into from
Mar 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/botonic-plugin-contentful-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Install dev dependencies
run: (cd ./packages/$PACKAGE && npm install -D)
- name: Build
run: (cd ./packages/$PACKAGE && npm run build)
run: (cd ./packages/$PACKAGE && npm run build_with_tests)
- name: Run tests
env:
CONTENTFUL_TEST_SPACE_ID: ${{ secrets.CONTENTFUL_TEST_SPACE_ID }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/botonic-plugin-dynamo-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Install dev dependencies
run: (cd ./packages/$PACKAGE && npm install -D)
- name: Build
run: (cd ./packages/$PACKAGE && npm run build)
run: (cd ./packages/$PACKAGE && npm run build_with_tests)
- name: Run tests
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"ts-mockito": "^2.5.0",
"ts-node": "^8.6.2",
"tslib": "^1.10.0",
"typescript": "~3.7.5"
"typescript": "^3.8.3"
},
"engines": {
"node": ">=8.0.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/botonic-plugin-contentful/jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// uncomment to extend jest test timeout while debugging from IDE
// jest.setTimeout(300000);
// jest.setTimeout(3000000)
18 changes: 18 additions & 0 deletions packages/botonic-plugin-contentful/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/botonic-plugin-contentful/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
"dependencies": {
"@babel/runtime": "^7.8.3",
"contentful": "^7.13.1",
"csv-parse": "^4.8.6",
"csv-stringify": "^5.3.6",
"escape-string-regexp": "^2.0.0",
"memoizee": "^0.4.14",
"moment": "^2.24.0",
"moment-timezone": "^0.5.27",
"node-nlp": "^4.0.2"
"node-nlp": "^4.0.2",
"sort-stream": "^1.0.1"
},
"devDependencies": {
"@types/memoizee": "^0.4.3",
Expand Down
9 changes: 7 additions & 2 deletions packages/botonic-plugin-contentful/src/cms/callback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CMS, ContentType, MESSAGE_TYPES, MessageContentType } from './cms'
import {
CMS,
ContentType,
MESSAGE_CONTENT_TYPES,
MessageContentType,
} from './cms'
import escapeStringRegexp from 'escape-string-regexp'
import { Context } from './context'
import { TopContent } from './contents'
Expand Down Expand Up @@ -54,7 +59,7 @@ export class ContentCallback extends Callback {
}

private static checkDeliverableModel(modelType: string): MessageContentType {
if (MESSAGE_TYPES.includes(modelType as MessageContentType)) {
if (MESSAGE_CONTENT_TYPES.includes(modelType as MessageContentType)) {
return modelType as MessageContentType
} else {
throw new Error(
Expand Down
28 changes: 20 additions & 8 deletions packages/botonic-plugin-contentful/src/cms/cms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
TopContent,
Content,
} from './contents'
import { enumValues } from '../util/enums'

export enum MessageContentType {
CAROUSEL = 'carousel',
Expand All @@ -23,8 +24,10 @@ export enum MessageContentType {
CHITCHAT = 'chitchat', //so far it's an alias for TEXT
STARTUP = 'startUp',
}
export const MESSAGE_TYPES = Object.values(MessageContentType).map(
m => m as MessageContentType

// CHITCHAT removed because it's an alias for texts
export const MESSAGE_CONTENT_TYPES = enumValues(MessageContentType).filter(
m => m != MessageContentType.CHITCHAT
)

export enum NonMessageTopContentType {
Expand All @@ -39,19 +42,28 @@ export const TopContentType = {
...MessageContentType,
...NonMessageTopContentType,
}
export const TOPCONTENT_TYPES = Object.values(TopContentType).map(
m => m as TopContentType
)
export const TOP_CONTENT_TYPES = [
...MESSAGE_CONTENT_TYPES,
...enumValues(NonMessageTopContentType),
]

export enum SubContentType {
BUTTON = 'button',
ELEMENT = 'element',
}
export type ContentType = TopContentType | SubContentType
export const ContentType = { ...TopContentType, ...SubContentType }
export const CONTENT_TYPES = Object.values(ContentType).map(
m => m as ContentType
)
export const CONTENT_TYPES = [
...TOP_CONTENT_TYPES,
...enumValues(SubContentType),
]

export type BotonicContentType = MessageContentType | SubContentType
export const BotonicContentType = { ...MessageContentType, ...SubContentType }
export const BOTONIC_CONTENT_TYPES = [
...MESSAGE_CONTENT_TYPES,
...enumValues(SubContentType),
]

export function isSameModel(model1: ContentType, model2: ContentType): boolean {
switch (model1) {
Expand Down
5 changes: 5 additions & 0 deletions packages/botonic-plugin-contentful/src/cms/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ export const DEFAULT_CONTEXT: Context = {}
export interface Context {
locale?: Locale
callbacks?: CallbackMap
/**
* When set, empty fields will be blank even if they have a value for the fallback locale
* NOT applying it so far for assets because cms.Asset does not support blank assets
*/
ignoreFallbackLocale?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TextFields } from './text'
import { UrlFields } from './url'

export class ButtonDelivery {
private static BUTTON_CONTENT_TYPE = 'button'
public static BUTTON_CONTENT_TYPE = 'button'
private static PAYLOAD_CONTENT_TYPE = 'payload'
constructor(private readonly delivery: DeliveryApi) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class CarouselDelivery extends DeliveryWithFollowUp {
)
}

private async elementFromEntry(
public async elementFromEntry(
entry: contentful.Entry<ElementFields>,
context: cms.Context
): Promise<cms.Element> {
Expand Down
11 changes: 11 additions & 0 deletions packages/botonic-plugin-contentful/src/contentful/delivery-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface DeliveryApi {
context: Context,
query?: any
): Promise<contentful.EntryCollection<T>>

getContentType(id: string): Promise<contentful.ContentType>
}

/**
Expand Down Expand Up @@ -67,6 +69,15 @@ export class AdaptorDeliveryApi implements DeliveryApi {
)
}

async getContentType(id: string): Promise<contentful.ContentType> {
try {
return this.client.getContentType(id)
} catch (e) {
console.error(`ERROR in getContentType for id ${id}: ${e}`)
throw e
}
}

private static queryFromContext(context: Context, query: any = {}): any {
if (context.locale) {
query['locale'] = context.locale
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as contentful from 'contentful/index'
import { Entry } from 'contentful/index'
import { ContentType, Entry } from 'contentful/index'
import memoize from 'memoizee'
import { ReducedClientApi } from './client-api'

export class CachedClientApi implements ReducedClientApi {
readonly getAsset: (id: string, query?: any) => Promise<contentful.Asset>
readonly getEntries: <T>(query: any) => Promise<contentful.EntryCollection<T>>
readonly getEntry: <T>(id: string, query?: any) => Promise<Entry<T>>
readonly getContentType: (id: string) => Promise<ContentType>

constructor(readonly client: ReducedClientApi, readonly cacheTtlMs = 10000) {
const options = (length: number) =>
Expand All @@ -24,5 +25,6 @@ export class CachedClientApi implements ReducedClientApi {
this.getAsset = memoize(client.getAsset, options(2))
this.getEntries = memoize(client.getEntries, options(1))
this.getEntry = memoize(client.getEntry, options(2))
this.getContentType = memoize(client.getContentType, options(1))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { ContentfulClientApi } from 'contentful'

export type ReducedClientApi = Pick<
ContentfulClientApi,
'getAsset' | 'getEntries' | 'getEntry'
'getAsset' | 'getEntries' | 'getEntry' | 'getContentType'
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Asset, ContentType, Entry, EntryCollection } from 'contentful'
import { DeliveryApi } from './delivery-api'
import { Context } from '../cms'
import {
ContentfulVisitor,
I18nEntryTraverser,
I18nValue,
VisitedField,
} from './traverser'

export class IgnoreFallbackDecorator implements DeliveryApi {
constructor(private readonly api: DeliveryApi) {}

getContentType(id: string): Promise<ContentType> {
return this.api.getContentType(id)
}

async getEntries<T>(
context: Context,
query: any = {}
): Promise<EntryCollection<T>> {
if (!context.ignoreFallbackLocale) {
return this.api.getEntries(context, query)
}
let entries = await this.api.getEntries<T>(this.i18nContext(context), query)

entries = { ...entries }
entries.items = await this.traverseEntries(context, entries.items)
return entries
}

async getEntry<T>(
id: string,
context: Context,
query: any = {}
): Promise<Entry<T>> {
if (!context.ignoreFallbackLocale) {
return this.api.getEntry(id, context, query)
}
const entry = await this.api.getEntry<T>(
id,
this.i18nContext(context),
query
)
return (await this.traverseEntries(context, [entry]))[0]
}

async traverseEntries<T>(
context: Context,
entries: Entry<T>[]
): Promise<Entry<T>[]> {
const visitor = new IgnoreFallbackVisitor(context)
return Promise.all(
entries.map(async item => {
const traverser = new I18nEntryTraverser(this.api, visitor)
return await traverser.traverse(item, context)
})
)
}

getAsset(id: string, query?: any): Promise<Asset> {
return this.api.getAsset(id, query)
}

private i18nContext(context: Context) {
return {
...context,
locale: '*',
} as Context
}
}

class IgnoreFallbackVisitor implements ContentfulVisitor {
contextForContentful: Context
constructor(readonly context: Context) {
if (!context.locale) {
throw new Error(
'Context.ignoreFallbackLocale set but Context.locale not set'
)
}
this.contextForContentful = {
...context,
locale: '*',
}
}

visitEntry<T>(entry: Entry<T>): Entry<T> {
return entry
}

visitStringField(vf: VisitedField<string>): I18nValue<string> {
return this.hackType(vf.value[vf.locale], '')
}
hackType<T>(t: T, defaultValue?: T): I18nValue<T> {
if (defaultValue != undefined) {
t = t ?? defaultValue
}
return (t as any) as I18nValue<T>
}

visitMultipleStringField(vf: VisitedField<string[]>): I18nValue<string[]> {
return this.hackType(vf.value[vf.locale], [])
}

visitSingleReference<T>(vf: VisitedField<Entry<T>>): I18nValue<Entry<T>> {
return this.hackType(vf.value[vf.locale], (undefined as any) as Entry<T>)
}

visitMultipleReference<T>(
vf: VisitedField<EntryCollection<T>>
): I18nValue<EntryCollection<T>> {
return this.hackType(vf.value[vf.locale])
}

name(): string {
return 'ignoreFallbackLocale'
}
}
Loading