Browse Source

Maintenance: Improve handling of taskbar items when they get reordered.

Co-authored-by: Florian Liebe <fl@zammad.com>
Co-authored-by: Tobias Schäfer <ts@zammad.com>
Tobias Schäfer 7 months ago
parent
commit
31c03b93ee

+ 100 - 0
app/frontend/apps/desktop/components/UserTaskbarTabs/__tests__/UserTaskbarTabs.spec.ts

@@ -24,6 +24,7 @@ import {
   mockUserCurrentTaskbarItemListQuery,
   waitForUserCurrentTaskbarItemListQueryCalls,
 } from '#desktop/entities/user/current/graphql/queries/userCurrentTaskbarItemList.mocks.ts'
+import { getUserCurrentTaskbarItemListUpdatesSubscriptionHandler } from '#desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemListUpdates.mocks.ts'
 import { getUserCurrentTaskbarItemUpdatesSubscriptionHandler } from '#desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemUpdates.mocks.ts'
 
 import UserTaskbarTabs, { type Props } from '../UserTaskbarTabs.vue'
@@ -365,6 +366,105 @@ describe('UserTaskbarTabs.vue', () => {
     expect(wrapper.queryByText('New ticket title')).not.toBeInTheDocument()
   })
 
+  it('supports updating the tabs after they got reordered', async () => {
+    mockUserCurrentTaskbarItemListQuery({
+      userCurrentTaskbarItemList: [
+        {
+          __typename: 'UserTaskbarItem',
+          id: convertToGraphQLId('Taskbar', 1),
+          key: 'TicketCreateScreen-999',
+          callback: EnumTaskbarEntity.TicketCreate,
+          entityAccess: EnumTaskbarEntityAccess.Granted,
+          entity: {
+            __typename: 'UserTaskbarItemEntityTicketCreate',
+            uid: '999',
+            title: 'First ticket',
+            createArticleTypeKey: 'phone-in',
+          },
+          prio: 1,
+        },
+        {
+          __typename: 'UserTaskbarItem',
+          id: convertToGraphQLId('Taskbar', 2),
+          key: 'TicketCreateScreen-998',
+          callback: EnumTaskbarEntity.TicketZoom,
+          entityAccess: EnumTaskbarEntityAccess.Granted,
+          entity: {
+            __typename: 'Ticket',
+            id: convertToGraphQLId('Ticket', 42),
+            internalId: 42,
+            number: '53042',
+            title: 'Second ticket',
+            stateColorCode: EnumTicketStateColorCode.Pending,
+            state: {
+              __typename: 'TicketState',
+              name: 'pending reminder',
+            },
+          },
+          prio: 2,
+        },
+      ],
+    })
+
+    const wrapper = await renderUserTaskbarTabs()
+
+    expect(wrapper.getByText('Tabs')).toBeInTheDocument()
+
+    let tabs = wrapper.getAllByRole('listitem')
+
+    expect(tabs).toHaveLength(2)
+    expect(tabs[0]).toHaveTextContent('First ticket')
+    expect(tabs[1]).toHaveTextContent('Second ticket')
+
+    // Reorder tabs.
+    await getUserCurrentTaskbarItemListUpdatesSubscriptionHandler().trigger({
+      userCurrentTaskbarItemListUpdates: {
+        taskbarItemList: [
+          {
+            __typename: 'UserTaskbarItem',
+            id: convertToGraphQLId('Taskbar', 2),
+            key: 'TicketCreateScreen-998',
+            callback: EnumTaskbarEntity.TicketZoom,
+            entityAccess: EnumTaskbarEntityAccess.Granted,
+            entity: {
+              __typename: 'Ticket',
+              id: convertToGraphQLId('Ticket', 42),
+              internalId: 42,
+              number: '53042',
+              title: 'Second ticket',
+              stateColorCode: EnumTicketStateColorCode.Pending,
+              state: {
+                __typename: 'TicketState',
+                name: 'pending reminder',
+              },
+            },
+            prio: 1,
+          },
+          {
+            __typename: 'UserTaskbarItem',
+            id: convertToGraphQLId('Taskbar', 1),
+            key: 'TicketCreateScreen-999',
+            callback: EnumTaskbarEntity.TicketCreate,
+            entityAccess: EnumTaskbarEntityAccess.Granted,
+            entity: {
+              __typename: 'UserTaskbarItemEntityTicketCreate',
+              uid: '999',
+              title: 'First ticket',
+              createArticleTypeKey: 'phone-in',
+            },
+            prio: 2,
+          },
+        ],
+      },
+    })
+
+    tabs = wrapper.getAllByRole('listitem')
+
+    expect(tabs).toHaveLength(2)
+    expect(tabs[0]).toHaveTextContent('Second ticket')
+    expect(tabs[1]).toHaveTextContent('First ticket')
+  })
+
   it('supports closing tabs', async () => {
     // Rely on the default ticket tab from the `UserTaskbarItem` factory.
     const wrapper = await renderUserTaskbarTabs()

+ 21 - 0
app/frontend/apps/desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemListUpdates.api.ts

@@ -0,0 +1,21 @@
+import * as Types from '#shared/graphql/types.ts';
+
+import gql from 'graphql-tag';
+import { UserCurrentTaskbarItemAttributesFragmentDoc } from '../fragments/userCurrentTaskbarItemAttributes.api';
+import * as VueApolloComposable from '@vue/apollo-composable';
+import * as VueCompositionApi from 'vue';
+export type ReactiveFunction<TParam> = () => TParam;
+
+export const UserCurrentTaskbarItemListUpdatesDocument = gql`
+    subscription userCurrentTaskbarItemListUpdates($userId: ID!) {
+  userCurrentTaskbarItemListUpdates(userId: $userId) {
+    taskbarItemList {
+      ...userCurrentTaskbarItemAttributes
+    }
+  }
+}
+    ${UserCurrentTaskbarItemAttributesFragmentDoc}`;
+export function useUserCurrentTaskbarItemListUpdatesSubscription(variables: Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables | VueCompositionApi.Ref<Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables> | ReactiveFunction<Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables>, options: VueApolloComposable.UseSubscriptionOptions<Types.UserCurrentTaskbarItemListUpdatesSubscription, Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables> | VueCompositionApi.Ref<VueApolloComposable.UseSubscriptionOptions<Types.UserCurrentTaskbarItemListUpdatesSubscription, Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables>> | ReactiveFunction<VueApolloComposable.UseSubscriptionOptions<Types.UserCurrentTaskbarItemListUpdatesSubscription, Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables>> = {}) {
+  return VueApolloComposable.useSubscription<Types.UserCurrentTaskbarItemListUpdatesSubscription, Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables>(UserCurrentTaskbarItemListUpdatesDocument, variables, options);
+}
+export type UserCurrentTaskbarItemListUpdatesSubscriptionCompositionFunctionResult = VueApolloComposable.UseSubscriptionReturn<Types.UserCurrentTaskbarItemListUpdatesSubscription, Types.UserCurrentTaskbarItemListUpdatesSubscriptionVariables>;

+ 7 - 0
app/frontend/apps/desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemListUpdates.graphql

@@ -0,0 +1,7 @@
+subscription userCurrentTaskbarItemListUpdates($userId: ID!) {
+  userCurrentTaskbarItemListUpdates(userId: $userId) {
+    taskbarItemList {
+      ...userCurrentTaskbarItemAttributes
+    }
+  }
+}

+ 8 - 0
app/frontend/apps/desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemListUpdates.mocks.ts

@@ -0,0 +1,8 @@
+import * as Types from '#shared/graphql/types.ts';
+
+import * as Mocks from '#tests/graphql/builders/mocks.ts'
+import * as Operations from './userCurrentTaskbarItemListUpdates.api.ts'
+
+export function getUserCurrentTaskbarItemListUpdatesSubscriptionHandler() {
+  return Mocks.getGraphQLSubscriptionHandler<Types.UserCurrentTaskbarItemListUpdatesSubscription>(Operations.UserCurrentTaskbarItemListUpdatesDocument)
+}

+ 22 - 0
app/frontend/apps/desktop/entities/user/current/stores/taskbarTabs.ts

@@ -10,6 +10,8 @@ import {
   EnumTaskbarApp,
   EnumTaskbarEntity,
   type UserCurrentTaskbarItemListQuery,
+  type UserCurrentTaskbarItemListUpdatesSubscription,
+  type UserCurrentTaskbarItemListUpdatesSubscriptionVariables,
   type UserCurrentTaskbarItemUpdatesSubscription,
   type UserCurrentTaskbarItemUpdatesSubscriptionVariables,
 } from '#shared/graphql/types.ts'
@@ -33,6 +35,7 @@ import {
   UserCurrentTaskbarItemListDocument,
   useUserCurrentTaskbarItemListQuery,
 } from '../graphql/queries/userCurrentTaskbarItemList.api.ts'
+import { UserCurrentTaskbarItemListUpdatesDocument } from '../graphql/subscriptions/userCurrentTaskbarItemListUpdates.api.ts'
 import { UserCurrentTaskbarItemUpdatesDocument } from '../graphql/subscriptions/userCurrentTaskbarItemUpdates.api.ts'
 
 import type { TaskbarTabContext } from '../types.ts'
@@ -140,6 +143,25 @@ export const useUserCurrentTaskbarTabsStore = defineStore(
       },
     })
 
+    taskbarTabsQuery.subscribeToMore<
+      UserCurrentTaskbarItemListUpdatesSubscriptionVariables,
+      UserCurrentTaskbarItemListUpdatesSubscription
+    >({
+      document: UserCurrentTaskbarItemListUpdatesDocument,
+      variables: {
+        userId: session.userId,
+      },
+      updateQuery(previous, { subscriptionData }) {
+        const updates = subscriptionData.data.userCurrentTaskbarItemListUpdates
+
+        if (!updates) return previous
+
+        return {
+          userCurrentTaskbarItemList: updates.taskbarItemList,
+        }
+      },
+    })
+
     const taskbarTabsRaw = taskbarTabsQuery.result()
     const taskbarTabsLoading = taskbarTabsQuery.loading()
 

+ 2 - 0
app/frontend/apps/desktop/pages/authentication/__tests__/login-maintenance.spec.ts

@@ -21,6 +21,7 @@ import { ConfigUpdatesDocument } from '#shared/graphql/subscriptions/configUpdat
 import { useApplicationStore } from '#shared/stores/application.ts'
 import { useAuthenticationStore } from '#shared/stores/authentication.ts'
 
+import { UserCurrentTaskbarItemListUpdatesDocument } from '#desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemListUpdates.api.ts'
 import { UserCurrentTaskbarItemUpdatesDocument } from '#desktop/entities/user/current/graphql/subscriptions/userCurrentTaskbarItemUpdates.api.ts'
 
 // TODO: import '#tests/graphql/builders/mocks.ts'
@@ -38,6 +39,7 @@ beforeEach(() => {
   mockPublicLinksSubscription()
   mockApplicationConfig({ product_name: 'Zammad' })
   mockGraphQLSubscription(UserCurrentTaskbarItemUpdatesDocument)
+  mockGraphQLSubscription(UserCurrentTaskbarItemListUpdatesDocument)
 })
 
 describe('testing login maintenance mode', () => {

+ 22 - 0
app/frontend/shared/graphql/types.ts

@@ -2600,6 +2600,8 @@ export type Subscriptions = {
   userCurrentDevicesUpdates: UserCurrentDevicesUpdatesPayload;
   /** Updates to account overview sorting records */
   userCurrentOverviewOrderingUpdates: UserCurrentOverviewOrderingUpdatesPayload;
+  /** Subscription for taskbar item list priority changes */
+  userCurrentTaskbarItemListUpdates: UserCurrentTaskbarItemListUpdatesPayload;
   /** Changes to the state of a taskbar item of the currently logged-in user */
   userCurrentTaskbarItemStateUpdates: UserCurrentTaskbarItemStateUpdatesPayload;
   /** Changes to the list of taskbar items of the currently logged-in user */
@@ -2686,6 +2688,12 @@ export type SubscriptionsUserCurrentOverviewOrderingUpdatesArgs = {
 };
 
 
+/** All available subscriptions */
+export type SubscriptionsUserCurrentTaskbarItemListUpdatesArgs = {
+  userId: Scalars['ID']['input'];
+};
+
+
 /** All available subscriptions */
 export type SubscriptionsUserCurrentTaskbarItemStateUpdatesArgs = {
   taskbarItemId: Scalars['ID']['input'];
@@ -3941,6 +3949,13 @@ export type UserCurrentTaskbarItemListPrioPayload = {
   success?: Maybe<Scalars['Boolean']['output']>;
 };
 
+/** Autogenerated return type of UserCurrentTaskbarItemListUpdates. */
+export type UserCurrentTaskbarItemListUpdatesPayload = {
+  __typename?: 'UserCurrentTaskbarItemListUpdatesPayload';
+  /** List of taskbar items */
+  taskbarItemList?: Maybe<Array<UserTaskbarItem>>;
+};
+
 /** Autogenerated return type of UserCurrentTaskbarItemStateUpdates. */
 export type UserCurrentTaskbarItemStateUpdatesPayload = {
   __typename?: 'UserCurrentTaskbarItemStateUpdatesPayload';
@@ -4513,6 +4528,13 @@ export type UserCurrentTaskbarItemListQueryVariables = Exact<{
 
 export type UserCurrentTaskbarItemListQuery = { __typename?: 'Queries', userCurrentTaskbarItemList?: Array<{ __typename?: 'UserTaskbarItem', id: string, key: string, callback: EnumTaskbarEntity, formId?: string | null, entityAccess?: EnumTaskbarEntityAccess | null, prio: number, lastContact: string, dirty: boolean, entity?: { __typename?: 'Organization', id: string, internalId: number } | { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', name: string } } | { __typename?: 'User', id: string, internalId: number } | { __typename?: 'UserTaskbarItemEntityTicketCreate', uid: string, title: string, createArticleTypeKey?: string | null } | null }> | null };
 
+export type UserCurrentTaskbarItemListUpdatesSubscriptionVariables = Exact<{
+  userId: Scalars['ID']['input'];
+}>;
+
+
+export type UserCurrentTaskbarItemListUpdatesSubscription = { __typename?: 'Subscriptions', userCurrentTaskbarItemListUpdates: { __typename?: 'UserCurrentTaskbarItemListUpdatesPayload', taskbarItemList?: Array<{ __typename?: 'UserTaskbarItem', id: string, key: string, callback: EnumTaskbarEntity, formId?: string | null, entityAccess?: EnumTaskbarEntityAccess | null, prio: number, lastContact: string, dirty: boolean, entity?: { __typename?: 'Organization', id: string, internalId: number } | { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', name: string } } | { __typename?: 'User', id: string, internalId: number } | { __typename?: 'UserTaskbarItemEntityTicketCreate', uid: string, title: string, createArticleTypeKey?: string | null } | null }> | null } };
+
 export type UserCurrentTaskbarItemStateUpdatesSubscriptionVariables = Exact<{
   taskbarItemId: Scalars['ID']['input'];
 }>;

+ 4 - 2
app/graphql/gql/mutations/user/current/taskbar_item/list_prio.rb

@@ -10,16 +10,18 @@ module Gql::Mutations
     field :success, Boolean, description: 'This indicates if sorting the taskbar item list was successful.'
 
     def resolve(list:)
+      prio = []
       list.each do |item|
         begin
           taskbar_item = Gql::ZammadSchema.verified_object_from_id(item.id, type: Taskbar)
+          prio << { id: taskbar_item.id, prio: item.prio }
         rescue ActiveRecord::RecordNotFound
           next
         end
-
-        taskbar_item.update!(prio: item.prio)
       end
 
+      Taskbar.reorder_list(context.current_user, prio)
+
       { success: true }
     end
   end

+ 1 - 4
app/graphql/gql/queries/user/current/taskbar_item/list.rb

@@ -10,10 +10,7 @@ module Gql::Queries
     type [Gql::Types::User::TaskbarItemType], null: true
 
     def resolve(app: nil)
-      clause = { user_id: context[:current_user].id }
-      clause[:app] = app if app
-
-      Taskbar.where(clause).reorder(:prio)
+      Taskbar.list(context[:current_user], app: app)
     end
   end
 end

+ 21 - 0
app/graphql/gql/subscriptions/user/current/taskbar_item/list_updates.rb

@@ -0,0 +1,21 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+module Gql::Subscriptions
+  class User::Current::TaskbarItem::ListUpdates < BaseSubscription
+
+    description 'Subscription for taskbar item list priority changes'
+
+    argument :user_id, GraphQL::Types::ID, loads: Gql::Types::UserType, description: 'Filter by user'
+
+    field :taskbar_item_list, [Gql::Types::User::TaskbarItemType], description: 'List of taskbar items'
+
+    def authorized?(user:)
+      user == context.current_user
+    end
+
+    def update(user:)
+      { taskbar_item_list: Taskbar.list(user) }
+    end
+
+  end
+end

Some files were not shown because too many files changed in this diff