useArticleDataHandler.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { noop } from 'lodash-es'
  3. import { computed, type ComputedRef, nextTick, type Ref, toValue } from 'vue'
  4. import { useTicketArticlesQuery } from '#shared/entities/ticket/graphql/queries/ticket/articles.api.ts'
  5. import { TicketArticleUpdatesDocument } from '#shared/entities/ticket/graphql/subscriptions/ticketArticlesUpdates.api.ts'
  6. import type {
  7. PageInfo,
  8. TicketArticlesQuery,
  9. TicketArticleUpdatesSubscription,
  10. TicketArticleUpdatesSubscriptionVariables,
  11. } from '#shared/graphql/types.ts'
  12. import { getApolloClient } from '#shared/server/apollo/client.ts'
  13. import { QueryHandler } from '#shared/server/apollo/handler/index.ts'
  14. import type { MaybeRefOrGetter } from '@vueuse/core'
  15. export interface AddArticleCallbackArgs {
  16. updates: TicketArticleUpdatesSubscription['ticketArticleUpdates']
  17. previousArticlesEdges: TicketArticlesQuery['articles']['edges']
  18. previousArticlesEdgesCount: number
  19. articlesQuery: unknown // :TODO type this query sustainable
  20. result: Ref<TicketArticlesQuery | undefined>
  21. allArticleLoaded: ComputedRef<boolean>
  22. refetchArticlesQuery: (pageSize: Maybe<number>) => void
  23. }
  24. export const useArticleDataHandler = (
  25. ticketId: MaybeRefOrGetter<string>,
  26. options: {
  27. pageSize: number
  28. firstArticlesCount?: MaybeRefOrGetter<number>
  29. onAddArticleCallback?: (args: AddArticleCallbackArgs) => void
  30. } = {
  31. pageSize: 20,
  32. firstArticlesCount: 5,
  33. },
  34. ) => {
  35. const graphqlTicketId = computed(() => toValue(ticketId))
  36. const firstArticlesCount = computed(() =>
  37. options.firstArticlesCount ? toValue(options.firstArticlesCount) : 5,
  38. )
  39. const articlesQuery = new QueryHandler(
  40. useTicketArticlesQuery(() => ({
  41. ticketId: graphqlTicketId.value,
  42. pageSize: options.pageSize || 20,
  43. firstArticlesCount: firstArticlesCount.value,
  44. })),
  45. )
  46. const articleResult = articlesQuery.result()
  47. const articleData = computed(() => articleResult.value)
  48. const allArticleLoaded = computed(() => {
  49. if (!articleResult.value?.articles.totalCount) return false
  50. return (
  51. articleResult.value?.articles.edges.length <
  52. articleResult.value?.articles.totalCount
  53. )
  54. })
  55. const refetchArticlesQuery = (pageSize: Maybe<number>) => {
  56. articlesQuery.refetch({
  57. ticketId: graphqlTicketId.value,
  58. pageSize,
  59. })
  60. }
  61. const isLoadingArticles = articlesQuery.loading()
  62. const adjustPageInfoAfterDeletion = (nextEndCursorEdge?: Maybe<string>) => {
  63. const newPageInfo: Pick<PageInfo, 'startCursor' | 'endCursor'> = {}
  64. if (nextEndCursorEdge) {
  65. newPageInfo.endCursor = nextEndCursorEdge
  66. } else {
  67. newPageInfo.startCursor = null
  68. newPageInfo.endCursor = null
  69. }
  70. return newPageInfo
  71. }
  72. articlesQuery.subscribeToMore<
  73. TicketArticleUpdatesSubscriptionVariables,
  74. TicketArticleUpdatesSubscription
  75. >(() => ({
  76. document: TicketArticleUpdatesDocument,
  77. variables: {
  78. ticketId: graphqlTicketId.value,
  79. },
  80. onError: noop,
  81. updateQuery(previous, { subscriptionData }) {
  82. const updates = subscriptionData.data.ticketArticleUpdates
  83. if (!previous.articles || updates.updateArticle) return previous
  84. const previousArticlesEdges = previous.articles.edges
  85. const previousArticlesEdgesCount = previousArticlesEdges.length
  86. if (updates.removeArticleId) {
  87. const edges = previousArticlesEdges.filter(
  88. (edge) => edge.node.id !== updates.removeArticleId,
  89. )
  90. const removedArticleVisible =
  91. edges.length !== previousArticlesEdgesCount
  92. if (removedArticleVisible && !allArticleLoaded.value) {
  93. refetchArticlesQuery(firstArticlesCount.value)
  94. return previous
  95. }
  96. const result = {
  97. ...previous,
  98. articles: {
  99. ...previous.articles,
  100. edges,
  101. totalCount: previous.articles.totalCount - 1,
  102. },
  103. }
  104. if (removedArticleVisible) {
  105. const nextEndCursorEdge =
  106. previousArticlesEdges[previousArticlesEdgesCount - 2]
  107. result.articles.pageInfo = {
  108. ...previous.articles.pageInfo,
  109. ...adjustPageInfoAfterDeletion(nextEndCursorEdge.cursor),
  110. }
  111. }
  112. // Trigger cache garbage collection after the returned article deletion subscription
  113. // updated the article list.
  114. nextTick(() => {
  115. getApolloClient().cache.gc()
  116. })
  117. return result
  118. }
  119. if (updates.addArticle) {
  120. options?.onAddArticleCallback?.({
  121. updates,
  122. previousArticlesEdges,
  123. previousArticlesEdgesCount,
  124. articlesQuery,
  125. result: articleResult,
  126. allArticleLoaded,
  127. refetchArticlesQuery,
  128. })
  129. }
  130. return previous
  131. },
  132. }))
  133. return {
  134. articlesQuery,
  135. articleResult,
  136. articleData,
  137. allArticleLoaded,
  138. isLoadingArticles,
  139. refetchArticlesQuery,
  140. }
  141. }