diff --git a/api/api.gen.go b/api/api.gen.go index 3e0510dfd..1113b2331 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -971,9 +971,10 @@ type ListEntitlementGrantsParamsOrderBy string // GetEntitlementHistoryParams defines parameters for GetEntitlementHistory. type GetEntitlementHistoryParams struct { - // From Start of time range to query entitlement: date-time in RFC 3339 format. + // From Start of time range to query entitlement: date-time in RFC 3339 format. Defaults to + // the last reset. // Gets truncated to the granularity of the underlying meter. - From time.Time `form:"from" json:"from"` + From *time.Time `form:"from,omitempty" json:"from,omitempty"` // To End of time range to query entitlement: date-time in RFC 3339 format. Defaults to now. // If not now then gets truncated to the granularity of the underlying meter. @@ -2840,16 +2841,9 @@ func (siw *ServerInterfaceWrapper) GetEntitlementHistory(w http.ResponseWriter, // Parameter object where we will unmarshal all parameters from the context var params GetEntitlementHistoryParams - // ------------- Required query parameter "from" ------------- - - if paramValue := r.URL.Query().Get("from"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "from"}) - return - } + // ------------- Optional query parameter "from" ------------- - err = runtime.BindQueryParameter("form", true, true, "from", r.URL.Query(), ¶ms.From) + err = runtime.BindQueryParameter("form", true, false, "from", r.URL.Query(), ¶ms.From) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "from", Err: err}) return @@ -3309,17 +3303,17 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/client/go/client.gen.go b/api/client/go/client.gen.go index 9df6673ee..823c587cf 100644 --- a/api/client/go/client.gen.go +++ b/api/client/go/client.gen.go @@ -971,9 +971,10 @@ type ListEntitlementGrantsParamsOrderBy string // GetEntitlementHistoryParams defines parameters for GetEntitlementHistory. type GetEntitlementHistoryParams struct { - // From Start of time range to query entitlement: date-time in RFC 3339 format. + // From Start of time range to query entitlement: date-time in RFC 3339 format. Defaults to + // the last reset. // Gets truncated to the granularity of the underlying meter. - From time.Time `form:"from" json:"from"` + From *time.Time `form:"from,omitempty" json:"from,omitempty"` // To End of time range to query entitlement: date-time in RFC 3339 format. Defaults to now. // If not now then gets truncated to the granularity of the underlying meter. @@ -3787,16 +3788,20 @@ func NewGetEntitlementHistoryRequest(server string, subjectIdOrKey SubjectIdOrKe if params != nil { queryValues := queryURL.Query() - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "from", runtime.ParamLocationQuery, params.From); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) + if params.From != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "from", runtime.ParamLocationQuery, *params.From); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } } } + } if params.To != nil { @@ -6898,17 +6903,17 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/client/node/schemas/openapi.ts b/api/client/node/schemas/openapi.ts index 36b69a8f7..1df0b9ae0 100644 --- a/api/client/node/schemas/openapi.ts +++ b/api/client/node/schemas/openapi.ts @@ -1989,10 +1989,11 @@ export interface operations { parameters: { query: { /** - * @description Start of time range to query entitlement: date-time in RFC 3339 format. + * @description Start of time range to query entitlement: date-time in RFC 3339 format. Defaults to + * the last reset. * Gets truncated to the granularity of the underlying meter. */ - from: string + from?: string /** * @description End of time range to query entitlement: date-time in RFC 3339 format. Defaults to now. * If not now then gets truncated to the granularity of the underlying meter. diff --git a/api/client/python/src/openmeter/_operations/_operations.py b/api/client/python/src/openmeter/_operations/_operations.py index 2b1b13280..845903412 100644 --- a/api/client/python/src/openmeter/_operations/_operations.py +++ b/api/client/python/src/openmeter/_operations/_operations.py @@ -719,8 +719,8 @@ def build_get_entitlement_history_request( subject_id_or_key: str, entitlement_id: str, *, - from_parameter: datetime.datetime, window_size: str, + from_parameter: Optional[datetime.datetime] = None, to: Optional[datetime.datetime] = None, window_time_zone: str = "UTC", **kwargs: Any @@ -740,7 +740,8 @@ def build_get_entitlement_history_request( _url: str = _url.format(**path_format_arguments) # type: ignore # Construct parameters - _params["from"] = _SERIALIZER.query("from_parameter", from_parameter, "iso-8601") + if from_parameter is not None: + _params["from"] = _SERIALIZER.query("from_parameter", from_parameter, "iso-8601") if to is not None: _params["to"] = _SERIALIZER.query("to", to, "iso-8601") _params["windowSize"] = _SERIALIZER.query("window_size", window_size, "str") @@ -4311,8 +4312,8 @@ def get_entitlement_history( subject_id_or_key: str, entitlement_id: str, *, - from_parameter: datetime.datetime, window_size: str, + from_parameter: Optional[datetime.datetime] = None, to: Optional[datetime.datetime] = None, window_time_zone: str = "UTC", **kwargs: Any @@ -4329,13 +4330,14 @@ def get_entitlement_history( :type subject_id_or_key: str :param entitlement_id: A unique ULID for an entitlement. Required. :type entitlement_id: str - :keyword from_parameter: Start of time range to query entitlement: date-time in RFC 3339 - format. - Gets truncated to the granularity of the underlying meter. Required. - :paramtype from_parameter: ~datetime.datetime :keyword window_size: Size of the time window to group the history by. Cannot be shorter than meter granularity. Known values are: "MINUTE", "HOUR", and "DAY". Required. :paramtype window_size: str + :keyword from_parameter: Start of time range to query entitlement: date-time in RFC 3339 + format. Defaults to + the last reset. + Gets truncated to the granularity of the underlying meter. Default value is None. + :paramtype from_parameter: ~datetime.datetime :keyword to: End of time range to query entitlement: date-time in RFC 3339 format. Defaults to now. If not now then gets truncated to the granularity of the underlying meter. Default value is @@ -4421,8 +4423,8 @@ def get_entitlement_history( _request = build_get_entitlement_history_request( subject_id_or_key=subject_id_or_key, entitlement_id=entitlement_id, - from_parameter=from_parameter, window_size=window_size, + from_parameter=from_parameter, to=to, window_time_zone=window_time_zone, headers=_headers, diff --git a/api/client/python/src/openmeter/aio/_operations/_operations.py b/api/client/python/src/openmeter/aio/_operations/_operations.py index 095c617c2..0a3970635 100644 --- a/api/client/python/src/openmeter/aio/_operations/_operations.py +++ b/api/client/python/src/openmeter/aio/_operations/_operations.py @@ -3610,8 +3610,8 @@ async def get_entitlement_history( subject_id_or_key: str, entitlement_id: str, *, - from_parameter: datetime.datetime, window_size: str, + from_parameter: Optional[datetime.datetime] = None, to: Optional[datetime.datetime] = None, window_time_zone: str = "UTC", **kwargs: Any @@ -3628,13 +3628,14 @@ async def get_entitlement_history( :type subject_id_or_key: str :param entitlement_id: A unique ULID for an entitlement. Required. :type entitlement_id: str - :keyword from_parameter: Start of time range to query entitlement: date-time in RFC 3339 - format. - Gets truncated to the granularity of the underlying meter. Required. - :paramtype from_parameter: ~datetime.datetime :keyword window_size: Size of the time window to group the history by. Cannot be shorter than meter granularity. Known values are: "MINUTE", "HOUR", and "DAY". Required. :paramtype window_size: str + :keyword from_parameter: Start of time range to query entitlement: date-time in RFC 3339 + format. Defaults to + the last reset. + Gets truncated to the granularity of the underlying meter. Default value is None. + :paramtype from_parameter: ~datetime.datetime :keyword to: End of time range to query entitlement: date-time in RFC 3339 format. Defaults to now. If not now then gets truncated to the granularity of the underlying meter. Default value is @@ -3720,8 +3721,8 @@ async def get_entitlement_history( _request = build_get_entitlement_history_request( subject_id_or_key=subject_id_or_key, entitlement_id=entitlement_id, - from_parameter=from_parameter, window_size=window_size, + from_parameter=from_parameter, to=to, window_time_zone=window_time_zone, headers=_headers, diff --git a/api/client/web/src/client/openapi.ts b/api/client/web/src/client/openapi.ts index 36b69a8f7..1df0b9ae0 100644 --- a/api/client/web/src/client/openapi.ts +++ b/api/client/web/src/client/openapi.ts @@ -1989,10 +1989,11 @@ export interface operations { parameters: { query: { /** - * @description Start of time range to query entitlement: date-time in RFC 3339 format. + * @description Start of time range to query entitlement: date-time in RFC 3339 format. Defaults to + * the last reset. * Gets truncated to the granularity of the underlying meter. */ - from: string + from?: string /** * @description End of time range to query entitlement: date-time in RFC 3339 format. Defaults to now. * If not now then gets truncated to the granularity of the underlying meter. diff --git a/api/openapi.yaml b/api/openapi.yaml index 96a07b1a0..c78cb3b0b 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1067,9 +1067,10 @@ paths: - $ref: "#/components/parameters/entitlementId" - name: from in: query - required: true + required: false description: | - Start of time range to query entitlement: date-time in RFC 3339 format. + Start of time range to query entitlement: date-time in RFC 3339 format. Defaults to + the last reset. Gets truncated to the granularity of the underlying meter. schema: type: string diff --git a/internal/entitlement/httpdriver/metered.go b/internal/entitlement/httpdriver/metered.go index b18ffc268..83c455097 100644 --- a/internal/entitlement/httpdriver/metered.go +++ b/internal/entitlement/httpdriver/metered.go @@ -276,7 +276,7 @@ func (h *meteredEntitlementHandler) GetEntitlementBalanceHistory() GetEntitlemen }, params: meteredentitlement.BalanceHistoryParams{ From: params.Params.From, - To: defaultx.WithDefault(params.Params.To, time.Now()), + To: params.Params.To, WindowSize: meteredentitlement.WindowSize(params.Params.WindowSize), WindowTimeZone: *tLocation, }, diff --git a/internal/entitlement/metered/balance.go b/internal/entitlement/metered/balance.go index 2a6e201b6..4b876ab57 100644 --- a/internal/entitlement/metered/balance.go +++ b/internal/entitlement/metered/balance.go @@ -39,8 +39,8 @@ const ( ) type BalanceHistoryParams struct { - From time.Time - To time.Time + From *time.Time + To *time.Time WindowSize WindowSize WindowTimeZone time.Location } @@ -95,26 +95,31 @@ func (e *connector) GetEntitlementBalanceHistory(ctx context.Context, entitlemen // TODO: we should guard against abuse, getting history is expensive // validate that we're working with a metered entitlement - ent, err := e.entitlementRepo.GetEntitlement(ctx, entitlementID) + entRepoEntity, err := e.entitlementRepo.GetEntitlement(ctx, entitlementID) if err != nil { return nil, credit.GrantBurnDownHistory{}, err } - _, err = ParseFromGenericEntitlement(ent) - if err != nil { - return nil, credit.GrantBurnDownHistory{}, err + + if entRepoEntity == nil { + return nil, credit.GrantBurnDownHistory{}, &entitlement.NotFoundError{EntitlementID: entitlementID} } - // query period cannot be before start of measuring usage - start, err := e.ownerConnector.GetStartOfMeasurement(ctx, credit.NamespacedGrantOwner{ - Namespace: entitlementID.Namespace, - ID: credit.GrantOwner(entitlementID.ID), - }) + ent, err := ParseFromGenericEntitlement(entRepoEntity) if err != nil { return nil, credit.GrantBurnDownHistory{}, err } - if params.From.Before(start) { - return nil, credit.GrantBurnDownHistory{}, &models.GenericUserError{Message: fmt.Sprintf("from cannot be before %s", start.UTC().Format(time.RFC3339))} + if params.From == nil { + params.From = &ent.LastReset + } + + if params.To == nil { + params.To = convert.ToPointer(time.Now()) + } + + // query period cannot be before start of measuring usage + if params.From.Before(ent.MeasureUsageFrom) { + return nil, credit.GrantBurnDownHistory{}, &models.GenericUserError{Message: fmt.Sprintf("from cannot be before %s", ent.MeasureUsageFrom.UTC().Format(time.RFC3339))} } owner := credit.NamespacedGrantOwner{ @@ -135,8 +140,8 @@ func (e *connector) GetEntitlementBalanceHistory(ctx context.Context, entitlemen if err != nil { return nil, credit.GrantBurnDownHistory{}, fmt.Errorf("failed to get owner query params: %w", err) } - meterParams.From = ¶ms.From - meterParams.To = ¶ms.To + meterParams.From = params.From + meterParams.To = params.To meterParams.WindowSize = convert.ToPointer(models.WindowSize(params.WindowSize)) meterParams.WindowTimeZone = ¶ms.WindowTimeZone diff --git a/internal/entitlement/metered/balance_test.go b/internal/entitlement/metered/balance_test.go index b6f8ab797..2bf50837a 100644 --- a/internal/entitlement/metered/balance_test.go +++ b/internal/entitlement/metered/balance_test.go @@ -510,8 +510,8 @@ func TestGetEntitlementHistory(t *testing.T) { assert.NoError(t, err) windowedHistory, burndownHistory, err := connector.GetEntitlementBalanceHistory(ctx, models.NamespacedID{Namespace: namespace, ID: ent.ID}, meteredentitlement.BalanceHistoryParams{ - From: startTime, - To: queryTime, + From: &startTime, + To: &queryTime, WindowTimeZone: *time.UTC, WindowSize: meteredentitlement.WindowSizeHour, }) @@ -548,6 +548,101 @@ func TestGetEntitlementHistory(t *testing.T) { assert.Len(t, segments, 3) }, }, + { + name: "If start time is not specified we are defaulting to the last reset", + run: func(t *testing.T, connector meteredentitlement.Connector, deps *testDependencies) { + ctx := context.Background() + startTime := testutils.GetRFC3339Time(t, "2024-03-01T00:00:00Z") + + // create featute in db + feature, err := deps.featureDB.CreateFeature(ctx, exampleFeature) + assert.NoError(t, err) + + // create entitlement in db + inp := getEntitlement(t, feature) + inp.MeasureUsageFrom = &startTime + ent, err := deps.entitlementDB.CreateEntitlement(ctx, inp) + assert.NoError(t, err) + + // grant at start + _, err = deps.grantDB.CreateGrant(ctx, credit.GrantRepoCreateGrantInput{ + OwnerID: credit.GrantOwner(ent.ID), + Namespace: namespace, + Amount: 10000, + Priority: 1, + EffectiveAt: startTime, + ExpiresAt: startTime.AddDate(0, 0, 3), + }) + assert.NoError(t, err) + + // register usage for meter & feature + deps.streaming.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Minute)) + + // let's do a reset + resetTime := startTime.Add(time.Hour * 2) + _, err = connector.ResetEntitlementUsage(ctx, + models.NamespacedID{Namespace: namespace, ID: ent.ID}, + meteredentitlement.ResetEntitlementUsageParams{ + At: resetTime, + RetainAnchor: true, + }, + ) + assert.NoError(t, err) + + queryTime := startTime.Add(time.Hour * 12) + + // register usage for meter & feature + deps.streaming.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Hour*2).Add(time.Minute)) + deps.streaming.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Hour*3).Add(time.Minute)) + deps.streaming.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Hour*5).Add(time.Minute)) + deps.streaming.AddSimpleEvent(meterSlug, 1100, startTime.Add(time.Hour*8).Add(time.Minute)) + deps.streaming.AddSimpleEvent(meterSlug, 100, queryTime.Add(-time.Second)) + + // grant after the reset + _, err = deps.grantDB.CreateGrant(ctx, credit.GrantRepoCreateGrantInput{ + OwnerID: credit.GrantOwner(ent.ID), + Namespace: namespace, + Amount: 10000, + Priority: 1, + EffectiveAt: resetTime, + ExpiresAt: startTime.AddDate(0, 0, 3), + }) + assert.NoError(t, err) + + windowedHistory, burndownHistory, err := connector.GetEntitlementBalanceHistory(ctx, models.NamespacedID{Namespace: namespace, ID: ent.ID}, meteredentitlement.BalanceHistoryParams{ + To: &queryTime, + WindowTimeZone: *time.UTC, + WindowSize: meteredentitlement.WindowSizeHour, + }) + assert.NoError(t, err) + + assert.Len(t, windowedHistory, 10) + + // deps.streaming.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Hour*2).Add(time.Minute)) + assert.Equal(t, 100.0, windowedHistory[0].UsageInPeriod) + assert.Equal(t, 10000.0, windowedHistory[0].BalanceAtStart) + // deps.streaming.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Hour*3).Add(time.Minute)) + assert.Equal(t, 100.0, windowedHistory[1].UsageInPeriod) + assert.Equal(t, 9900.0, windowedHistory[1].BalanceAtStart) + assert.Equal(t, 9800.0, windowedHistory[2].BalanceAtStart) + // deps.streaming.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Hour*5).Add(time.Minute)) + assert.Equal(t, 100.0, windowedHistory[3].UsageInPeriod) + assert.Equal(t, 9800.0, windowedHistory[3].BalanceAtStart) // even though EffectiveAt: startTime.Add(time.Hour * 5).Add(time.Minute * 30) grant happens here, it is only recognized at the next window + assert.Equal(t, 9700.0, windowedHistory[4].BalanceAtStart) + assert.Equal(t, 9700.0, windowedHistory[5].BalanceAtStart) + // deps.streaming.AddSimpleEvent(meterSlug, 1100, startTime.Add(time.Hour*8).Add(time.Minute)) + assert.Equal(t, 1100.0, windowedHistory[6].UsageInPeriod) + assert.Equal(t, 9700.0, windowedHistory[6].BalanceAtStart) + assert.Equal(t, 8600.0, windowedHistory[7].BalanceAtStart) + // deps.streaming.AddSimpleEvent(meterSlug, 100, queryTime.Add(-time.Second)) + assert.Equal(t, 100.0, windowedHistory[9].UsageInPeriod) + assert.Equal(t, 8600.0, windowedHistory[9].BalanceAtStart) + + // check returned burndownhistory + segments := burndownHistory.Segments() + assert.Len(t, segments, 2) + }, + }, } for _, tc := range tt {