Skip to content

Commit 4f03264

Browse files
committed
feat: add branch-based filtering for CDN content delivery and remove merge deletion functionality
1 parent 0eaa5a3 commit 4f03264

9 files changed

Lines changed: 200 additions & 56 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package io.tolgee.automation
2+
3+
import io.tolgee.ProjectAuthControllerTest
4+
import io.tolgee.component.contentDelivery.ContentDeliveryFileStorageProvider
5+
import io.tolgee.component.contentDelivery.cachePurging.ContentDeliveryCachePurging
6+
import io.tolgee.component.contentDelivery.cachePurging.ContentDeliveryCachePurgingProvider
7+
import io.tolgee.component.enabledFeaturesProvider.EnabledFeaturesProvider
8+
import io.tolgee.component.fileStorage.FileStorage
9+
import io.tolgee.constants.Feature
10+
import io.tolgee.development.testDataBuilder.data.ContentDeliveryConfigBranchingTestData
11+
import io.tolgee.fixtures.andIsOk
12+
import io.tolgee.fixtures.waitForNotThrowing
13+
import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod
14+
import io.tolgee.testing.assert
15+
import org.junit.jupiter.api.AfterEach
16+
import org.junit.jupiter.api.BeforeEach
17+
import org.junit.jupiter.api.Test
18+
import org.mockito.Mockito
19+
import org.mockito.kotlin.doReturn
20+
import org.mockito.kotlin.mock
21+
import org.mockito.kotlin.whenever
22+
import org.springframework.beans.factory.annotation.Autowired
23+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
24+
import org.springframework.boot.test.context.SpringBootTest
25+
import org.springframework.test.context.bean.override.mockito.MockitoBean
26+
import java.util.UUID
27+
28+
@SpringBootTest
29+
@AutoConfigureMockMvc
30+
class ContentDeliveryBranchAutopushTest : ProjectAuthControllerTest("/v2/projects/") {
31+
@MockitoBean
32+
@Autowired
33+
lateinit var contentDeliveryFileStorageProvider: ContentDeliveryFileStorageProvider
34+
35+
lateinit var fileStorageMock: FileStorage
36+
37+
@MockitoBean
38+
@Autowired
39+
lateinit var contentDeliveryCachePurgingProvider: ContentDeliveryCachePurgingProvider
40+
41+
lateinit var purgingMock: ContentDeliveryCachePurging
42+
43+
@MockitoBean
44+
@Autowired
45+
lateinit var enabledFeaturesProvider: EnabledFeaturesProvider
46+
47+
lateinit var testData: ContentDeliveryConfigBranchingTestData
48+
49+
@BeforeEach
50+
fun setup() {
51+
doReturn(arrayOf(Feature.BRANCHING)).whenever(enabledFeaturesProvider).get(org.mockito.kotlin.any())
52+
53+
currentDateProvider.forcedDate = currentDateProvider.date
54+
testData = ContentDeliveryConfigBranchingTestData()
55+
testDataService.saveTestData(testData.root)
56+
userAccount = testData.user
57+
this.projectSupplier = { testData.projectBuilder.self }
58+
59+
fileStorageMock = mock()
60+
doReturn(fileStorageMock).whenever(contentDeliveryFileStorageProvider).getContentStorageWithDefaultClient()
61+
purgingMock = mock()
62+
doReturn(listOf(purgingMock)).whenever(contentDeliveryCachePurgingProvider).purgings
63+
64+
// wait for initial invocations from test data saving, then clear
65+
Thread.sleep(1000)
66+
Mockito.clearInvocations(fileStorageMock)
67+
}
68+
69+
@AfterEach
70+
fun after() {
71+
currentDateProvider.forcedDate = null
72+
}
73+
74+
@Test
75+
@ProjectJWTAuthTestMethod
76+
fun `publishes CDN only for matching branch on main branch change`() {
77+
modifyTranslationOnBranch("key", "main")
78+
waitForStoreFileCalls(1)
79+
}
80+
81+
@Test
82+
@ProjectJWTAuthTestMethod
83+
fun `publishes CDN only for matching branch on feature branch change`() {
84+
modifyTranslationOnBranch("feature-key", "feature")
85+
waitForStoreFileCalls(1)
86+
}
87+
88+
@Test
89+
@ProjectJWTAuthTestMethod
90+
fun `does not publish feature CDN on main branch change`() {
91+
modifyTranslationOnBranch("key", "main")
92+
// only main CDN should publish (1 storeFile call)
93+
waitForStoreFileCalls(1)
94+
95+
Mockito.clearInvocations(fileStorageMock)
96+
97+
modifyTranslationOnBranch("feature-key", "feature")
98+
// only feature CDN should publish (1 storeFile call)
99+
waitForStoreFileCalls(1)
100+
}
101+
102+
private fun modifyTranslationOnBranch(
103+
keyName: String,
104+
branchName: String,
105+
) {
106+
performProjectAuthPost(
107+
"/translations",
108+
mapOf(
109+
"key" to keyName,
110+
"translations" to mapOf("en" to UUID.randomUUID().toString()),
111+
"branch" to branchName,
112+
),
113+
).andIsOk
114+
}
115+
116+
private fun waitForStoreFileCalls(expectedCount: Int) {
117+
waitForNotThrowing(timeout = 3000, pollTime = 200) {
118+
storeFileInvocations.assert.hasSize(expectedCount)
119+
}
120+
}
121+
122+
private val storeFileInvocations
123+
get() =
124+
Mockito
125+
.mockingDetails(fileStorageMock)
126+
.invocations
127+
.filter { it.method.name == "storeFile" }
128+
}

backend/data/src/main/kotlin/io/tolgee/activity/ActivityService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ class ActivityService(
184184
return provider.get()
185185
}
186186

187+
fun hasModifiedEntitiesOnBranch(
188+
revisionId: Long,
189+
branchId: Long,
190+
): Boolean {
191+
return activityModifiedEntityRepository.hasModifiedEntitiesOnBranch(revisionId, branchId)
192+
}
193+
187194
fun findActivityRevisionInfo(id: Long): ActivityRevisionInfo? {
188195
return activityRevisionRepository.findInfo(id)
189196
}

backend/data/src/main/kotlin/io/tolgee/component/automations/processors/ContentDeliveryPublishProcessor.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
package io.tolgee.component.automations.processors
22

3+
import io.tolgee.activity.ActivityService
34
import io.tolgee.batch.RequeueWithDelayException
45
import io.tolgee.component.automations.AutomationProcessor
56
import io.tolgee.component.contentDelivery.ContentDeliveryUploader
67
import io.tolgee.constants.Message
78
import io.tolgee.exceptions.FileStoreException
89
import io.tolgee.model.automations.AutomationAction
9-
import io.tolgee.security.ProjectHolder
10-
import io.tolgee.service.security.SecurityService
1110
import org.springframework.stereotype.Component
1211

1312
@Component
1413
class ContentDeliveryPublishProcessor(
1514
val contentDeliveryUploader: ContentDeliveryUploader,
16-
val securityService: SecurityService,
17-
val projectHolder: ProjectHolder,
15+
val activityService: ActivityService,
1816
) : AutomationProcessor {
1917
override fun process(
2018
action: AutomationAction,
@@ -24,6 +22,11 @@ class ContentDeliveryPublishProcessor(
2422
val config =
2523
action.contentDeliveryConfig
2624
?: throw IllegalStateException("Wrong params passed to content delivery publish processor")
25+
26+
if (!isOnConfigBranch(activityRevisionId, config.branch?.id)) {
27+
return
28+
}
29+
2730
contentDeliveryUploader.upload(contentDeliveryConfigId = config.id)
2831
} catch (e: Throwable) {
2932
when (e) {
@@ -42,4 +45,14 @@ class ContentDeliveryPublishProcessor(
4245
}
4346
}
4447
}
48+
49+
private fun isOnConfigBranch(
50+
activityRevisionId: Long?,
51+
configBranchId: Long?,
52+
): Boolean {
53+
if (activityRevisionId == null || configBranchId == null) {
54+
return true
55+
}
56+
return activityService.hasModifiedEntitiesOnBranch(activityRevisionId, configBranchId)
57+
}
4558
}

backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/ContentDeliveryConfigBranchingTestData.kt

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.tolgee.development.testDataBuilder.data
22

33
import io.tolgee.model.automations.AutomationAction
4+
import io.tolgee.model.automations.AutomationActionType
45
import io.tolgee.model.automations.AutomationTrigger
56
import io.tolgee.model.automations.AutomationTriggerType
67
import io.tolgee.model.branching.Branch
@@ -24,22 +25,44 @@ class ContentDeliveryConfigBranchingTestData : BaseTestData() {
2425
name = "Default server"
2526
}
2627

27-
val automation =
28+
val mainCdnAutomation =
2829
projectBuilder.addAutomation {
2930
this.triggers.add(
3031
AutomationTrigger(this)
3132
.also { it.type = AutomationTriggerType.TRANSLATION_DATA_MODIFICATION },
3233
)
3334
this.actions.add(
34-
AutomationAction(this).also { it.contentDeliveryConfig = defaultServerContentDeliveryConfig.self },
35+
AutomationAction(this).also {
36+
it.type = AutomationActionType.CONTENT_DELIVERY_PUBLISH
37+
it.contentDeliveryConfig = mainBranchCdnConfig.self
38+
},
3539
)
3640
}
3741

38-
val keyWithTranslation =
42+
val featureCdnAutomation =
43+
projectBuilder.addAutomation {
44+
this.triggers.add(
45+
AutomationTrigger(this)
46+
.also { it.type = AutomationTriggerType.TRANSLATION_DATA_MODIFICATION },
47+
)
48+
this.actions.add(
49+
AutomationAction(this).also {
50+
it.type = AutomationActionType.CONTENT_DELIVERY_PUBLISH
51+
it.contentDeliveryConfig = featureBranchCdnConfig.self
52+
},
53+
)
54+
}
55+
56+
val keyOnMainBranch =
3957
this.projectBuilder.addKey("key") {
4058
addTranslation("en", "Hello")
4159
}
4260

61+
val keyOnFeatureBranch =
62+
this.projectBuilder.addKey("feature-key") {
63+
addTranslation("en", "Feature Hello")
64+
}
65+
4366
init {
4467
projectBuilder.apply {
4568
self.useBranching = true
@@ -65,7 +88,11 @@ class ContentDeliveryConfigBranchingTestData : BaseTestData() {
6588
featureBranchCdnConfig.self.branch = featureBranch
6689
defaultServerContentDeliveryConfig.self.branch = mainBranch
6790

68-
keyWithTranslation.self.branch = mainBranch
91+
mainBranchCdnConfig.self.automationActions.addAll(mainCdnAutomation.self.actions)
92+
featureBranchCdnConfig.self.automationActions.addAll(featureCdnAutomation.self.actions)
93+
94+
keyOnMainBranch.self.branch = mainBranch
95+
keyOnFeatureBranch.self.branch = featureBranch
6996
}
7097
}
7198
}

backend/data/src/main/kotlin/io/tolgee/repository/activity/ActivityModifiedEntityRepository.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ interface ActivityModifiedEntityRepository : JpaRepository<ActivityModifiedEntit
3232
ignoredActivityTypes: List<ActivityType>,
3333
): Page<TranslationHistoryView>
3434

35+
@Query(
36+
"""
37+
select case when count(ame) > 0 then true else false end
38+
from ActivityModifiedEntity ame
39+
where ame.activityRevision.id = :revisionId and ame.branchId = :branchId
40+
""",
41+
)
42+
fun hasModifiedEntitiesOnBranch(
43+
revisionId: Long,
44+
branchId: Long,
45+
): Boolean
46+
3547
@Query(
3648
"""
3749
from ActivityModifiedEntity ame

e2e/cypress/support/dataCyType.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ declare namespace DataCy {
214214
"billing-usage-table-translations" |
215215
"billing_period_annual" |
216216
"branch-default-chip" |
217-
"branch-merge-detail-menu" |
218217
"branch-name-input" |
219218
"branch-protected-icon" |
220219
"branch-select-item" |

webapp/src/ee/branching/components/BranchesList.tsx

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { confirmation } from 'tg.hooks/confirmation';
1313
import { useHistory } from 'react-router-dom';
1414
import { LINKS } from 'tg.constants/links';
1515
import { BranchRenameModal } from './BranchRenameModal';
16-
import { BranchNameChipNode } from 'tg.component/branching/BranchNameChip';
1716
import { useProjectPermissions } from 'tg.hooks/useProjectPermissions';
1817
import { BranchProgressModal } from './BranchProgressModal';
1918
import { confirmProtected } from 'tg.ee.module/branching/components/utils/branchConfirmations';
@@ -121,23 +120,8 @@ export const BranchesList = () => {
121120
if (branch.merge && !branch.merge.mergedAt) {
122121
handleMergeDetail(branch);
123122
} else {
124-
confirmation({
125-
message: (
126-
<T
127-
keyName="branch_merges_create_title"
128-
params={{
129-
name: branch?.name,
130-
branch: <BranchNameChipNode />,
131-
targetName: branch?.originBranchName,
132-
}}
133-
/>
134-
),
135-
confirmButtonText: <T keyName="branch_merges_create_button" />,
136-
async onConfirm() {
137-
await mergeIntoSubmit({
138-
sourceBranchId: branch.id,
139-
});
140-
},
123+
mergeIntoSubmit({
124+
sourceBranchId: branch.id,
141125
});
142126
}
143127
};

webapp/src/ee/branching/merge/BranchMergeDetail.tsx

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ export const BranchMergeDetail: FC = () => {
9090
resolveMutation,
9191
resolveAllMutation,
9292
applyMutation,
93-
deleteMutation,
9493
refreshPreviewMutation,
9594
} = useMergeData(project.id, numericMergeId, selectedTab);
9695

@@ -247,17 +246,6 @@ export const BranchMergeDetail: FC = () => {
247246
await applyMerge();
248247
};
249248

250-
const handleCancel = async () => {
251-
await deleteMutation.mutateAsync({
252-
path: { projectId: project.id, mergeId: numericMergeId },
253-
});
254-
history.push(
255-
LINKS.PROJECT_BRANCHES.build({
256-
[PARAMS.PROJECT_ID]: project.id,
257-
})
258-
);
259-
};
260-
261249
const handleTabSelect = (tab: BranchMergeChangeType) => {
262250
userSelectedTab.current = true;
263251
setSelectedTab(tab);
@@ -340,7 +328,7 @@ export const BranchMergeDetail: FC = () => {
340328
) : (
341329
merge && (
342330
<StyledDetail>
343-
<MergeHeader merge={merge} onDelete={handleCancel} />
331+
<MergeHeader merge={merge} />
344332

345333
{isOutdated && (
346334
<Box mt={1}>

0 commit comments

Comments
 (0)