useArticleDataHandler.ts 4.7 KB

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