Skip to content

Commit

Permalink
Maintenance: Implemented automatic system set-up for new desktop view.
Browse files Browse the repository at this point in the history
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
  • Loading branch information
mgruner and dvuckovic committed Feb 2, 2024
1 parent a0f712a commit 77de6e1
Show file tree
Hide file tree
Showing 20 changed files with 634 additions and 42 deletions.
39 changes: 7 additions & 32 deletions app/controllers/getting_started_controller.rb
Expand Up @@ -53,43 +53,18 @@ def auto_wizard_admin
# 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
Expand Down
@@ -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('/')
})
})
})
})
@@ -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 +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>;
@@ -0,0 +1,10 @@
mutation systemSetupRunAutoWizard($token: String) {
systemSetupRunAutoWizard(token: $token) {
session {
...session
}
errors {
...errors
}
}
}
@@ -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 changes: 27 additions & 0 deletions app/frontend/apps/desktop/pages/guided-setup/routes.ts
Expand Up @@ -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',
Expand Down
Expand Up @@ -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) {
Expand Down Expand Up @@ -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(() => {
Expand Down
@@ -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>

0 comments on commit 77de6e1

Please sign in to comment.