Browse Source

Maintenance: Implemented automatic system set-up for new desktop view.

Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Martin Gruner 1 year ago
parent
commit
77de6e1372

+ 7 - 32
app/controllers/getting_started_controller.rb

@@ -53,43 +53,18 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password}
     # check if system setup is already done
     return if setup_done_response
 
-    # check it auto wizard is enabled
-    if !AutoWizard.enabled?
-      render json: {
+    begin
+      auto_wizard_admin = Service::System::RunAutoWizard.new.execute(token: params[:token])
+    rescue Service::System::RunAutoWizard::AutoWizardNotEnabledError
+      return render json: {
         auto_wizard: false,
       }
-      return
-    end
-
-    # verify auto wizard file
-    auto_wizard_data = AutoWizard.data
-    if auto_wizard_data.blank?
-      render json: {
-        auto_wizard:         true,
-        auto_wizard_success: false,
-        message:             __('Invalid auto wizard file.'),
-      }
-      return
-    end
-
-    # verify auto wizard token
-    if auto_wizard_data['Token'] && auto_wizard_data['Token'] != params[:token]
-      render json: {
-        auto_wizard:         true,
-        auto_wizard_success: false,
-      }
-      return
-    end
-
-    # execute auto wizard
-    auto_wizard_admin = AutoWizard.setup
-    if !auto_wizard_admin
-      render json: {
+    rescue Service::System::RunAutoWizard::AutoWizardExecutionError => e
+      return render json: {
         auto_wizard:         true,
         auto_wizard_success: false,
-        message:             __('Error during execution of auto wizard.'),
+        message:             e.message,
       }
-      return
     end
 
     # set current session user

+ 65 - 0
app/frontend/apps/desktop/pages/guided-setup/__tests__/guided-setup-automated-info.spec.ts

@@ -0,0 +1,65 @@
+// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+import { visitView } from '#tests/support/components/visitView.ts'
+import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
+import { mockAuthentication } from '#tests/support/mock-authentication.ts'
+import { EnumSystemSetupInfoStatus } from '#shared/graphql/types.ts'
+import { mockSystemSetupInfoQuery } from '../graphql/queries/systemSetupInfo.mocks.ts'
+
+describe('guided setup automated info', () => {
+  describe('when system is not ready', () => {
+    beforeEach(() => {
+      mockApplicationConfig({
+        system_init_done: false,
+      })
+
+      mockSystemSetupInfoQuery({
+        systemSetupInfo: {
+          status: EnumSystemSetupInfoStatus.Automated,
+          type: null,
+        },
+      })
+    })
+
+    it('shows info screen', async () => {
+      const view = await visitView('/guided-setup/automated')
+
+      expect(view.getByText('Automated Setup')).toBeInTheDocument()
+      expect(view.queryByIconName('spinner')).not.toBeInTheDocument()
+
+      expect(
+        view.getByText('This system is configured for automated setup.'),
+      ).toBeInTheDocument()
+
+      expect(view.getByText('Please use the provided URL.')).toBeInTheDocument()
+    })
+
+    it('redirects to info screen first', async () => {
+      const view = await visitView('/guided-setup')
+
+      await vi.waitFor(() => {
+        expect(
+          view,
+          'correctly redirects to guided setup automated info screen',
+        ).toHaveCurrentUrl('/guided-setup/automated')
+      })
+    })
+  })
+
+  describe('when system is ready', () => {
+    beforeEach(() => {
+      mockApplicationConfig({
+        system_init_done: true,
+      })
+      mockAuthentication(true)
+    })
+
+    it('redirects to home screen', async () => {
+      const view = await visitView('/guided-setup/automated')
+
+      await vi.waitFor(() => {
+        expect(view, 'correctly redirects to home screen').toHaveCurrentUrl('/')
+      })
+    })
+  })
+})

+ 125 - 0
app/frontend/apps/desktop/pages/guided-setup/__tests__/guided-setup-automated-run.spec.ts

@@ -0,0 +1,125 @@
+// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+import { flushPromises } from '@vue/test-utils'
+import { visitView } from '#tests/support/components/visitView.ts'
+import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
+import { mockAuthentication } from '#tests/support/mock-authentication.ts'
+import { EnumSystemSetupInfoStatus } from '#shared/graphql/types.ts'
+import {
+  mockSystemSetupRunAutoWizardMutation,
+  waitForSystemSetupRunAutoWizardMutationCalls,
+} from '#desktop/pages/guided-setup/graphql/mutations/systemSetupRunAutoWizard.mocks.ts'
+import { mockSystemSetupInfoQuery } from '../graphql/queries/systemSetupInfo.mocks.ts'
+
+describe('guided setup automated run', () => {
+  describe('when system is not ready', () => {
+    beforeEach(() => {
+      mockApplicationConfig({
+        system_init_done: false,
+      })
+
+      mockAuthentication(false)
+
+      mockSystemSetupInfoQuery({
+        systemSetupInfo: {
+          status: EnumSystemSetupInfoStatus.Automated,
+          type: null,
+        },
+      })
+    })
+
+    it('redirects to home screen after successful setup', async () => {
+      vi.useFakeTimers()
+
+      const view = await visitView('/guided-setup/automated/run')
+
+      expect(view.getByText('Automated Setup')).toBeInTheDocument()
+      expect(view.getByIconName('spinner')).toBeInTheDocument()
+
+      expect(
+        view.getByText('Relax, your system is being set up…'),
+      ).toBeInTheDocument()
+
+      await flushPromises()
+
+      expect(
+        view.getByText(
+          'The system was configured successfully. You are being redirected.',
+        ),
+      ).toBeInTheDocument()
+
+      await vi.runAllTimersAsync()
+      vi.useRealTimers()
+
+      await vi.waitFor(() => {
+        expect(view, 'correctly redirects to home screen').toHaveCurrentUrl('/')
+      })
+    })
+
+    it('shows an alert message and hides spinner on errors', async () => {
+      mockSystemSetupRunAutoWizardMutation({
+        systemSetupRunAutoWizard: {
+          errors: [
+            {
+              message: 'An unexpected error occurred during system setup.',
+              field: null,
+            },
+          ],
+        },
+      })
+
+      const view = await visitView('/guided-setup/automated/run')
+      await flushPromises()
+
+      expect(view.getByText('Automated Setup')).toBeInTheDocument()
+      expect(view.queryByIconName('spinner')).not.toBeInTheDocument()
+
+      expect(
+        view.getByText('An unexpected error occurred during system setup.'),
+      ).toBeInTheDocument()
+    })
+
+    it('supports optional token parameter', async () => {
+      await visitView('/guided-setup/automated/run/s3cr3t-t0k3n')
+      await flushPromises()
+
+      const calls = await waitForSystemSetupRunAutoWizardMutationCalls()
+
+      expect(calls.at(-1)?.variables).toEqual(
+        expect.objectContaining({
+          token: 's3cr3t-t0k3n',
+        }),
+      )
+    })
+  })
+
+  describe('when system is ready', () => {
+    beforeEach(() => {
+      mockApplicationConfig({
+        system_init_done: true,
+      })
+    })
+
+    it('redirects to home screen', async () => {
+      mockAuthentication(true)
+
+      const view = await visitView('/guided-setup/automated/run')
+
+      await vi.waitFor(() => {
+        expect(view, 'correctly redirects to home screen').toHaveCurrentUrl('/')
+      })
+    })
+
+    it('redirects to login screen', async () => {
+      mockAuthentication(false)
+
+      const view = await visitView('/guided-setup/automated/run')
+
+      await vi.waitFor(() => {
+        expect(view, 'correctly redirects to login screen').toHaveCurrentUrl(
+          '/login',
+        )
+      })
+    })
+  })
+})

+ 0 - 0
app/frontend/apps/desktop/pages/guided-setup/components/GuidedSetupEmailConfigurationCheck.vue → app/frontend/apps/desktop/pages/guided-setup/components/GuidedSetupStatusMessage.vue


+ 26 - 0
app/frontend/apps/desktop/pages/guided-setup/graphql/mutations/systemSetupRunAutoWizard.api.ts

@@ -0,0 +1,26 @@
+import * as Types from '#shared/graphql/types.ts';
+
+import gql from 'graphql-tag';
+import { SessionFragmentDoc } from '../../../../../../shared/graphql/fragments/session.api';
+import { ErrorsFragmentDoc } from '../../../../../../shared/graphql/fragments/errors.api';
+import * as VueApolloComposable from '@vue/apollo-composable';
+import * as VueCompositionApi from 'vue';
+export type ReactiveFunction<TParam> = () => TParam;
+
+export const SystemSetupRunAutoWizardDocument = gql`
+    mutation systemSetupRunAutoWizard($token: String) {
+  systemSetupRunAutoWizard(token: $token) {
+    session {
+      ...session
+    }
+    errors {
+      ...errors
+    }
+  }
+}
+    ${SessionFragmentDoc}
+${ErrorsFragmentDoc}`;
+export function useSystemSetupRunAutoWizardMutation(options: VueApolloComposable.UseMutationOptions<Types.SystemSetupRunAutoWizardMutation, Types.SystemSetupRunAutoWizardMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<Types.SystemSetupRunAutoWizardMutation, Types.SystemSetupRunAutoWizardMutationVariables>> = {}) {
+  return VueApolloComposable.useMutation<Types.SystemSetupRunAutoWizardMutation, Types.SystemSetupRunAutoWizardMutationVariables>(SystemSetupRunAutoWizardDocument, options);
+}
+export type SystemSetupRunAutoWizardMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<Types.SystemSetupRunAutoWizardMutation, Types.SystemSetupRunAutoWizardMutationVariables>;

+ 10 - 0
app/frontend/apps/desktop/pages/guided-setup/graphql/mutations/systemSetupRunAutoWizard.graphql

@@ -0,0 +1,10 @@
+mutation systemSetupRunAutoWizard($token: String) {
+  systemSetupRunAutoWizard(token: $token) {
+    session {
+      ...session
+    }
+    errors {
+      ...errors
+    }
+  }
+}

+ 12 - 0
app/frontend/apps/desktop/pages/guided-setup/graphql/mutations/systemSetupRunAutoWizard.mocks.ts

@@ -0,0 +1,12 @@
+import * as Types from '#shared/graphql/types.ts';
+
+import * as Mocks from '#tests/graphql/builders/mocks.ts'
+import * as Operations from './systemSetupRunAutoWizard.api.ts'
+
+export function mockSystemSetupRunAutoWizardMutation(defaults: Mocks.MockDefaultsValue<Types.SystemSetupRunAutoWizardMutation, Types.SystemSetupRunAutoWizardMutationVariables>) {
+  return Mocks.mockGraphQLResult(Operations.SystemSetupRunAutoWizardDocument, defaults)
+}
+
+export function waitForSystemSetupRunAutoWizardMutationCalls() {
+  return Mocks.waitForGraphQLMockCalls<Types.SystemSetupRunAutoWizardMutation>(Operations.SystemSetupRunAutoWizardDocument)
+}

+ 27 - 0
app/frontend/apps/desktop/pages/guided-setup/routes.ts

@@ -22,6 +22,33 @@ const route: RouteRecordRaw[] = [
           sidebar: false,
         },
       },
+      {
+        path: 'automated/',
+        name: 'GuidedSetupAutomatedInfo',
+        component: () =>
+          import('./views/GuidedSetupAutomated/GuidedSetupAutomatedInfo.vue'),
+        meta: {
+          title: __('Automated Setup'),
+          requiresAuth: false,
+          requiredPermission: null,
+          hasOwnLandmarks: true,
+          sidebar: false,
+        },
+      },
+      {
+        path: 'automated/run/:token?',
+        name: 'GuidedSetupAutomatedRun',
+        props: true,
+        component: () =>
+          import('./views/GuidedSetupAutomated/GuidedSetupAutomatedRun.vue'),
+        meta: {
+          title: __('Automated Setup'),
+          requiresAuth: false,
+          requiredPermission: null,
+          hasOwnLandmarks: true,
+          sidebar: false,
+        },
+      },
       {
         path: 'manual',
         name: 'GuidedSetupManual',

+ 6 - 3
app/frontend/apps/desktop/pages/guided-setup/stores/systemSetupInfo.ts

@@ -56,7 +56,7 @@ export const useSystemSetupInfoStore = defineStore('systemSetupInfo', () => {
       return '/guided-setup'
 
     if (status === EnumSystemSetupInfoStatus.Automated) {
-      return '/guided-setup/automated' // TODO: use real route
+      return '/guided-setup/automated'
     }
 
     if (status === EnumSystemSetupInfoStatus.InProgress) {
@@ -88,9 +88,12 @@ export const useSystemSetupInfoStore = defineStore('systemSetupInfo', () => {
   })
 
   const redirectNeeded = (currentRoutePath: string) => {
-    if (currentRoutePath !== redirectPath.value) return true
+    // Allow sub-paths for auto wizard execution
+    if (systemSetupInfo.value.status === EnumSystemSetupInfoStatus.Automated) {
+      return !currentRoutePath.startsWith(redirectPath.value)
+    }
 
-    return false
+    return currentRoutePath !== redirectPath.value
   }
 
   const systemSetupDone = computed(() => {

+ 16 - 0
app/frontend/apps/desktop/pages/guided-setup/views/GuidedSetupAutomated/GuidedSetupAutomatedInfo.vue

@@ -0,0 +1,16 @@
+<!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
+
+<script setup lang="ts">
+import LayoutPublicPage from '#desktop/components/layout/LayoutPublicPage/LayoutPublicPage.vue'
+</script>
+
+<template>
+  <LayoutPublicPage box-size="medium" :title="__('Automated Setup')">
+    <div class="text-center">
+      <CommonLabel>{{
+        $t('This system is configured for automated setup.')
+      }}</CommonLabel>
+      <CommonLabel>{{ $t('Please use the provided URL.') }}</CommonLabel>
+    </div>
+  </LayoutPublicPage>
+</template>

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