Skip to content

Commit

Permalink
Merge pull request #11 from atc0005/i6-extend-message-card
Browse files Browse the repository at this point in the history
Extend MessageCard type
  • Loading branch information
dasrick authored Apr 8, 2020
2 parents aab6d40 + 7a30a4f commit 52b2f9f
Show file tree
Hide file tree
Showing 2 changed files with 381 additions and 17 deletions.
338 changes: 338 additions & 0 deletions messagecard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
package goteamsnotify

import (
"errors"
"fmt"
"strings"
)

// MessageCardSectionFact represents a section fact entry that is usually
// displayed in a two-column key/value format.
type MessageCardSectionFact struct {

// Name is the key for an associated value in a key/value pair
Name string `json:"name"`

// Value is the value for an associated key in a key/value pair
Value string `json:"value"`
}

// MessageCardSectionImage represents an image as used by the heroImage and
// images properties of a section.
type MessageCardSectionImage struct {

// Image is the URL to the image.
Image string `json:"image"`

// Title is a short description of the image. Typically, this description
// is displayed in a tooltip as the user hovers their mouse over the
// image.
Title string `json:"title"`
}

// MessageCardSection represents a section to include in a message card.
type MessageCardSection struct {

// Title is the title property of a section. This property is displayed
// in a font that stands out, while not as prominent as the card's title.
// It is meant to introduce the section and summarize its content,
// similarly to how the card's title property is meant to summarize the
// whole card.
Title string `json:"title,omitempty"`

// Text is the section's text property. This property is very similar to
// the text property of the card. It can be used for the same purpose.
Text string `json:"text,omitempty"`

// ActivityImage is a property used to display a picture associated with
// the subject of a message card. For example, this might be the portrait
// of a person who performed an activity that the message card is
// associated with.
ActivityImage string `json:"activityImage,omitempty"`

// ActivityTitle is a property used to summarize the activity associated
// with a message card.
ActivityTitle string `json:"activityTitle,omitempty"`

// ActivitySubtitle is a property used to show brief, but extended
// information about an activity associated with a message card. Examples
// include the date and time the associated activity was taken or the
// handle of a person associated with the activity.
ActivitySubtitle string `json:"activitySubtitle,omitempty"`

// ActivityText is a property used to provide details about the activity.
// For example, if the message card is used to deliver updates about a
// topic, then this property would be used to hold the bulk of the content
// for the update notification.
ActivityText string `json:"activityText,omitempty"`

// Markdown represents a toggle to enable or disable Markdown formatting.
// By default, all text fields in a card and its sections can be formatted
// using basic Markdown.
Markdown bool `json:"markdown,omitempty"`

// StartGroup is the section's startGroup property. This property marks
// the start of a logical group of information. Typically, sections with
// startGroup set to true will be visually separated from previous card
// elements.
StartGroup bool `json:"startGroup,omitempty"`

// HeroImage is a property that allows for setting an image as the
// centerpiece of a message card. This property can also be used to add a
// banner to the message card.
// Note: heroImage is not currently supported by Microsoft Teams
// https://stackoverflow.com/a/45389789
// We use a pointer to this type in order to have the json package
// properly omit this field if not explicitly set.
// https://github.com/golang/go/issues/11939
// https://stackoverflow.com/questions/18088294/how-to-not-marshal-an-empty-struct-into-json-with-go
// https://stackoverflow.com/questions/33447334/golang-json-marshal-how-to-omit-empty-nested-struct
HeroImage *MessageCardSectionImage `json:"heroImage,omitempty"`

// Facts is a collection of MessageCardSectionFact values. A section entry
// usually is displayed in a two-column key/value format.
Facts []MessageCardSectionFact `json:"facts,omitempty"`

// Images is a property that allows for the inclusion of a photo gallery
// inside a section.
// We use a slice of pointers to this type in order to have the json
// package properly omit this field if not explicitly set.
// https://github.com/golang/go/issues/11939
// https://stackoverflow.com/questions/18088294/how-to-not-marshal-an-empty-struct-into-json-with-go
// https://stackoverflow.com/questions/33447334/golang-json-marshal-how-to-omit-empty-nested-struct
Images []*MessageCardSectionImage `json:"images,omitempty"`
}

// MessageCard represents a legacy actionable message card used via Office 365
// or Microsoft Teams connectors.
type MessageCard struct {
// Required; must be set to "MessageCard"
Type string `json:"@type"`

// Required; must be set to "https://schema.org/extensions"
Context string `json:"@context"`

// Summary is required if the card does not contain a text property,
// otherwise optional. The summary property is typically displayed in the
// list view in Outlook, as a way to quickly determine what the card is
// all about. Summary appears to only be used when there are sections defined
Summary string `json:"summary,omitempty"`

// Title is the title property of a card. is meant to be rendered in a
// prominent way, at the very top of the card. Use it to introduce the
// content of the card in such a way users will immediately know what to
// expect.
Title string `json:"title,omitempty"`

// Text is required if the card does not contain a summary property,
// otherwise optional. The text property is meant to be displayed in a
// normal font below the card's title. Use it to display content, such as
// the description of the entity being referenced, or an abstract of a
// news article.
Text string `json:"text,omitempty"`

// Specifies a custom brand color for the card. The color will be
// displayed in a non-obtrusive manner.
ThemeColor string `json:"themeColor,omitempty"`

// Sections is a collection of sections to include in the card.
Sections []*MessageCardSection `json:"sections,omitempty"`
}

// AddSection adds one or many additional MessageCardSection values to a
// MessageCard. Validation is performed to reject invalid values with an error
// message.
func (mc *MessageCard) AddSection(section ...*MessageCardSection) error {

for _, s := range section {

// bail if a completely nil section provided
if s == nil {
return fmt.Errorf("AddSection: nil MessageCardSection received")
}

// Perform validation of all MessageCardSection fields in an effort to
// avoid adding a MessageCardSection with zero value fields. This is
// done to avoid generating an empty sections JSON array since the
// Sections slice for the MessageCard type would technically not be at
// a zero value state. Due to this non-zero value state, the
// encoding/json package would end up including the Sections struct
// field in the output JSON.
// See also https://github.com/golang/go/issues/11939
switch {

// If any of these cases trigger, skip over the `default` case
// statement and add the section.
case s.Images != nil:
case s.Facts != nil:
case s.HeroImage != nil:
case s.StartGroup != false:
case s.Markdown != false:
case s.ActivityText != "":
case s.ActivitySubtitle != "":
case s.ActivityTitle != "":
case s.ActivityImage != "":
case s.Text != "":
case s.Title != "":

default:
return fmt.Errorf("all fields found to be at zero-value, skipping section")
}

mc.Sections = append(mc.Sections, s)

}

return nil

}

// AddFact adds one or many additional MessageCardSectionFact values to a
// MessageCardSection
func (mcs *MessageCardSection) AddFact(fact ...MessageCardSectionFact) error {

for _, f := range fact {

if f.Name == "" {
return fmt.Errorf("empty Name field received for new fact: %+v", f)
}

if f.Value == "" {
return fmt.Errorf("empty Name field received for new fact: %+v", f)
}
}

return nil

}

// AddFactFromKeyValue accepts a key and slice of values and converts them to
// MessageCardSectionFact values
func (mcs *MessageCardSection) AddFactFromKeyValue(key string, values ...string) error {

// validate arguments

if key == "" {
return errors.New("empty key received for new fact")
}

if len(values) < 1 {
return errors.New("no values received for new fact")
}

fact := MessageCardSectionFact{
Name: key,
Value: strings.Join(values, ", "),
}
// TODO: Explicitly define or use constructor?
// fact := NewMessageCardSectionFact()
// fact.Name = key
// fact.Value = strings.Join(values, ", ")

mcs.Facts = append(mcs.Facts, fact)

// if we made it this far then all should be well
return nil
}

// AddImage adds an image to a MessageCard section. These images are used to
// provide a photo gallery inside a MessageCard section.
func (mcs *MessageCardSection) AddImage(sectionImage ...MessageCardSectionImage) error {

for _, img := range sectionImage {
if img.Image == "" {
return fmt.Errorf("cannot add empty image URL")
}

if img.Title == "" {
return fmt.Errorf("cannot add empty image title")
}

mcs.Images = append(mcs.Images, &img)

}

return nil
}

// AddHeroImageStr adds a Hero Image to a MessageCard section using string
// arguments. This image is used as the centerpiece or banner of a message
// card.
func (mcs *MessageCardSection) AddHeroImageStr(imageURL string, imageTitle string) error {

if imageURL == "" {
return fmt.Errorf("cannot add empty hero image URL")
}

if imageTitle == "" {
return fmt.Errorf("cannot add empty hero image title")
}

heroImage := MessageCardSectionImage{
Image: imageURL,
Title: imageTitle,
}
// TODO: Explicitly define or use constructor?
// heroImage := NewMessageCardSectionImage()
// heroImage.Image = imageURL
// heroImage.Title = imageTitle

mcs.HeroImage = &heroImage

// our validation checks didn't find any problems
return nil

}

// AddHeroImage adds a Hero Image to a MessageCard section using a
// MessageCardSectionImage argument. This image is used as the centerpiece or
// banner of a message card.
func (mcs *MessageCardSection) AddHeroImage(heroImage MessageCardSectionImage) error {

if heroImage.Image == "" {
return fmt.Errorf("cannot add empty hero image URL")
}

if heroImage.Title == "" {
return fmt.Errorf("cannot add empty hero image title")
}

mcs.HeroImage = &heroImage

// our validation checks didn't find any problems
return nil

}

// NewMessageCard creates a new message card with fields required by the
// legacy message card format already predefined
func NewMessageCard() MessageCard {

// define expected values to meet Office 365 Connector card requirements
// https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#card-fields
msgCard := MessageCard{
Type: "MessageCard",
Context: "https://schema.org/extensions",
}

return msgCard
}

// NewMessageCardSection creates an empty message card section
func NewMessageCardSection() *MessageCardSection {
msgCardSection := MessageCardSection{}
return &msgCardSection
}

// NewMessageCardSectionFact creates an empty message card section fact
func NewMessageCardSectionFact() MessageCardSectionFact {
msgCardSectionFact := MessageCardSectionFact{}
return msgCardSectionFact
}

// NewMessageCardSectionImage creates an empty image for use with message card
// section
func NewMessageCardSectionImage() MessageCardSectionImage {
msgCardSectionImage := MessageCardSectionImage{}
return msgCardSectionImage
}
Loading

0 comments on commit 52b2f9f

Please sign in to comment.