feat: distinct NewUser and Product trial sources (#28882)

*
[DEVEX-847/distinct-newuser-and-product-trial-sources](https://linear.app/codercom/issue/DEVEX-847/distinct-newuser-and-product-trial-sources)
* for premium trial signups with a new user account creation, passes the
`NewUser` trial source.
* for premium trial signups from the /deployment/premium account page,
passes the `Product` trial source
This commit is contained in:
Samuel Volin
2026-09-01 11:15:02 -06:00
committed by GitHub
parent 7e0f62c204
commit 117c0f7b11
7 changed files with 38 additions and 8 deletions
+1
View File
@@ -223,6 +223,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
CompanyName: createUser.TrialInfo.CompanyName,
Country: createUser.TrialInfo.Country,
Developers: createUser.TrialInfo.Developers,
Source: codersdk.LicensorTrialSourceNewUser,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
+6 -4
View File
@@ -84,12 +84,12 @@ func TestFirstUser(t *testing.T) {
t.Run("Trial", func(t *testing.T) {
t.Parallel()
trialGenerated := make(chan struct{})
trialGenerated := make(chan codersdk.LicensorTrialRequest, 1)
entitlementsRefreshed := make(chan struct{})
client := coderdtest.New(t, &coderdtest.Options{
TrialGenerator: func(context.Context, codersdk.LicensorTrialRequest) error {
close(trialGenerated)
TrialGenerator: func(_ context.Context, req codersdk.LicensorTrialRequest) error {
trialGenerated <- req
return nil
},
RefreshEntitlements: func(context.Context) error {
@@ -111,7 +111,9 @@ func TestFirstUser(t *testing.T) {
_, err := client.CreateFirstUser(ctx, req)
require.NoError(t, err)
_ = testutil.TryReceive(ctx, t, trialGenerated)
trialReq := testutil.TryReceive(ctx, t, trialGenerated)
require.Equal(t, codersdk.LicensorTrialSourceNewUser, trialReq.Source)
_ = testutil.TryReceive(ctx, t, entitlementsRefreshed)
})
}
+8
View File
@@ -108,6 +108,14 @@ type GetUsersResponse struct {
Count int `json:"count"`
}
// Trial request source origination reported to the licensor.
const (
// LicensorTrialSourceNewUser is the first user setup flow.
LicensorTrialSourceNewUser = "NewUser"
// LicensorTrialSourceProduct is a request from within the product in-app trial request
LicensorTrialSourceProduct = "Product"
)
// @typescript-ignore LicensorTrialRequest
type LicensorTrialRequest struct {
DeploymentID string `json:"deployment_id"`
+1
View File
@@ -173,6 +173,7 @@ func (api *API) postTrialLicense(rw http.ResponseWriter, r *http.Request) {
rawLicense, err := api.trialer.Request(ctx, codersdk.LicensorTrialRequest{
DeploymentID: api.AGPL.DeploymentID,
Email: req.Email,
Source: codersdk.LicensorTrialSourceProduct,
FirstName: req.FirstName,
LastName: req.LastName,
PhoneNumber: req.PhoneNumber,
+6 -3
View File
@@ -23,8 +23,9 @@ import (
const LicenseRequestURL = "https://v2-licensor.coder.com/trial"
const (
// licenseRequestSource tells the licensor where a trial request originated.
licenseRequestSource = "Product"
// defaultLicenseRequestSource tells the licensor where a trial request
// originated when the caller does not specify a source.
defaultLicenseRequestSource = codersdk.LicensorTrialSourceProduct
// licenseRequestTimeout bounds a single request to the licensor.
licenseRequestTimeout = 30 * time.Second
)
@@ -56,7 +57,9 @@ func New(db database.Store, url string, keys map[string]ed25519.PublicKey) *Tria
// Request returns the raw signed license without storing it.
func (t *Trialer) Request(ctx context.Context, body codersdk.LicensorTrialRequest) (string, error) {
body.Source = licenseRequestSource
if body.Source == "" {
body.Source = defaultLicenseRequestSource
}
data, err := json.Marshal(body)
if err != nil {
return "", xerrors.Errorf("marshal: %w", err)
+2 -1
View File
@@ -80,6 +80,7 @@ func TestTrialerRequest(t *testing.T) {
req := codersdk.LicensorTrialRequest{
DeploymentID: "test-deployment",
Email: "coder@coder.com",
Source: codersdk.LicensorTrialSourceNewUser,
FirstName: "Coder",
LastName: "McCoder",
PhoneNumber: "+1 555 0100",
@@ -135,7 +136,7 @@ func TestTrialerRequest(t *testing.T) {
require.Equal(t, "application/json", got.contentType)
require.Equal(t, map[string]any{
"deployment_id": "test-deployment",
"source": "Product",
"source": codersdk.LicensorTrialSourceNewUser,
"email": req.Email,
"first_name": req.FirstName,
"last_name": req.LastName,
+14
View File
@@ -5926,6 +5926,20 @@ export const LicenseManagedAgentLimitExceededWarningText =
export const LicenseTelemetryRequiredErrorText =
"License requires telemetry but telemetry is disabled";
// From codersdk/users.go
/**
* Trial request source origination reported to the licensor.
* LicensorTrialSourceNewUser is the first user setup flow.
*/
export const LicensorTrialSourceNewUser = "NewUser";
// From codersdk/users.go
/**
* Trial request source origination reported to the licensor.
* LicensorTrialSourceProduct is a request from within the product in-app trial request
*/
export const LicensorTrialSourceProduct = "Product";
// From codersdk/deployment.go
export interface LinkConfig {
readonly name: string;