Skip to content
This repository has been archived by the owner on Mar 11, 2021. It is now read-only.

WIP: Update rendering package with minimal influence #2077

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion comment/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/fabric8-services/fabric8-wit/gormsupport"
"github.com/fabric8-services/fabric8-wit/rendering"
uuid "github.com/satori/go.uuid"
)

Expand All @@ -15,7 +16,7 @@ type Comment struct {
ParentID uuid.UUID `sql:"type:uuid"`
Creator uuid.UUID `sql:"type:uuid"` // Belongs To Identity
Body string
Markup string
Markup rendering.Markup
}

// TableName overrides the table name settings in Gorm to force a specific table name
Expand Down
2 changes: 1 addition & 1 deletion comment/comment_repository_blackbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (s *TestCommentRepository) SetupTest() {
s.repo = comment.NewRepository(s.DB)
}

func newComment(parentID uuid.UUID, body, markup string) *comment.Comment {
func newComment(parentID uuid.UUID, body string, markup rendering.Markup) *comment.Comment {
return &comment.Comment{
ParentID: parentID,
Body: body,
Expand Down
3 changes: 2 additions & 1 deletion comment/comment_revision_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/fabric8-services/fabric8-wit/errors"
"github.com/fabric8-services/fabric8-wit/log"
"github.com/fabric8-services/fabric8-wit/ptr"
"github.com/jinzhu/gorm"
errs "github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
Expand Down Expand Up @@ -45,7 +46,7 @@ func (r *GormCommentRevisionRepository) Create(ctx context.Context, modifierID u
CommentID: c.ID,
CommentParentID: c.ParentID,
CommentBody: &c.Body,
CommentMarkup: &c.Markup,
CommentMarkup: ptr.String(c.Markup.String()),
}
if revision.Type == RevisionTypeDelete {
revision.CommentBody = nil
Expand Down
2 changes: 1 addition & 1 deletion controller/comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func ConvertComment(request *http.Request, comment comment.Comment, additional .
Attributes: &app.CommentAttributes{
Body: &comment.Body,
BodyRendered: ptr.String(rendering.RenderMarkupToHTML(html.EscapeString(comment.Body), comment.Markup)),
Markup: ptr.String(rendering.NilSafeGetMarkup(&comment.Markup)),
Markup: ptr.String(comment.Markup.NilSafeGetMarkup().String()),
CreatedAt: &comment.CreatedAt,
UpdatedAt: &comment.UpdatedAt,
},
Expand Down
4 changes: 2 additions & 2 deletions controller/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ func NewRenderController(service *goa.Service) *RenderController {
func (c *RenderController) Render(ctx *app.RenderRenderContext) error {
content := ctx.Payload.Data.Attributes.Content
markup := ctx.Payload.Data.Attributes.Markup
if !rendering.IsMarkupSupported(markup) {
if err := rendering.Markup(markup).CheckValid(); err != nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewBadParameterError("Unsupported markup type", markup))
}
htmlResult := rendering.RenderMarkupToHTML(content, markup)
htmlResult := rendering.RenderMarkupToHTML(content, rendering.Markup(markup))
res := &app.MarkupRenderingSingle{Data: &app.MarkupRenderingData{
ID: uuid.NewV4().String(),
Type: RenderingType,
Expand Down
31 changes: 20 additions & 11 deletions controller/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ func (c *SearchController) Show(ctx *app.ShowSearchContext) error {
return ctx.OK(&response)
}
var result []workitem.WorkItem
var wits []workitem.WorkItemType
var count int
err := application.Transactional(c.db, func(appl application.Application) error {
if ctx.Q == nil || *ctx.Q == "" {
Expand All @@ -274,15 +275,15 @@ func (c *SearchController) Show(ctx *app.ShowSearchContext) error {
return goa.ErrInternal(fmt.Sprintf("unable to list the work items expression: %s: %s", *ctx.Q, err))
}
}
wits, err = loadWorkItemTypesFromArr(ctx.Context, appl, result)
if err != nil {
return errs.WithStack(err)
}
return nil
})
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}
wits, err := loadWorkItemTypesFromArr(ctx.Context, c.db, result)
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}
wis, err := ConvertWorkItems(ctx.Request, wits, result)
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
Expand Down Expand Up @@ -365,10 +366,22 @@ func (c *SearchController) enrichWorkItemList(ctx *app.ShowSearchContext, ancest
fetchInBatch = fetchInBatch.Sub(matchingIDs)

wis := []*workitem.WorkItem{}
var wits []workitem.WorkItemType
err := application.Transactional(c.db, func(appl application.Application) error {
var err error
wis, err = appl.WorkItems().LoadBatchByID(ctx, fetchInBatch)
return err
if err != nil {
return errs.WithStack(err)
}
wits = make([]workitem.WorkItemType, len(wis))
for i := 0; i < len(wis); i++ {
wit, err := appl.WorkItemTypes().Load(ctx.Context, wis[i].Type)
if err != nil {
return errs.Wrapf(err, "failed to load work item type: %s", wis[i].Type)
}
wits[i] = *wit
}
return nil
})
if err != nil {
log.Error(ctx, map[string]interface{}{
Expand All @@ -378,12 +391,8 @@ func (c *SearchController) enrichWorkItemList(ctx *app.ShowSearchContext, ancest
return errs.Wrapf(err, "unable to load work item items in batch: %s", fetchInBatch)
}

for _, ele := range wis {
wit, err := c.db.WorkItemTypes().Load(ctx.Context, ele.Type)
if err != nil {
return errs.Wrapf(err, "failed to load work item type: %s", ele.Type)
}
convertedWI, err := ConvertWorkItem(ctx.Request, *wit, *ele, hasChildren, includeParentWorkItem(ctx, ancestors, childLinks))
for i := 0; i < len(wis); i++ {
convertedWI, err := ConvertWorkItem(ctx.Request, wits[i], *wis[i], hasChildren, includeParentWorkItem(ctx, ancestors, childLinks))
if err != nil {
return errs.WithStack(err)
}
Expand Down
10 changes: 7 additions & 3 deletions controller/workitem.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,11 @@ func ConvertJSONAPIToWorkItem(ctx context.Context, method string, appl applicati
markup := val.(string)
// if no description existed before, set the markup in a new one
if target.Fields[workitem.SystemDescription] == nil {
target.Fields[workitem.SystemDescription] = rendering.MarkupContent{Markup: markup}
target.Fields[workitem.SystemDescription] = rendering.MarkupContent{Markup: rendering.Markup(markup)}
} else {
// only update the 'description' field in the existing description
existingDescription := target.Fields[workitem.SystemDescription].(rendering.MarkupContent)
existingDescription.Markup = markup
existingDescription.Markup = rendering.Markup(markup)
target.Fields[workitem.SystemDescription] = existingDescription
}
case workitem.SystemCodebase:
Expand All @@ -425,7 +425,7 @@ func ConvertJSONAPIToWorkItem(ctx context.Context, method string, appl applicati
}
if description, ok := target.Fields[workitem.SystemDescription].(rendering.MarkupContent); ok {
// verify the description markup
if !rendering.IsMarkupSupported(description.Markup) {
if err := description.Markup.CheckValid(); err != nil {
return errors.NewBadParameterError("data.relationships.attributes[system.description].markup", description.Markup)
}
}
Expand Down Expand Up @@ -713,6 +713,10 @@ func (c *WorkitemController) ListChildren(ctx *app.ListChildrenWorkitemContext)
return ctx.ConditionalEntities(result, c.config.GetCacheControlWorkItems, func() error {
var response app.WorkItemList
application.Transactional(c.db, func(appl application.Application) error {
wits, err := loadWorkItemTypesFromArr(ctx.Context, appl, result)
if err != nil {
return errs.Wrapf(err, "failed to load the work item types")
}
hasChildren := workItemIncludeHasChildren(ctx, appl)
converted, err := ConvertWorkItems(ctx.Request, wits, result, hasChildren)
if err != nil {
Expand Down
20 changes: 10 additions & 10 deletions remoteworkitem/remoteworkitem.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const (
var RemoteWorkItemKeyMaps = map[string]RemoteWorkItemMap{
ProviderGithub: {
AttributeMapper{AttributeExpression(GithubTitle), StringConverter{}}: remoteTitle,
AttributeMapper{AttributeExpression(GithubDescription), MarkupConverter{markup: rendering.SystemMarkupMarkdown}}: remoteDescription,
AttributeMapper{AttributeExpression(GithubDescription), MarkupConverter{markup: rendering.SystemMarkupMarkdown.String()}}: remoteDescription,
AttributeMapper{AttributeExpression(GithubState), GithubStateConverter{}}: remoteState,
AttributeMapper{AttributeExpression(GithubID), StringConverter{}}: remoteItemID,
AttributeMapper{AttributeExpression(GithubCreatorLogin), StringConverter{}}: remoteCreatorLogin,
Expand All @@ -73,14 +73,14 @@ var RemoteWorkItemKeyMaps = map[string]RemoteWorkItemMap{
AttributeMapper{AttributeExpression(GithubAssigneesProfileURL), PatternToListConverter{pattern: GithubAssigneesProfileURLPattern}}: RemoteAssigneeProfileURLs,
},
ProviderJira: {
AttributeMapper{AttributeExpression(JiraTitle), StringConverter{}}: remoteTitle,
AttributeMapper{AttributeExpression(JiraBody), MarkupConverter{markup: rendering.SystemMarkupJiraWiki}}: remoteDescription,
AttributeMapper{AttributeExpression(JiraState), JiraStateConverter{}}: remoteState,
AttributeMapper{AttributeExpression(JiraID), StringConverter{}}: remoteItemID,
AttributeMapper{AttributeExpression(JiraCreatorLogin), StringConverter{}}: remoteCreatorLogin,
AttributeMapper{AttributeExpression(JiraCreatorProfileURL), StringConverter{}}: remoteCreatorProfileURL,
AttributeMapper{AttributeExpression(JiraAssigneeLogin), ListConverter{}}: RemoteAssigneeLogins,
AttributeMapper{AttributeExpression(JiraAssigneeProfileURL), ListConverter{}}: RemoteAssigneeProfileURLs,
AttributeMapper{AttributeExpression(JiraTitle), StringConverter{}}: remoteTitle,
AttributeMapper{AttributeExpression(JiraBody), MarkupConverter{markup: rendering.SystemMarkupJiraWiki.String()}}: remoteDescription,
AttributeMapper{AttributeExpression(JiraState), JiraStateConverter{}}: remoteState,
AttributeMapper{AttributeExpression(JiraID), StringConverter{}}: remoteItemID,
AttributeMapper{AttributeExpression(JiraCreatorLogin), StringConverter{}}: remoteCreatorLogin,
AttributeMapper{AttributeExpression(JiraCreatorProfileURL), StringConverter{}}: remoteCreatorProfileURL,
AttributeMapper{AttributeExpression(JiraAssigneeLogin), ListConverter{}}: RemoteAssigneeLogins,
AttributeMapper{AttributeExpression(JiraAssigneeProfileURL), ListConverter{}}: RemoteAssigneeProfileURLs,
},
}

Expand Down Expand Up @@ -152,7 +152,7 @@ func (converter MarkupConverter) Convert(value interface{}, item AttributeAccess
}
switch value.(type) {
case string:
return rendering.NewMarkupContent(value.(string), converter.markup), nil
return rendering.NewMarkupContent(value.(string), rendering.Markup(converter.markup)), nil
default:
return nil, errors.Errorf("Unexpected type of value to convert: %T", value)
}
Expand Down
8 changes: 4 additions & 4 deletions rendering/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const (
blackfriday.EXTENSION_DEFINITION_LISTS
)

// MarkdownCommonHighlighter uses the blackfriday.MarkdownCommon setup but also includes
// code-prettify formatting of BlockCode segments
// MarkdownCommonHighlighter uses the blackfriday.MarkdownCommon setup but also
// includes code-prettify formatting of BlockCode segments
func MarkdownCommonHighlighter(input []byte) []byte {
renderer := highlightHTMLRenderer{blackfriday.HtmlRenderer(commonHTMLFlags, "", "")}
return blackfriday.MarkdownOptions(input, renderer, blackfriday.Options{
Expand All @@ -41,8 +41,8 @@ type highlightHTMLRenderer struct {
blackfriday.Renderer
}

// BlackCode overrides the standard Html Renderer to add support for prettify of source code within block
// If highlighter fail, normal Html.BlockCode is called
// BlackCode overrides the standard Html Renderer to add support for prettify of
// source code within block If highlighter fail, normal Html.BlockCode is called
func (h highlightHTMLRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
highlighted, err := syntaxhighlight.AsHTML(text)
if err != nil {
Expand Down
91 changes: 60 additions & 31 deletions rendering/markup_content.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,31 @@ package rendering

// MarkupContent defines the raw content of a field along with the markup language used to input the content.
type MarkupContent struct {
Content string `json:"content"`
Markup string `json:"markup"`
Content string `json:"content"`
Markup Markup `json:"markup"`
Rendered string `json:"rendered,omitempty"`
}

const (
// the key for the 'content' field when the MarkupContent is converted into/from a Map
// ContentKey is the key for the 'content' field when the MarkupContent is
// converted into/from a Map
ContentKey = "content"
// the key for the 'markup' field when the MarkupContent is converted into/from a Map
// MarkupKey is the key for the 'markup' field when the MarkupContent is
// converted into/from a Map
MarkupKey = "markup"
// RenderedKey is the key for the 'rendered' field when the MarkupContent is
// converted into/from a Map
RenderedKey = "rendered"
)

func (markupContent *MarkupContent) ToMap() map[string]interface{} {
result := make(map[string]interface{})
result[ContentKey] = markupContent.Content
if markupContent.Markup == "" {
result[MarkupKey] = SystemMarkupDefault
} else {
// ToMap returns the markup content object as a map
func (markupContent MarkupContent) ToMap() map[string]interface{} {
result := map[string]interface{}{
ContentKey: markupContent.Content,
RenderedKey: markupContent.Rendered,
MarkupKey: SystemMarkupDefault,
}
if markupContent.Markup != "" {
result[MarkupKey] = markupContent.Markup
}
return result
Expand All @@ -28,16 +36,28 @@ func (markupContent *MarkupContent) ToMap() map[string]interface{} {
// filling the 'Markup' field with the default value if no entry was found in the input or if the given markup is not supported.
// This avoids filling the DB with invalid markup types.
func NewMarkupContentFromMap(value map[string]interface{}) MarkupContent {
content := value[ContentKey].(string)
markup := SystemMarkupDefault
if m, ok := value[MarkupKey]; ok {
markup = m.(string)
// use default markup if the input is not supported
if !IsMarkupSupported(markup) {
markup = SystemMarkupDefault
res := MarkupContent{
Markup: SystemMarkupDefault,
}
if value == nil {
return res
}
if content, ok := value[ContentKey].(string); ok {
res.Content = content
}
if rendered, ok := value[RenderedKey].(string); ok {
res.Rendered = rendered
}
if markupVal, ok := value[MarkupKey]; ok {
if markupStr, ok := markupVal.(string); ok {
markup := Markup(markupStr)
// use default markup if the input is not supported
if err := markup.CheckValid(); err == nil {
res.Markup = markup
}
}
}
return MarkupContent{Content: content, Markup: markup}
return res
}

// NewMarkupContentFromLegacy creates a MarkupContent from the given content, using the default markup.
Expand All @@ -46,35 +66,44 @@ func NewMarkupContentFromLegacy(content string) MarkupContent {
}

// NewMarkupContent creates a MarkupContent from the given content, using the default markup.
func NewMarkupContent(content, markup string) MarkupContent {
func NewMarkupContent(content string, markup Markup) MarkupContent {
return MarkupContent{Content: content, Markup: markup}
}

// NewMarkupContentFromValue creates a MarkupContent from the given value,
// by converting a 'string', a 'map[string]interface{}' or casting a 'MarkupContent'. Otherwise, it returns nil.
// NewMarkupContentFromValue creates a MarkupContent from the given value, by
// converting a 'string', a 'map[string]interface{}', or a 'MarkupContent';
// otherwise, it returns nil.
func NewMarkupContentFromValue(value interface{}) *MarkupContent {
if value == nil {
return nil
}
switch value.(type) {
switch t := value.(type) {
case string:
result := NewMarkupContentFromLegacy(value.(string))
result := NewMarkupContentFromLegacy(t)
return &result
case MarkupContent:
result := value.(MarkupContent)
return &result
return &t
case map[string]interface{}:
result := NewMarkupContentFromMap(value.(map[string]interface{}))
result := NewMarkupContentFromMap(t)
return &result
default:
return nil
}
}

// NilSafeGetMarkup returns the given markup if it is not nil nor empty, otherwise it returns the default markup
func NilSafeGetMarkup(markup *string) string {
if markup != nil && *markup != "" {
return *markup
// NilSafeGetMarkup returns the given markup if it is not nil, nor empty, nor
// invalid; otherwise it returns the default markup.
func NilSafeGetMarkup(markup *string) Markup {
res := SystemMarkupDefault
if markup == nil {
return res
}
if *markup == "" {
return res
}
m := Markup(*markup)
if m.CheckValid() != nil {
return res
}
return SystemMarkupDefault
return m
}
Loading