From 05de9cafeb4c2eb457c9511070051adee4b45e9c Mon Sep 17 00:00:00 2001 From: Adam Chalkley Date: Fri, 23 Aug 2024 07:21:09 -0500 Subject: [PATCH] Update documentation for CodeBlock element - add `examples/adaptivecard/codeblock` - update README file to mention new CodeBlock support - minor linting fixes - minor doc comment adjustments to list supported programming languages for CodeBlock element refs GH-288 --- README.md | 11 ++ adaptivecard/adaptivecard.go | 34 ++++++- examples/adaptivecard/codeblock/main.go | 128 ++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 examples/adaptivecard/codeblock/main.go diff --git a/README.md b/README.md index 25e7563..1766fab 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ A package to send messages to a Microsoft Teams channel. - [Basic](#basic) - [Specify proxy server](#specify-proxy-server) - [User Mention](#user-mention) + - [CodeBlock](#codeblock) - [Tables](#tables) - [Set custom user agent](#set-custom-user-agent) - [Add an Action](#add-an-action) @@ -396,6 +397,14 @@ deprecated][o365-connector-retirement-announcement] `MessageCard` card format. - File: [user-mention-verbose](./examples/adaptivecard/user-mention-verbose/main.go) - this example does not necessarily reflect an optimal implementation +#### CodeBlock + +This example illustrates the use of a [`CodeBlock`][adaptivecard-codeblock]. +This feature is not available in the legacy [🚫 +deprecated][o365-connector-retirement-announcement] `MessageCard` card format. + +- File: [codeblock](./examples/adaptivecard/codeblock/main.go) + #### Tables These examples illustrates the use of a [`Table`][adaptivecard-table]. This @@ -511,4 +520,6 @@ using either this library or the original project. [adaptivecard-user-mentions]: [adaptivecard-table]: +[adaptivecard-codeblock]: + diff --git a/adaptivecard/adaptivecard.go b/adaptivecard/adaptivecard.go index 98ee236..fd36eb1 100644 --- a/adaptivecard/adaptivecard.go +++ b/adaptivecard/adaptivecard.go @@ -323,7 +323,7 @@ const ( TypeElementTextRun string = "TextRun" // Introduced in version 1.2 ) -// Known exension types for an Adaptive Card element. +// Known extension types for an Adaptive Card element. // // - https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cdesktop%2Cconnector-html#codeblock-in-adaptive-cards const ( @@ -612,7 +612,7 @@ type Element struct { // CodeSnippet provides the content for a CodeBlock element, specific to MSTeams. CodeSnippet string `json:"codeSnippet,omitempty"` - // Language specifies the langauge of a CodeBlock element, specific to MSTeams. + // Language specifies the language of a CodeBlock element, specific to MSTeams. Language string `json:"language,omitempty"` // StartLineNumber specifies the initial line number of CodeBlock element, specific to MSTeams. @@ -3167,6 +3167,36 @@ func (c *Card) AddContainer(prepend bool, container Container) error { // NewCodeBlock creates a new CodeBlock element with snippet, language, and // optional firstLine. This is an MSTeams extension element. +// +// Supported languages include: +// +// - Bash +// - C +// - C# +// - C++ +// - CSS +// - DOS +// - Go +// - GraphQL +// - HTML +// - Java +// - JavaScript +// - JSON +// - Perl +// - PHP +// - PlainText +// - PowerShell +// - Python +// - SQL +// - TypeScript +// - Verilog +// - VHDL +// - Visual Basic +// - XML +// +// See +// https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format +// for additional languages that may be supported. func NewCodeBlock(snippet string, language string, firstLine int) Element { codeBlock := Element{ Type: TypeElementMSTeamsCodeBlock, diff --git a/examples/adaptivecard/codeblock/main.go b/examples/adaptivecard/codeblock/main.go new file mode 100644 index 0000000..30fbd2e --- /dev/null +++ b/examples/adaptivecard/codeblock/main.go @@ -0,0 +1,128 @@ +// Copyright 2022 Adam Chalkley +// +// https://github.com/atc0005/go-teams-notify +// +// Licensed under the MIT License. See LICENSE file in the project root for +// full license information. + +/* + +This is an example of a client application which uses this library to generate +a Microsoft Teams message containing a codeblock in Adaptive Card format. + +Of note: + +- default timeout +- package-level logging is disabled by default +- validation of known webhook URL formats is *enabled* +- message submitted to Microsoft Teams consisting of title, formatted message + body and embedded codeblock + +See these links for Adaptive Card text formatting options: + +- https://docs.microsoft.com/en-us/adaptive-cards/authoring-cards/text-features +- https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cdesktop%2Cconnector-html#codeblock-in-adaptive-cards + + +*/ + +package main + +import ( + "log" + "os" + "strings" + + goteamsnotify "github.com/atc0005/go-teams-notify/v2" + "github.com/atc0005/go-teams-notify/v2/adaptivecard" +) + +func main() { + + // Initialize a new Microsoft Teams client. + mstClient := goteamsnotify.NewTeamsClient() + + // Set webhook url. + // + // NOTE: This is for illustration purposes only. Best practice is to NOT + // hardcode credentials of any kind. + webhookUrl := "https://example.logic.azure.com:443/workflows/GUID_HERE/triggers/manual/paths/invoke?api-version=YYYY-MM-DD&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=SIGNATURE_HERE" + + // Allow specifying webhook URL via environment variable, fall-back to + // hard-coded value in this example file. + expectedEnvVar := "WEBHOOK_URL" + envWebhookURL := os.Getenv(expectedEnvVar) + switch { + case envWebhookURL != "": + log.Printf( + "Using webhook URL %q from environment variable %q\n\n", + envWebhookURL, + expectedEnvVar, + ) + webhookUrl = envWebhookURL + default: + log.Println(expectedEnvVar, "environment variable not set.") + log.Printf("Using hardcoded value %q as fallback\n\n", webhookUrl) + } + + // The title for message (first TextBlock element). + msgTitle := "Hello world" + + // Formatted message body. + msgText := "Here are some examples of formatted stuff like " + + "\n * this list itself \n * **bold** \n * *italic* \n * ***bolditalic***" + + // Create card using provided formatted title and text. We'll modify the + // card and when finished use it to generate a message for delivery. + card, err := adaptivecard.NewTextBlockCard(msgText, msgTitle, true) + if err != nil { + log.Printf( + "failed to create card: %v", + err, + ) + os.Exit(1) + } + + // See also https://yourbasic.org/golang/multiline-string/ for other + // approaches to embedding formatted strings. + codeSnippet := ` +package main + +import "log/slog" + +func main() { + slog.Info("hello, world") +} +` + + // If you want to strip leading/trailing whitespace. + codeSnippet = strings.TrimSpace(codeSnippet) + + // Create CodeBlock using our snippet. + codeBlock := adaptivecard.NewCodeBlock(codeSnippet, "Go", 1) + + // Add CodeBlock to our Card. + if err := card.AddElement(false, codeBlock); err != nil { + log.Printf( + "failed to add codeblock to card: %v", + err, + ) + os.Exit(1) + } + + // Create Message from Card + msg, err := adaptivecard.NewMessageFromCard(card) + if err != nil { + log.Printf("failed to create message from card: %v", err) + os.Exit(1) + } + + // Send the message with default timeout/retry settings. + if err := mstClient.Send(webhookUrl, msg); err != nil { + log.Printf( + "failed to send message: %v", + err, + ) + os.Exit(1) + } +}