useTicketLiveUser.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { noop } from '@vueuse/shared'
  3. import { type Ref, type ComputedRef, onBeforeMount } from 'vue'
  4. import { ref, watch } from 'vue'
  5. import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
  6. import { useAppName } from '#shared/composables/useAppName.ts'
  7. import type { TicketLiveAppUser } from '#shared/entities/ticket/types.ts'
  8. import { EnumTaskbarApp } from '#shared/graphql/types.ts'
  9. import type { TicketLiveUser } from '#shared/graphql/types.ts'
  10. import { ensureGraphqlId } from '#shared/graphql/utils.ts'
  11. import {
  12. MutationHandler,
  13. SubscriptionHandler,
  14. } from '#shared/server/apollo/handler/index.ts'
  15. import { useSessionStore } from '#shared/stores/session.ts'
  16. import { useTicketLiveUserDeleteMutation } from '../graphql/mutations/live-user/delete.api.ts'
  17. import { useTicketLiveUserUpsertMutation } from '../graphql/mutations/live-user/ticketLiveUserUpsert.api.ts'
  18. import { useTicketLiveUserUpdatesSubscription } from '../graphql/subscriptions/live-user/ticketLiveUserUpdates.api.ts'
  19. export const useTicketLiveUser = (
  20. ticketInternalId: Ref<string>,
  21. isTicketAgent: ComputedRef<boolean>,
  22. editingForm: ComputedRef<boolean>,
  23. ) => {
  24. const liveUserList = ref<TicketLiveAppUser[]>([])
  25. const upsertMutation = new MutationHandler(useTicketLiveUserUpsertMutation())
  26. const deleteMutation = new MutationHandler(useTicketLiveUserDeleteMutation())
  27. const { userId } = useSessionStore()
  28. const updateLiveUser = async (ticketInternalId: string, editing = false) => {
  29. await upsertMutation
  30. .send({
  31. id: ensureGraphqlId('Ticket', ticketInternalId),
  32. editing,
  33. app: EnumTaskbarApp.Mobile,
  34. })
  35. .catch(noop)
  36. }
  37. const deleteLiveUser = async (ticketInternalId: string) => {
  38. await deleteMutation
  39. .send({
  40. id: ensureGraphqlId('Ticket', ticketInternalId),
  41. app: EnumTaskbarApp.Mobile,
  42. })
  43. .catch(noop)
  44. }
  45. const appName = useAppName()
  46. const updateLiveUserList = (liveUsers: TicketLiveUser[]) => {
  47. const mappedLiveUsers: TicketLiveAppUser[] = []
  48. liveUsers.forEach((liveUser) => {
  49. let appItems = liveUser.apps.filter((data) => data.editing)
  50. // Skip own live user item, when it's holds only the current app and is not editing on the other one.
  51. if (liveUser.user.id === userId) {
  52. if (appItems.length === 0) return
  53. appItems = appItems.filter((item) => item.name !== appName)
  54. if (appItems.length === 0) return
  55. }
  56. if (appItems.length === 0) {
  57. appItems = liveUser.apps
  58. }
  59. // Sort app items by last interaction.
  60. appItems.sort((a, b) => {
  61. return (
  62. new Date(b.lastInteraction).getTime() -
  63. new Date(a.lastInteraction).getTime()
  64. )
  65. })
  66. mappedLiveUsers.push({
  67. user: liveUser.user,
  68. ...appItems[0],
  69. app: appItems[0].name,
  70. })
  71. })
  72. return mappedLiveUsers
  73. }
  74. const liveUserSubscription = new SubscriptionHandler(
  75. useTicketLiveUserUpdatesSubscription(
  76. () => ({
  77. userId,
  78. key: `Ticket-${ticketInternalId.value}`,
  79. app: EnumTaskbarApp.Mobile,
  80. }),
  81. () => ({
  82. // We need to disable the cache here, because otherwise we have the following error, when
  83. // a ticket is open again which is already in the subscription cache:
  84. // "ApolloError: 'get' on proxy: property 'liveUsers' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '[object Array]' but got '[object Array]')"
  85. // At the end a cache for the subscription is not really needed, but we should create an issue on
  86. // apollo client side, when we have a minimal reproduction.
  87. fetchPolicy: 'no-cache',
  88. enabled: isTicketAgent.value,
  89. }),
  90. ),
  91. )
  92. liveUserSubscription.onResult((result) => {
  93. liveUserList.value = updateLiveUserList(
  94. (result.data?.ticketLiveUserUpdates.liveUsers as TicketLiveUser[]) || [],
  95. )
  96. })
  97. onBeforeRouteUpdate(async (to, from) => {
  98. const internalToId = to.params.internalId as string
  99. const internalFromId = from.params.internalId as string
  100. // update status when opening another ticket page without unmounting the page and don't block the page
  101. if (internalToId !== internalFromId) {
  102. liveUserList.value = []
  103. deleteLiveUser(internalFromId)
  104. updateLiveUser(internalToId)
  105. }
  106. })
  107. onBeforeRouteLeave(async (_, from) => {
  108. const internalId = from.params.internalId as string
  109. // update status when leaving to non-ticket page, but don't block the page
  110. deleteLiveUser(internalId)
  111. })
  112. onBeforeMount(async () => {
  113. // update status on opening the page. it is possible that this code will run,
  114. // when user doesn't have access to the ticket, because we fail after the route is rendered
  115. await updateLiveUser(ticketInternalId.value)
  116. })
  117. // Update live user editing status, when can submit value changes
  118. watch(editingForm, async (canSubmit) => {
  119. await updateLiveUser(ticketInternalId.value, canSubmit)
  120. })
  121. return {
  122. liveUserList,
  123. }
  124. }