From a056130051abfd7a3d3f0ba17327f0ced98a13f5 Mon Sep 17 00:00:00 2001 From: Wei Ming Chen Date: Fri, 29 Dec 2023 08:43:10 +0800 Subject: [PATCH] feat: send extra data while submitting interview form --- .../InterviewForm/TypeForm/index.js | 8 ++ .../ShareExperience/InterviewForm/index.js | 115 ++++++++++-------- .../InterviewStepsForm/index.js | 9 ++ src/components/ShareExperience/utils.js | 4 +- src/hooks/experiments/useShareLink.js | 6 +- src/utils/GAUtils.js | 25 ++++ 6 files changed, 109 insertions(+), 58 deletions(-) create mode 100644 src/utils/GAUtils.js diff --git a/src/components/ShareExperience/InterviewForm/TypeForm/index.js b/src/components/ShareExperience/InterviewForm/TypeForm/index.js index da36d3ea9..2cd683922 100644 --- a/src/components/ShareExperience/InterviewForm/TypeForm/index.js +++ b/src/components/ShareExperience/InterviewForm/TypeForm/index.js @@ -70,6 +70,9 @@ import { isValidSalary, } from './utils'; import { sendEvent } from 'utils/hotjarUtil'; +import { getUserPseudoId } from 'utils/GAUtils'; + +import { GA_MEASUREMENT_ID } from '../../../../config'; const header =
; const renderCompanyJobTitleHeader = ({ companyName, jobTitle }) => ( @@ -344,6 +347,11 @@ const TypeForm = ({ open, onClose }) => { return; } const body = bodyFromDraft(draft); + const ga_user_pseudo_id = await getUserPseudoId(GA_MEASUREMENT_ID); + body.extra = { + form_type: GA_CATEGORY.SHARE_INTERVIEW_TYPE_FORM, + ga_user_pseudo_id, + }; // section 的標題與預設文字 = 4 + 11 + 19 + 25 個字 const goalValue = calcInterviewExperienceValue(body, 59); diff --git a/src/components/ShareExperience/InterviewForm/index.js b/src/components/ShareExperience/InterviewForm/index.js index e81aec548..c9f076b31 100644 --- a/src/components/ShareExperience/InterviewForm/index.js +++ b/src/components/ShareExperience/InterviewForm/index.js @@ -26,8 +26,11 @@ import { GA_CATEGORY, GA_ACTION } from 'constants/gaConstants'; import PIXEL_CONTENT_CATEGORY from 'constants/pixelConstants'; import { LS_INTERVIEW_FORM_KEY } from 'constants/localStorageKey'; +import { getUserPseudoId } from 'utils/GAUtils'; + import SuccessFeedback from '../common/SuccessFeedback'; import FailFeedback from '../common/FailFeedback'; +import { GA_MEASUREMENT_ID } from '../../../config'; const createSection = id => ( subtitle, @@ -162,64 +165,70 @@ class InterviewForm extends React.Component { }); } - onSubmit() { + onSubmit = async () => { const valid = interviewFormCheck(getInterviewForm(this.state)); if (valid) { localStorage.removeItem(LS_INTERVIEW_FORM_KEY); - const p = this.props.createInterviewExperience({ - body: portInterviewFormToRequestFormat(getInterviewForm(this.state)), - }); - return p.then( - response => { - const experienceId = response.createInterviewExperience.experience.id; - - ReactGA.event({ - category: GA_CATEGORY.SHARE_INTERVIEW_ONE_PAGE, - action: GA_ACTION.UPLOAD_SUCCESS, - label: experienceId, - }); - ReactPixel.track('Purchase', { - value: 1, - currency: 'TWD', - content_category: - PIXEL_CONTENT_CATEGORY.UPLOAD_INTERVIEW_EXPERIENCE, - }); - return () => ( - { - // add delay to more ensure event being sent to GA. - setTimeout(() => { - window.location.replace(`/experiences/${experienceId}`); - }, 1500); - }} - /> - ); - }, - error => { - ReactGA.event({ - category: GA_CATEGORY.SHARE_INTERVIEW_ONE_PAGE, - action: GA_ACTION.UPLOAD_FAIL, - }); - - return ({ buttonClick }) => ( - - ); - }, - ); - } - this.handleState('submitted')(true); - const topInvalidElement = this.getTopInvalidElement(); - if (topInvalidElement !== null) { - scroller.scrollTo(topInvalidElement, { - duration: 1000, - delay: 100, - offset: -100, - smooth: true, - }); + const ga_user_pseudo_id = await getUserPseudoId(GA_MEASUREMENT_ID); + const extra = { + form_type: GA_CATEGORY.SHARE_INTERVIEW_ONE_PAGE, + ga_user_pseudo_id, + }; + + try { + const response = await this.props.createInterviewExperience({ + body: portInterviewFormToRequestFormat( + getInterviewForm(this.state), + extra, + ), + }); + const experienceId = response.createInterviewExperience.experience.id; + + ReactGA.event({ + category: GA_CATEGORY.SHARE_INTERVIEW_ONE_PAGE, + action: GA_ACTION.UPLOAD_SUCCESS, + label: experienceId, + }); + ReactPixel.track('Purchase', { + value: 1, + currency: 'TWD', + content_category: PIXEL_CONTENT_CATEGORY.UPLOAD_INTERVIEW_EXPERIENCE, + }); + return () => ( + { + // add delay to more ensure event being sent to GA. + setTimeout(() => { + window.location.replace(`/experiences/${experienceId}`); + }, 1500); + }} + /> + ); + } catch (error) { + ReactGA.event({ + category: GA_CATEGORY.SHARE_INTERVIEW_ONE_PAGE, + action: GA_ACTION.UPLOAD_FAIL, + }); + + return ({ buttonClick }) => ( + + ); + } + } else { + this.handleState('submitted')(true); + const topInvalidElement = this.getTopInvalidElement(); + if (topInvalidElement !== null) { + scroller.scrollTo(topInvalidElement, { + duration: 1000, + delay: 100, + offset: -100, + smooth: true, + }); + } + return Promise.reject(); } - return Promise.reject(); - } + }; getTopInvalidElement = () => { const order = INTERVIEW_FORM_ORDER; diff --git a/src/components/ShareExperience/InterviewStepsForm/index.js b/src/components/ShareExperience/InterviewStepsForm/index.js index f3be3562c..e019990ef 100644 --- a/src/components/ShareExperience/InterviewStepsForm/index.js +++ b/src/components/ShareExperience/InterviewStepsForm/index.js @@ -22,6 +22,7 @@ import { import StaticHelmet from 'common/StaticHelmet'; import { calcInterviewExperienceValue } from 'utils/uploadSuccessValueCalc'; +import { getUserPseudoId } from 'utils/GAUtils'; import { INVALID, INTERVIEW_FORM_ORDER, @@ -35,6 +36,8 @@ import SuccessFeedback from '../common/SuccessFeedback'; import FailFeedback from '../common/FailFeedback'; import RouteWithSubRoutes from '../../route'; +import { GA_MEASUREMENT_ID } from '../../../config'; + function isExpired(ts) { return Date.now() - ts > 1000 * 60 * 60 * 24 * 3; // 3 days } @@ -218,8 +221,14 @@ class InterviewForm extends React.Component { if (valid) { localStorage.removeItem(LS_INTERVIEW_STEPS_FORM_KEY); + const ga_user_pseudo_id = await getUserPseudoId(GA_MEASUREMENT_ID); + const extra = { + form_type: GA_CATEGORY.SHARE_INTERVIEW_3_STEPS, + ga_user_pseudo_id, + }; const body = portInterviewFormToRequestFormat( getInterviewForm(this.state), + extra, ); // section 的標題與預設文字 = 4 + 11 + 19 + 25 個字 goalValue = calcInterviewExperienceValue(body, 59); diff --git a/src/components/ShareExperience/utils.js b/src/components/ShareExperience/utils.js index 385d0ad95..fe42e8b9c 100644 --- a/src/components/ShareExperience/utils.js +++ b/src/components/ShareExperience/utils.js @@ -202,7 +202,7 @@ export const getCampaignTimeAndSalaryForm = ( ...getCampaignExtendedForm(extraFields, defaultContent)(state), }); -export const portInterviewFormToRequestFormat = interviewForm => { +export const portInterviewFormToRequestFormat = (interviewForm, extra) => { let body = { ...interviewForm, interviewTime: { @@ -229,7 +229,7 @@ export const portInterviewFormToRequestFormat = interviewForm => { ])(body); body = transferKeyToSnakecase(body); - + body.extra = extra; return body; }; diff --git a/src/hooks/experiments/useShareLink.js b/src/hooks/experiments/useShareLink.js index 7984ebbe7..c41bac445 100644 --- a/src/hooks/experiments/useShareLink.js +++ b/src/hooks/experiments/useShareLink.js @@ -4,15 +4,15 @@ import { path } from 'ramda'; const ACTIONS = [ { - prob: 0.33, + prob: 0.3333, type: 'INTERVIEW_FORM_ONE_PAGE', }, { - prob: 0.33, + prob: 0.3333, type: 'INTERVIEW_FORM_3_STEPS', }, { - prob: 0.34, + prob: 0.3334, type: 'INTERVIEW_FORM_TYPE_FORM', }, ]; diff --git a/src/utils/GAUtils.js b/src/utils/GAUtils.js new file mode 100644 index 000000000..797cea1ec --- /dev/null +++ b/src/utils/GAUtils.js @@ -0,0 +1,25 @@ +/** + * 從 GA SDK 取得 User Pseudo ID,又稱 Client ID + * 是使用者在該 GA 資源、該 device 上的 user id + * Ref: + * 1. https://support.google.com/analytics/answer/12675187?hl=en + * 2. https://developers.google.com/tag-platform/gtagjs/reference#get + * @param {*} ga_measurement_id + * @returns + */ +export const getUserPseudoId = async ga_measurement_id => { + return new Promise((resolve, reject) => { + try { + if (typeof window !== 'undefined' && typeof window.gtag !== 'undefined') { + window.gtag('get', ga_measurement_id, 'client_id', field => + resolve(field), + ); + } else { + resolve(null); + } + } catch (e) { + console.error(e); + reject(null); + } + }); +};