mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
fix: metadata filter support for admin/features
Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
ba21adf3ae
commit
bc3cf81e94
@ -86,16 +86,26 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
||||
async getAll(
|
||||
query: {
|
||||
archived?: boolean;
|
||||
project?: string;
|
||||
project?: string | string[];
|
||||
stale?: boolean;
|
||||
tag?: string[][];
|
||||
namePrefix?: string;
|
||||
} = { archived: false },
|
||||
): Promise<FeatureToggle[]> {
|
||||
const { archived, ...rest } = query;
|
||||
const rows = await this.db
|
||||
const { archived, tag, project, namePrefix } = query;
|
||||
let queryBuilder = this.db(TABLE)
|
||||
.select(FEATURE_COLUMNS)
|
||||
.from(TABLE)
|
||||
.where(rest)
|
||||
.modify(FeatureToggleStore.filterByArchived, archived);
|
||||
.modify(FeatureToggleStore.filterByArchived, archived)
|
||||
.modify(FeatureToggleStore.filterByProject, project)
|
||||
.modify(FeatureToggleStore.filterByNamePrefix, namePrefix);
|
||||
if (tag) {
|
||||
const tagQuery = this.db
|
||||
.from('feature_tag')
|
||||
.select('feature_name')
|
||||
.whereIn(['tag_type', 'tag_value'], tag);
|
||||
queryBuilder = queryBuilder.whereIn('features.name', tagQuery);
|
||||
}
|
||||
const rows = await queryBuilder;
|
||||
return rows.map(this.rowToFeature);
|
||||
}
|
||||
|
||||
@ -152,6 +162,44 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
||||
: queryBuilder.whereNull('archived_at');
|
||||
};
|
||||
|
||||
static filterByTags: Knex.QueryCallbackWithArgs = (
|
||||
queryBuilder: Knex.QueryBuilder,
|
||||
tag?: string[][],
|
||||
) => {
|
||||
if (tag && tag.length > 0) {
|
||||
const tagQuery = queryBuilder
|
||||
.from('feature_tag')
|
||||
.select('feature_name')
|
||||
.whereIn(['tag_type', 'tag_value'], tag);
|
||||
return queryBuilder.whereIn('feature.name', tagQuery);
|
||||
}
|
||||
return queryBuilder;
|
||||
};
|
||||
|
||||
static filterByProject: Knex.QueryCallbackWithArgs = (
|
||||
queryBuilder: Knex.QueryBuilder,
|
||||
project?: string | string[],
|
||||
) => {
|
||||
if (project) {
|
||||
if (Array.isArray(project) && project.length > 0) {
|
||||
return queryBuilder.whereIn('features.project', project);
|
||||
} else {
|
||||
return queryBuilder.where({ project });
|
||||
}
|
||||
}
|
||||
return queryBuilder;
|
||||
};
|
||||
|
||||
static filterByNamePrefix: Knex.QueryCallbackWithArgs = (
|
||||
queryBuilder: Knex.QueryBuilder,
|
||||
namePrefix?: string,
|
||||
) => {
|
||||
if (namePrefix) {
|
||||
return queryBuilder.whereILike('name', `${namePrefix}%`);
|
||||
}
|
||||
return queryBuilder;
|
||||
};
|
||||
|
||||
rowToFeature(row: FeaturesTable): FeatureToggle {
|
||||
if (!row) {
|
||||
throw new NotFoundError('No feature toggle found');
|
||||
|
@ -177,7 +177,11 @@ class FeatureController extends Controller {
|
||||
req: Request,
|
||||
res: Response<FeaturesSchema>,
|
||||
): Promise<void> {
|
||||
const features = await this.service.getMetadataForAllFeatures(false);
|
||||
const query = await this.prepQuery(req.query);
|
||||
const features = await this.service.getMetadataForAllFeatures(
|
||||
false,
|
||||
query,
|
||||
);
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
|
@ -988,8 +988,9 @@ class FeatureToggleService {
|
||||
|
||||
async getMetadataForAllFeatures(
|
||||
archived: boolean,
|
||||
query?: Partial<IFeatureToggleQuery>,
|
||||
): Promise<FeatureToggle[]> {
|
||||
return this.featureToggleStore.getAll({ archived });
|
||||
return this.featureToggleStore.getAll({ ...query, archived });
|
||||
}
|
||||
|
||||
async getMetadataForAllFeaturesByProjectId(
|
||||
|
@ -3,8 +3,10 @@ import { Store } from './store';
|
||||
|
||||
export interface IFeatureToggleQuery {
|
||||
archived: boolean;
|
||||
project: string;
|
||||
project: string | string[];
|
||||
namePrefix?: string;
|
||||
stale: boolean;
|
||||
tag?: string[][];
|
||||
}
|
||||
|
||||
export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
|
||||
|
@ -180,8 +180,8 @@ afterAll(async () => {
|
||||
test('returns list of feature toggles', async () =>
|
||||
app.request
|
||||
.get('/api/admin/features')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
expect(res.body.features).toHaveLength(4);
|
||||
}));
|
||||
@ -628,8 +628,8 @@ test('Can get features tagged by tag', async () => {
|
||||
.expect(201);
|
||||
return app.request
|
||||
.get(`/api/admin/features?tag=${tag.type}:${tag.value}`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
expect(res.body.features).toHaveLength(1);
|
||||
expect(res.body.features[0].name).toBe(feature1Name);
|
||||
@ -665,8 +665,8 @@ test('Can query for multiple tags using OR', async () => {
|
||||
.get(
|
||||
`/api/admin/features?tag[]=${tag.type}:${tag.value}&tag[]=${tag2.type}:${tag2.value}`,
|
||||
)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
expect(res.body.features).toHaveLength(2);
|
||||
expect(res.body.features.some((f) => f.name === feature1Name)).toBe(
|
||||
@ -715,8 +715,8 @@ test('Querying with multiple filters ANDs the filters', async () => {
|
||||
.expect(201);
|
||||
await app.request
|
||||
.get(`/api/admin/features?tag=${tag.type}:${tag.value}`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => expect(res.body.features).toHaveLength(2));
|
||||
await app.request
|
||||
.get(`/api/admin/features?namePrefix=test&tag=${tag.type}:${tag.value}`)
|
||||
|
@ -26,10 +26,8 @@ const fetchSegments = (): Promise<ISegment[]> => {
|
||||
};
|
||||
|
||||
const fetchFeatures = (): Promise<IFeatureToggleClient[]> => {
|
||||
return app.request
|
||||
.get(FEATURES_ADMIN_BASE_PATH)
|
||||
.expect(200)
|
||||
.then((res) => res.body.features);
|
||||
//@ts-expect-error
|
||||
return app.services.featureToggleService.getFeatureToggles({}, false);
|
||||
};
|
||||
|
||||
const fetchClientFeatures = (): Promise<IFeatureToggleClient[]> => {
|
||||
|
Loading…
Reference in New Issue
Block a user