Skip to content

Commit

Permalink
[SDPSUP-1118] [SDPA-3400]Update the webform conditional logic (#594) (#…
Browse files Browse the repository at this point in the history
…600)

* Update the webform conditional logic (#594)

* Updated tests for webform conditional logic

* Updated mapping error handling and tests

* Updated log request message to have full url so it's clickable

* Minor fix
  • Loading branch information
tim-yao authored Nov 20, 2019
1 parent f59b5a0 commit 7129da2
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 123 deletions.
83 changes: 59 additions & 24 deletions packages/ripple-nuxt-tide/lib/core/mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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),
Expand Down Expand Up @@ -233,12 +252,28 @@ export class Mapping {
const propMapping = itemConfig.props[prop]
const getProp = this[_getFieldVal](propMapping, item).then(res => {
props[prop] = res
}).catch(error => {
if (process.server) {
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}`))
})
Expand Down
15 changes: 11 additions & 4 deletions packages/ripple-nuxt-tide/lib/core/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
}
}
}
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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' })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = function (req, res, next) {
const reqUrl = url.parse(req.url)
const reqPath = decodeURI(reqUrl.pathname)
if (reqPath.includes('/api/v')) {
logger.info('Proxy %s %s to backend, res status code: %s.', method, reqPath, status, { label: 'Connect', requestQuery: reqUrl.query, requestId: req.headers['x-request-id'] })
logger.info('Proxy %s request to %s, res status code: %s.', method, process.env.CONTENT_API_SERVER + reqPath, status, { label: 'Connect', requestQuery: reqUrl.query, requestId: req.headers['x-request-id'] })
} else {
logger.info('Server got request: %s %s %s', status, method, reqPath, { label: 'Connect', requestQuery: reqUrl.query, requestId: req.requestId })
}
Expand Down
4 changes: 3 additions & 1 deletion packages/ripple-nuxt-tide/lib/templates/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ export default function ({ $axios, app, res }) {
$axios.onRequest(config => {
// Log all axios' requests
if (process.server) {
logger.info('Making %s request to %s', config.method.toUpperCase(), config.url, {label: 'Axios', requestId: config.headers['X-Request-Id']})
const baseURL = config.baseURL.length > 1 ? config.baseURL.substring(0, config.baseURL.length - 1) : ''
const fullUrl = baseURL + config.url
logger.info('Making %s request to %s', config.method.toUpperCase(), fullUrl, {label: 'Axios', requestId: config.headers['X-Request-Id']})
logger.debug('Headers %O', config.headers, {label: 'Axios'})
}
})
Expand Down
25 changes: 25 additions & 0 deletions packages/ripple-nuxt-tide/modules/webform/conditional-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { logger } from './../../lib/core'
* Will update the field object based on it's state.
* Supports the following states:
* - required
* - disabled
* - enabled
* - visible
* - invisible
* @param {Object} field
* @param {Object} data uses data.model property
*/
Expand All @@ -25,6 +29,25 @@ function testField (field, data) {
field.validator.splice(idxRequired, 1)
}
break

case 'disabled':
field.disabled = isPass
break

case 'enabled':
const enable = isPass
field.disabled = !enable
break

case 'visible':
field.visible = isPass
break

case 'invisible':
const invisible = isPass
field.visible = !invisible
break

default:
logger.warn('Form: State "%s" is not supported.', state, { label: 'Webform' })
break
Expand Down Expand Up @@ -113,9 +136,11 @@ function performTriggerCheck (rule) {
result = (rule.modelValue != null && rule.modelValue.length > 0)
break
case 'checked':
// This will only work with Drupal Webform "checkbox", not "checkboxes". "checkboxes" is not supported form element at this stage.
result = (rule.modelValue === true)
break
case 'unchecked':
// This will only work with Drupal Webform "checkbox", not "checkboxes". "checkboxes" is not supported form element at this stage.
result = (rule.modelValue == null || rule.modelValue === false)
break
case 'value':
Expand Down
5 changes: 2 additions & 3 deletions packages/ripple-nuxt-tide/modules/webform/mapping-filters.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { logger } from './../../lib/core'

module.exports = {
// Convert Drupal webform data struture to Vue Form Generator structure
webform: async (drupalFormEntity, { mapping }) => {
Expand Down Expand Up @@ -353,8 +351,9 @@ module.exports = {
} else if (!group.hasOwnProperty('fields') && field.type !== null) {
data.schema.groups.push({ 'fields': [field] })
} else {
const logger = require('@dpc-sdp/ripple-nuxt-tide/lib/core').logger
if (process.server) {
logger.warn(`Webform element type "%s" is not supported in nuxt-tide at this stage, please ask site admin to remove it from relative Tide webform or addd support for it.`, element['#type'], { label: 'Webform' })
logger.warn(`Webform element type "%s" is not supported in "ripple-nuxt-tide" at this stage, please ask site admin to remove it from relative Tide webform or addd support for it.`, element['#type'], { label: 'Webform' })
}
}
}
Expand Down
47 changes: 44 additions & 3 deletions packages/ripple-nuxt-tide/test/unit/mapping.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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.'))
}
})

Expand Down Expand Up @@ -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)

Expand Down
Loading

0 comments on commit 7129da2

Please sign in to comment.