Merge pull request #3477 from gradido/upgrade_frontend_skeleton

refactor(frontend): show skeleton only as long as needed
This commit is contained in:
einhornimmond 2025-04-29 20:28:18 +02:00 committed by GitHub
commit 2299bc451c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 132 additions and 94 deletions

View File

@ -95,6 +95,9 @@ describe('GddTransactionList', () => {
}) })
describe('timestamp property', () => { describe('timestamp property', () => {
beforeEach(async () => {
await wrapper.setProps({ timestamp: new Date().getTime() })
})
it('emits update-transactions when timestamp changes', async () => { it('emits update-transactions when timestamp changes', async () => {
await wrapper.setProps({ timestamp: 0 }) await wrapper.setProps({ timestamp: 0 })
expect(wrapper.emitted('update-transactions')).toBeTruthy() expect(wrapper.emitted('update-transactions')).toBeTruthy()

View File

@ -101,7 +101,7 @@ export default {
this.updateTransactions() this.updateTransactions()
}, },
timestamp: { timestamp: {
immediate: true, immediate: false,
handler: 'updateTransactions', handler: 'updateTransactions',
}, },
}, },

View File

@ -0,0 +1,56 @@
fragment balanceFields on Balance {
balance
balanceGDT
count
linkCount
}
fragment transactionFields on Transaction {
id
typeId
amount
balance
previousBalance
balanceDate
memo
linkedUser {
firstName
lastName
communityUuid
communityName
gradidoID
alias
}
decay {
decay
start
end
duration
}
linkId
}
query transactionsQuery($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
balance {
...balanceFields
}
transactions {
...transactionFields
}
}
}
query transactionsUserCountQuery($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
balance {
...balanceFields
}
transactions {
...transactionFields
}
}
communityStatistics {
totalUsers
}
}

View File

@ -5,6 +5,7 @@ import DashboardLayout from './DashboardLayout'
import { createStore } from 'vuex' import { createStore } from 'vuex'
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import routes from '@/routes/routes' import routes from '@/routes/routes'
import { useQuery } from '@vue/apollo-composable'
const toastErrorSpy = vi.fn() const toastErrorSpy = vi.fn()
@ -14,18 +15,35 @@ vi.mock('@/composables/useToast', () => ({
}), }),
})) }))
const mockQueryFn = vi.fn()
const mockRefetchFn = vi.fn() const mockRefetchFn = vi.fn()
const mockMutateFn = vi.fn() const mockMutateFn = vi.fn()
let onErrorHandler
let onResultHandler
const mockQueryResult = ref(null) const mockQueryResult = ref(null)
const loading = ref(false)
vi.mock('@vue/apollo-composable', () => ({ vi.mock('@vue/apollo-composable', () => ({
useLazyQuery: vi.fn(() => ({ useQuery: vi.fn(() => ({
load: mockQueryFn,
refetch: mockRefetchFn, refetch: mockRefetchFn,
result: mockQueryResult, result: mockQueryResult,
onResult: vi.fn(), onResult: (handler) => {
onError: vi.fn(), onResultHandler = handler
},
onError: (handler) => {
onErrorHandler = handler
},
loading,
})),
useLazyQuery: vi.fn(() => ({
refetch: mockRefetchFn,
result: mockQueryResult,
onResult: (handler) => {
onResultHandler = handler
},
onError: (handler) => {
onErrorHandler = handler
},
loading,
})), })),
useMutation: vi.fn(() => ({ useMutation: vi.fn(() => ({
mutate: mockMutateFn, mutate: mockMutateFn,
@ -103,17 +121,6 @@ describe('DashboardLayout', () => {
beforeEach(() => { beforeEach(() => {
vi.useFakeTimers() vi.useFakeTimers()
mockQueryFn.mockResolvedValue({
communityStatistics: {
totalUsers: 3113,
activeUsers: 1057,
deletedUsers: 35,
totalGradidoCreated: '4083774.05000000000000000000',
totalGradidoDecayed: '-1062639.13634129622923372197',
totalGradidoAvailable: '2513565.869444365732411569',
totalGradidoUnbookedDecayed: '-500474.6738366222166261272',
},
})
wrapper = createWrapper() wrapper = createWrapper()
}) })
@ -135,31 +142,32 @@ describe('DashboardLayout', () => {
describe('after a timeout', () => { describe('after a timeout', () => {
beforeEach(async () => { beforeEach(async () => {
vi.advanceTimersByTime(1500) vi.advanceTimersByTime(1500)
loading.value = false
await nextTick() await nextTick()
}) })
describe('update transactions', () => { describe('update transactions', () => {
beforeEach(async () => { beforeEach(async () => {
mockQueryResult.value = { onResultHandler({
transactionList: { data: {
balance: { transactionList: {
balanceGDT: '100', balance: {
count: 4, balanceGDT: '100',
linkCount: 8, count: 4,
balance: '1450', linkCount: 8,
balance: '1450',
},
transactions: ['transaction1', 'transaction2', 'transaction3', 'transaction4'],
}, },
transactions: ['transaction1', 'transaction2', 'transaction3', 'transaction4'],
}, },
} })
mockQueryFn.mockResolvedValue(mockQueryResult.value)
await wrapper.vm.updateTransactions({ currentPage: 2, pageSize: 5 }) await wrapper.vm.updateTransactions({ currentPage: 2, pageSize: 5 })
await nextTick() // Ensure all promises are resolved await nextTick() // Ensure all promises are resolved
}) })
it('load call to the API', () => { it('load call to the API', () => {
expect(mockQueryFn).toHaveBeenCalled() expect(useQuery).toHaveBeenCalled()
}) })
it('updates balance', () => { it('updates balance', () => {
@ -190,7 +198,7 @@ describe('DashboardLayout', () => {
describe('update transactions returns error', () => { describe('update transactions returns error', () => {
beforeEach(async () => { beforeEach(async () => {
mockQueryFn.mockRejectedValue(new Error('Ouch!')) wrapper.vm.skeleton = false
await wrapper await wrapper
.findComponent({ ref: 'router-view' }) .findComponent({ ref: 'router-view' })
.vm.$emit('update-transactions', { currentPage: 2, pageSize: 5 }) .vm.$emit('update-transactions', { currentPage: 2, pageSize: 5 })
@ -202,6 +210,7 @@ describe('DashboardLayout', () => {
}) })
it('toasts the error message', () => { it('toasts the error message', () => {
onErrorHandler({ message: 'Ouch!' })
expect(toastErrorSpy).toHaveBeenCalledWith('Ouch!') expect(toastErrorSpy).toHaveBeenCalledWith('Ouch!')
}) })
}) })

View File

@ -187,11 +187,10 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { onMounted, ref } from 'vue'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useLazyQuery, useMutation } from '@vue/apollo-composable' import { useQuery, useMutation } from '@vue/apollo-composable'
import { useI18n } from 'vue-i18n'
import ContentHeader from '@/layouts/templates/ContentHeader' import ContentHeader from '@/layouts/templates/ContentHeader'
import CommunityTemplate from '@/layouts/templates/CommunityTemplate' import CommunityTemplate from '@/layouts/templates/CommunityTemplate'
import Breadcrumb from '@/components/Breadcrumb/breadcrumb' import Breadcrumb from '@/components/Breadcrumb/breadcrumb'
@ -207,21 +206,23 @@ import GdtAmount from '@/components/Template/ContentHeader/GdtAmount'
import CommunityMember from '@/components/Template/ContentHeader/CommunityMember' import CommunityMember from '@/components/Template/ContentHeader/CommunityMember'
import NavCommunity from '@/components/Template/ContentHeader/NavCommunity' import NavCommunity from '@/components/Template/ContentHeader/NavCommunity'
import LastTransactions from '@/components/Template/RightSide/LastTransactions' import LastTransactions from '@/components/Template/RightSide/LastTransactions'
import { transactionsQuery, communityStatistics } from '@/graphql/queries' import { transactionsUserCountQuery } from '@/graphql/transactions.graphql'
import { logout } from '@/graphql/mutations' import { logout } from '@/graphql/mutations'
import CONFIG from '@/config' import CONFIG from '@/config'
import { useAppToast } from '@/composables/useToast' import { useAppToast } from '@/composables/useToast'
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()
const { load: useCommunityStatsQuery } = useLazyQuery(communityStatistics)
const { const {
load: useTransactionsQuery,
refetch: useRefetchTransactionsQuery, refetch: useRefetchTransactionsQuery,
result: transactionQueryResult, onError,
} = useLazyQuery(transactionsQuery, {}, { fetchPolicy: 'network-only' }) onResult,
} = useQuery(
transactionsUserCountQuery,
{ currentPage: 1, pageSize: 10, order: 'DESC' },
{ fetchPolicy: 'network-only' },
)
const { mutate: useLogoutMutation } = useMutation(logout) const { mutate: useLogoutMutation } = useMutation(logout)
const { t } = useI18n()
const { toastError } = useAppToast() const { toastError } = useAppToast()
const balance = ref(0) const balance = ref(0)
@ -230,15 +231,11 @@ const transactions = ref([])
const transactionCount = ref(0) const transactionCount = ref(0)
const transactionLinkCount = ref(0) const transactionLinkCount = ref(0)
const pending = ref(true) const pending = ref(true)
const visible = ref(false)
const hamburger = ref(true)
const darkMode = ref(false)
const skeleton = ref(true) const skeleton = ref(true)
const totalUsers = ref(null) const totalUsers = ref(null)
// only error correction, normally skeleton should be visible less than 1500ms
onMounted(() => { onMounted(() => {
updateTransactions({ currentPage: 1, pageSize: 10 })
getCommunityStatistics()
setTimeout(() => { setTimeout(() => {
skeleton.value = false skeleton.value = false
}, 1500) }, 1500)
@ -255,50 +252,38 @@ const logoutUser = async () => {
} }
} }
const updateTransactions = async ({ currentPage, pageSize }) => { const updateTransactions = ({ currentPage, pageSize }) => {
pending.value = true pending.value = true
try { useRefetchTransactionsQuery({ currentPage, pageSize })
await loadOrFetchTransactionQuery({ currentPage, pageSize })
if (!transactionQueryResult) return
const { transactionList } = transactionQueryResult.value
GdtBalance.value =
transactionList.balance.balanceGDT === null ? 0 : Number(transactionList.balance.balanceGDT)
transactions.value = transactionList.transactions
balance.value = Number(transactionList.balance.balance)
transactionCount.value = transactionList.balance.count
transactionLinkCount.value = transactionList.balance.linkCount
pending.value = false
} catch (error) {
pending.value = true
transactionCount.value = -1
toastError(error.message)
}
} }
const loadOrFetchTransactionQuery = async (queryVariables = { currentPage: 1, pageSize: 25 }) => { onResult((value) => {
return ( if (value && value.data) {
(await useTransactionsQuery(transactionsQuery, queryVariables)) || if (value.data.transactionList) {
(await useRefetchTransactionsQuery(queryVariables)) const tr = value.data.transactionList
) GdtBalance.value = tr.balance?.balanceGDT === null ? 0 : Number(tr.balance?.balanceGDT)
} transactions.value = tr.transactions || []
balance.value = Number(tr.balance?.balance) || 0
const getCommunityStatistics = async () => { transactionCount.value = tr.balance?.count || 0
try { transactionLinkCount.value = tr.balance?.linkCount || 0
const result = await useCommunityStatsQuery() }
totalUsers.value = result.communityStatistics.totalUsers if (value.data.communityStatistics) {
} catch { totalUsers.value = value.data.communityStatistics.totalUsers || 0
toastError(t('communityStatistics has no result, use default data')) }
} }
} pending.value = false
skeleton.value = false
})
onError((error) => {
transactionCount.value = -1
toastError(error.message)
})
const admin = () => { const admin = () => {
window.location.assign(CONFIG.ADMIN_AUTH_URL + store.state.token) window.location.assign(CONFIG.ADMIN_AUTH_URL + store.state.token)
store.dispatch('logout') // logout without redirect store.dispatch('logout') // logout without redirect
} }
const setVisible = (bool) => {
visible.value = bool
}
</script> </script>
<style> <style>
.breadcrumb { .breadcrumb {

View File

@ -272,10 +272,6 @@ const handleUpdateContributionForm = (item) => {
router.push({ params: { tab: 'contribute' } }) router.push({ params: { tab: 'contribute' } })
} }
const updateTransactions = (pagination) => {
emit('update-transactions', pagination)
}
const updateStatus = (id) => { const updateStatus = (id) => {
const item = items.value.find((item) => item.id === id) const item = items.value.find((item) => item.id === id)
if (item) { if (item) {

View File

@ -72,12 +72,4 @@ onContributionLinksError(() => {
onAdminUsersError(() => { onAdminUsersError(() => {
toastError('searchAdminUsers has no result, use default data') toastError('searchAdminUsers has no result, use default data')
}) })
const updateTransactions = (pagination) => {
emit('update-transactions', pagination)
}
onMounted(() => {
updateTransactions(0)
})
</script> </script>

View File

@ -172,7 +172,4 @@ function onBack() {
function updateTransactions(pagination) { function updateTransactions(pagination) {
emit('update-transactions', pagination) emit('update-transactions', pagination)
} }
// Equivalent to created hook
updateTransactions({})
</script> </script>