Browse Source

Feature: Mobile - Improved ticket query.

Martin Gruner 2 years ago
parent
commit
0083ccec86

+ 64 - 0
app/frontend/apps/mobile/modules/ticket/graphql/queries/ticket.api.ts

@@ -0,0 +1,64 @@
+import * as Types from '../../../../../../shared/graphql/types';
+
+import gql from 'graphql-tag';
+import { ObjectAttributeValuesFragmentDoc } from '../../../../../../shared/graphql/fragments/objectAttributeValues.api';
+import * as VueApolloComposable from '@vue/apollo-composable';
+import * as VueCompositionApi from 'vue';
+export type ReactiveFunction<TParam> = () => TParam;
+
+export const TicketDocument = gql`
+    query ticket($ticketId: ID, $ticketInternalId: Int, $ticketNumber: String, $withArticles: Boolean = false, $withObjectAttributes: Boolean = false) {
+  ticket(
+    ticketId: $ticketId
+    ticketInternalId: $ticketInternalId
+    ticketNumber: $ticketNumber
+  ) {
+    id
+    internalId
+    number
+    title
+    createdAt
+    updatedAt
+    owner {
+      firstname
+      lastname
+    }
+    customer {
+      firstname
+      lastname
+    }
+    organization {
+      name
+    }
+    state {
+      name
+      stateType {
+        name
+      }
+    }
+    group {
+      name
+    }
+    priority {
+      name
+    }
+    articles @include(if: $withArticles) {
+      edges {
+        node {
+          subject
+        }
+      }
+    }
+    objectAttributeValues @include(if: $withObjectAttributes) {
+      ...objectAttributeValues
+    }
+  }
+}
+    ${ObjectAttributeValuesFragmentDoc}`;
+export function useTicketQuery(variables: Types.TicketQueryVariables | VueCompositionApi.Ref<Types.TicketQueryVariables> | ReactiveFunction<Types.TicketQueryVariables> = {}, options: VueApolloComposable.UseQueryOptions<Types.TicketQuery, Types.TicketQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.TicketQuery, Types.TicketQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.TicketQuery, Types.TicketQueryVariables>> = {}) {
+  return VueApolloComposable.useQuery<Types.TicketQuery, Types.TicketQueryVariables>(TicketDocument, variables, options);
+}
+export function useTicketLazyQuery(variables: Types.TicketQueryVariables | VueCompositionApi.Ref<Types.TicketQueryVariables> | ReactiveFunction<Types.TicketQueryVariables> = {}, options: VueApolloComposable.UseQueryOptions<Types.TicketQuery, Types.TicketQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.TicketQuery, Types.TicketQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.TicketQuery, Types.TicketQueryVariables>> = {}) {
+  return VueApolloComposable.useLazyQuery<Types.TicketQuery, Types.TicketQueryVariables>(TicketDocument, variables, options);
+}
+export type TicketQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<Types.TicketQuery, Types.TicketQueryVariables>;

+ 6 - 3
app/frontend/apps/mobile/modules/ticket/graphql/queries/ticketById.graphql → app/frontend/apps/mobile/modules/ticket/graphql/queries/ticket.graphql

@@ -1,10 +1,13 @@
-query ticketsById(
-  $ticketId: ID!
+query ticket(
+  $ticketId: ID
+  $ticketInternalId: Int
+  $ticketNumber: String
   $withArticles: Boolean = false
   $withObjectAttributes: Boolean = false
 ) {
-  ticketById(ticketId: $ticketId) {
+  ticket(ticketId: $ticketId, ticketInternalId: $ticketInternalId, ticketNumber: $ticketNumber) {
     id
+    internalId
     number
     title
     createdAt

+ 51 - 0
app/frontend/apps/mobile/modules/ticket/graphql/queries/ticket/articles.api.ts

@@ -0,0 +1,51 @@
+import * as Types from '../../../../../../../shared/graphql/types';
+
+import gql from 'graphql-tag';
+import * as VueApolloComposable from '@vue/apollo-composable';
+import * as VueCompositionApi from 'vue';
+export type ReactiveFunction<TParam> = () => TParam;
+
+export const TicketArticlesDocument = gql`
+    query ticketArticles($ticketId: ID!) {
+  ticketArticles(ticketId: $ticketId) {
+    totalCount
+    edges {
+      node {
+        id
+        from
+        to
+        cc
+        subject
+        replyTo
+        messageId
+        messageIdMd5
+        inReplyTo
+        contentType
+        references
+        body
+        internal
+        createdAt
+        updatedAt
+        type {
+          name
+        }
+        sender {
+          name
+        }
+      }
+      cursor
+    }
+    pageInfo {
+      endCursor
+      hasNextPage
+    }
+  }
+}
+    `;
+export function useTicketArticlesQuery(variables: Types.TicketArticlesQueryVariables | VueCompositionApi.Ref<Types.TicketArticlesQueryVariables> | ReactiveFunction<Types.TicketArticlesQueryVariables>, options: VueApolloComposable.UseQueryOptions<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables>> = {}) {
+  return VueApolloComposable.useQuery<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables>(TicketArticlesDocument, variables, options);
+}
+export function useTicketArticlesLazyQuery(variables: Types.TicketArticlesQueryVariables | VueCompositionApi.Ref<Types.TicketArticlesQueryVariables> | ReactiveFunction<Types.TicketArticlesQueryVariables>, options: VueApolloComposable.UseQueryOptions<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables>> = {}) {
+  return VueApolloComposable.useLazyQuery<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables>(TicketArticlesDocument, variables, options);
+}
+export type TicketArticlesQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<Types.TicketArticlesQuery, Types.TicketArticlesQueryVariables>;

+ 37 - 0
app/frontend/apps/mobile/modules/ticket/graphql/queries/ticket/articles.graphql

@@ -0,0 +1,37 @@
+query ticketArticles(
+  $ticketId: ID!
+) {
+  ticketArticles(ticketId: $ticketId) {
+    totalCount
+    edges {
+      node {
+        id
+        from
+        to
+        cc
+        subject
+        replyTo
+        messageId
+        messageIdMd5
+        inReplyTo
+        contentType
+        references
+        body
+        internal
+        createdAt
+        updatedAt
+        type {
+          name
+        }
+        sender {
+          name
+        }
+      }
+      cursor
+    }
+    pageInfo {
+      endCursor
+      hasNextPage
+    }
+  }
+}

+ 0 - 59
app/frontend/apps/mobile/modules/ticket/graphql/queries/ticketById.api.ts

@@ -1,59 +0,0 @@
-import * as Types from '../../../../../../shared/graphql/types';
-
-import gql from 'graphql-tag';
-import { ObjectAttributeValuesFragmentDoc } from '../../../../../../shared/graphql/fragments/objectAttributeValues.api';
-import * as VueApolloComposable from '@vue/apollo-composable';
-import * as VueCompositionApi from 'vue';
-export type ReactiveFunction<TParam> = () => TParam;
-
-export const TicketsByIdDocument = gql`
-    query ticketsById($ticketId: ID!, $withArticles: Boolean = false, $withObjectAttributes: Boolean = false) {
-  ticketById(ticketId: $ticketId) {
-    id
-    number
-    title
-    createdAt
-    updatedAt
-    owner {
-      firstname
-      lastname
-    }
-    customer {
-      firstname
-      lastname
-    }
-    organization {
-      name
-    }
-    state {
-      name
-      stateType {
-        name
-      }
-    }
-    group {
-      name
-    }
-    priority {
-      name
-    }
-    articles @include(if: $withArticles) {
-      edges {
-        node {
-          subject
-        }
-      }
-    }
-    objectAttributeValues @include(if: $withObjectAttributes) {
-      ...objectAttributeValues
-    }
-  }
-}
-    ${ObjectAttributeValuesFragmentDoc}`;
-export function useTicketsByIdQuery(variables: Types.TicketsByIdQueryVariables | VueCompositionApi.Ref<Types.TicketsByIdQueryVariables> | ReactiveFunction<Types.TicketsByIdQueryVariables>, options: VueApolloComposable.UseQueryOptions<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables>> = {}) {
-  return VueApolloComposable.useQuery<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables>(TicketsByIdDocument, variables, options);
-}
-export function useTicketsByIdLazyQuery(variables: Types.TicketsByIdQueryVariables | VueCompositionApi.Ref<Types.TicketsByIdQueryVariables> | ReactiveFunction<Types.TicketsByIdQueryVariables>, options: VueApolloComposable.UseQueryOptions<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables>> = {}) {
-  return VueApolloComposable.useLazyQuery<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables>(TicketsByIdDocument, variables, options);
-}
-export type TicketsByIdQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<Types.TicketsByIdQuery, Types.TicketsByIdQueryVariables>;

+ 47 - 5
app/frontend/shared/graphql/types.ts

@@ -373,7 +373,9 @@ export type Queries = {
   /** The sessionId of the currently authenticated user. */
   sessionId: Scalars['String'];
   /** Fetch a ticket by ID */
-  ticketById: Ticket;
+  ticket: Ticket;
+  /** Fetch a ticket by ID */
+  ticketArticles: TicketArticleConnection;
   /** Fetch tickets of a given ticket overview */
   ticketsByOverview: TicketConnection;
   /** Translations for a given locale */
@@ -422,7 +424,19 @@ export type QueriesOverviewsArgs = {
 
 
 /** All available queries */
-export type QueriesTicketByIdArgs = {
+export type QueriesTicketArgs = {
+  ticketId?: InputMaybe<Scalars['ID']>;
+  ticketInternalId?: InputMaybe<Scalars['Int']>;
+  ticketNumber?: InputMaybe<Scalars['String']>;
+};
+
+
+/** All available queries */
+export type QueriesTicketArticlesArgs = {
+  after?: InputMaybe<Scalars['String']>;
+  before?: InputMaybe<Scalars['String']>;
+  first?: InputMaybe<Scalars['Int']>;
+  last?: InputMaybe<Scalars['Int']>;
   ticketId: Scalars['ID'];
 };
 
@@ -502,6 +516,8 @@ export type Ticket = Node & ObjectAttributeValueInterface & {
   firstResponseInMin?: Maybe<Scalars['Int']>;
   group: Group;
   id: Scalars['ID'];
+  /** Internal database ID */
+  internalId: Scalars['Int'];
   lastContactAgentAt?: Maybe<Scalars['ISO8601DateTime']>;
   lastContactAt?: Maybe<Scalars['ISO8601DateTime']>;
   lastContactCustomerAt?: Maybe<Scalars['ISO8601DateTime']>;
@@ -554,8 +570,10 @@ export type TicketArticle = Node & {
   originBy?: Maybe<User>;
   references?: Maybe<Scalars['String']>;
   replyTo?: Maybe<Scalars['String']>;
+  sender?: Maybe<TicketArticleType>;
   subject?: Maybe<Scalars['String']>;
   to?: Maybe<Scalars['String']>;
+  type?: Maybe<TicketArticleType>;
   /** Last update date/time of the record */
   updatedAt: Scalars['ISO8601DateTime'];
   /** Last user that updated this record */
@@ -584,6 +602,21 @@ export type TicketArticleEdge = {
   node: TicketArticle;
 };
 
+/** Ticket article types */
+export type TicketArticleType = Node & {
+  __typename?: 'TicketArticleType';
+  /** Create date/time of the record */
+  createdAt: Scalars['ISO8601DateTime'];
+  /** User that created this record */
+  createdBy: User;
+  id: Scalars['ID'];
+  name?: Maybe<Scalars['String']>;
+  /** Last update date/time of the record */
+  updatedAt: Scalars['ISO8601DateTime'];
+  /** Last user that updated this record */
+  updatedBy: User;
+};
+
 /** The connection type for Ticket. */
 export type TicketConnection = {
   __typename?: 'TicketConnection';
@@ -792,14 +825,23 @@ export type AccountLocaleMutationVariables = Exact<{
 
 export type AccountLocaleMutation = { __typename?: 'Mutations', accountLocale?: { __typename?: 'LocalePayload', success: boolean, errors?: Array<{ __typename?: 'UserError', message: string, field?: string | null }> | null } | null };
 
-export type TicketsByIdQueryVariables = Exact<{
-  ticketId: Scalars['ID'];
+export type TicketQueryVariables = Exact<{
+  ticketId?: InputMaybe<Scalars['ID']>;
+  ticketInternalId?: InputMaybe<Scalars['Int']>;
+  ticketNumber?: InputMaybe<Scalars['String']>;
   withArticles?: InputMaybe<Scalars['Boolean']>;
   withObjectAttributes?: InputMaybe<Scalars['Boolean']>;
 }>;
 
 
-export type TicketsByIdQuery = { __typename?: 'Queries', ticketById: { __typename?: 'Ticket', id: string, number: string, title: string, createdAt: any, updatedAt: any, owner: { __typename?: 'User', firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', firstname?: string | null, lastname?: string | null }, organization?: { __typename?: 'Organization', name: string } | null, state: { __typename?: 'TicketState', name: string, stateType: { __typename?: 'TicketStateType', name: string } }, group: { __typename?: 'Group', name: string }, priority: { __typename?: 'TicketPriority', name: string }, articles?: { __typename?: 'TicketArticleConnection', edges: Array<{ __typename?: 'TicketArticleEdge', node: { __typename?: 'TicketArticle', subject?: string | null } } | null> }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: string | null, attribute: { __typename?: 'ObjectManagerAttribute', name: string, display: string, dataType: string, dataOption?: any | null, screens?: any | null, editable: boolean, active: boolean } }> } };
+export type TicketQuery = { __typename?: 'Queries', ticket: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: any, updatedAt: any, owner: { __typename?: 'User', firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', firstname?: string | null, lastname?: string | null }, organization?: { __typename?: 'Organization', name: string } | null, state: { __typename?: 'TicketState', name: string, stateType: { __typename?: 'TicketStateType', name: string } }, group: { __typename?: 'Group', name: string }, priority: { __typename?: 'TicketPriority', name: string }, articles?: { __typename?: 'TicketArticleConnection', edges: Array<{ __typename?: 'TicketArticleEdge', node: { __typename?: 'TicketArticle', subject?: string | null } } | null> }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: string | null, attribute: { __typename?: 'ObjectManagerAttribute', name: string, display: string, dataType: string, dataOption?: any | null, screens?: any | null, editable: boolean, active: boolean } }> } };
+
+export type TicketArticlesQueryVariables = Exact<{
+  ticketId: Scalars['ID'];
+}>;
+
+
+export type TicketArticlesQuery = { __typename?: 'Queries', ticketArticles: { __typename?: 'TicketArticleConnection', totalCount: number, edges: Array<{ __typename?: 'TicketArticleEdge', cursor: string, node: { __typename?: 'TicketArticle', id: string, from?: string | null, to?: string | null, cc?: string | null, subject?: string | null, replyTo?: string | null, messageId?: string | null, messageIdMd5?: string | null, inReplyTo?: string | null, contentType: string, references?: string | null, body: string, internal: boolean, createdAt: any, updatedAt: any, type?: { __typename?: 'TicketArticleType', name?: string | null } | null, sender?: { __typename?: 'TicketArticleType', name?: string | null } | null } } | null>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean } } };
 
 export type TicketsByOverviewQueryVariables = Exact<{
   overviewId: Scalars['ID'];

+ 15 - 0
app/graphql/gql/concern/has_internal_id.rb

@@ -0,0 +1,15 @@
+# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+# Provide the internal id only for objects that need it, for example
+#   in URLs.
+module Gql::Concern::HasInternalId
+  extend ActiveSupport::Concern
+
+  included do
+    field :internal_id, Integer, null: false, description: 'Internal database ID'
+    def internal_id
+      object.id
+    end
+  end
+
+end

+ 1 - 1
app/graphql/gql/mutations/form/upload_cache/remove.rb

@@ -11,7 +11,7 @@ module Gql::Mutations
 
     def resolve(form_id:, file_ids:)
       cache = UploadCache.new(form_id)
-      file_ids.map { |file_id| cache.remove_item(Gql::ZammadSchema.object_from_id(file_id, only: Store).id) }
+      file_ids.map { |file_id| cache.remove_item(Gql::ZammadSchema.verified_object_from_id(file_id, type: ::Store).id) }
       { success: true }
     end
 

+ 1 - 1
app/graphql/gql/queries/overviews.rb

@@ -12,7 +12,7 @@ module Gql::Queries
     type Gql::Types::OverviewType.connection_type, null: false
 
     def resolve(...)
-      Ticket::Overviews.all(current_user: context.current_user)
+      ::Ticket::Overviews.all(current_user: context.current_user)
     end
 
   end

+ 33 - 0
app/graphql/gql/queries/ticket.rb

@@ -0,0 +1,33 @@
+# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+module Gql::Queries
+  class Ticket < BaseQuery
+
+    description 'Fetch a ticket by ID'
+
+    def self.authorize(_obj, ctx)
+      # Pundit authorization will be done via TicketType.
+      ctx.current_user
+    end
+
+    argument :ticket_id, GraphQL::Types::ID, required: false, description: 'Ticket ID'
+    argument :ticket_internal_id, Integer, required: false, description: 'Ticket internalId'
+    argument :ticket_number, String, required: false, description: 'Ticket number'
+
+    type Gql::Types::TicketType, null: false
+
+    def resolve(ticket_id: nil, ticket_internal_id: nil, ticket_number: nil)
+      if ticket_id
+        return Gql::ZammadSchema.verified_object_from_id(ticket_id, type: ::Ticket)
+      end
+      if ticket_internal_id
+        return ::Ticket.find_by(id: ticket_internal_id) || raise(ActiveRecord::RecordNotFound, "The ticket #{ticket_internal_id} could not be found.")
+      end
+      if ticket_number
+        return ::Ticket.find_by(number: ticket_number) || raise(ActiveRecord::RecordNotFound, "The ticket ##{ticket_number} could not be found.")
+      end
+
+      raise __("One of the arguments 'ticket_id', 'ticket_internal_id' or 'ticket_number' must be provided.")
+    end
+  end
+end

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