Skip to content

Commit

Permalink
feat: support start time parameter on read changes (#443)
Browse files Browse the repository at this point in the history
* feat: support start time parameter on read changes

feat: support start time parameter on read changes

* Update README.md

Co-authored-by: Ewan Harris <[email protected]>

* chore: bump go-sdk version to include start_time variable fix

* chore: bump go-sdk version, update changelog

feat: use RFC3339 in place of ISO8601 layout string

fix: resolve linting error from newline

docs: Update changelog with start time and prep for 0.6.4 release

---------

Co-authored-by: Ewan Harris <[email protected]>
  • Loading branch information
ryanpq and ewanharris authored Feb 7, 2025
1 parent 123afcc commit c160fcf
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

#### [Unreleased](https://github.com/openfga/cli/compare/v0.6.3...HEAD) (2025-02-06)
Added:
- Support for start-time parameter in changes command (#443)

#### [0.6.3](https://github.com/openfga/cli/compare/v0.6.2...v0.6.3) (2025-01-22)

Added:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ type document
| [Write Relationship Tuples](#write-relationship-tuples) | `write` | `--store-id`, `--model-id` | `fga tuple write user:anne can_view document:roadmap --store-id=01H0H015178Y2V4CX10C2KGHF4` |
| [Delete Relationship Tuples](#delete-relationship-tuples) | `delete` | `--store-id`, `--model-id` | `fga tuple delete user:anne can_view document:roadmap --store-id=01H0H015178Y2V4CX10C2KGHF4` |
| [Read Relationship Tuples](#read-relationship-tuples) | `read` | `--store-id`, `--model-id` | `fga tuple read --store-id=01H0H015178Y2V4CX10C2KGHF4 --model-id=01GXSA8YR785C4FYS3C0RTG7B1` |
| [Read Relationship Tuple Changes (Watch)](#read-relationship-tuple-changes-watch) | `changes` | `--store-id`, `--type`, `--continuation-token`, | `fga tuple changes --store-id=01H0H015178Y2V4CX10C2KGHF4 --type=document --continuation-token=M3w=` |
| [Read Relationship Tuple Changes (Watch)](#read-relationship-tuple-changes-watch) | `changes` | `--store-id`, `--type`, `--start-time`, `--continuation-token`, | `fga tuple changes --store-id=01H0H015178Y2V4CX10C2KGHF4 --type=document --start-time=2022-01-01T00:00:00Z --continuation-token=M3w=` |
| [Import Relationship Tuples](#import-relationship-tuples) | `import` | `--store-id`, `--model-id`, `--file` | `fga tuple import --store-id=01H0H015178Y2V4CX10C2KGHF4 --model-id=01GXSA8YR785C4FYS3C0RTG7B1 --file tuples.json` |

##### Write Relationship Tuples
Expand Down Expand Up @@ -896,6 +896,7 @@ fga tuple **changes** --type <type> --store-id=<store-id>
###### Parameters
* `--store-id`: Specifies the store id
* `--type`: Restrict to a specific type (optional)
* `--start-time`: Return changes since a specified time (optional)
* `--max-pages`: Max number of pages to retrieve (default: 20)
* `--continuation-token`: Continuation token to start changes from
Expand Down
35 changes: 29 additions & 6 deletions cmd/tuple/changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package tuple
import (
"context"
"fmt"
"time"

openfga "github.com/openfga/go-sdk"
"github.com/openfga/go-sdk/client"
Expand All @@ -32,15 +33,30 @@ import (
var MaxReadChangesPagesLength = 20

func readChanges(
fgaClient client.SdkClient, maxPages int, selectedType string, continuationToken string,
fgaClient client.SdkClient, maxPages int, selectedType string, startTime string, continuationToken string,
) (*openfga.ReadChangesResponse, error) {
changes := []openfga.TupleChange{}
pageIndex := 0

var startTimeObj *time.Time

if startTime != "" {
parsedTime, err := time.Parse(time.RFC3339, startTime)
if err != nil {
return nil, fmt.Errorf("failed to parse startTime: %w", err)
}

startTimeObj = &parsedTime
}

for {
body := &client.ClientReadChangesRequest{
Type: selectedType,
}
if startTimeObj != nil {
body.StartTime = *startTimeObj
}

options := &client.ClientReadChangesOptions{
ContinuationToken: &continuationToken,
}
Expand All @@ -67,10 +83,11 @@ func readChanges(

// changesCmd represents the changes command.
var changesCmd = &cobra.Command{
Use: "changes",
Short: "Read Relationship Tuple Changes (Watch)",
Long: "Get a list of relationship tuple changes (Writes and Deletes) across time.",
Example: "fga tuple changes --store-id=01H0H015178Y2V4CX10C2KGHF4 --type document --continuation-token=MXw=",
Use: "changes",
Short: "Read Relationship Tuple Changes (Watch)",
Long: "Get a list of relationship tuple changes (Writes and Deletes) across time.",
Example: `fga tuple changes --store-id=01H0H015178Y2V4CX10C2KGHF4 --type document
--start-time 2022-01-01T00:00:00Z --continuation-token=MXw=`,
RunE: func(cmd *cobra.Command, _ []string) error {
clientConfig := cmdutils.GetClientConfig(cmd)

Expand All @@ -89,12 +106,17 @@ var changesCmd = &cobra.Command{
return fmt.Errorf("failed to get tuple changes due to %w", err)
}

startTime, err := cmd.Flags().GetString("start-time")
if err != nil {
return fmt.Errorf("failed to get tuple changes due to %w", err)
}

continuationToken, err := cmd.Flags().GetString("continuation-token")
if err != nil {
return fmt.Errorf("failed to get tuple changes due to %w", err)
}

response, err := readChanges(fgaClient, maxPages, selectedType, continuationToken)
response, err := readChanges(fgaClient, maxPages, selectedType, startTime, continuationToken)
if err != nil {
return err
}
Expand All @@ -105,6 +127,7 @@ var changesCmd = &cobra.Command{

func init() {
changesCmd.Flags().String("type", "", "Type to restrict the changes by.")
changesCmd.Flags().String("start-time", "", "Time to return changes since.")
changesCmd.Flags().Int("max-pages", MaxReadChangesPagesLength, "Max number of pages to get.")
changesCmd.Flags().String("continuation-token", "", "Continuation token to start changes from.")
}
34 changes: 26 additions & 8 deletions cmd/tuple/changes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestReadChangesError(t *testing.T) {

mockFgaClient.EXPECT().ReadChanges(context.Background()).Return(mockBody)

_, err := readChanges(mockFgaClient, 5, "document", "")
_, err := readChanges(mockFgaClient, 5, "document", "", "")
if err == nil {
t.Error("Expect error but there is none")
}
Expand Down Expand Up @@ -82,7 +82,7 @@ func TestReadChangesEmpty(t *testing.T) {

mockFgaClient.EXPECT().ReadChanges(context.Background()).Return(mockBody)

output, err := readChanges(mockFgaClient, 5, "document", "")
output, err := readChanges(mockFgaClient, 5, "document", "", "")
if err != nil {
t.Error(err)
}
Expand Down Expand Up @@ -139,14 +139,20 @@ func TestReadChangesSinglePage(t *testing.T) {

mockBody := mock_client.NewMockSdkClientReadChangesRequestInterface(mockCtrl)

sTime, err := time.Parse(time.RFC3339, "2022-01-01T00:00:00Z")
if err != nil {
t.Error(err)
}

body := client.ClientReadChangesRequest{
Type: "document",
Type: "document",
StartTime: sTime,
}
mockBody.EXPECT().Body(body).Return(mockRequest)

mockFgaClient.EXPECT().ReadChanges(context.Background()).Return(mockBody)

output, err := readChanges(mockFgaClient, 5, "document", "")
output, err := readChanges(mockFgaClient, 5, "document", "2022-01-01T00:00:00Z", "")
if err != nil {
t.Error(err)
}
Expand Down Expand Up @@ -232,8 +238,14 @@ func TestReadChangesMultiPages(t *testing.T) {
mockBody1 := mock_client.NewMockSdkClientReadChangesRequestInterface(mockCtrl)
mockBody2 := mock_client.NewMockSdkClientReadChangesRequestInterface(mockCtrl)

sTime, err := time.Parse(time.RFC3339, "2022-01-01T00:00:00Z")
if err != nil {
t.Error(err)
}

body := client.ClientReadChangesRequest{
Type: "document",
Type: "document",
StartTime: sTime,
}

gomock.InOrder(
Expand All @@ -246,7 +258,7 @@ func TestReadChangesMultiPages(t *testing.T) {
mockFgaClient.EXPECT().ReadChanges(context.Background()).Return(mockBody2),
)

output, err := readChanges(mockFgaClient, 5, "document", "")
output, err := readChanges(mockFgaClient, 5, "document", "2022-01-01T00:00:00Z", "")
if err != nil {
t.Error(err)
}
Expand Down Expand Up @@ -303,14 +315,20 @@ func TestReadChangesMultiPagesLimit(t *testing.T) {

mockBody := mock_client.NewMockSdkClientReadChangesRequestInterface(mockCtrl)

sTime, err := time.Parse(time.RFC3339, "2022-01-01T00:00:00Z")
if err != nil {
t.Error(err)
}

body := client.ClientReadChangesRequest{
Type: "document",
Type: "document",
StartTime: sTime,
}
mockBody.EXPECT().Body(body).Return(mockRequest)

mockFgaClient.EXPECT().ReadChanges(context.Background()).Return(mockBody)

output, err := readChanges(mockFgaClient, 1, "document", "")
output, err := readChanges(mockFgaClient, 1, "document", "2022-01-01T00:00:00Z", "")
if err != nil {
t.Error(err)
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/nwidger/jsoncolor v0.3.2
github.com/oklog/ulid/v2 v2.1.0
github.com/openfga/api/proto v0.0.0-20250107154247-c22e6db5c4f5
github.com/openfga/go-sdk v0.6.4-0.20250107171931-2adebcc8c8bc
github.com/openfga/go-sdk v0.6.5
github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20241115164311-10e575c8e47c
github.com/openfga/openfga v1.8.4
github.com/spf13/cobra v1.8.1
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ github.com/openfga/api/proto v0.0.0-20250107154247-c22e6db5c4f5 h1:z9jaRoo+NIN1A
github.com/openfga/api/proto v0.0.0-20250107154247-c22e6db5c4f5/go.mod h1:m74TNgnAAIJ03gfHcx+xaRWnr+IbQy3y/AVNwwCFrC0=
github.com/openfga/go-sdk v0.6.4-0.20250107171931-2adebcc8c8bc h1:E7x5UZbNIbcCOhBRD3cqrmQz35qNNceMnZPb6UxZRnw=
github.com/openfga/go-sdk v0.6.4-0.20250107171931-2adebcc8c8bc/go.mod h1:zui7pHE3eLAYh2fFmEMrWg9XbxYns2WW5Xr/GEgili4=
github.com/openfga/go-sdk v0.6.4 h1:VAeH9exZuG1qaBt41+mjIK5RxTtuL9maS4d47YsYGZw=
github.com/openfga/go-sdk v0.6.4/go.mod h1:zui7pHE3eLAYh2fFmEMrWg9XbxYns2WW5Xr/GEgili4=
github.com/openfga/go-sdk v0.6.5-0.20250205144545-22df3ecba077 h1:o1s2Y5cjg/ALF9vN2BiTcF6ucrGDGfJKR7N8QdjKcR4=
github.com/openfga/go-sdk v0.6.5-0.20250205144545-22df3ecba077/go.mod h1:zui7pHE3eLAYh2fFmEMrWg9XbxYns2WW5Xr/GEgili4=
github.com/openfga/go-sdk v0.6.5 h1:2bxZkoLyphOahFETo9wPvls1AQ3IqbsygIyDkzRvx1k=
github.com/openfga/go-sdk v0.6.5/go.mod h1:zui7pHE3eLAYh2fFmEMrWg9XbxYns2WW5Xr/GEgili4=
github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20241115164311-10e575c8e47c h1:1y84C0V4NRfPtRi4MqQ7+gnFtYgeBKPIeIAPLdVJ7j4=
github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20241115164311-10e575c8e47c/go.mod h1:12RMe/HuRNyOzS33RQa53jwdcxE2znr8ycXMlVbgQN4=
github.com/openfga/openfga v1.8.4 h1:OqyRpuxMCxcS7irTFYFkhAIYzmAnczNwxUqjnuZOQyo=
Expand Down

0 comments on commit c160fcf

Please sign in to comment.