Просмотр исходного кода

Feature: Mobile - Apollo Client handler and app initialize improvements.

Dominik Klein 3 лет назад
Родитель
Сommit
b09af3def4

+ 7 - 0
.eslintrc.js

@@ -56,6 +56,12 @@ module.exports = {
     'no-shadow': 'off',
     '@typescript-eslint/no-shadow': 'off',
 
+    // Expect assertions are mandatory for async tests.
+    'jest/prefer-expect-assertions': [
+      'error',
+      { onlyFunctionsWithAsyncKeyword: true },
+    ],
+
     // Enforce v-bind directive usage in long form.
     'vue/v-bind-style': ['error', 'longform'],
 
@@ -77,6 +83,7 @@ module.exports = {
           ['@', path.resolve(__dirname, './app/frontend/')],
           ['@mobile', path.resolve(__dirname, './app/frontend/apps/mobile')],
           ['@common', path.resolve(__dirname, './app/frontend/common')],
+          ['@tests', path.resolve(__dirname, './app/frontend/tests')],
         ],
         extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'],
       },

+ 1 - 1
.gitlab/ci/jest.yml

@@ -10,4 +10,4 @@ jest:
     - echo "Also test eslint-plugin-zammad..."
     - cd .eslint
     - yarn install
-    - yarn test
+    - yarn test

+ 0 - 0
app/frontend/apps/mobile/initializer/.keep


+ 8 - 8
app/frontend/apps/mobile/main.ts

@@ -2,12 +2,15 @@
 
 import { createApp } from 'vue'
 import App from '@mobile/App.vue'
-import { DefaultApolloClient } from '@vue/apollo-composable'
+import {
+  DefaultApolloClient,
+  provideApolloClient,
+} from '@vue/apollo-composable'
 import apolloClient from '@common/server/apollo/client'
 import useSessionIdStore from '@common/stores/session/id'
 import '@common/styles/main.css'
 import initializeStore from '@common/stores'
-import InitializeHandler from '@common/initializer'
+import initializeStoreSubscriptions from '@common/initializer/storeSubscriptions'
 import useApplicationConfigStore from '@common//stores/application/config'
 import initializeRouter from '@common/router/index'
 import routes from '@mobile/router'
@@ -17,15 +20,12 @@ export default async function mountApp(): Promise<void> {
 
   app.provide(DefaultApolloClient, apolloClient)
 
+  provideApolloClient(apolloClient)
+
   initializeStore(app)
   initializeRouter(app, routes)
 
-  const initializer = new InitializeHandler(
-    app,
-    import.meta.globEager('/apps/mobile/initializer/*.ts'),
-  )
-
-  initializer.initialize()
+  initializeStoreSubscriptions()
 
   const sessionId = useSessionIdStore()
   await sessionId.checkSession()

+ 8 - 1
app/frontend/apps/mobile/views/Home.vue

@@ -6,6 +6,8 @@
     <p>{{ userData?.firstname }} {{ userData?.lastname }}</p>
     <br />
     <p v-on:click="logout">Logout</p>
+    <br />
+    <p v-on:click="refetchConfig">refetchConfig</p>
   </div>
 </template>
 
@@ -15,8 +17,9 @@ import useAuthenticatedStore from '@common/stores/authenticated'
 import useSessionUserStore from '@common/stores/session/user'
 import { storeToRefs } from 'pinia'
 import { useRouter } from 'vue-router'
+import useApplicationConfigStore from '@common/stores/application/config'
 
-// TODO ... testing the notification
+// TODO ... only testing the notifications.
 const { notify } = useNotifications()
 
 notify({
@@ -37,4 +40,8 @@ const logout = (): void => {
     router.push('/login')
   })
 }
+
+const refetchConfig = async (): Promise<void> => {
+  await useApplicationConfigStore().getConfig(true)
+}
 </script>

+ 2 - 2
app/frontend/common/components/CommonNotifications.vue

@@ -11,11 +11,11 @@
         <div v-for="notification in notifications" v-bind:key="notification.id">
           <div class="flex justify-center">
             <div class="flex items-center bg-black p-2 rounded text-white m-1">
-              <svg class="w-4 h-4 fill-current text-red-600">
+              <!-- <svg class="w-4 h-4 fill-current text-red-600">
                 <use
                   xlink:href="assets/images/icons.svg#icon-diagonal-cross"
                 ></use>
-              </svg>
+              </svg> -->
               <span class="ml-2">{{ notification.message }}</span>
             </div>
           </div>

+ 5 - 4
app/frontend/common/graphql/types.ts

@@ -1,4 +1,5 @@
 export type Maybe<T> = T | null;
+export type InputMaybe<T> = Maybe<T>;
 export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
 export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
 export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
@@ -122,10 +123,10 @@ export type Organization = Node & ObjectAttributeValueInterface & {
 
 /** Organizations that users can belong to */
 export type OrganizationMembersArgs = {
-  after?: Maybe<Scalars['String']>;
-  before?: Maybe<Scalars['String']>;
-  first?: Maybe<Scalars['Int']>;
-  last?: Maybe<Scalars['Int']>;
+  after?: InputMaybe<Scalars['String']>;
+  before?: InputMaybe<Scalars['String']>;
+  first?: InputMaybe<Scalars['Int']>;
+  last?: InputMaybe<Scalars['Int']>;
 };
 
 /** Information about pagination in a connection. */

+ 0 - 35
app/frontend/common/initializer.ts

@@ -1,35 +0,0 @@
-// Copyright (C) 2012-2021 Zammad Foundation, https://zammad-foundation.org/
-
-import { Initializer, InitializerModule } from '@common/types/initializer'
-import { ImportGlobEagerResult } from '@common/types/utils'
-import { App } from 'vue'
-
-export default class InitializeHandler implements Initializer {
-  app: App
-
-  modules: Array<InitializerModule>
-
-  constructor(app: App, specificAppModules?: ImportGlobEagerResult) {
-    this.app = app
-
-    const commonInitializerModules = import.meta.globEager('./initializer/*.ts')
-
-    this.modules = Object.values(commonInitializerModules).map(
-      (module) => module.default,
-    )
-
-    if (specificAppModules) {
-      const loadedSpecificAppModules: Array<InitializerModule> = Object.values(
-        specificAppModules,
-      ).map((module) => module.default as InitializerModule)
-
-      this.modules.push(...loadedSpecificAppModules)
-    }
-  }
-
-  initialize() {
-    this.modules.forEach((module: InitializerModule) => {
-      module(this.app)
-    })
-  }
-}

+ 1 - 1
app/frontend/common/initializer/storeSubscriptions.ts

@@ -4,7 +4,7 @@ import useAuthenticatedStore from '@common/stores/authenticated'
 import useSessionIdStore from '@common/stores/session/id'
 import useSessionUserStore from '@common/stores/session/user'
 
-export default function initialize(): void {
+export default function initializeStoreSubscriptions(): void {
   const sessionId = useSessionIdStore()
   const authenticated = useAuthenticatedStore()
   const sessionUser = useSessionUserStore()

+ 26 - 82
app/frontend/common/server/apollo/handler/BaseHandler.ts

@@ -1,21 +1,16 @@
 // Copyright (C) 2012-2021 Zammad Foundation, https://zammad-foundation.org/
 
-import log from '@common/utils/log'
 import { ApolloError, OperationVariables } from '@apollo/client/core'
 import {
   BaseHandlerOptions,
   CommonHandlerOptions,
   CommonHandlerOptionsParameter,
-  OperationFunction,
-  OperationOptions,
   OperationResult,
   OperationReturn,
-  OperationVariablesParameter,
 } from '@common/types/server/apollo/handler'
 import { Ref } from 'vue'
 import useNotifications from '@common/composables/useNotifications'
 import {
-  GraphQLErrorExtensionsHandler,
   GraphQLErrorReport,
   GraphQLErrorTypes,
   GraphQLHandlerError,
@@ -28,18 +23,8 @@ export default abstract class BaseHandler<
     TResult,
     TVariables
   > = OperationReturn<TResult, TVariables>,
-  TOperationFunction extends OperationFunction<
-    TResult,
-    TVariables
-  > = OperationFunction<TResult, TVariables>,
-  TOperationOptions extends OperationOptions<
-    TResult,
-    TVariables
-  > = OperationOptions<TResult, TVariables>,
   THandlerOptions = BaseHandlerOptions,
 > {
-  protected operation: TOperationFunction
-
   public operationResult!: TOperationReturn
 
   protected baseHandlerOptions: BaseHandlerOptions = {
@@ -47,32 +32,22 @@ export default abstract class BaseHandler<
     errorNotitifactionMessage:
       'An error occured during the operation. Please contact your administrator.',
     errorNotitifactionType: 'error',
-    errorLogLevel: 'error',
   }
 
-  protected handlerOptions!: CommonHandlerOptions<THandlerOptions>
-
-  public error: Maybe<GraphQLHandlerError> = null
+  public handlerOptions!: CommonHandlerOptions<THandlerOptions>
 
   constructor(
-    operation: TOperationFunction,
-    variables?: OperationVariablesParameter<TVariables>,
-    options?: TOperationOptions,
+    operationResult: TOperationReturn,
     handlerOptions?: CommonHandlerOptionsParameter<THandlerOptions>,
   ) {
-    this.operation = operation
+    this.operationResult = operationResult
 
     this.handlerOptions = this.mergedHandlerOptions(handlerOptions)
 
-    this.execute(variables, options)
+    this.initialize()
   }
 
-  public execute(
-    variables?: OperationVariablesParameter<TVariables>,
-    options?: TOperationOptions,
-  ): void {
-    this.operationResult = this.operationExecute(variables, options)
-
+  protected initialize(): void {
     this.operationResult.onError((error) => {
       this.handleError(error)
     })
@@ -86,61 +61,39 @@ export default abstract class BaseHandler<
     return this.operationResult.error
   }
 
-  protected abstract operationExecute(
-    variables?: OperationVariablesParameter<TVariables>,
-    options?: TOperationOptions,
-  ): TOperationReturn
-
   protected handleError(error: ApolloError): void {
     const options = this.handlerOptions
 
     if (options.errorShowNotification) {
-      const notification = useNotifications()
+      const { notify } = useNotifications()
 
-      notification.notify({
+      notify({
         message: options.errorNotitifactionMessage,
         type: options.errorNotitifactionType,
       })
     }
 
-    // TODO: Maybe it's also a option to use the error-Link from the apollo client for the general error output.
-    // Downside would be, that the error messages can not be avoided for a single query/mutation if i saw it correctly (but this is maybe
-    // only needed for some special things, like the first session id check).
-    const errorMessagePrefix = '[GraphQLClient]'
-    const { graphQLErrors, networkError } = error
-
-    if (graphQLErrors.length > 0) {
-      const { message, extensions }: GraphQLErrorReport = graphQLErrors[0]
-      const { type, backtrace }: GraphQLErrorExtensionsHandler = {
-        type: extensions?.type || GraphQLErrorTypes.NetworkError,
-        backtrace: extensions?.backtrace,
+    if (options.errorCallback) {
+      const { graphQLErrors, networkError } = error
+      let errorHandler: GraphQLHandlerError
+
+      if (graphQLErrors.length > 0) {
+        const { message, extensions }: GraphQLErrorReport = graphQLErrors[0]
+        errorHandler = {
+          type: extensions?.type || GraphQLErrorTypes.NetworkError,
+          message,
+        }
+      } else if (networkError) {
+        errorHandler = {
+          type: GraphQLErrorTypes.NetworkError,
+        }
+      } else {
+        errorHandler = {
+          type: GraphQLErrorTypes.UnkownError,
+        }
       }
 
-      this.error = {
-        message: `${errorMessagePrefix} GraphQL error - ${message}`,
-        type,
-        backtrace,
-      }
-    } else if (networkError) {
-      this.error = {
-        type: GraphQLErrorTypes.NetworkError,
-        message: `${errorMessagePrefix} Network error - ${networkError}`,
-      }
-    } else {
-      this.error = {
-        type: GraphQLErrorTypes.UnkownError,
-        message: 'Unknown error.',
-      }
-    }
-
-    if (options.errorLogLevel !== 'silent') {
-      const errorMessages: Array<string> = [this.error.message]
-
-      if (this.error.backtrace) {
-        errorMessages.push(this.error.backtrace)
-      }
-
-      log[options.errorLogLevel](...errorMessages)
+      options.errorCallback(errorHandler)
     }
   }
 
@@ -153,13 +106,4 @@ export default abstract class BaseHandler<
       handlerOptions,
     ) as CommonHandlerOptions<THandlerOptions>
   }
-
-  public abstract onLoaded(): Promise<Maybe<TResult>>
-
-  // TODO: Maybe we can add a pick func
-  public loadedResult(): Promise<Maybe<TResult>> {
-    return this.onLoaded()
-      .then((data: Maybe<TResult>) => data)
-      .catch(() => null)
-  }
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов