Merge branch 'master' into log-error

This commit is contained in:
Ulf Gebhardt 2023-02-02 12:16:12 +01:00 committed by GitHub
commit 9b057c5c10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 514 additions and 76 deletions

View File

@ -45,6 +45,7 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
import { User } from '@entity/User' import { User } from '@entity/User'
import { EventProtocolType } from '@/event/EventProtocolType' import { EventProtocolType } from '@/event/EventProtocolType'
import { logger, i18n as localization } from '@test/testSetup' import { logger, i18n as localization } from '@test/testSetup'
import { UserInputError } from 'apollo-server-express'
// mock account activation email to avoid console spam // mock account activation email to avoid console spam
// mock account activation email to avoid console spam // mock account activation email to avoid console spam
@ -681,7 +682,7 @@ describe('ContributionResolver', () => {
currentPage: 1, currentPage: 1,
pageSize: 25, pageSize: 25,
order: 'DESC', order: 'DESC',
filterConfirmed: false, statusFilter: null,
}, },
}), }),
).resolves.toEqual( ).resolves.toEqual(
@ -718,7 +719,76 @@ describe('ContributionResolver', () => {
resetToken() resetToken()
}) })
it('returns allCreation', async () => { it('throws an error with "NOT_VALID" in statusFilter', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: ['NOT_VALID'],
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new UserInputError(
'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[0]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.',
),
],
}),
)
})
it('throws an error with a null in statusFilter', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: [null],
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new UserInputError(
'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.',
),
],
}),
)
})
it('throws an error with null and "NOT_VALID" in statusFilter', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: [null, 'NOT_VALID'],
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new UserInputError(
'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.',
),
new UserInputError(
'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[1]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.',
),
],
}),
)
})
it('returns all contributions without statusFilter', async () => {
await expect( await expect(
query({ query({
query: listAllContributions, query: listAllContributions,
@ -726,7 +796,6 @@ describe('ContributionResolver', () => {
currentPage: 1, currentPage: 1,
pageSize: 25, pageSize: 25,
order: 'DESC', order: 'DESC',
filterConfirmed: false,
}, },
}), }),
).resolves.toEqual( ).resolves.toEqual(
@ -737,11 +806,301 @@ describe('ContributionResolver', () => {
contributionList: expect.arrayContaining([ contributionList: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: expect.any(Number), id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!', memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000', amount: '1000',
}), }),
expect.objectContaining({ expect.objectContaining({
id: expect.any(Number), id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all contributions for statusFilter = null', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: null,
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 2,
contributionList: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all contributions for statusFilter = []', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: [],
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 2,
contributionList: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all CONFIRMED contributions', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: ['CONFIRMED'],
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 1,
contributionList: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.not.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all PENDING contributions', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: ['PENDING'],
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 1,
contributionList: expect.arrayContaining([
expect.not.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all IN_PROGRESS Creation', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: ['IN_PROGRESS'],
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 0,
contributionList: expect.not.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all DENIED Creation', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: ['DENIED'],
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 0,
contributionList: expect.not.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all DELETED Creation', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: ['DELETED'],
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 0,
contributionList: expect.not.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
})
it('returns all CONFIRMED and PENDING Creation', async () => {
await expect(
query({
query: listAllContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
statusFilter: ['CONFIRMED', 'PENDING'],
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listAllContributions: {
contributionCount: 2,
contributionList: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
state: 'CONFIRMED',
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
state: 'PENDING',
memo: 'Test env contribution', memo: 'Test env contribution',
amount: '100', amount: '100',
}), }),

View File

@ -179,12 +179,23 @@ export class ContributionResolver {
async listAllContributions( async listAllContributions(
@Args() @Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
statusFilter?: ContributionStatus[],
): Promise<ContributionListResult> { ): Promise<ContributionListResult> {
const where: {
contributionStatus?: FindOperator<string> | null
} = {}
if (statusFilter && statusFilter.length) {
where.contributionStatus = In(statusFilter)
}
const [dbContributions, count] = await getConnection() const [dbContributions, count] = await getConnection()
.createQueryBuilder() .createQueryBuilder()
.select('c') .select('c')
.from(DbContribution, 'c') .from(DbContribution, 'c')
.innerJoinAndSelect('c.user', 'u') .innerJoinAndSelect('c.user', 'u')
.where(where)
.orderBy('c.createdAt', order) .orderBy('c.createdAt', order)
.limit(pageSize) .limit(pageSize)
.offset((currentPage - 1) * pageSize) .offset((currentPage - 1) * pageSize)

View File

@ -172,18 +172,23 @@ export const listContributions = gql`
` `
export const listAllContributions = ` export const listAllContributions = `
query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC) { query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusFilter: [ContributionStatus!]) {
listAllContributions(currentPage: $currentPage, pageSize: $pageSize, order: $order) { listAllContributions(currentPage: $currentPage, pageSize: $pageSize, order: $order, statusFilter: $statusFilter) {
contributionCount contributionCount
contributionList { contributionList {
id id
firstName firstName
lastName lastName
amount amount
memo memo
createdAt createdAt
confirmedAt confirmedAt
confirmedBy confirmedBy
contributionDate
state
messagesCount
deniedAt
deniedBy
} }
} }
} }

View File

@ -3,7 +3,7 @@
<b-form <b-form
ref="form" ref="form"
@submit.prevent="submit" @submit.prevent="submit"
class="p-3 bg-white appBoxShadow gradido-border-radius" class="form-style p-3 bg-white appBoxShadow gradido-border-radius"
> >
<label>{{ $t('contribution.selectDate') }}</label> <label>{{ $t('contribution.selectDate') }}</label>
<b-form-datepicker <b-form-datepicker
@ -23,50 +23,70 @@
<template #nav-next-year><span></span></template> <template #nav-next-year><span></span></template>
</b-form-datepicker> </b-form-datepicker>
<input-textarea <div
id="contribution-memo" v-if="(isThisMonth && maxGddThisMonth <= 0) || (!isThisMonth && maxGddLastMonth <= 0)"
v-model="form.memo" class="p-3"
:name="$t('form.message')" >
:label="$t('contribution.activity')" {{ noOpenCreation }}
:placeholder="$t('contribution.yourActivity')" </div>
:rules="{ required: true, min: 5, max: 255 }" <div v-else>
/> <input-textarea
<input-hour id="contribution-memo"
v-model="form.hours" v-model="form.memo"
:name="$t('form.hours')" :name="$t('form.message')"
:label="$t('form.hours')" :label="$t('contribution.activity')"
placeholder="0.25" :placeholder="$t('contribution.yourActivity')"
:rules="{ :rules="{ required: true, min: 5, max: 255 }"
required: true, />
min: 0.25, <input-hour
max: validMaxTime, v-model="form.hours"
gddCreationTime: [0.25, validMaxTime], :name="$t('form.hours')"
}" :label="$t('form.hours')"
:validMaxTime="validMaxTime" placeholder="0.25"
@updateAmount="updateAmount" :rules="{
></input-hour> required: true,
<input-amount min: 0.25,
id="contribution-amount" max: validMaxTime,
v-model="form.amount" gddCreationTime: [0.25, validMaxTime],
:name="$t('form.amount')" }"
:label="$t('form.amount')" :validMaxTime="validMaxTime"
placeholder="20" @updateAmount="updateAmount"
:rules="{ required: true, gddSendAmount: [20, validMaxGDD] }" ></input-hour>
typ="ContributionForm" <input-amount
></input-amount> id="contribution-amount"
v-model="form.amount"
:name="$t('form.amount')"
:label="$t('form.amount')"
placeholder="20"
:rules="{ required: true, gddSendAmount: [20, validMaxGDD] }"
typ="ContributionForm"
></input-amount>
<b-row class="mt-5"> <b-row class="mt-5">
<b-col> <b-col cols="12" lg="6">
<b-button type="reset" variant="secondary" @click="reset" data-test="button-cancel"> <b-button
{{ $t('form.cancel') }} block
</b-button> type="reset"
</b-col> variant="secondary"
<b-col class="text-right"> @click="reset"
<b-button type="submit" variant="gradido" :disabled="disabled" data-test="button-submit"> data-test="button-cancel"
{{ form.id ? $t('form.change') : $t('contribution.submit') }} >
</b-button> {{ $t('form.cancel') }}
</b-col> </b-button>
</b-row> </b-col>
<b-col cols="12" lg="6" class="text-right mt-4 mt-lg-0">
<b-button
block
type="submit"
variant="gradido"
:disabled="disabled"
data-test="button-submit"
>
{{ form.id ? $t('form.change') : $t('contribution.submit') }}
</b-button>
</b-col>
</b-row>
</div>
</b-form> </b-form>
</div> </div>
</template> </template>
@ -133,6 +153,18 @@ export default {
validMaxTime() { validMaxTime() {
return Number(this.validMaxGDD / 20) return Number(this.validMaxGDD / 20)
}, },
noOpenCreation() {
if (this.maxGddThisMonth <= 0 && this.maxGddLastMonth <= 0) {
return this.$t('contribution.noOpenCreation.allMonth')
}
if (this.isThisMonth && this.maxGddThisMonth <= 0) {
return this.$t('contribution.noOpenCreation.thisMonth')
}
if (!this.isThisMonth && this.maxGddLastMonth <= 0) {
return this.$t('contribution.noOpenCreation.lastMonth')
}
return ''
},
}, },
watch: { watch: {
value() { value() {
@ -142,6 +174,9 @@ export default {
} }
</script> </script>
<style> <style>
.form-style {
min-height: 410px;
}
span.errors { span.errors {
color: red; color: red;
} }

View File

@ -57,7 +57,16 @@
"yourContribution": "Dein Beitrag zum Gemeinwohl" "yourContribution": "Dein Beitrag zum Gemeinwohl"
}, },
"lastContribution": "Letzte Beiträge", "lastContribution": "Letzte Beiträge",
"noContributions": {
"allContributions": "Es wurden noch keine Beiträge eingereicht.",
"myContributions": "Du hast noch keine Beiträge eingereicht."
},
"noDateSelected": "Wähle irgendein Datum im Monat", "noDateSelected": "Wähle irgendein Datum im Monat",
"noOpenCreation": {
"allMonth": "Für alle beiden Monate ist dein Schöpfungslimit erreicht. Den Nächsten Monat kannst du wieder 1000 GDD Schöpfen.",
"lastMonth": "Für den ausgewählten Monat ist das Schöpfungslimit erreicht.",
"thisMonth": "Für den aktuellen Monat ist das Schöpfungslimit erreicht."
},
"selectDate": "Wann war dein Beitrag?", "selectDate": "Wann war dein Beitrag?",
"submit": "Einreichen", "submit": "Einreichen",
"submitted": "Der Beitrag wurde eingereicht.", "submitted": "Der Beitrag wurde eingereicht.",

View File

@ -57,7 +57,16 @@
"yourContribution": "Your Contributions to the Common Good" "yourContribution": "Your Contributions to the Common Good"
}, },
"lastContribution": "Last Contributions", "lastContribution": "Last Contributions",
"noContributions": {
"allContributions": "No contributions have been submitted yet.",
"myContributions": "You have not submitted any entries yet."
},
"noDateSelected": "Choose any date in the month", "noDateSelected": "Choose any date in the month",
"noOpenCreation": {
"allMonth": "For all two months your creation limit is reached. The next month you can create 1000 GDD again.",
"lastMonth": "The creation limit is reached for the selected month.",
"thisMonth": "The creation limit has been reached for the current month."
},
"selectDate": "When was your contribution?", "selectDate": "When was your contribution?",
"submit": "Submit", "submit": "Submit",
"submitted": "The contribution was submitted.", "submitted": "The contribution was submitted.",

View File

@ -20,28 +20,38 @@
/> />
</b-tab> </b-tab>
<b-tab no-body> <b-tab no-body>
<contribution-list <div v-if="items.length === 0">
@closeAllOpenCollapse="closeAllOpenCollapse" {{ $t('contribution.noContributions.myContributions') }}
:items="items" </div>
@update-list-contributions="updateListContributions" <div v-else>
@update-contribution-form="updateContributionForm" <contribution-list
@delete-contribution="deleteContribution" @closeAllOpenCollapse="closeAllOpenCollapse"
@update-state="updateState" :items="items"
:contributionCount="contributionCount" @update-list-contributions="updateListContributions"
:showPagination="true" @update-contribution-form="updateContributionForm"
:pageSize="pageSize" @delete-contribution="deleteContribution"
/> @update-state="updateState"
:contributionCount="contributionCount"
:showPagination="true"
:pageSize="pageSize"
/>
</div>
</b-tab> </b-tab>
<b-tab no-body> <b-tab no-body>
<contribution-list <div v-if="itemsAll.length === 0">
:items="itemsAll" {{ $t('contribution.noContributions.allContributions') }}
@update-list-contributions="updateListAllContributions" </div>
@update-contribution-form="updateContributionForm" <div v-else>
:contributionCount="contributionCountAll" <contribution-list
:showPagination="true" :items="itemsAll"
:pageSize="pageSizeAll" @update-list-contributions="updateListAllContributions"
:allContribution="true" @update-contribution-form="updateContributionForm"
/> :contributionCount="contributionCountAll"
:showPagination="true"
:pageSize="pageSizeAll"
:allContribution="true"
/>
</div>
</b-tab> </b-tab>
</b-tabs> </b-tabs>
</div> </div>