Skip to content

Commit

Permalink
feat: new project overview backend (#5344)
Browse files Browse the repository at this point in the history
Adding new project overview endpoint and deprecating the old one.
The new one has extra info about feature types, but does not have
features anymore, because features are coming from search endpoint.
  • Loading branch information
sjaanus authored Dec 1, 2023
1 parent 9f3648d commit 63f6af0
Show file tree
Hide file tree
Showing 21 changed files with 532 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
FeatureToggleDTO,
IFeatureEnvironment,
IFeatureToggleQuery,
IFeatureTypeCount,
IVariant,
} from 'lib/types/model';
import { LastSeenInput } from '../../../services/client-metrics/last-seen/last-seen-service';
import { EnvironmentFeatureNames } from '../feature-toggle-store';
import { FeatureConfigurationClient } from '../types/feature-toggle-strategies-store-type';
import { IFeatureProjectUserParams } from '../feature-toggle-controller';

export default class FakeFeatureToggleStore implements IFeatureToggleStore {
features: FeatureToggle[] = [];
Expand Down Expand Up @@ -314,4 +316,10 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
isPotentiallyStale(): Promise<boolean> {
throw new Error('Method not implemented.');
}

getFeatureTypeCounts(
params: IFeatureProjectUserParams,
): Promise<IFeatureTypeCount[]> {
throw new Error('Method not implemented.');
}
}
7 changes: 7 additions & 0 deletions src/lib/features/feature-toggle/feature-toggle-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
IFeatureToggleClientStore,
IFeatureToggleQuery,
IFeatureToggleStore,
IFeatureTypeCount,
IFlagResolver,
IProjectStore,
ISegment,
Expand Down Expand Up @@ -1087,6 +1088,12 @@ class FeatureToggleService {
return this.featureStrategiesStore.getFeatureOverview(params);
}

async getFeatureTypeCounts(
params: IFeatureProjectUserParams,
): Promise<IFeatureTypeCount[]> {
return this.featureToggleStore.getFeatureTypeCounts(params);
}

async getFeatureToggle(
featureName: string,
): Promise<FeatureToggleWithEnvironment> {
Expand Down
61 changes: 46 additions & 15 deletions src/lib/features/feature-toggle/feature-toggle-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import { DEFAULT_ENV } from '../../../lib/util';

import { FeatureToggleListBuilder } from './query-builders/feature-toggle-list-builder';
import { FeatureConfigurationClient } from './types/feature-toggle-strategies-store-type';
import { IFlagResolver } from '../../../lib/types';
import { IFeatureTypeCount, IFlagResolver } from '../../../lib/types';
import { FeatureToggleRowConverter } from './converters/feature-toggle-row-converter';
import { IFeatureProjectUserParams } from './feature-toggle-controller';

export type EnvironmentFeatureNames = { [key: string]: string[] };
export type EnvironmentFeatureNames = {
[key: string]: string[];
};

const FEATURE_COLUMNS = [
'name',
Expand Down Expand Up @@ -278,6 +281,27 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
);
}

async getFeatureTypeCounts({
projectId,
archived,
}: IFeatureProjectUserParams): Promise<IFeatureTypeCount[]> {
const query = this.db<FeaturesTable>(TABLE)
.select('type')
.count('type')
.groupBy('type');

query.where({
project: projectId,
archived,
});

const result = await query;
return result.map((row) => ({
type: row.type,
count: Number(row.count),
}));
}

async getAllByNames(names: string[]): Promise<FeatureToggle[]> {
const query = this.db<FeaturesTable>(TABLE).orderBy('name', 'asc');
query.whereIn('name', names);
Expand Down Expand Up @@ -596,27 +620,34 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
.update('variants', variantsString)
.where('feature_name', featureName);

const row = await this.db(TABLE)
.select(FEATURE_COLUMNS)
.where({ project: project, name: featureName });
const row = await this.db(TABLE).select(FEATURE_COLUMNS).where({
project: project,
name: featureName,
});

const toggle = this.rowToFeature(row[0]);
toggle.variants = newVariants;

return toggle;
}

async updatePotentiallyStaleFeatures(
currentTime?: string,
): Promise<{ name: string; potentiallyStale: boolean; project: string }[]> {
async updatePotentiallyStaleFeatures(currentTime?: string): Promise<
{
name: string;
potentiallyStale: boolean;
project: string;
}[]
> {
const query = this.db.raw(
`SELECT name, project, potentially_stale, (? > (features.created_at + ((
SELECT feature_types.lifetime_days
FROM feature_types
WHERE feature_types.id = features.type
) * INTERVAL '1 day'))) as current_staleness
FROM features
WHERE NOT stale = true`,
`SELECT name,
project,
potentially_stale,
(? > (features.created_at + ((SELECT feature_types.lifetime_days
FROM feature_types
WHERE feature_types.id = features.type) *
INTERVAL '1 day'))) as current_staleness
FROM features
WHERE NOT stale = true`,
[currentTime || this.db.fn.now()],
);

Expand Down
35 changes: 32 additions & 3 deletions src/lib/features/feature-toggle/types/feature-toggle-store-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import {
FeatureToggle,
FeatureToggleDTO,
IFeatureToggleQuery,
IFeatureTypeCount,
IVariant,
} from '../../../types/model';
import { Store } from '../../../types/stores/store';
import { LastSeenInput } from '../../../services/client-metrics/last-seen/last-seen-service';
import { FeatureConfigurationClient } from './feature-toggle-strategies-store-type';
import { IFeatureProjectUserParams } from '../feature-toggle-controller';

export interface IFeatureToggleStoreQuery {
archived: boolean;
Expand All @@ -17,40 +19,62 @@ export interface IFeatureToggleStoreQuery {

export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
count(query?: Partial<IFeatureToggleStoreQuery>): Promise<number>;

setLastSeen(data: LastSeenInput[]): Promise<void>;

getProjectId(name: string): Promise<string | undefined>;

create(project: string, data: FeatureToggleDTO): Promise<FeatureToggle>;

update(project: string, data: FeatureToggleDTO): Promise<FeatureToggle>;

archive(featureName: string): Promise<FeatureToggle>;

batchArchive(featureNames: string[]): Promise<FeatureToggle[]>;

batchStale(
featureNames: string[],
stale: boolean,
): Promise<FeatureToggle[]>;

batchDelete(featureNames: string[]): Promise<void>;

batchRevive(featureNames: string[]): Promise<FeatureToggle[]>;

revive(featureName: string): Promise<FeatureToggle>;

getAll(query?: Partial<IFeatureToggleStoreQuery>): Promise<FeatureToggle[]>;

getAllByNames(names: string[]): Promise<FeatureToggle[]>;

getFeatureToggleList(
featureQuery?: IFeatureToggleQuery,
userId?: number,
archived?: boolean,
): Promise<FeatureToggle[]>;

getArchivedFeatures(project?: string): Promise<FeatureToggle[]>;

getPlaygroundFeatures(
featureQuery?: IFeatureToggleQuery,
): Promise<FeatureConfigurationClient[]>;

countByDate(queryModifiers: {
archived?: boolean;
project?: string;
date?: string;
range?: string[];
dateAccessor: string;
}): Promise<number>;
updatePotentiallyStaleFeatures(
currentTime?: string,
): Promise<{ name: string; potentiallyStale: boolean; project: string }[]>;

updatePotentiallyStaleFeatures(currentTime?: string): Promise<
{
name: string;
potentiallyStale: boolean;
project: string;
}[]
>;

isPotentiallyStale(featureName: string): Promise<boolean>;

/**
Expand All @@ -59,6 +83,7 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
* TODO: Remove before release 5.0
*/
getVariants(featureName: string): Promise<IVariant[]>;

/**
* TODO: Remove before release 5.0
* @deprecated - Variants should be fetched from FeatureEnvironmentStore (since variants are now; since 4.18, connected to environments)
Expand All @@ -73,4 +98,8 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
): Promise<FeatureToggle>;

disableAllEnvironmentsForFeatures(names: string[]): Promise<void>;

getFeatureTypeCounts(
params: IFeatureProjectUserParams,
): Promise<IFeatureTypeCount[]>;
}
6 changes: 5 additions & 1 deletion src/lib/openapi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import {
profileSchema,
projectEnvironmentSchema,
projectOverviewSchema,
deprecatedProjectOverviewSchema,
projectSchema,
projectsSchema,
projectStatsSchema,
Expand Down Expand Up @@ -167,6 +168,7 @@ import {
dependenciesExistSchema,
validateArchiveFeaturesSchema,
searchFeaturesSchema,
featureTypeCountSchema,
} from './spec';
import { IServerOption } from '../types';
import { mapValues, omitKeys } from '../util';
Expand Down Expand Up @@ -375,7 +377,7 @@ export const schemas: UnleashSchemas = {
variantFlagSchema,
variantsSchema,
versionSchema,
projectOverviewSchema,
deprecatedProjectOverviewSchema,
importTogglesSchema,
importTogglesValidateSchema,
importTogglesValidateItemSchema,
Expand All @@ -397,6 +399,8 @@ export const schemas: UnleashSchemas = {
dependenciesExistSchema,
validateArchiveFeaturesSchema,
searchFeaturesSchema,
featureTypeCountSchema,
projectOverviewSchema,
};

// Remove JSONSchema keys that would result in an invalid OpenAPI spec.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`deprecatedProjectOverviewSchema 1`] = `
{
"errors": [
{
"instancePath": "",
"keyword": "required",
"message": "must have required property 'version'",
"params": {
"missingProperty": "version",
},
"schemaPath": "#/required",
},
],
"schema": "#/components/schemas/deprecatedProjectOverviewSchema",
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`featureTypeCountSchema 1`] = `
{
"errors": [
{
"instancePath": "",
"keyword": "required",
"message": "must have required property 'type'",
"params": {
"missingProperty": "type",
},
"schemaPath": "#/required",
},
],
"schema": "#/components/schemas/featureTypeCountSchema",
}
`;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`updateProjectEnterpriseSettings schema 1`] = `
exports[`projectOverviewSchema 1`] = `
{
"errors": [
{
Expand Down
28 changes: 28 additions & 0 deletions src/lib/openapi/spec/deprecated-project-overview-schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DeprecatedProjectOverviewSchema } from './deprecated-project-overview-schema';
import { validateSchema } from '../validate';

test('deprecatedProjectOverviewSchema', () => {
const data: DeprecatedProjectOverviewSchema = {
name: 'project',
version: 3,
featureNaming: {
description: 'naming description',
example: 'a',
pattern: '[aZ]',
},
};

expect(
validateSchema(
'#/components/schemas/deprecatedProjectOverviewSchema',
data,
),
).toBeUndefined();

expect(
validateSchema(
'#/components/schemas/deprecatedProjectOverviewSchema',
{},
),
).toMatchSnapshot();
});
Loading

0 comments on commit 63f6af0

Please sign in to comment.