Browse Source

Fix: Mobile - Hide priority column in overview for customers

Vladimir Sheremet 2 years ago
parent
commit
d54c2eb610

+ 2 - 2
app/frontend/apps/mobile/entities/ticket/helpers/ticketOverviewStorage.ts

@@ -1,9 +1,9 @@
 // Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
 
-import useSession from '@shared/stores/session'
+import { useSessionStore } from '@shared/stores/session'
 
 export const getTicketOverviewStorage = () => {
-  const session = useSession()
+  const session = useSessionStore()
 
   const LOCAL_STORAGE_NAME = `ticket-overviews-${session.user?.id || '1'}`
 

+ 7 - 16
app/frontend/apps/mobile/modules/ticket/__tests__/mocks/detail-view.ts

@@ -20,7 +20,7 @@ export const defaultTicket = () =>
   nullableMock<TicketQuery>({
     ticket: {
       __typename: 'Ticket',
-      id: '1fs432fdsfsg3qr32d',
+      id: '1fssfsg3qr32d',
       internalId: 1,
       number: '610001',
       title: 'Test Ticket View',
@@ -73,15 +73,6 @@ const address = {
   raw: '',
 }
 
-const nullableArticle = {
-  __typename: 'TicketArticle' as const,
-  references: null,
-  inReplyTo: null,
-  messageIdMd5: null,
-  messageId: null,
-  subject: null,
-}
-
 export const defaultArticles = (): TicketArticlesQuery =>
   nullableMock({
     description: {
@@ -90,8 +81,8 @@ export const defaultArticles = (): TicketArticlesQuery =>
         {
           __typename: 'TicketArticleEdge',
           node: {
-            ...nullableArticle,
-            id: '1fs432fczfsg3qr32d',
+            __typename: 'TicketArticle',
+            id: '1fs4323qr32d',
             internalId: 1,
             createdAt: ticketDate.toISOString(),
             to: address,
@@ -141,8 +132,8 @@ export const defaultArticles = (): TicketArticlesQuery =>
         {
           __typename: 'TicketArticleEdge',
           node: {
-            ...nullableArticle,
-            id: '1fs432fdsfsg3qr32f',
+            __typename: 'TicketArticle',
+            id: '1fs432fdsfsg3',
             internalId: 2,
             to: address,
             replyTo: address,
@@ -175,8 +166,8 @@ export const defaultArticles = (): TicketArticlesQuery =>
         {
           __typename: 'TicketArticleEdge',
           node: {
-            ...nullableArticle,
-            id: '1fs432fdsfsg3qr30f',
+            __typename: 'TicketArticle',
+            id: '1fs4sfsg3qr30f',
             internalId: 3,
             to: address,
             replyTo: address,

+ 57 - 31
app/frontend/apps/mobile/modules/ticket/__tests__/mocks/overview.ts

@@ -3,7 +3,7 @@
 import type { TicketsByOverviewQuery } from '@shared/graphql/types'
 import type { ConfidentTake } from '@shared/types/utils'
 import { mockGraphQLApi } from '@tests/support/mock-graphql-api'
-import { mock } from 'vitest-mock-extended'
+import { nullableMock } from '@tests/support/utils'
 import { TicketsByOverviewDocument } from '../../graphql/queries/ticketsByOverview.api'
 
 type TicketItemByOverview = ConfidentTake<
@@ -18,40 +18,66 @@ type TicketByOverviewPageInfo = ConfidentTake<
 
 const ticketDate = new Date(2022, 0, 29, 0, 0, 0, 0)
 
-export const ticketDefault: Partial<TicketItemByOverview> = {
-  id: 'af12',
-  title: 'Ticket 1',
-  number: '63001',
-  internalId: 1,
-  createdAt: ticketDate.toISOString(),
-  priority: {
-    id: 'fdsf214fse12e',
-    name: 'high',
-    defaultCreate: false,
-  },
-  customer: {
-    id: 'fdsf214fse12d',
-    firstname: 'John',
-    lastname: 'Doe',
-    fullname: 'John Doe',
-  },
-}
+export const ticketDefault = () =>
+  nullableMock<TicketItemByOverview>({
+    __typename: 'Ticket',
+    id: 'af12',
+    title: 'Ticket 1',
+    number: '63001',
+    internalId: 1,
+    createdAt: ticketDate.toISOString(),
+    updatedAt: ticketDate.toISOString(),
+    state: {
+      __typename: 'TicketState',
+      id: 'fsa234dsad2',
+      name: 'open',
+      stateType: {
+        __typename: 'TicketStateType',
+        name: 'open',
+      },
+    },
+    priority: {
+      __typename: 'TicketPriority',
+      id: 'fdsf214fse12e',
+      name: 'high',
+      defaultCreate: false,
+    },
+    group: {
+      __typename: 'Group',
+      id: 'asc234d',
+      name: 'open',
+    },
+    customer: {
+      __typename: 'User',
+      id: 'fdsf214fse12d',
+      firstname: 'John',
+      lastname: 'Doe',
+      fullname: 'John Doe',
+    },
+  })
 
 export const mockTicketsByOverview = (
-  tickets: Partial<TicketItemByOverview>[] = [ticketDefault],
+  tickets: Partial<TicketItemByOverview>[] = [ticketDefault()],
   pageInfo: Partial<TicketByOverviewPageInfo> = {},
   totalCount: number | null = null,
 ) => {
-  return mockGraphQLApi(TicketsByOverviewDocument).willResolve(
-    mock<TicketsByOverviewQuery>(
-      {
-        ticketsByOverview: {
-          totalCount: totalCount ?? tickets.length,
-          edges: tickets.map((node) => ({ node })),
-          pageInfo,
-        },
+  return mockGraphQLApi(
+    TicketsByOverviewDocument,
+  ).willResolve<TicketsByOverviewQuery>({
+    ticketsByOverview: {
+      __typename: 'TicketConnection',
+      totalCount: totalCount ?? tickets.length,
+      edges: tickets.map((node, index) => ({
+        __typename: 'TicketEdge',
+        node: nullableMock(node) as TicketItemByOverview,
+        cursor: `node${index}`,
+      })),
+      pageInfo: {
+        __typename: 'PageInfo',
+        hasNextPage: true,
+        endCursor: 'node1',
+        ...pageInfo,
       },
-      { deep: true },
-    ),
-  )
+    },
+  })
 }

+ 2 - 2
app/frontend/apps/mobile/modules/ticket/__tests__/tickets-view.spec.ts

@@ -146,7 +146,7 @@ it('takes filter from query', async () => {
 })
 
 it('pagination loads additional list', async () => {
-  const ticketOverviewsApi = mockTicketsByOverview([ticketDefault], {
+  const ticketOverviewsApi = mockTicketsByOverview([ticketDefault()], {
     hasNextPage: true,
     endCursor: 'cursor',
   })
@@ -170,7 +170,7 @@ it('pagination loads additional list', async () => {
 })
 
 it("pagination doesn't load if it is already loading more", async () => {
-  const ticketOverviewsApi = mockTicketsByOverview([ticketDefault], {
+  const ticketOverviewsApi = mockTicketsByOverview([ticketDefault()], {
     hasNextPage: true,
     endCursor: 'cursor',
   })

+ 3 - 0
app/frontend/apps/mobile/modules/ticket/components/TicketList/TicketList.vue

@@ -18,6 +18,7 @@ interface Props {
   overviewId: string
   maxCount: number
   orderBy: string
+  hiddenColumns: string[]
   orderDirection: EnumOrderDirection
 }
 
@@ -29,6 +30,8 @@ const ticketsQuery = new QueryHandler(
       overviewId: props.overviewId,
       orderBy: props.orderBy,
       orderDirection: props.orderDirection,
+      showUpdatedBy: !props.hiddenColumns.includes('updated_by'),
+      showPriority: !props.hiddenColumns.includes('priority'),
     }
   }),
 )

+ 2 - 1
app/frontend/apps/mobile/modules/ticket/components/TicketList/__tests__/TicketList.spec.ts

@@ -12,7 +12,7 @@ import TicketList from '../TicketList.vue'
 describe('testing a list of tickets', () => {
   it('shows warning when all available tickets are loaded', async () => {
     const ticketOverviewsApi = mockTicketsByOverview(
-      [ticketDefault],
+      [ticketDefault()],
       {
         hasNextPage: true,
         endCursor: 'cursor',
@@ -26,6 +26,7 @@ describe('testing a list of tickets', () => {
         overviewId: '1f',
         orderBy: 'name',
         orderDirection: EnumOrderDirection.Ascending,
+        hiddenColumns: [],
       },
       router: true,
       store: true,

+ 3 - 5
app/frontend/apps/mobile/modules/ticket/graphql/queries/ticketsByOverview.api.ts

@@ -7,7 +7,7 @@ import * as VueCompositionApi from 'vue';
 export type ReactiveFunction<TParam> = () => TParam;
 
 export const TicketsByOverviewDocument = gql`
-    query ticketsByOverview($overviewId: ID!, $orderBy: String, $orderDirection: EnumOrderDirection, $cursor: String, $pageSize: Int = 10, $withObjectAttributes: Boolean = false) {
+    query ticketsByOverview($overviewId: ID!, $orderBy: String, $orderDirection: EnumOrderDirection, $cursor: String, $showPriority: Boolean!, $showUpdatedBy: Boolean!, $pageSize: Int = 10, $withObjectAttributes: Boolean = false) {
   ticketsByOverview(
     overviewId: $overviewId
     orderBy: $orderBy
@@ -24,10 +24,8 @@ export const TicketsByOverviewDocument = gql`
         title
         createdAt
         updatedAt
-        owner {
+        updatedBy @include(if: $showUpdatedBy) {
           id
-          firstname
-          lastname
           fullname
         }
         customer {
@@ -51,7 +49,7 @@ export const TicketsByOverviewDocument = gql`
           id
           name
         }
-        priority {
+        priority @include(if: $showPriority) {
           id
           name
           uiColor

+ 4 - 4
app/frontend/apps/mobile/modules/ticket/graphql/queries/ticketsByOverview.graphql

@@ -3,6 +3,8 @@ query ticketsByOverview(
   $orderBy: String
   $orderDirection: EnumOrderDirection
   $cursor: String
+  $showPriority: Boolean!
+  $showUpdatedBy: Boolean!
   $pageSize: Int = 10
   $withObjectAttributes: Boolean = false
 ) {
@@ -22,10 +24,8 @@ query ticketsByOverview(
         title
         createdAt
         updatedAt
-        owner {
+        updatedBy @include(if: $showUpdatedBy) {
           id
-          firstname
-          lastname
           fullname
         }
         customer {
@@ -49,7 +49,7 @@ query ticketsByOverview(
           id
           name
         }
-        priority {
+        priority @include(if: $showPriority) {
           id
           name
           uiColor

+ 14 - 0
app/frontend/apps/mobile/modules/ticket/views/TicketOverview.vue

@@ -9,6 +9,7 @@ import { EnumOrderDirection } from '@shared/graphql/types'
 import CommonLoader from '@mobile/components/CommonLoader/CommonLoader.vue'
 import { useTicketsOverviews } from '@mobile/entities/ticket/stores/ticketOverviews'
 import CommonSelect from '@mobile/components/CommonSelect/CommonSelect.vue'
+import { useSessionStore } from '@shared/stores/session'
 import { useRouteQuery } from '@vueuse/router'
 import { storeToRefs } from 'pinia'
 import TicketList from '../components/TicketList/TicketList.vue'
@@ -44,6 +45,18 @@ const selectedOverviewLink = computed(() => {
   return selectedOverview.value?.link || null
 })
 
+const session = useSessionStore()
+
+const hiddenColumns = computed(() => {
+  if (session.hasPermission(['ticket.agent'])) return []
+  const viewColumns =
+    selectedOverview.value?.viewColumns.map((column) => column.key) || []
+  // show priority only if it is specified in overview
+  return ['priority', 'updated_by'].filter(
+    (name) => !viewColumns.includes(name),
+  )
+})
+
 const selectOverview = (link: string) => {
   const { query } = route
   return router.replace({ path: `/tickets/view/${link}`, query })
@@ -269,6 +282,7 @@ const directionOptions = computed(() => [
         :order-by="orderBy"
         :order-direction="orderDirection"
         :max-count="MAX_COUNT_TICKETS"
+        :hidden-columns="hiddenColumns"
       />
     </CommonLoader>
     <div v-else class="flex items-center justify-center gap-2 p-4 text-center">

+ 3 - 1
app/frontend/shared/graphql/types.ts

@@ -1115,12 +1115,14 @@ export type TicketsByOverviewQueryVariables = Exact<{
   orderBy?: InputMaybe<Scalars['String']>;
   orderDirection?: InputMaybe<EnumOrderDirection>;
   cursor?: InputMaybe<Scalars['String']>;
+  showPriority: Scalars['Boolean'];
+  showUpdatedBy: Scalars['Boolean'];
   pageSize?: InputMaybe<Scalars['Int']>;
   withObjectAttributes?: InputMaybe<Scalars['Boolean']>;
 }>;
 
 
-export type TicketsByOverviewQuery = { __typename?: 'Queries', ticketsByOverview: { __typename?: 'TicketConnection', totalCount: number, edges: Array<{ __typename?: 'TicketEdge', cursor: string, node: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: any, updatedAt: any, owner: { __typename?: 'User', id: string, firstname?: string | null, lastname?: string | null, fullname?: string | null }, customer: { __typename?: 'User', id: string, firstname?: string | null, lastname?: string | null, fullname?: string | null }, organization?: { __typename?: 'Organization', id: string, name: string } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', name: string } }, group: { __typename?: 'Group', id: string, name: string }, priority: { __typename?: 'TicketPriority', id: string, name: string, uiColor?: string | null, defaultCreate: boolean }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string, dataType: string, dataOption?: any | null } }> } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean } } };
+export type TicketsByOverviewQuery = { __typename?: 'Queries', ticketsByOverview: { __typename?: 'TicketConnection', totalCount: number, edges: Array<{ __typename?: 'TicketEdge', cursor: string, node: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: any, updatedAt: any, updatedBy?: { __typename?: 'User', id: string, fullname?: string | null }, customer: { __typename?: 'User', id: string, firstname?: string | null, lastname?: string | null, fullname?: string | null }, organization?: { __typename?: 'Organization', id: string, name: string } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', name: string } }, group: { __typename?: 'Group', id: string, name: string }, priority?: { __typename?: 'TicketPriority', id: string, name: string, uiColor?: string | null, defaultCreate: boolean }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string, dataType: string, dataOption?: any | null } }> } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean } } };
 
 export type TicketUpdatesSubscriptionVariables = Exact<{
   ticketId: Scalars['ID'];

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