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);
+ }
+ });
+};