From a472e0e8cc27570c4d20ad9437faaa718e34efa3 Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Sun, 25 Aug 2024 22:25:15 +0200 Subject: [PATCH 1/5] save messy checkpoint --- src/app/state/store.ts | 2 + src/app/state/teamSlice.ts | 229 +++++++++++++++++++++++++++++++++++ src/app/team/page.tsx | 60 +++++++++ src/app/utils/GoogleSheet.ts | 15 +++ 4 files changed, 306 insertions(+) create mode 100644 src/app/state/teamSlice.ts create mode 100644 src/app/team/page.tsx create mode 100644 src/app/utils/GoogleSheet.ts diff --git a/src/app/state/store.ts b/src/app/state/store.ts index 24f84c4d..b587e4de 100644 --- a/src/app/state/store.ts +++ b/src/app/state/store.ts @@ -8,6 +8,7 @@ import { radixSlice } from "./radixSlice"; import { rewardSlice } from "./rewardSlice"; import { priceInfoSlice } from "./priceInfoSlice"; import { i18nSlice } from "./i18nSlice"; +import { teamSlice } from "./teamSlice"; export const store = configureStore({ reducer: { @@ -20,6 +21,7 @@ export const store = configureStore({ priceInfo: priceInfoSlice.reducer, i18n: i18nSlice.reducer, rewardSlice: rewardSlice.reducer, + teamSlice: teamSlice.reducer, }, }); diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts new file mode 100644 index 00000000..1f2a20e9 --- /dev/null +++ b/src/app/state/teamSlice.ts @@ -0,0 +1,229 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { GoogleSheet } from "../utils/GoogleSheet"; + +export interface TeamState { + contributorMap: [string, Contributor][]; + votingResultRows: VotingResultRow[]; +} + +interface VotingResultRow { + phase: number; + user: string; + points: number; +} + +const initialState: TeamState = { + contributorMap: [], + votingResultRows: [], +}; + +interface Allocation { + contributors: number; + treasury?: number; + liquidity?: number; + stakers?: number; +} + +function getAllocation(phase: number): Allocation { + const table = [ + // phase, contributors, treasury, liqudiity, stakers + [1, 1, 0, 0, 0], + [4, 0.95, 0.05, 0, 0], + [18, 0.75, 0.05, 0.2, 0], + [23, 0.65, 0.05, 0.2, 0.1], + [26, 0.45, 0, 0.3, 0.25], + ]; + for (let row of table.reverse()) { + const [currentPhase, contributors, treasury, liquidity, stakers] = row; + if (phase >= currentPhase) { + return { + contributors, + treasury, + liquidity, + stakers, + }; + } + } + return { contributors: 1 }; +} + +// Returns the total DEXTR emission for each phase +function getEmission(phase: number): number { + return phase <= 67 + ? 100000 + : phase <= 119 + ? 75000 + : phase <= 171 + ? 50000 + : phase <= 224 + ? 25000 + : 0; +} + +export enum Expertise { + "DEVELOPER" = "DEVELOPER", + "DESIGN" = "DESIGN", + "SOCIAL_MEDIA" = "SOCIAL_MEDIA", + "ADMINISTRATION" = "ADMINISTRATION", + "TESTER" = "TESTER", +} + +export interface Contributor { + telegram: string; + github?: string; + discord?: string; + imageUrl?: string; + expertise?: Expertise[]; + radixWallet?: string; + // badges + isOG?: boolean; + isLongTerm?: boolean; + isActive?: boolean; + phasesActive?: string[]; + // analytics + tokensEarned?: number; + trophyGold?: number; + trophySilver?: number; + trophyBronze?: number; +} + +export const teamSlice = createSlice({ + name: "team", + initialState, + + // synchronous reducers + reducers: {}, + + // async thunks + extraReducers: () => {}, +}); + +export async function fetchTeamState(): Promise { + const [contributorMap, votingResultRows] = await Promise.all([ + fecthContributorMap(), + fetchVotingResultRows(), + ]); + // Compute contributor analytics + runContributorAnalytics(contributorMap, votingResultRows); + return { + contributorMap: Array.from(contributorMap.entries()), + votingResultRows, + }; +} + +function runContributorAnalytics( + contributorMap: Map, + votingResultRows: VotingResultRow[] +): void { + for (let phase = 1; phase <= 224; phase++) { + const phaseRows = votingResultRows.filter((row) => row.phase === phase); + runPhaseAnalytics(phase, contributorMap, phaseRows); + } +} + +interface Trophies { + gold: { [key: string]: number }; + silver: { [key: string]: number }; + bronze: { [key: string]: number }; +} + +function runPhaseAnalytics( + phase: number, + contributorMap: Map, + phaseRows: VotingResultRow[] +) { + if (phaseRows.length === 0) { + return; + } + // Total points + const total = phaseRows.map((row) => row.points).reduce((a, b) => a + b, 0); + // Run for all rows + const trophies: Trophies = { + gold: {}, + silver: {}, + bronze: {}, + }; + for (let i = 0; i < phaseRows.length; i++) { + const { user, points } = phaseRows[i]; + if (i === 0) { + trophies.gold[user] = trophies.gold[user] ? trophies.gold[user] + 1 : 1; + } + if (i === 1) { + trophies.silver[user] = trophies.silver[user] + ? trophies.silver[user] + 1 + : 1; + } + if (i === 2) { + trophies.bronze[user] = trophies.bronze[user] + ? trophies.bronze[user] + 1 + : 1; + } + // get contributor rewards tokens + const totalTokens = getAllocation(phase).contributors * getEmission(phase); + const userTokens = totalTokens * (points / total); + console.log(`${user}: ${userTokens} DEXTR`); + const contributor = contributorMap.get(user); + if (!contributor) { + continue; + } + contributor.tokensEarned = contributor.tokensEarned + ? contributor.tokensEarned + userTokens + : userTokens; + } + // Log for testing + console.log( + `Phase ${phase}:\ntotal points: ${total}\ntotal contributors: ${phaseRows.length}` + ); +} + +async function fecthContributorMap(): Promise> { + const contributorsCSV = await GoogleSheet.fetch( + "19iIwysEyjCPBfqyKS7paBR33-ZRo4dGs8zwH-2JKlFk", + "597869606" + ); + const contributors = contributorsCSV + .split("\n") + .slice(1) + .map((row) => rowToContributor(row)); + const contributorMap: Map = new Map(); + for (let contributor of contributors) { + contributorMap.set(contributor.telegram, contributor); + } + return contributorMap; +} + +async function fetchVotingResultRows(): Promise { + const votingResultRowsCSV = await GoogleSheet.fetch( + "19iIwysEyjCPBfqyKS7paBR33-ZRo4dGs8zwH-2JKlFk", + "1859086643" + ); + return votingResultRowsCSV + .split("\n") + .slice(1) + .map((row) => rowToVotingResultRow(row)); +} + +function rowToContributor(row: string): Contributor { + const [telegram, github, discord, imageUrl, expertiseStr, radixWallet] = + row.split(","); + const expertise = expertiseStr + .split(";") + .map((str) => str.toUpperCase() as Expertise); + return { + telegram, + github, + discord, + imageUrl, + expertise, + radixWallet, + } as Contributor; +} + +function rowToVotingResultRow(row: string): VotingResultRow { + const [phase, user, points] = row.split(","); + return { + phase: Number(phase), + user, + points: Number(points), + } as VotingResultRow; +} diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx new file mode 100644 index 00000000..68fd8cd1 --- /dev/null +++ b/src/app/team/page.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { useEffect } from "react"; +import { fetchTeamState, Contributor } from "state/teamSlice"; + +export default function Rewards() { + useEffect(() => { + fetchTeamState().then((teamState) => { + const contr = teamState.contributorMap + .map((arr): Contributor | undefined => arr[1]) + .filter((item): item is Contributor => item !== undefined) + .sort( + (a, b) => + (b as { tokensEarned: number }).tokensEarned - + (a as { tokensEarned: number }).tokensEarned + ); + console.log(contr); + }); + }, []); + + return ( +
+
+ {/* */} + +
+
+ ); +} + +function Contributors() { + return ( +
+ + +
+ ); +} + +function DexterParagraph({ text }: { text: string }) { + return

{text}

; +} + +function DexterHeading({ title }: { title: string }) { + return ( + <> +

+ {title} +

+ + ); +} diff --git a/src/app/utils/GoogleSheet.ts b/src/app/utils/GoogleSheet.ts new file mode 100644 index 00000000..0f08ac3a --- /dev/null +++ b/src/app/utils/GoogleSheet.ts @@ -0,0 +1,15 @@ +/** + * This Service loads data from a google sheet as single string in CSV format + */ +const fetchSheet = async (sheetId: string, gic: string) => { + const url = `https://docs.google.com/spreadsheets/d/${sheetId}/export?format=csv&gid=${gic}`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.text(); // Getting the CSV data as text +}; + +export const GoogleSheet = { + fetch: fetchSheet, +}; From 481d203c7747be310fa5db034aee2a922225ab88 Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Wed, 28 Aug 2024 00:11:43 +0200 Subject: [PATCH 2/5] working barchart race export function --- src/app/state/teamSlice.ts | 104 ++++++++++++++++++++++++++++++++----- src/app/team/page.tsx | 21 ++++---- 2 files changed, 103 insertions(+), 22 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 1f2a20e9..e7719c11 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -10,6 +10,7 @@ interface VotingResultRow { phase: number; user: string; points: number; + tokens?: number; } const initialState: TeamState = { @@ -66,6 +67,7 @@ export enum Expertise { "SOCIAL_MEDIA" = "SOCIAL_MEDIA", "ADMINISTRATION" = "ADMINISTRATION", "TESTER" = "TESTER", + "NA" = "NA", } export interface Contributor { @@ -111,12 +113,72 @@ export async function fetchTeamState(): Promise { }; } +export function showContributorRanking( + contributorMap: Map +) { + const contr = Array.from(contributorMap.entries()) + .map((arr): Contributor | undefined => arr[1]) + .filter( + (item): item is Contributor => + item !== undefined && item.tokensEarned !== undefined + ) + .sort( + (a, b) => + (b as { tokensEarned: number }).tokensEarned - + (a as { tokensEarned: number }).tokensEarned + ); + // eslint-disable-next-line no-console + console.log(contr.map((c) => `${c.telegram}: ${c.tokensEarned}`).join("\n")); +} + +export function exportBarchartRaceData( + contributorMap: Map, + votingResultRows: VotingResultRow[] +) { + const phasesArray = votingResultRows.map((row) => row.phase); + const uniqPhases = phasesArray + .filter((value, index, self) => self.indexOf(value) === index) + .sort((a, b) => a - b); + const finalRows = []; + const header = ["user", "imageUrl", "category"] + .concat(uniqPhases.map((num) => num.toString())) + .flat(); + finalRows.push(header); + // for each contributor + const uniqeUsers = Array.from(contributorMap.keys()); + for (let user of uniqeUsers) { + const contributor = contributorMap.get(user); + const userRow = [ + user, + contributor?.imageUrl || "", + contributor?.expertise[0], + ]; + let userTokens = 0; + const userVotingResultRows = votingResultRows.filter( + (row) => row.user === user + ); + for (let phase = 1; phase <= 31; phase++) { + const targetUserVotingResultRow = userVotingResultRows.find( + (row) => row.phase === phase + ); + if (targetUserVotingResultRow && targetUserVotingResultRow.tokens) { + userTokens += targetUserVotingResultRow.tokens; + } + userRow.push(userTokens.toString()); + } + finalRows.push(userRow); + } +} + function runContributorAnalytics( contributorMap: Map, votingResultRows: VotingResultRow[] ): void { for (let phase = 1; phase <= 224; phase++) { const phaseRows = votingResultRows.filter((row) => row.phase === phase); + if (phaseRows.length === 0) { + continue; + } runPhaseAnalytics(phase, contributorMap, phaseRows); } } @@ -160,20 +222,16 @@ function runPhaseAnalytics( } // get contributor rewards tokens const totalTokens = getAllocation(phase).contributors * getEmission(phase); - const userTokens = totalTokens * (points / total); - console.log(`${user}: ${userTokens} DEXTR`); + const tokens = totalTokens * (points / total); + phaseRows[i].tokens = tokens; const contributor = contributorMap.get(user); if (!contributor) { continue; } contributor.tokensEarned = contributor.tokensEarned - ? contributor.tokensEarned + userTokens - : userTokens; + ? contributor.tokensEarned + tokens + : tokens; } - // Log for testing - console.log( - `Phase ${phase}:\ntotal points: ${total}\ntotal contributors: ${phaseRows.length}` - ); } async function fecthContributorMap(): Promise> { @@ -201,6 +259,28 @@ async function fetchVotingResultRows(): Promise { .split("\n") .slice(1) .map((row) => rowToVotingResultRow(row)); + // // compute tokens earned for each row + // for (let phase = 1; phase <= 224; phase++) { + // const phaseRows = votingResultRows.filter((row) => row.phase === phase); + // if (phaseRows.length === 0) { + // continue; + // } + // const phasePoints = phaseRows + // .map((row) => row.points) + // .reduce((a, b) => a + b, 0); + // const phaseContributorAllocation = + // getAllocation(phase).contributors * getEmission(phase); + // for (let phaseRow = 0; phaseRow < phaseRows.length; phaseRow++) { + // const { user, points } = phaseRows[phaseRow]; + // const contributor = contributorMap.get(user); + // if (!contributor) { + // continue; + // } + // contributor.tokensEarned = contributor.tokensEarned + // ? contributor.tokensEarned + userTokens + // : userTokens; + // } + // } } function rowToContributor(row: string): Contributor { @@ -210,9 +290,9 @@ function rowToContributor(row: string): Contributor { .split(";") .map((str) => str.toUpperCase() as Expertise); return { - telegram, - github, - discord, + telegram: telegram.toLowerCase(), + github: github.toLowerCase(), + discord: discord.toLowerCase(), imageUrl, expertise, radixWallet, @@ -223,7 +303,7 @@ function rowToVotingResultRow(row: string): VotingResultRow { const [phase, user, points] = row.split(","); return { phase: Number(phase), - user, + user: user.toLowerCase(), points: Number(points), } as VotingResultRow; } diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 68fd8cd1..fe511a17 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -1,20 +1,21 @@ "use client"; import { useEffect } from "react"; -import { fetchTeamState, Contributor } from "state/teamSlice"; +import { + fetchTeamState, + showContributorRanking, + Contributor, + exportBarchartRaceData, +} from "state/teamSlice"; export default function Rewards() { useEffect(() => { fetchTeamState().then((teamState) => { - const contr = teamState.contributorMap - .map((arr): Contributor | undefined => arr[1]) - .filter((item): item is Contributor => item !== undefined) - .sort( - (a, b) => - (b as { tokensEarned: number }).tokensEarned - - (a as { tokensEarned: number }).tokensEarned - ); - console.log(contr); + const contributorMap = new Map( + teamState.contributorMap + ); + showContributorRanking(contributorMap); + exportBarchartRaceData(contributorMap, teamState.votingResultRows); }); }, []); From 2417812b80b5e3d90ad280f0b1176392dfd2f20d Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Thu, 29 Aug 2024 21:09:21 +0200 Subject: [PATCH 3/5] add script to fetch barchartRaceData --- package.json | 3 +- scripts/exportBarchartRaceData.js | 239 ++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 scripts/exportBarchartRaceData.js diff --git a/package.json b/package.json index 9158ec09..c03b4b11 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test-integration": "playwright test", "test-all": "npm run lint && npm run test && npm run test-integration", "copy-pr": "node ./scripts/copy-pr.js", - "fetchUsers": "node ./scripts/fetchUsers.js" + "fetchUsers": "node ./scripts/fetchUsers.js", + "exportBarchartRaceData": "node ./scripts/exportBarchartRaceData.js" }, "dependencies": { "@mdx-js/loader": "^3.0.0", diff --git a/scripts/exportBarchartRaceData.js b/scripts/exportBarchartRaceData.js new file mode 100644 index 00000000..8afe083d --- /dev/null +++ b/scripts/exportBarchartRaceData.js @@ -0,0 +1,239 @@ +// Script that fetches data to create barchart race of contributors earning tokens +// Example Result: https://public.flourish.studio/visualisation/19200770/ +// +// Run with the following command +// > npm run exportBarchartRaceData -- +// +// For example +// > npm run exportBarchartRaceData -- 31 + +/* eslint-disable no-console */ + +const fs = require("fs"); +const path = require("path"); + +// Get max phase passed in as argument +const args = process.argv.slice(2); +const MAX_PHASE = Number(args[0]) || 32; + +/** + * HELPER FUNCTION: Fetch from google sheet + */ +const fetchSheet = async (sheetId, gic) => { + const url = `https://docs.google.com/spreadsheets/d/${sheetId}/export?format=csv&gid=${gic}`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.text(); // Getting the CSV data as text +}; +const GoogleSheet = { + fetch: fetchSheet, +}; + +/** + * HELPER FUNCTION: Write Object to File + */ +const getTimestampedFileName = (scriptName, fileEnding) => { + // Get the current date and time + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, "0"); + const day = String(now.getDate()).padStart(2, "0"); + const hours = String(now.getHours()).padStart(2, "0"); + const minutes = String(now.getMinutes()).padStart(2, "0"); + const seconds = String(now.getSeconds()).padStart(2, "0"); + // Format the filename + return `${year}-${month}-${day}_${hours}${minutes}${seconds}_${scriptName}.${fileEnding}`; +}; + +const getFilePath = (scriptName, fileEnding) => { + const filename = getTimestampedFileName(scriptName, fileEnding); + const directory = path.join(__dirname, ".scriptOutputs"); + // Ensure the directory exists + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + } + // Return the full path for the file + return path.join(directory, filename); +}; + +const writeObjectOrStringToFile = (objOrString, scriptName, fileEnding) => { + // If obj is already a string, write it directly; otherwise, stringify it + const stringToWrite = + typeof objOrString === "string" + ? objOrString + : JSON.stringify(objOrString, null, 2); + const filePath = getFilePath(scriptName, fileEnding); + fs.writeFile(filePath, stringToWrite, (err) => { + if (err) { + console.error("Error writing file:", err); + } else { + // eslint-disable-next-line no-console + console.log(`Successfully saved output to file: ${filePath}`); + } + }); +}; + +/** + * HELPER FUNCTION: Get allocation distribution for each phase + * (e.g. how much % goes to contributors, stakers etc...) + */ +function getAllocation(phase) { + const table = [ + // phase, contributors, treasury, liqudiity, stakers + [1, 1, 0, 0, 0], + [4, 0.95, 0.05, 0, 0], + [18, 0.75, 0.05, 0.2, 0], + [23, 0.65, 0.05, 0.2, 0.1], + [26, 0.45, 0, 0.3, 0.25], + ]; + for (let row of table.reverse()) { + const [currentPhase, contributors, treasury, liquidity, stakers] = row; + if (phase >= currentPhase) { + return { + contributors, + treasury, + liquidity, + stakers, + }; + } + } + return { contributors: 1 }; +} + +/** + * HELPER FUNCTION: Returns the total DEXTR emission for each phase + * Has decided linear emission reduction model implemented. + */ +function getEmission(phase) { + return phase <= 67 + ? 100000 + : phase <= 119 + ? 75000 + : phase <= 171 + ? 50000 + : phase <= 224 + ? 25000 + : 0; +} + +async function fetchVotingResultRows() { + const votingResultRowsCSV = await GoogleSheet.fetch( + "19iIwysEyjCPBfqyKS7paBR33-ZRo4dGs8zwH-2JKlFk", + "1859086643" + ); + return votingResultRowsCSV + .split("\n") + .slice(1) + .map((row) => rowToVotingResultRow(row)); +} + +function rowToVotingResultRow(row) { + const [phase, user, points] = row.split(","); + return { + phase: Number(phase), + user: user.toLowerCase(), + points: Number(points), + }; +} + +async function fecthContributorMap() { + const contributorsCSV = await GoogleSheet.fetch( + "19iIwysEyjCPBfqyKS7paBR33-ZRo4dGs8zwH-2JKlFk", + "597869606" + ); + const contributors = contributorsCSV + .split("\n") + .slice(1) + .map((row) => rowToContributor(row)); + const contributorMap = new Map(); + for (let contributor of contributors) { + contributorMap.set(contributor.telegram, contributor); + } + return contributorMap; +} + +function rowToContributor(row) { + const [telegram, github, discord, imageUrl, expertiseStr, radixWallet] = + row.split(","); + const expertise = expertiseStr.split(";").map((str) => str.toUpperCase()); + return { + telegram: telegram.toLowerCase(), + github: github.toLowerCase(), + discord: discord.toLowerCase(), + imageUrl, + expertise, + radixWallet, + }; +} + +(async () => { + // Fetch raw voting results raw rows containing fields: + // phase, user, points + const votingResultRows = await fetchVotingResultRows(); + // Add DEXTR amounts to each row in votingResultRows + // Run for each phase + for (let phase = 1; phase <= MAX_PHASE; phase++) { + const phaseRows = votingResultRows.filter((row) => row.phase === phase); + if (phaseRows.length === 0) { + continue; + } + // Total points for this phase + const totalPoints = phaseRows + .map((row) => row.points) + .reduce((a, b) => a + b, 0); + // Total contributor rewards for this phase + const totalTokens = getAllocation(phase).contributors * getEmission(phase); + // Determine earned DEXTR tokens for each contributor in this phase + for (let i = 0; i < phaseRows.length; i++) { + // Save to new field tokens + phaseRows[i].tokens = (phaseRows[i].points / totalPoints) * totalTokens; + } + } + + // Fetch contributorMap from google sheet + const contributorMap = await fecthContributorMap(); + + // Generate barchart data as CSV + const phasesArray = votingResultRows.map((row) => row.phase); + const uniqPhases = phasesArray + .filter((value, index, self) => self.indexOf(value) === index) + .sort((a, b) => a - b); + const finalRows = []; + const header = ["user", "imageUrl", "category"] + .concat(uniqPhases.map((num) => num.toString())) + .flat(); + finalRows.push(header); + // for each contributor + const uniqeUsers = Array.from(contributorMap.entries()).map((arr) => arr[0]); + for (let user of uniqeUsers) { + const contributor = contributorMap.get(user); + const userRow = [ + user, + contributor?.imageUrl || "", + contributor?.expertise[0], + ]; + let userTokens = 0; + const userVotingResultRows = votingResultRows.filter( + (row) => row.user === user + ); + for (let phase = 1; phase <= MAX_PHASE; phase++) { + const targetUserVotingResultRow = userVotingResultRows.find( + (row) => row.phase === phase + ); + if (targetUserVotingResultRow && targetUserVotingResultRow.tokens) { + userTokens += targetUserVotingResultRow.tokens; + } + userRow.push(userTokens.toString()); + } + finalRows.push(userRow); + } + + // Write result to file + writeObjectOrStringToFile( + finalRows.map((row) => row.join(",")).join("\n"), + "exportBarchartRaceData", + "csv" + ); +})(); From b270ad9d0fb530e1de72cc67ec604447cee18009 Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Thu, 29 Aug 2024 21:15:09 +0200 Subject: [PATCH 4/5] remove unneeded code --- src/app/state/store.ts | 2 - src/app/state/teamSlice.ts | 309 ----------------------------------- src/app/team/page.tsx | 61 ------- src/app/utils/GoogleSheet.ts | 15 -- 4 files changed, 387 deletions(-) delete mode 100644 src/app/state/teamSlice.ts delete mode 100644 src/app/team/page.tsx delete mode 100644 src/app/utils/GoogleSheet.ts diff --git a/src/app/state/store.ts b/src/app/state/store.ts index b587e4de..24f84c4d 100644 --- a/src/app/state/store.ts +++ b/src/app/state/store.ts @@ -8,7 +8,6 @@ import { radixSlice } from "./radixSlice"; import { rewardSlice } from "./rewardSlice"; import { priceInfoSlice } from "./priceInfoSlice"; import { i18nSlice } from "./i18nSlice"; -import { teamSlice } from "./teamSlice"; export const store = configureStore({ reducer: { @@ -21,7 +20,6 @@ export const store = configureStore({ priceInfo: priceInfoSlice.reducer, i18n: i18nSlice.reducer, rewardSlice: rewardSlice.reducer, - teamSlice: teamSlice.reducer, }, }); diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts deleted file mode 100644 index e7719c11..00000000 --- a/src/app/state/teamSlice.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { GoogleSheet } from "../utils/GoogleSheet"; - -export interface TeamState { - contributorMap: [string, Contributor][]; - votingResultRows: VotingResultRow[]; -} - -interface VotingResultRow { - phase: number; - user: string; - points: number; - tokens?: number; -} - -const initialState: TeamState = { - contributorMap: [], - votingResultRows: [], -}; - -interface Allocation { - contributors: number; - treasury?: number; - liquidity?: number; - stakers?: number; -} - -function getAllocation(phase: number): Allocation { - const table = [ - // phase, contributors, treasury, liqudiity, stakers - [1, 1, 0, 0, 0], - [4, 0.95, 0.05, 0, 0], - [18, 0.75, 0.05, 0.2, 0], - [23, 0.65, 0.05, 0.2, 0.1], - [26, 0.45, 0, 0.3, 0.25], - ]; - for (let row of table.reverse()) { - const [currentPhase, contributors, treasury, liquidity, stakers] = row; - if (phase >= currentPhase) { - return { - contributors, - treasury, - liquidity, - stakers, - }; - } - } - return { contributors: 1 }; -} - -// Returns the total DEXTR emission for each phase -function getEmission(phase: number): number { - return phase <= 67 - ? 100000 - : phase <= 119 - ? 75000 - : phase <= 171 - ? 50000 - : phase <= 224 - ? 25000 - : 0; -} - -export enum Expertise { - "DEVELOPER" = "DEVELOPER", - "DESIGN" = "DESIGN", - "SOCIAL_MEDIA" = "SOCIAL_MEDIA", - "ADMINISTRATION" = "ADMINISTRATION", - "TESTER" = "TESTER", - "NA" = "NA", -} - -export interface Contributor { - telegram: string; - github?: string; - discord?: string; - imageUrl?: string; - expertise?: Expertise[]; - radixWallet?: string; - // badges - isOG?: boolean; - isLongTerm?: boolean; - isActive?: boolean; - phasesActive?: string[]; - // analytics - tokensEarned?: number; - trophyGold?: number; - trophySilver?: number; - trophyBronze?: number; -} - -export const teamSlice = createSlice({ - name: "team", - initialState, - - // synchronous reducers - reducers: {}, - - // async thunks - extraReducers: () => {}, -}); - -export async function fetchTeamState(): Promise { - const [contributorMap, votingResultRows] = await Promise.all([ - fecthContributorMap(), - fetchVotingResultRows(), - ]); - // Compute contributor analytics - runContributorAnalytics(contributorMap, votingResultRows); - return { - contributorMap: Array.from(contributorMap.entries()), - votingResultRows, - }; -} - -export function showContributorRanking( - contributorMap: Map -) { - const contr = Array.from(contributorMap.entries()) - .map((arr): Contributor | undefined => arr[1]) - .filter( - (item): item is Contributor => - item !== undefined && item.tokensEarned !== undefined - ) - .sort( - (a, b) => - (b as { tokensEarned: number }).tokensEarned - - (a as { tokensEarned: number }).tokensEarned - ); - // eslint-disable-next-line no-console - console.log(contr.map((c) => `${c.telegram}: ${c.tokensEarned}`).join("\n")); -} - -export function exportBarchartRaceData( - contributorMap: Map, - votingResultRows: VotingResultRow[] -) { - const phasesArray = votingResultRows.map((row) => row.phase); - const uniqPhases = phasesArray - .filter((value, index, self) => self.indexOf(value) === index) - .sort((a, b) => a - b); - const finalRows = []; - const header = ["user", "imageUrl", "category"] - .concat(uniqPhases.map((num) => num.toString())) - .flat(); - finalRows.push(header); - // for each contributor - const uniqeUsers = Array.from(contributorMap.keys()); - for (let user of uniqeUsers) { - const contributor = contributorMap.get(user); - const userRow = [ - user, - contributor?.imageUrl || "", - contributor?.expertise[0], - ]; - let userTokens = 0; - const userVotingResultRows = votingResultRows.filter( - (row) => row.user === user - ); - for (let phase = 1; phase <= 31; phase++) { - const targetUserVotingResultRow = userVotingResultRows.find( - (row) => row.phase === phase - ); - if (targetUserVotingResultRow && targetUserVotingResultRow.tokens) { - userTokens += targetUserVotingResultRow.tokens; - } - userRow.push(userTokens.toString()); - } - finalRows.push(userRow); - } -} - -function runContributorAnalytics( - contributorMap: Map, - votingResultRows: VotingResultRow[] -): void { - for (let phase = 1; phase <= 224; phase++) { - const phaseRows = votingResultRows.filter((row) => row.phase === phase); - if (phaseRows.length === 0) { - continue; - } - runPhaseAnalytics(phase, contributorMap, phaseRows); - } -} - -interface Trophies { - gold: { [key: string]: number }; - silver: { [key: string]: number }; - bronze: { [key: string]: number }; -} - -function runPhaseAnalytics( - phase: number, - contributorMap: Map, - phaseRows: VotingResultRow[] -) { - if (phaseRows.length === 0) { - return; - } - // Total points - const total = phaseRows.map((row) => row.points).reduce((a, b) => a + b, 0); - // Run for all rows - const trophies: Trophies = { - gold: {}, - silver: {}, - bronze: {}, - }; - for (let i = 0; i < phaseRows.length; i++) { - const { user, points } = phaseRows[i]; - if (i === 0) { - trophies.gold[user] = trophies.gold[user] ? trophies.gold[user] + 1 : 1; - } - if (i === 1) { - trophies.silver[user] = trophies.silver[user] - ? trophies.silver[user] + 1 - : 1; - } - if (i === 2) { - trophies.bronze[user] = trophies.bronze[user] - ? trophies.bronze[user] + 1 - : 1; - } - // get contributor rewards tokens - const totalTokens = getAllocation(phase).contributors * getEmission(phase); - const tokens = totalTokens * (points / total); - phaseRows[i].tokens = tokens; - const contributor = contributorMap.get(user); - if (!contributor) { - continue; - } - contributor.tokensEarned = contributor.tokensEarned - ? contributor.tokensEarned + tokens - : tokens; - } -} - -async function fecthContributorMap(): Promise> { - const contributorsCSV = await GoogleSheet.fetch( - "19iIwysEyjCPBfqyKS7paBR33-ZRo4dGs8zwH-2JKlFk", - "597869606" - ); - const contributors = contributorsCSV - .split("\n") - .slice(1) - .map((row) => rowToContributor(row)); - const contributorMap: Map = new Map(); - for (let contributor of contributors) { - contributorMap.set(contributor.telegram, contributor); - } - return contributorMap; -} - -async function fetchVotingResultRows(): Promise { - const votingResultRowsCSV = await GoogleSheet.fetch( - "19iIwysEyjCPBfqyKS7paBR33-ZRo4dGs8zwH-2JKlFk", - "1859086643" - ); - return votingResultRowsCSV - .split("\n") - .slice(1) - .map((row) => rowToVotingResultRow(row)); - // // compute tokens earned for each row - // for (let phase = 1; phase <= 224; phase++) { - // const phaseRows = votingResultRows.filter((row) => row.phase === phase); - // if (phaseRows.length === 0) { - // continue; - // } - // const phasePoints = phaseRows - // .map((row) => row.points) - // .reduce((a, b) => a + b, 0); - // const phaseContributorAllocation = - // getAllocation(phase).contributors * getEmission(phase); - // for (let phaseRow = 0; phaseRow < phaseRows.length; phaseRow++) { - // const { user, points } = phaseRows[phaseRow]; - // const contributor = contributorMap.get(user); - // if (!contributor) { - // continue; - // } - // contributor.tokensEarned = contributor.tokensEarned - // ? contributor.tokensEarned + userTokens - // : userTokens; - // } - // } -} - -function rowToContributor(row: string): Contributor { - const [telegram, github, discord, imageUrl, expertiseStr, radixWallet] = - row.split(","); - const expertise = expertiseStr - .split(";") - .map((str) => str.toUpperCase() as Expertise); - return { - telegram: telegram.toLowerCase(), - github: github.toLowerCase(), - discord: discord.toLowerCase(), - imageUrl, - expertise, - radixWallet, - } as Contributor; -} - -function rowToVotingResultRow(row: string): VotingResultRow { - const [phase, user, points] = row.split(","); - return { - phase: Number(phase), - user: user.toLowerCase(), - points: Number(points), - } as VotingResultRow; -} diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx deleted file mode 100644 index fe511a17..00000000 --- a/src/app/team/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { - fetchTeamState, - showContributorRanking, - Contributor, - exportBarchartRaceData, -} from "state/teamSlice"; - -export default function Rewards() { - useEffect(() => { - fetchTeamState().then((teamState) => { - const contributorMap = new Map( - teamState.contributorMap - ); - showContributorRanking(contributorMap); - exportBarchartRaceData(contributorMap, teamState.votingResultRows); - }); - }, []); - - return ( -
-
- {/* */} - -
-
- ); -} - -function Contributors() { - return ( -
- - -
- ); -} - -function DexterParagraph({ text }: { text: string }) { - return

{text}

; -} - -function DexterHeading({ title }: { title: string }) { - return ( - <> -

- {title} -

- - ); -} diff --git a/src/app/utils/GoogleSheet.ts b/src/app/utils/GoogleSheet.ts deleted file mode 100644 index 0f08ac3a..00000000 --- a/src/app/utils/GoogleSheet.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This Service loads data from a google sheet as single string in CSV format - */ -const fetchSheet = async (sheetId: string, gic: string) => { - const url = `https://docs.google.com/spreadsheets/d/${sheetId}/export?format=csv&gid=${gic}`; - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return await response.text(); // Getting the CSV data as text -}; - -export const GoogleSheet = { - fetch: fetchSheet, -}; From 6bf5aad80725b84341271c1dfd59c32b9ae5a5e1 Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Thu, 29 Aug 2024 21:18:34 +0200 Subject: [PATCH 5/5] remove from npm script --- package.json | 3 +-- scripts/exportBarchartRaceData.js | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c03b4b11..9158ec09 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "test-integration": "playwright test", "test-all": "npm run lint && npm run test && npm run test-integration", "copy-pr": "node ./scripts/copy-pr.js", - "fetchUsers": "node ./scripts/fetchUsers.js", - "exportBarchartRaceData": "node ./scripts/exportBarchartRaceData.js" + "fetchUsers": "node ./scripts/fetchUsers.js" }, "dependencies": { "@mdx-js/loader": "^3.0.0", diff --git a/scripts/exportBarchartRaceData.js b/scripts/exportBarchartRaceData.js index 8afe083d..5f577000 100644 --- a/scripts/exportBarchartRaceData.js +++ b/scripts/exportBarchartRaceData.js @@ -1,11 +1,10 @@ // Script that fetches data to create barchart race of contributors earning tokens -// Example Result: https://public.flourish.studio/visualisation/19200770/ // // Run with the following command -// > npm run exportBarchartRaceData -- +// > node ./scripts/exportBarchartRaceData.js -- // // For example -// > npm run exportBarchartRaceData -- 31 +// > node ./scripts/exportBarchartRaceData.js -- 31 /* eslint-disable no-console */