Skip to content

Commit 2105589

Browse files
authored
AAP-11113: Support for user-tracking suppression and distinguishing system calls (#336)
1 parent 9f74f79 commit 2105589

File tree

12 files changed

+649
-77
lines changed

12 files changed

+649
-77
lines changed

config-object-store/src/main/java/org/hypertrace/config/objectstore/DefaultObjectStore.java

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -145,29 +145,31 @@ public ConfigObject<T> upsertObject(RequestContext context, T data) {
145145
ConfigObjectImpl.tryBuild(response, this::buildDataFromValue)
146146
.orElseThrow(Status.INTERNAL::asRuntimeException);
147147

148-
configChangeEventGeneratorOptional.ifPresent(
149-
configChangeEventGenerator -> {
150-
if (response.hasPrevConfig()) {
151-
configChangeEventGenerator.sendUpdateNotification(
152-
context,
153-
this.buildClassNameForChangeEvent(upsertedObject.getData()),
154-
this.buildDataFromValue(response.getPrevConfig())
155-
.map(this::buildValueForChangeEvent)
156-
.orElseGet(
157-
() -> {
158-
log.error(
159-
"Unable to convert previousValue back to data for change event. Falling back to raw value {}",
160-
response.getPrevConfig());
161-
return response.getPrevConfig();
162-
}),
163-
this.buildValueForChangeEvent(upsertedObject.getData()));
164-
} else {
165-
configChangeEventGenerator.sendCreateNotification(
166-
context,
167-
this.buildClassNameForChangeEvent(upsertedObject.getData()),
168-
this.buildValueForChangeEvent(upsertedObject.getData()));
169-
}
170-
});
148+
if (!context.isUserTrackingSuppressed()) {
149+
configChangeEventGeneratorOptional.ifPresent(
150+
configChangeEventGenerator -> {
151+
if (response.hasPrevConfig()) {
152+
configChangeEventGenerator.sendUpdateNotification(
153+
context,
154+
this.buildClassNameForChangeEvent(upsertedObject.getData()),
155+
this.buildDataFromValue(response.getPrevConfig())
156+
.map(this::buildValueForChangeEvent)
157+
.orElseGet(
158+
() -> {
159+
log.error(
160+
"Unable to convert previousValue back to data for change event. Falling back to raw value {}",
161+
response.getPrevConfig());
162+
return response.getPrevConfig();
163+
}),
164+
this.buildValueForChangeEvent(upsertedObject.getData()));
165+
} else {
166+
configChangeEventGenerator.sendCreateNotification(
167+
context,
168+
this.buildClassNameForChangeEvent(upsertedObject.getData()),
169+
this.buildValueForChangeEvent(upsertedObject.getData()));
170+
}
171+
});
172+
}
171173
return upsertedObject;
172174
}
173175

@@ -188,12 +190,14 @@ public Optional<ConfigObject<T>> deleteObject(RequestContext context) {
188190
ConfigObject<T> object =
189191
ConfigObjectImpl.tryBuild(deletedConfig, this::buildDataFromValue)
190192
.orElseThrow(Status.INTERNAL::asRuntimeException);
191-
configChangeEventGeneratorOptional.ifPresent(
192-
configChangeEventGenerator ->
193-
configChangeEventGenerator.sendDeleteNotification(
194-
context,
195-
this.buildClassNameForChangeEvent(object.getData()),
196-
this.buildValueForChangeEvent(object.getData())));
193+
if (!context.isUserTrackingSuppressed()) {
194+
configChangeEventGeneratorOptional.ifPresent(
195+
configChangeEventGenerator ->
196+
configChangeEventGenerator.sendDeleteNotification(
197+
context,
198+
this.buildClassNameForChangeEvent(object.getData()),
199+
this.buildValueForChangeEvent(object.getData())));
200+
}
197201
return Optional.of(object);
198202
} catch (Exception exception) {
199203
if (Status.fromThrowable(exception).equals(Status.NOT_FOUND)) {

config-object-store/src/main/java/org/hypertrace/config/objectstore/IdentifiedObjectStore.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ private DeletedContextualConfigObject<T> processDeleteResult(
306306
DeletedContextualConfigObject<T> deletedContextualConfigObject =
307307
DeletedContextualConfigObjectImpl.tryBuild(deletedConfig, this::buildDataFromValue);
308308

309+
if (requestContext.isUserTrackingSuppressed()) {
310+
return deletedContextualConfigObject;
311+
}
312+
309313
if (deletedContextualConfigObject.getDeletedData().isPresent()) {
310314
T data = deletedContextualConfigObject.getDeletedData().get();
311315
configChangeEventGeneratorOptional.ifPresent(
@@ -320,6 +324,9 @@ private DeletedContextualConfigObject<T> processDeleteResult(
320324
}
321325

322326
private void tryReportCreation(RequestContext requestContext, ContextualConfigObject<T> result) {
327+
if (requestContext.isUserTrackingSuppressed()) {
328+
return;
329+
}
323330
configChangeEventGeneratorOptional.ifPresent(
324331
configChangeEventGenerator ->
325332
configChangeEventGenerator.sendCreateNotification(
@@ -331,6 +338,9 @@ private void tryReportCreation(RequestContext requestContext, ContextualConfigOb
331338

332339
private void tryReportUpdate(
333340
RequestContext requestContext, ContextualConfigObject<T> result, Value previousValue) {
341+
if (requestContext.isUserTrackingSuppressed()) {
342+
return;
343+
}
334344
configChangeEventGeneratorOptional.ifPresent(
335345
configChangeEventGenerator ->
336346
configChangeEventGenerator.sendUpdateNotification(

config-object-store/src/test/java/org/hypertrace/config/objectstore/DefaultObjectStoreTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.mockito.ArgumentMatchers.any;
55
import static org.mockito.ArgumentMatchers.eq;
6+
import static org.mockito.Mockito.doReturn;
7+
import static org.mockito.Mockito.lenient;
8+
import static org.mockito.Mockito.never;
69
import static org.mockito.Mockito.times;
710
import static org.mockito.Mockito.verify;
811
import static org.mockito.Mockito.when;
@@ -51,6 +54,7 @@ class DefaultObjectStoreTest {
5154

5255
@BeforeEach
5356
void beforeEach() {
57+
lenient().doReturn(false).when(this.mockRequestContext).isUserTrackingSuppressed();
5458
this.store = new TestObjectStore(this.mockStub, configChangeEventGenerator);
5559
}
5660

@@ -216,6 +220,61 @@ void generatesConfigUpsertRequest() {
216220
.build()));
217221
}
218222

223+
@Test
224+
void upsertCreate_suppressesChangeEvent_whenUserTrackingSuppressed() {
225+
doReturn(true).when(this.mockRequestContext).isUserTrackingSuppressed();
226+
when(this.mockStub.upsertConfig(any()))
227+
.thenReturn(
228+
UpsertConfigResponse.newBuilder()
229+
.setConfig(Values.of("test"))
230+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP.toEpochMilli())
231+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli())
232+
.build());
233+
234+
this.store.upsertObject(this.mockRequestContext, new TestInternalObject("test"));
235+
236+
verify(this.configChangeEventGenerator, never()).sendCreateNotification(any(), any(), any());
237+
verify(this.configChangeEventGenerator, never())
238+
.sendUpdateNotification(any(), any(), any(), any());
239+
}
240+
241+
@Test
242+
void upsertUpdate_suppressesChangeEvent_whenUserTrackingSuppressed() {
243+
doReturn(true).when(this.mockRequestContext).isUserTrackingSuppressed();
244+
when(this.mockStub.upsertConfig(any()))
245+
.thenReturn(
246+
UpsertConfigResponse.newBuilder()
247+
.setConfig(Values.of("updated"))
248+
.setPrevConfig(Values.of("original"))
249+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP.toEpochMilli())
250+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli())
251+
.build());
252+
253+
this.store.upsertObject(this.mockRequestContext, new TestInternalObject("updated"));
254+
255+
verify(this.configChangeEventGenerator, never()).sendCreateNotification(any(), any(), any());
256+
verify(this.configChangeEventGenerator, never())
257+
.sendUpdateNotification(any(), any(), any(), any());
258+
}
259+
260+
@Test
261+
void delete_suppressesChangeEvent_whenUserTrackingSuppressed() {
262+
doReturn(true).when(this.mockRequestContext).isUserTrackingSuppressed();
263+
when(this.mockStub.deleteConfig(any()))
264+
.thenReturn(
265+
DeleteConfigResponse.newBuilder()
266+
.setDeletedConfig(
267+
ContextSpecificConfig.newBuilder()
268+
.setConfig(Values.of("test"))
269+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP.toEpochMilli())
270+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli()))
271+
.build());
272+
273+
this.store.deleteObject(mockRequestContext);
274+
275+
verify(this.configChangeEventGenerator, never()).sendDeleteNotification(any(), any(), any());
276+
}
277+
219278
@lombok.Value
220279
private static class TestInternalObject {
221280
String name;

config-object-store/src/test/java/org/hypertrace/config/objectstore/IdentifiedObjectStoreTest.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.mockito.ArgumentMatchers.any;
55
import static org.mockito.ArgumentMatchers.eq;
6+
import static org.mockito.Mockito.doReturn;
7+
import static org.mockito.Mockito.lenient;
8+
import static org.mockito.Mockito.never;
69
import static org.mockito.Mockito.times;
710
import static org.mockito.Mockito.verify;
11+
import static org.mockito.Mockito.verifyNoInteractions;
812
import static org.mockito.Mockito.when;
913

1014
import com.google.protobuf.Struct;
@@ -87,6 +91,7 @@ class IdentifiedObjectStoreTest {
8791

8892
@BeforeEach
8993
void beforeEach() {
94+
lenient().doReturn(false).when(this.mockRequestContext).isUserTrackingSuppressed();
9095
this.store = new TestObjectStore(this.mockStub, configChangeEventGenerator);
9196
}
9297

@@ -423,6 +428,91 @@ void buildClassNameForChangeEvent_test() {
423428
assertEquals(TestApiObject.class.getName(), this.store.buildClassNameForChangeEvent(OBJECT_1));
424429
}
425430

431+
@Test
432+
void upsertCreate_suppressesChangeEvent_whenUserTrackingSuppressed() {
433+
doReturn(true).when(this.mockRequestContext).isUserTrackingSuppressed();
434+
when(this.mockStub.upsertConfig(any()))
435+
.thenReturn(
436+
UpsertConfigResponse.newBuilder()
437+
.setConfig(OBJECT_1_AS_VALUE)
438+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP_1.toEpochMilli())
439+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli())
440+
.build());
441+
442+
this.store.upsertObject(this.mockRequestContext, OBJECT_1);
443+
444+
verify(this.configChangeEventGenerator, never())
445+
.sendCreateNotification(any(), any(), any(), any());
446+
verify(this.configChangeEventGenerator, never())
447+
.sendUpdateNotification(any(), any(), any(), any(), any());
448+
}
449+
450+
@Test
451+
void upsertUpdate_suppressesChangeEvent_whenUserTrackingSuppressed() {
452+
doReturn(true).when(this.mockRequestContext).isUserTrackingSuppressed();
453+
when(this.mockStub.upsertConfig(any()))
454+
.thenReturn(
455+
UpsertConfigResponse.newBuilder()
456+
.setConfig(OBJECT_2_AS_VALUE)
457+
.setPrevConfig(OBJECT_1_AS_VALUE)
458+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP_1.toEpochMilli())
459+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli())
460+
.build());
461+
462+
this.store.upsertObject(this.mockRequestContext, OBJECT_2);
463+
464+
verify(this.configChangeEventGenerator, never())
465+
.sendCreateNotification(any(), any(), any(), any());
466+
verify(this.configChangeEventGenerator, never())
467+
.sendUpdateNotification(any(), any(), any(), any(), any());
468+
}
469+
470+
@Test
471+
void delete_suppressesChangeEvent_whenUserTrackingSuppressed() {
472+
doReturn(true).when(this.mockRequestContext).isUserTrackingSuppressed();
473+
when(this.mockStub.deleteConfig(any()))
474+
.thenReturn(
475+
DeleteConfigResponse.newBuilder()
476+
.setDeletedConfig(
477+
ContextSpecificConfig.newBuilder()
478+
.setConfig(OBJECT_1_AS_VALUE)
479+
.setContext(OBJECT_1.getId())
480+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP_1.toEpochMilli())
481+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli()))
482+
.build());
483+
484+
this.store.deleteObject(mockRequestContext, "first-id");
485+
486+
verify(this.configChangeEventGenerator, never())
487+
.sendDeleteNotification(any(), any(), any(), any());
488+
}
489+
490+
@Test
491+
void upsertAll_suppressesChangeEvents_whenUserTrackingSuppressed() {
492+
doReturn(true).when(this.mockRequestContext).isUserTrackingSuppressed();
493+
when(this.mockStub.upsertAllConfigs(any()))
494+
.thenReturn(
495+
UpsertAllConfigsResponse.newBuilder()
496+
.addUpsertedConfigs(
497+
UpsertedConfig.newBuilder()
498+
.setConfig(OBJECT_1_AS_VALUE)
499+
.setContext(OBJECT_1.getId())
500+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP_1.toEpochMilli())
501+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli()))
502+
.addUpsertedConfigs(
503+
UpsertedConfig.newBuilder()
504+
.setConfig(OBJECT_2_AS_VALUE)
505+
.setContext(OBJECT_2.getId())
506+
.setPrevConfig(OBJECT_1_AS_VALUE)
507+
.setCreationTimestamp(TEST_CREATE_TIMESTAMP_2.toEpochMilli())
508+
.setUpdateTimestamp(TEST_UPDATE_TIMESTAMP.toEpochMilli()))
509+
.build());
510+
511+
this.store.upsertObjects(this.mockRequestContext, List.of(OBJECT_1, OBJECT_2));
512+
513+
verifyNoInteractions(this.configChangeEventGenerator);
514+
}
515+
426516
@lombok.Value
427517
@Builder
428518
private static class TestInternalObject {

config-object-store/src/test/java/org/hypertrace/config/objectstore/IdentifiedObjectStoreWithFilterTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.jupiter.api.Assertions.assertTrue;
55
import static org.mockito.ArgumentMatchers.any;
66
import static org.mockito.Mockito.atLeastOnce;
7+
import static org.mockito.Mockito.lenient;
78
import static org.mockito.Mockito.verify;
89
import static org.mockito.Mockito.when;
910

@@ -127,6 +128,7 @@ private static Value convertToValue(TestInternalObject internalObject) {
127128

128129
@BeforeEach
129130
void beforeEach() {
131+
lenient().doReturn(false).when(this.mockRequestContext).isUserTrackingSuppressed();
130132
this.store = new TestObjectStore(this.mockStub, configChangeEventGenerator);
131133
}
132134

config-service-impl/src/main/java/org/hypertrace/config/service/ConfigServiceGrpcImpl.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@ public void upsertConfig(
6161
try {
6262
ConfigResourceContext configResourceContext = getConfigResourceContext(request);
6363
UpsertedConfig upsertedConfig =
64-
configStore.writeConfig(configResourceContext, getUserId(), request, getUserEmail());
64+
configStore.writeConfig(
65+
configResourceContext,
66+
getUserId(),
67+
request,
68+
getUserEmail(),
69+
isUserTrackingSuppressed());
6570
UpsertConfigResponse.Builder builder = UpsertConfigResponse.newBuilder();
6671
builder.setConfig(request.getConfig());
6772
builder.setCreationTimestamp(upsertedConfig.getCreationTimestamp());
@@ -252,7 +257,8 @@ public void upsertAllConfigs(
252257
requestedUpsert.getConfig()))
253258
.collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue));
254259
List<UpsertedConfig> upsertedConfigs =
255-
configStore.writeAllConfigs(valuesByContext, getUserId(), getUserEmail());
260+
configStore.writeAllConfigs(
261+
valuesByContext, getUserId(), getUserEmail(), isUserTrackingSuppressed());
256262
responseObserver.onNext(
257263
UpsertAllConfigsResponse.newBuilder().addAllUpsertedConfigs(upsertedConfigs).build());
258264
responseObserver.onCompleted();
@@ -328,4 +334,8 @@ private String getUserEmail() {
328334
private String getUserId() {
329335
return RequestContext.CURRENT.get().getUserId().orElse(DEFAULT_USER_ID);
330336
}
337+
338+
private boolean isUserTrackingSuppressed() {
339+
return RequestContext.CURRENT.get().isUserTrackingSuppressed();
340+
}
331341
}

config-service-impl/src/main/java/org/hypertrace/config/service/store/ConfigStore.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ UpsertedConfig writeConfig(
3232
ConfigResourceContext configResourceContext,
3333
String userId,
3434
UpsertConfigRequest request,
35-
String userEmail)
35+
String userEmail,
36+
boolean suppressUserTracking)
3637
throws IOException;
3738

3839
/**
@@ -82,7 +83,10 @@ List<ContextSpecificConfig> getAllConfigs(
8283
* @return the upserted configs
8384
*/
8485
List<UpsertedConfig> writeAllConfigs(
85-
Map<ConfigResourceContext, Value> resourceContextValueMap, String userId, String userEmail)
86+
Map<ConfigResourceContext, Value> resourceContextValueMap,
87+
String userId,
88+
String userEmail,
89+
boolean suppressUserTracking)
8690
throws IOException;
8791

8892
/**

0 commit comments

Comments
 (0)