Skip to content

Commit

Permalink
Release relevant donations (#1687)
Browse files Browse the repository at this point in the history
* get donation to giveth with donation box analytics (#1661)

* Add a method for finding relevant donations to the donationRepository.ts

* Add a method for calculating donations metrics to the donationService.ts

* Add a gql resolver for donations metrics to the donationResolver.ts

* Add unit test for gql query resolver

* Fix division by zero issue

* times 100 average percentage to represent percent

* add useDonationBox field to donation and draftDonation

* fill useDonationBox field with correct data in migrations

* Change donationMetrics endpoint based on change in data schema

* remove unused import

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* remove unused import

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* add default value for useDonationBox field

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* add false removed imports

* add default value for useDonationBox field

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix tests based on changes

* fix tests

* send useDonationBox as last arg and make it optional

* add relevant donation tx hash to donation creation flow

* calculate related donations based on relevant donation tx hash

* add unit tests

* update unit test

* fix bug

* fix bug in donation repository and tests are pass

* Clear All donations after donation metrics test cases

* Add new function for deleting project from db and use it to don't affect other test cases

* comment new test cases to ensure bug is related to that or not

* Clear donations after tests

* Comments new test cases in donationRepository.test.ts

* uncomment tests and Clean up test effects on DB

* Remove project addresses too

* Add a new method to testUtils.ts for removing project from db by id

* change donation dates to now

* Remove clear donations sections

* Change update count to a bigger number to pass test

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Remove old donation and add new ones (#1674)

* Remove unused variable

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
ae2079 and coderabbitai[bot] authored Jul 17, 2024
1 parent de0ea53 commit 0f760e0
Show file tree
Hide file tree
Showing 17 changed files with 592 additions and 6 deletions.
31 changes: 31 additions & 0 deletions migration/1719887968639-addUseDonationBoxToDraftDonations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';

export class AddUseDonationBoxToDraftDonations1719887968639
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'draft_donation',
new TableColumn({
name: 'useDonationBox',
type: 'boolean',
isNullable: true,
default: false,
}),
);

await queryRunner.addColumn(
'draft_donation',
new TableColumn({
name: 'relevantDonationTxHash',
type: 'varchar',
isNullable: true,
}),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('draft_donation', 'useDonationBox');
await queryRunner.dropColumn('draft_donation', 'relevantDonationTxHash');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';

export class AddUseDonationBoxAndRelevantTxHashToDonation1720634068179
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable('donation');
const useDonationBoxColumn = table?.findColumnByName('useDonationBox');
const relevantDonationTxHashColumn = table?.findColumnByName(
'relevantDonationTxHash',
);

if (!useDonationBoxColumn) {
await queryRunner.addColumn(
'donation',
new TableColumn({
name: 'useDonationBox',
type: 'boolean',
isNullable: true,
default: false,
}),
);
}

if (!relevantDonationTxHashColumn) {
await queryRunner.addColumn(
'donation',
new TableColumn({
name: 'relevantDonationTxHash',
type: 'varchar',
isNullable: true,
}),
);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('donation', 'useDonationBox');
await queryRunner.dropColumn('donation', 'relevantDonationTxHash');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

interface DonationUpdate {
id: number;
useDonationBox: boolean;
relevantDonationTxHash: string | null;
}

export class FillUseDonationBoxAndRelevantTxHashInDonation1720634181001
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
const givethProjectId = 1;
const timeDiff = 60 * 1000; // 1 minute in milliseconds

// Load all donations into memory
const donations = await queryRunner.query(
`SELECT id, "userId", "projectId", "createdAt", "transactionId" FROM donation`,
);

// Calculate relevant donations
const updates: DonationUpdate[] = [];
const userDonations = donations.reduce(
(acc, donation) => {
const userId = donation.userId;
if (!acc[userId]) {
acc[userId] = [];
}
acc[userId].push(donation);
return acc;
},
{} as Record<number, any[]>,
);

for (const userId in userDonations) {
const userDonationList = userDonations[userId];
userDonationList.sort(
(a, b) =>
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
);

for (let i = 0; i < userDonationList.length; i++) {
const donation = userDonationList[i];
if (donation.projectId === givethProjectId) {
let found = false;

// Check for donations after the current donation
for (let j = i + 1; j < userDonationList.length; j++) {
const nextDonation = userDonationList[j];
const timeDifference =
new Date(nextDonation.createdAt).getTime() -
new Date(donation.createdAt).getTime();
if (timeDifference <= timeDiff) {
if (nextDonation.projectId !== givethProjectId) {
updates.push({
id: donation.id,
useDonationBox: true,
relevantDonationTxHash: nextDonation.transactionId,
});
updates.push({
id: nextDonation.id,
useDonationBox: true,
relevantDonationTxHash: null,
});
found = true;
break;
}
} else {
break;
}
}

// Check for donations before the current donation if no relevant donation found
if (!found) {
for (let k = i - 1; k >= 0; k--) {
const prevDonation = userDonationList[k];
const timeDifference =
new Date(donation.createdAt).getTime() -
new Date(prevDonation.createdAt).getTime();
if (timeDifference <= timeDiff) {
if (prevDonation.projectId !== givethProjectId) {
updates.push({
id: donation.id,
useDonationBox: true,
relevantDonationTxHash: prevDonation.transactionId,
});
updates.push({
id: prevDonation.id,
useDonationBox: true,
relevantDonationTxHash: null,
});
break;
}
} else {
break;
}
}
}
}
}
}

// Perform batch update using a single query
const updateQuery = updates
.map(
update =>
`(${update.id}, ${update.useDonationBox}, ${
update.relevantDonationTxHash
? `'${update.relevantDonationTxHash}'`
: 'NULL'
})`,
)
.join(', ');

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

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`
UPDATE donation
SET "useDonationBox" = false,
"relevantDonationTxHash" = NULL
WHERE "useDonationBox" = true;
`,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type FetchedSavedFailDonationInterface = {
symbol: string;
chainvineReferred?: string;
safeTransactionId?: string;
useDonationBox?: boolean;
relevantDonationTxHash?: string;
};

export interface DonationSaveBackupInterface {
Expand Down
8 changes: 8 additions & 0 deletions src/entities/donation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ export class Donation extends BaseEntity {
// To match the superFluid Virtual Period
virtualPeriodEnd?: number;

@Field({ nullable: true })
@Column('boolean', { nullable: true, default: false })
useDonationBox?: boolean;

@Field({ nullable: true })
@Column({ nullable: true })
relevantDonationTxHash?: string;

static async findXdaiGivDonationsWithoutPrice() {
return this.createQueryBuilder('donation')
.where(`donation.currency = 'GIV' AND donation."valueUsd" IS NULL `)
Expand Down
8 changes: 8 additions & 0 deletions src/entities/draftDonation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,12 @@ export class DraftDonation extends BaseEntity {
@Field()
@Column({ nullable: true })
matchedDonationId?: number;

@Field()
@Column({ nullable: true, default: false })
useDonationBox?: boolean;

@Field()
@Column({ nullable: true })
relevantDonationTxHash?: string;
}
87 changes: 87 additions & 0 deletions src/repositories/donationRepository.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { assert } from 'chai';
import moment from 'moment';
import {
assertThrowsAsync,
createDonationData,
createProjectData,
deleteProjectDirectlyFromDb,
generateRandomEtheriumAddress,
generateRandomEvmTxHash,
saveDonationDirectlyToDb,
Expand All @@ -21,6 +23,7 @@ import {
findStableCoinDonationsWithoutPrice,
getPendingDonationsIds,
isVerifiedDonationExistsInQfRound,
findRelevantDonations,
sumDonationValueUsd,
sumDonationValueUsdForQfRound,
} from './donationRepository';
Expand Down Expand Up @@ -69,6 +72,7 @@ describe(
'isVerifiedDonationExistsInQfRound() test cases',
isVerifiedDonationExistsInQfRoundTestCases,
);
describe('findRelevantDonations', findRelevantDonationsTestCases);

function fillQfRoundDonationsUserScoresTestCases() {
let qfRound: QfRound;
Expand Down Expand Up @@ -1509,3 +1513,86 @@ function isVerifiedDonationExistsInQfRoundTestCases() {
await qfRound.save();
});
}

function findRelevantDonationsTestCases() {
it('should return relevant donations correctly', async () => {
const project1 = await saveProjectDirectlyToDb(createProjectData());
const project2 = await saveProjectDirectlyToDb(createProjectData());
const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress());

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

const donation2 = await saveDonationDirectlyToDb(
{
...createDonationData(),
projectId: project2.id,
createdAt: new Date(),
transactionId: 'tx1',
useDonationBox: true,
},
user.id,
project2.id,
);

const { donationsToGiveth, pairedDonations } = await findRelevantDonations(
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);
});
}
Loading

0 comments on commit 0f760e0

Please sign in to comment.