Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhance billing and pricing #2795

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5c4913c
feat: Free trial & administration subscriptions view
JanCizmar Dec 17, 2024
8e48ef3
feat: Free trial assigment & end handling
JanCizmar Dec 21, 2024
8a8358d
feat: Server task runner (draft)
JanCizmar Dec 21, 2024
92778c1
chore: Fix trial plan assigment
JanCizmar Dec 28, 2024
61b6a79
feat: All types of free trial with notice
JanCizmar Dec 28, 2024
803880a
fix: Make scheduling working correctly
JanCizmar Dec 28, 2024
ada8b30
feat: Cloud billing, trials and admin > working
JanCizmar Dec 30, 2024
0408c54
feat: Send e-mail about expiration & improvements
JanCizmar Dec 30, 2024
b88e819
feat: Send e-mail about expiration > Tests and fixes
JanCizmar Dec 31, 2024
b85079a
fix: Improvements, fixes and e2e test
JanCizmar Jan 2, 2025
f6561ca
chore: E2E tests
JanCizmar Jan 3, 2025
df67ef8
feat: Trials frontend > untested
JanCizmar Jan 7, 2025
08ab92b
chore: Refactoring and test of the restore endpoint
JanCizmar Jan 8, 2025
a9e3646
fix: Free trials and tests
JanCizmar Jan 9, 2025
020f86b
fix: Build
JanCizmar Jan 9, 2025
f0923d5
fix: Limit trials
JanCizmar Jan 9, 2025
3dcde61
fix: Proper limit error & Test the limits
JanCizmar Jan 9, 2025
37cd354
fix: Enable canceling trial early and subscribing to a real plan
JanCizmar Jan 14, 2025
7d5911c
chore: Add todos
JanCizmar Jan 14, 2025
a09e1fb
fix: Remove the trial subscription on create event & test it
JanCizmar Jan 14, 2025
c32f9c6
fix: Trial announcement
JanCizmar Jan 15, 2025
879b160
fix: Trial badge
JanCizmar Jan 15, 2025
457f7df
fix: Test trial limit reaching message & fixes
JanCizmar Jan 16, 2025
3e021bb
fix: Add tests & final touch
JanCizmar Jan 17, 2025
1793260
fix: Demo feedback issues
JanCizmar Jan 17, 2025
299357e
chore: Fix formatting and types
JanCizmar Jan 17, 2025
b377c37
fix: remove it
JanCizmar Jan 21, 2025
90fee77
feat: Remove all trial renew logic
JanCizmar Jan 22, 2025
53465f9
chore: Format code
JanCizmar Jan 22, 2025
4922f91
chore: Fix trial end updates and notice sending
JanCizmar Jan 23, 2025
0ac5652
feat: Simplify trial messaging & fix e-mails
JanCizmar Jan 28, 2025
800d17b
feat: trial info popover
JanCizmar Jan 28, 2025
825dbf0
chore: Fix trial plans E2e tests
JanCizmar Jan 28, 2025
5441cfa
chore: Add run configuration for single click e2e
JanCizmar Jan 28, 2025
c7ffd04
chore: fix backend integration tests & issues
JanCizmar Jan 29, 2025
aa65052
chore: Update schema, remove tsc from frontend build
JanCizmar Jan 29, 2025
8116c61
chore: Formatting, and code-style
JanCizmar Jan 29, 2025
1e1743a
chore: Fix administration E2E tests
JanCizmar Jan 29, 2025
c5eb197
chore: Fix more E2E tests
JanCizmar Jan 29, 2025
9474210
chore: Fix more E2E tests
JanCizmar Jan 29, 2025
2b73dca
chore: Fix more E2E tests
JanCizmar Jan 29, 2025
38b0e52
chore: Fix more E2E tests
JanCizmar Jan 29, 2025
083d03c
chore: Fix E2E > prevent the deadlock
JanCizmar Jan 30, 2025
1476805
chore: E2E fix translation agency delete & add banner test
JanCizmar Jan 30, 2025
770ae7a
chore: E2E fix the last tests!
JanCizmar Jan 30, 2025
d0e4f0c
chore: E2E fix datepicker
JanCizmar Jan 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .run/Backend for E2E Billing.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Backend for E2E Billing" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<option name="ACTIVE_PROFILES" value="e2e,e2e-billing" />
<option name="FRAME_DEACTIVATION_UPDATE_POLICY" value="UpdateClassesAndResources" />
<module name="tolgee-platform.server-app.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="io.tolgee.Application" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
9 changes: 9 additions & 0 deletions .run/Billing E2E.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Billing E2E" type="CompoundRunConfigurationType">
<toRun name="Run Dokcer E2E dependencies" type="GradleRunConfiguration" />
<toRun name="E2E tests (Billing)" type="js.build_tools.npm" />
<toRun name="Frontend for E2E tests" type="js.build_tools.npm" />
<toRun name="Backend for E2E Billing" type="SpringBootApplicationConfigurationType" />
<method v="2" />
</configuration>
</component>
6 changes: 3 additions & 3 deletions .run/E2E tests.run.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="E2E tests" type="js.build_tools.npm">
<package-json value="$PROJECT_DIR$/e2e/package.json" />
<package-json value="$PROJECT_DIR$/public/e2e/package.json" />
<command value="run" />
<scripts>
<script value="cy:open" />
</scripts>
<node-interpreter value="project" />
<envs>
<env name="CYPRESS_HOST" value="http://localhost:8081" />
<env name="CYPRESS_HOST" value="http://localhost:8202" />
</envs>
<method v="2" />
</configuration>
</component>
</component>
6 changes: 3 additions & 3 deletions .run/Frontend for E2E tests.run.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Frontend for E2E tests" type="js.build_tools.npm">
<package-json value="$PROJECT_DIR$/webapp/package.json" />
<package-json value="$PROJECT_DIR$/public/webapp/package.json" />
<command value="run" />
<scripts>
<script value="start" />
</scripts>
<arguments value="-- --port 8081 --host" />
<arguments value="-- --port 8202 --host --no-open" />
<node-interpreter value="project" />
<envs>
<env name="VITE_APP_API_URL" value="http://localhost:8201" />
</envs>
<method v="2" />
</configuration>
</component>
</component>
24 changes: 24 additions & 0 deletions .run/Open E2E for Billing.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Open E2E for Billing" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/public" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-Dorg.gradle.daemon=false" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="openBillingE2eDev" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
9 changes: 9 additions & 0 deletions .run/Public E2E.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Public E2E" type="CompoundRunConfigurationType">
<toRun name="Run Dokcer E2E dependencies" type="GradleRunConfiguration" />
<toRun name="E2E tests" type="js.build_tools.npm" />
<toRun name="Frontend for E2E tests" type="js.build_tools.npm" />
<toRun name="Backend for E2E tests" type="SpringBootApplicationConfigurationType" />
<method v="2" />
</configuration>
</component>
24 changes: 24 additions & 0 deletions .run/Run Dokcer E2E dependencies.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Dokcer E2E dependencies" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/public" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-Dorg.gradle.daemon=false" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runDockerE2eDev" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.tolgee.api.v2.controllers.suggestion

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import io.sentry.Sentry
import io.tolgee.constants.MtServiceType
import io.tolgee.dtos.cacheable.ProjectDto
Expand Down Expand Up @@ -144,7 +144,8 @@ class MtResultStreamer(
}

private fun OutputStreamWriter.writeJson(data: Any) {
this.write(jacksonObjectMapper().writeValueAsString(data) + "\n")
val string = objectMapper.writeValueAsString(data)
this.write(string + "\n")
this.flush()
}

Expand All @@ -166,6 +167,10 @@ class MtResultStreamer(
mtService.getMtTranslator(projectHolder.project.id, false)
}

private val objectMapper by lazy {
applicationContext.getBean(ObjectMapper::class.java)
}

private val baseBlank by lazy {
mtTranslator
.getBaseTranslation(dto.keyId, dto.baseText)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.tolgee.component

import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class TestClockHeaderFilter(
private val currentDateProvider: CurrentDateProvider,
) : OncePerRequestFilter() {
companion object {
const val TOLGEE_TEST_CLOCK_HEADER_NAME = "X-Tolgee-Test-Clock"
}

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain,
) {
response.addHeader(TOLGEE_TEST_CLOCK_HEADER_NAME, currentDateProvider.date.time.toString())
filterChain.doFilter(request, response)
}

override fun shouldNotFilter(request: HttpServletRequest): Boolean {
return currentDateProvider.forcedDate == null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.tolgee.hateoas.organization
import io.swagger.v3.oas.annotations.media.Schema
import io.tolgee.constants.Feature
import io.tolgee.hateoas.quickStart.QuickStartModel
import io.tolgee.publicBilling.PublicCloudSubscriptionModel
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation

Expand All @@ -14,4 +15,6 @@ open class PrivateOrganizationModel(
val enabledFeatures: Array<Feature>,
@get:Schema(example = "Quick start data for current user")
val quickStart: QuickStartModel?,
@get:Schema(example = "Current active subscription info")
val activeCloudSubscription: PublicCloudSubscriptionModel?,
) : IOrganizationModel by organizationModel, RepresentationModel<PrivateOrganizationModel>()
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package io.tolgee.hateoas.organization
import io.tolgee.constants.Feature
import io.tolgee.dtos.queryResults.organization.PrivateOrganizationView
import io.tolgee.hateoas.quickStart.QuickStartModelAssembler
import io.tolgee.publicBilling.CloudSubscriptionModelProvider
import org.springframework.stereotype.Component

@Component
class PrivateOrganizationModelAssembler(
private val organizationModelAssembler: OrganizationModelAssembler,
private val quickStartModelAssembler: QuickStartModelAssembler,
private val cloudSubscriptionModelProvider: CloudSubscriptionModelProvider?,
) {
fun toModel(
view: PrivateOrganizationView,
Expand All @@ -18,6 +20,7 @@ class PrivateOrganizationModelAssembler(
organizationModel = organizationModelAssembler.toModel(view.organization),
enabledFeatures = features,
quickStart = view.quickStart?.let { quickStartModelAssembler.toModel(it) },
activeCloudSubscription = cloudSubscriptionModelProvider?.provide(view.organization.id),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.tolgee.publicBilling

interface CloudSubscriptionModelProvider {
fun provide(organizationId: Long): PublicCloudSubscriptionModel?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.tolgee.publicBilling

import io.tolgee.api.SubscriptionStatus
import io.tolgee.constants.BillingPeriod

interface PublicCloudSubscriptionModel {
val currentBillingPeriod: BillingPeriod?
val cancelAtPeriodEnd: Boolean
val trialEnd: Long?
val status: SubscriptionStatus
}
14 changes: 5 additions & 9 deletions backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.tolgee.constants.Message
import io.tolgee.dtos.request.validators.ValidationErrorType
import io.tolgee.dtos.request.validators.exceptions.ValidationException
import io.tolgee.exceptions.BadRequestException
import io.tolgee.exceptions.ErrorException
import io.tolgee.exceptions.ErrorResponseBody
import io.tolgee.exceptions.ErrorResponseTyped
import io.tolgee.exceptions.NotFoundException
import io.tolgee.exceptions.*
import io.tolgee.security.ratelimit.RateLimitResponseBody
import io.tolgee.security.ratelimit.RateLimitedException
import io.tolgee.util.Logging
import io.tolgee.util.logger
import jakarta.persistence.EntityNotFoundException
import jakarta.servlet.http.HttpServletRequest
import org.apache.catalina.connector.ClientAbortException
import org.apache.commons.lang3.exception.ExceptionUtils
import org.hibernate.QueryException
import org.slf4j.LoggerFactory
import org.springframework.dao.InvalidDataAccessApiUsageException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
Expand All @@ -42,9 +39,7 @@ import java.util.*
import java.util.function.Consumer

@RestControllerAdvice
class ExceptionHandlers {
private val logger = LoggerFactory.getLogger(this::class.java)

class ExceptionHandlers : Logging {
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationExceptions(
ex: MethodArgumentNotValidException,
Expand Down Expand Up @@ -164,6 +159,7 @@ class ExceptionHandlers {
)
@ExceptionHandler(ErrorException::class)
fun handleServerError(ex: ErrorException): ResponseEntity<ErrorResponseBody> {
logger.debug("Exception with response status {} caught", ex.httpStatus, ex)
return ResponseEntity(ex.errorResponseBody, ex.httpStatus)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.tolgee.configuration

import io.tolgee.component.ExceptionHandlerFilter
import io.tolgee.component.TestClockHeaderFilter
import io.tolgee.component.VersionFilter
import org.springframework.boot.autoconfigure.security.SecurityProperties
import org.springframework.boot.web.servlet.FilterRegistrationBean
Expand All @@ -16,6 +17,12 @@ class FiltersConfiguration {
return registration
}

@Bean("filterRegistrationTestClockHeader")
fun testClockHeaderFilter(testClockHeaderFilter: TestClockHeaderFilter): FilterRegistrationBean<*> {
val registration = FilterRegistrationBean(testClockHeaderFilter)
return registration
}

@Bean("filterRegistrationSecurityExceptionHandler")
fun exceptionHandlerFilter(exceptionHandlerFilter: ExceptionHandlerFilter): FilterRegistrationBean<*> {
val registration = FilterRegistrationBean(exceptionHandlerFilter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.tolgee.activity.ActivityHandlerInterceptor
import io.tolgee.component.TestClockHeaderFilter
import io.tolgee.component.VersionFilter
import io.tolgee.configuration.tolgee.TolgeeProperties
import jakarta.servlet.MultipartConfigElement
Expand All @@ -19,11 +20,7 @@ import org.springframework.http.CacheControl
import org.springframework.scheduling.annotation.EnableAsync
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.util.unit.DataSize
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.config.annotation.*
import java.security.SecureRandom
import java.util.concurrent.TimeUnit

Expand Down Expand Up @@ -55,7 +52,7 @@ class WebConfiguration(
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.exposedHeaders(VersionFilter.TOLGEE_VERSION_HEADER_NAME)
.exposedHeaders(VersionFilter.TOLGEE_VERSION_HEADER_NAME, TestClockHeaderFilter.TOLGEE_TEST_CLOCK_HEADER_NAME)
}

override fun addInterceptors(registry: InterceptorRegistry) {
Expand Down
2 changes: 1 addition & 1 deletion backend/app/src/main/resources/application-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ spring:
tolgee:
billing:
enabled: false
frontend-url: http://localhost:8081
frontend-url: http://localhost:8202
authentication:
enabled: true
needs-email-verification: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ class BatchJobTestUtil(
waitForNotThrowing {
// the project was unlocked before job2 acquired the job
verify(batchJobProjectLockingManager, times(1)).unlockJobForProject(
ArgumentMatchers.eq(job.project.id),
ArgumentMatchers.eq(job.project?.id),
ArgumentMatchers.eq(job.id),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum class SubscriptionStatus(
PAST_DUE(false),
UNPAID(false),
ERROR(false),
TRIALING(true),

// might be stored on the EE side, but not license server (billing) side
KEY_USED_BY_ANOTHER_INSTANCE(false),
Expand Down
Loading
Loading