Skip to content

Commit

Permalink
Merge pull request #1726 from Giveth/release-optimize-donation-box-me…
Browse files Browse the repository at this point in the history
…trics

Release optimize donation box metrics
  • Loading branch information
divine-comedian authored Jul 25, 2024
2 parents aae0d77 + eb3a6a4 commit cc0eba1
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 93 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import config from '../src/config';

interface DonationUpdate {
id: number;
Expand All @@ -10,6 +11,12 @@ export class FillUseDonationBoxAndRelevantTxHashInDonation1720634181001
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
const environment = config.get('ENVIRONMENT') as string;

if (environment === 'test') {
return;
}

const givethProjectId = 1;
const timeDiff = 60 * 1000; // 1 minute in milliseconds

Expand Down
22 changes: 22 additions & 0 deletions migration/1721260881923-addDonationPercentageToDonation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';

export class AddDonationPercentageToDonation1721260881923
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'donation',
new TableColumn({
name: 'donationPercentage',
type: 'decimal',
precision: 5,
scale: 2,
isNullable: true,
}),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('donation', 'donationPercentage');
}
}
72 changes: 72 additions & 0 deletions migration/1721261762331-fillDonationPercentageInDonation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import config from '../src/config';

interface DonationUpdate {
id: number;
donationPercentage: number;
}

export class FillDonationPercentageInDonation1721261762331
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
const environment = config.get('ENVIRONMENT') as string;

if (environment === 'test') {
return;
}

// Load all donations with useDonationBox set to true
const donations = await queryRunner.query(
`SELECT id, "userId", "projectId", "createdAt", "transactionId", amount, "relevantDonationTxHash"
FROM donation WHERE "useDonationBox" = true`,
);

// Calculate the donation percentages
const updates: DonationUpdate[] = [];

for (const donation of donations) {
if (donation.projectId === 1 && donation.relevantDonationTxHash) {
const relevantDonation = donations.find(
d => d.transactionId === donation.relevantDonationTxHash,
);

if (relevantDonation) {
const totalAmount = donation.amount + relevantDonation.amount;
const donationPercentage = (donation.amount / totalAmount) * 100;

updates.push({
id: donation.id,
donationPercentage,
});
}
}
}

// Perform batch update using a single query
if (updates.length > 0) {
const updateQuery = updates
.map(update => `(${update.id}, ${update.donationPercentage})`)
.join(', ');

await queryRunner.query(
`
UPDATE donation AS d
SET "donationPercentage" = u."donationPercentage" FROM (VALUES ${updateQuery}) AS u(id
, "donationPercentage")
WHERE d.id = u.id;
`,
);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`
UPDATE donation
SET "donationPercentage" = NULL
WHERE "donationPercentage" IS NOT NULL;
`,
);
}
}
19 changes: 19 additions & 0 deletions migration/1721841071921-addIndexToUseDonationBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm';

export class AddIndexToUseDonationBox1721841071921
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createIndex(
'donation',
new TableIndex({
name: 'IDX_USE_DONATION_BOX',
columnNames: ['useDonationBox'],
}),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropIndex('donation', 'IDX_USE_DONATION_BOX');
}
}
4 changes: 4 additions & 0 deletions src/entities/donation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ export class Donation extends BaseEntity {
@Column({ nullable: true })
relevantDonationTxHash?: string;

@Field({ nullable: true })
@Column('decimal', { precision: 5, scale: 2, nullable: true })
donationPercentage?: number;

static async findXdaiGivDonationsWithoutPrice() {
return this.createQueryBuilder('donation')
.where(`donation.currency = 'GIV' AND donation."valueUsd" IS NULL `)
Expand Down
47 changes: 5 additions & 42 deletions src/repositories/donationRepository.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { assert } from 'chai';
import moment from 'moment';
import {
assertThrowsAsync,
createDonationData,
createProjectData,
deleteProjectDirectlyFromDb,
Expand All @@ -18,8 +17,8 @@ import {
createDonation,
fillQfRoundDonationsUserScores,
findDonationById,
findDonationsByProjectIdWhichUseDonationBox,
findDonationsByTransactionId,
findRelevantDonations,
getPendingDonationsIds,
getProjectQfRoundStats,
isVerifiedDonationExistsInQfRound,
Expand Down Expand Up @@ -61,7 +60,7 @@ describe(
'isVerifiedDonationExistsInQfRound() test cases',
isVerifiedDonationExistsInQfRoundTestCases,
);
describe('findRelevantDonations', findRelevantDonationsTestCases);
describe('findDonationsToGiveth() test cases', findDonationsToGivethTestCases);

function fillQfRoundDonationsUserScoresTestCases() {
let qfRound: QfRound;
Expand Down Expand Up @@ -1411,8 +1410,8 @@ function isVerifiedDonationExistsInQfRoundTestCases() {
});
}

function findRelevantDonationsTestCases() {
it('should return relevant donations correctly', async () => {
function findDonationsToGivethTestCases() {
it('should return giveth donations correctly', async () => {
const project1 = await saveProjectDirectlyToDb(createProjectData());
const project2 = await saveProjectDirectlyToDb(createProjectData());
const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress());
Expand Down Expand Up @@ -1441,55 +1440,19 @@ function findRelevantDonationsTestCases() {
project2.id,
);

const { donationsToGiveth, pairedDonations } = await findRelevantDonations(
const donationsToGiveth = await findDonationsByProjectIdWhichUseDonationBox(
new Date('2023-01-01'),
new Date(),
project1.id,
);

assert.equal(donationsToGiveth.length, 1);
assert.equal(pairedDonations.length, 1);
assert.equal(donationsToGiveth[0].id, donation1.id);
assert.equal(pairedDonations[0].id, donation2.id);

// Clean up
await Donation.remove([donation1, donation2]);
await deleteProjectDirectlyFromDb(project1.id);
await deleteProjectDirectlyFromDb(project2.id);
await User.remove(user);
});

it('should throw an error if the relevant donation does not exist', async () => {
// Create project and user
const givethProject = await saveProjectDirectlyToDb(createProjectData());
const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress());

const donationsToGiveth = await saveDonationDirectlyToDb(
{
...createDonationData(),
projectId: givethProject.id,
createdAt: new Date(),
relevantDonationTxHash: 'tx1',
useDonationBox: true,
},
user.id,
givethProject.id,
);

// Fetch relevant donations and expect an error
await assertThrowsAsync(
() =>
findRelevantDonations(
new Date('2023-01-01'),
new Date(),
givethProject.id,
),
`the relevant donation to this donation does not exist: donation id = ${donationsToGiveth.id}`,
);

// Clean up
await Donation.remove(donationsToGiveth);
await deleteProjectDirectlyFromDb(givethProject.id);
await User.remove(user);
});
}
30 changes: 5 additions & 25 deletions src/repositories/donationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,36 +537,16 @@ export async function isVerifiedDonationExistsInQfRound(params: {
}
}

export async function findRelevantDonations(
export async function findDonationsByProjectIdWhichUseDonationBox(
startDate: Date,
endDate: Date,
givethProjectId: number,
): Promise<{ donationsToGiveth: Donation[]; pairedDonations: Donation[] }> {
const donations = await Donation.find({
projectId: number,
): Promise<Donation[]> {
return await Donation.find({
where: {
createdAt: Between(startDate, endDate),
useDonationBox: true,
projectId: projectId,
},
});

const donationsToGiveth: Donation[] = [];
const pairedDonations: Donation[] = [];

donations.forEach(donation => {
if (donation.projectId === givethProjectId) {
donationsToGiveth.push(donation);
const relevantDonation = donations.find(
d => d.transactionId === donation.relevantDonationTxHash,
);
if (relevantDonation) {
pairedDonations.push(relevantDonation);
} else {
throw new Error(
`the relevant donation to this donation does not exist: donation id = ${donation.id}`,
);
}
}
});

return { donationsToGiveth, pairedDonations };
}
8 changes: 4 additions & 4 deletions src/resolvers/donationResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4797,7 +4797,6 @@ async function donationMetricsTestCases() {
it('should return correct donation metrics', async () => {
const walletAddress1 = generateRandomEtheriumAddress();
const walletAddress2 = generateRandomEtheriumAddress();
const project1 = await saveProjectDirectlyToDb(createProjectData('giveth'));
const project2 = await saveProjectDirectlyToDb(createProjectData());
const user1 = await saveUserDirectlyToDb(walletAddress1);
const user2 = await saveUserDirectlyToDb(walletAddress2);
Expand All @@ -4812,9 +4811,10 @@ async function donationMetricsTestCases() {
}),
useDonationBox: true,
relevantDonationTxHash: 'tx1',
donationPercentage: (100 / 1000) * 100,
},
user1.id,
project1.id,
1, // giveth project id
);

const donation2 = await saveDonationDirectlyToDb(
Expand All @@ -4826,9 +4826,10 @@ async function donationMetricsTestCases() {
}),
useDonationBox: true,
relevantDonationTxHash: 'tx2',
donationPercentage: (50 / 250) * 100,
},
user1.id,
project1.id,
1, // giveth project id
);

// Donations to another project
Expand Down Expand Up @@ -4881,7 +4882,6 @@ async function donationMetricsTestCases() {

// Clean up
await Donation.remove([donation1, donation2, donation3, donation4]);
await deleteProjectDirectlyFromDb(project1.id);
await deleteProjectDirectlyFromDb(project2.id);
await User.remove([user1, user2]);
});
Expand Down
12 changes: 12 additions & 0 deletions src/resolvers/donationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,17 @@ export class DonationResolver {
transactionTx = transactionId?.toLowerCase() as string;
}

let donationPercentage = 0;
if (relevantDonationTxHash) {
const relevantDonation = await Donation.findOne({
where: { transactionId: relevantDonationTxHash },
});

if (relevantDonation) {
const totalValue = amount + relevantDonation.amount;
donationPercentage = (amount / totalValue) * 100;
}
}
const donation = await Donation.create({
amount: Number(amount),
transactionId: transactionTx,
Expand All @@ -835,6 +846,7 @@ export class DonationResolver {
chainType: chainType as ChainType,
useDonationBox,
relevantDonationTxHash,
donationPercentage,
});
if (referrerId) {
// Fill referrer data if referrerId is valid
Expand Down
Loading

0 comments on commit cc0eba1

Please sign in to comment.