Skip to content

Commit

Permalink
Merge pull request #67 from AmbireTech/campaigns-analytics
Browse files Browse the repository at this point in the history
Campaigns analytics
  • Loading branch information
ivopaunov authored Apr 1, 2024
2 parents 3b7ebe2 + 7732c3a commit 80c0a4a
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 25 deletions.
95 changes: 88 additions & 7 deletions src/contexts/CampaignsContext/CampaignsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,52 @@ type CampaignData = {
analyticsData: any
}

type Timeframe = 'year' | 'month' | 'week' | 'day' | 'hour'
type Metric = 'count' | 'payed'
type EventType = 'IMPRESSION' | 'CLICK'

type AnalyticsDataKeys = {
campaignId: string
adUnit: string
adSlot: string
adSlotType: string
advertiser: string
publisher: string
ssp: string
sspPublisher: string
hostname: string
country: string
osName: string
}

type AnalyticsDataQuery = {
eventType: EventType
metric: Metric
timeframe: Timeframe
start: Date
end: Date
limit: number
segmentBy: keyof AnalyticsDataKeys
timezone: 'UTC'
}

const getAnalyticsKeyFromQuery = (queryParams: AnalyticsDataQuery): string => {
// TODO: hex or hash
const key = `${queryParams.eventType}_${queryParams.metric}_${queryParams.timeframe}_${queryParams.start}_${queryParams.end}_${queryParams.limit}_${queryParams.segmentBy}_${queryParams.timezone}`
return key
}

type AnalyticsData = {
value: string | Number
time: number
segment?: string
}

type AnalyticsDataRes = {
limit?: Number
aggr: AnalyticsData[]
}

const defaultcampaignData = {
campaignId: '',
campaign: {},
Expand All @@ -41,6 +87,7 @@ const defaultcampaignData = {

interface ICampaignsDataContext {
campaignsData: Map<string, CampaignData>
analyticsData: Map<string, AnalyticsData[]>
// TODO: all campaigns event aggregations by account
eventAggregates: any
updateCampaignDataById: (params: string, updateAnalytics: boolean) => void
Expand All @@ -52,11 +99,15 @@ const CampaignsDataContext = createContext<ICampaignsDataContext | null>(null)
const CampaignsDataProvider: FC<PropsWithChildren> = ({ children }) => {
const { showNotification } = useCustomNotifications()
const { adexServicesRequest } = useAdExApi()
// eslint-disable-next-line

const [campaignsData, setCampaignData] = useState<Map<string, CampaignData>>(
new Map<string, CampaignData>()
)

const [analyticsData, setAnalyticsData] = useState<Map<string, AnalyticsData[]>>(
new Map<string, AnalyticsData[]>()
)

// eslint-disable-next-line
const [eventAggregates, setEventAggregates] = useState<any>({})

Expand Down Expand Up @@ -129,23 +180,53 @@ const CampaignsDataProvider: FC<PropsWithChildren> = ({ children }) => {
}
}, [adexServicesRequest, showNotification])

const updateEventAggregates = useCallback(() => {
// TODO
}, [])
const updateEventAggregates = useCallback(
async (params: AnalyticsDataQuery) => {
try {
const analyticsDataRes = await adexServicesRequest<AnalyticsDataRes>('validator', {
route: '/v5_a/analytics',
method: 'GET',
queryParams: Object.entries(params).reduce(
(query: Record<string, string>, [key, value]) => {
const updated = { ...query }
updated[key] = value.toString()
return updated
},
{}
)
})

if (!analyticsDataRes.aggr?.length) {
// TODO do something
return
}
setAnalyticsData((prev) => {
const dataKey = getAnalyticsKeyFromQuery(params)

return new Map(prev.set(dataKey, analyticsDataRes.aggr))
})
} catch (err) {
console.log(err)
// TODO: see how to use campaignId out of segment
showNotification('error', `getting analytics ${params.timeframe}`, 'Data error')
}
},
[adexServicesRequest, showNotification]
)

useEffect(() => {
updateAllCampaignsData()
updateEventAggregates()
}, [updateAllCampaignsData, updateEventAggregates])

const contextValue = useMemo(
() => ({
campaignsData,
updateCampaignDataById,
updateAllCampaignsData,
eventAggregates
eventAggregates,
analyticsData
}),
[campaignsData, updateCampaignDataById, updateAllCampaignsData, eventAggregates]
[campaignsData, updateCampaignDataById, updateAllCampaignsData, eventAggregates, analyticsData]
)

return (
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useAdexServices/useAdExApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useAdExApi = () => {
const { showNotification } = useCustomNotifications()

const adexServicesRequest = useCallback(
async <T>(service: AdExService, reqOptions: ApiRequestOptions<T>) => {
async <T>(service: AdExService, reqOptions: ApiRequestOptions<T>): Promise<T> => {
// temp hax for using the same token fot validator auth
const authHeaderProp = service === 'backend' ? 'X-DSP-AUTH' : 'Authentication'

Expand Down
28 changes: 15 additions & 13 deletions src/hooks/useDropzone/useDropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,22 @@ const useDropzone = () => {
reader.onload = async (e: any) => {
const blob = new Blob([file], { type: file.type })
let response
if (file.type === 'application/zip') {
response = await uploadZipMedia(blob, file.name).catch((error) =>
console.error('ERROR: ', error)
)
} else {
response = await uploadMedia(blob, file.name).catch((error) =>
console.error('ERROR: ', error)
)
}
if (!response) {
console.error(response.error)
return
let ipfsUrl: string = ''
try {
if (file.type === 'application/zip') {
response = await uploadZipMedia(blob, file.name)
ipfsUrl = response.root.ipfsUrl
} else {
response = await uploadMedia(blob, file.name)
ipfsUrl = response.ipfsUrl
}
if (!response) {
console.error('No Response')
return
}
} catch (err) {
console.error('ERROR: ', err)
}
const ipfsUrl = response?.root ? response.root.ipfsUrl : response.ipfsUrl

let htmlBannerSizes: ImageSizes | null = null
const adUnit = {
Expand Down
19 changes: 15 additions & 4 deletions src/hooks/useMediaUpload/useMediaUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import { useAdExApi } from 'hooks/useAdexServices'
import { useCallback } from 'react'

type MediaUploadRes = {
ipfsUrl: string
webUrl: string
isPinned: boolean
}

type ZipUploadRes = {
root: MediaUploadRes
ipfsUrls: MediaUploadRes[]
}

const useMediaUpload = () => {
const { adexServicesRequest } = useAdExApi()

const uploadMedia = useCallback(
async (media: Blob, mediaName: string, shouldPin: boolean = false) => {
async (media: Blob, mediaName: string, shouldPin: boolean = false): Promise<MediaUploadRes> => {
const formData = new FormData()
formData.append('media', media, mediaName)
formData.append('shouldPin', shouldPin.toString())

return adexServicesRequest('backend', {
return adexServicesRequest<MediaUploadRes>('backend', {
route: '/dsp/ipfs/upload',
method: 'POST',
body: formData
Expand All @@ -20,12 +31,12 @@ const useMediaUpload = () => {
)

const uploadZipMedia = useCallback(
async (media: Blob, mediaName: string, shouldPin: boolean = false) => {
async (media: Blob, mediaName: string, shouldPin: boolean = false): Promise<ZipUploadRes> => {
const formData = new FormData()
formData.append('zip', media, mediaName)
formData.append('shouldPin', shouldPin.toString())

return adexServicesRequest('backend', {
return adexServicesRequest<ZipUploadRes>('backend', {
route: '/dsp/ipfs/upload-zip',
method: 'POST',
body: formData
Expand Down

0 comments on commit 80c0a4a

Please sign in to comment.