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

[MINT-3157] Plan Data Exchange Data Loader #1407

4 changes: 1 addition & 3 deletions pkg/graph/resolvers/model_plan.resolvers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions pkg/graph/resolvers/plan_data_exchange_approach.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package resolvers

import (
"context"
"fmt"
"time"

"github.com/google/uuid"
"go.uber.org/zap"

"github.com/cms-enterprise/mint-app/pkg/authentication"
"github.com/cms-enterprise/mint-app/pkg/storage/loaders"

"github.com/cms-enterprise/mint-app/pkg/models"
"github.com/cms-enterprise/mint-app/pkg/storage"
Expand Down Expand Up @@ -77,3 +79,8 @@ func PlanDataExchangeApproachUpdate(
retDataExchangeApproach, err := storage.PlanDataExchangeApproachUpdate(store, logger, existing)
return retDataExchangeApproach, err
}

// PlanDataExchangeApproachGetByModelPlanIDLoader calls a data loader to batch fetching a a plan data exchange object that corresponds to a model plan
func PlanDataExchangeApproachGetByModelPlanIDLoader(ctx context.Context, modelPlanID uuid.UUID) (*models.PlanDataExchangeApproach, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the introduction of this function, do we still need PlanDataExchangeApproachGetByID?

return loaders.PlanDataExchangeApproach.ByModelPlanID.Load(ctx, modelPlanID)
}
41 changes: 40 additions & 1 deletion pkg/graph/resolvers/plan_data_exchange_approach_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,45 @@
package resolvers

import "github.com/cms-enterprise/mint-app/pkg/models"
import (
"github.com/google/uuid"

"github.com/cms-enterprise/mint-app/pkg/models"
"github.com/cms-enterprise/mint-app/pkg/storage/loaders"
)

func (suite *ResolverSuite) TestPlanDataExchangeApproachGetByModelPlanIDLoader() {

plan1 := suite.createModelPlan("model plan 1")
approach1, err := PlanDataExchangeApproachGetByModelPlanIDLoader(suite.testConfigs.Context, plan1.ID)
suite.NoError(err)
suite.EqualValues(plan1.ID, approach1.ModelPlanID)
plan2 := suite.createModelPlan("model plan 2")

approach2, err := PlanDataExchangeApproachGetByModelPlanIDLoader(suite.testConfigs.Context, plan2.ID)
suite.NoError(err)
suite.EqualValues(plan2.ID, approach2.ModelPlanID)
plan3 := suite.createModelPlan("model plan 3")
approach3, err := PlanDataExchangeApproachGetByModelPlanIDLoader(suite.testConfigs.Context, plan3.ID)
suite.NoError(err)
suite.EqualValues(plan3.ID, approach3.ModelPlanID)
expectedResults := []loaders.KeyAndExpected[uuid.UUID, uuid.UUID]{
{Key: plan1.ID, Expected: approach1.ID},
{Key: plan2.ID, Expected: approach2.ID},
{Key: plan3.ID, Expected: approach3.ID},
}

verifyFunc := func(data *models.PlanDataExchangeApproach, expected uuid.UUID) bool {
if suite.NotNil(data) {
return suite.EqualValues(expected, data.ID)
}
return false

}

loaders.VerifyLoaders[uuid.UUID, *models.PlanDataExchangeApproach, uuid.UUID](suite.testConfigs.Context, &suite.Suite, loaders.PlanDataExchangeApproach.ByModelPlanID,
expectedResults, verifyFunc)

}

func (suite *ResolverSuite) TestPlanDataExchangeApproachGetByID() {
plan1 := suite.createModelPlan("model plan 1")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
WITH QUERIED_IDS AS (
/*Translate the input to a table */
SELECT UNNEST(CAST(:model_plan_ids AS UUID[])) AS model_plan_id
)

SELECT
approach.id,
approach.model_plan_id,
approach.data_to_collect_from_participants,
approach.data_to_collect_from_participants_reports_details,
approach.data_to_collect_from_participants_other,
approach.data_will_not_be_collected_from_participants,
approach.data_to_collect_from_participants_note,
approach.data_to_send_to_participants,
approach.data_to_send_to_participants_note,
approach.does_need_to_make_multi_payer_data_available,
approach.anticipated_multi_payer_data_availability_use_case,
approach.does_need_to_make_multi_payer_data_available_note,
approach.does_need_to_collect_and_aggregate_multi_source_data,
approach.multi_source_data_to_collect,
approach.multi_source_data_to_collect_other,
approach.does_need_to_collect_and_aggregate_multi_source_data_note,
approach.will_implement_new_data_exchange_methods,
approach.new_data_exchange_methods_description,
approach.new_data_exchange_methods_note,
approach.additional_data_exchange_considerations_description,
approach.created_by,
approach.created_dts,
approach.modified_by,
approach.modified_dts,
approach.marked_complete_by,
approach.marked_complete_dts,
approach.status
FROM plan_data_exchange_approach AS approach
INNER JOIN QUERIED_IDS ON approach.model_plan_id = QUERIED_IDS.model_plan_id
14 changes: 10 additions & 4 deletions pkg/sqlqueries/plan_data_exchange_approach.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ var planDataExchangeApproachGetByIDSQL string
//go:embed SQL/plan_data_exchange_approach/get_by_model_plan_id.sql
var planDataExchangeApproachGetByModelPlanIDSQL string

//go:embed SQL/plan_data_exchange_approach/get_by_model_plan_id_LOADER.sql
var planDataExchangeApproachGetByModelPlanIDLoaderSQL string

type planDataExchangeApproachScripts struct {
Create string
Update string
GetByID string
GetByModelPlanID string
//Uses a list of model_plan_ids to return a corresponding list of data exchange approach objects
GetByModelPlanIDLoader string
}

// PlanDataExchangeApproach houses all the SQL scripts for the plan_data_exchange_approach table
var PlanDataExchangeApproach = planDataExchangeApproachScripts{
Create: planDataExchangeApproachCreateSQL,
Update: planDataExchangeApproachUpdateSQL,
GetByID: planDataExchangeApproachGetByIDSQL,
GetByModelPlanID: planDataExchangeApproachGetByModelPlanIDSQL,
Create: planDataExchangeApproachCreateSQL,
Update: planDataExchangeApproachUpdateSQL,
GetByID: planDataExchangeApproachGetByIDSQL,
GetByModelPlanID: planDataExchangeApproachGetByModelPlanIDSQL,
GetByModelPlanIDLoader: planDataExchangeApproachGetByModelPlanIDLoaderSQL,
}
60 changes: 60 additions & 0 deletions pkg/storage/loaders/loader_wrapper_test_utilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package loaders

import (
"context"
"fmt"

"github.com/stretchr/testify/suite"
"golang.org/x/sync/errgroup"
)

type KeyAndExpected[K comparable, Expected any] struct {
Key K
Expected Expected
}

// verifyLoader is meant to be called by VerifyLoaders to validate a result set of data, and the returned results
func verifyLoader[K comparable, V any, Expected any](ctx context.Context, suite *suite.Suite, loaderWrapper LoaderWrapper[K, V], key K, expected Expected, validateResult func(V, Expected) bool) bool {
res, err := loaderWrapper.Load(ctx, key)
suite.NoError(err)
return validateResult(res, expected)
}

// VerifyLoaders validates a data loader. It takes a loaderWrapper and a list of expected results and loads the data loader asynchronously. This ensures that the dataloader is returning the results as expected
/*
example
expectedResults := []loaders.KeyAndExpected[uuid.UUID, uuid.UUID]{
{Key: model_plan1.ID, Expected: approach1.ID},
{Key: model_plan2.ID, Expected: approach2.ID},
{Key: model_plan3.ID, Expected: approach3.ID},
}

verifyFunc := func(data *models.PlanDataExchangeApproach, expected uuid.UUID) bool {
if suite.NotNil(data) {
return suite.EqualValues(expected, data.ID)
}
return false

}

loaders.VerifyLoaders[uuid.UUID, *models.PlanDataExchangeApproach, uuid.UUID](suite.testConfigs.Context, &suite.Suite, loaders.PlanDataExchangeApproach.ByModelPlanID,
expectedResults, verifyFunc)

This example takes the model_plan_id keys, and specifies the expected values.


*/
func VerifyLoaders[K comparable, V any, Expected any](ctx context.Context, suite *suite.Suite, loaderWrapper LoaderWrapper[K, V], expectedResults []KeyAndExpected[K, Expected], validateResult func(V, Expected) bool) {
g, ctx := errgroup.WithContext(ctx)
for _, expected := range expectedResults {
g.Go(func() error {
passed := verifyLoader[K, V, Expected](ctx, suite, loaderWrapper, expected.Key, expected.Expected, validateResult)
if !passed {
return fmt.Errorf("dataloader verification function failed")
}
return nil
})
}
err := g.Wait()
suite.NoError(err)
}
67 changes: 67 additions & 0 deletions pkg/storage/loaders/plan_data_exchange_approach_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package loaders

import (
"context"
"fmt"

"github.com/google/uuid"
"github.com/samber/lo"

"github.com/cms-enterprise/mint-app/pkg/appcontext"
"github.com/cms-enterprise/mint-app/pkg/models"
"github.com/cms-enterprise/mint-app/pkg/storage"

"github.com/graph-gophers/dataloader/v7"
)

// planDataExchangeApproachLoaders is a struct that holds LoaderWrappers related to Plan Data Exchange Approach
type planDataExchangeApproachLoaders struct {
// ByModelPlanID Gets a plan data exchange approach record associated with a model plan by the supplied model plan id
ByModelPlanID LoaderWrapper[uuid.UUID, *models.PlanDataExchangeApproach]
}

var PlanDataExchangeApproach = &planDataExchangeApproachLoaders{
ByModelPlanID: NewLoaderWrapper(batchPlanDataExchangeApproachByModelPlanID),
}

func batchPlanDataExchangeApproachByModelPlanID(ctx context.Context, modelPlanIDs []uuid.UUID) []*dataloader.Result[*models.PlanDataExchangeApproach] {
logger := appcontext.ZLogger(ctx)
output := make([]*dataloader.Result[*models.PlanDataExchangeApproach], len(modelPlanIDs))
loaders, err := Loaders(ctx)
if err != nil {
for index := range modelPlanIDs {
output[index] = &dataloader.Result[*models.PlanDataExchangeApproach]{Data: nil, Error: err}
}
return output
}

// TODO, when sorting and erroring utilities are merged to main, replace these functions with them
data, err := storage.PlanDataExchangeApproachGetByModelPlanIDLoader(loaders.DataReader.Store, logger, modelPlanIDs)
if err != nil {

for index := range modelPlanIDs {
output[index] = &dataloader.Result[*models.PlanDataExchangeApproach]{Data: nil, Error: err}
}
return output
}

approachByModelPlanID := lo.Associate(data, func(data *models.PlanDataExchangeApproach) (uuid.UUID, *models.PlanDataExchangeApproach) {
return data.ModelPlanID, data
})

// TODO (loaders) implement the generic sorting functionality once it is merged to main

// RETURN IN THE SAME ORDER REQUESTED
for index, id := range modelPlanIDs {

data, ok := approachByModelPlanID[id]
if ok {
output[index] = &dataloader.Result[*models.PlanDataExchangeApproach]{Data: data, Error: nil}
} else {
err2 := fmt.Errorf("plan data exchange approach not found for modelPlanID id %s", id)
output[index] = &dataloader.Result[*models.PlanDataExchangeApproach]{Data: nil, Error: err2}
}
}
return output

}
17 changes: 17 additions & 0 deletions pkg/storage/plan_data_exchange_approachStore.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
_ "embed"
"fmt"

"github.com/lib/pq"

"github.com/cms-enterprise/mint-app/pkg/sqlqueries"

"github.com/google/uuid"
Expand Down Expand Up @@ -56,3 +58,18 @@ func PlanDataExchangeApproachGetByModelPlanID(np sqlutils.NamedPreparer, _ *zap.
return sqlutils.GetProcedure[models.PlanDataExchangeApproach](np, sqlqueries.PlanDataExchangeApproach.GetByModelPlanID, utilitysql.CreateModelPlanIDQueryMap(modelPlanID))

}

// PlanDataExchangeApproachGetByModelPlanIDLoader returns the plan basics for a slice of model plan ids
func PlanDataExchangeApproachGetByModelPlanIDLoader(np sqlutils.NamedPreparer, _ *zap.Logger, modelPlanIDs []uuid.UUID) ([]*models.PlanDataExchangeApproach, error) {

args := map[string]interface{}{
"model_plan_ids": pq.Array(modelPlanIDs),
}

res, err := sqlutils.SelectProcedure[models.PlanDataExchangeApproach](np, sqlqueries.PlanDataExchangeApproach.GetByModelPlanIDLoader, args)
if err != nil {
return nil, err
}
return res, nil

}
Loading