diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AllKeysController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AllKeysController.kt index 17e823da6f..b1139b9a49 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AllKeysController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/AllKeysController.kt @@ -17,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @Suppress("MVCPathVariableInspection") @@ -40,8 +41,11 @@ class AllKeysController( @Operation(summary = "Get all keys in project") @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess - fun getAllKeys(): CollectionModel { - val allKeys = keyService.getAllSortedById(projectHolder.project.id) + fun getAllKeys( + @RequestParam(required = false) + branch: String? = null, + ): CollectionModel { + val allKeys = keyService.getAllSortedById(projectHolder.project.id, branch) return keyModelAssembler.toCollectionModel(allKeys) } diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/dataImport/V2ImportController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/dataImport/V2ImportController.kt index 37c5727b35..179ab7528a 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/dataImport/V2ImportController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/dataImport/V2ImportController.kt @@ -89,16 +89,17 @@ class V2ImportController( userAccount = authenticationFacade.authenticatedUserEntity, params = params, ) - return getImportAddFilesResultModel(errors, warnings) + return getImportAddFilesResultModel(errors, warnings, params) } private fun getImportAddFilesResultModel( errors: List, warnings: List, + params: ImportAddFilesParams, ): ImportAddFilesResultModel { val result: PagedModel? = try { - this.getImportResult(PageRequest.of(0, 100)) + this.getImportResult(PageRequest.of(0, 100), params.branch) } catch (e: NotFoundException) { null } @@ -115,9 +116,10 @@ class V2ImportController( @Parameter(description = "Whether override or keep all translations with unresolved conflicts") @RequestParam(defaultValue = "NO_FORCE") forceMode: ForceMode, + @RequestParam branch: String? = null, ) { val projectId = projectHolder.project.id - this.importService.import(projectId, authenticationFacade.authenticatedUser.id, forceMode) + this.importService.import(projectId, authenticationFacade.authenticatedUser.id, branch, forceMode) } @PutMapping("/apply-streaming", produces = [MediaType.APPLICATION_NDJSON_VALUE]) @@ -134,11 +136,12 @@ class V2ImportController( @Parameter(description = "Whether override or keep all translations with unresolved conflicts") @RequestParam(defaultValue = "NO_FORCE") forceMode: ForceMode, + @RequestParam branch: String? = null, ): ResponseEntity { val projectId = projectHolder.project.id return streamingImportProgressUtil.stream { writeStatus -> - this.importService.import(projectId, authenticationFacade.authenticatedUser.id, forceMode, writeStatus) + this.importService.import(projectId, authenticationFacade.authenticatedUser.id, branch, forceMode, writeStatus) } } @@ -148,10 +151,11 @@ class V2ImportController( @AllowApiAccess fun getImportResult( @ParameterObject pageable: Pageable, + @RequestParam branch: String? = null, ): PagedModel { val projectId = projectHolder.project.id val userId = authenticationFacade.authenticatedUser.id - val languages = importService.getResult(projectId, userId, pageable) + val languages = importService.getResult(projectId, userId, branch, pageable) return pagedLanguagesResourcesAssembler.toModel(languages, importLanguageModelAssembler) } @@ -160,8 +164,10 @@ class V2ImportController( @RequiresProjectPermissions([Scope.TRANSLATIONS_VIEW]) @AllowApiAccess @OpenApiOrderExtension(3) - fun cancelImport() { - this.importService.deleteImport(projectHolder.project.id, authenticationFacade.authenticatedUser.id) + fun cancelImport( + @RequestParam branch: String? = null, + ) { + this.importService.deleteImport(projectHolder.project.id, authenticationFacade.authenticatedUser.id, branch) } @GetMapping("/all-namespaces") diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/keys/KeyController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/keys/KeyController.kt index ec3fe27aa9..f575e7c991 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/keys/KeyController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/keys/KeyController.kt @@ -147,8 +147,10 @@ class KeyController( @ParameterObject @SortDefault("id") pageable: Pageable, + @RequestParam + branch: String? = null, ): PagedModel { - val data = keyService.getPaged(projectHolder.project.id, pageable) + val data = keyService.getPaged(projectHolder.project.id, branch, pageable) return keyPagedResourcesAssembler.toModel(data, keyModelAssembler) } @@ -168,7 +170,8 @@ class KeyController( key.checkInProject() checkNamespaceFeature(dto.namespace) keyService.edit(id, dto) - val view = KeyView(key.id, key.name, key?.namespace?.name, key.keyMeta?.description, key.keyMeta?.custom) + val view = + KeyView(key.id, key.name, key?.namespace?.name, key.keyMeta?.description, key.keyMeta?.custom, key.branch?.name) return keyModelAssembler.toModel(view) } diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/CreateOrUpdateTranslationsFacade.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/CreateOrUpdateTranslationsFacade.kt index 8eb881818c..65c66302c3 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/CreateOrUpdateTranslationsFacade.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/CreateOrUpdateTranslationsFacade.kt @@ -32,7 +32,7 @@ class CreateOrUpdateTranslationsFacade( @RequestBody @Valid dto: SetTranslationsWithKeyDto, ): SetTranslationsResponseModel { - val key = keyService.find(projectHolder.projectEntity.id, dto.key, dto.namespace) ?: return create(dto) + val key = keyService.find(projectHolder.projectEntity.id, dto.key, dto.namespace, dto.branch) ?: return create(dto) return setTranslations(dto, key) } @@ -74,7 +74,7 @@ class CreateOrUpdateTranslationsFacade( dto: SetTranslationsWithKeyDto, key: Key? = null, ): SetTranslationsResponseModel { - val keyNotNull = key ?: keyService.get(projectHolder.project.id, dto.key, dto.namespace) + val keyNotNull = key ?: keyService.get(projectHolder.project.id, dto.key, dto.namespace, dto.branch) securityService.checkLanguageTranslatePermissionsByTag( dto.translations.keys, projectHolder.project.id, diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt index d45ede84f3..0098a87531 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt @@ -158,6 +158,8 @@ When null, resulting file will be a flat key-value object. ) @RequestParam(value = "filterTag", required = false) filterTag: List? = null, + @Parameter(description = "Branch name to return translations from") + branch: String? = null, request: WebRequest, ): ResponseEntity>? { return projectLastModifiedManager.onlyWhenProjectDataChanged(request) { @@ -168,6 +170,7 @@ When null, resulting file will be a flat key-value object. translationService.getTranslations( languageTags = permittedTags, namespace = ns, + branch = branch, projectId = projectHolder.project.id, structureDelimiter = request.getStructureDelimiter(), filterTag = filterTag, diff --git a/backend/api/src/main/kotlin/io/tolgee/component/KeyComplexEditHelper.kt b/backend/api/src/main/kotlin/io/tolgee/component/KeyComplexEditHelper.kt index 2c86bd6253..44d50c568d 100644 --- a/backend/api/src/main/kotlin/io/tolgee/component/KeyComplexEditHelper.kt +++ b/backend/api/src/main/kotlin/io/tolgee/component/KeyComplexEditHelper.kt @@ -150,7 +150,7 @@ class KeyComplexEditHelper( } if (isKeyNameModified || isNamespaceChanged) { - edited = keyService.edit(key, dto.name, dto.namespace) + edited = keyService.edit(key, dto.name, dto.namespace, dto.branch) } return keyWithDataModelAssembler.toModel(edited) diff --git a/backend/api/src/main/kotlin/io/tolgee/controllers/ExportController.kt b/backend/api/src/main/kotlin/io/tolgee/controllers/ExportController.kt index d4d7246872..3e8d0be1eb 100644 --- a/backend/api/src/main/kotlin/io/tolgee/controllers/ExportController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/controllers/ExportController.kt @@ -69,6 +69,7 @@ class ExportController( translationService.getTranslations( allLanguages.map { it.tag }.toSet(), null, + null, projectHolder.project.id, '.', ) diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/contentDelivery/ContentDeliveryConfigModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/contentDelivery/ContentDeliveryConfigModel.kt index 05d0c03ab3..c7b17bcbe2 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/contentDelivery/ContentDeliveryConfigModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/contentDelivery/ContentDeliveryConfigModel.kt @@ -39,4 +39,5 @@ class ContentDeliveryConfigModel( override var messageFormat: ExportMessageFormat? = null override var supportArrays: Boolean = false override var fileStructureTemplate: String? = null + override var filterBranch: String? = null } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModel.kt index edf2849a6d..2952b96e77 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModel.kt @@ -21,5 +21,7 @@ open class KeyModel( val description: String?, @Schema(description = "Custom values of the key") val custom: Map?, + @Schema(description = "Branch of key", example = "dev") + val branch: String?, ) : RepresentationModel(), Serializable diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModelAssembler.kt index 527ebc51b3..e8fbdcca54 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModelAssembler.kt @@ -19,5 +19,6 @@ class KeyModelAssembler : namespace = view.namespace, description = view.description, custom = view.custom as? Map?, + branch = view.branch, ) } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModel.kt index 4377636fca..72f562f86c 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModel.kt @@ -37,5 +37,7 @@ open class KeyWithDataModel( val pluralArgName: String?, @Schema(description = "Custom values of the key") val custom: Map, + @Schema(description = "Branch of the key") + val branch: String?, ) : RepresentationModel(), Serializable diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModelAssembler.kt index c58dffaa1e..ee9eb73cea 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithDataModelAssembler.kt @@ -37,5 +37,6 @@ class KeyWithDataModelAssembler( isPlural = entity.isPlural, pluralArgName = entity.pluralArgName, custom = entity.keyMeta?.custom ?: mapOf(), + branch = entity.branch?.name, ) } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithScreenshotsModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithScreenshotsModelAssembler.kt index 6302b4d254..338306daff 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithScreenshotsModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyWithScreenshotsModelAssembler.kt @@ -38,6 +38,7 @@ class KeyWithScreenshotsModelAssembler( isPlural = entity.isPlural, pluralArgName = entity.pluralArgName, custom = entity.keyMeta?.custom ?: mapOf(), + branch = entity.branch?.name, ) } } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/AllKeysControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/AllKeysControllerTest.kt index 6bc8cc480f..f7f9a503d9 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/AllKeysControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/AllKeysControllerTest.kt @@ -1,8 +1,11 @@ package io.tolgee.api.v2.controllers import io.tolgee.ProjectAuthControllerTest +import io.tolgee.constants.Message import io.tolgee.development.testDataBuilder.data.KeysTestData import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.fixtures.andHasErrorMessage +import io.tolgee.fixtures.andIsBadRequest import io.tolgee.fixtures.andIsOk import io.tolgee.fixtures.andPrettyPrint import io.tolgee.fixtures.isValidId @@ -28,7 +31,7 @@ class AllKeysControllerTest : ProjectAuthControllerTest("/v2/projects/") { @Test @ProjectJWTAuthTestMethod - fun `returns all keys sorted`() { + fun `returns default branch keys sorted`() { performProjectAuthGet("all-keys").andPrettyPrint.andIsOk.andAssertThatJson { node("_embedded.keys") { isArray.hasSize(3) @@ -36,8 +39,41 @@ class AllKeysControllerTest : ProjectAuthControllerTest("/v2/projects/") { node("id").isValidId node("namespace").isNull() node("name").isEqualTo("first_key") + node("branch").isNull() + } + node("[1]") { + node("name").isEqualTo("second_key") + node("branch").isNull() + } + node("[2]") { + node("name").isEqualTo("key_with_referecnces") + node("branch").isNull() } } } } + + @Test + @ProjectJWTAuthTestMethod + fun `returns branch keys when branch param provided`() { + performProjectAuthGet("all-keys?branch=dev").andPrettyPrint.andIsOk.andAssertThatJson { + node("_embedded.keys") { + isArray.hasSize(1) + node("[0]") { + node("id").isValidId + node("name").isEqualTo("first_key") + node("branch").isEqualTo("dev") + } + } + } + } + + @Test + @ProjectJWTAuthTestMethod + fun `fails when branch does not exist`() { + performProjectAuthGet("all-keys?branch=does-not-exist") + .andPrettyPrint + .andIsBadRequest + .andHasErrorMessage(Message.BRANCH_NOT_FOUND) + } } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerModificationTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerModificationTest.kt index c1388b02ec..11b970905a 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerModificationTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerModificationTest.kt @@ -7,6 +7,7 @@ import io.tolgee.dtos.request.translation.SetTranslationsWithKeyDto import io.tolgee.fixtures.andAssertThatJson import io.tolgee.fixtures.andIsBadRequest import io.tolgee.fixtures.andIsForbidden +import io.tolgee.fixtures.andIsNotFound import io.tolgee.fixtures.andIsOk import io.tolgee.fixtures.andPrettyPrint import io.tolgee.fixtures.isValidId @@ -398,6 +399,56 @@ class TranslationsControllerModificationTest : ProjectAuthControllerTest("/v2/pr testOutdated(translation, true) } + @ProjectJWTAuthTestMethod + @Test + fun `sets translations for existing key in branch`() { + saveTestData() + performProjectAuthPut( + "/translations", + SetTranslationsWithKeyDto( + "branch key", + null, + mutableMapOf("en" to "English branch key"), + branch = "test-branch", + ), + ).andIsOk + .andAssertThatJson { + node("translations.en.text").isEqualTo("English branch key") + node("translations.en.id").isValidId + node("keyId").isValidId + node("keyName").isEqualTo("branch key") + } + } + + @ProjectJWTAuthTestMethod + @Test + fun `cannot set translations for key in branch without branch provided`() { + saveTestData() + performProjectAuthPut( + "/translations", + SetTranslationsWithKeyDto( + "branch key", + null, + mutableMapOf("en" to "Cannot do that"), + ), + ).andIsNotFound + } + + @ProjectJWTAuthTestMethod + @Test + fun `cannot set translations for key in default branch with different branch provided`() { + saveTestData() + performProjectAuthPut( + "/translations", + SetTranslationsWithKeyDto( + "A key", + null, + mutableMapOf("en" to "Cannot do that"), + branch = "test-branch", + ), + ).andIsNotFound + } + private fun testOutdated( translation: Translation, state: Boolean, diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt index 79204aa6aa..10820c7efe 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/translations/v2TranslationsController/TranslationsControllerViewTest.kt @@ -42,10 +42,11 @@ class TranslationsControllerViewTest : ProjectAuthControllerTest("/v2/projects/" @Test fun `returns correct data`() { testData.generateLotOfData() + testData.addDeletedBranch() testDataService.saveTestData(testData.root) userAccount = testData.user performProjectAuthGet("/translations?sort=id").andPrettyPrint.andIsOk.andAssertThatJson { - node("page.totalElements").isNumber.isGreaterThan(BigDecimal(100)) + node("page.totalElements").isNumber.isEqualTo(BigDecimal(101)) node("page.size").isEqualTo(20) node("selectedLanguages") { isArray.hasSize(2) @@ -105,6 +106,67 @@ class TranslationsControllerViewTest : ProjectAuthControllerTest("/v2/projects/" } } + @Test + @ProjectJWTAuthTestMethod + fun `return translations from non-branched keys`() { + testData.generateBranchedData(10) + testDataService.saveTestData(testData.root) + userAccount = testData.user + performProjectAuthGet("/translations?sort=id").andPrettyPrint.andIsOk.andAssertThatJson { + // 2 keys from the default branch, 10 keys from the feature branch should be filtered out + node("_embedded.keys").isArray.hasSize(2) + } + } + + @Test + @ProjectJWTAuthTestMethod + fun `return translations from default branch only`() { + testData.generateBranchedData(5, "main", true) + testData.generateBranchedData(10) + testDataService.saveTestData(testData.root) + userAccount = testData.user + performProjectAuthGet("/translations?sort=id").andPrettyPrint.andIsOk.andAssertThatJson { + // 2 non-branched keys + 5 keys from the default branch, 10 keys from the feature branch should be filtered out + node("_embedded.keys").isArray.hasSize(7) + } + } + + @Test + @ProjectJWTAuthTestMethod + fun `return translations from featured branch only`() { + testData.generateBranchedData(10) + testDataService.saveTestData(testData.root) + userAccount = testData.user + performProjectAuthGet("/translations?sort=id&branch=feature-branch").andPrettyPrint.andIsOk.andAssertThatJson { + // 10 keys from the feature branch should be returned + node("_embedded.keys").isArray.hasSize(10) + node("_embedded.keys[0].keyName").isEqualTo("key from branch feature-branch 1") + node("_embedded.keys[0].translations.en") { + node("text").isEqualTo("I am key 1's english translation from branch feature-branch.") + } + node("_embedded.keys[1].translations.de") { + node("text").isEqualTo("I am key 2's german translation from branch feature-branch.") + } + } + } + + /** + * Edge-case testing returning correct translations if there is soft-deleted branch with same name as active branch + * (deleted is in the process of hard-deleting) + */ + @Test + @ProjectJWTAuthTestMethod + fun `return translations from active branch only`() { + testData.generateBranchedData(10) + testData.addDeletedBranch() + testDataService.saveTestData(testData.root) + userAccount = testData.user + performProjectAuthGet("/translations?sort=id&branch=feature-branch").andPrettyPrint.andIsOk.andAssertThatJson { + // 10 keys from feature-branch (translation from soft-deleted feature-branch is ignored) + node("_embedded.keys").isArray.hasSize(10) + } + } + @Test @ProjectJWTAuthTestMethod fun `returns correct comment counts`() { diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/SingleStepImportControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/SingleStepImportControllerTest.kt index 50650f6d8a..57829c7f79 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/SingleStepImportControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/SingleStepImportControllerTest.kt @@ -121,6 +121,7 @@ class SingleStepImportControllerTest : ProjectAuthControllerTest("/v2/projects/" listOf(Pair(jsonFileName, simpleJson)), params = mapOf("createNewKeys" to false), ) + executeInNewTransaction { keyService.find(testData.project.id, "test", null).assert.isNull() } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerBranchingTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerBranchingTest.kt new file mode 100644 index 0000000000..4602693e4e --- /dev/null +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerBranchingTest.kt @@ -0,0 +1,76 @@ +package io.tolgee.api.v2.controllers.v2ImportController + +import io.tolgee.ProjectAuthControllerTest +import io.tolgee.development.testDataBuilder.data.dataImport.ImportBranchTestData +import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.fixtures.andIsNotFound +import io.tolgee.fixtures.andIsOk +import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod +import io.tolgee.util.performImport +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.io.Resource +import org.springframework.transaction.annotation.Transactional + +@Transactional +class V2ImportControllerBranchingTest : ProjectAuthControllerTest("/v2/projects/") { + @Value("classpath:import/simple.json") + lateinit var simpleJson: Resource + + lateinit var testData: ImportBranchTestData + + @BeforeEach + fun setup() { + testData = ImportBranchTestData() + testDataService.saveTestData(testData.root) + projectSupplier = { testData.project } + userAccount = testData.user + } + + @Test + @ProjectJWTAuthTestMethod + fun `add files returns result only for selected branch`() { + performImport( + mvc = mvc, + projectId = testData.project.id, + files = listOf("simple.json" to simpleJson), + params = mapOf("branch" to testData.featureBranch.name), + ).andIsOk.andAssertThatJson { + node("result._embedded.languages").isArray.hasSize(1) + } + + performProjectAuthGet("import/result?branch=${testData.featureBranch.name}") + .andIsOk + .andAssertThatJson { node("_embedded.languages").isArray.hasSize(1) } + + performProjectAuthGet("import/result") + .andIsNotFound + } + + @Test + @ProjectJWTAuthTestMethod + fun `cancel removes only branch import`() { + performImport( + mvc = mvc, + projectId = testData.project.id, + files = listOf("simple.json" to simpleJson), + ) + + performImport( + mvc = mvc, + projectId = testData.project.id, + files = listOf("simple.json" to simpleJson), + params = mapOf("branch" to testData.featureBranch.name), + ) + + performProjectAuthDelete("import?branch=${testData.featureBranch.name}") + .andIsOk + + performProjectAuthGet("import/result?branch=${testData.featureBranch.name}") + .andIsNotFound + + performProjectAuthGet("import/result") + .andIsOk + } +} diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerCreationTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerCreationTest.kt index b438f4027f..e5d63f0d64 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerCreationTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerCreationTest.kt @@ -63,6 +63,7 @@ class KeyControllerCreationTest : ProjectAuthControllerTest("/v2/projects/") { .andAssertThatJson { node("id").isValidId node("name").isEqualTo("super_key") + node("branch").isEqualTo("main") } } @@ -86,6 +87,18 @@ class KeyControllerCreationTest : ProjectAuthControllerTest("/v2/projects/") { } } + @ProjectJWTAuthTestMethod + @Test + fun `creates key with default branch (branch parameter not provided)`() { + performProjectAuthPost("keys", CreateKeyDto(name = "super_key_to_main_branch", branch = "dev")) + .andIsCreated + .andAssertThatJson { + node("id").isValidId + node("name").isEqualTo("super_key_to_main_branch") + node("branch").isEqualTo("dev") + } + } + @ProjectJWTAuthTestMethod @Test fun `validates description`() { diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt index 8c3669f18c..f189b0f3ce 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerKeySearchTest.kt @@ -149,4 +149,13 @@ class KeyControllerKeySearchTest : time.assert.isLessThan(4000) } } + + @Test + @ProjectJWTAuthTestMethod + fun `it search in default branch only`() { + saveAndPrepare() + performProjectAuthGet("keys/search?search=this-is-branched-key&languageTag=de").andAssertThatJson { + node("_embedded").isAbsent() + } + } } diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerTest.kt index 94cd71c8e0..8f0b6af32e 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerTest.kt @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest +import java.math.BigDecimal @SpringBootTest @AutoConfigureMockMvc @@ -43,6 +44,7 @@ class KeyControllerTest : ProjectAuthControllerTest("/v2/projects/") { @Test fun `returns all keys`() { testData.addNKeys(120) + testData.addNBranchedKeys(10) saveTestDataAndPrepare() performProjectAuthGet("keys") .andIsOk @@ -54,6 +56,7 @@ class KeyControllerTest : ProjectAuthControllerTest("/v2/projects/") { node("[2].namespace").isEqualTo("null") node("[1].description").isEqualTo("description") } + node("page.totalElements").isNumber.isEqualTo(BigDecimal(123)) } performProjectAuthGet("keys?page=1") .andIsOk @@ -67,6 +70,36 @@ class KeyControllerTest : ProjectAuthControllerTest("/v2/projects/") { } } + @ProjectJWTAuthTestMethod + @Test + fun `returns all keys from branch`() { + testData.addNKeys(5) + testData.addNBranchedKeys(110) + saveTestDataAndPrepare() + performProjectAuthGet("keys?branch=feature") + .andIsOk + .andAssertThatJson { + node("_embedded.keys") { + isArray.hasSize(20) + node("[0].id").isValidId + node("[1].name").isEqualTo("branch_key_2") + node("[1].description").isEqualTo("description of branched key") + node("[2].namespace").isEqualTo("null") + } + node("page.totalElements").isNumber.isEqualTo(BigDecimal(110)) + } + performProjectAuthGet("keys?page=1&branch=feature") + .andIsOk + .andAssertThatJson { + node("_embedded.keys") { + isArray.hasSize(20) + node("[0].id").isValidId + node("[1].name").isEqualTo("branch_key_22") + node("[2].namespace").isEqualTo("null") + } + } + } + @ProjectJWTAuthTestMethod @Test fun `returns single key`() { diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/ProjectsControllerCreateTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/ProjectsControllerCreateTest.kt index 2531bf83a7..069e1b5492 100644 --- a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/ProjectsControllerCreateTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/ProjectsControllerCreateTest.kt @@ -14,9 +14,11 @@ import io.tolgee.fixtures.andIsOk import io.tolgee.fixtures.andPrettyPrint import io.tolgee.fixtures.satisfies import io.tolgee.model.Project +import io.tolgee.model.branching.Branch import io.tolgee.model.enums.OrganizationRoleType import io.tolgee.model.enums.ProjectPermissionType import io.tolgee.testing.AuthorizedControllerTest +import io.tolgee.testing.assert import io.tolgee.testing.assertions.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -84,6 +86,29 @@ class ProjectsControllerCreateTest : AuthorizedControllerTest() { } } + @Test + fun `create project creates a default branch`() { + val userAccount = dbPopulator.createUserIfNotExists("testuser") + val organization = dbPopulator.createOrganization("Test Organization", userAccount) + loginAsUser("testuser") + val request = + CreateProjectRequest("branched", listOf(languageDTO), organizationId = organization.id, icuPlaceholders = true) + performAuthPost("/v2/projects", request).andIsOk.andAssertThatJson { + node("icuPlaceholders").isBoolean.isTrue + node("id").asNumber().satisfies { + projectService.get(it.toLong()).let { it -> + it.branches.size.assert + .isEqualTo(1) + it.branches.first().let { branch -> + branch.isDefault.assert.isTrue + branch.isProtected.assert.isTrue + branch.name.assert.isEqualTo(Branch.Companion.DEFAULT_BRANCH_NAME) + } + } + } + } + } + @Test fun `throws when slug occupied`() { dbPopulator.createBase() diff --git a/backend/app/src/test/kotlin/io/tolgee/service/ExportServiceTest.kt b/backend/app/src/test/kotlin/io/tolgee/service/ExportServiceTest.kt index e2cda7ac23..0ec8dc5985 100644 --- a/backend/app/src/test/kotlin/io/tolgee/service/ExportServiceTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/service/ExportServiceTest.kt @@ -1,10 +1,12 @@ package io.tolgee.service import io.tolgee.AbstractSpringTest +import io.tolgee.development.testDataBuilder.data.KeysTestData import io.tolgee.development.testDataBuilder.data.NamespacesTestData import io.tolgee.development.testDataBuilder.data.TranslationsTestData import io.tolgee.dtos.request.export.ExportParams import io.tolgee.exceptions.BadRequestException +import io.tolgee.exceptions.NotFoundException import io.tolgee.model.enums.TranslationState import io.tolgee.service.export.ExportService import io.tolgee.service.export.dataProvider.ExportDataProvider @@ -228,4 +230,22 @@ class ExportServiceTest : AbstractSpringTest() { ExportParams(fileStructureTemplate = "{namespace}/{languageTag}.{extension}") exportService.export(testData.project.id, exportParams) } + + @Test + fun `export fails for non existing branch`() { + val testData = KeysTestData() + testDataService.saveTestData(testData.root) + + assertThatThrownBy { + exportService.export(testData.project.id, ExportParams(filterBranch = "unknown")) + }.isInstanceOf(NotFoundException::class.java) + } + + @Test + fun `export accepts existing branch`() { + val testData = KeysTestData() + testDataService.saveTestData(testData.root) + + exportService.export(testData.project.id, ExportParams(filterBranch = "dev")) + } } diff --git a/backend/app/src/test/kotlin/io/tolgee/service/LanguageServiceTest.kt b/backend/app/src/test/kotlin/io/tolgee/service/LanguageServiceTest.kt index 6d29c31efe..700bae3f2d 100644 --- a/backend/app/src/test/kotlin/io/tolgee/service/LanguageServiceTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/service/LanguageServiceTest.kt @@ -180,6 +180,6 @@ class LanguageServiceTest : AbstractSpringTest() { """, ).setParameter("type", ActivityType.HARD_DELETE_LANGUAGE) .resultList - result.assert.hasSize(3) + result.assert.hasSize(6) } } diff --git a/backend/app/src/test/kotlin/io/tolgee/service/TranslationServiceTest.kt b/backend/app/src/test/kotlin/io/tolgee/service/TranslationServiceTest.kt index 6657acc7cd..fa9741d5db 100644 --- a/backend/app/src/test/kotlin/io/tolgee/service/TranslationServiceTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/service/TranslationServiceTest.kt @@ -18,8 +18,9 @@ class TranslationServiceTest : AbstractSpringTest() { val id = dbPopulator.populate().project.id val data = translationService.getTranslations( - languageTags = HashSet(Arrays.asList("en", "de")), + languageTags = HashSet(listOf("en", "de")), namespace = null, + branch = null, projectId = id, structureDelimiter = '.', ) @@ -35,8 +36,9 @@ class TranslationServiceTest : AbstractSpringTest() { val viewData = translationService.getTranslations( - languageTags = HashSet(Arrays.asList("en", "de")), + languageTags = HashSet(listOf("en", "de")), namespace = null, + branch = null, projectId = project.id, structureDelimiter = '.', ) @@ -78,4 +80,56 @@ class TranslationServiceTest : AbstractSpringTest() { assertThat(translation.auto).isFalse assertThat(translation.mtProvider).isNull() } + + @Test + fun `returns translations for default branch`() { + val testData = TranslationsTestData() + testData.generateBranchedData(10) + testDataService.saveTestData(testData.root) + val translations = + translationService.getTranslations( + languageTags = HashSet(listOf("en", "de")), + namespace = null, + branch = null, + projectId = testData.project.id, + structureDelimiter = '.', + ) + assertThat(translations).hasSize(2) + assertThat(translations["en"] as LinkedHashMap<*, *>).hasSize(1) + } + + @Test + @Transactional + fun `returns translations for feature branch`() { + val testData = TranslationsTestData() + testData.generateBranchedData(10) + testDataService.saveTestData(testData.root) + val translations = + translationService.getTranslations( + languageTags = HashSet(listOf("en", "de")), + namespace = null, + branch = "feature-branch", + projectId = testData.project.id, + structureDelimiter = '.', + ) + assertThat(translations).hasSize(2) + assertThat(translations["en"] as LinkedHashMap<*, *>).hasSize(10) + } + + @Test + @Transactional + fun `returns empty translations for invalid branch name`() { + val testData = TranslationsTestData() + testData.generateBranchedData(5) + testDataService.saveTestData(testData.root) + val translations = + translationService.getTranslations( + languageTags = HashSet(listOf("en", "de")), + namespace = null, + branch = "invalid-branch", + projectId = testData.project.id, + structureDelimiter = '.', + ) + assertThat(translations).hasSize(0) + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt index 7309ec61a8..ef22a5bd04 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/InterceptedEventsManager.kt @@ -37,6 +37,7 @@ import kotlin.reflect.jvm.javaField @Scope(SCOPE_SINGLETON) class InterceptedEventsManager( private val applicationContext: ApplicationContext, + private val preCommitEventPublisher: PreCommitEventPublisher, ) { private val logger = LoggerFactory.getLogger(this::class.java) @@ -66,7 +67,8 @@ class InterceptedEventsManager( val provider = getChangesProvider(collectionOwner, ownerField.name) ?: return - val stored = (collection.storedSnapshot as? HashMap<*, *>)?.values?.toList() + val storedSnapshotCollection = (collection.storedSnapshot as? HashMap<*, *>)?.values + val stored = storedSnapshotCollection?.toList() val old = activityHolder.modifiedCollections.computeIfAbsent(collectionOwner to ownerField.name) { @@ -80,6 +82,10 @@ class InterceptedEventsManager( activityModifiedEntity.modifications = (newChanges).toMutableMap() activityModifiedEntity.setEntityDescription(collectionOwner) + preCommitEventPublisher.onCollectionUpdate( + collectionOwner, + storedSnapshotCollection, + ) } private fun KCallable<*>.callIfInitialized(instance: Any?): Any? { diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt index 6f08c45447..941ae1bc0a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt @@ -1,5 +1,6 @@ package io.tolgee.activity.iterceptor +import io.tolgee.events.OnEntityCollectionPreUpdate import io.tolgee.events.OnEntityPreDelete import io.tolgee.events.OnEntityPrePersist import io.tolgee.events.OnEntityPreUpdate @@ -25,4 +26,11 @@ class PreCommitEventPublisher( fun onDelete(entity: Any?) { applicationContext.publishEvent(OnEntityPreDelete(this, entity)) } + + fun onCollectionUpdate( + entity: Any?, + previousState: MutableCollection?, + ) { + applicationContext.publishEvent(OnEntityCollectionPreUpdate(this, entity, previousState)) + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt b/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt index 7df47d1ed4..7b299bcc71 100644 --- a/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt +++ b/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt @@ -312,6 +312,15 @@ enum class Message { ALREADY_IMPERSONATING_USER, OPERATION_NOT_PERMITTED_IN_READ_ONLY_MODE, FILE_PROCESSING_FAILED, + BRANCH_NOT_FOUND, + CANNOT_DELETE_DEFAULT_BRANCH, + BRANCH_ALREADY_EXISTS, + ORIGIN_BRANCH_NOT_FOUND, + BRANCH_MERGE_NOT_FOUND, + BRANCH_MERGE_CHANGE_NOT_FOUND, + BRANCH_MERGE_REVISION_NOT_VALID, + BRANCH_MERGE_CONFLICTS_NOT_RESOLVED, + BRANCH_MERGE_ALREADY_MERGED, ; val code: String diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt index 7a5af214ac..efaf00f979 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt @@ -17,6 +17,7 @@ import io.tolgee.development.testDataBuilder.builders.UserAccountBuilder import io.tolgee.development.testDataBuilder.builders.UserPreferencesBuilder import io.tolgee.development.testDataBuilder.builders.slack.SlackUserConnectionBuilder import io.tolgee.model.Project +import io.tolgee.repository.KeyCodeReferenceRepository import io.tolgee.service.TenantService import io.tolgee.service.automations.AutomationService import io.tolgee.service.bigMeta.BigMetaService @@ -92,6 +93,7 @@ class TestDataService( private val contentDeliveryConfigService: ContentDeliveryConfigService, private val languageStatsListener: LanguageStatsListener, private val invitationService: InvitationService, + private val keyCodeReferenceRepository: KeyCodeReferenceRepository, ) : Logging { @Transactional fun saveTestData(ft: TestDataBuilder.() -> Unit): TestDataBuilder { @@ -305,7 +307,10 @@ class TestDataService( savePermissions(builder) saveMtServiceConfigs(builder) saveAllNamespaces(builder) + saveBranches(builder) + saveBranchMerges(builder) saveKeyData(builder) + saveBranchMergeChanges(builder) saveTranslationData(builder) saveImportData(builder) saveAutoTranslationConfigs(builder) @@ -486,6 +491,7 @@ class TestDataService( private fun saveAllKeyDependants(keyBuilders: List) { val metas = keyBuilders.map { it.data.meta?.self }.filterNotNull() tagService.saveAll(metas.flatMap { it.tags }) + keyCodeReferenceRepository.saveAll(metas.flatMap { it.codeReferences }) keyMetaService.saveAll(metas) } @@ -615,6 +621,24 @@ class TestDataService( } } + private fun saveBranches(builder: ProjectBuilder) { + builder.data.branches.filter { it.self.id == 0L }.forEach { + entityManager.persist(it.self) + } + } + + private fun saveBranchMerges(builder: ProjectBuilder) { + builder.data.branchMerges.filter { it.self.id == 0L }.forEach { + entityManager.persist(it.self) + } + } + + private fun saveBranchMergeChanges(builder: ProjectBuilder) { + builder.data.branchMergeChanges.filter { it.self.id == 0L }.forEach { + entityManager.persist(it.self) + } + } + private fun saveSuggestions(builder: ProjectBuilder) { builder.data.suggestions.forEach { entityManager.persist(it.self) diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchBuilder.kt new file mode 100644 index 0000000000..aa19722c08 --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchBuilder.kt @@ -0,0 +1,16 @@ +package io.tolgee.development.testDataBuilder.builders + +import io.tolgee.model.branching.Branch + +class BranchBuilder( + val projectBuilder: ProjectBuilder, +) : BaseEntityDataBuilder() { + override val self: Branch = + Branch() + .apply { + this.project = projectBuilder.self + }.run { + projectBuilder.self.branches.add(this) + this + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchMergeBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchMergeBuilder.kt new file mode 100644 index 0000000000..3eeac67f6e --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchMergeBuilder.kt @@ -0,0 +1,15 @@ +package io.tolgee.development.testDataBuilder.builders + +import io.tolgee.development.testDataBuilder.FT +import io.tolgee.model.branching.BranchMerge +import io.tolgee.model.branching.BranchMergeChange + +class BranchMergeBuilder( + val projectBuilder: ProjectBuilder, +) : BaseEntityDataBuilder() { + override val self: BranchMerge = BranchMerge() + + fun addChange(ft: FT): BranchMergeChangeBuilder { + return projectBuilder.addBranchMergeChange(this, ft) + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchMergeChangeBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchMergeChangeBuilder.kt new file mode 100644 index 0000000000..854c21e88f --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/BranchMergeChangeBuilder.kt @@ -0,0 +1,12 @@ +package io.tolgee.development.testDataBuilder.builders + +import io.tolgee.model.branching.BranchMergeChange + +class BranchMergeChangeBuilder( + val branchMergeBuilder: BranchMergeBuilder, +) : BaseEntityDataBuilder() { + override val self: BranchMergeChange = + BranchMergeChange().apply { + branchMergeBuilder.let { branchMerge = it.self } + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/ProjectBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/ProjectBuilder.kt index 8b04f18e80..5218255163 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/ProjectBuilder.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/ProjectBuilder.kt @@ -13,6 +13,9 @@ import io.tolgee.model.Prompt import io.tolgee.model.Screenshot import io.tolgee.model.automations.Automation import io.tolgee.model.batch.BatchJob +import io.tolgee.model.branching.Branch +import io.tolgee.model.branching.BranchMerge +import io.tolgee.model.branching.BranchMergeChange import io.tolgee.model.contentDelivery.ContentDeliveryConfig import io.tolgee.model.contentDelivery.ContentStorage import io.tolgee.model.dataImport.Import @@ -76,6 +79,9 @@ class ProjectBuilder( val aiPlaygroundResults = mutableListOf() val labels = mutableListOf() val suggestions = mutableListOf() + val branches = mutableListOf() + val branchMerges = mutableListOf() + val branchMergeChanges = mutableListOf() } var data = DATA() @@ -96,6 +102,18 @@ class ProjectBuilder( fun addLabel(ft: FT