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

feat(tag): add record tagging feature #29

Merged
merged 18 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
19 changes: 19 additions & 0 deletions api/handlers/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package handlers

import (
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/sirupsen/logrus"
)

func writeJSON(w http.ResponseWriter, status int, v interface{}) error {
Expand All @@ -11,6 +15,21 @@ func writeJSON(w http.ResponseWriter, status int, v interface{}) error {
return json.NewEncoder(w).Encode(v)
}

func internalServerError(w http.ResponseWriter, logger logrus.FieldLogger, msg string) error {
ref := time.Now().Unix()

logger.Errorf("ref (%d): %s", ref, msg)
response := &ErrorResponse{
Reason: fmt.Sprintf(
"%s - ref (%d)",
http.StatusText(http.StatusInternalServerError),
ref,
),
}

return writeJSON(w, http.StatusInternalServerError, response)
}

func writeJSONError(w http.ResponseWriter, status int, msg string) error {
response := &ErrorResponse{
Reason: msg,
Expand Down
3 changes: 2 additions & 1 deletion api/handlers/lineage_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func TestLineageHandler(t *testing.T) {
graph := new(mock.Graph)
graph.On(
"Query",
lineage.QueryCfg{TypeWhitelist: []string{"bqtable"}}).Return(lineage.AdjacencyMap{}, models.ErrNoSuchType{"bqtable"})
lineage.QueryCfg{TypeWhitelist: []string{"bqtable"}}).
Return(lineage.AdjacencyMap{}, models.ErrNoSuchType{TypeName: "bqtable"})
lp := new(mock.LineageProvider)
lp.On("Graph").Return(graph, nil)

Expand Down
206 changes: 206 additions & 0 deletions api/handlers/tag_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package handlers

import (
"encoding/json"
"errors"
"fmt"
"net/http"

"github.com/odpf/columbus/tag"
"github.com/sirupsen/logrus"

"github.com/gorilla/mux"
)

var (
errEmptyRecordURN = errors.New("record urn is empty")
errEmptyRecordType = errors.New("type is empty")
errNilTagService = errors.New("tag service is nil")
errEmptyTemplateURN = errors.New("template urn is empty")
)

// TagHandler is handler to manage tag related requests
type TagHandler struct {
service *tag.Service
logger logrus.FieldLogger
}

// NewTagHandler initializes tag handler based on the service
func NewTagHandler(logger logrus.FieldLogger, service *tag.Service) *TagHandler {
if service == nil {
panic("cannot create TagHandler with nil tag.Service")
}

return &TagHandler{
service: service,
logger: logger,
}
}

// Create handles tag creation requests
func (h *TagHandler) Create(w http.ResponseWriter, r *http.Request) {
if h.service == nil {
writeJSONError(w, http.StatusInternalServerError, errNilTagService.Error())
return
}

var requestBody tag.Tag
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
writeJSONError(w, http.StatusBadRequest, err.Error())
return
}

err := h.service.Create(&requestBody)
if errors.As(err, new(tag.TemplateNotFoundError)) {
writeJSONError(w, http.StatusNotFound, err.Error())
return
}
if errors.As(err, new(tag.ValidationError)) {
writeJSONError(w, http.StatusUnprocessableEntity, err.Error())
return
}
if err != nil {
internalServerError(w, h.logger, fmt.Sprintf("error creating tag: %s", err.Error()))
return
}

writeJSON(w, http.StatusCreated, requestBody)
}

// GetByRecord handles get tag by record requests
func (h *TagHandler) GetByRecord(w http.ResponseWriter, r *http.Request) {
if h.service == nil {
writeJSONError(w, http.StatusInternalServerError, errNilTagService.Error())
return
}

muxVar := mux.Vars(r)
recordType, exists := muxVar["type"]
if !exists || recordType == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordType.Error())
return
}
recordURN, exists := muxVar["record_urn"]
if !exists || recordURN == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordURN.Error())
return
}
tags, err := h.service.GetByRecord(recordType, recordURN)
if err != nil {
internalServerError(w, h.logger, fmt.Sprintf("error getting record tags: %s", err.Error()))

} else {
writeJSON(w, http.StatusOK, tags)
}
}

// FindByRecordAndTemplate handles get tag by record requests
func (h *TagHandler) FindByRecordAndTemplate(w http.ResponseWriter, r *http.Request) {
if h.service == nil {
writeJSONError(w, http.StatusInternalServerError, errNilTagService.Error())
return
}

muxVar := mux.Vars(r)
recordType, exists := muxVar["type"]
if !exists || recordType == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordType.Error())
return
}
recordURN, exists := muxVar["record_urn"]
if !exists || recordURN == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordURN.Error())
return
}
templateURN, exists := muxVar["template_urn"]
if !exists || templateURN == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyTemplateURN.Error())
return
}
tags, err := h.service.FindByRecordAndTemplate(recordType, recordURN, templateURN)
if err != nil {
internalServerError(w, h.logger, fmt.Sprintf("error finding a tag with record and template: %s", err.Error()))

} else {
writeJSON(w, http.StatusOK, tags)
}
}

// Update handles tag update requests
func (h *TagHandler) Update(w http.ResponseWriter, r *http.Request) {
if h.service == nil {
writeJSONError(w, http.StatusInternalServerError, errNilTagService.Error())
return
}

muxVar := mux.Vars(r)
recordURN, exists := muxVar["record_urn"]
if !exists || recordURN == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordURN.Error())
return
}
recordType, exists := muxVar["type"]
if !exists || recordType == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordType.Error())
return
}
templateURN, exists := muxVar["template_urn"]
if !exists || templateURN == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyTemplateURN.Error())
return
}

var requestBody tag.Tag
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
writeJSONError(w, http.StatusBadRequest, err.Error())
return
}

requestBody.RecordURN = recordURN
requestBody.RecordType = recordType
requestBody.TemplateURN = templateURN
err := h.service.Update(&requestBody)
if err != nil {
if errors.As(err, new(tag.NotFoundError)) {
writeJSONError(w, http.StatusNotFound, err.Error())
}

internalServerError(w, h.logger, fmt.Sprintf("error updating a template: %s", err.Error()))

return
}

writeJSON(w, http.StatusOK, requestBody)
}

// Delete handles delete tag by record and template requests
func (h *TagHandler) Delete(w http.ResponseWriter, r *http.Request) {
if h.service == nil {
writeJSONError(w, http.StatusInternalServerError, errNilTagService.Error())
return
}
muxVar := mux.Vars(r)
recordType, exists := muxVar["type"]
if !exists || recordType == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordType.Error())
return
}
recordURN, exists := muxVar["record_urn"]
if !exists || recordURN == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyRecordURN.Error())
return
}
templateURN, exists := muxVar["template_urn"]
if !exists || templateURN == "" {
writeJSONError(w, http.StatusBadRequest, errEmptyTemplateURN.Error())
return
}

err := h.service.Delete(recordType, recordURN, templateURN)
if err != nil {
internalServerError(w, h.logger, fmt.Sprintf("error deleting a template: %s", err.Error()))

} else {
writeJSON(w, http.StatusNoContent, nil)
}
}
Loading