-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cb285f2
commit 470b124
Showing
4 changed files
with
347 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ cypress/reports | |
cypress.env.json | ||
backstage_docs | ||
vite.config.d.ts | ||
|
||
.nx | ||
|
||
# Logs | ||
logs | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
import { describe, test, beforeEach, expect } from 'vitest'; | ||
import { ContentSourceMaps } from '../csm'; | ||
import { vercelStegaDecode } from '@vercel/stega'; | ||
|
||
type Mappings = { | ||
[key: string]: | ||
| { | ||
origin: string; | ||
href: string; | ||
} | ||
| { | ||
[nestedKey: string]: { | ||
origin: string; | ||
href: string; | ||
}; | ||
}; | ||
}; | ||
|
||
type EncodedResponse = | ||
| { | ||
[key: string]: string; | ||
} | ||
| Array<{ [key: string]: string }>; | ||
|
||
function testEncodingDecoding(encodedResponse: EncodedResponse, mappings: Mappings) { | ||
if (Array.isArray(encodedResponse)) { | ||
encodedResponse.forEach((item, index) => { | ||
const itemMappings = mappings[index]; | ||
for (const [key, expectedValue] of Object.entries(itemMappings)) { | ||
const encodedValue = item[key]; | ||
const decodedValue = vercelStegaDecode(encodedValue); | ||
expect(decodedValue).toEqual(expectedValue); | ||
} | ||
}); | ||
} else { | ||
for (const [key, expectedValue] of Object.entries(mappings)) { | ||
const encodedValue = encodedResponse[key]; | ||
const decodedValue = vercelStegaDecode(encodedValue); | ||
expect(decodedValue).toEqual(expectedValue); | ||
} | ||
} | ||
} | ||
|
||
describe('Content Source Maps', () => { | ||
let csm: ContentSourceMaps; | ||
|
||
beforeEach(() => { | ||
csm = new ContentSourceMaps(); | ||
}); | ||
|
||
describe('GraphQL', () => { | ||
test('basic example', () => { | ||
const graphQLResponse = { | ||
data: { | ||
post: { | ||
title: 'Title of the post', | ||
subtitle: 'Subtitle of the post', | ||
}, | ||
}, | ||
extensions: { | ||
contentSourceMaps: { | ||
version: 1.0, | ||
spaces: ['foo'], | ||
environments: ['master'], | ||
fields: ['title', 'subtitle'], | ||
locales: ['en-US'], | ||
entries: [{ space: 0, environment: 0, id: 'a1b2c3' }], | ||
assets: [], | ||
mappings: { | ||
'/post/title': { | ||
source: { | ||
entry: 0, | ||
field: 0, | ||
locale: 0, | ||
}, | ||
}, | ||
'/post/subtitle': { | ||
source: { | ||
entry: 0, | ||
field: 1, | ||
locale: 0, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
const encodedGraphQLResponse = csm.encodeSourceMap(graphQLResponse); | ||
testEncodingDecoding(encodedGraphQLResponse.data.post, { | ||
title: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/a1b2c3/?focusedField=title&focusedLocale=en-US', | ||
}, | ||
subtitle: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/a1b2c3/?focusedField=subtitle&focusedLocale=en-US', | ||
}, | ||
}); | ||
}); | ||
|
||
test('collections', () => { | ||
const graphQLResponse = { | ||
data: { | ||
postCollection: { | ||
items: [ | ||
{ | ||
title: 'Title of the post', | ||
}, | ||
{ | ||
title: 'Title of the post 2', | ||
}, | ||
{ | ||
title: 'Title of the post 3', | ||
}, | ||
], | ||
}, | ||
}, | ||
extensions: { | ||
contentSourceMaps: { | ||
version: 1.0, | ||
spaces: ['foo'], | ||
environments: ['master'], | ||
fields: ['title'], | ||
locales: ['en-US'], | ||
entries: [ | ||
{ space: 0, environment: 0, id: 'a1b2c3' }, | ||
{ space: 0, environment: 0, id: 'd4e5f6' }, | ||
{ space: 0, environment: 0, id: 'g7h8i9' }, | ||
], | ||
assets: [], | ||
mappings: { | ||
'/postCollection/items/0/title': { | ||
source: { | ||
entry: 0, | ||
field: 0, | ||
locale: 0, | ||
}, | ||
}, | ||
'/postCollection/items/1/title': { | ||
source: { | ||
entry: 1, | ||
field: 0, | ||
locale: 0, | ||
}, | ||
}, | ||
'/postCollection/items/2/title': { | ||
source: { | ||
entry: 2, | ||
field: 0, | ||
locale: 0, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
const encodedGraphQLResponse = csm.encodeSourceMap(graphQLResponse); | ||
testEncodingDecoding(encodedGraphQLResponse.data.postCollection.items, { | ||
0: { | ||
title: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/a1b2c3/?focusedField=title&focusedLocale=en-US', | ||
}, | ||
}, | ||
1: { | ||
title: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/d4e5f6/?focusedField=title&focusedLocale=en-US', | ||
}, | ||
}, | ||
2: { | ||
title: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/g7h8i9/?focusedField=title&focusedLocale=en-US', | ||
}, | ||
}, | ||
}); | ||
}); | ||
|
||
test('aliasing with multiple locales', () => { | ||
const graphQLResponse = { | ||
data: { | ||
postCollection: { | ||
items: [ | ||
{ | ||
akanTitle: 'Lorem', | ||
aghemTitle: 'Ipsum', | ||
spanishTitle: 'Dolor', | ||
}, | ||
], | ||
}, | ||
}, | ||
extensions: { | ||
contentSourceMaps: { | ||
version: 1.0, | ||
spaces: ['foo'], | ||
environments: ['master'], | ||
fields: ['title'], | ||
locales: ['ak', 'agq', 'es'], | ||
entries: [{ space: 0, environment: 0, id: 'a1b2c3' }], | ||
assets: [], | ||
mappings: { | ||
'/postCollection/items/0/akanTitle': { | ||
source: { | ||
entry: 0, | ||
field: 0, | ||
locale: 0, | ||
}, | ||
}, | ||
'/postCollection/items/0/aghemTitle': { | ||
source: { | ||
entry: 0, | ||
field: 0, | ||
locale: 1, | ||
}, | ||
}, | ||
'/postCollection/items/0/spanishTitle': { | ||
source: { | ||
entry: 0, | ||
field: 0, | ||
locale: 2, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
const encodedGraphQLResponse = csm.encodeSourceMap(graphQLResponse); | ||
testEncodingDecoding(encodedGraphQLResponse.data.postCollection.items[0], { | ||
akanTitle: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/a1b2c3/?focusedField=title&focusedLocale=ak', | ||
}, | ||
aghemTitle: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/a1b2c3/?focusedField=title&focusedLocale=agq', | ||
}, | ||
spanishTitle: { | ||
origin: 'contentful.com', | ||
href: 'https://app.contentful.com/spaces/foo/environments/master/entries/a1b2c3/?focusedField=title&focusedLocale=es', | ||
}, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { vercelStegaEncode } from '@vercel/stega'; | ||
import { debug } from '../helpers'; | ||
import jsonPointer from 'json-pointer'; | ||
|
||
type Source = { | ||
field: number; | ||
locale: number; | ||
} & ({ entry: number } | { asset: number }); | ||
|
||
interface EntitySource { | ||
space: number; | ||
environment: number; | ||
id: string; | ||
} | ||
|
||
type Mappings = Record<string, { source: Source }>; | ||
|
||
interface IContentSourceMaps { | ||
version: number; | ||
spaces: string[]; | ||
environments: string[]; | ||
fields: string[]; | ||
locales: string[]; | ||
entries: EntitySource[]; | ||
assets: EntitySource[]; | ||
mappings: Mappings; | ||
} | ||
|
||
interface GraphQLResponse { | ||
data: any; | ||
extensions: { | ||
contentSourceMaps: IContentSourceMaps; | ||
}; | ||
} | ||
|
||
export class ContentSourceMaps { | ||
constructor() {} | ||
|
||
private isUrlOrIsoDate(string: string): boolean { | ||
//@TODO - implement check for ISO date or URL | ||
return false; | ||
} | ||
|
||
private getHref( | ||
source: Source, | ||
entries: EntitySource[], | ||
assets: EntitySource[], | ||
spaces: string[], | ||
environments: string[], | ||
fields: string[], | ||
locales: string[] | ||
): string | null { | ||
const isEntry = 'entry' in source; | ||
const content = isEntry ? entries[source.entry] : assets[source.asset]; | ||
if (!content) return null; | ||
|
||
const space = spaces[content.space]; | ||
const environment = environments[content.environment]; | ||
const contentId = content.id; | ||
const field = fields[source.field]; | ||
const locale = locales[source.locale]; | ||
|
||
const basePath = `https://app.contentful.com/spaces/${space}/environments/${environment}`; | ||
const path = isEntry ? 'entries' : 'assets'; | ||
return `${basePath}/${path}/${contentId}/?focusedField=${field}&focusedLocale=${locale}`; | ||
} | ||
|
||
encodeSourceMap(graphqlResponse: GraphQLResponse): GraphQLResponse { | ||
if ( | ||
!graphqlResponse || | ||
!graphqlResponse.extensions || | ||
!graphqlResponse.extensions.contentSourceMaps | ||
) { | ||
debug.error('GraphQL response does not contain Content Source Maps information.'); | ||
return graphqlResponse; | ||
} | ||
const { spaces, environments, fields, locales, entries, assets, mappings } = | ||
graphqlResponse.extensions.contentSourceMaps; | ||
let data = graphqlResponse.data; | ||
|
||
for (let pointer in mappings) { | ||
const { source } = mappings[pointer]; | ||
const href = this.getHref(source, entries, assets, spaces, environments, fields, locales); | ||
|
||
if (href && jsonPointer.has(data, pointer)) { | ||
let currentValue = jsonPointer.get(data, pointer); | ||
const encodedValue = vercelStegaEncode({ | ||
origin: 'contentful.com', | ||
href, | ||
}); | ||
jsonPointer.set(data, pointer, `${encodedValue}${currentValue}`); | ||
} else { | ||
debug.error(`Pointer ${pointer} not found in GraphQL data or href could not be generated.`); | ||
} | ||
} | ||
return graphqlResponse; | ||
} | ||
} |