Commit c2bd4117e440da4c1a2ff982e31ae7ae45e23ef2

Authored by zbeacon
2 parents 9cdf3dd8 f73b05a8
Showing 18 changed files with 210 additions and 107 deletions
... ... @@ -50,4 +50,8 @@ public class TbRateLimits {
50 50 return bucket.tryConsume(1);
51 51 }
52 52
  53 + public boolean tryConsume(long number) {
  54 + return bucket.tryConsume(number);
  55 + }
  56 +
53 57 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.queue.util;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +
  20 +import java.lang.annotation.Retention;
  21 +import java.lang.annotation.RetentionPolicy;
  22 +
  23 +@Retention(RetentionPolicy.RUNTIME)
  24 +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'")
  25 +public @interface TbTransportComponent {
  26 +}
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
20 20 import org.thingsboard.server.common.data.id.DeviceProfileId;
21 21 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
22 22 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  23 +import org.thingsboard.server.common.transport.limits.TransportRateLimitType;
23 24 import org.thingsboard.server.gen.transport.TransportProtos;
24 25 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
25 26 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
... ... @@ -69,6 +70,8 @@ public interface TransportService {
69 70
70 71 boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback);
71 72
  73 + boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits);
  74 +
72 75 void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback);
73 76
74 77 void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback);
... ...
... ... @@ -23,11 +23,13 @@ import org.thingsboard.server.common.data.id.DeviceId;
23 23 import org.thingsboard.server.common.data.id.TenantId;
24 24 import org.thingsboard.server.common.transport.TransportTenantProfileCache;
25 25 import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
  26 +import org.thingsboard.server.queue.util.TbTransportComponent;
26 27
27 28 import java.util.concurrent.ConcurrentHashMap;
28 29 import java.util.concurrent.ConcurrentMap;
29 30
30 31 @Service
  32 +@TbTransportComponent
31 33 @Slf4j
32 34 public class DefaultTransportRateLimitService implements TransportRateLimitService {
33 35
... ... @@ -43,23 +45,21 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
43 45 }
44 46
45 47 @Override
46   - public TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limitType) {
47   - TransportRateLimit[] limits = perTenantLimits.get(tenantId);
48   - if (limits == null) {
49   - limits = fetchProfileAndInit(tenantId);
50   - perTenantLimits.put(tenantId, limits);
51   - }
52   - return limits[limitType.ordinal()];
53   - }
54   -
55   - @Override
56   - public TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limitType) {
57   - TransportRateLimit[] limits = perDeviceLimits.get(deviceId);
58   - if (limits == null) {
59   - limits = fetchProfileAndInit(tenantId);
60   - perDeviceLimits.put(deviceId, limits);
  48 + public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) {
  49 + TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId);
  50 + TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId);
  51 + for (TransportRateLimitType limitType : limits) {
  52 + TransportRateLimit rateLimit;
  53 + if (limitType.isTenantLevel()) {
  54 + rateLimit = tenantLimits[limitType.ordinal()];
  55 + } else {
  56 + rateLimit = deviceLimits[limitType.ordinal()];
  57 + }
  58 + if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) {
  59 + return limitType;
  60 + }
61 61 }
62   - return limits[limitType.ordinal()];
  62 + return null;
63 63 }
64 64
65 65 @Override
... ... @@ -75,7 +75,17 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
75 75 mergeLimits(tenantId, fetchProfileAndInit(tenantId));
76 76 }
77 77
78   - public void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) {
  78 + @Override
  79 + public void remove(TenantId tenantId) {
  80 + perTenantLimits.remove(tenantId);
  81 + }
  82 +
  83 + @Override
  84 + public void remove(DeviceId deviceId) {
  85 + perDeviceLimits.remove(deviceId);
  86 + }
  87 +
  88 + private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) {
79 89 TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId);
80 90 if (oldRateLimits == null) {
81 91 perTenantLimits.put(tenantId, newRateLimits);
... ... @@ -90,16 +100,6 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
90 100 }
91 101 }
92 102
93   - @Override
94   - public void remove(TenantId tenantId) {
95   - perTenantLimits.remove(tenantId);
96   - }
97   -
98   - @Override
99   - public void remove(DeviceId deviceId) {
100   - perDeviceLimits.remove(deviceId);
101   - }
102   -
103 103 private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) {
104 104 return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId)));
105 105 }
... ... @@ -112,4 +112,22 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
112 112 }
113 113 return rateLimits;
114 114 }
  115 +
  116 + private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) {
  117 + TransportRateLimit[] limits = perTenantLimits.get(tenantId);
  118 + if (limits == null) {
  119 + limits = fetchProfileAndInit(tenantId);
  120 + perTenantLimits.put(tenantId, limits);
  121 + }
  122 + return limits;
  123 + }
  124 +
  125 + private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) {
  126 + TransportRateLimit[] limits = perDeviceLimits.get(deviceId);
  127 + if (limits == null) {
  128 + limits = fetchProfileAndInit(tenantId);
  129 + perDeviceLimits.put(deviceId, limits);
  130 + }
  131 + return limits;
  132 + }
115 133 }
... ...
... ... @@ -23,6 +23,11 @@ public class DummyTransportRateLimit implements TransportRateLimit {
23 23 }
24 24
25 25 @Override
  26 + public boolean tryConsume(long number) {
  27 + return true;
  28 + }
  29 +
  30 + @Override
26 31 public boolean tryConsume() {
27 32 return true;
28 33 }
... ...
... ... @@ -31,4 +31,8 @@ public class SimpleTransportRateLimit implements TransportRateLimit {
31 31 return rateLimit.tryConsume();
32 32 }
33 33
  34 + @Override
  35 + public boolean tryConsume(long number) {
  36 + return number <= 0 || rateLimit.tryConsume(number);
  37 + }
34 38 }
... ...
... ... @@ -21,4 +21,6 @@ public interface TransportRateLimit {
21 21
22 22 boolean tryConsume();
23 23
  24 + boolean tryConsume(long number);
  25 +
24 26 }
... ...
... ... @@ -21,9 +21,7 @@ import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult
21 21
22 22 public interface TransportRateLimitService {
23 23
24   - TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limit);
25   -
26   - TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limit);
  24 + TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits);
27 25
28 26 void update(TenantProfileUpdateResult update);
29 27
... ...
... ... @@ -19,15 +19,29 @@ import lombok.Getter;
19 19
20 20 public enum TransportRateLimitType {
21 21
22   - TENANT_MAX_MSGS("transport.tenant.max.msg"),
23   - TENANT_MAX_DATA_POINTS("transport.tenant.max.dataPoints"),
24   - DEVICE_MAX_MSGS("transport.device.max.msg"),
25   - DEVICE_MAX_DATA_POINTS("transport.device.max.dataPoints");
  22 + TENANT_MAX_MSGS("transport.tenant.msg", true, true),
  23 + TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true),
  24 + TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false),
  25 + DEVICE_MAX_MSGS("transport.device.msg", false, true),
  26 + DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true),
  27 + DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false);
26 28
27 29 @Getter
28 30 private final String configurationKey;
  31 + @Getter
  32 + private final boolean tenantLevel;
  33 + @Getter
  34 + private final boolean deviceLevel;
  35 + @Getter
  36 + private final boolean messageLevel;
  37 + @Getter
  38 + private final boolean dataPointLevel;
29 39
30   - TransportRateLimitType(String configurationKey) {
  40 + TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) {
31 41 this.configurationKey = configurationKey;
  42 + this.tenantLevel = tenantLevel;
  43 + this.deviceLevel = !tenantLevel;
  44 + this.messageLevel = messageLevel;
  45 + this.dataPointLevel = !messageLevel;
32 46 }
33 47 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
23 23 import org.thingsboard.server.common.data.id.DeviceProfileId;
24 24 import org.thingsboard.server.common.transport.TransportDeviceProfileCache;
25 25 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  26 +import org.thingsboard.server.queue.util.TbTransportComponent;
26 27
27 28 import java.util.Optional;
28 29 import java.util.concurrent.ConcurrentHashMap;
... ... @@ -30,7 +31,7 @@ import java.util.concurrent.ConcurrentMap;
30 31
31 32 @Slf4j
32 33 @Component
33   -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'")
  34 +@TbTransportComponent
34 35 public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache {
35 36
36 37 private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>();
... ...
... ... @@ -79,6 +79,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
79 79 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
80 80 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
81 81 import org.thingsboard.server.queue.provider.TbTransportQueueFactory;
  82 +import org.thingsboard.server.queue.util.TbTransportComponent;
82 83
83 84 import javax.annotation.PostConstruct;
84 85 import javax.annotation.PreDestroy;
... ... @@ -103,7 +104,7 @@ import java.util.concurrent.atomic.AtomicInteger;
103 104 */
104 105 @Slf4j
105 106 @Service
106   -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'")
  107 +@TbTransportComponent
107 108 public class DefaultTransportService implements TransportService {
108 109
109 110 @Value("${transport.sessions.inactivity_timeout}")
... ... @@ -363,7 +364,11 @@ public class DefaultTransportService implements TransportService {
363 364
364 365 @Override
365 366 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
366   - if (checkLimits(sessionInfo, msg, callback)) {
  367 + int dataPoints = 0;
  368 + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
  369 + dataPoints += tsKv.getKvCount();
  370 + }
  371 + if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) {
367 372 reportActivityInternal(sessionInfo);
368 373 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
369 374 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
... ... @@ -384,7 +389,7 @@ public class DefaultTransportService implements TransportService {
384 389
385 390 @Override
386 391 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
387   - if (checkLimits(sessionInfo, msg, callback)) {
  392 + if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) {
388 393 reportActivityInternal(sessionInfo);
389 394 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
390 395 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
... ... @@ -574,37 +579,34 @@ public class DefaultTransportService implements TransportService {
574 579 sessions.remove(toSessionId(sessionInfo));
575 580 }
576 581
  582 + private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS};
  583 + private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values();
  584 +
577 585 @Override
578 586 public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) {
  587 + return checkLimits(sessionInfo, msg, callback, 0, DEFAULT);
  588 + }
  589 +
  590 + @Override
  591 + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits) {
579 592 if (log.isTraceEnabled()) {
580 593 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
581 594 }
582 595 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
583   -
584   - TransportRateLimit tenantRateLimit = rateLimitService.getRateLimit(tenantId, TransportRateLimitType.TENANT_MAX_MSGS);
585   -
586   - if (!tenantRateLimit.tryConsume()) {
587   - if (callback != null) {
588   - callback.onError(new TbRateLimitsException(EntityType.TENANT));
589   - }
590   - if (log.isTraceEnabled()) {
591   - log.trace("[{}][{}] Tenant level rate limit detected: {}", toSessionId(sessionInfo), tenantId, msg);
592   - }
593   - return false;
594   - }
595 596 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
596   - TransportRateLimit deviceRateLimit = rateLimitService.getRateLimit(tenantId, deviceId, TransportRateLimitType.DEVICE_MAX_MSGS);
597   - if (!deviceRateLimit.tryConsume()) {
  597 +
  598 + TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits);
  599 + if (limit == null) {
  600 + return true;
  601 + } else {
598 602 if (callback != null) {
599   - callback.onError(new TbRateLimitsException(EntityType.DEVICE));
  603 + callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE));
600 604 }
601 605 if (log.isTraceEnabled()) {
602   - log.trace("[{}][{}] Device level rate limit detected: {}", toSessionId(sessionInfo), deviceId, msg);
  606 + log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg);
603 607 }
604 608 return false;
605 609 }
606   -
607   - return true;
608 610 }
609 611
610 612 protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) {
... ...
... ... @@ -33,6 +33,7 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
33 33 import org.thingsboard.server.gen.transport.TransportProtos;
34 34 import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
35 35 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
  36 +import org.thingsboard.server.queue.util.TbTransportComponent;
36 37
37 38 import java.util.Collections;
38 39 import java.util.Optional;
... ... @@ -43,7 +44,7 @@ import java.util.concurrent.locks.Lock;
43 44 import java.util.concurrent.locks.ReentrantLock;
44 45
45 46 @Component
46   -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'")
  47 +@TbTransportComponent
47 48 @Slf4j
48 49 public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache {
49 50
... ...
... ... @@ -91,6 +91,12 @@
91 91 </mat-error>
92 92 </mat-form-field>
93 93 </div>
  94 + <div fxLayout="column" fxLayoutAlign="flex-end start">
  95 + <tb-rule-chain-autocomplete
  96 + labelText="device-profile.default-rule-chain"
  97 + formControlName="defaultRuleChainId">
  98 + </tb-rule-chain-autocomplete>
  99 + </div>
94 100 </div>
95 101 <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;">
96 102 {{ 'device.is-gateway' | translate }}
... ...
... ... @@ -42,6 +42,7 @@ import { ErrorStateMatcher } from '@angular/material/core';
42 42 import { StepperSelectionEvent } from '@angular/cdk/stepper';
43 43 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
44 44 import { MediaBreakpoints } from '@shared/models/constants';
  45 +import { RuleChainId } from '@shared/models/id/rule-chain-id';
45 46
46 47 @Component({
47 48 selector: 'tb-device-wizard',
... ... @@ -103,6 +104,7 @@ export class DeviceWizardDialogComponent extends
103 104 addProfileType: [0],
104 105 deviceProfileId: [null, Validators.required],
105 106 newDeviceProfileTitle: [{value: null, disabled: true}],
  107 + defaultRuleChainId: [{value: null, disabled: true}],
106 108 description: ['']
107 109 }
108 110 );
... ... @@ -114,6 +116,7 @@ export class DeviceWizardDialogComponent extends
114 116 this.deviceWizardFormGroup.get('deviceProfileId').enable();
115 117 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null);
116 118 this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable();
  119 + this.deviceWizardFormGroup.get('defaultRuleChainId').disable();
117 120 this.deviceWizardFormGroup.updateValueAndValidity();
118 121 this.createProfile = false;
119 122 this.createTransportConfiguration = false;
... ... @@ -122,6 +125,7 @@ export class DeviceWizardDialogComponent extends
122 125 this.deviceWizardFormGroup.get('deviceProfileId').disable();
123 126 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);
124 127 this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable();
  128 + this.deviceWizardFormGroup.get('defaultRuleChainId').enable();
125 129 this.deviceWizardFormGroup.updateValueAndValidity();
126 130 this.createProfile = true;
127 131 this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value &&
... ... @@ -274,6 +278,9 @@ export class DeviceWizardDialogComponent extends
274 278 provisionConfiguration: deviceProvisionConfiguration
275 279 }
276 280 };
  281 + if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) {
  282 + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value);
  283 + }
277 284 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe(
278 285 map(profile => profile.id),
279 286 tap((profileId) => {
... ...
... ... @@ -142,6 +142,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
142 142 tenantNamePattern = {value: null, disabled: true};
143 143 }
144 144 const basicGroup = this.fb.group({
  145 + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],
145 146 firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],
146 147 lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''],
147 148 tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
... ... @@ -151,11 +152,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
151 152 alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false]
152 153 });
153 154
154   - if (MapperConfigType.GITHUB !== type) {
155   - basicGroup.addControl('emailAttributeKey',
156   - this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required));
157   - }
158   -
159 155 this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => {
160 156 if (domain === 'CUSTOM') {
161 157 basicGroup.get('tenantNamePattern').enable();
... ... @@ -358,11 +354,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
358 354 mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
359 355 } else {
360 356 mapperConfig.removeControl('custom');
361   - if (mapperConfig.get('basic')) {
362   - mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic));
363   - } else {
  357 + if (!mapperConfig.get('basic')) {
364 358 mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic));
365 359 }
  360 + if (type === MapperConfigType.GITHUB) {
  361 + mapperConfig.get('basic.emailAttributeKey').disable();
  362 + mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false});
  363 + } else {
  364 + mapperConfig.get('basic.emailAttributeKey').enable();
  365 + }
366 366 }
367 367 }
368 368
... ...
... ... @@ -97,10 +97,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
97 97 ngOnInit() {
98 98 this.entitySelectFormGroup.get('entityType').valueChanges.subscribe(
99 99 (value) => {
100   - if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER ||
101   - value === AliasEntityType.CURRENT_USER_OWNER) {
102   - this.modelValue.id = NULL_UUID;
103   - }
104 100 this.updateView(value, this.modelValue.id);
105 101 }
106 102 );
... ... @@ -140,20 +136,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
140 136 }
141 137
142 138 updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) {
143   - if (this.modelValue.entityType !== entityType ||
144   - this.modelValue.id !== entityId) {
145   - this.modelValue = {
146   - entityType,
147   - id: this.modelValue.entityType !== entityType ? null : entityId
148   - };
149   - if (this.modelValue.entityType && (this.modelValue.id ||
150   - this.modelValue.entityType === AliasEntityType.CURRENT_TENANT ||
151   - this.modelValue.entityType === AliasEntityType.CURRENT_USER ||
152   - this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER)) {
153   - this.propagateChange(this.modelValue);
154   - } else {
155   - this.propagateChange(null);
156   - }
  139 + if (this.modelValue.entityType !== entityType || this.modelValue.id !== entityId) {
  140 + this.modelValue = {
  141 + entityType,
  142 + id: this.modelValue.entityType !== entityType ? null : entityId
  143 + };
  144 +
  145 + if (this.modelValue.entityType === AliasEntityType.CURRENT_TENANT
  146 + || this.modelValue.entityType === AliasEntityType.CURRENT_USER
  147 + || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) {
  148 + this.modelValue.id = NULL_UUID;
  149 + }
  150 +
  151 + if (this.modelValue.entityType && this.modelValue.id) {
  152 + this.propagateChange(this.modelValue);
  153 + } else {
  154 + this.propagateChange(null);
  155 + }
157 156 }
158 157 }
159 158 }
... ...
... ... @@ -36,7 +36,6 @@ import { TranslateService } from '@ngx-translate/core';
36 36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
37 37 import { ResizeObserver } from '@juggle/resize-observer';
38 38 import { TbEditorCompleter } from '@shared/models/ace/completion.models';
39   -import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
40 39
41 40 @Component({
42 41 selector: 'tb-js-func',
... ... @@ -64,6 +63,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
64 63 private jsEditor: ace.Ace.Editor;
65 64 private editorsResizeCaf: CancelAnimationFrame;
66 65 private editorResize$: ResizeObserver;
  66 + private ignoreChange = false;
67 67
68 68 toastTargetId = `jsFuncEditor-${guid()}`;
69 69
... ... @@ -154,8 +154,10 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
154 154 this.jsEditor.session.setUseWrapMode(true);
155 155 this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
156 156 this.jsEditor.on('change', () => {
157   - this.cleanupJsErrors();
158   - this.updateView();
  157 + if (!this.ignoreChange) {
  158 + this.cleanupJsErrors();
  159 + this.updateView();
  160 + }
159 161 });
160 162 if (this.editorCompleter) {
161 163 this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])];
... ... @@ -332,7 +334,9 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
332 334 writeValue(value: string): void {
333 335 this.modelValue = value;
334 336 if (this.jsEditor) {
  337 + this.ignoreChange = true;
335 338 this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
  339 + this.ignoreChange = false;
336 340 }
337 341 }
338 342
... ...
... ... @@ -61,6 +61,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
61 61 private jsonEditor: ace.Ace.Editor;
62 62 private editorsResizeCaf: CancelAnimationFrame;
63 63 private editorResize$: ResizeObserver;
  64 + private ignoreChange = false;
64 65
65 66 toastTargetId = `jsonContentEditor-${guid()}`;
66 67
... ... @@ -140,8 +141,13 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
140 141 this.jsonEditor.session.setUseWrapMode(true);
141 142 this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
142 143 this.jsonEditor.on('change', () => {
143   - this.cleanupJsonErrors();
144   - this.updateView();
  144 + if (!this.ignoreChange) {
  145 + this.cleanupJsonErrors();
  146 + this.updateView();
  147 + }
  148 + });
  149 + this.jsonEditor.on('blur', () => {
  150 + this.contentValid = !this.validateContent || this.doValidate(true);
145 151 });
146 152 this.editorResize$ = new ResizeObserver(() => {
147 153 this.onAceEditorResize();
... ... @@ -210,34 +216,36 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
210 216 this.cleanupJsonErrors();
211 217 this.contentValid = true;
212 218 this.propagateChange(this.contentBody);
213   - this.contentValid = this.doValidate();
  219 + this.contentValid = this.doValidate(true);
214 220 this.propagateChange(this.contentBody);
215 221 }
216 222 }
217 223
218   - private doValidate(): boolean {
  224 + private doValidate(showErrorToast = false): boolean {
219 225 try {
220 226 if (this.validateContent && this.contentType === ContentType.JSON) {
221 227 JSON.parse(this.contentBody);
222 228 }
223 229 return true;
224 230 } catch (ex) {
225   - let errorInfo = 'Error:';
226   - if (ex.name) {
227   - errorInfo += ' ' + ex.name + ':';
228   - }
229   - if (ex.message) {
230   - errorInfo += ' ' + ex.message;
  231 + if (showErrorToast) {
  232 + let errorInfo = 'Error:';
  233 + if (ex.name) {
  234 + errorInfo += ' ' + ex.name + ':';
  235 + }
  236 + if (ex.message) {
  237 + errorInfo += ' ' + ex.message;
  238 + }
  239 + this.store.dispatch(new ActionNotificationShow(
  240 + {
  241 + message: errorInfo,
  242 + type: 'error',
  243 + target: this.toastTargetId,
  244 + verticalPosition: 'bottom',
  245 + horizontalPosition: 'left'
  246 + }));
  247 + this.errorShowed = true;
231 248 }
232   - this.store.dispatch(new ActionNotificationShow(
233   - {
234   - message: errorInfo,
235   - type: 'error',
236   - target: this.toastTargetId,
237   - verticalPosition: 'bottom',
238   - horizontalPosition: 'left'
239   - }));
240   - this.errorShowed = true;
241 249 return false;
242 250 }
243 251 }
... ... @@ -256,8 +264,9 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
256 264 this.contentBody = value;
257 265 this.contentValid = true;
258 266 if (this.jsonEditor) {
  267 + this.ignoreChange = true;
259 268 this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
260   - // this.jsonEditor.
  269 + this.ignoreChange = false;
261 270 }
262 271 }
263 272
... ...