Event filter restricted usageSummary, added for testCase(with default disabled) (#7107)

This commit is contained in:
mohitdeuex 2022-09-01 20:15:16 +05:30 committed by GitHub
parent f4c0d82882
commit 9e689f7f8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 484 additions and 176 deletions

View File

@ -22,6 +22,7 @@ import javax.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.openmetadata.catalog.filter.FilterRegistry; import org.openmetadata.catalog.filter.FilterRegistry;
import org.openmetadata.catalog.filter.Filters; import org.openmetadata.catalog.filter.Filters;
import org.openmetadata.catalog.resources.settings.SettingsCache;
import org.openmetadata.catalog.settings.Settings; import org.openmetadata.catalog.settings.Settings;
import org.openmetadata.catalog.settings.SettingsType; import org.openmetadata.catalog.settings.SettingsType;
import org.openmetadata.catalog.util.FilterUtil; import org.openmetadata.catalog.util.FilterUtil;
@ -118,6 +119,7 @@ public class SettingsRepository {
.insertSettings(setting.getConfigType().toString(), JsonUtils.pojoToJson(setting.getConfigValue())); .insertSettings(setting.getConfigType().toString(), JsonUtils.pojoToJson(setting.getConfigValue()));
if (setting.getConfigType().equals(ACTIVITY_FEED_FILTER_SETTING)) { if (setting.getConfigType().equals(ACTIVITY_FEED_FILTER_SETTING)) {
FilterRegistry.add(FilterUtil.getEventFilterFromSettings(setting)); FilterRegistry.add(FilterUtil.getEventFilterFromSettings(setting));
SettingsCache.getInstance().putSettings(setting);
} }
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);

View File

@ -1,86 +0,0 @@
package org.openmetadata.catalog.kafka;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.config.SslConfigs;
import org.openmetadata.catalog.events.WebhookPublisher;
import org.openmetadata.catalog.jdbi3.CollectionDAO;
import org.openmetadata.catalog.resources.events.EventResource;
import org.openmetadata.catalog.type.ChangeEvent;
import org.openmetadata.catalog.type.Webhook;
import org.openmetadata.catalog.util.JsonUtils;
@Slf4j
public class KafkaWebhookEventPublisher extends WebhookPublisher {
protected final Webhook webhook;
private static KafkaProducer<String, String> producer;
Properties properties = new Properties();
public KafkaWebhookEventPublisher(Webhook webhook, CollectionDAO dao) {
super(webhook, dao);
this.webhook = webhook;
if (webhook.getKafkaProperties().getSecurityProtocol().equals(KafkaEventConfiguration.SecurityProtocol.SSL)) {
// configuration for SSL Encryption
if (webhook.getKafkaProperties().getSSLProtocol() != null
&& webhook.getKafkaProperties().getSSLTrustStoreLocation() != null
&& webhook.getKafkaProperties().getSSLTrustStorePassword() != null
&& webhook.getKafkaProperties().getSSLKeystoreLocation() != null
&& webhook.getKafkaProperties().getSSLKeystorePassword() != null
&& webhook.getKafkaProperties().getSSLKeyPassword() != null) {
properties.put(
CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, webhook.getKafkaProperties().getSecurityProtocol());
properties.put(SslConfigs.SSL_PROTOCOL_CONFIG, webhook.getKafkaProperties().getSSLProtocol());
properties.put(
SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, webhook.getKafkaProperties().getSSLTrustStoreLocation());
properties.put(
SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, webhook.getKafkaProperties().getSSLTrustStorePassword());
// configuration for SSL Authentication
properties.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, webhook.getKafkaProperties().getSSLKeystoreLocation());
properties.put(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG, webhook.getKafkaProperties().getSSLKeystorePassword());
properties.put(SslConfigs.SSL_KEY_PASSWORD_CONFIG, webhook.getKafkaProperties().getSSLKeyPassword());
} else {
LOG.info("The SSL could not be configured as the required properties are not defined!");
}
}
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, webhook.getEndpoint().toString());
properties.put(ProducerConfig.ACKS_CONFIG, webhook.getKafkaProperties().getAcks());
properties.put(ProducerConfig.RETRIES_CONFIG, webhook.getKafkaProperties().getRetries());
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, webhook.getBatchSize());
properties.put(ProducerConfig.LINGER_MS_CONFIG, webhook.getKafkaProperties().getLingerMS());
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, webhook.getKafkaProperties().getBufferMemory());
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, webhook.getKafkaProperties().getKeySerializer());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, webhook.getKafkaProperties().getValueSerializer());
producer = new KafkaProducer<>(properties);
}
@Override
public void onStart() {
LOG.info("Kafka Webhook Publisher Started");
}
@Override
public void onShutdown() {
producer.flush();
producer.close();
}
@Override
public void publish(EventResource.ChangeEventList events) throws JsonProcessingException {
if (webhook.getKafkaProperties().getTopics() != null) {
for (String topic : webhook.getKafkaProperties().getTopics()) {
for (ChangeEvent event : events.getData()) {
String eventJson = JsonUtils.pojoToJson(event);
producer.send(new ProducerRecord<>(topic, eventJson));
}
}
} else {
LOG.info("Topics are null");
}
}
}

View File

@ -358,8 +358,7 @@ public class WebhookResource extends EntityResource<Webhook, WebhookRepository>
.withTimeout(create.getTimeout()) .withTimeout(create.getTimeout())
.withEnabled(create.getEnabled()) .withEnabled(create.getEnabled())
.withSecretKey(create.getSecretKey()) .withSecretKey(create.getSecretKey())
.withKafkaProperties(create.getKafkaProperties())
.withStatus(Boolean.TRUE.equals(create.getEnabled()) ? Status.ACTIVE : Status.DISABLED) .withStatus(Boolean.TRUE.equals(create.getEnabled()) ? Status.ACTIVE : Status.DISABLED)
.withWebhookType(WebhookType.fromValue(create.getWebhookType().value())); .withWebhookType(create.getWebhookType() == null ? WebhookType.generic : create.getWebhookType());
} }
} }

View File

@ -61,6 +61,10 @@ public class SettingsCache {
} }
} }
public void putSettings(Settings setting) throws RuntimeException {
SETTINGS_CACHE.put(setting.getConfigType().toString(), setting);
}
public static void cleanUp() { public static void cleanUp() {
SETTINGS_CACHE.invalidateAll(); SETTINGS_CACHE.invalidateAll();
INITIALIZED = false; INITIALIZED = false;

View File

@ -401,7 +401,10 @@ public final class EntityUtil {
deleteFilter.ifPresent( deleteFilter.ifPresent(
eventFilter -> eventFilter ->
filters.add( filters.add(
new Filters().withEventType(EventType.ENTITY_SOFT_DELETED).withFields(eventFilter.getFields()))); new Filters()
.withEventType(EventType.ENTITY_SOFT_DELETED)
.withInclude(eventFilter.getInclude())
.withExclude(eventFilter.getExclude())));
} }
public static EntityReference copy(EntityReference from, EntityReference to) { public static EntityReference copy(EntityReference from, EntityReference to) {

View File

@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.filter.EventFilter; import org.openmetadata.catalog.filter.EventFilter;
import org.openmetadata.catalog.filter.Filters; import org.openmetadata.catalog.filter.Filters;
import org.openmetadata.catalog.settings.Settings; import org.openmetadata.catalog.settings.Settings;
@ -36,6 +37,7 @@ import org.openmetadata.catalog.type.FieldChange;
public class FilterUtil { public class FilterUtil {
private static final String TEST_CASE_RESULT = "testCaseResult"; private static final String TEST_CASE_RESULT = "testCaseResult";
private static final String WILDCARD_FILTER = "all";
public static boolean shouldProcessRequest(ChangeEvent changeEvent, Map<String, Map<EventType, Filters>> filtersMap) { public static boolean shouldProcessRequest(ChangeEvent changeEvent, Map<String, Map<EventType, Filters>> filtersMap) {
if (filtersMap != null && !filtersMap.isEmpty()) { if (filtersMap != null && !filtersMap.isEmpty()) {
@ -44,20 +46,16 @@ public class FilterUtil {
Map<EventType, Filters> filtersOfEntity = filtersMap.get(entityType); Map<EventType, Filters> filtersOfEntity = filtersMap.get(entityType);
if (filtersOfEntity == null || filtersOfEntity.size() == 0) { if (filtersOfEntity == null || filtersOfEntity.size() == 0) {
// check if we have all entities Filter // check if we have all entities Filter
return handleWithWildCardFilter(filtersMap.get("all"), eventType, getUpdateField(changeEvent)); return handleWithWildCardFilter(filtersMap.get(WILDCARD_FILTER), eventType, getUpdateField(changeEvent));
} else { } else {
Filters sf; Filters sf;
if ((sf = filtersOfEntity.get(eventType)) == null) { if ((sf = filtersOfEntity.get(eventType)) == null) {
return false; return false;
} else { } else {
if (sf.getFields().contains("all")) { if (entityType.equals(Entity.TEST_CASE)) {
return true; return handleTestCaseFilter(changeEvent, sf);
} else { } else {
if (entityType.equals("testCase")) { return checkIfFilterContainField(sf, getUpdateField(changeEvent));
return handleTestCaseFilter(changeEvent, sf);
} else {
return checkIfFilterContainField(sf, getUpdateField(changeEvent));
}
} }
} }
} }
@ -68,15 +66,18 @@ public class FilterUtil {
private static boolean handleTestCaseFilter(ChangeEvent changeEvent, Filters sf) { private static boolean handleTestCaseFilter(ChangeEvent changeEvent, Filters sf) {
List<FieldChange> fieldChanges = getAllFieldChange(changeEvent); List<FieldChange> fieldChanges = getAllFieldChange(changeEvent);
for (FieldChange fieldChange : fieldChanges) { for (FieldChange fieldChange : fieldChanges) {
if (fieldChange.getName().equals(TEST_CASE_RESULT)) { if (fieldChange.getName().equals(TEST_CASE_RESULT) && fieldChange.getNewValue() != null) {
TestCaseResult testCaseResult = (TestCaseResult) fieldChange.getNewValue(); TestCaseResult testCaseResult = (TestCaseResult) fieldChange.getNewValue();
TestCaseStatus status = testCaseResult.getTestCaseStatus(); TestCaseStatus status = testCaseResult.getTestCaseStatus();
if (sf.getFields().contains(TEST_CASE_RESULT + status.toString())) { String statusField = TEST_CASE_RESULT + status.toString();
if (sf.getInclude().contains(statusField)) {
return true; return true;
} else if (sf.getExclude().contains(statusField)) {
return false;
} }
} }
} }
return checkIfFilterContainField(sf, getUpdateField(changeEvent)); return sf.getInclude().contains(WILDCARD_FILTER);
} }
public static boolean handleWithWildCardFilter( public static boolean handleWithWildCardFilter(
@ -84,8 +85,7 @@ public class FilterUtil {
if (wildCardFilter != null && !wildCardFilter.isEmpty()) { if (wildCardFilter != null && !wildCardFilter.isEmpty()) {
// check if we have all entities Filter // check if we have all entities Filter
Filters f = wildCardFilter.get(type); Filters f = wildCardFilter.get(type);
boolean allFieldCheck = checkIfFilterContainField(f, updatedField); return checkIfFilterContainField(f, updatedField);
return f != null && (f.getFields().contains("all") || allFieldCheck);
} }
return false; return false;
} }
@ -93,10 +93,14 @@ public class FilterUtil {
public static boolean checkIfFilterContainField(Filters f, List<String> updatedField) { public static boolean checkIfFilterContainField(Filters f, List<String> updatedField) {
if (f != null) { if (f != null) {
for (String changed : updatedField) { for (String changed : updatedField) {
if (f.getFields().contains(changed)) { // field is present in excluded
if (f.getExclude().contains(changed)) {
return false;
} else if (f.getInclude().contains(changed)) {
return true; return true;
} }
} }
return f.getInclude().contains(WILDCARD_FILTER);
} }
return false; return false;
} }
@ -111,12 +115,13 @@ public class FilterUtil {
} }
public static List<FieldChange> getAllFieldChange(ChangeEvent changeEvent) { public static List<FieldChange> getAllFieldChange(ChangeEvent changeEvent) {
ChangeDescription description = changeEvent.getChangeDescription();
List<FieldChange> allFieldChange = new ArrayList<>(); List<FieldChange> allFieldChange = new ArrayList<>();
allFieldChange.addAll(description.getFieldsAdded()); ChangeDescription description = changeEvent.getChangeDescription();
allFieldChange.addAll(description.getFieldsUpdated()); if (description != null) {
allFieldChange.addAll(description.getFieldsDeleted()); allFieldChange.addAll(description.getFieldsAdded());
allFieldChange.addAll(description.getFieldsUpdated());
allFieldChange.addAll(description.getFieldsDeleted());
}
return allFieldChange; return allFieldChange;
} }

View File

@ -7,27 +7,210 @@
"filters": [ "filters": [
{ {
"eventType": "entityCreated", "eventType": "entityCreated",
"fields": [ "include": [
"all" "all"
] ],
"exclude": []
}, },
{ {
"eventType": "entityUpdated", "eventType": "entityUpdated",
"fields": [ "include": [
"all" "all"
] ],
"exclude": ["usageSummary"]
}, },
{ {
"eventType": "entityDeleted", "eventType": "entityDeleted",
"fields": [ "include": [
"all" "all"
] ],
"exclude": []
}, },
{ {
"eventType": "entitySoftDeleted", "eventType": "entitySoftDeleted",
"fields": [ "include": [
"all" "all"
] ],
"exclude": []
}
]
},
{
"entityType": "table",
"filters": [
{
"eventType": "entityCreated",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entityUpdated",
"include": [
"description",
"owner",
"tags",
"followers"
],
"exclude": []
},
{
"eventType": "entityDeleted",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entitySoftDeleted",
"include": [
"all"
],
"exclude": []
}
]
},
{
"entityType": "dashboard",
"filters": [
{
"eventType": "entityCreated",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entityUpdated",
"include": [
"description",
"owner",
"tags",
"followers"
],
"exclude": []
},
{
"eventType": "entityDeleted",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entitySoftDeleted",
"include": [
"all"
],
"exclude": []
}
]
},
{
"entityType": "pipeline",
"filters": [
{
"eventType": "entityCreated",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entityUpdated",
"include": [
"description",
"owner",
"tags",
"followers"
],
"exclude": []
},
{
"eventType": "entityDeleted",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entitySoftDeleted",
"include": [
"all"
],
"exclude": []
}
]
},
{
"entityType": "mlmodel",
"filters": [
{
"eventType": "entityCreated",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entityUpdated",
"include": [
"description",
"owner",
"tags",
"followers"
],
"exclude": []
},
{
"eventType": "entityDeleted",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entitySoftDeleted",
"include": [
"all"
],
"exclude": []
}
]
},
{
"entityType": "testCase",
"filters": [
{
"eventType": "entityCreated",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entityUpdated",
"include": [
"testCaseResultSuccess",
"testCaseResultFailed",
"testCaseResultAborted"
],
"exclude": []
},
{
"eventType": "entityDeleted",
"include": [
"all"
],
"exclude": []
},
{
"eventType": "entitySoftDeleted",
"include": [
"all"
],
"exclude": []
} }
] ]
} }

View File

@ -51,12 +51,8 @@
"description": "Secret set by the webhook client used for computing HMAC SHA256 signature of webhook payload and sent in `X-OM-Signature` header in POST requests to publish the events.", "description": "Secret set by the webhook client used for computing HMAC SHA256 signature of webhook payload and sent in `X-OM-Signature` header in POST requests to publish the events.",
"type": "string" "type": "string"
}, },
"kafkaProperties": {
"description": "Properties of Kafka Producer",
"$ref": "../../configuration/kafkaEventConfiguration.json"
},
"webhookType": { "webhookType": {
"description": "Type of webhook slack,generic,kafka etc", "description": "Type of webhook slack,generic,msteams etc",
"$ref": "../../entity/events/webhook.json#/definitions/webhookType" "$ref": "../../entity/events/webhook.json#/definitions/webhookType"
} }
}, },

View File

@ -8,7 +8,7 @@
"javaInterfaces": ["org.openmetadata.catalog.EntityInterface"], "javaInterfaces": ["org.openmetadata.catalog.EntityInterface"],
"definitions": { "definitions": {
"webhookType": { "webhookType": {
"description": "Type of webhook slack, generic, kafka, etc.", "description": "Type of webhook slack, generic, msteams, etc.",
"type": "string", "type": "string",
"javaType": "org.openmetadata.catalog.type.WebhookType", "javaType": "org.openmetadata.catalog.type.WebhookType",
"default": "generic", "default": "generic",
@ -44,7 +44,7 @@
"type": "string" "type": "string"
}, },
"webhookType": { "webhookType": {
"description": "Type of webhook slack, generic, kafka, etc.", "description": "Type of webhook slack, generic, msteams, etc.",
"$ref": "#/definitions/webhookType" "$ref": "#/definitions/webhookType"
}, },
"description": { "description": {
@ -68,10 +68,6 @@
"type": "integer", "type": "integer",
"default": 10 "default": 10
}, },
"kafkaProperties": {
"description": "Properties of Kafka Producer",
"$ref": "../../configuration/kafkaEventConfiguration.json"
},
"timeout": { "timeout": {
"description": "Connection timeout in seconds. (Default 10s).", "description": "Connection timeout in seconds. (Default 10s).",
"type": "integer", "type": "integer",

View File

@ -17,6 +17,140 @@
"entityDeleted" "entityDeleted"
] ]
}, },
"entityTypes": {
"javaType": "org.openmetadata.catalog.filter.EntityTypes",
"description": "Type of event.",
"type": "string",
"enum": [
"All",
"Chart",
"Dashboard",
"Database",
"Database Schema",
"Glossary",
"Glossary Term",
"Location",
"Metrics",
"Ml Model",
"Pipeline",
"Report",
"Table",
"Topic",
"Test Case"
],
"javaEnums": [
{
"name": "all"
},
{
"name": "chart"
},
{
"name": "dashboard"
},
{
"name": "database"
},
{
"name": "databaseSchema"
},
{
"name": "glossary"
},
{
"name": "glossaryTerm"
},
{
"name": "location"
},
{
"name": "metrics"
},
{
"name": "mlmodel"
},
{
"name": "pipeline"
},
{
"name": "report"
},
{
"name": "table"
},
{
"name": "topic"
},
{
"name": "testCase"
}
]
},
"fieldTypes": {
"javaType": "org.openmetadata.catalog.filter.FieldType",
"description": "Type of event.",
"type": "string",
"enum": [
"All",
"Display Name",
"Description",
"Owner",
"Location",
"Tags",
"Usage Summary",
"Followers",
"Sample Data",
"Synonyms",
"Glossary",
"Test Case Result Success",
"Test Case Result Failed",
"Test Case Result Aborted"
],
"javaEnums": [
{
"name": "all"
},
{
"name": "displayName"
},
{
"name": "description"
},
{
"name": "owner"
},
{
"name": "location"
},
{
"name": "tags"
},
{
"name": "usageSummary"
},
{
"name": "followers"
},
{
"name": "sampleData"
},
{
"name": "synonyms"
},
{
"name": "glossary"
},
{
"name": "testCaseResultSuccess"
},
{
"name": "testCaseResultFailure"
},
{
"name": "testCaseResultAborted"
}
]
},
"filters": { "filters": {
"type": "object", "type": "object",
"javaType": "org.openmetadata.catalog.filter.Filters", "javaType": "org.openmetadata.catalog.filter.Filters",
@ -25,14 +159,23 @@
"description": "Event type that is being requested.", "description": "Event type that is being requested.",
"$ref": "#/definitions/eventType" "$ref": "#/definitions/eventType"
}, },
"fields": { "include": {
"description": "Field on which to apply the filter on", "description": "Field which are allowed to pass",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
}, },
"default": ["all"], "default": ["all"],
"uniqueItems": true "uniqueItems": true
},
"exclude": {
"description": "Field which are not allowed to pass",
"type": "array",
"items": {
"type": "string"
},
"default": [],
"uniqueItems": true
} }
}, },
"required": ["eventType"], "required": ["eventType"],

View File

@ -22,7 +22,6 @@ import static org.openmetadata.catalog.util.EntityUtil.fieldDeleted;
import static org.openmetadata.catalog.util.EntityUtil.fieldUpdated; import static org.openmetadata.catalog.util.EntityUtil.fieldUpdated;
import static org.openmetadata.catalog.util.TestUtils.ADMIN_AUTH_HEADERS; import static org.openmetadata.catalog.util.TestUtils.ADMIN_AUTH_HEADERS;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -65,16 +64,14 @@ public class WebhookResourceTest extends EntityResourceTest<Webhook, CreateWebho
allEntityFilter.setEntityType("all"); allEntityFilter.setEntityType("all");
allEntityFilter.setFilters( allEntityFilter.setFilters(
List.of( List.of(
new Filters().withEventType(EventType.ENTITY_CREATED).withFields(allFilter), new Filters().withEventType(EventType.ENTITY_CREATED).withInclude(allFilter).withExclude(new HashSet<>()),
new Filters().withEventType(EventType.ENTITY_UPDATED).withFields(allFilter), new Filters().withEventType(EventType.ENTITY_UPDATED).withInclude(allFilter).withExclude(new HashSet<>()),
new Filters().withEventType(EventType.ENTITY_DELETED).withFields(allFilter), new Filters().withEventType(EventType.ENTITY_DELETED).withInclude(allFilter).withExclude(new HashSet<>()),
new Filters().withEventType(EventType.ENTITY_SOFT_DELETED).withFields(allFilter))); new Filters()
.withEventType(EventType.ENTITY_SOFT_DELETED)
.withInclude(allFilter)
.withExclude(new HashSet<>())));
ALL_EVENTS_FILTER.add(allEntityFilter); ALL_EVENTS_FILTER.add(allEntityFilter);
try {
System.out.println(JsonUtils.pojoToJson(ALL_EVENTS_FILTER));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
} }
public WebhookResourceTest() { public WebhookResourceTest() {
@ -189,9 +186,12 @@ public class WebhookResourceTest extends EntityResourceTest<Webhook, CreateWebho
Set<String> allFilter = new HashSet<>(); Set<String> allFilter = new HashSet<>();
allFilter.add("all"); allFilter.add("all");
Filters createFilter = new Filters().withEventType(EventType.ENTITY_CREATED).withFields(allFilter); Filters createFilter =
Filters updateFilter = new Filters().withEventType(EventType.ENTITY_UPDATED).withFields(allFilter); new Filters().withEventType(EventType.ENTITY_CREATED).withInclude(allFilter).withExclude(new HashSet<>());
Filters deleteFilter = new Filters().withEventType(EventType.ENTITY_DELETED).withFields(allFilter); Filters updateFilter =
new Filters().withEventType(EventType.ENTITY_UPDATED).withInclude(allFilter).withExclude(new HashSet<>());
Filters deleteFilter =
new Filters().withEventType(EventType.ENTITY_DELETED).withInclude(allFilter).withExclude(new HashSet<>());
EventFilter f1 = new EventFilter().withEntityType("all").withFilters(List.of(createFilter)); EventFilter f1 = new EventFilter().withEntityType("all").withFilters(List.of(createFilter));
EventFilter f2 = EventFilter f2 =
@ -300,16 +300,18 @@ public class WebhookResourceTest extends EntityResourceTest<Webhook, CreateWebho
String name = EventType.ENTITY_CREATED + ":" + entity; String name = EventType.ENTITY_CREATED + ":" + entity;
String uri = baseUri + "/" + EventType.ENTITY_CREATED + "/" + entity; String uri = baseUri + "/" + EventType.ENTITY_CREATED + "/" + entity;
Set<String> allFiler = new HashSet<>(); Set<String> allFilter = new HashSet<>();
allFiler.add("all"); allFilter.add("all");
Filters createFilter = new Filters().withEventType(EventType.ENTITY_CREATED).withFields(allFiler); Filters createFilter =
new Filters().withEventType(EventType.ENTITY_CREATED).withInclude(allFilter).withExclude(new HashSet<>());
EventFilter f1 = new EventFilter().withEntityType(entity).withFilters(List.of(createFilter)); EventFilter f1 = new EventFilter().withEntityType(entity).withFilters(List.of(createFilter));
createWebhook(name, uri, List.of(f1)); createWebhook(name, uri, List.of(f1));
// Create webhook with endpoint api/v1/test/webhook/entityUpdated/<entity> to receive entityUpdated events // Create webhook with endpoint api/v1/test/webhook/entityUpdated/<entity> to receive entityUpdated events
name = EventType.ENTITY_UPDATED + ":" + entity; name = EventType.ENTITY_UPDATED + ":" + entity;
uri = baseUri + "/" + EventType.ENTITY_UPDATED + "/" + entity; uri = baseUri + "/" + EventType.ENTITY_UPDATED + "/" + entity;
Filters updateFilter = new Filters().withEventType(EventType.ENTITY_UPDATED).withFields(allFiler); Filters updateFilter =
new Filters().withEventType(EventType.ENTITY_UPDATED).withInclude(allFilter).withExclude(new HashSet<>());
EventFilter f2 = new EventFilter().withEntityType(entity).withFilters(List.of(updateFilter)); EventFilter f2 = new EventFilter().withEntityType(entity).withFilters(List.of(updateFilter));
createWebhook(name, uri, List.of(f2)); createWebhook(name, uri, List.of(f2));

View File

@ -90,19 +90,23 @@ const mockData = {
filters: [ filters: [
{ {
eventType: 'entityCreated', eventType: 'entityCreated',
fields: ['all'], include: ['all'],
exclude: [],
}, },
{ {
eventType: 'entityUpdated', eventType: 'entityUpdated',
fields: ['all'], include: ['all'],
exclude: [],
}, },
{ {
eventType: 'entityDeleted', eventType: 'entityDeleted',
fields: ['all'], include: ['all'],
exclude: [],
}, },
{ {
eventType: 'entitySoftDeleted', eventType: 'entitySoftDeleted',
fields: ['all'], include: ['all'],
exclude: [],
}, },
], ],
}, },

View File

@ -38,8 +38,8 @@ const EventFilterSelect = ({
metric === EventType.EntityUpdated metric === EventType.EntityUpdated
? Object.values(EventUpdateTypes).map((updateType) => ({ ? Object.values(EventUpdateTypes).map((updateType) => ({
title: startCase(updateType), title: startCase(updateType),
value: updateType, value: `${EventType.EntityUpdated}-${updateType}`,
key: updateType, key: `${EventType.EntityUpdated}-${updateType}`,
})) }))
: undefined, : undefined,
})), })),

View File

@ -34,19 +34,23 @@ export const EVENT_FILTERS_DEFAULT_VALUE = {
filters: [ filters: [
{ {
eventType: 'entityCreated', eventType: 'entityCreated',
fields: ['all'], include: ['all'],
exclude: [],
}, },
{ {
eventType: 'entityUpdated', eventType: 'entityUpdated',
fields: ['all'], include: ['all'],
exclude: [],
}, },
{ {
eventType: 'entityDeleted', eventType: 'entityDeleted',
fields: ['all'], include: ['all'],
exclude: [],
}, },
{ {
eventType: 'entitySoftDeleted', eventType: 'entitySoftDeleted',
fields: ['all'], include: ['all'],
exclude: [],
}, },
], ],
} as EventFilter; } as EventFilter;

View File

@ -3,19 +3,23 @@ import { Filters } from '../../generated/settings/settings';
export const formData = [ export const formData = [
{ {
eventType: 'entityCreated', eventType: 'entityCreated',
fields: [], include: [],
exclude: [],
}, },
{ {
eventType: 'entityUpdated', eventType: 'entityUpdated',
fields: [], include: [],
exclude: [],
}, },
{ {
eventType: 'entitySoftDeleted', eventType: 'entitySoftDeleted',
fields: [], include: [],
exclude: [],
}, },
{ {
eventType: 'entityDeleted', eventType: 'entityDeleted',
fields: [], include: [],
exclude: [],
}, },
] as Filters[]; ] as Filters[];
@ -25,4 +29,5 @@ export const ActivityFeedEntity = {
dashboard: 'Dashboard', dashboard: 'Dashboard',
pipeline: 'Pipeline', pipeline: 'Pipeline',
mlmodel: 'ML Model', mlmodel: 'ML Model',
testCase: 'Test Case',
} as Record<string, string>; } as Record<string, string>;

View File

@ -93,13 +93,13 @@ const ActivityFeedSettingsPage: React.FC = () => {
}; };
const generateTreeData = (entityType: string, data?: Filters[]) => { const generateTreeData = (entityType: string, data?: Filters[]) => {
return data?.map(({ eventType, fields }) => { return data?.map(({ eventType, include }) => {
const key = `${entityType}-${eventType}` as string; const key = `${entityType}-${eventType}` as string;
return { return {
key: key, key: key,
title: startCase(eventType), title: startCase(eventType),
data: fields, data: include,
children: children:
eventType === 'entityUpdated' eventType === 'entityUpdated'
? [ ? [
@ -133,13 +133,13 @@ const ActivityFeedSettingsPage: React.FC = () => {
filters && filters &&
filters.map((obj) => { filters.map((obj) => {
if ( if (
obj?.fields && obj.include &&
obj.fields.length === 1 && obj.include.length === 1 &&
obj.fields[0] === 'all' obj.include[0] === 'all'
) { ) {
checkedArray.push(`${entityType}-${obj.eventType}`); checkedArray.push(`${entityType}-${obj.eventType}`);
} else { } else {
obj?.fields?.forEach((entityUpdated) => { obj?.include?.forEach((entityUpdated) => {
const name = `${entityType}-${obj.eventType}-${entityUpdated}`; const name = `${entityType}-${obj.eventType}-${entityUpdated}`;
checkedArray.push(name); checkedArray.push(name);
}); });

View File

@ -1,5 +1,8 @@
import { isEmpty, isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import { Filters } from '../../generated/settings/settings'; import { Filters } from '../../generated/settings/settings';
import { getDiffArray } from '../../utils/CommonUtils';
const entityUpdatedFields = ['description', 'owner', 'tags', 'followers'];
export const getPayloadFromSelected = ( export const getPayloadFromSelected = (
selectedOptions: Record<string, string[]>, selectedOptions: Record<string, string[]>,
@ -25,7 +28,8 @@ export const getPayloadFromSelected = (
...valueAcc, ...valueAcc,
{ {
eventType: selected[1], eventType: selected[1],
fields: ['all'], include: ['all'],
exclude: [],
}, },
]; ];
} else { } else {
@ -39,11 +43,14 @@ export const getPayloadFromSelected = (
resultArr.push(...arr[0]); resultArr.push(...arr[0]);
if (!isUndefined(nonUpdatedFields) && !isEmpty(nonUpdatedFields)) { if (!isUndefined(nonUpdatedFields) && !isEmpty(nonUpdatedFields)) {
const selectedUpdatedData = nonUpdatedFields.filter(
(name) => !isUndefined(name) || (!isEmpty(name) && name)
);
resultArr.push({ resultArr.push({
eventType: 'entityUpdated', eventType: 'entityUpdated',
fields: nonUpdatedFields.filter( include: selectedUpdatedData,
(name) => !isUndefined(name) || (!isEmpty(name) && name) exclude: getDiffArray(entityUpdatedFields, selectedUpdatedData),
),
}); });
} }

View File

@ -14,7 +14,15 @@
import { Popover } from 'antd'; import { Popover } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { capitalize, isEmpty, isNil, isNull, isUndefined } from 'lodash'; import {
capitalize,
differenceWith,
isEmpty,
isEqual,
isNil,
isNull,
isUndefined,
} from 'lodash';
import { import {
EntityFieldThreadCount, EntityFieldThreadCount,
ExtraInfo, ExtraInfo,
@ -879,3 +887,10 @@ export const getIngestionStatuses = (ingestion: IngestionPipeline) => {
); );
}); });
}; };
export const getDiffArray = (
compareWith: string[],
toCompare: string[]
): string[] => {
return differenceWith(compareWith, toCompare, isEqual);
};

View File

@ -1,11 +1,15 @@
import { Store } from 'antd/lib/form/interface'; import { Store } from 'antd/lib/form/interface';
import { isEqual } from 'lodash'; import { isEqual, isUndefined } from 'lodash';
import { import {
EVENT_FILTERS_DEFAULT_VALUE, EVENT_FILTERS_DEFAULT_VALUE,
EVENT_FILTER_FORM_INITIAL_VALUE, EVENT_FILTER_FORM_INITIAL_VALUE,
} from '../components/AddWebhook/WebhookConstants'; } from '../components/AddWebhook/WebhookConstants';
import { TERM_ALL } from '../constants/constants'; import { TERM_ALL } from '../constants/constants';
import { EventFilter, Filters } from '../generated/entity/events/webhook'; import {
EventFilter,
EventType,
Filters,
} from '../generated/entity/events/webhook';
export const getEventFilters = (data: Store): EventFilter[] => { export const getEventFilters = (data: Store): EventFilter[] => {
if (isEqual(data, EVENT_FILTER_FORM_INITIAL_VALUE)) { if (isEqual(data, EVENT_FILTER_FORM_INITIAL_VALUE)) {
@ -18,17 +22,39 @@ export const getEventFilters = (data: Store): EventFilter[] => {
} }
if (value) { if (value) {
const selectedFilter = data[`${key}-tree`] as string[]; const selectedFilter = data[`${key}-tree`] as string[];
const entityUpdatedFields = selectedFilter
?.map((filter) =>
filter.includes(`${EventType.EntityUpdated}-`)
? filter.split('-')[1]
: undefined
)
.filter((filter) => filter);
const eventFilters = [
...selectedFilter.filter(
(filter) => !filter.includes(`${EventType.EntityUpdated}-`)
),
entityUpdatedFields ? EventType.EntityUpdated : undefined,
];
const includeData = (entityUpdatedFields as string[]).filter(
(entityUpdatedField) => !isUndefined(entityUpdatedField)
);
return [ return [
...acc, ...acc,
{ {
entityType: key, entityType: key,
filters: filters:
selectedFilter[0] === TERM_ALL eventFilters[0] === TERM_ALL
? EVENT_FILTERS_DEFAULT_VALUE.filters ? EVENT_FILTERS_DEFAULT_VALUE.filters
: (selectedFilter.map((filter) => ({ : (eventFilters.map((filter) => ({
eventType: filter, eventType: filter,
fields: [TERM_ALL], include:
filter === EventType.EntityUpdated
? includeData
: [TERM_ALL],
exclude: [],
})) as Filters[]), })) as Filters[]),
}, },
]; ];