From 5da036f8e527e23c24cbca82b5146e622ba18264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Enrique=20Colina=20Rodr=C3=ADguez?= Date: Mon, 12 Aug 2024 20:34:10 +0200 Subject: [PATCH 1/7] fix: CICD Fix JReleaser path mismatch in dotCLI Release (#29375) (#29513) ### Proposed Changes * Fix cleanup-runner action. Docker commands are not available on macOS runners. * Enable cleanup-runner on `cli-build-artifacts` and `cli-release-process` to avoid disk space issues. * Setting the right path to get the cli binaries and pushing as artifacts of the release. ### Additional Info Related to #29375 (CICD Fix JReleaser path mismatch in dotCLI Release). --- .github/actions/cleanup-runner/action.yml | 2 +- .github/workflows/cli-build-artifacts.yml | 7 ++++--- .github/workflows/cli-release-process.yml | 1 + tools/dotcms-cli/jreleaser.yml | 8 ++++---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/actions/cleanup-runner/action.yml b/.github/actions/cleanup-runner/action.yml index 69f11a94e736..788512c8fd1e 100644 --- a/.github/actions/cleanup-runner/action.yml +++ b/.github/actions/cleanup-runner/action.yml @@ -40,7 +40,7 @@ runs: - name: Cleanup Docker shell: bash run: | - if [[ "$RUNNER_OS" == "Linux" ]] || [[ "$RUNNER_OS" == "macOS" ]]; then + if [[ "$RUNNER_OS" == "Linux" ]]; then echo 'Cleanup Docker' docker system prune -f docker volume prune -f diff --git a/.github/workflows/cli-build-artifacts.yml b/.github/workflows/cli-build-artifacts.yml index 9df95a8539cb..959beb0e3701 100644 --- a/.github/workflows/cli-build-artifacts.yml +++ b/.github/workflows/cli-build-artifacts.yml @@ -44,9 +44,9 @@ jobs: id: set-os run: | if [[ "${{ inputs.buildNativeImage }}" == "true" ]]; then - RUNNERS='[{ "os": "ubuntu-22.04", "label": "Linux" }, { "os": "macos-13", "label": "macOS-Intel" }, { "os": "macos-14", "label": "macOS-Silicon" }]' + RUNNERS='[{ "os": "ubuntu-22.04", "label": "Linux", "platform": "linux-x86_64" }, { "os": "macos-13", "label": "macOS-Intel", "platform": "osx-x86_64" }, { "os": "macos-14", "label": "macOS-Silicon", "platform": "osx-aarch_64" }]' else - RUNNERS='[{ "os": "ubuntu-22.04", "label": "Linux" }]' + RUNNERS='[{ "os": "ubuntu-22.04", "label": "Linux", "platform": "linux-x86_64" }]' fi echo "runners=$RUNNERS" >> $GITHUB_OUTPUT @@ -68,6 +68,7 @@ jobs: ref: ${{ env.BRANCH }} - uses: ./.github/actions/maven-job with: + cleanup-runner: true stage-name: "Build Native Image ${{ matrix.label }}" maven-args: "package -Pnative -Pdist -DskipTests=$SKIP_TESTS -pl :dotcms-cli" native: true @@ -80,7 +81,7 @@ jobs: id: upload-artifact uses: actions/upload-artifact@v4 with: - name: cli-artifacts-${{ matrix.os }} + name: cli-artifacts-${{ matrix.platform }} path: | ${{ github.workspace }}/tools/dotcms-cli/cli/target/distributions/*.zip retention-days: 2 diff --git a/.github/workflows/cli-release-process.yml b/.github/workflows/cli-release-process.yml index 86dd7096c552..3313008f2679 100644 --- a/.github/workflows/cli-release-process.yml +++ b/.github/workflows/cli-release-process.yml @@ -151,6 +151,7 @@ jobs: JRELEASER_ARTIFACTORY_PASSWORD: ${{ secrets.EE_REPO_PASSWORD }} JRELEASER_DRY_RUN: ${{ github.event.inputs.dry-run || 'false' }} with: + cleanup-runner: true github-token: ${{ secrets.GITHUB_TOKEN }} stage-name: "JReleaser" maven-args: "-Prelease validate -DartifactsDir=artifacts -Dm2Dir=$HOME/.m2/repository -Djreleaser.git.root.search=true -pl :dotcms-cli-parent -Dmaven.plugin.validation=VERBOSE" diff --git a/tools/dotcms-cli/jreleaser.yml b/tools/dotcms-cli/jreleaser.yml index b85247a10bfe..cd564cf19131 100644 --- a/tools/dotcms-cli/jreleaser.yml +++ b/tools/dotcms-cli/jreleaser.yml @@ -54,16 +54,16 @@ distributions: dotcms-cli-native: type: BINARY - artifacts: - - path: '{{artifactsDir}}/artifacts-osx-aarch_64/distributions/dotcms-cli-{{projectVersion}}-osx-aarch_64.zip' + artifacts: + - path: '{{artifactsDir}}/cli-artifacts-osx-aarch_64/dotcms-cli-{{projectVersion}}-osx-aarch_64.zip' platform: 'osx-aarch_64' extraProperties: optional: 'true' - - path: '{{artifactsDir}}/artifacts-osx-x86_64/distributions/dotcms-cli-{{projectVersion}}-osx-x86_64.zip' + - path: '{{artifactsDir}}/cli-artifacts-osx-x86_64/dotcms-cli-{{projectVersion}}-osx-x86_64.zip' platform: 'osx-x86_64' extraProperties: optional: 'true' - - path: '{{artifactsDir}}/artifacts-linux-x86_64/distributions/dotcms-cli-{{projectVersion}}-linux-x86_64.zip' + - path: '{{artifactsDir}}/cli-artifacts-linux-x86_64/dotcms-cli-{{projectVersion}}-linux-x86_64.zip' platform: 'linux-x86_64' extraProperties: optional: 'true' From d9f18fcd74daa36f5163cd38d0ff48d9269693e0 Mon Sep 17 00:00:00 2001 From: Fabrizzio Araya <37148755+fabrizzio-dotCMS@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:33:50 -0600 Subject: [PATCH 2/7] fix(FlakeyTest) Refs: #28640 (#29515) ### Proposed Changes * Trying to narrow the source for errors by creating languages checking codes with case sensitivity in mind * There are too many DataGens dedicated to this purpose we need to unify them * I'm renaming one of them see we can tell them apart when choosing one over the other * Removed a Country Code generator since it depended on the iso code list and we look forward to using random country codes * And finally the LanguageDataGen has been modified to discard attempts to create existing languages * After using the LanguageDataGen it will reset its values. * I will move the uniqueness validation into our LangaugeAPI#save method in a separate PR --- .../business/ESContentFactoryImplTest.java | 22 ++--- .../dotcms/datagen/LanguageCodeDataGen.java | 32 ------- .../com/dotcms/datagen/LanguageDataGen.java | 45 +++++++--- .../remote/bundler/DependencyBundlerTest.java | 16 ++-- .../business/PublisherAPIImplTest.java | 11 ++- .../util/DependencyModDateUtilTest.java | 18 ++-- .../publisher/util/PushedAssetUtilTest.java | 4 +- .../publishing/PublisherAPIImplTest.java | 3 +- .../velocity/viewtools/WorkflowToolTest.java | 4 +- .../DeterministicIdentifierAPITest.java | 13 ++- .../business/ContentletAPITest.java | 83 +++++++++---------- .../business/LanguageAPITest.java | 12 +-- ...ataGen.java => UniqueLanguageDataGen.java} | 49 +++++++++-- ...sk211012AddCompanyDefaultLanguageTest.java | 4 +- ...306MigrateLegacyLanguageVariablesTest.java | 4 +- 15 files changed, 164 insertions(+), 156 deletions(-) delete mode 100644 dotcms-integration/src/test/java/com/dotcms/datagen/LanguageCodeDataGen.java rename dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/{LanguageDataGen.java => UniqueLanguageDataGen.java} (50%) diff --git a/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImplTest.java b/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImplTest.java index b9c48cd2d5be..d0308fd999c7 100644 --- a/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImplTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImplTest.java @@ -32,7 +32,7 @@ import com.dotmarketing.portlets.contentlet.model.Contentlet; import com.dotmarketing.portlets.contentlet.model.IndexPolicy; import com.dotmarketing.portlets.folders.model.Folder; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; @@ -512,8 +512,8 @@ private void runLuceneQueryAndValidateResults(final String query, final Contentl @Test public void test_findContentletByIdentifier() throws Exception { - final Language language1 = new LanguageDataGen().nextPersisted(); - final Language language2 = new LanguageDataGen().nextPersisted(); + final Language language1 = new UniqueLanguageDataGen().nextPersisted(); + final Language language2 = new UniqueLanguageDataGen().nextPersisted(); final ContentType blogType = TestDataUtils.getBlogLikeContentType(site); @@ -561,7 +561,7 @@ public void test_findContentletByIdentifier() throws Exception { @Test public void test_cached_es_query_response() throws Exception { - final Language language1 = new LanguageDataGen().nextPersisted(); + final Language language1 = new UniqueLanguageDataGen().nextPersisted(); final ContentType blogType = TestDataUtils.getBlogLikeContentType(site); @@ -604,7 +604,7 @@ public void test_cached_es_query_response() throws Exception { */ @Test public void test_cached_es_query_different_responses_for_all_params() throws Exception { - final Language language1 = new LanguageDataGen().nextPersisted(); + final Language language1 = new UniqueLanguageDataGen().nextPersisted(); final ContentType blogType = TestDataUtils.getBlogLikeContentType(site); @@ -715,8 +715,8 @@ public void test_findContentletByIdentifierAnyLanguage() final ContentletAPI contentletAPI = APILocator.getContentletAPI(); final User user = APILocator.systemUser(); - final Language language1 = new LanguageDataGen().nextPersisted(); - final Language language2 = new LanguageDataGen().nextPersisted(); + final Language language1 = new UniqueLanguageDataGen().nextPersisted(); + final Language language2 = new UniqueLanguageDataGen().nextPersisted(); final ContentType bannerLikeContentType = TestDataUtils.getBannerLikeContentType(); @@ -750,8 +750,8 @@ public void test_findContentletByIdentifierAnyLanguageNoArchived() final ContentletAPI contentletAPI = APILocator.getContentletAPI(); final User user = APILocator.systemUser(); - final Language language1 = new LanguageDataGen().nextPersisted(); - final Language language2 = new LanguageDataGen().nextPersisted(); + final Language language1 = new UniqueLanguageDataGen().nextPersisted(); + final Language language2 = new UniqueLanguageDataGen().nextPersisted(); final ContentType bannerLikeContentType = TestDataUtils.getBannerLikeContentType(); @@ -789,8 +789,8 @@ public void test_findContentletByIdentifierAnyLanguageIncludeDeleted() final ContentletAPI contentletAPI = APILocator.getContentletAPI(); final User user = APILocator.systemUser(); - final Language language1 = new LanguageDataGen().nextPersisted(); - final Language language2 = new LanguageDataGen().nextPersisted(); + final Language language1 = new UniqueLanguageDataGen().nextPersisted(); + final Language language2 = new UniqueLanguageDataGen().nextPersisted(); final ContentType bannerLikeContentType = TestDataUtils.getBannerLikeContentType(); diff --git a/dotcms-integration/src/test/java/com/dotcms/datagen/LanguageCodeDataGen.java b/dotcms-integration/src/test/java/com/dotcms/datagen/LanguageCodeDataGen.java deleted file mode 100644 index f2519d3c17d8..000000000000 --- a/dotcms-integration/src/test/java/com/dotcms/datagen/LanguageCodeDataGen.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.dotcms.datagen; - -import java.util.Locale; -import java.util.Random; - -/** - * Generater a language code - * @author jsanca - */ -public class LanguageCodeDataGen implements DataGen { - - final String[] languageCodes = Locale.getISOLanguages(); - final Random random = new Random(); - - @Override - public String next() { - - final int languageIndex = this.random.nextInt(languageCodes.length); - final String languageCode = this.languageCodes[languageIndex]; - return languageCode; - } - - @Override - public String persist(String object) { - throw new UnsupportedOperationException(); - } - - @Override - public String nextPersisted() { - throw new UnsupportedOperationException(); - } -} diff --git a/dotcms-integration/src/test/java/com/dotcms/datagen/LanguageDataGen.java b/dotcms-integration/src/test/java/com/dotcms/datagen/LanguageDataGen.java index 13b07674a4f5..5fd8dba89e6d 100644 --- a/dotcms-integration/src/test/java/com/dotcms/datagen/LanguageDataGen.java +++ b/dotcms-integration/src/test/java/com/dotcms/datagen/LanguageDataGen.java @@ -1,7 +1,10 @@ package com.dotcms.datagen; +import static com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen.testLanguageExist; + import com.dotcms.business.WrapInTransaction; import com.dotmarketing.business.APILocator; +import com.dotmarketing.portlets.languagesmanager.business.LanguageAPI; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.util.Logger; @@ -10,12 +13,22 @@ */ public class LanguageDataGen extends AbstractDataGen { - private final long currentTime = System.currentTimeMillis(); + private String languageCode; + private String languageName; + private String countryCode; + private String country; + + private void setDefaults() { + long currentTime = System.currentTimeMillis(); + languageCode = org.apache.commons.lang.RandomStringUtils.randomAlphanumeric(5); + languageName = "testLanguage" + currentTime; + countryCode = "testCountryCode" + currentTime; + country = "testCountry" + currentTime; + } - private String languageCode = org.apache.commons.lang.RandomStringUtils.randomAlphanumeric(5); - private String languageName = "testLanguage" + currentTime; - private String countryCode = "testCountryCode" + currentTime; - private String country = "testCountry" + currentTime; + public LanguageDataGen() { + setDefaults(); + } @SuppressWarnings("unused") public LanguageDataGen languageCode(final String languageCode) { @@ -55,12 +68,22 @@ public Language next() { @WrapInTransaction @Override public Language persist(final Language language) { - try { - APILocator.getLanguageAPI().saveLanguage(language); - return language; - } catch (Exception e) { - throw new RuntimeException("Unable to persist Language.", e); - } + + final LanguageAPI languageAPI = APILocator.getLanguageAPI(); + try { + //Our system allows for the same language to be cre-created with the same language code and country code which is source for trouble + boolean languageExists = (testLanguageExist(language.getLanguageCode(), language.getCountryCode()) > 0); + if (languageExists) { + return languageAPI.getLanguage(language.getLanguageCode(), language.getCountryCode()); + } + languageAPI.saveLanguage(language); + return language; + } catch (Exception e) { + throw new RuntimeException("Unable to persist Language.", e); + } finally { + // Reset the values to the default ones + setDefaults(); + } } /** diff --git a/dotcms-integration/src/test/java/com/dotcms/enterprise/publishing/remote/bundler/DependencyBundlerTest.java b/dotcms-integration/src/test/java/com/dotcms/enterprise/publishing/remote/bundler/DependencyBundlerTest.java index 7bd08ac12726..79996ba95aca 100644 --- a/dotcms-integration/src/test/java/com/dotcms/enterprise/publishing/remote/bundler/DependencyBundlerTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/enterprise/publishing/remote/bundler/DependencyBundlerTest.java @@ -61,7 +61,7 @@ import com.dotmarketing.portlets.contentlet.model.IndexPolicy; import com.dotmarketing.portlets.folders.model.Folder; import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.portlets.links.model.Link; import com.dotmarketing.portlets.rules.RuleDataGen; @@ -201,7 +201,7 @@ private static Collection createContentletWithBlockEditorField() throws DotDa final StoryBlockAPI storyBlockAPI = APILocator.getStoryBlockAPI(); final ContentletAPI contentletAPI = APILocator.getContentletAPI(); final Host site = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen().host(site).nextPersisted(); final ContentType referencedContentType = new ContentTypeDataGen().host(site).nextPersisted(); final Field storyBlockField = @@ -238,7 +238,7 @@ private static Collection createContentletWithThirdPartyTestCase() throws DotDataException, DotSecurityException { final Host host = createHostWithDependencies(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final TestData contentTypeWithDependencies = createContentTypeWithDependencies(); final ContentType contentType = (ContentType) contentTypeWithDependencies.assetsToAddInBundle; @@ -586,7 +586,7 @@ private static Host createHostWithDependencies(){ private static Collection createContentTestCase() throws DotDataException, DotSecurityException, IOException { final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen().host(host).nextPersisted(); final Contentlet contentlet = new ContentletDataGen(contentType.id()) @@ -654,7 +654,7 @@ private static Collection createContentTestCase() final WorkflowScheme systemWorkflowScheme = APILocator.getWorkflowAPI().findSystemWorkflowScheme(); - final Language imageFileLanguage = new LanguageDataGen().nextPersisted(); + final Language imageFileLanguage = new UniqueLanguageDataGen().nextPersisted(); final Folder imageFolder = new FolderDataGen().site(host).nextPersisted(); File tempFile = File.createTempFile("contentWithImageBundleTest", ".jpg"); URL url = FocalPointAPITest.class.getResource("/images/test.jpg"); @@ -826,7 +826,7 @@ private static Collection createRuleTestCase() { } private static Collection createLanguageTestCase() { - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); return list( new TestData(language, new HashMap<>(), new HashMap<>(), filterDescriptorAllDependencies, "Language with filterDescriptorAllDependencies"), @@ -1604,7 +1604,7 @@ public void includeContenletChildWithSeveralVersionAssetByModDate(ModDateTestDat final Contentlet contentParent = (Contentlet) relationShip.get("contentParent"); final Contentlet contentletChildAnotherLang = ContentletDataGen.checkout(contentletChild); - final Language anotherLang = new LanguageDataGen().nextPersisted(); + final Language anotherLang = new UniqueLanguageDataGen().nextPersisted(); contentletChildAnotherLang.setLanguageId(anotherLang.getId()); ContentletDataGen.checkin(contentletChildAnotherLang); @@ -1766,7 +1766,7 @@ private Map createPushContext(final ModDateTestData modDateTestD private Map createRelationShip() { final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentTypeParent = new ContentTypeDataGen() .host(host) diff --git a/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherAPIImplTest.java b/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherAPIImplTest.java index 532195d7820e..e98fef3e8a9f 100644 --- a/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherAPIImplTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherAPIImplTest.java @@ -23,7 +23,7 @@ import com.dotmarketing.portlets.contentlet.model.Contentlet; import com.dotmarketing.portlets.folders.model.Folder; import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.portlets.templates.model.Template; import com.dotmarketing.util.UUIDGenerator; @@ -32,7 +32,6 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.Optional; import org.junit.Assert; import org.junit.BeforeClass; @@ -54,7 +53,7 @@ public static void prepare() throws Exception { @Test public void test_deleteElementFromPublishQueueTable_OneAsset() throws DotPublisherException { final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) @@ -84,7 +83,7 @@ public void test_deleteElementFromPublishQueueTable_OneAsset() throws DotPublish @Test public void test_deleteElementFromPublishQueueTable_TwoAssets() throws DotPublisherException { final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) @@ -122,7 +121,7 @@ public void test_deleteElementFromPublishQueueTable_TwoAssets() throws DotPublis @Test public void test_deleteElementsFromPublishQueueTable_TwoAssets() throws DotPublisherException { final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) @@ -187,7 +186,7 @@ public void test_getQueueBundleIdsToProcess_ShouldExcludeFailedIntegrityStatus(f final ContentType contentTypeForContentlet = new ContentTypeDataGen().nextPersisted(); final Contentlet contentlet = new ContentletDataGen(contentTypeForContentlet).nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen().nextPersisted(); diff --git a/dotcms-integration/src/test/java/com/dotcms/publisher/util/DependencyModDateUtilTest.java b/dotcms-integration/src/test/java/com/dotcms/publisher/util/DependencyModDateUtilTest.java index 17eb4a2690ed..ece3dda2a3f7 100644 --- a/dotcms-integration/src/test/java/com/dotcms/publisher/util/DependencyModDateUtilTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/publisher/util/DependencyModDateUtilTest.java @@ -16,26 +16,19 @@ import com.dotcms.datagen.PushPublishingEndPointDataGen; import com.dotcms.datagen.PushedAssetDataGen; import com.dotcms.datagen.SiteDataGen; -import com.dotcms.datagen.TestDataUtils; -import com.dotcms.enterprise.publishing.remote.bundler.DependencyBundler; -import com.dotcms.publisher.assets.bean.PushedAsset; import com.dotcms.publisher.bundle.bean.Bundle; -import com.dotcms.publisher.endpoint.bean.PublishingEndPoint; import com.dotcms.publisher.endpoint.bean.impl.PushPublishingEndPoint; import com.dotcms.publisher.environment.bean.Environment; import com.dotcms.publisher.pusher.PushPublisher; import com.dotcms.publisher.pusher.PushPublisherConfig; import com.dotcms.publisher.util.dependencies.DependencyModDateUtil; -import com.dotcms.publishing.BundlerStatus; import com.dotcms.publishing.PublisherConfig.Operation; -import com.dotcms.publishing.output.BundleOutput; import com.dotcms.util.IntegrationTestInitService; import com.dotmarketing.beans.Host; import com.dotmarketing.business.APILocator; import com.dotmarketing.exception.DotDataException; -import com.dotmarketing.exception.DotSecurityException; import com.dotmarketing.portlets.contentlet.model.Contentlet; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.liferay.portal.model.User; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -46,7 +39,6 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import org.jetbrains.annotations.NotNull; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -109,7 +101,7 @@ public void excludeByModDateContentNotPushBefore(final TestCase testCase) throws final DependencyModDateUtil dependencyModDateUtil = new DependencyModDateUtil(config); final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) @@ -149,7 +141,7 @@ public void excludeByModDateContentWithPushAssetBeforeModDate(final TestCase tes final DependencyModDateUtil dependencyModDateUtil = new DependencyModDateUtil(config); final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) @@ -205,7 +197,7 @@ public void excludeByModDateContentWithPushAssetAfterModDate(final TestCase test final DependencyModDateUtil dependencyModDateUtil = new DependencyModDateUtil(config); final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) @@ -283,7 +275,7 @@ public void excludeByModDateContentWithTwoEnvironment(final TestCase testCase) t final DependencyModDateUtil dependencyModDateUtil = new DependencyModDateUtil(config); final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) diff --git a/dotcms-integration/src/test/java/com/dotcms/publisher/util/PushedAssetUtilTest.java b/dotcms-integration/src/test/java/com/dotcms/publisher/util/PushedAssetUtilTest.java index 729da76c6c3c..7ac9a6518354 100644 --- a/dotcms-integration/src/test/java/com/dotcms/publisher/util/PushedAssetUtilTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/publisher/util/PushedAssetUtilTest.java @@ -16,7 +16,7 @@ import com.dotmarketing.business.APILocator; import com.dotmarketing.exception.DotDataException; import com.dotmarketing.portlets.contentlet.model.Contentlet; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.liferay.portal.model.User; import org.junit.Assert; @@ -67,7 +67,7 @@ public void savePushedAssetForAllEnv() throws DotDataException { final PushedAssetUtil pushedAssetUtil = new PushedAssetUtil(config); final Host host = new SiteDataGen().nextPersisted(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final ContentType contentType = new ContentTypeDataGen() .host(host) diff --git a/dotcms-integration/src/test/java/com/dotcms/publishing/PublisherAPIImplTest.java b/dotcms-integration/src/test/java/com/dotcms/publishing/PublisherAPIImplTest.java index 5b42cfa00d83..44263adf1079 100644 --- a/dotcms-integration/src/test/java/com/dotcms/publishing/PublisherAPIImplTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/publishing/PublisherAPIImplTest.java @@ -64,6 +64,7 @@ import com.dotmarketing.portlets.folders.model.Folder; import com.dotmarketing.portlets.htmlpageasset.business.HTMLPageAssetAPI; import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.portlets.links.model.Link; import com.dotmarketing.portlets.rules.RuleDataGen; @@ -151,7 +152,7 @@ public static void createLanguageVariableIfNeeded() throws DotSecurityException, APILocator.getContentTypeAPI(systemUser).find(LanguageVariableAPI.LANGUAGEVARIABLE_VAR_NAME); if (langVariables.isEmpty()) { - final Language language = new com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final Host host = new SiteDataGen().nextPersisted(); languageVariableCreated = new ContentletDataGen(languageVariableContentType.id()) diff --git a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/WorkflowToolTest.java b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/WorkflowToolTest.java index a89f4b511a70..c67b886bf3c2 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/WorkflowToolTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/WorkflowToolTest.java @@ -15,7 +15,7 @@ import com.dotmarketing.exception.DotDataException; import com.dotmarketing.exception.DotSecurityException; import com.dotmarketing.portlets.contentlet.model.Contentlet; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.util.UtilMethods; import com.liferay.portal.model.User; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -48,7 +48,7 @@ public static Object[] fireTestCases() throws Exception { //Setting web app environment IntegrationTestInitService.getInstance().init(); long english = APILocator.getLanguageAPI().getDefaultLanguage().getId(); - long spaninsh = new LanguageDataGen().nextPersisted().getId(); + long spaninsh = new UniqueLanguageDataGen().nextPersisted().getId(); ContentType contentType = TestDataUtils.getEmployeeLikeContentType(); Contentlet employeeContent = TestDataUtils.getEmployeeContent(true,1,contentType.id()); diff --git a/dotcms-integration/src/test/java/com/dotmarketing/business/DeterministicIdentifierAPITest.java b/dotcms-integration/src/test/java/com/dotmarketing/business/DeterministicIdentifierAPITest.java index 108418073440..3dd3a077cb99 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/business/DeterministicIdentifierAPITest.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/business/DeterministicIdentifierAPITest.java @@ -415,14 +415,13 @@ public static Object[] getLanguageTestCases() throws Exception { prepareIfNecessary(); //Propose a set of test languages - final List testCases = Stream - .of(new LanguageTestCase("es", "US", "United States", "Language:es:US", 4913155), - new LanguageTestCase("ep", "", "", "Language:ep:", 5292269), - new LanguageTestCase("sg", "SAG", "", "Language:sg:SAG", 4713118), - new LanguageTestCase("en", "NZ", "New Zealand", "Language:en:NZ", 5382528)) - .collect(Collectors.toList()); - return testCases.toArray(); + return Stream + .of(new LanguageTestCase("es", "US", "United States", "Language:es:US", 4913155), + new LanguageTestCase("ep", "", "", "Language:ep:", 5292269), + new LanguageTestCase("sg", "SAG", "", "Language:sg:SAG", 4713118), + new LanguageTestCase("en", "NZ", "New Zealand", "Language:en:NZ", 5382528)) + .toArray(); } static class LanguageTestCase { diff --git a/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/business/ContentletAPITest.java b/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/business/ContentletAPITest.java index 73c64471802c..73bcc22e18a4 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/business/ContentletAPITest.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/portlets/contentlet/business/ContentletAPITest.java @@ -1,11 +1,24 @@ package com.dotmarketing.portlets.contentlet.business; +import static com.dotcms.content.business.json.ContentletJsonAPI.SAVE_CONTENTLET_AS_JSON; +import static com.dotcms.contenttype.model.type.BaseContentType.FILEASSET; +import static com.dotmarketing.business.APILocator.getContentTypeFieldAPI; +import static com.dotmarketing.portlets.contentlet.model.Contentlet.VARIANT_ID; +import static java.io.File.separator; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import com.dotcms.api.system.event.ContentletSystemEventUtil; import com.dotcms.concurrent.DotConcurrentFactory; import com.dotcms.concurrent.DotSubmitter; import com.dotcms.content.elasticsearch.business.ESContentFactoryImpl; import com.dotcms.content.elasticsearch.business.ESContentletAPIImpl; -import com.dotcms.content.model.type.text.FloatTextFieldType; import com.dotcms.contenttype.business.ContentTypeAPI; import com.dotcms.contenttype.business.ContentTypeAPIImpl; import com.dotcms.contenttype.model.field.BinaryField; @@ -36,7 +49,6 @@ import com.dotcms.datagen.FileAssetDataGen; import com.dotcms.datagen.FolderDataGen; import com.dotcms.datagen.HTMLPageDataGen; -import com.dotcms.datagen.LanguageCodeDataGen; import com.dotcms.datagen.LanguageDataGen; import com.dotcms.datagen.PersonaDataGen; import com.dotcms.datagen.SiteDataGen; @@ -141,24 +153,6 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import io.vavr.Tuple2; import io.vavr.control.Try; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.time.DateUtils; -import org.apache.commons.lang.time.FastDateFormat; -import org.apache.velocity.app.VelocityEngine; -import org.apache.velocity.context.Context; -import org.apache.velocity.context.InternalContextAdapterImpl; -import org.apache.velocity.runtime.parser.node.SimpleNode; -import org.awaitility.Awaitility; -import org.elasticsearch.action.search.SearchResponse; -import org.jetbrains.annotations.NotNull; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -186,20 +180,23 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; - -import static com.dotcms.content.business.json.ContentletJsonAPI.SAVE_CONTENTLET_AS_JSON; -import static com.dotcms.contenttype.model.type.BaseContentType.FILEASSET; -import static com.dotmarketing.business.APILocator.getContentTypeFieldAPI; -import static com.dotmarketing.portlets.contentlet.model.Contentlet.VARIANT_ID; -import static java.io.File.separator; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang.time.FastDateFormat; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.context.Context; +import org.apache.velocity.context.InternalContextAdapterImpl; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.awaitility.Awaitility; +import org.elasticsearch.action.search.SearchResponse; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; /** * Created by Jonathan Gamba. Date: 3/20/12 Time: 12:12 PM @@ -3114,12 +3111,11 @@ public void no_destroy_limited_content_locked_by_admin_by_limited_user() final User chrisPublisher = TestUserUtils.getChrisPublisherUser(); final User adminUser = TestUserUtils.getAdminUser(); - final LanguageCodeDataGen languageCodeDataGen = new LanguageCodeDataGen(); - final String languageCode1 = languageCodeDataGen.next(); - final Language language1 = new LanguageDataGen().languageCode(languageCode1) + + final Language language1 = new LanguageDataGen() .countryCode("IT").nextPersisted(); - final String languageCode2 = languageCodeDataGen.next(); - final Language language2 = new LanguageDataGen().languageCode(languageCode2) + + final Language language2 = new LanguageDataGen() .countryCode("IT").nextPersisted(); final Structure testStructure = createStructure( "JUnit Test Destroy Structure_" + new Date().getTime(), @@ -3168,12 +3164,11 @@ public void destroy_content_locked_by_admin_by_other_admin_user() final User adminUser2 = TestUserUtils.getAdminUser(); final User adminUser = TestUserUtils.getAdminUser(); - final LanguageCodeDataGen languageCodeDataGen = new LanguageCodeDataGen(); - final String languageCode1 = languageCodeDataGen.next(); - final Language language1 = new LanguageDataGen().languageCode(languageCode1) + + final Language language1 = new LanguageDataGen() .countryCode("IT").nextPersisted(); - final String languageCode2 = languageCodeDataGen.next(); - final Language language2 = new LanguageDataGen().languageCode(languageCode2) + + final Language language2 = new LanguageDataGen() .countryCode("IT").nextPersisted(); final Structure testStructure = createStructure( "JUnit Test Destroy Structure_" + new Date().getTime(), diff --git a/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/LanguageAPITest.java b/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/LanguageAPITest.java index e1a49c473aa3..ce6e18e072a7 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/LanguageAPITest.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/LanguageAPITest.java @@ -58,7 +58,7 @@ public static void prepare() throws Exception { systemUser = APILocator.systemUser(); - language= new LanguageDataGen().nextPersisted(); + language= new UniqueLanguageDataGen().nextPersisted(); } @@ -80,7 +80,7 @@ public void languageCache() throws Exception { - Language lan = new LanguageDataGen().nextPersisted(); + Language lan = new UniqueLanguageDataGen().nextPersisted(); existingLanguagesCount = APILocator.getLanguageAPI().getLanguages().size(); CacheLocator.getLanguageCache().clearCache(); @@ -192,7 +192,7 @@ public void getStringsAsMap_returnMap() throws Exception { final String SYSTEM_PROPERTYFILE_KEY = "contenttypes.action.cancel"; final LanguageAPIImpl languageAPi = new LanguageAPIImpl(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final LanguageAPIImpl lapi = Mockito.spy(languageAPi); Mockito.doReturn(APILocator.systemUser()).when(lapi).getUser(); final ContentType langKeyType = APILocator.getContentTypeAPI(APILocator.systemUser()) @@ -298,7 +298,7 @@ public void test_deleteLanguage_WithExistingContent_ShouldFail() { Language newLanguage = null; ContentType testType = null; try { - newLanguage = new LanguageDataGen().nextPersisted(); + newLanguage = new UniqueLanguageDataGen().nextPersisted(); testType = new ContentTypeDataGen().nextPersisted(); // We don't care about the reference to the content since deleting the type will take care of it new ContentletDataGen(testType.id()) @@ -337,10 +337,10 @@ public void Create_Content_Under_Default_Lang_Make_New_Default_Language_And_Test throws DotDataException, DotSecurityException, DotIndexException { final LanguageAPI languageAPI = APILocator.getLanguageAPI(); final Language defaultLang = languageAPI.getDefaultLanguage(); - final Language newDefaultLanguage = new LanguageDataGen().nextPersisted(); + final Language newDefaultLanguage = new UniqueLanguageDataGen().nextPersisted(); final User admin = mockAdminUser(); try { - final Language thirdLanguage = new LanguageDataGen().nextPersisted(); + final Language thirdLanguage = new UniqueLanguageDataGen().nextPersisted(); final ContentType news = getNewsLikeContentType("News"); final Contentlet persistedWithOldDefaultLang = new ContentletDataGen(news) diff --git a/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/LanguageDataGen.java b/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/UniqueLanguageDataGen.java similarity index 50% rename from dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/LanguageDataGen.java rename to dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/UniqueLanguageDataGen.java index e1cde411d669..626eeadff72b 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/LanguageDataGen.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/portlets/languagesmanager/business/UniqueLanguageDataGen.java @@ -1,17 +1,19 @@ package com.dotmarketing.portlets.languagesmanager.business; import com.dotcms.business.WrapInTransaction; -import java.util.Set; -import org.apache.commons.lang.RandomStringUtils; - import com.dotcms.datagen.AbstractDataGen; import com.dotmarketing.business.APILocator; import com.dotmarketing.common.db.DotConnect; import com.dotmarketing.portlets.languagesmanager.model.Language; +import com.dotmarketing.util.StringUtils; +import io.vavr.Tuple2; +import java.util.Set; +import org.apache.commons.lang.RandomStringUtils; -import io.vavr.Tuple2;; +public class UniqueLanguageDataGen extends AbstractDataGen { -public class LanguageDataGen extends AbstractDataGen { + //These are the languages that we have identified as restricted cause they are used in the tests + private static final Set restrictedLanguageCodes = Set.of("de", "en", "es", "fr", "fi", "nl", "ru", "zh", "ep", "sg"); @Override public Language next() { @@ -42,16 +44,45 @@ private Tuple2 uniqueLangAndCountry(){ while(count>0) { language = RandomStringUtils.randomAlphabetic(2); // Skip if the country is one of the ones that we already have - if (Set.of("de", "en", "es", "fr", "fi", "nl", "ru", "zh", "ep", "sg").contains(language)) { + if (isRestrictedLanguageCode(language)) { continue; } country = RandomStringUtils.randomAlphabetic(2); - count = new DotConnect().setSQL("select count(*) as test from language where language_code=? and country_code=?").addParam(language).addParam(country).getInt("test"); + count = testLanguageExist(language, country); } - + return new Tuple2<>(language, country); - + + } + + /** + * Test if a language with that code exists + * @param languageCode + * @return + */ + public static boolean isRestrictedLanguageCode(final String languageCode) { + return restrictedLanguageCodes.contains(languageCode.toLowerCase()); + } + + /** + * Test if a language with that code and country exists + * @param languageCode + * @param countryCode + * @return + */ + public static int testLanguageExist(final String languageCode, final String countryCode) { + + if (!StringUtils.isSet(countryCode)) { + return new DotConnect().setSQL( + "select count(*) as test from language where lower(language_code)=lower(?) and country_code is null " + ).addParam(languageCode).getInt("test"); + } + + return new DotConnect().setSQL( + "select count(*) as test from language where lower(language_code)=lower(?) and lower(country_code)=lower(?)" + ).addParam(languageCode).addParam(countryCode).getInt("test"); + } } diff --git a/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task211012AddCompanyDefaultLanguageTest.java b/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task211012AddCompanyDefaultLanguageTest.java index f2144317d441..340a0fc7446d 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task211012AddCompanyDefaultLanguageTest.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task211012AddCompanyDefaultLanguageTest.java @@ -14,7 +14,7 @@ import com.dotmarketing.exception.DotDataException; import com.dotmarketing.exception.DotSecurityException; import com.dotmarketing.portlets.languagesmanager.business.LanguageAPI; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; @@ -82,7 +82,7 @@ public void Test_UpgradeTask_Added_New_Language_Success() throws DotDataException, DotSecurityException { final LanguageAPI languageAPI = APILocator.getLanguageAPI(); final Language defaultLang = languageAPI.getDefaultLanguage(); - final Language language = new LanguageDataGen().nextPersisted(); + final Language language = new UniqueLanguageDataGen().nextPersisted(); final Task211012AddCompanyDefaultLanguage task = new Task211012AddCompanyDefaultLanguage(); final Tuple2 defaultLanguageDeclaration = getDeclaredDefaultLanguage(); diff --git a/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task240306MigrateLegacyLanguageVariablesTest.java b/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task240306MigrateLegacyLanguageVariablesTest.java index 06042333c71f..477afa8fb4c0 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task240306MigrateLegacyLanguageVariablesTest.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/startup/runonce/Task240306MigrateLegacyLanguageVariablesTest.java @@ -12,7 +12,7 @@ import com.dotmarketing.portlets.contentlet.business.ContentletAPI; import com.dotmarketing.portlets.contentlet.model.Contentlet; import com.dotmarketing.portlets.languagesmanager.business.LanguageAPI; -import com.dotmarketing.portlets.languagesmanager.business.LanguageDataGen; +import com.dotmarketing.portlets.languagesmanager.business.UniqueLanguageDataGen; import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; @@ -245,7 +245,7 @@ static void createLangVariantInNotExists(final String languageCode, final String final List languages = languageAPI.getLanguages(); final boolean exists = languages.stream().anyMatch(l -> l.getLanguageCode().equalsIgnoreCase(languageCode) && l.getCountryCode().equalsIgnoreCase(countryCode)); if (!exists) { - new LanguageDataGen().persist(new Language(new Random().nextLong(), languageCode, countryCode, languageName, countryName)); + new UniqueLanguageDataGen().persist(new Language(new Random().nextLong(), languageCode, countryCode, languageName, countryName)); } } From a4340d2303153eb7168085c1e4e16618edf88b37 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Mon, 12 Aug 2024 16:32:20 -0400 Subject: [PATCH 3/7] fix(Template): Unable to bring back old versions of templates (#29509) ### Proposed Changes * Allow users to bring back old versions of templates ### Video https://github.com/user-attachments/assets/d915ab5b-e22c-4565-92d0-f4b6e5827cbc --------- Co-authored-by: Nollymar Longa --- .../dot-template-create-edit.component.ts | 3 +- .../WEB-INF/messages/Language.properties | 1 + .../portlet/ext/common/edit_versions_inc.jsp | 41 +++++++++++++++++-- .../webapp/html/templates/push_history.jsp | 35 +++++++++++++++- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-create-edit/dot-template-create-edit.component.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-create-edit/dot-template-create-edit.component.ts index 9bc6021f5030..08112b8ba53a 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-create-edit/dot-template-create-edit.component.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-templates/dot-template-create-edit/dot-template-create-edit.component.ts @@ -139,7 +139,8 @@ export class DotTemplateCreateEditComponent implements OnInit, OnDestroy { * @memberof DotTemplateBuilderComponent */ onCustomEvent($event: CustomEvent): void { - this.store.goToEditTemplate($event.detail.data.id, $event.detail.data.inode); + const { data } = $event.detail; + this.store.goToEditTemplate(data.id, data.inode); } private createTemplate(): void { diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index 42c26973f33a..782f5f57793a 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -1216,6 +1216,7 @@ dot.template.builder.action.create=Create dot.template.builder.add.container=Add Container dot.template.builder.add.box=Box dot.template.builder.add.row=Row +dot.template.builder.bringing.back.template.version=Bringing back template version... dot.template.builder.edit.classes=Edit Classes dot.template.builder.edit.box=Edit Box dot.template.builder.label.classes=Classes diff --git a/dotCMS/src/main/webapp/html/portlet/ext/common/edit_versions_inc.jsp b/dotCMS/src/main/webapp/html/portlet/ext/common/edit_versions_inc.jsp index e8bceb66b32c..a5b0a5fd79e3 100644 --- a/dotCMS/src/main/webapp/html/portlet/ext/common/edit_versions_inc.jsp +++ b/dotCMS/src/main/webapp/html/portlet/ext/common/edit_versions_inc.jsp @@ -67,10 +67,45 @@ <%@page import="com.dotmarketing.business.PermissionAPI"%> <%@page import="com.dotmarketing.business.Permissionable"%> + -
- <%= LanguageUtil.get(pageContext, "Identifier") %> : <%=(ident!=null?ident.getId():"") %> +
+
+ <%= LanguageUtil.get(pageContext, "Identifier") %> : <%=(ident!=null?ident.getId():"") %> +
+
+
+ <%= LanguageUtil.get(pageContext, "dot.template.builder.bringing.back.template.version") %> +
@@ -114,7 +149,7 @@ <%= LanguageUtil.get(pageContext, "Delete") %> <% } %> <% if(!hideBringBack) { %> - - <%= LanguageUtil.get(pageContext, "Bring-Back") %> + - <%= LanguageUtil.get(pageContext, "Bring-Back") %> <%}%> <% } %> <% } else { %> diff --git a/dotCMS/src/main/webapp/html/templates/push_history.jsp b/dotCMS/src/main/webapp/html/templates/push_history.jsp index 73dfa6f09bbc..9d12d3c080e7 100644 --- a/dotCMS/src/main/webapp/html/templates/push_history.jsp +++ b/dotCMS/src/main/webapp/html/templates/push_history.jsp @@ -18,7 +18,7 @@ Template template = APILocator.getTemplateAPI().findWorkingTemplate(templateId,user,true); request.setAttribute(com.dotmarketing.util.WebKeys.VERSIONS_INODE_EDIT, template); - request.setAttribute("hideBringBack", true); + request.setAttribute("hideBringBack", false); %> <%@ include file="/html/portlet/ext/common/edit_versions_inc.jsp" %> @@ -43,4 +43,37 @@ }); document.dispatchEvent(customEvent); } + + let isBringBack = false; + function bringBackTemplateVersion(inode){ + if(!isBringBack && confirm('<%=UtilMethods.escapeSingleQuotes(LanguageUtil.get(pageContext, "folder.replace.template.working.version"))%>')){ + setIsBringBack(true); + fetch(`/api/v1/versionables/${inode}/_bringback`, { + method: 'PUT' + }) + .then(response => response.json()) + .then(({ entity }) => { + const customEvent = document.createEvent('CustomEvent'); + customEvent.initCustomEvent('ng-event', false, false, { + name: 'bring-back-version', + data: { + id: entity.versionId, + inode: entity.inode, + type: 'template' + } + }) + document.dispatchEvent(customEvent); + }) + .catch((error) => { + console.error('Error bringing back version: ', error); + }) + .finally(() => setIsBringBack(false)); + } + } + + function setIsBringBack(isBringBack){ + const element = document.querySelector("[data-messageId='bring-back-message']"); + element.style.display = isBringBack ? 'flex' : 'none'; + isBringBack = isBringBack + } From 0f1c029067899b38b00355c8f3759495076af2af Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Mon, 12 Aug 2024 17:27:21 -0400 Subject: [PATCH 4/7] fix(UVE): Update angular pages when changes in the editor are made (#29532) ### Proposed Changes * Update angular pages when changes in the editor are made ### Checklist - [x] Tests ### Video https://github.com/user-attachments/assets/ae191098-b9a3-45ca-b26c-9837ed79b091 ### Video [After Feedback] https://github.com/user-attachments/assets/45502f47-6641-4758-a424-3460fa90ec63 --------- Co-authored-by: Jalinson Diaz --- .../container/container.component.spec.ts | 8 +- .../layout/container/container.component.ts | 8 +- .../dotcms-layout.component.spec.ts | 79 +++++++++++++------ .../dotcms-layout/dotcms-layout.component.ts | 21 +++-- .../dotcms-context/page-context.service.ts | 62 ++++++++++++--- .../dotcms-context/page-context.spec.ts | 62 +++++++++++---- .../angular/src/lib/utils/testing.utils.ts | 11 +++ 7 files changed, 182 insertions(+), 69 deletions(-) diff --git a/core-web/libs/sdk/angular/src/lib/layout/container/container.component.spec.ts b/core-web/libs/sdk/angular/src/lib/layout/container/container.component.spec.ts index 32512598f716..ee728f077234 100644 --- a/core-web/libs/sdk/angular/src/lib/layout/container/container.component.spec.ts +++ b/core-web/libs/sdk/angular/src/lib/layout/container/container.component.spec.ts @@ -38,7 +38,7 @@ describe('ContainerComponent', () => { { provide: PageContextService, useValue: { - pageContextValue: { + context: { pageAsset: { containers: PageResponseMock.containers }, @@ -116,7 +116,7 @@ describe('ContainerComponent', () => { { provide: PageContextService, useValue: { - pageContextValue: { + context: { pageAsset: { containers: PageResponseMock.containers }, @@ -139,7 +139,7 @@ describe('ContainerComponent', () => { { provide: PageContextService, useValue: { - pageContextValue: { + context: { pageAsset: { containers: PageResponseMock.containers }, @@ -168,7 +168,7 @@ describe('ContainerComponent', () => { { provide: PageContextService, useValue: { - pageContextValue: { + context: { pageAsset: { containers: PageResponseMock.containers }, diff --git a/core-web/libs/sdk/angular/src/lib/layout/container/container.component.ts b/core-web/libs/sdk/angular/src/lib/layout/container/container.component.ts index 80f06fb0f5af..e8d6b2ed9bed 100644 --- a/core-web/libs/sdk/angular/src/lib/layout/container/container.component.ts +++ b/core-web/libs/sdk/angular/src/lib/layout/container/container.component.ts @@ -54,13 +54,9 @@ export class ContainerComponent implements OnChanges { @HostBinding('attr.data-testid') testId = 'dot-container'; ngOnChanges() { - const { - pageAsset: { containers }, - components, - isInsideEditor - } = this.pageContextService.pageContextValue; + const { pageAsset, components, isInsideEditor } = this.pageContextService.context; const { acceptTypes, maxContentlets, variantId, path, contentlets } = getContainersData( - containers, + pageAsset.containers, this.container ); const { identifier, uuid } = this.container; diff --git a/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.spec.ts b/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.spec.ts index 7e45bf9ab956..f66d5b427b05 100644 --- a/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.spec.ts +++ b/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.spec.ts @@ -8,13 +8,25 @@ import { ActivatedRoute, Router } from '@angular/router'; import * as dotcmsClient from '@dotcms/client'; -import { PageResponseMock } from './../../utils/testing.utils'; +import { PageResponseMock, PageResponseOneRowMock } from './../../utils/testing.utils'; import { DotcmsLayoutComponent } from './dotcms-layout.component'; import { DotCMSContentlet, DotCMSPageAsset } from '../../models'; import { PageContextService } from '../../services/dotcms-context/page-context.service'; import { RowComponent } from '../row/row.component'; +interface Callback { + [key: string]: (data: unknown) => void; +} + +interface DotCmsClientMock extends dotcmsClient.DotCmsClient { + editor: { + on: (type: string, callbackFn: (data: unknown) => void) => void; + off: jest.Mock; + callbacks: Callback; + }; +} + @Component({ selector: 'dotcms-mock-component', standalone: true, @@ -33,9 +45,11 @@ jest.mock('@dotcms/client', () => ({ DotCmsClient: { instance: { editor: { - on: jest.fn(), + on: function (type: string, callbackFn: (data: unknown) => void): void { + this.callbacks[type] = callbackFn; + }, off: jest.fn(), - callbacks: {} + callbacks: {} as Callback } } }, @@ -48,19 +62,15 @@ const { DotCmsClient } = dotcmsClient as jest.Mocked; describe('DotcmsLayoutComponent', () => { let spectator: Spectator; + let pageContextService: PageContextService; const createComponent = createRoutingFactory({ component: DotcmsLayoutComponent, imports: [MockComponent(RowComponent)], providers: [ + PageContextService, { provide: ActivatedRoute, useValue: { url: of([]) } }, - { provide: Router, useValue: {} }, - { - provide: PageContextService, - useValue: { - setContext: jest.fn() - } - } + { provide: Router, useValue: {} } ] }); @@ -74,6 +84,8 @@ describe('DotcmsLayoutComponent', () => { }, detectChanges: false }); + + pageContextService = spectator.inject(PageContextService, true); }); afterEach(() => { @@ -86,9 +98,9 @@ describe('DotcmsLayoutComponent', () => { }); it('should save pageContext', () => { + const setContextSpy = jest.spyOn(pageContextService, 'setContext'); spectator.detectChanges(); - jest.spyOn(spectator.inject(PageContextService), 'setContext'); - expect(spectator.inject(PageContextService).setContext).toHaveBeenCalled(); + expect(setContextSpy).toHaveBeenCalled(); }); describe('inside editor', () => { @@ -101,18 +113,12 @@ describe('DotcmsLayoutComponent', () => { expect(updateNavigationSpy).toHaveBeenCalled(); }); - it('should listen to SET_PAGE_DATA message', () => { - spectator.detectChanges(); - window.dispatchEvent( - new MessageEvent('message', { data: { name: 'SET_PAGE_DATA', payload: {} } }) - ); - expect(spectator.inject(PageContextService).setContext).toHaveBeenCalled(); - }); - describe('onReload', () => { const client = DotCmsClient.instance; + let editorOnSpy: jest.SpyInstance; beforeEach(() => { + editorOnSpy = jest.spyOn(client.editor, 'on'); spectator.setInput('onReload', () => { /* do nothing */ }); @@ -120,7 +126,7 @@ describe('DotcmsLayoutComponent', () => { }); it('should subscribe to the `CHANGE` event', () => { - expect(client.editor.on).toHaveBeenCalled(); + expect(editorOnSpy).toHaveBeenCalled(); }); it('should remove listener on unmount', () => { @@ -153,8 +159,37 @@ describe('DotcmsLayoutComponent', () => { beforeEach(() => spectator.detectChanges()); it('should update the page asset when changes are made in the editor', () => { - expect(client.editor.on).toHaveBeenCalledWith('changes', expect.any(Function)); + const editorOnSpy = jest.spyOn(client.editor, 'on'); + expect(editorOnSpy).toHaveBeenCalledWith('changes', expect.any(Function)); }); }); }); + + describe('template', () => { + beforeEach(() => spectator.detectChanges()); + + it('should render rows', () => { + expect(spectator.queryAll(RowComponent).length).toBe(3); + }); + + it('should pass the correct row to RowComponent', () => { + const rowComponents = spectator.queryAll(RowComponent); + const rows = PageResponseMock.layout.body.rows; + expect(rowComponents.length).toBe(rows.length); + + rowComponents.forEach((component, index) => { + expect(component.row).toEqual(rows[index]); + }); + }); + + it('should update the page asset when changes are made in the editor', () => { + const { editor } = DotCmsClient.instance as DotCmsClientMock; + editor.callbacks['changes'](PageResponseOneRowMock); + spectator.detectChanges(); + const rowComponents = spectator.queryAll(RowComponent); + const rows = PageResponseMock.layout.body.rows; + expect(rowComponents.length).toBe(1); + expect(rowComponents[0].row).toEqual(rows[0]); + }); + }); }); diff --git a/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.ts b/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.ts index 0d86309e6bc6..c6647f2c9b0a 100644 --- a/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.ts +++ b/core-web/libs/sdk/angular/src/lib/layout/dotcms-layout/dotcms-layout.component.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +import { AsyncPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -35,10 +35,12 @@ import { RowComponent } from '../row/row.component'; @Component({ selector: 'dotcms-layout', standalone: true, - imports: [RowComponent], + imports: [RowComponent, AsyncPipe], template: ` - @for (row of this.pageAsset?.layout?.body?.rows; track $index) { - + @if (pageAsset$ | async; as page) { + @for (row of this.page?.layout?.body?.rows; track $index) { + + } } `, styleUrl: './dotcms-layout.component.css', @@ -88,9 +90,10 @@ export class DotcmsLayoutComponent implements OnInit { private readonly pageContextService = inject(PageContextService); private readonly destroyRef$ = inject(DestroyRef); private client!: DotCmsClient; + protected readonly pageAsset$ = this.pageContextService.currentPage$; ngOnInit() { - this.setContext(this.pageAsset); + this.pageContextService.setContext(this.pageAsset, this.components); if (!isInsideEditor()) { return; @@ -111,9 +114,7 @@ export class DotcmsLayoutComponent implements OnInit { return; } - const pageAsset = data as DotCMSPageAsset; - this.pageAsset = pageAsset; // Update the page asset with the Editor Response - this.setContext(pageAsset); // Update the context with the new page asset + this.pageContextService.setPageAsset(data as DotCMSPageAsset); }); postMessageToEditor({ action: CUSTOMER_ACTIONS.CLIENT_READY, payload: this.editor }); @@ -122,8 +123,4 @@ export class DotcmsLayoutComponent implements OnInit { ngOnDestroy() { this.client.editor.off('changes'); } - - private setContext(pageAsset: DotCMSPageAsset) { - this.pageContextService.setContext(pageAsset, this.components); - } } diff --git a/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.service.ts b/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.service.ts index a2d58bd6de37..5d76a51b35de 100644 --- a/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.service.ts +++ b/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.service.ts @@ -1,34 +1,78 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +import { BehaviorSubject, Observable } from 'rxjs'; import { Injectable } from '@angular/core'; +import { map } from 'rxjs/operators'; + import { isInsideEditor } from '@dotcms/client'; import { DotCMSPageComponent, DotCMSPageContext } from '../../models'; import { DotCMSPageAsset } from '../../models/dotcms.model'; +/** + * @author dotCMS + * @description This service is responsible for managing the page context. + * @export + * @class PageContextService + */ @Injectable({ providedIn: 'root' }) export class PageContextService { - private pageContext: DotCMSPageContext | null = null; + private context$ = new BehaviorSubject(null); - get pageContextValue(): DotCMSPageContext { - return this.pageContext as DotCMSPageContext; + /** + * @description Get the context + * @readonly + * @type {DotCMSPageContext} + * @memberof PageContextService + */ + get context(): DotCMSPageContext { + return this.context$.getValue() as DotCMSPageContext; + } + + /** + * @description Get the context as an observable + * @readonly + * @memberof PageContextService + */ + get contextObs$() { + return this.context$.asObservable(); + } + + /** + * @description Get the current page asset + * @readonly + * @type {(Observable)} + * @memberof PageContextService + */ + get currentPage$(): Observable { + return this.contextObs$.pipe(map((context) => context?.pageAsset || null)); } /** - * Set the context * - * @protected + * @description Set the context * @param {DotCMSPageAsset} value * @memberof DotcmsContextService */ setContext(pageAsset: DotCMSPageAsset, components: DotCMSPageComponent) { - this.pageContext = { - components, + this.context$.next({ pageAsset, + components, isInsideEditor: isInsideEditor() - }; + }); + } + + /** + * @description Set the page asset in the context + * @param {DotCMSPageAsset} pageAsset + * @memberof PageContextService + */ + setPageAsset(pageAsset: DotCMSPageAsset) { + this.context$.next({ + ...this.context, + pageAsset + }); } } diff --git a/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.spec.ts b/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.spec.ts index e3f40e410670..59231a97553b 100644 --- a/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.spec.ts +++ b/core-web/libs/sdk/angular/src/lib/services/dotcms-context/page-context.spec.ts @@ -5,6 +5,10 @@ import { TestBed } from '@angular/core/testing'; import { PageContextService } from './page-context.service'; import { DotCMSPageAsset, DotCMSPageComponent } from '../../models'; +import { PageResponseMock } from '../../utils/testing.utils'; + +const initialPageAssetMock = {} as DotCMSPageAsset; +const initialComponentsMock = {} as DotCMSPageComponent; describe('PageContextService', () => { let spectator: SpectatorService; @@ -18,33 +22,59 @@ describe('PageContextService', () => { service = spectator.service; }); - it('should be created', () => { - expect(service).toBeTruthy(); + it('should set the context', () => { + service.setContext(initialPageAssetMock, initialComponentsMock); + + expect(service.context).toEqual({ + components: initialComponentsMock, + pageAsset: initialPageAssetMock, + isInsideEditor: false + }); }); - it('should set the context', () => { - const pageAssetMock = {} as DotCMSPageAsset; - const componentsMock = {} as DotCMSPageComponent; + it('should set the page asset in the context', () => { + service.setContext(initialPageAssetMock, initialComponentsMock); - service.setContext(pageAssetMock, componentsMock); + const newPageAssetMock = PageResponseMock as unknown as DotCMSPageAsset; - expect(service.pageContextValue).toEqual({ - components: componentsMock, - pageAsset: pageAssetMock, + service.setPageAsset(newPageAssetMock); + + expect(service.context).toEqual({ + components: initialComponentsMock, + pageAsset: newPageAssetMock, isInsideEditor: false }); }); it('should return the context', () => { - const pageAssetMock = {} as DotCMSPageAsset; - const componentsMock = {} as DotCMSPageComponent; - - service.setContext(pageAssetMock, componentsMock); + service.setContext(initialPageAssetMock, initialComponentsMock); - expect(service.pageContextValue).toEqual({ - components: componentsMock, - pageAsset: pageAssetMock, + expect(service.context).toEqual({ + components: initialComponentsMock, + pageAsset: initialPageAssetMock, isInsideEditor: false }); }); + + it('should return the context as an observable', (done) => { + service.setContext(initialPageAssetMock, initialComponentsMock); + + service.contextObs$.subscribe((context) => { + expect(context).toEqual({ + components: initialComponentsMock, + pageAsset: initialPageAssetMock, + isInsideEditor: false + }); + done(); + }); + }); + + it('should return the page asset as an observable', (done) => { + service.setContext(initialPageAssetMock, initialComponentsMock); + + service.currentPage$.subscribe((pageAsset) => { + expect(pageAsset).toEqual(initialPageAssetMock); + done(); + }); + }); }); diff --git a/core-web/libs/sdk/angular/src/lib/utils/testing.utils.ts b/core-web/libs/sdk/angular/src/lib/utils/testing.utils.ts index 7ffeac951952..247693dfe56d 100644 --- a/core-web/libs/sdk/angular/src/lib/utils/testing.utils.ts +++ b/core-web/libs/sdk/angular/src/lib/utils/testing.utils.ts @@ -907,6 +907,17 @@ export const PageResponseMock = { } }; +export const PageResponseOneRowMock = { + ...PageResponseMock, + layout: { + ...PageResponseMock.layout, + body: { + ...PageResponseMock.layout.body, + rows: [PageResponseMock.layout.body.rows[0]] + } + } +}; + export const NavMock = { children: [ { From 53216fddd4178939ea26c3cb50e6c53bf71ecb29 Mon Sep 17 00:00:00 2001 From: Victor Alfaro Date: Mon, 12 Aug 2024 15:47:51 -0600 Subject: [PATCH 5/7] #29281: Adding missing default embeddings url (#29533) --- dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java b/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java index 83971a94a74f..2f8bcdcf1c55 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java @@ -4,7 +4,7 @@ public enum AppKeys { API_URL("apiUrl", "https://api.openai.com/v1/chat/completions"), API_IMAGE_URL("apiImageUrl", "https://api.openai.com/v1/images/generations"), - API_EMBEDDINGS_URL("apiEmbeddingsUrl", null), + API_EMBEDDINGS_URL("apiEmbeddingsUrl", "https://api.openai.com/v1/embeddings"), API_KEY("apiKey", null), ROLE_PROMPT( "rolePrompt", From a7ced0df4f514f7460a91812745dd0e09f1a0a39 Mon Sep 17 00:00:00 2001 From: Valentino Giardino <77643678+valentinogiardino@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:57:02 -0300 Subject: [PATCH 6/7] fix(GraphQL) Refs #29270 Improve error message for unauthorized page request (#29521) ### Proposed Changes Enhance the error handling capabilities of our GraphQL API by introducing a custom exception class that aligns with the GraphQL error handling guidelines. * Added a custom GraphQL exception class that implements `GraphQLError`. * Introduced `PermissionDeniedGraphQLException` for unauthorized access. * Fixed error message for unauthorized page requests. * Added Postman test for exception handling. ### Checklist - [x] Unauthorized query request should return a correct error message ### Screenshots #### Before: ![image](https://github.com/user-attachments/assets/8ee2cd67-af11-4616-abec-534dd2b56b88) #### After: ![image](https://github.com/user-attachments/assets/8dd74fb9-d0ee-4c80-8d99-6056ade8eac5) This PR fixes #29270 --- .../datafetcher/page/PageDataFetcher.java | 9 ++ .../exception/CustomGraphQLException.java | 75 +++++++++++++++ .../PermissionDeniedGraphQLException.java | 27 ++++++ .../main/resources/postman/GraphQLTests.json | 95 +++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 dotCMS/src/main/java/com/dotcms/graphql/exception/CustomGraphQLException.java create mode 100644 dotCMS/src/main/java/com/dotcms/graphql/exception/PermissionDeniedGraphQLException.java diff --git a/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java b/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java index d616d396203f..ac884c9c1270 100644 --- a/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java +++ b/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java @@ -1,8 +1,10 @@ package com.dotcms.graphql.datafetcher.page; import com.dotcms.graphql.DotGraphQLContext; +import com.dotcms.graphql.exception.PermissionDeniedGraphQLException; import com.dotmarketing.beans.Host; import com.dotmarketing.business.APILocator; +import com.dotmarketing.exception.DotSecurityException; import com.dotmarketing.portlets.contentlet.model.Contentlet; import com.dotmarketing.portlets.contentlet.transform.DotContentletTransformer; import com.dotmarketing.portlets.contentlet.transform.DotTransformerBuilder; @@ -92,6 +94,13 @@ public Contentlet get(final DataFetchingEnvironment environment) throws Exceptio } catch (HTMLPageAssetNotFoundException e) { Logger.error(this, e.getMessage()); return null; + }catch (DotSecurityException e) { + Logger.error(this, e.getMessage()); + if(mode.equals(PageMode.WORKING)) { + throw new PermissionDeniedGraphQLException( + "Unauthorized: You do not have the necessary permissions to request this page in edit mode."); + } + throw new PermissionDeniedGraphQLException(); } final HTMLPageAsset pageAsset = pageUrl.getHTMLPage(); diff --git a/dotCMS/src/main/java/com/dotcms/graphql/exception/CustomGraphQLException.java b/dotCMS/src/main/java/com/dotcms/graphql/exception/CustomGraphQLException.java new file mode 100644 index 000000000000..69e516b67f7c --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/graphql/exception/CustomGraphQLException.java @@ -0,0 +1,75 @@ +package com.dotcms.graphql.exception; + +import graphql.ErrorType; +import graphql.GraphQLError; +import graphql.GraphQLException; +import graphql.language.SourceLocation; +import java.util.Collections; + +import java.util.List; +import java.util.Map; + +/** + * Custom exception class for GraphQL errors. + *

+ * This class extends {@link GraphQLException} and implements {@link GraphQLError}, + * allowing for customized error handling in GraphQL applications. + *

+ *

+ * By implementing {@link GraphQLError}, this exception can provide additional + * context or metadata about the error, such as locations, extensions, and error types. + *

+ */ +public class CustomGraphQLException extends RuntimeException implements GraphQLError { + + /** + * Constructs a new CustomGraphQLException with the specified detail message. + * + * @param message the detail message explaining the reason for the exception. + */ + public CustomGraphQLException(String message) { + super(message); + } + + /** + * Provides additional metadata about the error. + *

+ * This implementation returns an empty map, but subclasses can override this + * method to include custom extensions that provide more information about the error. + *

+ * + * @return an empty map by default, which can be overridden by subclasses. + */ + @Override + public Map getExtensions() { + return Collections.emptyMap(); + } + + /** + * Returns the list of source locations related to the error. + *

+ * This implementation returns an empty list, but subclasses can override this + * method to include the specific locations in the GraphQL query where the error occurred. + *

+ * + * @return an empty list by default, which can be overridden by subclasses. + */ + @Override + public List getLocations() { + return Collections.emptyList(); + } + + /** + * Returns the type of the error. + *

+ * This implementation returns {@code null}, but subclasses can override this + * method to provide a specific {@link ErrorType} for the error. + *

+ * + * @return {@code null} by default, which can be overridden by subclasses. + */ + @Override + public ErrorType getErrorType() { + return null; + } +} \ No newline at end of file diff --git a/dotCMS/src/main/java/com/dotcms/graphql/exception/PermissionDeniedGraphQLException.java b/dotCMS/src/main/java/com/dotcms/graphql/exception/PermissionDeniedGraphQLException.java new file mode 100644 index 000000000000..39973190bd8d --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/graphql/exception/PermissionDeniedGraphQLException.java @@ -0,0 +1,27 @@ +package com.dotcms.graphql.exception; + +/** + * Custom exception for permission denial in GraphQL operations. + * This exception is used to indicate that a user does not have the necessary permissions + * to access a specific resource. + */ +public class PermissionDeniedGraphQLException extends CustomGraphQLException { + + private static final String DEFAULT_MESSAGE = "Permission denied: You do not have permission to access this resource."; + + /** + * Constructs a new PermissionDeniedGraphQLException with a specific message. + * + * @param message The detail message to be used. + */ + public PermissionDeniedGraphQLException(String message) { + super(message); + } + + /** + * Constructs a new PermissionDeniedGraphQLException with a default message. + */ + public PermissionDeniedGraphQLException() { + this(DEFAULT_MESSAGE); + } +} diff --git a/dotcms-postman/src/main/resources/postman/GraphQLTests.json b/dotcms-postman/src/main/resources/postman/GraphQLTests.json index c5803fc60879..87d4f5f6e40a 100644 --- a/dotcms-postman/src/main/resources/postman/GraphQLTests.json +++ b/dotcms-postman/src/main/resources/postman/GraphQLTests.json @@ -2751,6 +2751,101 @@ } ] }, + { + "name": "Checking Exception Messages", + "item": [ + { + "name": "invalidateSession", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/logout", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "logout" + ] + } + }, + "response": [] + }, + { + "name": "GivenRequestByURI_Page_EditMode_WithoutAuth", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Response contains errors and correct error message\", function () {", + " var jsonData = pm.response.json();", + " console.log(jsonData)", + " pm.expect(jsonData.errors, 'FAILED:[jsonData.errors]').to.be.an('array').that.is.not.empty;", + "", + " var error = jsonData.errors[0];", + " pm.expect(error.message, 'FAILED:[error.message]').to.equal(\"Exception while fetching data (/page) : Unauthorized: You do not have the necessary permissions to request this page in edit mode.\");", + "});", + "", + "pm.test(\"Page data is null\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.data.page, 'FAILED:[jsonData.data.page]').to.be.null;", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "graphql", + "graphql": { + "query": "{\n page(url: \"/myCoolPageWithDotAsset\", pageMode: \"WORKING\") {\n title\n }\n}\n\n", + "variables": "" + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/graphql", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "graphql" + ] + } + }, + "response": [] + } + ] + }, { "name": "pre_ImportBundleWithMexicoPageAndRule", "event": [ From 6616302c594256c353d672d5f4a1d047f2771c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Enrique=20Colina=20Rodr=C3=ADguez?= Date: Tue, 13 Aug 2024 00:02:27 +0200 Subject: [PATCH 7/7] feat(deployment): CICD Automate Deployment of Empty Starter via GitH (#29376) (#29541) ### Proposed Changes * Including Empty Starter generation flow to the `Publish Starter` workflow. ### Additional Info Related to #29376 (CICD Automate Deployment of Empty Starter via GitH). --- .github/workflows/publish-starter.yml | 58 ++++++++++++++++++--------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/.github/workflows/publish-starter.yml b/.github/workflows/publish-starter.yml index c18e83157490..7188612e0e5c 100644 --- a/.github/workflows/publish-starter.yml +++ b/.github/workflows/publish-starter.yml @@ -7,11 +7,12 @@ on: required: true type: choice options: - - 'Full Starter' + - 'full' + - 'empty' changelog: description: 'Changes description' required: true - type: string + type: string dry-run: description: 'Enable dry-run mode' required: true @@ -23,47 +24,66 @@ defaults: env: STARTER_TYPE: ${{ github.event.inputs.type }} + EMPTY_STARTER_URL: ${{ vars.DOT_EMPTY_STARTER_URL }} + EMPTY_STARTER_TOKEN: ${{ secrets.DOT_EMPTY_STARTER_ACCESS_TOKEN }} + FULL_STARTER_URL: ${{ vars.DOT_STARTER_URL }} + FULL_STARTER_TOKEN: ${{ secrets.DOT_STARTER_ACCESS_TOKEN }} + DOWNLOAD_ENDPOINT: api/v1/maintenance/_downloadStarterWithAssets?oldAssets=true jobs: - get-full-starter: + get-starter: runs-on: macos-13 if: github.repository == 'dotcms/core' environment: trunk steps: - - name: Context + - name: 'Github context' run: | - echo "Generating a Full Starter..." + echo "::group::Github context" echo "${{ toJSON(github.event.inputs) }}" + echo "::endgroup::" + - name: 'Get zip file' id: get-zip-file - env: - URL: ${{ vars.DOT_STARTER_URL }} - ACCESS_TOKEN: ${{ secrets.DOT_STARTER_ACCESS_TOKEN }} run: | echo "::group::Getting zip file" + echo "::notice::Starter type: $STARTER_TYPE" + function download_starter { + URL=$1/${{ env.DOWNLOAD_ENDPOINT }} + ACCESS_TOKEN=$2 + OUTPUT_FILENAME=$3 + curl -s -w "%{http_code}" --output ${OUTPUT_FILENAME} $URL \ + -H "Content-Type: application/json" \ + -H "Accept: application/octet-stream" \ + -H "Authorization: Bearer $ACCESS_TOKEN" + } mkdir -p starter && cd starter - RESP=$(curl -s -w "%{http_code}" --output $(date +'%Y%m%d').zip $URL \ - -H "Content-Type: application/json" \ - -H "Accept: application/octet-stream" \ - -H "Authorization: Bearer $ACCESS_TOKEN") - if [[ "$RESP" != "200" ]]; then - echo "RESP: $RESP" + if [[ "$STARTER_TYPE" == "empty" ]]; then + echo "::debug::Empty Starter: downloading from [${{ env.EMPTY_STARTER_URL }}/${{ env.DOWNLOAD_ENDPOINT }}]" + RESPONSE=$(download_starter ${{ env.EMPTY_STARTER_URL }} ${{ env.EMPTY_STARTER_TOKEN }} empty_$(date +'%Y%m%d').zip) + else + echo "::debut::Full Starter: downloading from [${{ env.FULL_STARTER_URL }}/${{ env.DOWNLOAD_ENDPOINT }}]" + RESPONSE=$(download_starter ${{ env.FULL_STARTER_URL }} ${{ env.FULL_STARTER_TOKEN }} $(date +'%Y%m%d').zip) + fi + echo "::notice::Status Code: $RESPONSE" + if [[ "$RESPONSE" != "200" ]]; then + echo "::error::Failed with status code: $RESPONSE" exit 1 fi ls -ltrh - echo "::endgroup::" + # echo "::endgroup::" + - name: 'Upload artifacts' id: upload-artifacts uses: actions/upload-artifact@v4 with: - name: starter + name: ${{ env.STARTER_TYPE }}-starter path: | ${{ github.workspace }}/starter/*.zip retention-days: 2 - if-no-files-found: ignore + if-no-files-found: ignore deploy-artifacts: - needs: [ get-full-starter ] + needs: [ get-starter ] runs-on: ubuntu-20.04 environment: trunk outputs: @@ -86,7 +106,7 @@ jobs: uses: actions/download-artifact@v4 with: github-token: ${{ github.token }} - name: starter + name: ${{ env.STARTER_TYPE }}-starter path: ${{ github.workspace }}/starter - name: 'Listing artifacts'