diff --git a/.circleci/config.yml b/.circleci/config.yml index 38f442305..d259b85b7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,45 +48,44 @@ build_docker_image: &build_docker_image ./build.sh no_output_timeout: 20m -build_steps: &build_steps - # Initialization. - - checkout - - setup_remote_docker - - run: *install_dependency - - run: *install_deploysuite - # Restoration of node_modules from cache. - # - restore_cache: *restore_cache_settings_for_build - # Build of Docker image. - - run: - name: "configuring environment" - command: | - ./awsconfiguration.sh ${DEPLOY_ENV} - source awsenvconf - ./psvar-processor.sh -t appenv -p /config/${APPNAME}/buildvar - source buildvar_env - # ./buildenv.sh -e ${DEPLOY_ENV} -b dev_communityapp_buildvar,dev_communityapp_deployvar -l dev_communityapp_buildvar_ps - - run: *build_docker_image - # Caching node modules. - # - save_cache: *save_cache_settings - # Deployment. - - deploy: - name: Running MasterScript - command: | - source awsenvconf - # source buildenvvar - ./psvar-processor.sh -t appenv -p /config/${APPNAME}/deployvar - source deployvar_env - ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -j /config/${APPNAME}/appvar -i ${APPNAME} -p FARGATE - # ./master_deploy.sh -d ECS -e DEV -t latest -s dev_communityapp_taskvar -i communityapp -p FARGATE - if [ "${DEPLOY_ENV}" = "PROD" ]; - then - # Executing plan - curl --request POST \ - --url https://circleci.com/api/v2/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pipeline \ - --header "Circle-Token: ${CIRCLE_TOKEN}" \ - --header 'content-type: application/json' \ - --data '{"branch":"'"$CIRCLE_BRANCH"'","parameters":{"run_smoketesting":true , "run_performancetesting":false, "run_basedeployment": false}}' - fi +build_steps: &build_steps # Initialization. + - checkout + - setup_remote_docker + - run: *install_dependency + - run: *install_deploysuite + # Restoration of node_modules from cache. + # - restore_cache: *restore_cache_settings_for_build + # Build of Docker image. + - run: + name: "configuring environment" + command: | + ./awsconfiguration.sh ${DEPLOY_ENV} + source awsenvconf + ./psvar-processor.sh -t appenv -p /config/${APPNAME}/buildvar + source buildvar_env + # ./buildenv.sh -e ${DEPLOY_ENV} -b dev_communityapp_buildvar,dev_communityapp_deployvar -l dev_communityapp_buildvar_ps + - run: *build_docker_image + # Caching node modules. + # - save_cache: *save_cache_settings + # Deployment. + - deploy: + name: Running MasterScript + command: | + source awsenvconf + # source buildenvvar + ./psvar-processor.sh -t appenv -p /config/${APPNAME}/deployvar + source deployvar_env + ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -j /config/${APPNAME}/appvar -i ${APPNAME} -p FARGATE + # ./master_deploy.sh -d ECS -e DEV -t latest -s dev_communityapp_taskvar -i communityapp -p FARGATE + if [ "${DEPLOY_ENV}" = "PROD" ]; + then + # Executing plan + curl --request POST \ + --url https://circleci.com/api/v2/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pipeline \ + --header "Circle-Token: ${CIRCLE_TOKEN}" \ + --header 'content-type: application/json' \ + --data '{"branch":"'"$CIRCLE_BRANCH"'","parameters":{"run_smoketesting":true , "run_performancetesting":false, "run_basedeployment": false}}' + fi jobs: # Build & Deploy against development backend @@ -95,7 +94,7 @@ jobs: environment: DEPLOY_ENV: "DEV" LOGICAL_ENV: "dev" - APPNAME: "community-app" + APPNAME: "community-app" steps: *build_steps # Build & Deploy against production backend @@ -107,7 +106,6 @@ jobs: APPNAME: "community-app" steps: *build_steps - # Test job for the cases when we do not need deployment. It just rapidly # installs (updates) app dependencies, and runs tests (ESLint, Stylelint, # Jest unit-tests). @@ -123,7 +121,7 @@ jobs: command: git config --global url."https://git@".insteadOf git:// - run: name: App npm install - command: npm install + command: npm ci no_output_timeout: 20m - save_cache: key: test-node-modules-{{ checksum "package-lock.json" }} @@ -181,8 +179,6 @@ workflows: branches: only: - develop - - v6 - - PM-2479 - "build-prod": context: org-global diff --git a/Dockerfile b/Dockerfile index d1c888941..b82836be6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -165,7 +165,7 @@ ENV TOPGEAR_ALLOWED_SUBMISSIONS_DOMAINS=$TOPGEAR_ALLOWED_SUBMISSIONS_DOMAINS RUN npm config set unsafe-perm true RUN git config --global url."https://git@".insteadOf git:// -RUN npm install +RUN npm ci RUN npm test RUN npm run build diff --git a/package-lock.json b/package-lock.json index d7229823c..81daca27e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19939,6 +19939,27 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "r7insight_node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/r7insight_node/-/r7insight_node-2.1.1.tgz", + "integrity": "sha512-xx0kgFxSHWY9aG1109uv4w2b+JLwHseSowOWo1bzCTDBpUk3er2rZdtQ90mAjUYbkh6Hus9DAwWvmHsX5pHaIQ==", + "requires": { + "codependency": "^2.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "reconnect-core": "^1.3.0" + }, + "dependencies": { + "codependency": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/codependency/-/codependency-2.1.0.tgz", + "integrity": "sha512-JIdmYkE8Z6jwH1OUf4a5H5jk9YShPQkaYPUAiN+ktyChmPP77LGbeKrxWGPqdCnpTmt0hRIn8TXBVu01U3HDhg==", + "requires": { + "semver": "^5.3.0" + } + } + } + }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -25073,8 +25094,8 @@ } }, "tc-core-library-js": { - "version": "github:appirio-tech/tc-core-library-js#049c58633669e1a906ad5a6be08db817471e6ae6", - "from": "github:appirio-tech/tc-core-library-js#v2.6.3.1", + "version": "github:topcoder-platform/tc-core-library-js#1075136355e1e1c4779f2138a30f3ffbd718bfa4", + "from": "github:topcoder-platform/tc-core-library-js#master", "requires": { "auth0-js": "^9.4.2", "axios": "^0.19.0", @@ -25846,8 +25867,8 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "topcoder-react-lib": { - "version": "github:topcoder-platform/topcoder-react-lib#4f8468736883ac51649b5fe0b66ad37cb16a6a89", - "from": "github:topcoder-platform/topcoder-react-lib#auth0", + "version": "github:topcoder-platform/topcoder-react-lib#d35f85facf0b2ae2bc80e9a9fc7eadcd7e4c01c6", + "from": "github:topcoder-platform/topcoder-react-lib#develop", "requires": { "@topcoder-platform/tc-auth-lib": "git+https://github.com/topcoder-platform/tc-auth-lib.git#v2.0", "auth0-js": "^6.8.4", @@ -25864,7 +25885,7 @@ "react-redux": "^6.0.1", "redux": "^3.7.2", "redux-actions": "^2.4.0", - "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6", + "tc-core-library-js": "github:topcoder-platform/tc-core-library-js#master", "to-capital-case": "^1.0.0", "topcoder-react-utils": "github:topcoder-platform/topcoder-react-utils#v6" }, @@ -26100,18 +26121,16 @@ } }, "tc-core-library-js": { - "version": "github:appirio-tech/tc-core-library-js#d16413db30b1eed21c0cf426e185bedb2329ddab", - "from": "github:appirio-tech/tc-core-library-js#v2.6", + "version": "github:topcoder-platform/tc-core-library-js#1075136355e1e1c4779f2138a30f3ffbd718bfa4", + "from": "github:topcoder-platform/tc-core-library-js#master", "requires": { - "auth0-js": "^9.4.2", - "axios": "^0.12.0", + "axios": "^0.30.2", "bunyan": "^1.8.12", - "jsonwebtoken": "^8.3.0", - "jwks-rsa": "^1.3.0", - "le_node": "^1.3.1", - "lodash": "^4.17.10", + "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.2.0", + "lodash": "^4.17.15", "millisecond": "^0.1.2", - "request": "^2.88.0" + "r7insight_node": "^2.1.0" }, "dependencies": { "auth0-js": { diff --git a/package.json b/package.json index b61a5d540..702259347 100644 --- a/package.json +++ b/package.json @@ -162,9 +162,9 @@ "showdown": "^1.8.6", "slick-carousel": "^1.8.1", "supertest": "^3.1.0", - "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.3.1", + "tc-core-library-js": "github:topcoder-platform/tc-core-library-js#master", "tc-ui": "^1.0.12", - "topcoder-react-lib": "github:topcoder-platform/topcoder-react-lib#auth0", + "topcoder-react-lib": "github:topcoder-platform/topcoder-react-lib#develop", "topcoder-react-ui-kit": "2.0.1", "topcoder-react-utils": "github:topcoder-platform/topcoder-react-utils#v6", "turndown": "^4.0.2", diff --git a/src/shared/containers/challenge-detail/index.jsx b/src/shared/containers/challenge-detail/index.jsx index 752f14302..af21ce89f 100644 --- a/src/shared/containers/challenge-detail/index.jsx +++ b/src/shared/containers/challenge-detail/index.jsx @@ -57,6 +57,7 @@ import { getService } from 'services/contentful'; import { getSubmissionArtifacts as getSubmissionArtifactsService } from 'services/submissions'; import getReviewSummationsService from 'services/reviewSummations'; import { buildMmSubmissionData, buildStatisticsData } from 'utils/mm-review-summations'; +import { appendUtmParamsToUrl } from 'utils/utm'; // import { // getDisplayRecommendedChallenges, // getRecommendedTags, @@ -349,7 +350,11 @@ class ChallengeDetailPageContainer extends React.Component { } = this.props; if (!auth.tokenV3) { const utmSource = communityId || 'community-app-main'; - window.location.href = `${config.URL.AUTH}/member?retUrl=${encodeURIComponent(`${window.location.origin}${window.location.pathname}`)}&utm_source=${utmSource}®Source=challenges`; + window.location.href = appendUtmParamsToUrl( + `${config.URL.AUTH}/member?retUrl=${encodeURIComponent(`${window.location.origin}${window.location.pathname}`)}®Source=challenges`, { + utm_source: utmSource, + }, + ); } else { // Show security reminder to all registrants this.setState({ diff --git a/src/shared/utils/utm.js b/src/shared/utils/utm.js new file mode 100644 index 000000000..529647ca6 --- /dev/null +++ b/src/shared/utils/utm.js @@ -0,0 +1,69 @@ +// UTM cookie configuration constants +const TC_UTM_COOKIE_NAME = 'tc_utm'; + +/** + * Retrieves and parses the tc_utm cookie + * @returns Parsed UTM parameters or null if cookie doesn't exist + */ +export function getUtmCookie() { + try { + const cookies = document.cookie.split(';'); + const cookieStr = cookies.find(cookie => cookie.trim().startsWith(`${TC_UTM_COOKIE_NAME}=`)); + + if (!cookieStr) { + return null; + } + + // handle values that might contain '=' + const cookieValue = decodeURIComponent(cookieStr.split('=').slice(1).join('=')); + return JSON.parse(cookieValue); + } catch (error) { + return null; + } +} + +/** + * Appends UTM parameters from the tc_utm cookie to a given URL + * Only appends parameters that exist in the cookie + * @param url - The base URL to append parameters to + * @returns URL with UTM parameters appended, or original URL if no cookie exists + */ +export function appendUtmParamsToUrl(url, defaultParams = {}) { + if (!url) { + return url; + } + + const utmParams = getUtmCookie(); + + // If there are no cookie params and no defaults, nothing to do + if ( + (!utmParams || Object.keys(utmParams).length === 0) + && (!defaultParams || Object.keys(defaultParams).length === 0) + ) { + return url; + } + + try { + const urlObj = new URL(url, window.location.origin); + const paramNames = ['utm_source', 'utm_medium', 'utm_campaign']; + + paramNames.forEach((param) => { + const cookieVal = utmParams && utmParams[param]; + const defaultVal = defaultParams && defaultParams[param]; + + // Cookie takes precedence and will overwrite existing query param + if (cookieVal) { + urlObj.searchParams.set(param, cookieVal); + } else if (defaultVal) { + // Only apply default if the URL does not already have the param + if (!urlObj.searchParams.has(param)) { + urlObj.searchParams.set(param, defaultVal); + } + } + }); + + return urlObj.toString(); + } catch (error) { + return url; + } +}