Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

Commit

Permalink
feat: Include pinned safe apps in dashboard widget
Browse files Browse the repository at this point in the history
  • Loading branch information
usame-algan committed May 3, 2022
1 parent 8dffa88 commit 76b5fc9
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/components/Dashboard/SafeApps/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { GENERIC_APPS_ROUTE } from 'src/routes/routes'
import Card, { CARD_HEIGHT, CARD_PADDING } from 'src/components/Dashboard/SafeApps/Card'
import ExploreIcon from 'src/assets/icons/explore.svg'
import { SafeApp } from 'src/routes/safe/components/Apps/types'
import { getAppsUsageData, rankTrackedSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount'
import { getAppsUsageData, rankSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount'
import { FEATURED_APPS_TAG } from 'src/components/Dashboard/FeaturedApps/FeaturedApps'
import { WidgetTitle, WidgetBody, WidgetContainer } from 'src/components/Dashboard/styled'

Expand Down Expand Up @@ -61,7 +61,7 @@ const useRankedApps = (allApps: SafeApp[], pinnedSafeApps: SafeApp[], size: numb
if (!allApps.length) return []

const trackData = getAppsUsageData()
const rankedSafeAppIds = rankTrackedSafeApps(trackData)
const rankedSafeAppIds = rankSafeApps(trackData, pinnedSafeApps)
const featuredSafeAppIds = allApps.filter((app) => app.tags?.includes(FEATURED_APPS_TAG)).map((app) => app.id)

const nonFeaturedApps = allApps.filter((app) => !featuredSafeAppIds.includes(app.id))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AppTrackData, rankTrackedSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount'
import { AppTrackData, rankSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount'
import { SafeApp } from 'src/routes/safe/components/Apps/types'
import { SafeAppAccessPolicyTypes } from '@gnosis.pm/safe-react-gateway-sdk'
import { FETCH_STATUS } from 'src/utils/requests'

describe('rankTrackedSafeApps', () => {
it('ranks more recent apps higher', () => {
Expand All @@ -24,7 +27,7 @@ describe('rankTrackedSafeApps', () => {
openCount: 1,
},
}
const result = rankTrackedSafeApps(trackedSafeApps)
const result = rankSafeApps(trackedSafeApps, [])
expect(result).toEqual(['3', '2', '4', '1'])
})

Expand All @@ -42,7 +45,7 @@ describe('rankTrackedSafeApps', () => {
},
'3': {
timestamp: 8,
txCount: 3,
txCount: 4,
openCount: 4,
},
'4': {
Expand All @@ -51,7 +54,53 @@ describe('rankTrackedSafeApps', () => {
openCount: 2,
},
}
const result = rankTrackedSafeApps(trackedSafeApps)
const result = rankSafeApps(trackedSafeApps, [])
expect(result).toEqual(['3', '2', '4', '1'])
})

it('includes pinned apps in ranking', () => {
const trackedSafeApps: AppTrackData = {
'1': {
timestamp: 1,
txCount: 1,
openCount: 1,
},
'2': {
timestamp: 4,
txCount: 4,
openCount: 6,
},
'3': {
timestamp: 8,
txCount: 3,
openCount: 4,
},
'4': {
timestamp: 5,
txCount: 2,
openCount: 2,
},
}

const pinnedApps: SafeApp[] = [
{
id: '5',
url: '',
name: '',
iconUrl: '',
description: '',
chainIds: ['1'],
provider: undefined,
accessControl: {
type: SafeAppAccessPolicyTypes.DomainAllowlist,
value: [],
},
fetchStatus: FETCH_STATUS.SUCCESS,
tags: [],
},
]

const result = rankSafeApps(trackedSafeApps, pinnedApps)
expect(result).toEqual(['2', '3', '5', '4', '1'])
})
})
48 changes: 33 additions & 15 deletions src/routes/safe/components/Apps/trackAppUsageCount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ export const APPS_DASHBOARD = 'APPS_DASHBOARD'

const TX_COUNT_WEIGHT = 2
const OPEN_COUNT_WEIGHT = 1
const MORE_RECENT_MULTIPLIER = 2
const LESS_RECENT_MULTIPLIER = 1
const PINNED_WEIGHT = 10

export type AppTrackData = {
[safeAppId: string]: {
Expand Down Expand Up @@ -46,20 +45,39 @@ export const trackSafeAppTxCount = (id: SafeApp['id']): void => {
})
}

export const rankTrackedSafeApps = (apps: AppTrackData): string[] => {
const appsMap = Object.entries(apps)
const normalizeBetweenTwoRanges = (val: number, minVal: number, maxVal: number, newMin: number, newMax: number) => {
return newMin + ((val - minVal) * (newMax - newMin)) / (maxVal - minVal)
}

export const rankSafeApps = (apps: AppTrackData, pinnedSafeApps: SafeApp[]): string[] => {
const appsWithScore = computeTrackedSafeAppsScore(apps)

for (const app of pinnedSafeApps) {
if (appsWithScore[app.id]) {
appsWithScore[app.id] += PINNED_WEIGHT
} else {
appsWithScore[app.id] = PINNED_WEIGHT
}
}

return Object.entries(appsWithScore)
.sort((a, b) => b[1] - a[1])
.map((app) => app[0])
}

export const computeTrackedSafeAppsScore = (apps: AppTrackData): Record<string, number> => {
const scoredApps: Record<string, number> = {}

return appsMap
.sort((a, b) => {
// The more recently used app gets a bigger score/relevancy multiplier
const aTimeMultiplier = a[1].timestamp - b[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER
const bTimeMultiplier = b[1].timestamp - a[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER
const sortedByTimestamp = Object.entries(apps).sort((a, b) => {
return a[1].timestamp - b[1].timestamp
})

// The sorting score is a weighted function where the OPEN_COUNT weights differently than the TX_COUNT
const aScore = (TX_COUNT_WEIGHT * a[1].txCount + OPEN_COUNT_WEIGHT * a[1].openCount) * aTimeMultiplier
const bScore = (TX_COUNT_WEIGHT * b[1].txCount + OPEN_COUNT_WEIGHT * b[1].openCount) * bTimeMultiplier
for (const [idx, app] of sortedByTimestamp.entries()) {
// UNIX Timestamps add too much weight, so we normalize by uniformly distributing them to range [1..2]
const timeMultiplier = normalizeBetweenTwoRanges(idx, 0, sortedByTimestamp.length, 1, 2)

scoredApps[app[0]] = (TX_COUNT_WEIGHT * app[1].txCount + OPEN_COUNT_WEIGHT * app[1].openCount) * timeMultiplier
}

return bScore - aScore
})
.map((values) => values[0])
return scoredApps
}

0 comments on commit 76b5fc9

Please sign in to comment.