1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-18 13:48:58 +02:00

fix: decouple metrics from ofter-read table

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
andreas-unleash 2023-09-21 16:42:19 +03:00
parent 70e7446dbe
commit 30c8b394fb
No known key found for this signature in database
GPG Key ID: DB82A1577B38F66B
4 changed files with 91 additions and 49 deletions

View File

@ -349,7 +349,6 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
acc.project = r.project; acc.project = r.project;
acc.stale = r.stale; acc.stale = r.stale;
acc.lastSeenAt = r.last_seen_at; acc.lastSeenAt = r.last_seen_at;
acc.createdAt = r.created_at; acc.createdAt = r.created_at;
acc.type = r.type; acc.type = r.type;
if (!acc.environments[r.environment]) { if (!acc.environments[r.environment]) {
@ -414,6 +413,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
}); });
featureToggle.archived = archived; featureToggle.archived = archived;
return featureToggle; return featureToggle;
} }
throw new NotFoundError( throw new NotFoundError(
@ -507,15 +507,21 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
'feature_environments.feature_name', 'feature_environments.feature_name',
'features.name', 'features.name',
) )
.leftJoin( .leftJoin('feature_environments_metrics', function () {
'feature_environments_metrics', this.on(
'feature_environments_metrics.feature_name', 'feature_environments_metrics.feature_name',
'features.name', '=',
) 'feature_environments.feature_name',
).andOn(
'feature_environments_metrics.environment',
'=',
'feature_environments.environment',
);
})
.leftJoin( .leftJoin(
'environments', 'environments',
'feature_environments.environment',
'environments.name', 'environments.name',
'feature_environments.environment',
) )
.leftJoin('feature_tag as ft', 'ft.feature_name', 'features.name'); .leftJoin('feature_tag as ft', 'ft.feature_name', 'features.name');
@ -524,7 +530,6 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
'features.description as description', 'features.description as description',
'features.type as type', 'features.type as type',
'features.created_at as created_at', 'features.created_at as created_at',
'features.last_seen_at as last_seen_at',
'features.stale as stale', 'features.stale as stale',
'features.impression_data as impression_data', 'features.impression_data as impression_data',
'feature_environments.enabled as enabled', 'feature_environments.enabled as enabled',
@ -559,26 +564,36 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
if (rows.length > 0) { if (rows.length > 0) {
const overview = rows.reduce((acc, row) => { const overview = rows.reduce((acc, row) => {
if (acc[row.feature_name] !== undefined) { if (acc[row.feature_name] !== undefined) {
acc[row.feature_name].environments.push( const currentEnv =
FeatureStrategiesStore.getEnvironment(row), FeatureStrategiesStore.getEnvironment(row);
); acc[row.feature_name].environments.push(currentEnv);
if (this.isNewTag(acc[row.feature_name], row)) { if (this.isNewTag(acc[row.feature_name], row)) {
this.addTag(acc[row.feature_name], row); this.addTag(acc[row.feature_name], row);
} }
if (
new Date(currentEnv.lastSeenAt) >
new Date(acc[row.feature_name].lastSeenAt)
) {
acc[row.feature_name].lastSeenAt =
currentEnv.lastSeenAt;
}
} else { } else {
const envOverview =
FeatureStrategiesStore.getEnvironment(row);
acc[row.feature_name] = { acc[row.feature_name] = {
type: row.type, type: row.type,
description: row.description, description: row.description,
favorite: row.favorite, favorite: row.favorite,
name: row.feature_name, name: row.feature_name,
lastSeenAt: row.env_last_seen_at,
createdAt: row.created_at, createdAt: row.created_at,
lastSeenAt: row.last_seen_at,
stale: row.stale, stale: row.stale,
impressionData: row.impression_data, impressionData: row.impression_data,
environments: [ environments: [envOverview],
FeatureStrategiesStore.getEnvironment(row),
],
}; };
if (this.isNewTag(acc[row.feature_name], row)) { if (this.isNewTag(acc[row.feature_name], row)) {
this.addTag(acc[row.feature_name], row); this.addTag(acc[row.feature_name], row);
} }

View File

@ -77,10 +77,9 @@ export default class FeatureToggleClientStore
'features.project as project', 'features.project as project',
'features.stale as stale', 'features.stale as stale',
'features.impression_data as impression_data', 'features.impression_data as impression_data',
'features.last_seen_at as last_seen_at',
'features.created_at as created_at', 'features.created_at as created_at',
'fe.variants as variants', 'fe.variants as variants',
'fe.last_seen_at as env_last_seen_at', 'fem.last_seen_at as env_last_seen_at',
'fe.enabled as enabled', 'fe.enabled as enabled',
'fe.environment as environment', 'fe.environment as environment',
'fs.id as strategy_id', 'fs.id as strategy_id',
@ -96,6 +95,15 @@ export default class FeatureToggleClientStore
'df.parent as parent', 'df.parent as parent',
'df.variants as parent_variants', 'df.variants as parent_variants',
'df.enabled as parent_enabled', 'df.enabled as parent_enabled',
this.db.raw(`(
SELECT
CASE
WHEN COUNT(*) > 0 THEN MAX(last_seen_at)
ELSE NULL
END
FROM feature_environments_metrics
WHERE features.name = feature_environments_metrics.feature_name
) as last_seen_at`),
] as (string | Raw<any>)[]; ] as (string | Raw<any>)[];
let query = this.db('features') let query = this.db('features')
@ -115,13 +123,18 @@ export default class FeatureToggleClientStore
'enabled', 'enabled',
'environment', 'environment',
'variants', 'variants',
'last_seen_at',
) )
.where({ environment }) .where({ environment })
.as('fe'), .as('fe'),
'fe.feature_name', 'fe.feature_name',
'features.name', 'features.name',
) )
.leftJoin('feature_environments_metrics as fem', function () {
this.on('fem.feature_name', '=', 'features.name').andOnVal(
'fem.environment',
environment,
);
})
.leftJoin( .leftJoin(
'feature_strategy_segment as fss', 'feature_strategy_segment as fss',
`fss.feature_strategy_id`, `fss.feature_strategy_id`,

View File

@ -20,7 +20,6 @@ const FEATURE_COLUMNS = [
'stale', 'stale',
'created_at', 'created_at',
'impression_data', 'impression_data',
'last_seen_at',
'archived_at', 'archived_at',
]; ];
@ -86,7 +85,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
async get(name: string): Promise<FeatureToggle> { async get(name: string): Promise<FeatureToggle> {
return this.db return this.db
.first(FEATURE_COLUMNS) .first(this.columnsWithMetrics())
.from(TABLE) .from(TABLE)
.where({ name }) .where({ name })
.then(this.rowToFeature); .then(this.rowToFeature);
@ -101,7 +100,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
): Promise<FeatureToggle[]> { ): Promise<FeatureToggle[]> {
const { archived, ...rest } = query; const { archived, ...rest } = query;
const rows = await this.db const rows = await this.db
.select(FEATURE_COLUMNS) .select(this.columnsWithMetrics())
.from(TABLE) .from(TABLE)
.where(rest) .where(rest)
.modify(FeatureToggleStore.filterByArchived, archived); .modify(FeatureToggleStore.filterByArchived, archived);
@ -109,9 +108,11 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
} }
async getAllByNames(names: string[]): Promise<FeatureToggle[]> { async getAllByNames(names: string[]): Promise<FeatureToggle[]> {
const query = this.db<FeaturesTable>(TABLE).orderBy('name', 'asc'); const rows = await this.db
query.whereIn('name', names); .select(this.columnsWithMetrics())
const rows = await query; .from(TABLE)
.orderBy('name', 'asc')
.whereIn('name', names);
return rows.map(this.rowToFeature); return rows.map(this.rowToFeature);
} }
@ -187,18 +188,6 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
.forUpdate() .forUpdate()
.skipLocked(), .skipLocked(),
); );
// Updating the toggle's last_seen_at also for backwards compatibility
await this.db(TABLE)
.update({ last_seen_at: now })
.whereIn(
'name',
this.db(TABLE)
.select('name')
.whereIn('name', toggleNames)
.forUpdate()
.skipLocked(),
);
} }
} catch (err) { } catch (err) {
this.logger.error('Could not update lastSeen, error: ', err); this.logger.error('Could not update lastSeen, error: ', err);
@ -286,7 +275,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
try { try {
const row = await this.db(TABLE) const row = await this.db(TABLE)
.insert(this.dtoToRow(project, data)) .insert(this.dtoToRow(project, data))
.returning(FEATURE_COLUMNS); .returning(this.columnsWithMetrics());
return this.rowToFeature(row[0]); return this.rowToFeature(row[0]);
} catch (err) { } catch (err) {
@ -310,7 +299,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
const row = await this.db(TABLE) const row = await this.db(TABLE)
.where({ name: data.name }) .where({ name: data.name })
.update(this.dtoToRow(project, data)) .update(this.dtoToRow(project, data))
.returning(FEATURE_COLUMNS); .returning(this.columnsWithMetrics());
return this.rowToFeature(row[0]); return this.rowToFeature(row[0]);
} }
@ -320,7 +309,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
const row = await this.db(TABLE) const row = await this.db(TABLE)
.where({ name }) .where({ name })
.update({ archived_at: now }) .update({ archived_at: now })
.returning(FEATURE_COLUMNS); .returning(this.columnsWithMetrics());
return this.rowToFeature(row[0]); return this.rowToFeature(row[0]);
} }
@ -329,7 +318,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
const rows = await this.db(TABLE) const rows = await this.db(TABLE)
.whereIn('name', names) .whereIn('name', names)
.update({ archived_at: now }) .update({ archived_at: now })
.returning(FEATURE_COLUMNS); .returning(this.columnsWithMetrics());
return rows.map((row) => this.rowToFeature(row)); return rows.map((row) => this.rowToFeature(row));
} }
@ -340,7 +329,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
const rows = await this.db(TABLE) const rows = await this.db(TABLE)
.whereIn('name', names) .whereIn('name', names)
.update({ stale }) .update({ stale })
.returning(FEATURE_COLUMNS); .returning(this.columnsWithMetrics());
return rows.map((row) => this.rowToFeature(row)); return rows.map((row) => this.rowToFeature(row));
} }
@ -362,7 +351,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
const row = await this.db(TABLE) const row = await this.db(TABLE)
.where({ name }) .where({ name })
.update({ archived_at: null }) .update({ archived_at: null })
.returning(FEATURE_COLUMNS); .returning(this.columnsWithMetrics());
return this.rowToFeature(row[0]); return this.rowToFeature(row[0]);
} }
@ -370,7 +359,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
const rows = await this.db(TABLE) const rows = await this.db(TABLE)
.whereIn('name', names) .whereIn('name', names)
.update({ archived_at: null }) .update({ archived_at: null })
.returning(FEATURE_COLUMNS); .returning(this.columnsWithMetrics());
return rows.map((row) => this.rowToFeature(row)); return rows.map((row) => this.rowToFeature(row));
} }
@ -418,11 +407,13 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
.update('variants', variantsString) .update('variants', variantsString)
.where('feature_name', featureName); .where('feature_name', featureName);
const row = await this.db(TABLE) const rows = await this.db(TABLE)
.select(FEATURE_COLUMNS) .select(this.columnsWithMetrics())
.where({ project: project, name: featureName }); .where({ project: project, name: featureName });
const toggle = this.rowToFeature(row[0]); const toggle = this.rowToFeature(
(rows as unknown as FeaturesTable[])[0],
);
toggle.variants = newVariants; toggle.variants = newVariants;
return toggle; return toggle;
@ -483,6 +474,21 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
return result?.potentially_stale ?? false; return result?.potentially_stale ?? false;
} }
private columnsWithMetrics = () => {
return [
...FEATURE_COLUMNS,
this.db.raw(`(
SELECT
CASE
WHEN COUNT(*) > 0 THEN MAX(last_seen_at)
ELSE NULL
END
FROM feature_environments_metrics
WHERE features.name = feature_environments_metrics.feature_name
) as last_seen_at`),
];
};
} }
module.exports = FeatureToggleStore; module.exports = FeatureToggleStore;

View File

@ -34,7 +34,14 @@ exports.up = function (db, cb) {
features.impression_data as impression_data, features.impression_data as impression_data,
features.created_at as created_at, features.created_at as created_at,
features.archived_at as archived_at, features.archived_at as archived_at,
features.last_seen_at as last_seen_at, (
SELECT
CASE
WHEN COUNT(*) > 0 THEN MAX(last_seen_at)
END
FROM feature_environments_metrics
WHERE features.name = feature_environments_metrics.feature_name
) as last_seen_at,
feature_environments_metrics.last_seen_at as env_last_seen_at, feature_environments_metrics.last_seen_at as env_last_seen_at,
feature_environments.enabled as enabled, feature_environments.enabled as enabled,
feature_environments.environment as environment, feature_environments.environment as environment,
@ -53,9 +60,10 @@ exports.up = function (db, cb) {
feature_strategies.variants as strategy_variants feature_strategies.variants as strategy_variants
FROM features FROM features
LEFT JOIN feature_environments ON feature_environments.feature_name = features.name LEFT JOIN feature_environments ON feature_environments.feature_name = features.name
LEFT JOIN feature_environments_metrics ON feature_environments_metrics.feature_name = features.name
LEFT JOIN feature_strategies ON feature_strategies.feature_name = feature_environments.feature_name LEFT JOIN feature_strategies ON feature_strategies.feature_name = feature_environments.feature_name
and feature_strategies.environment = feature_environments.environment AND feature_strategies.environment = feature_environments.environment
LEFT JOIN feature_environments_metrics ON feature_environments_metrics.feature_name = feature_environments.feature_name
AND feature_environments_metrics.environment = feature_environments.environment
LEFT JOIN environments ON feature_environments.environment = environments.name LEFT JOIN environments ON feature_environments.environment = environments.name
LEFT JOIN feature_strategy_segment as fss ON fss.feature_strategy_id = feature_strategies.id; LEFT JOIN feature_strategy_segment as fss ON fss.feature_strategy_id = feature_strategies.id;
`, `,