Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 34 additions & 28 deletions core/src/components/setup/RecommendedApps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@
import axios from '@nextcloud/axios'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { PwdConfirmationMode } from '@nextcloud/password-confirmation'
import { generateUrl, imagePath } from '@nextcloud/router'
import pLimit from 'p-limit'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import logger from '../../logger.js'
Expand Down Expand Up @@ -147,35 +147,41 @@ export default {
},

methods: {
installApps() {
this.installingApps = true

const limit = pLimit(1)
const installing = this.recommendedApps
async installApps() {
const apps = this.recommendedApps
.filter((app) => !app.active && app.isCompatible && app.canInstall && app.isSelected)
.map((app) => limit(async () => {
logger.info(`installing ${app.id}`)
app.loading = true
return axios.post(generateUrl('settings/apps/enable'), { appIds: [app.id], groups: [] })
.catch((error) => {
logger.error(`could not install ${app.id}`, { error })
app.isSelected = false
app.installationError = true
})
.then(() => {
logger.info(`installed ${app.id}`)
app.loading = false
app.active = true
})
}))
logger.debug(`installing ${installing.length} recommended apps`)
Promise.all(installing)
.then(() => {
logger.info('all recommended apps installed, redirecting …')

window.location = this.defaultPageUrl
if (apps.length === 0) {
return
}

this.installingApps = true
apps.forEach((app) => {
app.loading = true
})
const appIds = apps.map((app) => app.id)
logger.debug(`installing ${apps.length} recommended apps`, { appIds })

try {
await axios.post(
generateUrl('settings/apps/enable'),
{ appIds, groups: [] },
{ confirmPassword: PwdConfirmationMode.Strict },
)
apps.forEach((app) => {
app.loading = false
app.active = true
})
.catch((error) => logger.error('could not install recommended apps', { error }))
logger.info('all recommended apps installed, redirecting …')
window.location = this.defaultPageUrl
} catch (error) {
logger.error('could not install recommended apps', { error })
apps.forEach((app) => {
app.loading = false
app.isSelected = false
app.installationError = true
})
this.installingApps = false
}
},

customIcon(appId) {
Expand Down
4 changes: 4 additions & 0 deletions core/src/recommendedapps.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
*/

import { getCSPNonce } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { translate as t } from '@nextcloud/l10n'
import { addPasswordConfirmationInterceptors } from '@nextcloud/password-confirmation'
import Vue from 'vue'
import RecommendedApps from './components/setup/RecommendedApps.vue'
import logger from './logger.js'

addPasswordConfirmationInterceptors(axios)

__webpack_nonce__ = getCSPNonce()

Vue.mixin({
Expand Down
66 changes: 60 additions & 6 deletions cypress/e2e/core/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/

import { randomString } from '../../support/utils/randomString.ts'
import { handlePasswordConfirmation } from '../core-utils.ts'

type RecommendedAppsMode = 'skip' | 'install-success' | 'install-failure'

/**
* DO NOT RENAME THIS FILE to .cy.ts ⚠️
Expand All @@ -30,6 +33,22 @@ describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {
sharedSetup()
})

it('Sqlite - Install recommended apps (success)', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })

sharedSetup('install-success')
})

it('Sqlite - Install recommended apps (failure)', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })

sharedSetup('install-failure')
})

it('MySQL', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
Expand Down Expand Up @@ -110,8 +129,12 @@ describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {

/**
* Shared admin setup function for the Nextcloud setup
*
* @param mode How to handle the recommended apps screen at the end of the
* install assistant: skip it, exercise the install button with a
* stubbed success response, or stub a failure response.
*/
function sharedSetup() {
function sharedSetup(mode: RecommendedAppsMode = 'skip') {
const randAdmin = 'admin-' + randomString(10)

// mock appstore
Expand Down Expand Up @@ -140,10 +163,41 @@ function sharedSetup() {
.should('be.visible')
})

// Skip the setup apps
cy.get('[data-cy-setup-recommended-apps-skip]').click()
if (mode === 'skip') {
// Skip the setup apps
cy.get('[data-cy-setup-recommended-apps-skip]').click()

// Go to files
cy.visit('/apps/files/')
cy.get('[data-cy-files-content]').should('be.visible')
// Go to files
cy.visit('/apps/files/')
cy.get('[data-cy-files-content]').should('be.visible')
return
}

// Stub the bulk enable endpoint so we exercise the frontend flow without
// hitting the real app store.
cy.intercept('POST', '**/settings/apps/enable', mode === 'install-success'
? { statusCode: 200, body: { data: { update_required: false } } }
: { statusCode: 500, body: { data: { message: 'Forced failure' } } }).as('enableApps')

cy.get('[data-cy-setup-recommended-apps-install]').click()

// The strict password-confirmation dialog must appear and must result in a
// Basic auth header on the enable request.
cy.findByRole('dialog', { name: 'Authentication required' })
.should('be.visible')
handlePasswordConfirmation(randAdmin)
cy.wait('@enableApps')
.its('request.headers.authorization')
.should('match', /^Basic /)

if (mode === 'install-success') {
// Frontend redirects via window.location to the default page.
cy.location('pathname', { timeout: 10000 })
.should('not.include', '/core/apps/recommended')
} else {
// Stay on the recommended-apps page and surface the per-app error state.
cy.location('pathname').should('include', '/core/apps/recommended')
cy.get('[data-cy-setup-recommended-apps]')
.should('contain.text', 'App download or installation failed')
}
}
4 changes: 2 additions & 2 deletions dist/core-common.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/core-common.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/core-recommendedapps.js

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions dist/core-recommendedapps.js.license
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-FileCopyrightText: webfansplz
SPDX-FileCopyrightText: perfect-debounce developers
SPDX-FileCopyrightText: hookable developers
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: debounce developers
SPDX-FileCopyrightText: atomiks
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
Expand All @@ -13,11 +18,21 @@ SPDX-FileCopyrightText: Matt Zabriskie
SPDX-FileCopyrightText: GitHub Inc.
SPDX-FileCopyrightText: Feross Aboukhadijeh
SPDX-FileCopyrightText: Evan You
SPDX-FileCopyrightText: Eduardo San Martin Morote
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: David Clark
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
SPDX-FileCopyrightText: Anthony Fu <anthonyfu117@hotmail.com>


This file is generated from multiple sources. Included packages:
- @floating-ui/core
- version: 1.7.5
- license: MIT
- @floating-ui/utils
- version: 0.2.11
- license: MIT
- @nextcloud/auth
- version: 2.5.3
- license: GPL-3.0-or-later
Expand All @@ -27,6 +42,9 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/browser-storage
- version: 0.5.0
- license: GPL-3.0-or-later
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- semver
- version: 7.7.2
- license: ISC
Expand All @@ -42,6 +60,48 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/logger
- version: 3.0.3
- license: GPL-3.0-or-later
- @nextcloud/vue
- version: 9.6.0
- license: AGPL-3.0-or-later
- @vue/devtools-shared
- version: 8.1.0
- license: MIT
- @vue/reactivity
- version: 3.5.30
- license: MIT
- @vue/runtime-core
- version: 3.5.30
- license: MIT
- @vue/runtime-dom
- version: 3.5.30
- license: MIT
- @vue/shared
- version: 3.5.30
- license: MIT
- @vueuse/core
- version: 14.2.1
- license: MIT
- @vueuse/shared
- version: 14.2.1
- license: MIT
- perfect-debounce
- version: 2.1.0
- license: MIT
- @vue/devtools-api
- version: 8.1.0
- license: MIT
- @vue/devtools-kit
- version: 8.1.0
- license: MIT
- vue-router
- version: 5.0.3
- license: MIT
- vue
- version: 3.5.30
- license: MIT
- @nextcloud/password-confirmation
- version: 6.1.0
- license: MIT
- @nextcloud/router
- version: 3.1.0
- license: GPL-3.0-or-later
Expand All @@ -54,15 +114,27 @@ This file is generated from multiple sources. Included packages:
- base64-js
- version: 1.5.1
- license: MIT
- birpc
- version: 2.9.0
- license: MIT
- css-loader
- version: 7.1.2
- license: MIT
- debounce
- version: 3.0.0
- license: MIT
- dompurify
- version: 3.4.0
- license: (MPL-2.0 OR Apache-2.0)
- escape-html
- version: 1.0.3
- license: MIT
- focus-trap
- version: 8.0.1
- license: MIT
- hookable
- version: 5.5.3
- license: MIT
- ieee754
- version: 1.2.1
- license: BSD-3-Clause
Expand All @@ -75,6 +147,9 @@ This file is generated from multiple sources. Included packages:
- style-loader
- version: 4.0.0
- license: MIT
- tabbable
- version: 6.4.0
- license: MIT
- vue-loader
- version: 15.11.1
- license: MIT
Expand Down
2 changes: 1 addition & 1 deletion dist/core-recommendedapps.js.map

Large diffs are not rendered by default.

Loading