diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/MicrosoftTeamsNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/MicrosoftTeamsNotificationChannel.java index d3113787e5..358e2d1f6d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/MicrosoftTeamsNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/MicrosoftTeamsNotificationChannel.java @@ -15,16 +15,17 @@ */ package org.thingsboard.server.service.notification.channels; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Strings; -import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.Setter; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.thingsboard.common.util.JacksonUtil; @@ -37,6 +38,8 @@ import org.thingsboard.server.common.data.notification.template.MicrosoftTeamsDe import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.security.system.SystemSecurityService; +import java.net.URI; +import java.net.URISyntaxException; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.List; @@ -56,17 +59,93 @@ public class MicrosoftTeamsNotificationChannel implements NotificationChannel request = new HttpEntity<>(JacksonUtil.toString(teamsAdaptiveCard), headers); + restTemplate.postForEntity(new URI(targetConfig.getWebhookUrl()), request, String.class); + } + + private void sendTeamsMessageCard(MicrosoftTeamsNotificationTargetConfig targetConfig, MicrosoftTeamsDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) throws JsonProcessingException, URISyntaxException { + TeamsMessageCard teamsMessageCard = new TeamsMessageCard(); + teamsMessageCard.setThemeColor(Strings.emptyToNull(processedTemplate.getThemeColor())); + if (StringUtils.isEmpty(processedTemplate.getSubject())) { + teamsMessageCard.setText(processedTemplate.getBody()); + } else { + teamsMessageCard.setSummary(processedTemplate.getSubject()); + TeamsMessageCard.Section section = new TeamsMessageCard.Section(); section.setActivityTitle(processedTemplate.getSubject()); section.setActivitySubtitle(processedTemplate.getBody()); - message.setSections(List.of(section)); + teamsMessageCard.setSections(List.of(section)); } + var button = processedTemplate.getButton(); + String uri = getButtonUri(processedTemplate, ctx); + + if (StringUtils.isNotBlank(uri) && button.getText() != null) { + TeamsMessageCard.ActionCard actionCard = new TeamsMessageCard.ActionCard(); + actionCard.setType("OpenUri"); + actionCard.setName(button.getText()); + var target = new TeamsMessageCard.ActionCard.Target("default", uri); + actionCard.setTargets(List.of(target)); + teamsMessageCard.setPotentialAction(List.of(actionCard)); + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity request = new HttpEntity<>(JacksonUtil.toString(teamsMessageCard), headers); + restTemplate.postForEntity(new URI(targetConfig.getWebhookUrl()), request, String.class); + } + + private String getButtonUri(MicrosoftTeamsDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) throws JsonProcessingException { var button = processedTemplate.getButton(); if (button != null && button.isEnabled()) { String uri; @@ -99,17 +178,9 @@ public class MicrosoftTeamsNotificationChannel implements NotificationChannel sections; - private List potentialAction; - - @Data - public static class Section { - private String activityTitle; - private String activitySubtitle; - private String activityImage; - private List facts; - private boolean markdown; - - @Data - public static class Fact { - private final String name; - private final String value; - } - } - - @Data - @JsonInclude(JsonInclude.Include.NON_NULL) - public static class ActionCard { - @JsonProperty("@type") - private String type; // ActionCard, OpenUri - private String name; - private List inputs; // for ActionCard - private List actions; // for ActionCard - private List targets; - - @Data - public static class Input { - @JsonProperty("@type") - private String type; // TextInput, DateInput, MultichoiceInput - private String id; - private boolean isMultiple; - private String title; - private boolean isMultiSelect; - - @Data - public static class Choice { - private final String display; - private final String value; - } - } - - @Data - public static class Action { - @JsonProperty("@type") - private final String type; // HttpPOST - private final String name; - private final String target; // url - } - - @Data - public static class Target { - private final String os; - private final String uri; - } - } - - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/TeamsAdaptiveCard.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/TeamsAdaptiveCard.java new file mode 100644 index 0000000000..b5d434bac0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/TeamsAdaptiveCard.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.dao.util.ImageUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @link AdaptiveCard Designer + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TeamsAdaptiveCard { + private String type = "message"; + private List attachments; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Attachment { + private String contentType = "application/vnd.microsoft.card.adaptive"; + private AdaptiveCard content; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class AdaptiveCard { + @JsonProperty("$schema") + private final String schema = "http://adaptivecards.io/schemas/adaptive-card.json"; + private final String type = "AdaptiveCard"; + private BackgroundImage backgroundImage; + @JsonProperty("body") + private List textBlocks = new ArrayList<>(); + private List actions = new ArrayList<>(); + } + + @Data + @NoArgsConstructor + public static class BackgroundImage { + private String url; + private final String fillMode = "repeat"; + + public BackgroundImage(String color) { + // This is the only one way how to specify color the custom color for the card + url = ImageUtils.getEmbeddedBase64EncodedImg(color); + } + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class TextBlock { + private final String type = "TextBlock"; + private String text; + private String weight = "Normal"; + private String size = "Medium"; + private String spacing = "None"; + private String color = "#FFFFFF"; + private final boolean wrap = true; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ActionOpenUrl { + private final String type = "Action.OpenUrl"; + private String title; + private String url; + } + +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/TeamsMessageCard.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/TeamsMessageCard.java new file mode 100644 index 0000000000..65cdfec765 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/TeamsMessageCard.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.channels; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class TeamsMessageCard { + @JsonProperty("@type") + private final String type = "MessageCard"; + @JsonProperty("@context") + private final String context = "http://schema.org/extensions"; + private String themeColor; + private String summary; + private String text; + private List
sections; + private List potentialAction; + + @Data + public static class Section { + private String activityTitle; + private String activitySubtitle; + private String activityImage; + private List facts; + private boolean markdown; + + @Data + public static class Fact { + private final String name; + private final String value; + } + } + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class ActionCard { + @JsonProperty("@type") + private String type; // ActionCard, OpenUri + private String name; + private List inputs; // for ActionCard + private List actions; // for ActionCard + private List targets; + + @Data + public static class Input { + @JsonProperty("@type") + private String type; // TextInput, DateInput, MultichoiceInput + private String id; + private boolean isMultiple; + private String title; + private boolean isMultiSelect; + + @Data + public static class Choice { + private final String display; + private final String value; + } + } + + @Data + public static class Action { + @JsonProperty("@type") + private final String type; // HttpPOST + private final String name; + private final String target; // url + } + + @Data + public static class Target { + private final String os; + private final String uri; + } + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 53ae5f36ae..8bae1feca9 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpEntity; import org.springframework.test.web.servlet.ResultActions; import org.springframework.web.client.RestTemplate; import org.thingsboard.common.util.JacksonUtil; @@ -85,8 +86,12 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.notification.DefaultNotifications; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.notification.channels.MicrosoftTeamsNotificationChannel; +import org.thingsboard.server.service.notification.channels.TeamsAdaptiveCard; +import org.thingsboard.server.service.notification.channels.TeamsMessageCard; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -752,7 +757,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { } @Test - public void testMicrosoftTeamsNotifications() throws Exception { + public void testMicrosoftTeamsNotificationsWithOfficeConnector() throws URISyntaxException { RestTemplate restTemplate = mock(RestTemplate.class); microsoftTeamsNotificationChannel.setRestTemplate(restTemplate); @@ -760,6 +765,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { var targetConfig = new MicrosoftTeamsNotificationTargetConfig(); targetConfig.setWebhookUrl(webhookUrl); targetConfig.setChannelName("My channel"); + targetConfig.setUseOldApi(true); NotificationTarget target = new NotificationTarget(); target.setName("Microsoft Teams channel"); target.setConfiguration(targetConfig); @@ -770,7 +776,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { String templateParams = "${recipientTitle} - ${entityType}"; template.setSubject("Subject: " + templateParams); template.setBody("Body: " + templateParams); - template.setThemeColor("ff0000"); + template.setThemeColor("#ff0000"); var button = new MicrosoftTeamsDeliveryMethodNotificationTemplate.Button(); button.setEnabled(true); button.setText("Button: " + templateParams); @@ -803,11 +809,13 @@ public class NotificationApiTest extends AbstractNotificationApiTest { assertThat(preview.getRecipientsCountByTarget().get(target.getName())).isEqualTo(1); assertThat(preview.getRecipientsPreview()).containsOnly(targetConfig.getChannelName()); - var messageCaptor = ArgumentCaptor.forClass(MicrosoftTeamsNotificationChannel.Message.class); + ArgumentCaptor> messageCaptor = ArgumentCaptor.forClass(HttpEntity.class); notificationCenter.processNotificationRequest(tenantId, notificationRequest, null); - verify(restTemplate, timeout(20000)).postForEntity(eq(webhookUrl), messageCaptor.capture(), any()); + verify(restTemplate, timeout(20000)).postForEntity(eq(new URI(webhookUrl)), messageCaptor.capture(), any()); + + HttpEntity value = messageCaptor.getValue(); + TeamsMessageCard message = JacksonUtil.fromString(value.getBody(), TeamsMessageCard.class); - var message = messageCaptor.getValue(); String expectedParams = "My channel - Device"; assertThat(message.getThemeColor()).isEqualTo(template.getThemeColor()); assertThat(message.getSections().get(0).getActivityTitle()).isEqualTo("Subject: " + expectedParams); @@ -816,6 +824,74 @@ public class NotificationApiTest extends AbstractNotificationApiTest { assertThat(message.getPotentialAction().get(0).getTargets().get(0).getUri()).isEqualTo("https://" + expectedParams); } + @Test + public void testMicrosoftTeamsNotificationsWithWorkflow() throws Exception { + RestTemplate restTemplate = mock(RestTemplate.class); + microsoftTeamsNotificationChannel.setRestTemplate(restTemplate); + + String webhookUrl = "https://webhook.com/webhookb2/9628fa60-d873-11ed-913c-a196b1f9b445"; + var targetConfig = new MicrosoftTeamsNotificationTargetConfig(); + targetConfig.setWebhookUrl(webhookUrl); + targetConfig.setChannelName("My channel"); + targetConfig.setUseOldApi(false); + NotificationTarget target = new NotificationTarget(); + target.setName("Microsoft Teams channel"); + target.setConfiguration(targetConfig); + target = saveNotificationTarget(target); + + var template = new MicrosoftTeamsDeliveryMethodNotificationTemplate(); + template.setEnabled(true); + String templateParams = "${recipientTitle} - ${entityType}"; + template.setSubject("Subject: " + templateParams); + template.setBody("Body: " + templateParams); + template.setThemeColor("#ff0000"); + var button = new MicrosoftTeamsDeliveryMethodNotificationTemplate.Button(); + button.setEnabled(true); + button.setText("Button: " + templateParams); + button.setLinkType(LinkType.LINK); + button.setLink("https://" + templateParams); + template.setButton(button); + NotificationTemplate notificationTemplate = new NotificationTemplate(); + notificationTemplate.setName("Notification to Teams"); + notificationTemplate.setNotificationType(NotificationType.GENERAL); + NotificationTemplateConfig templateConfig = new NotificationTemplateConfig(); + templateConfig.setDeliveryMethodsTemplates(Map.of( + NotificationDeliveryMethod.MICROSOFT_TEAMS, template + )); + notificationTemplate.setConfiguration(templateConfig); + notificationTemplate = saveNotificationTemplate(notificationTemplate); + + NotificationRequest notificationRequest = NotificationRequest.builder() + .tenantId(tenantId) + .originatorEntityId(tenantAdminUserId) + .templateId(notificationTemplate.getId()) + .targets(List.of(target.getUuidId())) + .info(EntityActionNotificationInfo.builder() + .entityId(new DeviceId(UUID.randomUUID())) + .actionType(ActionType.ADDED) + .userId(tenantAdminUserId.getId()) + .build()) + .build(); + + NotificationRequestPreview preview = doPost("/api/notification/request/preview", notificationRequest, NotificationRequestPreview.class); + assertThat(preview.getRecipientsCountByTarget().get(target.getName())).isEqualTo(1); + assertThat(preview.getRecipientsPreview()).containsOnly(targetConfig.getChannelName()); + + ArgumentCaptor> messageCaptor = ArgumentCaptor.forClass(HttpEntity.class); + notificationCenter.processNotificationRequest(tenantId, notificationRequest, null); + verify(restTemplate, timeout(20000)).postForEntity(eq(new URI(webhookUrl)), messageCaptor.capture(), any()); + + HttpEntity value = messageCaptor.getValue(); + TeamsAdaptiveCard message = JacksonUtil.fromString(value.getBody(), TeamsAdaptiveCard.class); + String expectedParams = "My channel - Device"; + assertThat(message).isNotNull(); + assertThat(message.getAttachments().get(0).getContent().getBackgroundImage().getUrl()).isNotEmpty(); + assertThat(message.getAttachments().get(0).getContent().getTextBlocks().get(0).getText()).isEqualTo("Subject: " + expectedParams); + assertThat(message.getAttachments().get(0).getContent().getTextBlocks().get(1).getText()).isEqualTo("Body: " + expectedParams); + assertThat(message.getAttachments().get(0).getContent().getActions().get(0).getTitle()).isEqualTo("Button: " + expectedParams); + assertThat(message.getAttachments().get(0).getContent().getActions().get(0).getUrl()).isEqualTo("https://" + expectedParams); + } + @Test public void testMobileAppNotifications() throws Exception { loginCustomerUser(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/MicrosoftTeamsNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/MicrosoftTeamsNotificationTargetConfig.java index e07abe7c00..dbd09698f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/MicrosoftTeamsNotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/MicrosoftTeamsNotificationTargetConfig.java @@ -28,6 +28,7 @@ public class MicrosoftTeamsNotificationTargetConfig extends NotificationTargetCo private String webhookUrl; @NotEmpty private String channelName; + private Boolean useOldApi = Boolean.TRUE; @Override public NotificationTargetType getType() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java index e92adba01d..19649655a3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java @@ -46,9 +46,11 @@ import org.thingsboard.server.common.data.StringUtils; import org.w3c.dom.Document; import javax.imageio.ImageIO; +import java.awt.Color; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.Base64; import java.util.Map; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -265,6 +267,94 @@ public class ImageUtils { return new int[]{thumbnailWidth, thumbnailHeight}; } + public static String getEmbeddedBase64EncodedImg(String colorStr) { + try { + Color color = parseColor(colorStr); // Support for hex, rgb, hsla + BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + image.setRGB(0, 0, color.getRGB()); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageIO.write(image, "png", outputStream); + byte[] imageBytes = outputStream.toByteArray(); + String base64String = Base64.getEncoder().encodeToString(imageBytes); + + return "data:image/png;base64," + base64String; + } catch (Exception e) { + log.warn("Failed to generate embedded image for color: {}", colorStr, e); + return null; + } + } + + private static Color parseColor(String colorStr) { + if (colorStr.startsWith("#")) { + return Color.decode(colorStr); + } + + if (colorStr.startsWith("rgb")) { + return parseRgbColor(colorStr); + } + + if (colorStr.startsWith("hsl")) { + return parseHslaColor(colorStr); + } + + throw new IllegalArgumentException("Unsupported color format: " + colorStr); + } + + private static Color parseRgbColor(String rgb) { + String[] rgbValues = rgb.replaceAll("[^0-9,]", "").split(","); + int r = Integer.parseInt(rgbValues[0]); + int g = Integer.parseInt(rgbValues[1]); + int b = Integer.parseInt(rgbValues[2]); + return new Color(r, g, b); + } + + private static Color parseHslaColor(String hsla) { + String[] hslaValues = hsla.replaceAll("[^0-9.,]", "").split(","); + float h = Float.parseFloat(hslaValues[0]); + float s = Float.parseFloat(hslaValues[1]) / 100; + float l = Float.parseFloat(hslaValues[2]) / 100; + float a = hslaValues.length > 3 ? Float.parseFloat(hslaValues[3]) : 1.0f; + return hslaToColor(h, s, l, a); + } + + private static Color hslaToColor(float h, float s, float l, float alpha) { + float c = (1 - Math.abs(2 * l - 1)) * s; + float x = c * (1 - Math.abs((h / 60) % 2 - 1)); + float m = l - c / 2; + + float r = 0, g = 0, b = 0; + if (h < 60) { + r = c; + g = x; + } else if (h < 120) { + r = x; + g = c; + } else if (h < 180) { + g = c; + b = x; + } else if (h < 240) { + g = x; + b = c; + } else if (h < 300) { + r = x; + b = c; + } else { + r = c; + b = x; + } + + r += m; + g += m; + b += m; + + return new Color(clamp(r), clamp(g), clamp(b), clamp(alpha)); + } + + private static float clamp(float value) { + return Math.max(0, Math.min(1, value)); + } + @Data @AllArgsConstructor @NoArgsConstructor diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html index 40cfe51e98..311dbee6d9 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html @@ -121,6 +121,19 @@
+
+ + {{ "notification.use-old-api" | translate }} + + + open_in_new + +
notification.webhook-url diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts index 0a4be127ad..ad7c50d604 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.ts @@ -32,7 +32,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { NotificationService } from '@core/http/notification.service'; import { EntityType } from '@shared/models/entity-type.models'; -import { deepTrim, isDefinedAndNotNull } from '@core/utils'; +import { deepTrim, isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { Authority } from '@shared/models/authority.enum'; @@ -100,6 +100,7 @@ export class RecipientNotificationDialogComponent extends conversation: [{value: '', disabled: true}, Validators.required], webhookUrl: [{value: '', disabled: true}, Validators.required], channelName: [{value: '', disabled: true}, Validators.required], + useOldApi: [{value: !this.isAdd, disabled: true}], description: [null] }) }); @@ -120,6 +121,7 @@ export class RecipientNotificationDialogComponent extends case NotificationTargetType.MICROSOFT_TEAMS: this.targetNotificationForm.get('configuration.webhookUrl').enable({emitEvent: false}); this.targetNotificationForm.get('configuration.channelName').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.useOldApi').enable({emitEvent: false}); break; } this.targetNotificationForm.get('configuration.type').enable({emitEvent: false}); @@ -169,6 +171,10 @@ export class RecipientNotificationDialogComponent extends this.targetNotificationForm.get('configuration.usersFilter.filterByTenants') .patchValue(!Array.isArray(this.data.target.configuration.usersFilter.tenantProfilesIds), {onlySelf: true}); } + if (data.target.configuration.type === NotificationTargetType.MICROSOFT_TEAMS + && isUndefinedOrNull(this.data.target.configuration.useOldApi)) { + this.targetNotificationForm.get('configuration.useOldApi').patchValue(true, {emitEvent: false}); + } } } diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index cfb1312209..dd5550e907 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -290,6 +290,7 @@ export interface SlackNotificationTargetConfig { export interface MicrosoftTeamsNotificationTargetConfig { webhookUrl: string; channelName: string; + useOldApi?: boolean; } export enum NotificationTargetType { PLATFORM_USERS = 'PLATFORM_USERS', diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 37ea4a1a87..1a2089823c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4295,6 +4295,8 @@ "type": "Type", "unread": "Unread", "updated": "Updated", + "use-deprecated-webhook-connectors": "Use deprecated Webhook connectors", + "use-old-api": "Use old API", "use-template": "Use template", "view-all": "View all", "warning": "Warning",