mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: search now also returns segments used (#5429)
This commit is contained in:
		
							parent
							
								
									91d616cb6a
								
							
						
					
					
						commit
						51f87bdfd9
					
				@ -6,6 +6,7 @@ import {
 | 
			
		||||
import getLogger from '../../../test/fixtures/no-logger';
 | 
			
		||||
import { FeatureSearchQueryParameters } from '../../openapi/spec/feature-search-query-parameters';
 | 
			
		||||
import { IUnleashStores } from '../../types';
 | 
			
		||||
import { DEFAULT_ENV } from '../../util';
 | 
			
		||||
 | 
			
		||||
let app: IUnleashTest;
 | 
			
		||||
let db: ITestDb;
 | 
			
		||||
@ -470,7 +471,7 @@ test('should not return duplicate entries when sorting by last seen', async () =
 | 
			
		||||
 | 
			
		||||
    await stores.environmentStore.create({
 | 
			
		||||
        name: 'production',
 | 
			
		||||
        type: 'production',
 | 
			
		||||
        type: 'development',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await app.linkProjectToEnvironment('default', 'production');
 | 
			
		||||
@ -509,20 +510,31 @@ test('should not return duplicate entries when sorting by last seen', async () =
 | 
			
		||||
test('should search features by description', async () => {
 | 
			
		||||
    const description = 'secretdescription';
 | 
			
		||||
    await app.createFeature('my_feature_a');
 | 
			
		||||
    await app.createFeature({ name: 'my_feature_b', description });
 | 
			
		||||
    await app.createFeature({
 | 
			
		||||
        name: 'my_feature_b',
 | 
			
		||||
        description,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const { body } = await searchFeatures({
 | 
			
		||||
        query: 'descr',
 | 
			
		||||
    });
 | 
			
		||||
    expect(body).toMatchObject({
 | 
			
		||||
        features: [{ name: 'my_feature_b', description }],
 | 
			
		||||
        features: [
 | 
			
		||||
            {
 | 
			
		||||
                name: 'my_feature_b',
 | 
			
		||||
                description,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should support multiple search values', async () => {
 | 
			
		||||
    const description = 'secretdescription';
 | 
			
		||||
    await app.createFeature('my_feature_a');
 | 
			
		||||
    await app.createFeature({ name: 'my_feature_b', description });
 | 
			
		||||
    await app.createFeature({
 | 
			
		||||
        name: 'my_feature_b',
 | 
			
		||||
        description,
 | 
			
		||||
    });
 | 
			
		||||
    await app.createFeature('my_feature_c');
 | 
			
		||||
 | 
			
		||||
    const { body } = await searchFeatures({
 | 
			
		||||
@ -530,7 +542,10 @@ test('should support multiple search values', async () => {
 | 
			
		||||
    });
 | 
			
		||||
    expect(body).toMatchObject({
 | 
			
		||||
        features: [
 | 
			
		||||
            { name: 'my_feature_b', description },
 | 
			
		||||
            {
 | 
			
		||||
                name: 'my_feature_b',
 | 
			
		||||
                description,
 | 
			
		||||
            },
 | 
			
		||||
            { name: 'my_feature_c' },
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
@ -598,3 +613,38 @@ test('should search features by project with operators', async () => {
 | 
			
		||||
        features: [{ name: 'my_feature_b' }],
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should return segments in payload with no duplicates/nulls', async () => {
 | 
			
		||||
    await app.createFeature('my_feature_a');
 | 
			
		||||
    const { body: mySegment } = await app.createSegment({
 | 
			
		||||
        name: 'my_segment_a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await stores.environmentStore.create({
 | 
			
		||||
        name: 'development',
 | 
			
		||||
        type: 'development',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await app.linkProjectToEnvironment('default', 'development');
 | 
			
		||||
    await app.enableFeature('my_feature_a', 'development');
 | 
			
		||||
    await app.addStrategyToFeatureEnv(
 | 
			
		||||
        {
 | 
			
		||||
            name: 'default',
 | 
			
		||||
            segments: [mySegment.id],
 | 
			
		||||
        },
 | 
			
		||||
        DEFAULT_ENV,
 | 
			
		||||
        'my_feature_a',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const { body } = await searchFeatures({});
 | 
			
		||||
 | 
			
		||||
    expect(body).toMatchObject({
 | 
			
		||||
        features: [
 | 
			
		||||
            {
 | 
			
		||||
                name: 'my_feature_a',
 | 
			
		||||
                segments: [mySegment.name],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -614,6 +614,21 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
 | 
			
		||||
                        'feature_tag as ft',
 | 
			
		||||
                        'ft.feature_name',
 | 
			
		||||
                        'features.name',
 | 
			
		||||
                    )
 | 
			
		||||
                    .leftJoin(
 | 
			
		||||
                        'feature_strategies',
 | 
			
		||||
                        'feature_strategies.feature_name',
 | 
			
		||||
                        'features.name',
 | 
			
		||||
                    )
 | 
			
		||||
                    .leftJoin(
 | 
			
		||||
                        'feature_strategy_segment',
 | 
			
		||||
                        'feature_strategy_segment.feature_strategy_id',
 | 
			
		||||
                        'feature_strategies.id',
 | 
			
		||||
                    )
 | 
			
		||||
                    .leftJoin(
 | 
			
		||||
                        'segments',
 | 
			
		||||
                        'feature_strategy_segment.segment_id',
 | 
			
		||||
                        'segments.id',
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                if (this.flagResolver.isEnabled('useLastSeenRefactor')) {
 | 
			
		||||
@ -645,6 +660,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
 | 
			
		||||
                    'environments.sort_order as environment_sort_order',
 | 
			
		||||
                    'ft.tag_value as tag_value',
 | 
			
		||||
                    'ft.tag_type as tag_type',
 | 
			
		||||
                    'segments.name as segment_name',
 | 
			
		||||
                ] as (string | Raw<any> | Knex.QueryBuilder)[];
 | 
			
		||||
 | 
			
		||||
                let lastSeenQuery = 'feature_environments.last_seen_at';
 | 
			
		||||
@ -735,7 +751,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
 | 
			
		||||
        const rows = await finalQuery;
 | 
			
		||||
 | 
			
		||||
        if (rows.length > 0) {
 | 
			
		||||
            const overview = this.getFeatureOverviewData(rows);
 | 
			
		||||
            const overview = this.getAggregatedSearchData(rows);
 | 
			
		||||
            const features = sortEnvironments(overview);
 | 
			
		||||
            return {
 | 
			
		||||
                features,
 | 
			
		||||
@ -861,6 +877,62 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getAggregatedSearchData(rows): IFeatureOverview {
 | 
			
		||||
        return rows.reduce((acc, row) => {
 | 
			
		||||
            if (acc[row.feature_name] !== undefined) {
 | 
			
		||||
                const environmentExists = acc[
 | 
			
		||||
                    row.feature_name
 | 
			
		||||
                ].environments.some(
 | 
			
		||||
                    (existingEnvironment) =>
 | 
			
		||||
                        existingEnvironment.name === row.environment,
 | 
			
		||||
                );
 | 
			
		||||
                if (!environmentExists) {
 | 
			
		||||
                    acc[row.feature_name].environments.push(
 | 
			
		||||
                        FeatureStrategiesStore.getEnvironment(row),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const segmentExists = acc[row.feature_name].segments.includes(
 | 
			
		||||
                    row.segment_name,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if (row.segment_name && !segmentExists) {
 | 
			
		||||
                    acc[row.feature_name].segments.push(row.segment_name);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (this.isNewTag(acc[row.feature_name], row)) {
 | 
			
		||||
                    this.addTag(acc[row.feature_name], row);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                acc[row.feature_name] = {
 | 
			
		||||
                    type: row.type,
 | 
			
		||||
                    description: row.description,
 | 
			
		||||
                    favorite: row.favorite,
 | 
			
		||||
                    name: row.feature_name,
 | 
			
		||||
                    createdAt: row.created_at,
 | 
			
		||||
                    stale: row.stale,
 | 
			
		||||
                    impressionData: row.impression_data,
 | 
			
		||||
                    lastSeenAt: row.last_seen_at,
 | 
			
		||||
                    environments: [FeatureStrategiesStore.getEnvironment(row)],
 | 
			
		||||
                    segments: row.segment_name ? [row.segment_name] : [],
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                if (this.isNewTag(acc[row.feature_name], row)) {
 | 
			
		||||
                    this.addTag(acc[row.feature_name], row);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            const featureRow = acc[row.feature_name];
 | 
			
		||||
            if (
 | 
			
		||||
                featureRow.lastSeenAt === undefined ||
 | 
			
		||||
                new Date(row.env_last_seen_at) >
 | 
			
		||||
                    new Date(featureRow.last_seen_at)
 | 
			
		||||
            ) {
 | 
			
		||||
                featureRow.lastSeenAt = row.env_last_seen_at;
 | 
			
		||||
            }
 | 
			
		||||
            return acc;
 | 
			
		||||
        }, {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getFeatureOverviewData(rows): IFeatureOverview {
 | 
			
		||||
        return rows.reduce((acc, row) => {
 | 
			
		||||
            if (acc[row.feature_name] !== undefined) {
 | 
			
		||||
 | 
			
		||||
@ -56,15 +56,6 @@ const fetchSegmentStrategies = (
 | 
			
		||||
        .expect(200)
 | 
			
		||||
        .then((res) => res.body);
 | 
			
		||||
 | 
			
		||||
const createSegment = (
 | 
			
		||||
    postData: object,
 | 
			
		||||
    expectStatusCode = 201,
 | 
			
		||||
): Promise<unknown> =>
 | 
			
		||||
    app.request
 | 
			
		||||
        .post(SEGMENTS_BASE_PATH)
 | 
			
		||||
        .send(postData)
 | 
			
		||||
        .expect(expectStatusCode);
 | 
			
		||||
 | 
			
		||||
const updateSegment = (
 | 
			
		||||
    id: number,
 | 
			
		||||
    postData: object,
 | 
			
		||||
@ -94,7 +85,13 @@ const addSegmentsToStrategy = (
 | 
			
		||||
 | 
			
		||||
const mockFeatureToggle = () => ({
 | 
			
		||||
    name: randomId(),
 | 
			
		||||
    strategies: [{ name: 'flexibleRollout', constraints: [], parameters: {} }],
 | 
			
		||||
    strategies: [
 | 
			
		||||
        {
 | 
			
		||||
            name: 'flexibleRollout',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
            parameters: {},
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const validateSegment = (
 | 
			
		||||
@ -136,13 +133,38 @@ afterEach(async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should validate segments', async () => {
 | 
			
		||||
    await createSegment({ something: 'a' }, 400);
 | 
			
		||||
    await createSegment({ name: randomId(), something: 'b' }, 400);
 | 
			
		||||
    await createSegment({ name: randomId(), constraints: 'b' }, 400);
 | 
			
		||||
    await createSegment({ constraints: [] }, 400);
 | 
			
		||||
    await createSegment({ name: randomId(), constraints: [{}] }, 400);
 | 
			
		||||
    await createSegment({ name: randomId(), constraints: [] });
 | 
			
		||||
    await createSegment({ name: randomId(), description: '', constraints: [] });
 | 
			
		||||
    await app.createSegment({ something: 'a' }, 400);
 | 
			
		||||
    await app.createSegment(
 | 
			
		||||
        {
 | 
			
		||||
            name: randomId(),
 | 
			
		||||
            something: 'b',
 | 
			
		||||
        },
 | 
			
		||||
        400,
 | 
			
		||||
    );
 | 
			
		||||
    await app.createSegment(
 | 
			
		||||
        {
 | 
			
		||||
            name: randomId(),
 | 
			
		||||
            constraints: 'b',
 | 
			
		||||
        },
 | 
			
		||||
        400,
 | 
			
		||||
    );
 | 
			
		||||
    await app.createSegment({ constraints: [] }, 400);
 | 
			
		||||
    await app.createSegment(
 | 
			
		||||
        {
 | 
			
		||||
            name: randomId(),
 | 
			
		||||
            constraints: [{}],
 | 
			
		||||
        },
 | 
			
		||||
        400,
 | 
			
		||||
    );
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: randomId(),
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: randomId(),
 | 
			
		||||
        description: '',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should fail on missing properties', async () => {
 | 
			
		||||
@ -159,23 +181,38 @@ test('should fail on missing properties', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should create segments', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'c', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'b', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'c',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'b',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const segments = await fetchSegments();
 | 
			
		||||
    expect(segments.map((s) => s.name)).toEqual(['a', 'b', 'c']);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should update segments', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const [segmentA] = await fetchSegments();
 | 
			
		||||
    expect(segmentA.id).toBeGreaterThan(0);
 | 
			
		||||
    expect(segmentA.name).toEqual('a');
 | 
			
		||||
    expect(segmentA.createdAt).toBeDefined();
 | 
			
		||||
    expect(segmentA.constraints.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
    await updateSegment(segmentA.id, { ...segmentA, name: 'b' });
 | 
			
		||||
    await updateSegment(segmentA.id, {
 | 
			
		||||
        ...segmentA,
 | 
			
		||||
        name: 'b',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const [segmentB] = await fetchSegments();
 | 
			
		||||
    expect(segmentB.id).toEqual(segmentA.id);
 | 
			
		||||
@ -185,15 +222,29 @@ test('should update segments', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should update segment constraints', async () => {
 | 
			
		||||
    const constraintA = { contextName: 'a', operator: 'IN', values: ['x'] };
 | 
			
		||||
    const constraintB = { contextName: 'b', operator: 'IN', values: ['y'] };
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [constraintA] });
 | 
			
		||||
    const constraintA = {
 | 
			
		||||
        contextName: 'a',
 | 
			
		||||
        operator: 'IN',
 | 
			
		||||
        values: ['x'],
 | 
			
		||||
    };
 | 
			
		||||
    const constraintB = {
 | 
			
		||||
        contextName: 'b',
 | 
			
		||||
        operator: 'IN',
 | 
			
		||||
        values: ['y'],
 | 
			
		||||
    };
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [constraintA],
 | 
			
		||||
    });
 | 
			
		||||
    const [segmentA] = await fetchSegments();
 | 
			
		||||
    expect(segmentA.constraints).toEqual([constraintA]);
 | 
			
		||||
 | 
			
		||||
    await app.request
 | 
			
		||||
        .put(`${SEGMENTS_BASE_PATH}/${segmentA.id}`)
 | 
			
		||||
        .send({ ...segmentA, constraints: [constraintB, constraintA] })
 | 
			
		||||
        .send({
 | 
			
		||||
            ...segmentA,
 | 
			
		||||
            constraints: [constraintB, constraintA],
 | 
			
		||||
        })
 | 
			
		||||
        .expect(204);
 | 
			
		||||
 | 
			
		||||
    const [segmentB] = await fetchSegments();
 | 
			
		||||
@ -201,7 +252,10 @@ test('should update segment constraints', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should delete segments', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const segments = await fetchSegments();
 | 
			
		||||
    expect(segments.length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
@ -213,7 +267,10 @@ test('should delete segments', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should not delete segments used by strategies', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const toggle = mockFeatureToggle();
 | 
			
		||||
    await createFeatureToggle(app, toggle);
 | 
			
		||||
    const [segment] = await fetchSegments();
 | 
			
		||||
@ -238,9 +295,18 @@ test('should not delete segments used by strategies', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should list strategies by segment', async () => {
 | 
			
		||||
    await createSegment({ name: 'S1', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'S2', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'S3', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'S1',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'S2',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'S3',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const toggle1 = mockFeatureToggle();
 | 
			
		||||
    const toggle2 = mockFeatureToggle();
 | 
			
		||||
    const toggle3 = mockFeatureToggle();
 | 
			
		||||
@ -305,9 +371,18 @@ test('should list strategies by segment', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should list segments by strategy', async () => {
 | 
			
		||||
    await createSegment({ name: 'S1', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'S2', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'S3', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'S1',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'S2',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'S3',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const toggle1 = mockFeatureToggle();
 | 
			
		||||
    const toggle2 = mockFeatureToggle();
 | 
			
		||||
    const toggle3 = mockFeatureToggle();
 | 
			
		||||
@ -376,7 +451,10 @@ test('should list segments by strategy', async () => {
 | 
			
		||||
 | 
			
		||||
test('should reject duplicate segment names', async () => {
 | 
			
		||||
    await validateSegment({ name: 'a' });
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await validateSegment({ name: 'a' }, 409);
 | 
			
		||||
    await validateSegment({ name: 'b' });
 | 
			
		||||
});
 | 
			
		||||
@ -388,31 +466,75 @@ test('should reject empty segment names', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should reject duplicate segment names on create', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] }, 409);
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment(
 | 
			
		||||
        {
 | 
			
		||||
            name: 'a',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
        },
 | 
			
		||||
        409,
 | 
			
		||||
    );
 | 
			
		||||
    await validateSegment({ name: 'b' });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should reject duplicate segment names on update', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'b', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'b',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const [segmentA, segmentB] = await fetchSegments();
 | 
			
		||||
    await updateSegment(segmentA.id, { name: 'b', constraints: [] }, 409);
 | 
			
		||||
    await updateSegment(segmentB.id, { name: 'a', constraints: [] }, 409);
 | 
			
		||||
    await updateSegment(segmentA.id, { name: 'a', constraints: [] });
 | 
			
		||||
    await updateSegment(segmentA.id, { name: 'c', constraints: [] });
 | 
			
		||||
    await updateSegment(
 | 
			
		||||
        segmentA.id,
 | 
			
		||||
        {
 | 
			
		||||
            name: 'b',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
        },
 | 
			
		||||
        409,
 | 
			
		||||
    );
 | 
			
		||||
    await updateSegment(
 | 
			
		||||
        segmentB.id,
 | 
			
		||||
        {
 | 
			
		||||
            name: 'a',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
        },
 | 
			
		||||
        409,
 | 
			
		||||
    );
 | 
			
		||||
    await updateSegment(segmentA.id, {
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await updateSegment(segmentA.id, {
 | 
			
		||||
        name: 'c',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('Should anonymise createdBy field if anonymiseEventLog flag is set', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await createSegment({ name: 'b', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'b',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const segments = await fetchSegments();
 | 
			
		||||
    expect(segments).toHaveLength(2);
 | 
			
		||||
    expect(segments[0].createdBy).toContain('unleash.run');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('Should show usage in features and projects', async () => {
 | 
			
		||||
    await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
    await app.createSegment({
 | 
			
		||||
        name: 'a',
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    const toggle = mockFeatureToggle();
 | 
			
		||||
    await createFeatureToggle(app, toggle);
 | 
			
		||||
    const [segment] = await fetchSegments();
 | 
			
		||||
@ -427,7 +549,12 @@ test('Should show usage in features and projects', async () => {
 | 
			
		||||
    await addSegmentsToStrategy([segment.id], feature.strategies[0].id);
 | 
			
		||||
 | 
			
		||||
    const segments = await fetchSegments();
 | 
			
		||||
    expect(segments).toMatchObject([{ usedInFeatures: 1, usedInProjects: 1 }]);
 | 
			
		||||
    expect(segments).toMatchObject([
 | 
			
		||||
        {
 | 
			
		||||
            usedInFeatures: 1,
 | 
			
		||||
            usedInProjects: 1,
 | 
			
		||||
        },
 | 
			
		||||
    ]);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('detect strategy usage in change requests', () => {
 | 
			
		||||
@ -497,7 +624,10 @@ describe('detect strategy usage in change requests', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should not delete segments used by strategies in CRs', async () => {
 | 
			
		||||
        await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
        await app.createSegment({
 | 
			
		||||
            name: 'a',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
        });
 | 
			
		||||
        const toggle = mockFeatureToggle();
 | 
			
		||||
        await createFeatureToggle(enterpriseApp, toggle);
 | 
			
		||||
        const [segment] = await enterpriseFetchSegments();
 | 
			
		||||
@ -538,7 +668,10 @@ describe('detect strategy usage in change requests', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Should show segment usage in addStrategy events', async () => {
 | 
			
		||||
        await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
        await app.createSegment({
 | 
			
		||||
            name: 'a',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
        });
 | 
			
		||||
        const toggle = mockFeatureToggle();
 | 
			
		||||
        await createFeatureToggle(enterpriseApp, toggle);
 | 
			
		||||
        const [segment] = await enterpriseFetchSegments();
 | 
			
		||||
@ -585,7 +718,10 @@ describe('detect strategy usage in change requests', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Should show segment usage in updateStrategy events', async () => {
 | 
			
		||||
        await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
        await app.createSegment({
 | 
			
		||||
            name: 'a',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
        });
 | 
			
		||||
        const toggle = mockFeatureToggle();
 | 
			
		||||
        await createFeatureToggle(enterpriseApp, toggle);
 | 
			
		||||
        const [segment] = await enterpriseFetchSegments();
 | 
			
		||||
@ -641,7 +777,10 @@ describe('detect strategy usage in change requests', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('If a segment is used in an existing strategy and in a CR for the same strategy, the strategy should be listed both places', async () => {
 | 
			
		||||
        await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
        await app.createSegment({
 | 
			
		||||
            name: 'a',
 | 
			
		||||
            constraints: [],
 | 
			
		||||
        });
 | 
			
		||||
        const toggle = mockFeatureToggle();
 | 
			
		||||
        await createFeatureToggle(enterpriseApp, toggle);
 | 
			
		||||
        const [segment] = await enterpriseFetchSegments();
 | 
			
		||||
@ -697,7 +836,7 @@ describe('detect strategy usage in change requests', () => {
 | 
			
		||||
        // because they use the same db, we can use the regular app
 | 
			
		||||
        // (through `createSegment` and `createFeatureToggle`) to
 | 
			
		||||
        // create the segment and the flag
 | 
			
		||||
        await createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
        await app.createSegment({ name: 'a', constraints: [] });
 | 
			
		||||
        const toggle = mockFeatureToggle();
 | 
			
		||||
        await createFeatureToggle(app, toggle);
 | 
			
		||||
        const [segment] = await enterpriseFetchSegments();
 | 
			
		||||
 | 
			
		||||
@ -99,6 +99,8 @@ export interface IUnleashHttpAPI {
 | 
			
		||||
    ): supertest.Test;
 | 
			
		||||
 | 
			
		||||
    getRecordedEvents(): supertest.Test;
 | 
			
		||||
 | 
			
		||||
    createSegment(postData: object, expectStatusCode?: number): supertest.Test;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function httpApis(
 | 
			
		||||
@ -260,6 +262,16 @@ function httpApis(
 | 
			
		||||
                .set('Content-Type', 'application/json')
 | 
			
		||||
                .expect(expectedResponseCode);
 | 
			
		||||
        },
 | 
			
		||||
        createSegment(
 | 
			
		||||
            postData: object,
 | 
			
		||||
            expectedResponseCode = 201,
 | 
			
		||||
        ): supertest.Test {
 | 
			
		||||
            return request
 | 
			
		||||
                .post(`/api/admin/segments`)
 | 
			
		||||
                .send(postData)
 | 
			
		||||
                .set('Content-Type', 'application/json')
 | 
			
		||||
                .expect(expectedResponseCode);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        getRecordedEvents(
 | 
			
		||||
            project: string | null = null,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user