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:
parent
70e7446dbe
commit
30c8b394fb
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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`,
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
`,
|
`,
|
||||||
|
Loading…
Reference in New Issue
Block a user