Skip to content

Commit

Permalink
Cherry pick #6883 (#6888)
Browse files Browse the repository at this point in the history
Cherry pick  #6883
  • Loading branch information
sjaanus committed Apr 18, 2024
1 parent e4ad98c commit a59d179
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 32 deletions.
37 changes: 7 additions & 30 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@
"name": "unleash-server",
"description": "Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.",
"version": "5.11.2",
"keywords": [
"unleash",
"feature toggle",
"feature",
"toggle"
],
"keywords": ["unleash", "feature toggle", "feature", "toggle"],
"files": [
"dist",
"docs",
Expand Down Expand Up @@ -80,23 +75,11 @@
"testTimeout": 10000,
"globalSetup": "./scripts/jest-setup.js",
"transform": {
"^.+\\.tsx?$": [
"@swc/jest"
]
"^.+\\.tsx?$": ["@swc/jest"]
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"testPathIgnorePatterns": [
"/dist/",
"/node_modules/",
"/frontend/"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json"
],
"testPathIgnorePatterns": ["/dist/", "/node_modules/", "/frontend/"],
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/dist/",
Expand Down Expand Up @@ -234,14 +217,8 @@
"tough-cookie": "4.1.3"
},
"lint-staged": {
"*.{js,ts}": [
"biome check --apply --no-errors-on-unmatched"
],
"*.{jsx,tsx}": [
"biome check --apply --no-errors-on-unmatched"
],
"*.json": [
"biome format --write --no-errors-on-unmatched"
]
"*.{js,ts}": ["biome check --apply --no-errors-on-unmatched"],
"*.{jsx,tsx}": ["biome check --apply --no-errors-on-unmatched"],
"*.json": ["biome format --write --no-errors-on-unmatched"]
}
}
1 change: 1 addition & 0 deletions src/lib/__snapshots__/create-config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ exports[`should create default config 1`] = `
"experiments": {
"adminTokenKillSwitch": false,
"anonymiseEventLog": false,
"applicationOverviewNewQuery": false,
"automatedActions": false,
"bearerTokenMiddleware": false,
"caseInsensitiveInOperators": false,
Expand Down
165 changes: 164 additions & 1 deletion src/lib/db/client-applications-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import type { Logger, LogProvider } from '../logger';
import type { Db } from './db';
import type { IApplicationOverview } from '../features/metrics/instance/models';
import { applySearchFilters } from '../features/feature-search/search-utils';
import type { IFlagResolver } from '../types';
import metricsHelper from '../util/metrics-helper';
import { DB_TIME } from '../metric-events';

const COLUMNS = [
'app_name',
Expand Down Expand Up @@ -125,9 +128,24 @@ export default class ClientApplicationsStore

private logger: Logger;

constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
private timer: Function;

private flagResolver: IFlagResolver;

constructor(
db: Db,
eventBus: EventEmitter,
getLogger: LogProvider,
flagResolver: IFlagResolver,
) {
this.db = db;
this.logger = getLogger('client-applications-store.ts');
this.flagResolver = flagResolver;
this.timer = (action: string) =>
metricsHelper.wrapTimer(eventBus, DB_TIME, {
store: 'client-applications',
action,
});
}

async upsert(details: Partial<IClientApplication>): Promise<void> {
Expand Down Expand Up @@ -291,6 +309,60 @@ export default class ClientApplicationsStore
async getApplicationOverview(
appName: string,
): Promise<IApplicationOverview> {
if (!this.flagResolver.isEnabled('applicationOverviewNewQuery')) {
return this.getOldApplicationOverview(appName);
}
const stopTimer = this.timer('getApplicationOverview');
const query = this.db
.with('metrics', (qb) => {
qb.select([
'cme.app_name',
'cme.environment',
'f.project',
this.db.raw(
'array_agg(DISTINCT cme.feature_name) as features',
),
])
.from('client_metrics_env as cme')
.leftJoin('features as f', 'f.name', 'cme.feature_name')
.groupBy('cme.app_name', 'cme.environment', 'f.project');
})
.select([
'm.project',
'm.environment',
'm.features',
'ci.instance_id',
'ci.sdk_version',
'ci.last_seen',
'a.strategies',
])
.from({ a: 'client_applications' })
.leftJoin('metrics as m', 'm.app_name', 'a.app_name')
.leftJoin('client_instances as ci', function () {
this.on('ci.app_name', '=', 'm.app_name').andOn(
'ci.environment',
'=',
'm.environment',
);
})
.where('a.app_name', appName)
.orderBy('m.environment', 'asc');
const rows = await query;
stopTimer();
if (!rows.length) {
throw new NotFoundError(`Could not find appName=${appName}`);
}
const existingStrategies: string[] = await this.db
.select('name')
.from('strategies')
.pluck('name');
return this.mapApplicationOverviewData(rows, existingStrategies);
}

async getOldApplicationOverview(
appName: string,
): Promise<IApplicationOverview> {
const stopTimer = this.timer('getApplicationOverviewOld');
const query = this.db
.with('metrics', (qb) => {
qb.distinct(
Expand Down Expand Up @@ -321,6 +393,7 @@ export default class ClientApplicationsStore
.where('a.app_name', appName)
.orderBy('cme.environment', 'asc');
const rows = await query;
stopTimer();
if (!rows.length) {
throw new NotFoundError(`Could not find appName=${appName}`);
}
Expand All @@ -334,6 +407,96 @@ export default class ClientApplicationsStore
mapApplicationOverviewData(
rows: any[],
existingStrategies: string[],
): IApplicationOverview {
if (!this.flagResolver.isEnabled('applicationOverviewNewQuery')) {
return this.mapOldApplicationOverviewData(rows, existingStrategies);
}
const featureCount = new Set(rows.flatMap((row) => row.features)).size;
const missingStrategies: Set<string> = new Set();

const environments = rows.reduce((acc, row) => {
const {
environment,
instance_id,
sdk_version,
last_seen,
project,
features,
strategies,
} = row;

if (!environment) return acc;

strategies.forEach((strategy) => {
if (
!DEPRECATED_STRATEGIES.includes(strategy) &&
!existingStrategies.includes(strategy)
) {
missingStrategies.add(strategy);
}
});

const featuresNotMappedToProject = !project;

let env = acc.find((e) => e.name === environment);
if (!env) {
env = {
name: environment,
instanceCount: instance_id ? 1 : 0,
sdks: sdk_version ? [sdk_version] : [],
lastSeen: last_seen,
uniqueInstanceIds: new Set(
instance_id ? [instance_id] : [],
),
issues: {
missingFeatures: featuresNotMappedToProject
? features
: [],
},
};
acc.push(env);
} else {
if (instance_id) {
env.uniqueInstanceIds.add(instance_id);
env.instanceCount = env.uniqueInstanceIds.size;
}
if (featuresNotMappedToProject) {
env.issues.missingFeatures = features;
}
if (sdk_version && !env.sdks.includes(sdk_version)) {
env.sdks.push(sdk_version);
}
if (new Date(last_seen) > new Date(env.lastSeen)) {
env.lastSeen = last_seen;
}
}

return acc;
}, []);
environments.forEach((env) => {
delete env.uniqueInstanceIds;
env.sdks.sort();
});

return {
projects: [
...new Set(
rows
.filter((row) => row.project != null)
.map((row) => row.project),
),
],
featureCount,
environments,
issues: {
missingStrategies: [...missingStrategies],
},
};
}

private mapOldApplicationOverviewData(
rows: any[],
existingStrategies: string[],
): IApplicationOverview {
const featureCount = new Set(rows.map((row) => row.feature_name)).size;
const missingStrategies: Set<string> = new Set();
Expand Down
1 change: 1 addition & 0 deletions src/lib/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const createStores = (
db,
eventBus,
getLogger,
flagResolver,
),
clientInstanceStore: new ClientInstanceStore(db, eventBus, getLogger),
clientMetricsStoreV2: new ClientMetricsStoreV2(
Expand Down
7 changes: 6 additions & 1 deletion src/lib/types/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export type IFlagKey =
| 'variantDependencies'
| 'disableShowContextFieldSelectionValues'
| 'bearerTokenMiddleware'
| 'projectOverviewRefactorFeedback';
| 'projectOverviewRefactorFeedback'
| 'applicationOverviewNewQuery';

export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;

Expand Down Expand Up @@ -288,6 +289,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_PROJECT_OVERVIEW_REFACTOR_FEEDBACK,
false,
),
applicationOverviewNewQuery: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_APPLICATION_OVERVIEW_NEW_QUERY,
false,
),
};

export const defaultExperimentalOptions: IExperimentalOptions = {
Expand Down
1 change: 1 addition & 0 deletions src/server-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ process.nextTick(async () => {
disableShowContextFieldSelectionValues: false,
variantDependencies: true,
projectOverviewRefactorFeedback: true,
applicationOverviewNewQuery: true,
},
},
authentication: {
Expand Down
1 change: 1 addition & 0 deletions src/test/e2e/api/admin/applications.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ beforeAll(async () => {
experimental: {
flags: {
strictSchemaValidation: true,
applicationOverviewNewQuery: true,
},
},
},
Expand Down

0 comments on commit a59d179

Please sign in to comment.