Skip to content

Commit

Permalink
Merge pull request #119 from vtex-apps/B2BTEAM-1261
Browse files Browse the repository at this point in the history
Impersonate Metrics
  • Loading branch information
Mauro Takeda authored Aug 14, 2023
2 parents 8f234bf + a7c3435 commit 2136158
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 8 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Added metrics to Impersonate events
- Added metrics to Stop Impersonating events
- Added metrics to change team events
## [1.26.2] - 2023-08-11

### Added
Expand Down
4 changes: 2 additions & 2 deletions react/components/EditUserModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const EditUserModal: FunctionComponent<Props> = ({
canEdit,
canEditSales,
isSalesAdmin,
canManageOrg
canManageOrg,
}) => {
const { formatMessage } = useIntl()
const [userState, setUserState] = useState({} as UserDetails)
Expand Down Expand Up @@ -116,7 +116,7 @@ const EditUserModal: FunctionComponent<Props> = ({
const filteredArray = rolesData.listRoles.filter((role: any) => {
if (isAdmin) return true

if(canManageOrg) {
if (canManageOrg) {
return true
}

Expand Down
4 changes: 2 additions & 2 deletions react/components/NewUserModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const NewUserModal: FunctionComponent<Props> = ({
canEdit,
canEditSales,
isSalesAdmin,
canManageOrg
canManageOrg,
}) => {
const { formatMessage } = useIntl()
const [userState, setUserState] = useState({
Expand Down Expand Up @@ -119,7 +119,7 @@ const NewUserModal: FunctionComponent<Props> = ({
const filteredArray = rolesData.listRoles.filter((role: any) => {
if (isAdmin) return true

if(canManageOrg) return true
if (canManageOrg) return true

if (role.slug.includes('customer') && canEdit) {
return true
Expand Down
9 changes: 8 additions & 1 deletion react/components/OrganizationUsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import REMOVE_USER from '../graphql/removeUser.graphql'
import GET_COST_CENTER from '../graphql/getCostCenterStorefront.graphql'
import IMPERSONATE_USER from '../graphql/impersonateB2BUser.graphql'
import { B2B_CHECKOUT_SESSION_KEY } from '../utils/constants'
import { sendImpersonateMetric } from '../utils/metrics/impersonate'

interface Props {
organizationId: string
Expand All @@ -32,7 +33,7 @@ interface CellRendererProps {
updateCellMeasurements: () => void
}

interface B2BUserSimple extends UserDetails {
export interface B2BUserSimple extends UserDetails {
costCenterName: string
role: RoleSimple
organizationName: string
Expand Down Expand Up @@ -380,6 +381,12 @@ const OrganizationUsersTable: FunctionComponent<Props> = ({
showToast(formatMessage(storeMessages.toastImpersonateFailure))
}
} else {
const metricParams = {
costCenterData,
target: rowData,
}

sendImpersonateMetric(metricParams)
window.location.reload()
}
})
Expand Down
17 changes: 17 additions & 0 deletions react/components/UserWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import SET_CURRENT_ORGANIZATION from '../graphql/setCurrentOrganization.graphql'
import STOP_IMPERSONATION from '../graphql/impersonateUser.graphql'
import { B2B_CHECKOUT_SESSION_KEY } from '../utils/constants'
import '../css/user-widget.css'
import { sendStopImpersonateMetric } from '../utils/metrics/impersonate'
import type { ChangeTeamParams } from '../utils/metrics/changeTeam'
import { sendChangeTeamMetric } from '../utils/metrics/changeTeam'

const CSS_HANDLES = [
'userWidgetContainer',
Expand Down Expand Up @@ -216,6 +219,13 @@ const UserWidget: VtexFunctionComponent<UserWidgetProps> = ({
sessionStorage.removeItem(B2B_CHECKOUT_SESSION_KEY)
}

const metricParams = {
sessionResponse,
email: userWidgetData?.checkImpersonation?.email,
...organizationsState,
}

sendStopImpersonateMetric(metricParams)
window.location.reload()
})
.catch(error => {
Expand Down Expand Up @@ -254,6 +264,13 @@ const UserWidget: VtexFunctionComponent<UserWidgetProps> = ({
costId: organizationsState.currentCostCenter,
},
})

const metricParams: ChangeTeamParams = {
sessionResponse,
...organizationsState,
}

sendChangeTeamMetric(metricParams)
} catch (error) {
setErrorOrganization(true)
} finally {
Expand Down
9 changes: 8 additions & 1 deletion react/modules/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ interface KeyValue {
value: string
}

interface Session {
export interface Session {
type?: 'Session'
id: string
namespaces: {
store: {
Expand All @@ -14,6 +15,12 @@ interface Session {
isAuthenticated: KeyValue
email?: KeyValue
}
account: {
accountName: KeyValue
}
authentication: {
storeUserEmail: KeyValue
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"react-apollo": "^3.1.5",
"react-intl": "^5.13.4",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2"
"react-router-dom": "^5.1.2",
"axios": "1.4.0"
}
}
57 changes: 57 additions & 0 deletions react/utils/metrics/changeTeam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Session } from '../../modules/session'
import type { Metric } from './metrics'
import { sendMetric } from './metrics'

type ChangeTeamFieldsMetric = {
date: string
user_role: string
user_email?: string
org_id: string
cost_center_id: string
}

type ChangeTeamMetric = Metric & { fields: ChangeTeamFieldsMetric }

export type StopImpersonateMetricParams = {
sessionResponse: Session
currentCostCenter: string
costCenterInput: string
currentOrganization: string
organizationInput: string
email: string
}

export type ChangeTeamParams = {
sessionResponse: Session
currentRoleName: string
currentOrganization: string
currentCostCenter: string
}

const buildMetric = (metricParams: ChangeTeamParams): ChangeTeamMetric => {
return {
name: 'b2b-suite-buyerorg-data' as const,
account:
metricParams.sessionResponse?.namespaces?.account?.accountName?.value,
kind: 'change-team-ui-event',
description: 'User change team/organization - UI',
fields: {
date: new Date().toISOString(),
user_role: metricParams.currentRoleName,
user_email:
metricParams.sessionResponse?.namespaces?.profile?.email?.value,
org_id: metricParams.currentOrganization,
cost_center_id: metricParams.currentCostCenter,
},
}
}

export const sendChangeTeamMetric = async (metricParams: ChangeTeamParams) => {
try {
const metric: ChangeTeamMetric = buildMetric(metricParams)

await sendMetric(metric)
} catch (error) {
console.warn('Unable to log metrics', error)
}
}
133 changes: 133 additions & 0 deletions react/utils/metrics/impersonate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import type { B2BUserSimple } from '../../components/OrganizationUsersTable'
import type { Session } from '../../modules/session'
import { getSession } from '../../modules/session'
import type { Metric } from './metrics'
import { sendMetric } from './metrics'

type ImpersonatePerson = {
email: string
buyer_org_id: string
cost_center_id: string
cost_center_name: string
}

type ImpersonateUser = ImpersonatePerson
type ImpersonateTarget = ImpersonatePerson & { buyer_org_name: string }

type ImpersonateFieldsMetric = {
user: ImpersonateUser
target: ImpersonateTarget
date: string
}

type ImpersonateMetric = Metric & { fields: ImpersonateFieldsMetric }

export type ImpersonateMetricParams = {
costCenterData: {
getCostCenterByIdStorefront: {
id: string
name: string
organization: string
}
}
target: B2BUserSimple
}

export type StopImpersonateMetricParams = {
sessionResponse: Session
currentCostCenter: string
costCenterInput: string
currentOrganization: string
organizationInput: string
email: string
}

const buildImpersonateMetric = async (
metricParams: ImpersonateMetricParams
) => {
const { target, costCenterData } = metricParams

const session = await getSession()
const sessionResponse = session?.response
const isSession =
sessionResponse &&
(!sessionResponse.type || sessionResponse.type === 'Session')

return {
name: 'b2b-suite-buyerorg-data' as const,
kind: 'impersonate-ui-event',
description: 'Impersonate User Action - UI',
account: isSession
? (sessionResponse as Session).namespaces?.account?.accountName?.value
: undefined,
fields: {
user: {
email: isSession
? (sessionResponse as Session).namespaces?.profile?.email?.value
: undefined,
buyer_org_id: costCenterData?.getCostCenterByIdStorefront.organization,
cost_center_id: costCenterData?.getCostCenterByIdStorefront.id,
cost_center_name: costCenterData?.getCostCenterByIdStorefront.name,
},
target: {
email: target.email,
buyer_org_id: target.orgId,
buyer_org_name: target.organizationName,
cost_center_id: target.costId,
cost_center_name: target.costCenterName,
},
date: new Date().toISOString(),
},
} as ImpersonateMetric
}

const buildStopImpersonateMetric = (
metricParams: StopImpersonateMetricParams
) => {
return {
name: 'b2b-suite-buyerorg-data' as const,
kind: 'stop-impersonate-ui-event',
description: 'Stop Impersonate User Action - UI',
account:
metricParams.sessionResponse?.namespaces?.account?.accountName?.value,
fields: {
user: {
email:
metricParams.sessionResponse?.namespaces?.authentication
?.storeUserEmail?.value,
},
target: {
email: metricParams.email,
buyer_org_id: metricParams.currentOrganization,
buyer_org_name: metricParams.organizationInput,
cost_center_id: metricParams.currentCostCenter,
cost_center_name: metricParams.costCenterInput,
},
date: new Date().toISOString(),
},
} as ImpersonateMetric
}

export const sendImpersonateMetric = async (
metricParams: ImpersonateMetricParams
) => {
try {
const metric = await buildImpersonateMetric(metricParams)

await sendMetric(metric)
} catch (error) {
console.warn('Unable to log metrics', error)
}
}

export const sendStopImpersonateMetric = async (
metricParams: StopImpersonateMetricParams
) => {
try {
const metric = buildStopImpersonateMetric(metricParams)

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

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

type ImpersonateMetric = {
kind: 'impersonate-ui-event'
description: 'Impersonate User Action - UI'
}

type StopImpersonateMetric = {
kind: 'stop-impersonate-ui-event'
description: 'Stop Impersonate User Action - UI'
}

export type ChangeTeamMetric = {
kind: 'change-team-ui-event'
description: 'User change team/organization - UI'
}

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

export const sendMetric = async (metric: Metric) => {
try {
await axios.post(ANALYTICS_URL, metric)
} catch (error) {
console.warn('Unable to log metrics', error)
}
}
Loading

0 comments on commit 2136158

Please sign in to comment.