NIFI-9440 Allow Controller Services to have configurable Bulletins

Signed-off-by: Matthew Burgess <mattyb149@apache.org>

This closes #6035
This commit is contained in:
Nissim Shiman 2021-12-06 22:52:07 +00:00 committed by Matthew Burgess
parent b3b13a4ee0
commit db11961026
No known key found for this signature in database
GPG Key ID: 05D3DEB8126DAD24
28 changed files with 171 additions and 5 deletions

View File

@ -27,6 +27,7 @@ public class VersionedControllerService extends VersionedConfigurableExtension {
private String annotationData;
private ScheduledState scheduledState;
private String bulletinLevel;
@ApiModelProperty(value = "Lists the APIs this Controller Service implements.")
public List<ControllerServiceAPI> getControllerServiceApis() {
@ -59,4 +60,13 @@ public class VersionedControllerService extends VersionedConfigurableExtension {
public void setScheduledState(final ScheduledState scheduledState) {
this.scheduledState = scheduledState;
}
@ApiModelProperty("The level at which the controller service will report bulletins.")
public String getBulletinLevel() {
return bulletinLevel;
}
public void setBulletinLevel(String bulletinLevel) {
this.bulletinLevel = bulletinLevel;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -1265,7 +1265,11 @@ You can obtain information about Controller Services by clicking the "Usage" and
image:controller-services-info-buttons.png["Controller Services Information Buttons"]
When the DFM clicks the "Configure" button, a Configure Controller Service window opens. It has three tabs: Settings, Properties,and Comments. This window is similar to the Configure Processor window. The Settings tab provides a place for the DFM to give the Controller Service a unique name (if desired). It also lists the UUID, Type, Bundle and Support information for the service and provides a list of other components (reporting tasks or other controller services) that reference the service.
When the DFM clicks the "Configure" button, a Configure Controller Service window opens. It has three tabs: Settings, Properties,and Comments. This window is similar to the Configure Processor window.
The Settings tab provides a place for the DFM to give the Controller Service a unique name (if desired). It also lists the UUID, Type, Bundle and Support information for the service and provides a list of other components (reporting tasks or other controller services) that reference the service.
Finally, the Bulletin level is able to be modified. Whenever the Controller Service writes to its log, the Controller Service will also generate a Bulletin. This setting indicates the lowest level of Bulletin that should be shown in the User Interface. By default, the Bulletin level is set to WARN, which means it will display all warning and error-level bulletins.
image:configure-controller-service-settings.png["Configure Controller Service Settings"]

View File

@ -58,6 +58,7 @@ public class ControllerServiceDTO extends ComponentDTO {
private Collection<String> validationErrors;
private String validationStatus;
private String bulletinLevel;
/**
* @return controller service name
@ -133,6 +134,20 @@ public class ControllerServiceDTO extends ComponentDTO {
this.comments = comments;
}
/**
* @return the level at which this controller service will report bulletins
*/
@ApiModelProperty(
value = "The level at which the controller service will report bulletins."
)
public String getBulletinLevel() {
return bulletinLevel;
}
public void setBulletinLevel(String bulletinLevel) {
this.bulletinLevel = bulletinLevel;
}
/**
* @return whether this controller service persists state
*/

View File

@ -47,6 +47,8 @@ import org.apache.nifi.controller.VerifiableControllerService;
import org.apache.nifi.controller.exception.ControllerServiceInstantiationException;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.logging.LogRepositoryFactory;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.InstanceClassLoader;
import org.apache.nifi.nar.NarCloseable;
@ -86,6 +88,8 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
private static final Logger LOG = LoggerFactory.getLogger(StandardControllerServiceNode.class);
public static final String BULLETIN_OBSERVER_ID = "bulletin-observer";
private final AtomicReference<ControllerServiceDetails> controllerServiceHolder = new AtomicReference<>(null);
private final ControllerServiceProvider serviceProvider;
private final ServiceStateTransition stateTransition;
@ -98,6 +102,7 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
private final Set<Tuple<ComponentNode, PropertyDescriptor>> referencingComponents = new HashSet<>();
private volatile String comment;
private volatile ProcessGroup processGroup;
private volatile LogLevel bulletinLevel = LogLevel.WARN;
private final AtomicBoolean active;
@ -712,4 +717,23 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
public ParameterLookup getParameterLookup() {
return getParameterContext();
}
@Override
public LogLevel getBulletinLevel() {
return bulletinLevel;
}
@Override
public synchronized void setBulletinLevel(LogLevel level) {
// handling backward compatibility with nifi 1.16 and earlier when bulletinLevel did not exist in flow.xml/flow.json
// and bulletins were always logged at WARN level
if (level == null) {
level = LogLevel.WARN;
}
LogRepositoryFactory.getRepository(getIdentifier()).setObservationLevel(BULLETIN_OBSERVER_ID, level);
this.bulletinLevel = level;
}
}

View File

@ -1197,6 +1197,14 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
service.setComments(proposed.getComments());
service.setName(proposed.getName());
if (proposed.getBulletinLevel() != null) {
service.setBulletinLevel(LogLevel.valueOf(proposed.getBulletinLevel()));
} else {
// this situation exists for backward compatibility with nifi 1.16 and earlier where controller services do not have bulletinLevels set in flow.xml/flow.json
// and bulletinLevels are at the WARN level by default
service.setBulletinLevel(LogLevel.WARN);
}
final Set<String> sensitiveDynamicPropertyNames = getSensitiveDynamicPropertyNames(service, proposed.getProperties(), proposed.getPropertyDescriptors().values());
final Map<String, String> properties = populatePropertiesMap(service, proposed.getProperties(), service.getProcessGroup());
service.setProperties(properties, true, sensitiveDynamicPropertyNames);

View File

@ -442,6 +442,7 @@ public class NiFiRegistryFlowMapper {
versionedService.setAnnotationData(controllerService.getAnnotationData());
versionedService.setBundle(mapBundle(controllerService.getBundleCoordinate()));
versionedService.setComments(controllerService.getComments());
versionedService.setBulletinLevel(controllerService.getBulletinLevel().name());
versionedService.setControllerServiceApis(mapControllerServiceApis(controllerService));
versionedService.setProperties(mapProperties(controllerService, serviceProvider));

View File

@ -24,6 +24,7 @@ import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.LoggableComponent;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.components.ConfigVerificationResult;
@ -141,6 +142,10 @@ public interface ControllerServiceNode extends ComponentNode, VersionedComponent
String getComments();
void setBulletinLevel(LogLevel valueOf);
LogLevel getBulletinLevel();
void verifyCanEnable();
void verifyCanDisable();

View File

@ -239,6 +239,15 @@ public class StandardFlowSnippet implements FlowSnippet {
serviceNode.setAnnotationData(controllerServiceDTO.getAnnotationData());
serviceNode.setComments(controllerServiceDTO.getComments());
serviceNode.setName(controllerServiceDTO.getName());
if (controllerServiceDTO.getBulletinLevel() != null) {
serviceNode.setBulletinLevel(LogLevel.valueOf(controllerServiceDTO.getBulletinLevel()));
} else {
// this situation exists for backward compatibility with nifi 1.16 and earlier where controller services do not have bulletinLevels set in flow.xml/flow.json
// and bulletinLevels are at the WARN level by default
serviceNode.setBulletinLevel(LogLevel.WARN);
}
if (!topLevel) {
serviceNode.setVersionedComponentId(controllerServiceDTO.getVersionedComponentId());
}

View File

@ -487,8 +487,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
LogRepositoryFactory.getRepository(serviceNode.getIdentifier()).setLogger(serviceNode.getLogger());
if (registerLogObserver) {
// Register log observer to provide bulletins when reporting task logs anything at WARN level or above
logRepository.addObserver(StandardProcessorNode.BULLETIN_OBSERVER_ID, LogLevel.WARN, new ControllerServiceLogObserver(bulletinRepository, serviceNode));
logRepository.addObserver(StandardProcessorNode.BULLETIN_OBSERVER_ID, serviceNode.getBulletinLevel(), new ControllerServiceLogObserver(bulletinRepository, serviceNode));
}
if (firstTimeAdded) {

View File

@ -115,6 +115,7 @@ public class FlowFromDOMFactory {
dto.setVersionedComponentId(getString(element, "versionedComponentId"));
dto.setName(getString(element, "name"));
dto.setComments(getString(element, "comment"));
dto.setBulletinLevel(getString(element, "bulletinLevel"));
dto.setType(getString(element, "class"));
dto.setBundle(getBundle(DomUtils.getChild(element, "bundle")));

View File

@ -624,6 +624,7 @@ public class StandardFlowSerializer implements FlowSerializer<Document> {
addTextElement(serviceElement, "versionedComponentId", serviceNode.getVersionedComponentId());
addTextElement(serviceElement, "name", serviceNode.getName());
addTextElement(serviceElement, "comment", serviceNode.getComments());
addTextElement(serviceElement, "bulletinLevel", serviceNode.getBulletinLevel().toString());
addTextElement(serviceElement, "class", serviceNode.getCanonicalClassName());
addBundle(serviceElement, serviceNode.getBundleCoordinate());

View File

@ -68,6 +68,7 @@ import org.apache.nifi.groups.ComponentIdGenerator;
import org.apache.nifi.groups.ComponentScheduler;
import org.apache.nifi.groups.FlowSynchronizationOptions;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
@ -753,6 +754,14 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
serviceNode.setAnnotationData(versionedControllerService.getAnnotationData());
serviceNode.setComments(versionedControllerService.getComments());
if (versionedControllerService.getBulletinLevel() != null) {
serviceNode.setBulletinLevel(LogLevel.valueOf(versionedControllerService.getBulletinLevel()));
} else {
// this situation exists for backward compatibility with nifi 1.16 and earlier where controller services do not have bulletinLevels set in flow.xml/flow.json
// and bulletinLevels are at the WARN level by default
serviceNode.setBulletinLevel(LogLevel.WARN);
}
final Set<String> sensitiveDynamicPropertyNames = getSensitiveDynamicPropertyNames(serviceNode, versionedControllerService);
final Map<String, String> decryptedProperties = decryptProperties(versionedControllerService.getProperties(), encryptor);
serviceNode.setProperties(decryptedProperties, false, sensitiveDynamicPropertyNames);

View File

@ -23,6 +23,7 @@ import org.apache.nifi.controller.serialization.FlowEncodingVersion;
import org.apache.nifi.controller.serialization.FlowFromDOMFactory;
import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.reporting.BulletinRepository;
import org.apache.nifi.util.BundleUtils;
import org.apache.nifi.util.DomUtils;
@ -175,6 +176,7 @@ public class ControllerServiceLoader {
controllerService.getBundleCoordinate(), Collections.emptySet(), false, true, null);
clone.setName(controllerService.getName());
clone.setComments(controllerService.getComments());
clone.setBulletinLevel(controllerService.getBulletinLevel());
if (controllerService.getProperties() != null) {
Map<String,String> properties = new HashMap<>();
@ -206,6 +208,15 @@ public class ControllerServiceLoader {
final ControllerServiceNode node = flowController.getFlowManager().createControllerService(dto.getType(), dto.getId(), coordinate, Collections.emptySet(), false, true, null);
node.setName(dto.getName());
node.setComments(dto.getComments());
if (dto.getBulletinLevel() != null) {
node.setBulletinLevel(LogLevel.valueOf(dto.getBulletinLevel()));
} else {
// this situation exists for backward compatibility with nifi 1.16 and earlier where controller services do not have bulletinLevels set in flow.xml/flow.json
// and bulletinLevels are at the WARN level by default
node.setBulletinLevel(LogLevel.WARN);
}
node.setVersionedComponentId(dto.getVersionedComponentId());
return node;
}

View File

@ -735,6 +735,7 @@ public class FingerprintFactory {
addBundleFingerprint(builder, dto.getBundle());
builder.append(dto.getComments());
builder.append(dto.getBulletinLevel());
builder.append(dto.getAnnotationData());
builder.append(dto.getState());

View File

@ -502,6 +502,7 @@
<xs:element name="versionedComponentId" type="NonEmptyStringType" minOccurs="0" maxOccurs="1" />
<xs:element name="name" type="NonEmptyStringType" />
<xs:element name="comment" type="xs:string" />
<xs:element name="bulletinLevel" type="LogLevel" minOccurs="0" maxOccurs="1" />
<xs:element name="class" type="NonEmptyStringType" />
<xs:element name="bundle" type="BundleType" />
<xs:element name="enabled" type="xs:boolean" />

View File

@ -821,13 +821,15 @@ public class TestFlowController {
assertEquals(ServiceA.class.getCanonicalName(), controllerServiceNode.getCanonicalClassName());
assertEquals(ServiceA.class.getSimpleName(), controllerServiceNode.getComponentType());
assertEquals(ServiceA.class.getCanonicalName(), controllerServiceNode.getComponent().getClass().getCanonicalName());
assertEquals(LogLevel.WARN, controllerServiceNode.getBulletinLevel());
controller.getReloadComponent().reload(controllerServiceNode, ServiceB.class.getName(), coordinate, Collections.emptySet());
// ids and coordinate should stay the same
// ids, coordinate and bulletin Level should stay the same
assertEquals(id, controllerServiceNode.getIdentifier());
assertEquals(id, controllerServiceNode.getComponent().getIdentifier());
assertEquals(coordinate.getCoordinate(), controllerServiceNode.getBundleCoordinate().getCoordinate());
assertEquals(LogLevel.WARN, controllerServiceNode.getBulletinLevel());
// in this test we happened to change between two services that have different canonical class names
// but in the running application the DAO layer would call verifyCanUpdateBundle and would prevent this so
@ -1133,6 +1135,7 @@ public class TestFlowController {
csDto.setState(controllerServiceNode.getState().name());
csDto.setAnnotationData(controllerServiceNode.getAnnotationData());
csDto.setComments(controllerServiceNode.getComments());
csDto.setBulletinLevel(controllerServiceNode.getBulletinLevel().name());
csDto.setPersistsState(controllerServiceNode.getControllerServiceImplementation().getClass().isAnnotationPresent(Stateful.class));
csDto.setRestricted(controllerServiceNode.isRestricted());
csDto.setExtensionMissing(controllerServiceNode.isExtensionMissing());
@ -1164,6 +1167,7 @@ public class TestFlowController {
csDto.setState(controllerServiceNode.getState().name());
csDto.setAnnotationData(controllerServiceNode.getAnnotationData());
csDto.setComments(controllerServiceNode.getComments());
csDto.setBulletinLevel(controllerServiceNode.getBulletinLevel().name());
csDto.setPersistsState(controllerServiceNode.getControllerServiceImplementation().getClass().isAnnotationPresent(Stateful.class));
csDto.setRestricted(controllerServiceNode.isRestricted());
csDto.setExtensionMissing(controllerServiceNode.isExtensionMissing());

View File

@ -519,6 +519,7 @@ public class NiFiRegistryFlowMapperTest {
when(controllerServiceNode.getBundleCoordinate()).thenReturn(mock(BundleCoordinate.class));
when(controllerServiceNode.getControllerServiceImplementation()).thenReturn(mock(ControllerService.class));
when(controllerServiceNode.getProperties()).thenReturn(Collections.emptyMap());
when(controllerServiceNode.getBulletinLevel()).thenReturn(LogLevel.WARN);
return controllerServiceNode;
}

View File

@ -48,6 +48,7 @@
<id>edf22ee5-376a-46dc-a38a-919351124457</id>
<name>ControllerService</name>
<comment/>
<bulletinLevel>WARN</bulletinLevel>
<class>org.apache.nifi.controller.service.mock.ServiceD</class>
<enabled>false</enabled>
<property>

View File

@ -48,6 +48,7 @@
<id>edf22ee5-376a-46dc-a38a-919351124456</id>
<name>ControllerService</name>
<comment/>
<bulletinLevel>WARN</bulletinLevel>
<class>org.apache.nifi.controller.service.mock.ServiceD</class>
<enabled>false</enabled>
<property>

View File

@ -62,6 +62,7 @@ public class ControllerServiceAuditor extends NiFiAuditor {
private static final String NAME = "Name";
private static final String ANNOTATION_DATA = "Annotation Data";
private static final String EXTENSION_VERSION = "Extension Version";
private static final String BULLETIN_LEVEL = "Bulletin Level";
/**
* Audits the creation of controller service via createControllerService().
@ -436,6 +437,9 @@ public class ControllerServiceAuditor extends NiFiAuditor {
if (controllerServiceDTO.getComments() != null) {
values.put(COMMENTS, controllerService.getComments());
}
if (controllerServiceDTO.getBulletinLevel() != null) {
values.put(BULLETIN_LEVEL, controllerService.getBulletinLevel().name());
}
return values;
}

View File

@ -1635,6 +1635,7 @@ public final class DtoFactory {
dto.setState(controllerServiceNode.getState().name());
dto.setAnnotationData(controllerServiceNode.getAnnotationData());
dto.setComments(controllerServiceNode.getComments());
dto.setBulletinLevel(controllerServiceNode.getBulletinLevel().name());
dto.setPersistsState(controllerServiceClass.isAnnotationPresent(Stateful.class));
dto.setSupportsSensitiveDynamicProperties(controllerServiceNode.isSupportsSensitiveDynamicProperties());
dto.setRestricted(controllerServiceNode.isRestricted());
@ -4128,6 +4129,7 @@ public final class DtoFactory {
copy.setId(original.getId());
copy.setParentGroupId(original.getParentGroupId());
copy.setName(original.getName());
copy.setBulletinLevel(original.getBulletinLevel());
copy.setProperties(copy(original.getProperties()));
copy.setSensitiveDynamicPropertyNames(copy(original.getSensitiveDynamicPropertyNames()));
copy.setReferencingComponents(copy(original.getReferencingComponents()));

View File

@ -33,6 +33,7 @@ import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.controller.service.StandardConfigurationContext;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.logging.LogRepository;
import org.apache.nifi.logging.repository.NopLogRepository;
import org.apache.nifi.nar.ExtensionManager;
@ -326,7 +327,8 @@ public class StandardControllerServiceDAO extends ComponentDAO implements Contro
controllerServiceDTO.getAnnotationData(),
controllerServiceDTO.getComments(),
controllerServiceDTO.getProperties(),
controllerServiceDTO.getBundle())) {
controllerServiceDTO.getBundle(),
controllerServiceDTO.getBulletinLevel())) {
modificationRequest = true;
// validate the request
@ -356,6 +358,7 @@ public class StandardControllerServiceDAO extends ComponentDAO implements Contro
final String annotationData = controllerServiceDTO.getAnnotationData();
final String comments = controllerServiceDTO.getComments();
final Map<String, String> properties = controllerServiceDTO.getProperties();
final String bulletinLevel = controllerServiceDTO.getBulletinLevel();
controllerService.pauseValidationTrigger(); // avoid causing validation to be triggered multiple times
try {
@ -372,6 +375,9 @@ public class StandardControllerServiceDAO extends ComponentDAO implements Contro
final Set<String> sensitiveDynamicPropertyNames = controllerServiceDTO.getSensitiveDynamicPropertyNames();
controllerService.setProperties(properties, false, sensitiveDynamicPropertyNames == null ? Collections.emptySet() : sensitiveDynamicPropertyNames);
}
if (isNotNull(bulletinLevel)) {
controllerService.setBulletinLevel(LogLevel.valueOf(bulletinLevel));
}
} finally {
controllerService.resumeValidationTrigger();
}

View File

@ -50,6 +50,21 @@
<div class="setting-name">Supports Controller Service</div>
<div id="controller-service-compatible-apis" class="setting-field"></div>
</div>
<div class="setting">
<div class="bulletin-setting">
<div class="setting-name">
Bulletin level
<div class="fa fa-question-circle" alt="Info" title="The level at which this controller service will generate bulletins."></div>
</div>
<div class="controller-service-editable setting-field">
<div id="controller-service-bulletin-level-combo"></div>
</div>
<div class="controller-service-read-only setting-field hidden">
<span id="read-only-controller-service-bulletin-level"></span>
</div>
</div>
<div class="clear"></div>
</div>
</div>
<div class="spacer">&nbsp;</div>
<div class="settings-right">

View File

@ -98,6 +98,9 @@
if ($('#controller-service-comments').val() !== entity.component['comments']) {
return true;
}
if ($('#controller-service-bulletin-level-combo').combo('getSelectedOption').value !== (entity.component['bulletinLevel'] + '')) {
return true;
}
// defer to the properties
return $('#controller-service-properties').propertytable('isSaveRequired');
@ -114,6 +117,7 @@
var controllerServiceDto = {};
controllerServiceDto['id'] = $('#controller-service-id').text();
controllerServiceDto['name'] = $('#controller-service-name').val();
controllerServiceDto['bulletinLevel'] = $('#controller-service-bulletin-level-combo').combo('getSelectedOption').value;
controllerServiceDto['comments'] = $('#controller-service-comments').val();
// set the properties
@ -1859,6 +1863,27 @@
}
});
// initialize the bulletin combo
$('#controller-service-bulletin-level-combo').combo({
options: [{
text: 'DEBUG',
value: 'DEBUG'
}, {
text: 'INFO',
value: 'INFO'
}, {
text: 'WARN',
value: 'WARN'
}, {
text: 'ERROR',
value: 'ERROR'
}, {
text: 'NONE',
value: 'NONE'
}]
});
// initialize the enable scope combo
$('#enable-controller-service-scope').combo({
options: [{
@ -2005,6 +2030,11 @@
$('#controller-service-name').val(controllerService['name']);
$('#controller-service-comments').val(controllerService['comments']);
// select the appropriate bulletin level
$('#controller-service-bulletin-level-combo').combo('setSelectedOption', {
value: controllerService['bulletinLevel']
});
// set the implemented apis
if (!nfCommon.isEmpty(controllerService['controllerServiceApis'])) {
var formattedControllerServiceApis = nfCommon.getFormattedServiceApis(controllerService['controllerServiceApis']);
@ -2193,6 +2223,7 @@
nfCommon.populateField('controller-service-id', controllerService['id']);
nfCommon.populateField('controller-service-type', nfCommon.formatType(controllerService));
nfCommon.populateField('controller-service-bundle', nfCommon.formatBundle(controllerService['bundle']));
nfCommon.populateField('read-only-controller-service-bulletin-level', controllerService['bulletinLevel']);
nfCommon.populateField('read-only-controller-service-name', controllerService['name']);
nfCommon.populateField('read-only-controller-service-comments', controllerService['comments']);

View File

@ -266,6 +266,7 @@ public class StandardFlowComparator implements FlowComparator {
addIfDifferent(differences, DifferenceType.BUNDLE_CHANGED, serviceA, serviceB, VersionedControllerService::getBundle);
compareProperties(serviceA, serviceB, serviceA.getProperties(), serviceB.getProperties(), serviceA.getPropertyDescriptors(), serviceB.getPropertyDescriptors(), differences);
addIfDifferent(differences, DifferenceType.SCHEDULED_STATE_CHANGED, serviceA, serviceB, VersionedControllerService::getScheduledState);
addIfDifferent(differences, DifferenceType.BULLETIN_LEVEL_CHANGED, serviceA, serviceB, VersionedControllerService::getBulletinLevel, true, "WARN");
}
private String decrypt(final String value, final VersionedPropertyDescriptor descriptor) {

View File

@ -226,6 +226,7 @@ public class VersionedFlowBuilder {
service.setGroupIdentifier(group.getIdentifier());
service.setIdentifier(UUID.randomUUID().toString());
service.setName(type);
service.setBulletinLevel("WARN");
service.setPosition(new Position(0, 0));
service.setProperties(new HashMap<>());
service.setPropertyDescriptors(new HashMap<>());