From 103631de15a0dfd6c8fbc813f50be4eb89b50ef0 Mon Sep 17 00:00:00 2001
From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Date: Tue, 6 May 2025 13:59:32 +0700
Subject: [PATCH 01/48] V15 QA Updated the build stage to align with the azure
pipelines yml (#19235)
---
build/nightly-E2E-test-pipelines.yml | 55 +++++++++++++++++++---------
1 file changed, 37 insertions(+), 18 deletions(-)
diff --git a/build/nightly-E2E-test-pipelines.yml b/build/nightly-E2E-test-pipelines.yml
index 8cbb065cd7..54af8efc06 100644
--- a/build/nightly-E2E-test-pipelines.yml
+++ b/build/nightly-E2E-test-pipelines.yml
@@ -32,25 +32,17 @@ stages:
- job: A
displayName: Build Umbraco CMS
pool:
- vmImage: 'ubuntu-latest'
+ vmImage: "windows-latest"
steps:
- checkout: self
- fetchDepth: 0
- submodules: true
+ submodules: false
+ lfs: false,
+ fetchDepth: 500
+ - template: templates/backoffice-install.yml
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
- - template: templates/backoffice-install.yml
- - script: npm run build:for:cms
- displayName: Run build (Bellissima)
- workingDirectory: src/Umbraco.Web.UI.Client
- - script: npm ci --no-fund --no-audit --prefer-offline
- displayName: Run npm ci (Login)
- workingDirectory: src/Umbraco.Web.UI.Login
- - script: npm run build
- displayName: Run npm build (Login)
- workingDirectory: src/Umbraco.Web.UI.Login
- task: DotNetCoreCLI@2
displayName: Run dotnet restore
inputs:
@@ -62,7 +54,7 @@ stages:
inputs:
command: build
projects: $(solution)
- arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
+ arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
- task: PublishPipelineArtifact@1
displayName: Publish nupkg
inputs:
@@ -74,6 +66,33 @@ stages:
targetPath: $(Build.SourcesDirectory)
artifactName: build_output
+ - job: B
+ displayName: Build Bellissima Package
+ pool:
+ vmImage: "ubuntu-latest"
+ steps:
+ - checkout: self
+ submodules: false
+ lfs: false,
+ fetchDepth: 500
+ - template: templates/backoffice-install.yml
+ - script: npm run build:for:npm
+ displayName: Run build:for:npm
+ workingDirectory: src/Umbraco.Web.UI.Client
+ - bash: |
+ echo "##[command]Running npm pack"
+ echo "##[debug]Output directory: $(Build.ArtifactStagingDirectory)"
+ mkdir $(Build.ArtifactStagingDirectory)/npm
+ npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm
+ mv .npmrc $(Build.ArtifactStagingDirectory)/npm/
+ displayName: Run npm pack
+ workingDirectory: src/Umbraco.Web.UI.Client
+ - task: PublishPipelineArtifact@1
+ displayName: Publish Bellissima npm artifact
+ inputs:
+ targetPath: $(Build.ArtifactStagingDirectory)/npm
+ artifactName: npm
+
- stage: E2E
displayName: E2E Tests
dependsOn: Build
@@ -209,8 +228,8 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- - pwsh: npx playwright install --with-deps
- displayName: Install Playwright
+ - pwsh: npx playwright install chromium
+ displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
@@ -381,8 +400,8 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- - pwsh: npx playwright install --with-deps
- displayName: Install Playwright
+ - pwsh: npx playwright install chromium
+ displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
From b106305167ede089e978b88ecef25a36018e889b Mon Sep 17 00:00:00 2001
From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Date: Tue, 6 May 2025 14:15:30 +0700
Subject: [PATCH 02/48] V15 QA Added acceptance tests for tiptap style select
(#19234)
---
.../package-lock.json | 8 +-
.../Umbraco.Tests.AcceptanceTest/package.json | 2 +-
.../RichTextEditor/TiptapStyleSelect.spec.ts | 114 ++++++++++++++++++
3 files changed, 119 insertions(+), 5 deletions(-)
create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/TiptapStyleSelect.spec.ts
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
index 1353eb665a..f975ad0cbd 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
+++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
@@ -8,7 +8,7 @@
"hasInstallScript": true,
"dependencies": {
"@umbraco/json-models-builders": "^2.0.33",
- "@umbraco/playwright-testhelpers": "^15.0.49",
+ "@umbraco/playwright-testhelpers": "^15.0.50",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -66,9 +66,9 @@
}
},
"node_modules/@umbraco/playwright-testhelpers": {
- "version": "15.0.49",
- "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.49.tgz",
- "integrity": "sha512-1At/e057u6rB3T3iH8tR6SLXnYRZJsCVjmm8jm+6sftJDvgB0Q5kXKaSDyLTU6wVuLALiDNUuNuJ86FgOOdUJw==",
+ "version": "15.0.50",
+ "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.50.tgz",
+ "integrity": "sha512-gi5bb4DShw3lmEdmQhKpPdkS6Uzg4CdNkrJDSkkUTE8CKY7T5goyE4QBTU8kj+LMHR2DnB7qyRUXrYWyS1ECiQ==",
"dependencies": {
"@umbraco/json-models-builders": "2.0.33",
"node-fetch": "^2.6.7"
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json
index 98d612ca81..0396992d5c 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/package.json
+++ b/tests/Umbraco.Tests.AcceptanceTest/package.json
@@ -21,7 +21,7 @@
},
"dependencies": {
"@umbraco/json-models-builders": "^2.0.33",
- "@umbraco/playwright-testhelpers": "^15.0.49",
+ "@umbraco/playwright-testhelpers": "^15.0.50",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/TiptapStyleSelect.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/TiptapStyleSelect.spec.ts
new file mode 100644
index 0000000000..faab11fe5f
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/TiptapStyleSelect.spec.ts
@@ -0,0 +1,114 @@
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
+import {expect} from "@playwright/test";
+
+const contentName = 'TestContent';
+const documentTypeName = 'TestDocumentTypeForContent';
+const customDataTypeName = 'Test RTE Tiptap Style Select';
+const inputText = 'This is Tiptap test';
+
+test.beforeEach(async ({umbracoApi, umbracoUi}) => {
+ const customDataTypeId = await umbracoApi.dataType.createTiptapDataTypeWithStyleSelect(customDataTypeName);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.enterRTETipTapEditor(inputText);
+ await umbracoUi.content.selectAllRTETipTapEditorText();
+})
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.document.ensureNameNotExists(contentName);
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
+
+test('can apply page header format', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.content.clickStyleSelectButton();
+
+ // Act
+ await umbracoUi.content.hoverCascadingMenuItemWithName('Headers');
+ await umbracoUi.content.clickCascadingMenuItemWithName('Page header');
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.markup).toEqual('
' + inputText + '
');
+});
+
+test('can apply section header format', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.content.clickStyleSelectButton();
+
+ // Act
+ await umbracoUi.content.hoverCascadingMenuItemWithName('Headers');
+ await umbracoUi.content.clickCascadingMenuItemWithName('Section header');
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.markup).toEqual('' + inputText + '
');
+});
+
+test('can apply paragraph header format', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.content.clickStyleSelectButton();
+
+ // Act
+ await umbracoUi.content.hoverCascadingMenuItemWithName('Headers');
+ await umbracoUi.content.clickCascadingMenuItemWithName('Paragraph header');
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.markup).toEqual('' + inputText + '
');
+});
+
+test('can apply paragraph blocks format', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.content.clickStyleSelectButton();
+
+ // Act
+ await umbracoUi.content.hoverCascadingMenuItemWithName('Blocks');
+ await umbracoUi.content.clickCascadingMenuItemWithName('Paragraph');
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.markup).toEqual('' + inputText + '
');
+});
+
+test('can apply block quote format', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.content.clickStyleSelectButton();
+
+ // Act
+ await umbracoUi.content.hoverCascadingMenuItemWithName('Containers');
+ await umbracoUi.content.clickCascadingMenuItemWithName('Block quote');
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.markup).toEqual('' + inputText + '
');
+});
+
+test('can apply code block format', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.content.clickStyleSelectButton();
+
+ // Act
+ await umbracoUi.content.hoverCascadingMenuItemWithName('Containers');
+ await umbracoUi.content.clickCascadingMenuItemWithName('Code block');
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.markup).toEqual('' + inputText + '
');
+});
\ No newline at end of file
From b01def08724ce10e7164a2f467192cf47e145a56 Mon Sep 17 00:00:00 2001
From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
Date: Thu, 8 May 2025 08:53:29 +0200
Subject: [PATCH 03/48] V15 QA added a fix for flaky integration tests run on
SQL Server Linux (#18965)
---
build/azure-pipelines.yml | 27 ++++
build/nightly-E2E-test-pipelines.yml | 218 ++++++++++++++++++++++++++-
2 files changed, 241 insertions(+), 4 deletions(-)
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index a33ee744f7..92621c30d3 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -432,6 +432,33 @@ stages:
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ - powershell: |
+ $maxAttempts = 12
+ $attempt = 0
+ $status = ""
+
+ while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
+ Start-Sleep -Seconds 5
+ # We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
+ $status = docker inspect -f '{{.State.Status}}' mssql
+
+ if ($status -ne 'running') {
+ Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
+ $attempt++
+ }
+ }
+
+ if ($status -eq 'running') {
+ Write-Host "SQL Server container is running"
+ docker ps -a
+ } else {
+ Write-Host "SQL Server did not become ready in time. Last known status: $status"
+ docker logs mssql
+ exit 1
+ }
+ displayName: Wait for SQL Server to be ready (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
diff --git a/build/nightly-E2E-test-pipelines.yml b/build/nightly-E2E-test-pipelines.yml
index 54af8efc06..0740eed585 100644
--- a/build/nightly-E2E-test-pipelines.yml
+++ b/build/nightly-E2E-test-pipelines.yml
@@ -93,6 +93,189 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)/npm
artifactName: npm
+ - stage: Integration
+ displayName: Integration Tests
+ dependsOn: Build
+ jobs:
+ # Integration Tests (SQLite)
+ - job:
+ timeoutInMinutes: 180
+ displayName: Integration Tests (SQLite)
+ strategy:
+ matrix:
+ # Windows:
+ # vmImage: 'windows-latest'
+ # We split the tests into 3 parts for each OS to reduce the time it takes to run them on the pipeline
+ LinuxPart1Of3:
+ vmImage: "ubuntu-latest"
+ # Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
+ LinuxPart2Of3:
+ vmImage: "ubuntu-latest"
+ # Filter tests that are part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
+ LinuxPart3Of3:
+ vmImage: "ubuntu-latest"
+ # Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
+ testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
+ macOSPart1Of3:
+ vmImage: "macOS-latest"
+ # Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
+ macOSPart2Of3:
+ vmImage: "macOS-latest"
+ # Filter tests that are part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
+ macOSPart3Of3:
+ vmImage: "macOS-latest"
+ # Filter tests that are not part of the Umbraco.Infrastructure namespace.
+ testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
+ pool:
+ vmImage: $(vmImage)
+ variables:
+ Tests__Database__DatabaseType: "Sqlite"
+ steps:
+ - checkout: self
+ submodules: false
+ lfs: false,
+ fetchDepth: 1
+ fetchFilter: tree:0
+ # Setup test environment
+ - task: DownloadPipelineArtifact@2
+ displayName: Download build artifacts
+ inputs:
+ artifact: build_output
+ path: $(Build.SourcesDirectory)
+
+ - task: UseDotNet@2
+ displayName: Use .NET SDK from global.json
+ inputs:
+ useGlobalJson: true
+
+ # Test
+ - task: DotNetCoreCLI@2
+ displayName: Run dotnet test
+ inputs:
+ command: test
+ projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
+ testRunTitle: Integration Tests SQLite - $(Agent.OS)
+ arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
+
+ # Integration Tests (SQL Server)
+ - job:
+ timeoutInMinutes: 180
+ displayName: Integration Tests (SQL Server)
+ strategy:
+ matrix:
+ # We split the tests into 3 parts for each OS to reduce the time it takes to run them on the pipeline
+ WindowsPart1Of3:
+ vmImage: "windows-latest"
+ Tests__Database__DatabaseType: LocalDb
+ Tests__Database__SQLServerMasterConnectionString: N/A
+ # Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
+ WindowsPart2Of3:
+ vmImage: "windows-latest"
+ Tests__Database__DatabaseType: LocalDb
+ Tests__Database__SQLServerMasterConnectionString: N/A
+ # Filter tests that are part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
+ WindowsPart3Of3:
+ vmImage: "windows-latest"
+ Tests__Database__DatabaseType: LocalDb
+ Tests__Database__SQLServerMasterConnectionString: N/A
+ # Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
+ testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
+ LinuxPart1Of3:
+ vmImage: "ubuntu-latest"
+ SA_PASSWORD: UmbracoIntegration123!
+ Tests__Database__DatabaseType: SqlServer
+ Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
+ # Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
+ LinuxPart2Of3:
+ vmImage: "ubuntu-latest"
+ SA_PASSWORD: UmbracoIntegration123!
+ Tests__Database__DatabaseType: SqlServer
+ Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
+ # Filter tests that are part of the Umbraco.Infrastructure.Service namespace
+ testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
+ LinuxPart3Of3:
+ vmImage: "ubuntu-latest"
+ SA_PASSWORD: UmbracoIntegration123!
+ Tests__Database__DatabaseType: SqlServer
+ Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
+ # Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
+ testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
+ pool:
+ vmImage: $(vmImage)
+ steps:
+ # Setup test environment
+ - task: DownloadPipelineArtifact@2
+ displayName: Download build artifacts
+ inputs:
+ artifact: build_output
+ path: $(Build.SourcesDirectory)
+
+ - task: UseDotNet@2
+ displayName: Use .NET SDK from global.json
+ inputs:
+ useGlobalJson: true
+
+ # Start SQL Server
+ - powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
+ displayName: Start SQL Server Docker image (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
+ - powershell: |
+ $maxAttempts = 12
+ $attempt = 0
+ $status = ""
+
+ while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
+ Start-Sleep -Seconds 5
+ # We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
+ $status = docker inspect -f '{{.State.Status}}' mssql
+
+ if ($status -ne 'running') {
+ Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
+ $attempt++
+ }
+ }
+
+ if ($status -eq 'running') {
+ Write-Host "SQL Server container is running"
+ docker ps -a
+ } else {
+ Write-Host "SQL Server did not become ready in time. Last known status: $status"
+ docker logs mssql
+ exit 1
+ }
+ displayName: Wait for SQL Server to be ready (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: SqlLocalDB start MSSQLLocalDB
+ displayName: Start SQL Server LocalDB (Windows)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+
+ # Test
+ - task: DotNetCoreCLI@2
+ displayName: Run dotnet test
+ inputs:
+ command: test
+ projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
+ testRunTitle: Integration Tests SQL Server - $(Agent.OS)
+ arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
+
+ # Stop SQL Server
+ - pwsh: docker stop mssql
+ displayName: Stop SQL Server Docker image (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
+ - pwsh: SqlLocalDB stop MSSQLLocalDB
+ displayName: Stop SQL Server LocalDB (Windows)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
+
- stage: E2E
displayName: E2E Tests
dependsOn: Build
@@ -290,17 +473,17 @@ stages:
testCommand: "npm run testSqlite -- --shard=1/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
- CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
+ CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart2Of3:
testCommand: "npm run testSqlite -- --shard=2/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
- CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
+ CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart3Of3:
testCommand: "npm run testSqlite -- --shard=3/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
- CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
+ CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
WindowsPart1Of3:
vmImage: "windows-latest"
testCommand: "npm run testSqlite -- --shard=1/3"
@@ -371,6 +554,33 @@ stages:
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ - powershell: |
+ $maxAttempts = 12
+ $attempt = 0
+ $status = ""
+
+ while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
+ Start-Sleep -Seconds 5
+ # We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
+ $status = docker inspect -f '{{.State.Status}}' mssql
+
+ if ($status -ne 'running') {
+ Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
+ $attempt++
+ }
+ }
+
+ if ($status -eq 'running') {
+ Write-Host "SQL Server container is running"
+ docker ps -a
+ } else {
+ Write-Host "SQL Server did not become ready in time. Last known status: $status"
+ docker logs mssql
+ exit 1
+ }
+ displayName: Wait for SQL Server to be ready (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
@@ -447,7 +657,7 @@ stages:
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
-
+
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
From 275478066eb121b2e935adff70838b5081bf91e3 Mon Sep 17 00:00:00 2001
From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
Date: Thu, 8 May 2025 11:12:37 +0200
Subject: [PATCH 04/48] V15 QA complex block grid test (#18347)
---
.../BlockGrid/ComplexBlockGridTest.spec.ts | 134 ++++++++++++++++++
1 file changed, 134 insertions(+)
create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ComplexBlockGridTest.spec.ts
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ComplexBlockGridTest.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ComplexBlockGridTest.spec.ts
new file mode 100644
index 0000000000..78b1ea1213
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ComplexBlockGridTest.spec.ts
@@ -0,0 +1,134 @@
+import {expect} from '@playwright/test';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
+
+// DocumentType
+const documentTypeName = 'TestDocumentType';
+let documentTypeId = '';
+const groupName = 'TestGroup';
+
+// Content
+const contentName = 'TestContent';
+let contentId = '';
+
+// Property Value
+const wrongPropertyValue = 'This is a test with wrong value**';
+const correctPropertyValue = 'Test';
+
+// ElementTypes
+// TextString Element Type (for Block List)
+const textStringElementGroupName = 'TextStringElementGroup';
+const textStringElementTypeName = 'TestElementWithTextString';
+const textStringElementRegex = '^[a-zA-Z0-9]*$';
+let textStringElementTypeId = '';
+// Area Element Type (for Block Grid)
+const areaElementTypeName = 'TestElementArea';
+const areaAlias = 'testArea';
+let areaElementTypeId = '';
+// Rich Text Editor Element Type (for Block Grid)
+const richTextEditorElementGroupName = 'RichTextEditorElementGroup';
+const richTextEditorElementTypeName = 'RichTextEditorTestElement';
+let richTextEditorElementTypeId = '';
+// Block List Element Type
+const blockListElementTypeName = 'BlockListElement';
+const blockListGroupName = 'BlockListGroup';
+let blockListElementTypeId = '';
+
+// DataTypes
+const blockGridDataTypeName = 'TestBlockGridEditor';
+const blockListDataTypeName = 'TestBlockListEditor';
+const textStringElementDataTypeName = 'Textstring';
+const richTextDataTypeName = 'Rich Text Editor';
+let blockListDataTypeId = '';
+let blockGridDataTypeId = '';
+
+test.beforeEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(areaElementTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(richTextEditorElementTypeName);
+ await umbracoApi.document.ensureNameNotExists(contentName);
+ await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(areaElementTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(richTextEditorElementTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
+ await umbracoApi.document.ensureNameNotExists(contentName);
+ await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
+});
+
+test('can update property value nested in a block grid area with an RTE with a block list editor', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ test.slow();
+ // Arrange
+ // ElementType with Textstring And REGEX only accept letters and numbers
+ const textStringElementDataType = await umbracoApi.dataType.getByName(textStringElementDataTypeName);
+ textStringElementTypeId = await umbracoApi.documentType.createElementTypeWithRegexValidation(textStringElementTypeName, textStringElementGroupName, textStringElementDataTypeName, textStringElementDataType.id, textStringElementRegex);
+ // Block List Editor with Textstring
+ blockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListDataTypeName, textStringElementTypeId);
+ // ElementType with Block List Editor
+ blockListElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockListElementTypeName, blockListGroupName, blockListDataTypeName, blockListDataTypeId);
+ // Rich Text Editor in an ElementType, with a Block(Element Type), the block contains a Block List Editor
+ const richTextEditorId = await umbracoApi.dataType.createRichTextEditorWithABlock(richTextDataTypeName, blockListElementTypeId);
+ richTextEditorElementTypeId = await umbracoApi.documentType.createDefaultElementType(richTextEditorElementTypeName, richTextEditorElementGroupName, richTextDataTypeName, richTextEditorId);
+ // ElementType Area that is Empty
+ areaElementTypeId = await umbracoApi.documentType.createEmptyElementType(areaElementTypeName);
+ // Block Grid with 2 blocks, one with RTE and Inline, and one with areas
+ blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockWithInlineEditingModeAndABlockWithAnArea(blockGridDataTypeName, richTextEditorElementTypeId, true, areaElementTypeId, areaAlias);
+ // Document Type with the following
+ documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId, groupName);
+ // Creates Content
+ contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+
+ // Act
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockGridElementWithName(areaElementTypeName);
+ await umbracoUi.content.clickSelectBlockElementWithName(areaElementTypeName);
+ await umbracoUi.content.clickAddBlockGridElementWithName(richTextEditorElementTypeName);
+ await umbracoUi.content.clickExactLinkWithName(richTextEditorElementTypeName);
+ await umbracoUi.content.clickInsertBlockButton();
+ await umbracoUi.content.clickExactLinkWithName(blockListElementTypeName);
+ await umbracoUi.content.clickAddBlockGridElementWithName(textStringElementTypeName);
+ await umbracoUi.content.clickExactLinkWithName(textStringElementTypeName);
+ // Enter text in the textstring block that won't match regex
+ await umbracoUi.content.enterPropertyValue(textStringElementDataTypeName, wrongPropertyValue);
+ await umbracoUi.content.clickCreateButtonForModalWithElementTypeNameAndGroupName(textStringElementTypeName, textStringElementGroupName);
+ await umbracoUi.content.clickCreateButtonForModalWithElementTypeNameAndGroupName(blockListElementTypeName, blockListGroupName);
+ await umbracoUi.content.clickCreateButtonForModalWithElementTypeNameAndGroupName(richTextEditorElementTypeName, richTextEditorElementGroupName);
+ await umbracoUi.content.clickSaveAndPublishButton();
+ // Checks that the error notification is shown since the textstring block has the wrong value
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved, true, true);
+ await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished, true, true);
+ // Updates the textstring block with the correct value
+ await umbracoUi.waitForTimeout(1000);
+ await umbracoUi.content.clickBlockElementWithName(blockListElementTypeName);
+ await umbracoUi.content.clickEditBlockListEntryWithName(textStringElementTypeName);
+ await umbracoUi.content.enterPropertyValue(textStringElementDataTypeName, correctPropertyValue);
+ await umbracoUi.content.clickUpdateButtonForModalWithElementTypeNameAndGroupName(textStringElementTypeName, textStringElementGroupName);
+ await umbracoUi.content.clickUpdateButtonForModalWithElementTypeNameAndGroupName(blockListElementTypeName, blockListGroupName);
+ await umbracoUi.content.clickSaveAndPublishButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved, true, true);
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published, true, true);
+ // Checks if published
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.variants[0].state).toBe('Published');
+ // Checks if the textstring block has the correct value after reloading the page
+ await umbracoUi.reloadPage();
+ // Waits to make sure the page has loaded
+ await umbracoUi.waitForTimeout(2000);
+ await umbracoUi.content.clickBlockElementWithName(blockListElementTypeName);
+ // Needs to wait to make sure it has loaded
+ await umbracoUi.waitForTimeout(2000);
+ await umbracoUi.content.clickEditBlockListEntryWithName(textStringElementTypeName);
+ await umbracoUi.content.doesPropertyContainValue(textStringElementDataTypeName, correctPropertyValue);
+});
From 822cfe9c286a1b40e2015dab8e6ed8ddda9dca0b Mon Sep 17 00:00:00 2001
From: Mads Rasmussen
Date: Thu, 8 May 2025 11:17:57 +0200
Subject: [PATCH 05/48] Batch item rest requests (#19233)
---
.../src/packages/core/entity-item/index.ts | 1 +
.../index.ts | 1 +
.../item-data-api-get-request.controller.ts | 66 +++++++++++++++++++
.../types.ts | 6 ++
.../src/packages/core/entity-item/types.ts | 1 +
.../item/item-server-data-source-base.ts | 24 ++++---
.../packages/core/resources/data-api/types.ts | 3 +
.../src/packages/core/resources/index.ts | 13 ++--
.../try-execute/batch-try-execute.function.ts | 17 +++++
.../core/resources/try-execute/index.ts | 5 ++
.../try-execute.controller.ts | 8 +--
.../{ => try-execute}/tryExecute.function.ts | 2 +-
.../tryExecuteAndNotify.function.ts | 2 +-
.../tryXhrRequest.function.ts | 8 +--
.../src/packages/core/resources/types.ts | 1 +
.../core/utils/array/batch-array.test.ts | 25 +++++++
.../packages/core/utils/array/batch-array.ts | 16 +++++
.../src/packages/core/utils/array/index.ts | 1 +
.../src/packages/core/utils/index.ts | 1 +
.../item/data-type-item.server.data-source.ts | 19 ++++--
.../dictionary-item.server.data-source.ts | 19 ++++--
...ument-blueprint-item.server.data-source.ts | 24 ++++---
.../document-type-item.server.data-source.ts | 20 ++++--
.../document-item.server.data-source.ts | 19 ++++--
.../document-url.server.data-source.ts | 20 ++++--
.../item/language-item.server.data-source.ts | 19 ++++--
.../media-type-item.server.data-source.ts | 19 ++++--
.../item/media-item.server.data-source.ts | 26 +++++---
.../media-url.server.data-source.ts | 19 ++++--
.../member-group-item.server.data-source.ts | 19 ++++--
.../member-type-item.server.data-source.ts | 19 ++++--
.../member-item.server.data-source.ts | 19 ++++--
.../static-file-item.server.data-source.ts | 27 +++++---
.../item/template-item.server.data-source.ts | 19 ++++--
.../user-group-item.server.data-source.ts | 19 ++++--
.../item/user-item.server.data-source.ts | 19 ++++--
.../item/webhook-item.server.data-source.ts | 19 ++++--
37 files changed, 450 insertions(+), 115 deletions(-)
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/item-data-api-get-request.controller.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/types.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/resources/data-api/types.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/batch-try-execute.function.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/index.ts
rename src/Umbraco.Web.UI.Client/src/packages/core/resources/{ => try-execute}/try-execute.controller.ts (86%)
rename src/Umbraco.Web.UI.Client/src/packages/core/resources/{ => try-execute}/tryExecute.function.ts (93%)
rename src/Umbraco.Web.UI.Client/src/packages/core/resources/{ => try-execute}/tryExecuteAndNotify.function.ts (96%)
rename src/Umbraco.Web.UI.Client/src/packages/core/resources/{ => try-execute}/tryXhrRequest.function.ts (94%)
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.test.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/utils/array/index.ts
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts
index 5ada9eda75..a0ee69f820 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts
@@ -1 +1,2 @@
+export * from './item-data-api-get-request-controller/index.js';
export * from './entity-item-ref/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/index.ts
new file mode 100644
index 0000000000..5bd7a5f06f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/index.ts
@@ -0,0 +1 @@
+export * from './item-data-api-get-request.controller.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/item-data-api-get-request.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/item-data-api-get-request.controller.ts
new file mode 100644
index 0000000000..b10a783b97
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/item-data-api-get-request.controller.ts
@@ -0,0 +1,66 @@
+import type { UmbItemDataApiGetRequestControllerArgs } from './types.js';
+import {
+ batchTryExecute,
+ tryExecute,
+ UmbError,
+ type UmbApiError,
+ type UmbCancelError,
+ type UmbDataApiResponse,
+} from '@umbraco-cms/backoffice/resources';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { batchArray } from '@umbraco-cms/backoffice/utils';
+import { umbPeekError } from '@umbraco-cms/backoffice/notification';
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+
+export class UmbItemDataApiGetRequestController<
+ ResponseModelType extends UmbDataApiResponse,
+> extends UmbControllerBase {
+ #apiCallback: (args: { uniques: Array }) => Promise;
+ #uniques: Array;
+ #batchSize: number = 40;
+
+ constructor(host: UmbControllerHost, args: UmbItemDataApiGetRequestControllerArgs) {
+ super(host);
+ this.#apiCallback = args.api;
+ this.#uniques = args.uniques;
+ }
+
+ async request() {
+ if (!this.#uniques) throw new Error('Uniques are missing');
+
+ let data: ResponseModelType['data'] | undefined;
+ let error: UmbError | UmbApiError | UmbCancelError | Error | undefined;
+
+ if (this.#uniques.length > this.#batchSize) {
+ const chunks = batchArray(this.#uniques, this.#batchSize);
+ const results = await batchTryExecute(this, chunks, (chunk) => this.#apiCallback({ uniques: chunk }));
+
+ const errors = results.filter((promiseResult) => promiseResult.status === 'rejected');
+
+ if (errors.length > 0) {
+ error = await this.#getAndHandleErrorResult(errors);
+ }
+
+ data = results
+ .filter((promiseResult) => promiseResult.status === 'fulfilled')
+ .flatMap((promiseResult) => promiseResult.value.data);
+ } else {
+ const result = await tryExecute(this, this.#apiCallback({ uniques: this.#uniques }));
+ data = result.data;
+ error = result.error;
+ }
+
+ return { data, error };
+ }
+
+ async #getAndHandleErrorResult(errors: Array) {
+ // TODO: We currently expect all the errors to be the same, but we should handle this better in the future.
+ const error = errors[0];
+ await umbPeekError(this, {
+ headline: 'Error fetching items',
+ message: 'An error occurred while fetching items.',
+ });
+
+ return new UmbError(error.reason);
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/types.ts
new file mode 100644
index 0000000000..fc4f1a47e4
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/types.ts
@@ -0,0 +1,6 @@
+import type { UmbDataApiResponse } from '@umbraco-cms/backoffice/resources';
+
+export interface UmbItemDataApiGetRequestControllerArgs {
+ api: (args: { uniques: Array }) => Promise;
+ uniques: Array;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts
index 31240f7f67..78aae14245 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts
@@ -1,4 +1,5 @@
import type { UmbNamedEntityModel } from '@umbraco-cms/backoffice/entity';
+export type * from './item-data-api-get-request-controller/types.js';
export interface UmbDefaultItemModel extends UmbNamedEntityModel {
icon?: string;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts
index 02df1f5429..8e3f1eeea5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts
@@ -1,10 +1,11 @@
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbDataSourceResponse } from '../data-source-response.interface.js';
import type { UmbItemDataSource } from './item-data-source.interface.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
export interface UmbItemServerDataSourceBaseArgs {
- getItems: (uniques: Array) => Promise>>;
+ getItems?: (uniques: Array) => Promise>>;
mapper: (item: ServerItemType) => ClientItemType;
}
@@ -14,10 +15,10 @@ export interface UmbItemServerDataSourceBaseArgs
+ extends UmbControllerBase
implements UmbItemDataSource
{
- #host: UmbControllerHost;
- #getItems: (uniques: Array) => Promise>>;
+ #getItems?: (uniques: Array) => Promise>>;
#mapper: (item: ServerItemType) => ClientItemType;
/**
@@ -27,7 +28,7 @@ export abstract class UmbItemServerDataSourceBase) {
- this.#host = host;
+ super(host);
this.#getItems = args.getItems;
this.#mapper = args.mapper;
}
@@ -39,14 +40,17 @@ export abstract class UmbItemServerDataSourceBase) {
+ if (!this.#getItems) throw new Error('getItems is not implemented');
if (!uniques) throw new Error('Uniques are missing');
- const { data, error } = await tryExecute(this.#host, this.#getItems(uniques));
- if (data) {
- const items = data.map((item) => this.#mapper(item));
- return { data: items };
- }
+ const { data, error } = await tryExecute(this, this.#getItems(uniques));
- return { error };
+ return { data: this._getMappedItems(data), error };
+ }
+
+ protected _getMappedItems(items: Array | undefined): Array | undefined {
+ if (!items) return undefined;
+ if (!this.#mapper) throw new Error('Mapper is not implemented');
+ return items.map((item) => this.#mapper(item));
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/data-api/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/data-api/types.ts
new file mode 100644
index 0000000000..dfade83718
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/data-api/types.ts
@@ -0,0 +1,3 @@
+export interface UmbDataApiResponse {
+ data: ResponseType['data'];
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts
index e1e1756111..04e74539c4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts
@@ -1,12 +1,9 @@
export * from './api-interceptor.controller.js';
-export * from './resource.controller.js';
-export * from './try-execute.controller.js';
-export * from './tryExecute.function.js';
-export * from './tryExecuteAndNotify.function.js';
-export * from './tryXhrRequest.function.js';
-export * from './extractUmbNotificationColor.function.js';
-export * from './extractUmbColorVariable.function.js';
-export * from './isUmbNotifications.function.js';
export * from './apiTypeValidators.function.js';
+export * from './extractUmbColorVariable.function.js';
+export * from './extractUmbNotificationColor.function.js';
+export * from './isUmbNotifications.function.js';
+export * from './resource.controller.js';
+export * from './try-execute/index.js';
export * from './umb-error.js';
export type * from './types.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/batch-try-execute.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/batch-try-execute.function.ts
new file mode 100644
index 0000000000..47a810ceb8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/batch-try-execute.function.ts
@@ -0,0 +1,17 @@
+import { tryExecute } from './tryExecute.function.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+
+/**
+ * Batches promises and returns a promise that resolves to an array of results
+ * @param {UmbControllerHost} host - The host to use for the request and where notifications will be shown
+ * @param {Array>} chunks - The array of chunks to process
+ * @param {(chunk: Array) => Promise} callback - The function to call for each chunk
+ * @returns {Promise[]>} - A promise that resolves to an array of results
+ */
+export function batchTryExecute(
+ host: UmbControllerHost,
+ chunks: Array>,
+ callback: (chunk: Array) => Promise,
+): Promise[]> {
+ return Promise.allSettled(chunks.map((chunk) => tryExecute(host, callback(chunk), { disableNotifications: true })));
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/index.ts
new file mode 100644
index 0000000000..18d2ee9f97
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/index.ts
@@ -0,0 +1,5 @@
+export * from './batch-try-execute.function.js';
+export * from './try-execute.controller.js';
+export * from './tryExecute.function.js';
+export * from './tryExecuteAndNotify.function.js';
+export * from './tryXhrRequest.function.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts
similarity index 86%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute.controller.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts
index 353c081138..afca34ad55 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts
@@ -1,7 +1,7 @@
-import { isProblemDetailsLike } from './apiTypeValidators.function.js';
-import { UmbResourceController } from './resource.controller.js';
-import type { UmbApiResponse, UmbTryExecuteOptions } from './types.js';
-import { UmbApiError, UmbCancelError } from './umb-error.js';
+import { isProblemDetailsLike } from '../apiTypeValidators.function.js';
+import { UmbResourceController } from '../resource.controller.js';
+import type { UmbApiResponse, UmbTryExecuteOptions } from '../types.js';
+import { UmbApiError, UmbCancelError } from '../umb-error.js';
export class UmbTryExecuteController extends UmbResourceController {
#abortSignal?: AbortSignal;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecute.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecute.function.ts
similarity index 93%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecute.function.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecute.function.ts
index 07c4b26cfd..7deca7c0e4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecute.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecute.function.ts
@@ -1,5 +1,5 @@
+import type { UmbApiResponse, UmbTryExecuteOptions } from '../types.js';
import { UmbTryExecuteController } from './try-execute.controller.js';
-import type { UmbApiResponse, UmbTryExecuteOptions } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecuteAndNotify.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecuteAndNotify.function.ts
similarity index 96%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecuteAndNotify.function.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecuteAndNotify.function.ts
index 1708d759c0..6bddf902de 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecuteAndNotify.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecuteAndNotify.function.ts
@@ -1,5 +1,5 @@
+import type { UmbApiResponse } from '../types.js';
import { UmbTryExecuteController } from './try-execute.controller.js';
-import type { UmbApiResponse } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbDeprecation } from '@umbraco-cms/backoffice/utils';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryXhrRequest.function.ts
similarity index 94%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryXhrRequest.function.ts
index a2e96d5d40..2be9e84fa7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryXhrRequest.function.ts
@@ -1,8 +1,8 @@
+import { UmbCancelablePromise } from '../cancelable-promise.js';
+import { UmbApiError } from '../umb-error.js';
+import { isProblemDetailsLike } from '../apiTypeValidators.function.js';
+import type { UmbApiResponse, XhrRequestOptions } from '../types.js';
import { UmbTryExecuteController } from './try-execute.controller.js';
-import { UmbCancelablePromise } from './cancelable-promise.js';
-import { UmbApiError } from './umb-error.js';
-import { isProblemDetailsLike } from './apiTypeValidators.function.js';
-import type { UmbApiResponse, XhrRequestOptions } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { umbHttpClient } from '@umbraco-cms/backoffice/http-client';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts
index fd7413e8f1..6a438f0bab 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts
@@ -1,4 +1,5 @@
import type { UmbApiError, UmbCancelError, UmbError } from './umb-error.js';
+export type * from './data-api/types.js';
export interface XhrRequestOptions extends UmbTryExecuteOptions {
baseUrl?: string;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.test.ts
new file mode 100644
index 0000000000..e958f3fea1
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.test.ts
@@ -0,0 +1,25 @@
+import { expect } from '@open-wc/testing';
+import { batchArray } from './batch-array.js';
+
+describe('batchArray', () => {
+ it('should split an array into chunks of the specified size', () => {
+ const array = [1, 2, 3, 4, 5];
+ const batchSize = 2;
+ const result = batchArray(array, batchSize);
+ expect(result).to.deep.equal([[1, 2], [3, 4], [5]]);
+ });
+
+ it('should handle arrays smaller than the batch size', () => {
+ const array = [1];
+ const batchSize = 2;
+ const result = batchArray(array, batchSize);
+ expect(result).to.deep.equal([[1]]);
+ });
+
+ it('should handle empty arrays', () => {
+ const array: number[] = [];
+ const batchSize = 2;
+ const result = batchArray(array, batchSize);
+ expect(result).to.deep.equal([]);
+ });
+});
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.ts
new file mode 100644
index 0000000000..fde5b6703d
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.ts
@@ -0,0 +1,16 @@
+/**
+ * Splits an array into chunks of a specified size
+ * @param { Array } array - The array to split
+ * @param {number }batchSize - The size of each chunk
+ * @returns {Array>} - An array of chunks
+ */
+export function batchArray(
+ array: Array,
+ batchSize: number,
+): Array> {
+ const chunks: Array> = [];
+ for (let i = 0; i < array.length; i += batchSize) {
+ chunks.push(array.slice(i, i + batchSize));
+ }
+ return chunks;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/index.ts
new file mode 100644
index 0000000000..106dce2d85
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/index.ts
@@ -0,0 +1 @@
+export * from './batch-array.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts
index aa191db2e7..a59e5dc990 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts
@@ -1,3 +1,4 @@
+export * from './array/index.js';
export * from './bytes/bytes.function.js';
export * from './debounce/debounce.function.js';
export * from './deprecation/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts
index d373f16b32..4b578c8b1d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts
@@ -6,6 +6,7 @@ import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/property-editor';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
let manifestPropertyEditorUis: Array = [];
@@ -26,7 +27,6 @@ export class UmbDataTypeItemServerDataSource extends UmbItemServerDataSourceBase
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
@@ -37,10 +37,21 @@ export class UmbDataTypeItemServerDataSource extends UmbItemServerDataSourceBase
})
.unsubscribe();
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => DataTypeService.getItemDataType({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => DataTypeService.getItemDataType({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: DataTypeItemResponseModel): UmbDataTypeItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/repository/item/dictionary-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/repository/item/dictionary-item.server.data-source.ts
index c5b3714b91..245931dbfc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/repository/item/dictionary-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/repository/item/dictionary-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { DictionaryItemItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { DictionaryService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Dictionary items
@@ -21,14 +22,24 @@ export class UmbDictionaryItemServerDataSource extends UmbItemServerDataSourceBa
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => DictionaryService.getItemDictionary({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => DictionaryService.getItemDictionary({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: DictionaryItemItemResponseModel): UmbDictionaryItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/item/document-blueprint-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/item/document-blueprint-item.server.data-source.ts
index 104ecfbee1..bc44153a60 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/item/document-blueprint-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/item/document-blueprint-item.server.data-source.ts
@@ -5,6 +5,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { DocumentBlueprintItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A data source for Document Blueprint items that fetches data from the server
@@ -15,7 +16,6 @@ export class UmbDocumentBlueprintItemServerDataSource extends UmbItemServerDataS
DocumentBlueprintItemResponseModel,
UmbDocumentBlueprintItemModel
> {
- #host: UmbControllerHost;
/**
* Creates an instance of UmbDocumentBlueprintItemServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
@@ -23,16 +23,14 @@ export class UmbDocumentBlueprintItemServerDataSource extends UmbItemServerDataS
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
- this.#host = host;
}
async getItemsByDocumentType(unique: string) {
if (!unique) throw new Error('Unique is missing');
const { data, error } = await tryExecute(
- this.#host,
+ this,
DocumentTypeService.getDocumentTypeByIdBlueprint({ path: { id: unique } }),
);
@@ -47,11 +45,21 @@ export class UmbDocumentBlueprintItemServerDataSource extends UmbItemServerDataS
return { error };
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) =>
- DocumentBlueprintService.getItemDocumentBlueprint({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => DocumentBlueprintService.getItemDocumentBlueprint({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: DocumentBlueprintItemResponseModel): UmbDocumentBlueprintItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts
index 1fed1d9574..f68f70d88c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { DocumentTypeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A data source for Document Type items that fetches data from the server
@@ -20,13 +21,24 @@ export class UmbDocumentTypeItemServerDataSource extends UmbItemServerDataSource
* @memberof UmbDocumentTypeItemServerDataSource
*/
constructor(host: UmbControllerHost) {
- super(host, { getItems, mapper });
+ super(host, { mapper });
+ }
+
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => DocumentTypeService.getItemDocumentType({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
}
}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => DocumentTypeService.getItemDocumentType({ query: { id: uniques } });
-
const mapper = (item: DocumentTypeItemResponseModel): UmbDocumentTypeItemModel => {
return {
entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE,
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts
index a9e2dfe005..5c6453b35e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts
@@ -4,6 +4,7 @@ import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/external
import { DocumentService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A data source for Document items that fetches data from the server
@@ -21,14 +22,24 @@ export class UmbDocumentItemServerDataSource extends UmbItemServerDataSourceBase
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => DocumentService.getItemDocument({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => DocumentService.getItemDocument({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: DocumentItemResponseModel): UmbDocumentItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/repository/document-url.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/repository/document-url.server.data-source.ts
index bcf61c137d..8891d6b6c1 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/repository/document-url.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/repository/document-url.server.data-source.ts
@@ -3,6 +3,7 @@ import { DocumentService } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
import type { DocumentUrlInfoResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Document URLs
@@ -19,11 +20,22 @@ export class UmbDocumentUrlServerDataSource extends UmbItemServerDataSourceBase<
* @memberof UmbDocumentUrlServerDataSource
*/
constructor(host: UmbControllerHost) {
- super(host, { getItems, mapper });
+ super(host, { mapper });
+ }
+
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => DocumentService.getDocumentUrls({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
}
}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => DocumentService.getDocumentUrls({ query: { id: uniques } });
-
const mapper = (item: DocumentUrlInfoResponseModel): UmbDocumentUrlsModel => ({ unique: item.id, urls: item.urlInfos });
diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/repository/item/language-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/language/repository/item/language-item.server.data-source.ts
index 3e9b7e5fe9..b38c275278 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/language/repository/item/language-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/language/repository/item/language-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { LanguageItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { LanguageService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Language items
@@ -21,14 +22,24 @@ export class UmbLanguageItemServerDataSource extends UmbItemServerDataSourceBase
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => LanguageService.getItemLanguage({ query: { isoCode: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => LanguageService.getItemLanguage({ query: { isoCode: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: LanguageItemResponseModel): UmbLanguageItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts
index 8897295e51..402cb410a8 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { MediaTypeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { MediaTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A data source for Media Type items that fetches data from the server
@@ -21,14 +22,24 @@ export class UmbMediaTypeItemServerDataSource extends UmbItemServerDataSourceBas
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => MediaTypeService.getItemMediaType({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => MediaTypeService.getItemMediaType({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: MediaTypeItemResponseModel): UmbMediaTypeItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts
index e56a7d9567..d4edb0f5fc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts
@@ -5,6 +5,7 @@ import { MediaService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A data source for Media items that fetches data from the server
@@ -15,7 +16,6 @@ export class UmbMediaItemServerDataSource extends UmbItemServerDataSourceBase<
MediaItemResponseModel,
UmbMediaItemModel
> {
- #host: UmbControllerHost;
/**
* Creates an instance of UmbMediaItemServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
@@ -23,10 +23,8 @@ export class UmbMediaItemServerDataSource extends UmbItemServerDataSourceBase<
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
- this.#host = host;
}
/**
@@ -38,17 +36,25 @@ export class UmbMediaItemServerDataSource extends UmbItemServerDataSourceBase<
* ```
*/
async search({ query, skip, take }: { query: string; skip: number; take: number }) {
- const { data, error } = await tryExecute(
- this.#host,
- MediaService.getItemMediaSearch({ query: { query, skip, take } }),
- );
+ const { data, error } = await tryExecute(this, MediaService.getItemMediaSearch({ query: { query, skip, take } }));
const mapped = data?.items.map((item) => mapper(item));
return { data: mapped, error };
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => MediaService.getItemMedia({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => MediaService.getItemMedia({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: MediaItemResponseModel): UmbMediaItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/url/repository/media-url.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/url/repository/media-url.server.data-source.ts
index d52e8952c6..24571d50d3 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/url/repository/media-url.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/url/repository/media-url.server.data-source.ts
@@ -1,5 +1,6 @@
import type { UmbMediaUrlModel } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
import { MediaService, type MediaUrlInfoResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
@@ -19,14 +20,24 @@ export class UmbMediaUrlServerDataSource extends UmbItemServerDataSourceBase<
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => MediaService.getMediaUrls({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => MediaService.getMediaUrls({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: MediaUrlInfoResponseModel): UmbMediaUrlModel => {
const url = item.urlInfos.length ? item.urlInfos[0].url : undefined;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/member-group-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/member-group-item.server.data-source.ts
index 3bdf68a8ee..6840673866 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/member-group-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/member-group-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { MemberGroupItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { MemberGroupService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Member Group items
@@ -21,14 +22,24 @@ export class UmbMemberGroupItemServerDataSource extends UmbItemServerDataSourceB
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => MemberGroupService.getItemMemberGroup({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => MemberGroupService.getItemMemberGroup({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: MemberGroupItemResponseModel): UmbMemberGroupItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/repository/item/member-type-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/repository/item/member-type-item.server.data-source.ts
index c83d7443b9..68444ad9ef 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/repository/item/member-type-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/repository/item/member-type-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { MemberTypeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Member Type items
@@ -21,14 +22,24 @@ export class UmbMemberTypeItemServerDataSource extends UmbItemServerDataSourceBa
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => MemberTypeService.getItemMemberType({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => MemberTypeService.getItemMemberType({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: MemberTypeItemResponseModel): UmbMemberTypeItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/member-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/member-item.server.data-source.ts
index 2853948b74..adc992d919 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/member-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/member-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { MemberItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { MemberService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Member items
@@ -21,14 +22,24 @@ export class UmbMemberItemServerDataSource extends UmbItemServerDataSourceBase<
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => MemberService.getItemMember({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => MemberService.getItemMember({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: MemberItemResponseModel): UmbMemberItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts
index 3eba1cef8b..5e38b171e7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts
@@ -4,6 +4,7 @@ import type { StaticFileItemResponseModel } from '@umbraco-cms/backoffice/extern
import { StaticFileService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Static File items
@@ -21,20 +22,28 @@ export class UmbStaticFileItemServerDataSource extends UmbItemServerDataSourceBa
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
+
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const serializer = new UmbServerFilePathUniqueSerializer();
+ const paths = uniques.map((unique) => serializer.toServerPath(unique)!);
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => StaticFileService.getItemStaticFile({ query: { path: args.uniques } }),
+ uniques: paths,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
}
-const getItems = (uniques: Array) => {
- const serializer = new UmbServerFilePathUniqueSerializer();
- const path = uniques.map((unique) => serializer.toServerPath(unique)!);
-
- /* eslint-disable local-rules/no-direct-api-import */
- return StaticFileService.getItemStaticFile({ query: { path } });
-};
-
const mapper = (item: StaticFileItemResponseModel): UmbStaticFileItemModel => {
const serializer = new UmbServerFilePathUniqueSerializer();
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts
index 2e5fc3c7c2..9355611023 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts
@@ -4,6 +4,7 @@ import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'
import type { TemplateItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { TemplateService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for Template items
@@ -21,14 +22,24 @@ export class UmbTemplateItemServerDataSource extends UmbItemServerDataSourceBase
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => TemplateService.getItemTemplate({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => TemplateService.getItemTemplate({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: TemplateItemResponseModel): UmbTemplateItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts
index fdb9236739..aad5b6ef54 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts
@@ -3,6 +3,7 @@ import type { UserGroupItemResponseModel } from '@umbraco-cms/backoffice/externa
import { UserGroupService } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
/**
* A server data source for User Group items
@@ -20,14 +21,24 @@ export class UmbUserGroupItemServerDataSource extends UmbItemServerDataSourceBas
*/
constructor(host: UmbControllerHost) {
super(host, {
- getItems,
mapper,
});
}
-}
-/* eslint-disable local-rules/no-direct-api-import */
-const getItems = (uniques: Array) => UserGroupService.getItemUserGroup({ query: { id: uniques } });
+ override async getItems(uniques: Array) {
+ if (!uniques) throw new Error('Uniques are missing');
+
+ const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ api: (args) => UserGroupService.getItemUserGroup({ query: { id: args.uniques } }),
+ uniques,
+ });
+
+ const { data, error } = await itemRequestManager.request();
+
+ return { data: this._getMappedItems(data), error };
+ }
+}
const mapper = (item: UserGroupItemResponseModel): UmbUserGroupItemModel => {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/item/user-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/item/user-item.server.data-source.ts
index 18f74820c8..323c08f8a6 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/item/user-item.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/repository/item/user-item.server.data-source.ts
@@ -1,6 +1,7 @@
import { UMB_USER_ENTITY_TYPE } from '../../entity.js';
import type { UmbUserItemModel } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';
import type { UserItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { UserService } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
@@ -18,14 +19,24 @@ export class UmbUserItemServerDataSource extends UmbItemServerDataSourceBase