mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 1029-Change-text-for-expired-session
This commit is contained in:
commit
9714b0de5d
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -399,7 +399,7 @@ jobs:
|
||||
report_name: Coverage Frontend
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 86
|
||||
min_coverage: 87
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
@ -441,7 +441,7 @@ jobs:
|
||||
report_name: Coverage Admin Interface
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 52
|
||||
min_coverage: 55
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
16
README.md
16
README.md
@ -69,6 +69,22 @@ We are currently restructuring the service to reduce dependencies and unify busi
|
||||
|
||||
Once you have `docker-compose` up and running, you can open [http://localhost/vue](http://localhost/vue) and create yourself a new wallet account.
|
||||
|
||||
## How to release
|
||||
|
||||
A release is tagged on Github by its version number and published as github release. This is done automatically when a new version is defined in the [package.json](./package.json) and merged into master - furthermore we set all our sub-package-versions to the same version as the main package.json version to make version management as simple as possible.
|
||||
Each release is accompanied with release notes automatically generated from the git log which is available as [CHANGELOG.md](./CHANGELOG.md).
|
||||
|
||||
To generate the Changelog and set a new Version you should use the following commands in the main folder
|
||||
```bash
|
||||
git fetch --all
|
||||
yarn release
|
||||
```
|
||||
|
||||
The first command `git fetch --all` will make sure you have all tags previously defined which is required to generate a correct changelog. The second command `yarn release` will execute the changelog tool and set version numbers in the main package and sub-packages. It is required to do `yarn install` before you can use this command.
|
||||
After generating a new version you should commit the changes. This will be the CHANGELOG.md and several package.json files. This commit will be omitted in the changelog.
|
||||
|
||||
Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `yarn release` command.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Issue | Solution | Description |
|
||||
|
||||
BIN
admin/public/img/brand/gradido_logo_w.png
Normal file
BIN
admin/public/img/brand/gradido_logo_w.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
@ -1,21 +1,6 @@
|
||||
<template>
|
||||
<div class="component-creation-formular">
|
||||
<div>
|
||||
<h3>
|
||||
{{
|
||||
this.type === 'singleCreation'
|
||||
? 'Einzelschöpfung für ' + item.firstName + ' ' + item.lastName + ''
|
||||
: 'Mehrfachschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
|
||||
}}
|
||||
</h3>
|
||||
<div v-show="this.type === 'massCreation' && Object.keys(this.itemsMassCreation).length <= 0">
|
||||
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="this.type === 'singleCreation' || Object.keys(this.itemsMassCreation).length > 0"
|
||||
class="shadow p-3 mb-5 bg-white rounded"
|
||||
>
|
||||
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||
<b-form ref="creationForm">
|
||||
<b-row class="m-4">
|
||||
<label>Monat Auswählen</label>
|
||||
@ -27,7 +12,9 @@
|
||||
size="lg"
|
||||
@change="updateRadioSelected(beforeLastMonth, 0, creation[0])"
|
||||
>
|
||||
{{ beforeLastMonth.short }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }}
|
||||
<label for="beforeLastMonth">
|
||||
{{ beforeLastMonth.short }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }}
|
||||
</label>
|
||||
</b-form-radio>
|
||||
</b-col>
|
||||
<b-col>
|
||||
@ -38,7 +25,9 @@
|
||||
size="lg"
|
||||
@change="updateRadioSelected(lastMonth, 1, creation[1])"
|
||||
>
|
||||
{{ lastMonth.short }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }}
|
||||
<label for="lastMonth">
|
||||
{{ lastMonth.short }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }}
|
||||
</label>
|
||||
</b-form-radio>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
@ -49,7 +38,9 @@
|
||||
size="lg"
|
||||
@change="updateRadioSelected(currentMonth, 2, creation[2])"
|
||||
>
|
||||
{{ currentMonth.short }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }}
|
||||
<label for="currentMonth">
|
||||
{{ currentMonth.short }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }}
|
||||
</label>
|
||||
</b-form-radio>
|
||||
</b-col>
|
||||
</b-row>
|
||||
@ -73,7 +64,6 @@
|
||||
:min="rangeMin"
|
||||
:max="rangeMax"
|
||||
step="10"
|
||||
@load="checkFormForUpdate('range')"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
</div>
|
||||
@ -86,7 +76,6 @@
|
||||
v-model="text"
|
||||
:state="text.length >= 10"
|
||||
placeholder="Mindestens 10 Zeichen eingeben"
|
||||
@load="checkFormForUpdate('text')"
|
||||
rows="3"
|
||||
></b-form-textarea>
|
||||
</div>
|
||||
@ -143,25 +132,34 @@ export default {
|
||||
item: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
creationUserData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
creation: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
itemsMassCreation: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
radioSelected: '',
|
||||
text: '',
|
||||
value: 0,
|
||||
text: !this.creationUserData.memo ? '' : this.creationUserData.memo,
|
||||
value: !this.creationUserData.amount ? 0 : this.creationUserData.amount / 10000,
|
||||
rangeMin: 0,
|
||||
rangeMax: 1000,
|
||||
currentMonth: {
|
||||
@ -181,6 +179,7 @@ export default {
|
||||
createdIndex: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// Auswählen eines Zeitraumes
|
||||
updateRadioSelected(name, index, openCreation) {
|
||||
@ -189,27 +188,12 @@ export default {
|
||||
if (this.type === 'massCreation') {
|
||||
// An Creation.vue emitten und radioSelectedMass aktualisieren
|
||||
this.$emit('update-radio-selected', [name, index])
|
||||
}
|
||||
// Wenn Einzelschöpfung
|
||||
if (this.type === 'singleCreation') {
|
||||
} else if (this.type === 'singleCreation') {
|
||||
this.rangeMin = 0
|
||||
// Der maximale offene Betrag an GDD die für ein User noch geschöpft werden kann
|
||||
this.rangeMax = openCreation
|
||||
}
|
||||
},
|
||||
checkFormForUpdate(input) {
|
||||
switch (input) {
|
||||
case 'text':
|
||||
this.text = this.creationUserData.text
|
||||
break
|
||||
case 'range':
|
||||
this.value = this.creationUserData.creationGdd
|
||||
break
|
||||
default:
|
||||
// TODO: Toast
|
||||
alert("I don't know such values")
|
||||
}
|
||||
},
|
||||
submitCreation() {
|
||||
// Formular Prüfen ob ein Zeitraum ausgewählt wurde. Ansonsten abbrechen und Hinweis anzeigen
|
||||
if (this.radioSelected === '') {
|
||||
@ -227,6 +211,7 @@ export default {
|
||||
if (this.text.length < 10) {
|
||||
return alert('Bitte gib einen Text ein der länger als 10 Zeichen ist!')
|
||||
}
|
||||
|
||||
if (this.type === 'massCreation') {
|
||||
// Die anzahl der Mitglieder aus der Mehrfachschöpfung
|
||||
const i = Object.keys(this.itemsMassCreation).length
|
||||
@ -249,9 +234,7 @@ export default {
|
||||
|
||||
// lösche alle Mitglieder aus der MehrfachSchöpfungsListe nach dem alle Mehrfachschpfungen zum bestätigen gesendet wurden.
|
||||
this.$emit('remove-all-bookmark')
|
||||
}
|
||||
|
||||
if (this.type === 'singleCreation') {
|
||||
} else if (this.type === 'singleCreation') {
|
||||
this.submitObj = {
|
||||
email: this.item.email,
|
||||
creationDate: this.radioSelected.long,
|
||||
@ -260,43 +243,32 @@ export default {
|
||||
moderator: Number(this.$store.state.moderator.id),
|
||||
}
|
||||
|
||||
if (this.pagetype === 'PageCreationConfirm') {
|
||||
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
|
||||
alert('UPDATE EINZEL SCHÖPFUNG ABSENDEN FÜR >> ')
|
||||
// umschreiben, update eine bestehende Schöpfung eine
|
||||
this.$emit('update-creation-data', {
|
||||
datum: this.radioSelected.long,
|
||||
creationGdd: this.value,
|
||||
text: this.text,
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: createPendingCreation,
|
||||
variables: this.submitObj,
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('update-user-data', this.item, result.data.createPendingCreation)
|
||||
this.$toasted.success(
|
||||
`Offene Schöpfung (${this.value} GDD) für ${this.item.email} wurde gespeichert und liegen zur Bestätigung bereit`,
|
||||
)
|
||||
this.$store.commit('openCreationsPlus', 1)
|
||||
this.submitObj = null
|
||||
this.createdIndex = null
|
||||
// das creation Formular reseten
|
||||
this.$refs.creationForm.reset()
|
||||
// Den geschöpften Wert auf o setzen
|
||||
this.value = 0
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.error(error.message)
|
||||
this.submitObj = null
|
||||
// das creation Formular reseten
|
||||
this.$refs.creationForm.reset()
|
||||
// Den geschöpften Wert auf o setzen
|
||||
this.value = 0
|
||||
})
|
||||
} else {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: createPendingCreation,
|
||||
variables: this.submitObj,
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('update-user-data', this.item, result.data.createPendingCreation)
|
||||
this.$toasted.success(
|
||||
`Offene schöpfung (${this.value} GDD) für ${this.item.email} wurde gespeichert, liegen zur bestätigung bereit`,
|
||||
)
|
||||
this.$store.commit('openCreationsPlus', 1)
|
||||
this.submitObj = null
|
||||
this.createdIndex = null
|
||||
// das creation Formular reseten
|
||||
this.$refs.creationForm.reset()
|
||||
// Den geschöpften Wert auf o setzen
|
||||
this.value = 0
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.error(error.message)
|
||||
this.submitObj = null
|
||||
// das creation Formular reseten
|
||||
this.$refs.creationForm.reset()
|
||||
// Den geschöpften Wert auf o setzen
|
||||
this.value = 0
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
searchModeratorData() {
|
||||
|
||||
118
admin/src/components/EditCreationFormular.spec.js
Normal file
118
admin/src/components/EditCreationFormular.spec.js
Normal file
@ -0,0 +1,118 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import EditCreationFormular from './EditCreationFormular.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloMutateMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
updatePendingCreation: {
|
||||
creation: [0, 0, 0],
|
||||
date: new Date(),
|
||||
memo: 'qwertzuiopasdfghjkl',
|
||||
moderator: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const stateCommitMock = jest.fn()
|
||||
|
||||
const mocks = {
|
||||
$moment: jest.fn(() => {
|
||||
return {
|
||||
format: jest.fn((m) => m),
|
||||
subtract: jest.fn(() => {
|
||||
return {
|
||||
format: jest.fn((m) => m),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
$apollo: {
|
||||
mutate: apolloMutateMock,
|
||||
},
|
||||
$store: {
|
||||
commit: stateCommitMock,
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
type: '',
|
||||
item: {},
|
||||
row: [],
|
||||
creation: [],
|
||||
itemsMassCreation: {},
|
||||
}
|
||||
|
||||
describe('EditCreationFormular', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(EditCreationFormular, { localVue, mocks, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV element with the class.component-edit-creation-formular', () => {
|
||||
expect(wrapper.find('.component-edit-creation-formular').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('radio buttons to selcet month', () => {
|
||||
it('has three radio buttons', () => {
|
||||
expect(wrapper.findAll('input[type="radio"]').length).toBe(3)
|
||||
})
|
||||
|
||||
describe('with single creation', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||
await wrapper.setData({ rangeMin: 180 })
|
||||
})
|
||||
|
||||
describe('first radio button', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('input[type="radio"]').at(0).setChecked()
|
||||
})
|
||||
|
||||
it('sets rangeMin to 0', () => {
|
||||
expect(wrapper.vm.rangeMin).toBe(0)
|
||||
})
|
||||
|
||||
it('sets rangeMax to 200', () => {
|
||||
expect(wrapper.vm.rangeMax).toBe(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('second radio button', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
|
||||
})
|
||||
|
||||
it('sets rangeMin to 0', () => {
|
||||
expect(wrapper.vm.rangeMin).toBe(0)
|
||||
})
|
||||
|
||||
it('sets rangeMax to 400', () => {
|
||||
expect(wrapper.vm.rangeMax).toBe(400)
|
||||
})
|
||||
})
|
||||
|
||||
describe('third radio button', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('input[type="radio"]').at(2).setChecked()
|
||||
})
|
||||
|
||||
it('sets rangeMin to 0', () => {
|
||||
expect(wrapper.vm.rangeMin).toBe(0)
|
||||
})
|
||||
|
||||
it('sets rangeMax to 400', () => {
|
||||
expect(wrapper.vm.rangeMax).toBe(600)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
285
admin/src/components/EditCreationFormular.vue
Normal file
285
admin/src/components/EditCreationFormular.vue
Normal file
@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div class="component-edit-creation-formular">
|
||||
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||
<b-form ref="updateCreationForm">
|
||||
<b-row class="m-4">
|
||||
<label>Monat Auswählen</label>
|
||||
<b-col class="text-left">
|
||||
<b-form-radio
|
||||
v-model="radioSelected"
|
||||
:value="beforeLastMonth"
|
||||
:disabled="selectedOpenCreationAmount[0] === 0"
|
||||
size="lg"
|
||||
@change="updateRadioSelected(beforeLastMonth, 0, selectedOpenCreationAmount[0])"
|
||||
>
|
||||
<label for="beforeLastMonth">
|
||||
{{ beforeLastMonth.short }}
|
||||
{{
|
||||
selectedOpenCreationAmount[0] != null
|
||||
? selectedOpenCreationAmount[0] + ' GDD'
|
||||
: ''
|
||||
}}
|
||||
</label>
|
||||
</b-form-radio>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-form-radio
|
||||
v-model="radioSelected"
|
||||
:value="lastMonth"
|
||||
:disabled="selectedOpenCreationAmount[1] === 0"
|
||||
size="lg"
|
||||
@change="updateRadioSelected(lastMonth, 1, selectedOpenCreationAmount[1])"
|
||||
>
|
||||
<label for="lastMonth">
|
||||
{{ lastMonth.short }}
|
||||
{{
|
||||
selectedOpenCreationAmount[1] != null
|
||||
? selectedOpenCreationAmount[1] + ' GDD'
|
||||
: ''
|
||||
}}
|
||||
</label>
|
||||
</b-form-radio>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-form-radio
|
||||
v-model="radioSelected"
|
||||
:value="currentMonth"
|
||||
:disabled="selectedOpenCreationAmount[2] === 0"
|
||||
size="lg"
|
||||
@change="updateRadioSelected(currentMonth, 2, selectedOpenCreationAmount[2])"
|
||||
>
|
||||
<label for="currentMonth">
|
||||
{{ currentMonth.short }}
|
||||
{{
|
||||
selectedOpenCreationAmount[2] != null
|
||||
? selectedOpenCreationAmount[2] + ' GDD'
|
||||
: ''
|
||||
}}
|
||||
</label>
|
||||
</b-form-radio>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="m-4">
|
||||
<label>Betrag Auswählen</label>
|
||||
<div>
|
||||
<b-input-group prepend="GDD" append=".00">
|
||||
<b-form-input
|
||||
type="number"
|
||||
v-model="value"
|
||||
:min="rangeMin"
|
||||
:max="rangeMax"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
|
||||
<b-input-group prepend="0" :append="String(rangeMax)" class="mt-3">
|
||||
<b-form-input
|
||||
type="range"
|
||||
v-model="value"
|
||||
:min="rangeMin"
|
||||
:max="rangeMax"
|
||||
step="10"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
</div>
|
||||
</b-row>
|
||||
<b-row class="m-4">
|
||||
<label>Text eintragen</label>
|
||||
<div>
|
||||
<b-form-textarea
|
||||
id="textarea-state"
|
||||
v-model="text"
|
||||
:state="text.length >= 10"
|
||||
placeholder="Mindestens 10 Zeichen eingeben"
|
||||
rows="3"
|
||||
></b-form-textarea>
|
||||
</div>
|
||||
</b-row>
|
||||
<b-row class="m-4">
|
||||
<b-col class="text-center">
|
||||
<b-button type="reset" variant="danger" @click="$refs.updateCreationForm.reset()">
|
||||
zurücksetzen
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-center">
|
||||
<div class="text-right">
|
||||
<b-button
|
||||
type="button"
|
||||
variant="success"
|
||||
@click="submitCreation"
|
||||
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
||||
>
|
||||
Update Schöpfung ({{ type }},{{ pagetype }})
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { updatePendingCreation } from '../graphql/updatePendingCreation'
|
||||
export default {
|
||||
name: 'EditCreationFormular',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
pagetype: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
row: {
|
||||
type: Array,
|
||||
required: Object,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
creationUserData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
creation: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.pagetype === 'PageCreationConfirm' && this.creationUserData.date) {
|
||||
switch (this.$moment(this.creationUserData.date).format('MMMM')) {
|
||||
case this.currentMonth.short:
|
||||
this.createdIndex = 2
|
||||
this.radioSelected = this.currentMonth
|
||||
break
|
||||
case this.lastMonth.short:
|
||||
this.createdIndex = 1
|
||||
this.radioSelected = this.lastMonth
|
||||
break
|
||||
case this.beforeLastMonth.short:
|
||||
this.createdIndex = 0
|
||||
this.radioSelected = this.beforeLastMonth
|
||||
break
|
||||
default:
|
||||
throw new Error('Something went wrong')
|
||||
}
|
||||
this.selectedOpenCreationAmount[this.createdIndex] =
|
||||
this.creation[this.createdIndex] + this.creationUserData.amount / 10000
|
||||
this.rangeMax = this.selectedOpenCreationAmount[this.createdIndex]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
radioSelected: '',
|
||||
text: !this.creationUserData.memo ? '' : this.creationUserData.memo,
|
||||
value: !this.creationUserData.amount ? 0 : this.creationUserData.amount / 10000,
|
||||
rangeMin: 0,
|
||||
rangeMax: 1000,
|
||||
currentMonth: {
|
||||
short: this.$moment().format('MMMM'),
|
||||
long: this.$moment().format('YYYY-MM-DD'),
|
||||
},
|
||||
lastMonth: {
|
||||
short: this.$moment().subtract(1, 'month').format('MMMM'),
|
||||
long: this.$moment().subtract(1, 'month').format('YYYY-MM') + '-01',
|
||||
},
|
||||
beforeLastMonth: {
|
||||
short: this.$moment().subtract(2, 'month').format('MMMM'),
|
||||
long: this.$moment().subtract(2, 'month').format('YYYY-MM') + '-01',
|
||||
},
|
||||
submitObj: null,
|
||||
isdisabled: true,
|
||||
createdIndex: null,
|
||||
selectedOpenCreationAmount: {},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateRadioSelected(name, index, openCreation) {
|
||||
this.createdIndex = index
|
||||
this.rangeMin = 0
|
||||
this.rangeMax = this.creation[index]
|
||||
},
|
||||
submitCreation() {
|
||||
// Formular Prüfen ob ein Zeitraum ausgewählt wurde. Ansonsten abbrechen und Hinweis anzeigen
|
||||
if (this.radioSelected === '') {
|
||||
return alert('Bitte wähle einen Zeitraum!')
|
||||
}
|
||||
// Formular Prüfen ob der GDD Betrag grösser 0 ist. Ansonsten abbrechen und Hinweis anzeigen
|
||||
if (this.value <= 0) {
|
||||
return alert('Bitte gib einen GDD Betrag an!')
|
||||
}
|
||||
// Formular Prüfen ob der Text vorhanden ist. Ansonsten abbrechen und Hinweis anzeigen
|
||||
if (this.text === '') {
|
||||
return alert('Bitte gib einen Text ein!')
|
||||
}
|
||||
// Formular Prüfen ob der Text länger als 10 Zeichen hat. Ansonsten abbrechen und Hinweis anzeigen
|
||||
if (this.text.length < 10) {
|
||||
return alert('Bitte gib einen Text ein der länger als 10 Zeichen ist!')
|
||||
}
|
||||
this.submitObj = {
|
||||
id: this.item.id,
|
||||
email: this.item.email,
|
||||
creationDate: this.radioSelected.long,
|
||||
amount: Number(this.value),
|
||||
memo: this.text,
|
||||
moderator: Number(this.$store.state.moderator.id),
|
||||
}
|
||||
|
||||
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updatePendingCreation,
|
||||
variables: this.submitObj,
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('update-user-data', this.item, result.data.updatePendingCreation.creation)
|
||||
this.$emit('update-creation-data', {
|
||||
amount: Number(result.data.updatePendingCreation.amount),
|
||||
date: result.data.updatePendingCreation.date,
|
||||
memo: result.data.updatePendingCreation.memo,
|
||||
moderator: Number(result.data.updatePendingCreation.moderator),
|
||||
row: this.row,
|
||||
})
|
||||
this.$toasted.success(
|
||||
`Offene schöpfung (${this.value} GDD) für ${this.item.email} wurde geändert, liegt zur Bestätigung bereit`,
|
||||
)
|
||||
this.submitObj = null
|
||||
this.createdIndex = null
|
||||
// das creation Formular reseten
|
||||
this.$refs.updateCreationForm.reset()
|
||||
// Den geschöpften Wert auf o setzen
|
||||
this.value = 0
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.error(error.message)
|
||||
this.submitObj = null
|
||||
// das creation Formular reseten
|
||||
this.$refs.updateCreationForm.reset()
|
||||
// Den geschöpften Wert auf o setzen
|
||||
this.value = 0
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -47,5 +47,6 @@ export default {
|
||||
<style>
|
||||
.navbar-brand-img {
|
||||
height: 2rem;
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -60,13 +60,25 @@
|
||||
<b-row class="mb-2">
|
||||
<b-col></b-col>
|
||||
</b-row>
|
||||
|
||||
{{ type }}
|
||||
<creation-formular
|
||||
v-if="type === 'PageUserSearch'"
|
||||
type="singleCreation"
|
||||
:pagetype="type"
|
||||
:creation="row.item.creation"
|
||||
:item="row.item"
|
||||
:creationUserData="creationData"
|
||||
:creationUserData="creationUserData"
|
||||
@update-creation-data="updateCreationData"
|
||||
@update-user-data="updateUserData"
|
||||
/>
|
||||
<edit-creation-formular
|
||||
v-else
|
||||
type="singleCreation"
|
||||
:pagetype="type"
|
||||
:creation="row.item.creation"
|
||||
:item="row.item"
|
||||
:row="row"
|
||||
:creationUserData="creationUserData"
|
||||
@update-creation-data="updateCreationData"
|
||||
@update-user-data="updateUserData"
|
||||
/>
|
||||
@ -119,6 +131,7 @@
|
||||
|
||||
<script>
|
||||
import CreationFormular from '../components/CreationFormular.vue'
|
||||
import EditCreationFormular from '../components/EditCreationFormular.vue'
|
||||
|
||||
export default {
|
||||
name: 'UserTable',
|
||||
@ -147,10 +160,11 @@ export default {
|
||||
},
|
||||
components: {
|
||||
CreationFormular,
|
||||
EditCreationFormular,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
creationData: {},
|
||||
creationUserData: {},
|
||||
overlay: false,
|
||||
overlayBookmarkType: '',
|
||||
overlayItem: [],
|
||||
@ -219,19 +233,28 @@ export default {
|
||||
this.$emit('remove-confirm-result', item, 'remove')
|
||||
},
|
||||
editCreationUserTable(row, rowItem) {
|
||||
alert('editCreationUserTable')
|
||||
if (!row.detailsShowing) {
|
||||
alert('offen edit loslegen')
|
||||
// this.item = rowItem
|
||||
this.creationData = rowItem
|
||||
// alert(this.creationData)
|
||||
this.creationUserData = rowItem
|
||||
} else {
|
||||
this.creationUserData = {}
|
||||
}
|
||||
row.toggleDetails()
|
||||
},
|
||||
updateCreationData(data) {
|
||||
this.creationData = {
|
||||
...data,
|
||||
}
|
||||
// console.log('updateCreationData this.creationUserData11=> ', this.creationUserData)
|
||||
// console.log('updateCreationData data=> ', data)
|
||||
// this.creationUserData = {
|
||||
// ...this.creationUserData,
|
||||
// ...data,
|
||||
// }
|
||||
// console.log('updateCreationData this.creationUserData22=> ', this.creationUserData)
|
||||
|
||||
this.creationUserData.amount = data.amount
|
||||
this.creationUserData.date = data.date
|
||||
this.creationUserData.memo = data.memo
|
||||
this.creationUserData.moderator = data.moderator
|
||||
|
||||
data.row.toggleDetails()
|
||||
},
|
||||
updateUserData(rowItem, newCreation) {
|
||||
rowItem.creation = newCreation
|
||||
|
||||
7
admin/src/graphql/deletePendingCreation.js
Normal file
7
admin/src/graphql/deletePendingCreation.js
Normal file
@ -0,0 +1,7 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const deletePendingCreation = gql`
|
||||
mutation ($id: Float!) {
|
||||
deletePendingCreation(id: $id)
|
||||
}
|
||||
`
|
||||
@ -3,6 +3,7 @@ import gql from 'graphql-tag'
|
||||
export const getPendingCreations = gql`
|
||||
query {
|
||||
getPendingCreations {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
email
|
||||
|
||||
27
admin/src/graphql/updatePendingCreation.js
Normal file
27
admin/src/graphql/updatePendingCreation.js
Normal file
@ -0,0 +1,27 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const updatePendingCreation = gql`
|
||||
mutation (
|
||||
$id: Int!
|
||||
$email: String!
|
||||
$amount: Int!
|
||||
$memo: String!
|
||||
$creationDate: String!
|
||||
$moderator: Int!
|
||||
) {
|
||||
updatePendingCreation(
|
||||
id: $id
|
||||
email: $email
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
creationDate: $creationDate
|
||||
moderator: $moderator
|
||||
) {
|
||||
amount
|
||||
date
|
||||
memo
|
||||
creation
|
||||
moderator
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -5,6 +5,7 @@ export const verifyLogin = gql`
|
||||
verifyLogin {
|
||||
firstName
|
||||
lastName
|
||||
isAdmin
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ Vue.use(Toasted, {
|
||||
},
|
||||
})
|
||||
|
||||
addNavigationGuards(router, store)
|
||||
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
||||
|
||||
new Vue({
|
||||
moment,
|
||||
|
||||
@ -4,19 +4,20 @@ import CONFIG from './config'
|
||||
|
||||
import Vue from 'vue'
|
||||
import VueApollo from 'vue-apollo'
|
||||
import Vuex from 'vuex'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import i18n from './i18n'
|
||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||
import moment from 'vue-moment'
|
||||
import store from './store/store'
|
||||
import router from './router/router'
|
||||
|
||||
jest.mock('vue')
|
||||
jest.mock('vue-apollo')
|
||||
jest.mock('vuex')
|
||||
jest.mock('vue-i18n')
|
||||
jest.mock('vue-moment')
|
||||
|
||||
const storeMock = jest.fn()
|
||||
Vuex.Store = storeMock
|
||||
jest.mock('./store/store')
|
||||
jest.mock('./i18n')
|
||||
jest.mock('./router/router')
|
||||
|
||||
jest.mock('apollo-boost', () => {
|
||||
return {
|
||||
@ -65,8 +66,12 @@ describe('main', () => {
|
||||
expect(Vue).toBeCalled()
|
||||
})
|
||||
|
||||
it('calls VueI18n', () => {
|
||||
expect(VueI18n).toBeCalled()
|
||||
it('calls i18n', () => {
|
||||
expect(Vue).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
i18n,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('calls BootstrapVue', () => {
|
||||
@ -81,7 +86,92 @@ describe('main', () => {
|
||||
expect(Vue.use).toBeCalledWith(moment)
|
||||
})
|
||||
|
||||
it.skip('creates a store', () => {
|
||||
expect(storeMock).toBeCalled()
|
||||
it('creates a store', () => {
|
||||
expect(Vue).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
store,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('creates a router', () => {
|
||||
expect(Vue).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
router,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('ApolloLink', () => {
|
||||
// mock store
|
||||
const storeDispatchMock = jest.fn()
|
||||
store.state = {
|
||||
token: 'some-token',
|
||||
}
|
||||
store.dispatch = storeDispatchMock
|
||||
|
||||
// mock i18n.t
|
||||
i18n.t = jest.fn((t) => t)
|
||||
|
||||
// mock apllo response
|
||||
const responseMock = {
|
||||
errors: [{ message: '403.13 - Client certificate revoked' }],
|
||||
}
|
||||
|
||||
// mock router
|
||||
const routerPushMock = jest.fn()
|
||||
router.push = routerPushMock
|
||||
router.currentRoute = {
|
||||
path: '/overview',
|
||||
}
|
||||
|
||||
// mock context
|
||||
const setContextMock = jest.fn()
|
||||
const getContextMock = jest.fn(() => {
|
||||
return {
|
||||
response: {
|
||||
headers: {
|
||||
get: jest.fn(),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// mock apollo link function params
|
||||
const operationMock = {
|
||||
setContext: setContextMock,
|
||||
getContext: getContextMock,
|
||||
}
|
||||
|
||||
const forwardMock = jest.fn(() => {
|
||||
return [responseMock]
|
||||
})
|
||||
|
||||
// get apollo link callback
|
||||
const middleware = ApolloLink.mock.calls[0][0]
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
// run the callback with mocked params
|
||||
middleware(operationMock, forwardMock)
|
||||
})
|
||||
|
||||
it('sets authorization header', () => {
|
||||
expect(setContextMock).toBeCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer some-token',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('apollo response is 403.13', () => {
|
||||
it.skip('dispatches logout', () => {
|
||||
expect(storeDispatchMock).toBeCalledWith('logout', null)
|
||||
})
|
||||
|
||||
it.skip('redirects to logout', () => {
|
||||
expect(routerPushMock).toBeCalledWith('/logout')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -21,24 +21,26 @@
|
||||
</b-col>
|
||||
<b-col cols="12" lg="7" class="shadow p-3 mb-5 rounded bg-info">
|
||||
<user-table
|
||||
v-if="massCreation.length > 0"
|
||||
v-show="itemsMassCreation.length > 0"
|
||||
class="shadow p-3 mb-5 bg-white rounded"
|
||||
type="UserListMassCreation"
|
||||
:itemsUser="massCreation"
|
||||
:itemsUser="itemsMassCreation"
|
||||
:fieldsTable="fields"
|
||||
:criteria="null"
|
||||
:creation="creation"
|
||||
@update-item="updateItem"
|
||||
/>
|
||||
|
||||
<div v-if="itemsMassCreation.length === 0">
|
||||
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
|
||||
</div>
|
||||
<creation-formular
|
||||
v-if="massCreation.length > 0"
|
||||
v-else
|
||||
type="massCreation"
|
||||
:creation="creation"
|
||||
:itemsMassCreation="massCreation"
|
||||
@update-radio-selected="updateRadioSelected"
|
||||
:items="itemsMassCreation"
|
||||
@remove-all-bookmark="removeAllBookmark"
|
||||
/>
|
||||
{{ itemsMassCreation }}
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
@ -72,7 +74,7 @@ export default {
|
||||
{ key: 'bookmark', label: 'löschen' },
|
||||
],
|
||||
itemsList: [],
|
||||
massCreation: [],
|
||||
itemsMassCreation: [],
|
||||
radioSelectedMass: '',
|
||||
criteria: '',
|
||||
creation: [null, null, null],
|
||||
@ -111,12 +113,12 @@ export default {
|
||||
findArr = this.itemsList.find((arr) => arr.id === e.id)
|
||||
index = this.itemsList.indexOf(findArr)
|
||||
this.itemsList.splice(index, 1)
|
||||
this.massCreation.push(e)
|
||||
this.itemsMassCreation.push(e)
|
||||
break
|
||||
case 'remove':
|
||||
findArr = this.massCreation.find((arr) => arr.id === e.id)
|
||||
index = this.massCreation.indexOf(findArr)
|
||||
this.massCreation.splice(index, 1)
|
||||
findArr = this.itemsMassCreation.find((arr) => arr.id === e.id)
|
||||
index = this.itemsMassCreation.indexOf(findArr)
|
||||
this.itemsMassCreation.splice(index, 1)
|
||||
this.itemsList.push(e)
|
||||
break
|
||||
default:
|
||||
@ -124,19 +126,19 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
updateRadioSelected(obj) {
|
||||
this.radioSelectedMass = obj[0]
|
||||
},
|
||||
// updateRadioSelected(obj) {
|
||||
// this.radioSelectedMass = obj[0]
|
||||
// },
|
||||
|
||||
removeAllBookmark() {
|
||||
alert('remove all bookmarks')
|
||||
const index = 0
|
||||
let i = 0
|
||||
|
||||
for (i; i < this.massCreation.length; i++) {
|
||||
this.itemsList.push(this.massCreation[i])
|
||||
for (i; i < this.itemsMassCreation.length; i++) {
|
||||
this.itemsList.push(this.itemsMassCreation[i])
|
||||
}
|
||||
this.massCreation.splice(index, this.massCreation.length)
|
||||
this.itemsMassCreation.splice(index, this.itemsMassCreation.length)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import CreationConfirm from './CreationConfirm.vue'
|
||||
import { deletePendingCreation } from '../graphql/deletePendingCreation'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
const toastedErrorMock = jest.fn()
|
||||
const toastedSuccessMock = jest.fn()
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
getPendingCreations: [
|
||||
{
|
||||
id: 1,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
@ -18,6 +21,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
moderator: 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
firstName: 'Räuber',
|
||||
lastName: 'Hotzenplotz',
|
||||
email: 'raeuber@hotzenplotz.de',
|
||||
@ -30,15 +34,19 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
},
|
||||
})
|
||||
|
||||
const apolloMutateMock = jest.fn().mockResolvedValue({})
|
||||
|
||||
const mocks = {
|
||||
$store: {
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
mutate: apolloMutateMock,
|
||||
},
|
||||
$toasted: {
|
||||
error: toastedErrorMock,
|
||||
success: toastedSuccessMock,
|
||||
},
|
||||
$moment: jest.fn((value) => {
|
||||
return {
|
||||
@ -73,6 +81,68 @@ describe('CreationConfirm', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm creation delete with success', () => {
|
||||
beforeEach(async () => {
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
data: {
|
||||
getPendingCreations: [
|
||||
{
|
||||
id: 1,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
amount: 500,
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
firstName: 'Räuber',
|
||||
lastName: 'Hotzenplotz',
|
||||
email: 'raeuber@hotzenplotz.de',
|
||||
amount: 1000000,
|
||||
memo: 'Gut Ergatert',
|
||||
date: new Date(),
|
||||
moderator: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
await wrapper
|
||||
.findComponent({ name: 'UserTable' })
|
||||
.vm.$emit('remove-confirm-result', { id: 1 }, 'remove')
|
||||
})
|
||||
|
||||
it('calls the deletePendingCreation mutation', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: deletePendingCreation,
|
||||
variables: { id: 1 },
|
||||
})
|
||||
})
|
||||
|
||||
it('commits openCreationsMinus to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastedSuccessMock).toBeCalledWith('Pending Creation has been deleted')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm creation delete with error', () => {
|
||||
beforeEach(async () => {
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
|
||||
await wrapper
|
||||
.findComponent({ name: 'UserTable' })
|
||||
.vm.$emit('remove-confirm-result', { id: 1 }, 'remove')
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastedErrorMock).toBeCalledWith('Ouchhh!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('server response is error', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
@ -86,17 +156,5 @@ describe('CreationConfirm', () => {
|
||||
expect(toastedErrorMock).toBeCalledWith('Ouch!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm creation', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper
|
||||
.findComponent({ name: 'UserTable' })
|
||||
.vm.$emit('remove-confirm-result', 1, 'remove')
|
||||
})
|
||||
|
||||
it('commits openCreationsMinus to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
<script>
|
||||
import UserTable from '../components/UserTable.vue'
|
||||
import { getPendingCreations } from '../graphql/getPendingCreations'
|
||||
import { deletePendingCreation } from '../graphql/deletePendingCreation'
|
||||
|
||||
export default {
|
||||
name: 'CreationConfirm',
|
||||
@ -48,20 +49,27 @@ export default {
|
||||
confirmResult: [],
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
removeConfirmResult(e, event) {
|
||||
if (event === 'remove') {
|
||||
let index = 0
|
||||
let findArr = {}
|
||||
|
||||
findArr = this.confirmResult.find((arr) => arr.id === e.id)
|
||||
|
||||
index = this.confirmResult.indexOf(findArr)
|
||||
|
||||
this.confirmResult.splice(index, 1)
|
||||
|
||||
this.$store.commit('openCreationsMinus', 1)
|
||||
const findArr = this.confirmResult.find((arr) => arr.id === e.id)
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: deletePendingCreation,
|
||||
variables: {
|
||||
id: findArr.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
index = this.confirmResult.indexOf(findArr)
|
||||
this.confirmResult.splice(index, 1)
|
||||
this.$store.commit('openCreationsMinus', 1)
|
||||
this.$toasted.success('Pending Creation has been deleted')
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.error(error.message)
|
||||
})
|
||||
}
|
||||
},
|
||||
getPendingCreations() {
|
||||
|
||||
74
admin/src/pages/Overview.spec.js
Normal file
74
admin/src/pages/Overview.spec.js
Normal file
@ -0,0 +1,74 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Overview from './Overview.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
getPendingCreations: [
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
const mocks = {
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
$store: {
|
||||
commit: storeCommitMock,
|
||||
state: {
|
||||
openCreations: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('Overview', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(Overview, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('calls getPendingCreations', () => {
|
||||
expect(apolloQueryMock).toBeCalled()
|
||||
})
|
||||
|
||||
it('commts three pending creations to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 3)
|
||||
})
|
||||
|
||||
describe('with open creations', () => {
|
||||
it('renders a link to confirm creations', () => {
|
||||
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('2')
|
||||
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('without open creations', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$store.state.openCreations = 0
|
||||
})
|
||||
|
||||
it('renders a link to confirm creations', () => {
|
||||
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('0')
|
||||
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -29,26 +29,6 @@
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
<br />
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-card border-variant="info" header="offene Registrierung" align="center">
|
||||
<b-card-text>Unbestätigte E-mail Registrierung</b-card-text>
|
||||
</b-card>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-card border-variant="info" header="geschöpfte Stunden" align="center">
|
||||
<b-card-text>Wievile Stunden können noch von Mitgliedern geschöpft werden?</b-card-text>
|
||||
</b-card>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-card border-variant="info" header="Gemeinschafts Konto" align="center">
|
||||
<b-card-text>
|
||||
Für jedes Mitglied kann für das Gemeinschaftskonto geschöpft werden. Pro Monat 1000 x
|
||||
Mitglieder
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<hr />
|
||||
<br />
|
||||
<b-list-group>
|
||||
@ -57,22 +37,19 @@
|
||||
</b-list-group-item>
|
||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||
Mitglieder
|
||||
<b-badge class="bg-success" pill>14</b-badge>
|
||||
<b-badge class="bg-success" pill>2400</b-badge>
|
||||
</b-list-group-item>
|
||||
|
||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||
aktive Mitglieder
|
||||
<b-badge class="bg-primary" pill>12</b-badge>
|
||||
<b-badge class="bg-primary" pill>2201</b-badge>
|
||||
</b-list-group-item>
|
||||
|
||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||
nicht bestätigte Mitglieder
|
||||
<b-badge class="bg-warning text-dark" pill>2</b-badge>
|
||||
<b-badge class="bg-warning text-dark" pill>120</b-badge>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
<b-button @click="$store.commit('resetOpenCreations')">
|
||||
lösche alle offenen Test Schöpfungen
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -81,7 +58,7 @@ import { getPendingCreations } from '../graphql/getPendingCreations'
|
||||
export default {
|
||||
name: 'overview',
|
||||
methods: {
|
||||
getPendingCreations() {
|
||||
async getPendingCreations() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: getPendingCreations,
|
||||
@ -89,7 +66,6 @@ export default {
|
||||
.then((result) => {
|
||||
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
|
||||
})
|
||||
.catch()
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
||||
@ -1,12 +1,28 @@
|
||||
import { verifyLogin } from '../graphql/verifyLogin'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const addNavigationGuards = (router, store) => {
|
||||
const addNavigationGuards = (router, store, apollo) => {
|
||||
// store token on `authenticate`
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (to.path === '/authenticate' && to.query && to.query.token) {
|
||||
// TODO verify user to get user data
|
||||
store.commit('token', to.query.token)
|
||||
next({ path: '/' })
|
||||
await apollo
|
||||
.query({
|
||||
query: verifyLogin,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((result) => {
|
||||
const moderator = result.data.verifyLogin
|
||||
if (moderator.isAdmin) {
|
||||
store.commit('moderator', moderator)
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
next({ path: '/not-found' })
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
next({ path: '/not-found' })
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
@ -16,7 +32,9 @@ const addNavigationGuards = (router, store) => {
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (
|
||||
!CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes
|
||||
!store.state.token && // we do not have a token
|
||||
(!store.state.token || // we do not have a token
|
||||
!store.state.moderator || // no moderator set in store
|
||||
!store.state.moderator.isAdmin) && // user is no admin
|
||||
to.path !== '/not-found' && // we are not on `not-found`
|
||||
to.path !== '/logout' // we are not on `logout`
|
||||
) {
|
||||
|
||||
@ -2,6 +2,13 @@ import addNavigationGuards from './guards'
|
||||
import router from './router'
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
isAdmin: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const store = {
|
||||
commit: storeCommitMock,
|
||||
@ -10,7 +17,11 @@ const store = {
|
||||
},
|
||||
}
|
||||
|
||||
addNavigationGuards(router, store)
|
||||
const apollo = {
|
||||
query: apolloQueryMock,
|
||||
}
|
||||
|
||||
addNavigationGuards(router, store, apollo)
|
||||
|
||||
describe('navigation guards', () => {
|
||||
beforeEach(() => {
|
||||
@ -21,18 +32,70 @@ describe('navigation guards', () => {
|
||||
const navGuard = router.beforeHooks[0]
|
||||
const next = jest.fn()
|
||||
|
||||
describe('with valid token', () => {
|
||||
it('commits the token to the store', async () => {
|
||||
describe('with valid token and as admin', () => {
|
||||
beforeEach(() => {
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
})
|
||||
|
||||
it('commits the token to the store', async () => {
|
||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||
})
|
||||
|
||||
it('commits the moderator to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true })
|
||||
})
|
||||
|
||||
it('redirects to /', async () => {
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
expect(next).toBeCalledWith({ path: '/' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('with valid token and not as admin', () => {
|
||||
beforeEach(() => {
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
isAdmin: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
})
|
||||
|
||||
it('commits the token to the store', async () => {
|
||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||
})
|
||||
|
||||
it('does not commit the moderator to the store', () => {
|
||||
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
|
||||
})
|
||||
|
||||
it('redirects to /not-found', async () => {
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('with valid token and server error on verification', () => {
|
||||
beforeEach(() => {
|
||||
apolloQueryMock.mockRejectedValue({
|
||||
message: 'Ouch!',
|
||||
})
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
})
|
||||
|
||||
it('commits the token to the store', async () => {
|
||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||
})
|
||||
|
||||
it('does not commit the moderator to the store', () => {
|
||||
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
|
||||
})
|
||||
|
||||
it('redirects to /not-found', async () => {
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('without valid token', () => {
|
||||
it('does not commit the token to the store', async () => {
|
||||
navGuard({ path: '/authenticate' }, {}, next)
|
||||
@ -55,9 +118,16 @@ describe('navigation guards', () => {
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
|
||||
it('does not redirect when token in store', () => {
|
||||
it('redirects to not found with token in store and not moderator', () => {
|
||||
store.state.token = 'valid token'
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
|
||||
it('does not redirect with token in store and as moderator', () => {
|
||||
store.state.token = 'valid token'
|
||||
store.state.moderator = { isAdmin: true }
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
})
|
||||
|
||||
@ -29,6 +29,7 @@ export const mutations = {
|
||||
export const actions = {
|
||||
logout: ({ commit, state }) => {
|
||||
commit('token', null)
|
||||
commit('moderator', null)
|
||||
window.localStorage.clear()
|
||||
},
|
||||
}
|
||||
@ -41,7 +42,7 @@ const store = new Vuex.Store({
|
||||
],
|
||||
state: {
|
||||
token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null,
|
||||
moderator: { name: 'Dertest Moderator', id: 0 },
|
||||
moderator: null,
|
||||
openCreations: 0,
|
||||
},
|
||||
// Syncronous mutation of the state
|
||||
|
||||
@ -3,8 +3,14 @@ import CONFIG from '../config'
|
||||
|
||||
jest.mock('../config')
|
||||
|
||||
const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations, setOpenCreations } =
|
||||
mutations
|
||||
const {
|
||||
token,
|
||||
openCreationsPlus,
|
||||
openCreationsMinus,
|
||||
resetOpenCreations,
|
||||
setOpenCreations,
|
||||
moderator,
|
||||
} = mutations
|
||||
const { logout } = actions
|
||||
|
||||
CONFIG.DEBUG_DISABLE_AUTH = true
|
||||
@ -43,6 +49,14 @@ describe('Vuex store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('moderator', () => {
|
||||
it('sets the moderator object in state', () => {
|
||||
const state = { moderator: null }
|
||||
moderator(state, { id: 1 })
|
||||
expect(state.moderator).toEqual({ id: 1 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('setOpenCreations', () => {
|
||||
it('sets the open creations to given value', () => {
|
||||
const state = { openCreations: 24 }
|
||||
@ -67,6 +81,11 @@ describe('Vuex store', () => {
|
||||
expect(commit).toBeCalledWith('token', null)
|
||||
})
|
||||
|
||||
it('deletes the moderator in store', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toBeCalledWith('moderator', null)
|
||||
})
|
||||
|
||||
it.skip('clears the window local storage', () => {
|
||||
expect(windowStorageMock).toBeCalled()
|
||||
})
|
||||
|
||||
22
backend/src/graphql/arg/UpdatePendingCreationArgs.ts
Normal file
22
backend/src/graphql/arg/UpdatePendingCreationArgs.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export default class CreatePendingCreationArgs {
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
@Field(() => Int)
|
||||
amount: number
|
||||
|
||||
@Field(() => String)
|
||||
memo: string
|
||||
|
||||
@Field(() => String)
|
||||
creationDate: string
|
||||
|
||||
@Field(() => Int)
|
||||
moderator: number
|
||||
}
|
||||
19
backend/src/graphql/model/UpdatePendingCreation.ts
Normal file
19
backend/src/graphql/model/UpdatePendingCreation.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class UpdatePendingCreation {
|
||||
@Field(() => Date)
|
||||
date: Date
|
||||
|
||||
@Field(() => String)
|
||||
memo: string
|
||||
|
||||
@Field(() => Number)
|
||||
amount: number
|
||||
|
||||
@Field(() => Number)
|
||||
moderator: number
|
||||
|
||||
@Field(() => [Number])
|
||||
creation: number[]
|
||||
}
|
||||
@ -2,12 +2,15 @@ import { Resolver, Query, Arg, Args, Authorized, Mutation } from 'type-graphql'
|
||||
import { getCustomRepository, Raw } from 'typeorm'
|
||||
import { UserAdmin } from '../model/UserAdmin'
|
||||
import { PendingCreation } from '../model/PendingCreation'
|
||||
import { UpdatePendingCreation } from '../model/UpdatePendingCreation'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import { TransactionCreationRepository } from '../../typeorm/repository/TransactionCreation'
|
||||
import { PendingCreationRepository } from '../../typeorm/repository/PendingCreation'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
|
||||
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
|
||||
import moment from 'moment'
|
||||
import { LoginPendingTasksAdmin } from '@entity/LoginPendingTasksAdmin'
|
||||
|
||||
@Resolver()
|
||||
export class AdminResolver {
|
||||
@ -29,6 +32,7 @@ export class AdminResolver {
|
||||
return adminUsers
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.SEARCH_USERS])
|
||||
@Mutation(() => [Number])
|
||||
async createPendingCreation(
|
||||
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
|
||||
@ -53,6 +57,52 @@ export class AdminResolver {
|
||||
return await getUserCreations(user.id)
|
||||
}
|
||||
|
||||
// @Authorized([RIGHTS.SEARCH_USERS])
|
||||
@Mutation(() => UpdatePendingCreation)
|
||||
async updatePendingCreation(
|
||||
@Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs,
|
||||
): Promise<UpdatePendingCreation> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const user = await userRepository.findByEmail(email)
|
||||
|
||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||
const updatedCreation = await pendingCreationRepository.findOneOrFail({ id })
|
||||
|
||||
if (updatedCreation.userId !== user.id)
|
||||
throw new Error('user of the pending creation and send user does not correspond')
|
||||
|
||||
updatedCreation.amount = BigInt(amount * 10000)
|
||||
updatedCreation.memo = memo
|
||||
updatedCreation.date = new Date(creationDate)
|
||||
updatedCreation.moderator = moderator
|
||||
|
||||
await pendingCreationRepository.save(updatedCreation)
|
||||
const result = new UpdatePendingCreation()
|
||||
result.amount = parseInt(updatedCreation.amount.toString())
|
||||
result.memo = updatedCreation.memo
|
||||
result.date = updatedCreation.date
|
||||
result.moderator = updatedCreation.moderator
|
||||
result.creation = await getUserCreations(user.id)
|
||||
|
||||
return result
|
||||
|
||||
// const creations = await getUserCreations(user.id)
|
||||
// const creationDateObj = new Date(creationDate)
|
||||
// if (isCreationValid(creations, amount, creationDateObj)) {
|
||||
// const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||
// const loginPendingTaskAdmin = pendingCreationRepository.create()
|
||||
// loginPendingTaskAdmin.userId = user.id
|
||||
// loginPendingTaskAdmin.amount = BigInt(amount * 10000)
|
||||
// loginPendingTaskAdmin.created = new Date()
|
||||
// loginPendingTaskAdmin.date = creationDateObj
|
||||
// loginPendingTaskAdmin.memo = memo
|
||||
// loginPendingTaskAdmin.moderator = moderator
|
||||
//
|
||||
// pendingCreationRepository.save(loginPendingTaskAdmin)
|
||||
// }
|
||||
// return await getUserCreations(user.id)
|
||||
}
|
||||
|
||||
@Query(() => [PendingCreation])
|
||||
async getPendingCreations(): Promise<PendingCreation[]> {
|
||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||
@ -76,6 +126,14 @@ export class AdminResolver {
|
||||
)
|
||||
return pendingCreationsPromise
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async deletePendingCreation(@Arg('id') id: number): Promise<boolean> {
|
||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||
const entity = await pendingCreationRepository.findOneOrFail(id)
|
||||
const res = await pendingCreationRepository.delete(entity)
|
||||
return !!res
|
||||
}
|
||||
}
|
||||
|
||||
async function getUserCreations(id: number): Promise<number[]> {
|
||||
|
||||
@ -15,12 +15,19 @@ const addNavigationGuards = (router, store, apollo) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (to.path === '/authenticate' && to.query.token) {
|
||||
store.commit('token', to.query.token)
|
||||
const result = await apollo.query({
|
||||
query: verifyLogin,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
store.dispatch('login', result.data.verifyLogin)
|
||||
next({ path: '/overview' })
|
||||
await apollo
|
||||
.query({
|
||||
query: verifyLogin,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((result) => {
|
||||
store.dispatch('login', result.data.verifyLogin)
|
||||
next({ path: '/overview' })
|
||||
})
|
||||
.catch(() => {
|
||||
store.dispatch('logout')
|
||||
next()
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
|
||||
@ -2,15 +2,28 @@ import addNavigationGuards from './guards'
|
||||
import router from './router'
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
const storeDispatchMock = jest.fn()
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
firstName: 'Peter',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const store = {
|
||||
commit: storeCommitMock,
|
||||
state: {
|
||||
token: null,
|
||||
},
|
||||
dispatch: storeDispatchMock,
|
||||
}
|
||||
|
||||
addNavigationGuards(router, store)
|
||||
const apollo = {
|
||||
query: apolloQueryMock,
|
||||
}
|
||||
|
||||
addNavigationGuards(router, store, apollo)
|
||||
|
||||
describe('navigation guards', () => {
|
||||
beforeEach(() => {
|
||||
@ -29,6 +42,46 @@ describe('navigation guards', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticate', () => {
|
||||
const navGuard = router.beforeHooks[1]
|
||||
const next = jest.fn()
|
||||
|
||||
describe('with valid token', () => {
|
||||
beforeEach(() => {
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
})
|
||||
|
||||
it('commts the token to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||
})
|
||||
|
||||
it('calls verifyLogin', () => {
|
||||
expect(apolloQueryMock).toBeCalled()
|
||||
})
|
||||
|
||||
it('commits login to the store', () => {
|
||||
expect(storeDispatchMock).toBeCalledWith('login', { firstName: 'Peter' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('with valid token and server error', () => {
|
||||
beforeEach(() => {
|
||||
apolloQueryMock.mockRejectedValue({
|
||||
message: 'Ouch!',
|
||||
})
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
})
|
||||
|
||||
it('dispatches logout to store', () => {
|
||||
expect(storeDispatchMock).toBeCalledWith('logout')
|
||||
})
|
||||
|
||||
it('calls next', () => {
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authorization', () => {
|
||||
const navGuard = router.beforeHooks[2]
|
||||
const next = jest.fn()
|
||||
|
||||
@ -6,6 +6,7 @@ PROJECT_DIR="${SCRIPT_DIR}/../"
|
||||
FRONTEND_DIR="${PROJECT_DIR}/frontend/"
|
||||
BACKEND_DIR="${PROJECT_DIR}/backend/"
|
||||
DATABASE_DIR="${PROJECT_DIR}/database/"
|
||||
ADMIN_DIR="${PROJECT_DIR}/admin/"
|
||||
|
||||
# navigate to project directory
|
||||
cd ${PROJECT_DIR}
|
||||
@ -23,6 +24,8 @@ cd ${BACKEND_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${DATABASE_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${ADMIN_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
|
||||
# generate changelog
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user