Skip to content

Commit

Permalink
chore: add feature flag support for filtered status code
Browse files Browse the repository at this point in the history
  • Loading branch information
psrikanth88 committed Oct 2, 2023
1 parent 7f353e6 commit 6fca43d
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 18 deletions.
5 changes: 5 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ const MappedToDestinationKey = 'context.mappedToDestination';
const GENERIC_TRUE_VALUES = ['true', 'True', 'TRUE', 't', 'T', '1'];
const GENERIC_FALSE_VALUES = ['false', 'False', 'FALSE', 'f', 'F', '0'];

const HTTP_CUSTOM_STATUS_CODES = {
FILTERED: 298,
};

module.exports = {
EventType,
GENERIC_TRUE_VALUES,
Expand All @@ -58,4 +62,5 @@ module.exports = {
SpecedTraits,
TraitsMapping,
WhiteListedTraits,
HTTP_CUSTOM_STATUS_CODES,
};
2 changes: 1 addition & 1 deletion src/controllers/userTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default class UserTransformController {
);
const events = ctx.request.body as ProcessorTransformationRequest[];
const processedRespone: UserTransformationServiceResponse =
await UserTransformService.transformRoutine(events);
await UserTransformService.transformRoutine(events, ctx.state.features);

Check warning on line 20 in src/controllers/userTransform.ts

View check run for this annotation

Codecov / codecov/patch

src/controllers/userTransform.ts#L20

Added line #L20 was not covered by tests
ctx.body = processedRespone.transformedEvents;
ControllerUtility.postProcess(ctx, processedRespone.retryStatus);
logger.debug(
Expand Down
49 changes: 49 additions & 0 deletions src/middlewares/featureFlag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Context, Next } from 'koa';

export interface FeatureFlags {
[key: string]: boolean | string;
}

export const FEATURE_FILTER_CODE = 'filter-code';

export default class FeatureFlagMiddleware {
public static async handle(ctx: Context, next: Next): Promise<void> {

Check warning on line 10 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L10

Added line #L10 was not covered by tests
// Initialize ctx.state.features if it doesn't exist
ctx.state.features = (ctx.state.features || {}) as FeatureFlags;

// Get headers from the request
const { headers } = ctx.request;

Check warning on line 15 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L15

Added line #L15 was not covered by tests

// Filter headers that start with 'X-Feature-'
const featureHeaders = Object.keys(headers).filter((key) =>
key.toLowerCase().startsWith('x-feature-'),

Check warning on line 19 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L18-L19

Added lines #L18 - L19 were not covered by tests
);

// Convert feature headers to feature flags in ctx.state.features
featureHeaders.forEach((featureHeader) => {

Check warning on line 23 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L23

Added line #L23 was not covered by tests
// Get the feature name by removing the prefix, and convert to camelCase
const featureName = featureHeader

Check warning on line 25 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L25

Added line #L25 was not covered by tests
.substring(10)
.replace(/X-Feature-/g, '')
.toLowerCase();

let value: string | boolean | undefined;
const valueString = headers[featureHeader] as string;

Check warning on line 31 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L31

Added line #L31 was not covered by tests
if (valueString === 'true' || valueString === '?1') {
value = true;

Check warning on line 33 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L33

Added line #L33 was not covered by tests
} else if (valueString === 'false' || valueString === '?0') {
value = false;
} else {
value = valueString;

Check warning on line 37 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L35-L37

Added lines #L35 - L37 were not covered by tests
}

// Set the feature flag in ctx.state.features
if (value !== undefined) {
ctx.state.features[featureName] = value;

Check warning on line 42 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L42

Added line #L42 was not covered by tests
}
});

// Move to the next middleware
await next();

Check warning on line 47 in src/middlewares/featureFlag.ts

View check run for this annotation

Codecov / codecov/patch

src/middlewares/featureFlag.ts#L47

Added line #L47 was not covered by tests
}
}
4 changes: 3 additions & 1 deletion src/routes/userTransform.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Router from '@koa/router';
import RouteActivationController from '../middlewares/routeActivation';
import FeatureFlagController from '../middlewares/featureFlag';
import UserTransformController from '../controllers/userTransform';

const router = new Router();

router.post(
'/customTransform',
RouteActivationController.isUserTransformRouteActive,
FeatureFlagController.handle,
UserTransformController.transform,
);
router.post(
Expand All @@ -31,4 +33,4 @@ router.post(
);

const userTransformRoutes = router.routes();
export default userTransformRoutes;
export default userTransformRoutes;
31 changes: 17 additions & 14 deletions src/services/userTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ import stats from '../util/stats';
import { CommonUtils } from '../util/common';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { CatchErr, FixMe } from '../util/types';
import { FeatureFlags, FEATURE_FILTER_CODE } from '../middlewares/featureFlag';
import { HTTP_CUSTOM_STATUS_CODES } from '../constants';

export default class UserTransformService {
public static async transformRoutine(
events: ProcessorTransformationRequest[],
features: FeatureFlags,
): Promise<UserTransformationServiceResponse> {
let retryStatus = 200;
const groupedEvents: NonNullable<unknown> = groupBy(
Expand All @@ -42,8 +45,7 @@ export default class UserTransformService {
);
}
const responses = await Promise.all<FixMe>(
Object.entries(groupedEvents).map(async ([dest, destEvents]) => {
logger.debug(`dest: ${dest}`);
Object.entries(groupedEvents).map(async ([, destEvents]) => {
const eventsToProcess = destEvents as ProcessorTransformationRequest[];
const transformationVersionId =
eventsToProcess[0]?.destination?.Transformations[0]?.VersionID;
Expand Down Expand Up @@ -117,18 +119,19 @@ export default class UserTransformService {
} as ProcessorTransformationResponse);
});

// TODO: add feature flag based on rudder-server version to do this
// find difference between input and output messageIds
const messageIdsNotInOutput = CommonUtils.setDiff(messageIdsSet, messageIdsInOutputSet);
const droppedEvents = messageIdsNotInOutput.map((id) => ({
statusCode: 298,
metadata: {
...commonMetadata,
messageId: id,
messageIds: null,
},
}));
transformedEvents.push(...droppedEvents);
if (features[FEATURE_FILTER_CODE]) {
// find difference between input and output messageIds
const messageIdsNotInOutput = CommonUtils.setDiff(messageIdsSet, messageIdsInOutputSet);
const droppedEvents = messageIdsNotInOutput.map((id) => ({
statusCode: HTTP_CUSTOM_STATUS_CODES.FILTERED,
metadata: {
...commonMetadata,
messageId: id,
messageIds: null,
},
}));
transformedEvents.push(...droppedEvents);
}

transformedEvents.push(...transformedEventsWithMetadata);
} catch (error: CatchErr) {
Expand Down
7 changes: 5 additions & 2 deletions test/__tests__/user_transformation_ts.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UserTransformService from '../../src/services/userTransform';
import fetch from 'node-fetch';
import UserTransformService from '../../src/services/userTransform';
import { FeatureFlags, FEATURE_FILTER_CODE } from '../../src/middlewares/featureFlag';
jest.mock('node-fetch', () => jest.fn());

const integration = 'user_transformation_service';
Expand Down Expand Up @@ -29,7 +30,9 @@ describe('User Transform Service', () => {
status: 200,
json: jest.fn().mockResolvedValue(respBody),
});
const output = await UserTransformService.transformRoutine(inputData);
const output = await UserTransformService.transformRoutine(inputData, {
[FEATURE_FILTER_CODE]: true,
} as FeatureFlags);
expect(fetch).toHaveBeenCalledWith(
`https://api.rudderlabs.com/transformation/getByVersionId?versionId=${versionId}`,
);
Expand Down

0 comments on commit 6fca43d

Please sign in to comment.