diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index cb8cab1c3..5d0612fde 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -5061,6 +5061,11 @@ "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=", "dev": true }, + "js-cookie": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.1.4.tgz", + "integrity": "sha1-2k7FA4ZvFJ0WTPJfV57zEBUCXY0=" + }, "js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", diff --git a/package.json b/package.json index e12432a87..8b9dfd8c6 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "history": "^1.17.0", "html5-uploader": "^0.1.1", "isomorphic-fetch": "^2.2.1", + "js-cookie": "^2.1.4", "linkify-it": "^2.0.3", "lodash": "^4.16.4", "moment": "^2.14.1", diff --git a/src/components/SelectDropdown/SelectDropdown.jsx b/src/components/SelectDropdown/SelectDropdown.jsx index 88bced05f..b80914316 100644 --- a/src/components/SelectDropdown/SelectDropdown.jsx +++ b/src/components/SelectDropdown/SelectDropdown.jsx @@ -12,11 +12,19 @@ class SelectDropdown extends Component { } componentWillMount() { + let selectedOption = _.find(this.props.options, (o) => o.value === this.props.value) + if (!selectedOption) { + selectedOption = this.props.options[0] + } this.setState({ - selectedOption: this.props.selectedOption || this.props.options[0] - }, function() { - this.props.setValue(this.state.selectedOption) - }) + selectedOption + }/*, function() { + // FIXME intentionally commented because it was causing multiple renders when used in mobility testing template + // Need to further analyze + // It does not seem to add any value either in both of its usage (it is used in App Screens section + // for design projects and in mobility testing projects) + this.props.setValue(this.state.selectedOption.value) + }*/) } handleClick(option) { @@ -25,7 +33,7 @@ class SelectDropdown extends Component { this.props.onSelect(this.state.selectedOption) } }) - this.props.setValue(option) + this.props.setValue(option.value) } render() { diff --git a/src/config/constants.js b/src/config/constants.js index 7d16d77a2..120a32549 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -260,4 +260,8 @@ export const LS_INCOMPLETE_PROJECT = 'incompleteProject' export const CONNECT_MESSAGE_API_URL = process.env.CONNECT_MESSAGE_API_URL || TC_API_URL -export const NEW_PROJECT_PATH = '/new-project' \ No newline at end of file +export const NEW_PROJECT_PATH = '/new-project' + +// Analytics constants +export const GA_CLICK_ID = '_gclid' +export const GA_CLIENT_ID = '_gacid' \ No newline at end of file diff --git a/src/config/projectQuestions/mobility_testing.v1.0.js b/src/config/projectQuestions/mobility_testing.v1.0.js new file mode 100644 index 000000000..99be349b3 --- /dev/null +++ b/src/config/projectQuestions/mobility_testing.v1.0.js @@ -0,0 +1,430 @@ +import _ from 'lodash' +import { Icons } from 'appirio-tech-react-components' +// import NumberText from '../../components/NumberText/NumberText' +import { findProduct} from '../projectWizard' + + +const isFileRequired = (project, subSections) => { + const subSection = _.find(subSections, (s) => s.type === 'questions') + const fields = _.filter(subSection.questions, q => q.type.indexOf('see-attached') > -1) + // iterate over all seeAttached type fields to check + // if any see attached is checked. + return _.some(_.map( + _.map(fields, 'fieldName'), + fn => _.get(project, `${fn}.seeAttached`) + )) +} + +const sections = [ + { + id: 'appDefinition', + title: (project, showProduct) => { + const product = _.get(project, 'details.products[0]') + if (showProduct && product) { + const prd = findProduct(product) + if (prd) return prd.name + } + return 'Definition' + }, + required: true, + description: 'Please answer a few basic questions about your project. You can also provide the needed information in a supporting document--add a link in the notes section or upload it below.', + subSections: [ + { + id: 'projectName', + required: true, + validationError: 'Please provide a name to your project', + fieldName: 'name', + description: '', + title: 'Project Name', + type: 'project-name' + }, + { + id: 'questions', + required: true, + hideTitle: true, + title: 'Questions', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + required: true, + validationError: 'Please let us know what kind of application you would like to test.', + title: 'What kind of application would you like to test?', + description: 'Please let us know the type of application to test. If you are unsure, please select "Other"', + fieldName: 'details.appDefinition.mobilityTestingType', + type: 'select-dropdown', + options: [ + { value: '', title: 'Select' }, + { value: 'finserv', title: 'Banking or Financial Services' }, + { value: 'ecommerce', title: 'eCommerce' }, + { value: 'entertainment', title: 'Media / Entertainment' }, + { value: 'gaming', title: 'Gaming' }, + { value: 'health', title: 'Health and Fitness' }, + { value: 'manufacturing', title: 'Manufacturing' }, + { value: 'retail', title: 'Retail' }, + { value: 'travel', title: 'Travel / Transportation' }, + { value: 'other', title: 'Other' } + ] + }, + { + icon: 'question', + required: true, + validationError: 'Please let us know if you have test cases.', + title: 'Do you have test cases written?', + description: 'Please let us know if you have any test cases written. If not, they can be created as part of your test cycle.', + fieldName: 'details.appDefinition.testCases', + type: 'radio-group', + options: [ + {value: 'true', label: 'Yes I have test cases.'}, + {value: 'false', label: 'No I do not have test cases.'} + ] + }, + { + icon: 'question', + title: 'Please tell us about your users.', + description: 'Please share information about your end users. Where are they from? What is their goal? This information can help us find the best testers for your application.', + type: 'textbox', + fieldName: 'details.appDefinition.userInfo' + }, + { + icon: 'question', + title: 'Which is your primary device target?', + description: 'Select only the device that you need to develop for. \ + In most cases limiting the scope of your project would result \ + in better final result. Topcoder recommends to always start \ + with the mobile phone view and expand to other devices as your \ + app matures.', + fieldName: 'details.appDefinition.primaryTarget', + type: 'tiled-radio-group', + options: [ + {value: 'phone', title: 'Phone', icon: Icons.IconTechOutlineMobile, iconOptions: { fill: '#00000'}, desc: 'iOS, Android, Hybrid'}, + {value: 'tablet', title: 'Tablet', icon: Icons.IconTechOutlineTablet, iconOptions: { fill: '#00000'}, desc: 'iOS, Android, Hybrid'}, + {value: 'wearable', title: 'Wearable', icon: Icons.IconTechOutlineWatchApple, iconOptions: { fill: '#00000'}, desc: 'Watch OS, Android Wear'} + ] + } + ] + }, + { + id: 'notes', + fieldName: 'details.appDefinition.notes', + title: 'Notes', + description: 'Please enter any additional information like \ + requirements and/or test cases. After creating \ + your project you will be able to upload files.', + type: 'notes' + } + ] + }, + { + id: 'testingNeeds', + required: false, + title: 'Testing Needs', + description: 'Please answer these additional questions to better help us understand your needs.', + subSections: [ + { + id: 'scope', + required: false, + title: 'Scope', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + id: 'testingNeeds.description', + fieldName: 'details.testingNeeds.description', + description: '', + title: 'Please describe your website and/or application.', + type: 'textbox' + }, + { + icon: 'question', + id: 'testingNeeds.inScope', + fieldName: 'details.testingNeeds.inScope', + description: '', + title: 'Please describe which features or components are in-scope in this testing effort.', + type: 'textbox' + }, + { + icon: 'question', + id: 'testingNeeds.outOfScope', + fieldName: 'details.testingNeeds.outOfScope', + description: '', + title: 'Are any features or components out of scope? If yes, please describe.', + type: 'textbox' + }, + { + icon: 'question', + id: 'testingNeeds.duration', + fieldName: 'details.testingNeeds.duration', + description: '', + title: 'Do you have a specific timeline for testing? If so, please provide approximate start and end dates.', + type: 'textbox' + } + ] + }, + { + id: 'testerDetails', + required: false, + title: 'Tester Details', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + id: 'testerDetails.demographics', + fieldName: 'details.testerDetails.demographics', + description: '', + title: 'Do you have preferred demographics you would like to target?', + type: 'textbox' + }, + { + icon: 'question', + id: 'testerDetails.geographies', + fieldName: 'details.testerDetails.geographies', + description: '', + title: 'Would you like to target any specific geographies?', + type: 'textbox' + }, + { + icon: 'question', + id: 'testerDetails.skills', + fieldName: 'details.testerDetails.skills', + description: '', + title: 'Are any specific skills required to test your application? If so, please list them.', + type: 'textbox' + } + ] + }, + { + id: 'testEnvironment', + required: false, + title: 'Testing Enviroment', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + id: 'testEnvironment.environmentDetails', + fieldName: 'details.testEnvironment.environmentDetails', + description: '', + title: 'Do you have a version of the application available for testers to access? If so, please provide details. Details can include a test URL, access information, etc.', + type: 'textbox' + }, + { + icon: 'question', + id: 'testEnvironment.assets', + fieldName: 'details.testEnvironment.assets', + description: '', + title: 'Are any test assets available? For exmaple: test plan, test scenario, test scripts, test data.', + type: 'textbox' + }, + { + icon: 'question', + id: 'testEnvironment.other', + fieldName: 'details.testEnvironment.otherInformation', + description: '', + title: 'Are there any other specific details related to the environment you can share?', + type: 'textbox' + } + ] + }, + { + id: 'targetApplication', + required: false, + title: 'Target Application', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + id: 'targetApplication.description', + fieldName: 'details.targetApplication.description', + description: 'Please describe your application.', + title: '', + type: 'textbox' + }, + { + icon: 'question', + id: 'targetApplication.platform', + fieldName: 'details.targetApplication.platform', + description: 'Please list all platforms the application should be tested on.', + title: '', + type: 'textbox' + }, + { + icon: 'question', + id: 'targetApplication.training', + fieldName: 'details.targetApplication.training', + description: '', + title: 'Does the application require training to utilize it properly? If so, are you able to provide these inputs?', + type: 'textbox' + } + ] + }, { + id: 'cyclePreferences', + required: false, + title: 'Test Cycle Preferences', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + id: 'preferences.suggestions', + fieldName: 'details.cyclePreferences.usabilitySuggestions', + description: 'Would you like usability suggestions included in the issue report?', + title: '', + type: 'textbox' + }, + { + icon: 'question', + id: 'preferences.omissions', + fieldName: 'details.cyclePreferences.omissions', + description: 'Are there any types of defects you would like ommitted from issue reports?', + title: '', + type: 'textbox' + } + ] + }, + { + id: 'files', + required: isFileRequired, + title: (project) => `Project Files (${_.get(project, 'attachments', []).length})` || 'Files', + description: '', + type: 'files', + fieldName: 'attachments' + } + ] + } +] + +export default sections + +export const basicSections = [ + { + id: 'appDefinition', + title: '', + required: true, + description: 'Please answer a few basic questions about your project and, as an option, add links to supporting documents in the “Notes” section. If you have any files to upload, you’ll be able to do so later.', + subSections: [ + { + id: 'projectName', + required: true, + validationError: 'Please provide a name to your project', + fieldName: 'name', + description: '', + title: 'Project Name', + type: 'project-name' + }, + { + id: 'questions', + required: true, + hideTitle: true, + title: 'Questions', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + required: true, + validationError: 'Please let us know what kind of application you would like to test.', + title: 'What kind of application would you like to test?', + description: 'Please let us know the type of application to test. If you are unsure, please select "Other"', + fieldName: 'details.appDefinition.mobilityTestingType', + type: 'select-dropdown', + options: [ + { value: '', title: 'Select' }, + { value: 'finserv', title: 'Banking or Financial Services' }, + { value: 'ecommerce', title: 'eCommerce' }, + { value: 'entertainment', title: 'Media / Entertainment' }, + { value: 'gaming', title: 'Gaming' }, + { value: 'health', title: 'Health and Fitness' }, + { value: 'manufacturing', title: 'Manufacturing' }, + { value: 'retail', title: 'Retail' }, + { value: 'travel', title: 'Travel / Transportation' }, + { value: 'other', title: 'Other' } + ] + }, + { + icon: 'question', + required: true, + validationError: 'Please let us know if you have test cases.', + title: 'Do you have test cases written?', + description: 'Please let us know if you have any test cases written. If not, they can be created as part of your test cycle.', + fieldName: 'details.appDefinition.testCases', + type: 'radio-group', + options: [ + {value: 'true', label: 'Yes I have test cases.'}, + {value: 'false', label: 'No I do not have test cases.'} + ] + }, + { + icon: 'question', + title: 'Please tell us about your users.', + description: 'Please share information about your end users. Where are they from? What is their goal? This information can help us find the best testers for your application.', + type: 'textbox', + fieldName: 'details.appDefinition.userInfo' + }, + { + icon: 'question', + title: 'Which is your primary device target?', + description: 'Select only the device that you need to develop for. \ + In most cases limiting the scope of your project would result \ + in better final result. Topcoder recommends to always start \ + with the mobile phone view and expand to other devices as your \ + app matures.', + fieldName: 'details.appDefinition.primaryTarget', + type: 'tiled-radio-group', + options: [ + {value: 'phone', title: 'Phone', icon: Icons.IconTechOutlineMobile, iconOptions: { fill: '#00000'}, desc: 'iOS, Android, Hybrid'}, + {value: 'tablet', title: 'Tablet', icon: Icons.IconTechOutlineTablet, iconOptions: { fill: '#00000'}, desc: 'iOS, Android, Hybrid'}, + {value: 'wearable', title: 'Wearable', icon: Icons.IconTechOutlineWatchApple, iconOptions: { fill: '#00000'}, desc: 'Watch OS, Android Wear'} + ] + } + /*{ + icon: 'question', + title: 'Approximately how many platform/device - browser configurations to be tested?', + description: '', + fieldName: 'details.appDefinition.browserConfigurations', + type: 'tiled-radio-group', + options: [ + {value: 'upto5', title: 'configurations', icon: NumberText, iconOptions: { number: '5' }, desc: 'or fewer'}, + {value: 'upTo10', title: 'configurations', icon: NumberText, iconOptions: { number: '10' }, desc: 'or fewer'}, + {value: 'upTo20', title: 'configurations', icon: NumberText, iconOptions: { number: '20' }, desc: 'or fewer'}, + {value: 'dontKnow', title: 'Do not know', icon: SVGIconImage, iconOptions: { filePath: 'icon-dont-know', fill: '#00000'}, desc: 'We will find the best fit for you.'} + ] + } + { + id: 'projectInfo', + required: true, + validationError: 'Please provide any user accounts \ + or passwords to access the acount', + fieldName: 'description', + description: 'Please provide any user accounts \ + or passwords to access the acount', + title: 'Access Information', + type: 'textbox' + }, + { + icon: 'question', + required: true, + validationError: 'Please let us know users of your application', + title: 'Who are the users of your application? ', + description: 'Describe the roles and needs of your target users', + type: 'textbox', + fieldName: 'details.appDefinition.users.value' + }*/ + ] + }, + { + id: 'notes', + fieldName: 'details.appDefinition.notes', + title: 'Notes', + description: 'Please enter any additional information like \ + requirements and/or test cases. After creating \ + your project you will be able to upload files.', + type: 'notes' + } + ] + } +] diff --git a/src/config/projectSpecification/typeToSpecification.json b/src/config/projectSpecification/typeToSpecification.json index 70f10e0bc..3c12a8fa4 100644 --- a/src/config/projectSpecification/typeToSpecification.json +++ b/src/config/projectSpecification/typeToSpecification.json @@ -11,7 +11,7 @@ "generic_chatbot": "generic_chatbot.v1.0", "generic_dev": "app_dev.v1.0", "real_world_testing": "real_world_testing.v1.0", - "mobility_testing": "app_dev.v1.0", + "mobility_testing": "mobility_testing.v1.0", "website_performance": "performance_testing.v1.0", "digital_accessability": "crowd_testing.v1.0", "open_source_automation": "crowd_testing.v1.0", diff --git a/src/config/projectWizard/index.js b/src/config/projectWizard/index.js index b9b0d3845..4d059e46c 100644 --- a/src/config/projectWizard/index.js +++ b/src/config/projectWizard/index.js @@ -142,9 +142,9 @@ const products = { } } }, - 'Real World Testing': { - icon: 'product-qa-crowd-testing', - info: 'Exploratory Testing, Cross browser-device Testing', + QA: { + icon: 'product-cat-qa', + info: 'Test and fix bugs in your software', question: 'What kind of quality assurance (QA) do you need?', id: 'quality_assurance', aliases: ['all-quality-assurance'], @@ -161,8 +161,7 @@ const products = { details: 'App Certification, Lab on Hire, User Sentiment Analysis', icon: 'product-qa-mobility-testing', id: 'mobility_testing', - aliases: ['mobility-testing', 'mobility_testing'], - disabled: true + aliases: ['mobility-testing', 'mobility_testing'] }, 'Website Performance': { brief: 'TBD', diff --git a/src/projects/create/containers/CreateContainer.jsx b/src/projects/create/containers/CreateContainer.jsx index 18cd551c5..32e6c37b6 100644 --- a/src/projects/create/containers/CreateContainer.jsx +++ b/src/projects/create/containers/CreateContainer.jsx @@ -1,9 +1,10 @@ import _ from 'lodash' +import Cookies from 'js-cookie' import React, { PropTypes } from 'react' import { withRouter, browserHistory } from 'react-router' import { connect } from 'react-redux' import { renderComponent, branch, compose, withProps } from 'recompose' -import { createProjectWithStatus as createProjectAction, fireProjectDirty, fireProjectDirtyUndo } from '../../actions/project' +import { createProject as createProjectAction, fireProjectDirty, fireProjectDirtyUndo } from '../../actions/project' import CoderBot from '../../../components/CoderBot/CoderBot' import spinnerWhileLoading from '../../../components/LoadingSpinner' import ProjectWizard from '../components/ProjectWizard' @@ -13,7 +14,9 @@ import { LS_INCOMPLETE_PROJECT, PROJECT_STATUS_IN_REVIEW, ACCOUNTS_APP_REGISTER_URL, - NEW_PROJECT_PATH + NEW_PROJECT_PATH, + GA_CLIENT_ID, + GA_CLICK_ID } from '../../../config/constants' const page404 = compose( @@ -131,6 +134,14 @@ class CreateConainer extends React.Component { // if user is logged in and has a valid role, create project // uses dirtyProject from the state as it has the latest changes from the user // this.props.createProjectAction(project) + const gaClickId = Cookies.get(GA_CLICK_ID) + const gaClientId = Cookies.get(GA_CLIENT_ID) + if(gaClientId || gaClickId) { + const googleAnalytics = {} + googleAnalytics[GA_CLICK_ID] = gaClickId + googleAnalytics[GA_CLIENT_ID] = gaClientId + _.set(project, 'details.utm.google', googleAnalytics) + } this.props.createProjectAction(project, PROJECT_STATUS_IN_REVIEW) } else { // redirect to registration/login page diff --git a/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss b/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss index 0959b23b3..009830460 100644 --- a/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss +++ b/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss @@ -60,10 +60,8 @@ .dropdown-wrap.default { background: $tc-gray-neutral-light; border-color: $tc-gray-20; - position: relative; - width: 150px; - left: 170px; - margin-top: -45px; + max-width: 300px; + margin-left: 0px; &:after { border-bottom-color: $tc-gray-70; diff --git a/src/projects/detail/components/SpecQuestions.jsx b/src/projects/detail/components/SpecQuestions.jsx index 499b48b2f..0f15f0d99 100644 --- a/src/projects/detail/components/SpecQuestions.jsx +++ b/src/projects/detail/components/SpecQuestions.jsx @@ -7,6 +7,7 @@ import SpecQuestionList from './SpecQuestionList/SpecQuestionList' import SpecQuestionIcons from './SpecQuestionList/SpecQuestionIcons' import SpecFeatureQuestion from './SpecFeatureQuestion' import ColorSelector from './../../../components/ColorSelector/ColorSelector' +import SelectDropdown from './../../../components/SelectDropdown/SelectDropdown' // HOC for TextareaInput const SeeAttachedTextareaInput = seeAttachedWrapperField(TCFormFields.Textarea) @@ -123,6 +124,13 @@ const SpecQuestions = ({questions, project, dirtyProject, resetFeatures, showFea _.assign(elemProps, { defaultColors: q.defaultColors }) // child = break + case 'select-dropdown': + ChildElem = SelectDropdown + _.assign(elemProps, { + options: q.options, + theme: 'default' + }) + break default: ChildElem =