diff --git a/frontend/src/component/segments/SegmentDelete/SegmentDeleteUsedSegment/sort-strategies.test.tsx b/frontend/src/component/segments/SegmentDelete/SegmentDeleteUsedSegment/sort-strategies.test.tsx new file mode 100644 index 0000000000..6e8435e677 --- /dev/null +++ b/frontend/src/component/segments/SegmentDelete/SegmentDeleteUsedSegment/sort-strategies.test.tsx @@ -0,0 +1,152 @@ +import { + sortAndFlattenStrategies, + sortStrategiesByFeature, +} from './sort-strategies'; + +describe('sorting strategies by feature', () => { + test('strategies with the same id are sorted: existing first, then change requests', () => { + const strategies = [{ id: 'a', featureName: 'feature1' }]; + const changeRequestStrategies = [ + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + ]; + + const expected = { + feature1: [ + { id: 'a', featureName: 'feature1' }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + ], + }; + + expect( + sortStrategiesByFeature(strategies, changeRequestStrategies), + ).toStrictEqual(expected); + }); + + test('the same strategy used in multiple change requests is sorted by change request id', () => { + const changeRequestStrategies = [ + { id: 'a', featureName: 'feature1', changeRequest: { id: 2 } }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + ]; + + const expected = { + feature1: [ + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 2 } }, + ], + }; + + expect( + sortStrategiesByFeature([], changeRequestStrategies), + ).toStrictEqual(expected); + }); + + test('strategies are sorted by id, with change requests strategies being listed before existing strategies if their ids would indicate that', () => { + const strategies = [{ id: 'b', featureName: 'feature1' }]; + const changeRequestStrategies = [ + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + ]; + + const expected = { + feature1: [ + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { id: 'b', featureName: 'feature1' }, + ], + }; + + expect( + sortStrategiesByFeature(strategies, changeRequestStrategies), + ).toStrictEqual(expected); + }); + + test('strategies without ids (new strategies) are sorted by change request id', () => { + const changeRequestStrategies = [ + { featureName: 'feature1', changeRequest: { id: 2 } }, + { featureName: 'feature1', changeRequest: { id: 1 } }, + ]; + + const expected = { + feature1: [ + { featureName: 'feature1', changeRequest: { id: 1 } }, + { featureName: 'feature1', changeRequest: { id: 2 } }, + ], + }; + + expect( + sortStrategiesByFeature([], changeRequestStrategies), + ).toStrictEqual(expected); + }); + + test('if new strategies have the same change request id, they should be listed in the same order as in the input', () => { + const changeRequestStrategies = [ + { key: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { key: 'b', featureName: 'feature1', changeRequest: { id: 1 } }, + ]; + + const expected = { + feature1: [ + { key: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { key: 'b', featureName: 'feature1', changeRequest: { id: 1 } }, + ], + }; + + expect( + sortStrategiesByFeature([], changeRequestStrategies), + ).toStrictEqual(expected); + }); + + test('all the various sorts work together', () => { + const strategies = [ + { id: 'a', featureName: 'feature1' }, + { id: 'b', featureName: 'feature1' }, + { id: 'd', featureName: 'feature1' }, + ]; + const changeRequestStrategies = [ + { id: 'a', featureName: 'feature1', changeRequest: { id: 2 } }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { id: 'c', featureName: 'feature1', changeRequest: { id: 1 } }, + { key: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { key: 'b', featureName: 'feature1', changeRequest: { id: 1 } }, + ]; + + const expected = { + feature1: [ + { id: 'a', featureName: 'feature1' }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 2 } }, + { id: 'b', featureName: 'feature1' }, + { id: 'c', featureName: 'feature1', changeRequest: { id: 1 } }, + { id: 'd', featureName: 'feature1' }, + { key: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { key: 'b', featureName: 'feature1', changeRequest: { id: 1 } }, + ], + }; + + expect( + sortStrategiesByFeature(strategies, changeRequestStrategies), + ).toStrictEqual(expected); + }); +}); + +test('when multiple flag names are provided, the list will be sorted on flag name first, then the criteria used in `sortStrategiesByFeature`', () => { + const strategies = [ + { id: 'b', featureName: 'feature2' }, + { id: 'a', featureName: 'feature1' }, + ]; + const changeRequestStrategies = [ + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 2 } }, + { id: 'b', featureName: 'feature2', changeRequest: { id: 2 } }, + ]; + + const expected = [ + { id: 'a', featureName: 'feature1' }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 1 } }, + { id: 'a', featureName: 'feature1', changeRequest: { id: 2 } }, + { id: 'b', featureName: 'feature2' }, + { id: 'b', featureName: 'feature2', changeRequest: { id: 2 } }, + ]; + + expect( + sortAndFlattenStrategies(strategies, changeRequestStrategies), + ).toStrictEqual(expected); +}); diff --git a/frontend/src/component/segments/SegmentDelete/SegmentDeleteUsedSegment/sort-strategies.ts b/frontend/src/component/segments/SegmentDelete/SegmentDeleteUsedSegment/sort-strategies.ts new file mode 100644 index 0000000000..2a583ee1b3 --- /dev/null +++ b/frontend/src/component/segments/SegmentDelete/SegmentDeleteUsedSegment/sort-strategies.ts @@ -0,0 +1,71 @@ +// use generics here to make it easier to test +export const sortStrategiesByFeature = < + T extends { id: string; featureName?: string }, + U extends { + id?: string; + featureName: string; + changeRequest: { id: number }; + }, +>( + strategies: T[], + changeRequestStrategies: U[], +): { [flagName: string]: (T | U)[] } => { + const isExistingStrategy = (strategy: T | U) => 'id' in strategy; + const isNewStrategy = (strategy: T | U) => !isExistingStrategy(strategy); + + const collected = [...strategies, ...changeRequestStrategies].reduce( + (acc, strategy) => { + if (!strategy.featureName) { + return acc; + } + const registered = acc[strategy.featureName]; + if (registered) { + registered.push(strategy); + } else { + acc[strategy.featureName] = [strategy]; + } + + return acc; + }, + {} as { [flagName: string]: (T | U)[] }, + ); + + const sorted = Object.entries(collected).map( + ([featureName, strategies]) => { + strategies.sort((a, b) => { + if (isNewStrategy(a) && isNewStrategy(b)) { + return a.changeRequest.id - b.changeRequest.id; + } + if (isNewStrategy(a)) { + return -1; + } + if (isNewStrategy(b)) { + return 1; + } + return 0; + }); + [featureName, strategies]; + }, + ); + + const collected2 = Object.fromEntries(sorted); + + return collected2; +}; + +export const sortAndFlattenStrategies = < + T extends { id: string; featureName?: string }, + U extends { + id?: string; + featureName: string; + changeRequest: { id: number }; + }, +>( + strategies: T[], + changeRequestStrategies: U[], +): (T | U)[] => { + const sorted = sortStrategiesByFeature(strategies, changeRequestStrategies); + + // flatten list of + return Object.values(sorted).flat(); +}; diff --git a/frontend/src/hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment.ts b/frontend/src/hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment.ts index 16ccebe982..6782aa137b 100644 --- a/frontend/src/hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment.ts +++ b/frontend/src/hooks/api/getters/useStrategiesBySegment/useStrategiesBySegment.ts @@ -11,7 +11,7 @@ type ChangeRequestNewStrategy = { featureName: string; strategyName: string; environment: string; - changeRequests: [ChangeRequestInfo, ...ChangeRequestInfo[]]; + changeRequest: ChangeRequestInfo; }; type ChangeRequestUpdatedStrategy = ChangeRequestNewStrategy & { id: string };