1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

chore: avoid duplicates (#5381)

This PR handles the case where a single strategy is used in multiple
change requests. Instead of listing the strategy several times in the
output, we consolidate the entries and add a new `changeRequestIds`
property. This is a non-empty list that points to all the change
requests it is used in.

This is required for us to be able to link back to the change requests
from the UI overview.
This commit is contained in:
Thomas Heartman 2023-11-22 10:26:35 +01:00 committed by GitHub
parent 8ffc92af5b
commit fac2578922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 45 deletions

View File

@ -9,6 +9,7 @@ let db: ITestDb;
let user: IUser;
const CR_ID = 123456;
const CR_ID_2 = 234567;
const FLAG_NAME = 'crarm-test-flag';
let readModel: IChangeRequestSegmentUsageReadModel;
@ -32,16 +33,22 @@ afterAll(async () => {
});
afterEach(async () => {
await db.rawDatabase.table('change_requests').where('id', CR_ID).delete();
await db.rawDatabase
.table('change_requests')
.where('id', CR_ID)
.orWhere('id', CR_ID_2)
.delete();
await db.rawDatabase
.table('change_request_events')
.where('change_request_id', CR_ID)
.orWhere('change_request_id', CR_ID_2)
.delete();
});
const createCR = async (state) => {
const createCR = async (state, changeRequestId = CR_ID) => {
await db.rawDatabase.table('change_requests').insert({
id: CR_ID,
id: changeRequestId,
environment: 'default',
state,
project: 'default',
@ -52,52 +59,72 @@ const createCR = async (state) => {
});
};
const addChangeRequestChange = async (flagName, action, change) => {
const addChangeRequestChange = async (
flagName,
action,
change,
changeRequestId,
) => {
await db.rawDatabase.table('change_request_events').insert({
feature: flagName,
action,
payload: change,
created_at: '2023-01-01 00:01:00',
change_request_id: CR_ID,
change_request_id: changeRequestId,
created_by: user.id,
});
};
const addStrategyToCr = async (segmentId: number, flagName: string) => {
await addChangeRequestChange(flagName, 'addStrategy', {
name: 'flexibleRollout',
title: '',
disabled: false,
segments: [segmentId],
variants: [],
parameters: {
groupId: flagName,
rollout: '100',
stickiness: 'default',
const addStrategyToCr = async (
segmentId: number,
flagName: string,
changeRequestId = CR_ID,
) => {
await addChangeRequestChange(
flagName,
'addStrategy',
{
name: 'flexibleRollout',
title: '',
disabled: false,
segments: [segmentId],
variants: [],
parameters: {
groupId: flagName,
rollout: '100',
stickiness: 'default',
},
constraints: [],
},
constraints: [],
});
changeRequestId,
);
};
const updateStrategyInCr = async (
strategyId: string,
segmentId: number,
flagName: string,
changeRequestId = CR_ID,
) => {
await addChangeRequestChange(flagName, 'updateStrategy', {
id: strategyId,
name: 'flexibleRollout',
title: '',
disabled: false,
segments: [segmentId],
variants: [],
parameters: {
groupId: flagName,
rollout: '100',
stickiness: 'default',
await addChangeRequestChange(
flagName,
'updateStrategy',
{
id: strategyId,
name: 'flexibleRollout',
title: '',
disabled: false,
segments: [segmentId],
variants: [],
parameters: {
groupId: flagName,
rollout: '100',
stickiness: 'default',
},
constraints: [],
},
constraints: [],
});
changeRequestId,
);
};
test.each([
@ -127,6 +154,7 @@ test.each([
strategyName: 'flexibleRollout',
environment: 'default',
featureName: FLAG_NAME,
changeRequestIds: [CR_ID],
},
]);
} else {
@ -165,6 +193,7 @@ test.each([
strategyName: 'flexibleRollout',
environment: 'default',
featureName: FLAG_NAME,
changeRequestIds: [CR_ID],
},
]);
} else {
@ -172,3 +201,35 @@ test.each([
}
},
);
test(`If the same strategy appears in multiple CRs with the same segment, they should all be listed in its changeRequestIds`, async () => {
await createCR('In review', CR_ID);
await createCR('In review', CR_ID_2);
const segmentId = 3;
const strategyId = randomId();
await updateStrategyInCr(strategyId, segmentId, FLAG_NAME, CR_ID);
await updateStrategyInCr(strategyId, segmentId, FLAG_NAME, CR_ID_2);
const result = await readModel.getStrategiesUsedInActiveChangeRequests(
segmentId,
);
expect(result).toHaveLength(1);
expect(result).toMatchObject([
{
id: strategyId,
projectId: 'default',
strategyName: 'flexibleRollout',
environment: 'default',
featureName: FLAG_NAME,
},
]);
const crIds = result[0].changeRequestIds;
expect(crIds).toContain(CR_ID);
expect(crIds).toContain(CR_ID_2);
expect(crIds).toHaveLength(2);
});

View File

@ -3,6 +3,7 @@ type NewStrategy = {
featureName: string;
strategyName: string;
environment: string;
changeRequestIds: [string, string[]];
};
type ExistingStrategy = NewStrategy & { id: string };

View File

@ -13,17 +13,6 @@ export class ChangeRequestSegmentUsageReadModel
this.db = db;
}
mapRow = (row): ChangeRequestStrategy => {
const { payload, project, environment, feature } = row;
return {
projectId: project,
featureName: feature,
environment: environment,
strategyName: payload.name,
...(payload.id ? { id: payload.id } : {}),
};
};
public async getStrategiesUsedInActiveChangeRequests(
segmentId: number,
): Promise<ChangeRequestStrategy[]> {
@ -38,8 +27,35 @@ export class ChangeRequestSegmentUsageReadModel
const queryResult = await query;
const strategies = queryResult.rows
.filter((row) => row.payload?.segments?.includes(segmentId))
.map(this.mapRow);
.map((row) => {
const { payload, project, environment, feature } = row;
return {
projectId: project,
featureName: feature,
environment: environment,
strategyName: payload.name,
...(payload.id ? { id: payload.id } : {}),
changeRequestId: row.change_request_id,
};
});
return strategies;
const deduped = strategies.reduce((acc, strategy) => {
const { changeRequestId, ...rest } = strategy;
const existingData = acc[strategy.id];
if (existingData) {
existingData.changeRequestIds.push(strategy.changeRequestId);
} else {
acc[strategy.id] = {
...rest,
changeRequestIds: [strategy.changeRequestId],
};
}
return acc;
}, {});
return Object.values(deduped);
}
}

View File

@ -369,6 +369,7 @@ export class SegmentsController extends Controller {
featureName: strategy.featureName,
strategyName: strategy.strategyName,
environment: strategy.environment,
changeRequestIds: strategy.changeRequestIds,
});
res.json({

View File

@ -529,6 +529,7 @@ describe('detect strategy usage in change requests', () => {
featureName: toggle.name,
projectId: 'default',
strategyName: 'flexibleRollout',
changeRequestIds: [CR_ID],
},
]);
expect(strategies).toStrictEqual([]);
@ -576,7 +577,9 @@ describe('detect strategy usage in change requests', () => {
const { strategies, changeRequestStrategies } =
await fetchSegmentStrategies(segment.id);
expect(changeRequestStrategies).toMatchObject([{ id: strategyId }]);
expect(changeRequestStrategies).toMatchObject([
{ id: strategyId, changeRequestIds: [CR_ID] },
]);
expect(strategies).toStrictEqual([]);
});