From b7df2303162ad5eb22c9a12e5e34497523f37854 Mon Sep 17 00:00:00 2001 From: Christopher Cali Date: Tue, 19 Sep 2023 10:00:43 -0400 Subject: [PATCH 1/6] Begin moving user analytics functionality to separate composable --- apps/web/src/composables/analytics.ts | 217 ++++++++++++++++++ apps/web/src/composables/user.ts | 179 +-------------- apps/web/src/pages/overview/Overview.vue | 4 +- .../overview/components/BreakdownChart.vue | 45 ++-- 4 files changed, 248 insertions(+), 197 deletions(-) create mode 100644 apps/web/src/composables/analytics.ts diff --git a/apps/web/src/composables/analytics.ts b/apps/web/src/composables/analytics.ts new file mode 100644 index 000000000..175393ac5 --- /dev/null +++ b/apps/web/src/composables/analytics.ts @@ -0,0 +1,217 @@ +import { readonly, ref, watchEffect } from 'vue' +import { UserAnalyticsData } from '@casimir/types' +import useEnvironment from '@/composables/environment' +import useTxData from '../mockData/mock_transaction_data' + +const { usersUrl } = useEnvironment() +const { mockData, txData } = useTxData() + +export default function useAnalytics() { + const finishedComputingUerAnalytics = ref(false) + const getUserAnalyticsError = ref(null) + const rawUserAnalytics = ref(null) + const userAnalytics = ref({ + oneMonth: { + labels: [], + data: [] + }, + sixMonth: { + labels: [], + data: [] + }, + oneYear: { + labels: [], + data: [] + }, + historical: { + labels: [], + data: [] + } + }) + + function computeUserAnalytics() { + finishedComputingUerAnalytics.value = false + // const result = userAnalytics.value + console.log('rawUserAnalytics in computeAnalytics :>> ', rawUserAnalytics) + const sortedTransactions = rawUserAnalytics.value.sort((a: any, b: any) => { + new Date(a.receivedAt).getTime() - new Date(b.receivedAt).getTime() + }) + + let earliest: any = null + const latest: any = new Date().getTime() + const oneYear = new Date().getTime() - 31536000000 + const sixMonths = new Date().getTime() - 15768000000 + const oneMonth = new Date().getTime() - 2628000000 + sortedTransactions.forEach((tx: any) => { + const receivedAt = new Date(tx.receivedAt) + if (!earliest) earliest = receivedAt.getTime() + if (receivedAt.getTime() < earliest) earliest = receivedAt.getTime() + }) + const historicalInterval = (latest - earliest) / 11 + + sortedTransactions.forEach((tx: any) => { + const { receivedAt, walletAddress, walletBalance } = tx + /* Historical */ + let historicalDataIndex = userAnalytics.value.historical.data.findIndex((obj: any) => obj.walletAddress === walletAddress) + if (historicalDataIndex === -1) { + const dataLength = userAnalytics.value.historical.data.push({ walletAddress, walletBalance: Array(12).fill(0) }) + historicalDataIndex = dataLength - 1 + } + // Determine which interval the receivedAt falls into + const intervalIndex = Math.floor((new Date(receivedAt).getTime() - earliest) / historicalInterval) + // Set the value of the intervalIndex to the walletBalance + userAnalytics.value.historical.data[historicalDataIndex].walletBalance[intervalIndex] = walletBalance + + /* One Year */ + if (new Date(receivedAt).getTime() > oneYear) { + let oneYearDataIndex = userAnalytics.value.oneYear.data.findIndex((obj: any) => obj.walletAddress === walletAddress) + if (oneYearDataIndex === -1) { + const dataLength = userAnalytics.value.oneYear.data.push({ walletAddress, walletBalance: Array(12).fill(0) }) + oneYearDataIndex = dataLength - 1 + } + const monthsAgo = (new Date().getFullYear() - new Date(receivedAt).getFullYear()) * 12 + (new Date().getMonth() - new Date(receivedAt).getMonth()) + const intervalIndex = 11 - monthsAgo + userAnalytics.value.oneYear.data[oneYearDataIndex].walletBalance[intervalIndex] = walletBalance + } + + /* Six Months */ + if (new Date(receivedAt).getTime() > sixMonths) { + let sixMonthDataIndex = userAnalytics.value.sixMonth.data.findIndex((obj: any) => obj.walletAddress === walletAddress) + if (sixMonthDataIndex === -1) { + const dataLength = userAnalytics.value.sixMonth.data.push({ walletAddress, walletBalance: Array(6).fill(0) }) + sixMonthDataIndex = dataLength - 1 + } + const monthsAgo = (new Date().getFullYear() - new Date(receivedAt).getFullYear()) * 12 + (new Date().getMonth() - new Date(receivedAt).getMonth()) + const intervalIndex = 5 - monthsAgo + userAnalytics.value.sixMonth.data[sixMonthDataIndex].walletBalance[intervalIndex] = walletBalance + } + + /* One Month */ + if (new Date(receivedAt).getTime() > oneMonth) { + let oneMonthDataIndex = userAnalytics.value.oneMonth.data.findIndex((obj: any) => obj.walletAddress === walletAddress) + if (oneMonthDataIndex === -1) { + const dataLength = userAnalytics.value.oneMonth.data.push({ walletAddress, walletBalance: Array(30).fill(0) }) + oneMonthDataIndex = dataLength - 1 + } + const daysAgo = Math.floor((new Date().getTime() - new Date(receivedAt).getTime()) / 86400000) + const intervalIndex = 29 - daysAgo + userAnalytics.value.oneMonth.data[oneMonthDataIndex].walletBalance[intervalIndex] = walletBalance + } + }) + + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + + // Set the historical labels array to the interval labels + let previousMonth: any = null + userAnalytics.value.historical.labels = Array(12).fill(0).map((_, i) => { + const date = new Date(earliest + (historicalInterval * i)) + const currentMonth = date.getMonth() + if (!previousMonth) { + previousMonth = currentMonth + return date.getMonth() === 0 ? `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()}` : `${months[date.getMonth()]} ${date.getDate()}` + } else if (currentMonth < previousMonth) { + previousMonth = currentMonth + return `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()}` + } else { + previousMonth = currentMonth + return `${months[date.getMonth()]} ${date.getDate()}` + } + }) + + // Set the oneYear labels array to the interval labels + userAnalytics.value.oneYear.labels = Array(12).fill(0).map((_, i) => { + const date = new Date (new Date().setDate(1)) + const monthIndex = new Date(date.setMonth(date.getMonth() - (11 - i))) + return `${months[monthIndex.getMonth()]} ${monthIndex.getFullYear()}` + }) + + // Set the sixMonth labels array to the interval labels + userAnalytics.value.sixMonth.labels = Array(6).fill(0).map((_, i) => { + const date = new Date (new Date().setDate(1)) + const monthIndex = new Date(date.setMonth(date.getMonth() - (5 - i))) + return `${months[monthIndex.getMonth()]} ${monthIndex.getFullYear()}` + }) + + // Set the oneMonth labels array to the interval labels + userAnalytics.value.oneMonth.labels = [] + for (let i = 30; i > 0; i--) { + const date = new Date().getTime() - ((i - 1) * 86400000) + userAnalytics.value.oneMonth.labels.push(`${new Date(date).getMonth() + 1}/${new Date(date).getDate()}`) + } + // userAnalytics.value = result + finishedComputingUerAnalytics.value = true + } + + async function getUserAnalytics() { + try { + const requestOptions = { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + } + // TODO: Re-enable this when athena is ready + const response = await fetch(`${usersUrl}/analytics`, requestOptions) + const { error, message, data: athenaData } = await response.json() + // console.log('data from analytics :>> ', data) + // userAnalytics.value = athenaData + getUserAnalyticsError.value = error + if (error) throw new Error(`Error in getUserAnalytics: ${message}`) + + // TODO: Get events, actions, and contract data from the API + // Then format the data to be used in the charts (see computeUserAnalytics) and give to Steve. + + // We get the user's analytics (wallet balance) data here. + mockData() + console.log('txData.value :>> ', txData.value) + const data = txData.value + + // TODO: Pass data from above when the API / data is ready + rawUserAnalytics.value = data + + // This compute's the user's wallet balance over time + computeUserAnalytics() + return { error, message, data } + } catch (error: any) { + throw new Error(error.message || 'Error getting user analytics') + } + } + + async function updateAnalytics() { + await getUserAnalytics() + } + + watchEffect(async () => { + resetUserAnalytics() + await getUserAnalytics() + }) + + function resetUserAnalytics() { + getUserAnalyticsError.value = null + userAnalytics.value = { + oneMonth: { + labels: [], + data: [] + }, + sixMonth: { + labels: [], + data: [] + }, + oneYear: { + labels: [], + data: [] + }, + historical: { + labels: [], + data: [] + } + } + } + + return { + finishedComputingUerAnalytics: readonly(finishedComputingUerAnalytics), + userAnalytics: readonly(userAnalytics), + getUserAnalyticsError: readonly(getUserAnalyticsError), + updateAnalytics, + } +} \ No newline at end of file diff --git a/apps/web/src/composables/user.ts b/apps/web/src/composables/user.ts index f576f2e10..1ad2474fe 100644 --- a/apps/web/src/composables/user.ts +++ b/apps/web/src/composables/user.ts @@ -1,7 +1,7 @@ import { onMounted, onUnmounted, readonly, ref, watch } from 'vue' import * as Session from 'supertokens-web-js/recipe/session' import { ethers } from 'ethers' -import { Account, BreakdownAmount, BreakdownString, ContractEventsByAddress, Currency, LoginCredentials, Pool, ProviderString, RegisteredOperator, User, UserAnalyticsData, UserWithAccountsAndOperators } from '@casimir/types' +import { Account, BreakdownAmount, BreakdownString, ContractEventsByAddress, Currency, LoginCredentials, Pool, ProviderString, RegisteredOperator, User, UserWithAccountsAndOperators } from '@casimir/types' import useContracts from '@/composables/contracts' import useEnvironment from '@/composables/environment' import useEthers from '@/composables/ethers' @@ -11,9 +11,6 @@ import usePrice from '@/composables/price' import useTrezor from '@/composables/trezor' import useWalletConnect from '@/composables/walletConnectV2' import { Operator, Scanner } from '@casimir/ssv' -import useTxData from '../mockData/mock_transaction_data' - -const { mockData, txData } = useTxData() // Test address: 0xd557a5745d4560B24D36A68b52351ffF9c86A212 const { manager, registry, views } = useContracts() @@ -27,30 +24,10 @@ const { loginWithWalletConnectV2, initializeWalletConnect, uninitializeWalletCon const initializeComposable = ref(false) const initializedUser = ref(false) -const finishedComputingUerAnalytics = ref(false) const nonregisteredOperators = ref([]) const provider = new ethers.providers.JsonRpcProvider(ethereumUrl) -const rawUserAnalytics = ref(null) const registeredOperators = ref([]) const user = ref(undefined) -const userAnalytics = ref({ - oneMonth: { - labels: [], - data: [] - }, - sixMonth: { - labels: [], - data: [] - }, - oneYear: { - labels: [], - data: [] - }, - historical: { - labels: [], - data: [] - } -}) const currentStaked = ref({ usd: '$0.00', @@ -138,119 +115,6 @@ export default function useUser() { await Promise.all(txs) } - function computeUserAnalytics() { - finishedComputingUerAnalytics.value = false - // const result = userAnalytics.value - console.log('rawUserAnalytics in computeAnalytics :>> ', rawUserAnalytics) - const sortedTransactions = rawUserAnalytics.value.sort((a: any, b: any) => { - new Date(a.receivedAt).getTime() - new Date(b.receivedAt).getTime() - }) - - let earliest: any = null - const latest: any = new Date().getTime() - const oneYear = new Date().getTime() - 31536000000 - const sixMonths = new Date().getTime() - 15768000000 - const oneMonth = new Date().getTime() - 2628000000 - sortedTransactions.forEach((tx: any) => { - const receivedAt = new Date(tx.receivedAt) - if (!earliest) earliest = receivedAt.getTime() - if (receivedAt.getTime() < earliest) earliest = receivedAt.getTime() - }) - const historicalInterval = (latest - earliest) / 11 - - sortedTransactions.forEach((tx: any) => { - const { receivedAt, walletAddress, walletBalance } = tx - /* Historical */ - let historicalDataIndex = userAnalytics.value.historical.data.findIndex((obj: any) => obj.walletAddress === walletAddress) - if (historicalDataIndex === -1) { - const dataLength = userAnalytics.value.historical.data.push({ walletAddress, walletBalance: Array(12).fill(0) }) - historicalDataIndex = dataLength - 1 - } - // Determine which interval the receivedAt falls into - const intervalIndex = Math.floor((new Date(receivedAt).getTime() - earliest) / historicalInterval) - // Set the value of the intervalIndex to the walletBalance - userAnalytics.value.historical.data[historicalDataIndex].walletBalance[intervalIndex] = walletBalance - - /* One Year */ - if (new Date(receivedAt).getTime() > oneYear) { - let oneYearDataIndex = userAnalytics.value.oneYear.data.findIndex((obj: any) => obj.walletAddress === walletAddress) - if (oneYearDataIndex === -1) { - const dataLength = userAnalytics.value.oneYear.data.push({ walletAddress, walletBalance: Array(12).fill(0) }) - oneYearDataIndex = dataLength - 1 - } - const monthsAgo = (new Date().getFullYear() - new Date(receivedAt).getFullYear()) * 12 + (new Date().getMonth() - new Date(receivedAt).getMonth()) - const intervalIndex = 11 - monthsAgo - userAnalytics.value.oneYear.data[oneYearDataIndex].walletBalance[intervalIndex] = walletBalance - } - - /* Six Months */ - if (new Date(receivedAt).getTime() > sixMonths) { - let sixMonthDataIndex = userAnalytics.value.sixMonth.data.findIndex((obj: any) => obj.walletAddress === walletAddress) - if (sixMonthDataIndex === -1) { - const dataLength = userAnalytics.value.sixMonth.data.push({ walletAddress, walletBalance: Array(6).fill(0) }) - sixMonthDataIndex = dataLength - 1 - } - const monthsAgo = (new Date().getFullYear() - new Date(receivedAt).getFullYear()) * 12 + (new Date().getMonth() - new Date(receivedAt).getMonth()) - const intervalIndex = 5 - monthsAgo - userAnalytics.value.sixMonth.data[sixMonthDataIndex].walletBalance[intervalIndex] = walletBalance - } - - /* One Month */ - if (new Date(receivedAt).getTime() > oneMonth) { - let oneMonthDataIndex = userAnalytics.value.oneMonth.data.findIndex((obj: any) => obj.walletAddress === walletAddress) - if (oneMonthDataIndex === -1) { - const dataLength = userAnalytics.value.oneMonth.data.push({ walletAddress, walletBalance: Array(30).fill(0) }) - oneMonthDataIndex = dataLength - 1 - } - const daysAgo = Math.floor((new Date().getTime() - new Date(receivedAt).getTime()) / 86400000) - const intervalIndex = 29 - daysAgo - userAnalytics.value.oneMonth.data[oneMonthDataIndex].walletBalance[intervalIndex] = walletBalance - } - }) - - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - - // Set the historical labels array to the interval labels - let previousMonth: any = null - userAnalytics.value.historical.labels = Array(12).fill(0).map((_, i) => { - const date = new Date(earliest + (historicalInterval * i)) - const currentMonth = date.getMonth() - if (!previousMonth) { - previousMonth = currentMonth - return date.getMonth() === 0 ? `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()}` : `${months[date.getMonth()]} ${date.getDate()}` - } else if (currentMonth < previousMonth) { - previousMonth = currentMonth - return `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()}` - } else { - previousMonth = currentMonth - return `${months[date.getMonth()]} ${date.getDate()}` - } - }) - - // Set the oneYear labels array to the interval labels - userAnalytics.value.oneYear.labels = Array(12).fill(0).map((_, i) => { - const date = new Date (new Date().setDate(1)) - const monthIndex = new Date(date.setMonth(date.getMonth() - (11 - i))) - return `${months[monthIndex.getMonth()]} ${monthIndex.getFullYear()}` - }) - - // Set the sixMonth labels array to the interval labels - userAnalytics.value.sixMonth.labels = Array(6).fill(0).map((_, i) => { - const date = new Date (new Date().setDate(1)) - const monthIndex = new Date(date.setMonth(date.getMonth() - (5 - i))) - return `${months[monthIndex.getMonth()]} ${monthIndex.getFullYear()}` - }) - - // Set the oneMonth labels array to the interval labels - userAnalytics.value.oneMonth.labels = [] - for (let i = 30; i > 0; i--) { - const date = new Date().getTime() - ((i - 1) * 86400000) - userAnalytics.value.oneMonth.labels.push(`${new Date(date).getMonth() + 1}/${new Date(date).getDate()}`) - } - // userAnalytics.value = result - finishedComputingUerAnalytics.value = true - } - async function getAllTimeStakingRewards() : Promise { try { /* Get User's Current Stake */ @@ -397,39 +261,6 @@ export default function useUser() { } } - async function getUserAnalytics() { - try { - const requestOptions = { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - } - // TODO: Re-enable this when athena is ready - const response = await fetch(`${usersUrl}/analytics`, requestOptions) - const { error, message, data: athenaData } = await response.json() - // console.log('data from analytics :>> ', data) - if (error) throw new Error(`Error in getUserAnalytics: ${message}`) - - // TODO: Get events, actions, and contract data from the API - // Then format the data to be used in the charts (see computeUserAnalytics) and give to Steve. - - // We get the user's analytics (wallet balance) data here. - mockData() - console.log('txData.value :>> ', txData.value) - const data = txData.value - - // TODO: Pass data from above when the API / data is ready - rawUserAnalytics.value = data - - // This compute's the user's wallet balance over time - computeUserAnalytics() - return { error, message, data } - } catch (error: any) { - throw new Error(error.message || 'Error getting user analytics') - } - } - async function getUserOperators(): Promise { const userAddresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] @@ -545,7 +376,7 @@ export default function useUser() { watch(user, async () => { if (user.value && !initializedUser.value) { initializedUser.value = true - await Promise.all([refreshBreakdown(), getUserOperators(), getUserAnalytics()]) + await Promise.all([refreshBreakdown(), getUserOperators()]) } else if (!user.value) { uninitializeUser() } @@ -653,7 +484,7 @@ export default function useUser() { } function uninitializeUser() { - // Update user accounts, operators, analytics + // Update user accounts, operators user.value = undefined // Reset currentStaked, totalWalletBalance, and stakingRewards currentStaked.value = { @@ -701,15 +532,11 @@ export default function useUser() { return { currentStaked: readonly(currentStaked), - finishedComputingUerAnalytics: readonly(finishedComputingUerAnalytics), nonregisteredOperators: readonly(nonregisteredOperators), - // rawUserAnalytics: readonly(rawUserAnalytics), // Causes there to be crazy warnings in browser console - rawUserAnalytics, registeredOperators: readonly(registeredOperators), stakingRewards: readonly(stakingRewards), totalWalletBalance: readonly(totalWalletBalance), user: readonly(user), - userAnalytics: readonly(userAnalytics), addAccountToUser, addOperator, login, diff --git a/apps/web/src/pages/overview/Overview.vue b/apps/web/src/pages/overview/Overview.vue index 023097633..dda5cee79 100644 --- a/apps/web/src/pages/overview/Overview.vue +++ b/apps/web/src/pages/overview/Overview.vue @@ -32,9 +32,9 @@ import BreakdownTable from './components/BreakdownTable.vue'
-
+
diff --git a/apps/web/src/pages/overview/components/BreakdownChart.vue b/apps/web/src/pages/overview/components/BreakdownChart.vue index 7e9696534..4830ba925 100644 --- a/apps/web/src/pages/overview/components/BreakdownChart.vue +++ b/apps/web/src/pages/overview/components/BreakdownChart.vue @@ -1,11 +1,12 @@