diff --git a/packages/ripple-nuxt-tide/lib/core/mapping.js b/packages/ripple-nuxt-tide/lib/core/mapping.js index 003daa66f..5b0462099 100644 --- a/packages/ripple-nuxt-tide/lib/core/mapping.js +++ b/packages/ripple-nuxt-tide/lib/core/mapping.js @@ -27,36 +27,51 @@ export class Mapping { } get (data, type = 'tideField') { - let result - let dataMode = 'single' + let result = [] + let dataMode if (Array.isArray(data)) { dataMode = 'array' - // If the data is array, return an array. - result = [] - for (const item of data) { - // Mapping items only for those in mapping configs. - if (this.mappingConfig[type][item.type]) { - const component = this[_getComponent](item, type) - if (component) { - result.push(component) - } + } else { + dataMode = 'single' + data = [data] + } + + for (const item of data) { + // Mapping items only for those in mapping configs. + if (this.mappingConfig[type][item.type]) { + const component = this[_getComponent](item, type) + result.push(component) + } else { + if (process.server) { + logger.warn(`"${item.type}" is not a supported component in map.`, { label: 'Mapping' }) } } - } else { - // If the data is just one item object, return a single item. - result = this[_getComponent](data, type) || {} } // Return a promise as some props' value need to be fetched from Tide. return new Promise(function (resolve, reject) { if (dataMode === 'single') { - resolve(result) + if (result[0]) { + result[0].catch(error => { + if (process.server) { + logger.error('Mapping failed to get result due to a error.', { error, label: 'Mapping' }) + } + reject(error) + }) + resolve(result[0]) + } else { + reject(new Error('Mapping failed to get result.')) + } } else { - let allFetched = Promise.all(result).catch(error => { - reject(new Error(`Mapping failed by resolve the fetching. Error: ${error}`)) + Promise.all(result).catch(error => { + if (process.server) { + logger.error('Mapping failed to get result due to a error.', { error, label: 'Mapping' }) + } + reject(error) + }).then(res => { + resolve(res) }) - resolve(allFetched) } }) } @@ -70,6 +85,9 @@ export class Mapping { for (const filter of filters) { const mapping = this // Pass mapping instance into filters, so we can use filters inside filter. + if (typeof this.mappingFilters[filter] !== 'function') { + return new Error(`Mapping filter "${filter}" is not a function or not defined.`) + } fieldValue = this.mappingFilters[filter](fieldValue, { mapping }) } return fieldValue @@ -129,10 +147,6 @@ export class Mapping { default: let itemConfig = this.mappingConfig[type][item.type] - if (typeof itemConfig === 'undefined') { - throw new Error(`"${item.type}" is not a supported component in map.`) - } - // If it's one to multiple mode, we switch to the right one based on expression. if (this.mappingConfig[type][item.type].components) { const components = this.mappingConfig[type][item.type].components @@ -143,9 +157,14 @@ export class Mapping { itemConfig = component[0] } + const data = await this[_getProps](item, itemConfig) + if (data instanceof Error) { + throw data + } + return { name: this[_getName](itemConfig), - data: await this[_getProps](item, itemConfig), + data: data, cols: this[_getCols](itemConfig), childCols: this[_getChildCols](itemConfig), class: this[_getClass](itemConfig), @@ -233,12 +252,26 @@ export class Mapping { const propMapping = itemConfig.props[prop] const getProp = this[_getFieldVal](propMapping, item).then(res => { props[prop] = res + }).catch(error => { + logger.error('Failed to get prop "%s" value.', prop, { error, label: 'Mapping' }) }) getProps.push(getProp) } return new Promise(function (resolve, reject) { const allFetched = Promise.all(getProps).then(() => { - return props + // If we got any coding error(e.g. custom mapping configuration error), stop doing further and fail the mapping. + // So developer can notice it and fix it. + let getPropsError = null + const propsValues = Object.values(props) + const noError = propsValues.every(value => { + if (value instanceof Error) { + getPropsError = value + return false + } + return true + }) + + return noError ? props : getPropsError }).catch(error => { reject(new Error(`Get props failed by resolve the fetching. Error: ${error}`)) }) diff --git a/packages/ripple-nuxt-tide/lib/core/middleware.js b/packages/ripple-nuxt-tide/lib/core/middleware.js index 0e4e35a9b..3b7e22c40 100644 --- a/packages/ripple-nuxt-tide/lib/core/middleware.js +++ b/packages/ripple-nuxt-tide/lib/core/middleware.js @@ -91,7 +91,9 @@ export default async function (context, pageData) { if (typeof context.res !== 'undefined') { context.res.statusCode = 500 } - logger.error('Failed to get the page data.', { error }) + if (process.server) { + logger.error('Failed to get the page data.', { error, label: 'Middleware' }) + } } } } @@ -115,6 +117,10 @@ export default async function (context, pageData) { const addComponentFromPromise = (promise, name) => { const addMapping = promise.then(res => { pageData.tidePage[name] = res + }).catch(error => { + if (process.server) { + logger.error('Failed to add component for "%s"', name, { error, label: 'Middleware' }) + } }) asyncTasks.push(addMapping) } @@ -203,7 +209,9 @@ export default async function (context, pageData) { const siteSectionData = await context.app.$tide.getSiteData(headersConfig, pageData.tidePage.section) if (siteSectionData instanceof Error) { - logger.error('Could not get site section data from Tide API.', { error: siteSectionData, label: 'Middleware' }) + if (process.server) { + logger.error('Could not get site section data from Tide API.', { error: siteSectionData, label: 'Middleware' }) + } } else { // Section navigation component will only use the main menu. const addSectionNavMenu = siteSectionData.hierarchicalMenus.menuMain @@ -253,9 +261,8 @@ export default async function (context, pageData) { } } } catch (error) { - // TODO: Take some action if above mapping error happens. if (process.server) { - logger.error('Failed to get the mapped component.', { error }) + logger.error('Failed to get the mapped component.', { error, label: 'Middleware' }) } } diff --git a/packages/ripple-nuxt-tide/test/unit/mapping.test.js b/packages/ripple-nuxt-tide/test/unit/mapping.test.js index 204421345..2862960bd 100644 --- a/packages/ripple-nuxt-tide/test/unit/mapping.test.js +++ b/packages/ripple-nuxt-tide/test/unit/mapping.test.js @@ -43,6 +43,17 @@ describe('mapping', () => { class: ['test-class-a', 'test-class-b'] }, + testUndefinedFilter: { + component: 'rpl-test-component', + props: { + a: { + field: 'a', + filters: ['undefinedFilter'] + }, + b: 'b' + } + }, + testFetchItem: { component: 'rpl-test-fetch-component', props: { @@ -193,16 +204,46 @@ describe('mapping', () => { test('should get error by given a item not in mapping config', async () => { const mapping = new Mapping(config) - expect.assertions(1) + expect.assertions(2) const item = { type: 'testItemNotMapped' } try { + // Test single mode await mapping.get(item, 'testField') } catch (e) { - expect(e).toEqual(new Error('"testItemNotMapped" is not a supported component in map.')) + expect(e).toEqual(new Error('Mapping failed to get result.')) + } + + // Test array mode + const result = await mapping.get([item], 'testField') + expect(result).toEqual([]) + }) + + test('should get error by given an undefined filter in mapping config', async () => { + const mapping = new Mapping(config) + expect.assertions(2) + + const item = { + type: 'testUndefinedFilter', + a: 'value a', + b: 'value b' + } + + try { + // Test single mode + await mapping.get(item, 'testField') + } catch (e) { + expect(e).toEqual(new Error('Mapping filter "undefinedFilter" is not a function or not defined.')) + } + + try { + // Test array mode + await mapping.get([item], 'testField') + } catch (e) { + expect(e).toEqual(new Error('Mapping filter "undefinedFilter" is not a function or not defined.')) } }) @@ -244,7 +285,7 @@ describe('mapping', () => { expect(result).toEqual(components) }) - test('should still get resovled if fetcher failed', async () => { + test('should still get resolved if fetcher failed', async () => { const mapping = new Mapping(config, tideApi) expect.assertions(1)