diff --git a/changelog/unreleased/bugfix-configurable-request-concurrency b/changelog/unreleased/bugfix-configurable-request-concurrency new file mode 100644 index 00000000000..56655620f7e --- /dev/null +++ b/changelog/unreleased/bugfix-configurable-request-concurrency @@ -0,0 +1,6 @@ +Bugfix: Configurable concurrent requests + +In order to ease the load on the backend we've introduced config options to limit the number of concurrent requests in certain areas. + +https://github.com/owncloud/web/issues/10227 +https://github.com/owncloud/web/pull/10230 diff --git a/docs/getting-started.md b/docs/getting-started.md index dd3ec35b7a2..c2ed26b8e27 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -81,6 +81,12 @@ Depending on the backend you are using, there are sample config files provided i - `options.logoutUrl` Adds a link to the user's profile page to point him to an external page, where he can manage his session and devices. This is helpful when an external IdP is used. This option is disabled by default. - `options.ocm.openRemotely` Specifies whether opening files in remote shares in their original ocm instance should be enabled. Defaults to `false`. - `options.userListRequiresFilter` Defines whether one or more filters must be set in order to list users in the Web admin settings. Set this option to 'true' if running in an environment with a lot of users and listing all users could slow down performance. Defaults to `false`. +- `options.concurrentRequests` This accepts an object with the following optional fields to customize the maximum number of concurrent requests in code paths where we limit concurrent requests + - `resourceBatchActions` Concurrent number of file/folder/space batch actions like e.g. accepting shares. Defaults to 4. + - `sse` Concurrent number of SSE event handlers. Defaults to 4. + - `shares` Accepts an object regarding the following sharing related options: + - `create` Concurrent number of share invites. Defaults to 4. + - `list` Concurrent number of individually loaded shares. Defaults to 2. #### Scripts and Styles diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue index 24226dbc10c..d44907cb513 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue @@ -152,6 +152,7 @@ import { useCapabilityFilesSharingResharingDefault, useCapabilityShareJailEnabled, useClientService, + useConfigurationManager, useStore } from '@ownclouders/web-pkg' @@ -198,6 +199,7 @@ export default defineComponent({ const saving = ref(false) const savingDelayed = ref(false) const notifyEnabled = ref(false) + const configurationManager = useConfigurationManager() watch(saving, (newValue) => { if (!newValue) { @@ -245,6 +247,10 @@ export default defineComponent({ { prefix: 'sm:', description: 'federated' } ] + const createSharesConcurrentRequests = computed(() => { + return configurationManager.options.concurrentRequests.shares.create + }) + return { resource: inject('resource'), hasResharing: useCapabilityFilesSharingResharing(store), @@ -260,6 +266,7 @@ export default defineComponent({ contextMenuButtonRef, notifyEnabled, federatedUsers, + createSharesConcurrentRequests, // CERN accountType, @@ -476,7 +483,9 @@ export default defineComponent({ this.saving = true const errors = [] - const saveQueue = new PQueue({ concurrency: 4 }) + const saveQueue = new PQueue({ + concurrency: this.createSharesConcurrentRequests + }) const savePromises = [] this.selectedCollaborators.forEach((collaborator) => { savePromises.push( diff --git a/packages/web-app-files/src/store/actions.ts b/packages/web-app-files/src/store/actions.ts index cdb9973d0e1..9d1308f0d8d 100644 --- a/packages/web-app-files/src/store/actions.ts +++ b/packages/web-app-files/src/store/actions.ts @@ -349,7 +349,7 @@ export default { context.commit('LOAD_INDICATORS', path) } }, - async loadShares(context, { client, path, storageId, useCached = true }) { + async loadShares(context, { client, configurationManager, path, storageId, useCached = true }) { if (context.state.sharesLoading) { await context.state.sharesLoading } @@ -366,7 +366,9 @@ export default { const currentlyLoadedShares = [...context.state.outgoingShares, ...context.state.incomingShares] const shares = [] - const shareQueriesQueue = new PQueue({ concurrency: 2 }) + const shareQueriesQueue = new PQueue({ + concurrency: configurationManager.options.concurrentRequests.shares.list + }) const shareQueriesPromises = [] const { highlightedFile } = context.getters diff --git a/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts b/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts index 8f963019369..18f8a0286d2 100644 --- a/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts +++ b/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts @@ -2,7 +2,7 @@ import CreateAndUpload from 'web-app-files/src/components/AppBar/CreateAndUpload import { mock } from 'jest-mock-extended' import { Resource, SpaceResource } from '@ownclouders/web-client/src/helpers' import { Drive } from '@ownclouders/web-client/src/generated' -import { useRequest } from '@ownclouders/web-pkg' +import { FileAction, useFileActionsCreateNewFile, useRequest } from '@ownclouders/web-pkg' import { eventBus, UppyResource } from '@ownclouders/web-pkg' import { createStore, @@ -14,12 +14,15 @@ import { import { RouteLocation } from 'vue-router' import { useExtensionRegistry } from '@ownclouders/web-pkg' import { useExtensionRegistryMock } from 'web-test-helpers/src/mocks/useExtensionRegistryMock' +import { ref } from 'vue' jest.mock('@ownclouders/web-pkg', () => ({ ...jest.requireActual('@ownclouders/web-pkg'), useAccessToken: jest.fn(), useExtensionRegistry: jest.fn(), - useRequest: jest.fn() + useRequest: jest.fn(), + useFileActionsCreateNewFile: jest.fn(), + useFileActions: jest.fn() })) const elSelector = { @@ -190,6 +193,16 @@ function getWrapper({ })) jest.mocked(useExtensionRegistry).mockImplementation(() => useExtensionRegistryMock()) + jest.mocked(useFileActionsCreateNewFile).mockReturnValue( + mock>({ + actions: ref([ + mock({ label: () => 'Plain text file', ext: 'txt' }), + mock({ label: () => 'Mark-down file', ext: 'md' }), + mock({ label: () => 'Draw.io document', ext: 'drawio' }) + ]) + }) + ) + const storeOptions = { ...defaultStoreMockOptions, getters: { diff --git a/packages/web-app-files/tests/unit/components/AppBar/__snapshots__/CreateAndUpload.spec.ts.snap b/packages/web-app-files/tests/unit/components/AppBar/__snapshots__/CreateAndUpload.spec.ts.snap index 4f601b22987..167ad8070bf 100644 --- a/packages/web-app-files/tests/unit/components/AppBar/__snapshots__/CreateAndUpload.spec.ts.snap +++ b/packages/web-app-files/tests/unit/components/AppBar/__snapshots__/CreateAndUpload.spec.ts.snap @@ -53,7 +53,34 @@ exports[`CreateAndUpload component file handlers should show additional handlers Folder - +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • - +
  • + +
  • +
  • + +
  • +
  • + +
  • +