Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[SDPSUP-1118] [SDPA-3400]Update the webform conditional logic (#594) #600

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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