Skip to content

Commit 0cfaf44

Browse files
committed
feat(teams-workflows): add RawCardPayload option to send AdaptiveCard JSON directly
1 parent 148c9a9 commit 0cfaf44

2 files changed

Lines changed: 87 additions & 4 deletions

File tree

pkg/services/teams-workflows.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ func (n *TeamsWorkflowsNotification) GetTemplater(name string, f texttemplate.Fu
162162
type TeamsWorkflowsOptions struct {
163163
RecipientUrls map[string]string `json:"recipientUrls"`
164164
InsecureSkipVerify bool `json:"insecureSkipVerify"`
165+
// RawCardPayload sends the AdaptiveCard JSON directly as the POST body,
166+
// skipping the "type":"message" + "attachments" envelope. Use this for
167+
// webhook endpoints that expect the card at the root level.
168+
RawCardPayload bool `json:"rawCardPayload"`
165169
httputil.TransportOptions
166170
}
167171

@@ -223,7 +227,7 @@ func (s teamsWorkflowsService) Send(notification Notification, dest Destination)
223227
}
224228

225229
// Generate message payload
226-
message, err := teamsWorkflowsNotificationToReader(notification)
230+
message, err := teamsWorkflowsNotificationToReader(notification, s.opts.RawCardPayload)
227231
if err != nil {
228232
return fmt.Errorf("failed to generate message payload for teams-workflows: %w", err)
229233
}
@@ -484,17 +488,21 @@ func buildAdaptiveCard(data *notificationData) *adaptiveCard {
484488
return card
485489
}
486490

487-
func teamsWorkflowsNotificationToReader(n Notification) ([]byte, error) {
491+
func teamsWorkflowsNotificationToReader(n Notification, rawCardPayload bool) ([]byte, error) {
488492
// Check if a custom AdaptiveCard template is provided
489493
if n.TeamsWorkflows != nil && n.TeamsWorkflows.AdaptiveCard != "" {
490-
// Use the custom AdaptiveCard template directly, wrapped in message envelope.
494+
// Use the custom AdaptiveCard template directly.
491495
// Keep it as raw JSON so custom cards are not lossy-converted through limited structs.
492496
cardBytes := bytes.TrimSpace([]byte(n.TeamsWorkflows.AdaptiveCard))
493497
var raw json.RawMessage
494498
if err := json.Unmarshal(cardBytes, &raw); err != nil {
495499
return nil, fmt.Errorf("teams-workflows adaptiveCard unmarshalling error %w", err)
496500
}
497501

502+
if rawCardPayload {
503+
return raw, nil
504+
}
505+
498506
payload := adaptiveMessage{
499507
Type: "message",
500508
Attachments: []adaptiveAttachment{
@@ -513,12 +521,17 @@ func teamsWorkflowsNotificationToReader(n Notification) ([]byte, error) {
513521
return nil, err
514522
}
515523

516-
// Build AdaptiveCard and wrap it in the message envelope
524+
// Build AdaptiveCard
517525
card := buildAdaptiveCard(data)
518526
cardBytes, err := json.Marshal(card)
519527
if err != nil {
520528
return nil, fmt.Errorf("teams-workflows adaptive card marshalling error %w", err)
521529
}
530+
531+
if rawCardPayload {
532+
return cardBytes, nil
533+
}
534+
522535
payload := adaptiveMessage{
523536
Type: "message",
524537
Attachments: []adaptiveAttachment{

pkg/services/teams-workflows_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,3 +938,73 @@ func TestTeamsWorkflows_ActionNonOpenUri(t *testing.T) {
938938
// Non-OpenUri actions should not be converted
939939
assert.Empty(t, card.Actions)
940940
}
941+
942+
func TestTeamsWorkflows_RawCardPayload_BuiltIn(t *testing.T) {
943+
var receivedBody []byte
944+
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
945+
data, err := io.ReadAll(request.Body)
946+
require.NoError(t, err)
947+
receivedBody = data
948+
writer.WriteHeader(http.StatusOK)
949+
}))
950+
defer server.Close()
951+
952+
webhookURL := server.URL + "/powerautomate/webhook"
953+
954+
service := NewTeamsWorkflowsService(TeamsWorkflowsOptions{
955+
RecipientUrls: map[string]string{
956+
"test": webhookURL,
957+
},
958+
RawCardPayload: true,
959+
})
960+
961+
notification := Notification{
962+
TeamsWorkflows: &TeamsWorkflowsNotification{
963+
Title: "Test Title",
964+
Text: "Test message body",
965+
},
966+
}
967+
968+
err := service.Send(notification, Destination{Recipient: "test", Service: "teams-workflows"})
969+
require.NoError(t, err)
970+
971+
var root map[string]any
972+
require.NoError(t, json.Unmarshal(receivedBody, &root))
973+
assert.Equal(t, "AdaptiveCard", root["type"])
974+
assert.NotContains(t, root, "attachments")
975+
}
976+
977+
func TestTeamsWorkflows_RawCardPayload_CustomCard(t *testing.T) {
978+
var receivedBody []byte
979+
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
980+
data, err := io.ReadAll(request.Body)
981+
require.NoError(t, err)
982+
receivedBody = data
983+
writer.WriteHeader(http.StatusOK)
984+
}))
985+
defer server.Close()
986+
987+
webhookURL := server.URL + "/powerautomate/webhook"
988+
989+
service := NewTeamsWorkflowsService(TeamsWorkflowsOptions{
990+
RecipientUrls: map[string]string{
991+
"test": webhookURL,
992+
},
993+
RawCardPayload: true,
994+
})
995+
996+
notification := Notification{
997+
TeamsWorkflows: &TeamsWorkflowsNotification{
998+
AdaptiveCard: `{"type":"AdaptiveCard","version":"1.4","body":[{"type":"TextBlock","text":"hello"}]}`,
999+
},
1000+
}
1001+
1002+
err := service.Send(notification, Destination{Recipient: "test", Service: "teams-workflows"})
1003+
require.NoError(t, err)
1004+
1005+
var root map[string]any
1006+
require.NoError(t, json.Unmarshal(receivedBody, &root))
1007+
assert.Equal(t, "AdaptiveCard", root["type"])
1008+
assert.Equal(t, "1.4", root["version"])
1009+
assert.NotContains(t, root, "attachments")
1010+
}

0 commit comments

Comments
 (0)