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