From 051ed3458032a7ada35de805acc25b131b93aff2 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Fri, 20 Feb 2026 16:09:12 +1100 Subject: [PATCH] feat: convert `soft_limit` to `limit` (#22048) In relation to [`internal#1281`](https://github.com/coder/internal/issues/1281) Remove the `soft_limit` field from the `Feature` type and simplify license limit handling. This change: - Removes the `soft_limit` field from the API and SDK - Uses the soft limit value as the single `limit` value in the UI and API - Simplifies warning logic to only show warnings when the limit is exceeded - Updates tests to reflect the new behavior - Updates the UI to use the single limit value for display --- coderd/apidoc/docs.go | 4 - coderd/apidoc/swagger.json | 4 - codersdk/deployment.go | 4 - docs/reference/api/enterprise.md | 2 - docs/reference/api/schemas.md | 16 +- enterprise/coderd/coderd_test.go | 6 +- .../coderd/coderdenttest/coderdenttest.go | 8 +- enterprise/coderd/license/license.go | 187 ++----- enterprise/coderd/license/license_test.go | 500 +++++++++--------- enterprise/coderd/workspaces_test.go | 2 +- site/src/api/typesGenerated.ts | 6 - .../ManagedAgentsConsumption.stories.tsx | 64 +-- .../ManagedAgentsConsumption.tsx | 2 +- 13 files changed, 301 insertions(+), 504 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 29e7788106..db59eec279 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -15368,10 +15368,6 @@ const docTemplate = `{ "limit": { "type": "integer" }, - "soft_limit": { - "description": "SoftLimit is the soft limit of the feature, and is only used for showing\nincluded limits in the dashboard. No license validation or warnings are\ngenerated from this value.", - "type": "integer" - }, "usage_period": { "description": "UsagePeriod denotes that the usage is a counter that accumulates over\nthis period (and most likely resets with the issuance of the next\nlicense).\n\nThese dates are determined from the license that this entitlement comes\nfrom, see enterprise/coderd/license/license.go.\n\nOnly certain features set these fields:\n- FeatureManagedAgentLimit", "allOf": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 350116ecd9..14b4552027 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -13895,10 +13895,6 @@ "limit": { "type": "integer" }, - "soft_limit": { - "description": "SoftLimit is the soft limit of the feature, and is only used for showing\nincluded limits in the dashboard. No license validation or warnings are\ngenerated from this value.", - "type": "integer" - }, "usage_period": { "description": "UsagePeriod denotes that the usage is a counter that accumulates over\nthis period (and most likely resets with the issuance of the next\nlicense).\n\nThese dates are determined from the license that this entitlement comes\nfrom, see enterprise/coderd/license/license.go.\n\nOnly certain features set these fields:\n- FeatureManagedAgentLimit", "allOf": [ diff --git a/codersdk/deployment.go b/codersdk/deployment.go index c7c60ae1db..ef375e8647 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -372,10 +372,6 @@ type Feature struct { // Below is only for features that use usage periods. - // SoftLimit is the soft limit of the feature, and is only used for showing - // included limits in the dashboard. No license validation or warnings are - // generated from this value. - SoftLimit *int64 `json:"soft_limit,omitempty"` // UsagePeriod denotes that the usage is a counter that accumulates over // this period (and most likely resets with the issuance of the next // license). diff --git a/docs/reference/api/enterprise.md b/docs/reference/api/enterprise.md index 1f4d739641..57b674d8ff 100644 --- a/docs/reference/api/enterprise.md +++ b/docs/reference/api/enterprise.md @@ -329,7 +329,6 @@ curl -X GET http://coder-server:8080/api/v2/entitlements \ "enabled": true, "entitlement": "entitled", "limit": 0, - "soft_limit": 0, "usage_period": { "end": "2019-08-24T14:15:22Z", "issued_at": "2019-08-24T14:15:22Z", @@ -341,7 +340,6 @@ curl -X GET http://coder-server:8080/api/v2/entitlements \ "enabled": true, "entitlement": "entitled", "limit": 0, - "soft_limit": 0, "usage_period": { "end": "2019-08-24T14:15:22Z", "issued_at": "2019-08-24T14:15:22Z", diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index efd14c2a68..b05abbc2c1 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -3902,7 +3902,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "enabled": true, "entitlement": "entitled", "limit": 0, - "soft_limit": 0, "usage_period": { "end": "2019-08-24T14:15:22Z", "issued_at": "2019-08-24T14:15:22Z", @@ -3914,7 +3913,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "enabled": true, "entitlement": "entitled", "limit": 0, - "soft_limit": 0, "usage_period": { "end": "2019-08-24T14:15:22Z", "issued_at": "2019-08-24T14:15:22Z", @@ -4196,7 +4194,6 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith "enabled": true, "entitlement": "entitled", "limit": 0, - "soft_limit": 0, "usage_period": { "end": "2019-08-24T14:15:22Z", "issued_at": "2019-08-24T14:15:22Z", @@ -4207,13 +4204,12 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith ### Properties -| Name | Type | Required | Restrictions | Description | -|---------------|----------------------------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `actual` | integer | false | | | -| `enabled` | boolean | false | | | -| `entitlement` | [codersdk.Entitlement](#codersdkentitlement) | false | | | -| `limit` | integer | false | | | -| `soft_limit` | integer | false | | Soft limit is the soft limit of the feature, and is only used for showing included limits in the dashboard. No license validation or warnings are generated from this value. | +| Name | Type | Required | Restrictions | Description | +|---------------|----------------------------------------------|----------|--------------|-------------| +| `actual` | integer | false | | | +| `enabled` | boolean | false | | | +| `entitlement` | [codersdk.Entitlement](#codersdkentitlement) | false | | | +| `limit` | integer | false | | | |`usage_period`|[codersdk.UsagePeriod](#codersdkusageperiod)|false||Usage period denotes that the usage is a counter that accumulates over this period (and most likely resets with the issuance of the next license). These dates are determined from the license that this entitlement comes from, see enterprise/coderd/license/license.go. Only certain features set these fields: - FeatureManagedAgentLimit| diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index d31d947313..7662078c7e 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -678,7 +678,7 @@ func TestManagedAgentLimit(t *testing.T) { // expiry warnings. GraceAt: time.Now().Add(time.Hour * 24 * 60), ExpiresAt: time.Now().Add(time.Hour * 24 * 90), - }).ManagedAgentLimit(1, 1), + }).ManagedAgentLimit(1), }) // Get entitlements to check that the license is a-ok. @@ -689,11 +689,7 @@ func TestManagedAgentLimit(t *testing.T) { require.True(t, agentLimit.Enabled) require.NotNil(t, agentLimit.Limit) require.EqualValues(t, 1, *agentLimit.Limit) - require.NotNil(t, agentLimit.SoftLimit) - require.EqualValues(t, 1, *agentLimit.SoftLimit) require.Empty(t, sdkEntitlements.Errors) - // There should be a warning since we're really close to our agent limit. - require.Equal(t, sdkEntitlements.Warnings[0], "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.") // Create a fake provision response that claims there are agents in the // template and every built workspace. diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index 71a43484d5..e117414e3b 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -231,12 +231,8 @@ func (opts *LicenseOptions) AIGovernanceAddon(limit int64) *LicenseOptions { return opts.Feature(codersdk.FeatureAIGovernanceUserLimit, limit) } -func (opts *LicenseOptions) ManagedAgentLimit(soft int64, hard int64) *LicenseOptions { - // These don't use named or exported feature names, see - // enterprise/coderd/license/license.go. - opts = opts.Feature(codersdk.FeatureName("managed_agent_limit_soft"), soft) - opts = opts.Feature(codersdk.FeatureName("managed_agent_limit_hard"), hard) - return opts +func (opts *LicenseOptions) ManagedAgentLimit(limit int64) *LicenseOptions { + return opts.Feature(codersdk.FeatureManagedAgentLimit, limit) } func (opts *LicenseOptions) Feature(name codersdk.FeatureName, value int64) *LicenseOptions { diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index e0f7206c7e..141444a4b2 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -15,60 +15,9 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" - "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" ) -const ( - // These features are only included in the license and are not actually - // entitlements after the licenses are processed. These values will be - // merged into the codersdk.FeatureManagedAgentLimit feature. - // - // The reason we need two separate features is because the License v3 format - // uses map[string]int64 for features, so we're unable to use a single value - // with a struct like `{"soft": 100, "hard": 200}`. This is unfortunate and - // we should fix this with a new license format v4 in the future. - // - // These are intentionally not exported as they should not be used outside - // of this package (except tests). - featureManagedAgentLimitHard codersdk.FeatureName = "managed_agent_limit_hard" - featureManagedAgentLimitSoft codersdk.FeatureName = "managed_agent_limit_soft" -) - -var ( - // Mapping of license feature names to the SDK feature name. - // This is used to map from multiple usage period features into a single SDK - // feature. - featureGrouping = map[codersdk.FeatureName]struct { - // The parent feature. - sdkFeature codersdk.FeatureName - // Whether the value of the license feature is the soft limit or the hard - // limit. - isSoft bool - }{ - // Map featureManagedAgentLimitHard and featureManagedAgentLimitSoft to - // codersdk.FeatureManagedAgentLimit. - featureManagedAgentLimitHard: { - sdkFeature: codersdk.FeatureManagedAgentLimit, - isSoft: false, - }, - featureManagedAgentLimitSoft: { - sdkFeature: codersdk.FeatureManagedAgentLimit, - isSoft: true, - }, - } - - // Features that are forbidden to be set in a license. These are the SDK - // features in the usagedBasedFeatureGrouping map. - licenseForbiddenFeatures = func() map[codersdk.FeatureName]struct{} { - features := make(map[codersdk.FeatureName]struct{}) - for _, feature := range featureGrouping { - features[feature.sdkFeature] = struct{}{} - } - return features - }() -) - // Entitlements processes licenses to return whether features are enabled or not. // TODO(@deansheather): This function and the related LicensesEntitlements // function should be refactored into smaller functions that: @@ -280,17 +229,15 @@ func LicensesEntitlements( // licenses with the corresponding features actually set // trump this default entitlement, even if they are set to a // smaller value. - defaultManagedAgentsIsuedAt = time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC) - defaultManagedAgentsStart = defaultManagedAgentsIsuedAt - defaultManagedAgentsEnd = defaultManagedAgentsStart.AddDate(100, 0, 0) - defaultManagedAgentsSoftLimit int64 = 1000 - defaultManagedAgentsHardLimit int64 = 1000 + defaultManagedAgentsIsuedAt = time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC) + defaultManagedAgentsStart = defaultManagedAgentsIsuedAt + defaultManagedAgentsEnd = defaultManagedAgentsStart.AddDate(100, 0, 0) + defaultManagedAgentsLimit int64 = 1000 ) entitlements.AddFeature(codersdk.FeatureManagedAgentLimit, codersdk.Feature{ Enabled: true, Entitlement: entitlement, - SoftLimit: &defaultManagedAgentsSoftLimit, - Limit: &defaultManagedAgentsHardLimit, + Limit: &defaultManagedAgentsLimit, UsagePeriod: &codersdk.UsagePeriod{ IssuedAt: defaultManagedAgentsIsuedAt, Start: defaultManagedAgentsStart, @@ -310,15 +257,6 @@ func LicensesEntitlements( // Add all features from the feature set. for _, featureName := range claims.FeatureSet.Features() { - if _, ok := licenseForbiddenFeatures[featureName]; ok { - // Ignore any FeatureSet features that are forbidden to be set in a license. - continue - } - if _, ok := featureGrouping[featureName]; ok { - // These features need very special handling due to merging - // multiple feature values into a single SDK feature. - continue - } if featureName.UsesLimit() || featureName.UsesUsagePeriod() { // Limit and usage period features are handled below. // They don't provide default values as they are always enabled @@ -335,30 +273,24 @@ func LicensesEntitlements( }) } - // A map of SDK feature name to the uncommitted usage feature. - uncommittedUsageFeatures := map[codersdk.FeatureName]usageLimit{} - // Features al-la-carte for featureName, featureValue := range claims.Features { - if _, ok := licenseForbiddenFeatures[featureName]; ok { - entitlements.Errors = append(entitlements.Errors, - fmt.Sprintf("Feature %s is forbidden to be set in a license.", featureName)) - continue + // Old-style licenses encode the managed agent limit as + // separate soft/hard features. + // + // This could be removed in a future release, but can only be + // done once all old licenses containing this are no longer in use. + if featureName == "managed_agent_limit_soft" { + // Maps the soft limit to the canonical feature name + featureName = codersdk.FeatureManagedAgentLimit } - if featureValue < 0 { - // We currently don't use negative values for features. + if featureName == "managed_agent_limit_hard" { + // We can safely ignore the hard limit as it is no longer used. continue } - // Special handling for grouped (e.g. usage period) features. - if grouping, ok := featureGrouping[featureName]; ok { - ul := uncommittedUsageFeatures[grouping.sdkFeature] - if grouping.isSoft { - ul.Soft = &featureValue - } else { - ul.Hard = &featureValue - } - uncommittedUsageFeatures[grouping.sdkFeature] = ul + if featureValue < 0 { + // We currently don't use negative values for features. continue } @@ -372,6 +304,17 @@ func LicensesEntitlements( // Handling for limit features. switch { + case featureName.UsesUsagePeriod(): + entitlements.AddFeature(featureName, codersdk.Feature{ + Enabled: featureValue > 0, + Entitlement: entitlement, + Limit: &featureValue, + UsagePeriod: &codersdk.UsagePeriod{ + IssuedAt: claims.IssuedAt.Time, + Start: usagePeriodStart, + End: usagePeriodEnd, + }, + }) case featureName.UsesLimit(): if featureValue <= 0 { // 0 limit value or less doesn't make sense, so we skip it. @@ -402,46 +345,6 @@ func LicensesEntitlements( } } - // Apply uncommitted usage features to the entitlements. - for featureName, ul := range uncommittedUsageFeatures { - if ul.Soft == nil || ul.Hard == nil { - // Invalid license. - entitlements.Errors = append(entitlements.Errors, - fmt.Sprintf("Invalid license (%s): feature %s has missing soft or hard limit values", license.UUID.String(), featureName)) - continue - } - if *ul.Hard < *ul.Soft { - entitlements.Errors = append(entitlements.Errors, - fmt.Sprintf("Invalid license (%s): feature %s has a hard limit less than the soft limit", license.UUID.String(), featureName)) - continue - } - if *ul.Hard < 0 || *ul.Soft < 0 { - entitlements.Errors = append(entitlements.Errors, - fmt.Sprintf("Invalid license (%s): feature %s has a soft or hard limit less than 0", license.UUID.String(), featureName)) - continue - } - - feature := codersdk.Feature{ - Enabled: true, - Entitlement: entitlement, - SoftLimit: ul.Soft, - Limit: ul.Hard, - // `Actual` will be populated below when warnings are generated. - UsagePeriod: &codersdk.UsagePeriod{ - IssuedAt: claims.IssuedAt.Time, - Start: usagePeriodStart, - End: usagePeriodEnd, - }, - } - // If the hard limit is 0, the feature is disabled. - if *ul.Hard <= 0 { - feature.Enabled = false - feature.SoftLimit = ptr.Ref(int64(0)) - feature.Limit = ptr.Ref(int64(0)) - } - entitlements.AddFeature(featureName, feature) - } - addonFeatures := make(map[codersdk.FeatureName]codersdk.Feature) // Finally, add all features from the addons. We do this last so that @@ -557,32 +460,9 @@ func LicensesEntitlements( entitlements.AddFeature(codersdk.FeatureManagedAgentLimit, agentLimit) // Only issue warnings if the feature is enabled. - if agentLimit.Enabled { - var softLimit int64 - if agentLimit.SoftLimit != nil { - softLimit = *agentLimit.SoftLimit - } - var hardLimit int64 - if agentLimit.Limit != nil { - hardLimit = *agentLimit.Limit - } - - // Issue a warning early: - // 1. If the soft limit and hard limit are equal, at 75% of the hard - // limit. - // 2. If the limit is greater than the soft limit, at 75% of the - // difference between the hard limit and the soft limit. - softWarningThreshold := int64(float64(hardLimit) * 0.75) - if hardLimit > softLimit && softLimit > 0 { - softWarningThreshold = softLimit + int64(float64(hardLimit-softLimit)*0.75) - } - if managedAgentCount >= *agentLimit.Limit { - entitlements.Warnings = append(entitlements.Warnings, - codersdk.LicenseManagedAgentLimitExceededWarningText) - } else if managedAgentCount >= softWarningThreshold { - entitlements.Warnings = append(entitlements.Warnings, - "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.") - } + if agentLimit.Enabled && agentLimit.Limit != nil && managedAgentCount >= *agentLimit.Limit { + entitlements.Warnings = append(entitlements.Warnings, + codersdk.LicenseManagedAgentLimitExceededWarningText) } } } @@ -683,11 +563,6 @@ var ( type Features map[codersdk.FeatureName]int64 -type usageLimit struct { - Soft *int64 - Hard *int64 // 0 means "disabled" -} - // Claims is the full set of claims in a license. type Claims struct { jwt.RegisteredClaims diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index a1184972bd..db0f6898c9 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -76,8 +76,7 @@ func TestEntitlements(t *testing.T) { f := make(license.Features) for _, name := range codersdk.FeatureNames { if name == codersdk.FeatureManagedAgentLimit { - f[codersdk.FeatureName("managed_agent_limit_soft")] = 100 - f[codersdk.FeatureName("managed_agent_limit_hard")] = 200 + f[codersdk.FeatureManagedAgentLimit] = 100 continue } f[name] = 1 @@ -533,8 +532,7 @@ func TestEntitlements(t *testing.T) { t.Run("Premium", func(t *testing.T) { t.Parallel() const userLimit = 1 - const expectedAgentSoftLimit = 1000 - const expectedAgentHardLimit = 1000 + const expectedAgentLimit = 1000 db, _ := dbtestutil.NewDB(t) licenseOptions := coderdenttest.LicenseOptions{ @@ -566,8 +564,7 @@ func TestEntitlements(t *testing.T) { agentEntitlement := entitlements.Features[featureName] require.True(t, agentEntitlement.Enabled) require.Equal(t, codersdk.EntitlementEntitled, agentEntitlement.Entitlement) - require.EqualValues(t, expectedAgentSoftLimit, *agentEntitlement.SoftLimit) - require.EqualValues(t, expectedAgentHardLimit, *agentEntitlement.Limit) + require.EqualValues(t, expectedAgentLimit, *agentEntitlement.Limit) // This might be shocking, but there's a sound reason for this. // See license.go for more details. @@ -840,7 +837,7 @@ func TestEntitlements(t *testing.T) { }, }). UserLimit(100). - ManagedAgentLimit(100, 200) + ManagedAgentLimit(100) lic := database.License{ ID: 1, @@ -882,16 +879,15 @@ func TestEntitlements(t *testing.T) { managedAgentLimit, ok := entitlements.Features[codersdk.FeatureManagedAgentLimit] require.True(t, ok) - require.NotNil(t, managedAgentLimit.SoftLimit) - require.EqualValues(t, 100, *managedAgentLimit.SoftLimit) require.NotNil(t, managedAgentLimit.Limit) - require.EqualValues(t, 200, *managedAgentLimit.Limit) + // The soft limit value (100) is used as the single Limit. + require.EqualValues(t, 100, *managedAgentLimit.Limit) require.NotNil(t, managedAgentLimit.Actual) require.EqualValues(t, 175, *managedAgentLimit.Actual) - // Should've also populated a warning. + // Usage exceeds the limit, so an exceeded warning should be present. require.Len(t, entitlements.Warnings, 1) - require.Equal(t, "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.", entitlements.Warnings[0]) + require.Equal(t, codersdk.LicenseManagedAgentLimitExceededWarningText, entitlements.Warnings[0]) }) } @@ -1121,13 +1117,12 @@ func TestLicenseEntitlements(t *testing.T) { { Name: "ManagedAgentLimit", Licenses: []*coderdenttest.LicenseOptions{ - enterpriseLicense().UserLimit(100).ManagedAgentLimit(100, 200), + enterpriseLicense().UserLimit(100).ManagedAgentLimit(100), }, Arguments: license.FeatureArguments{ ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { - // 175 will generate a warning as it's over 75% of the - // difference between the soft and hard limit. - return 174, nil + // 74 is below the limit (soft=100), so no warning. + return 74, nil }, }, AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) { @@ -1136,9 +1131,9 @@ func TestLicenseEntitlements(t *testing.T) { feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) assert.True(t, feature.Enabled) - assert.Equal(t, int64(100), *feature.SoftLimit) - assert.Equal(t, int64(200), *feature.Limit) - assert.Equal(t, int64(174), *feature.Actual) + // Soft limit value is used as the single Limit. + assert.Equal(t, int64(100), *feature.Limit) + assert.Equal(t, int64(74), *feature.Actual) }, }, { @@ -1151,7 +1146,7 @@ func TestLicenseEntitlements(t *testing.T) { WithIssuedAt(time.Now().Add(-time.Hour * 2)), enterpriseLicense(). UserLimit(100). - ManagedAgentLimit(100, 100). + ManagedAgentLimit(100). WithIssuedAt(time.Now().Add(-time.Hour * 1)). GracePeriod(time.Now()), }, @@ -1168,7 +1163,6 @@ func TestLicenseEntitlements(t *testing.T) { feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] assert.Equal(t, codersdk.EntitlementGracePeriod, feature.Entitlement) assert.True(t, feature.Enabled) - assert.Equal(t, int64(100), *feature.SoftLimit) assert.Equal(t, int64(100), *feature.Limit) assert.Equal(t, int64(74), *feature.Actual) }, @@ -1183,7 +1177,7 @@ func TestLicenseEntitlements(t *testing.T) { WithIssuedAt(time.Now().Add(-time.Hour * 2)), enterpriseLicense(). UserLimit(100). - ManagedAgentLimit(100, 200). + ManagedAgentLimit(100). WithIssuedAt(time.Now().Add(-time.Hour * 1)). Expired(time.Now()), }, @@ -1196,84 +1190,33 @@ func TestLicenseEntitlements(t *testing.T) { feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] assert.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement) assert.False(t, feature.Enabled) - assert.Nil(t, feature.SoftLimit) assert.Nil(t, feature.Limit) assert.Nil(t, feature.Actual) }, }, { - Name: "ManagedAgentLimitWarning/ApproachingLimit/DifferentSoftAndHardLimit", + Name: "ManagedAgentLimitWarning/ExceededLimit", Licenses: []*coderdenttest.LicenseOptions{ enterpriseLicense(). UserLimit(100). - ManagedAgentLimit(100, 200), + ManagedAgentLimit(100), }, Arguments: license.FeatureArguments{ ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { - return 175, nil + return 150, nil }, }, AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) { assert.Len(t, entitlements.Warnings, 1) - assert.Equal(t, "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.", entitlements.Warnings[0]) + assert.Equal(t, codersdk.LicenseManagedAgentLimitExceededWarningText, entitlements.Warnings[0]) assertNoErrors(t, entitlements) feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) assert.True(t, feature.Enabled) - assert.Equal(t, int64(100), *feature.SoftLimit) - assert.Equal(t, int64(200), *feature.Limit) - assert.Equal(t, int64(175), *feature.Actual) - }, - }, - { - Name: "ManagedAgentLimitWarning/ApproachingLimit/EqualSoftAndHardLimit", - Licenses: []*coderdenttest.LicenseOptions{ - enterpriseLicense(). - UserLimit(100). - ManagedAgentLimit(100, 100), - }, - Arguments: license.FeatureArguments{ - ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { - return 75, nil - }, - }, - AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) { - assert.Len(t, entitlements.Warnings, 1) - assert.Equal(t, "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.", entitlements.Warnings[0]) - assertNoErrors(t, entitlements) - - feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] - assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) - assert.True(t, feature.Enabled) - assert.Equal(t, int64(100), *feature.SoftLimit) + // Soft limit (100) is used as the single Limit. assert.Equal(t, int64(100), *feature.Limit) - assert.Equal(t, int64(75), *feature.Actual) - }, - }, - { - Name: "ManagedAgentLimitWarning/BreachedLimit", - Licenses: []*coderdenttest.LicenseOptions{ - enterpriseLicense(). - UserLimit(100). - ManagedAgentLimit(100, 200), - }, - Arguments: license.FeatureArguments{ - ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { - return 200, nil - }, - }, - AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) { - assert.Len(t, entitlements.Warnings, 1) - assert.Equal(t, "You have built more workspaces with managed agents than your license allows.", entitlements.Warnings[0]) - assertNoErrors(t, entitlements) - - feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] - assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) - assert.True(t, feature.Enabled) - assert.Equal(t, int64(100), *feature.SoftLimit) - assert.Equal(t, int64(200), *feature.Limit) - assert.Equal(t, int64(200), *feature.Actual) + assert.Equal(t, int64(150), *feature.Actual) }, }, { @@ -1472,173 +1415,240 @@ func TestAIBridgeSoftWarning(t *testing.T) { func TestUsageLimitFeatures(t *testing.T) { t.Parallel() - cases := []struct { - sdkFeatureName codersdk.FeatureName - softLimitFeatureName codersdk.FeatureName - hardLimitFeatureName codersdk.FeatureName - }{ - { - sdkFeatureName: codersdk.FeatureManagedAgentLimit, - softLimitFeatureName: codersdk.FeatureName("managed_agent_limit_soft"), - hardLimitFeatureName: codersdk.FeatureName("managed_agent_limit_hard"), - }, - } + // Ensures that usage limit features are ranked by issued at, not by + // values. + t.Run("IssuedAtRanking", func(t *testing.T) { + t.Parallel() - for _, c := range cases { - t.Run(string(c.sdkFeatureName), func(t *testing.T) { - t.Parallel() + // Generate 2 real licenses both with managed agent limit + // features. lic2 should trump lic1 even though it has a lower + // limit, because it was issued later. + lic1 := database.License{ + ID: 1, + UploadedAt: time.Now(), + Exp: time.Now().Add(time.Hour), + UUID: uuid.New(), + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + IssuedAt: time.Now().Add(-time.Minute * 2), + NotBefore: time.Now().Add(-time.Minute * 2), + ExpiresAt: time.Now().Add(time.Hour * 2), + Features: license.Features{ + codersdk.FeatureManagedAgentLimit: 100, + }, + }), + } + lic2Iat := time.Now().Add(-time.Minute * 1) + lic2Nbf := lic2Iat.Add(-time.Minute) + lic2Exp := lic2Iat.Add(time.Hour) + lic2 := database.License{ + ID: 2, + UploadedAt: time.Now(), + Exp: lic2Exp, + UUID: uuid.New(), + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + IssuedAt: lic2Iat, + NotBefore: lic2Nbf, + ExpiresAt: lic2Exp, + Features: license.Features{ + codersdk.FeatureManagedAgentLimit: 50, + }, + }), + } - // Test for either a missing soft or hard limit feature value. - t.Run("MissingGroupedFeature", func(t *testing.T) { - t.Parallel() + const actualAgents = 10 + arguments := license.FeatureArguments{ + ActiveUserCount: 10, + ReplicaCount: 0, + ExternalAuthCount: 0, + ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { + return actualAgents, nil + }, + } - for _, feature := range []codersdk.FeatureName{ - c.softLimitFeatureName, - c.hardLimitFeatureName, - } { - t.Run(string(feature), func(t *testing.T) { - t.Parallel() + // Load the licenses in both orders to ensure the correct + // behavior is observed no matter the order. + for _, order := range [][]database.License{ + {lic1, lic2}, + {lic2, lic1}, + } { + entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), order, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments) + require.NoError(t, err) - lic := database.License{ - ID: 1, - UploadedAt: time.Now(), - Exp: time.Now().Add(time.Hour), - UUID: uuid.New(), - JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - Features: license.Features{ - feature: 100, - }, - }), - } + feature, ok := entitlements.Features[codersdk.FeatureManagedAgentLimit] + require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit) + require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) + require.NotNil(t, feature.Limit) + require.EqualValues(t, 50, *feature.Limit) + require.NotNil(t, feature.Actual) + require.EqualValues(t, actualAgents, *feature.Actual) + require.NotNil(t, feature.UsagePeriod) + require.WithinDuration(t, lic2Iat, feature.UsagePeriod.IssuedAt, 2*time.Second) + require.WithinDuration(t, lic2Nbf, feature.UsagePeriod.Start, 2*time.Second) + require.WithinDuration(t, lic2Exp, feature.UsagePeriod.End, 2*time.Second) + } + }) +} - arguments := license.FeatureArguments{ - ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { - return 0, nil - }, - } - entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments) - require.NoError(t, err) +// TestOldStyleManagedAgentLicenses ensures backward compatibility with +// older licenses that encode the managed agent limit using separate +// "managed_agent_limit_soft" and "managed_agent_limit_hard" feature keys +// instead of the canonical "managed_agent_limit" key. +func TestOldStyleManagedAgentLicenses(t *testing.T) { + t.Parallel() - feature, ok := entitlements.Features[c.sdkFeatureName] - require.True(t, ok, "feature %s not found", c.sdkFeatureName) - require.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement) + t.Run("SoftAndHard", func(t *testing.T) { + t.Parallel() - require.Len(t, entitlements.Errors, 1) - require.Equal(t, fmt.Sprintf("Invalid license (%v): feature %s has missing soft or hard limit values", lic.UUID, c.sdkFeatureName), entitlements.Errors[0]) - }) - } - }) + lic := database.License{ + ID: 1, + UploadedAt: time.Now(), + Exp: time.Now().Add(time.Hour), + UUID: uuid.New(), + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureName("managed_agent_limit_soft"): 100, + codersdk.FeatureName("managed_agent_limit_hard"): 200, + }, + }), + } - t.Run("HardBelowSoft", func(t *testing.T) { - t.Parallel() + const actualAgents = 42 + arguments := license.FeatureArguments{ + ManagedAgentCountFn: func(_ context.Context, _, _ time.Time) (int64, error) { + return actualAgents, nil + }, + } - lic := database.License{ - ID: 1, - UploadedAt: time.Now(), - Exp: time.Now().Add(time.Hour), - UUID: uuid.New(), - JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - Features: license.Features{ - c.softLimitFeatureName: 100, - c.hardLimitFeatureName: 50, - }, - }), - } + entitlements, err := license.LicensesEntitlements( + context.Background(), time.Now(), []database.License{lic}, + map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments, + ) + require.NoError(t, err) + require.Empty(t, entitlements.Errors) - arguments := license.FeatureArguments{ - ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { - return 0, nil - }, - } - entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments) - require.NoError(t, err) + feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] + require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) + require.True(t, feature.Enabled) + require.NotNil(t, feature.Limit) + // The soft limit should be used as the canonical limit. + require.EqualValues(t, 100, *feature.Limit) + require.NotNil(t, feature.Actual) + require.EqualValues(t, actualAgents, *feature.Actual) + require.NotNil(t, feature.UsagePeriod) + }) - feature, ok := entitlements.Features[c.sdkFeatureName] - require.True(t, ok, "feature %s not found", c.sdkFeatureName) - require.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement) + t.Run("OnlySoft", func(t *testing.T) { + t.Parallel() - require.Len(t, entitlements.Errors, 1) - require.Equal(t, fmt.Sprintf("Invalid license (%v): feature %s has a hard limit less than the soft limit", lic.UUID, c.sdkFeatureName), entitlements.Errors[0]) - }) + lic := database.License{ + ID: 1, + UploadedAt: time.Now(), + Exp: time.Now().Add(time.Hour), + UUID: uuid.New(), + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureName("managed_agent_limit_soft"): 75, + }, + }), + } - // Ensures that these features are ranked by issued at, not by - // values. - t.Run("IssuedAtRanking", func(t *testing.T) { - t.Parallel() + const actualAgents = 10 + arguments := license.FeatureArguments{ + ManagedAgentCountFn: func(_ context.Context, _, _ time.Time) (int64, error) { + return actualAgents, nil + }, + } - // Generate 2 real licenses both with managed agent limit - // features. lic2 should trump lic1 even though it has a lower - // limit, because it was issued later. - lic1 := database.License{ - ID: 1, - UploadedAt: time.Now(), - Exp: time.Now().Add(time.Hour), - UUID: uuid.New(), - JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - IssuedAt: time.Now().Add(-time.Minute * 2), - NotBefore: time.Now().Add(-time.Minute * 2), - ExpiresAt: time.Now().Add(time.Hour * 2), - Features: license.Features{ - c.softLimitFeatureName: 100, - c.hardLimitFeatureName: 200, - }, - }), - } - lic2Iat := time.Now().Add(-time.Minute * 1) - lic2Nbf := lic2Iat.Add(-time.Minute) - lic2Exp := lic2Iat.Add(time.Hour) - lic2 := database.License{ - ID: 2, - UploadedAt: time.Now(), - Exp: lic2Exp, - UUID: uuid.New(), - JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - IssuedAt: lic2Iat, - NotBefore: lic2Nbf, - ExpiresAt: lic2Exp, - Features: license.Features{ - c.softLimitFeatureName: 50, - c.hardLimitFeatureName: 100, - }, - }), - } + entitlements, err := license.LicensesEntitlements( + context.Background(), time.Now(), []database.License{lic}, + map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments, + ) + require.NoError(t, err) + require.Empty(t, entitlements.Errors) - const actualAgents = 10 - arguments := license.FeatureArguments{ - ActiveUserCount: 10, - ReplicaCount: 0, - ExternalAuthCount: 0, - ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) { - return actualAgents, nil - }, - } + feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] + require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) + require.True(t, feature.Enabled) + require.NotNil(t, feature.Limit) + require.EqualValues(t, 75, *feature.Limit) + }) - // Load the licenses in both orders to ensure the correct - // behavior is observed no matter the order. - for _, order := range [][]database.License{ - {lic1, lic2}, - {lic2, lic1}, - } { - entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), order, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments) - require.NoError(t, err) + // A license with only the hard limit key should silently ignore it, + // leaving the feature unset (not entitled). + t.Run("OnlyHard", func(t *testing.T) { + t.Parallel() - feature, ok := entitlements.Features[c.sdkFeatureName] - require.True(t, ok, "feature %s not found", c.sdkFeatureName) - require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) - require.NotNil(t, feature.Limit) - require.EqualValues(t, 100, *feature.Limit) - require.NotNil(t, feature.SoftLimit) - require.EqualValues(t, 50, *feature.SoftLimit) - require.NotNil(t, feature.Actual) - require.EqualValues(t, actualAgents, *feature.Actual) - require.NotNil(t, feature.UsagePeriod) - require.WithinDuration(t, lic2Iat, feature.UsagePeriod.IssuedAt, 2*time.Second) - require.WithinDuration(t, lic2Nbf, feature.UsagePeriod.Start, 2*time.Second) - require.WithinDuration(t, lic2Exp, feature.UsagePeriod.End, 2*time.Second) - } - }) - }) - } + lic := database.License{ + ID: 1, + UploadedAt: time.Now(), + Exp: time.Now().Add(time.Hour), + UUID: uuid.New(), + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureName("managed_agent_limit_hard"): 200, + }, + }), + } + + arguments := license.FeatureArguments{ + ManagedAgentCountFn: func(_ context.Context, _, _ time.Time) (int64, error) { + return 0, nil + }, + } + + entitlements, err := license.LicensesEntitlements( + context.Background(), time.Now(), []database.License{lic}, + map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments, + ) + require.NoError(t, err) + require.Empty(t, entitlements.Errors) + + feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] + require.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement) + }) + + // Old-style license with both soft and hard set to zero should + // explicitly disable the feature (and override any Premium default). + t.Run("ExplicitZero", func(t *testing.T) { + t.Parallel() + + lic := database.License{ + ID: 1, + UploadedAt: time.Now(), + Exp: time.Now().Add(time.Hour), + UUID: uuid.New(), + JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ + FeatureSet: codersdk.FeatureSetPremium, + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + codersdk.FeatureName("managed_agent_limit_soft"): 0, + codersdk.FeatureName("managed_agent_limit_hard"): 0, + }, + }), + } + + const actualAgents = 5 + arguments := license.FeatureArguments{ + ActiveUserCount: 10, + ManagedAgentCountFn: func(_ context.Context, _, _ time.Time) (int64, error) { + return actualAgents, nil + }, + } + + entitlements, err := license.LicensesEntitlements( + context.Background(), time.Now(), []database.License{lic}, + map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments, + ) + require.NoError(t, err) + + feature := entitlements.Features[codersdk.FeatureManagedAgentLimit] + require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) + require.False(t, feature.Enabled) + require.NotNil(t, feature.Limit) + require.EqualValues(t, 0, *feature.Limit) + require.NotNil(t, feature.Actual) + require.EqualValues(t, actualAgents, *feature.Actual) + }) } func TestManagedAgentLimitDefault(t *testing.T) { @@ -1676,20 +1686,16 @@ func TestManagedAgentLimitDefault(t *testing.T) { require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit) require.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement) require.Nil(t, feature.Limit) - require.Nil(t, feature.SoftLimit) require.Nil(t, feature.Actual) require.Nil(t, feature.UsagePeriod) }) - // "Premium" licenses should receive a default managed agent limit of: - // soft = 1000 - // hard = 1000 + // "Premium" licenses should receive a default managed agent limit of 1000. t.Run("Premium", func(t *testing.T) { t.Parallel() const userLimit = 33 - const softLimit = 1000 - const hardLimit = 1000 + const defaultLimit = 1000 lic := database.License{ ID: 1, UploadedAt: time.Now(), @@ -1720,9 +1726,7 @@ func TestManagedAgentLimitDefault(t *testing.T) { require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit) require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) require.NotNil(t, feature.Limit) - require.EqualValues(t, hardLimit, *feature.Limit) - require.NotNil(t, feature.SoftLimit) - require.EqualValues(t, softLimit, *feature.SoftLimit) + require.EqualValues(t, defaultLimit, *feature.Limit) require.NotNil(t, feature.Actual) require.EqualValues(t, actualAgents, *feature.Actual) require.NotNil(t, feature.UsagePeriod) @@ -1731,8 +1735,8 @@ func TestManagedAgentLimitDefault(t *testing.T) { require.NotZero(t, feature.UsagePeriod.End) }) - // "Premium" licenses with an explicit managed agent limit should not - // receive a default managed agent limit. + // "Premium" licenses with an explicit managed agent limit should use + // that value instead of the default. t.Run("PremiumExplicitValues", func(t *testing.T) { t.Parallel() @@ -1744,9 +1748,8 @@ func TestManagedAgentLimitDefault(t *testing.T) { JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ FeatureSet: codersdk.FeatureSetPremium, Features: license.Features{ - codersdk.FeatureUserLimit: 100, - codersdk.FeatureName("managed_agent_limit_soft"): 100, - codersdk.FeatureName("managed_agent_limit_hard"): 200, + codersdk.FeatureUserLimit: 100, + codersdk.FeatureManagedAgentLimit: 100, }, }), } @@ -1768,9 +1771,7 @@ func TestManagedAgentLimitDefault(t *testing.T) { require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit) require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement) require.NotNil(t, feature.Limit) - require.EqualValues(t, 200, *feature.Limit) - require.NotNil(t, feature.SoftLimit) - require.EqualValues(t, 100, *feature.SoftLimit) + require.EqualValues(t, 100, *feature.Limit) require.NotNil(t, feature.Actual) require.EqualValues(t, actualAgents, *feature.Actual) require.NotNil(t, feature.UsagePeriod) @@ -1792,9 +1793,8 @@ func TestManagedAgentLimitDefault(t *testing.T) { JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ FeatureSet: codersdk.FeatureSetPremium, Features: license.Features{ - codersdk.FeatureUserLimit: 100, - codersdk.FeatureName("managed_agent_limit_soft"): 0, - codersdk.FeatureName("managed_agent_limit_hard"): 0, + codersdk.FeatureUserLimit: 100, + codersdk.FeatureManagedAgentLimit: 0, }, }), } @@ -1818,8 +1818,6 @@ func TestManagedAgentLimitDefault(t *testing.T) { require.False(t, feature.Enabled) require.NotNil(t, feature.Limit) require.EqualValues(t, 0, *feature.Limit) - require.NotNil(t, feature.SoftLimit) - require.EqualValues(t, 0, *feature.SoftLimit) require.NotNil(t, feature.Actual) require.EqualValues(t, actualAgents, *feature.Actual) require.NotNil(t, feature.UsagePeriod) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 0172faf712..aeb524265b 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -4728,7 +4728,7 @@ func TestWorkspaceAITask(t *testing.T) { Features: license.Features{ codersdk.FeatureTemplateRBAC: 1, }, - }).ManagedAgentLimit(10, 20), + }).ManagedAgentLimit(10), }) client, _ := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7d55fd621f..997d65adda 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2112,12 +2112,6 @@ export interface Feature { readonly enabled: boolean; readonly limit?: number; readonly actual?: number; - /** - * SoftLimit is the soft limit of the feature, and is only used for showing - * included limits in the dashboard. No license validation or warnings are - * generated from this value. - */ - readonly soft_limit?: number; /** * UsagePeriod denotes that the usage is a counter that accumulates over * this period (and most likely resets with the issuance of the next diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.stories.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.stories.tsx index 24b65093d3..7c891bb7b7 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.stories.tsx @@ -9,8 +9,7 @@ const meta: Meta = { managedAgentFeature: { enabled: true, actual: 50000, - soft_limit: 60000, - limit: 120000, + limit: 60000, usage_period: { start: "February 27, 2025", end: "February 27, 2026", @@ -31,8 +30,7 @@ export const ZeroUsage: Story = { managedAgentFeature: { enabled: true, actual: 0, - soft_limit: 60000, - limit: 120000, + limit: 60000, usage_period: { start: "February 27, 2025", end: "February 27, 2026", @@ -47,9 +45,8 @@ export const NearLimit: Story = { args: { managedAgentFeature: { enabled: true, - actual: 115000, - soft_limit: 60000, - limit: 120000, + actual: 55000, + limit: 60000, usage_period: { start: "February 27, 2025", end: "February 27, 2026", @@ -60,13 +57,12 @@ export const NearLimit: Story = { }, }; -export const OverIncluded: Story = { +export const OverLimit: Story = { args: { managedAgentFeature: { enabled: true, actual: 80000, - soft_limit: 60000, - limit: 120000, + limit: 60000, usage_period: { start: "February 27, 2025", end: "February 27, 2026", @@ -82,25 +78,7 @@ export const LowUsage: Story = { managedAgentFeature: { enabled: true, actual: 25000, - soft_limit: 60000, - limit: 120000, - usage_period: { - start: "February 27, 2025", - end: "February 27, 2026", - issued_at: "February 27, 2025", - }, - entitlement: "entitled", - }, - }, -}; - -export const IncludedAtLimit: Story = { - args: { - managedAgentFeature: { - enabled: true, - actual: 25000, - soft_limit: 30500, - limit: 30500, + limit: 60000, usage_period: { start: "February 27, 2025", end: "February 27, 2026", @@ -116,7 +94,6 @@ export const Disabled: Story = { managedAgentFeature: { enabled: false, actual: undefined, - soft_limit: undefined, limit: undefined, usage_period: undefined, entitlement: "not_entitled", @@ -136,7 +113,6 @@ export const ErrorMissingData: Story = { managedAgentFeature: { enabled: true, actual: undefined, - soft_limit: undefined, limit: undefined, usage_period: undefined, entitlement: "entitled", @@ -149,25 +125,7 @@ export const ErrorNegativeValues: Story = { managedAgentFeature: { enabled: true, actual: -100, - soft_limit: 60000, - limit: 120000, - usage_period: { - start: "February 27, 2025", - end: "February 27, 2026", - issued_at: "February 27, 2025", - }, - entitlement: "entitled", - }, - }, -}; - -export const ErrorSoftLimitExceedsLimit: Story = { - args: { - managedAgentFeature: { - enabled: true, - actual: 50000, - soft_limit: 150000, - limit: 120000, + limit: 60000, usage_period: { start: "February 27, 2025", end: "February 27, 2026", @@ -183,8 +141,7 @@ export const ErrorInvalidDates: Story = { managedAgentFeature: { enabled: true, actual: 50000, - soft_limit: 60000, - limit: 120000, + limit: 60000, usage_period: { start: "invalid-date", end: "February 27, 2026", @@ -200,8 +157,7 @@ export const ErrorEndBeforeStart: Story = { managedAgentFeature: { enabled: true, actual: 50000, - soft_limit: 60000, - limit: 120000, + limit: 60000, usage_period: { start: "February 27, 2026", end: "February 27, 2025", diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.tsx index 3f93354654..6bf022f944 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.tsx @@ -39,7 +39,7 @@ export const ManagedAgentsConsumption: FC = ({ } const usage = managedAgentFeature.actual; - const included = managedAgentFeature.soft_limit; + const included = managedAgentFeature.limit; const startDate = managedAgentFeature.usage_period?.start; const endDate = managedAgentFeature.usage_period?.end;