From 6db7fe4a8e074450a04bbbe4c16dec053db634d0 Mon Sep 17 00:00:00 2001 From: Martin Gondermann Date: Fri, 24 Mar 2017 15:40:41 +0100 Subject: [PATCH] Add helper for Office 365 / MS Teams Notifications --- help/literate/templates/template-project.html | 1 + help/msteamsnotification.md | 52 +++ .../msteamsnotification.png | Bin 0 -> 18293 bytes help/templates/template.cshtml | 1 + src/app/FakeLib/FakeLib.fsproj | 1 + src/app/FakeLib/Office365ConnectorHelper.fs | 357 ++++++++++++++++++ 6 files changed, 412 insertions(+) create mode 100644 help/msteamsnotification.md create mode 100644 help/pics/msteamsnotification/msteamsnotification.png create mode 100644 src/app/FakeLib/Office365ConnectorHelper.fs diff --git a/help/literate/templates/template-project.html b/help/literate/templates/template-project.html index 288a133e7fc..56350b57cfb 100644 --- a/help/literate/templates/template-project.html +++ b/help/literate/templates/template-project.html @@ -80,6 +80,7 @@

{project-name}

  • WiX Setup Generation
  • Using Chocolatey
  • Using Slack
  • +
  • Using Microsoft Teams
  • Using SonarQube
  • Fake.Deploy
  • diff --git a/help/msteamsnotification.md b/help/msteamsnotification.md new file mode 100644 index 00000000000..3d11f73768b --- /dev/null +++ b/help/msteamsnotification.md @@ -0,0 +1,52 @@ +# Sending Notifications to a Microsoft Teams channel + +In this article you will learn how to create a Office 365 webhook integration on a MS Teams channel and send a notification to it. This article assumes that you already have a Microsoft Teams team and channel setup. + +## Adding a Webhook Integration to a Channel + +Follow the [instructions](https://msdn.microsoft.com/en-us/microsoft-teams/connectors) for setting up a webhook connector to your channel. When finished, you should have a Webhook URL that looks like "https://outlook.office.com/webhook/some-random-text/IncomingWebhook/some/random/text". + +## Sending a Notification to the Webhook + + // The webhook URL from the integration you set up + let webhookUrl = "https://outlook.office.com/webhook/some-random-text/IncomingWebhook/some/random/text" + + let imageUrl = sprintf "https://connectorsdemo.azurewebsites.net/images/%s" + + let notification p = + { p with + Summary = Some "Max Muster ran a build" + Title = Some "Sample Project" + Sections = + [ { SectionDefaults with + ActivityTitle = Some "Max Muster" + ActivitySubtitle = Some "on Sample Project" + ActivityText = Some "Build successful!" + ActivityImage = + imageUrl "MSC12_Oscar_002.jpg" + |> ImageUri.FromUrl + |> Some + } + { SectionDefaults with + Title = Some "Details" + Facts = [ { Name = "Labels"; Value = "FOO, BAR" } + { Name = "Version"; Value = "1.0.0" } + { Name = "Trello Id"; Value = "1101" } ] + } + ] + PotentialActions = + [ + { + Name = "View in Trello" + Target = System.Uri("https://trello.com/c/1101/") + } + ] + } + + Office365Notification webhookUrl notification |> ignore + +The result should look something like this: + +![alt text](pics/msteamsnotification/msteamsnotification.png "Microsoft Teams Notification Result") + +For additional information on the parameters, check out the [Office 356 Connectors API Reference](https://dev.outlook.com/connectors/reference) diff --git a/help/pics/msteamsnotification/msteamsnotification.png b/help/pics/msteamsnotification/msteamsnotification.png new file mode 100644 index 0000000000000000000000000000000000000000..5738196356aea27a035eca58c7d657f5b429e7fc GIT binary patch literal 18293 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 literal 0 HcmV?d00001 diff --git a/help/templates/template.cshtml b/help/templates/template.cshtml index 3d2d3ee50b5..a8b7679f9e5 100644 --- a/help/templates/template.cshtml +++ b/help/templates/template.cshtml @@ -81,6 +81,7 @@
  • WiX Setup Generation
  • Using Chocolatey
  • Using Slack
  • +
  • Using Microsoft Teams
  • Using SonarQube
  • Fake.Deploy
  • diff --git a/src/app/FakeLib/FakeLib.fsproj b/src/app/FakeLib/FakeLib.fsproj index 4b291618eb8..923bba73c5d 100644 --- a/src/app/FakeLib/FakeLib.fsproj +++ b/src/app/FakeLib/FakeLib.fsproj @@ -192,6 +192,7 @@ + diff --git a/src/app/FakeLib/Office365ConnectorHelper.fs b/src/app/FakeLib/Office365ConnectorHelper.fs new file mode 100644 index 00000000000..0a7c19cbe28 --- /dev/null +++ b/src/app/FakeLib/Office365ConnectorHelper.fs @@ -0,0 +1,357 @@ +/// Contains a task to send notification messages to a [Office 356 Connector](https://dev.outlook.com/connectors/reference) webhook +/// +/// ## Sample +/// +/// let imageUrl = sprintf "https://connectorsdemo.azurewebsites.net/images/%s" +/// +/// let notification p = +/// { p with +/// Summary = Some "Max Muster ran a build" +/// Title = Some "Sample Project" +/// Sections = +/// [ { SectionDefaults with +/// ActivityTitle = Some "Max Muster" +/// ActivitySubtitle = Some "on Sample Project" +/// ActivityImage = +/// imageUrl "MSC12_Oscar_002.jpg" +/// |> ImageUri.FromUrl +/// |> Some +/// } +/// { SectionDefaults with +/// Title = Some "Details" +/// Facts = [ { Name = "Labels"; Value = "FOO, BAR" } +/// { Name = "Version"; Value = "1.0.0" } +/// { Name = "Trello Id"; Value = "1101" } ] +/// } +/// ] +/// PotentialActions = +/// [ +/// { +/// Name = "View in Trello" +/// Target = System.Uri("https://trello.com/c/1101/") +/// } +/// ] +/// } +/// +/// let webhookURL = "" +/// +/// Office365Notification webhookURL notification |> ignore +/// + +module Fake.Office365ConnectorHelper + +open System.Net +open System +open Newtonsoft.Json + +/// This type alias for string gives you a hint where you can use markdown +type MarkdownString = string + +/// This type alias for string gives you a hint where you **can't** use markdown +type SimpleString = string + +/// This type alias gives you a hint where you have to use a Hex color value (e.g. #AAFF77) +type ColorHexValue = string + +/// [omit] +let inline private writeJson (w: JsonWriter) (x: ^T) = (^T: (member WriteJson: JsonWriter -> JsonWriter) x, w) + +/// [omit] +let private writePropertyName title (writer: JsonWriter) = + writer.WritePropertyName(title) + writer + +/// [omit] +let private writeString (value: string) (writer: JsonWriter) = + writer.WriteValue(value) + writer + +/// [omit] +let private writeNamedString title value (writer: JsonWriter) = + writer + |> writePropertyName title + |> writeString value + +/// [omit] +let private writeNonEmptyValue title value (writer: JsonWriter) = + match value with + | Some v when v |> isNotNullOrEmpty -> + writer + |> writePropertyName title + |> writeString v + | _ -> writer + +/// [omit] +let private asList title (writeValues: JsonWriter -> JsonWriter) (writer: JsonWriter) = + writer.WritePropertyName(title) + writer.WriteStartArray() + writer |> writeValues |> ignore + writer.WriteEndArray() + writer + +/// [omit] +let private asObject (writeValues: JsonWriter -> JsonWriter) (writer: JsonWriter) = + writer.WriteStartObject() + writer |> writeValues |> ignore + writer.WriteEndObject() + writer + +/// Represents an action button +type ViewAction = + { + /// (Required) The name of the Action (appears on the button). + Name: SimpleString + + /// (Required) The Url of the link for the button + Target: Uri + } + + /// Writes the action to a JSON writer + member self.WriteJson (writer: JsonWriter) = + writer |> asObject (fun _ -> + writer + |> writeNamedString "@context" "http://schema.org" + |> writeNamedString "@type" "ViewAction" + |> writeNamedString "name" self.Name + |> asList "target" (fun _ -> writer |> writeString (self.Target.ToString()))) + + +/// Represents the URI to an image (either a normal URI or a DataUri) +type ImageUri = + /// A simple URI of the image + | ImageUrl of Uri + + /// A Data uri of the image encoded as Base64 data + | DataUri of string + + /// Writes the image uri to a JSON writer + member self.WriteJson (writer: JsonWriter) = + match self with + | ImageUrl uri -> writer |> writeString (uri.ToString()) + | DataUri uri -> writer |> writeString uri + + /// Creates a new ImageUrl from a given url string + static member FromUrl url = + System.Uri(url) |> ImageUrl + + /// Creates a new DataUri from a given file + /// png, gif, jpg and bmp files are supported + static member FromFile fileName = + let allowedExtensions = [ "png"; "gif"; "jpg"; "bmp" ] + let extension = System.IO.Path.GetExtension(fileName) |> toLower + + match extension with + | ext when (allowedExtensions |> List.contains ext) -> + let imageBytes = System.IO.File.ReadAllBytes(fileName) + let data = Convert.ToBase64String(imageBytes, Base64FormattingOptions.None) + + DataUri (sprintf "data:image/%s,base64,%s" ext data) |> Some + | _ -> + traceError (sprintf "Extension \"%s\" is not supported!" extension) + None + +/// A simple key/value pair +type Fact = + { + /// (Required) Name of the fact + Name: SimpleString + + /// (Required) Value of the fact + Value: MarkdownString + } + + /// Writes the fact to a JSON writer + member self.WriteJson (writer: JsonWriter) = + writer |> asObject (fun _ -> + writer + |> writeNamedString "name" self.Name + |> writeNamedString "value" self.Value) + +/// Represents a described image object +type Image = + { + /// (Optional) Alt-text for the image + Title: SimpleString option + + /// (Required) A URL to the image file or a data URI with the base64-encoded image inline + Image: ImageUri + } + + static member FromUrlWithoutTitle url = + { Title = None + Image = url |> ImageUri.FromUrl } + + /// Writes the Image to a JSON writer + member self.WriteJson (writer: JsonWriter) = + writer |> asObject (fun w -> + w + |> writeNonEmptyValue "title" self.Title + |> writePropertyName "image" + |> self.Image.WriteJson) + +/// A section in a ConnectorCard +type Section = + { + /// (Optional) The title of the section + Title: MarkdownString option + + /// (Optional) Title of the event or action. Often this will be the name of the "actor". + ActivityTitle: MarkdownString option + + /// (Optional) A subtitle describing the event or action. Often this will be a summary of the action. + ActivitySubtitle: MarkdownString option + + /// (Optional) An image representing the action. Often this is an avatar of the "actor" of the activity. + ActivityImage: ImageUri option + + /// (Optional) A full description of the action. + ActivityText: MarkdownString option + + /// A list of facts, displayed as key-value pairs. + Facts: Fact list + + /// A list of images that will be displayed at the bottom of the section. + Images: Image list + + /// (Optional) A text that will appear before the activity. + Text: string option + + /// (Optional) Set this to false to disable markdown parsing on this section's content. Markdown parsing is enabled by default. + IsMarkdown: bool option + + /// This list of ViewAction objects will power the action links found at the bottom of the section + PotentialActions: ViewAction list + } + + /// Writes the Section to a JSON writer + member self.WriteJson (writer: JsonWriter) = + writer |> asObject (fun _ -> + writer + |> writeNonEmptyValue "title" self.Title + |> writeNonEmptyValue "activityTitle" self.ActivityTitle + |> writeNonEmptyValue "activitySubtitle" self.ActivitySubtitle + |> writeNonEmptyValue "activityText" self.ActivityText + |> fun _ -> match self.ActivityImage with + | Some i -> writer |> writePropertyName "activityImage" |> i.WriteJson + | _ -> writer + |> fun _ -> match self.Facts with + | [] -> writer + | _ -> writer |> asList "facts" (fun _ -> self.Facts |> List.fold writeJson writer) + + |> fun _ -> match self.Images with + | [] -> writer + | _ -> writer |> asList "images" (fun _ -> self.Images |> List.fold writeJson writer) + |> fun _ -> match self.IsMarkdown with + | Some false -> writer.WritePropertyName("markdown") + writer.WriteValue(false) + writer + | _ -> writer + |> fun _ -> match self.PotentialActions with + | [] -> writer + | _ -> writer |> asList "potentialAction" (fun _ -> self.PotentialActions |> List.fold writeJson writer)) + +/// This is the base data, which will be sent to the Office 365 webhook connector +type ConnectorCard = + { + /// (Required, if the text property is not populated) A string used for summarizing card content. This will be shown as the message subject. + Summary: SimpleString option + + /// (Optional) A title for the Connector message. Shown at the top of the message. + Title: SimpleString option + + /// The main text of the card. This will be rendered below the sender information and optional title, and above any sections or actions present. + Text: MarkdownString option + + /// (Optional) Accent color used for branding or indicating status in the card + ThemeColor: ColorHexValue option + + /// Contains a list of sections to display in the card + Sections: Section list + + /// This array of ViewAction objects will power the action links found at the bottom of the card + PotentialActions: ViewAction list + } + + member self.WriteJson (writer: JsonWriter) = + writer |> asObject (fun _ -> + writer + |> writeNonEmptyValue "summary" self.Summary + |> writeNonEmptyValue "title" self.Title + |> writeNonEmptyValue "text" self.Text + |> writeNonEmptyValue "themeColor" self.ThemeColor + |> fun _ -> match self.Sections with + | [] -> writer + | _ -> writer |> asList "sections" (fun _ -> self.Sections |> List.fold writeJson writer) + |> fun _ -> match self.PotentialActions with + | [] -> writer + | _ -> writer |> asList "potentialAction" (fun _ -> self.PotentialActions |> List.fold writeJson writer)) + + /// Converts the connector card to a JSON string + member self.AsJson() = + let sb = System.Text.StringBuilder() + let sw = new System.IO.StringWriter(sb) + use writer = new JsonTextWriter(sw) + writer |> self.WriteJson |> ignore + sb.ToString () + +/// Default values for a Section in a ConnectorCard (everything is empty here) +let SectionDefaults = + { + Title = None + ActivityTitle = None + ActivitySubtitle = None + ActivityImage = None + ActivityText = None + Facts = [] + Images = [] + Text = None + IsMarkdown = None + PotentialActions = [] + } + +/// Default values for a ConnectorCard (everything is empty here) +let ConnectorCardDefaults = + { + Summary = None + Title = None + Text = None + ThemeColor = None + Sections = [] + PotentialActions = [] + } + +/// [omit] +let private validateParams webhookURL (card : ConnectorCard) = + if webhookURL = "" then failwith "You must specify a webhook URL" + if card.Text.IsNone && card.Sections.Length = 0 then failwith "You must specify a message or include a section" + + let validateAction (action: ViewAction) = + if action.Name |> isNullOrEmpty then + failwith "You must specifiy a name for a ViewAction" + + let validateSection (section: Section) = + if section.Text.IsNone && section.ActivityText.IsNone && section.ActivityTitle.IsNone && section.Facts.Length = 0 && section.Images.Length = 0 then + failwith "You must specifiy a text or an activityText/activityTitle or some facts or some images in a section" + section.PotentialActions |> List.iter validateAction + () + + card.Sections |> List.iter validateSection + card.PotentialActions |> List.iter validateAction + + card + +/// Sends a notification to an Office 365 Connector +/// ## Parameters +/// - `webhookURL` - The Office 365 webhook connector URL +/// - `setParams` - Function used to override the default notification parameters +let Office365Notification (webhookURL : string) (setParams: ConnectorCard -> ConnectorCard) = + let sendNotification (card: ConnectorCard) = + use client = (new WebClient()) + + client.Headers.Add(HttpRequestHeader.ContentType, "application/json") + client.UploadString(webhookURL, "POST", card.AsJson ()) + + ConnectorCardDefaults + |> setParams + |> validateParams webhookURL + |> sendNotification