Skip to content

Commit

Permalink
Release Hotfixes for QF Round (#1521)
Browse files Browse the repository at this point in the history
* add activeQfRoundId to sortingBy InstantBoosting

* add orderBy totalDonations and totalReactions

* fix filtering by QF

* remove qfRounds joins for non qf round filters

* add some temp logs

* remove temp logs

* Fix projectActualserviceView

* fix stream balance depleted issue (#1496)

Co-authored-by: mohammadranjbarz <[email protected]>

* rebuild

* refresh and fetch user address separately (#1499)

* Added pg_trgm extension migration (#1502)

* fix recurring donation count

* fix actual matching cap (#1507)

* fix query error

* fix user donations count

* fix recurring donation count tests

* fix user recurring donation query

* fix user recurring donation test

* add donations relation to qfround

* add findArchivedQfRounds endpoint

* add findArchivedQfRounds endpoint

* feat: add sponsors & banner images upload

* add sortBy to findArchivedQfRounds

* 1.23.3

* add new test graphql query

* add tests for new QfArchivedRounds

* fixes on qfArchivedRounds query

* add new tests for qfArchivedRounds query

* fix findArchivedQfRounds tests

* fix: keep already uploaded sponsors images

* fix skip and limit for findArchivedQfRounds

* Add logs and refactor the bootstrap code to help investigate latency problem

* Add poolSize to orm config

* Fix eslint errors

* remove changing squareRootSumOfProjects when cap is overflown

* Trigger ortto activity when user saves their profile info for the first time (#1520)

* add newUser to updateUser query

* add createOrttoProfile

* add createOrttoProfile to NotificationAdapterInterface

* add createOrttoProfile to MockNotificationAdapter

* add CREATE_ORTTO_PROFILE event

* Allow to set the matching pool token & amount to be something other than usd (#1517)

* Allow to set the matching pool token & amount to be something other than USD in adminjs

* Allow to set the matching pool token & amount to be something other than USD in QFRound table

* add null to allocatedTokenSymbol and allocatedTokenChainId

* add nullable true to allocatedTokenSymbol and allocatedTokenChainId

* add allocatedFundUSDPreferred and allocatedFundUSD to qfRound

---------

Co-authored-by: Ramin <[email protected]>
Co-authored-by: Mohammad Ranjbar Z <[email protected]>
Co-authored-by: Amin Latifi <[email protected]>
Co-authored-by: Meriem-BM <[email protected]>
  • Loading branch information
5 people authored May 2, 2024
1 parent c97552f commit 354ccb0
Show file tree
Hide file tree
Showing 20 changed files with 843 additions and 33 deletions.
29 changes: 29 additions & 0 deletions migration/1714018700116-add_archived_QFRound_fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddArchivedQFRoundFields1714018700116
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "qf_round"
ADD COLUMN IF NOT EXISTS "bannerBgImage" character varying
`);

await queryRunner.query(`
ALTER TABLE "qf_round"
ADD COLUMN IF NOT EXISTS "sponsorsImgs" character varying[] DEFAULT '{}' NOT NULL
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "qf_round"
DROP COLUMN IF EXISTS "bannerBgImage"
`);

await queryRunner.query(`
ALTER TABLE "qf_round"
DROP COLUMN IF EXISTS "sponsorsImgs"
`);
}
}
25 changes: 25 additions & 0 deletions migration/1714566501335-addTokenAndChainToQFRound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddTokenAndChainToQFRound1714566501335
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE IF EXISTS "qf_round"
ADD COLUMN IF NOT EXISTS "allocatedTokenSymbol" text,
ADD COLUMN IF NOT EXISTS "allocatedTokenChainId" integer,
ADD COLUMN IF NOT EXISTS "allocatedFundUSDPreferred" boolean,
ADD COLUMN IF NOT EXISTS "allocatedFundUSD" integer;
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE IF EXISTS "qf_round"
DROP COLUMN "allocatedTokenSymbol",
DROP COLUMN "allocatedTokenChainId",
DROP COLUMN "allocatedFundUSDPreferred",
DROP COLUMN "allocatedFundUSD";
`);
}
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "giveth-graphql-api",
"version": "1.23.2",
"version": "1.23.3",
"description": "Backend GraphQL server for Giveth originally forked from Topia",
"main": "./dist/index.js",
"dependencies": {
Expand Down
5 changes: 5 additions & 0 deletions src/adapters/notifications/MockNotificationAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { logger } from '../../utils/logger';
import { RecurringDonation } from '../../entities/recurringDonation';

export class MockNotificationAdapter implements NotificationAdapterInterface {
async createOrttoProfile(params: User): Promise<void> {
logger.debug('MockNotificationAdapter createOrttoProfile', params);
return Promise.resolve(undefined);
}

async updateOrttoPeople(params: OrttoPerson[]): Promise<void> {
logger.debug('MockNotificationAdapter updateOrttoPeople', params);
return Promise.resolve(undefined);
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/notifications/NotificationAdapterInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface OrttoPerson {
}

export interface NotificationAdapterInterface {
createOrttoProfile(params: User): Promise<void>;

updateOrttoPeople(params: OrttoPerson[]): Promise<void>;

donationReceived(params: {
Expand Down
18 changes: 17 additions & 1 deletion src/adapters/notifications/NotificationCenterAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface {
return;
}

async createOrttoProfile(user: User): Promise<void> {
try {
const { id, email, firstName, lastName } = user;
await callSendNotification({
eventName: NOTIFICATIONS_EVENT_NAMES.CREATE_ORTTO_PROFILE,
trackId: 'create-ortto-profile-' + user.id,
userWalletAddress: user.walletAddress!,
segment: {
payload: { userId: id, email, firstName, lastName },
},
});
} catch (e) {
logger.error('createOrttoProfile >> error', e);
}
}

async updateOrttoPeople(people: OrttoPerson[]): Promise<void> {
// TODO we should me this to notification-center, it's not good that we call Ortto directly
try {
Expand Down Expand Up @@ -1196,7 +1212,7 @@ interface SendNotificationBody {
email?: string;
trackId?: string;
metadata?: any;
projectId: string;
projectId?: string;
userWalletAddress: string;
segment?: {
payload: any;
Expand Down
1 change: 1 addition & 0 deletions src/analytics/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ export enum NOTIFICATIONS_EVENT_NAMES {
SUPER_TOKENS_BALANCE_WEEK = 'One week left in stream balance',
SUPER_TOKENS_BALANCE_MONTH = 'One month left in stream balance',
SUPER_TOKENS_BALANCE_DEPLETED = 'Stream balance depleted',
CREATE_ORTTO_PROFILE = 'Create Ortto profile',
}
29 changes: 29 additions & 0 deletions src/entities/qfRound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import {
UpdateDateColumn,
CreateDateColumn,
Index,
OneToMany,
} from 'typeorm';
import { Project } from './project';
import { Donation } from './donation';

@Entity()
@ObjectType()
Expand Down Expand Up @@ -43,6 +45,22 @@ export class QfRound extends BaseEntity {
@Column()
allocatedFund: number;

@Field(_type => Number, { nullable: true })
@Column({ nullable: true })
allocatedFundUSD: number;

@Field(_type => Boolean, { nullable: true })
@Column({ nullable: true })
allocatedFundUSDPreferred: boolean;

@Field(_type => String, { nullable: true })
@Column({ nullable: true })
allocatedTokenSymbol: string;

@Field(_type => Number, { nullable: true })
@Column({ nullable: true })
allocatedTokenChainId: number;

@Field(_type => Number)
@Column('real', { default: 0.2 })
maximumReward: number;
Expand All @@ -67,6 +85,14 @@ export class QfRound extends BaseEntity {
@Column()
endDate: Date;

@Field(_type => String, { nullable: true })
@Column('text', { nullable: true })
bannerBgImage: string;

@Field(_type => [String])
@Column('text', { array: true, default: [] })
sponsorsImgs: string[];

@UpdateDateColumn()
updatedAt: Date;

Expand All @@ -76,6 +102,9 @@ export class QfRound extends BaseEntity {
@ManyToMany(_type => Project, project => project.qfRounds)
projects: Project[];

@OneToMany(_type => Donation, donation => donation.qfRound)
donations: Donation[];

// only projects with status active can be listed automatically
isEligibleNetwork(donationNetworkId: number): boolean {
// when not specified, all are valid
Expand Down
79 changes: 79 additions & 0 deletions src/repositories/qfRoundRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Field, Float, Int, ObjectType, registerEnumType } from 'type-graphql';
import { QfRound } from '../entities/qfRound';
import { AppDataSource } from '../orm';
import { QfArchivedRoundsOrderBy } from '../resolvers/qfRoundResolver';

const qfRoundEstimatedMatchingParamsCacheDuration = Number(
process.env.QF_ROUND_ESTIMATED_MATCHING_CACHE_DURATION || 60000,
Expand All @@ -11,6 +13,83 @@ export const findAllQfRounds = async (): Promise<QfRound[]> => {
.getMany();
};

export enum QfArchivedRoundsSortType {
allocatedFund = 'allocatedFund',
totalDonations = 'totalDonations',
uniqueDonors = 'uniqueDonors',
beginDate = 'beginDate',
}

registerEnumType(QfArchivedRoundsSortType, {
name: 'QfArchivedRoundsSortType',
description: 'The attributes by which archived rounds can be sorted.',
});

@ObjectType()
export class QFArchivedRounds {
@Field(_type => String)
id: string;

@Field(_type => String, { nullable: true })
name: string;

@Field(_type => String)
slug: string;

@Field(_type => Boolean)
isActive: boolean;

@Field(_type => Int)
allocatedFund: number;

@Field(_type => [Int])
eligibleNetworks: number;

@Field(_type => Date)
beginDate: Date;

@Field(_type => Date)
endDate: Date;

@Field(_type => Float, { nullable: true })
totalDonations: number;

@Field(_type => String, { nullable: true })
uniqueDonors: string;
}

export const findArchivedQfRounds = async (
limit: number,
skip: number,
orderBy: QfArchivedRoundsOrderBy,
): Promise<QFArchivedRounds[]> => {
const { direction, field } = orderBy;
const fieldMap = {
[QfArchivedRoundsSortType.beginDate]: 'qfRound.beginDate',
[QfArchivedRoundsSortType.allocatedFund]: 'qfRound.allocatedFund',
[QfArchivedRoundsSortType.totalDonations]: 'SUM(donation.amount)',
[QfArchivedRoundsSortType.uniqueDonors]:
'COUNT(DISTINCT donation.fromWalletAddress)',
};
const fullRounds = await QfRound.createQueryBuilder('qfRound')
.where('"isActive" = false')
.leftJoin('qfRound.donations', 'donation')
.select('qfRound.id', 'id')
.addSelect('qfRound.name', 'name')
.addSelect('qfRound.slug', 'slug')
.addSelect('qfRound.isActive', 'isActive')
.addSelect('qfRound.endDate', 'endDate')
.addSelect('qfRound.eligibleNetworks', 'eligibleNetworks')
.addSelect('SUM(donation.amount)', 'totalDonations')
.addSelect('COUNT(DISTINCT donation.fromWalletAddress)', 'uniqueDonors')
.addSelect('qfRound.allocatedFund', 'allocatedFund')
.addSelect('qfRound.beginDate', 'beginDate')
.groupBy('qfRound.id')
.orderBy(fieldMap[field], direction, 'NULLS LAST')
.getRawMany();
return fullRounds.slice(skip, skip + limit);
};

export const findActiveQfRound = async (): Promise<QfRound | null> => {
return QfRound.createQueryBuilder('qf_round')
.where('"isActive" = true')
Expand Down
1 change: 0 additions & 1 deletion src/resolvers/qfRoundHistoryResolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Arg, Int, Query, Resolver } from 'type-graphql';

import { QfRoundHistory } from '../entities/qfRoundHistory';
import { getQfRoundHistory } from '../repositories/qfRoundHistoryRepository';

Expand Down
79 changes: 78 additions & 1 deletion src/resolvers/qfRoundResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,95 @@ import {
saveDonationDirectlyToDb,
saveProjectDirectlyToDb,
saveUserDirectlyToDb,
SEED_DATA,
} from '../../test/testUtils';
import { Project } from '../entities/project';
import { QfRound } from '../entities/qfRound';
import {
refreshProjectDonationSummaryView,
refreshProjectEstimatedMatchingView,
} from '../services/projectViewsService';
import { qfRoundStatsQuery } from '../../test/graphqlQueries';
import {
fetchQFArchivedRounds,
qfRoundStatsQuery,
} from '../../test/graphqlQueries';
import { generateRandomString } from '../utils/utils';
import { OrderDirection } from './projectResolver';
import { QfArchivedRoundsSortType } from '../repositories/qfRoundRepository';

describe('Fetch estimatedMatching test cases', fetchEstimatedMatchingTestCases);
describe('Fetch qfRoundStats test cases', fetchQfRoundStatesTestCases);
describe('Fetch archivedQFRounds test cases', fetchArchivedQFRoundsTestCases);

function fetchArchivedQFRoundsTestCases() {
it('should return correct data when fetching archived QF rounds', async () => {
await QfRound.update({}, { isActive: true });
const qfRound1 = QfRound.create({
isActive: true,
name: 'test1',
slug: generateRandomString(10),
allocatedFund: 100000,
minimumPassportScore: 8,
beginDate: new Date(),
endDate: moment().add(10, 'days').toDate(),
});
await qfRound1.save();

await saveDonationDirectlyToDb(
{
...createDonationData(),
valueUsd: 150,
qfRoundId: qfRound1.id,
status: 'verified',
},
SEED_DATA.FIRST_USER.id,
SEED_DATA.FIRST_PROJECT.id,
);
await saveDonationDirectlyToDb(
{
...createDonationData(),
valueUsd: 250,
qfRoundId: qfRound1.id,
status: 'verified',
},
SEED_DATA.FIRST_USER.id,
SEED_DATA.FIRST_PROJECT.id,
);

const qfRound2 = QfRound.create({
isActive: false,
name: 'test2',
slug: generateRandomString(10),
allocatedFund: 200000,
minimumPassportScore: 8,
beginDate: moment().add(-10, 'days').toDate(),
endDate: moment().add(10, 'days').toDate(),
});
await qfRound2.save();
const qfRound3 = QfRound.create({
isActive: false,
name: 'test3',
slug: generateRandomString(10),
allocatedFund: 300000,
minimumPassportScore: 8,
beginDate: new Date(),
endDate: moment().add(10, 'days').toDate(),
});
await qfRound3.save();
const result = await axios.post(graphqlUrl, {
query: fetchQFArchivedRounds,
variables: {
orderBy: {
direction: OrderDirection.DESC,
field: QfArchivedRoundsSortType.beginDate,
},
},
});
const res = result.data.data.qfArchivedRounds;
assert.equal(res[0].id, qfRound3.id);
assert.equal(res.length, 2);
});
}

function fetchQfRoundStatesTestCases() {
let qfRound: QfRound;
Expand Down
Loading

0 comments on commit 354ccb0

Please sign in to comment.