From 47b7877ab44b31de22b17cc6c6001302ccae6479 Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Sun, 9 Aug 2020 15:35:49 +0200 Subject: [PATCH] add nextLogs and prevLogs suggestions to log. --- database/mongodb/logs.go | 7 +++ graph2/complexity.go | 6 +++ graph2/gqlgen.yml | 2 + graph2/schema/types/Log.gql | 14 ++++++ graph2/types/log.go | 8 +++ models/log.go | 10 ++++ services/logs.go | 99 +++++++++++++++++++++++++++++++++++++ 7 files changed, 146 insertions(+) diff --git a/database/mongodb/logs.go b/database/mongodb/logs.go index a31318d..03507eb 100644 --- a/database/mongodb/logs.go +++ b/database/mongodb/logs.go @@ -101,6 +101,13 @@ func (r *logRepository) List(ctx context.Context, filter models.LogFilter) ([]*m if len(filter.Events) > 0 { query["event"] = bson.M{"$in": filter.Events} } + if filter.MinDate != nil && filter.MaxDate != nil { + query["date"] = bson.M{"$gte": *filter.MinDate, "$lte": *filter.MaxDate} + } else if filter.MinDate != nil { + query["date"] = bson.M{"$gte": *filter.MinDate} + } else if filter.MaxDate != nil { + query["date"] = bson.M{"$lt": *filter.MaxDate} + } logs := make([]*models.Log, 0, 32) err := r.logs.Find(query).Sort("-date").Limit(filter.Limit).All(&logs) diff --git a/graph2/complexity.go b/graph2/complexity.go index 18f5ec9..d60d3ca 100644 --- a/graph2/complexity.go +++ b/graph2/complexity.go @@ -164,6 +164,12 @@ func complexity() (cr graphcore.ComplexityRoot) { cr.Log.Posts = func(childComplexity int, kinds []string) int { return childComplexity + hugeSublistComplexity } + cr.Log.NextLogs = func(childComplexity int) int { + return childComplexity + (subListComplexity / 2) + } + cr.Log.PrevLogs = func(childComplexity int) int { + return childComplexity + (subListComplexity / 2) + } cr.Story.Chapters = func(childComplexity int) int { return childComplexity + bigSublistComplexity diff --git a/graph2/gqlgen.yml b/graph2/gqlgen.yml index b43ebe6..7322231 100644 --- a/graph2/gqlgen.yml +++ b/graph2/gqlgen.yml @@ -37,6 +37,8 @@ models: model: git.aiterp.net/rpdata/api/models.LogFilter LogImporter: model: git.aiterp.net/rpdata/api/models.LogImporter + LogSuggestion: + model: git.aiterp.net/rpdata/api/models.LogSuggestion Comment: model: git.aiterp.net/rpdata/api/models.Comment ChapterCommentMode: diff --git a/graph2/schema/types/Log.gql b/graph2/schema/types/Log.gql index 563ad9e..5aa125b 100644 --- a/graph2/schema/types/Log.gql +++ b/graph2/schema/types/Log.gql @@ -34,6 +34,20 @@ type Log { # The posts of the logfile, which can be filtered by kinds. posts(kinds:[String!]): [Post!]! + + # Suggested next logs. + nextLogs: [LogSuggestion!]! + + # Suggested previous logs. + prevLogs: [LogSuggestion!]! +} + +# Suggested log +type LogSuggestion { + log: Log! + hasEvent: Boolean! + hasChannel: Boolean! + characters: [Character!]! } # Filter for logs query diff --git a/graph2/types/log.go b/graph2/types/log.go index f140f61..f5d3a67 100644 --- a/graph2/types/log.go +++ b/graph2/types/log.go @@ -31,6 +31,14 @@ func (r *logResolver) Posts(ctx context.Context, log *models.Log, kinds []string return r.logs.ListPosts(ctx, &models.PostFilter{LogID: &log.ShortID}) } +func (r *logResolver) NextLogs(ctx context.Context, obj *models.Log) ([]*models.LogSuggestion, error) { + return r.logs.NextLogs(ctx, obj) +} + +func (r *logResolver) PrevLogs(ctx context.Context, obj *models.Log) ([]*models.LogSuggestion, error) { + return r.logs.PrevLogs(ctx, obj) +} + // LogResolver is a resolver func LogResolver(s *services.Bundle) *logResolver { return &logResolver{characters: s.Characters, logs: s.Logs, channels: s.Channels} diff --git a/models/log.go b/models/log.go index 91ffb2d..01424d8 100644 --- a/models/log.go +++ b/models/log.go @@ -15,6 +15,14 @@ type Log struct { CharacterIDs []string `bson:"characterIds"` } +// A LogSuggestion is a suggestion for a log. +type LogSuggestion struct { + Log *Log + Characters []*Character + HasChannel bool + HasEvent bool +} + // IsChangeObject is an interface implementation to identify it as a valid // ChangeObject in GQL. func (*Log) IsChangeObject() { @@ -28,6 +36,8 @@ type LogFilter struct { Characters []string Channels []string Events []string + MinDate *time.Time + MaxDate *time.Time Limit int } diff --git a/services/logs.go b/services/logs.go index 9cdbb10..157f1fb 100644 --- a/services/logs.go +++ b/services/logs.go @@ -783,3 +783,102 @@ func (s *LogService) makeCharacterMap(characters []*models.Character) map[string return characterMap } + +func (s *LogService) NextLogs(ctx context.Context, log *models.Log) ([]*models.LogSuggestion, error) { + minDate := log.Date.Add(time.Millisecond) + logs, err := s.logs.List(ctx, models.LogFilter{ + MinDate: &minDate, + }) + if err != nil { + return nil, err + } + + sort.Slice(logs, func(i, j int) bool { + return logs[i].Date.Before(logs[j].Date) + }) + + return s.findSuggestions(ctx, log, logs) +} + +func (s *LogService) PrevLogs(ctx context.Context, log *models.Log) ([]*models.LogSuggestion, error) { + logs, err := s.logs.List(ctx, models.LogFilter{ + MaxDate: &log.Date, + }) + if err != nil { + return nil, err + } + + return s.findSuggestions(ctx, log, logs) +} + +func (s *LogService) findSuggestions(ctx context.Context, log *models.Log, logs []*models.Log) ([]*models.LogSuggestion, error) { + characters, err := s.characterService.List(ctx, models.CharacterFilter{ + IDs: log.CharacterIDs, + }) + if err != nil { + return nil, err + } + + charIntersect := func(l1, l2 *models.Log) []*models.Character { + results := make([]*models.Character, 0, len(l1.CharacterIDs)) + for _, c1 := range characters { + for _, c2ID := range l2.CharacterIDs { + if c1.ID == c2ID { + results = append(results, c1) + break + } + } + } + + return results + } + groupKey := func(characters []*models.Character) string { + if len(characters) == 0 { + return "" + } else if len(characters) == 1 { + return characters[0].ID + } + + builder := strings.Builder{} + builder.WriteString(characters[0].ID) + + for _, character := range characters { + builder.WriteRune(',') + builder.WriteString(character.ID) + } + + return builder.String() + } + + suggestions := make([]*models.LogSuggestion, 0, 16) + foundGroups := make(map[string]bool) + foundChannel := false + + for _, log2 := range logs { + hasEvent := log.EventName != "" && log2.EventName == log.EventName + hasChannel := log.ChannelName == log2.ChannelName + characters := charIntersect(log, log2) + groupKey := groupKey(characters) + + suggestion := &models.LogSuggestion{ + Log: log2, + Characters: characters, + HasChannel: hasChannel, + HasEvent: hasEvent, + } + + if hasChannel && foundChannel { + foundChannel = true + foundGroups[groupKey] = true + suggestions = append(suggestions, suggestion) + } else if hasEvent { + foundGroups[groupKey] = true + suggestions = append(suggestions, suggestion) + } else if len(suggestions) < 8 && !hasEvent && len(characters) > 1 && !foundGroups[groupKey] { + foundGroups[groupKey] = true + suggestions = append(suggestions, suggestion) + } + } + + return suggestions, nil +}