Browse Source

Feature: Mobile - Deployment improvements.

Martin Gruner 3 years ago
parent
commit
7660a4cbac

+ 0 - 1
.gitignore

@@ -90,7 +90,6 @@
 yarn-error.log
 /public/vite
 /public/vite-dev
-/public/vite-test
 /node_modules
 
 # Eslint

+ 1 - 0
.gitlab/ci/browser-core/build.yml

@@ -11,6 +11,7 @@ browser:build:
     - public/assets/application-*
     - public/assets/knowledge_base*
     - public/assets/print-*
+    - public/vite
   variables:
     RAILS_ENV: "production"
   script:

+ 3 - 3
Gemfile

@@ -52,12 +52,12 @@ gem 'rszr'
 # performance - Memcached
 gem 'dalli', require: false
 
+# Vite is required by the web server
+gem 'vite_rails'
+
 # Only load gems for asset compilation if they are needed to avoid
 #   having unneeded runtime dependencies like NodeJS.
 group :assets do
-  # asset handling - frontend tool chain
-  gem 'vite_rails', require: false
-
   # asset handling - javascript execution for e.g. linux
   gem 'execjs', require: false
 

+ 3 - 0
app/frontend/apps/mobile/App.vue

@@ -18,6 +18,7 @@ import useMetaTitle from '@common/composables/useMetaTitle'
 import { useRoute, useRouter } from 'vue-router'
 import emitter from '@common/utils/emitter'
 import { onBeforeUnmount } from 'vue'
+import useAppUpdateCheck from '@common/composables/useAppUpdateCheck'
 
 const router = useRouter()
 const route = useRoute()
@@ -30,6 +31,8 @@ useMetaTitle().initializeMetaTitle()
 const applicationLoaded = useApplicationLoadedStore()
 applicationLoaded.setLoaded()
 
+useAppUpdateCheck()
+
 // Add a watcher for authenticated changes (e.g. login/logout in a other browser tab).
 authenticated.$subscribe(async (mutation, state) => {
   if (state.value && !sessionId.value) {

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

@@ -51,7 +51,7 @@ const { notify, clearAllNotifications } = useNotifications()
 notify({
   message: __('Hello Home!!!'),
   type: NotificationTypes.WARN,
-  duration: 10000,
+  durationMS: 10000,
 })
 
 const sessionUser = useSessionUserStore()

+ 13 - 6
app/frontend/common/components/common/CommonNotifications.vue

@@ -11,8 +11,9 @@
         <div v-for="notification in notifications" v-bind:key="notification.id">
           <div class="flex justify-center">
             <div
-              class="flex items-center py-2 px-4 m-1 rounded"
-              v-bind:class="getClassName(notification.type)"
+              class="flex items-center py-2 px-4 m-1 rounded cursor-pointer"
+              v-bind:class="getClassName(notification)"
+              v-on:click="clickHandler(notification)"
             >
               <CommonIcon
                 v-bind:name="iconNameMap[notification.type]"
@@ -36,7 +37,7 @@
 
 <script setup lang="ts">
 import useNotifications from '@common/composables/useNotifications'
-import { NotificationTypes } from '@common/types/notification'
+import { NotificationInterface } from '@common/types/notification'
 
 const notificationTypeClassMap = {
   warn: 'bg-yellow text-white',
@@ -52,9 +53,15 @@ const iconNameMap = {
   info: 'info',
 }
 
-const { notifications } = useNotifications()
+const { notifications, removeNotification } = useNotifications()
 
-const getClassName = (notificationType: NotificationTypes) => {
-  return notificationTypeClassMap[notificationType]
+const getClassName = (notification: NotificationInterface) => {
+  return notificationTypeClassMap[notification.type]
+}
+
+const clickHandler = (notification: NotificationInterface) => {
+  const { callback } = notification
+  removeNotification(notification.id)
+  if (callback) callback()
 }
 </script>

+ 105 - 0
app/frontend/common/composables/useAppUpdateCheck.ts

@@ -0,0 +1,105 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import useNotifications from '@common/composables/useNotifications'
+import {
+  useApplicationBuildChecksumQuery,
+  useAppVersionSubscription,
+} from '@common/graphql/api'
+import {
+  ApplicationBuildChecksumQuery,
+  ApplicationBuildChecksumQueryVariables,
+  AppVersionMessage,
+  AppVersionSubscription,
+  AppVersionSubscriptionVariables,
+} from '@common/graphql/types'
+import {
+  QueryHandler,
+  SubscriptionHandler,
+} from '@common/server/apollo/handler'
+import { NotificationTypes } from '@common/types/notification'
+import { onMounted, reactive, watch } from 'vue'
+import { useRouteQuery } from '@vueuse/router'
+
+let query: QueryHandler<
+  ApplicationBuildChecksumQuery,
+  ApplicationBuildChecksumQueryVariables
+>
+let previousChecksum: string
+let subscription: SubscriptionHandler<
+  AppVersionSubscription,
+  AppVersionSubscriptionVariables
+>
+
+export default function useAppUpdateCheck() {
+  function notify(message: string) {
+    useNotifications().notify({
+      message,
+      type: NotificationTypes.WARN,
+      persistent: true,
+      callback: () => {
+        window.location.reload()
+      },
+    })
+  }
+
+  onMounted(() => {
+    if (query) return
+
+    // Default poll interval: every minute.
+    const defaultPollInterval = 60 * 1000
+
+    const applicationRebuildCheckInterval = useRouteQuery(
+      'ApplicationRebuildCheckInterval',
+      defaultPollInterval.toString(),
+    )
+
+    const options = reactive({
+      pollInterval: parseInt(applicationRebuildCheckInterval.value, 10),
+    })
+
+    watch(applicationRebuildCheckInterval, () => {
+      options.pollInterval = parseInt(applicationRebuildCheckInterval.value, 10)
+    })
+
+    query = new QueryHandler(useApplicationBuildChecksumQuery(options))
+
+    let notificationMessage = __(
+      'A newer version of the app is available. Please reload at your earliest.',
+    )
+
+    query.watchOnResult((queryResult): void => {
+      if (!queryResult?.applicationBuildChecksum.length) return
+      if (!previousChecksum) {
+        previousChecksum = queryResult?.applicationBuildChecksum
+      }
+      if (queryResult?.applicationBuildChecksum !== previousChecksum) {
+        notify(notificationMessage)
+      }
+    })
+
+    subscription = new SubscriptionHandler(useAppVersionSubscription())
+    subscription.onResult((result) => {
+      const message = result.data?.appVersion.message
+      if (!message) {
+        // TODO: replace fake test flag with a real test flag implementation.
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        ;(window as any).appVersionSubscriptionReady = true
+        return
+      }
+      switch (message) {
+        case AppVersionMessage.ConfigChanged:
+          notificationMessage = __(
+            'The configuration of Zammad has changed. Please reload at your earliest.',
+          )
+          break
+        case AppVersionMessage.RestartAuto:
+        case AppVersionMessage.RestartManual:
+          // TODO: this case cannot be handled right now. Legacy interface performs a connectivity check.
+          break
+        default:
+          break
+      }
+      notify(notificationMessage)
+    })
+  })
+}

+ 7 - 5
app/frontend/common/composables/useNotifications.ts

@@ -8,7 +8,7 @@ import type {
 } from '@common/types/notification'
 
 const notifications = ref<NotificationInterface[]>([])
-const defaultNotificationDuration = 5000
+const defaultNotificationDurationMS = 5000
 
 function removeNotification(id: string) {
   notifications.value = notifications.value.filter(
@@ -22,7 +22,6 @@ function clearAllNotifications() {
 
 export default function useNotifications() {
   function notify(notification: NewNotificationInterface): string {
-    // TODO: Check different solution for the optional id in the interface, but required field in the removeNotification function.
     let { id } = notification
     if (!id) {
       id = uuid()
@@ -32,9 +31,11 @@ export default function useNotifications() {
 
     notifications.value.push(newNotification)
 
-    setTimeout(() => {
-      removeNotification(newNotification.id)
-    }, newNotification.duration || defaultNotificationDuration)
+    if (!newNotification.persistent) {
+      setTimeout(() => {
+        removeNotification(newNotification.id)
+      }, newNotification.durationMS || defaultNotificationDurationMS)
+    }
 
     return newNotification.id
   }
@@ -42,6 +43,7 @@ export default function useNotifications() {
   return {
     notify,
     notifications,
+    removeNotification,
     clearAllNotifications,
   }
 }

+ 46 - 0
app/frontend/common/graphql/api.ts

@@ -75,6 +75,28 @@ export function useLogoutMutation(options: VueApolloComposable.UseMutationOption
   return VueApolloComposable.useMutation<Types.LogoutMutation, Types.LogoutMutationVariables>(LogoutDocument, options);
 }
 export type LogoutMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<Types.LogoutMutation, Types.LogoutMutationVariables>;
+export const ApplicationBuildChecksumDocument = gql`
+    query applicationBuildChecksum {
+  applicationBuildChecksum
+}
+    `;
+
+/**
+ * __useApplicationBuildChecksumQuery__
+ *
+ * To run a query within a Vue component, call `useApplicationBuildChecksumQuery` and pass it any options that fit your needs.
+ * When your component renders, `useApplicationBuildChecksumQuery` returns an object from Apollo Client that contains result, loading and error properties
+ * you can use to render your UI.
+ *
+ * @param options that will be passed into the query, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/query.html#options;
+ *
+ * @example
+ * const { result, loading, error } = useApplicationBuildChecksumQuery();
+ */
+export function useApplicationBuildChecksumQuery(options: VueApolloComposable.UseQueryOptions<Types.ApplicationBuildChecksumQuery, Types.ApplicationBuildChecksumQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<Types.ApplicationBuildChecksumQuery, Types.ApplicationBuildChecksumQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<Types.ApplicationBuildChecksumQuery, Types.ApplicationBuildChecksumQueryVariables>> = {}) {
+  return VueApolloComposable.useQuery<Types.ApplicationBuildChecksumQuery, Types.ApplicationBuildChecksumQueryVariables>(ApplicationBuildChecksumDocument, {}, options);
+}
+export type ApplicationBuildChecksumQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<Types.ApplicationBuildChecksumQuery, Types.ApplicationBuildChecksumQueryVariables>;
 export const ApplicationConfigDocument = gql`
     query applicationConfig {
   applicationConfig {
@@ -261,6 +283,30 @@ export function useTranslationsQuery(variables: Types.TranslationsQueryVariables
   return VueApolloComposable.useQuery<Types.TranslationsQuery, Types.TranslationsQueryVariables>(TranslationsDocument, variables, options);
 }
 export type TranslationsQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<Types.TranslationsQuery, Types.TranslationsQueryVariables>;
+export const AppVersionDocument = gql`
+    subscription appVersion {
+  appVersion {
+    message
+  }
+}
+    `;
+
+/**
+ * __useAppVersionSubscription__
+ *
+ * To run a query within a Vue component, call `useAppVersionSubscription` and pass it any options that fit your needs.
+ * When your component renders, `useAppVersionSubscription` returns an object from Apollo Client that contains result, loading and error properties
+ * you can use to render your UI.
+ *
+ * @param options that will be passed into the subscription, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/subscription.html#options;
+ *
+ * @example
+ * const { result, loading, error } = useAppVersionSubscription();
+ */
+export function useAppVersionSubscription(options: VueApolloComposable.UseSubscriptionOptions<Types.AppVersionSubscription, Types.AppVersionSubscriptionVariables> | VueCompositionApi.Ref<VueApolloComposable.UseSubscriptionOptions<Types.AppVersionSubscription, Types.AppVersionSubscriptionVariables>> | ReactiveFunction<VueApolloComposable.UseSubscriptionOptions<Types.AppVersionSubscription, Types.AppVersionSubscriptionVariables>> = {}) {
+  return VueApolloComposable.useSubscription<Types.AppVersionSubscription, Types.AppVersionSubscriptionVariables>(AppVersionDocument, {}, options);
+}
+export type AppVersionSubscriptionCompositionFunctionResult = VueApolloComposable.UseSubscriptionReturn<Types.AppVersionSubscription, Types.AppVersionSubscriptionVariables>;
 export const ConfigUpdatedDocument = gql`
     subscription configUpdated {
   configUpdated {

+ 3 - 0
app/frontend/common/graphql/queries/applicationBuildChecksum.graphql

@@ -0,0 +1,3 @@
+query applicationBuildChecksum {
+  applicationBuildChecksum
+}

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