feat(views): save view and position in Typesense
This commit is contained in:
parent
5641da27f7
commit
43f24661d7
@ -19,6 +19,8 @@ package models
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/typesense/typesense-go/typesense/api"
|
||||||
|
"github.com/typesense/typesense-go/typesense/api/pointer"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -534,6 +536,15 @@ func (l *AddTaskToTypesense) Handle(msg *message.Message) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = typesenseClient.Collection("tasks").
|
||||||
|
Documents().
|
||||||
|
Delete(context.Background(), &api.DeleteDocumentsParams{
|
||||||
|
FilterBy: pointer.String("task_id:" + strconv.FormatInt(event.Task.ID, 10)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = typesenseClient.Collection("tasks").
|
_, err = typesenseClient.Collection("tasks").
|
||||||
Documents().
|
Documents().
|
||||||
Create(context.Background(), ttask)
|
Create(context.Background(), ttask)
|
||||||
|
@ -27,26 +27,27 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
taskPropertyID string = "id"
|
taskPropertyID string = "id"
|
||||||
taskPropertyTitle string = "title"
|
taskPropertyTitle string = "title"
|
||||||
taskPropertyDescription string = "description"
|
taskPropertyDescription string = "description"
|
||||||
taskPropertyDone string = "done"
|
taskPropertyDone string = "done"
|
||||||
taskPropertyDoneAt string = "done_at"
|
taskPropertyDoneAt string = "done_at"
|
||||||
taskPropertyDueDate string = "due_date"
|
taskPropertyDueDate string = "due_date"
|
||||||
taskPropertyCreatedByID string = "created_by_id"
|
taskPropertyCreatedByID string = "created_by_id"
|
||||||
taskPropertyProjectID string = "project_id"
|
taskPropertyProjectID string = "project_id"
|
||||||
taskPropertyRepeatAfter string = "repeat_after"
|
taskPropertyRepeatAfter string = "repeat_after"
|
||||||
taskPropertyPriority string = "priority"
|
taskPropertyPriority string = "priority"
|
||||||
taskPropertyStartDate string = "start_date"
|
taskPropertyStartDate string = "start_date"
|
||||||
taskPropertyEndDate string = "end_date"
|
taskPropertyEndDate string = "end_date"
|
||||||
taskPropertyHexColor string = "hex_color"
|
taskPropertyHexColor string = "hex_color"
|
||||||
taskPropertyPercentDone string = "percent_done"
|
taskPropertyPercentDone string = "percent_done"
|
||||||
taskPropertyUID string = "uid"
|
taskPropertyUID string = "uid"
|
||||||
taskPropertyCreated string = "created"
|
taskPropertyCreated string = "created"
|
||||||
taskPropertyUpdated string = "updated"
|
taskPropertyUpdated string = "updated"
|
||||||
taskPropertyPosition string = "position"
|
taskPropertyPosition string = "position"
|
||||||
taskPropertyBucketID string = "bucket_id"
|
taskPropertyBucketID string = "bucket_id"
|
||||||
taskPropertyIndex string = "index"
|
taskPropertyIndex string = "index"
|
||||||
|
taskPropertyProjectViewID string = "project_view_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -416,29 +416,6 @@ func convertParsedFilterToTypesense(rawFilters []*taskFilter) (filterBy string,
|
|||||||
|
|
||||||
func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCount int64, err error) {
|
func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCount int64, err error) {
|
||||||
|
|
||||||
var sortbyFields []string
|
|
||||||
for i, param := range opts.sortby {
|
|
||||||
// Validate the params
|
|
||||||
if err := param.validate(); err != nil {
|
|
||||||
return nil, totalCount, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typesense does not allow sorting by ID, so we sort by created timestamp instead
|
|
||||||
if param.sortBy == "id" {
|
|
||||||
param.sortBy = "created"
|
|
||||||
}
|
|
||||||
|
|
||||||
sortbyFields = append(sortbyFields, param.sortBy+"(missing_values:last):"+param.orderBy.String())
|
|
||||||
|
|
||||||
if i == 2 {
|
|
||||||
// Typesense supports up to 3 sorting parameters
|
|
||||||
// https://typesense.org/docs/0.25.0/api/search.html#ranking-and-sorting-parameters
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sortby := strings.Join(sortbyFields, ",")
|
|
||||||
|
|
||||||
projectIDStrings := []string{}
|
projectIDStrings := []string{}
|
||||||
for _, id := range opts.projectIDs {
|
for _, id := range opts.projectIDs {
|
||||||
projectIDStrings = append(projectIDStrings, strconv.FormatInt(id, 10))
|
projectIDStrings = append(projectIDStrings, strconv.FormatInt(id, 10))
|
||||||
@ -454,6 +431,34 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
|
|||||||
"(" + filter + ")",
|
"(" + filter + ")",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sortbyFields []string
|
||||||
|
for i, param := range opts.sortby {
|
||||||
|
// Validate the params
|
||||||
|
if err := param.validate(); err != nil {
|
||||||
|
return nil, totalCount, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typesense does not allow sorting by ID, so we sort by created timestamp instead
|
||||||
|
if param.sortBy == taskPropertyID {
|
||||||
|
param.sortBy = taskPropertyCreated
|
||||||
|
}
|
||||||
|
|
||||||
|
if param.sortBy == taskPropertyPosition {
|
||||||
|
filterBy = append(filterBy, "project_view_id: "+strconv.FormatInt(param.projectViewID, 10))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
sortbyFields = append(sortbyFields, param.sortBy+"(missing_values:last):"+param.orderBy.String())
|
||||||
|
|
||||||
|
if i == 2 {
|
||||||
|
// Typesense supports up to 3 sorting parameters
|
||||||
|
// https://typesense.org/docs/0.25.0/api/search.html#ranking-and-sorting-parameters
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortby := strings.Join(sortbyFields, ",")
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// Actual search
|
// Actual search
|
||||||
|
|
||||||
|
@ -258,7 +258,6 @@ func getTaskIndexFromSearchString(s string) (index int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
|
||||||
func getRawTasksForProjects(s *xorm.Session, projects []*Project, a web.Auth, opts *taskSearchOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
|
func getRawTasksForProjects(s *xorm.Session, projects []*Project, a web.Auth, opts *taskSearchOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
|
||||||
|
|
||||||
// If the user does not have any projects, don't try to get any tasks
|
// If the user does not have any projects, don't try to get any tasks
|
||||||
|
@ -19,6 +19,8 @@ package models
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/config"
|
"code.vikunja.io/api/pkg/config"
|
||||||
@ -157,6 +159,10 @@ func CreateTypesenseCollections() error {
|
|||||||
Name: "created_by_id",
|
Name: "created_by_id",
|
||||||
Type: "int64",
|
Type: "int64",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "project_view_id",
|
||||||
|
Type: "int64",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "reminders",
|
Name: "reminders",
|
||||||
Type: "object[]", // TODO
|
Type: "object[]", // TODO
|
||||||
@ -243,14 +249,17 @@ func ReindexAllTasks() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int64]*Project) (ttask *typesenseTask, err error) {
|
func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int64]*Project) (ttasks []*typesenseTask, err error) {
|
||||||
positions := []*TaskPosition{}
|
positions := []*TaskPosition{}
|
||||||
err = s.Where("task_id = ?", task.ID).Find(&positions)
|
err = s.Where("task_id = ?", task.ID).Find(&positions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ttask = convertTaskToTypesenseTask(task, positions)
|
for _, position := range positions {
|
||||||
|
ttask := convertTaskToTypesenseTask(task, position)
|
||||||
|
ttasks = append(ttasks, ttask)
|
||||||
|
}
|
||||||
|
|
||||||
var p *Project
|
var p *Project
|
||||||
if projectsCache == nil {
|
if projectsCache == nil {
|
||||||
@ -271,11 +280,15 @@ func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int6
|
|||||||
}
|
}
|
||||||
|
|
||||||
comment := &TaskComment{TaskID: task.ID}
|
comment := &TaskComment{TaskID: task.ID}
|
||||||
ttask.Comments, _, _, err = comment.ReadAll(s, &user.User{ID: p.OwnerID}, "", -1, -1)
|
comments, _, _, err := comment.ReadAll(s, &user.User{ID: p.OwnerID}, "", -1, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not fetch comments for task %d: %s", task.ID, err.Error())
|
return nil, fmt.Errorf("could not fetch comments for task %d: %s", task.ID, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, t := range ttasks {
|
||||||
|
t.Comments = comments
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,16 +305,31 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
projects := make(map[int64]*Project)
|
projects := make(map[int64]*Project)
|
||||||
|
|
||||||
typesenseTasks := []interface{}{}
|
typesenseTasks := []interface{}{}
|
||||||
|
taskIDs := []string{}
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
|
|
||||||
ttask, err := getTypesenseTaskForTask(s, task, projects)
|
ttasks, err := getTypesenseTaskForTask(s, task, projects)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
typesenseTasks = append(typesenseTasks, ttask)
|
for _, ttask := range ttasks {
|
||||||
|
typesenseTasks = append(typesenseTasks, ttask)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskIDs = append(taskIDs, strconv.FormatInt(task.ID, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = typesenseClient.Collection("tasks").
|
||||||
|
Documents().
|
||||||
|
Delete(context.Background(), &api.DeleteDocumentsParams{
|
||||||
|
FilterBy: pointer.String("task_id:[" + strings.Join(taskIDs, ",") + "]"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not delete old tasks in Typesense", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = typesenseClient.Collection("tasks").
|
_, err = typesenseClient.Collection("tasks").
|
||||||
@ -398,6 +426,7 @@ func indexDummyTask() (err error) {
|
|||||||
|
|
||||||
type typesenseTask struct {
|
type typesenseTask struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
TaskID string `json:"task_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Done bool `json:"done"`
|
Done bool `json:"done"`
|
||||||
@ -422,17 +451,22 @@ type typesenseTask struct {
|
|||||||
Assignees interface{} `json:"assignees"`
|
Assignees interface{} `json:"assignees"`
|
||||||
Labels interface{} `json:"labels"`
|
Labels interface{} `json:"labels"`
|
||||||
//RelatedTasks interface{} `json:"related_tasks"` // TODO
|
//RelatedTasks interface{} `json:"related_tasks"` // TODO
|
||||||
Attachments interface{} `json:"attachments"`
|
Attachments interface{} `json:"attachments"`
|
||||||
Comments interface{} `json:"comments"`
|
Comments interface{} `json:"comments"`
|
||||||
Positions []*struct {
|
Position float64 `json:"position"`
|
||||||
Position float64 `json:"position"`
|
ProjectViewID int64 `json:"project_view_id"`
|
||||||
ProjectViewID int64 `json:"project_view_id"`
|
|
||||||
} `json:"positions"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesenseTask {
|
func convertTaskToTypesenseTask(task *Task, position *TaskPosition) *typesenseTask {
|
||||||
|
|
||||||
|
var projectViewID int64
|
||||||
|
if position != nil {
|
||||||
|
projectViewID = position.ProjectViewID
|
||||||
|
}
|
||||||
|
|
||||||
tt := &typesenseTask{
|
tt := &typesenseTask{
|
||||||
ID: fmt.Sprintf("%d", task.ID),
|
ID: fmt.Sprintf("%d_%d", task.ID, projectViewID),
|
||||||
|
TaskID: fmt.Sprintf("%d", task.ID),
|
||||||
Title: task.Title,
|
Title: task.Title,
|
||||||
Description: task.Description,
|
Description: task.Description,
|
||||||
Done: task.Done,
|
Done: task.Done,
|
||||||
@ -457,7 +491,9 @@ func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesens
|
|||||||
Assignees: task.Assignees,
|
Assignees: task.Assignees,
|
||||||
Labels: task.Labels,
|
Labels: task.Labels,
|
||||||
//RelatedTasks: task.RelatedTasks,
|
//RelatedTasks: task.RelatedTasks,
|
||||||
Attachments: task.Attachments,
|
Attachments: task.Attachments,
|
||||||
|
Position: position.Position,
|
||||||
|
ProjectViewID: projectViewID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.DoneAt.IsZero() {
|
if task.DoneAt.IsZero() {
|
||||||
@ -473,16 +509,6 @@ func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesens
|
|||||||
tt.EndDate = nil
|
tt.EndDate = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, position := range positions {
|
|
||||||
tt.Positions = append(tt.Positions, &struct {
|
|
||||||
Position float64 `json:"position"`
|
|
||||||
ProjectViewID int64 `json:"project_view_id"`
|
|
||||||
}{
|
|
||||||
Position: position.Position,
|
|
||||||
ProjectViewID: position.ProjectViewID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return tt
|
return tt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user