Skip to content

Commit

Permalink
Merge pull request #312 from AmbireTech/ssp-analytics
Browse files Browse the repository at this point in the history
Ssp analytics
  • Loading branch information
ivopaunov authored Dec 4, 2024
2 parents c9e64ae + f386c68 commit 96270f5
Show file tree
Hide file tree
Showing 19 changed files with 3,794 additions and 3,223 deletions.
26 changes: 15 additions & 11 deletions src/components/AdminPanel/AdminAnalytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,23 +126,27 @@ const AdminAnalytics = () => {
const headings = useMemo(() => [analType.toString(), ...headingsDefault], [analType])

// TODO: change campaign analytics to analytics
const { analyticsData, getAnalyticsKeyAndUpdate, mappedAnalytics } = useCampaignAnalytics()
const {
// analyticsData,
getAnalyticsKeyAndUpdate,
mappedAnalytics
} = useCampaignAnalytics()

const analytics = useMemo(
() => analyticsData.get(analyticsKey?.key || ''),
[analyticsData, analyticsKey]
)
// const analytics = useMemo(
// () => analyticsData.get(analyticsKey?.key || ''),
// [analyticsData, analyticsKey]
// )

const adminMappedAnalytics = useMemo(
() => mappedAnalytics.get(analyticsKey?.key || ''),
[analyticsKey, mappedAnalytics]
)

useEffect(() => {
console.log({ analytics })
console.log({ mappedAnalytics })
console.log({ adminMappedAnalytics })
}, [analytics, mappedAnalytics, adminMappedAnalytics])
// useEffect(() => {
// console.log({ analytics })
// console.log({ mappedAnalytics })
// console.log({ adminMappedAnalytics })
// }, [analytics, mappedAnalytics, adminMappedAnalytics])

useEffect(() => {
setAnalyticsKey(undefined)
Expand All @@ -164,7 +168,7 @@ const AdminAnalytics = () => {
placement || undefined
)
setAnalyticsKey(key)
console.log('key', key)
// console.log('key', key)
}

checkAnalytics()
Expand Down
2 changes: 0 additions & 2 deletions src/components/AdminPanel/AdminPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ const AdminPanel = () => {
const navigate = useNavigate()
const { tabValue = 'campaigns', accountId } = useParams()

console.log({ tabValue })

return (
<Container fluid>
<StickyPanel>
Expand Down
21 changes: 16 additions & 5 deletions src/components/AdminPanel/SSPsAnalytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ const SSPsAnalytics = ({
format: selectedFormats
}),
limit: 666,
groupBy
groupBy,
showBidCount: true
})
setAnalyticsKey(key)
console.log('key', key)
// console.log('key', key)
}

checkAnalytics()
Expand All @@ -171,15 +172,19 @@ const SSPsAnalytics = ({
const data: { elements: DataElement[]; totalRequests: number } = useMemo(() => {
return {
elements:
analytics?.data.map(({ count, value }) => {
analytics?.data.map(({ count, value, bids, imps }) => {
const bidsToImpressionRation = ((imps || 0) / (bids || 1)) * 100
return {
id: value.toString() + count.toString(),
columns: [
{
value: value.toString(),
element: mapSegmentLabel(groupBy, value).label
},
{ value: count, element: <NumberFormatter value={count} thousandSeparator /> }
{ value: count, element: <NumberFormatter value={count} thousandSeparator /> },
{ value: bids, element: <NumberFormatter value={bids} thousandSeparator /> },
{ value: imps, element: <NumberFormatter value={imps} thousandSeparator /> },
{ value: bidsToImpressionRation, element: `${bidsToImpressionRation.toFixed(2)} %` }
]
}
}) || [],
Expand Down Expand Up @@ -327,7 +332,13 @@ const SSPsAnalytics = ({
<Stack>
<CustomTable
pageSize={10}
headings={[groupBy?.toString() || 'data', 'count']}
headings={[
groupBy?.toString() || 'data',
'slots',
'bids',
'impressions',
'bids success rate'
]}
data={data.elements}
loading={loading}
/>
Expand Down
2 changes: 0 additions & 2 deletions src/components/CampaignAnalytics/TimeFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ export const TimeFrame = ({ forAdmin, campaignId }: { forAdmin: boolean; campaig
const { width: windowWidth } = useViewportSize()
const [filteredData, setFilteredData] = useState<FilteredAnalytics[]>([])

// console.log({ kors: Array.isArray(timeFrames) })

const [metricsToShow, setMetricsToShow] = useState<MetricsToShow>({
// segment: true,
impressions: true,
Expand Down
7 changes: 2 additions & 5 deletions src/components/CreateCampaign/CreateCampaign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { Flex, Stack, Paper, Text, Box } from '@mantine/core'
import useCreateCampaignContext from 'hooks/useCreateCampaignContext'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { modals } from '@mantine/modals'
import type {
unstable_Blocker as Blocker,
unstable_BlockerFunction as BlockerFunction
} from 'react-router-dom'
import { unstable_useBlocker as useBlocker, useNavigate } from 'react-router-dom'
import type { Blocker, BlockerFunction } from 'react-router-dom'
import { useBlocker, useNavigate } from 'react-router-dom'
import { defaultConfirmModalProps } from 'components/common/Modals/CustomConfirmModal'
import throttle from 'lodash.throttle'
import { SuccessModal } from 'components/common/Modals'
Expand Down
150 changes: 123 additions & 27 deletions src/components/CreateCampaign/StepThree/CpmHelper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { SSPsAnalyticsData, CampaignUI } from 'types'
import useSSPsAnalytics from 'hooks/useCampaignAnalytics/useSSPsAnalytics'
import { getRecommendedCPMRangeAdvanced, parseRange } from 'helpers/createCampaignHelpers'
import {
getCPMRangeAdvancedData,
parseRange,
MAGIC_NUMBER,
cpmToStatisticsPrecision
} from 'helpers/createCampaignHelpers'
import { useMemo, useState, useEffect } from 'react'
import { campaignDataToSSPAnalyticsQuery, DAY } from 'helpers'
import {
Expand All @@ -10,33 +15,42 @@ import {
Text,
Tooltip,
RangeSlider,
RingProgress,
// RingProgress,
Paper,
Center,
Overlay,
Loader,
Space,
NumberFormatter
NumberFormatter,
SemiCircleProgress,
Divider
} from '@mantine/core'
import InfoFilledIcon from 'resources/icons/InfoFilled'
import { Sparkline } from '@mantine/charts'

// TODO: get some real time stats about that
const EXPECTED_WINNING_BIDS_RATE = 0.05
// NOTE: default value if there is no recent data (no active campaigns past 48 hours)
// Most probably it will be ~ 0.2 but let's keep it lower
const EXPECTED_WINNING_BIDS_RATE = 0.06942

const getCMPRangeMarks = (analytics: SSPsAnalyticsData[]) => {
const cpms = analytics
.map((x) => [parseRange(x.value.toString()).min, parseRange(x.value.toString()).max])
.map((x) => {
const { min, max } = parseRange(x.value.toString())
return [min, max]
})
.flat()
.filter((c) => typeof c === 'number' && !Number.isNaN(c))
.sort((a, b) => a - b)
.filter((item, pos, self) => {
return self.indexOf(item) === pos
})
.map((x, i) => ({
label: x.toString(),
value: i
}))
.map((x, i) => {
const price = x * MAGIC_NUMBER
return {
label: cpmToStatisticsPrecision(price).toString(),
value: i
}
})
return cpms
}

Expand Down Expand Up @@ -77,15 +91,15 @@ export function CPMHelper({

const [cpmSliderRange, setCpmRange] = useState<[number, number]>([0, 1])

const recommendedCPM = useMemo(
const cpmData = useMemo(
() =>
analytics?.data.length
? getRecommendedCPMRangeAdvanced(
? getCPMRangeAdvancedData(
analytics.data,
Number(cpmRangeData.find((x) => x.value === cpmSliderRange[0])?.label) || 0,
Number(cpmRangeData.find((x) => x.value === cpmSliderRange[1])?.label) || 0
)
: { min: 'N/A', max: 'N/A', count: 0, supply: 0 },
: { min: 'N/A', max: 'N/A', count: 0, supply: 0, bids: 0, imps: 0 },
[analytics?.data, cpmSliderRange, cpmRangeData]
)

Expand All @@ -104,24 +118,33 @@ export function CPMHelper({
}, [campaign])

const supplyCovered = useMemo(
() => (recommendedCPM.count / recommendedCPM.supply) * 100,
[recommendedCPM.count, recommendedCPM.supply]
() => (cpmData.count / cpmData.supply) * 100,
[cpmData.count, cpmData.supply]
)

const impressionsCovered = useMemo(() => {
const dailySupply = (recommendedCPM.count / 2) * EXPECTED_WINNING_BIDS_RATE
const dailySupply =
(cpmData.count / 2) *
Math.max((cpmData.imps || 0) / (cpmData.bids || 1), EXPECTED_WINNING_BIDS_RATE)
const campaignDays = Number(campaign.activeTo - campaign.activeFrom) / DAY
const dailyImpressions = estimatedMaxImpressions / campaignDays
const percentCovered = Number(((dailySupply / dailyImpressions) * 100).toFixed(2))

return percentCovered > 100 ? 100 : percentCovered
}, [campaign.activeFrom, campaign.activeTo, estimatedMaxImpressions, recommendedCPM.count])
}, [
campaign.activeFrom,
campaign.activeTo,
estimatedMaxImpressions,
cpmData.bids,
cpmData.count,
cpmData.imps
])

const cpmDistributionChartData = useMemo(() => {
return analytics?.data.length
? cpmRangeData.map(
(_x, i) =>
getRecommendedCPMRangeAdvanced(
getCPMRangeAdvancedData(
analytics?.data,
Number(cpmRangeData[i]?.label),
Number(cpmRangeData[i + 1]?.label || cpmRangeData[i]?.label)
Expand Down Expand Up @@ -165,13 +188,15 @@ export function CPMHelper({
<Stack gap="xs" justify="stretch">
<Text>Supply CPM distribution</Text>
<Sparkline
px={6} // TODO: Magic atm to match the slider fix it with proper theme prop
h={180}
data={cpmDistributionChartData}
// data={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}
color="info"
strokeWidth={5}
curveType="monotone"
curveType="stepAfter"
fillOpacity={0.69}
/>
<Space h="xl" />
<RangeSlider
color="info"
size="xl"
Expand All @@ -182,19 +207,17 @@ export function CPMHelper({
onCPMRangeChange(cpmRangeData[val[0]]?.label, cpmRangeData[val[1]]?.label)
}}
min={0}
step={1}
minRange={1}
max={cpmRangeData[cpmRangeData.length - 1]?.value}
marks={cpmRangeData}
label={(val) => cpmRangeData[val]?.label}
labelAlwaysOn
styles={{ markLabel: { transform: 'rotate(45deg)', transformOrigin: 'top left' } }}
/>
<Space h="xl" />
<Text>
{'Expected impressions: Min: '}
<NumberFormatter value={estimatedMinImpressions} thousandSeparator /> {'- Max: '}
<NumberFormatter value={estimatedMaxImpressions} thousandSeparator />
</Text>
<Group>

{/* <Group>
<RingProgress
size={200}
thickness={16}
Expand Down Expand Up @@ -237,8 +260,81 @@ export function CPMHelper({
</Text>
</Center>
}
/>
/> */}
<Group>
<Stack gap={0}>
<Tooltip
label={`CPM range: ${cpmSliderRange[0]} - ${
cpmSliderRange[1]
} covers ${supplyCovered.toFixed(
2
)}% of the total supply matching campaign targeting and creatives formats`}
>
<SemiCircleProgress
size={200}
thickness={16}
value={supplyCovered}
filledSegmentColor="info"
// emptySegmentColor="brandDarker"
labelPosition="bottom"
// sections={[
// {
// value: supplyCovered,
// color: 'info',
// tooltip: `CPM range: ${cpmSliderRange[0]} - ${
// cpmSliderRange[1]
// } covers ${supplyCovered.toFixed(
// 2
// )}% of the total supply matching campaign targeting and creatives formats`
// }
// ]}
label={
<Center>
<Text c="info" fw="bolder" ta="center" size="md">
Supply <br />
{supplyCovered.toFixed(2)}%
</Text>
</Center>
}
/>
</Tooltip>
<Divider size={0} label="coverage" />
<Tooltip
label={`CPM range: ${cpmSliderRange[0]} - ${cpmSliderRange[1]} covers ${impressionsCovered}% of the total expected maximum impressions for the selected budged and period of the campaign`}
>
<SemiCircleProgress
size={200}
thickness={16}
value={impressionsCovered}
orientation="down"
filledSegmentColor="success"
labelPosition="bottom"
// sections={[
// {
// value: impressionsCovered,
// color: 'info',
// tooltip: `CPM range: ${cpmSliderRange[0]} - ${cpmSliderRange[1]} covers ${impressionsCovered}% of the total expected maximum impressions for the selected budged and period of the campaign`
// }
// ]}
label={
<Center>
<Text c="success" fw="bolder" ta="center" size="md">
Campaign <br />
{impressionsCovered}%
</Text>
</Center>
}
/>
</Tooltip>
</Stack>
<Text>
{'Expected impressions: Min: '}
<br />
<NumberFormatter value={estimatedMinImpressions} thousandSeparator /> {'- Max: '}
<NumberFormatter value={estimatedMaxImpressions} thousandSeparator />
</Text>
</Group>
{/* </Group> */}
</Stack>
</Paper>
</Stack>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CreateCampaign/StepThree/StepThree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const StepThree = () => {
)

return (
<SimpleGrid cols={{ base: 1, lg: 2 }}>
<SimpleGrid cols={{ base: 1, xl: 2 }}>
<Stack gap="xl">
<Stack gap="xs">
<Text c="secondaryText" size="sm" fw="bold">
Expand Down
Loading

0 comments on commit 96270f5

Please sign in to comment.