Skip to content
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
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ jobs:
- persist_to_workspace:
root: .
paths:
- dist
- dist

# Just tests commited code.
deployDev:
docker:
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:
- attach_workspace:
at: ./workspace
- run: ./deploy.sh DISCOURSE


workflows:
version: 2
Expand All @@ -100,7 +100,7 @@ workflows:
- test
filters:
branches:
only: ['dev', 'feature/metadata-management']
only: ['dev', 'dev-msinteg']
- deployTest02:
requires:
- test
Expand Down
1 change: 1 addition & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ export const AUTOCOMPLETE_TRIGGER_LENGTH = 3
export const MAINTENANCE_MODE = process.env.MAINTENANCE_MODE

export const LS_INCOMPLETE_PROJECT = 'incompleteProject'
export const LS_INCOMPLETE_WIZARD = 'incompleteWizard'


export const PROJECTS_API_URL = process.env.PROJECTS_API_URL || TC_API_URL
Expand Down
29 changes: 19 additions & 10 deletions src/config/projectWizard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,20 +501,16 @@ export function getProjectCreationTemplateField(product, sectionId, subSectionId
*
* @return {object} object containing price and time estimate
*/
export function getProductEstimate(productId, productConfig) {
let specification = 'topcoder.v1'
let product = null
export function getProductEstimate(projectTemplate, productConfig) {
let price = 0
let minTime = 0
let maxTime = 0
if (productId) {
specification = typeToSpecification[productId]
product = findProduct(productId)
price = _.get(product, 'basePriceEstimate', 0)
minTime = _.get(product, 'baseTimeEstimateMin', 0)
maxTime = _.get(product, 'baseTimeEstimateMax', 0)
if (projectTemplate) {
price = _.get(projectTemplate, 'scope.basePriceEstimate', 0)
minTime = _.get(projectTemplate, 'scope.baseTimeEstimateMin', 0)
maxTime = _.get(projectTemplate, 'scope.baseTimeEstimateMax', 0)
}
const sections = require(`../projectQuestions/${specification}`).default
const sections = projectTemplate.scope.sections
if (sections) {
sections.forEach((section) => {
const subSections = section.subSections
Expand All @@ -532,6 +528,19 @@ export function getProductEstimate(productId, productConfig) {
minTime += _.get(qOption, 'minTimeUp', 0)
maxTime += _.get(qOption, 'maxTimeUp', 0)
}
// right now we are supporting only radio-group and tiled-radio-group type of questions
if(['checkbox-group'].indexOf(q.type) !== -1 && q.affectsQuickQuote) {
const answer = _.get(productConfig, q.fieldName)
if (answer) {
answer.forEach((a) => {
const qOption = _.find(q.options, (o) => o.value === a)
console.log(qOption)
price += _.get(qOption, 'quoteUp', 0)
minTime += _.get(qOption, 'minTimeUp', 0)
maxTime += _.get(qOption, 'maxTimeUp', 0)
})
}
}
})
}
})
Expand Down
226 changes: 226 additions & 0 deletions src/helpers/dependentQuestionsHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import _ from 'lodash'

/**
* Stack datastructure to perform required operations -
* push, pop, peek, empty to perform arithmatic/logical operaitons
*
* Adding new operator to be supported
* 1. Add the symbol/keyword of operation in allowedOps array
* 2. Add new 'case' in the switch present inside the method for newly added operator applyOp
* 3. If required, decide the precendence inside the method hasPrecedence
*
*/

class Stack {
constructor() {
this.top = -1
this.items = []
}

push(item) {
const idx = this.top
this.items[idx+1] = item
this.top = idx + 1
}

pop() {
const popIdx = this.top
const item = this.items[popIdx]
this.top = popIdx - 1
return item
}

peek() {
return this.items[this.top]
}

empty() {
if(this.top === -1)
return true
return false
}
}

/**
* ex - 4 * 5 - 2
* @param op2 peeked from ops
* @param op1 found in the expression
*
* TODO implementation could be more precise and can be based on JS operator precedence values
* see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
*
* @returns true if 'op2' has higher or same precedence as 'op1', otherwise returns false
*/
function hasPrecedence(op1, op2) {
if (op2 === '(' || op2 === ')')
return false
if ((op1 === '*' || op1 === '/' || op1 === '!') && (op2 === '+' || op2 === '-' || op2 === '==' || op2 === '>' || op2 === '<' || op2 === 'contains' || op2 === 'hasLength'))
return false
else
return true
}
/**
* A utility method to apply an operation 'op' on operands
* @param op operator
* @param b operand
* @param a operand
*
* @returns result of operation applied
*/
function applyOp(op, b, a) {
switch (op) {
case '+':
return a + b
case '-':
return a - b
case '*':
return a * b
case '/':
//should we handle the case of b == 0 ?
return a / b
case '==':
return a === b
case '!=':
return a !== b
case '&&':
return a && b
case '||':
return a || b
case '>':
return a > b
case '<':
return a < b
case '!':
return !b
case 'contains':
return (a || []).indexOf(b) > -1
case 'hasLength':
return (a || []).length === b
}
return 0
}

// list of operations allowed by the parser
const allowedOps = ['+', '-', '*', '/', '==', '!=', '&&', '||', '>', '<', 'contains', 'hasLength', '!']
// operators that work only with one param
const oneParamOps = ['!']

// will split expression into tokens mainly using spaces
// additionally we let "(", ")", "!" to be used without spaces around
const opsSplitters = ['\\s+', '\\(', '\\)', '!']
const splitRegexp = new RegExp('(' + opsSplitters.join('|') + ')')

/**
* Javascript parser to parse the logical/arithmetic opertaion provided
* this is based on javascript implementation of "Shunting Yard Algo"
*
* @param expression an expression to be evaluated
* @param data data json to fetch the operands value
*
* @returns true, if the expression evaluates to true otherwise false
*
*/
export function evaluate(expression, data) {
const tokens = expression
.split(splitRegexp)
// remove unnecessary spaces around tokens
.map((token) => token.trim())
// remove empty tokens
.filter((token) => token !== '')

// Stack for operands: 'values'
const values = new Stack()

// Stack for Operators: 'ops'
const ops = new Stack()

for (let i = 0; i < tokens.length; i++) {
if (tokens[i] === '(') {
ops.push(tokens[i])

// Closing brace encountered, solve expression since the last opening brace
} else if (tokens[i] === ')') {
while (ops.peek() !== '(') {
const op = ops.pop()
if (oneParamOps.indexOf(op) !== -1) {
values.push(applyOp(op, values.pop()))
} else {
values.push(applyOp(op, values.pop(), values.pop()))
}
}
ops.pop()//removing opening brace

// Current token is an operator.
} else if (allowedOps.indexOf(tokens[i]) > -1) {
/**
* While top of 'ops' has same or greater precedence to current token,
* Apply operator on top of 'ops' to top two elements in values stack
*/
while (!ops.empty() && hasPrecedence(tokens[i], ops.peek())) {
const op = ops.pop()
if (oneParamOps.indexOf(op) !== -1) {
values.push(applyOp(op, values.pop()))
} else {
values.push(applyOp(op, values.pop(), values.pop()))
}
}

// Push current token to ops
ops.push(tokens[i])

} else {
//console.log(tokens[i])
if (tokens[i] in data) {
//console.log("val : ",data[tokens[i]])
values.push(_.get(data, tokens[i]))
} else {
/*if(tokens[i] == "true")
values.push(true);
else if(tokens[i] == "false")
values.push(false); */
if (!isNaN(tokens[i])) {
values.push(parseInt(tokens[i]))
} else {
//removing single quotes around the text values
values.push(tokens[i].replace(/'/g, ''))
}
}
}
}
//debugger
// Parsed expression tokens are pushed to values/ops respectively,
// Running while loop to evaluate the expression
while (!ops.empty()) {
const op = ops.pop()
if (oneParamOps.indexOf(op) !== -1) {
values.push(applyOp(op, values.pop()))
} else {
values.push(applyOp(op, values.pop(), values.pop()))
}
}
// Top contains result, return it
return values.pop()
}

/**
* Parses expression to find variable names in format of domain name:
* string1.string2.string3 and so on. Minimum one dot "." is required.
*
* @param {String} expression expression
*
* @returns {Array} list of variable names
*/
export function getFieldNamesFromExpression(expression) {
const re = /([a-z]+[a-z0-9]*(?:\.[a-z]+[a-z0-9]*)+)/ig
let match
const fieldNames = []

do {
match = re.exec(expression)
if (match) {
fieldNames.push(match[1])
}
} while (match)

return fieldNames
}
78 changes: 78 additions & 0 deletions src/helpers/dependentQuestionsHelper.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint quotes: 0 */
import chai from 'chai'
import { evaluate } from './dependentQuestionsHelper'

chai.should()

const testData = {
a: 1,
b: 2,
c: 1,
someArray: [1, 2, 3],
someArrayWithText: ['a', 'b', 'c'],
f: false,
t: true,
}

describe('Evaluate', () => {

describe('operator', () => {
it('hasLength (true)', () => {
const expression = 'someArray hasLength 3'
const result = evaluate(expression, testData)

result.should.equal(true)
})

it('hasLength (false)', () => {
const expression = 'someArray hasLength 4'
const result = evaluate(expression, testData)

result.should.equal(false)
})

it('!false => true', () => {
const expression = '!f'
const result = evaluate(expression, testData)

result.should.equal(true)
})

it('!true => false', () => {
const expression = '!t'
const result = evaluate(expression, testData)

result.should.equal(false)
})

it('! with conditions', () => {
const expression = '!t || !f'
const result = evaluate(expression, testData)

result.should.equal(true)
})
})

describe('expression format', () => {
it('no spaces near parenthesis', () => {
const expression = 'someArray contains (a + b) * 2'
const result = evaluate(expression, testData)

result.should.equal(false)
})

it('no spaces near parenthesis with conditions', () => {
const expression = `(someArrayWithText contains 'a') || (someArrayWithText contains 'b') || (someArrayWithText contains 'c')`
const result = evaluate(expression, testData)

result.should.equal(true)
})

it('multiple spaces', () => {
const expression = 'someArray contains ( b - a ) * 2'
const result = evaluate(expression, testData)

result.should.equal(true)
})
})
})
Loading