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

Save projects in Algolia #447

Merged
merged 4 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all 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 configs/config.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ algolia {
internal_index_name = "internal"
links_index_name = "links"
missing_fields_index_name = "missing_fields"
projects_index_name = "projects"
search_api_key = ""
write_api_key = ""
}
Expand Down Expand Up @@ -96,6 +97,11 @@ feature_flags {
flag "api_v2" {
enabled = false
}

// projects enables the projects feature in the UI.
flag "projects" {
enabled = false
}
}

// google_workspace configures Hermes to work with Google Workspace.
Expand Down
63 changes: 60 additions & 3 deletions internal/api/v2/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/hashicorp-forge/hermes/internal/server"
"github.com/hashicorp-forge/hermes/pkg/algolia"
"github.com/hashicorp-forge/hermes/pkg/models"
"gorm.io/gorm"
)
Expand Down Expand Up @@ -177,6 +178,19 @@ func ProjectsHandler(srv server.Server) http.Handler {

srv.Logger.Info("created project", logArgs...)

// Request post-processing.
go func() {
// Save project in Algolia.
if err := saveProjectInAlgolia(proj, srv.AlgoWrite); err != nil {
srv.Logger.Error("error saving project in Algolia",
append([]interface{}{
"error", err,
}, logArgs...)...,
)
return
}
}()

default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
Expand Down Expand Up @@ -303,11 +317,11 @@ func ProjectHandler(srv server.Server) http.Handler {
switch strings.ToLower(*req.Status) {
case "active":
case "archived":
case "complete":
case "completed":
default:
http.Error(w,
"Bad request: invalid status"+
` (valid values are "active", "archived", "complete")`,
` (valid values are "active", "archived", "completed")`,
http.StatusBadRequest)
return
}
Expand Down Expand Up @@ -354,7 +368,7 @@ func ProjectHandler(srv server.Server) http.Handler {
patch.Status = models.ActiveProjectStatus
case "archived":
patch.Status = models.ArchivedProjectStatus
case "complete":
case "completed":
patch.Status = models.CompletedProjectStatus
}
}
Expand All @@ -375,6 +389,19 @@ func ProjectHandler(srv server.Server) http.Handler {

srv.Logger.Info("updated project", logArgs...)

// Request post-processing.
go func() {
// Save project in Algolia.
if err := saveProjectInAlgolia(patch, srv.AlgoWrite); err != nil {
srv.Logger.Error("error saving project in Algolia",
append([]interface{}{
"error", err,
}, logArgs...)...,
)
return
}
}()

default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
Expand Down Expand Up @@ -409,3 +436,33 @@ func getProjectIDFromPath(path string, re *regexp.Regexp) (uint, error) {

return uint(projectID), nil
}

// saveProjectInAlgolia saves a project in Algolia.
func saveProjectInAlgolia(
proj models.Project,
algoClient *algolia.Client,
) error {
// Convert project to Algolia object.
projObj := map[string]any{
"createdTime": proj.ProjectCreatedAt.Unix(),
"creator": proj.Creator.EmailAddress,
Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense to use creator instead of owner for projects.

"description": proj.Description,
"jiraIssueID": proj.JiraIssueID,
"modifiedTime": proj.ProjectModifiedAt.Unix(),
"objectID": fmt.Sprintf("%d", proj.ID),
"status": proj.Status.ToString(),
"title": proj.Title,
}

// Save project in Algolia.
res, err := algoClient.Projects.SaveObject(projObj)
if err != nil {
return fmt.Errorf("error saving object: %w", err)
}
err = res.Wait()
if err != nil {
return fmt.Errorf("error waiting for save: %w", err)
}

return nil
}
9 changes: 9 additions & 0 deletions pkg/algolia/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type Client struct {
// MissingFields is an Algolia index for storing missing fields from indexed
// documents.
MissingFields *search.Index

// Projects is an Algolia index for storing projects.
Projects *search.Index
}

// Config is the configuration for interacting with the Algolia API.
Expand All @@ -78,6 +81,9 @@ type Config struct {
// fields from indexed documents.
MissingFieldsIndexName string `hcl:"missing_fields_index_name,optional"`

// ProjectsIndexName is the name of the Algolia index for storing projects.
ProjectsIndexName string `hcl:"projects_index_name,optional"`

// SearchAPIKey is the Algolia API Key for searching Hermes indices.
SearchAPIKey string `hcl:"search_api_key,optional"`

Expand Down Expand Up @@ -107,6 +113,7 @@ func New(cfg *Config) (*Client, error) {
c.Internal = a.InitIndex(cfg.InternalIndexName)
c.Links = a.InitIndex(cfg.LinksIndexName)
c.MissingFields = a.InitIndex(cfg.MissingFieldsIndexName)
c.Projects = a.InitIndex(cfg.ProjectsIndexName)

// Configure the docs index.
err := configureMainIndex(cfg.DocsIndexName, c.Docs, search.Settings{
Expand Down Expand Up @@ -297,6 +304,7 @@ func NewSearchClient(cfg *Config) (*Client, error) {
c.DraftsModifiedTimeDesc = a.InitIndex(cfg.DraftsIndexName + "_modifiedTime_desc")
c.Internal = a.InitIndex(cfg.InternalIndexName)
c.Links = a.InitIndex(cfg.LinksIndexName)
c.Projects = a.InitIndex(cfg.ProjectsIndexName)

return c, nil
}
Expand All @@ -310,6 +318,7 @@ func validate(c *Config) error {
validation.Field(&c.InternalIndexName, validation.Required),
validation.Field(&c.LinksIndexName, validation.Required),
validation.Field(&c.MissingFieldsIndexName, validation.Required),
validation.Field(&c.ProjectsIndexName, validation.Required),
validation.Field(&c.SearchAPIKey, validation.Required),
)
}