mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: implement column created_by_user_id in feature_tag (#5695)
## About the changes Adds the new nullable column created_by_user_id to the data used by feature-tag-store and feature-tag-service. Also updates openapi schemas.
This commit is contained in:
		
							parent
							
								
									e0f83347ab
								
							
						
					
					
						commit
						4e56d1d8d5
					
				@ -24,4 +24,6 @@ export interface FeatureTagSchema {
 | 
			
		||||
     * @deprecated
 | 
			
		||||
     */
 | 
			
		||||
    value?: string;
 | 
			
		||||
    /** The id of the user who created this tag */
 | 
			
		||||
    createdByUserId?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import { DB_TIME } from '../metric-events';
 | 
			
		||||
import {
 | 
			
		||||
    IFeatureAndTag,
 | 
			
		||||
    IFeatureTag,
 | 
			
		||||
    IFeatureTagInsert,
 | 
			
		||||
    IFeatureTagStore,
 | 
			
		||||
} from '../types/stores/feature-tag-store';
 | 
			
		||||
import { Db } from './db';
 | 
			
		||||
@ -18,6 +19,7 @@ interface FeatureTagTable {
 | 
			
		||||
    feature_name: string;
 | 
			
		||||
    tag_type: string;
 | 
			
		||||
    tag_value: string;
 | 
			
		||||
    created_by_user_id?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
@ -82,6 +84,7 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
            featureName: row.feature_name,
 | 
			
		||||
            tagType: row.tag_type,
 | 
			
		||||
            tagValue: row.tag_value,
 | 
			
		||||
            createdByUserId: row.created_by_user_id,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -91,6 +94,7 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
            featureName: row.feature_name,
 | 
			
		||||
            tagType: row.tag_type,
 | 
			
		||||
            tagValue: row.tag_value,
 | 
			
		||||
            createdByUserId: row.created_by_user_id,
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -138,13 +142,18 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
            featureName: row.feature_name,
 | 
			
		||||
            tagType: row.tag_type,
 | 
			
		||||
            tagValue: row.tag_value,
 | 
			
		||||
            createdByUserId: row.created_by_user_id,
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async tagFeature(featureName: string, tag: ITag): Promise<ITag> {
 | 
			
		||||
    async tagFeature(
 | 
			
		||||
        featureName: string,
 | 
			
		||||
        tag: ITag,
 | 
			
		||||
        createdByUserId: number,
 | 
			
		||||
    ): Promise<ITag> {
 | 
			
		||||
        const stopTimer = this.timer('tagFeature');
 | 
			
		||||
        await this.db<FeatureTagTable>(TABLE)
 | 
			
		||||
            .insert(this.featureAndTagToRow(featureName, tag))
 | 
			
		||||
            .insert(this.featureAndTagToRow(featureName, tag, createdByUserId))
 | 
			
		||||
            .onConflict(COLUMNS)
 | 
			
		||||
            .merge();
 | 
			
		||||
        stopTimer();
 | 
			
		||||
@ -177,6 +186,7 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
            featureName: row.feature_name,
 | 
			
		||||
            tagType: row.tag_type,
 | 
			
		||||
            tagValue: row.tag_value,
 | 
			
		||||
            createdByUserId: row.created_by_user_id,
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -186,7 +196,9 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
        stopTimer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async tagFeatures(featureTags: IFeatureTag[]): Promise<IFeatureAndTag[]> {
 | 
			
		||||
    async tagFeatures(
 | 
			
		||||
        featureTags: IFeatureTagInsert[],
 | 
			
		||||
    ): Promise<IFeatureAndTag[]> {
 | 
			
		||||
        if (featureTags.length !== 0) {
 | 
			
		||||
            const rows = await this.db(TABLE)
 | 
			
		||||
                .insert(featureTags.map(this.featureTagToRow))
 | 
			
		||||
@ -204,7 +216,11 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
        const stopTimer = this.timer('untagFeature');
 | 
			
		||||
        try {
 | 
			
		||||
            await this.db(TABLE)
 | 
			
		||||
                .where(this.featureAndTagToRow(featureName, tag))
 | 
			
		||||
                .where({
 | 
			
		||||
                    feature_name: featureName,
 | 
			
		||||
                    tag_type: tag.type,
 | 
			
		||||
                    tag_value: tag.value,
 | 
			
		||||
                })
 | 
			
		||||
                .delete();
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            this.logger.error(err);
 | 
			
		||||
@ -233,11 +249,13 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
        featureName,
 | 
			
		||||
        tagType,
 | 
			
		||||
        tagValue,
 | 
			
		||||
    }: IFeatureTag): FeatureTagTable {
 | 
			
		||||
        createdByUserId,
 | 
			
		||||
    }: IFeatureTagInsert): FeatureTagTable {
 | 
			
		||||
        return {
 | 
			
		||||
            feature_name: featureName,
 | 
			
		||||
            tag_type: tagType,
 | 
			
		||||
            tag_value: tagValue,
 | 
			
		||||
            created_by_user_id: createdByUserId,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -248,11 +266,13 @@ class FeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
    featureAndTagToRow(
 | 
			
		||||
        featureName: string,
 | 
			
		||||
        { type, value }: ITag,
 | 
			
		||||
        createdByUserId: number,
 | 
			
		||||
    ): FeatureTagTable {
 | 
			
		||||
        return {
 | 
			
		||||
            feature_name: featureName,
 | 
			
		||||
            tag_type: type,
 | 
			
		||||
            tag_value: value,
 | 
			
		||||
            created_by_user_id: createdByUserId,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ let app: IUnleashTest;
 | 
			
		||||
let db: ITestDb;
 | 
			
		||||
const sortOrderFirst = 0;
 | 
			
		||||
const sortOrderSecond = 10;
 | 
			
		||||
const TESTUSERID = 3333;
 | 
			
		||||
 | 
			
		||||
const createSegment = async (segmentName: string) => {
 | 
			
		||||
    const segment = await app.services.segmentService.create(
 | 
			
		||||
@ -2991,7 +2992,7 @@ test('Can filter based on tags', async () => {
 | 
			
		||||
    await db.stores.featureToggleStore.create('default', {
 | 
			
		||||
        name: 'not-tagged',
 | 
			
		||||
    });
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature('to-be-tagged', tag);
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature('to-be-tagged', tag, TESTUSERID);
 | 
			
		||||
    await app.request
 | 
			
		||||
        .get('/api/admin/projects/default/features?tag=simple:hello-tags')
 | 
			
		||||
        .expect((res) => {
 | 
			
		||||
@ -3028,10 +3029,12 @@ test('Can query for features with namePrefix and tags', async () => {
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(
 | 
			
		||||
        'to-be-tagged-nameprefix-and-tags',
 | 
			
		||||
        tag,
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(
 | 
			
		||||
        'tagged-but-not-hit-nameprefix-and-tags',
 | 
			
		||||
        tag,
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    await app.request
 | 
			
		||||
        .get(
 | 
			
		||||
@ -3065,13 +3068,26 @@ test('Can query for two tags at the same time. Tags are ORed together', async ()
 | 
			
		||||
            name: 'tagged-with-both-tags',
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(taggedWithFirst.name, tag);
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(
 | 
			
		||||
        taggedWithFirst.name,
 | 
			
		||||
        tag,
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(
 | 
			
		||||
        taggedWithSecond.name,
 | 
			
		||||
        secondTag,
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(
 | 
			
		||||
        taggedWithBoth.name,
 | 
			
		||||
        tag,
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(
 | 
			
		||||
        taggedWithBoth.name,
 | 
			
		||||
        secondTag,
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(taggedWithBoth.name, tag);
 | 
			
		||||
    await db.stores.featureTagStore.tagFeature(taggedWithBoth.name, secondTag);
 | 
			
		||||
    await app.request
 | 
			
		||||
        .get(
 | 
			
		||||
            `/api/admin/projects/default/features?tag=${tag.type}:${tag.value}&tag=${secondTag.type}:${secondTag.value}`,
 | 
			
		||||
 | 
			
		||||
@ -35,6 +35,12 @@ export const featureTagSchema = {
 | 
			
		||||
            description:
 | 
			
		||||
                'The value of the tag. This property is deprecated and will be removed in a future version of Unleash. Superseded by the `tagValue` property.',
 | 
			
		||||
        },
 | 
			
		||||
        createdByUserId: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
            nullable: true,
 | 
			
		||||
            example: 1,
 | 
			
		||||
            description: 'The id of the user who created this tag',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: {},
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
@ -159,4 +159,5 @@ export const featureTagSchema = joi.object().keys({
 | 
			
		||||
    tagValue: joi.string(),
 | 
			
		||||
    type: nameType.optional(),
 | 
			
		||||
    value: joi.string().optional(),
 | 
			
		||||
    createdByUserId: joi.number().optional(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import { IFeatureToggleStore, IUnleashStores } from '../types/stores';
 | 
			
		||||
import { tagSchema } from './tag-schema';
 | 
			
		||||
import {
 | 
			
		||||
    IFeatureTag,
 | 
			
		||||
    IFeatureTagInsert,
 | 
			
		||||
    IFeatureTagStore,
 | 
			
		||||
} from '../types/stores/feature-tag-store';
 | 
			
		||||
import { ITagStore } from '../types/stores/tag-store';
 | 
			
		||||
@ -61,7 +62,11 @@ class FeatureTagService {
 | 
			
		||||
        const featureToggle = await this.featureToggleStore.get(featureName);
 | 
			
		||||
        const validatedTag = await tagSchema.validateAsync(tag);
 | 
			
		||||
        await this.createTagIfNeeded(validatedTag, userName, addedByUserId);
 | 
			
		||||
        await this.featureTagStore.tagFeature(featureName, validatedTag);
 | 
			
		||||
        await this.featureTagStore.tagFeature(
 | 
			
		||||
            featureName,
 | 
			
		||||
            validatedTag,
 | 
			
		||||
            addedByUserId,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        await this.eventService.storeEvent({
 | 
			
		||||
            type: FEATURE_TAGGED,
 | 
			
		||||
@ -88,25 +93,26 @@ class FeatureTagService {
 | 
			
		||||
                this.createTagIfNeeded(tag, userName, updatedByUserId),
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
        const createdFeatureTags: IFeatureTag[] = featureNames.flatMap(
 | 
			
		||||
        const createdFeatureTags: IFeatureTagInsert[] = featureNames.flatMap(
 | 
			
		||||
            (featureName) =>
 | 
			
		||||
                addedTags.map((addedTag) => ({
 | 
			
		||||
                    featureName,
 | 
			
		||||
                    tagType: addedTag.type,
 | 
			
		||||
                    tagValue: addedTag.value,
 | 
			
		||||
                    createdByUserId: updatedByUserId,
 | 
			
		||||
                })),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        await this.featureTagStore.tagFeatures(createdFeatureTags);
 | 
			
		||||
 | 
			
		||||
        const removedFeatureTags: IFeatureTag[] = featureNames.flatMap(
 | 
			
		||||
            (featureName) =>
 | 
			
		||||
        const removedFeatureTags: Omit<IFeatureTag, 'createdByUserId'>[] =
 | 
			
		||||
            featureNames.flatMap((featureName) =>
 | 
			
		||||
                removedTags.map((addedTag) => ({
 | 
			
		||||
                    featureName,
 | 
			
		||||
                    tagType: addedTag.type,
 | 
			
		||||
                    tagValue: addedTag.value,
 | 
			
		||||
                })),
 | 
			
		||||
        );
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        await this.featureTagStore.untagFeatures(removedFeatureTags);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import variantsExportV3 from '../../test/examples/variantsexport_v3.json';
 | 
			
		||||
import EventService from './event-service';
 | 
			
		||||
import { SYSTEM_USER_ID } from '../types';
 | 
			
		||||
const oldExportExample = require('./state-service-export-v1.json');
 | 
			
		||||
const TESTUSERID = 3333;
 | 
			
		||||
 | 
			
		||||
function getSetup() {
 | 
			
		||||
    const stores = createStores();
 | 
			
		||||
@ -398,10 +399,14 @@ test('Should not import an existing tag', async () => {
 | 
			
		||||
    };
 | 
			
		||||
    await stores.tagTypeStore.createTagType(data.tagTypes[0]);
 | 
			
		||||
    await stores.tagStore.createTag(data.tags[0]);
 | 
			
		||||
    await stores.featureTagStore.tagFeature(data.featureTags[0].featureName, {
 | 
			
		||||
        type: data.featureTags[0].tagType,
 | 
			
		||||
        value: data.featureTags[0].tagValue,
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature(
 | 
			
		||||
        data.featureTags[0].featureName,
 | 
			
		||||
        {
 | 
			
		||||
            type: data.featureTags[0].tagType,
 | 
			
		||||
            value: data.featureTags[0].tagValue,
 | 
			
		||||
        },
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    await stateService.import({
 | 
			
		||||
        data,
 | 
			
		||||
        userId: SYSTEM_USER_ID,
 | 
			
		||||
@ -466,10 +471,14 @@ test('should export tag, tagtypes but not feature tags if the feature is not exp
 | 
			
		||||
    };
 | 
			
		||||
    await stores.tagTypeStore.createTagType(data.tagTypes[0]);
 | 
			
		||||
    await stores.tagStore.createTag(data.tags[0]);
 | 
			
		||||
    await stores.featureTagStore.tagFeature(data.featureTags[0].featureName, {
 | 
			
		||||
        type: data.featureTags[0].tagType,
 | 
			
		||||
        value: data.featureTags[0].tagValue,
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature(
 | 
			
		||||
        data.featureTags[0].featureName,
 | 
			
		||||
        {
 | 
			
		||||
            type: data.featureTags[0].tagType,
 | 
			
		||||
            value: data.featureTags[0].tagValue,
 | 
			
		||||
        },
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const exported = await stateService.export({
 | 
			
		||||
        includeFeatureToggles: false,
 | 
			
		||||
@ -504,10 +513,14 @@ test('should export tag, tagtypes, featureTags and features', async () => {
 | 
			
		||||
    };
 | 
			
		||||
    await stores.tagTypeStore.createTagType(data.tagTypes[0]);
 | 
			
		||||
    await stores.tagStore.createTag(data.tags[0]);
 | 
			
		||||
    await stores.featureTagStore.tagFeature(data.featureTags[0].featureName, {
 | 
			
		||||
        type: data.featureTags[0].tagType,
 | 
			
		||||
        value: data.featureTags[0].tagValue,
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature(
 | 
			
		||||
        data.featureTags[0].featureName,
 | 
			
		||||
        {
 | 
			
		||||
            type: data.featureTags[0].tagType,
 | 
			
		||||
            value: data.featureTags[0].tagValue,
 | 
			
		||||
        },
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const exported = await stateService.export({
 | 
			
		||||
        includeFeatureToggles: true,
 | 
			
		||||
@ -667,10 +680,14 @@ test('exporting to new format works', async () => {
 | 
			
		||||
        parameters: {},
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature('Some-feature', {
 | 
			
		||||
        type: 'simple',
 | 
			
		||||
        value: 'Test',
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature(
 | 
			
		||||
        'Some-feature',
 | 
			
		||||
        {
 | 
			
		||||
            type: 'simple',
 | 
			
		||||
            value: 'Test',
 | 
			
		||||
        },
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
    const exported = await stateService.export({});
 | 
			
		||||
    expect(exported.featureStrategies).toHaveLength(1);
 | 
			
		||||
});
 | 
			
		||||
@ -725,10 +742,14 @@ test('featureStrategies can keep existing', async () => {
 | 
			
		||||
        parameters: {},
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature('Some-feature', {
 | 
			
		||||
        type: 'simple',
 | 
			
		||||
        value: 'Test',
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature(
 | 
			
		||||
        'Some-feature',
 | 
			
		||||
        {
 | 
			
		||||
            type: 'simple',
 | 
			
		||||
            value: 'Test',
 | 
			
		||||
        },
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const exported = await stateService.export({});
 | 
			
		||||
    await stateService.import({
 | 
			
		||||
@ -776,10 +797,14 @@ test('featureStrategies should not keep existing if dropBeforeImport', async ()
 | 
			
		||||
        parameters: {},
 | 
			
		||||
        constraints: [],
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature('Some-feature', {
 | 
			
		||||
        type: 'simple',
 | 
			
		||||
        value: 'Test',
 | 
			
		||||
    });
 | 
			
		||||
    await stores.featureTagStore.tagFeature(
 | 
			
		||||
        'Some-feature',
 | 
			
		||||
        {
 | 
			
		||||
            type: 'simple',
 | 
			
		||||
            value: 'Test',
 | 
			
		||||
        },
 | 
			
		||||
        TESTUSERID,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const exported = await stateService.export({});
 | 
			
		||||
    exported.featureStrategies = [];
 | 
			
		||||
 | 
			
		||||
@ -616,13 +616,18 @@ export default class StateService {
 | 
			
		||||
        userName: string,
 | 
			
		||||
        userId: number,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const featureTagsToInsert = featureTags.filter((tag) =>
 | 
			
		||||
            keepExisting
 | 
			
		||||
                ? !oldFeatureTags.some((old) =>
 | 
			
		||||
                      this.compareFeatureTags(old, tag),
 | 
			
		||||
                  )
 | 
			
		||||
                : true,
 | 
			
		||||
        );
 | 
			
		||||
        const featureTagsToInsert = featureTags
 | 
			
		||||
            .filter((tag) =>
 | 
			
		||||
                keepExisting
 | 
			
		||||
                    ? !oldFeatureTags.some((old) =>
 | 
			
		||||
                          this.compareFeatureTags(old, tag),
 | 
			
		||||
                      )
 | 
			
		||||
                    : true,
 | 
			
		||||
            )
 | 
			
		||||
            .map((tag) => ({
 | 
			
		||||
                createdByUserId: userId,
 | 
			
		||||
                ...tag,
 | 
			
		||||
            }));
 | 
			
		||||
        if (featureTagsToInsert.length > 0) {
 | 
			
		||||
            const importedFeatureTags =
 | 
			
		||||
                await this.featureTagStore.tagFeatures(featureTagsToInsert);
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,12 @@ export interface IFeatureTag {
 | 
			
		||||
    featureName: string;
 | 
			
		||||
    tagType: string;
 | 
			
		||||
    tagValue: string;
 | 
			
		||||
    createdByUserId?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IFeatureTagInsert
 | 
			
		||||
    extends Omit<IFeatureTag, 'created_by_user_id'> {
 | 
			
		||||
    createdByUserId: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IFeatureAndTag {
 | 
			
		||||
@ -15,8 +21,14 @@ export interface IFeatureTagStore extends Store<IFeatureTag, IFeatureTag> {
 | 
			
		||||
    getAllTagsForFeature(featureName: string): Promise<ITag[]>;
 | 
			
		||||
    getAllFeaturesForTag(tagValue: string): Promise<string[]>;
 | 
			
		||||
    getAllByFeatures(features: string[]): Promise<IFeatureTag[]>;
 | 
			
		||||
    tagFeature(featureName: string, tag: ITag): Promise<ITag>;
 | 
			
		||||
    tagFeatures(featureTags: IFeatureTag[]): Promise<IFeatureAndTag[]>;
 | 
			
		||||
    tagFeature(
 | 
			
		||||
        featureName: string,
 | 
			
		||||
        tag: ITag,
 | 
			
		||||
        createdByUserId: number,
 | 
			
		||||
    ): Promise<ITag>;
 | 
			
		||||
    tagFeatures(featureTags: IFeatureTagInsert[]): Promise<IFeatureAndTag[]>;
 | 
			
		||||
    untagFeature(featureName: string, tag: ITag): Promise<void>;
 | 
			
		||||
    untagFeatures(featureTags: IFeatureTag[]): Promise<void>;
 | 
			
		||||
    untagFeatures(
 | 
			
		||||
        featureTags: Omit<IFeatureTag, 'createdByUserId'>[],
 | 
			
		||||
    ): Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ let featureToggleStore: IFeatureToggleStore;
 | 
			
		||||
 | 
			
		||||
const featureName = 'test-tag';
 | 
			
		||||
const tag = { type: 'simple', value: 'test' };
 | 
			
		||||
const TESTUSERID = 3333;
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
    db = await dbInit('feature_tag_store_serial', getLogger);
 | 
			
		||||
@ -31,12 +32,13 @@ afterEach(async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should tag feature', async () => {
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag);
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag, TESTUSERID);
 | 
			
		||||
    const featureTags = await featureTagStore.getAllTagsForFeature(featureName);
 | 
			
		||||
    const featureTag = await featureTagStore.get({
 | 
			
		||||
        featureName,
 | 
			
		||||
        tagType: tag.type,
 | 
			
		||||
        tagValue: tag.value,
 | 
			
		||||
        createdByUserId: TESTUSERID,
 | 
			
		||||
    });
 | 
			
		||||
    expect(featureTags).toHaveLength(1);
 | 
			
		||||
    expect(featureTags[0]).toStrictEqual(tag);
 | 
			
		||||
@ -45,39 +47,41 @@ test('should tag feature', async () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('feature tag exists', async () => {
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag);
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag, TESTUSERID);
 | 
			
		||||
    const exists = await featureTagStore.exists({
 | 
			
		||||
        featureName,
 | 
			
		||||
        tagType: tag.type,
 | 
			
		||||
        tagValue: tag.value,
 | 
			
		||||
        createdByUserId: TESTUSERID,
 | 
			
		||||
    });
 | 
			
		||||
    expect(exists).toBe(true);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should delete feature tag', async () => {
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag);
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag, TESTUSERID);
 | 
			
		||||
    await featureTagStore.delete({
 | 
			
		||||
        featureName,
 | 
			
		||||
        tagType: tag.type,
 | 
			
		||||
        tagValue: tag.value,
 | 
			
		||||
        createdByUserId: TESTUSERID,
 | 
			
		||||
    });
 | 
			
		||||
    const featureTags = await featureTagStore.getAllTagsForFeature(featureName);
 | 
			
		||||
    expect(featureTags).toHaveLength(0);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('should untag feature', async () => {
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag);
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag, TESTUSERID);
 | 
			
		||||
    await featureTagStore.untagFeature(featureName, tag);
 | 
			
		||||
    const featureTags = await featureTagStore.getAllTagsForFeature(featureName);
 | 
			
		||||
    expect(featureTags).toHaveLength(0);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('get all feature tags', async () => {
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag);
 | 
			
		||||
    await featureTagStore.tagFeature(featureName, tag, TESTUSERID);
 | 
			
		||||
    await featureToggleStore.create('default', {
 | 
			
		||||
        name: 'some-other-toggle',
 | 
			
		||||
    });
 | 
			
		||||
    await featureTagStore.tagFeature('some-other-toggle', tag);
 | 
			
		||||
    await featureTagStore.tagFeature('some-other-toggle', tag, TESTUSERID);
 | 
			
		||||
    const all = await featureTagStore.getAll();
 | 
			
		||||
    expect(all).toHaveLength(2);
 | 
			
		||||
});
 | 
			
		||||
@ -87,11 +91,17 @@ test('should import feature tags', async () => {
 | 
			
		||||
        name: 'some-other-toggle-import',
 | 
			
		||||
    });
 | 
			
		||||
    await featureTagStore.tagFeatures([
 | 
			
		||||
        { featureName, tagType: tag.type, tagValue: tag.value },
 | 
			
		||||
        {
 | 
			
		||||
            featureName,
 | 
			
		||||
            tagType: tag.type,
 | 
			
		||||
            tagValue: tag.value,
 | 
			
		||||
            createdByUserId: TESTUSERID,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            featureName: 'some-other-toggle-import',
 | 
			
		||||
            tagType: tag.type,
 | 
			
		||||
            tagValue: tag.value,
 | 
			
		||||
            createdByUserId: TESTUSERID,
 | 
			
		||||
        },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								src/test/fixtures/fake-feature-tag-store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								src/test/fixtures/fake-feature-tag-store.ts
									
									
									
									
										vendored
									
									
								
							@ -46,11 +46,16 @@ export default class FakeFeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
        return this.featureTags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async tagFeature(featureName: string, tag: ITag): Promise<ITag> {
 | 
			
		||||
    async tagFeature(
 | 
			
		||||
        featureName: string,
 | 
			
		||||
        tag: ITag,
 | 
			
		||||
        createdByUserId: number,
 | 
			
		||||
    ): Promise<ITag> {
 | 
			
		||||
        this.featureTags.push({
 | 
			
		||||
            featureName,
 | 
			
		||||
            tagType: tag.type,
 | 
			
		||||
            tagValue: tag.value,
 | 
			
		||||
            createdByUserId,
 | 
			
		||||
        });
 | 
			
		||||
        return Promise.resolve(tag);
 | 
			
		||||
    }
 | 
			
		||||
@ -67,10 +72,14 @@ export default class FakeFeatureTagStore implements IFeatureTagStore {
 | 
			
		||||
    async tagFeatures(featureTags: IFeatureTag[]): Promise<IFeatureAndTag[]> {
 | 
			
		||||
        return Promise.all(
 | 
			
		||||
            featureTags.map(async (fT) => {
 | 
			
		||||
                const saved = await this.tagFeature(fT.featureName, {
 | 
			
		||||
                    value: fT.tagValue,
 | 
			
		||||
                    type: fT.tagType,
 | 
			
		||||
                });
 | 
			
		||||
                const saved = await this.tagFeature(
 | 
			
		||||
                    fT.featureName,
 | 
			
		||||
                    {
 | 
			
		||||
                        value: fT.tagValue,
 | 
			
		||||
                        type: fT.tagType,
 | 
			
		||||
                    },
 | 
			
		||||
                    fT.createdByUserId,
 | 
			
		||||
                );
 | 
			
		||||
                return {
 | 
			
		||||
                    featureName: fT.featureName,
 | 
			
		||||
                    tag: saved,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user