Merge pull request #2791 from gradido/refactor-find-contribtiions

refactor(admin): admin list contributions for creation transaction list query
This commit is contained in:
Moriz Wahl 2023-03-23 10:17:25 +01:00 committed by GitHub
commit 7fd6d947cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 190 additions and 204 deletions

View File

@ -1,43 +1,75 @@
import { mount } from '@vue/test-utils'
import CreationTransactionList from './CreationTransactionList'
import { toastErrorSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { adminListContributions } from '../graphql/adminListContributions'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue
localVue.use(VueApollo)
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
creationTransactionList: {
const defaultData = () => {
return {
adminListContributions: {
contributionCount: 2,
contributionList: [
{
id: 1,
amount: 5.8,
createdAt: '2022-09-21T11:09:51.000Z',
confirmedAt: null,
contributionDate: '2022-08-01T00:00:00.000Z',
memo: 'für deine Hilfe, Fräulein Rottenmeier',
firstName: 'Bibi',
lastName: 'Bloxberg',
userId: 99,
email: 'bibi@bloxberg.de',
amount: 500,
memo: 'Danke für alles',
date: new Date(),
moderator: 1,
state: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
deniedAt: null,
confirmedBy: null,
confirmedAt: null,
contributionDate: new Date(),
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
},
{
id: 2,
amount: '47',
createdAt: '2022-09-21T11:09:28.000Z',
confirmedAt: '2022-09-21T11:09:28.000Z',
contributionDate: '2022-08-01T00:00:00.000Z',
memo: 'für deine Hilfe, Frau Holle',
state: 'CONFIRMED',
firstName: 'Räuber',
lastName: 'Hotzenplotz',
userId: 100,
email: 'raeuber@hotzenplotz.de',
amount: 1000000,
memo: 'Gut Ergattert',
date: new Date(),
moderator: 1,
state: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
deniedAt: null,
confirmedBy: null,
confirmedAt: new Date(),
contributionDate: new Date(),
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
},
],
},
},
})
}
}
const mocks = {
$d: jest.fn((t) => t),
$t: jest.fn((t) => t),
$apollo: {
query: apolloQueryMock,
},
}
const propsData = {
@ -48,47 +80,44 @@ const propsData = {
describe('CreationTransactionList', () => {
let wrapper
const adminListContributionsMock = jest.fn()
mockClient.setRequestHandler(
adminListContributions,
adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
const Wrapper = () => {
return mount(CreationTransactionList, { localVue, mocks, propsData })
return mount(CreationTransactionList, { localVue, mocks, propsData, apolloProvider })
}
describe('mount', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper()
})
describe('server error', () => {
it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
describe('sever success', () => {
it('sends query to Apollo when created', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
pageSize: 10,
order: 'DESC',
userId: 1,
},
}),
)
})
})
it('has two values for the transaction', () => {
expect(wrapper.find('tbody').findAll('tr').length).toBe(2)
})
describe('query transaction with error', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({ message: 'OUCH!' })
wrapper = Wrapper()
})
it('calls the API', () => {
expect(apolloQueryMock).toBeCalled()
})
it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('OUCH!')
})
})
describe('watch currentPage', () => {
beforeEach(async () => {
jest.clearAllMocks()
@ -101,3 +130,4 @@ describe('CreationTransactionList', () => {
})
})
})
})

View File

@ -42,7 +42,7 @@
</div>
</template>
<script>
import { creationTransactionList } from '../graphql/creationTransactionList'
import { adminListContributions } from '../graphql/adminListContributions'
export default {
name: 'CreationTransactionList',
props: {
@ -92,33 +92,26 @@ export default {
],
}
},
methods: {
getTransactions() {
this.$apollo
.query({
query: creationTransactionList,
variables: {
apollo: {
AdminListContributions: {
query() {
return adminListContributions
},
variables() {
return {
currentPage: this.currentPage,
pageSize: this.perPage,
order: 'DESC',
userId: parseInt(this.userId),
}
},
})
.then((result) => {
this.rows = result.data.creationTransactionList.contributionCount
this.items = result.data.creationTransactionList.contributionList
})
.catch((error) => {
this.toastError(error.message)
})
update({ adminListContributions }) {
this.rows = adminListContributions.contributionCount
this.items = adminListContributions.contributionList
},
error({ message }) {
this.toastError(message)
},
created() {
this.getTransactions()
},
watch: {
currentPage() {
this.getTransactions()
},
},
}

View File

@ -1,17 +1,19 @@
import gql from 'graphql-tag'
export const adminListAllContributions = gql`
export const adminListContributions = gql`
query (
$currentPage: Int = 1
$pageSize: Int = 25
$order: Order = DESC
$statusFilter: [ContributionStatus!]
$userId: Int
) {
adminListAllContributions(
adminListContributions(
currentPage: $currentPage
pageSize: $pageSize
order: $order
statusFilter: $statusFilter
userId: $userId
) {
contributionCount
contributionList {

View File

@ -1,23 +0,0 @@
import gql from 'graphql-tag'
export const creationTransactionList = gql`
query ($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC, $userId: Int!) {
creationTransactionList(
currentPage: $currentPage
pageSize: $pageSize
order: $order
userId: $userId
) {
contributionCount
contributionList {
id
amount
createdAt
confirmedAt
contributionDate
memo
state
}
}
}
`

View File

@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
import CreationConfirm from './CreationConfirm'
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
import { denyContribution } from '../graphql/denyContribution'
import { adminListAllContributions } from '../graphql/adminListAllContributions'
import { adminListContributions } from '../graphql/adminListContributions'
import { confirmContribution } from '../graphql/confirmContribution'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo'
@ -38,7 +38,7 @@ const mocks = {
const defaultData = () => {
return {
adminListAllContributions: {
adminListContributions: {
contributionCount: 2,
contributionList: [
{
@ -92,14 +92,14 @@ const defaultData = () => {
describe('CreationConfirm', () => {
let wrapper
const adminListAllContributionsMock = jest.fn()
const adminListContributionsMock = jest.fn()
const adminDeleteContributionMock = jest.fn()
const adminDenyContributionMock = jest.fn()
const confirmContributionMock = jest.fn()
mockClient.setRequestHandler(
adminListAllContributions,
adminListAllContributionsMock
adminListContributions,
adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
@ -337,7 +337,7 @@ describe('CreationConfirm', () => {
})
it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,
@ -352,7 +352,7 @@ describe('CreationConfirm', () => {
})
it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,
@ -368,7 +368,7 @@ describe('CreationConfirm', () => {
})
it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,
@ -384,7 +384,7 @@ describe('CreationConfirm', () => {
})
it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,
@ -400,7 +400,7 @@ describe('CreationConfirm', () => {
})
it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,

View File

@ -85,7 +85,7 @@
<script>
import Overlay from '../components/Overlay'
import OpenCreationsTable from '../components/Tables/OpenCreationsTable'
import { adminListAllContributions } from '../graphql/adminListAllContributions'
import { adminListContributions } from '../graphql/adminListContributions'
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
import { confirmContribution } from '../graphql/confirmContribution'
import { denyContribution } from '../graphql/denyContribution'
@ -397,7 +397,7 @@ export default {
apollo: {
ListAllContributions: {
query() {
return adminListAllContributions
return adminListContributions
},
variables() {
return {
@ -407,11 +407,11 @@ export default {
}
},
fetchPolicy: 'no-cache',
update({ adminListAllContributions }) {
this.rows = adminListAllContributions.contributionCount
this.items = adminListAllContributions.contributionList
update({ adminListContributions }) {
this.rows = adminListContributions.contributionCount
this.items = adminListContributions.contributionList
if (this.statusFilter === FILTER_TAB_MAP[0]) {
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount)
this.$store.commit('setOpenCreations', adminListContributions.contributionCount)
}
},
error({ message }) {

View File

@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils'
import Overview from './Overview'
import { adminListAllContributions } from '../graphql/adminListAllContributions'
import { adminListContributions } from '../graphql/adminListContributions'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { toastErrorSpy } from '../../test/testSetup'
@ -30,7 +30,7 @@ const mocks = {
const defaultData = () => {
return {
adminListAllContributions: {
adminListContributions: {
contributionCount: 2,
contributionList: [
{
@ -84,11 +84,11 @@ const defaultData = () => {
describe('Overview', () => {
let wrapper
const adminListAllContributionsMock = jest.fn()
const adminListContributionsMock = jest.fn()
mockClient.setRequestHandler(
adminListAllContributions,
adminListAllContributionsMock
adminListContributions,
adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
@ -109,8 +109,8 @@ describe('Overview', () => {
})
})
it('calls the adminListAllContributions query', () => {
expect(adminListAllContributionsMock).toBeCalledWith({
it('calls the adminListContributions query', () => {
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,

View File

@ -31,7 +31,7 @@
</div>
</template>
<script>
import { adminListAllContributions } from '../graphql/adminListAllContributions'
import { adminListContributions } from '../graphql/adminListContributions'
export default {
name: 'overview',
@ -43,7 +43,7 @@ export default {
apollo: {
AllContributions: {
query() {
return adminListAllContributions
return adminListContributions
},
variables() {
// may be at some point we need a pagination here
@ -51,8 +51,8 @@ export default {
statusFilter: this.statusFilter,
}
},
update({ adminListAllContributions }) {
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount)
update({ adminListContributions }) {
this.$store.commit('setOpenCreations', adminListContributions.contributionCount)
},
error({ message }) {
this.toastError(message)

View File

@ -6,7 +6,7 @@ module.exports = {
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: {
global: {
lines: 80,
lines: 81,
},
},
setupFiles: ['<rootDir>/test/testSetup.ts'],

View File

@ -43,10 +43,9 @@ export enum RIGHTS {
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS',
ADMIN_LIST_CONTRIBUTIONS = 'ADMIN_LIST_CONTRIBUTIONS',
CONFIRM_CONTRIBUTION = 'CONFIRM_CONTRIBUTION',
SEND_ACTIVATION_EMAIL = 'SEND_ACTIVATION_EMAIL',
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN',
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',

View File

@ -27,7 +27,7 @@ import {
import {
listAllContributions,
listContributions,
adminListAllContributions,
adminListContributions,
} from '@/seeds/graphql/queries'
import {
sendContributionConfirmedEmail,
@ -2669,12 +2669,12 @@ describe('ContributionResolver', () => {
})
})
describe('adminListAllContribution', () => {
describe('adminListContributions', () => {
describe('unauthenticated', () => {
it('returns an error', async () => {
await expect(
query({
query: adminListAllContributions,
query: adminListContributions,
}),
).resolves.toEqual(
expect.objectContaining({
@ -2699,7 +2699,7 @@ describe('ContributionResolver', () => {
it('returns an error', async () => {
await expect(
query({
query: adminListAllContributions,
query: adminListContributions,
}),
).resolves.toEqual(
expect.objectContaining({
@ -2723,9 +2723,9 @@ describe('ContributionResolver', () => {
it('returns 17 creations in total', async () => {
const {
data: { adminListAllContributions: contributionListObject },
}: { data: { adminListAllContributions: ContributionListResult } } = await query({
query: adminListAllContributions,
data: { adminListContributions: contributionListObject },
}: { data: { adminListContributions: ContributionListResult } } = await query({
query: adminListContributions,
})
expect(contributionListObject.contributionList).toHaveLength(17)
expect(contributionListObject).toMatchObject({
@ -2890,9 +2890,9 @@ describe('ContributionResolver', () => {
it('returns two pending creations with page size set to 2', async () => {
const {
data: { adminListAllContributions: contributionListObject },
}: { data: { adminListAllContributions: ContributionListResult } } = await query({
query: adminListAllContributions,
data: { adminListContributions: contributionListObject },
}: { data: { adminListContributions: ContributionListResult } } = await query({
query: adminListContributions,
variables: {
currentPage: 1,
pageSize: 2,

View File

@ -136,15 +136,15 @@ export class ContributionResolver {
): Promise<ContributionListResult> {
const user = getUser(context)
const [dbContributions, count] = await findContributions(
const [dbContributions, count] = await findContributions({
order,
currentPage,
pageSize,
true,
['messages'],
user.id,
withDeleted: true,
relations: ['messages'],
userId: user.id,
statusFilter,
)
})
return new ContributionListResult(
count,
dbContributions.map((contribution) => new Contribution(contribution, user)),
@ -159,15 +159,13 @@ export class ContributionResolver {
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
statusFilter?: ContributionStatus[] | null,
): Promise<ContributionListResult> {
const [dbContributions, count] = await findContributions(
const [dbContributions, count] = await findContributions({
order,
currentPage,
pageSize,
false,
['user'],
undefined,
relations: ['user'],
statusFilter,
)
})
return new ContributionListResult(
count,
@ -384,23 +382,25 @@ export class ContributionResolver {
return result
}
@Authorized([RIGHTS.LIST_UNCONFIRMED_CONTRIBUTIONS])
@Query(() => ContributionListResult) // [UnconfirmedContribution]
async adminListAllContributions(
@Authorized([RIGHTS.ADMIN_LIST_CONTRIBUTIONS])
@Query(() => ContributionListResult)
async adminListContributions(
@Args()
{ currentPage = 1, pageSize = 3, order = Order.DESC }: Paginated,
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
statusFilter?: ContributionStatus[] | null,
@Arg('userId', () => Int, { nullable: true })
userId?: number | null,
): Promise<ContributionListResult> {
const [dbContributions, count] = await findContributions(
const [dbContributions, count] = await findContributions({
order,
currentPage,
pageSize,
true,
['user', 'messages'],
undefined,
withDeleted: true,
userId,
relations: ['user', 'messages'],
statusFilter,
)
})
return new ContributionListResult(
count,
@ -562,33 +562,6 @@ export class ContributionResolver {
return true
}
@Authorized([RIGHTS.CREATION_TRANSACTION_LIST])
@Query(() => ContributionListResult)
async creationTransactionList(
@Args()
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Arg('userId', () => Int) userId: number,
): Promise<ContributionListResult> {
const offset = (currentPage - 1) * pageSize
const [contributionResult, count] = await getConnection()
.createQueryBuilder()
.select('c')
.from(DbContribution, 'c')
.leftJoinAndSelect('c.user', 'u')
.where(`user_id = ${userId}`)
.withDeleted()
.limit(pageSize)
.offset(offset)
.orderBy('c.created_at', order)
.getManyAndCount()
return new ContributionListResult(
count,
contributionResult.map((contribution) => new Contribution(contribution, contribution.user)),
)
// return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
}
@Authorized([RIGHTS.OPEN_CREATIONS])
@Query(() => [OpenCreation])
async openCreations(@Ctx() context: Context): Promise<OpenCreation[]> {

View File

@ -3,21 +3,30 @@ import { Order } from '@enum/Order'
import { Contribution as DbContribution } from '@entity/Contribution'
import { In } from '@dbTools/typeorm'
interface FindContributionsOptions {
order: Order
currentPage: number
pageSize: number
withDeleted?: boolean
relations?: string[]
userId?: number | null
statusFilter?: ContributionStatus[] | null
}
export const findContributions = async (
order: Order,
currentPage: number,
pageSize: number,
withDeleted: boolean,
relations: string[],
userId?: number,
statusFilter?: ContributionStatus[] | null,
): Promise<[DbContribution[], number]> =>
DbContribution.findAndCount({
options: FindContributionsOptions,
): Promise<[DbContribution[], number]> => {
const { order, currentPage, pageSize, withDeleted, relations, userId, statusFilter } = {
withDeleted: false,
relations: [],
...options,
}
return DbContribution.findAndCount({
where: {
...(statusFilter && statusFilter.length && { contributionStatus: In(statusFilter) }),
...(userId && { userId }),
},
withDeleted: withDeleted,
withDeleted,
order: {
createdAt: order,
id: order,
@ -26,3 +35,4 @@ export const findContributions = async (
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
}

View File

@ -220,18 +220,20 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF
`
// from admin interface
export const adminListAllContributions = gql`
export const adminListContributions = gql`
query (
$currentPage: Int = 1
$pageSize: Int = 25
$order: Order = DESC
$statusFilter: [ContributionStatus!]
$userId: Int
) {
adminListAllContributions(
adminListContributions(
currentPage: $currentPage
pageSize: $pageSize
order: $order
statusFilter: $statusFilter
userId: $userId
) {
contributionCount
contributionList {