Skip to content

Commit

Permalink
feat: implement createdByUserId for all features (#5725)
Browse files Browse the repository at this point in the history
## About the changes

Implements setting values on the created_by_user_id column on the
features table in the db
  • Loading branch information
daveleek authored Dec 22, 2023
1 parent 9d8487a commit 9ac1070
Show file tree
Hide file tree
Showing 26 changed files with 213 additions and 36 deletions.
3 changes: 3 additions & 0 deletions src/lib/db/feature-strategy-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ test('returns 0 if no custom strategies are in use', async () => {

featureToggleStore.create('default', {
name: 'test-toggle-2',
createdByUserId: 9999,
});

strategyStore.createStrategy({
Expand Down Expand Up @@ -68,6 +69,7 @@ test('counts custom strategies in use', async () => {

await featureToggleStore.create('default', {
name: 'test-toggle',
createdByUserId: 9999,
});

await strategyStore.createStrategy({
Expand Down Expand Up @@ -112,6 +114,7 @@ test('increment sort order on each new insert', async () => {

await featureToggleStore.create('default', {
name: 'test-toggle-increment',
createdByUserId: 9999,
});

const { id: firstId } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ beforeAll(async () => {

await db.stores.featureToggleStore.create('default', {
name: FLAG_NAME,
createdByUserId: 9999,
});
});

Expand Down
2 changes: 2 additions & 0 deletions src/lib/features/feature-search/feature.search.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ test('should search features by project with operators', async () => {

await db.stores.featureToggleStore.create('project_b', {
name: 'my_feature_b',
createdByUserId: 9999,
});

await db.stores.projectStore.create({
Expand All @@ -671,6 +672,7 @@ test('should search features by project with operators', async () => {

await db.stores.featureToggleStore.create('project_c', {
name: 'my_feature_c',
createdByUserId: 9999,
});

const { body } = await searchFeatures({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
IVariant,
} from 'lib/types/model';
import { LastSeenInput } from '../../../services/client-metrics/last-seen/last-seen-service';
import { EnvironmentFeatureNames } from '../feature-toggle-store';
import {
EnvironmentFeatureNames,
FeatureToggleInsert,
} from '../feature-toggle-store';
import { FeatureConfigurationClient } from '../types/feature-toggle-strategies-store-type';
import { IFeatureProjectUserParams } from '../feature-toggle-controller';

Expand Down Expand Up @@ -104,7 +107,10 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
};
}

async create(project: string, data: FeatureToggle): Promise<FeatureToggle> {
async create(
project: string,
data: FeatureToggleInsert,
): Promise<FeatureToggle> {
const inserted: FeatureToggle = { ...data, project };
this.features.push(inserted);
return inserted;
Expand Down
9 changes: 7 additions & 2 deletions src/lib/features/feature-toggle/feature-toggle-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1150,9 +1150,14 @@ class FeatureToggleService {
if (exists) {
let featureData;
if (isValidated) {
featureData = value;
featureData = { createdByUserId, ...value };
} else {
featureData = await featureMetadataSchema.validateAsync(value);
const validated =
await featureMetadataSchema.validateAsync(value);
featureData = {
createdByUserId,
...validated,
};
}
const featureName = featureData.name;
const createdToggle = await this.featureToggleStore.create(
Expand Down
33 changes: 29 additions & 4 deletions src/lib/features/feature-toggle/feature-toggle-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export interface FeaturesTable {
impression_data: boolean;
archived?: boolean;
archived_at?: Date;
created_by_user_id?: number;
}

export interface FeatureToggleInsert
extends Omit<FeatureToggleDTO, 'createdByUserId'> {
createdByUserId: number;
}

interface VariantDTO {
Expand Down Expand Up @@ -457,7 +463,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
return sortedVariants;
}

dtoToRow(project: string, data: FeatureToggleDTO): FeaturesTable {
insertToRow(project: string, data: FeatureToggleInsert): FeaturesTable {
const row = {
name: data.name,
description: data.description,
Expand All @@ -467,20 +473,39 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
stale: data.stale,
created_at: data.createdAt,
impression_data: data.impressionData,
created_by_user_id: data.createdByUserId,
};
if (!row.created_at) {
delete row.created_at;
}

return row;
}

async create(
dtoToUpdateRow(
project: string,
data: FeatureToggleDTO,
): Omit<FeaturesTable, 'created_by_user_id'> {
const row = {
name: data.name,
description: data.description,
type: data.type,
project,
archived_at: data.archived ? new Date() : null,
stale: data.stale,
impression_data: data.impressionData,
};

return row;
}

async create(
project: string,
data: FeatureToggleInsert,
): Promise<FeatureToggle> {
try {
const row = await this.db(TABLE)
.insert(this.dtoToRow(project, data))
.insert(this.insertToRow(project, data))
.returning(FEATURE_COLUMNS);

return this.rowToFeature(row[0]);
Expand All @@ -504,7 +529,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
): Promise<FeatureToggle> {
const row = await this.db(TABLE)
.where({ name: data.name })
.update(this.dtoToRow(project, data))
.update(this.dtoToUpdateRow(project, data))
.returning(FEATURE_COLUMNS);

return this.rowToFeature(row[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,17 @@ test('Should get archived toggles via project', async () => {
await db.stores.featureToggleStore.create('proj-1', {
name: 'feat-proj-1',
archived: true,
createdByUserId: 9999,
});
await db.stores.featureToggleStore.create('proj-2', {
name: 'feat-proj-2',
archived: true,
createdByUserId: 9999,
});
await db.stores.featureToggleStore.create('proj-2', {
name: 'feat-proj-2-2',
archived: true,
createdByUserId: 9999,
});

await app.request
Expand Down Expand Up @@ -151,6 +154,7 @@ test('Should disable all environments when reviving a toggle', async () => {
await db.stores.featureToggleStore.create('default', {
name: 'feat-proj-1',
archived: true,
createdByUserId: 9999,
});

await db.stores.environmentStore.create({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IFeatureToggleStore,
IProjectStore,
} from '../../../types';
import { FeatureToggleInsert } from '../feature-toggle-store';

let stores;
let db;
Expand Down Expand Up @@ -49,10 +50,11 @@ describe('potentially_stale marking', () => {
};

test('it returns an empty list if no toggles were updated', async () => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
{
name: 'feature1',
type: 'release',
createdByUserId: 9999,
},
];
await Promise.all(
Expand All @@ -68,14 +70,16 @@ describe('potentially_stale marking', () => {
});

test('it returns only updated toggles', async () => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
{
name: 'feature1',
type: 'release',
createdByUserId: 9999,
},
{
name: 'feature2',
type: 'kill-switch',
createdByUserId: 9999,
},
];
await Promise.all(
Expand All @@ -102,13 +106,13 @@ describe('potentially_stale marking', () => {
])(
'it marks toggles based on their type (days elapsed: %s)',
async (daysElapsed, expectedMarkedFeatures) => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
'release',
'experiment',
'operational',
'kill-switch',
'permission',
].map((type) => ({ name: type, type }));
].map((type) => ({ name: type, type, createdByUserId: 9999 }));
await Promise.all(
features.map((feature) =>
featureToggleStore.create('default', feature),
Expand Down Expand Up @@ -143,11 +147,12 @@ describe('potentially_stale marking', () => {
},
);
test('it does not mark toggles already flagged as stale', async () => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
{
name: 'feature1',
type: 'release',
stale: true,
createdByUserId: 9999,
},
];
await Promise.all(
Expand All @@ -163,10 +168,11 @@ describe('potentially_stale marking', () => {
});

test('it does not return toggles previously marked as potentially_stale', async () => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
{
name: 'feature1',
type: 'release',
createdByUserId: 9999,
},
];
await Promise.all(
Expand Down Expand Up @@ -197,10 +203,11 @@ describe('potentially_stale marking', () => {

describe('changing feature types', () => {
test("if a potentially stale feature changes to a type that shouldn't be stale, it's 'potentially_stale' marker is removed.", async () => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
{
name: 'feature1',
type: 'release',
createdByUserId: 9999,
},
];
await Promise.all(
Expand Down Expand Up @@ -247,10 +254,11 @@ describe('potentially_stale marking', () => {
});

test('if a fresh feature changes to a type that should be stale, it gets marked as potentially stale', async () => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
{
name: 'feature1',
type: 'kill-switch',
createdByUserId: 9999,
},
];
await Promise.all(
Expand Down Expand Up @@ -280,11 +288,12 @@ describe('potentially_stale marking', () => {
});

test('if a stale feature changes to a type that should be stale, it does not get marked as potentially stale', async () => {
const features: FeatureToggleDTO[] = [
const features: FeatureToggleInsert[] = [
{
name: 'feature1',
type: 'kill-switch',
stale: true,
createdByUserId: 9999,
},
];
await Promise.all(
Expand Down Expand Up @@ -314,9 +323,15 @@ describe('potentially_stale marking', () => {
name: 'MyProject',
description: 'MyProject',
});
await featureToggleStore.create('default', { name: 'featureA' });
await featureToggleStore.create('default', {
name: 'featureA',
createdByUserId: 9999,
});

await featureToggleStore.create('MyProject', { name: 'featureB' });
await featureToggleStore.create('MyProject', {
name: 'featureB',
createdByUserId: 9999,
});

const playgroundFeatures =
await featureToggleStore.getPlaygroundFeatures({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ beforeAll(async () => {
stores = db.stores;
featureStrategiesStore = stores.featureStrategiesStore;
featureToggleStore = stores.featureToggleStore;
await featureToggleStore.create('default', { name: featureName });
await featureToggleStore.create('default', {
name: featureName,
createdByUserId: 9999,
});
});

afterAll(async () => {
Expand Down Expand Up @@ -74,8 +77,14 @@ test('Can successfully update project for all strategies belonging to feature',
test('Can query for features with tags', async () => {
const tag = { type: 'simple', value: 'hello-tags' };
await stores.tagStore.createTag(tag);
await featureToggleStore.create('default', { name: 'to-be-tagged' });
await featureToggleStore.create('default', { name: 'not-tagged' });
await featureToggleStore.create('default', {
name: 'to-be-tagged',
createdByUserId: 9999,
});
await featureToggleStore.create('default', {
name: 'not-tagged',
createdByUserId: 9999,
});
await stores.featureTagStore.tagFeature('to-be-tagged', tag);
const features = await featureStrategiesStore.getFeatureOverview({
projectId: 'default',
Expand All @@ -87,9 +96,11 @@ test('Can query for features with tags', async () => {
test('Can query for features with namePrefix', async () => {
await featureToggleStore.create('default', {
name: 'nameprefix-to-be-hit',
createdByUserId: 9999,
});
await featureToggleStore.create('default', {
name: 'nameprefix-not-be-hit',
createdByUserId: 9999,
});
const features = await featureStrategiesStore.getFeatureOverview({
projectId: 'default',
Expand All @@ -103,12 +114,15 @@ test('Can query for features with namePrefix and tags', async () => {
await stores.tagStore.createTag(tag);
await featureToggleStore.create('default', {
name: 'to-be-tagged-nameprefix-and-tags',
createdByUserId: 9999,
});
await featureToggleStore.create('default', {
name: 'not-tagged-nameprefix-and-tags',
createdByUserId: 9999,
});
await featureToggleStore.create('default', {
name: 'tagged-but-not-hit-nameprefix-and-tags',
createdByUserId: 9999,
});
await stores.featureTagStore.tagFeature(
'to-be-tagged-nameprefix-and-tags',
Expand Down
Loading

0 comments on commit 9ac1070

Please sign in to comment.