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

Feature/cache topics impl #105

Merged
merged 4 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cache/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cache

const (
// TopicCacheKey is used to cache the topics
TopicCacheKey = "root-topic-cache"
)
30 changes: 14 additions & 16 deletions cache/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,27 @@ func getMockTopicCache(ctx context.Context) (*TopicCache, error) {
return nil, err
}

testTopicCache.Cache.Set("economy", GetMockTopic("6734", "economy", "Economy", ""))
testTopicCache.Cache.Set("environmentalaccounts", GetMockTopic("1834", "environmentalaccounts", "Environmental Accounts", "6734"))
testTopicCache.Cache.Set("governmentpublicsectorandtaxes", GetMockTopic("8268", "governmentpublicsectorandtaxes", "Government Public Sector and Taxes", "6734"))
testTopicCache.Cache.Set("publicsectorfinance", GetMockTopic("3687", "publicsectorfinance", "Public Sector Finance", "8268"))
rootTopicID := testTopicCache.GetTopicCacheKey()
testTopicCache.Set(rootTopicID, GetMockRootTopic(rootTopicID))

return testTopicCache, nil
}

// GetMockTopic returns a mocked topic which contains all the information for the mock data topic
func GetMockTopic(id, slug, title, parentID string) *Topic {
mockTopic := &Topic{
ID: id,
Slug: slug,
LocaliseKeyName: title,
ParentID: parentID,
Query: "1834,1234",
// GetMockRootTopic returns the mocked root topic
func GetMockRootTopic(rootTopicID string) *Topic {
mockDataTopic := &Topic{
ID: rootTopicID,
Slug: "root",
}

mockTopic.List = NewSubTopicsMap()
mockTopic.List.AppendSubtopicID("1234", Subtopic{ID: "1234", LocaliseKeyName: "International Migration", ReleaseDate: timeHelper("2022-10-10T08:30:00Z")})
mockTopic.List.AppendSubtopicID("1834", Subtopic{ID: "1834", LocaliseKeyName: "Environmental Accounts", ReleaseDate: timeHelper("2022-11-09T09:30:00Z")})
mockDataTopic.List = NewSubTopicsMap()
mockDataTopic.List.AppendSubtopicID("economy", Subtopic{ID: "6734", Slug: "economy", LocaliseKeyName: "Economy", ReleaseDate: timeHelper("2022-10-10T08:30:00Z"), ParentID: ""})
mockDataTopic.List.AppendSubtopicID("environmentalaccounts", Subtopic{ID: "1834", Slug: "environmentalaccounts", LocaliseKeyName: "Environmental Accounts", ReleaseDate: timeHelper("2022-10-10T08:30:00Z"), ParentID: "6734"})
mockDataTopic.List.AppendSubtopicID("governmentpublicsectorandtaxes", Subtopic{ID: "8268", Slug: "governmentpublicsectorandtaxes", LocaliseKeyName: "Government Public Sector and Taxes", ReleaseDate: timeHelper("2022-10-10T08:30:00Z"), ParentID: "6734"})
mockDataTopic.List.AppendSubtopicID("publicsectorfinance", Subtopic{ID: "3687", Slug: "publicsectorfinance", LocaliseKeyName: "Public Sector Finance", ReleaseDate: timeHelper("2022-10-10T08:30:00Z"), ParentID: "8268"})
mockDataTopic.List.AppendSubtopicID("internationalmigration", Subtopic{ID: "1234", Slug: "internationalmigration", LocaliseKeyName: "International Migration", ReleaseDate: timeHelper("2022-10-10T08:30:00Z")})
Copy link
Contributor

Choose a reason for hiding this comment

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

Think we've talked about the lack of need for the concept of 'DataTopic' here before.


return mockTopic
return mockDataTopic
}

// timeHelper is a helper function given a time returns a Time pointer
Expand Down
24 changes: 12 additions & 12 deletions cache/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,30 @@ func TestGetMockCacheList(t *testing.T) {

So(cacheList.Topic, ShouldNotBeNil)

topic, err := cacheList.Topic.GetData(ctx, topicSlug)
topic, err := cacheList.Topic.GetTopic(ctx, topicSlug)
So(topic, ShouldNotBeNil)
So(err, ShouldBeNil)
})
})
}

func TestGetMockDataTopic(t *testing.T) {
func TestGetMockRootTopic(t *testing.T) {
t.Parallel()

id := "6734"
slug := topicSlug
title := "Economy"
parentID := ""
rootTopicID := "root-topic-cache"
slug := "root"

Convey("When GetMockTopic is called", t, func() {
mockTopic := GetMockTopic(id, slug, title, parentID)
Convey("When GetMockRootTopic is called", t, func() {
mockTopic := GetMockRootTopic(rootTopicID)

Convey("Then the mock topic is returned", func() {
Convey("Then the mocked root topic is returned", func() {
So(mockTopic, ShouldNotBeNil)
So(mockTopic.ID, ShouldEqual, id)
So(mockTopic.ID, ShouldEqual, rootTopicID)
So(mockTopic.Slug, ShouldEqual, slug)
So(mockTopic.LocaliseKeyName, ShouldEqual, title)
So(mockTopic.ParentID, ShouldNotBeNil)

subtopic, exists := mockTopic.List.Get("economy")
So(exists, ShouldBeTrue)
So(subtopic, ShouldResemble, Subtopic{ID: "6734", Slug: "economy", LocaliseKeyName: "Economy", ReleaseDate: timeHelper("2022-10-10T08:30:00Z"), ParentID: ""})
})
})
}
78 changes: 48 additions & 30 deletions cache/private/private_topic.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ import (
"github.com/ONSdigital/log.go/v2/log"
)

// UpdateTopics is a function to update the topic cache in publishing (private) mode.
// UpdateTopicCache is a function to update the topic cache in publishing (private) mode.
// This function talks to the dp-topic-api via its private endpoints to retrieve the root topic and its subtopic ids
// The data returned by the dp-topic-api is of type *models.PrivateSubtopics which is then transformed in this function for the controller
// If an error has occurred, this is captured in log.Error and then an empty topic is returned
func UpdateTopics(ctx context.Context, serviceAuthToken string, topicClient topicCli.Clienter) func() []*cache.Topic {
return func() []*cache.Topic {
var topics []*cache.Topic
processedTopics := make(map[string]bool)
func UpdateTopicCache(ctx context.Context, serviceAuthToken string, topicClient topicCli.Clienter) func() *cache.Topic {
return func() *cache.Topic {
processedTopics := make(map[string]struct{})

// get root topic from dp-topic-api
rootTopic, err := topicClient.GetRootTopicsPrivate(ctx, topicCli.Headers{ServiceAuthToken: "Bearer " + serviceAuthToken})
Expand All @@ -26,73 +25,92 @@ func UpdateTopics(ctx context.Context, serviceAuthToken string, topicClient topi
"req_headers": topicCli.Headers{},
}
log.Error(ctx, "failed to get root topic from topic-api", err, logData)
return []*cache.Topic{cache.GetEmptyTopic()}
return cache.GetEmptyTopic()
}

// dereference rootTopic's subtopics(items) to allow ranging through them
// dereference root topics items to allow ranging through them
var rootTopicItems []models.TopicResponse
if rootTopic.PrivateItems != nil {
rootTopicItems = *rootTopic.PrivateItems
} else {
err := errors.New("root topic subtopics(items) is nil")
log.Error(ctx, "failed to dereference rootTopic subtopics pointer", err)
return []*cache.Topic{cache.GetEmptyTopic()}
err := errors.New("root topic private items is nil")
log.Error(ctx, "failed to dereference root topic items pointer", err)
return cache.GetEmptyTopic()
}

// Initialize topicCache
topicCache := &cache.Topic{
ID: cache.TopicCacheKey,
LocaliseKeyName: "Root",
List: cache.NewSubTopicsMap(),
}

// recursively process topics and their subtopics
for i := range rootTopicItems {
processTopic(ctx, serviceAuthToken, topicClient, rootTopicItems[i].ID, &topics, processedTopics, "")
processTopic(ctx, serviceAuthToken, topicClient, rootTopicItems[i].ID, topicCache, processedTopics, "", 0)
}

// Check if any topics were found
if len(topics) == 0 {
if len(topicCache.List.GetSubtopics()) == 0 {
err := errors.New("root topic found, but no subtopics were returned")
log.Error(ctx, "No topics loaded into cache - root topic found, but no subtopics were returned", err)
return []*cache.Topic{cache.GetEmptyTopic()}
return cache.GetEmptyTopic()
}
return topics
return topicCache
}
}

func processTopic(ctx context.Context, serviceAuthToken string, topicClient topicCli.Clienter, topicID string, topics *[]*cache.Topic, processedTopics map[string]bool, parentTopicID string) {
// Check if the topic is already processed
if processedTopics[topicID] {
func processTopic(ctx context.Context, serviceAuthToken string, topicClient topicCli.Clienter, topicID string, topicCache *cache.Topic, processedTopics map[string]struct{}, parentTopicID string, depth int) {
log.Info(ctx, "Processing topic at depth", log.Data{
"topic_id": topicID,
"depth": depth,
})

// Check if the topic has already been processed
if _, exists := processedTopics[topicID]; exists {
err := errors.New("topic already processed")
log.Error(ctx, "Skipping already processed topic", err, log.Data{
"topic_id": topicID,
"depth": depth,
})
return
}

// Get the topic details from the topic client
topic, err := topicClient.GetTopicPrivate(ctx, topicCli.Headers{ServiceAuthToken: "Bearer " + serviceAuthToken}, topicID)
if err != nil {
logData := log.Data{
topicID: topicID,
}
log.Error(ctx, "failed to get topic details from topic-api", err, logData)
log.Error(ctx, "failed to get topic details from topic-api", err, log.Data{
"topic_id": topicID,
"depth": depth,
})
return
}

if topic != nil {
// Append the current topic to the list of topics
*topics = append(*topics, mapTopicModelToCache(*topic.Current, parentTopicID))
// Initialize subtopic list for the current topic if it doesn't exist
subtopic := mapTopicModelToCache(*topic.Current, parentTopicID)

// Add the current topic to the topicCache's List
topicCache.List.AppendSubtopicID(topic.Current.Slug, subtopic)

// Mark this topic as processed
processedTopics[topicID] = true
processedTopics[topicID] = struct{}{}

// Process each subtopic recursively
if topic.Current.SubtopicIds != nil {
for _, subTopicID := range *topic.Current.SubtopicIds {
processTopic(ctx, serviceAuthToken, topicClient, subTopicID, topics, processedTopics, topicID)
processTopic(ctx, serviceAuthToken, topicClient, subTopicID, topicCache, processedTopics, topicID, depth+1)
}
}
}
}

func mapTopicModelToCache(topic models.Topic, parentID string) *cache.Topic {
rootTopicCache := &cache.Topic{
func mapTopicModelToCache(topic models.Topic, parentID string) cache.Subtopic {
return cache.Subtopic{
ID: topic.ID,
Slug: topic.Slug,
LocaliseKeyName: topic.Title,
ParentID: parentID,
ReleaseDate: topic.ReleaseDate,
List: cache.NewSubTopicsMap(),
ParentID: parentID,
}
return rootTopicCache
}
Loading