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 all 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/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

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

## [1.5.5] - 2023-06-29

### Fixed
Expand Down
4 changes: 1 addition & 3 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"vtex.my-account": "1.x",
"vtex.my-account-commons": "1.x"
},
"registries": [
"smartcheckout"
],
"registries": ["smartcheckout"],
"policies": [],
"$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema"
}
23 changes: 23 additions & 0 deletions react/components/QuoteDetails/QuoteDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
import QuoteTable from './QuoteTable'
import QuoteUpdateHistory from './QuoteUpdateHistory'
import { Status } from '../../utils/status'
import { sendCreateQuoteMetric } from '../../utils/metrics/createQuote'
import type { UseQuoteMetricsParams } from '../../utils/metrics/useQuote'
import { sendUseQuoteMetric } from '../../utils/metrics/useQuote'

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

const isNewQuote = !params?.id
const sessionResponse: any = useSessionResponse()

Check warning on line 83 in react/components/QuoteDetails/QuoteDetails.tsx

View workflow job for this annotation

GitHub Actions / QE / Lint Node.js

Unexpected any. Specify a different type
const intl = useIntl()
const { formatMessage } = intl
const { url: checkoutUrl } = useCheckoutURL()
Expand Down Expand Up @@ -194,15 +199,25 @@
createQuoteMutation({
variables: cart,
})
.then((result: any) => {

Check warning on line 202 in react/components/QuoteDetails/QuoteDetails.tsx

View workflow job for this annotation

GitHub Actions / QE / Lint Node.js

Unexpected any. Specify a different type
if (result.data.createQuote) {
const metricsParam = {
quoteId: result.data.createQuote,
sessionResponse,
workspace,
sendToSalesRep,
account,
}

sendCreateQuoteMetric(metricsParam)

toastMessage(quoteMessages.createSuccess)
handleClearCart(orderForm.orderFormId).then(() => {
setQuoteState(initialState)
setUpdatingSubtotal(0)
setOriginalSubtotal(0)
setTimeout(() => {
refetchOrderForm().then((resp: any) => {

Check warning on line 220 in react/components/QuoteDetails/QuoteDetails.tsx

View workflow job for this annotation

GitHub Actions / QE / Lint Node.js

Unexpected any. Specify a different type
if (resp?.data?.orderForm) {
setOrderForm(resp?.data?.orderForm)
}
Expand Down Expand Up @@ -292,6 +307,14 @@
setUsingQuoteState(false)
})
.then(() => {
const metricsParam: UseQuoteMetricsParams = {
quoteState,
orderFormId: variables.orderFormId,
account,
sessionResponse,
}

sendUseQuoteMetric(metricsParam)
goToCheckout(checkoutUrl)
setUsingQuoteState(false)
})
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
135 changes: 135 additions & 0 deletions react/utils/metrics/createQuote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import axios from 'axios'

import type { Metric, SessionResponse } from './metrics'
import { sendMetric } from './metrics'

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 CreateQuoteFieldsMetric = {
cost_center_id: string
cost_center_name: string
buyer_org_id: string
buyer_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
}

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

type CreateQuoteMetric = Metric & { fields: CreateQuoteFieldsMetric }

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 buildCreateQuoteMetric = async (
metricsParam: CreateQuoteMetricsParam
): Promise<CreateQuoteMetric> => {
const { namespaces } = metricsParam.sessionResponse
const userEmail = namespaces?.profile?.email?.value

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

const metric: CreateQuoteMetric = {
name: 'b2b-suite-buyerorg-data',
kind: 'create-quote-ui-event',
description: 'Create Quotation Action - UI',
account: metricsParam.account,
fields: {
buyer_org_id: metricsData.organization,
buyer_org_name: metricsData.organizationName,
cost_center_id: metricsData.costId,
cost_center_name: metricsData.costCenterName,
member_id: namespaces?.profile?.id?.value,
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 sendCreateQuoteMetric = async (
metricsParam: CreateQuoteMetricsParam
) => {
try {
const metric = await buildCreateQuoteMetric(metricsParam)

await sendMetric(metric)
} catch (error) {
console.warn('Unable to log metrics', error)
}
}
37 changes: 37 additions & 0 deletions react/utils/metrics/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import axios from 'axios'

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

type CreateQuoteMetric = {
kind: 'create-quote-ui-event'
description: 'Create Quotation Action - UI'
}

type UseQuoteMetric = {
kind: 'use-quote-ui-event'
description: 'Use Quotation Action - UI'
}

export type Metric = {
name: 'b2b-suite-buyerorg-data'
account: string
} & (CreateQuoteMetric | UseQuoteMetric)

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

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

export const sendMetric = async (metric: Metric) => {
try {
await axios.post(ANALYTICS_URL, metric)
} catch (error) {
console.warn('Unable to log metrics', error)
}
}
65 changes: 65 additions & 0 deletions react/utils/metrics/useQuote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Metric, SessionResponse } from './metrics'
import { sendMetric } from './metrics'

type UseQuoteFieldsMetric = {
quote_id: string
quote_reference_name: string
order_form_id: string
quote_creation_date: string
quote_use_date: string
creator_email: string
user_email: string
cost_center_name: string
buyer_org_id: string
buyer_org_name: string
quote_last_update: string
}

type UseQuoteMetric = Metric & { fields: UseQuoteFieldsMetric }

export type UseQuoteMetricsParams = {
quoteState: Quote
orderFormId: string
account: string
sessionResponse: SessionResponse
}

const buildUseQuoteMetric = (
metricsParam: UseQuoteMetricsParams
): UseQuoteMetric => {
const { quoteState, orderFormId, account, sessionResponse } = metricsParam

const metric: UseQuoteMetric = {
name: 'b2b-suite-buyerorg-data',
kind: 'use-quote-ui-event',
description: 'Use Quotation Action - UI',
account,
fields: {
buyer_org_id: quoteState.organization,
buyer_org_name: quoteState.organizationName,
cost_center_name: quoteState.costCenterName,
quote_id: quoteState.id,
quote_reference_name: quoteState.referenceName,
order_form_id: orderFormId,
quote_creation_date: quoteState.creationDate,
quote_use_date: new Date().toISOString(),
creator_email: quoteState.creatorEmail,
user_email: sessionResponse.namespaces?.profile?.email?.value,
quote_last_update: quoteState.lastUpdate,
},
}

return metric
}

export const sendUseQuoteMetric = async (
metricsParam: UseQuoteMetricsParams
) => {
try {
const metric = buildUseQuoteMetric(metricsParam)

await sendMetric(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
Loading