diff --git a/apps/ai-image-tagging/frontend/.prettierrc.js b/apps/ai-image-tagging/frontend/.prettierrc.js index 5b03dfcea58..13d14c76333 100644 --- a/apps/ai-image-tagging/frontend/.prettierrc.js +++ b/apps/ai-image-tagging/frontend/.prettierrc.js @@ -3,5 +3,5 @@ module.exports = { tabWidth: 2, useTabs: false, singleQuote: true, - jsxBracketSameLine: true + jsxBracketSameLine: true, }; diff --git a/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.js b/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.js index 6c633081379..3c30d2d08ea 100644 --- a/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.js +++ b/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.js @@ -1,6 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { CheckboxField, TextInput, Pill, Button, Note } from '@contentful/forma-36-react-components'; +import { + CheckboxField, + TextInput, + Pill, + Button, + Note, +} from '@contentful/forma-36-react-components'; import get from 'lodash.get'; import { styles } from './styles'; @@ -12,26 +18,25 @@ async function callAPI(url) { } export class AITagView extends React.Component { - static propTypes = { entries: PropTypes.object.isRequired, notifier: PropTypes.object.isRequired, locale: PropTypes.string.isRequired, - space: PropTypes.object.isRequired + space: PropTypes.object.isRequired, }; - constructor(props){ + constructor(props) { super(props); this.state = { - value: "", + value: '', tags: props.entries.imageTags.getValue() || [], overwrite: true, isMissingImage: !props.entries.image.getValue(), unsupportedImageType: false, imageRequirementsNotMet: false, - isFetchingTags: false - } + isFetchingTags: false, + }; this.validateImage(); } @@ -39,7 +44,7 @@ export class AITagView extends React.Component { componentDidMount() { this.props.entries.image.onValueChanged(() => { this.setState(() => ({ - isMissingImage: !this.props.entries.image.getValue() + isMissingImage: !this.props.entries.image.getValue(), })); // always validate the image this.validateImage(); @@ -50,7 +55,9 @@ export class AITagView extends React.Component { const MAX_FILE_SIZE = 5 * Math.pow(2, 20); // 5MB limit from AWS Rekognition const MIN_DIMENSION_SIZE = 80; // 80px limit const imageId = get(this.props.entries.image.getValue(), 'sys.id'); - if (!imageId) { return ; } + if (!imageId) { + return; + } const file = await this.props.space.getAsset(imageId); const locale = this.props.locale; @@ -58,46 +65,49 @@ export class AITagView extends React.Component { const details = get(file, `fields.file.${locale}.details`); // test if file extension is PNG/JPEG/JPG const isImageTypeValid = new RegExp(/^image\/(png|jpe?g)$/, 'i').test(contentType); - const isImageIncompatible = details.size > MAX_FILE_SIZE || - details.width < MIN_DIMENSION_SIZE || - details.height < MIN_DIMENSION_SIZE; + const isImageIncompatible = + details.size > MAX_FILE_SIZE || + details.width < MIN_DIMENSION_SIZE || + details.height < MIN_DIMENSION_SIZE; this.setState(() => ({ unsupportedImageType: !isImageTypeValid, - imageRequirementsNotMet: isImageIncompatible - })) - } + imageRequirementsNotMet: isImageIncompatible, + })); + }; toggleOverwrite = () => { - this.setState(state => ({ - overwrite: !state.overwrite + this.setState((state) => ({ + overwrite: !state.overwrite, })); - } + }; updateTags = async (tags) => { const tagsWithoutDuplicates = [...new Set(tags)]; await this.props.entries.imageTags.setValue(tagsWithoutDuplicates); this.setState(() => ({ - tags: tagsWithoutDuplicates + tags: tagsWithoutDuplicates, })); - } + }; addTag = async (e) => { - if (e.key !== "Enter" || !e.target.value) { return; } + if (e.key !== 'Enter' || !e.target.value) { + return; + } await this.updateTags([e.target.value, ...this.state.tags]); this.setState(() => ({ - value: "" + value: '', })); - } + }; deleteTag = async (tag) => { - const newTags = this.state.tags.filter(t => t !== tag); + const newTags = this.state.tags.filter((t) => t !== tag); await this.updateTags(newTags); - } + }; - fetchTags = async () => { - const imageId = get(this.props.entries.image.getValue(), 'sys.id') + fetchTags = async () => { + const imageId = get(this.props.entries.image.getValue(), 'sys.id'); const file = await this.props.space.getAsset(imageId); const locale = this.props.locale; const fullURL = get(file, `fields.file.${locale}.url`); @@ -110,67 +120,72 @@ export class AITagView extends React.Component { // upload new tags const newTags = this.state.overwrite ? aiTags : [...aiTags, ...this.state.tags]; this.updateTags(newTags); - } catch(e) { - this.props.notifier.error("Image Tagging failed. Please try again later.") + } catch (e) { + this.props.notifier.error('Image Tagging failed. Please try again later.'); } finally { this.setState(() => ({ isFetchingTags: false })); } - } + }; updateValue = (e) => { this.setState({ - value: e.target.value + value: e.target.value, }); - } + }; render() { - let hasImageError = !this.state.isMissingImage && (this.state.unsupportedImageType || this.state.imageRequirementsNotMet) - let imageErrorMsg = this.state.unsupportedImageType ? "Unfortunately, we can only auto-tag PNG and JPG file types" : "Please make sure your image is less than 5MB and has dimensions of at least 80px for both width and height"; - - return
- -
- { - this.state.tags.map((tag, index) => ( + let hasImageError = + !this.state.isMissingImage && + (this.state.unsupportedImageType || this.state.imageRequirementsNotMet); + let imageErrorMsg = this.state.unsupportedImageType + ? 'Unfortunately, we can only auto-tag PNG and JPG file types' + : 'Please make sure your image is less than 5MB and has dimensions of at least 80px for both width and height'; + + return ( +
+ +
+ {this.state.tags.map((tag, index) => ( - )) - } + ))} +
+ {hasImageError && ( + + {imageErrorMsg} + + )} + +
- { - hasImageError && - { imageErrorMsg } - } - - -
+ ); } } diff --git a/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.spec.js b/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.spec.js index c5d82ead958..4800297fc17 100644 --- a/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.spec.js +++ b/apps/ai-image-tagging/frontend/src/components/AITagView/AITagView.spec.js @@ -11,30 +11,36 @@ const sdk = { fields: { image: { getValue: jest.fn(), - onValueChanged: jest.fn() + onValueChanged: jest.fn(), }, imageTags: { getValue: jest.fn(), - setValue: jest.fn() - } - } + setValue: jest.fn(), + }, + }, }, space: { - getAsset: jest.fn() + getAsset: jest.fn(), }, notifier: { - error: jest.fn() - } + error: jest.fn(), + }, }; configure({ testIdAttribute: 'data-test-id' }); function renderComponent(sdk) { - return render() + return render( + + ); } describe('AITagView', () => { - describe('if there is no image', () => { it('should disable everything', async () => { const appView = renderComponent(sdk); @@ -45,25 +51,24 @@ describe('AITagView', () => { expect(getByTestId('image-tag').disabled).toBeTruthy(); expect(appView.container).toMatchSnapshot(); }); - }) + }); describe('if there is an image', () => { beforeEach(() => { const imgData = { - url: "//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.png", + url: '//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.png', contentType: 'image/png', - details: { size: 200, height: 100, width: 100 } + details: { size: 200, height: 100, width: 100 }, }; sdk.space.getAsset.mockImplementation(() => ({ - fields: {file: { 'en-US': imgData }} - })) - - }) + fields: { file: { 'en-US': imgData } }, + })); + }); it('should enable everything', async () => { sdk.entry.fields.image.getValue.mockImplementation(() => ({ sys: { - id: '098dsjnwe9ds' - } + id: '098dsjnwe9ds', + }, })); const { getByTestId, getByText, container } = renderComponent(sdk); @@ -78,8 +83,8 @@ describe('AITagView', () => { const tags = ['tag1', 'tag2']; sdk.entry.fields.image.getValue.mockImplementation(() => ({ sys: { - id: '098dsjnwe9ds' - } + id: '098dsjnwe9ds', + }, })); sdk.entry.fields.imageTags.getValue.mockImplementation(() => tags); @@ -91,8 +96,8 @@ describe('AITagView', () => { it('should add image tags on Enter', async () => { sdk.entry.fields.image.getValue.mockImplementation(() => ({ sys: { - id: '098dsjnwe9ds' - } + id: '098dsjnwe9ds', + }, })); sdk.entry.fields.imageTags.getValue.mockImplementation(() => []); @@ -101,7 +106,7 @@ describe('AITagView', () => { await waitFor(() => getByTestId('image-tag')); const tagInput = getByTestId('image-tag'); - fireEvent.change(tagInput, {target: { value: 'new tag'} }); + fireEvent.change(tagInput, { target: { value: 'new tag' } }); fireEvent.keyPress(tagInput, { key: 'Enter', keyCode: 13 }); await waitFor(() => expect(getAllByTestId('cf-ui-pill')).toHaveLength(1)); @@ -111,8 +116,8 @@ describe('AITagView', () => { it('should ignore duplicate image tags', async () => { sdk.entry.fields.image.getValue.mockImplementation(() => ({ sys: { - id: '098dsjnwe9ds' - } + id: '098dsjnwe9ds', + }, })); sdk.entry.fields.imageTags.getValue.mockImplementation(() => ['tag1', 'tag2']); @@ -121,36 +126,34 @@ describe('AITagView', () => { await waitFor(() => getByTestId('image-tag')); const tagInput = getByTestId('image-tag'); - fireEvent.change(tagInput, {target: { value: 'tag1'} }); + fireEvent.change(tagInput, { target: { value: 'tag1' } }); fireEvent.keyPress(tagInput, { key: 'Enter', keyCode: 13 }); await waitFor(() => expect(getAllByTestId('cf-ui-pill')).toHaveLength(2)); expect(appView.container).toMatchSnapshot(); }); - }) - - - + }); describe('Calling AI Tags', () => { beforeEach(() => { const imgData = { url: '//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.jpeg', contentType: 'image/png', - details: { size: 200, height: 100, width: 100 } + details: { size: 200, height: 100, width: 100 }, }; - const expectedPath = '/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.jpeg' + const expectedPath = + '/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.jpeg'; sdk.entry.fields.image.getValue.mockImplementation(() => ({ sys: { - id: '098dsjnwe9ds' - } + id: '098dsjnwe9ds', + }, })); sdk.space.getAsset.mockImplementation(() => ({ - fields: {file: { 'en-US': imgData }} - })) + fields: { file: { 'en-US': imgData } }, + })); sdk.entry.fields.imageTags.getValue.mockImplementation(() => []); - fetchMock.get(`/tags/${expectedPath}`, {tags: ['ai-tag-1', 'ai-tag-2', 'ai-tag-3']}) + fetchMock.get(`/tags/${expectedPath}`, { tags: ['ai-tag-1', 'ai-tag-2', 'ai-tag-3'] }); }); afterEach(fetchMock.reset); @@ -158,12 +161,12 @@ describe('AITagView', () => { it('should fetch tags and render them on btn click', async () => { const appView = renderComponent(sdk); const { getByTestId, getAllByTestId } = appView; - await waitFor(() => getByTestId('image-tag')) + await waitFor(() => getByTestId('image-tag')); getByTestId('image-tag').value = 'new tag'; fireEvent.click(getByTestId('cf-ui-button')); - await waitFor(() => expect(getAllByTestId('cf-ui-pill')).toHaveLength(3)) + await waitFor(() => expect(getAllByTestId('cf-ui-pill')).toHaveLength(3)); expect(appView.container).toMatchSnapshot(); }); @@ -172,12 +175,12 @@ describe('AITagView', () => { sdk.entry.fields.imageTags.getValue.mockImplementation(() => ['prior-tag']); const appView = renderComponent(sdk); const { getByTestId, getAllByTestId } = appView; - await waitFor(() => getAllByTestId('cf-ui-pill')) + await waitFor(() => getAllByTestId('cf-ui-pill')); expect(getAllByTestId('cf-ui-pill')).toHaveLength(1); getByTestId('image-tag').value = 'new tag'; fireEvent.click(getByTestId('cf-ui-button')); - await waitFor(() => expect(getAllByTestId('cf-ui-pill')).toHaveLength(3)) + await waitFor(() => expect(getAllByTestId('cf-ui-pill')).toHaveLength(3)); expect(appView.container).toMatchSnapshot(); }); @@ -200,13 +203,13 @@ describe('AITagView', () => { it('should disable btn if image type is unsupported', async () => { const imgData = { - url: "//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.gif", + url: '//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.gif', contentType: 'image/gif', - details: { size: 200, height: 100, width: 100 } + details: { size: 200, height: 100, width: 100 }, }; sdk.space.getAsset.mockImplementation(() => ({ - fields: {file: { 'en-US': imgData }} - })) + fields: { file: { 'en-US': imgData } }, + })); const appView = renderComponent(sdk); const { getByTestId } = appView; await waitFor(() => expect(getByTestId('cf-ui-button').disabled).toBeTruthy()); @@ -217,13 +220,13 @@ describe('AITagView', () => { it('should disable btn if image dimensions are invalid', async () => { const imgData = { - url: "//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.png", + url: '//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.png', contentType: 'image/png', - details: { size: 2000, height: 70, width: 200 } + details: { size: 2000, height: 70, width: 200 }, }; sdk.space.getAsset.mockImplementation(() => ({ - fields: {file: { 'en-US': imgData }} - })) + fields: { file: { 'en-US': imgData } }, + })); const appView = renderComponent(sdk); const { getByTestId } = appView; @@ -234,13 +237,13 @@ describe('AITagView', () => { it('should disable btn if image is too big', async () => { const imgData = { - url: "//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.png", + url: '//images.ctfassets.net/k3tebg1cbyuz/4dgP2U7BeMuk0icguS4qGw/59b8fe25285cdd1b5fcc69bd5555b3be/doge.png', contentType: 'image/png', - details: { size: 6000000, height: 3000, width: 300 } + details: { size: 6000000, height: 3000, width: 300 }, }; sdk.space.getAsset.mockImplementation(() => ({ - fields: {file: { 'en-US': imgData }} - })) + fields: { file: { 'en-US': imgData } }, + })); const appView = renderComponent(sdk); const { getByTestId } = appView; diff --git a/apps/ai-image-tagging/frontend/src/components/AITagView/styles.js b/apps/ai-image-tagging/frontend/src/components/AITagView/styles.js index 40f2e8715c4..dd9f7667b43 100644 --- a/apps/ai-image-tagging/frontend/src/components/AITagView/styles.js +++ b/apps/ai-image-tagging/frontend/src/components/AITagView/styles.js @@ -3,21 +3,21 @@ import tokens from '@contentful/forma-36-tokens'; export const styles = { inputWrapper: css({ - marginTop: tokens.spacingXs + marginTop: tokens.spacingXs, }), pillWrapper: css({ marginTop: tokens.spacingXs, - marginBottom: tokens.spacingL + marginBottom: tokens.spacingL, }), pill: css({ marginRight: tokens.spacingS, - marginTop: tokens.spacingXs + marginTop: tokens.spacingXs, }), btn: css({ - marginRight: tokens.spacingM + marginRight: tokens.spacingM, }), fileWarning: css({ marginTop: tokens.spacingM, - marginBottom: tokens.spacingM - }) -} + marginBottom: tokens.spacingM, + }), +}; diff --git a/apps/ai-image-tagging/frontend/src/components/AppView/AppView.js b/apps/ai-image-tagging/frontend/src/components/AppView/AppView.js index e17c5ae6a67..1d2d99b413b 100644 --- a/apps/ai-image-tagging/frontend/src/components/AppView/AppView.js +++ b/apps/ai-image-tagging/frontend/src/components/AppView/AppView.js @@ -12,7 +12,7 @@ import appLogo from './app-logo.svg'; const makeContentType = (contentTypeId, contentTypeName) => ({ sys: { - id: contentTypeId + id: contentTypeId, }, name: contentTypeName, displayField: 'title', @@ -21,28 +21,28 @@ const makeContentType = (contentTypeId, contentTypeName) => ({ id: 'title', name: 'Title', required: true, - type: 'Symbol' + type: 'Symbol', }, { id: 'image', name: 'Image', required: true, type: 'Link', - linkType: 'Asset' + linkType: 'Asset', }, { id: 'imageTags', name: 'Image tags', required: true, type: 'Array', - items: { type: 'Symbol' } - } - ] + items: { type: 'Symbol' }, + }, + ], }); export class AppView extends Component { static propTypes = { - sdk: PropTypes.object.isRequired + sdk: PropTypes.object.isRequired, }; state = { @@ -50,7 +50,7 @@ export class AppView extends Component { allContentTypesIds: [], contentTypeId: 'imageWithAiTags', contentTypeName: 'Image with AI tags', - isContentTypeIdPristine: true + isContentTypeIdPristine: true, }; async componentDidMount() { @@ -58,7 +58,7 @@ export class AppView extends Component { const [isInstalled, allContentTypes] = await Promise.all([ app.isInstalled(), - space.getContentTypes() + space.getContentTypes(), ]); const allContentTypesIds = allContentTypes.items.map(({ sys: { id } }) => id); @@ -68,7 +68,7 @@ export class AppView extends Component { this.setState({ isInstalled, allContentTypesIds }, () => app.setReady()); app.onConfigure(this.installApp); - app.onConfigurationCompleted(err => { + app.onConfigurationCompleted((err) => { if (!err) { this.setState({ isInstalled: true }); } @@ -118,23 +118,23 @@ export class AppView extends Component { targetState: { EditorInterface: { [contentType.sys.id]: { - controls: [{ fieldId: 'imageTags' }] - } - } - } + controls: [{ fieldId: 'imageTags' }], + }, + }, + }, }; }; onContentTypeNameChange = ({ target: { value } }) => - this.setState(oldState => ({ + this.setState((oldState) => ({ ...(oldState.isContentTypeIdPristine && { contentTypeId: camelCase(value) }), - contentTypeName: value + contentTypeName: value, })); onContentTypeIdChange = ({ target: { value } }) => this.setState({ isContentTypeIdPristine: false, - contentTypeId: value + contentTypeId: value, }); render() { diff --git a/apps/ai-image-tagging/frontend/src/components/AppView/AppView.spec.js b/apps/ai-image-tagging/frontend/src/components/AppView/AppView.spec.js index 73e6e4c4cb0..2b79d094071 100644 --- a/apps/ai-image-tagging/frontend/src/components/AppView/AppView.spec.js +++ b/apps/ai-image-tagging/frontend/src/components/AppView/AppView.spec.js @@ -6,25 +6,24 @@ import { AppView } from './AppView'; configure({ testIdAttribute: 'data-test-id' }); - describe('AppView', () => { - let props + let props; beforeEach(() => { props = { sdk: { ...mockProps.sdk, space: { - getContentTypes: jest.fn(() => Promise.resolve({ items: [] })) + getContentTypes: jest.fn(() => Promise.resolve({ items: [] })), }, app: { setReady: jest.fn(), isInstalled: jest.fn(), onConfigure: jest.fn(), - onConfigurationCompleted: jest.fn() - } - } + onConfigurationCompleted: jest.fn(), + }, + }, }; - }) + }); describe('when the app is not installed', () => { beforeEach(() => props.sdk.app.isInstalled.mockImplementation(() => Promise.resolve(false))); @@ -36,9 +35,11 @@ describe('AppView', () => { }); it('should render inline validation if the content type id is taken', async () => { - props.sdk.space.getContentTypes.mockImplementation(() => Promise.resolve({ - items: [{ sys: { id: 'imageWithFocalPoint' } }] - })); + props.sdk.space.getContentTypes.mockImplementation(() => + Promise.resolve({ + items: [{ sys: { id: 'imageWithFocalPoint' } }], + }) + ); const { getByTestId, getByText } = render(); await waitFor(() => getByText('Configuration')); expect(getByTestId('content-type-name')).toMatchSnapshot(); diff --git a/apps/ai-image-tagging/frontend/src/components/AppView/ConfigurationContent.js b/apps/ai-image-tagging/frontend/src/components/AppView/ConfigurationContent.js index 224f9b62987..72e3dca0faf 100644 --- a/apps/ai-image-tagging/frontend/src/components/AppView/ConfigurationContent.js +++ b/apps/ai-image-tagging/frontend/src/components/AppView/ConfigurationContent.js @@ -10,8 +10,8 @@ export const ConfigurationContent = () => ( Go to the "Content" page - Create a new entry of type "AI Image Tagging" (or the name you chose during - the installation) + Create a new entry of type "AI Image Tagging" (or the name you chose during the + installation) Fill in the required fields and publish @@ -21,9 +21,7 @@ export const ConfigurationContent = () => ( Go to the "content model" page - - Edit the content type that needs to reference the image with the AI tags - + Edit the content type that needs to reference the image with the AI tags Create a new field of type "reference" Set a validation rule requiring that the content type the reference points to is of type diff --git a/apps/ai-image-tagging/frontend/src/components/AppView/InstallationContent.js b/apps/ai-image-tagging/frontend/src/components/AppView/InstallationContent.js index 7a77ff1c7c5..a7721403548 100644 --- a/apps/ai-image-tagging/frontend/src/components/AppView/InstallationContent.js +++ b/apps/ai-image-tagging/frontend/src/components/AppView/InstallationContent.js @@ -8,7 +8,7 @@ export function InstallationContent({ contentTypeId, contentTypeName, onContentTypeNameChange, - onContentTypeIdChange + onContentTypeIdChange, }) { const validationMessageId = allContentTypesIds.includes(contentTypeId) ? `A content type with ID "${contentTypeId}" already exists. Try a different ID.` @@ -27,7 +27,7 @@ export function InstallationContent({ name="contentTypeName" textInputProps={{ placeholder: 'e.g. AI Tagged Image', - testId: 'content-type-name-input' + testId: 'content-type-name-input', }} helpText="You can use this content type to add tags to images" value={contentTypeName} @@ -58,5 +58,5 @@ InstallationContent.propTypes = { contentTypeId: PropTypes.string.isRequired, contentTypeName: PropTypes.string.isRequired, onContentTypeNameChange: PropTypes.func.isRequired, - onContentTypeIdChange: PropTypes.func.isRequired + onContentTypeIdChange: PropTypes.func.isRequired, }; diff --git a/apps/ai-image-tagging/frontend/src/components/AppView/styles.js b/apps/ai-image-tagging/frontend/src/components/AppView/styles.js index 1d0a9dab1e4..29cfc0098d1 100644 --- a/apps/ai-image-tagging/frontend/src/components/AppView/styles.js +++ b/apps/ai-image-tagging/frontend/src/components/AppView/styles.js @@ -12,7 +12,7 @@ export const styles = { backgroundColor: tokens.colorWhite, zIndex: '2', boxShadow: '0px 0px 20px rgba(0, 0, 0, 0.1)', - borderRadius: '2px' + borderRadius: '2px', }), background: css({ display: 'block', @@ -21,15 +21,15 @@ export const styles = { top: '0', width: '100%', height: '300px', - backgroundColor: tokens.orange500 // corresponds to logo + backgroundColor: tokens.orange500, // corresponds to logo }), featuresListItem: css({ listStyleType: 'disc', - marginLeft: tokens.spacingM + marginLeft: tokens.spacingM, }), light: css({ color: tokens.gray600, - marginTop: tokens.spacingM + marginTop: tokens.spacingM, }), logo: css({ width: '100%', @@ -38,19 +38,19 @@ export const styles = { marginTop: tokens.spacingXl, marginBottom: tokens.spacingXl, '& > img': css({ - width: '80px' - }) + width: '80px', + }), }), paragraph: css({ - marginTop: tokens.spacingM + marginTop: tokens.spacingM, }), heading: css({ - marginBottom: tokens.spacingM + marginBottom: tokens.spacingM, }), input: css({ - marginTop: tokens.spacingM + marginTop: tokens.spacingM, }), list: css({ - marginTop: tokens.spacingXs - }) + marginTop: tokens.spacingXs, + }), }; diff --git a/apps/ai-image-tagging/frontend/src/components/Divider/styles.js b/apps/ai-image-tagging/frontend/src/components/Divider/styles.js index 432a9788a14..60af7dee00a 100644 --- a/apps/ai-image-tagging/frontend/src/components/Divider/styles.js +++ b/apps/ai-image-tagging/frontend/src/components/Divider/styles.js @@ -7,6 +7,6 @@ export const styles = { marginBottom: tokens.spacingL, border: 0, height: '1px', - backgroundColor: tokens.gray300 - }) + backgroundColor: tokens.gray300, + }), }; diff --git a/apps/ai-image-tagging/frontend/src/index.js b/apps/ai-image-tagging/frontend/src/index.js index 0b2e53d99b1..1bb8402a1eb 100644 --- a/apps/ai-image-tagging/frontend/src/index.js +++ b/apps/ai-image-tagging/frontend/src/index.js @@ -7,7 +7,7 @@ import './index.css'; import { AppView } from './components/AppView'; import { AITagView } from './components/AITagView'; -init(sdk => { +init((sdk) => { if (sdk.location.is(locations.LOCATION_APP_CONFIG)) { render(, document.getElementById('root')); } @@ -19,8 +19,8 @@ init(sdk => { , document.getElementById('root') ); diff --git a/apps/ai-image-tagging/frontend/src/test/mockProps.js b/apps/ai-image-tagging/frontend/src/test/mockProps.js index b91d5d9117c..910d2f0d97c 100644 --- a/apps/ai-image-tagging/frontend/src/test/mockProps.js +++ b/apps/ai-image-tagging/frontend/src/test/mockProps.js @@ -7,8 +7,8 @@ export default { extension: 'ai-image-tagging', space: 'cyu19ucaypb9', environment: 'master', - user: '2userId252' + user: '2userId252', }, - window: {} - } + window: {}, + }, }; diff --git a/apps/ai-image-tagging/lambda/app.js b/apps/ai-image-tagging/lambda/app.js index 92c562152d9..e1fed026828 100644 --- a/apps/ai-image-tagging/lambda/app.js +++ b/apps/ai-image-tagging/lambda/app.js @@ -14,7 +14,7 @@ const documentClient = new AWS.DynamoDB.DocumentClient(); const deps = { fetch, rekog, - documentClient + documentClient, }; const app = express(); diff --git a/apps/ai-image-tagging/lambda/handler.js b/apps/ai-image-tagging/lambda/handler.js index c96185d6ea6..5d417b5cba2 100644 --- a/apps/ai-image-tagging/lambda/handler.js +++ b/apps/ai-image-tagging/lambda/handler.js @@ -7,7 +7,7 @@ module.exports = async (method, path, { fetch, rekog, documentClient }) => { if (method !== 'GET') { return { status: 405, - body: { message: 'Method not allowed.' } + body: { message: 'Method not allowed.' }, }; } @@ -26,7 +26,7 @@ module.exports = async (method, path, { fetch, rekog, documentClient }) => { console.error(`Hard usage limit exceeded for space ${spaceId}. Aborting.`); return { status: 403, - message: 'Usage exceeded.' + message: 'Usage exceeded.', }; } } catch (err) { @@ -37,12 +37,12 @@ module.exports = async (method, path, { fetch, rekog, documentClient }) => { try { return { status: 200, - body: { tags: await tag(path, { fetch, rekog }) } + body: { tags: await tag(path, { fetch, rekog }) }, }; } catch (err) { return { status: 400, - body: { message: err.message || err.errorMessage } + body: { message: err.message || err.errorMessage }, }; } }; diff --git a/apps/ai-image-tagging/lambda/handler.test.js b/apps/ai-image-tagging/lambda/handler.test.js index fd6445bd5d3..91d05f67fd0 100644 --- a/apps/ai-image-tagging/lambda/handler.test.js +++ b/apps/ai-image-tagging/lambda/handler.test.js @@ -15,11 +15,9 @@ describe('handler', () => { const { status, body } = await handle('GET', '/some-space/some-image', mocks); expect(status).toBe(200); - expect(body).toEqual({ tags: ['cat', 'yolo']}); + expect(body).toEqual({ tags: ['cat', 'yolo'] }); - expect(mocks.fetch).toBeCalledWith( - 'https://images.ctfassets.net/some-space/some-image' - ); + expect(mocks.fetch).toBeCalledWith('https://images.ctfassets.net/some-space/some-image'); expect(mocks.documentClient.update).toBeCalledTimes(1); expect(mocks.rekog.detectLabels).toBeCalledTimes(1); }); diff --git a/apps/ai-image-tagging/lambda/mocks.js b/apps/ai-image-tagging/lambda/mocks.js index 8928da06515..50cc34294e3 100644 --- a/apps/ai-image-tagging/lambda/mocks.js +++ b/apps/ai-image-tagging/lambda/mocks.js @@ -2,32 +2,29 @@ const fetch = jest.fn().mockResolvedValue({ status: 200, - arrayBuffer: jest.fn().mockResolvedValue('SOME_ARR_BUFF') + arrayBuffer: jest.fn().mockResolvedValue('SOME_ARR_BUFF'), }); const documentClient = { update: jest.fn().mockReturnValue({ promise: jest.fn().mockResolvedValue({ Attributes: { - reqs: 7 - } - }) - }) + reqs: 7, + }, + }), + }), }; const rekog = { detectLabels: jest.fn().mockReturnValue({ promise: jest.fn().mockResolvedValue({ - Labels: [ - { Name: 'cat' }, - { Name: 'yolo' } - ] - }) - }) + Labels: [{ Name: 'cat' }, { Name: 'yolo' }], + }), + }), }; module.exports = { fetch, documentClient, - rekog + rekog, }; diff --git a/apps/ai-image-tagging/lambda/tag.js b/apps/ai-image-tagging/lambda/tag.js index 70336a1639a..e8b116e0eb7 100644 --- a/apps/ai-image-tagging/lambda/tag.js +++ b/apps/ai-image-tagging/lambda/tag.js @@ -12,10 +12,10 @@ const fetchImage = async (imageUrl, fetch) => { } }; -const getDetectParams = imageData => ({ - Image: { Bytes: imageData, }, +const getDetectParams = (imageData) => ({ + Image: { Bytes: imageData }, MaxLabels: 10, - MinConfidence: 70.0 + MinConfidence: 70.0, }); module.exports = async (path, { fetch, rekog }) => { @@ -23,5 +23,5 @@ module.exports = async (path, { fetch, rekog }) => { const params = getDetectParams(imageData); const tags = await rekog.detectLabels(params).promise(); - return tags.Labels.map(label => label.Name); + return tags.Labels.map((label) => label.Name); }; diff --git a/apps/ai-image-tagging/lambda/tag.test.js b/apps/ai-image-tagging/lambda/tag.test.js index ae4cc44fa03..1d7fc2f5ada 100644 --- a/apps/ai-image-tagging/lambda/tag.test.js +++ b/apps/ai-image-tagging/lambda/tag.test.js @@ -14,10 +14,10 @@ describe('tagging', () => { expect(rekog.detectLabels).toBeCalledWith({ Image: { - Bytes: 'SOME_ARR_BUFF' + Bytes: 'SOME_ARR_BUFF', }, MaxLabels: 10, - MinConfidence: 70.0 + MinConfidence: 70.0, }); }); diff --git a/apps/ai-image-tagging/lambda/usage.js b/apps/ai-image-tagging/lambda/usage.js index 45dd3c42be8..53391b2358c 100644 --- a/apps/ai-image-tagging/lambda/usage.js +++ b/apps/ai-image-tagging/lambda/usage.js @@ -5,14 +5,16 @@ const { DateTime } = require('luxon'); module.exports = async (spaceId, documentClient) => { const period = DateTime.utc().startOf('month').toSeconds(); - const { Attributes } = await documentClient.update({ - TableName: process.env.TABLE_NAME, - Key: { period, spaceId }, - UpdateExpression: 'SET #attr = if_not_exists(#attr, :zero) + :incr', - ExpressionAttributeNames: { '#attr': 'reqs' }, - ExpressionAttributeValues: { ':incr': 1, ':zero': 0 }, - ReturnValues: 'ALL_NEW' - }).promise(); + const { Attributes } = await documentClient + .update({ + TableName: process.env.TABLE_NAME, + Key: { period, spaceId }, + UpdateExpression: 'SET #attr = if_not_exists(#attr, :zero) + :incr', + ExpressionAttributeNames: { '#attr': 'reqs' }, + ExpressionAttributeValues: { ':incr': 1, ':zero': 0 }, + ReturnValues: 'ALL_NEW', + }) + .promise(); const reqs = Attributes && Attributes.reqs; diff --git a/apps/ai-image-tagging/lambda/usage.test.js b/apps/ai-image-tagging/lambda/usage.test.js index 6b74b540b84..842a597b6ed 100644 --- a/apps/ai-image-tagging/lambda/usage.test.js +++ b/apps/ai-image-tagging/lambda/usage.test.js @@ -14,7 +14,7 @@ describe('usage', () => { test('reports usage', async () => { global.Date = class extends OriginalDate { - constructor () { + constructor() { return new OriginalDate(2015, 1, 15); } }; @@ -32,7 +32,7 @@ describe('usage', () => { UpdateExpression: 'SET #attr = if_not_exists(#attr, :zero) + :incr', ExpressionAttributeNames: { '#attr': 'reqs' }, ExpressionAttributeValues: { ':incr': 1, ':zero': 0 }, - ReturnValues: 'ALL_NEW' + ReturnValues: 'ALL_NEW', }); }); }); diff --git a/apps/brandfolder/src/index.js b/apps/brandfolder/src/index.js index c9285cc9daa..7015d17d3c1 100644 --- a/apps/brandfolder/src/index.js +++ b/apps/brandfolder/src/index.js @@ -7,22 +7,22 @@ const BF_EMBED_URL = `https://integration-panel-ui.brandfolder-svc.com?channel=m const CTA = 'Select an asset on Brandfolder'; const FIELDS_TO_PERSIST = [ - 'asset', - 'cdn_url', - 'mux_hls_url', - 'extension', - 'filename', - 'height', - 'id', - 'included', - 'mimetype', - 'position', - 'relationships', - 'size', - 'thumbnail_url', - 'type', - 'url', - 'width', + 'asset', + 'cdn_url', + 'mux_hls_url', + 'extension', + 'filename', + 'height', + 'id', + 'included', + 'mimetype', + 'position', + 'relationships', + 'size', + 'thumbnail_url', + 'type', + 'url', + 'width', ]; function makeThumbnail(attachment) { @@ -54,7 +54,7 @@ function renderDialog(sdk) { sdk.window.startAutoResizer(); - window.addEventListener('message', e => { + window.addEventListener('message', (e) => { if (e.source !== iframe.contentWindow) { return; } @@ -64,7 +64,7 @@ function renderDialog(sdk) { sdk.close([payload]); } else if (event === 'selectedAsset' && payload.attachments.length !== 0) { const att_id = payload.attachments[0].id; - const attachment = payload.included.find(att => att.id === att_id); + const attachment = payload.included.find((att) => att.id === att_id); if (attachment) { sdk.close([attachment]); } @@ -80,7 +80,7 @@ async function openDialog(sdk, _currentValue, config) { shouldCloseOnEscapePress: true, parameters: { ...config }, width: 400, - allowHeightOverflow: true + allowHeightOverflow: true, }); if (!Array.isArray(result)) { @@ -105,7 +105,7 @@ async function openDialog(sdk, _currentValue, config) { // url: "https://s3.amazonaws.com/bf.boulder.prod/pfbfh7-f4zem0-dpcdo2/original/brandfolder-icon.png" // width: 312 // }] - return result.map(asset => pick(asset, FIELDS_TO_PERSIST)); + return result.map((asset) => pick(asset, FIELDS_TO_PERSIST)); } setup({ @@ -122,12 +122,12 @@ setup({ name: 'Brandfolder API key', description: 'If you want to use just one API key (https://brandfolder.com/profile#integrations) for all users, enter it here.', - required: false - } + required: false, + }, ], validateParameters: () => {}, makeThumbnail, renderDialog, openDialog, - isDisabled: () => false + isDisabled: () => false, }); diff --git a/apps/bynder/src/index.js b/apps/bynder/src/index.js index 181f3a127d1..85ee904763a 100644 --- a/apps/bynder/src/index.js +++ b/apps/bynder/src/index.js @@ -5,7 +5,7 @@ import logo from './logo.svg'; const CTA = 'Select a file on Bynder'; -const BYNDER_BASE_URL = "https://d8ejoa1fys2rk.cloudfront.net"; +const BYNDER_BASE_URL = 'https://d8ejoa1fys2rk.cloudfront.net'; const BYNDER_SDK_URL = `${BYNDER_BASE_URL}/5.0.5/modules/compactview/bynder-compactview-2-latest.js`; const FIELDS_TO_PERSIST = [ @@ -29,7 +29,7 @@ const FIELDS_TO_PERSIST = [ 'type', 'watermarked', 'width', - "videoPreviewURLs" + 'videoPreviewURLs', ]; const FIELD_SELECTION = ` @@ -79,56 +79,59 @@ function prepareBynderHTML() { function transformAsset(asset) { const thumbnails = { - "webimage": asset.files.webImage?.url, - "thul": asset.files.thumbnail?.url - } + webimage: asset.files.webImage?.url, + thul: asset.files.thumbnail?.url, + }; Object.entries(asset.files) - .filter(([name]) => !["webImage", "thumbnail"].includes(name)) - .forEach(([key, value]) => thumbnails[key] = value?.url); - - return ({ - "id": asset.databaseId, - "orientation": asset.orientation.toLowerCase(), - "archive": asset.isArchived ? 1 : 0, - "type": asset.type.toLowerCase(), - "fileSize": asset.fileSize, - "description": asset.description, - "name": asset.name, - "height": asset.height, - "width": asset.width, - "copyright": asset.copyright, - "extension": asset.extensions, - "userCreated": asset.createdBy, - "datePublished": asset.publishedAt, - "dateCreated": asset.createdAt, - "dateModified": asset.updatedAt, - "watermarked": asset.isWatermarked ? 1 : 0, - "limited": asset.isLimitedUse ? 1 : 0, - "isPublic": asset.isPublic ? 1 : 0, - "brandId": asset.brandId, - "thumbnails": thumbnails, - "original": asset.originalUrl, - "videoPreviewURLs": asset.previewUrls || [] - }) + .filter(([name]) => !['webImage', 'thumbnail'].includes(name)) + .forEach(([key, value]) => (thumbnails[key] = value?.url)); + + return { + id: asset.databaseId, + orientation: asset.orientation.toLowerCase(), + archive: asset.isArchived ? 1 : 0, + type: asset.type.toLowerCase(), + fileSize: asset.fileSize, + description: asset.description, + name: asset.name, + height: asset.height, + width: asset.width, + copyright: asset.copyright, + extension: asset.extensions, + userCreated: asset.createdBy, + datePublished: asset.publishedAt, + dateCreated: asset.createdAt, + dateModified: asset.updatedAt, + watermarked: asset.isWatermarked ? 1 : 0, + limited: asset.isLimitedUse ? 1 : 0, + isPublic: asset.isPublic ? 1 : 0, + brandId: asset.brandId, + thumbnails: thumbnails, + original: asset.originalUrl, + videoPreviewURLs: asset.previewUrls || [], + }; } function checkMessageEvent(e) { if (e.origin !== BYNDER_BASE_URL) { - e.stopImmediatePropagation() + e.stopImmediatePropagation(); } } function renderDialog(sdk) { const config = sdk.parameters.invocation; - const { assetTypes, bynderURL } = config + const { assetTypes, bynderURL } = config; let types = []; if (!assetTypes) { // We default to just images in this fallback since this is the behavior the App had in its initial release types = ['IMAGE']; } else { - types = assetTypes.trim().split(',').map((type) => type.toUpperCase()); + types = assetTypes + .trim() + .split(',') + .map((type) => type.toUpperCase()); } const script = document.createElement('script'); @@ -142,24 +145,24 @@ function renderDialog(sdk) { sdk.window.startAutoResizer(); - window.addEventListener("message", checkMessageEvent) + window.addEventListener('message', checkMessageEvent); function onSuccess(assets, selected) { sdk.close(Array.isArray(assets) ? assets.map(transformAsset) : []); - window.removeEventListener("message", checkMessageEvent) + window.removeEventListener('message', checkMessageEvent); } - script.addEventListener("load", () => { + script.addEventListener('load', () => { window.BynderCompactView.open({ - language: "en_US", - mode: "MultiSelect", + language: 'en_US', + mode: 'MultiSelect', assetTypes: types, portal: { url: bynderURL, editable: true }, assetFieldSelection: FIELD_SELECTION, - container: document.getElementById("bynder-compactview"), - onSuccess: onSuccess + container: document.getElementById('bynder-compactview'), + onSuccess: onSuccess, }); - }) + }); } async function openDialog(sdk, _currentValue, config) { @@ -169,16 +172,16 @@ async function openDialog(sdk, _currentValue, config) { shouldCloseOnOverlayClick: true, shouldCloseOnEscapePress: true, parameters: { ...config }, - width: 1400 + width: 1400, }); if (!Array.isArray(result)) { return []; } - return result.map(item => ({ + return result.map((item) => ({ ...pick(item, FIELDS_TO_PERSIST), - src: item.thumbnails && item.thumbnails.webimage + src: item.thumbnails && item.thumbnails.webimage, })); } @@ -188,17 +191,20 @@ function isDisabled() { function validateParameters({ bynderURL, assetTypes }) { const hasValidProtocol = bynderURL.startsWith('https://'); - const isHTMLSafe = ['"', '<', '>'].every(unsafe => !bynderURL.includes(unsafe)); + const isHTMLSafe = ['"', '<', '>'].every((unsafe) => !bynderURL.includes(unsafe)); if (!hasValidProtocol || !isHTMLSafe) { return 'Provide a valid Bynder URL.'; } - const types = assetTypes.trim().split(',').map(type => type.trim()); - const isAssetTypesValid = types.every(type => validAssetTypes.includes(type)); + const types = assetTypes + .trim() + .split(',') + .map((type) => type.trim()); + const isAssetTypesValid = types.every((type) => validAssetTypes.includes(type)); if (!isAssetTypesValid) { - return `Only valid asset types may be selected: ${validAssetTypes.join(',')}` + return `Only valid asset types may be selected: ${validAssetTypes.join(',')}`; } return null; @@ -213,24 +219,24 @@ setup({ 'The Bynder app is a widget that allows editors to select media from their Bynder account. Select or upload a file on Bynder and designate the assets that you want your entry to reference.', parameterDefinitions: [ { - "id": "bynderURL", - "type": "Symbol", - "name": "Bynder URL", - "description": "Provide Bynder URL of your account.", - "required": true + id: 'bynderURL', + type: 'Symbol', + name: 'Bynder URL', + description: 'Provide Bynder URL of your account.', + required: true, }, { - "id": "assetTypes", - "type": "Symbol", - "name": "Asset types", - "description": "Choose which types of assets can be selected.", - "default": validAssetTypes.join(','), - "required": true - } + id: 'assetTypes', + type: 'Symbol', + name: 'Asset types', + description: 'Choose which types of assets can be selected.', + default: validAssetTypes.join(','), + required: true, + }, ], makeThumbnail, renderDialog, openDialog, isDisabled, - validateParameters + validateParameters, }); diff --git a/apps/cloudinary/src/index.js b/apps/cloudinary/src/index.js index cbe734298db..f1a54ab6a59 100644 --- a/apps/cloudinary/src/index.js +++ b/apps/cloudinary/src/index.js @@ -41,29 +41,29 @@ const FIELDS_TO_PERSIST = [ 'resource_type', 'original_url', 'original_secure_url', - 'raw_transformation' + 'raw_transformation', ]; function makeThumbnail(resource, config) { const cloudinary = new cloudinaryCore({ cloud_name: config.cloudName, - api_key: config.apiKey + api_key: config.apiKey, }); let url; - resource.raw_transformation = resource.raw_transformation || ""; + resource.raw_transformation = resource.raw_transformation || ''; const alt = [resource.public_id, ...(resource.tags || [])].join(', '); let transformations = `${resource.raw_transformation}/c_fill,h_100,w_150`; if (resource.resource_type === 'image' && VALID_IMAGE_FORMATS.includes(resource.format)) { url = cloudinary.url(resource.public_id, { type: resource.type, - rawTransformation: transformations + rawTransformation: transformations, }); } else if (resource.resource_type === 'video') { url = cloudinary.video_thumbnail_url(resource.public_id, { type: resource.type, - rawTransformation: transformations + rawTransformation: transformations, }); } @@ -74,15 +74,15 @@ function renderDialog(sdk) { const { cloudinary } = window; const config = sdk.parameters.invocation; - const transformations = [] + const transformations = []; // Handle format - if(config.format!=='none'){ + if (config.format !== 'none') { transformations.push({ fetch_format: config.format }); } // Handle quality - if(config.quality!=='none'){ + if (config.quality !== 'none') { transformations.push({ quality: config.quality }); } @@ -93,19 +93,18 @@ function renderDialog(sdk) { multiple: config.maxFiles > 1, inline_container: '#root', remove_header: true, - default_transformations: [transformations] + default_transformations: [transformations], }; - const instance = cloudinary.createMediaLibrary(options, { - insertHandler: data => sdk.close(data) + insertHandler: (data) => sdk.close(data), }); const showOptions = {}; if (typeof config.startFolder === 'string' && config.startFolder.length) { - showOptions.folder = {path: config.startFolder}; + showOptions.folder = { path: config.startFolder }; } - + instance.show(showOptions); sdk.window.updateHeight(window.outerHeight); @@ -120,28 +119,28 @@ async function openDialog(sdk, currentValue, config) { shouldCloseOnOverlayClick: true, shouldCloseOnEscapePress: true, parameters: { ...config, maxFiles }, - width: 1400 + width: 1400, }); if (result && Array.isArray(result.assets)) { - return result.assets.map(asset => extractAsset(asset)); + return result.assets.map((asset) => extractAsset(asset)); } else { return []; } } -function extractAsset(asset){ - let res = pick(asset,FIELDS_TO_PERSIST); +function extractAsset(asset) { + let res = pick(asset, FIELDS_TO_PERSIST); // if we have a derived images, we replace the URL with the derived URL and store the origianl URL seperatly - if(asset.derived){ + if (asset.derived) { res = { ...res, original_url: res.url, original_secure_url: res.secure_url, url: asset.derived[0].url, secure_url: asset.derived[0].secure_url, - raw_transformation: asset.derived[0].raw_transformation - } + raw_transformation: asset.derived[0].raw_transformation, + }; } return res; } @@ -178,57 +177,62 @@ setup({ color: '#F4B21B', parameterDefinitions: [ { - "id": "cloudName", - "name": "Cloud name", - "description": "The Cloudinary cloud name that the app will connect to.", - "type": "Symbol", - "required": true + id: 'cloudName', + name: 'Cloud name', + description: 'The Cloudinary cloud name that the app will connect to.', + type: 'Symbol', + required: true, }, { - "id": "apiKey", - "name": "API key", - "description": "The Cloduinary API Key that can be found in your Cloudinary console.", - "type": "Symbol", - "required": true + id: 'apiKey', + name: 'API key', + description: 'The Cloduinary API Key that can be found in your Cloudinary console.', + type: 'Symbol', + required: true, }, { - "id": "maxFiles", - "name": "Max number of files", - "description": "The max number of files that can be added to a single field. Must be between 1 and 25", - "type": "Number", - "required": false, - "default": 10 + id: 'maxFiles', + name: 'Max number of files', + description: + 'The max number of files that can be added to a single field. Must be between 1 and 25', + type: 'Number', + required: false, + default: 10, }, { - "id": "startFolder", - "name": "Starting folder", - "description": "A path to a folder which the Cloudinary Media Library will automatically browse to on load", - "type": "Symbol", - "required": false, - "default": "" + id: 'startFolder', + name: 'Starting folder', + description: + 'A path to a folder which the Cloudinary Media Library will automatically browse to on load', + type: 'Symbol', + required: false, + default: '', }, { - "id": "quality", - "name": "Media Quality", - "description": "The quality level of your assets. This can be a fixed number ranging from 1-100, or you can get Cloudinary to decide the most optimized level by setting it to 'auto'. More options are available such as: auto:low/auto:eco/auto:good/auto:best. If you wish to use the original level, set it to 'none'.", - "type": "List", - "value": "auto,none,auto:low,auto:eco,auto:good,auto:best,10,20,30,40,50,60,70,80,90,100", - "required": true, - "default": "auto" + id: 'quality', + name: 'Media Quality', + description: + "The quality level of your assets. This can be a fixed number ranging from 1-100, or you can get Cloudinary to decide the most optimized level by setting it to 'auto'. More options are available such as: auto:low/auto:eco/auto:good/auto:best. If you wish to use the original level, set it to 'none'.", + type: 'List', + value: 'auto,none,auto:low,auto:eco,auto:good,auto:best,10,20,30,40,50,60,70,80,90,100', + required: true, + default: 'auto', }, { - "id": "format", - "name": "Format", - "description": "The format of the assets. This can be set manually to a specific format - 'jpg' as an example (all supported formats can be found here - https://cloudinary.com/documentation/image_transformations#supported_image_formats. By setting it to 'auto', Cloudinary will decide on the most optimized format for your users. If you wish to keep the original format, set it to 'none'.", - "type": "List", - "value":"auto,none,gif,webp,bmp,flif,heif,heic,ico,jpg,jpe,jpeg,jp2,wdp,jxr,hdp,png,psd,arw,cr2,svg,tga,tif,tiff", - "required": true, - "default": "auto" - } + id: 'format', + name: 'Format', + description: + "The format of the assets. This can be set manually to a specific format - 'jpg' as an example (all supported formats can be found here - https://cloudinary.com/documentation/image_transformations#supported_image_formats. By setting it to 'auto', Cloudinary will decide on the most optimized format for your users. If you wish to keep the original format, set it to 'none'.", + type: 'List', + value: + 'auto,none,gif,webp,bmp,flif,heif,heic,ico,jpg,jpe,jpeg,jp2,wdp,jxr,hdp,png,psd,arw,cr2,svg,tga,tif,tiff', + required: true, + default: 'auto', + }, ], makeThumbnail, renderDialog, openDialog, isDisabled, - validateParameters + validateParameters, }); diff --git a/apps/commercelayer/src/dataTransformer.js b/apps/commercelayer/src/dataTransformer.js index 1c0a09a334c..8eec84f3e57 100644 --- a/apps/commercelayer/src/dataTransformer.js +++ b/apps/commercelayer/src/dataTransformer.js @@ -4,7 +4,7 @@ import get from 'lodash/get'; * Transforms the API response of CommerceLayer into * the product schema expected by the SkuPicker component */ -export const dataTransformer = projectUrl => product => { +export const dataTransformer = (projectUrl) => (product) => { const { id } = product; const image = get(product, ['imageUrl']) || get(product, ['attributes', 'image_url']); const name = get(product, ['name']) || get(product, ['attributes', 'name']); @@ -14,6 +14,6 @@ export const dataTransformer = projectUrl => product => { image, name, sku, - externalLink: `${projectUrl}/admin/skus/${id}/edit` + externalLink: `${projectUrl}/admin/skus/${id}/edit`, }; }; diff --git a/apps/commercelayer/src/index.js b/apps/commercelayer/src/index.js index 384974aae7b..50e9d6c490e 100644 --- a/apps/commercelayer/src/index.js +++ b/apps/commercelayer/src/index.js @@ -40,7 +40,7 @@ async function getAccessToken(clientId, endpoint) { // CLayerAuth SDK will throw if not present. By setting to empty // string we prevent the SDK exception and the value is ignored // by the Commerce Layer Auth API. - clientSecret: '' + clientSecret: '', }) ).accessToken; } @@ -64,16 +64,16 @@ async function fetchSKUs(installationParams, search, pagination) { const { clientId, apiEndpoint } = installationParams; const accessToken = await getAccessToken(clientId, apiEndpoint); - const URL = `${apiEndpoint}/api/skus?page[size]=${PER_PAGE}&page[number]=${pagination.offset / - PER_PAGE + - 1}${search.length ? `&filter[q][name_or_code_cont]=${search}` : ''}`; + const URL = `${apiEndpoint}/api/skus?page[size]=${PER_PAGE}&page[number]=${ + pagination.offset / PER_PAGE + 1 + }${search.length ? `&filter[q][name_or_code_cont]=${search}` : ''}`; const res = await fetch(URL, { headers: { Accept: 'application/vnd.api+json', - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, }, - method: 'GET' + method: 'GET', }); return await res.json(); @@ -96,14 +96,14 @@ const fetchProductPreviews = async function fetchProductPreviews(skus, config) { // Here we account for the edge case where the user has picked more than 25 // products, which is the max amount of pagination results. We need to fetch // and compile the complete selection result doing 1 request per 25 items. - const resultPromises = chunk(skus, PREVIEWS_PER_PAGE).map(async skusSubset => { + const resultPromises = chunk(skus, PREVIEWS_PER_PAGE).map(async (skusSubset) => { const URL = `${apiEndpoint}/api/skus?page[size]=${PREVIEWS_PER_PAGE}&filter[q][code_in]=${skusSubset}`; const res = await fetch(URL, { headers: { Accept: 'application/vnd.api+json', - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, }, - method: 'GET' + method: 'GET', }); return await res.json(); }); @@ -116,8 +116,8 @@ const fetchProductPreviews = async function fetchProductPreviews(skus, config) { const missingProducts = difference( skus, - foundProducts.map(product => product.sku) - ).map(sku => ({ sku, isMissing: true, image: '', name: '', id: '' })); + foundProducts.map((product) => product.sku) + ).map((sku) => ({ sku, isMissing: true, image: '', name: '', id: '' })); return [...foundProducts, ...missingProducts]; }; @@ -138,11 +138,11 @@ async function renderDialog(sdk) { count: PER_PAGE, limit: PER_PAGE, total: result.meta.record_count, - offset: pagination.offset + offset: pagination.offset, }, - products: result.data.map(dataTransformer(sdk.parameters.installation.apiEndpoint)) + products: result.data.map(dataTransformer(sdk.parameters.installation.apiEndpoint)), }; - } + }, }); sdk.window.startAutoResizer(); @@ -156,7 +156,7 @@ async function openDialog(sdk, currentValue, config) { shouldCloseOnOverlayClick: true, shouldCloseOnEscapePress: true, parameters: config, - width: 1400 + width: 1400, }); return Array.isArray(skus) ? skus : []; @@ -180,19 +180,19 @@ setup({ name: 'Client ID', description: 'The client ID', type: 'Symbol', - required: true + required: true, }, { id: 'apiEndpoint', name: 'API Endpoint', description: 'The Commerce Layer API endpoint', type: 'Symbol', - required: true - } + required: true, + }, ], fetchProductPreviews, renderDialog, openDialog, isDisabled, - validateParameters + validateParameters, }); diff --git a/apps/commercetools/src/AppConfig/FieldSelector/ToggleGroup.tsx b/apps/commercetools/src/AppConfig/FieldSelector/ToggleGroup.tsx index 79ca50046ce..da355146c13 100644 --- a/apps/commercetools/src/AppConfig/FieldSelector/ToggleGroup.tsx +++ b/apps/commercetools/src/AppConfig/FieldSelector/ToggleGroup.tsx @@ -1,8 +1,8 @@ -import React from "react"; -import { ToggleButton } from "@contentful/forma-36-react-components"; -import { Field, PickerMode } from "../../interfaces"; -import { css } from "emotion"; -import tokens from "@contentful/forma-36-tokens"; +import React from 'react'; +import { ToggleButton } from '@contentful/forma-36-react-components'; +import { Field, PickerMode } from '../../interfaces'; +import { css } from 'emotion'; +import tokens from '@contentful/forma-36-tokens'; const styles = { toggleGroup: (isPickerModeSetToProduct: boolean) => @@ -10,20 +10,20 @@ const styles = { marginTop: tokens.spacingXs, marginLeft: tokens.spacingL, - "> :first-of-type": css({ + '> :first-of-type': css({ borderTopRightRadius: 0, borderBottomRightRadius: 0, - position: "relative", - zIndex: isPickerModeSetToProduct ? 1 : 0 + position: 'relative', + zIndex: isPickerModeSetToProduct ? 1 : 0, }), - "> :last-of-type": css({ + '> :last-of-type': css({ borderTopLeftRadius: 0, borderBottomLeftRadius: 0, - marginLeft: "-1px", - position: "relative", - zIndex: isPickerModeSetToProduct ? 0 : 1 - }) - }) + marginLeft: '-1px', + position: 'relative', + zIndex: isPickerModeSetToProduct ? 0 : 1, + }), + }), }; interface Props { @@ -33,19 +33,13 @@ interface Props { } export function ToggleGroup({ activePickerMode, onChange, field }: Props) { - const isPickerModeSetToProduct = activePickerMode === "product"; + const isPickerModeSetToProduct = activePickerMode === 'product'; return (
- onChange("product")} - isActive={isPickerModeSetToProduct} - > + onChange('product')} isActive={isPickerModeSetToProduct}> {field.type === 'Symbol' ? 'Product' : 'Products'} - onChange("category")} - isActive={!isPickerModeSetToProduct} - > + onChange('category')} isActive={!isPickerModeSetToProduct}> {field.type === 'Symbol' ? 'Category' : 'Categories'}
diff --git a/apps/commercetools/src/AppConfig/FieldSelector/index.tsx b/apps/commercetools/src/AppConfig/FieldSelector/index.tsx index f5ab908e0d3..199ad1ef46b 100644 --- a/apps/commercetools/src/AppConfig/FieldSelector/index.tsx +++ b/apps/commercetools/src/AppConfig/FieldSelector/index.tsx @@ -13,11 +13,11 @@ import { PickerMode, ContentType } from '../../interfaces'; const styles = { fieldGroup: css({ display: 'flex', - flexDirection: 'column' + flexDirection: 'column', }), select: css({ - marginLeft: '10px' - }) + marginLeft: '10px', + }), }; interface Props { @@ -52,13 +52,13 @@ export default class FieldSelector extends React.Component { render() { const { compatibleFields, contentTypes, selectedFields } = this.props; - return contentTypes.map(ct => { + return contentTypes.map((ct) => { const fields = compatibleFields[ct.sys.id]; return (
{ct.name}
- {fields.map(field => { + {fields.map((field) => { const type = get(selectedFields, [ct.sys.id, field.id], null); const isChecked = !!type; @@ -76,7 +76,7 @@ export default class FieldSelector extends React.Component { {isChecked && ( this.onSelectedFieldTypeChange(ct.sys.id, field.id, type)} + onChange={(type) => this.onSelectedFieldTypeChange(ct.sys.id, field.id, type)} field={field} /> )} diff --git a/apps/commercetools/src/AppConfig/FieldTypeInstructions.tsx b/apps/commercetools/src/AppConfig/FieldTypeInstructions.tsx index 55ad63347be..77ae31bed91 100644 --- a/apps/commercetools/src/AppConfig/FieldTypeInstructions.tsx +++ b/apps/commercetools/src/AppConfig/FieldTypeInstructions.tsx @@ -13,8 +13,8 @@ export const FieldTypeInstructions = ({ contentTypesFound, space, environment }: {contentTypesFound ? ( This app can only be used with Short text or{' '} - Short text, list fields. Select which fields you’d like to enable for - this app. + Short text, list fields. Select which fields you’d like to enable for this + app. ) : ( <> @@ -23,8 +23,8 @@ export const FieldTypeInstructions = ({ contentTypesFound, space, environment }: Short text, list fields. - There are no content types with Short text or Short text, list{' '} - fields in this environment. You can add one in your{' '} + There are no content types with Short text or Short text, list fields in + this environment. You can add one in your{' '} + } + > content model {' '} and assign it to the app from this screen. diff --git a/apps/commercetools/src/AppConfig/index.spec.tsx b/apps/commercetools/src/AppConfig/index.spec.tsx index c39309a6ea6..483f37f7c59 100644 --- a/apps/commercetools/src/AppConfig/index.spec.tsx +++ b/apps/commercetools/src/AppConfig/index.spec.tsx @@ -11,13 +11,13 @@ const contentTypes = [ name: 'CT1', fields: [ { id: 'product_x', name: 'Product X', type: 'Symbol' }, - { id: 'y', name: 'Y', type: 'Object' } - ] + { id: 'y', name: 'Y', type: 'Object' }, + ], }, { sys: { id: 'ct2' }, name: 'CT2', - fields: [{ id: 'foo', name: 'FOO', type: 'Text' }] + fields: [{ id: 'foo', name: 'FOO', type: 'Text' }], }, { sys: { id: 'ct3' }, @@ -25,24 +25,24 @@ const contentTypes = [ fields: [ { id: 'bar', name: 'BAR', type: 'Object' }, { id: 'baz', name: 'BAZ', type: 'Object' }, - { id: 'product_a', name: 'Product A', type: 'Symbol' } - ] - } + { id: 'product_a', name: 'Product A', type: 'Symbol' }, + ], + }, ]; const makeSdkMock = () => ({ ids: { - app: 'some-app' + app: 'some-app', }, space: { getContentTypes: jest.fn().mockResolvedValue({ items: contentTypes }), - getEditorInterfaces: jest.fn().mockResolvedValue({ items: [] }) + getEditorInterfaces: jest.fn().mockResolvedValue({ items: [] }), }, app: { setReady: jest.fn(), getParameters: jest.fn().mockResolvedValue(null), - onConfigure: jest.fn().mockReturnValue(undefined) - } + onConfigure: jest.fn().mockReturnValue(undefined), + }, }); const renderComponent = (sdk: unknown) => { @@ -63,13 +63,13 @@ describe('AppConfig', () => { [/Client Secret/, ''], [/^API URL/, ''], [/Auth URL/, ''], - [/commercetools data locale/, ''] + [/commercetools data locale/, ''], ].forEach(([labelRe, expected]) => { const configInput = getByLabelText(labelRe) as HTMLInputElement; expect(configInput.value).toEqual(expected); }); - [/Product X$/].forEach(labelRe => { + [/Product X$/].forEach((labelRe) => { const fieldCheckbox = getByLabelText(labelRe) as HTMLInputElement; expect(fieldCheckbox.checked).toBe(false); }); @@ -83,7 +83,7 @@ describe('AppConfig', () => { clientSecret: 'some-secret', apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', - locale: 'en' + locale: 'en', }); sdk.space.getEditorInterfaces.mockResolvedValueOnce({ items: [ @@ -93,21 +93,21 @@ describe('AppConfig', () => { { fieldId: 'product_a', widgetNamespace: 'app', - widgetId: 'some-app' + widgetId: 'some-app', }, { fieldId: 'bar', widgetNamespace: 'app', - widgetId: 'some-diff-app' + widgetId: 'some-diff-app', }, { fieldId: 'product_d', widgetNamespace: 'app', - widgetId: 'some-app' - } - ] - } - ] + widgetId: 'some-app', + }, + ], + }, + ], }); const { getByLabelText } = renderComponent(sdk); @@ -119,7 +119,7 @@ describe('AppConfig', () => { [/Client Secret/, 'some-secret'], [/^API URL/, 'some-endpoint'], [/Auth URL/, 'some-auth-endpoint'], - [/commercetools data locale/, 'en'] + [/commercetools data locale/, 'en'], ].forEach(([labelRe, expected]) => { const configInput = getByLabelText(labelRe as RegExp) as HTMLInputElement; expect(configInput.value).toEqual(expected); @@ -141,7 +141,7 @@ describe('AppConfig', () => { [/Client Secret/, 'some-secret'], [/^API URL/, 'some-endpoint'], [/Auth URL/, 'some-auth-endpoint'], - [/commercetools data locale/, 'en'] + [/commercetools data locale/, 'en'], ].forEach(([labelRe, value]) => { const configInput = getByLabelText(labelRe as RegExp) as HTMLInputElement; fireEvent.change(configInput, { target: { value } }); @@ -158,14 +158,14 @@ describe('AppConfig', () => { apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', locale: 'en', - fieldsConfig: {} + fieldsConfig: {}, }, targetState: { EditorInterface: { ct1: {}, - ct3: {} - } - } + ct3: {}, + }, + }, }); }); }); diff --git a/apps/commercetools/src/AppConfig/index.tsx b/apps/commercetools/src/AppConfig/index.tsx index a5724ca212b..cace2086bec 100644 --- a/apps/commercetools/src/AppConfig/index.tsx +++ b/apps/commercetools/src/AppConfig/index.tsx @@ -7,7 +7,7 @@ import { Paragraph, Typography, TextField, - Form + Form, } from '@contentful/forma-36-react-components'; import FieldSelector from './FieldSelector'; @@ -18,13 +18,13 @@ import { editorInterfacesToSelectedFields, selectedFieldsToTargetState, CompatibleFields, - FieldsConfig + FieldsConfig, } from './fields'; import { validateParameters } from './parameters'; import { styles } from './styles'; -import { Hash, EditorInterface, ContentType } from '../interfaces'; +import { Hash, EditorInterface, ContentType } from '../interfaces'; import logo from '../logo.svg'; import { FieldTypeInstructions } from './FieldTypeInstructions'; @@ -44,7 +44,7 @@ export default class AppConfig extends React.Component { contentTypes: [], compatibleFields: {}, selectedFields: {}, - parameters: toInputParameters(parameterDefinitions, null) + parameters: toInputParameters(parameterDefinitions, null), }; async componentDidMount() { @@ -55,7 +55,7 @@ export default class AppConfig extends React.Component { const [contentTypesResponse, eisResponse, parameters] = await Promise.all([ space.getContentTypes(), space.getEditorInterfaces(), - app.getParameters() + app.getParameters(), ]); const fieldsConfig = get(parameters, ['fieldsConfig'], {}); @@ -64,7 +64,7 @@ export default class AppConfig extends React.Component { const editorInterfaces = (eisResponse as Hash).items as EditorInterface[]; const compatibleFields = getCompatibleFields(contentTypes); - const filteredContentTypes = contentTypes.filter(ct => { + const filteredContentTypes = contentTypes.filter((ct) => { const fields = compatibleFields[ct.sys.id]; return fields && fields.length > 0; }); @@ -75,7 +75,7 @@ export default class AppConfig extends React.Component { contentTypes: filteredContentTypes, compatibleFields, selectedFields: editorInterfacesToSelectedFields(editorInterfaces, fieldsConfig, ids.app), - parameters: toInputParameters(parameterDefinitions, parameters) + parameters: toInputParameters(parameterDefinitions, parameters), }, () => app.setReady() ); @@ -85,7 +85,7 @@ export default class AppConfig extends React.Component { const { contentTypes, selectedFields } = this.state; const parameters = { ...toAppParameters(parameterDefinitions, this.state.parameters), - fieldsConfig: selectedFields + fieldsConfig: selectedFields, }; const error = validateParameters(parameters); @@ -97,15 +97,15 @@ export default class AppConfig extends React.Component { return { parameters, - targetState: selectedFieldsToTargetState(contentTypes, selectedFields) + targetState: selectedFieldsToTargetState(contentTypes, selectedFields), }; }; onParameterChange = (key: string, e: React.ChangeEvent) => { const { value } = e.currentTarget; - this.setState(state => ({ - parameters: { ...state.parameters, [key]: value } + this.setState((state) => ({ + parameters: { ...state.parameters, [key]: value }, })); }; @@ -117,7 +117,7 @@ export default class AppConfig extends React.Component { const { contentTypes, compatibleFields, selectedFields, parameters } = this.state; const { sdk } = this.props; const { - ids: { space, environment } + ids: { space, environment }, } = sdk; return ( @@ -134,7 +134,7 @@ export default class AppConfig extends React.Component { Configuration - {parameterDefinitions.map(def => { + {parameterDefinitions.map((def) => { const key = `config-input-${def.id}`; return ( { labelText={def.name} textInputProps={{ width: 'large', - maxLength: 255 + maxLength: 255, }} helpText={def.description} value={parameters[def.id]} diff --git a/apps/commercetools/src/Editor/CategoryPreviews/CategoryPreviews.tsx b/apps/commercetools/src/Editor/CategoryPreviews/CategoryPreviews.tsx index 84ca7e2aba6..d5ed22d1f74 100644 --- a/apps/commercetools/src/Editor/CategoryPreviews/CategoryPreviews.tsx +++ b/apps/commercetools/src/Editor/CategoryPreviews/CategoryPreviews.tsx @@ -21,7 +21,7 @@ interface State { export class CategoryPreviews extends React.Component { state = { - categoryPreviews: [] + categoryPreviews: [], }; componentDidMount() { diff --git a/apps/commercetools/src/Editor/CategoryPreviews/SortableList.spec.tsx b/apps/commercetools/src/Editor/CategoryPreviews/SortableList.spec.tsx index ecfa435850c..47b233d1ed3 100644 --- a/apps/commercetools/src/Editor/CategoryPreviews/SortableList.spec.tsx +++ b/apps/commercetools/src/Editor/CategoryPreviews/SortableList.spec.tsx @@ -6,7 +6,7 @@ import categoryPreviews from '../../__mocks__/categoryPreviews'; const defaultProps: Props = { disabled: false, categoryPreviews, - deleteFn: jest.fn() + deleteFn: jest.fn(), }; const renderComponent = (props: Props) => { @@ -16,7 +16,7 @@ const renderComponent = (props: Props) => { jest.mock('react-sortable-hoc', () => ({ SortableContainer: (x: any) => x, SortableElement: (x: any) => x, - SortableHandle: (x: any) => x + SortableHandle: (x: any) => x, })); describe('SortableList', () => { diff --git a/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.spec.tsx b/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.spec.tsx index 3b03d284de0..71949339ca1 100644 --- a/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.spec.tsx +++ b/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.spec.tsx @@ -4,14 +4,14 @@ import { Props, SortableListItem } from './SortableListItem'; import categoryPreviews from '../../__mocks__/categoryPreviews'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); const defaultProps: Props = { category: categoryPreviews[0], disabled: false, onDelete: jest.fn(), - isSortable: false + isSortable: false, }; const renderComponent = (props: Props) => { @@ -21,7 +21,7 @@ const renderComponent = (props: Props) => { jest.mock('react-sortable-hoc', () => ({ SortableContainer: (x: any) => x, SortableElement: (x: any) => x, - SortableHandle: (x: any) => x + SortableHandle: (x: any) => x, })); describe('SortableListItem', () => { @@ -35,7 +35,7 @@ describe('SortableListItem', () => { it('should render successfully the error variation for missing slug', () => { const component = renderComponent({ ...defaultProps, - category: { ...categoryPreviews[0], name: '' } + category: { ...categoryPreviews[0], name: '' }, }); expect(component.container).toMatchSnapshot(); }); diff --git a/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.tsx b/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.tsx index 44db1c0eb3d..880bb1fc236 100644 --- a/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.tsx +++ b/apps/commercetools/src/Editor/CategoryPreviews/SortableListItem.tsx @@ -9,7 +9,7 @@ import { IconButton, Subheading, Tag, - Typography + Typography, } from '@contentful/forma-36-react-components'; import tokens from '@contentful/forma-36-tokens'; import { Category } from '../../interfaces'; @@ -27,11 +27,11 @@ const styles = { padding: 0, position: 'relative', ':not(:first-of-type)': css({ - marginTop: tokens.spacingXs - }) + marginTop: tokens.spacingXs, + }), }), dragHandle: css({ - height: 'auto' + height: 'auto', }), actions: css({ position: 'absolute', @@ -41,38 +41,38 @@ const styles = { display: 'inline-block', marginRight: tokens.spacingXs, svg: css({ - transition: `fill ${tokens.transitionDurationDefault} ${tokens.transitionEasingDefault}` + transition: `fill ${tokens.transitionDurationDefault} ${tokens.transitionEasingDefault}`, }), '&:hover': { svg: css({ - fill: tokens.colorBlack - }) - } - }) + fill: tokens.colorBlack, + }), + }, + }), }), description: css({ padding: tokens.spacingM, flex: '1 0 auto', display: 'flex', flexDirection: 'column', - justifyContent: 'center' + justifyContent: 'center', }), heading: (category: Category) => css({ fontSize: tokens.fontSizeL, marginBottom: category.isMissing || !category.name ? 0 : tokens.spacing2Xs, - ...(category.name && { textTransform: 'capitalize' }) + ...(category.name && { textTransform: 'capitalize' }), }), subheading: css({ color: tokens.gray500, fontSize: tokens.fontSizeS, - marginBottom: 0 + marginBottom: 0, }), slug: css({ color: tokens.gray500, fontSize: tokens.fontSizeS, - marginBottom: 0 - }) + marginBottom: 0, + }), }; const CardDragHandle = SortableHandle(() => ( @@ -117,7 +117,7 @@ export const SortableListItem = SortableElement( iconProps={{ icon: 'Close' }} {...{ buttonType: 'muted', - onClick: onDelete + onClick: onDelete, }} />
diff --git a/apps/commercetools/src/Editor/Field.tsx b/apps/commercetools/src/Editor/Field.tsx index 077c782d140..1b7f00d1a7b 100644 --- a/apps/commercetools/src/Editor/Field.tsx +++ b/apps/commercetools/src/Editor/Field.tsx @@ -1,14 +1,14 @@ -import * as React from "react"; -import { Button } from "@contentful/forma-36-react-components"; -import tokens from "@contentful/forma-36-tokens"; -import get from "lodash/get"; -import { css } from "emotion"; -import { FieldExtensionSDK } from "@contentful/app-sdk"; -import { ProductPreviews } from "./ProductPreviews/ProductPreviews"; -import { CategoryPreviews } from "./CategoryPreviews/CategoryPreviews"; -import { fetchProductPreviews } from "../api/fetchProductPreviews"; -import { fetchCategoryPreviews } from "../api/fetchCategoryPreviews"; -import logo from "../logo.svg"; +import * as React from 'react'; +import { Button } from '@contentful/forma-36-react-components'; +import tokens from '@contentful/forma-36-tokens'; +import get from 'lodash/get'; +import { css } from 'emotion'; +import { FieldExtensionSDK } from '@contentful/app-sdk'; +import { ProductPreviews } from './ProductPreviews/ProductPreviews'; +import { CategoryPreviews } from './CategoryPreviews/CategoryPreviews'; +import { fetchProductPreviews } from '../api/fetchProductPreviews'; +import { fetchCategoryPreviews } from '../api/fetchCategoryPreviews'; +import logo from '../logo.svg'; interface Props { sdk: FieldExtensionSDK; @@ -21,17 +21,17 @@ interface State { const styles = { sortable: css({ - marginBottom: tokens.spacingM + marginBottom: tokens.spacingM, }), container: css({ - display: "flex" + display: 'flex', }), logo: css({ - display: "block", - width: "30px", - height: "30px", - marginRight: tokens.spacingM - }) + display: 'block', + width: '30px', + height: '30px', + marginRight: tokens.spacingM, + }), }; function fieldValueToState(value?: string | string[]): string[] { @@ -41,23 +41,23 @@ function fieldValueToState(value?: string | string[]): string[] { return Array.isArray(value) ? value : [value]; } -function makeCTAText(fieldType: string, pickerMode: "category" | "product") { - const isArray = fieldType === "Array"; +function makeCTAText(fieldType: string, pickerMode: 'category' | 'product') { + const isArray = fieldType === 'Array'; const beingSelected = - pickerMode === "category" + pickerMode === 'category' ? isArray - ? "categories" - : "a category" + ? 'categories' + : 'a category' : isArray - ? "products" - : "a product"; + ? 'products' + : 'a product'; return `Select ${beingSelected}`; } export default class Field extends React.Component { state = { value: fieldValueToState(this.props.sdk.field.getValue()), - editingDisabled: true + editingDisabled: true, }; componentDidMount() { @@ -81,8 +81,8 @@ export default class Field extends React.Component { return get( sdk, - ["parameters", "installation", "fieldsConfig", contentTypeId, fieldId], - "product" + ['parameters', 'installation', 'fieldsConfig', contentTypeId, fieldId], + 'product' ); }; @@ -90,7 +90,7 @@ export default class Field extends React.Component { this.setState({ value: skus }); if (skus.length > 0) { - const value = this.props.sdk.field.type === "Array" ? skus : skus[0]; + const value = this.props.sdk.field.type === 'Array' ? skus : skus[0]; this.props.sdk.field.setValue(value); } else { this.props.sdk.field.removeValue(); @@ -102,7 +102,7 @@ export default class Field extends React.Component { const skus = await sdk.dialogs.openCurrentApp({ allowHeightOverflow: true, - position: "center", + position: 'center', title: makeCTAText(sdk.field.type, this.getPickerMode()), shouldCloseOnOverlayClick: true, shouldCloseOnEscapePress: true, @@ -111,9 +111,9 @@ export default class Field extends React.Component { fieldValue: fieldValueToState(sdk.field.getValue()), fieldType: sdk.field.type, fieldId: sdk.field.id, - pickerMode: this.getPickerMode() + pickerMode: this.getPickerMode(), }, - width: 1400 + width: 1400, }); const result = Array.isArray(skus) ? skus : []; @@ -125,10 +125,10 @@ export default class Field extends React.Component { render = () => { const { value: data, editingDisabled } = this.state; - const isPickerTypeSetToCategory = this.getPickerMode() === "category"; + const isPickerTypeSetToCategory = this.getPickerMode() === 'category'; const hasItems = data.length > 0; const config = this.props.sdk.parameters.installation; - const fieldType = get(this.props, ["sdk", "field", "type"], ""); + const fieldType = get(this.props, ['sdk', 'field', 'type'], ''); return ( <> @@ -140,9 +140,7 @@ export default class Field extends React.Component { disabled={editingDisabled} categories={data} onChange={this.updateStateValue} - fetchCategoryPreviews={categories => - fetchCategoryPreviews(categories, config) - } + fetchCategoryPreviews={(categories) => fetchCategoryPreviews(categories, config)} /> ) : ( { disabled={editingDisabled} skus={data} onChange={this.updateStateValue} - fetchProductPreviews={(skus: string[]) => - fetchProductPreviews(skus, config) - } + fetchProductPreviews={(skus: string[]) => fetchProductPreviews(skus, config)} /> )}
diff --git a/apps/commercetools/src/Editor/ProductPreviews/ProductPreviews.tsx b/apps/commercetools/src/Editor/ProductPreviews/ProductPreviews.tsx index 549b29a6a6d..d6f1e796a6c 100644 --- a/apps/commercetools/src/Editor/ProductPreviews/ProductPreviews.tsx +++ b/apps/commercetools/src/Editor/ProductPreviews/ProductPreviews.tsx @@ -21,7 +21,7 @@ interface State { export class ProductPreviews extends React.Component { state = { - productPreviews: [] + productPreviews: [], }; componentDidMount() { diff --git a/apps/commercetools/src/Editor/ProductPreviews/SortableList.spec.tsx b/apps/commercetools/src/Editor/ProductPreviews/SortableList.spec.tsx index 6a51e4b94d2..b46409785ee 100644 --- a/apps/commercetools/src/Editor/ProductPreviews/SortableList.spec.tsx +++ b/apps/commercetools/src/Editor/ProductPreviews/SortableList.spec.tsx @@ -6,7 +6,7 @@ import productPreviews from '../../__mocks__/productPreviews'; const defaultProps: Props = { disabled: false, productPreviews, - deleteFn: jest.fn() + deleteFn: jest.fn(), }; const renderComponent = (props: Props) => { @@ -16,7 +16,7 @@ const renderComponent = (props: Props) => { jest.mock('react-sortable-hoc', () => ({ SortableContainer: (x: any) => x, SortableElement: (x: any) => x, - SortableHandle: (x: any) => x + SortableHandle: (x: any) => x, })); describe('SortableList', () => { diff --git a/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.spec.tsx b/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.spec.tsx index cc8e1688c15..7713c0b84fc 100644 --- a/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.spec.tsx +++ b/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.spec.tsx @@ -4,14 +4,14 @@ import { Props, SortableListItem } from './SortableListItem'; import productPreviews from '../../__mocks__/productPreviews'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); const defaultProps: Props = { product: productPreviews[0], disabled: false, onDelete: jest.fn(), - isSortable: false + isSortable: false, }; const renderComponent = (props: Props) => { @@ -21,7 +21,7 @@ const renderComponent = (props: Props) => { jest.mock('react-sortable-hoc', () => ({ SortableContainer: (x: any) => x, SortableElement: (x: any) => x, - SortableHandle: (x: any) => x + SortableHandle: (x: any) => x, })); describe('SortableListItem', () => { @@ -53,7 +53,7 @@ describe('SortableListItem', () => { it('should render successfully the error variation for missing product', () => { const component = renderComponent({ ...defaultProps, - product: { ...productPreviews[0], name: '' } + product: { ...productPreviews[0], name: '' }, }); fireEvent(component.getByTestId('image'), new Event('error')); expect(component.container).toMatchSnapshot(); diff --git a/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.tsx b/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.tsx index 700799bd239..597aa0e3d19 100644 --- a/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.tsx +++ b/apps/commercetools/src/Editor/ProductPreviews/SortableListItem.tsx @@ -11,7 +11,7 @@ import { SkeletonImage, Subheading, Tag, - Typography + Typography, } from '@contentful/forma-36-react-components'; import tokens from '@contentful/forma-36-tokens'; import { Product } from '../../interfaces'; @@ -31,8 +31,8 @@ const styles = { padding: 0, position: 'relative', ':not(:first-of-type)': css({ - marginTop: tokens.spacingXs - }) + marginTop: tokens.spacingXs, + }), }), imageWrapper: (imageHasLoaded: boolean) => css({ @@ -49,11 +49,11 @@ const styles = { position: 'absolute', left: '50%', top: '50%', - transform: 'translate(-50%, -50%)' - }) + transform: 'translate(-50%, -50%)', + }), }), dragHandle: css({ - height: 'auto' + height: 'auto', }), actions: css({ position: 'absolute', @@ -63,36 +63,36 @@ const styles = { display: 'inline-block', marginRight: tokens.spacingXs, svg: css({ - transition: `fill ${tokens.transitionDurationDefault} ${tokens.transitionEasingDefault}` + transition: `fill ${tokens.transitionDurationDefault} ${tokens.transitionEasingDefault}`, }), '&:hover': { svg: css({ - fill: tokens.colorBlack - }) - } - }) + fill: tokens.colorBlack, + }), + }, + }), }), description: css({ flex: '1 0 auto', display: 'flex', flexDirection: 'column', - justifyContent: 'center' + justifyContent: 'center', }), heading: (product: Product) => css({ fontSize: tokens.fontSizeL, marginBottom: product.isMissing || !product.name ? 0 : tokens.spacing2Xs, - ...(product.name && { textTransform: 'capitalize' }) + ...(product.name && { textTransform: 'capitalize' }), }), subheading: css({ color: tokens.gray500, fontSize: tokens.fontSizeS, - marginBottom: 0 + marginBottom: 0, }), skeletonImage: css({ width: `${IMAGE_SIZE}px`, height: `${IMAGE_SIZE}px`, - padding: tokens.spacingM + padding: tokens.spacingM, }), errorImage: css({ backgroundColor: tokens.gray100, @@ -108,9 +108,9 @@ const styles = { position: 'absolute', top: '50%', left: '50%', - transform: 'translate(-50%, -50%)' - }) - }) + transform: 'translate(-50%, -50%)', + }), + }), }; const CardDragHandle = SortableHandle(() => ( @@ -172,7 +172,7 @@ export const SortableListItem = SortableElement( iconProps={{ icon: 'Close' }} {...{ buttonType: 'muted', - onClick: onDelete + onClick: onDelete, }} /> diff --git a/apps/commercetools/src/index.tsx b/apps/commercetools/src/index.tsx index eb7b4f14dab..d0d190ea39a 100644 --- a/apps/commercetools/src/index.tsx +++ b/apps/commercetools/src/index.tsx @@ -1,27 +1,27 @@ -import "./vendor/ct-picker.min.js"; +import './vendor/ct-picker.min.js'; -import * as React from "react"; -import { render } from "react-dom"; -import { renderDialog } from "./renderDialog"; +import * as React from 'react'; +import { render } from 'react-dom'; +import { renderDialog } from './renderDialog'; import { init, locations, FieldExtensionSDK, DialogExtensionSDK, - AppExtensionSDK -} from "@contentful/app-sdk"; + AppExtensionSDK, +} from '@contentful/app-sdk'; -import "@contentful/forma-36-react-components/dist/styles.css"; -import "@contentful/forma-36-fcss/dist/styles.css"; +import '@contentful/forma-36-react-components/dist/styles.css'; +import '@contentful/forma-36-fcss/dist/styles.css'; -import Field from "./Editor/Field"; -import AppConfig from "./AppConfig"; +import Field from './Editor/Field'; +import AppConfig from './AppConfig'; -import "./index.css"; +import './index.css'; -init(sdk => { - const root = document.getElementById("root"); +init((sdk) => { + const root = document.getElementById('root'); if (sdk.location.is(locations.LOCATION_DIALOG)) { renderDialog(sdk as DialogExtensionSDK); diff --git a/apps/dropbox/src/index.js b/apps/dropbox/src/index.js index 69473e57c43..1b23a01589d 100644 --- a/apps/dropbox/src/index.js +++ b/apps/dropbox/src/index.js @@ -11,7 +11,7 @@ const FIELDS_TO_PERSIST = [ 'bytes', 'link', 'id', - 'icon' + 'icon', ]; function makeThumbnail(file) { @@ -22,14 +22,14 @@ function makeThumbnail(file) { } async function openDialog() { - return new Promise(resolve => { + return new Promise((resolve) => { window.Dropbox.choose({ - success: files => - resolve(Array.isArray(files) ? files.map(file => pick(file, FIELDS_TO_PERSIST)) : []), + success: (files) => + resolve(Array.isArray(files) ? files.map((file) => pick(file, FIELDS_TO_PERSIST)) : []), linkType: 'preview', multiselect: true, folderselect: false, - extensions: ['.jpg', '.jpeg', '.gif', '.svg', '.png'] + extensions: ['.jpg', '.jpeg', '.gif', '.svg', '.png'], }); }); } @@ -46,5 +46,5 @@ setup({ renderDialog: () => {}, openDialog, isDisabled: () => false, - validateParameters: () => {} + validateParameters: () => {}, }); diff --git a/apps/frontify/src/index.js b/apps/frontify/src/index.js index 3da0926d0f2..c498d6e272c 100755 --- a/apps/frontify/src/index.js +++ b/apps/frontify/src/index.js @@ -13,26 +13,26 @@ setup({ 'The Frontify app enables editors to access all digital brand assets in Frontify directly from Contentful.', parameterDefinitions: [ { - "id": "domain", - "type": "Symbol", - "name": "Frontify Domain", - "description": "Your Frontify domain, e.g. https://weare.frontify.com", - "default": "https://weare.frontify.com", - "required": true + id: 'domain', + type: 'Symbol', + name: 'Frontify Domain', + description: 'Your Frontify domain, e.g. https://weare.frontify.com', + default: 'https://weare.frontify.com', + required: true, }, { - "id": "defaultAccessToken", - "type": "Symbol", - "name": "Access Token", - "description": "Your Frontify access token", - "required": false - } + id: 'defaultAccessToken', + type: 'Symbol', + name: 'Access Token', + description: 'Your Frontify access token', + required: false, + }, ], makeThumbnail, renderDialog, openDialog, isDisabled, - validateParameters + validateParameters, }); function makeThumbnail(resource) { @@ -60,7 +60,7 @@ function renderDialog(sdk) { const chooser = iframe.contentWindow; // cross document messaging - window.addEventListener('message', e => { + window.addEventListener('message', (e) => { if (e.source !== chooser) { return; } @@ -96,14 +96,14 @@ async function openDialog(sdk) { shouldCloseOnOverlayClick: true, shouldCloseOnEscapePress: true, parameters: { domain: params.installation.domain, accessToken }, - width: 1400 + width: 1400, }); if (!Array.isArray(result)) { return []; } - return result.map(item => ({ + return result.map((item) => ({ id: item.id, title: item.title, name: item.name, @@ -113,7 +113,7 @@ async function openDialog(sdk) { created: item.created, generic_url: item.generic_url, preview_url: item.preview_url, - src: item.preview_url + src: item.preview_url, })); } @@ -123,7 +123,7 @@ function isDisabled() { function validateParameters({ domain }) { const hasValidProtocol = domain.startsWith('https://'); - const isHTMLSafe = ['"', '<', '>'].every(unsafe => !domain.includes(unsafe)); + const isHTMLSafe = ['"', '<', '>'].every((unsafe) => !domain.includes(unsafe)); if (hasValidProtocol && isHTMLSafe) { return null; diff --git a/apps/gatsby/src/AppConfig/AppConfig.spec.js b/apps/gatsby/src/AppConfig/AppConfig.spec.js index 75c5d28b390..f375baa5d5b 100644 --- a/apps/gatsby/src/AppConfig/AppConfig.spec.js +++ b/apps/gatsby/src/AppConfig/AppConfig.spec.js @@ -1,21 +1,21 @@ -import { enabledContentTypesToTargetState } from "./AppConfig"; +import { enabledContentTypesToTargetState } from './AppConfig'; -describe("enabledContentTypesToTargetState", () => { +describe('enabledContentTypesToTargetState', () => { const contentTypes = [ { sys: { - id: "page", + id: 'page', }, }, { sys: { - id: "seo", + id: 'seo', }, }, ]; - const enabledContentTypes = ["page"]; - describe("when the content type already has the app assigned", () => { - it("does not overwrite the existing position", () => { + const enabledContentTypes = ['page']; + describe('when the content type already has the app assigned', () => { + it('does not overwrite the existing position', () => { const currentState = { EditorInterface: { page: { @@ -33,8 +33,8 @@ describe("enabledContentTypesToTargetState", () => { expect(result.EditorInterface.page.sidebar.position).toEqual(6); }); }); - describe("when the content type does not already have the app assigned", () => { - it("sets Gatsby to position 3", () => { + describe('when the content type does not already have the app assigned', () => { + it('sets Gatsby to position 3', () => { const currentState = { EditorInterface: {} }; const result = enabledContentTypesToTargetState( currentState, diff --git a/apps/gatsby/src/AppConfig/ContentTypesPanel.spec.js b/apps/gatsby/src/AppConfig/ContentTypesPanel.spec.js index 5beb1f1e38d..b3ceeed86d2 100644 --- a/apps/gatsby/src/AppConfig/ContentTypesPanel.spec.js +++ b/apps/gatsby/src/AppConfig/ContentTypesPanel.spec.js @@ -1,11 +1,11 @@ -import React from "react"; -import { ContentTypesSelection } from "./ContentTypesPanel"; -import { cleanup, render } from "@testing-library/react"; +import React from 'react'; +import { ContentTypesSelection } from './ContentTypesPanel'; +import { cleanup, render } from '@testing-library/react'; -describe("ContentTypesList", function() { +describe('ContentTypesList', function () { let contentTypeId = 1; - const environment = "master"; - const space = "12512asfasf"; + const environment = 'master'; + const space = '12512asfasf'; const contentType = (name) => ({ sys: { id: String(++contentTypeId) }, @@ -14,26 +14,22 @@ describe("ContentTypesList", function() { afterEach(cleanup); - it("should show Skeleton on nil values", () => { + it('should show Skeleton on nil values', () => { const { container } = render(); expect(container).toMatchSnapshot(); }); - it("should show a Note if there are no content types", () => { + it('should show a Note if there are no content types', () => { const { container } = render( - + ); expect(container).toMatchSnapshot(); }); - it("should show options per content type", () => { - const contentTypes = [contentType("posts"), contentType("authors")]; + it('should show options per content type', () => { + const contentTypes = [contentType('posts'), contentType('authors')]; const { queryByText } = render( ); - expect(queryByText("posts")).toBeDefined(); - expect(queryByText("authors")).toBeDefined(); + expect(queryByText('posts')).toBeDefined(); + expect(queryByText('authors')).toBeDefined(); }); - it("should show a select with the same value as the enabled content types", () => { - const posts = contentType("posts"); - const authors = contentType("authors"); + it('should show a select with the same value as the enabled content types', () => { + const posts = contentType('posts'); + const authors = contentType('authors'); const contentTypes = [posts, authors]; const enabledTypes = [posts.sys.id]; const { queryByRole, queryAllByRole } = render( @@ -61,10 +57,10 @@ describe("ContentTypesList", function() { /> ); - const select = queryByRole("listbox"); - const options = queryAllByRole("option") - const postsOption = options.find(option => option.label === "posts") - const authorsOption = options.find(option => option.label === "authors") + const select = queryByRole('listbox'); + const options = queryAllByRole('option'); + const postsOption = options.find((option) => option.label === 'posts'); + const authorsOption = options.find((option) => option.label === 'authors'); expect(select.value === postsOption.value).toBe(true); expect(select.value === authorsOption.value).toBe(false); diff --git a/apps/gatsby/src/index.js b/apps/gatsby/src/index.js index 83a9173fb0a..f8e07b79e75 100644 --- a/apps/gatsby/src/index.js +++ b/apps/gatsby/src/index.js @@ -10,7 +10,7 @@ import '@contentful/forma-36-react-components/dist/styles.css'; import '@contentful/forma-36-fcss/dist/styles.css'; import './index.css'; -init(sdk => { +init((sdk) => { const root = document.getElementById('root'); if (sdk.location.is(locations.LOCATION_ENTRY_SIDEBAR)) { diff --git a/apps/gatsby/src/utils.js b/apps/gatsby/src/utils.js index aa3c0ec14e4..ad491df0fd9 100644 --- a/apps/gatsby/src/utils.js +++ b/apps/gatsby/src/utils.js @@ -1,5 +1,5 @@ export function isValidUrl(url) { - // eslint-disable-next-line no-useless-escape + // eslint-disable no-useless-escape const regex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}[\.,:][a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g; return regex.test(url); diff --git a/apps/google-analytics/.prettierrc.js b/apps/google-analytics/.prettierrc.js index 5b03dfcea58..13d14c76333 100644 --- a/apps/google-analytics/.prettierrc.js +++ b/apps/google-analytics/.prettierrc.js @@ -3,5 +3,5 @@ module.exports = { tabWidth: 2, useTabs: false, singleQuote: true, - jsxBracketSameLine: true + jsxBracketSameLine: true, }; diff --git a/apps/google-analytics/src/Analytics.tsx b/apps/google-analytics/src/Analytics.tsx index fbf8bb6ceaa..0c46e8a146d 100644 --- a/apps/google-analytics/src/Analytics.tsx +++ b/apps/google-analytics/src/Analytics.tsx @@ -9,7 +9,7 @@ import { RangeOption, AnalyticsProps, AnalyticsState, ChartData, GapiError } fro const RANGE_OPTIONS: RangeOption[] = [ { label: 'Last 24 hours', startDaysAgo: 1, endDaysAgo: 0 }, { label: 'Last 7 days', startDaysAgo: 7, endDaysAgo: 0 }, - { label: 'Last 28 days', startDaysAgo: 28, endDaysAgo: 0 } + { label: 'Last 28 days', startDaysAgo: 28, endDaysAgo: 0 }, ]; const INITIAL_RANGE_INDEX = 1; @@ -21,8 +21,8 @@ function getRangeDates(rangeOptionIndex: number) { return { startEnd: { start: new Date(today - DAY_IN_MS * range.startDaysAgo), - end: new Date(today - DAY_IN_MS * range.endDaysAgo) - } + end: new Date(today - DAY_IN_MS * range.endDaysAgo), + }, }; } @@ -33,7 +33,7 @@ export default class Analytics extends React.Component -
-
- {formattedPageViews} - pageviews + return ( + <> +
+
+ {formattedPageViews} + pageviews +
+
- -
- { - this.updateTotalPageViews(d); - this.setState({ loading: false }); - }} - onQuery={() => this.setState({ loading: true })} - onError={error => this.handleError(error)} - pagePath={pagePath} - start={start} - end={end} - dimensions={dimensions} - sdk={sdk} - gapi={gapi} - // remove 'ga:' prefix from view id - viewId={viewId.replace(/^ga:/, '')} - /> - ; + { + this.updateTotalPageViews(d); + this.setState({ loading: false }); + }} + onQuery={() => this.setState({ loading: true })} + onError={(error) => this.handleError(error)} + pagePath={pagePath} + start={start} + end={end} + dimensions={dimensions} + sdk={sdk} + gapi={gapi} + // remove 'ga:' prefix from view id + viewId={viewId.replace(/^ga:/, '')} + /> + + ); } } diff --git a/apps/google-analytics/src/AppConfig.tsx b/apps/google-analytics/src/AppConfig.tsx index ea4a08866c8..d3964fa5306 100644 --- a/apps/google-analytics/src/AppConfig.tsx +++ b/apps/google-analytics/src/AppConfig.tsx @@ -9,16 +9,12 @@ import { Button, Select, FormLabel, - TextInput + TextInput, } from '@contentful/forma-36-react-components'; import styles from './styles'; import { AppConfigParams, AppConfigState, AllContentTypes, ContentTypes } from './typings'; import { getAndUpdateSavedParams } from './utils'; -import { - ContentType, - EditorInterface, - CollectionResponse -} from '@contentful/app-sdk'; +import { ContentType, EditorInterface, CollectionResponse } from '@contentful/app-sdk'; import { ReactComponent as GoogleLogo } from './ga-logo.svg'; export default class AppConfig extends React.Component { @@ -26,32 +22,30 @@ export default class AppConfig extends React.Component - >, - getAndUpdateSavedParams(sdk) + sdk.space.getContentTypes() as Promise>, + getAndUpdateSavedParams(sdk), ]); const allContentTypes = sortBy(spaceContentTypes, 'name').reduce( (acc: AllContentTypes, contentType) => { const fields = sortBy( // use only short text fields of content type - contentType.fields.filter(f => f.type === 'Symbol'), + contentType.fields.filter((f) => f.type === 'Symbol'), // sort by field name 'name' - ) + ); if (fields.length) { acc[contentType.sys.id] = { ...contentType, - fields + fields, }; } @@ -66,10 +60,10 @@ export default class AppConfig extends React.Component f.id === slugField) + !allContentTypes[type].fields.some((f) => f.id === slugField) ) { // remove the content type from the list - delete contentTypes[type] + delete contentTypes[type]; } } @@ -84,7 +78,7 @@ export default class AppConfig extends React.Component sdk.app.setReady() ); @@ -110,10 +104,10 @@ export default class AppConfig extends React.Component key && !contentTypes[key].slugField)) { + if (ctKeys.some((key) => key && !contentTypes[key].slugField)) { notifier.error('Please complete or remove the incomplete content type rows!'); return false; } @@ -132,30 +126,34 @@ export default class AppConfig extends React.Component { + this.setState((prevState) => { const contentTypes: ContentTypes = {}; // remove contentType[prevKey] field and replace with the new contentType @@ -164,7 +162,7 @@ export default class AppConfig extends React.Component { + this.setState((prevState) => { const prevContentTypes = prevState.contentTypes; const curContentTypeProps = prevContentTypes[key]; @@ -187,24 +185,24 @@ export default class AppConfig extends React.Component ({ + this.setState((prevState) => ({ contentTypes: { ...prevState.contentTypes, - '': { slugField: '', urlPrefix: '' } - } + '': { slugField: '', urlPrefix: '' }, + }, })); } removeContentType(key: string) { - this.setState(prevState => { + this.setState((prevState) => { const contentTypes = { ...prevState.contentTypes }; delete contentTypes[key]; @@ -215,8 +213,8 @@ export default class AppConfig extends React.Component 0 + const contentTypeEntries = Object.entries(contentTypes); + const hasSelectedContentTypes = contentTypeEntries.length > 0; return ( <> @@ -232,7 +230,8 @@ export default class AppConfig extends React.Component + rel="noopener noreferrer" + > documentation . @@ -258,7 +257,7 @@ export default class AppConfig extends React.Component @@ -288,25 +287,29 @@ export default class AppConfig extends React.Component
- {hasSelectedContentTypes && <> + {hasSelectedContentTypes && ( + <> Content type Slug field URL prefix - } + + )}
Remover
{contentTypeEntries.map(([key, { slugField, urlPrefix }], index) => (
+ className={[styles.contentTypeGrid, styles.contentTypeGridInputs].join(' ')} + > - ); + return ; }; } init((sdk) => { if (sdk.location.is(locations.LOCATION_APP_CONFIG)) { - render( - , - document.getElementById('root') - ); + render(, document.getElementById('root')); } else { - render( - , - document.getElementById('root') - ); + render(, document.getElementById('root')); } }); diff --git a/apps/mux/src/player.test.tsx b/apps/mux/src/player.test.tsx index 3fa7dcce5e5..be5cd3bbb17 100644 --- a/apps/mux/src/player.test.tsx +++ b/apps/mux/src/player.test.tsx @@ -9,7 +9,12 @@ import Player from './player'; configure({ adapter: new Adapter() }); test('preview player has a Mux poster image', () => { - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper.find('video').props().poster).toContain( 'https://image.mux.com/asdf/thumbnail.jpg' diff --git a/apps/mux/src/player.tsx b/apps/mux/src/player.tsx index 533b4338418..679b3ac6bbe 100644 --- a/apps/mux/src/player.tsx +++ b/apps/mux/src/player.tsx @@ -40,17 +40,13 @@ class Player extends React.Component { if (Hls.isSupported()) { this.hls.loadSource(this.props.playbackUrl); this.hls.attachMedia(this.playerRef.current); - } else if ( - this.playerRef.current.canPlayType('application/vnd.apple.mpegurl') - ) { + } else if (this.playerRef.current.canPlayType('application/vnd.apple.mpegurl')) { this.playerRef.current.src = this.props.playbackUrl; } } convertRatio = () => { - const [width, height] = this.props.ratio - .split(':') - .map((n) => parseFloat(n)); + const [width, height] = this.props.ratio.split(':').map((n) => parseFloat(n)); return height / width; }; diff --git a/apps/mux/src/signingTokens.tsx b/apps/mux/src/signingTokens.tsx index 1d39852f0de..cebafae3954 100644 --- a/apps/mux/src/signingTokens.tsx +++ b/apps/mux/src/signingTokens.tsx @@ -2,12 +2,7 @@ import * as jwt from 'jsonwebtoken'; const getPrivateKey = (key: string) => Buffer.from(key, 'base64'); -const sign = ( - playbackId: string, - signingKeyId: string, - signingKeyPrivate: string, - aud: string -) => +const sign = (playbackId: string, signingKeyId: string, signingKeyPrivate: string, aud: string) => jwt.sign({}, getPrivateKey(signingKeyPrivate), { algorithm: 'RS256', keyid: signingKeyId, diff --git a/apps/mux/test/index.test.tsx b/apps/mux/test/index.test.tsx index 7d21e7420c4..7b57c07fb0d 100644 --- a/apps/mux/test/index.test.tsx +++ b/apps/mux/test/index.test.tsx @@ -14,7 +14,8 @@ configure({ adapter: new Adapter() }); /* * This was a valid private key, but it has since been revoked */ -const keyPrivate = 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNDZmMGl2MUdZV3VCNHpCNkZjTG9KcW05S1l0eXVBdEZ4WEZ2Q0h5MWc0OVNBNnhICnI2TzJjS1IrRmd0MlZkckxWbmc1UkdtdE5qNEd2WllMT2k4RWJ6dzBMeE9UaWtvK2xKZmVGMS95SC82Ymh2dnQKd3d4L1R4Z3RoQnpUUHp3Ry9oRjVHbGZQeXRheC91emNSN3dFY1JRUUhHeENHcUJ1ZXFZUk5GRGFnVEFJTmJOVwpJK2tkV0hZVG5lSldJTXhNb0UwLzBxOUYvN1ZCY2pibHhDLzdjeXNGUjNvbUdRR1JDejZsSzJxOFlIVGtiQm1tCk1uYnRBeEtkcElqTDljOTJERmhDdDN6ZXhGY0JadDhzMlg5UUQ4N2FBWEhvVkNDRzVMWFZNMUlaQWNzWmt5N2kKVEhLcVVVbXNJekhDdGZRKzhnQ3RGbm1DNkQrT2RHTk9ieDdoMndJREFRQUJBb0lCQUFyOXBOVEEvWkRlZTlyWQpFRXpVcUJpVndVZ3NMMUdyV2FiNm52MnQ1NlYrV2R0TGlmcDAwTzRIUXY4VmRwVVdoeEtabzBvbVAvS0tkQkRiCkdaZXBoWEZKV3N1YkNsaDIxU2FmWGwyS2lFbjdKTThUZ3BzVUUyRmlMWEJmWStOOXBtakZ0eThLWmtISXM3YzMKQUR1R1hFQ0pVMjNMM0RVazRiQ1NLK3Ayck5YbndHNHA5MDlGbkZiczRpTDl6a1hWK292bkhwQnZSYXJQRGFGZApZT0M1M3ZlekVHbHNuQU9tTnUxUitDZEZMWTZDY0grenNrU3ZXSjFGUVIvUUJ3Q0Q5UmpsS085bm4xb3BLWlJnCkxwdW1tSlFRRzNCNjhvendqenhPSnBFU3hRa0w3WVVoV1V4dFlQR0lQdmI2bm1xZVA1aUxLWXd4djhGZXlYN3cKaGZRWVhLRUNnWUVBOHhDSHl2Zm9mZlRRU1pIM3dYUWg4c2dmQUNwNlhCZ1pFWXhETkJFcHppUmx6RnZqOUlPNwp1MWtWN3pFNDE0dXRMWFRrYklSVmNpRkR3aVZ1NlkwK0lYT0lYbDRscUhjZHJBWmhiQnAxeDV3MnJjbEhXNmtICjZvM2ZqSGJKVHFMMXI2b25yUUVQSXRpQkVQMUN6Q013dnZ3WG9KcnJ2NUdieXg4bW1BVkI3ZFVDZ1lFQTc4V0EKaXIrQ0hFVnpOeCtwd0JKRXNsNnVOZnZoa1dIaldrTHF4ajMzbzA5QTJCeXlqc1ZRb1UzQi8xZnhiQ05zMmhHNwpEYUpaWFdGK3VXMklCS3VsUkFXeVN1WEJkNGhvbXlpdXRIWWUzL0Z2YnlwaFVyN3BCWFBhUWxPUkNOa1hqV3pYCmI3clVDNXIrU2t6M1E5TEtjTmppRlVYaTRucXVpUEFaQ2krK2VPOENnWUVBdmRmVVo5ZjNNNkdwcVR5ajZPbisKdGZST0drQVRMNmoyczNqODZFYmJneEYwblFmTVpLY2JVcm5DNHY1cjZoWkRIWFRtRUVmUHdRTndPOHdtODYySQpzSEhmT2UySXRpcks5eGhJc1RsOWNubDFUNGtjL2Q5b3VtOHpBaStwRFkxRUhYN2wzRDh1aGtYWmtONXVkS2lyCm93K2NtS2xIcG1sZzZHWWRLN0UzakQwQ2dZRUFyK2xFLzRhMW5LeFBkWGZqZ0tsbWdUNzVyVjJaQnFLOHZMSXYKc1RZeGd6MVlJN1lhUXFqOUdQc0ZnNk12MnRpNnVkc2NVMHB6S2hHbmViK2tkVmpCTFlESWFDN2NuQ2dXSncvWAo3VXBrS0lUbjdyVTNKaEF1d2ZOWGhDWHZXSUI5eVNLN2hKdWJpdEF5RkswWEZFbUlnUForR0lGbmppWFgrMXU3CjR6OVlEVDBDZ1lCWHBGZ2hWMnpEWW9SeExFQXQ4SUxhOXh5S1RBQmhGbDl2a1Y1TzBzQUVQd0xZSkJXS2YyTTQKOVNxZ3I3Q0JMeEN6U1NMejFWZXMyUFZCUytnR2JJYUFtQWpXbGU4bTF0ejc3MWtFMDhQdGdzOWhuT09wMy9DYwozOGt3dnJoM250YkNDbjk2MldjaEs1aHdLREU4UXd6OXhPU2JSdEpDWWhDVkJzczc5Y2Q1b2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=' +const keyPrivate = + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNDZmMGl2MUdZV3VCNHpCNkZjTG9KcW05S1l0eXVBdEZ4WEZ2Q0h5MWc0OVNBNnhICnI2TzJjS1IrRmd0MlZkckxWbmc1UkdtdE5qNEd2WllMT2k4RWJ6dzBMeE9UaWtvK2xKZmVGMS95SC82Ymh2dnQKd3d4L1R4Z3RoQnpUUHp3Ry9oRjVHbGZQeXRheC91emNSN3dFY1JRUUhHeENHcUJ1ZXFZUk5GRGFnVEFJTmJOVwpJK2tkV0hZVG5lSldJTXhNb0UwLzBxOUYvN1ZCY2pibHhDLzdjeXNGUjNvbUdRR1JDejZsSzJxOFlIVGtiQm1tCk1uYnRBeEtkcElqTDljOTJERmhDdDN6ZXhGY0JadDhzMlg5UUQ4N2FBWEhvVkNDRzVMWFZNMUlaQWNzWmt5N2kKVEhLcVVVbXNJekhDdGZRKzhnQ3RGbm1DNkQrT2RHTk9ieDdoMndJREFRQUJBb0lCQUFyOXBOVEEvWkRlZTlyWQpFRXpVcUJpVndVZ3NMMUdyV2FiNm52MnQ1NlYrV2R0TGlmcDAwTzRIUXY4VmRwVVdoeEtabzBvbVAvS0tkQkRiCkdaZXBoWEZKV3N1YkNsaDIxU2FmWGwyS2lFbjdKTThUZ3BzVUUyRmlMWEJmWStOOXBtakZ0eThLWmtISXM3YzMKQUR1R1hFQ0pVMjNMM0RVazRiQ1NLK3Ayck5YbndHNHA5MDlGbkZiczRpTDl6a1hWK292bkhwQnZSYXJQRGFGZApZT0M1M3ZlekVHbHNuQU9tTnUxUitDZEZMWTZDY0grenNrU3ZXSjFGUVIvUUJ3Q0Q5UmpsS085bm4xb3BLWlJnCkxwdW1tSlFRRzNCNjhvendqenhPSnBFU3hRa0w3WVVoV1V4dFlQR0lQdmI2bm1xZVA1aUxLWXd4djhGZXlYN3cKaGZRWVhLRUNnWUVBOHhDSHl2Zm9mZlRRU1pIM3dYUWg4c2dmQUNwNlhCZ1pFWXhETkJFcHppUmx6RnZqOUlPNwp1MWtWN3pFNDE0dXRMWFRrYklSVmNpRkR3aVZ1NlkwK0lYT0lYbDRscUhjZHJBWmhiQnAxeDV3MnJjbEhXNmtICjZvM2ZqSGJKVHFMMXI2b25yUUVQSXRpQkVQMUN6Q013dnZ3WG9KcnJ2NUdieXg4bW1BVkI3ZFVDZ1lFQTc4V0EKaXIrQ0hFVnpOeCtwd0JKRXNsNnVOZnZoa1dIaldrTHF4ajMzbzA5QTJCeXlqc1ZRb1UzQi8xZnhiQ05zMmhHNwpEYUpaWFdGK3VXMklCS3VsUkFXeVN1WEJkNGhvbXlpdXRIWWUzL0Z2YnlwaFVyN3BCWFBhUWxPUkNOa1hqV3pYCmI3clVDNXIrU2t6M1E5TEtjTmppRlVYaTRucXVpUEFaQ2krK2VPOENnWUVBdmRmVVo5ZjNNNkdwcVR5ajZPbisKdGZST0drQVRMNmoyczNqODZFYmJneEYwblFmTVpLY2JVcm5DNHY1cjZoWkRIWFRtRUVmUHdRTndPOHdtODYySQpzSEhmT2UySXRpcks5eGhJc1RsOWNubDFUNGtjL2Q5b3VtOHpBaStwRFkxRUhYN2wzRDh1aGtYWmtONXVkS2lyCm93K2NtS2xIcG1sZzZHWWRLN0UzakQwQ2dZRUFyK2xFLzRhMW5LeFBkWGZqZ0tsbWdUNzVyVjJaQnFLOHZMSXYKc1RZeGd6MVlJN1lhUXFqOUdQc0ZnNk12MnRpNnVkc2NVMHB6S2hHbmViK2tkVmpCTFlESWFDN2NuQ2dXSncvWAo3VXBrS0lUbjdyVTNKaEF1d2ZOWGhDWHZXSUI5eVNLN2hKdWJpdEF5RkswWEZFbUlnUForR0lGbmppWFgrMXU3CjR6OVlEVDBDZ1lCWHBGZ2hWMnpEWW9SeExFQXQ4SUxhOXh5S1RBQmhGbDl2a1Y1TzBzQUVQd0xZSkJXS2YyTTQKOVNxZ3I3Q0JMeEN6U1NMejFWZXMyUFZCUytnR2JJYUFtQWpXbGU4bTF0ejc3MWtFMDhQdGdzOWhuT09wMy9DYwozOGt3dnJoM250YkNDbjk2MldjaEs1aHdLREU4UXd6OXhPU2JSdEpDWWhDVkJzczc5Y2Q1b2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo='; const SDK_MOCK = { parameters: { @@ -34,17 +35,19 @@ const SDK_MOCK = { }; const SDK_MOCK_WITH_SIGNED_URLS = { - ...SDK_MOCK, ...{ - parameters: { - installation: { - muxAccessTokenId: 'abcd1234', - muxAccessTokenSecret: 'efgh5678', - muxSigningKeyId: 'signing-key-id', - muxSigningKeyPrivate: keyPrivate, - muxEnableSignedUrls: true + ...SDK_MOCK, + ...{ + parameters: { + installation: { + muxAccessTokenId: 'abcd1234', + muxAccessTokenSecret: 'efgh5678', + muxSigningKeyId: 'signing-key-id', + muxSigningKeyPrivate: keyPrivate, + muxEnableSignedUrls: true, + }, }, }, -}} +}; test('throws an error if required installation parameters are not configured', () => { const mockedSdk = { @@ -83,11 +86,13 @@ test('displays a player when the state has a playbackUrl and posterUrl', async ( }; const wrapper = await shallow(); - wrapper.instance().forceUpdate() - await (wrapper.instance() as App).setPublicPlayback('test-playbackId123') - expect(wrapper.state('playbackUrl')).toEqual('https://stream.mux.com/test-playbackId123.m3u8') - expect(wrapper.state('posterUrl')).toEqual('https://image.mux.com/test-playbackId123/thumbnail.jpg') - expect(wrapper.find('Player')).toHaveLength(1) + wrapper.instance().forceUpdate(); + await (wrapper.instance() as App).setPublicPlayback('test-playbackId123'); + expect(wrapper.state('playbackUrl')).toEqual('https://stream.mux.com/test-playbackId123.m3u8'); + expect(wrapper.state('posterUrl')).toEqual( + 'https://image.mux.com/test-playbackId123/thumbnail.jpg' + ); + expect(wrapper.find('Player')).toHaveLength(1); }); test('displays a player when the state has a signed playbackUrl and signed posterUrl', async () => { @@ -104,11 +109,15 @@ test('displays a player when the state has a signed playbackUrl and signed poste }; const wrapper = await shallow(); - wrapper.instance().forceUpdate() - await (wrapper.instance() as App).setSignedPlayback('test-playbackId123') - expect(wrapper.state('playbackUrl')).toMatch('https://stream.mux.com/test-playbackId123.m3u8?token=') - expect(wrapper.state('posterUrl')).toMatch('https://image.mux.com/test-playbackId123/thumbnail.jpg?token=') - expect(wrapper.find('Player')).toHaveLength(1) + wrapper.instance().forceUpdate(); + await (wrapper.instance() as App).setSignedPlayback('test-playbackId123'); + expect(wrapper.state('playbackUrl')).toMatch( + 'https://stream.mux.com/test-playbackId123.m3u8?token=' + ); + expect(wrapper.state('posterUrl')).toMatch( + 'https://image.mux.com/test-playbackId123/thumbnail.jpg?token=' + ); + expect(wrapper.find('Player')).toHaveLength(1); }); test('displays an error when we have a signed playbackId but no signing keys', async () => { @@ -125,10 +134,12 @@ test('displays an error when we have a signed playbackId but no signing keys', a }; const wrapper = await shallow(); - wrapper.instance().forceUpdate() - await (wrapper.instance() as App).setSignedPlayback('test-playbackId123') - expect(wrapper.state('error')).toEqual('Error: this asset was created with a signed playback ID, but signing keys do not exist for your account') - expect(wrapper.find('Player')).toHaveLength(0) + wrapper.instance().forceUpdate(); + await (wrapper.instance() as App).setSignedPlayback('test-playbackId123'); + expect(wrapper.state('error')).toEqual( + 'Error: this asset was created with a signed playback ID, but signing keys do not exist for your account' + ); + expect(wrapper.find('Player')).toHaveLength(0); }); test('displays an error if the asset is errored', () => { @@ -137,7 +148,7 @@ test('displays an error if the asset is errored', () => { field: { ...SDK_MOCK.field, getValue: () => ({ - error: 'Input file does not contain a duration' + error: 'Input file does not contain a duration', }), }, }; diff --git a/apps/mux/test/player.test.tsx b/apps/mux/test/player.test.tsx index 060ca9cbb78..7c2ca0c70f3 100644 --- a/apps/mux/test/player.test.tsx +++ b/apps/mux/test/player.test.tsx @@ -9,7 +9,12 @@ import Player from '../src/player'; configure({ adapter: new Adapter() }); test('preview player has a Mux poster image', () => { - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper.find('video').props().poster).toContain( 'https://image.mux.com/asdf/thumbnail.jpg' diff --git a/apps/netlify/src/app/NetlifyIcon.js b/apps/netlify/src/app/NetlifyIcon.js index ad89418032f..ef92103865f 100644 --- a/apps/netlify/src/app/NetlifyIcon.js +++ b/apps/netlify/src/app/NetlifyIcon.js @@ -10,7 +10,8 @@ export default () => ( fy="-50%" r="100.11%" gradientTransform="matrix(0 .9989 -1 0 0 -1)" - id="a"> + id="a" + > diff --git a/apps/netlify/src/app/index.js b/apps/netlify/src/app/index.js index f99fb56a8a6..7cad3737548 100644 --- a/apps/netlify/src/app/index.js +++ b/apps/netlify/src/app/index.js @@ -1,20 +1,20 @@ -import React from "react"; -import PropTypes from "prop-types"; -import uniqBy from "lodash.uniqby"; +import React from 'react'; +import PropTypes from 'prop-types'; +import uniqBy from 'lodash.uniqby'; import { editorInterfacesToEnabledContentTypes, enabledContentTypesToTargetState, -} from "./target-state"; +} from './target-state'; -import NetlifyConnection from "./netlify-connection"; -import NetlifyConfigEditor from "./netlify-config-editor"; -import NetlifyContentTypes from "./netlify-content-types"; -import * as NetlifyClient from "./netlify-client"; -import * as NetlifyIntegration from "./netlify-integration"; -import NetlifyIcon from "./NetlifyIcon"; -import styles from "./styles"; +import NetlifyConnection from './netlify-connection'; +import NetlifyConfigEditor from './netlify-config-editor'; +import NetlifyContentTypes from './netlify-content-types'; +import * as NetlifyClient from './netlify-client'; +import * as NetlifyIntegration from './netlify-integration'; +import NetlifyIcon from './NetlifyIcon'; +import styles from './styles'; -import { parametersToConfig, configToParameters } from "../config"; +import { parametersToConfig, configToParameters } from '../config'; export default class NetlifyAppConfig extends React.Component { static propTypes = { @@ -50,10 +50,7 @@ export default class NetlifyAppConfig extends React.Component { ]); const config = parametersToConfig(parameters); - const enabledContentTypes = editorInterfacesToEnabledContentTypes( - eisResponse.items, - ids.app - ); + const enabledContentTypes = editorInterfacesToEnabledContentTypes(eisResponse.items, ids.app); // First empty site (so no UI click is needed). if (!Array.isArray(config.sites) || config.sites.length < 1) { @@ -74,10 +71,7 @@ export default class NetlifyAppConfig extends React.Component { { config, enabledContentTypes, - contentTypes: contentTypesResponse.items.map((ct) => [ - ct.sys.id, - ct.name, - ]), + contentTypes: contentTypesResponse.items.map((ct) => [ct.sys.id, ct.name]), netlifySites: uniqBy(netlifySites, (s) => s.id), ticketId, }, @@ -87,33 +81,23 @@ export default class NetlifyAppConfig extends React.Component { onAppConfigure = async () => { if (!this.state.token) { - this.notifyError( - "You must be connected to Netlify to configure the app." - ); + this.notifyError('You must be connected to Netlify to configure the app.'); return false; } - const configuredNetlifySiteIds = this.state.config.sites.map( - (site) => site.netlifySiteId - ); - const availableNetlifySiteIds = this.state.netlifySites.map( - (site) => site.id - ); + const configuredNetlifySiteIds = this.state.config.sites.map((site) => site.netlifySiteId); + const availableNetlifySiteIds = this.state.netlifySites.map((site) => site.id); - if ( - !configuredNetlifySiteIds.every((id) => - availableNetlifySiteIds.includes(id) - ) - ) { + if (!configuredNetlifySiteIds.every((id) => availableNetlifySiteIds.includes(id))) { this.notifyError( - "Looks like some sites were deleted in Netlify. Pick a new site or remove outdated configuration." + 'Looks like some sites were deleted in Netlify. Pick a new site or remove outdated configuration.' ); return false; } try { const isInstalled = await this.props.sdk.app.isInstalled(); - const method = isInstalled ? "update" : "install"; + const method = isInstalled ? 'update' : 'install'; const config = await NetlifyIntegration[method]({ config: this.state.config, // eslint-disable-line react/no-access-state-in-setstate accessToken: this.state.token, // eslint-disable-line react/no-access-state-in-setstate @@ -129,14 +113,14 @@ export default class NetlifyAppConfig extends React.Component { ), }; } catch (err) { - this.notifyError(err, "Failed to configure the app."); + this.notifyError(err, 'Failed to configure the app.'); return false; } }; notifyError = (err, fallbackMessage) => { - let message = fallbackMessage || "Operation failed."; - if (typeof err === "string") { + let message = fallbackMessage || 'Operation failed.'; + if (typeof err === 'string') { message = err; } else if (err.useMessage && err.message) { message = err.message; @@ -161,7 +145,7 @@ export default class NetlifyAppConfig extends React.Component { this.state.ticketId, (err, token) => { if (err || !token) { - this.notifyError(err, "Failed to connect with Netlify. Try again!"); + this.notifyError(err, 'Failed to connect with Netlify. Try again!'); } else { this.initNetlifyConnection(token); } @@ -172,9 +156,7 @@ export default class NetlifyAppConfig extends React.Component { initNetlifyConnection = async ({ token, email }) => { try { const { sites, counts } = await NetlifyClient.listSites(token); - this.props.sdk.notifier.success( - "Netlify account connected successfully." - ); + this.props.sdk.notifier.success('Netlify account connected successfully.'); this.setState({ token, email, @@ -182,7 +164,7 @@ export default class NetlifyAppConfig extends React.Component { netlifyCounts: counts, }); } catch (err) { - this.notifyError(err, "Failed to connect with Netlify. Try again!"); + this.notifyError(err, 'Failed to connect with Netlify. Try again!'); } }; diff --git a/apps/netlify/src/app/netlify-client.js b/apps/netlify/src/app/netlify-client.js index 185b02a2064..6d0dbbbab17 100644 --- a/apps/netlify/src/app/netlify-client.js +++ b/apps/netlify/src/app/netlify-client.js @@ -3,7 +3,7 @@ import { NETLIFY_API_BASE, NETLIFY_AUTHORIZE_ENDPOINT, NETLIFY_AUTH_WINDOW_OPTS, - NETLIFY_AUTH_POLL_INTERVAL + NETLIFY_AUTH_POLL_INTERVAL, } from '../constants'; async function request(method, url, accessToken, body) { @@ -80,7 +80,10 @@ export function getAccessTokenWithTicket(ticketId, cb) { if (err) { cb(err); } else if (authorizedTicketId) { - exchangeTicketForToken(authorizedTicketId).then(token => cb(null, token), err => cb(err)); + exchangeTicketForToken(authorizedTicketId).then( + (token) => cb(null, token), + (err) => cb(err) + ); } }); @@ -95,7 +98,7 @@ export async function listSites(accessToken) { // Get sites for each account. const sitesForAccounts = await Promise.all( - accounts.map(account => { + accounts.map((account) => { return get(`/${account.slug}/sites?page=1&per_page=100`, accessToken); }) ); @@ -106,7 +109,7 @@ export async function listSites(accessToken) { }, []); // Filter out sites with no build configuration. - const buildable = sites.filter(site => { + const buildable = sites.filter((site) => { const settings = site.build_settings; return null !== settings && typeof settings === 'object'; }); @@ -115,14 +118,14 @@ export async function listSites(accessToken) { sites: buildable, counts: { buildable: buildable.length, - unavailable: sites.length - buildable.length - } + unavailable: sites.length - buildable.length, + }, }; } export function createBuildHook(siteId, accessToken) { return post(`/sites/${siteId}/build_hooks`, accessToken, { - title: 'Contentful integration' + title: 'Contentful integration', }); } @@ -134,7 +137,7 @@ export function createNotificationHook(siteId, accessToken, { event, url }) { return post('/hooks', accessToken, { site_id: siteId, event, - data: { url } + data: { url }, }); } diff --git a/apps/netlify/src/app/netlify-config-editor.js b/apps/netlify/src/app/netlify-config-editor.js index 35b815ea9b8..00fb1e668bc 100644 --- a/apps/netlify/src/app/netlify-config-editor.js +++ b/apps/netlify/src/app/netlify-config-editor.js @@ -11,7 +11,7 @@ import { SelectField, Option, TextField, - TextLink + TextLink, } from '@contentful/forma-36-react-components'; import { MAX_CONFIGS } from '../constants'; @@ -20,25 +20,25 @@ const PICK_OPTION_VALUE = '__pick__'; const styles = { container: css({ - margin: `${tokens.spacingXl} 0` + margin: `${tokens.spacingXl} 0`, }), row: css({ display: 'flex', - margin: `${tokens.spacingXl} 0` + margin: `${tokens.spacingXl} 0`, }), item: css({ - marginRight: tokens.spacingXl + marginRight: tokens.spacingXl, }), removeBtn: css({ - marginTop: tokens.spacingL + marginTop: tokens.spacingL, }), splitter: css({ marginTop: tokens.spacingL, marginBottom: tokens.spacingL, border: 0, height: '1px', - backgroundColor: tokens.gray300 - }) + backgroundColor: tokens.gray300, + }), }; export default class NetlifyConfigEditor extends React.Component { @@ -46,12 +46,12 @@ export default class NetlifyConfigEditor extends React.Component { disabled: PropTypes.bool.isRequired, siteConfigs: PropTypes.array.isRequired, netlifySites: PropTypes.array.isRequired, - onSiteConfigsChange: PropTypes.func.isRequired + onSiteConfigsChange: PropTypes.func.isRequired, }; onNetlifySiteChange = (configIndex, netlifySiteId) => { const { netlifySites, siteConfigs, onSiteConfigsChange } = this.props; - const site = netlifySites.find(site => site.id === netlifySiteId) || {}; + const site = netlifySites.find((site) => site.id === netlifySiteId) || {}; const updated = siteConfigs.map((siteConfig, i) => { if (configIndex === i) { @@ -59,7 +59,7 @@ export default class NetlifyConfigEditor extends React.Component { ...siteConfig, netlifySiteId: site.id, netlifySiteName: site.name, - netlifySiteUrl: site.ssl_url || site.url + netlifySiteUrl: site.ssl_url || site.url, }; } else { return siteConfig; @@ -83,7 +83,7 @@ export default class NetlifyConfigEditor extends React.Component { onSiteConfigsChange(updated); }; - onRemove = configIndex => { + onRemove = (configIndex) => { const { siteConfigs, onSiteConfigsChange } = this.props; const updated = siteConfigs.filter((_, i) => i !== configIndex); onSiteConfigsChange(updated); @@ -115,9 +115,10 @@ export default class NetlifyConfigEditor extends React.Component { labelText="Netlify site:" selectProps={{ isDisabled: disabled, width: 'medium' }} value={siteConfig.netlifySiteId || PICK_OPTION_VALUE} - onChange={e => this.onNetlifySiteChange(configIndex, e.target.value)}> + onChange={(e) => this.onNetlifySiteChange(configIndex, e.target.value)} + > - {netlifySites.map(netlifySite => { + {netlifySites.map((netlifySite) => { return (
@@ -146,7 +148,8 @@ export default class NetlifyConfigEditor extends React.Component {
diff --git a/apps/netlify/src/app/netlify-connection.js b/apps/netlify/src/app/netlify-connection.js index 178930c3252..f93c7ceb7db 100644 --- a/apps/netlify/src/app/netlify-connection.js +++ b/apps/netlify/src/app/netlify-connection.js @@ -8,7 +8,7 @@ import { Typography, Heading, Paragraph, Icon } from '@contentful/forma-36-react const styles = { auth: css({ display: 'flex', - justifyContent: 'center' + justifyContent: 'center', }), button: css({ backgroundColor: '#00ad9e', @@ -21,25 +21,25 @@ const styles = { boxShadow: '0 2px 4px 0 rgba(14,30,37,.12)', cursor: 'pointer', fontSize: '16px', - boxSizing: 'border-box' + boxSizing: 'border-box', }), splitter: css({ marginTop: tokens.spacingL, marginBottom: tokens.spacingL, border: 0, height: '1px', - backgroundColor: tokens.gray300 + backgroundColor: tokens.gray300, }), connectAgain: css({ marginTop: tokens.spacingXs, textAlign: 'center', - color: tokens.gray600 + color: tokens.gray600, }), connectAgainIcon: css({ fill: tokens.gray600, marginRight: tokens.spacingXs, - verticalAlign: 'middle' - }) + verticalAlign: 'middle', + }), }; export default class NetlifyConnection extends React.Component { @@ -49,9 +49,9 @@ export default class NetlifyConnection extends React.Component { email: PropTypes.string, netlifyCounts: PropTypes.shape({ buildable: PropTypes.number.isRequired, - unavailable: PropTypes.number.isRequired + unavailable: PropTypes.number.isRequired, }), - onConnectClick: PropTypes.func.isRequired + onConnectClick: PropTypes.func.isRequired, }; render() { @@ -89,7 +89,7 @@ export default class NetlifyConnection extends React.Component { ); } - getSitePlural = count => { + getSitePlural = (count) => { return count === 1 ? 'site' : 'sites'; }; diff --git a/apps/netlify/src/app/netlify-content-types.js b/apps/netlify/src/app/netlify-content-types.js index ae8b1329670..45737d0630a 100644 --- a/apps/netlify/src/app/netlify-content-types.js +++ b/apps/netlify/src/app/netlify-content-types.js @@ -1,5 +1,5 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React from 'react'; +import PropTypes from 'prop-types'; import { Typography, @@ -9,7 +9,7 @@ import { Paragraph, FieldGroup, CheckboxField, -} from "@contentful/forma-36-react-components"; +} from '@contentful/forma-36-react-components'; export default class NetlifyContentTypes extends React.Component { static propTypes = { @@ -39,13 +39,7 @@ export default class NetlifyContentTypes extends React.Component { }; render() { - const { - disabled, - contentTypes, - enabledContentTypes, - space, - environment, - } = this.props; + const { disabled, contentTypes, enabledContentTypes, space, environment } = this.props; const allSelected = contentTypes.length === enabledContentTypes.length; @@ -53,20 +47,19 @@ export default class NetlifyContentTypes extends React.Component { if (contentTypes.length === 0) { contentToRender = ( - There are no content types in this environment. You - can add a{" "} + There are no content types in this environment. You can add a{' '} content type - {" "} + {' '} and assign it to the app from this screen. ); @@ -103,8 +96,7 @@ export default class NetlifyContentTypes extends React.Component { Content Types - Select which content types will show the Netlify functionality in the - sidebar. + Select which content types will show the Netlify functionality in the sidebar. {contentToRender} diff --git a/apps/netlify/src/app/netlify-integration.js b/apps/netlify/src/app/netlify-integration.js index 08533e9cf7b..76be2cccb70 100644 --- a/apps/netlify/src/app/netlify-integration.js +++ b/apps/netlify/src/app/netlify-integration.js @@ -14,7 +14,7 @@ export async function install({ config, accessToken }) { config = prepareConfig(config); // Create build hooks for all sites. - const buildHookPromises = config.sites.map(siteConfig => { + const buildHookPromises = config.sites.map((siteConfig) => { return NetlifyClient.createBuildHook(siteConfig.netlifySiteId, accessToken); }); @@ -23,16 +23,16 @@ export async function install({ config, accessToken }) { const updatedConfig = { sites: config.sites.map((siteConfig, i) => { return { ...siteConfig, buildHookId: buildHooks[i].id }; - }) + }), }; // Create Netlify notification hooks for all sites. - const netlifyHookPromises = uniqBy(updatedConfig.sites, s => s.netlifySiteId).reduce( + const netlifyHookPromises = uniqBy(updatedConfig.sites, (s) => s.netlifySiteId).reduce( (acc, siteConfig) => { const siteId = siteConfig.netlifySiteId; const url = getPostPublishUrl(siteId + siteConfig.buildHookId); - const promisesForSite = NETLIFY_HOOK_EVENTS.map(event => { + const promisesForSite = NETLIFY_HOOK_EVENTS.map((event) => { const hook = { event, url }; return NetlifyClient.createNotificationHook(siteId, accessToken, hook); }); @@ -46,7 +46,7 @@ export async function install({ config, accessToken }) { // Merge flattened notification hook IDs to configuration. updatedConfig.netlifyHookIds = netlifyHooks.reduce((acc, hooksForSite) => { - return acc.concat(hooksForSite.map(h => h.id)); + return acc.concat(hooksForSite.map((h) => h.id)); }, []); return updatedConfig; @@ -60,9 +60,9 @@ export async function update({ config, accessToken }) { // Remove references to removed hooks from configuration. const updatedConfig = { - sites: config.sites.map(siteConfig => { + sites: config.sites.map((siteConfig) => { return { ...siteConfig, buildHookId: undefined }; - }) + }), }; // Proceed as in the installation step. @@ -70,7 +70,7 @@ export async function update({ config, accessToken }) { } function prepareConfig(config = {}) { - const sites = (config.sites || []).map(siteConfig => { + const sites = (config.sites || []).map((siteConfig) => { return { ...siteConfig, name: (siteConfig.name || '').trim() }; }); @@ -88,7 +88,7 @@ function validateSiteConfigs(siteConfigs) { } // Find all site configurations with incomplete information. - const incomplete = siteConfigs.filter(siteConfig => { + const incomplete = siteConfigs.filter((siteConfig) => { return !siteConfig.netlifySiteId || siteConfig.name.length < 1; }); @@ -97,7 +97,7 @@ function validateSiteConfigs(siteConfigs) { } // Display names must be unique. - const uniqueNames = uniqBy(siteConfigs, config => config.name); + const uniqueNames = uniqBy(siteConfigs, (config) => config.name); if (uniqueNames.length !== siteConfigs.length) { throw makeError('Display names must be unique.'); @@ -115,7 +115,7 @@ async function removeExistingHooks({ config, accessToken }) { const netlifyHookIds = config.netlifyHookIds || []; // ...remove build hooks for it and... - const buildHookRemovalPromises = siteConfigs.map(siteConfig => { + const buildHookRemovalPromises = siteConfigs.map((siteConfig) => { const { netlifySiteId, buildHookId } = siteConfig; if (netlifySiteId && buildHookId) { return NetlifyClient.deleteBuildHook(netlifySiteId, buildHookId, accessToken); @@ -126,8 +126,8 @@ async function removeExistingHooks({ config, accessToken }) { // ...remove Netlify hooks for it. const netlifyHookRemovalPromises = netlifyHookIds - .filter(id => typeof id === 'string' && id.length > 0) - .map(id => NetlifyClient.deleteNotificationHook(id, accessToken)); + .filter((id) => typeof id === 'string' && id.length > 0) + .map((id) => NetlifyClient.deleteNotificationHook(id, accessToken)); const removalPromises = buildHookRemovalPromises.concat(netlifyHookRemovalPromises); diff --git a/apps/netlify/src/app/netlify-integration.spec.js b/apps/netlify/src/app/netlify-integration.spec.js index b314feefdf7..44d8d4b1f00 100644 --- a/apps/netlify/src/app/netlify-integration.spec.js +++ b/apps/netlify/src/app/netlify-integration.spec.js @@ -8,7 +8,7 @@ describe('netlify-integration', () => { return Promise.resolve({ id: 'build-hook-id-1' }); }); - [1, 2, 3].forEach(i => { + [1, 2, 3].forEach((i) => { NetlifyClient.createNotificationHook.mockImplementationOnce(() => { return Promise.resolve({ id: `notification-hook-id-${i}` }); }); @@ -24,11 +24,11 @@ describe('netlify-integration', () => { name: 'Site 1', netlifySiteId: 'id1', netlifySiteName: 'foo-bar', - netlifySiteUrl: 'https://foo-bar.netlify.com' - } - ] + netlifySiteUrl: 'https://foo-bar.netlify.com', + }, + ], }, - accessToken: 'token' + accessToken: 'token', }); expect(NetlifyClient.createBuildHook).toHaveBeenCalledTimes(1); @@ -36,10 +36,10 @@ describe('netlify-integration', () => { expect(NetlifyClient.createNotificationHook).toHaveBeenCalledTimes(3); - ['deploy_building', 'deploy_created', 'deploy_failed'].forEach(event => { + ['deploy_building', 'deploy_created', 'deploy_failed'].forEach((event) => { expect(NetlifyClient.createNotificationHook).toHaveBeenCalledWith('id1', 'token', { event, - url: expect.stringMatching(/id1build-hook-id-1/) // channel is site ID + build hook ID + url: expect.stringMatching(/id1build-hook-id-1/), // channel is site ID + build hook ID }); }); @@ -47,7 +47,7 @@ describe('netlify-integration', () => { netlifyHookIds: [ 'notification-hook-id-1', 'notification-hook-id-2', - 'notification-hook-id-3' + 'notification-hook-id-3', ], sites: [ { @@ -55,9 +55,9 @@ describe('netlify-integration', () => { name: 'Site 1', netlifySiteId: 'id1', netlifySiteName: 'foo-bar', - netlifySiteUrl: 'https://foo-bar.netlify.com' - } - ] + netlifySiteUrl: 'https://foo-bar.netlify.com', + }, + ], }); }); @@ -66,9 +66,14 @@ describe('netlify-integration', () => { [{ sites: [] }, expect.stringMatching(/at least one/)], [{ sites: [{ netlifySiteId: 'x', name: '' }] }, expect.stringMatching(/provide a name/)], [ - { sites: [{ netlifySiteId: 'x', name: 'x' }, { netlifySiteId: 'y', name: 'x' }] }, - expect.stringMatching(/must be unique/) - ] + { + sites: [ + { netlifySiteId: 'x', name: 'x' }, + { netlifySiteId: 'y', name: 'x' }, + ], + }, + expect.stringMatching(/must be unique/), + ], ]; expect.assertions(3); @@ -88,7 +93,7 @@ describe('netlify-integration', () => { const notificationHooks = [ 'notification-hook-id-1', 'notification-hook-id-2', - 'notification-hook-id-3' + 'notification-hook-id-3', ]; await NetlifyIntegration.update({ @@ -100,18 +105,18 @@ describe('netlify-integration', () => { name: 'Site 1', netlifySiteId: 'id1', netlifySiteName: 'foo-bar', - netlifySiteUrl: 'https://foo-bar.netlify.com' - } - ] + netlifySiteUrl: 'https://foo-bar.netlify.com', + }, + ], }, - accessToken: 'token' + accessToken: 'token', }); expect(NetlifyClient.deleteBuildHook).toHaveBeenCalledTimes(1); expect(NetlifyClient.deleteBuildHook).toHaveBeenCalledWith('id1', 'build-hook-id-1', 'token'); expect(NetlifyClient.deleteNotificationHook).toHaveBeenCalledTimes(3); - notificationHooks.forEach(id => { + notificationHooks.forEach((id) => { expect(NetlifyClient.deleteNotificationHook).toHaveBeenCalledWith(id, 'token'); }); diff --git a/apps/netlify/src/app/styles.js b/apps/netlify/src/app/styles.js index 3c5bc021d19..501427619a7 100644 --- a/apps/netlify/src/app/styles.js +++ b/apps/netlify/src/app/styles.js @@ -12,7 +12,7 @@ const styles = { backgroundColor: tokens.colorWhite, zIndex: '2', boxShadow: '0px 0px 20px rgba(0, 0, 0, 0.1)', - borderRadius: '2px' + borderRadius: '2px', }), background: css({ display: 'block', @@ -22,36 +22,36 @@ const styles = { width: '100%', height: '300px', backgroundColor: 'rgb(23,64,121)', - background: 'linear-gradient(90deg, rgba(23,64,121,1) 0%, rgba(27,158,156,1) 100%)' + background: 'linear-gradient(90deg, rgba(23,64,121,1) 0%, rgba(27,158,156,1) 100%)', }), section: css({ - margin: `${tokens.spacingXl} 0` + margin: `${tokens.spacingXl} 0`, }), input: css({ - marginTop: tokens.spacingM + marginTop: tokens.spacingM, }), splitter: css({ marginTop: tokens.spacingL, marginBottom: tokens.spacingL, border: 0, height: '1px', - backgroundColor: tokens.gray300 + backgroundColor: tokens.gray300, }), icon: css({ display: 'flex', justifyContent: 'center', marginTop: tokens.spacingXl, - marginBottom: tokens.spacingXl + marginBottom: tokens.spacingXl, }), checks: css({ marginTop: tokens.spacingM, - display: 'flex' + display: 'flex', }), pills: css({ - margin: `0 ${tokens.spacingXs}` + margin: `0 ${tokens.spacingXs}`, }), relative: css({ - position: 'relative' + position: 'relative', }), configurationProtector: css({ zIndex: 9999, @@ -61,7 +61,7 @@ const styles = { bottom: 0, pointerEvents: 'none', backgroundColor: 'rgba(255, 255, 255, 0.60)', - position: 'absolute' - }) + position: 'absolute', + }), }; -export default styles +export default styles; diff --git a/apps/netlify/src/app/target-state.js b/apps/netlify/src/app/target-state.js index c1676d0e284..77c27d1fdac 100644 --- a/apps/netlify/src/app/target-state.js +++ b/apps/netlify/src/app/target-state.js @@ -1,12 +1,12 @@ import get from 'lodash.get'; export function editorInterfacesToEnabledContentTypes(eis, appId) { - const findAppWidget = item => item.widgetNamespace === 'app' && item.widgetId === appId; + const findAppWidget = (item) => item.widgetNamespace === 'app' && item.widgetId === appId; return eis - .filter(ei => !!get(ei, ['sidebar'], []).find(findAppWidget)) - .map(ei => get(ei, ['sys', 'contentType', 'sys', 'id'])) - .filter(ctId => typeof ctId === 'string' && ctId.length > 0); + .filter((ei) => !!get(ei, ['sidebar'], []).find(findAppWidget)) + .map((ei) => get(ei, ['sys', 'contentType', 'sys', 'id'])) + .filter((ctId) => typeof ctId === 'string' && ctId.length > 0); } export function enabledContentTypesToTargetState(contentTypes, enabledContentTypes) { @@ -14,8 +14,8 @@ export function enabledContentTypesToTargetState(contentTypes, enabledContentTyp EditorInterface: contentTypes.reduce((acc, [ctId]) => { return { ...acc, - [ctId]: enabledContentTypes.includes(ctId) ? { sidebar: { position: 2 } } : {} + [ctId]: enabledContentTypes.includes(ctId) ? { sidebar: { position: 2 } } : {}, }; - }, {}) + }, {}), }; } diff --git a/apps/netlify/src/app/target-state.spec.js b/apps/netlify/src/app/target-state.spec.js index de6fc564070..54212151ba8 100644 --- a/apps/netlify/src/app/target-state.spec.js +++ b/apps/netlify/src/app/target-state.spec.js @@ -1,6 +1,6 @@ import { editorInterfacesToEnabledContentTypes, - enabledContentTypesToTargetState + enabledContentTypesToTargetState, } from '../../src/app/target-state'; describe('target-state', () => { @@ -17,7 +17,7 @@ describe('target-state', () => { { sys: { contentType: { sys: { id: 'ct1' } } }, // "some-app" app in the sidebar - enabled. - sidebar: [{ widgetNamespace: 'app', widgetId: 'some-app' }] + sidebar: [{ widgetNamespace: 'app', widgetId: 'some-app' }], }, { sys: { @@ -26,9 +26,9 @@ describe('target-state', () => { controls: [{ fieldId: 'title', widgetNamespace: 'app', widgetId: 'some-app' }], editor: { widgetNamespace: 'app', - widgetId: 'some-app' - } - } + widgetId: 'some-app', + }, + }, }, { sys: { contentType: { sys: { id: 'ct3' } } }, @@ -36,17 +36,17 @@ describe('target-state', () => { { widgetNamespace: 'extension', widgetId: 'some-ext' }, { widgetNamespace: 'builtin-sidebar', widgetId: 'publish-button' }, // "some-app" app deep in the sidebar - still enabled. - { widgetNamespace: 'app', widgetId: 'some-app' } - ] + { widgetNamespace: 'app', widgetId: 'some-app' }, + ], }, { sys: { contentType: { sys: { id: 'ct4' } } }, sidebar: [ { widgetNamespace: 'app', widgetId: 'some-diff-app' }, // "some-app" ID is used for an extension, not an app - not enabled. - { widgetNamespace: 'extension', widgetId: 'some-app' } - ] - } + { widgetNamespace: 'extension', widgetId: 'some-app' }, + ], + }, ], 'some-app' ); @@ -60,7 +60,7 @@ describe('target-state', () => { const contentTypes = [ ['ct1', 'Content Type no 1'], ['ct2', 'Content Type no 2'], - ['ct3', 'Content Type no 3'] + ['ct3', 'Content Type no 3'], ]; const enabled = ['ct1', 'ct3']; @@ -71,8 +71,8 @@ describe('target-state', () => { EditorInterface: { ct1: { sidebar: { position: 2 } }, ct2: {}, - ct3: { sidebar: { position: 2 } } - } + ct3: { sidebar: { position: 2 } }, + }, }); }); }); diff --git a/apps/netlify/src/config.js b/apps/netlify/src/config.js index aa94d27be09..f73c27a4ad5 100644 --- a/apps/netlify/src/config.js +++ b/apps/netlify/src/config.js @@ -1,8 +1,8 @@ -const readCsvParam = csv => +const readCsvParam = (csv) => (csv || '') .split(',') - .map(val => val.trim()) - .filter(val => val.length > 0); + .map((val) => val.trim()) + .filter((val) => val.length > 0); export function configToParameters(config) { const flat = { @@ -13,9 +13,9 @@ export function configToParameters(config) { names: (acc.names || []).concat([site.name]), siteIds: (acc.siteIds || []).concat([site.netlifySiteId]), siteNames: (acc.siteNames || []).concat([site.netlifySiteName]), - siteUrls: (acc.siteUrls || []).concat([site.netlifySiteUrl]) + siteUrls: (acc.siteUrls || []).concat([site.netlifySiteUrl]), }; - }, {}) + }, {}), }; return Object.keys(flat).reduce((acc, key) => { @@ -39,8 +39,8 @@ export function parametersToConfig(parameters) { name: names[i], netlifySiteId: siteIds[i], netlifySiteName: siteNames[i], - netlifySiteUrl: siteUrls[i] + netlifySiteUrl: siteUrls[i], }; - }) + }), }; } diff --git a/apps/netlify/src/config.spec.js b/apps/netlify/src/config.spec.js index 1ea0fb00c01..76e6c9d685f 100644 --- a/apps/netlify/src/config.spec.js +++ b/apps/netlify/src/config.spec.js @@ -8,16 +8,16 @@ const config = { name: 'Site 1', netlifySiteId: 'id1', netlifySiteName: 'foo-bar', - netlifySiteUrl: 'https://foo-bar.netlify.com' + netlifySiteUrl: 'https://foo-bar.netlify.com', }, { buildHookId: 'bh2', name: 'Site 2', netlifySiteId: 'id2', netlifySiteName: 'bar-baz', - netlifySiteUrl: 'https://bar-baz.netlify.com' - } - ] + netlifySiteUrl: 'https://bar-baz.netlify.com', + }, + ], }; const parameters = { @@ -26,7 +26,7 @@ const parameters = { names: 'Site 1,Site 2', siteIds: 'id1,id2', siteNames: 'foo-bar,bar-baz', - siteUrls: 'https://foo-bar.netlify.com,https://bar-baz.netlify.com' + siteUrls: 'https://foo-bar.netlify.com,https://bar-baz.netlify.com', }; describe('config', () => { @@ -45,7 +45,7 @@ describe('config', () => { expect( parametersToConfig({ ...parameters, - names: ' Site 1 ,, Site 2,,,,' + names: ' Site 1 ,, Site 2,,,,', }) ).toEqual(config); }); diff --git a/apps/netlify/src/constants.js b/apps/netlify/src/constants.js index 29ce3fc58f8..6a76c9bce64 100644 --- a/apps/netlify/src/constants.js +++ b/apps/netlify/src/constants.js @@ -13,7 +13,7 @@ export const NETLIFY_STATE_TO_EVENT = { uploaded: EVENT_BUILD_STARTED, building: EVENT_BUILD_STARTED, ready: EVENT_BUILD_READY, - error: EVENT_BUILD_FAILED + error: EVENT_BUILD_FAILED, }; export const PUBNUB_PUBLISH_KEY = 'pub-c-a99421b9-4f21-467b-ac0c-d0292824e8e1'; diff --git a/apps/netlify/src/index.js b/apps/netlify/src/index.js index e75cc4a41fc..2b89659ca71 100644 --- a/apps/netlify/src/index.js +++ b/apps/netlify/src/index.js @@ -11,7 +11,7 @@ import '@contentful/forma-36-fcss/dist/styles.css'; import NeflifySidebar from './sidebar'; import NetlifyAppConfig from './app'; -init(sdk => { +init((sdk) => { const root = document.getElementById('root'); if (sdk.location.is(locations.LOCATION_APP_CONFIG)) { diff --git a/apps/netlify/src/sidebar/build-button.js b/apps/netlify/src/sidebar/build-button.js index b0af9f95fce..3f301ac48e1 100644 --- a/apps/netlify/src/sidebar/build-button.js +++ b/apps/netlify/src/sidebar/build-button.js @@ -16,19 +16,19 @@ const styles = { marginTop: tokens.spacingS, marginBottom: tokens.spacingM, fontSize: tokens.fontSizeS, - fontWeight: tokens.fontWeightNormal + fontWeight: tokens.fontWeightNormal, }), header: css({ display: 'flex', - marginBottom: tokens.spacingS - }) + marginBottom: tokens.spacingS, + }), }; export default class NeflifySidebarBuildButton extends React.Component { static propTypes = { site: PropTypes.object.isRequired, users: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired, - userId: PropTypes.string.isRequired + userId: PropTypes.string.isRequired, }; state = { history: [] }; @@ -50,7 +50,7 @@ export default class NeflifySidebarBuildButton extends React.Component { normalizeMessage.bind(null, site.netlifySiteId, this.props.users) ); - this.pubsub.addListener(msg => { + this.pubsub.addListener((msg) => { const inOrder = !isOutOfOrder(msg, this.state.history); const notDuplicate = !isDuplicate(msg, this.state.history); @@ -58,7 +58,7 @@ export default class NeflifySidebarBuildButton extends React.Component { this.setState(({ history }) => { return { history: [msg].concat(history), - ...messageToState(msg) + ...messageToState(msg), }; }); } @@ -74,7 +74,7 @@ export default class NeflifySidebarBuildButton extends React.Component { if (filteredHistory.length > 0) { this.setState({ history: filteredHistory, - ...messageToState(filteredHistory[0]) + ...messageToState(filteredHistory[0]), }); } @@ -91,7 +91,7 @@ export default class NeflifySidebarBuildButton extends React.Component { this.pubsub.publish({ contentful: true, event: EVENT_TRIGGERED, - userId: this.props.userId + userId: this.props.userId, }); const { buildHookId } = this.props.site; @@ -101,7 +101,7 @@ export default class NeflifySidebarBuildButton extends React.Component { if (!res.ok) { this.pubsub.publish({ contentful: true, - event: EVENT_TRIGGER_FAILED + event: EVENT_TRIGGER_FAILED, }); } }; diff --git a/apps/netlify/src/sidebar/index.js b/apps/netlify/src/sidebar/index.js index 988316dd44c..00a25b648b7 100644 --- a/apps/netlify/src/sidebar/index.js +++ b/apps/netlify/src/sidebar/index.js @@ -12,16 +12,16 @@ import { parametersToConfig } from '../config'; const styles = { previewContent: css({ display: 'flex', - alignContent: 'center' + alignContent: 'center', }), separator: css({ - marginTop: tokens.spacingS - }) + marginTop: tokens.spacingS, + }), }; export default class NetlifySidebar extends React.Component { static propTypes = { - sdk: PropTypes.object.isRequired + sdk: PropTypes.object.isRequired, }; constructor(props) { @@ -32,7 +32,7 @@ export default class NetlifySidebar extends React.Component { this.state = { users: [], sites, - selectedSiteIndex: 0 + selectedSiteIndex: 0, }; } @@ -47,7 +47,7 @@ export default class NetlifySidebar extends React.Component { this.setState({ users: items }); }; - selectSite = e => { + selectSite = (e) => { this.setState({ selectedSiteIndex: parseInt(e.target.value, 10) }); }; @@ -78,7 +78,8 @@ export default class NetlifySidebar extends React.Component { target="_blank" rel="noopener noreferrer" buttonType="muted" - isFullWidth> + isFullWidth + >
Open site
diff --git a/apps/netlify/src/sidebar/message-processor.js b/apps/netlify/src/sidebar/message-processor.js index 41ce8cdcb60..5d1dc9943c5 100644 --- a/apps/netlify/src/sidebar/message-processor.js +++ b/apps/netlify/src/sidebar/message-processor.js @@ -8,7 +8,7 @@ import { EVENT_BUILD_READY, EVENT_BUILD_FAILED, NETLIFY_EVENTS, - NETLIFY_STATE_TO_EVENT + NETLIFY_STATE_TO_EVENT, } from '../constants'; function isValidNetlifyMessage(msg, siteId) { @@ -22,7 +22,7 @@ function normalizeNetlifyMessage(msg, siteId) { event, siteId, buildId: msg.id, - error: msg.error_message + error: msg.error_message, }; } @@ -31,7 +31,7 @@ function isValidContentfulMessage(msg) { } function normalizeContentfulMessage(msg, users) { - const user = (users || []).find(u => u.sys.id === msg.userId); + const user = (users || []).find((u) => u.sys.id === msg.userId); const userName = user ? `${user.firstName || ''} ${user.lastName || ''}`.trim() : null; const normalized = { ...msg, userName }; @@ -93,7 +93,7 @@ export function messageToState(msg) { status: 'Triggering...', busy: true, ok: true, - info + info, }; } @@ -101,7 +101,7 @@ export function messageToState(msg) { return { busy: false, ok: false, - info: 'Try again! If the problem persists make sure the Netlify site still exists.' + info: 'Try again! If the problem persists make sure the Netlify site still exists.', }; } @@ -109,7 +109,7 @@ export function messageToState(msg) { return { status: 'Building...', busy: true, - ok: true + ok: true, }; } @@ -117,7 +117,7 @@ export function messageToState(msg) { return { busy: false, ok: true, - info: `Last built ${formattedTime}.` + info: `Last built ${formattedTime}.`, }; } @@ -125,7 +125,7 @@ export function messageToState(msg) { return { busy: false, ok: false, - info: msg.error || 'Unknown error. Try again!' + info: msg.error || 'Unknown error. Try again!', }; } diff --git a/apps/netlify/src/sidebar/message-processor.spec.js b/apps/netlify/src/sidebar/message-processor.spec.js index eac2a3749e6..639b8c43c16 100644 --- a/apps/netlify/src/sidebar/message-processor.spec.js +++ b/apps/netlify/src/sidebar/message-processor.spec.js @@ -2,7 +2,7 @@ import { normalizeMessage, isOutOfOrder, isDuplicate, - messageToState + messageToState, } from '../../src/sidebar/message-processor'; import { @@ -11,7 +11,7 @@ import { EVENT_TRIGGER_FAILED, EVENT_BUILD_STARTED, EVENT_BUILD_FAILED, - EVENT_BUILD_READY + EVENT_BUILD_READY, } from '../../src/constants'; describe('message-processor', () => { @@ -20,13 +20,13 @@ describe('message-processor', () => { const normalized = normalizeMessage('site-id', [], { id: 'build-id', site_id: 'site-id', - state: 'uploaded' + state: 'uploaded', }); expect(normalized).toEqual({ event: EVENT_BUILD_STARTED, buildId: 'build-id', - siteId: 'site-id' + siteId: 'site-id', }); }); @@ -34,7 +34,7 @@ describe('message-processor', () => { const normalized = normalizeMessage('site-id', [], { id: 'build-id', site_id: 'different-site', - state: 'uploaded' + state: 'uploaded', }); expect(normalized).toBeNull(); @@ -44,7 +44,7 @@ describe('message-processor', () => { const normalized = normalizeMessage('site-id', [], { id: 'build-id', site_id: 'site-id', - state: 'BOOM' + state: 'BOOM', }); expect(normalized).toBeNull(); @@ -54,41 +54,41 @@ describe('message-processor', () => { const normalized = normalizeMessage('site-id', [], { contentful: true, event: EVENT_TRIGGERED, - someProperty: 'hello world' + someProperty: 'hello world', }); expect(normalized).toEqual({ event: EVENT_TRIGGERED, someProperty: 'hello world', - userName: null + userName: null, }); }); it('resolves user name in Contentful events if possible', () => { const users = [ { sys: { id: 'uid1' }, firstName: 'Jakub', lastName: 'Jakub' }, - { sys: { id: 'uid2' }, firstName: 'David', lastName: 'David' } + { sys: { id: 'uid2' }, firstName: 'David', lastName: 'David' }, ]; const normalized = normalizeMessage('site-id', users, { contentful: true, event: EVENT_TRIGGERED, userId: 'uid2', - someProperty: 'hello world' + someProperty: 'hello world', }); expect(normalized).toEqual({ event: EVENT_TRIGGERED, someProperty: 'hello world', userId: 'uid2', - userName: 'David David' + userName: 'David David', }); }); it('ignores Contentful events for unsupported events', () => { const normalized = normalizeMessage('site-id', [], { contentful: true, - event: 'BOOM' + event: 'BOOM', }); expect(normalized).toBeNull(); @@ -103,16 +103,16 @@ describe('message-processor', () => { }); it('returns true if the build already failed or succeeded in the past', () => { - [EVENT_BUILD_FAILED, EVENT_BUILD_READY].forEach(event => { + [EVENT_BUILD_FAILED, EVENT_BUILD_READY].forEach((event) => { const result = isOutOfOrder( { event: EVENT_BUILD_STARTED, - buildId: 'build-id' + buildId: 'build-id', }, [ { event, buildId: 'some-other-build' }, { event: 'some-other-event', buildId: 'build-id' }, - { event, buildId: 'build-id' } + { event, buildId: 'build-id' }, ] ); @@ -124,9 +124,12 @@ describe('message-processor', () => { const result = isOutOfOrder( { event: EVENT_BUILD_STARTED, - buildId: 'b3' + buildId: 'b3', }, - [{ event: EVENT_BUILD_FAILED, buildId: 'b2' }, { event: EVENT_BUILD_READY, buildId: 'b1' }] + [ + { event: EVENT_BUILD_FAILED, buildId: 'b2' }, + { event: EVENT_BUILD_READY, buildId: 'b1' }, + ] ); expect(result).toBe(false); @@ -144,7 +147,7 @@ describe('message-processor', () => { const msg = { event: NETLIFY_EVENTS[0], buildId: 'build-id' }; const result = isDuplicate(msg, [ { event: NETLIFY_EVENTS[1], buildId: 'build-id' }, - { event: NETLIFY_EVENTS[0], buildId: 'other-build' } + { event: NETLIFY_EVENTS[0], buildId: 'other-build' }, ]); expect(result).toBe(false); }); @@ -162,14 +165,14 @@ describe('message-processor', () => { it('produces state for "triggered" message', () => { const state = messageToState({ t, - event: EVENT_TRIGGERED + event: EVENT_TRIGGERED, }); expect(state).toEqual({ busy: true, info: expect.stringMatching(/at 2:26:02 PM/), ok: true, - status: 'Triggering...' + status: 'Triggering...', }); }); @@ -177,53 +180,53 @@ describe('message-processor', () => { const state = messageToState({ t, event: EVENT_TRIGGERED, - userName: 'Jakub' + userName: 'Jakub', }); expect(state).toEqual({ busy: true, info: expect.stringMatching(/by Jakub.+at 2:26:02 PM/), ok: true, - status: 'Triggering...' + status: 'Triggering...', }); }); it('produces state for "tiggering failed" message', () => { const state = messageToState({ t, - event: EVENT_TRIGGER_FAILED + event: EVENT_TRIGGER_FAILED, }); expect(state).toEqual({ busy: false, ok: false, - info: 'Try again! If the problem persists make sure the Netlify site still exists.' + info: 'Try again! If the problem persists make sure the Netlify site still exists.', }); }); it('produces state for "build started" message', () => { const state = messageToState({ t, - event: EVENT_BUILD_STARTED + event: EVENT_BUILD_STARTED, }); expect(state).toEqual({ status: 'Building...', busy: true, - ok: true + ok: true, }); }); it('produces state for "build ready" message', () => { const state = messageToState({ t, - event: EVENT_BUILD_READY + event: EVENT_BUILD_READY, }); expect(state).toEqual({ busy: false, ok: true, - info: expect.stringMatching(/Last built.+at 2:26:02 PM/) + info: expect.stringMatching(/Last built.+at 2:26:02 PM/), }); }); @@ -231,13 +234,13 @@ describe('message-processor', () => { const state = messageToState({ t, event: EVENT_BUILD_FAILED, - error: 'Something wrong happened.' + error: 'Something wrong happened.', }); expect(state).toEqual({ busy: false, ok: false, - info: 'Something wrong happened.' + info: 'Something wrong happened.', }); }); }); diff --git a/apps/netlify/src/sidebar/pubnub-client.js b/apps/netlify/src/sidebar/pubnub-client.js index 7704d1a3dee..706bad4f870 100644 --- a/apps/netlify/src/sidebar/pubnub-client.js +++ b/apps/netlify/src/sidebar/pubnub-client.js @@ -2,7 +2,7 @@ import PubNub from 'pubnub'; import { PUBNUB_PUBLISH_KEY, PUBNUB_SUBSCRIBE_KEY } from '../constants'; -const isObject = val => typeof val === 'object' && val !== null && !Array.isArray(val); +const isObject = (val) => typeof val === 'object' && val !== null && !Array.isArray(val); function timetokenToDate(timetoken) { // timetokens arrive as strings @@ -33,25 +33,25 @@ export function createPubSub(channel, normalizeFn) { return { start, - addListener: fn => state.listeners.push(fn), + addListener: (fn) => state.listeners.push(fn), publish, getHistory, - stop + stop, }; function start() { state.instance = new PubNub({ publishKey: PUBNUB_PUBLISH_KEY, - subscribeKey: PUBNUB_SUBSCRIBE_KEY + subscribeKey: PUBNUB_SUBSCRIBE_KEY, }); state.mainListener = { message: ({ message, timetoken }) => { const normalized = normalize(message, timetoken, normalizeFn); if (isObject(normalized)) { - state.listeners.forEach(fn => fn(normalized)); + state.listeners.forEach((fn) => fn(normalized)); } - } + }, }; state.instance.addListener(state.mainListener); @@ -62,7 +62,7 @@ export function createPubSub(channel, normalizeFn) { return state.instance.publish({ message, channel, - storeInHistory: true + storeInHistory: true, }); } diff --git a/apps/netlify/src/sidebar/pubnub-client.spec.js b/apps/netlify/src/sidebar/pubnub-client.spec.js index 1ed40f0e37c..3de3e87f0e7 100644 --- a/apps/netlify/src/sidebar/pubnub-client.spec.js +++ b/apps/netlify/src/sidebar/pubnub-client.spec.js @@ -8,10 +8,10 @@ const timetoken2 = '15641267509990000000'; const entries = [ { timetoken, entry: { test: true } }, - { timetoken: timetoken2, entry: { hello: 'world' } } + { timetoken: timetoken2, entry: { hello: 'world' } }, ]; -const normalizeFn = obj => { +const normalizeFn = (obj) => { return Object.keys(obj).reduce((acc, key) => { return { ...acc, [key.toUpperCase()]: obj[key] }; }, {}); @@ -27,7 +27,7 @@ describe('pubnub-client', () => { subscribe: jest.fn(), unsubscribe: jest.fn(), publish: jest.fn(() => Promise.resolve('PUBNUB PUBLISH RESULT')), - history: jest.fn(() => Promise.resolve('PUBNUB HISTORY RESULT')) + history: jest.fn(() => Promise.resolve('PUBNUB HISTORY RESULT')), }; PubNub.mockImplementation(() => pubNubMock); @@ -35,31 +35,31 @@ describe('pubnub-client', () => { describe('start', () => { it('creates a PubNub instance, registers a listener and subscribes to a channel', () => { - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); pubsub.start(); expect(PubNub).toHaveBeenCalledTimes(1); expect(PubNub).toHaveBeenCalledWith({ publishKey: expect.any(String), - subscribeKey: expect.any(String) + subscribeKey: expect.any(String), }); expect(pubNubMock.addListener).toHaveBeenCalledTimes(1); expect(pubNubMock.addListener).toHaveBeenCalledWith({ - message: expect.any(Function) + message: expect.any(Function), }); expect(pubNubMock.subscribe).toHaveBeenCalledTimes(1); expect(pubNubMock.subscribe).toHaveBeenCalledWith({ - channels: ['channel'] + channels: ['channel'], }); }); }); describe('publish', () => { it('publishes a message', async () => { - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); pubsub.start(); @@ -69,7 +69,7 @@ describe('pubnub-client', () => { expect(pubNubMock.publish).toHaveBeenCalledWith({ message: { test: true }, channel: 'channel', - storeInHistory: true + storeInHistory: true, }); expect(result).toBe('PUBNUB PUBLISH RESULT'); @@ -78,24 +78,24 @@ describe('pubnub-client', () => { describe('stop', () => { it('removes listeners and unsubscribes from a channel', () => { - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); pubsub.start(); pubsub.stop(); expect(pubNubMock.removeListener).toHaveBeenCalledTimes(1); expect(pubNubMock.removeListener).toHaveBeenCalledWith({ - message: expect.any(Function) + message: expect.any(Function), }); expect(pubNubMock.unsubscribe).toHaveBeenCalledTimes(1); expect(pubNubMock.unsubscribe).toHaveBeenCalledWith({ - channels: ['channel'] + channels: ['channel'], }); }); it('does nothing if was not started yet', () => { - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); pubsub.stop(); @@ -106,7 +106,7 @@ describe('pubnub-client', () => { describe('addListener', () => { it('registers a function to be called on message', () => { - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); const listener = jest.fn(); pubsub.addListener(listener); pubsub.start(); @@ -117,7 +117,7 @@ describe('pubnub-client', () => { expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith({ test: true, - t: expect.any(Date) + t: expect.any(Date), }); }); @@ -134,12 +134,12 @@ describe('pubnub-client', () => { expect(listener).toHaveBeenCalledWith({ TEST: true, HELLO: 'world', - t: expect.any(Date) + t: expect.any(Date), }); }); it('does not call registered listener for an invalid message', () => { - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); const listener = jest.fn(); pubsub.addListener(listener); pubsub.start(); @@ -153,7 +153,7 @@ describe('pubnub-client', () => { describe('getHistory', () => { it('gets PubNub history for a channel', () => { - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); pubsub.start(); pubsub.getHistory(); @@ -161,21 +161,21 @@ describe('pubnub-client', () => { expect(pubNubMock.history).toHaveBeenCalledWith({ channel: 'channel', count: 25, - stringifiedTimeToken: true + stringifiedTimeToken: true, }); }); it('returns history in reverse order', async () => { pubNubMock.history.mockImplementation(() => Promise.resolve({ messages: entries })); - const pubsub = createPubSub('channel', x => x); + const pubsub = createPubSub('channel', (x) => x); pubsub.start(); const history = await pubsub.getHistory(); expect(history).toEqual([ { t: expect.any(Date), hello: 'world' }, - { t: expect.any(Date), test: true } + { t: expect.any(Date), test: true }, ]); }); @@ -189,7 +189,7 @@ describe('pubnub-client', () => { expect(history).toEqual([ { t: expect.any(Date), HELLO: 'world' }, - { t: expect.any(Date), TEST: true } + { t: expect.any(Date), TEST: true }, ]); }); }); diff --git a/apps/optimizely/src/AppPage.spec.js b/apps/optimizely/src/AppPage.spec.js index 9d9ce20e67c..054a2a3e2d4 100644 --- a/apps/optimizely/src/AppPage.spec.js +++ b/apps/optimizely/src/AppPage.spec.js @@ -10,9 +10,9 @@ const basicProps = { openAuth: () => {}, accessToken: '', client: { - getProjects: () => Promise.resolve(projectData) + getProjects: () => Promise.resolve(projectData), }, - sdk: mockProps.sdk + sdk: mockProps.sdk, }; configure({ testIdAttribute: 'data-test-id' }); @@ -28,15 +28,15 @@ describe('AppPage', () => { const sdk = { ...basicProps.sdk, space: { - getContentTypes: jest.fn(() => Promise.resolve(contentTypesData)) + getContentTypes: jest.fn(() => Promise.resolve(contentTypesData)), }, app: { setReady: jest.fn(), getParameters: jest.fn(() => Promise.resolve({ optimizelyProjectId: '123' })), - onConfigure: jest.fn(fn => { + onConfigure: jest.fn((fn) => { configFunc = fn; - }) - } + }), + }, }; const props = { ...basicProps, accessToken: '123', sdk }; diff --git a/apps/optimizely/src/AppPage/Config.js b/apps/optimizely/src/AppPage/Config.js index 12fb46127fb..ad874086f68 100644 --- a/apps/optimizely/src/AppPage/Config.js +++ b/apps/optimizely/src/AppPage/Config.js @@ -10,7 +10,7 @@ export default class Config extends React.Component { client: PropTypes.object.isRequired, allContentTypes: PropTypes.array.isRequired, config: PropTypes.object.isRequired, - updateConfig: PropTypes.func.isRequired + updateConfig: PropTypes.func.isRequired, }; constructor(props) { @@ -18,7 +18,7 @@ export default class Config extends React.Component { this.state = { loadingProjects: true, - allProjects: null + allProjects: null, }; } @@ -28,38 +28,38 @@ export default class Config extends React.Component { // eslint-disable-next-line react/no-did-mount-set-state this.setState({ allProjects, - loadingProjects: false + loadingProjects: false, }); } - onProjectChange = event => { + onProjectChange = (event) => { this.props.updateConfig({ - optimizelyProjectId: event.target.value + optimizelyProjectId: event.target.value, }); }; - onDeleteContentType = contentTypeId => { + onDeleteContentType = (contentTypeId) => { const { contentTypes } = this.props.config; const newContentTypes = { - ...contentTypes + ...contentTypes, }; delete newContentTypes[contentTypeId]; this.props.updateConfig({ - contentTypes: newContentTypes + contentTypes: newContentTypes, }); }; - onAddContentType = contentTypeConfig => { + onAddContentType = (contentTypeConfig) => { const { contentTypes } = this.props.config; this.props.updateConfig({ contentTypes: { ...contentTypes, - ...contentTypeConfig - } + ...contentTypeConfig, + }, }); }; diff --git a/apps/optimizely/src/AppPage/Connect.js b/apps/optimizely/src/AppPage/Connect.js index d7b260e76b2..91f2a2bbdab 100644 --- a/apps/optimizely/src/AppPage/Connect.js +++ b/apps/optimizely/src/AppPage/Connect.js @@ -9,8 +9,8 @@ import ConnectButton from '../ConnectButton'; const styles = { spacing: css({ - margin: `${tokens.spacingM} 0` - }) + margin: `${tokens.spacingM} 0`, + }), }; export default function Connect({ openAuth }) { @@ -27,5 +27,5 @@ export default function Connect({ openAuth }) { } Connect.propTypes = { - openAuth: PropTypes.func.isRequired + openAuth: PropTypes.func.isRequired, }; diff --git a/apps/optimizely/src/AppPage/ContentTypes.js b/apps/optimizely/src/AppPage/ContentTypes.js index 3f1440cb67c..877ce6ea06e 100644 --- a/apps/optimizely/src/AppPage/ContentTypes.js +++ b/apps/optimizely/src/AppPage/ContentTypes.js @@ -16,30 +16,30 @@ import { Option, Modal, Typography, - SectionHeading + SectionHeading, } from '@contentful/forma-36-react-components'; import ReferenceField, { hasFieldLinkValidations } from './ReferenceField'; import RefToolTip from './RefToolTip'; const styles = { table: css({ - marginTop: tokens.spacingL + marginTop: tokens.spacingL, }), link: css({ - marginRight: tokens.spacingS + marginRight: tokens.spacingS, }), contentTypeRow: css({ - gridTemplateColumns: 'auto 6rem' + gridTemplateColumns: 'auto 6rem', }), refList: css({ display: 'flex', flexDirection: 'row', - flexWrap: 'wrap' + flexWrap: 'wrap', }), sectionHeading: css({ marginTop: tokens.spacingL, - marginBottom: tokens.spacingM - }) + marginBottom: tokens.spacingM, + }), }; ContentTypes.propTypes = { @@ -47,7 +47,7 @@ ContentTypes.propTypes = { allContentTypes: PropTypes.array.isRequired, allReferenceFields: PropTypes.object.isRequired, onAddContentType: PropTypes.func.isRequired, - onDeleteContentType: PropTypes.func.isRequired + onDeleteContentType: PropTypes.func.isRequired, }; function isContentTypeValidSelection(contentType, addedContentTypes, isEditMode) { @@ -64,33 +64,33 @@ export default function ContentTypes({ allContentTypes, allReferenceFields, onAddContentType, - onDeleteContentType + onDeleteContentType, }) { const [selectedContentType, selectContentType] = useState(''); const [selectedReferenceFields, selectRef] = useState({}); const [modalOpen, toggleModal] = useState(false); const [isEditMode, setEditMode] = useState(false); - const contentType = allContentTypes.find(ct => ct.sys.id === selectedContentType); + const contentType = allContentTypes.find((ct) => ct.sys.id === selectedContentType); let referenceFields = []; let checkedFields = {}; - const addableContentTypes = allContentTypes.filter(ct => + const addableContentTypes = allContentTypes.filter((ct) => isContentTypeValidSelection(ct, addedContentTypes, isEditMode) ); if (contentType) { referenceFields = contentType.fields .filter( - field => + (field) => field.linkType === 'Entry' || (field.type === 'Array' && field.items.linkType === 'Entry') ) - .map(ref => ref.id); + .map((ref) => ref.id); checkedFields = referenceFields.reduce((acc, id) => { const checkedReferences = { ...allReferenceFields[selectedContentType], - ...selectedReferenceFields + ...selectedReferenceFields, }; if (checkedReferences) { @@ -101,11 +101,11 @@ export default function ContentTypes({ }, {}); } - const onSelectReferenceField = fields => { + const onSelectReferenceField = (fields) => { selectRef({ ...selectedReferenceFields, ...fields }); }; - const onSelectContentType = ctId => { + const onSelectContentType = (ctId) => { selectRef({}); selectContentType(ctId); }; @@ -130,7 +130,7 @@ export default function ContentTypes({ closeModal(); }; - const onEdit = ctId => { + const onEdit = (ctId) => { setEditMode(true); onSelectContentType(ctId); toggleModal(true); @@ -145,13 +145,14 @@ export default function ContentTypes({ buttonType="muted" onClick={() => toggleModal(true)} disabled={!addableContentTypes.length} - testId="add-content"> + testId="add-content" + > Add content type {addedContentTypes.length > 0 ? ( - {addedContentTypes.map(id => ( + {addedContentTypes.map((id) => ( onSelectContentType(e.target.value)} + onChange={(e) => onSelectContentType(e.target.value)} value={selectedContentType || ''} testId="content-type-selector" - required> + required + > - {addableContentTypes.map(ct => ( + {addableContentTypes.map((ct) => ( @@ -199,13 +201,13 @@ export default function ContentTypes({ Reference Fields
- {referenceFields.map(field => ( + {referenceFields.map((field) => ( onSelectReferenceField({ [field]: checked })} + onSelect={(checked) => onSelectReferenceField({ [field]: checked })} testId="reference-field" /> ))} @@ -218,7 +220,8 @@ export default function ContentTypes({
diff --git a/apps/optimizely/src/AppPage/ReferenceField.js b/apps/optimizely/src/AppPage/ReferenceField.js index 05c4628dd78..28a36d2b6df 100644 --- a/apps/optimizely/src/AppPage/ReferenceField.js +++ b/apps/optimizely/src/AppPage/ReferenceField.js @@ -9,15 +9,15 @@ import RefToolTip from './RefToolTip'; const styles = { container: css({ position: 'relative', - marginRight: '0.5rem' - }) + marginRight: '0.5rem', + }), }; ReferenceField.propTypes = { id: PropTypes.string.isRequired, checked: PropTypes.bool.isRequired, contentType: PropTypes.object.isRequired, - onSelect: PropTypes.func.isRequired + onSelect: PropTypes.func.isRequired, }; export default function ReferenceField({ id, checked, contentType, onSelect }) { @@ -30,7 +30,7 @@ export default function ReferenceField({ id, checked, contentType, onSelect }) { id={`reference-field-${id}`} checked={checked || disabled} disabled={disabled} - onChange={e => onSelect(e.target.checked)} + onChange={(e) => onSelect(e.target.checked)} labelText={field.name} labelIsLight={true} /> @@ -40,15 +40,15 @@ export default function ReferenceField({ id, checked, contentType, onSelect }) { } export function findFieldById(id, contentType) { - return contentType.fields.find(field => field.id === id); + return contentType.fields.find((field) => field.id === id); } export function getFieldLinkValidations(field) { - return get(field, ['items', 'validations'], field.validations).filter(v => v.linkContentType); + return get(field, ['items', 'validations'], field.validations).filter((v) => v.linkContentType); } export function getNonFieldLinkValidations(field) { - return get(field, ['items', 'validations'], field.validations).filter(v => !v.linkContentType); + return get(field, ['items', 'validations'], field.validations).filter((v) => !v.linkContentType); } export function hasFieldLinkValidations(field) { diff --git a/apps/optimizely/src/AppPage/ReferenceForm.js b/apps/optimizely/src/AppPage/ReferenceForm.js index e736a9e329f..e3490162c0e 100644 --- a/apps/optimizely/src/AppPage/ReferenceForm.js +++ b/apps/optimizely/src/AppPage/ReferenceForm.js @@ -7,25 +7,25 @@ import ReferenceField from './ReferenceField'; const styles = { referenceForm: css({ - marginTop: tokens.spacingM + marginTop: tokens.spacingM, }), referenceGrid: css({ display: 'grid', gridTemplateColumns: '15rem 15rem', - gridColumnGap: '1rem' - }) + gridColumnGap: '1rem', + }), }; ReferenceForm.propTypes = { fields: PropTypes.object.isRequired, allContentTypes: PropTypes.array.isRequired, selectedContentType: PropTypes.string.isRequired, - onSelect: PropTypes.func.isRequired + onSelect: PropTypes.func.isRequired, }; export default function ReferenceForm({ allContentTypes, selectedContentType, fields, onSelect }) { if (!fields) return null; - const contentTypeEntity = allContentTypes.find(ct => ct.sys.id === selectedContentType); + const contentTypeEntity = allContentTypes.find((ct) => ct.sys.id === selectedContentType); const ids = Object.keys(fields); return ( @@ -34,7 +34,7 @@ export default function ReferenceForm({ allContentTypes, selectedContentType, fi
{ids .filter((_, ind) => ind % 2 === 0) - .map(id => ( + .map((id) => ( {ids .filter((_, ind) => ind % 2 === 1) - .map(id => ( + .map((id) => ( ({ + (prevState) => ({ allContentTypes, config: { contentTypes: enabledContentTypes, optimizelyProjectId: currentParameters ? currentParameters.optimizelyProjectId - : prevState.optimizelyProjectId - } + : prevState.optimizelyProjectId, + }, }), () => app.setReady() ); @@ -112,7 +112,7 @@ export default class AppPage extends React.Component { } const needsVariationContainerInSpace = !this.state.allContentTypes.find( - ct => ct.sys.id === VARIATION_CONTAINER_ID + (ct) => ct.sys.id === VARIATION_CONTAINER_ID ); if (needsVariationContainerInSpace) { @@ -126,7 +126,7 @@ export default class AppPage extends React.Component { this.props.sdk.space .getContentTypes() - .then(data => this.setState({ allContentTypes: data.items })); + .then((data) => this.setState({ allContentTypes: data.items })); if (!res) { this.props.sdk.notifier.error('Something went wrong, please try again.'); @@ -135,20 +135,20 @@ export default class AppPage extends React.Component { return { parameters: { - optimizelyProjectId: config.optimizelyProjectId + optimizelyProjectId: config.optimizelyProjectId, }, targetState: { EditorInterface: { - [VARIATION_CONTAINER_ID]: { editor: true, sidebar: { position: 0 } } - } - } + [VARIATION_CONTAINER_ID]: { editor: true, sidebar: { position: 0 } }, + }, + }, }; }; createVariationContainerContentType = async () => { const variationContainer = await this.props.sdk.space.createContentType({ sys: { - id: VARIATION_CONTAINER_ID + id: VARIATION_CONTAINER_ID, }, name: 'Variation Container', displayField: 'experimentTitle', @@ -156,17 +156,17 @@ export default class AppPage extends React.Component { { id: 'experimentTitle', name: 'Experiment title', - type: 'Symbol' + type: 'Symbol', }, { id: 'experimentId', name: 'Experiment ID', - type: 'Symbol' + type: 'Symbol', }, { id: 'meta', name: 'Meta', - type: 'Object' + type: 'Object', }, { id: 'variations', @@ -175,15 +175,15 @@ export default class AppPage extends React.Component { items: { type: 'Link', validations: [], - linkType: 'Entry' - } + linkType: 'Entry', + }, }, { id: 'experimentKey', name: 'Experiment Key', - type: 'Symbol' - } - ] + type: 'Symbol', + }, + ], }); await this.props.sdk.space.updateContentType(variationContainer); @@ -199,13 +199,12 @@ export default class AppPage extends React.Component { for (const contentField of ct.fields) { const validations = contentField.type === 'Array' ? contentField.items.validations : contentField.validations; - const index = (validations || []).findIndex(v => v.linkContentType); + const index = (validations || []).findIndex((v) => v.linkContentType); if (index > -1) { const linkValidations = validations[index]; - const includesVariationContainer = linkValidations.linkContentType.includes( - VARIATION_CONTAINER_ID - ); + const includesVariationContainer = + linkValidations.linkContentType.includes(VARIATION_CONTAINER_ID); const fieldsToEnable = contentTypes[ct.sys.id] || {}; @@ -219,7 +218,7 @@ export default class AppPage extends React.Component { (!Object.keys(contentTypes).includes(ct.sys.id) || !fieldsToEnable[contentField.id]) ) { linkValidations.linkContentType = linkValidations.linkContentType.filter( - lct => lct !== VARIATION_CONTAINER_ID + (lct) => lct !== VARIATION_CONTAINER_ID ); hasChanges = true; } @@ -235,7 +234,7 @@ export default class AppPage extends React.Component { return true; } - const updates = output.map(ct => { + const updates = output.map((ct) => { return this.props.sdk.space.updateContentType(ct); }); @@ -253,7 +252,7 @@ export default class AppPage extends React.Component { for (const field of ct.fields) { if (field.type === 'Array' && field.items.linkType === 'Entry') { - output[field.id] = field.items.validations.some(val => + output[field.id] = field.items.validations.some((val) => val.linkContentType.includes(VARIATION_CONTAINER_ID) ); continue; @@ -262,13 +261,13 @@ export default class AppPage extends React.Component { if (field.type === 'Link' && field.linkType === 'Entry') { output[field.id] = field.validations.length === 0 || - field.validations.some(val => val.linkContentType.includes(VARIATION_CONTAINER_ID)); + field.validations.some((val) => val.linkContentType.includes(VARIATION_CONTAINER_ID)); } } const keys = Object.keys(output); - if (keys.some(key => output[key])) { + if (keys.some((key) => output[key])) { return { ...acc, [ct.sys.id]: output }; } @@ -276,12 +275,12 @@ export default class AppPage extends React.Component { }, {}); }; - updateConfig = config => { - this.setState(state => ({ + updateConfig = (config) => { + this.setState((state) => ({ config: { ...state.config, - ...config - } + ...config, + }, })); }; diff --git a/apps/optimizely/src/ConnectButton/OptimizelyLogo.js b/apps/optimizely/src/ConnectButton/OptimizelyLogo.js index aa9902ba7b9..074f62233dc 100644 --- a/apps/optimizely/src/ConnectButton/OptimizelyLogo.js +++ b/apps/optimizely/src/ConnectButton/OptimizelyLogo.js @@ -8,7 +8,8 @@ export default function ConnectButton() { data-name="Layer 1" width="216" height="42" - viewBox="0 0 216 42"> + viewBox="0 0 216 42" + > ConnectWithOptimizelyButton + buttonType="naked" + > ); } ConnectButton.propTypes = { - openAuth: PropTypes.func.isRequired + openAuth: PropTypes.func.isRequired, }; diff --git a/apps/optimizely/src/EditorPage.spec.js b/apps/optimizely/src/EditorPage.spec.js index 66aba19854a..1704c59fcf0 100644 --- a/apps/optimizely/src/EditorPage.spec.js +++ b/apps/optimizely/src/EditorPage.spec.js @@ -1,37 +1,37 @@ -import React from "react"; -import { render, wait, configure } from "@testing-library/react"; +import React from 'react'; +import { render, wait, configure } from '@testing-library/react'; -import mockProps from "./mockProps"; -import mockVariantData from "./mockData/mockVariantData"; +import mockProps from './mockProps'; +import mockVariantData from './mockData/mockVariantData'; -import EditorPage from "../src/EditorPage"; +import EditorPage from '../src/EditorPage'; -configure({ testIdAttribute: "data-test-id" }); +configure({ testIdAttribute: 'data-test-id' }); -describe("EditorPage", () => { +describe('EditorPage', () => { let sdk; beforeEach(() => { sdk = mockProps.sdk; - sdk.entry.fields.experimentId.onValueChanged = fn => () => {}; + sdk.entry.fields.experimentId.onValueChanged = (fn) => () => {}; Date.now = jest.fn(() => 100); Math.random = jest.fn(() => 0.5); }); - it("should show the reauth modal when no client is available", () => { + it('should show the reauth modal when no client is available', () => { const { getByTestId } = render(); - expect(getByTestId("reconnect-optimizely")).toMatchSnapshot(); + expect(getByTestId('reconnect-optimizely')).toMatchSnapshot(); }); - it("should show the preemtive reconnect warning box", () => { + it('should show the preemtive reconnect warning box', () => { const expires = (Date.now() + 50000).toString(); const { getByTestId } = render( {}} expires={expires} />); - expect(getByTestId("preemptive-connect")).toMatchSnapshot(); + expect(getByTestId('preemptive-connect')).toMatchSnapshot(); }); - it("should show the experiment data when loaded", async () => { + it('should show the experiment data when loaded', async () => { const space = { getContentTypes: () => Promise.resolve(mockVariantData.contentTypes), getEntries: () => Promise.resolve(mockVariantData.entries), @@ -42,7 +42,7 @@ describe("EditorPage", () => { sdk = { ...sdk, space }; sdk.entry.fields.experimentTitle.setValue = jest.fn(); - sdk.entry.fields.experimentId.onValueChanged = fn => { + sdk.entry.fields.experimentId.onValueChanged = (fn) => { valueChange = fn; return () => {}; }; @@ -61,7 +61,7 @@ describe("EditorPage", () => { ); await wait(); - valueChange("15049730511"); + valueChange('15049730511'); await wait(); diff --git a/apps/optimizely/src/EditorPage/index.js b/apps/optimizely/src/EditorPage/index.js index 0500b1bb2a6..b4a18c244b4 100644 --- a/apps/optimizely/src/EditorPage/index.js +++ b/apps/optimizely/src/EditorPage/index.js @@ -17,18 +17,18 @@ import ConnectButton from '../ConnectButton'; const styles = { root: css({ - margin: tokens.spacingXl + margin: tokens.spacingXl, }), paragraph: css({ - marginBottom: tokens.spacingM + marginBottom: tokens.spacingM, }), link: css({ cursor: 'pointer', - textDecoration: 'underline' - }) + textDecoration: 'underline', + }), }; -const methods = state => { +const methods = (state) => { return { setInitialData({ experiments, contentTypes, referenceInfo }) { state.experiments = experiments; @@ -57,16 +57,16 @@ const methods = state => { }, updateExperiment(id, experiment) { const index = state.experiments.findIndex( - experiment => experiment.id.toString() === id.toString() + (experiment) => experiment.id.toString() === id.toString() ); if (index !== -1) { state.experiments[index] = experiment; } - } + }, }; }; -const getInitialValue = sdk => ({ +const getInitialValue = (sdk) => ({ loaded: false, error: false, experiments: [], @@ -75,7 +75,7 @@ const getInitialValue = sdk => ({ variations: sdk.entry.fields.variations.getValue() || [], experimentId: sdk.entry.fields.experimentId.getValue(), entries: {}, - experimentsResults: {} + experimentsResults: {}, }); const fetchInitialData = async (sdk, client) => { @@ -84,7 +84,7 @@ const fetchInitialData = async (sdk, client) => { const [contentTypesRes, entriesRes, experiments] = await Promise.all([ space.getContentTypes({ order: 'name', limit: 1000 }), space.getEntries({ links_to_entry: ids.entry, skip: 0, limit: 1000 }), - client.getExperiments() + client.getExperiments(), ]); return { @@ -95,8 +95,8 @@ const fetchInitialData = async (sdk, client) => { entries: entriesRes.items, variationContainerId: ids.entry, variationContainerContentTypeId: ids.contentType, - defaultLocale: locales.default - }) + defaultLocale: locales.default, + }), }; }; @@ -111,7 +111,7 @@ export default function EditorPage(props) { const [showAuth, setShowAuth] = useState(isCloseToExpiration(props.expires)); const experiment = state.experiments.find( - experiment => experiment.id.toString() === state.experimentId + (experiment) => experiment.id.toString() === state.experimentId ); /** @@ -119,7 +119,7 @@ export default function EditorPage(props) { */ useEffect(() => { fetchInitialData(props.sdk, props.client) - .then(data => { + .then((data) => { actions.setInitialData(data); return data; }) @@ -135,7 +135,7 @@ export default function EditorPage(props) { if (state.experimentId) { props.client .getExperiment(state.experimentId) - .then(experiment => { + .then((experiment) => { actions.updateExperiment(state.experimentId, experiment); return experiment; }) @@ -154,13 +154,15 @@ export default function EditorPage(props) { * Subscribe for changes in entry */ useEffect(() => { - const unsubsribeExperimentChange = props.sdk.entry.fields.experimentId.onValueChanged(data => { - actions.setExperimentId(data); - }); - const unsubscribeVariationsChange = props.sdk.entry.fields.variations.onValueChanged(data => { + const unsubsribeExperimentChange = props.sdk.entry.fields.experimentId.onValueChanged( + (data) => { + actions.setExperimentId(data); + } + ); + const unsubscribeVariationsChange = props.sdk.entry.fields.variations.onValueChanged((data) => { actions.setVariations(data || []); }); - const unsubscribeMetaChange = props.sdk.entry.fields.meta.onValueChanged(data => { + const unsubscribeMetaChange = props.sdk.entry.fields.meta.onValueChanged((data) => { actions.setMeta(data || {}); }); return () => { @@ -172,7 +174,7 @@ export default function EditorPage(props) { actions, props.sdk.entry.fields.experimentId, props.sdk.entry.fields.meta, - props.sdk.entry.fields.variations + props.sdk.entry.fields.variations, ]); /** @@ -192,7 +194,7 @@ export default function EditorPage(props) { if (state.loaded && experiment) { props.client .getExperimentResults(experiment.id) - .then(results => { + .then((results) => { actions.setExperimentResults(experiment.id, results); return results; }) @@ -200,13 +202,13 @@ export default function EditorPage(props) { } }, [actions, experiment, props.client, state.loaded]); - const getExperimentResults = experiment => { + const getExperimentResults = (experiment) => { if (!experiment) { return undefined; } return { url: props.client.getResultsUrl(experiment.campaign_id, experiment.id), - results: state.experimentsResults[experiment.id] + results: state.experimentsResults[experiment.id], }; }; @@ -214,16 +216,16 @@ export default function EditorPage(props) { * Handlers */ - const onChangeExperiment = value => { + const onChangeExperiment = (value) => { props.sdk.entry.fields.meta.setValue({}); props.sdk.entry.fields.experimentId.setValue(value.experimentId); props.sdk.entry.fields.experimentKey.setValue(value.experimentKey); }; - const onLinkVariation = async variation => { + const onLinkVariation = async (variation) => { const data = await props.sdk.dialogs.selectSingleEntry({ locale: props.sdk.locales.default, - contentTypes: state.referenceInfo.linkContentTypes + contentTypes: state.referenceInfo.linkContentTypes, }); if (!data) { @@ -234,7 +236,7 @@ export default function EditorPage(props) { const meta = props.sdk.entry.fields.meta.getValue() || {}; props.sdk.entry.fields.meta.setValue({ ...meta, - [variation.key]: data.sys.id + [variation.key]: data.sys.id, }); props.sdk.entry.fields.variations.setValue([ ...values, @@ -242,19 +244,19 @@ export default function EditorPage(props) { sys: { type: 'Link', id: data.sys.id, - linkType: 'Entry' - } - } + linkType: 'Entry', + }, + }, ]); }; - const onOpenEntry = entryId => { + const onOpenEntry = (entryId) => { props.sdk.navigator.openEntry(entryId, { slideIn: true }); }; const onCreateVariation = async (variation, contentTypeId) => { const data = await props.sdk.navigator.openNewEntry(contentTypeId, { - slideIn: true + slideIn: true, }); if (!data) { @@ -266,7 +268,7 @@ export default function EditorPage(props) { props.sdk.entry.fields.meta.setValue({ ...meta, - [variation.key]: data.entity.sys.id + [variation.key]: data.entity.sys.id, }); props.sdk.entry.fields.variations.setValue([ ...values, @@ -274,9 +276,9 @@ export default function EditorPage(props) { sys: { type: 'Link', id: data.entity.sys.id, - linkType: 'Entry' - } - } + linkType: 'Entry', + }, + }, ]); }; @@ -287,7 +289,7 @@ export default function EditorPage(props) { delete meta[variation.key]; } props.sdk.entry.fields.meta.setValue(meta); - props.sdk.entry.fields.variations.setValue(values.filter(item => item.sys.id !== entryId)); + props.sdk.entry.fields.variations.setValue(values.filter((item) => item.sys.id !== entryId)); }; const onClearVariations = () => { @@ -374,10 +376,10 @@ EditorPage.propTypes = { locales: PropTypes.object.isRequired, navigator: PropTypes.shape({ openEntry: PropTypes.func.isRequired, - openNewEntry: PropTypes.func.isRequired + openNewEntry: PropTypes.func.isRequired, }).isRequired, dialogs: PropTypes.shape({ - selectSingleEntry: PropTypes.func.isRequired + selectSingleEntry: PropTypes.func.isRequired, }).isRequired, entry: PropTypes.shape({ fields: PropTypes.shape({ @@ -385,14 +387,14 @@ EditorPage.propTypes = { experimentKey: PropTypes.object.isRequired, variations: PropTypes.object.isRequired, meta: PropTypes.object.isRequired, - experimentTitle: PropTypes.object.isRequired + experimentTitle: PropTypes.object.isRequired, }).isRequired, - getSys: PropTypes.func.isRequired + getSys: PropTypes.func.isRequired, }).isRequired, parameters: PropTypes.shape({ installation: PropTypes.shape({ - optimizelyProjectId: PropTypes.string.isRequired - }).isRequired - }).isRequired - }).isRequired + optimizelyProjectId: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + }).isRequired, }; diff --git a/apps/optimizely/src/EditorPage/reference-info.js b/apps/optimizely/src/EditorPage/reference-info.js index 891847791f9..16b97329476 100644 --- a/apps/optimizely/src/EditorPage/reference-info.js +++ b/apps/optimizely/src/EditorPage/reference-info.js @@ -5,7 +5,7 @@ export const COMBINED_LINK_VALIDATION_ALL = 'ALL'; export const COMBINED_LINK_VALIDATION_INTERSECTION = 'INTERSECTION'; export const COMBINED_LINK_VALIDATION_CONFLICT = 'CONFLICT'; -const isEntryLink = f => get(f, ['linkType']) === 'Entry'; +const isEntryLink = (f) => get(f, ['linkType']) === 'Entry'; // Find all field IDs in an entry given that are linking to the // variation container. @@ -15,7 +15,7 @@ function findLinkingFieldIds(entry, ct, variationContainerId) { const locales = Object.keys(get(entry, ['fields', field.id], {})); // For every locale in this field... - const found = !!locales.find(locale => { + const found = !!locales.find((locale) => { const value = get(entry, ['fields', field.id, locale]); // If the field-locale is a single Entry refenrence, check @@ -27,7 +27,7 @@ function findLinkingFieldIds(entry, ct, variationContainerId) { // If the field-locale is an array of Entry references, check // if at least one item links to the container. if (field.type === 'Array' && isEntryLink(field.items) && Array.isArray(value)) { - return !!value.find(item => get(item, ['sys', 'id']) === variationContainerId); + return !!value.find((item) => get(item, ['sys', 'id']) === variationContainerId); } // In any other case we're sure we're not linking to the @@ -77,7 +77,7 @@ function normalizeLinkContentTypeValidation( // If validation value is already an array only do the variation // container content type. if (Array.isArray(ctIds)) { - return ctIds.filter(ctId => ctId !== variationContainerContentTypeId); + return ctIds.filter((ctId) => ctId !== variationContainerContentTypeId); } return []; @@ -88,7 +88,7 @@ function normalizeLinkContentTypeValidation( // `hasLinkValidation` boolean flag. function prepareFieldLinkValidations(field, variationContainerContentTypeId) { const validations = get(field, getValidationsPath(field), []); - const linkContentTypeValidation = validations.find(v => v && v.linkContentType); + const linkContentTypeValidation = validations.find((v) => v && v.linkContentType); if (!linkContentTypeValidation) { return { hasLinkValidation: false }; @@ -99,7 +99,7 @@ function prepareFieldLinkValidations(field, variationContainerContentTypeId) { linkContentTypes: normalizeLinkContentTypeValidation( linkContentTypeValidation, variationContainerContentTypeId - ) + ), }; } @@ -111,13 +111,13 @@ function prepareReferenceInfoForEntry( variationContainerId, variationContainerContentTypeId ) { - return findLinkingFieldIds(entry, ct, variationContainerId).map(fieldId => { - const field = (ct.fields || []).find(f => f.id === fieldId); + return findLinkingFieldIds(entry, ct, variationContainerId).map((fieldId) => { + const field = (ct.fields || []).find((f) => f.id === fieldId); return { id: field.id, name: field.name, - ...prepareFieldLinkValidations(field, variationContainerContentTypeId) + ...prepareFieldLinkValidations(field, variationContainerContentTypeId), }; }); } @@ -129,7 +129,7 @@ function prepareReferenceInfoForEntry( // - conflicting if intersection results in an empty array // - all content types otherwise function combineLinkValidations(linkValidations, ctMap) { - const getCtName = ctId => get(ctMap, [ctId, 'name'], 'Untitled'); + const getCtName = (ctId) => get(ctMap, [ctId, 'name'], 'Untitled'); if (linkValidations.length > 0) { const linkValidationsIntersection = intersection(...linkValidations); @@ -137,7 +137,7 @@ function combineLinkValidations(linkValidations, ctMap) { return { combinedLinkValidationType: COMBINED_LINK_VALIDATION_INTERSECTION, linkContentTypes: linkValidationsIntersection, - linkContentTypeNames: linkValidationsIntersection.map(getCtName) + linkContentTypeNames: linkValidationsIntersection.map(getCtName), }; } @@ -148,15 +148,15 @@ function combineLinkValidations(linkValidations, ctMap) { return { combinedLinkValidationType: COMBINED_LINK_VALIDATION_ALL, linkContentTypes: allContentTypeIds, - linkContentTypeNames: allContentTypeIds.map(getCtName) + linkContentTypeNames: allContentTypeIds.map(getCtName), }; } // Combine link validations for all fields in a single entry. function combineLinkValidtionsForEntry(entryReferenceInfo, ctMap) { const linkValidations = entryReferenceInfo - .filter(info => info.hasLinkValidation) - .map(info => info.linkContentTypes); + .filter((info) => info.hasLinkValidation) + .map((info) => info.linkContentTypes); return combineLinkValidations(linkValidations, ctMap); } @@ -164,7 +164,7 @@ function combineLinkValidtionsForEntry(entryReferenceInfo, ctMap) { // Combine link validations for all entries. function combineLinkValidationsForReferences(references, ctMap) { const hasConflict = !!references.find( - ref => ref.combinedLinkValidationType === COMBINED_LINK_VALIDATION_CONFLICT + (ref) => ref.combinedLinkValidationType === COMBINED_LINK_VALIDATION_CONFLICT ); // If there's at least one conflict combination is conflicting too. @@ -173,8 +173,8 @@ function combineLinkValidationsForReferences(references, ctMap) { } const linkValidations = references - .filter(ref => ref.combinedLinkValidationType === COMBINED_LINK_VALIDATION_INTERSECTION) - .map(ref => ref.linkContentTypes); + .filter((ref) => ref.combinedLinkValidationType === COMBINED_LINK_VALIDATION_INTERSECTION) + .map((ref) => ref.linkContentTypes); // Use the regular algorithm otherwise for all intersections. return combineLinkValidations(linkValidations, ctMap); @@ -185,11 +185,11 @@ export default function prepareReferenceInfo({ entries, variationContainerId, variationContainerContentTypeId, - defaultLocale + defaultLocale, }) { const ctMap = contentTypes.reduce((acc, ct) => ({ ...acc, [ct.sys.id]: ct }), {}); - const references = entries.map(entry => { + const references = entries.map((entry) => { const ctId = get(entry, ['sys', 'contentType', 'sys', 'id']); const ct = ctMap[ctId]; @@ -204,13 +204,13 @@ export default function prepareReferenceInfo({ id: entry.sys.id, title: get(entry, ['fields', ct.displayField, defaultLocale], 'Untitled'), contentTypeName: ct.name, - referencedFromFields: entryReferenceInfo.map(f => f.name), - ...combineLinkValidtionsForEntry(entryReferenceInfo, ctMap) + referencedFromFields: entryReferenceInfo.map((f) => f.name), + ...combineLinkValidtionsForEntry(entryReferenceInfo, ctMap), }; }); return { references, - ...combineLinkValidationsForReferences(references, ctMap) + ...combineLinkValidationsForReferences(references, ctMap), }; } diff --git a/apps/optimizely/src/EditorPage/subcomponents/constants.js b/apps/optimizely/src/EditorPage/subcomponents/constants.js index da03569c47e..fa180fb95fa 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/constants.js +++ b/apps/optimizely/src/EditorPage/subcomponents/constants.js @@ -3,5 +3,5 @@ export const Status = { AddContent: 'AddContent', PublishVariations: 'PublishVariations', StartExperiment: 'StartExperiment', - Finished: 'Finished' + Finished: 'Finished', }; diff --git a/apps/optimizely/src/EditorPage/subcomponents/experiment-section.js b/apps/optimizely/src/EditorPage/subcomponents/experiment-section.js index dc44eb77357..1feed9d7142 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/experiment-section.js +++ b/apps/optimizely/src/EditorPage/subcomponents/experiment-section.js @@ -6,26 +6,26 @@ import { SelectField, Option, Paragraph, - TextLink + TextLink, } from '@contentful/forma-36-react-components'; import tokens from '@contentful/forma-36-tokens'; import { ExperimentType } from './prop-types'; const styles = { heading: css({ - marginBottom: tokens.spacingL + marginBottom: tokens.spacingL, }), description: css({ marginTop: tokens.spacingS, color: tokens.gray600, whiteSpace: 'nowrap', overflow: 'hidden', - textOverflow: 'ellipsis' + textOverflow: 'ellipsis', }), clearDescription: css({ marginTop: tokens.spacingXs, - color: tokens.gray500 - }) + color: tokens.gray500, + }), }; const NOT_SELECTED = '-1'; @@ -40,36 +40,37 @@ export default function ExperimentSection(props) { labelText="Optimizely experiment" required value={props.experiment ? props.experiment.id.toString() : ''} - onChange={e => { + onChange={(e) => { const value = e.target.value; if (value === NOT_SELECTED) { props.onChangeExperiment({ experimentId: '', - experimentKey: '' + experimentKey: '', }); } else { const experiment = props.experiments.find( - experiment => experiment.id.toString() === value + (experiment) => experiment.id.toString() === value ); if (experiment) { props.onChangeExperiment({ experimentId: experiment.id.toString(), - experimentKey: experiment.key.toString() + experimentKey: experiment.key.toString(), }); } } }} selectProps={{ width: 'large', - isDisabled: props.disabled === true || props.loaded === false + isDisabled: props.disabled === true || props.loaded === false, }} id="experiment" - name="experiment"> + name="experiment" + > {props.loaded === false && } {props.loaded && ( - {props.experiments.map(experiment => ( + {props.experiments.map((experiment) => ( @@ -98,9 +99,9 @@ ExperimentSection.propTypes = { experiment: ExperimentType, experiments: PropTypes.arrayOf(ExperimentType.isRequired), onChangeExperiment: PropTypes.func.isRequired, - onClearVariations: PropTypes.func.isRequired + onClearVariations: PropTypes.func.isRequired, }; ExperimentSection.defaultProps = { - experiments: [] + experiments: [], }; diff --git a/apps/optimizely/src/EditorPage/subcomponents/prop-types.js b/apps/optimizely/src/EditorPage/subcomponents/prop-types.js index 4879bb24142..d53b1f0987b 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/prop-types.js +++ b/apps/optimizely/src/EditorPage/subcomponents/prop-types.js @@ -6,7 +6,7 @@ export const VariationType = PropTypes.shape({ weight: PropTypes.number, key: PropTypes.string.isRequired, variation_id: PropTypes.number.isRequired, - description: PropTypes.string + description: PropTypes.string, }); export const ExperimentType = PropTypes.shape({ @@ -15,5 +15,5 @@ export const ExperimentType = PropTypes.shape({ key: PropTypes.string.isRequired, description: PropTypes.string, status: PropTypes.string.isRequired, - variations: PropTypes.arrayOf(VariationType.isRequired).isRequired + variations: PropTypes.arrayOf(VariationType.isRequired).isRequired, }); diff --git a/apps/optimizely/src/EditorPage/subcomponents/references-section.js b/apps/optimizely/src/EditorPage/subcomponents/references-section.js index a6e557880c0..a17bc78ad80 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/references-section.js +++ b/apps/optimizely/src/EditorPage/subcomponents/references-section.js @@ -7,25 +7,25 @@ import { Tooltip, SkeletonContainer, SkeletonBodyText, - Paragraph + Paragraph, } from '@contentful/forma-36-react-components'; import tokens from '@contentful/forma-36-tokens'; const styles = { heading: css({ - marginBottom: tokens.spacingL + marginBottom: tokens.spacingL, }), container: css({ display: 'flex', - marginBottom: `-${tokens.spacingM}` + marginBottom: `-${tokens.spacingM}`, }), item: css({ marginRight: tokens.spacingM, - marginBottom: tokens.spacingM + marginBottom: tokens.spacingM, }), emptyParagraph: css({ - marginBottom: tokens.spacingM - }) + marginBottom: tokens.spacingM, + }), }; function ReferenceItem(props) { @@ -39,7 +39,7 @@ function ReferenceItem(props) { } ReferenceItem.propTypes = { entry: PropTypes.object.isRequired, - onClick: PropTypes.func.isRequired + onClick: PropTypes.func.isRequired, }; function Container(props) { @@ -54,13 +54,13 @@ function Container(props) { } Container.propTypes = { - children: PropTypes.any + children: PropTypes.any, }; export default function ReferencesSection(props) { - const onItemClick = id => () => { + const onItemClick = (id) => () => { props.sdk.navigator.openEntry(id, { - slideIn: true + slideIn: true, }); }; @@ -95,9 +95,9 @@ export default function ReferencesSection(props) { ReferencesSection.propTypes = { loaded: PropTypes.bool.isRequired, references: PropTypes.array, - sdk: PropTypes.object.isRequired + sdk: PropTypes.object.isRequired, }; ReferencesSection.defaultProps = { - references: [] + references: [], }; diff --git a/apps/optimizely/src/EditorPage/subcomponents/section-splitter.js b/apps/optimizely/src/EditorPage/subcomponents/section-splitter.js index fe604f791fd..3e427e85ac4 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/section-splitter.js +++ b/apps/optimizely/src/EditorPage/subcomponents/section-splitter.js @@ -8,8 +8,8 @@ const styles = { marginBottom: tokens.spacingL, border: 0, height: '1px', - backgroundColor: tokens.gray300 - }) + backgroundColor: tokens.gray300, + }), }; export default function SectionSplitter() { diff --git a/apps/optimizely/src/EditorPage/subcomponents/status-bar.js b/apps/optimizely/src/EditorPage/subcomponents/status-bar.js index f5116275798..5dde12687a6 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/status-bar.js +++ b/apps/optimizely/src/EditorPage/subcomponents/status-bar.js @@ -8,27 +8,27 @@ import { getEntryStatus } from './utils'; const styles = { note: css({ - marginBottom: tokens.spacingL + marginBottom: tokens.spacingL, }), container: css({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', - maxWidth: 1000 + maxWidth: 1000, }), item: css({ display: 'flex', alignItems: 'center', fontSize: tokens.fontSizeM, - color: tokens.gray700 + color: tokens.gray700, }), itemSeparator: css({ marginLeft: tokens.spacingM, - marginRight: tokens.spacingM + marginRight: tokens.spacingM, }), itemIcon: css({ - marginRight: tokens.spacingS - }) + marginRight: tokens.spacingS, + }), }; function StatusItem(props) { @@ -47,7 +47,7 @@ function StatusItem(props) { StatusItem.propTypes = { children: PropTypes.string, - active: PropTypes.bool + active: PropTypes.bool, }; function StatusSeparator() { @@ -89,7 +89,7 @@ export default function StatusBar(props) { [Status.SelectExperiment]: false, [Status.StartExperiment]: false, [Status.AddContent]: false, - [Status.PublishVariations]: false + [Status.PublishVariations]: false, }; if (props.loaded) { @@ -113,5 +113,5 @@ StatusBar.propTypes = { loaded: PropTypes.bool.isRequired, experiment: PropTypes.object, variations: PropTypes.array, - entries: PropTypes.object + entries: PropTypes.object, }; diff --git a/apps/optimizely/src/EditorPage/subcomponents/utils.js b/apps/optimizely/src/EditorPage/subcomponents/utils.js index fbb4894c192..400f75a7159 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/utils.js +++ b/apps/optimizely/src/EditorPage/subcomponents/utils.js @@ -1,6 +1,6 @@ import get from 'lodash.get'; -export const getEntryStatus = sys => { +export const getEntryStatus = (sys) => { if (sys.archivedVersion) { return 'archived'; } else if (sys.publishedVersion) { @@ -16,15 +16,15 @@ export const getEntryStatus = sys => { export const getAdditionalEntryInformation = (entry, allContentTypes, defaultLocale) => { const contentTypeId = get(entry, ['sys', 'contentType', 'sys', 'id']); - const contentType = allContentTypes.find(contentType => contentType.sys.id === contentTypeId); + const contentType = allContentTypes.find((contentType) => contentType.sys.id === contentTypeId); if (!contentType) { throw new Error(`Content type #${contentTypeId} is not present`); } const displayField = contentType.displayField; const descriptionFieldType = contentType.fields - .filter(field => field.id !== displayField) - .find(field => field.type === 'Text'); + .filter((field) => field.id !== displayField) + .find((field) => field.type === 'Text'); const description = descriptionFieldType ? get(entry, ['fields', descriptionFieldType.id, defaultLocale], '') @@ -36,6 +36,6 @@ export const getAdditionalEntryInformation = (entry, allContentTypes, defaultLoc title, description, contentType: contentType.name, - status + status, }; }; diff --git a/apps/optimizely/src/EditorPage/subcomponents/variation-item.js b/apps/optimizely/src/EditorPage/subcomponents/variation-item.js index c8997d3fc86..096140e03e5 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/variation-item.js +++ b/apps/optimizely/src/EditorPage/subcomponents/variation-item.js @@ -9,7 +9,7 @@ import { DropdownList, DropdownListItem, Note, - TextLink + TextLink, } from '@contentful/forma-36-react-components'; import tokens from '@contentful/forma-36-tokens'; import { SDKContext, GlobalStateContext } from './all-context'; @@ -19,29 +19,29 @@ import { getAdditionalEntryInformation } from './utils'; const styles = { variationContainer: css({ - marginTop: tokens.spacingXl + marginTop: tokens.spacingXl, }), variationTitle: css({ small: { color: tokens.gray600, fontWeight: tokens.fontWeightNormal, marginLeft: tokens.spacingXs, - fontSize: tokens.fontSizeL - } + fontSize: tokens.fontSizeL, + }, }), variationDescription: css({ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', color: tokens.gray600, - marginTop: tokens.spacingXs + marginTop: tokens.spacingXs, }), entryCard: css({ - marginTop: tokens.spacingM + marginTop: tokens.spacingM, }), missingNote: css({ - marginTop: tokens.spacingM - }) + marginTop: tokens.spacingM, + }), }; function getPercentOfTraffic(variation) { @@ -60,10 +60,10 @@ function useEntryCard(id) { const fetchEntry = useCallback(() => { sdk.space .getEntry(id) - .then(entry => { + .then((entry) => { const data = { ...entry, - meta: getAdditionalEntryInformation(entry, allContentTypes, sdk.locales.default) + meta: getAdditionalEntryInformation(entry, allContentTypes, sdk.locales.default), }; actions.setEntry(id, data); return entry; @@ -84,7 +84,7 @@ function useEntryCard(id) { return { entry, loading: !entry, - error + error, }; } @@ -131,7 +131,7 @@ export function SelectedReference(props) { SelectedReference.propTypes = { sys: PropTypes.object.isRequired, onEditClick: PropTypes.func.isRequired, - onRemoveClick: PropTypes.func.isRequired + onRemoveClick: PropTypes.func.isRequired, }; export default function VariationItem(props) { @@ -170,7 +170,7 @@ export default function VariationItem(props) { )} {!props.sys && ( { + onCreate={(contentType) => { props.onCreateVariation(props.variation, contentType); }} onDuplicateClick={() => {}} @@ -190,5 +190,5 @@ VariationItem.propTypes = { onCreateVariation: PropTypes.func, onLinkVariation: PropTypes.func, onOpenEntry: PropTypes.func.isRequired, - onRemoveVariation: PropTypes.func.isRequired + onRemoveVariation: PropTypes.func.isRequired, }; diff --git a/apps/optimizely/src/EditorPage/subcomponents/variation-select.js b/apps/optimizely/src/EditorPage/subcomponents/variation-select.js index e6885768109..b5099c56ffb 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/variation-select.js +++ b/apps/optimizely/src/EditorPage/subcomponents/variation-select.js @@ -4,7 +4,7 @@ import { TextLink, Dropdown, DropdownList, - DropdownListItem + DropdownListItem, } from '@contentful/forma-36-react-components'; import { css } from 'emotion'; import tokens from '@contentful/forma-36-tokens'; @@ -12,11 +12,11 @@ import { GlobalStateContext } from './all-context'; const styles = { container: css({ - marginTop: tokens.spacingM + marginTop: tokens.spacingM, }), item: css({ - marginBottom: tokens.spacingXs - }) + marginBottom: tokens.spacingXs, + }), }; export default function VariationSelect(props) { @@ -39,10 +39,12 @@ export default function VariationSelect(props) { icon="Plus" onClick={() => { setShowDropdown(true); - }}> + }} + > Create entry and link - }> + } + > Select content type {linkContentTypes.map((value, index) => ( @@ -51,7 +53,8 @@ export default function VariationSelect(props) { onClick={() => { props.onCreate(value); setShowDropdown(false); - }}> + }} + > {linkContentTypeNames[index]} ))} @@ -69,5 +72,5 @@ export default function VariationSelect(props) { VariationSelect.propTypes = { onLinkExistingClick: PropTypes.func.isRequired, - onCreate: PropTypes.func.isRequired + onCreate: PropTypes.func.isRequired, }; diff --git a/apps/optimizely/src/EditorPage/subcomponents/variations-section.js b/apps/optimizely/src/EditorPage/subcomponents/variations-section.js index 196bca168a2..ef46ddecafa 100644 --- a/apps/optimizely/src/EditorPage/subcomponents/variations-section.js +++ b/apps/optimizely/src/EditorPage/subcomponents/variations-section.js @@ -5,7 +5,7 @@ import { Heading, Paragraph, SkeletonContainer, - SkeletonBodyText + SkeletonBodyText, } from '@contentful/forma-36-react-components'; import tokens from '@contentful/forma-36-tokens'; import { ExperimentType } from './prop-types'; @@ -14,11 +14,11 @@ import VariationItem from './variation-item'; const styles = { container: css({ marginTop: tokens.spacingS, - maxWidth: 1000 + maxWidth: 1000, }), unassignedHeader: css({ - marginTop: tokens.spacingXl - }) + marginTop: tokens.spacingXl, + }), }; function Container(props) { @@ -31,23 +31,23 @@ function Container(props) { } Container.propTypes = { - children: PropTypes.any + children: PropTypes.any, }; function mergeReferencesAndVariations(variationReferences, variations, meta) { const linkedReferences = Object.values(meta); - const mappedVariations = variations.map(variation => { + const mappedVariations = variations.map((variation) => { const entryId = meta[variation.key]; - const reference = variationReferences.find(item => item.sys.id === entryId); + const reference = variationReferences.find((item) => item.sys.id === entryId); return { variation, - sys: reference ? reference.sys : undefined + sys: reference ? reference.sys : undefined, }; }); const unmappedReferences = variationReferences.filter( - item => linkedReferences.includes(item.sys.id) === false + (item) => linkedReferences.includes(item.sys.id) === false ); return { mappedVariations, unmappedReferences }; @@ -83,7 +83,7 @@ export default function VariationsSection(props) { Content created in this experiment is only available for this experiment. - {mappedVariations.map(item => ( + {mappedVariations.map((item) => ( These entries have no corresponding variations in Optimizely. - {unmappedReferences.map(item => ( + {unmappedReferences.map((item) => ( { + is: jest.fn((l) => { return l === LOCATION; - }) + }), }, window: { startAutoResizer: jest.fn(), - stopAutoResizer: jest.fn() + stopAutoResizer: jest.fn(), }, ids: {}, space: {}, @@ -32,17 +32,17 @@ function mockSdk() { fields: { experimentId: { getValue: jest.fn(() => 'exp123'), - onValueChanged: jest.fn(() => mockUnsub) + onValueChanged: jest.fn(() => mockUnsub), }, meta: { getValue: jest.fn(), - onValueChanged: jest.fn(() => jest.fn()) + onValueChanged: jest.fn(() => jest.fn()), }, variations: { getValue: jest.fn(), - onValueChanged: jest.fn(() => jest.fn()) - } - } + onValueChanged: jest.fn(() => jest.fn()), + }, + }, }, contentType: { sys: { @@ -50,8 +50,8 @@ function mockSdk() { sys: { type: 'Link', linkType: 'Space', - id: 'cyu19ucaypb9' - } + id: 'cyu19ucaypb9', + }, }, id: 'variationContainer', type: 'ContentType', @@ -61,10 +61,10 @@ function mockSdk() { sys: { id: 'master', type: 'Link', - linkType: 'Environment' - } + linkType: 'Environment', + }, }, - revision: 3 + revision: 3, }, name: 'Variation Container', description: null, @@ -78,7 +78,7 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'experimentId', @@ -88,7 +88,7 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'meta', @@ -98,7 +98,7 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'variations', @@ -112,9 +112,9 @@ function mockSdk() { items: { type: 'Link', validations: [], - linkType: 'Entry' - } - } + linkType: 'Entry', + }, + }, ].concat( VALID_FIELDS ? { @@ -125,11 +125,11 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, } : [] - ) - } + ), + }, }; } diff --git a/apps/optimizely/src/Sidebar/index.js b/apps/optimizely/src/Sidebar/index.js index 272cb59397f..21500e11560 100644 --- a/apps/optimizely/src/Sidebar/index.js +++ b/apps/optimizely/src/Sidebar/index.js @@ -6,15 +6,15 @@ import { css } from 'emotion'; const styles = { button: css({ - marginBottom: tokens.spacingS - }) + marginBottom: tokens.spacingS, + }), }; const getExperimentUrl = (projectId, experimentId) => { return `https://app.optimizely.com/v2/projects/${projectId}/experiments/${experimentId}/variations`; }; -const getAllExperimentsUrl = projectId => { +const getAllExperimentsUrl = (projectId) => { return `https://app.optimizely.com/v2/projects/${projectId}/experiments`; }; @@ -30,7 +30,7 @@ export default function Sidebar(props) { }, [props.sdk.window]); useEffect(() => { - const unsubscribe = props.sdk.entry.fields.experimentId.onValueChanged(value => { + const unsubscribe = props.sdk.entry.fields.experimentId.onValueChanged((value) => { setExperimentId(value); }); return () => { @@ -49,7 +49,8 @@ export default function Sidebar(props) { disabled={!experimentId} href={getExperimentUrl(projectId, experimentId)} target="_blank" - data-test-id="view-experiment"> + data-test-id="view-experiment" + > View in Optimizely
@@ -69,14 +71,14 @@ Sidebar.propTypes = { sdk: PropTypes.shape({ entry: PropTypes.shape({ fields: PropTypes.shape({ - experimentId: PropTypes.object.isRequired - }).isRequired + experimentId: PropTypes.object.isRequired, + }).isRequired, }), window: PropTypes.object.isRequired, parameters: PropTypes.shape({ installation: PropTypes.shape({ - optimizelyProjectId: PropTypes.string.isRequired - }) - }) - }).isRequired + optimizelyProjectId: PropTypes.string.isRequired, + }), + }), + }).isRequired, }; diff --git a/apps/optimizely/src/components.spec.js b/apps/optimizely/src/components.spec.js index 40c272b6911..993eaaf8ea3 100644 --- a/apps/optimizely/src/components.spec.js +++ b/apps/optimizely/src/components.spec.js @@ -11,8 +11,8 @@ describe('error message components', () => { it('should match snapshot', () => { const mockSdk = { ids: { - extension: 'test-extension' - } + extension: 'test-extension', + }, }; const missingFields = [{ id: 'key', type: 'symbol' }]; @@ -45,7 +45,7 @@ describe('error message components', () => { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'experimentId', @@ -55,7 +55,7 @@ describe('error message components', () => { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'meta', @@ -65,7 +65,7 @@ describe('error message components', () => { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'variations', @@ -79,9 +79,9 @@ describe('error message components', () => { items: { type: 'Link', validations: [], - linkType: 'Entry' - } - } + linkType: 'Entry', + }, + }, ].concat( valid ? { @@ -92,16 +92,16 @@ describe('error message components', () => { required: false, validations: [], disabled: false, - omitted: false + omitted: false, } : [] - ) + ), }; }; it('should produce an invalid response', () => { expect(isValidContentType(makeFields())).toEqual([ false, - [{ id: 'experimentKey', type: 'Symbol' }] + [{ id: 'experimentKey', type: 'Symbol' }], ]); }); it('should produce a valid response', () => { diff --git a/apps/optimizely/src/errors-messages.js b/apps/optimizely/src/errors-messages.js index 37b03f8b2b0..d5a04e944ce 100644 --- a/apps/optimizely/src/errors-messages.js +++ b/apps/optimizely/src/errors-messages.js @@ -6,27 +6,27 @@ import { Note } from '@contentful/forma-36-react-components'; const styles = { container: css({ - margin: tokens.spacingL - }) + margin: tokens.spacingL, + }), }; const requiredFields = [ { id: 'experimentTitle', - type: 'Symbol' + type: 'Symbol', }, { id: 'experimentId', type: 'Symbol' }, { id: 'experimentKey', type: 'Symbol' }, { id: 'meta', type: 'Object' }, - { id: 'variations', type: 'Array' } + { id: 'variations', type: 'Array' }, ]; export function isValidContentType(contentType) { const missing = []; - requiredFields.forEach(item => { + requiredFields.forEach((item) => { const exists = contentType.fields.find( - field => field.id === item.id && field.type === item.type + (field) => field.id === item.id && field.type === item.type ); if (!exists) { missing.push(item); @@ -53,11 +53,11 @@ export function IncorrectContentType(props) {
Required:{' '} - {requiredFields.map(item => `${item.id} (${item.type})`).join(', ')} + {requiredFields.map((item) => `${item.id} (${item.type})`).join(', ')}
Missing:{' '} - {props.missingFields.map(item => `${item.id} (${item.type})`).join(',')} + {props.missingFields.map((item) => `${item.id} (${item.type})`).join(',')}
@@ -68,7 +68,7 @@ IncorrectContentType.propTypes = { missingFields: PropTypes.array.isRequired, sdk: PropTypes.shape({ ids: PropTypes.shape({ - extension: PropTypes.string - }).isRequired - }).isRequired + extension: PropTypes.string, + }).isRequired, + }).isRequired, }; diff --git a/apps/optimizely/src/index.js b/apps/optimizely/src/index.js index c38b3e19617..174333d3c8a 100644 --- a/apps/optimizely/src/index.js +++ b/apps/optimizely/src/index.js @@ -54,14 +54,14 @@ export default class App extends React.Component { sdk: PropTypes.shape({ contentType: PropTypes.object, location: PropTypes.shape({ - is: PropTypes.func.isRequired + is: PropTypes.func.isRequired, }), parameters: PropTypes.shape({ installation: PropTypes.shape({ - optimizelyProjectId: PropTypes.string.isRequired - }).isRequired - }).isRequired - }).isRequired + optimizelyProjectId: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + }).isRequired, }; constructor(props) { @@ -73,12 +73,12 @@ export default class App extends React.Component { this.state = { client: token ? this.makeClient(token) : null, accessToken: token, - expires + expires, }; this.listener = window.addEventListener( 'message', - event => { + (event) => { const { data, origin } = event; const { token, expires } = data; @@ -94,13 +94,13 @@ export default class App extends React.Component { ); } - makeClient = token => { + makeClient = (token) => { return new OptimizelyClient({ accessToken: token, project: this.props.sdk.parameters.installation.optimizelyProjectId, onReauth: () => { this.setState({ client: null }); - } + }, }); }; @@ -153,6 +153,6 @@ export default class App extends React.Component { } } -init(sdk => { +init((sdk) => { render(, document.getElementById('root')); }); diff --git a/apps/optimizely/src/index.spec.js b/apps/optimizely/src/index.spec.js index af0a30ea74c..c6ee4ab2ca5 100644 --- a/apps/optimizely/src/index.spec.js +++ b/apps/optimizely/src/index.spec.js @@ -4,12 +4,12 @@ import { cleanup, render, configure } from '@testing-library/react'; import App from '../src'; global.window.close = () => {}; -global.window.encodeURIComponent = x => x; +global.window.encodeURIComponent = (x) => x; global.window.addEventListener = jest.fn(); global.window.localStorage = { getItem: () => {}, - setItem: () => {} + setItem: () => {}, }; const LOCATION_ENTRY_SIDEBAR = 'entry-sidebar'; @@ -23,17 +23,17 @@ function mockSdk() { return { parameters: { installation: { - optimizelyProjectId: PROJECT_ID - } + optimizelyProjectId: PROJECT_ID, + }, }, location: { - is: jest.fn(l => { + is: jest.fn((l) => { return l === LOCATION; - }) + }), }, window: { startAutoResizer: () => {}, - stopAutoResizer: () => {} + stopAutoResizer: () => {}, }, ids: {}, space: {}, @@ -42,17 +42,17 @@ function mockSdk() { fields: { experimentId: { getValue: jest.fn(() => 'exp123'), - onValueChanged: jest.fn(() => jest.fn()) + onValueChanged: jest.fn(() => jest.fn()), }, meta: { getValue: jest.fn(), - onValueChanged: jest.fn(() => jest.fn()) + onValueChanged: jest.fn(() => jest.fn()), }, variations: { getValue: jest.fn(), - onValueChanged: jest.fn(() => jest.fn()) - } - } + onValueChanged: jest.fn(() => jest.fn()), + }, + }, }, contentType: { sys: { @@ -60,8 +60,8 @@ function mockSdk() { sys: { type: 'Link', linkType: 'Space', - id: 'cyu19ucaypb9' - } + id: 'cyu19ucaypb9', + }, }, id: 'variationContainer', type: 'ContentType', @@ -71,10 +71,10 @@ function mockSdk() { sys: { id: 'master', type: 'Link', - linkType: 'Environment' - } + linkType: 'Environment', + }, }, - revision: 3 + revision: 3, }, name: 'Variation Container', description: null, @@ -88,7 +88,7 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'experimentId', @@ -98,7 +98,7 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'meta', @@ -108,7 +108,7 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'variations', @@ -122,9 +122,9 @@ function mockSdk() { items: { type: 'Link', validations: [], - linkType: 'Entry' - } - } + linkType: 'Entry', + }, + }, ].concat( VALID_FIELDS ? { @@ -135,11 +135,11 @@ function mockSdk() { required: false, validations: [], disabled: false, - omitted: false + omitted: false, } : [] - ) - } + ), + }, }; } diff --git a/apps/optimizely/src/mockProps.js b/apps/optimizely/src/mockProps.js index 3a6cdfb1dc4..7af550ed163 100644 --- a/apps/optimizely/src/mockProps.js +++ b/apps/optimizely/src/mockProps.js @@ -4,7 +4,7 @@ export default { user: { sys: { type: 'User', - id: '2userId252' + id: '2userId252', }, firstName: 'leroy', lastName: 'jenkins', @@ -13,24 +13,24 @@ export default { spaceMembership: { sys: { type: 'SpaceMembership', - id: 'cyu19ucaypb9-2userId252' + id: 'cyu19ucaypb9-2userId252', }, admin: true, - roles: [] - } + roles: [], + }, }, parameters: { instance: {}, installation: { - optimizelyProjectId: '14632250064' - } + optimizelyProjectId: '14632250064', + }, }, locales: { available: ['en-US'], default: 'en-US', names: { - 'en-US': 'English (United States)' - } + 'en-US': 'English (United States)', + }, }, space: {}, dialogs: {}, @@ -42,7 +42,7 @@ export default { environment: 'master', contentType: 'variationContainer', entry: '928dNykvv7L31PNIH8d0W', - user: '2userId252' + user: '2userId252', }, contentType: { sys: { @@ -50,8 +50,8 @@ export default { sys: { type: 'Link', linkType: 'Space', - id: 'cyu19ucaypb9' - } + id: 'cyu19ucaypb9', + }, }, id: 'variationContainer', type: 'ContentType', @@ -61,10 +61,10 @@ export default { sys: { id: 'master', type: 'Link', - linkType: 'Environment' - } + linkType: 'Environment', + }, }, - revision: 4 + revision: 4, }, name: 'Variation Container', description: null, @@ -78,7 +78,7 @@ export default { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'experimentId', @@ -88,7 +88,7 @@ export default { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'meta', @@ -98,7 +98,7 @@ export default { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'variations', @@ -112,8 +112,8 @@ export default { items: { type: 'Link', validations: [], - linkType: 'Entry' - } + linkType: 'Entry', + }, }, { id: 'experimentKey', @@ -123,9 +123,9 @@ export default { required: false, validations: [], disabled: false, - omitted: false - } - ] + omitted: false, + }, + ], }, entry: { fields: { @@ -143,49 +143,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, experimentId: { getValue: jest.fn(), @@ -204,49 +204,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: ['14942730290'] + __private__memoized__arguments__: ['14942730290'], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, meta: { getValue: jest.fn(), @@ -265,49 +265,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [{}] + __private__memoized__arguments__: [{}], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, variations: { getValue: jest.fn(), @@ -320,7 +320,7 @@ export default { items: { type: 'Link', validations: [], - linkType: 'Entry' + linkType: 'Entry', }, _defaultLocale: 'en-US', _fieldLocales: { @@ -330,49 +330,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, experimentKey: { id: 'experimentKey', @@ -388,51 +388,51 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } - } - } + _responseHandlers: {}, + }, + }, + }, + }, + }, }, editor: { editorInterface: { @@ -443,8 +443,8 @@ export default { sys: { id: 'cyu19ucaypb9', type: 'Link', - linkType: 'Space' - } + linkType: 'Space', + }, }, version: 8, createdAt: '2019-05-24T07:45:48.999Z', @@ -452,118 +452,118 @@ export default { sys: { id: '5aZ5pwqlNWeMnNIZP6lCE1', type: 'Link', - linkType: 'User' - } + linkType: 'User', + }, }, updatedAt: '2019-07-10T07:19:11.763Z', updatedBy: { sys: { id: '2userId252', type: 'Link', - linkType: 'User' - } + linkType: 'User', + }, }, contentType: { sys: { id: 'variationContainer', type: 'Link', - linkType: 'ContentType' - } + linkType: 'ContentType', + }, }, environment: { sys: { id: 'master', type: 'Link', - linkType: 'Environment' - } - } + linkType: 'Environment', + }, + }, }, editor: { settings: {}, widgetId: 'optimizely', - widgetNamespace: 'extension' + widgetNamespace: 'extension', }, sidebar: [ { settings: {}, widgetId: 'optimizely-app-olocz4yVWMnb79x6', - widgetNamespace: 'extension' + widgetNamespace: 'extension', }, { settings: {}, widgetId: 'publication-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { settings: {}, widgetId: 'content-preview-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'jobs-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'content-workflows-tasks-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'incoming-links-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'translation-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'versions-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'users-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'entry-activity-widget', - widgetNamespace: 'sidebar-builtin' - } + widgetNamespace: 'sidebar-builtin', + }, ], controls: [ { fieldId: 'experimentTitle', widgetId: 'singleLine', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'experimentId', widgetId: 'singleLine', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'meta', widgetId: 'objectEditor', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'variations', widgetId: 'entryLinksEditor', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'experimentKey', widgetId: 'singleLine', - widgetNamespace: 'builtin' - } - ] - } - } + widgetNamespace: 'builtin', + }, + ], + }, + }, }, client: { sdk: { @@ -571,7 +571,7 @@ export default { user: { sys: { type: 'User', - id: '2userId252' + id: '2userId252', }, firstName: 'leroy', lastName: 'jenkins', @@ -580,24 +580,24 @@ export default { spaceMembership: { sys: { type: 'SpaceMembership', - id: 'cyu19ucaypb9-2userId252' + id: 'cyu19ucaypb9-2userId252', }, admin: true, - roles: [] - } + roles: [], + }, }, parameters: { instance: {}, installation: { - optimizelyProjectId: '14632250064' - } + optimizelyProjectId: '14632250064', + }, }, locales: { available: ['en-US'], default: 'en-US', names: { - 'en-US': 'English (United States)' - } + 'en-US': 'English (United States)', + }, }, space: {}, dialogs: {}, @@ -609,7 +609,7 @@ export default { environment: 'master', contentType: 'variationContainer', entry: '928dNykvv7L31PNIH8d0W', - user: '2userId252' + user: '2userId252', }, contentType: { sys: { @@ -617,8 +617,8 @@ export default { sys: { type: 'Link', linkType: 'Space', - id: 'cyu19ucaypb9' - } + id: 'cyu19ucaypb9', + }, }, id: 'variationContainer', type: 'ContentType', @@ -628,10 +628,10 @@ export default { sys: { id: 'master', type: 'Link', - linkType: 'Environment' - } + linkType: 'Environment', + }, }, - revision: 4 + revision: 4, }, name: 'Variation Container', description: null, @@ -645,7 +645,7 @@ export default { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'experimentId', @@ -655,7 +655,7 @@ export default { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'meta', @@ -665,7 +665,7 @@ export default { required: false, validations: [], disabled: false, - omitted: false + omitted: false, }, { id: 'variations', @@ -679,8 +679,8 @@ export default { items: { type: 'Link', validations: [], - linkType: 'Entry' - } + linkType: 'Entry', + }, }, { id: 'experimentKey', @@ -690,9 +690,9 @@ export default { required: false, validations: [], disabled: false, - omitted: false - } - ] + omitted: false, + }, + ], }, entry: { fields: { @@ -710,49 +710,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, experimentId: { id: 'experimentId', @@ -769,49 +769,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: ['14942730290'] + __private__memoized__arguments__: ['14942730290'], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, meta: { id: 'meta', @@ -829,49 +829,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [{}] + __private__memoized__arguments__: [{}], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, variations: { id: 'variations', @@ -882,7 +882,7 @@ export default { items: { type: 'Link', validations: [], - linkType: 'Entry' + linkType: 'Entry', }, _defaultLocale: 'en-US', _fieldLocales: { @@ -892,49 +892,49 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } + _responseHandlers: {}, + }, + }, + }, }, experimentKey: { id: 'experimentKey', @@ -950,51 +950,51 @@ export default { _valueSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _isDisabledSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _schemaErrorsChangedSignal: { _id: 0, _listeners: {}, - __private__memoized__arguments__: [null] + __private__memoized__arguments__: [null], }, _channel: { _messageHandlers: { sysChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, valueChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, isDisabledChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, schemaErrorsChanged: { _id: 5, - _listeners: {} + _listeners: {}, }, localeSettingsChanged: { _id: 1, - _listeners: {} + _listeners: {}, }, showDisabledFieldsChanged: { _id: 1, - _listeners: {} - } + _listeners: {}, + }, }, - _responseHandlers: {} - } - } - } - } - } + _responseHandlers: {}, + }, + }, + }, + }, + }, }, editor: { editorInterface: { @@ -1005,8 +1005,8 @@ export default { sys: { id: 'cyu19ucaypb9', type: 'Link', - linkType: 'Space' - } + linkType: 'Space', + }, }, version: 8, createdAt: '2019-05-24T07:45:48.999Z', @@ -1014,120 +1014,120 @@ export default { sys: { id: '5aZ5pwqlNWeMnNIZP6lCE1', type: 'Link', - linkType: 'User' - } + linkType: 'User', + }, }, updatedAt: '2019-07-10T07:19:11.763Z', updatedBy: { sys: { id: '2userId252', type: 'Link', - linkType: 'User' - } + linkType: 'User', + }, }, contentType: { sys: { id: 'variationContainer', type: 'Link', - linkType: 'ContentType' - } + linkType: 'ContentType', + }, }, environment: { sys: { id: 'master', type: 'Link', - linkType: 'Environment' - } - } + linkType: 'Environment', + }, + }, }, editor: { settings: {}, widgetId: 'optimizely', - widgetNamespace: 'extension' + widgetNamespace: 'extension', }, sidebar: [ { settings: {}, widgetId: 'optimizely-app-olocz4yVWMnb79x6', - widgetNamespace: 'extension' + widgetNamespace: 'extension', }, { settings: {}, widgetId: 'publication-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { settings: {}, widgetId: 'content-preview-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'jobs-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'content-workflows-tasks-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'incoming-links-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'translation-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'versions-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'users-widget', - widgetNamespace: 'sidebar-builtin' + widgetNamespace: 'sidebar-builtin', }, { disabled: true, widgetId: 'entry-activity-widget', - widgetNamespace: 'sidebar-builtin' - } + widgetNamespace: 'sidebar-builtin', + }, ], controls: [ { fieldId: 'experimentTitle', widgetId: 'singleLine', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'experimentId', widgetId: 'singleLine', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'meta', widgetId: 'objectEditor', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'variations', widgetId: 'entryLinksEditor', - widgetNamespace: 'builtin' + widgetNamespace: 'builtin', }, { fieldId: 'experimentKey', widgetId: 'singleLine', - widgetNamespace: 'builtin' - } - ] - } - } + widgetNamespace: 'builtin', + }, + ], + }, + }, }, project: '14632250064', - baseURL: 'https://api.optimizely.com/v2' - } + baseURL: 'https://api.optimizely.com/v2', + }, }; diff --git a/apps/optimizely/src/optimizely-client.js b/apps/optimizely/src/optimizely-client.js index d0936bbb1c3..ad62356d1b0 100644 --- a/apps/optimizely/src/optimizely-client.js +++ b/apps/optimizely/src/optimizely-client.js @@ -10,11 +10,11 @@ export default class OptimizelyClient { this.onReauth = onReauth; } - makeRequest = async url => { + makeRequest = async (url) => { const response = await fetch(`${this.baseURL}${url}`, { headers: { - Authorization: `Bearer ${this.accessToken}` - } + Authorization: `Bearer ${this.accessToken}`, + }, }); if (response.ok) { @@ -29,7 +29,7 @@ export default class OptimizelyClient { return this.makeRequest('/projects'); } - getExperiment = experimentId => { + getExperiment = (experimentId) => { return this.makeRequest(`/experiments/${experimentId}`); }; @@ -54,13 +54,13 @@ export default class OptimizelyClient { } } - experiments = experiments.filter(experiment => { + experiments = experiments.filter((experiment) => { return experiment.status !== 'archived'; }); return experiments; }; - getExperimentResults = experimentId => { + getExperimentResults = (experimentId) => { return this.makeRequest(`/experiments/${experimentId}/results`); }; diff --git a/apps/shopify/src/basePagination.js b/apps/shopify/src/basePagination.js index e99efd83216..c4f84bfaf1d 100644 --- a/apps/shopify/src/basePagination.js +++ b/apps/shopify/src/basePagination.js @@ -31,9 +31,9 @@ export default class BasePagination { return { pagination: { - hasNextPage: this.hasNextProductPage + hasNextPage: this.hasNextProductPage, }, - products: products.map(this.dataTransformer) + products: products.map(this.dataTransformer), }; } diff --git a/apps/shopify/src/collectionPagination.js b/apps/shopify/src/collectionPagination.js index a04fd3e4a86..9ac781e0b2c 100644 --- a/apps/shopify/src/collectionPagination.js +++ b/apps/shopify/src/collectionPagination.js @@ -1,20 +1,20 @@ import { collectionDataTransformer } from './dataTransformer'; import BasePagination from './basePagination'; -const makePagination = async sdk => { +const makePagination = async (sdk) => { const pagination = new BasePagination({ sdk, dataTransformer: collectionDataTransformer, - fetchProducts: async function(search, PER_PAGE) { + fetchProducts: async function (search, PER_PAGE) { const query = { query: search }; return await this.shopifyClient.collection.fetchQuery({ first: PER_PAGE, sortBy: 'TITLE', reverse: true, - ...(search.length && query) + ...(search.length && query), }); - } + }, }); await pagination.init(); return pagination; diff --git a/apps/shopify/src/constants.js b/apps/shopify/src/constants.js index 0b766edd5bc..84714bf8a84 100644 --- a/apps/shopify/src/constants.js +++ b/apps/shopify/src/constants.js @@ -6,15 +6,15 @@ export const DEFAULT_SHOPIFY_VARIANT_TITLE = 'Default Title'; export const SKU_TYPES = [ { id: 'product', - name: 'Product' + name: 'Product', }, { id: 'variant', name: 'Product variant', - default: true + default: true, }, { id: 'collection', - name: 'Collection' - } + name: 'Collection', + }, ]; diff --git a/apps/shopify/src/dataTransformer.js b/apps/shopify/src/dataTransformer.js index 8ca22823660..b86eee167f5 100644 --- a/apps/shopify/src/dataTransformer.js +++ b/apps/shopify/src/dataTransformer.js @@ -33,7 +33,7 @@ export const collectionDataTransformer = (collection, apiEndpoint) => { name: collection.title, displaySKU: handle ? `Handle: ${handle}` : `Collection ID: ${collection.id}`, sku: collection.id, - ...(externalLink ? { externalLink } : {}) + ...(externalLink ? { externalLink } : {}), }; }; @@ -66,7 +66,7 @@ export const productDataTransformer = (product, apiEndpoint) => { name: product.title, displaySKU: sku ? `SKU: ${sku}` : `Product ID: ${product.id}`, sku: product.id, - ...(externalLink ? { externalLink } : {}) + ...(externalLink ? { externalLink } : {}), }; }; @@ -74,7 +74,7 @@ export const productDataTransformer = (product, apiEndpoint) => { * Transforms the API response of Shopify product variants into * the product schema expected by the SkuPicker component */ -export const productVariantDataTransformer = product => { +export const productVariantDataTransformer = (product) => { const image = get(product, ['image', 'src'], ''); const sku = get(product, ['sku'], ''); const variantSKU = get(product, ['variantSKU'], ''); @@ -84,14 +84,14 @@ export const productVariantDataTransformer = product => { image, name: product.title, displaySKU: variantSKU ? `SKU: ${variantSKU}` : `Product ID: ${sku}`, - sku + sku, }; }; -export const productsToVariantsTransformer = products => +export const productsToVariantsTransformer = (products) => flatten( - products.map(product => { - const variants = product.variants.map(variant => ({ + products.map((product) => { + const variants = product.variants.map((variant) => ({ ...variant, variantSKU: variant.sku, sku: variant.id, @@ -99,37 +99,33 @@ export const productsToVariantsTransformer = products => title: variant.title === DEFAULT_SHOPIFY_VARIANT_TITLE ? product.title - : `${product.title} (${variant.title})` + : `${product.title} (${variant.title})`, })); return variants; }) ); -export const previewsToProductVariants = ({ apiEndpoint }) => ({ - sku, - id, - image, - product, - title -}) => { - const productIdDecoded = atob(product.id); - const productId = - productIdDecoded && productIdDecoded.slice(productIdDecoded.lastIndexOf('/') + 1); - return { - id, - image: get(image, ['src'], ''), - // TODO: Remove sku:id when @contentful/ecommerce-app-base supports internal IDs - // as an alternative piece of info to persist instead of the SKU. - // For now this is a temporary hack. - sku: id, - displaySKU: sku ? `SKU: ${sku}` : `Product ID: ${id}`, - productId: product.id, - name: title === DEFAULT_SHOPIFY_VARIANT_TITLE ? product.title : `${product.title} (${title})`, - ...(apiEndpoint && - productId && { - externalLink: `https://${apiEndpoint}${ - last(apiEndpoint) === '/' ? '' : '/' - }admin/products/${productId}` - }) +export const previewsToProductVariants = + ({ apiEndpoint }) => + ({ sku, id, image, product, title }) => { + const productIdDecoded = atob(product.id); + const productId = + productIdDecoded && productIdDecoded.slice(productIdDecoded.lastIndexOf('/') + 1); + return { + id, + image: get(image, ['src'], ''), + // TODO: Remove sku:id when @contentful/ecommerce-app-base supports internal IDs + // as an alternative piece of info to persist instead of the SKU. + // For now this is a temporary hack. + sku: id, + displaySKU: sku ? `SKU: ${sku}` : `Product ID: ${id}`, + productId: product.id, + name: title === DEFAULT_SHOPIFY_VARIANT_TITLE ? product.title : `${product.title} (${title})`, + ...(apiEndpoint && + productId && { + externalLink: `https://${apiEndpoint}${ + last(apiEndpoint) === '/' ? '' : '/' + }admin/products/${productId}`, + }), + }; }; -}; diff --git a/apps/shopify/src/index.js b/apps/shopify/src/index.js index 7835417c5a4..1073dcb4713 100644 --- a/apps/shopify/src/index.js +++ b/apps/shopify/src/index.js @@ -3,7 +3,7 @@ import { fetchProductVariantPreviews, fetchProductPreviews, fetchCollectionPreviews, - makeSkuResolver + makeSkuResolver, } from './skuResolvers'; import { SKU_TYPES } from './constants'; @@ -25,7 +25,7 @@ function makeCTA(fieldType, skuType) { function makeSaveBtnText(skuType) { if (skuType === 'product') { - return selectedSKUs => { + return (selectedSKUs) => { switch (selectedSKUs.length) { case 0: return 'Save products'; @@ -38,7 +38,7 @@ function makeSaveBtnText(skuType) { } if (skuType === 'collection') { - return selectedSKUs => { + return (selectedSKUs) => { switch (selectedSKUs.length) { case 0: return 'Save collections'; @@ -50,7 +50,7 @@ function makeSaveBtnText(skuType) { }; } - return selectedSKUs => { + return (selectedSKUs) => { switch (selectedSKUs.length) { case 0: return 'Save product variants'; @@ -101,7 +101,7 @@ async function renderDialog(sdk) { fetchProducts: await makeSkuResolver(sdk, skuType), searchDelay: 750, skuType, - makeSaveBtnText: makeSaveBtnText(skuType) + makeSaveBtnText: makeSaveBtnText(skuType), }); sdk.window.startAutoResizer(); @@ -115,7 +115,7 @@ async function openDialog(sdk, currentValue, config) { shouldCloseOnOverlayClick: true, shouldCloseOnEscapePress: true, parameters: config, - width: 1400 + width: 1400, }); return Array.isArray(skus) ? skus : []; @@ -139,20 +139,20 @@ setup({ name: 'Storefront Access Token', description: 'The storefront access token to your Shopify store', type: 'Symbol', - required: true + required: true, }, { id: 'apiEndpoint', name: 'API Endpoint', description: 'The Shopify API endpoint', type: 'Symbol', - required: true - } + required: true, + }, ], skuTypes: SKU_TYPES, fetchProductPreviews: fetchPreviews, renderDialog, openDialog, isDisabled, - validateParameters + validateParameters, }); diff --git a/apps/shopify/src/productPagination.js b/apps/shopify/src/productPagination.js index 9ccd2fd8061..3da95cd6b12 100644 --- a/apps/shopify/src/productPagination.js +++ b/apps/shopify/src/productPagination.js @@ -1,20 +1,20 @@ import { productDataTransformer } from './dataTransformer'; import BasePagination from './basePagination'; -const makePagination = async sdk => { +const makePagination = async (sdk) => { const pagination = new BasePagination({ sdk, dataTransformer: productDataTransformer, - fetchProducts: async function(search, PER_PAGE) { + fetchProducts: async function (search, PER_PAGE) { const query = { query: `variants:['sku:${search}'] OR title:${search}` }; return await this.shopifyClient.product.fetchQuery({ first: PER_PAGE, sortBy: 'TITLE', reverse: true, - ...(search.length && query) + ...(search.length && query), }); - } + }, }); await pagination.init(); return pagination; diff --git a/apps/shopify/src/productVariantPagination.js b/apps/shopify/src/productVariantPagination.js index 14e9be1cf9e..507b3577954 100644 --- a/apps/shopify/src/productVariantPagination.js +++ b/apps/shopify/src/productVariantPagination.js @@ -50,9 +50,9 @@ class Pagination { // There is going to be a next page in the following two complimentary cases: // A). There are more products to fetch via the Shopify API // B). There are variants left to consume in the in-memory variants list - hasNextPage: this.hasNextProductPage || this.variants.length > 0 + hasNextPage: this.hasNextProductPage || this.variants.length > 0, }, - products: variants.map(productVariantDataTransformer) + products: variants.map(productVariantDataTransformer), }; } @@ -73,7 +73,7 @@ class Pagination { ? await this._fetchProducts(search) : await this._fetchNextPage(this.products); this.hasNextProductPage = - nextProducts.length > 0 && nextProducts.every(product => product.hasNextPage); + nextProducts.length > 0 && nextProducts.every((product) => product.hasNextPage); const nextVariants = productsToVariantsTransformer(nextProducts); this.products = uniqBy([...this.products, ...nextProducts], 'id'); @@ -93,7 +93,7 @@ class Pagination { first: PER_PAGE, sortBy: 'TITLE', reverse: true, - ...(search.length && query) + ...(search.length && query), }); } @@ -112,7 +112,7 @@ class Pagination { } } -const makePagination = async sdk => { +const makePagination = async (sdk) => { const pagination = new Pagination(sdk); await pagination.init(); return pagination; diff --git a/apps/shopify/src/skuResolvers.js b/apps/shopify/src/skuResolvers.js index b73e479e339..e68077766c3 100644 --- a/apps/shopify/src/skuResolvers.js +++ b/apps/shopify/src/skuResolvers.js @@ -20,7 +20,7 @@ export async function makeShopifyClient(config) { return Client.buildClient({ domain: apiEndpoint, - storefrontAccessToken + storefrontAccessToken, }); } @@ -37,12 +37,12 @@ export const fetchCollectionPreviews = async (skus, config) => { } const shopifyClient = await makeShopifyClient(config); - const collections = (await shopifyClient.collection.fetchAll(250)).filter(collection => + const collections = (await shopifyClient.collection.fetchAll(250)).filter((collection) => skus.includes(collection.id) ); return skus.map((sku) => { - const collection = collections.find((collection) => collection.id === sku) + const collection = collections.find((collection) => collection.id === sku); return collection ? collectionDataTransformer(collection, config.apiEndpoint) @@ -52,8 +52,8 @@ export const fetchCollectionPreviews = async (skus, config) => { image: '', id: sku, name: '', - } - }) + }; + }); }; /** @@ -72,7 +72,7 @@ export const fetchProductPreviews = async (skus, config) => { const products = await shopifyClient.product.fetchMultiple(skus); return skus.map((sku) => { - const product = products.find((product) => product?.id === sku) + const product = products.find((product) => product?.id === sku); return product ? productDataTransformer(product, config.apiEndpoint) @@ -82,8 +82,8 @@ export const fetchProductPreviews = async (skus, config) => { image: '', id: sku, name: '', - } - }) + }; + }); }; /** @@ -99,7 +99,7 @@ export const fetchProductVariantPreviews = async (skus, config) => { } const validIds = skus - .map(sku => { + .map((sku) => { try { // If not valid base64 window.atob will throw const unencodedId = atob(sku); @@ -108,10 +108,10 @@ export const fetchProductVariantPreviews = async (skus, config) => { return null; } }) - .filter(sku => sku && /^gid.*ProductVariant/.test(sku.unencodedId)) + .filter((sku) => sku && /^gid.*ProductVariant/.test(sku.unencodedId)) .map(({ sku }) => sku); - const queryIds = validIds.map(sku => `"${sku}"`).join(','); + const queryIds = validIds.map((sku) => `"${sku}"`).join(','); const query = ` { nodes (ids: [${queryIds}]) { @@ -138,9 +138,9 @@ export const fetchProductVariantPreviews = async (skus, config) => { headers: { Accept: 'application/json', 'Content-Type': 'application/json', - 'x-shopify-storefront-access-token': storefrontAccessToken + 'x-shopify-storefront-access-token': storefrontAccessToken, }, - body: JSON.stringify({ query }) + body: JSON.stringify({ query }), }); const data = await res.json(); @@ -150,8 +150,8 @@ export const fetchProductVariantPreviews = async (skus, config) => { const variantPreviews = nodes.map(previewsToProductVariants(config)); const missingVariants = difference( skus, - variantPreviews.map(variant => variant.sku) - ).map(sku => ({ sku, isMissing: true, name: '', image: '' })); + variantPreviews.map((variant) => variant.sku) + ).map((sku) => ({ sku, isMissing: true, name: '', image: '' })); return [...variantPreviews, ...missingVariants]; }; @@ -162,19 +162,19 @@ export const fetchProductVariantPreviews = async (skus, config) => { * Shopify does not support indexed pagination, only infinite scrolling * @see https://community.shopify.com/c/Shopify-APIs-SDKs/How-to-display-more-than-20-products-in-my-app-when-products-are/td-p/464090 for more details (KarlOffenberger's answer) */ -export const makeProductVariantSearchResolver = async sdk => { +export const makeProductVariantSearchResolver = async (sdk) => { const pagination = await makeProductVariantPagination(sdk); - return search => pagination.fetchNext(search); + return (search) => pagination.fetchNext(search); }; -export const makeProductSearchResolver = async sdk => { +export const makeProductSearchResolver = async (sdk) => { const pagination = await makeProductPagination(sdk); - return search => pagination.fetchNext(search); + return (search) => pagination.fetchNext(search); }; -export const makeCollectionSearchResolver = async sdk => { +export const makeCollectionSearchResolver = async (sdk) => { const pagination = await makeCollectionPagination(sdk); - return search => pagination.fetchNext(search); + return (search) => pagination.fetchNext(search); }; /** diff --git a/apps/smartling/frontend/src/AppConfig.tsx b/apps/smartling/frontend/src/AppConfig.tsx index fd27f9bbc83..07bf3077f06 100644 --- a/apps/smartling/frontend/src/AppConfig.tsx +++ b/apps/smartling/frontend/src/AppConfig.tsx @@ -8,7 +8,7 @@ import { CheckboxField, TextField, TextLink, - Note + Note, } from '@contentful/forma-36-react-components'; import get from 'lodash.get'; // @ts-ignore 2307 @@ -31,7 +31,7 @@ export default class AppConfig extends React.Component { state: State = { contentTypes: [], selectedContentTypes: [], - projectId: '' + projectId: '', }; async componentDidMount() { @@ -40,28 +40,28 @@ export default class AppConfig extends React.Component { sdk.app.onConfigure(this.configure); const [ctsRes, parameters, eiRes] = await Promise.all([ - sdk.space.getContentTypes({limit: 1000}), + sdk.space.getContentTypes({ limit: 1000 }), sdk.app.getParameters() as Promise, - sdk.space.getEditorInterfaces() + sdk.space.getEditorInterfaces(), ]); const selectedContentTypes = eiRes.items - .filter(ei => - get(ei, ['sidebar'], []).some(item => { + .filter((ei) => + get(ei, ['sidebar'], []).some((item) => { return item.widgetNamespace === 'app' && item.widgetId === this.props.sdk.ids.app; }) ) - .map(ei => get(ei, ['sys', 'contentType', 'sys', 'id'])) - .filter(ctId => typeof ctId === 'string' && ctId.length > 0); + .map((ei) => get(ei, ['sys', 'contentType', 'sys', 'id'])) + .filter((ctId) => typeof ctId === 'string' && ctId.length > 0); const items = ctsRes ? (ctsRes.items as { name: string; sys: { id: string } }[]) : []; // eslint-disable-next-line react/no-did-mount-set-state this.setState( { - contentTypes: items.map(ct => ({ name: ct.name, id: ct.sys.id })), + contentTypes: items.map((ct) => ({ name: ct.name, id: ct.sys.id })), projectId: parameters ? parameters.projectId : '', - selectedContentTypes + selectedContentTypes, }, () => sdk.app.setReady() ); @@ -75,14 +75,14 @@ export default class AppConfig extends React.Component { return { parameters: { - projectId: this.state.projectId + projectId: this.state.projectId, }, targetState: { EditorInterface: this.state.selectedContentTypes.reduce((acc: any, ct) => { acc[ct] = { sidebar: { position: 1 } }; return acc; - }, {}) - } + }, {}), + }, }; }; @@ -96,12 +96,12 @@ export default class AppConfig extends React.Component { if (selectedContentTypes.includes(id)) { return { - selectedContentTypes: selectedContentTypes.filter(ct => ct !== id) + selectedContentTypes: selectedContentTypes.filter((ct) => ct !== id), }; } return { - selectedContentTypes: selectedContentTypes.concat([id]) + selectedContentTypes: selectedContentTypes.concat([id]), }; }); } @@ -109,7 +109,7 @@ export default class AppConfig extends React.Component { render() { const { sdk } = this.props; const { - ids: { space, environment } + ids: { space, environment }, } = sdk; return (
@@ -124,7 +124,8 @@ export default class AppConfig extends React.Component { + rel="noopener noreferrer" + > Smartling {' '} app allows you to view the status of your translation without leaving Contentful. @@ -145,7 +146,7 @@ export default class AppConfig extends React.Component { labelText="Smartling project ID" value={this.state.projectId} // @ts-ignore 2339 - onChange={e => this.setProjectId(e.target.value)} + onChange={(e) => this.setProjectId(e.target.value)} helpText="To get the project ID, see the 'Project Settings > API' of your Smartling project." /> @@ -163,14 +164,15 @@ export default class AppConfig extends React.Component { environment === 'master' ? `https://app.contentful.com/spaces/${space}/content_types` : `https://app.contentful.com/spaces/${space}/environments/${environment}/content_types` - }> + } + > content type {' '} and assign it to the app from this screen. ) : ( - {this.state.contentTypes.map(ct => ( + {this.state.contentTypes.map((ct) => ( this.toggleCt(ct.id)} labelText={ct.name} diff --git a/apps/smartling/frontend/src/Sidebar.tsx b/apps/smartling/frontend/src/Sidebar.tsx index 39ec8752be0..627eb9ff210 100644 --- a/apps/smartling/frontend/src/Sidebar.tsx +++ b/apps/smartling/frontend/src/Sidebar.tsx @@ -11,7 +11,7 @@ import { SkeletonContainer, SkeletonImage, Spinner, - Note + Note, } from '@contentful/forma-36-react-components'; import smartlingClient from './smartlingClient'; @@ -86,7 +86,7 @@ function formatDate(date: string) { month: 'short', day: 'numeric', hour: 'numeric', - minute: 'numeric' + minute: 'numeric', }); } @@ -120,7 +120,7 @@ function sortSubs(a: Submission, b: Submission) { function formatSmartlingEntry(entry: SmartlingContentfulEntry): SmartlingContentfulEntry { return { ...entry, - translationSubmissions: entry.translationSubmissions.slice().sort(sortSubs) + translationSubmissions: entry.translationSubmissions.slice().sort(sortSubs), }; } @@ -182,10 +182,10 @@ export default class Sidebar extends React.Component { } else if (res.code === 'SUCCESS') { this.setState({ smartlingEntry: formatSmartlingEntry(res.data), - showAllSubs: res.data.translationSubmissions.length <= SUBS_TO_SHOW + showAllSubs: res.data.translationSubmissions.length <= SUBS_TO_SHOW, }); } else { - this.setState({generalError: true}); + this.setState({ generalError: true }); } }; @@ -215,7 +215,8 @@ export default class Sidebar extends React.Component { buttonType="muted" isFullWidth className="request-translation" - onClick={() => window.open(smartlingLink)}> + onClick={() => window.open(smartlingLink)} + > Request Translation ); @@ -228,7 +229,8 @@ export default class Sidebar extends React.Component { buttonType="primary" isFullWidth className="signin" - onClick={() => this.runAuthFlow()}> + onClick={() => this.runAuthFlow()} + > Sign in with Smartling
@@ -267,11 +269,12 @@ export default class Sidebar extends React.Component {
Translation submissions - {subsToShow.map(sub => ( + {subsToShow.map((sub) => ( this.linkToFile(sub)}> + onClick={() => this.linkToFile(sub)} + >
{this.getLocaleByName(sub.targetLocaleExternalId)}
{sub.status.toLowerCase() === 'in_progress' && ( @@ -306,19 +309,25 @@ export default class Sidebar extends React.Component { smartlingBody = <>; } } else if (generalError) { - statusTag = ( + statusTag = ( Disconnected - ); + ); - smartlingBody = ( - - Please ensure that you have access to the connected Smartling project. -
- View documentation -
- ); + smartlingBody = ( + + Please ensure that you have access to the connected Smartling project. +
+ + View documentation + +
+ ); } return ( diff --git a/apps/smartling/frontend/src/index.spec.tsx b/apps/smartling/frontend/src/index.spec.tsx index 6643da08e2e..4bb0a56f7be 100644 --- a/apps/smartling/frontend/src/index.spec.tsx +++ b/apps/smartling/frontend/src/index.spec.tsx @@ -9,7 +9,7 @@ import contentTypeResponse from './mockData/contentTypeResponse.json'; import entryMockResponse from './mockData/entryMockResponse.json'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); let mockSdk: any; @@ -22,26 +22,26 @@ describe('App', () => { location: { is() { return false; - } + }, }, parameters: { installation: { - projectId: 'project-id-123' - } + projectId: 'project-id-123', + }, }, window: { - startAutoResizer() {} + startAutoResizer() {}, }, locales: { names: { 'en-US': 'English (United State)', - 'de-DE': 'German (Germany)' - } + 'de-DE': 'German (Germany)', + }, }, ids: { app: 'smartling-app-id', entry: 'entry-123', - space: 'space-123' + space: 'space-123', }, app: { async setReady() {}, @@ -52,21 +52,21 @@ describe('App', () => { async getParameters() { return null; }, - async onConfigurationCompleted() {} + async onConfigurationCompleted() {}, }, space: { async getEditorInterfaces() { return { - items: [] + items: [], }; }, async getContentTypes() { return contentTypeResponse; - } + }, }, notifier: { - error() {} - } + error() {}, + }, }; }); @@ -149,8 +149,8 @@ describe('App', () => { } }), setItem: jest.fn(), - removeItem: jest.fn() - } + removeItem: jest.fn(), + }, }); fetchMock.get( @@ -187,15 +187,11 @@ describe('App', () => { } }), setItem: jest.fn(), - removeItem: jest.fn() - } + removeItem: jest.fn(), + }, }); - fetchMock.get( - '/refresh?refresh_token=', - 401, - { overwriteRoutes: true } - ); + fetchMock.get('/refresh?refresh_token=', 401, { overwriteRoutes: true }); fetchMock.get( '/entry?spaceId=space-123&projectId=project-id-123&entryId=entry-123', {}, diff --git a/apps/smartling/frontend/src/index.tsx b/apps/smartling/frontend/src/index.tsx index 7f0657fa5dc..4b9abc348eb 100644 --- a/apps/smartling/frontend/src/index.tsx +++ b/apps/smartling/frontend/src/index.tsx @@ -11,11 +11,17 @@ import standalone from './standalone'; If we are running this code in standalone mode, it just means we have been redirected with an access or refresh_token */ -if (window.location.search.includes('access_token') || window.location.search.includes('refresh_token')) { +if ( + window.location.search.includes('access_token') || + window.location.search.includes('refresh_token') +) { standalone(window); } else { - init(sdk => { - if (sdk.location.is(locations.LOCATION_APP_CONFIG) || sdk.location.is(locations.LOCATION_ENTRY_SIDEBAR)) { + init((sdk) => { + if ( + sdk.location.is(locations.LOCATION_APP_CONFIG) || + sdk.location.is(locations.LOCATION_ENTRY_SIDEBAR) + ) { render( , document.getElementById('root') diff --git a/apps/smartling/lambda/jest.config.js b/apps/smartling/lambda/jest.config.js index 91a2d2c0d31..4a5b465ecb5 100644 --- a/apps/smartling/lambda/jest.config.js +++ b/apps/smartling/lambda/jest.config.js @@ -1,4 +1,4 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', -}; \ No newline at end of file +}; diff --git a/apps/typeform/frontend/src/AppConfig/AppConfig.tsx b/apps/typeform/frontend/src/AppConfig/AppConfig.tsx index 5cfd3c575b0..af8916b02cf 100644 --- a/apps/typeform/frontend/src/AppConfig/AppConfig.tsx +++ b/apps/typeform/frontend/src/AppConfig/AppConfig.tsx @@ -9,7 +9,7 @@ import { Select, Option, FormLabel, - Note + Note, } from '@contentful/forma-36-react-components'; import FieldSelector from './FieldSelector'; import { @@ -20,14 +20,14 @@ import { InstallationParameters, SelectedFields, WorkspaceOption, - WorkspacesResponse + WorkspacesResponse, } from '../typings'; import { getCompatibleFields, editorInterfacesToSelectedFields, selectedFieldsToTargetState, validateParameters, - getToken + getToken, } from '../utils'; import { styles } from './styles'; @@ -57,7 +57,7 @@ export class AppConfig extends React.Component { selectedContentTypes: [], selectedFields: {}, selectedWorkspaceId: '', - accessToken: getToken() + accessToken: getToken(), }; async componentDidMount() { @@ -69,13 +69,13 @@ export class AppConfig extends React.Component { sdk.space.getContentTypes(), sdk.space.getEditorInterfaces(), sdk.app.getParameters(), - this.fetchWorkspaces() + this.fetchWorkspaces(), ]); const contentTypes = (contentTypesResponse as Hash).items as ContentType[]; const editorInterfaces = (eisResponse as Hash).items as EditorInterface[]; const compatibleFields = getCompatibleFields(contentTypes); - const filteredContentTypes = contentTypes.filter(ct => { + const filteredContentTypes = contentTypes.filter((ct) => { const fields = compatibleFields[ct.sys.id]; return fields && fields.length > 0; }); @@ -87,7 +87,7 @@ export class AppConfig extends React.Component { selectedWorkspaceId: get(parameters, ['selectedWorkspaceId'], ''), compatibleFields, contentTypes: filteredContentTypes, - selectedFields: editorInterfacesToSelectedFields(editorInterfaces, sdk.ids.app) + selectedFields: editorInterfacesToSelectedFields(editorInterfaces, sdk.ids.app), }, () => sdk.app.setReady() ); @@ -97,8 +97,8 @@ export class AppConfig extends React.Component { try { const response = await fetch(`/workspaces`, { headers: { - Authorization: `Bearer ${this.state.accessToken}` - } + Authorization: `Bearer ${this.state.accessToken}`, + }, }); const result: WorkspacesResponse = await response.json(); this.setState({ workspaces: this.normalizeWorkspaceResponse(result) }); @@ -110,9 +110,9 @@ export class AppConfig extends React.Component { }; normalizeWorkspaceResponse = (response: WorkspacesResponse) => { - return response.workspaces.items.map(workspace => ({ + return response.workspaces.items.map((workspace) => ({ name: workspace.name, - id: workspace.id + id: workspace.id, })); }; @@ -134,13 +134,13 @@ export class AppConfig extends React.Component { return { parameters: { selectedWorkspaceId }, - targetState: selectedFieldsToTargetState(contentTypes, selectedFields) + targetState: selectedFieldsToTargetState(contentTypes, selectedFields), }; }; selectedWorkspaceIdIsValid = (): boolean => { return !!this.state.workspaces.find( - workspace => workspace.id === this.state.selectedWorkspaceId + (workspace) => workspace.id === this.state.selectedWorkspaceId ); }; @@ -157,17 +157,12 @@ export class AppConfig extends React.Component { }; render() { - const { - contentTypes, - compatibleFields, - selectedFields, - selectedWorkspaceId, - workspaces - } = this.state; + const { contentTypes, compatibleFields, selectedFields, selectedWorkspaceId, workspaces } = + this.state; const { sdk } = this.props; const { - ids: { space, environment } + ids: { space, environment }, } = sdk; return ( @@ -183,7 +178,8 @@ export class AppConfig extends React.Component { + rel="noopener noreferrer" + > Typeform {' '} app allows you to reference your forms from Typeform without leaving Contentful. @@ -203,11 +199,12 @@ export class AppConfig extends React.Component { onChange={(event: any) => this.setWorkSpaceId(event.currentTarget.value)} hasError={workspaces.length > 0 && !this.selectedWorkspaceIdIsValid()} value={selectedWorkspaceId} - data-test-id="typeform-select"> + data-test-id="typeform-select" + > - {workspaces.map(workspace => ( + {workspaces.map((workspace) => ( @@ -242,7 +239,8 @@ export class AppConfig extends React.Component { environment === 'master' ? `https://app.contentful.com/spaces/${space}/content_types` : `https://app.contentful.com/spaces/${space}/environments/${environment}/content_types` - }> + } + > content model {' '} and assign it to the app from this screen. diff --git a/apps/typeform/frontend/src/AppConfig/FieldSelector.spec.tsx b/apps/typeform/frontend/src/AppConfig/FieldSelector.spec.tsx index fd95e92ade9..1047d3d650a 100644 --- a/apps/typeform/frontend/src/AppConfig/FieldSelector.spec.tsx +++ b/apps/typeform/frontend/src/AppConfig/FieldSelector.spec.tsx @@ -5,11 +5,11 @@ import { typeforms } from '../__mocks__/typeforms'; import { sdk as mockSdk } from '../__mocks__/sdk'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); window.fetch = jest.fn(() => ({ - json: () => typeforms + json: () => typeforms, })) as any; const defaultProps: Props = { @@ -19,24 +19,24 @@ const defaultProps: Props = { name: 'CT1', fields: [ { id: 'x', name: 'X', type: 'Symbol' }, - { id: 'y', name: 'Y', type: 'Object' } - ] + { id: 'y', name: 'Y', type: 'Object' }, + ], }, { sys: { id: 'ct2' }, name: 'CT2', fields: [ { id: 'foo', name: 'FOO', type: 'Text' }, - { id: 'z', name: 'Z', type: 'Array', items: { type: 'Symbol' } } - ] - } + { id: 'z', name: 'Z', type: 'Array', items: { type: 'Symbol' } }, + ], + }, ], compatibleFields: { ct1: [{ id: 'x', name: 'X', type: 'Symbol' }], - ct2: [] + ct2: [], }, selectedFields: {}, - onSelectedFieldsChange: jest.fn() + onSelectedFieldsChange: jest.fn(), }; describe('FieldSelector', () => { @@ -50,7 +50,7 @@ describe('FieldSelector', () => { it('should render successfully with preselected fields', async () => { const props = { ...defaultProps, - selectedFields: { ct1: ['x'] } + selectedFields: { ct1: ['x'] }, }; const component = render(); expect(component.container).toMatchSnapshot(); diff --git a/apps/typeform/frontend/src/AppConfig/FieldSelector.tsx b/apps/typeform/frontend/src/AppConfig/FieldSelector.tsx index d791f76386a..528cdbb0144 100644 --- a/apps/typeform/frontend/src/AppConfig/FieldSelector.tsx +++ b/apps/typeform/frontend/src/AppConfig/FieldSelector.tsx @@ -23,7 +23,7 @@ export default class FieldSelector extends React.Component { if (e.currentTarget.checked) { updated[ctId] = (updated[ctId] || []).concat([fieldId]); } else { - updated[ctId] = (updated[ctId] || []).filter(cur => cur !== fieldId); + updated[ctId] = (updated[ctId] || []).filter((cur) => cur !== fieldId); } this.props.onSelectedFieldsChange(updated); @@ -34,13 +34,13 @@ export default class FieldSelector extends React.Component { return ( - {contentTypes.map(ct => { + {contentTypes.map((ct) => { const fields = compatibleFields[ct.sys.id]; return (
{ct.name} - {fields.map(field => ( + {fields.map((field) => ( { if (oauthWindow === null) { return; } - + const handleTokenEvent = ({ data, source }: any) => { if (source !== oauthWindow) { return; } - + const { token, error } = data; - + if (error) { console.error('There was an error authenticating. Please try again.'); } else if (token) { @@ -50,22 +50,18 @@ export function TypeformOAuth({ } }; - window.addEventListener('message', handleTokenEvent); + window.addEventListener('message', handleTokenEvent); return () => window.removeEventListener('message', handleTokenEvent); - }, [oauthWindow, setToken]) + }, [oauthWindow, setToken]); const executeOauth = () => { - const url = `${BASE_URL}/oauth/authorize?&client_id=${ - CLIENT_ID - }&redirect_uri=${encodeURIComponent( + const url = `${BASE_URL}/oauth/authorize?&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent( `${window.location.origin}/callback` )}&scope=forms:read+workspaces:read`; - setOAuthWindow(window.open(url, 'Typeform Contentful', 'left=150,top=10,width=800,height=900')) - + setOAuthWindow(window.open(url, 'Typeform Contentful', 'left=150,top=10,width=800,height=900')); }; - return (
diff --git a/apps/typeform/frontend/src/TypeFormWidget.tsx b/apps/typeform/frontend/src/TypeFormWidget.tsx index 4f6d827304b..519424ee4bb 100644 --- a/apps/typeform/frontend/src/TypeFormWidget.tsx +++ b/apps/typeform/frontend/src/TypeFormWidget.tsx @@ -16,7 +16,7 @@ export function TypeformPreviewWidget({ sdk }: Props) { typeformEmbed.makeWidget(element, value, { hideFooter: true, hideHeaders: true, - opacity: 0 + opacity: 0, }); sdk.window.updateHeight(SDK_WINDOW_HEIGHT); }, [value, sdk.window]); @@ -26,7 +26,7 @@ export function TypeformPreviewWidget({ sdk }: Props) { ref={el} style={{ width: '100%', - height: SDK_WINDOW_HEIGHT + height: SDK_WINDOW_HEIGHT, }} /> ); diff --git a/apps/typeform/frontend/src/index.tsx b/apps/typeform/frontend/src/index.tsx index 5d51a432e41..7c95429432c 100644 --- a/apps/typeform/frontend/src/index.tsx +++ b/apps/typeform/frontend/src/index.tsx @@ -5,7 +5,7 @@ import { locations, AppExtensionSDK, FieldExtensionSDK, - DialogExtensionSDK + DialogExtensionSDK, } from '@contentful/app-sdk'; import '@contentful/forma-36-react-components/dist/styles.css'; import '@contentful/forma-36-fcss/dist/styles.css'; @@ -18,7 +18,7 @@ import processTokenCallback from './processTokenCallback'; if (window.location.search.includes('token')) { processTokenCallback(window); } else { - init(sdk => { + init((sdk) => { if (sdk.location.is(locations.LOCATION_APP_CONFIG)) { render(, document.getElementById('root')); } else if (sdk.location.is(locations.LOCATION_DIALOG)) { diff --git a/apps/typeform/lambda/app.js b/apps/typeform/lambda/app.js index dccb6cc26ee..f91256f02f4 100644 --- a/apps/typeform/lambda/app.js +++ b/apps/typeform/lambda/app.js @@ -8,7 +8,7 @@ const handleWorkspaces = require('./workspaces-handler'); const fetchAccessToken = require('./fetch-access-token'); const deps = { - fetch + fetch, }; const app = express(); diff --git a/apps/typeform/lambda/constants.js b/apps/typeform/lambda/constants.js index 9656139ff2d..e5087a443ba 100644 --- a/apps/typeform/lambda/constants.js +++ b/apps/typeform/lambda/constants.js @@ -3,5 +3,5 @@ const BASE_URL = 'https://api.typeform.com'; module.exports = { - BASE_URL + BASE_URL, }; diff --git a/apps/typeform/lambda/fetch-access-token.js b/apps/typeform/lambda/fetch-access-token.js index 670816a255e..efcab7ec21e 100644 --- a/apps/typeform/lambda/fetch-access-token.js +++ b/apps/typeform/lambda/fetch-access-token.js @@ -13,9 +13,9 @@ module.exports = async (code, origin, { fetch }) => { const response = await fetch(ENDPOINT, { method: 'POST', headers: { - 'Content-Type': 'application/x-www-form-urlencoded' + 'Content-Type': 'application/x-www-form-urlencoded', }, - body + body, }); if (!response.ok) { diff --git a/apps/typeform/lambda/fetch-forms.js b/apps/typeform/lambda/fetch-forms.js index 64643c386c3..a45dd352af3 100644 --- a/apps/typeform/lambda/fetch-forms.js +++ b/apps/typeform/lambda/fetch-forms.js @@ -6,14 +6,14 @@ const fetchForms = async (method, path, token, { fetch }) => { if (method !== 'GET') { return { status: 405, - body: { message: 'Method not allowed.' } + body: { message: 'Method not allowed.' }, }; } const [, workspaceId] = path.split('/'); const response = await fetch(`${BASE_URL}/forms?page_size=200&workspace_id=${workspaceId}`, { headers: { - Authorization: 'Bearer ' + token - } + Authorization: 'Bearer ' + token, + }, }); if (!response.ok) { diff --git a/apps/typeform/lambda/fetch-workspaces.js b/apps/typeform/lambda/fetch-workspaces.js index 493ed08ea74..17e1cd2b5b8 100644 --- a/apps/typeform/lambda/fetch-workspaces.js +++ b/apps/typeform/lambda/fetch-workspaces.js @@ -6,13 +6,13 @@ const fetchWorkspaces = async (method, _path, token, { fetch }) => { if (method !== 'GET') { return { status: 405, - body: { message: 'Method not allowed.' } + body: { message: 'Method not allowed.' }, }; } const response = await fetch(`${BASE_URL}/workspaces`, { headers: { - Authorization: 'Bearer ' + token - } + Authorization: 'Bearer ' + token, + }, }); if (!response.ok) { diff --git a/apps/typeform/lambda/forms-handler.js b/apps/typeform/lambda/forms-handler.js index 28b243e9e7d..8864cd50eba 100644 --- a/apps/typeform/lambda/forms-handler.js +++ b/apps/typeform/lambda/forms-handler.js @@ -6,20 +6,20 @@ module.exports = async (method, path, token, { fetch }) => { if (method !== 'GET') { return { status: 405, - body: { message: 'Method not allowed.' } + body: { message: 'Method not allowed.' }, }; } try { return { status: 200, - body: { forms: await fetchForms(method, path, token, { fetch }) } + body: { forms: await fetchForms(method, path, token, { fetch }) }, }; } catch (err) { const { message, code } = err; return { status: code, - body: { message } + body: { message }, }; } }; diff --git a/apps/typeform/lambda/mocks.js b/apps/typeform/lambda/mocks.js index 20262eedbe5..cce9e0afe2e 100644 --- a/apps/typeform/lambda/mocks.js +++ b/apps/typeform/lambda/mocks.js @@ -2,9 +2,9 @@ const fetch = jest.fn().mockResolvedValue({ status: 200, - arrayBuffer: jest.fn().mockResolvedValue('SOME_ARR_BUFF') + arrayBuffer: jest.fn().mockResolvedValue('SOME_ARR_BUFF'), }); module.exports = { - fetch + fetch, }; diff --git a/apps/typeform/lambda/test/fetch-access-token.test.js b/apps/typeform/lambda/test/fetch-access-token.test.js index e0fdee31e58..aafd190b171 100644 --- a/apps/typeform/lambda/test/fetch-access-token.test.js +++ b/apps/typeform/lambda/test/fetch-access-token.test.js @@ -11,8 +11,8 @@ describe('auth handler', () => { 'http://some-origin', Object.assign(mocks, { fetch: jest.fn().mockResolvedValue({ - status: 500 - }) + status: 500, + }), }) ); expect(fn()).rejects.toEqual(new Error('Typeform token exchange failed')); diff --git a/apps/typeform/lambda/test/forms-handler.test.js b/apps/typeform/lambda/test/forms-handler.test.js index b21d25567ae..c0514659036 100644 --- a/apps/typeform/lambda/test/forms-handler.test.js +++ b/apps/typeform/lambda/test/forms-handler.test.js @@ -22,7 +22,7 @@ describe('forms handler', () => { const error = new Error('Some error happened'); error.code = errorCode; throw error; - } + }, }) ); diff --git a/apps/typeform/lambda/workspaces-handler.js b/apps/typeform/lambda/workspaces-handler.js index 578fc52dae0..9ae47eea43e 100644 --- a/apps/typeform/lambda/workspaces-handler.js +++ b/apps/typeform/lambda/workspaces-handler.js @@ -6,20 +6,20 @@ module.exports = async (method, path, token, { fetch }) => { if (method !== 'GET') { return { status: 405, - body: { message: 'Method not allowed.' } + body: { message: 'Method not allowed.' }, }; } try { return { status: 200, - body: { workspaces: await fetchWorkspaces(method, path, token, { fetch }) } + body: { workspaces: await fetchWorkspaces(method, path, token, { fetch }) }, }; } catch (err) { const { message, code } = err; return { status: code, - body: { message } + body: { message }, }; } }; diff --git a/examples/blog-post-metrics/src/setupTests.ts b/examples/blog-post-metrics/src/setupTests.ts index 1dd407a63ef..8f2609b7b3e 100644 --- a/examples/blog-post-metrics/src/setupTests.ts +++ b/examples/blog-post-metrics/src/setupTests.ts @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom"; +import '@testing-library/jest-dom'; diff --git a/examples/content-type-selection-config/set-editors.tsx b/examples/content-type-selection-config/set-editors.tsx index 44655279688..718ffd93039 100644 --- a/examples/content-type-selection-config/set-editors.tsx +++ b/examples/content-type-selection-config/set-editors.tsx @@ -1,6 +1,6 @@ -import React, { Component } from "react"; -import { AppExtensionSDK } from "@contentful/app-sdk"; -import { Heading, List, ListItem, Switch } from "@contentful/forma-36-react-components"; +import React, { Component } from 'react'; +import { AppExtensionSDK } from '@contentful/app-sdk'; +import { Heading, List, ListItem, Switch } from '@contentful/forma-36-react-components'; interface ConfigProps { sdk: AppExtensionSDK; @@ -87,7 +87,7 @@ export default class Config extends Component { } toggleElement(index: number, current: boolean) { - this.setState(state => { + this.setState((state) => { state.data[index].active = !current; return state; }); @@ -113,4 +113,3 @@ export default class Config extends Component { ); } } - diff --git a/examples/default-values-backend/public/config.js b/examples/default-values-backend/public/config.js index 14db57da71a..30d3ebfb1db 100644 --- a/examples/default-values-backend/public/config.js +++ b/examples/default-values-backend/public/config.js @@ -1,22 +1,22 @@ -import React, { Component } from "react"; -import { render } from "react-dom"; +import React, { Component } from 'react'; +import { render } from 'react-dom'; -import { init, locations } from "@contentful/app-sdk"; -import "@contentful/forma-36-react-components/dist/styles.css"; -import "@contentful/forma-36-fcss/dist/styles.css"; -import { Heading, Note, Form, TextField, Option } from "@contentful/forma-36-react-components"; +import { init, locations } from '@contentful/app-sdk'; +import '@contentful/forma-36-react-components/dist/styles.css'; +import '@contentful/forma-36-fcss/dist/styles.css'; +import { Heading, Note, Form, TextField, Option } from '@contentful/forma-36-react-components'; class Config extends Component { constructor(props) { super(props); - this.state = { parameters: { defaultValue: "a default" } }; + this.state = { parameters: { defaultValue: 'a default' } }; this.app = this.props.sdk.app; this.app.onConfigure(() => this.onConfigure()); } async componentDidMount() { const parameters = await this.app.getParameters(); - this.setState({ parameters: parameters || { defaultValue: "a default" } }, () => + this.setState({ parameters: parameters || { defaultValue: 'a default' } }, () => this.app.setReady() ); } @@ -32,7 +32,7 @@ class Config extends Component { name="default value" labelText="Default value" value={this.state.parameters.defaultValue} - onChange={e => this.setState({ parameters: { defaultValue: e.target.value } })} + onChange={(e) => this.setState({ parameters: { defaultValue: e.target.value } })} > ); @@ -47,11 +47,11 @@ class Config extends Component { } } -init(sdk => { - const root = document.getElementById("root"); +init((sdk) => { + const root = document.getElementById('root'); if (sdk.location.is(locations.LOCATION_APP_CONFIG)) { render(, root); } else { - throw new Error("rendered outside of config location"); + throw new Error('rendered outside of config location'); } }); diff --git a/examples/default-values-backend/src/index.ts b/examples/default-values-backend/src/index.ts index 5c50b74a16e..21c34839fa4 100644 --- a/examples/default-values-backend/src/index.ts +++ b/examples/default-values-backend/src/index.ts @@ -5,19 +5,17 @@ * with the Content Management API (CMA). */ -import * as Hapi from "@hapi/hapi"; -import fetch from "node-fetch"; -import { getManagementToken } from "@contentful/node-apps-toolkit"; -import path from "path"; -import dotenv from "dotenv"; -import fs from "fs"; +import * as Hapi from '@hapi/hapi'; +import fetch from 'node-fetch'; +import { getManagementToken } from '@contentful/node-apps-toolkit'; +import path from 'path'; +import dotenv from 'dotenv'; +import fs from 'fs'; dotenv.config(); const { APP_ID, CONTENT_TYPE_ID, BASE_URL, PRIVATE_APP_KEY } = process.env; if (!APP_ID || !PRIVATE_APP_KEY) { - throw new Error( - "APP ID or private key not specified. Make sure to run app setup first." - ); + throw new Error('APP ID or private key not specified. Make sure to run app setup first.'); } // ------------------- @@ -27,56 +25,56 @@ if (!APP_ID || !PRIVATE_APP_KEY) { const startServer = async () => { const server = Hapi.server({ port: 3543, - host: "localhost", + host: 'localhost', routes: { files: { - relativeTo: path.join(__dirname, "../../dist"), + relativeTo: path.join(__dirname, '../../dist'), }, }, }); - await server.register(require("@hapi/inert")); + await server.register(require('@hapi/inert')); // Here we attach the webook handler to our server server.route({ - method: "POST", - path: "/event-handler", + method: 'POST', + path: '/event-handler', handler: addDefaultDataOnEntryCreation, }); // Here we register a very simple frontend to allow changing settings // The files for this are under ../public server.route({ - method: "GET", - path: "/frontend", + method: 'GET', + path: '/frontend', handler: function (request, h: any) { - return h.file("index.html"); + return h.file('index.html'); }, }); server.route({ - method: "GET", - path: "/{param*}", + method: 'GET', + path: '/{param*}', handler: { directory: { - path: "./", + path: './', }, }, }); await server.start(); - console.log("Server running on %s", server.info.uri); + console.log('Server running on %s', server.info.uri); }; -process.on("unhandledRejection", (err) => { - console.error("[Unhandled Rejection]"); +process.on('unhandledRejection', (err) => { + console.error('[Unhandled Rejection]'); console.log(err); process.exit(1); }); -process.on("uncaughtException", (error) => { - console.error("[Uncaught Exception]"); +process.on('uncaughtException', (error) => { + console.error('[Uncaught Exception]'); console.error(error); process.exit(1); @@ -90,10 +88,7 @@ startServer(); // This route is listening to a webhook that is setup to be called // whenever an Entry of our example content type is created -const addDefaultDataOnEntryCreation = async ( - request: Hapi.Request, - h: Hapi.ResponseToolkit -) => { +const addDefaultDataOnEntryCreation = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => { try { const payload = request.payload as { sys: { @@ -115,16 +110,15 @@ const addDefaultDataOnEntryCreation = async ( console.log( `Entry's content type: ${contentType.sys.id} did not match the content type created for the App, ignoring` ); - return h.response("success").code(204); + return h.response('success').code(204); } // We generate an AppToken based on our RSA keypair const spaceId = space.sys.id; const environmentId = environment.sys.id; - const privateKey = fs.readFileSync( - path.join(__dirname, "../../", PRIVATE_APP_KEY), - { encoding: "utf8" } - ); + const privateKey = fs.readFileSync(path.join(__dirname, '../../', PRIVATE_APP_KEY), { + encoding: 'utf8', + }); const appAccessToken = await getManagementToken(privateKey, { appInstallationId: APP_ID, spaceId, @@ -135,38 +129,37 @@ const addDefaultDataOnEntryCreation = async ( const appInstallation = await fetch( `${BASE_URL}/spaces/${spaceId}/environments/${environmentId}/app_installations/${APP_ID}`, { - method: "GET", + method: 'GET', headers: { Authorization: `Bearer ${appAccessToken}`, - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, } ).then((r) => r.json()); - const defaultValue = - appInstallation?.parameters?.defaultValue || "Default value"; + const defaultValue = appInstallation?.parameters?.defaultValue || 'Default value'; // We make a request to contentful's CMA to update the Entry with our defaul values const res = await fetch( `${BASE_URL}/spaces/${spaceId}/environments/${environmentId}/entries/${id}`, { - method: "PUT", + method: 'PUT', headers: { Authorization: `Bearer ${appAccessToken}`, - "X-Contentful-Content-Type": contentType.sys.id, - "Content-Type": "application/json", - "X-Contentful-Version": version, + 'X-Contentful-Content-Type': contentType.sys.id, + 'Content-Type': 'application/json', + 'X-Contentful-Version': version, }, body: JSON.stringify({ - fields: { title: { "en-US": defaultValue } }, + fields: { title: { 'en-US': defaultValue } }, }), } ); if (res.status === 200) { console.log(`Set default values for Entry ${id}`); - return h.response("success").code(204); + return h.response('success').code(204); } else { - throw new Error("failed to set default values" + (await res.text())); + throw new Error('failed to set default values' + (await res.text())); } } catch (e) { console.error(e); diff --git a/examples/default-values-backend/src/setup.ts b/examples/default-values-backend/src/setup.ts index 6aadd13a23b..a13f77db581 100644 --- a/examples/default-values-backend/src/setup.ts +++ b/examples/default-values-backend/src/setup.ts @@ -8,19 +8,12 @@ * + Set up a webhook that reacts to new Entries */ -import nodeFetch from "node-fetch"; -import fs from "fs"; -import path from "path"; -require("dotenv").config(); -const { - ORG_ID, - SPACE_ID, - ENVIRONMENT_ID, - HOSTED_APP_URL, - BASE_URL, - CMA_TOKEN, - PRIVATE_APP_KEY, -} = process.env; +import nodeFetch from 'node-fetch'; +import fs from 'fs'; +import path from 'path'; +require('dotenv').config(); +const { ORG_ID, SPACE_ID, ENVIRONMENT_ID, HOSTED_APP_URL, BASE_URL, CMA_TOKEN, PRIVATE_APP_KEY } = + process.env; // --------------------------------------- // Main setup flow @@ -32,25 +25,22 @@ async function main() { if (process.env.APP_ID) { APP_ID = process.env.APP_ID; console.log( - "Found an existing APP_ID in .env, re-using it. If you want to set up a new app, remove APP_ID from .env" + 'Found an existing APP_ID in .env, re-using it. If you want to set up a new app, remove APP_ID from .env' ); } else { APP_ID = await createAppDefinition(); - fs.appendFileSync(path.join(__dirname, "..", ".env"), `APP_ID=${APP_ID}\n`); + fs.appendFileSync(path.join(__dirname, '..', '.env'), `APP_ID=${APP_ID}\n`); } // Install App into space/environment await installApp(APP_ID, { // Here you are able to pass installation-specific configuration variables - defaultValue: "This is the default value set by your app", + defaultValue: 'This is the default value set by your app', }); // Create an App Key - if (fs.existsSync(path.join(__dirname, "..", PRIVATE_APP_KEY as string))) { - console.log( - "Found an existing private key under %s, re-using it.", - PRIVATE_APP_KEY - ); + if (fs.existsSync(path.join(__dirname, '..', PRIVATE_APP_KEY as string))) { + console.log('Found an existing private key under %s, re-using it.', PRIVATE_APP_KEY); } else { await createAppKey(APP_ID); } @@ -58,14 +48,11 @@ async function main() { // Create an example Content Type if (process.env.CONTENT_TYPE_ID) { console.log( - "Found an existing CONTENT_TYPE_ID in .env, re-using it. If you want to set up a new content type, remove CONTENT_TYPE_ID from .env" + 'Found an existing CONTENT_TYPE_ID in .env, re-using it. If you want to set up a new content type, remove CONTENT_TYPE_ID from .env' ); } else { const CONTENT_TYPE_ID = await createContentType(); - fs.appendFileSync( - path.join(__dirname, "..", ".env"), - `CONTENT_TYPE_ID=${CONTENT_TYPE_ID}\n` - ); + fs.appendFileSync(path.join(__dirname, '..', '.env'), `CONTENT_TYPE_ID=${CONTENT_TYPE_ID}\n`); } // Add event webhook @@ -88,35 +75,30 @@ const isOk = (status: number) => { async function createAppDefinition() { const body = { - name: "Default field values", + name: 'Default field values', src: `${HOSTED_APP_URL}/frontend`, locations: [ { - location: "app-config", + location: 'app-config', }, ], }; - const response = await nodeFetch( - `${BASE_URL}/organizations/${ORG_ID}/app_definitions`, - { - method: "POST", - headers: { - "Content-Type": "application/vnd.contentful.management.v1+json", - Authorization: `Bearer ${CMA_TOKEN}`, - }, - body: JSON.stringify(body), - } - ); + const response = await nodeFetch(`${BASE_URL}/organizations/${ORG_ID}/app_definitions`, { + method: 'POST', + headers: { + 'Content-Type': 'application/vnd.contentful.management.v1+json', + Authorization: `Bearer ${CMA_TOKEN}`, + }, + body: JSON.stringify(body), + }); if (isOk(response.status)) { const j = await response.json(); console.log(`Created app definition. APP_ID is ${j.sys.id}`); return j.sys.id; } else { - throw new Error( - "App definition creation failed: " + (await response.text()) - ); + throw new Error('App definition creation failed: ' + (await response.text())); } } @@ -124,9 +106,9 @@ async function installApp(APP_ID: string, parameters: any) { const response = await nodeFetch( `${BASE_URL}/spaces/${SPACE_ID}/environments/${ENVIRONMENT_ID}/app_installations/${APP_ID}`, { - method: "PUT", + method: 'PUT', headers: { - "Content-Type": "application/vnd.contentful.management.v1+json", + 'Content-Type': 'application/vnd.contentful.management.v1+json', Authorization: `Bearer ${CMA_TOKEN}`, }, body: JSON.stringify({ @@ -138,7 +120,7 @@ async function installApp(APP_ID: string, parameters: any) { if (isOk(response.status)) { console.log(`Installed app!`); } else { - throw new Error("App installation failed: " + (await response.text())); + throw new Error('App installation failed: ' + (await response.text())); } } @@ -150,9 +132,9 @@ async function createAppKey(APP_ID: string) { const response = await nodeFetch( `${BASE_URL}/organizations/${ORG_ID}/app_definitions/${APP_ID}/keys`, { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', Authorization: `Bearer ${CMA_TOKEN}`, }, body: JSON.stringify(body), @@ -160,33 +142,28 @@ async function createAppKey(APP_ID: string) { ); if (!isOk(response.status)) { - throw new Error("Key creation failed: " + (await response.text())); + throw new Error('Key creation failed: ' + (await response.text())); } const { generated, jwk } = await response.json(); const { privateKey } = generated; - fs.writeFileSync( - path.join(__dirname, "..", PRIVATE_APP_KEY as string), - privateKey - ); - console.log( - `New key pair created for app ${APP_ID} and stored under "./keys"` - ); + fs.writeFileSync(path.join(__dirname, '..', PRIVATE_APP_KEY as string), privateKey); + console.log(`New key pair created for app ${APP_ID} and stored under "./keys"`); return { jwk, privateKey }; } async function createContentType() { const body = { - name: "ExampleWithDefaultTitle", - displayField: "title", + name: 'ExampleWithDefaultTitle', + displayField: 'title', fields: [ { - id: "title", - name: "Title", + id: 'title', + name: 'Title', required: true, localized: true, - type: "Symbol", + type: 'Symbol', }, ], }; @@ -194,9 +171,9 @@ async function createContentType() { const response = await nodeFetch( `${BASE_URL}/spaces/${SPACE_ID}/environments/${ENVIRONMENT_ID}/content_types/${body.name}`, { - method: "PUT", + method: 'PUT', headers: { - "Content-Type": "application/vnd.contentful.management.v1+json", + 'Content-Type': 'application/vnd.contentful.management.v1+json', Authorization: `Bearer ${CMA_TOKEN}`, }, body: JSON.stringify(body), @@ -204,9 +181,9 @@ async function createContentType() { ); if (isOk(response.status)) { - console.log("Set up example content type!"); + console.log('Set up example content type!'); } else { - throw new Error("Content type setup failed: " + (await response.text())); + throw new Error('Content type setup failed: ' + (await response.text())); } const responseBody = await response.json(); @@ -214,36 +191,36 @@ async function createContentType() { const publishResponse = await nodeFetch( `${BASE_URL}/spaces/${SPACE_ID}/environments/${ENVIRONMENT_ID}/content_types/${responseBody.sys.id}/published`, { - method: "PUT", + method: 'PUT', headers: { - "Content-Type": "application/vnd.contentful.management.v1+json", + 'Content-Type': 'application/vnd.contentful.management.v1+json', Authorization: `Bearer ${CMA_TOKEN}`, - "X-Contentful-Version": responseBody.sys.version, + 'X-Contentful-Version': responseBody.sys.version, }, - body: "{}", + body: '{}', } ); if (isOk(publishResponse.status)) { - console.log("Published example content type!"); + console.log('Published example content type!'); return responseBody.sys.id; } else { - throw new Error("Publish content type failed: " + responseBody); + throw new Error('Publish content type failed: ' + responseBody); } } async function createAppEvent(APP_ID: string) { const body = { targetUrl: `${HOSTED_APP_URL}/event-handler`, - topics: ["Entry.create"], + topics: ['Entry.create'], }; const response = await nodeFetch( `${BASE_URL}/organizations/${ORG_ID}/app_definitions/${APP_ID}/event_subscription`, { - method: "PUT", + method: 'PUT', headers: { - "Content-Type": "application/vnd.contentful.management.v1+json", + 'Content-Type': 'application/vnd.contentful.management.v1+json', Authorization: `Bearer ${CMA_TOKEN}`, }, body: JSON.stringify(body), @@ -251,8 +228,8 @@ async function createAppEvent(APP_ID: string) { ); if (isOk(response.status)) { - console.log("Set up App Event!"); + console.log('Set up App Event!'); } else { - throw new Error("App event setup failed: " + (await response.text())); + throw new Error('App event setup failed: ' + (await response.text())); } } diff --git a/examples/page-location/.eslintrc.js b/examples/page-location/.eslintrc.js index 036806858b8..7e98c40fe4f 100644 --- a/examples/page-location/.eslintrc.js +++ b/examples/page-location/.eslintrc.js @@ -3,6 +3,6 @@ module.exports = { require.resolve('@contentful/eslint-config-extension/typescript'), require.resolve('@contentful/eslint-config-extension/jest'), require.resolve('@contentful/eslint-config-extension/jsx-a11y'), - require.resolve('@contentful/eslint-config-extension/react') - ] + require.resolve('@contentful/eslint-config-extension/react'), + ], }; diff --git a/packages/dam-app-base/jest.config.js b/packages/dam-app-base/jest.config.js index fa428d4c5a1..eed3adbd1f6 100644 --- a/packages/dam-app-base/jest.config.js +++ b/packages/dam-app-base/jest.config.js @@ -1,23 +1,18 @@ module.exports = { - roots: ["/src"], + roots: ['/src'], testMatch: [ - "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", - "/src/**/*.{spec,test}.{js,jsx,ts,tsx}", + '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', + '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', ], - testEnvironment: "jsdom", + testEnvironment: 'jsdom', transform: { - "^.+\\.(ts|tsx)$": "ts-jest", + '^.+\\.(ts|tsx)$': 'ts-jest', }, transformIgnorePatterns: [ - "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$", - "^.+\\.module\\.(css|sass|scss)$", + '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', + '^.+\\.module\\.(css|sass|scss)$', ], modulePaths: [], - moduleFileExtensions: [ - "js", - "ts", - "tsx", - "jsx" - ], + moduleFileExtensions: ['js', 'ts', 'tsx', 'jsx'], resetMocks: true, }; diff --git a/packages/dam-app-base/src/AppConfig/AppConfig.spec.tsx b/packages/dam-app-base/src/AppConfig/AppConfig.spec.tsx index db6758496f4..30097beb7d8 100644 --- a/packages/dam-app-base/src/AppConfig/AppConfig.spec.tsx +++ b/packages/dam-app-base/src/AppConfig/AppConfig.spec.tsx @@ -10,36 +10,36 @@ const contentTypes = [ { sys: { id: 'ct1' }, name: 'CT no 1', - fields: [{ id: 'obj', name: 'Some object', type: 'Object' }] + fields: [{ id: 'obj', name: 'Some object', type: 'Object' }], }, { sys: { id: 'ct2' }, name: 'CT no 2', - fields: [{ id: 'txt', name: 'Some text', type: 'Text' }] + fields: [{ id: 'txt', name: 'Some text', type: 'Text' }], }, { sys: { id: 'ct3' }, name: 'CT no 3', fields: [ { id: 'txt', name: 'Some other text', type: 'Text' }, - { id: 'obj', name: 'Some other object', type: 'Object' } - ] - } + { id: 'obj', name: 'Some other object', type: 'Object' }, + ], + }, ]; const makeSdkMock = () => ({ space: { getContentTypes: jest.fn().mockResolvedValue({ items: contentTypes }), - getEditorInterfaces: jest.fn().mockResolvedValue({ items: [] }) + getEditorInterfaces: jest.fn().mockResolvedValue({ items: [] }), }, app: { setReady: jest.fn(), getParameters: jest.fn().mockResolvedValue(null), - onConfigure: jest.fn().mockReturnValue(undefined) + onConfigure: jest.fn().mockReturnValue(undefined), }, ids: { - app: 'some-app' - } + app: 'some-app', + }, }); const validate = () => null; // Means no error @@ -66,14 +66,16 @@ describe('AppConfig', () => { const { getByLabelText } = renderComponent(sdk); await wait(() => getByLabelText(/Cloud name/)); - [[/Cloud name/, ''], [/API key/, ''], [/Max number of files/, '10']].forEach( - ([labelRe, expected]) => { - const configInput = getByLabelText(labelRe) as HTMLInputElement; - expect(configInput.value).toEqual(expected); - } - ); + [ + [/Cloud name/, ''], + [/API key/, ''], + [/Max number of files/, '10'], + ].forEach(([labelRe, expected]) => { + const configInput = getByLabelText(labelRe) as HTMLInputElement; + expect(configInput.value).toEqual(expected); + }); - [/Some object/, /Some other object/].forEach(labelRe => { + [/Some object/, /Some other object/].forEach((labelRe) => { const fieldCheckbox = getByLabelText(labelRe) as HTMLInputElement; expect(fieldCheckbox.checked).toBe(false); }); @@ -84,7 +86,7 @@ describe('AppConfig', () => { sdk.app.getParameters.mockResolvedValueOnce({ cloudName: 'test-cloud', apiKey: 'test-api-key', - maxFiles: 12 + maxFiles: 12, }); sdk.space.getEditorInterfaces.mockResolvedValueOnce({ items: [ @@ -94,11 +96,11 @@ describe('AppConfig', () => { { fieldId: 'obj', widgetNamespace: 'app', - widgetId: 'some-app' - } - ] - } - ] + widgetId: 'some-app', + }, + ], + }, + ], }); const { getByLabelText } = renderComponent(sdk); @@ -107,13 +109,16 @@ describe('AppConfig', () => { [ [/Cloud name/, 'test-cloud'], [/API key/, 'test-api-key'], - [/Max number of files/, '12'] + [/Max number of files/, '12'], ].forEach(([labelRe, expected]) => { const configInput = getByLabelText(labelRe as RegExp) as HTMLInputElement; expect(configInput.value).toEqual(expected); }); - [[/Some object/, false], [/Some other object/, true]].forEach(([labelRe, expected]) => { + [ + [/Some object/, false], + [/Some other object/, true], + ].forEach(([labelRe, expected]) => { const fieldCheckbox = getByLabelText(labelRe as RegExp) as HTMLInputElement; expect(fieldCheckbox.checked).toBe(expected); }); @@ -126,7 +131,7 @@ describe('AppConfig', () => { [ [/Cloud name/, 'test-cloud'], [/API key/, 'test-api-key'], - [/Max number of files/, '12'] + [/Max number of files/, '12'], ].forEach(([labelRe, value]) => { const configInput = getByLabelText(labelRe as RegExp) as HTMLInputElement; fireEvent.change(configInput, { target: { value } }); @@ -142,14 +147,14 @@ describe('AppConfig', () => { parameters: { cloudName: 'test-cloud', apiKey: 'test-api-key', - maxFiles: 12 + maxFiles: 12, }, targetState: { EditorInterface: { ct1: {}, - ct3: { controls: [{ fieldId: 'obj' }] } - } - } + ct3: { controls: [{ fieldId: 'obj' }] }, + }, + }, }); }); }); diff --git a/packages/dam-app-base/src/AppConfig/AppConfig.tsx b/packages/dam-app-base/src/AppConfig/AppConfig.tsx index b0519f8b614..cbc7b89b776 100644 --- a/packages/dam-app-base/src/AppConfig/AppConfig.tsx +++ b/packages/dam-app-base/src/AppConfig/AppConfig.tsx @@ -1,9 +1,6 @@ -import * as React from "react"; +import * as React from 'react'; -import { - AppExtensionSDK, - CollectionResponse, -} from "@contentful/app-sdk"; +import { AppExtensionSDK, CollectionResponse } from '@contentful/app-sdk'; import { Heading, Paragraph, @@ -14,13 +11,13 @@ import { SelectField, Option, TextLink, -} from "@contentful/forma-36-react-components"; -import tokens from "@contentful/forma-36-tokens"; -import { css } from "@emotion/css"; +} from '@contentful/forma-36-react-components'; +import tokens from '@contentful/forma-36-tokens'; +import { css } from '@emotion/css'; -import FieldSelector from "./FieldSelector"; +import FieldSelector from './FieldSelector'; -import { toInputParameters, toExtensionParameters } from "./parameters"; +import { toInputParameters, toExtensionParameters } from './parameters'; import { getCompatibleFields, @@ -30,9 +27,9 @@ import { ContentType, CompatibleFields, SelectedFields, -} from "./fields"; +} from './fields'; -import { Config, ParameterDefinition, ValidateParametersFn } from "../interfaces"; +import { Config, ParameterDefinition, ValidateParametersFn } from '../interfaces'; interface Props { sdk: AppExtensionSDK; @@ -53,25 +50,25 @@ interface State { const styles = { body: css({ - height: "auto", - minHeight: "65vh", - margin: "0 auto", + height: 'auto', + minHeight: '65vh', + margin: '0 auto', marginTop: tokens.spacingXl, padding: `${tokens.spacingXl} ${tokens.spacing2Xl}`, maxWidth: tokens.contentWidthText, backgroundColor: tokens.colorWhite, zIndex: 2, - boxShadow: "0px 0px 20px rgba(0, 0, 0, 0.1)", - borderRadius: "2px", + boxShadow: '0px 0px 20px rgba(0, 0, 0, 0.1)', + borderRadius: '2px', }), background: (color: string) => css({ - display: "block", - position: "absolute", + display: 'block', + position: 'absolute', zIndex: -1, top: 0, - width: "100%", - height: "300px", + width: '100%', + height: '300px', backgroundColor: color, }), section: css({ @@ -81,15 +78,15 @@ const styles = { marginTop: tokens.spacingL, marginBottom: tokens.spacingL, border: 0, - height: "1px", + height: '1px', backgroundColor: tokens.gray300, }), icon: css({ - display: "flex", - justifyContent: "center", - "> img": { - display: "block", - width: "70px", + display: 'flex', + justifyContent: 'center', + '> img': { + display: 'block', + width: '70px', margin: `${tokens.spacingXl} 0`, }, }), @@ -98,8 +95,8 @@ const styles = { export default class AppConfig extends React.Component { state = { contentTypes: [] as ContentType[], - compatibleFields: ({} as any) as CompatibleFields, - selectedFields: ({} as any) as SelectedFields, + compatibleFields: {} as any as CompatibleFields, + selectedFields: {} as any as SelectedFields, parameters: toInputParameters(this.props.parameterDefinitions, null), }; @@ -118,12 +115,8 @@ export default class AppConfig extends React.Component { app.getParameters(), ]); - const contentTypes = (contentTypesResponse as CollectionResponse< - ContentType - >).items; - const editorInterfaces = (eisResponse as CollectionResponse< - EditorInterface - >).items; + const contentTypes = (contentTypesResponse as CollectionResponse).items; + const editorInterfaces = (eisResponse as CollectionResponse).items; const compatibleFields = getCompatibleFields(contentTypes); const filteredContentTypes = contentTypes.filter((ct) => { @@ -135,14 +128,8 @@ export default class AppConfig extends React.Component { { contentTypes: filteredContentTypes, compatibleFields, - selectedFields: editorInterfacesToSelectedFields( - editorInterfaces, - ids.app - ), - parameters: toInputParameters( - this.props.parameterDefinitions, - parameters - ), + selectedFields: editorInterfacesToSelectedFields(editorInterfaces, ids.app), + parameters: toInputParameters(this.props.parameterDefinitions, parameters), }, () => app.setReady() ); @@ -158,10 +145,7 @@ export default class AppConfig extends React.Component { } return { - parameters: toExtensionParameters( - this.props.parameterDefinitions, - parameters - ), + parameters: toExtensionParameters(this.props.parameterDefinitions, parameters), targetState: selectedFieldsToTargetState(contentTypes, selectedFields), }; }; @@ -185,7 +169,12 @@ export default class AppConfig extends React.Component { ); } - onParameterChange = (key: string, e: React.ChangeEvent | React.ChangeEvent) => { + onParameterChange = ( + key: string, + e: + | React.ChangeEvent + | React.ChangeEvent + ) => { const { value } = e.currentTarget; this.setState((state) => ({ @@ -199,7 +188,7 @@ export default class AppConfig extends React.Component { }; buildSelectField = (key: string, def: Record) => { - const values = def.value.split(","); + const values = def.value.split(','); return ( { }; renderApp() { - const { - contentTypes, - compatibleFields, - selectedFields, - parameters, - } = this.state; + const { contentTypes, compatibleFields, selectedFields, parameters } = this.state; const { parameterDefinitions, sdk } = this.props; const { ids } = sdk; const { space, environment } = ids; - const hasConfigurationOptions = - parameterDefinitions && parameterDefinitions.length > 0; + const hasConfigurationOptions = parameterDefinitions && parameterDefinitions.length > 0; return ( <> {hasConfigurationOptions && ( @@ -241,7 +224,7 @@ export default class AppConfig extends React.Component {
{parameterDefinitions.map((def) => { const key = `config-input-${def.id}`; - if (def.type === "List") { + if (def.type === 'List') { return this.buildSelectField(key, def); } else { return ( @@ -252,8 +235,8 @@ export default class AppConfig extends React.Component { name={key} labelText={def.name} textInputProps={{ - width: def.type === "Symbol" ? "large" : "medium", - type: def.type === "Symbol" ? "text" : "number", + width: def.type === 'Symbol' ? 'large' : 'medium', + type: def.type === 'Symbol' ? 'text' : 'number', maxLength: 255, }} helpText={def.description} @@ -271,31 +254,29 @@ export default class AppConfig extends React.Component { Assign to fields {contentTypes.length > 0 ? ( - This app can only be used with JSON object{" "} - fields. Select which JSON fields you’d like to enable for this - app. + This app can only be used with JSON object fields. Select which JSON + fields you’d like to enable for this app. ) : ( <> - This app can be used only with JSON object{" "} - fields. + This app can be used only with JSON object fields. - There are no content types with JSON object{" "} - fields in this environment. You can add one in your{" "} + There are no content types with JSON object fields in this + environment. You can add one in your{' '} content model - {" "} + {' '} and assign it to the app from this screen. diff --git a/packages/dam-app-base/src/AppConfig/FieldSelector.tsx b/packages/dam-app-base/src/AppConfig/FieldSelector.tsx index cd425514d9c..22f8c731338 100644 --- a/packages/dam-app-base/src/AppConfig/FieldSelector.tsx +++ b/packages/dam-app-base/src/AppConfig/FieldSelector.tsx @@ -23,7 +23,7 @@ export default class FieldSelector extends React.Component { if (e.currentTarget.checked) { updated[ctId] = (updated[ctId] || []).concat([fieldId]); } else { - updated[ctId] = (updated[ctId] || []).filter(cur => cur !== fieldId); + updated[ctId] = (updated[ctId] || []).filter((cur) => cur !== fieldId); } this.props.onSelectedFieldsChange(updated); @@ -34,13 +34,13 @@ export default class FieldSelector extends React.Component { return ( - {contentTypes.map(ct => { + {contentTypes.map((ct) => { const fields = compatibleFields[ct.sys.id]; return (
{ct.name} - {fields.map(field => ( + {fields.map((field) => ( { @@ -32,8 +38,8 @@ describe('fields', () => { ct2: [], ct3: [ { id: 'bar', name: 'BAR', type: 'Object' }, - { id: 'baz', name: 'BAZ', type: 'Object' } - ] + { id: 'baz', name: 'BAZ', type: 'Object' }, + ], }); }); }); @@ -52,31 +58,31 @@ describe('fields', () => { sys: { contentType: { sys: { id: 'ct1' } } }, controls: [ { fieldId: 'foo', widgetNamespace: 'app', widgetId: 'some-app' }, - { fieldId: 'bar', widgetNamespace: 'app', widgetId: 'some-diff-app' } - ] + { fieldId: 'bar', widgetNamespace: 'app', widgetId: 'some-diff-app' }, + ], }, { sys: { contentType: { sys: { id: 'ct2' } } }, - controls: [] + controls: [], }, { - sys: { contentType: { sys: { id: 'ct3' } } } + sys: { contentType: { sys: { id: 'ct3' } } }, }, { sys: { contentType: { sys: { id: 'ct4' } } }, controls: [ { fieldId: 'foo', widgetNamespace: 'builtin', widgetId: 'singleLine' }, { fieldId: 'bar', widgetNamespace: 'app', widgetId: 'some-app' }, - { fieldId: 'baz', widgetNamespace: 'app', widgetId: 'some-app' } - ] - } + { fieldId: 'baz', widgetNamespace: 'app', widgetId: 'some-app' }, + ], + }, ], 'some-app' ); expect(result).toEqual({ ct1: ['foo'], - ct4: ['bar', 'baz'] + ct4: ['bar', 'baz'], }); }); }); @@ -85,15 +91,15 @@ describe('fields', () => { it('converts selected field map to target state', () => { const result = selectedFieldsToTargetState(contentTypes, { ct1: ['y'], - ct3: ['bar'] + ct3: ['bar'], }); expect(result).toEqual({ EditorInterface: { ct1: { controls: [{ fieldId: 'y' }] }, ct2: {}, - ct3: { controls: [{ fieldId: 'bar' }] } - } + ct3: { controls: [{ fieldId: 'bar' }] }, + }, }); }); }); diff --git a/packages/dam-app-base/src/AppConfig/fields.ts b/packages/dam-app-base/src/AppConfig/fields.ts index 071f21d3bca..082763103f2 100644 --- a/packages/dam-app-base/src/AppConfig/fields.ts +++ b/packages/dam-app-base/src/AppConfig/fields.ts @@ -30,7 +30,7 @@ export function getCompatibleFields(contentTypes: ContentType[]): CompatibleFiel return contentTypes.reduce((acc, ct) => { return { ...acc, - [ct.sys.id]: (ct.fields || []).filter(field => field.type === 'Object') + [ct.sys.id]: (ct.fields || []).filter((field) => field.type === 'Object'), }; }, {}); } @@ -42,9 +42,9 @@ export function editorInterfacesToSelectedFields( return eis.reduce((acc, ei) => { const ctId = get(ei, ['sys', 'contentType', 'sys', 'id']); const fieldIds = get(ei, ['controls'], []) - .filter(control => control.widgetNamespace === 'app' && control.widgetId === appId) - .map(control => control.fieldId) - .filter(fieldId => typeof fieldId === 'string' && fieldId.length > 0); + .filter((control) => control.widgetNamespace === 'app' && control.widgetId === appId) + .map((control) => control.fieldId) + .filter((fieldId) => typeof fieldId === 'string' && fieldId.length > 0); if (ctId && fieldIds.length > 0) { return { ...acc, [ctId]: fieldIds }; @@ -63,9 +63,9 @@ export function selectedFieldsToTargetState( const { id } = ct.sys; const fields = selectedFields[id] || []; const targetState = - fields.length > 0 ? { controls: fields.map(fieldId => ({ fieldId })) } : {}; + fields.length > 0 ? { controls: fields.map((fieldId) => ({ fieldId })) } : {}; return { ...acc, [id]: targetState }; - }, {}) + }, {}), }; } diff --git a/packages/dam-app-base/src/AppConfig/parameters.spec.ts b/packages/dam-app-base/src/AppConfig/parameters.spec.ts index 440c36c4198..7192a56f3bf 100644 --- a/packages/dam-app-base/src/AppConfig/parameters.spec.ts +++ b/packages/dam-app-base/src/AppConfig/parameters.spec.ts @@ -7,14 +7,14 @@ export const definitions: ParameterDefinition[] = [ name: 'Cloud name', description: 'The cloud_name of the account to access.', type: 'Symbol', - required: true + required: true, }, { id: 'apiKey', name: 'API key', description: 'The account API key.', type: 'Symbol', - required: true + required: true, }, { id: 'maxFiles', @@ -22,8 +22,8 @@ export const definitions: ParameterDefinition[] = [ description: 'Max number of files that can be added to a single field. Between 1 and 25.', type: 'Number', required: false, - default: 10 - } + default: 10, + }, ]; describe('parameters', () => { @@ -34,7 +34,7 @@ describe('parameters', () => { expect(result).toEqual({ cloudName: '', apiKey: '', - maxFiles: '10' + maxFiles: '10', }); }); @@ -42,13 +42,13 @@ describe('parameters', () => { const result = toInputParameters(definitions, { cloudName: 'cloud', apiKey: 'key', - maxFiles: 15 + maxFiles: 15, }); expect(result).toEqual({ cloudName: 'cloud', apiKey: 'key', - maxFiles: '15' + maxFiles: '15', }); }); }); @@ -58,13 +58,13 @@ describe('parameters', () => { const result = toExtensionParameters(definitions, { cloudName: 'CLOUD', apiKey: 'KEY', - maxFiles: '17' + maxFiles: '17', }); expect(result).toEqual({ cloudName: 'CLOUD', apiKey: 'KEY', - maxFiles: 17 + maxFiles: 17, }); }); }); diff --git a/packages/dam-app-base/src/AppConfig/parameters.ts b/packages/dam-app-base/src/AppConfig/parameters.ts index 4033c9c3d1b..2ef85f5ee99 100644 --- a/packages/dam-app-base/src/AppConfig/parameters.ts +++ b/packages/dam-app-base/src/AppConfig/parameters.ts @@ -10,7 +10,7 @@ export function toInputParameters( const defaultValue = typeof def.default === 'undefined' ? '' : `${def.default}`; return { ...acc, - [def.id]: `${get(parameterValues, [def.id], defaultValue)}` + [def.id]: `${get(parameterValues, [def.id], defaultValue)}`, }; }, {}); } @@ -23,7 +23,7 @@ export function toExtensionParameters( const value = inputValues[def.id]; return { ...acc, - [def.id]: def.type === 'Number' ? parseInt(value, 10) : value + [def.id]: def.type === 'Number' ? parseInt(value, 10) : value, }; }, {}); } diff --git a/packages/dam-app-base/src/Editor/Field.tsx b/packages/dam-app-base/src/Editor/Field.tsx index 73588628e2b..5ebdb0086d1 100644 --- a/packages/dam-app-base/src/Editor/Field.tsx +++ b/packages/dam-app-base/src/Editor/Field.tsx @@ -23,17 +23,17 @@ interface State { const styles = { sortable: css({ - marginBottom: tokens.spacingM + marginBottom: tokens.spacingM, }), container: css({ - display: 'flex' + display: 'flex', }), logo: css({ display: 'block', width: '30px', height: '30px', - marginRight: tokens.spacingM - }) + marginRight: tokens.spacingM, + }), }; const isObject = (o: any) => typeof o === 'object' && o !== null && !Array.isArray(o); @@ -53,7 +53,7 @@ export default class Field extends React.Component { this.state = { value: Array.isArray(value) ? value : [], valid, - editingDisabled: false + editingDisabled: false, }; } @@ -131,7 +131,8 @@ export default class Field extends React.Component { buttonType="muted" size="small" onClick={this.onDialogOpen} - disabled={isDisabled}> + disabled={isDisabled} + > {this.props.cta}
diff --git a/packages/dam-app-base/src/Editor/SortableComponent.tsx b/packages/dam-app-base/src/Editor/SortableComponent.tsx index dafd69e77ef..eb59d4992d3 100644 --- a/packages/dam-app-base/src/Editor/SortableComponent.tsx +++ b/packages/dam-app-base/src/Editor/SortableComponent.tsx @@ -37,11 +37,11 @@ const DragHandle = SortableHandle(({ url, alt }: DragHandleProp const styles = { container: css({ - maxWidth: '600px' + maxWidth: '600px', }), grid: css({ display: 'grid', - gridTemplateColumns: 'repeat(3, 1fr)' + gridTemplateColumns: 'repeat(3, 1fr)', }), card: (disabled: boolean) => css({ @@ -55,15 +55,15 @@ const styles = { maxWidth: '150px', maxHeight: '100px', margin: 'auto', - userSelect: 'none' // Image selection sometimes makes drag and drop ugly. - } + userSelect: 'none', // Image selection sometimes makes drag and drop ugly. + }, }), remove: css({ position: 'absolute', top: '-10px', right: '-10px', - backgroundColor: 'white' - }) + backgroundColor: 'white', + }), }; const SortableItem = SortableElement((props: SortableElementProps) => { @@ -103,7 +103,7 @@ const SortableList = SortableContainer((props: SortableC return { counts, - list: [...acc.list, item] + list: [...acc.list, item], }; }, { counts: {}, list: [] } diff --git a/packages/dam-app-base/src/index.tsx b/packages/dam-app-base/src/index.tsx index cc814328665..35d6c6e0bdf 100644 --- a/packages/dam-app-base/src/index.tsx +++ b/packages/dam-app-base/src/index.tsx @@ -6,7 +6,7 @@ import { locations, FieldExtensionSDK, DialogExtensionSDK, - AppExtensionSDK + AppExtensionSDK, } from '@contentful/app-sdk'; import '@contentful/forma-36-react-components/dist/styles.css'; @@ -18,7 +18,7 @@ import AppConfig from './AppConfig/AppConfig'; import { Integration } from './interfaces'; export function setup(integration: Integration) { - init(sdk => { + init((sdk) => { const root = document.getElementById('root'); if (sdk.location.is(locations.LOCATION_DIALOG)) { @@ -57,4 +57,4 @@ export function setup(integration: Integration) { } export * from './AppConfig/fields'; -export * from './interfaces'; \ No newline at end of file +export * from './interfaces'; diff --git a/packages/dam-app-base/src/interfaces.ts b/packages/dam-app-base/src/interfaces.ts index 016e0820722..cfffdb459e2 100644 --- a/packages/dam-app-base/src/interfaces.ts +++ b/packages/dam-app-base/src/interfaces.ts @@ -1,7 +1,4 @@ -import { - DialogExtensionSDK, - FieldExtensionSDK, -} from "@contentful/app-sdk"; +import { DialogExtensionSDK, FieldExtensionSDK } from '@contentful/app-sdk'; /** * Object containing all information configured on the app configuration page. @@ -43,7 +40,7 @@ export interface ParameterDefinition { * - List: List of texts * - Number: Integer */ - type: "Symbol" | "List" | "Number"; + type: 'Symbol' | 'List' | 'Number'; /** * Whether it is possible without providing a value. @@ -57,9 +54,7 @@ export interface ParameterDefinition { * @param parameters Object containg the entered parameters. * @returns `string` containing an error message. `null` if the parameters are valid. */ -export type ValidateParametersFn = ( - parameters: Record -) => string | null; +export type ValidateParametersFn = (parameters: Record) => string | null; /** * Returns the url of the thumbnail of an asset. @@ -68,10 +63,7 @@ export type ValidateParametersFn = ( * @param config App configuration * @returns Tuple containing (1) the url and (2) the text represantation of the asset (optional) */ -export type ThumbnailFn = ( - asset: Asset, - config: Config -) => [string, string | undefined]; +export type ThumbnailFn = (asset: Asset, config: Config) => [string, string | undefined]; export type DeleteFn = (index: number) => void; @@ -126,10 +118,7 @@ export type OpenDialogFn = ( * @param config App configuration * @returns true, if the button in the field location should be disabled. false, if the button should be enabled */ -export type DisabledPredicateFn = ( - currentValue: Asset[], - config: Config -) => boolean; +export type DisabledPredicateFn = (currentValue: Asset[], config: Config) => boolean; export interface Integration { /** diff --git a/packages/ecommerce-app-base/jest.config.js b/packages/ecommerce-app-base/jest.config.js index d89558b89e9..eed3adbd1f6 100644 --- a/packages/ecommerce-app-base/jest.config.js +++ b/packages/ecommerce-app-base/jest.config.js @@ -1,24 +1,18 @@ module.exports = { - roots: ["/src"], + roots: ['/src'], testMatch: [ - "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", - "/src/**/*.{spec,test}.{js,jsx,ts,tsx}", + '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', + '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', ], - testEnvironment: "jsdom", + testEnvironment: 'jsdom', transform: { - "^.+\\.(ts|tsx)$": "ts-jest", + '^.+\\.(ts|tsx)$': 'ts-jest', }, transformIgnorePatterns: [ - "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$", - "^.+\\.module\\.(css|sass|scss)$", + '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', + '^.+\\.module\\.(css|sass|scss)$', ], modulePaths: [], - moduleFileExtensions: [ - "js", - "ts", - "tsx", - "jsx" - ], + moduleFileExtensions: ['js', 'ts', 'tsx', 'jsx'], resetMocks: true, }; - diff --git a/packages/ecommerce-app-base/src/AppConfig/AppConfig.spec.tsx b/packages/ecommerce-app-base/src/AppConfig/AppConfig.spec.tsx index 2ad6ad353b3..0b580ae7201 100644 --- a/packages/ecommerce-app-base/src/AppConfig/AppConfig.spec.tsx +++ b/packages/ecommerce-app-base/src/AppConfig/AppConfig.spec.tsx @@ -12,16 +12,16 @@ const contentTypes = [ name: 'CT1', fields: [ { id: 'product_x', name: 'Product X', type: 'Symbol' }, - { id: 'y', name: 'Y', type: 'Object' } - ] + { id: 'y', name: 'Y', type: 'Object' }, + ], }, { sys: { id: 'ct2' }, name: 'CT2', fields: [ { id: 'foo', name: 'FOO', type: 'Text' }, - { id: 'z', name: 'Z', type: 'Array', items: { type: 'Symbol' } } - ] + { id: 'z', name: 'Z', type: 'Array', items: { type: 'Symbol' } }, + ], }, { sys: { id: 'ct3' }, @@ -30,24 +30,24 @@ const contentTypes = [ { id: 'bar', name: 'BAR', type: 'Object' }, { id: 'baz', name: 'BAZ', type: 'Object' }, { id: 'product_d', name: 'Product D', type: 'Array', items: { type: 'Symbol' } }, - { id: 'product_a', name: 'Product A', type: 'Symbol' } - ] - } + { id: 'product_a', name: 'Product A', type: 'Symbol' }, + ], + }, ]; const makeSdkMock = () => ({ ids: { - app: 'some-app' + app: 'some-app', }, space: { getContentTypes: jest.fn().mockResolvedValue({ items: contentTypes }), - getEditorInterfaces: jest.fn().mockResolvedValue({ items: [] }) + getEditorInterfaces: jest.fn().mockResolvedValue({ items: [] }), }, app: { setReady: jest.fn(), getParameters: jest.fn().mockResolvedValue(null), - onConfigure: jest.fn().mockReturnValue(undefined) - } + onConfigure: jest.fn().mockReturnValue(undefined), + }, }); const validate = () => null; // Means no error @@ -80,13 +80,13 @@ describe('AppConfig', () => { [/Client Secret/, ''], [/^API Endpoint/, ''], [/Auth API Endpoint/, ''], - [/Commercetools data locale/, ''] + [/Commercetools data locale/, ''], ].forEach(([labelRe, expected]) => { const configInput = getByLabelText(labelRe) as HTMLInputElement; expect(configInput.value).toEqual(expected); }); - [/Product X$/, /Product D$/].forEach(labelRe => { + [/Product X$/, /Product D$/].forEach((labelRe) => { const fieldCheckbox = getByLabelText(labelRe) as HTMLInputElement; expect(fieldCheckbox.checked).toBe(false); }); @@ -100,7 +100,7 @@ describe('AppConfig', () => { clientSecret: 'some-secret', apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', - locale: 'en' + locale: 'en', }); sdk.space.getEditorInterfaces.mockResolvedValueOnce({ items: [ @@ -109,10 +109,10 @@ describe('AppConfig', () => { controls: [ { fieldId: 'product_a', widgetNamespace: 'app', widgetId: 'some-app' }, { fieldId: 'bar', widgetNamespace: 'app', widgetId: 'some-diff-app' }, - { fieldId: 'product_d', widgetNamespace: 'app', widgetId: 'some-app' } - ] - } - ] + { fieldId: 'product_d', widgetNamespace: 'app', widgetId: 'some-app' }, + ], + }, + ], }); const { getByLabelText } = renderComponent(sdk); @@ -124,13 +124,16 @@ describe('AppConfig', () => { [/Client Secret/, 'some-secret'], [/^API Endpoint/, 'some-endpoint'], [/Auth API Endpoint/, 'some-auth-endpoint'], - [/Commercetools data locale/, 'en'] + [/Commercetools data locale/, 'en'], ].forEach(([labelRe, expected]) => { const configInput = getByLabelText(labelRe as RegExp) as HTMLInputElement; expect(configInput.value).toEqual(expected); }); - [[/Product X$/, false], [/Product D$/, true]].forEach(([labelRe, expected]) => { + [ + [/Product X$/, false], + [/Product D$/, true], + ].forEach(([labelRe, expected]) => { const fieldCheckbox = getByLabelText(labelRe as RegExp) as HTMLInputElement; expect(fieldCheckbox.checked).toBe(expected); }); @@ -146,7 +149,7 @@ describe('AppConfig', () => { [/Client Secret/, 'some-secret'], [/^API Endpoint/, 'some-endpoint'], [/Auth API Endpoint/, 'some-auth-endpoint'], - [/Commercetools data locale/, 'en'] + [/Commercetools data locale/, 'en'], ].forEach(([labelRe, value]) => { const configInput = getByLabelText(labelRe as RegExp) as HTMLInputElement; fireEvent.change(configInput, { target: { value } }); @@ -165,15 +168,15 @@ describe('AppConfig', () => { clientSecret: 'some-secret', apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', - locale: 'en' + locale: 'en', }, targetState: { EditorInterface: { ct1: {}, ct2: {}, - ct3: { controls: [{ fieldId: 'product_d' }] } - } - } + ct3: { controls: [{ fieldId: 'product_d' }] }, + }, + }, }); }); }); diff --git a/packages/ecommerce-app-base/src/AppConfig/AppConfig.tsx b/packages/ecommerce-app-base/src/AppConfig/AppConfig.tsx index e3781367efc..3cdf8b08457 100644 --- a/packages/ecommerce-app-base/src/AppConfig/AppConfig.tsx +++ b/packages/ecommerce-app-base/src/AppConfig/AppConfig.tsx @@ -8,7 +8,7 @@ import { Typography, TextField, Form, - TextLink + TextLink, } from '@contentful/forma-36-react-components'; import tokens from '@contentful/forma-36-tokens'; import { css } from '@emotion/css'; @@ -25,7 +25,7 @@ import { ContentType, CompatibleFields, SelectedFields, - FieldsSkuTypes + FieldsSkuTypes, } from './fields'; import { Config, Integration, ParameterDefinition, ValidateParametersFn } from '../interfaces'; @@ -61,7 +61,7 @@ const styles = { backgroundColor: tokens.colorWhite, zIndex: 2, boxShadow: '0px 0px 20px rgba(0, 0, 0, 0.1)', - borderRadius: '2px' + borderRadius: '2px', }), background: (color: string) => css({ @@ -71,17 +71,17 @@ const styles = { top: 0, width: '100%', height: '300px', - backgroundColor: color + backgroundColor: color, }), section: css({ - margin: `${tokens.spacingXl} 0` + margin: `${tokens.spacingXl} 0`, }), splitter: css({ marginTop: tokens.spacingL, marginBottom: tokens.spacingL, border: 0, height: '1px', - backgroundColor: tokens.gray300 + backgroundColor: tokens.gray300, }), icon: css({ display: 'flex', @@ -89,9 +89,9 @@ const styles = { '> img': { display: 'block', width: '70px', - margin: `${tokens.spacingXl} 0` - } - }) + margin: `${tokens.spacingXl} 0`, + }, + }), }; export default class AppConfig extends React.Component { @@ -101,7 +101,7 @@ export default class AppConfig extends React.Component { selectedFields: {}, fieldSkuTypes: {}, parameters: toInputParameters(this.props.parameterDefinitions, null), - appReady: false + appReady: false, }; componentDidMount() { @@ -116,14 +116,14 @@ export default class AppConfig extends React.Component { const [contentTypesResponse, eisResponse, parameters] = await Promise.all([ space.getContentTypes(), space.getEditorInterfaces(), - app.getParameters() + app.getParameters(), ]); const contentTypes = (contentTypesResponse as CollectionResponse).items; const editorInterfaces = (eisResponse as CollectionResponse).items; const compatibleFields = getCompatibleFields(contentTypes); - const filteredContentTypes = contentTypes.filter(ct => { + const filteredContentTypes = contentTypes.filter((ct) => { const fields = compatibleFields[ct.sys.id]; return fields && fields.length > 0; }); @@ -135,7 +135,7 @@ export default class AppConfig extends React.Component { selectedFields: editorInterfacesToSelectedFields(editorInterfaces, ids.app), parameters: toInputParameters(this.props.parameterDefinitions, parameters), fieldSkuTypes: (parameters as { skuTypes?: FieldsSkuTypes })?.skuTypes ?? {}, - appReady: true + appReady: true, }, () => app.setReady() ); @@ -158,7 +158,7 @@ export default class AppConfig extends React.Component { return { parameters: updatedParameters, - targetState: selectedFieldsToTargetState(contentTypes, selectedFields) + targetState: selectedFieldsToTargetState(contentTypes, selectedFields), }; }; @@ -184,8 +184,8 @@ export default class AppConfig extends React.Component { onParameterChange = (key: string, e: React.ChangeEvent) => { const { value } = e.currentTarget; - this.setState(state => ({ - parameters: { ...state.parameters, [key]: value } + this.setState((state) => ({ + parameters: { ...state.parameters, [key]: value }, })); }; @@ -198,17 +198,11 @@ export default class AppConfig extends React.Component { }; renderApp() { - const { - contentTypes, - compatibleFields, - selectedFields, - fieldSkuTypes, - parameters, - appReady - } = this.state; + const { contentTypes, compatibleFields, selectedFields, fieldSkuTypes, parameters, appReady } = + this.state; const { parameterDefinitions, sdk, skuTypes } = this.props; const { - ids: { space, environment } + ids: { space, environment }, } = sdk; const hasConfigurationOptions = parameterDefinitions && parameterDefinitions.length > 0; @@ -218,7 +212,7 @@ export default class AppConfig extends React.Component { Configuration - {parameterDefinitions.map(def => { + {parameterDefinitions.map((def) => { const key = `config-input-${def.id}`; return ( @@ -231,7 +225,7 @@ export default class AppConfig extends React.Component { textInputProps={{ width: def.type === 'Symbol' ? 'large' : 'medium', type: def.type === 'Symbol' ? 'text' : 'number', - maxLength: 255 + maxLength: 255, }} helpText={def.description} value={parameters[def.id]} @@ -268,7 +262,8 @@ export default class AppConfig extends React.Component { environment === 'master' ? `https://app.contentful.com/spaces/${space}/content_types` : `https://app.contentful.com/spaces/${space}/environments/${environment}/content_types` - }> + } + > content model {' '} and assign it to the app from this screen. diff --git a/packages/ecommerce-app-base/src/AppConfig/FieldSelector.tsx b/packages/ecommerce-app-base/src/AppConfig/FieldSelector.tsx index 2464ffae66d..72ff7d9cd01 100644 --- a/packages/ecommerce-app-base/src/AppConfig/FieldSelector.tsx +++ b/packages/ecommerce-app-base/src/AppConfig/FieldSelector.tsx @@ -9,7 +9,7 @@ import { FieldGroup, Flex, RadioButtonField, - Paragraph + Paragraph, } from '@contentful/forma-36-react-components'; import { ContentType, CompatibleFields, SelectedFields, FieldsSkuTypes } from './fields'; @@ -36,7 +36,7 @@ export default class FieldSelector extends React.Component { this.state = { initialSelectedFields: { ...props.selectedFields }, - changedSkuTypes: {} + changedSkuTypes: {}, }; } @@ -50,7 +50,7 @@ export default class FieldSelector extends React.Component { if (e.currentTarget.checked) { updated[ctId] = (updated[ctId] || []).concat([fieldId]); } else { - updated[ctId] = (updated[ctId] || []).filter(cur => cur !== fieldId); + updated[ctId] = (updated[ctId] || []).filter((cur) => cur !== fieldId); } this.props.onSelectedFieldsChange(updated); @@ -77,7 +77,7 @@ export default class FieldSelector extends React.Component { changedSkuTypes[ctId][fieldId] = true; this.setState({ - changedSkuTypes + changedSkuTypes, }); } @@ -96,21 +96,21 @@ export default class FieldSelector extends React.Component { contentTypes, selectedFields, fieldSkuTypes, - skuTypes = [] + skuTypes = [], } = this.props; const { changedSkuTypes } = this.state; - const defaultSkuType = skuTypes.find(skuType => skuType.default === true)?.id; + const defaultSkuType = skuTypes.find((skuType) => skuType.default === true)?.id; return ( - {contentTypes.map(ct => { + {contentTypes.map((ct) => { const fields = compatibleFields[ct.sys.id]; return (
{ct.name} - {fields.map(field => ( + {fields.map((field) => ( { {skuTypes.length > 0 && (selectedFields[ct.sys.id] || []).includes(field.id) ? ( <> - {skuTypes.map(skuType => ( + {skuTypes.map((skuType) => ( { @@ -40,8 +43,8 @@ describe('fields', () => { ct2: [{ id: 'z', name: 'Z', type: 'Array', items: { type: 'Symbol' } }], ct3: [ { id: 'd', name: 'D', type: 'Array', items: { type: 'Symbol' } }, - { id: 'a', name: 'A', type: 'Symbol' } - ] + { id: 'a', name: 'A', type: 'Symbol' }, + ], }); }); }); @@ -60,31 +63,31 @@ describe('fields', () => { sys: { contentType: { sys: { id: 'ct1' } } }, controls: [ { fieldId: 'foo', widgetNamespace: 'app', widgetId: 'some-app' }, - { fieldId: 'bar', widgetNamespace: 'app', widgetId: 'some-diff-app' } - ] + { fieldId: 'bar', widgetNamespace: 'app', widgetId: 'some-diff-app' }, + ], }, { sys: { contentType: { sys: { id: 'ct2' } } }, - controls: [] + controls: [], }, { - sys: { contentType: { sys: { id: 'ct3' } } } + sys: { contentType: { sys: { id: 'ct3' } } }, }, { sys: { contentType: { sys: { id: 'ct4' } } }, controls: [ { fieldId: 'foo', widgetNamespace: 'builtin', widgetId: 'singleLine' }, { fieldId: 'bar', widgetNamespace: 'app', widgetId: 'some-app' }, - { fieldId: 'baz', widgetNamespace: 'app', widgetId: 'some-app' } - ] - } + { fieldId: 'baz', widgetNamespace: 'app', widgetId: 'some-app' }, + ], + }, ], 'some-app' ); expect(result).toEqual({ ct1: ['foo'], - ct4: ['bar', 'baz'] + ct4: ['bar', 'baz'], }); }); }); @@ -93,15 +96,15 @@ describe('fields', () => { it('converts selected field map to target state', () => { const result = selectedFieldsToTargetState(contentTypes, { ct1: ['x'], - ct3: ['a'] + ct3: ['a'], }); expect(result).toEqual({ EditorInterface: { ct1: { controls: [{ fieldId: 'x' }] }, ct2: {}, - ct3: { controls: [{ fieldId: 'a' }] } - } + ct3: { controls: [{ fieldId: 'a' }] }, + }, }); }); }); diff --git a/packages/ecommerce-app-base/src/AppConfig/fields.ts b/packages/ecommerce-app-base/src/AppConfig/fields.ts index 55cd23703c3..062596c29c8 100644 --- a/packages/ecommerce-app-base/src/AppConfig/fields.ts +++ b/packages/ecommerce-app-base/src/AppConfig/fields.ts @@ -45,7 +45,7 @@ export function getCompatibleFields(contentTypes: ContentType[]): CompatibleFiel return contentTypes.reduce((acc, ct) => { return { ...acc, - [ct.sys.id]: (ct.fields || []).filter(isCompatibleField) + [ct.sys.id]: (ct.fields || []).filter(isCompatibleField), }; }, {}); } @@ -57,9 +57,9 @@ export function editorInterfacesToSelectedFields( return eis.reduce((acc, ei) => { const ctId = get(ei, ['sys', 'contentType', 'sys', 'id']); const fieldIds = get(ei, ['controls'], []) - .filter(control => control.widgetNamespace === 'app' && control.widgetId === appId) - .map(control => control.fieldId) - .filter(fieldId => typeof fieldId === 'string' && fieldId.length > 0); + .filter((control) => control.widgetNamespace === 'app' && control.widgetId === appId) + .map((control) => control.fieldId) + .filter((fieldId) => typeof fieldId === 'string' && fieldId.length > 0); if (ctId && fieldIds.length > 0) { return { ...acc, [ctId]: fieldIds }; @@ -78,9 +78,9 @@ export function selectedFieldsToTargetState( const { id } = ct.sys; const fields = selectedFields[id] || []; const targetState = - fields.length > 0 ? { controls: fields.map(fieldId => ({ fieldId })) } : {}; + fields.length > 0 ? { controls: fields.map((fieldId) => ({ fieldId })) } : {}; return { ...acc, [id]: targetState }; - }, {}) + }, {}), }; } diff --git a/packages/ecommerce-app-base/src/AppConfig/parameters.spec.ts b/packages/ecommerce-app-base/src/AppConfig/parameters.spec.ts index 979cb68f98a..e761a22446b 100644 --- a/packages/ecommerce-app-base/src/AppConfig/parameters.spec.ts +++ b/packages/ecommerce-app-base/src/AppConfig/parameters.spec.ts @@ -7,43 +7,43 @@ export const definitions: ParameterDefinition[] = [ name: 'Commercetools Project Key', description: 'The Commercetools project key', type: 'Symbol', - required: true + required: true, }, { id: 'clientId', name: 'Client ID', description: 'The client ID', type: 'Symbol', - required: true + required: true, }, { id: 'clientSecret', name: 'Client Secret', description: 'The client secret', type: 'Symbol', - required: true + required: true, }, { id: 'apiEndpoint', name: 'API Endpoint', description: 'The Commercetools API endpoint', type: 'Symbol', - required: true + required: true, }, { id: 'authApiEndpoint', name: 'Auth API Endpoint', description: 'The auth API endpoint', type: 'Symbol', - required: true + required: true, }, { id: 'locale', name: 'Commercetools data locale', description: 'The Commercetools data locale to display', type: 'Symbol', - required: true - } + required: true, + }, ]; describe('parameters', () => { @@ -57,7 +57,7 @@ describe('parameters', () => { clientSecret: '', apiEndpoint: '', authApiEndpoint: '', - locale: '' + locale: '', }); }); @@ -68,7 +68,7 @@ describe('parameters', () => { clientSecret: 'some-secret', apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', - locale: 'en' + locale: 'en', }); expect(result).toEqual({ @@ -77,7 +77,7 @@ describe('parameters', () => { clientSecret: 'some-secret', apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', - locale: 'en' + locale: 'en', }); }); }); @@ -92,8 +92,8 @@ describe('parameters', () => { name: 'Some number param', description: 'Description', type: 'Number', - required: true - } + required: true, + }, ], { projectKey: 'some-key', @@ -102,7 +102,7 @@ describe('parameters', () => { apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', locale: 'en', - numParam: '12345' + numParam: '12345', } ); @@ -113,7 +113,7 @@ describe('parameters', () => { apiEndpoint: 'some-endpoint', authApiEndpoint: 'some-auth-endpoint', locale: 'en', - numParam: 12345 + numParam: 12345, }); }); }); diff --git a/packages/ecommerce-app-base/src/AppConfig/parameters.ts b/packages/ecommerce-app-base/src/AppConfig/parameters.ts index 2dd862217b0..81cbf3990b8 100644 --- a/packages/ecommerce-app-base/src/AppConfig/parameters.ts +++ b/packages/ecommerce-app-base/src/AppConfig/parameters.ts @@ -10,7 +10,7 @@ export function toInputParameters( const defaultValue = typeof def.default === 'undefined' ? '' : `${def.default}`; return { ...acc, - [def.id]: `${get(parameterValues, [def.id], defaultValue)}` + [def.id]: `${get(parameterValues, [def.id], defaultValue)}`, }; }, {}); } @@ -23,7 +23,7 @@ export function toAppParameters( const value = inputValues[def.id]; return { ...acc, - [def.id]: def.type === 'Number' ? parseInt(value, 10) : value + [def.id]: def.type === 'Number' ? parseInt(value, 10) : value, }; }, {}); } diff --git a/packages/ecommerce-app-base/src/Editor/Field.tsx b/packages/ecommerce-app-base/src/Editor/Field.tsx index d5017c45a4b..5cf447d96f0 100644 --- a/packages/ecommerce-app-base/src/Editor/Field.tsx +++ b/packages/ecommerce-app-base/src/Editor/Field.tsx @@ -9,7 +9,7 @@ import { OpenDialogFn, DisabledPredicateFn, MakeCTAFn, - Integration + Integration, } from '../interfaces'; import { FieldsSkuTypes } from '../AppConfig/fields'; @@ -30,17 +30,17 @@ interface State { const styles = { sortable: css({ - marginBottom: tokens.spacingM + marginBottom: tokens.spacingM, }), container: css({ - display: 'flex' + display: 'flex', }), logo: css({ display: 'block', width: '30px', height: '30px', - marginRight: tokens.spacingM - }) + marginRight: tokens.spacingM, + }), }; function fieldValueToState(value?: string | string[]): string[] { @@ -53,7 +53,7 @@ function fieldValueToState(value?: string | string[]): string[] { export default class Field extends React.Component { state = { value: fieldValueToState(this.props.sdk.field.getValue()), - editingDisabled: true + editingDisabled: true, }; componentDidMount() { @@ -86,7 +86,7 @@ export default class Field extends React.Component { const { skuTypes, sdk } = this.props; const config = sdk.parameters.installation; - const defaultSkuType = skuTypes?.find(skuType => skuType.default === true)?.id; + const defaultSkuType = skuTypes?.find((skuType) => skuType.default === true)?.id; const skuType = (config as { skuTypes?: FieldsSkuTypes }).skuTypes?.[sdk.contentType.sys.id]?.[ sdk.field.id @@ -96,7 +96,7 @@ export default class Field extends React.Component { ...config, fieldValue: fieldValueToState(sdk.field.getValue()), fieldType: sdk.field.type, - skuType + skuType, }); if (result.length) { this.updateStateValue(result); @@ -111,7 +111,7 @@ export default class Field extends React.Component { const config = sdk.parameters.installation; const isDisabled = editingDisabled || this.props.isDisabled(selectedSKUs, config); - const defaultSkuType = skuTypes?.find(skuType => skuType.default === true)?.id; + const defaultSkuType = skuTypes?.find((skuType) => skuType.default === true)?.id; const skuType = (config as { skuTypes?: FieldsSkuTypes }).skuTypes?.[sdk.contentType.sys.id]?.[ sdk.field.id @@ -139,7 +139,8 @@ export default class Field extends React.Component { buttonType="muted" size="small" onClick={this.onDialogOpen} - disabled={isDisabled}> + disabled={isDisabled} + > {this.props.makeCTA(sdk.field.type, skuType)}
diff --git a/packages/ecommerce-app-base/src/Editor/SortableComponent.tsx b/packages/ecommerce-app-base/src/Editor/SortableComponent.tsx index 823ecfc35ce..e0e43ba1ef5 100644 --- a/packages/ecommerce-app-base/src/Editor/SortableComponent.tsx +++ b/packages/ecommerce-app-base/src/Editor/SortableComponent.tsx @@ -23,7 +23,7 @@ interface State { export class SortableComponent extends React.Component { state = { - productPreviews: [] + productPreviews: [], }; componentDidMount() { diff --git a/packages/ecommerce-app-base/src/Editor/SortableList.spec.tsx b/packages/ecommerce-app-base/src/Editor/SortableList.spec.tsx index 4cbc8ed5d87..ed73140d242 100644 --- a/packages/ecommerce-app-base/src/Editor/SortableList.spec.tsx +++ b/packages/ecommerce-app-base/src/Editor/SortableList.spec.tsx @@ -6,7 +6,7 @@ import productPreviews from '../__mocks__/productPreviews'; const defaultProps: Props = { disabled: false, productPreviews, - deleteFn: jest.fn() + deleteFn: jest.fn(), }; const renderComponent = (props: Props) => { @@ -16,7 +16,7 @@ const renderComponent = (props: Props) => { jest.mock('react-sortable-hoc', () => ({ SortableContainer: (x: any) => x, SortableElement: (x: any) => x, - SortableHandle: (x: any) => x + SortableHandle: (x: any) => x, })); describe('SortableList', () => { diff --git a/packages/ecommerce-app-base/src/Editor/SortableListItem.spec.tsx b/packages/ecommerce-app-base/src/Editor/SortableListItem.spec.tsx index 8953a8e81dd..9ad89080bca 100644 --- a/packages/ecommerce-app-base/src/Editor/SortableListItem.spec.tsx +++ b/packages/ecommerce-app-base/src/Editor/SortableListItem.spec.tsx @@ -4,14 +4,14 @@ import { Props, SortableListItem } from './SortableListItem'; import productPreviews from '../__mocks__/productPreviews'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); const defaultProps: Props = { product: productPreviews[0], disabled: false, onDelete: jest.fn(), - isSortable: false + isSortable: false, }; const renderComponent = (props: Props) => { @@ -21,7 +21,7 @@ const renderComponent = (props: Props) => { jest.mock('react-sortable-hoc', () => ({ SortableContainer: (x: any) => x, SortableElement: (x: any) => x, - SortableHandle: (x: any) => x + SortableHandle: (x: any) => x, })); describe('SortableListItem', () => { @@ -53,7 +53,7 @@ describe('SortableListItem', () => { it('should render successfully the error variation for missing product', () => { const component = renderComponent({ ...defaultProps, - product: { ...productPreviews[0], name: '' } + product: { ...productPreviews[0], name: '' }, }); fireEvent(component.getByTestId('image'), new Event('error')); expect(component.container).toMatchSnapshot(); diff --git a/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.spec.tsx b/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.spec.tsx index d66195acb7b..2c7fc96ce3c 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.spec.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.spec.tsx @@ -3,13 +3,13 @@ import { configure, render, cleanup } from '@testing-library/react'; import { getPagesRange, Props, Paginator } from '.'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); const defaultProps: Props = { activePage: 3, pageCount: 12, - setActivePage: jest.fn() + setActivePage: jest.fn(), }; const renderComponent = (props: Props) => { diff --git a/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.tsx b/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.tsx index c04ea94cb25..615441044b1 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/Paginator/index.tsx @@ -44,7 +44,8 @@ export function Paginator(props: Props) { className={styles.button} buttonType="muted" disabled={hasOnlyOnePage || activePageIsAtPaginatorStart} - onClick={() => setActivePage(1)}> + onClick={() => setActivePage(1)} + > right ); @@ -78,7 +80,8 @@ export function Paginator(props: Props) { buttonType="muted" className={styles.button} disabled={hasOnlyOnePage || activePageIsAtPaginatorEnd} - onClick={() => setActivePage(pageCount)}> + onClick={() => setActivePage(pageCount)} + > right
diff --git a/packages/ecommerce-app-base/src/SkuPicker/Paginator/styles.ts b/packages/ecommerce-app-base/src/SkuPicker/Paginator/styles.ts index 293c4c97ea2..2c3f8b34a79 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/Paginator/styles.ts +++ b/packages/ecommerce-app-base/src/SkuPicker/Paginator/styles.ts @@ -5,16 +5,16 @@ export const styles = { borderRadius: 0, maxWidth: '45px', span: { - overflow: 'visible !important' - } + overflow: 'visible !important', + }, }), chevronLeft: css({ display: 'flex', - opacity: 0.6 + opacity: 0.6, }), chevronRight: css({ display: 'flex', transform: 'rotate(180deg)', - opacity: 0.6 - }) + opacity: 0.6, + }), }; diff --git a/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.spec.tsx b/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.spec.tsx index 2b1a99d2a58..00265ab0bfa 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.spec.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.spec.tsx @@ -4,13 +4,13 @@ import { Props, ProductListItem } from './ProductListItem'; import productPreviews from '../../__mocks__/productPreviews'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); const defaultProps: Props = { product: productPreviews[0], selectProduct: jest.fn(), - isSelected: false + isSelected: false, }; const renderComponent = (props: Props) => { diff --git a/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.tsx b/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.tsx index e950241f46e..2e7c23e9f6f 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/ProductList/ProductListItem.tsx @@ -126,7 +126,8 @@ export const ProductListItem = (props: Props) => { tabIndex={-1} className={`${styles.product} ${isSelected ? styles.selectedProduct : ''}`} onKeyUp={noop} - onClick={() => selectProduct(product.sku)}> + onClick={() => selectProduct(product.sku)} + > {!imageHasLoaded && !imageHasErrored && ( diff --git a/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.spec.tsx b/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.spec.tsx index b01bc095e35..08da9084f66 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.spec.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.spec.tsx @@ -6,7 +6,7 @@ import productPreviews from '../../__mocks__/productPreviews'; const defaultProps: Props = { products: productPreviews, selectProduct: jest.fn(), - selectedSKUs: [] + selectedSKUs: [], }; const renderComponent = (props: Props) => { @@ -24,7 +24,7 @@ describe('ProductList', () => { it('should render successfully with selected items', () => { const component = renderComponent({ ...defaultProps, - selectedSKUs: [productPreviews[1].sku] + selectedSKUs: [productPreviews[1].sku], }); expect(component.container).toMatchSnapshot(); }); diff --git a/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.tsx b/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.tsx index 16da6c53601..d234ab896c1 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/ProductList/index.tsx @@ -15,17 +15,13 @@ const styles = { display: 'flex', flexWrap: 'wrap', marginLeft: `-${tokens.spacingS}`, - marginRight: `-${tokens.spacingS}` - }) + marginRight: `-${tokens.spacingS}`, + }), }; -export const ProductList = ({ - selectProduct, - selectedSKUs, - products, -}: Props) => ( +export const ProductList = ({ selectProduct, selectedSKUs, products }: Props) => (
- {products.map(product => ( + {products.map((product) => ( ( + place="bottom" + >
+{props.productCount}
diff --git a/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/ProductSelectionListItem.tsx b/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/ProductSelectionListItem.tsx index 8d44b541617..8cc6c539e75 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/ProductSelectionListItem.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/ProductSelectionListItem.tsx @@ -16,11 +16,11 @@ const styles = { position: 'relative', overflow: 'hidden', ':not(:first-of-type)': css({ - marginLeft: tokens.spacingXs + marginLeft: tokens.spacingXs, }), '&:last-child': css({ - marginRight: tokens.spacingXs - }) + marginRight: tokens.spacingXs, + }), }), product: css({ border: '1px solid', @@ -41,9 +41,9 @@ const styles = { borderColor: tokens.gray500, cursor: 'pointer', '> span > div': { - opacity: 1 - } - } + opacity: 1, + }, + }, }), previewImg: (imageHasLoaded: boolean) => css({ @@ -51,7 +51,7 @@ const styles = { margin: '0 auto', minWidth: 'auto', height: '40px', - overflow: 'hidden' + overflow: 'hidden', }), removeIcon: css({ backgroundColor: 'rgba(0,0,0,.65)', @@ -69,8 +69,8 @@ const styles = { position: 'absolute', top: '50%', left: '50%', - transform: 'translate(-50%, -50%)' - }) + transform: 'translate(-50%, -50%)', + }), }), errorImage: css({ backgroundColor: tokens.gray100, @@ -85,9 +85,9 @@ const styles = { position: 'absolute', top: '50%', left: '50%', - transform: 'translate(-50%, -50%)' - }) - }) + transform: 'translate(-50%, -50%)', + }), + }), }; export const ProductSelectionListItem = (props: Props) => { @@ -105,7 +105,8 @@ export const ProductSelectionListItem = (props: Props) => { className={styles.product} onKeyUp={noop} data-test-id={`selection-preview-${product.sku}`} - onClick={() => selectProduct(product.sku)}> + onClick={() => selectProduct(product.sku)} + >
diff --git a/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.spec.tsx b/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.spec.tsx index 2008c05dd9b..28548f6dda4 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.spec.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.spec.tsx @@ -5,7 +5,7 @@ import productPreviews from '../../__mocks__/productPreviews'; const defaultProps: Props = { products: productPreviews, - selectProduct: jest.fn() + selectProduct: jest.fn(), }; const renderComponent = (props: Props) => { diff --git a/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.tsx b/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.tsx index 0d1ee15fb6d..6e97c31611a 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/ProductSelectionList/index.tsx @@ -11,15 +11,15 @@ export interface Props { const styles = { productList: css({ - display: 'flex' - }) + display: 'flex', + }), }; const MAX_PRODUCTS = 10; export const ProductSelectionList = ({ selectProduct, products }: Props) => (
- {products.slice(0, MAX_PRODUCTS).map(product => ( + {products.slice(0, MAX_PRODUCTS).map((product) => ( ))} {products.length > MAX_PRODUCTS && ( diff --git a/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.spec.tsx b/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.spec.tsx index f38aab4c485..324497b1720 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.spec.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.spec.tsx @@ -6,56 +6,55 @@ import productPreviews from '../__mocks__/productPreviews'; import { DialogExtensionSDK } from '@contentful/app-sdk'; configure({ - testIdAttribute: 'data-test-id' + testIdAttribute: 'data-test-id', }); - const renderComponent = async (props: Props) => { const component = render(); // wait for data to load and render await component.findByText('Dress Twin-Set rose'); - component.getAllByTestId('image').forEach(img => fireEvent(img, new Event('load'))); + component.getAllByTestId('image').forEach((img) => fireEvent(img, new Event('load'))); return component; }; jest.mock('react-sortable-hoc', () => ({ SortableContainer: (x: any) => x, SortableElement: (x: any) => x, - SortableHandle: (x: any) => x + SortableHandle: (x: any) => x, })); describe('SkuPicker', () => { let defaultProps: Props; beforeEach(() => { defaultProps = { - sdk: ({ + sdk: { parameters: { installation: {}, invocation: { fieldType: 'Symbol', - fieldValue: [] - } + fieldValue: [], + }, }, close: jest.fn(), notifier: { success: jest.fn(), - error: jest.fn() - } - } as unknown) as DialogExtensionSDK, - fetchProductPreviews: jest.fn(skus => - productPreviews.filter(preview => skus.includes(preview.sku)) + error: jest.fn(), + }, + } as unknown as DialogExtensionSDK, + fetchProductPreviews: jest.fn((skus) => + productPreviews.filter((preview) => skus.includes(preview.sku)) ) as any, fetchProducts: jest.fn(() => ({ pagination: { count: 3, limit: 20, offset: 0, - total: 3 + total: 3, }, - products: productPreviews - })) as any + products: productPreviews, + })) as any, }; - }) + }); afterEach(cleanup); it('should render successfully with no products selected', async () => { @@ -69,10 +68,10 @@ describe('SkuPicker', () => { ...defaultProps, fetchProducts: jest.fn(() => ({ pagination: { - hasNextPage: true + hasNextPage: true, }, - products: productPreviews.slice(0, 2) - })) as any + products: productPreviews.slice(0, 2), + })) as any, }); expect(await findByTestId('infinite-scrolling-pagination')).toBeTruthy(); }); @@ -82,10 +81,10 @@ describe('SkuPicker', () => { ...defaultProps, fetchProducts: jest.fn(() => ({ pagination: { - hasNextPage: false + hasNextPage: false, }, - products: productPreviews.slice(0, 2) - })) as any + products: productPreviews.slice(0, 2), + })) as any, }); expect(queryByTestId('infinite-scrolling-pagination')).toBeNull(); }); @@ -108,8 +107,8 @@ describe('SkuPicker', () => { const { findByTestId } = await renderComponent({ ...defaultProps, sdk: merge({}, defaultProps.sdk, { - parameters: { invocation: { fieldType: 'Array' } } - }) + parameters: { invocation: { fieldType: 'Array' } }, + }), }); const productA = await findByTestId(`product-preview-${productPreviews[1].sku}`); const productB = await findByTestId(`product-preview-${productPreviews[2].sku}`); diff --git a/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.tsx b/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.tsx index 60d404dfd07..8465dfbba50 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.tsx +++ b/packages/ecommerce-app-base/src/SkuPicker/SkuPicker.tsx @@ -11,7 +11,7 @@ import { Pagination, Product, ProductPreviewsFn, - ProductsFn + ProductsFn, } from '../interfaces'; import { ProductSelectionList } from './ProductSelectionList'; import { styles } from './styles'; @@ -56,11 +56,11 @@ export class SkuPicker extends Component { count: 0, limit: 0, offset: 0, - total: 0 + total: 0, }, products: [], selectedProducts: [], - selectedSKUs: get(this.props, ['sdk', 'parameters', 'invocation', 'fieldValue'], []) + selectedSKUs: get(this.props, ['sdk', 'parameters', 'invocation', 'fieldValue'], []), }; setSearchCallback: () => void; @@ -86,7 +86,7 @@ export class SkuPicker extends Component { const { activePage, pagination: { limit }, - search + search, } = this.state; const offset = (activePage - 1) * limit; const fetched = await this.props.fetchProducts(search, { offset }); @@ -117,7 +117,7 @@ export class SkuPicker extends Component { loadMoreProducts = async () => { const { pagination, products } = await this.props.fetchProducts(this.state.search); - this.setState(oldState => ({ pagination, products: [...oldState.products, ...products] })); + this.setState((oldState) => ({ pagination, products: [...oldState.products, ...products] })); }; setActivePage = (activePage: number) => { @@ -135,14 +135,14 @@ export class SkuPicker extends Component { if (this.state.selectedSKUs.includes(sku)) { this.setState( ({ selectedSKUs }) => ({ - selectedSKUs: selectedSKUs.filter(productSku => productSku !== sku) + selectedSKUs: selectedSKUs.filter((productSku) => productSku !== sku), }), () => this.updateSelectedProducts() ); } else { this.setState( ({ selectedSKUs }) => ({ - selectedSKUs: onlyOneProductCanBeSelected ? [sku] : [...selectedSKUs, sku] + selectedSKUs: onlyOneProductCanBeSelected ? [sku] : [...selectedSKUs, sku], }), () => this.updateSelectedProducts() ); @@ -167,7 +167,7 @@ export class SkuPicker extends Component { testId="sku-search" width="medium" value={search} - onChange={event => this.setSearch((event.target as HTMLInputElement).value)} + onChange={(event) => this.setSearch((event.target as HTMLInputElement).value)} /> {!!pagination.total && ( @@ -182,7 +182,8 @@ export class SkuPicker extends Component { className={styles.saveBtn} buttonType="primary" onClick={() => this.props.sdk.close(selectedSKUs)} - disabled={selectedSKUs.length === 0}> + disabled={selectedSKUs.length === 0} + > {makeSaveBtnText(selectedSKUs, skuType)}
@@ -206,7 +207,8 @@ export class SkuPicker extends Component { className={styles.loadMoreButton} buttonType="naked" testId="infinite-scrolling-pagination" - onClick={this.loadMoreProducts}> + onClick={this.loadMoreProducts} + > Load more )} diff --git a/packages/ecommerce-app-base/src/SkuPicker/styles.ts b/packages/ecommerce-app-base/src/SkuPicker/styles.ts index d29e32f43fc..cc4457eff67 100644 --- a/packages/ecommerce-app-base/src/SkuPicker/styles.ts +++ b/packages/ecommerce-app-base/src/SkuPicker/styles.ts @@ -17,8 +17,8 @@ function makeBodyStyle() { padding: `${tokens.spacingL} ${tokens.spacingL} 0 ${tokens.spacingL}`, [`@media screen and (min-height: ${STICKY_HEADER_BREAKPOINT}px)`]: { - padding: `calc(${tokens.spacingL} + ${HEADER_HEIGHT}px) ${tokens.spacingL} 0 ${tokens.spacingL}` - } + padding: `calc(${tokens.spacingL} + ${HEADER_HEIGHT}px) ${tokens.spacingL} 0 ${tokens.spacingL}`, + }, }); } @@ -34,22 +34,22 @@ export const styles = { position: 'fixed', top: 0, zIndex: 1, - width: `calc(100% - 2rem)` - } + width: `calc(100% - 2rem)`, + }, }), body: makeBodyStyle(), total: css({ fontSize: tokens.fontSizeS, color: tokens.gray600, display: 'block', - marginTop: tokens.spacingS + marginTop: tokens.spacingS, }), saveBtn: css({ - marginRight: tokens.spacingM + marginRight: tokens.spacingM, }), paginator: css({ margin: `${tokens.spacingM} auto ${tokens.spacingL} auto`, - textAlign: 'center' + textAlign: 'center', }), leftsideControls: css({ position: 'relative', @@ -58,19 +58,19 @@ export const styles = { zIndex: 1, position: 'absolute', top: '10px', - left: '10px' + left: '10px', }), input: css({ - paddingLeft: '35px' - }) + paddingLeft: '35px', + }), }), rightsideControls: css({ justifyContent: 'flex-end', flexGrow: 1, - display: 'flex' + display: 'flex', }), loadMoreButton: css({ width: '100%', - marginTop: tokens.spacingXs - }) + marginTop: tokens.spacingXs, + }), }; diff --git a/packages/ecommerce-app-base/src/__mocks__/productPreviews.ts b/packages/ecommerce-app-base/src/__mocks__/productPreviews.ts index 7b9b4c5acc3..98b30c06f2f 100644 --- a/packages/ecommerce-app-base/src/__mocks__/productPreviews.ts +++ b/packages/ecommerce-app-base/src/__mocks__/productPreviews.ts @@ -6,22 +6,22 @@ const productPreviews: Product[] = [ image: 'https://s3-eu-west-1.amazonaws.com/commercetools-example/products/picture-1.jpg', name: 'Dress Twin-Set rose', sku: 'M0E20130820E90Z', - externalLink: 'https://mc.commercetools.com/example-project/products/28a7442d/general' + externalLink: 'https://mc.commercetools.com/example-project/products/28a7442d/general', }, { id: '4e5446e8', image: 'https://s3-eu-west-1.amazonaws.com/commercetools-example/products/picture-2.jpg', name: 'Michael Kors – Shopper “Jet Set Travel”', sku: 'A0E2300FX102203', - externalLink: 'https://mc.commercetools.com/example-project/products/4e5446e8/general' + externalLink: 'https://mc.commercetools.com/example-project/products/4e5446e8/general', }, { id: 'db3c234l', image: 'https://s3-eu-west-1.amazonaws.com/commercetools-example/products/picture-3.jpg', name: 'Shirt “Jenny“ Polo Ralph Lauren white', sku: 'M0E21300900DZN7', - externalLink: 'https://mc.commercetools.com/example-project/products/db3c234l/general' - } + externalLink: 'https://mc.commercetools.com/example-project/products/db3c234l/general', + }, ]; export default productPreviews; diff --git a/packages/ecommerce-app-base/src/index.tsx b/packages/ecommerce-app-base/src/index.tsx index 8141d68ece3..9af5aeff296 100644 --- a/packages/ecommerce-app-base/src/index.tsx +++ b/packages/ecommerce-app-base/src/index.tsx @@ -6,7 +6,7 @@ import { locations, FieldExtensionSDK, DialogExtensionSDK, - AppExtensionSDK + AppExtensionSDK, } from '@contentful/app-sdk'; import '@contentful/forma-36-react-components/dist/styles.css'; @@ -18,7 +18,7 @@ import AppConfig from './AppConfig/AppConfig'; import { Integration } from './interfaces'; export function setup(integration: Integration) { - init(sdk => { + init((sdk) => { const root = document.getElementById('root'); if (sdk.location.is(locations.LOCATION_DIALOG)) {