Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creation Quote Metric #55

Merged
merged 25 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ node/node_modules
react/package-lock.json
node/package-lock.json
node/node_modules


.vscode/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

+ ### Added
+ - Quotation created metrics sent to Analytics Redshift

## [1.5.5] - 2023-06-29

### Fixed
Expand Down
13 changes: 13 additions & 0 deletions react/components/QuoteDetails/QuoteDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import AlertMessage from './AlertMessage'
import QuoteTable from './QuoteTable'
import QuoteUpdateHistory from './QuoteUpdateHistory'
import { Status } from '../../utils/status'
import { sendMetric } from '../../utils/metrics'

const localStore = storageFactory(() => localStorage)
const MAX_DISCOUNT_PERCENTAGE = 99
Expand Down Expand Up @@ -72,6 +73,8 @@ const QuoteDetails: FunctionComponent = () => {
const {
route: { params },
navigate,
workspace,
mairatma marked this conversation as resolved.
Show resolved Hide resolved
account,
} = useRuntime()

const isNewQuote = !params?.id
Expand Down Expand Up @@ -196,6 +199,16 @@ const QuoteDetails: FunctionComponent = () => {
})
.then((result: any) => {
if (result.data.createQuote) {
const metricsParam = {
quoteId: result.data.createQuote,
sessionResponse,
workspace,
sendToSalesRep,
account,
}

sendMetric(metricsParam)

toastMessage(quoteMessages.createSuccess)
handleClearCart(orderForm.orderFormId).then(() => {
setQuoteState(initialState)
Expand Down
3 changes: 2 additions & 1 deletion react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"graphql": "^14.5.8",
"react": "^16.9.2",
"react-apollo": "^3.1.5",
"react-intl": "^5.13.4"
"react-intl": "^5.13.4",
"axios": "1.4.0"
},
"devDependencies": {
"@types/jest": "^24.0.18",
Expand Down
150 changes: 150 additions & 0 deletions react/utils/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import axios from 'axios'

const ANALYTICS_URL = 'https://rc.vtex.com/api/analytics/schemaless-events'

const GRAPHQL_URL = (accountName: string, workspace?: string) => {
if (workspace) {
return `https://${workspace}--${accountName}.myvtex.com/_v/private/graphql/v1`
}

return `https://${accountName}.myvtex.com/_v/private/graphql/v1`
}

type Metric = {
name: 'b2b-suite-buyerorg-data'
kind: 'create-quote-ui-event'
description: 'Create Quotation Action - UI'
account: string
}

type QuoteFieldsMetric = {
cost_center_id: string
cost_center_name: string
buy_org_id: string
buy_org_name: string
member_id: string
member_email: string
role: string
creation_date: string
quote_id: string
quote_reference_name: string
send_to_sales_rep: boolean
}

export type SessionProfile = {
id: { value: string }
email: { value: string }
}

type SessionResponse = {
namespaces: {
profile: SessionProfile
}
}

type MetricsParam = {
quoteId: string
sessionResponse: SessionResponse
workspace: string
account: string
sendToSalesRep: boolean
}

type QuoteMetric = Metric & { fields: QuoteFieldsMetric }

const fetchMetricsData = async (
accountName: string,
workspace: string,
quoteId: string,
userEmail: string
) => {
const query = JSON.stringify({
query: `query GetMetricsData($id: String, $email: String!) {
getQuote(id: $id) @context(provider: "vtex.b2b-quotes-graphql") {
organization
organizationName
costCenterName
referenceName
creatorRole
creationDate
},
getUserByEmail(email: $email) @context(provider: "vtex.storefront-permissions") {
costId
}
}`,
variables: { id: quoteId, email: userEmail },
})

const { data, errors } = (
await axios.post(GRAPHQL_URL(accountName, workspace), query)
).data

if (errors) {
console.error('Graphql errors', errors)
throw new Error('Graphql Errors when trying get quote and user data')
}

const quoteResult = data?.getQuote as Omit<QuoteMetricsData, 'costId'>
const costId = (data?.getUserByEmail?.[0].costId ?? '') as string

return {
...quoteResult,
costId,
}
}

const buildQuoteMetric = async (
metricsParam: MetricsParam
): Promise<QuoteMetric> => {
const { namespaces } = metricsParam.sessionResponse
const userEmail = namespaces?.profile?.email?.value

const metricsData = await fetchMetricsData(
metricsParam.account,
metricsParam.workspace,
metricsParam.quoteId,
userEmail
)

const metric: QuoteMetric = {
name: 'b2b-suite-buyerorg-data',
kind: 'create-quote-ui-event',
description: 'Create Quotation Action - UI',
account: metricsParam.account,
fields: {
buy_org_id: metricsData.organization,
buy_org_name: metricsData.organizationName,
cost_center_id: metricsData.costId,
cost_center_name: metricsData.costCenterName,
member_id: namespaces?.profile?.id?.value,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line 98 the code is assuming that namespaces.profile.email.value is always set, but here you're handling the case where namespaces and profile might not be set. If that's a possibility we need to also add this to lines 97 and 98, skipping the whole metric process in case the values are not set (since those are required).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of skipping the while metric process, I'll check the namespace, profile, and email, and let it save null values. I believe it's better to save the metric with the data we have than not save it at all

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, if the metric is still useful, let's save it!

member_email: userEmail,
role: metricsData.creatorRole,
creation_date: metricsData.creationDate,
quote_id: metricsParam.quoteId,
quote_reference_name: metricsData.referenceName,
send_to_sales_rep: metricsParam.sendToSalesRep,
},
}

return metric
}

type QuoteMetricsData = {
organization: string // organizationId
organizationName: string
costId: string
costCenterName: string
referenceName: string // quote reference name
creatorRole: string
creationDate: string
}

export const sendMetric = async (metricsParam: MetricsParam) => {
try {
const metric = await buildQuoteMetric(metricsParam)

await axios.post(ANALYTICS_URL, metric)
} catch (error) {
console.warn('Unable to log metrics', error)
}
}
30 changes: 29 additions & 1 deletion react/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2485,6 +2485,15 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==

[email protected]:
version "1.4.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"

babel-jest@^25.5.1:
version "25.5.1"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.5.1.tgz#bc2e6101f849d6f6aec09720ffc7bc5332e62853"
Expand Down Expand Up @@ -2841,7 +2850,7 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==

combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
Expand Down Expand Up @@ -3330,6 +3339,11 @@ focus-visible@^5.2.0:
resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-5.2.0.tgz#3a9e41fccf587bd25dcc2ef045508284f0a4d6b3"
integrity sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==

follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==

for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
Expand All @@ -3340,6 +3354,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=

form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"

form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
Expand Down Expand Up @@ -4935,6 +4958,11 @@ prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"

proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==

psl@^1.1.28:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
Expand Down