Browse Source

New platform version notification

pull/8334/head
ViacheslavKlimov 4 years ago
parent
commit
043bb253c7
  1. 17
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  2. 8
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  3. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java
  4. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java
  5. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NewPlatformVersionTrigger.java
  6. 9
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  7. 14
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html
  8. 15
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts
  9. 5
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts
  10. 11
      ui-ngx/src/app/shared/models/notification.models.ts
  11. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

17
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java

@ -15,16 +15,15 @@
*/ */
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo; import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
@ -36,17 +35,21 @@ public class NewPlatformVersionTriggerProcessor implements NotificationRuleTrigg
@Override @Override
public boolean matchesFilter(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
// todo: don't send repetitive notification after platform restart? if (!partitionService.isMyPartition(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)) {
if (!partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition()) {
return false; return false;
} }
return trigger.getMessage().isUpdateAvailable(); return trigger.getUpdateInfo().isUpdateAvailable();
} }
@Override @Override
public RuleOriginatedNotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger) { public RuleOriginatedNotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger) {
UpdateMessage updateInfo = trigger.getUpdateInfo();
return NewPlatformVersionNotificationInfo.builder() return NewPlatformVersionNotificationInfo.builder()
.message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {})) .latestVersion(updateInfo.getLatestVersion())
.latestVersionReleaseNotesUrl(updateInfo.getLatestVersionReleaseNotesUrl())
.upgradeInstructionsUrl(updateInfo.getUpgradeInstructionsUrl())
.currentVersion(updateInfo.getCurrentVersion())
.currentVersionReleaseNotesUrl(updateInfo.getCurrentVersionReleaseNotesUrl())
.build(); .build();
} }

8
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.update; package org.thingsboard.server.service.update;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -28,6 +27,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger; import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -74,8 +74,8 @@ public class DefaultUpdateService implements UpdateService {
private String version; private String version;
private UUID instanceId = null; private UUID instanceId = null;
@PostConstruct @AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
private void init() { public void init() {
version = buildProperties != null ? buildProperties.getVersion() : "unknown"; version = buildProperties != null ? buildProperties.getVersion() : "unknown";
updateMessage = new UpdateMessage(false, version, "", "", "", ""); updateMessage = new UpdateMessage(false, version, "", "", "", "");
if (updatesEnabled) { if (updatesEnabled) {
@ -132,7 +132,7 @@ public class DefaultUpdateService implements UpdateService {
updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class); updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) { if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessor.process(NewPlatformVersionTrigger.builder() notificationRuleProcessor.process(NewPlatformVersionTrigger.builder()
.message(updateMessage) .updateInfo(updateMessage)
.build()); .build());
} }
} catch (Exception e) { } catch (Exception e) {

16
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java

@ -19,7 +19,6 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.UpdateMessage;
import java.util.Map; import java.util.Map;
@ -31,11 +30,22 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Builder @Builder
public class NewPlatformVersionNotificationInfo implements RuleOriginatedNotificationInfo { public class NewPlatformVersionNotificationInfo implements RuleOriginatedNotificationInfo {
private Map<String, String> message; private String latestVersion;
private String latestVersionReleaseNotesUrl;
private String upgradeInstructionsUrl;
private String currentVersion;
private String currentVersionReleaseNotesUrl;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return message; return mapOf(
"latestVersion", latestVersion,
"latestVersionReleaseNotesUrl", latestVersionReleaseNotesUrl,
"upgradeInstructionsUrl", upgradeInstructionsUrl,
"currentVersion", currentVersion,
"currentVersionReleaseNotesUrl", currentVersionReleaseNotesUrl
);
} }
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java

@ -20,8 +20,6 @@ import lombok.Data;
@Data @Data
public class NewPlatformVersionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { public class NewPlatformVersionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
// TODO: don't forget to create default notification configs
@Override @Override
public NotificationRuleTriggerType getTriggerType() { public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION; return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;

2
common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NewPlatformVersionTrigger.java

@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
@Builder @Builder
public class NewPlatformVersionTrigger implements NotificationRuleTrigger { public class NewPlatformVersionTrigger implements NotificationRuleTrigger {
private final UpdateMessage message; private final UpdateMessage updateInfo;
@Override @Override
public NotificationRuleTriggerType getType() { public NotificationRuleTriggerType getType() {

9
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivi
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent; import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
@ -144,6 +145,14 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
apiUsageLimitRuleTriggerConfig.setNotifyOn(Set.of(ApiUsageStateValue.WARNING, ApiUsageStateValue.DISABLED)); apiUsageLimitRuleTriggerConfig.setNotifyOn(Set.of(ApiUsageStateValue.WARNING, ApiUsageStateValue.DISABLED));
createRule(tenantId, "API usage limit", apiUsageLimitNotificationTemplate.getId(), apiUsageLimitRuleTriggerConfig, createRule(tenantId, "API usage limit", apiUsageLimitNotificationTemplate.getId(), apiUsageLimitRuleTriggerConfig,
List.of(affectedTenantAdmins.getId(), sysAdmins.getId()), "Send notification to tenant admins and system admins when API feature usage state changed"); List.of(affectedTenantAdmins.getId(), sysAdmins.getId()), "Send notification to tenant admins and system admins when API feature usage state changed");
NotificationTemplate newPlatformVersionNotificationTemplate = createTemplate(tenantId, "New platform version notification", NotificationType.NEW_PLATFORM_VERSION,
"New version <b>${latestVersion}</b> is available",
"Current platform version is ${currentVersion}",
null, "Open release notes", "${latestVersionReleaseNotesUrl}");
NewPlatformVersionNotificationRuleTriggerConfig newPlatformVersionRuleTriggerConfig = new NewPlatformVersionNotificationRuleTriggerConfig();
createRule(tenantId, "New platform version", newPlatformVersionNotificationTemplate.getId(), newPlatformVersionRuleTriggerConfig,
List.of(sysAdmins.getId(), tenantAdmins.getId()), "Send notification to system admins and tenant admins when new platform version is available");
return; return;
} }

14
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html

@ -478,6 +478,20 @@
</section> </section>
</form> </form>
</mat-step> </mat-step>
<mat-step *ngIf="ruleNotificationForm.get('triggerType').value === triggerType.NEW_PLATFORM_VERSION"
[stepControl]="newPlatformVersionTemplateForm">
<ng-template matStepLabel>{{ 'notification.new-platform-version-trigger-settings' | translate }}</ng-template>
<form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig">
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form>
</mat-step>
</mat-horizontal-stepper> </mat-horizontal-stepper>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>

15
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts

@ -94,6 +94,7 @@ export class RuleNotificationDialogComponent extends
ruleEngineEventsTemplateForm: FormGroup; ruleEngineEventsTemplateForm: FormGroup;
entitiesLimitTemplateForm: FormGroup; entitiesLimitTemplateForm: FormGroup;
apiUsageLimitTemplateForm: FormGroup; apiUsageLimitTemplateForm: FormGroup;
newPlatformVersionTemplateForm: FormGroup;
triggerType = TriggerType; triggerType = TriggerType;
triggerTypes: TriggerType[]; triggerTypes: TriggerType[];
@ -294,6 +295,12 @@ export class RuleNotificationDialogComponent extends
}) })
}); });
this.newPlatformVersionTemplateForm = this.fb.group({
triggerConfig: this.fb.group({
})
});
this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([ this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([
[TriggerType.ALARM, this.alarmTemplateForm], [TriggerType.ALARM, this.alarmTemplateForm],
[TriggerType.ALARM_COMMENT, this.alarmCommentTemplateForm], [TriggerType.ALARM_COMMENT, this.alarmCommentTemplateForm],
@ -302,7 +309,8 @@ export class RuleNotificationDialogComponent extends
[TriggerType.ALARM_ASSIGNMENT, this.alarmAssignmentTemplateForm], [TriggerType.ALARM_ASSIGNMENT, this.alarmAssignmentTemplateForm],
[TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, this.ruleEngineEventsTemplateForm], [TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, this.ruleEngineEventsTemplateForm],
[TriggerType.ENTITIES_LIMIT, this.entitiesLimitTemplateForm], [TriggerType.ENTITIES_LIMIT, this.entitiesLimitTemplateForm],
[TriggerType.API_USAGE_LIMIT, this.apiUsageLimitTemplateForm] [TriggerType.API_USAGE_LIMIT, this.apiUsageLimitTemplateForm],
[TriggerType.NEW_PLATFORM_VERSION, this.newPlatformVersionTemplateForm]
]); ]);
if (data.isAdd || data.isCopy) { if (data.isAdd || data.isCopy) {
@ -433,9 +441,10 @@ export class RuleNotificationDialogComponent extends
private allowTriggerTypes(): TriggerType[] { private allowTriggerTypes(): TriggerType[] {
if (this.isSysAdmin()) { if (this.isSysAdmin()) {
return [TriggerType.ENTITIES_LIMIT, TriggerType.API_USAGE_LIMIT]; return [TriggerType.ENTITIES_LIMIT, TriggerType.API_USAGE_LIMIT, TriggerType.NEW_PLATFORM_VERSION];
} }
return Object.values(TriggerType).filter(type => type !== TriggerType.ENTITIES_LIMIT && type !== TriggerType.API_USAGE_LIMIT); return Object.values(TriggerType).filter(type => type !== TriggerType.ENTITIES_LIMIT && type !== TriggerType.API_USAGE_LIMIT
&& type !== TriggerType.NEW_PLATFORM_VERSION);
} }
get allowEntityTypeForEntityAction(): EntityType[] { get allowEntityTypeForEntityAction(): EntityType[] {

5
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts

@ -176,9 +176,10 @@ export class TemplateNotificationDialogComponent
private allowNotificationType(): NotificationType[] { private allowNotificationType(): NotificationType[] {
if (this.isSysAdmin()) { if (this.isSysAdmin()) {
return [NotificationType.GENERAL, NotificationType.ENTITIES_LIMIT, NotificationType.API_USAGE_LIMIT]; return [NotificationType.GENERAL, NotificationType.ENTITIES_LIMIT, NotificationType.API_USAGE_LIMIT, NotificationType.NEW_PLATFORM_VERSION];
} }
return Object.values(NotificationType) return Object.values(NotificationType)
.filter(type => type !== NotificationType.ENTITIES_LIMIT && type !== NotificationType.API_USAGE_LIMIT); .filter(type => type !== NotificationType.ENTITIES_LIMIT && type !== NotificationType.API_USAGE_LIMIT
&& type !== NotificationType.NEW_PLATFORM_VERSION);
} }
} }

11
ui-ngx/src/app/shared/models/notification.models.ts

@ -442,6 +442,7 @@ export enum NotificationType {
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT', RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT',
ENTITIES_LIMIT = 'ENTITIES_LIMIT', ENTITIES_LIMIT = 'ENTITIES_LIMIT',
API_USAGE_LIMIT = 'API_USAGE_LIMIT', API_USAGE_LIMIT = 'API_USAGE_LIMIT',
NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION',
RULE_ENGINE = 'RULE_ENGINE' RULE_ENGINE = 'RULE_ENGINE'
} }
@ -536,6 +537,12 @@ export const NotificationTemplateTypeTranslateMap = new Map<NotificationType, No
helpId: 'notification/api_usage_limit' helpId: 'notification/api_usage_limit'
} }
], ],
[NotificationType.NEW_PLATFORM_VERSION,
{
name: 'notification.template-type.new-platform-version',
helpId: 'notification/new_platform_version'
}
],
[NotificationType.RULE_ENGINE, [NotificationType.RULE_ENGINE,
{ {
name: 'notification.template-type.rule-engine', name: 'notification.template-type.rule-engine',
@ -552,7 +559,8 @@ export enum TriggerType {
ALARM_ASSIGNMENT = 'ALARM_ASSIGNMENT', ALARM_ASSIGNMENT = 'ALARM_ASSIGNMENT',
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT', RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT',
ENTITIES_LIMIT = 'ENTITIES_LIMIT', ENTITIES_LIMIT = 'ENTITIES_LIMIT',
API_USAGE_LIMIT = 'API_USAGE_LIMIT' API_USAGE_LIMIT = 'API_USAGE_LIMIT',
NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION'
} }
export const TriggerTypeTranslationMap = new Map<TriggerType, string>([ export const TriggerTypeTranslationMap = new Map<TriggerType, string>([
@ -564,4 +572,5 @@ export const TriggerTypeTranslationMap = new Map<TriggerType, string>([
[TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, 'notification.trigger.rule-engine-lifecycle-event'], [TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, 'notification.trigger.rule-engine-lifecycle-event'],
[TriggerType.ENTITIES_LIMIT, 'notification.trigger.entities-limit'], [TriggerType.ENTITIES_LIMIT, 'notification.trigger.entities-limit'],
[TriggerType.API_USAGE_LIMIT, 'notification.trigger.api-usage-limit'], [TriggerType.API_USAGE_LIMIT, 'notification.trigger.api-usage-limit'],
[TriggerType.NEW_PLATFORM_VERSION, 'notification.trigger.new-platform-version'],
]); ]);

5
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2749,6 +2749,7 @@
"all": "All", "all": "All",
"api-feature-hint": "If the field is empty, the trigger will be applied to all api features", "api-feature-hint": "If the field is empty, the trigger will be applied to all api features",
"api-usage-trigger-settings": "API usage trigger settings", "api-usage-trigger-settings": "API usage trigger settings",
"new-platform-version-trigger-settings": "New platform version trigger settings",
"at-least-one-should-be-selected": "At least one should be selected", "at-least-one-should-be-selected": "At least one should be selected",
"basic-settings": "Basic settings", "basic-settings": "Basic settings",
"button-text": "Button text", "button-text": "Button text",
@ -2932,7 +2933,8 @@
"entity-action": "Entity action", "entity-action": "Entity action",
"general": "General", "general": "General",
"rule-engine-lifecycle-event": "Rule engine lifecycle event", "rule-engine-lifecycle-event": "Rule engine lifecycle event",
"rule-engine": "Rule engine" "rule-engine": "Rule engine",
"new-platform-version": "New platform version"
}, },
"templates": "Templates", "templates": "Templates",
"tenant-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all tenant profiles", "tenant-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all tenant profiles",
@ -2949,6 +2951,7 @@
"entities-limit": "Entities limit", "entities-limit": "Entities limit",
"entity-action": "Entity action", "entity-action": "Entity action",
"rule-engine-lifecycle-event": "Rule engine lifecycle event", "rule-engine-lifecycle-event": "Rule engine lifecycle event",
"new-platform-version": "New platform version",
"trigger": "Trigger", "trigger": "Trigger",
"trigger-required": "Trigger is required" "trigger-required": "Trigger is required"
}, },

Loading…
Cancel
Save