Merge branch 'master' into test-transaction-page

This commit is contained in:
Moriz Wahl 2023-01-17 17:05:21 +01:00 committed by GitHub
commit 610fbcc8fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 130 additions and 77 deletions

View File

@ -4,12 +4,14 @@ export const communityStatistics = gql`
query { query {
communityStatistics { communityStatistics {
totalUsers totalUsers
activeUsers
deletedUsers deletedUsers
totalGradidoCreated totalGradidoCreated
totalGradidoDecayed totalGradidoDecayed
totalGradidoAvailable dynamicStatisticsFields {
totalGradidoUnbookedDecayed activeUsers
totalGradidoAvailable
totalGradidoUnbookedDecayed
}
} }
} }
` `

View File

@ -17,12 +17,14 @@ const defaultData = () => {
return { return {
communityStatistics: { communityStatistics: {
totalUsers: 3113, totalUsers: 3113,
activeUsers: 1057,
deletedUsers: 35, deletedUsers: 35,
totalGradidoCreated: '4083774.05000000000000000000', totalGradidoCreated: '4083774.05000000000000000000',
totalGradidoDecayed: '-1062639.13634129622923372197', totalGradidoDecayed: '-1062639.13634129622923372197',
totalGradidoAvailable: '2513565.869444365732411569', dynamicStatisticsFields: {
totalGradidoUnbookedDecayed: '-500474.6738366222166261272', activeUsers: 1057,
totalGradidoAvailable: '2513565.869444365732411569',
totalGradidoUnbookedDecayed: '-500474.6738366222166261272',
},
}, },
} }
} }

View File

@ -31,7 +31,9 @@ export default {
return communityStatistics return communityStatistics
}, },
update({ communityStatistics }) { update({ communityStatistics }) {
this.statistics = communityStatistics const totals = { ...communityStatistics.dynamicStatisticsFields }
this.statistics = { ...communityStatistics, ...totals }
delete this.statistics.dynamicStatisticsFields
}, },
error({ message }) { error({ message }) {
this.toastError(message) this.toastError(message)

View File

@ -2,13 +2,25 @@ import { ObjectType, Field } from 'type-graphql'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
@ObjectType() @ObjectType()
export class CommunityStatistics { export class DynamicStatisticsFields {
@Field(() => Number)
totalUsers: number
@Field(() => Number) @Field(() => Number)
activeUsers: number activeUsers: number
@Field(() => Decimal)
totalGradidoAvailable: Decimal
@Field(() => Decimal)
totalGradidoUnbookedDecayed: Decimal
}
@ObjectType()
export class CommunityStatistics {
@Field(() => Number)
allUsers: number
@Field(() => Number)
totalUsers: number
@Field(() => Number) @Field(() => Number)
deletedUsers: number deletedUsers: number
@ -18,9 +30,7 @@ export class CommunityStatistics {
@Field(() => Decimal) @Field(() => Decimal)
totalGradidoDecayed: Decimal totalGradidoDecayed: Decimal
@Field(() => Decimal) // be carefull querying this, takes longer than 2 secs.
totalGradidoAvailable: Decimal @Field(() => DynamicStatisticsFields)
dynamicStatisticsFields: DynamicStatisticsFields
@Field(() => Decimal)
totalGradidoUnbookedDecayed: Decimal
} }

View File

@ -1,81 +1,113 @@
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { Resolver, Query, Authorized } from 'type-graphql' import { Resolver, Query, Authorized, FieldResolver } from 'type-graphql'
import { getConnection } from '@dbTools/typeorm' import { getConnection } from '@dbTools/typeorm'
import { Transaction as DbTransaction } from '@entity/Transaction' import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User' import { User as DbUser } from '@entity/User'
import { CommunityStatistics } from '@model/CommunityStatistics' import { CommunityStatistics, DynamicStatisticsFields } from '@model/CommunityStatistics'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ @Resolver((of) => CommunityStatistics)
@Resolver()
export class StatisticsResolver { export class StatisticsResolver {
@Authorized([RIGHTS.COMMUNITY_STATISTICS]) @Authorized([RIGHTS.COMMUNITY_STATISTICS])
@Query(() => CommunityStatistics) @Query(() => CommunityStatistics)
async communityStatistics(): Promise<CommunityStatistics> { async communityStatistics(): Promise<CommunityStatistics> {
const allUsers = await DbUser.count({ withDeleted: true }) return new CommunityStatistics()
const totalUsers = await DbUser.count() }
const deletedUsers = allUsers - totalUsers
@FieldResolver(() => Decimal)
async allUsers(): Promise<number> {
return await DbUser.count({ withDeleted: true })
}
@FieldResolver()
async totalUsers(): Promise<number> {
return await DbUser.count()
}
@FieldResolver()
async deletedUsers(): Promise<number> {
return (await this.allUsers()) - (await this.totalUsers())
}
@FieldResolver()
async totalGradidoCreated(): Promise<Decimal> {
const queryRunner = getConnection().createQueryRunner()
try {
await queryRunner.connect()
const { totalGradidoCreated } = await queryRunner.manager
.createQueryBuilder()
.select('SUM(transaction.amount) AS totalGradidoCreated')
.from(DbTransaction, 'transaction')
.where('transaction.typeId = 1')
.getRawOne()
return totalGradidoCreated
} finally {
await queryRunner.release()
}
}
@FieldResolver()
async totalGradidoDecayed(): Promise<Decimal> {
const queryRunner = getConnection().createQueryRunner()
try {
await queryRunner.connect()
const { totalGradidoDecayed } = await queryRunner.manager
.createQueryBuilder()
.select('SUM(transaction.decay) AS totalGradidoDecayed')
.from(DbTransaction, 'transaction')
.where('transaction.decay IS NOT NULL')
.getRawOne()
return totalGradidoDecayed
} finally {
await queryRunner.release()
}
}
@FieldResolver()
async dynamicStatisticsFields(): Promise<DynamicStatisticsFields> {
let totalGradidoAvailable: Decimal = new Decimal(0) let totalGradidoAvailable: Decimal = new Decimal(0)
let totalGradidoUnbookedDecayed: Decimal = new Decimal(0) let totalGradidoUnbookedDecayed: Decimal = new Decimal(0)
const receivedCallDate = new Date() const receivedCallDate = new Date()
const queryRunner = getConnection().createQueryRunner() const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect() try {
await queryRunner.connect()
const lastUserTransactions = await queryRunner.manager const lastUserTransactions = await queryRunner.manager
.createQueryBuilder(DbUser, 'user') .createQueryBuilder(DbUser, 'user')
.select('transaction.balance', 'balance') .select('transaction.balance', 'balance')
.addSelect('transaction.balance_date', 'balanceDate') .addSelect('transaction.balance_date', 'balanceDate')
.innerJoin(DbTransaction, 'transaction', 'user.id = transaction.user_id') .innerJoin(DbTransaction, 'transaction', 'user.id = transaction.user_id')
.where( .where(
`transaction.balance_date = (SELECT MAX(t.balance_date) FROM transactions AS t WHERE t.user_id = user.id)`, `transaction.balance_date = (SELECT MAX(t.balance_date) FROM transactions AS t WHERE t.user_id = user.id)`,
) )
.orderBy('transaction.balance_date', 'DESC') .orderBy('transaction.balance_date', 'DESC')
.addOrderBy('transaction.id', 'DESC') .addOrderBy('transaction.id', 'DESC')
.getRawMany() .getRawMany()
const activeUsers = lastUserTransactions.length const activeUsers = lastUserTransactions.length
lastUserTransactions.forEach(({ balance, balanceDate }) => { lastUserTransactions.forEach(({ balance, balanceDate }) => {
const decay = calculateDecay(new Decimal(balance), new Date(balanceDate), receivedCallDate) const decay = calculateDecay(new Decimal(balance), new Date(balanceDate), receivedCallDate)
if (decay) { if (decay) {
totalGradidoAvailable = totalGradidoAvailable.plus(decay.balance.toString()) totalGradidoAvailable = totalGradidoAvailable.plus(decay.balance.toString())
totalGradidoUnbookedDecayed = totalGradidoUnbookedDecayed.plus(decay.decay.toString()) totalGradidoUnbookedDecayed = totalGradidoUnbookedDecayed.plus(decay.decay.toString())
}
})
return {
activeUsers,
totalGradidoAvailable,
totalGradidoUnbookedDecayed,
} }
}) } finally {
await queryRunner.release()
const { totalGradidoCreated } = await queryRunner.manager
.createQueryBuilder()
.select('SUM(transaction.amount) AS totalGradidoCreated')
.from(DbTransaction, 'transaction')
.where('transaction.typeId = 1')
.getRawOne()
const { totalGradidoDecayed } = await queryRunner.manager
.createQueryBuilder()
.select('SUM(transaction.decay) AS totalGradidoDecayed')
.from(DbTransaction, 'transaction')
.where('transaction.decay IS NOT NULL')
.getRawOne()
await queryRunner.release()
return {
totalUsers,
activeUsers,
deletedUsers,
totalGradidoCreated,
totalGradidoDecayed,
totalGradidoAvailable,
totalGradidoUnbookedDecayed,
} }
} }
} }

View File

@ -23,6 +23,7 @@ describe('ContributionForm', () => {
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$d: jest.fn((d) => d), $d: jest.fn((d) => d),
$n: jest.fn((n) => n),
$store: { $store: {
state: { state: {
creation: ['1000', '1000', '1000'], creation: ['1000', '1000', '1000'],

View File

@ -9,10 +9,10 @@ describe('InputAmount', () => {
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$n: jest.fn((n) => n),
$i18n: { $i18n: {
locale: jest.fn(() => 'en'), locale: jest.fn(() => 'en'),
}, },
$n: jest.fn((n) => String(n)),
$route: { $route: {
params: {}, params: {},
}, },
@ -46,13 +46,14 @@ describe('InputAmount', () => {
describe('amount normalization', () => { describe('amount normalization', () => {
describe('if invalid', () => { describe('if invalid', () => {
beforeEach(() => { beforeEach(async () => {
await wrapper.setProps({ value: '12m34' })
valid = false valid = false
}) })
it('is not normalized', () => { it('is not normalized', () => {
wrapper.vm.normalizeAmount(valid) wrapper.vm.normalizeAmount(false)
expect(wrapper.vm.amountValue).toBe(0.0) expect(wrapper.vm.currentValue).toBe('12m34')
}) })
}) })
@ -97,13 +98,14 @@ describe('InputAmount', () => {
describe('amount normalization', () => { describe('amount normalization', () => {
describe('if invalid', () => { describe('if invalid', () => {
beforeEach(() => { beforeEach(async () => {
await wrapper.setProps({ value: '12m34' })
valid = false valid = false
}) })
it('is not normalized', () => { it('is not normalized', () => {
wrapper.vm.normalizeAmount(valid) wrapper.vm.normalizeAmount(valid)
expect(wrapper.vm.amountValue).toBe(0.0) expect(wrapper.vm.currentValue).toBe('12m34')
}) })
}) })

View File

@ -20,7 +20,7 @@
trim trim
v-focus="amountFocused" v-focus="amountFocused"
@focus="amountFocused = true" @focus="amountFocused = true"
@blur="normalizeAmount(true)" @blur="normalizeAmount(valid)"
:disabled="disabled" :disabled="disabled"
autocomplete="off" autocomplete="off"
></b-form-input> ></b-form-input>
@ -90,5 +90,8 @@ export default {
this.currentValue = this.$n(this.amountValue, 'ungroupedDecimal') this.currentValue = this.$n(this.amountValue, 'ungroupedDecimal')
}, },
}, },
mounted() {
if (this.value !== '') this.normalizeAmount(true)
},
} }
</script> </script>

View File

@ -13,7 +13,6 @@ export const verifyLogin = gql`
hasElopage hasElopage
publisherId publisherId
isAdmin isAdmin
creation
hideAmountGDD hideAmountGDD
hideAmountGDT hideAmountGDT
} }