-
Notifications
You must be signed in to change notification settings - Fork 11
feat: add global filtering that applies aross all queries #546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { createServiceFactory } from '@ngneat/spectator/jest'; | ||
import { SPAN_SCOPE } from '../span'; | ||
import { GraphQlFieldFilter } from './field/graphql-field-filter'; | ||
import { GlobalGraphQlFilterService } from './global-graphql-filter.service'; | ||
import { GraphQlOperatorType } from './graphql-filter'; | ||
|
||
describe('Global graphql filter serivce', () => { | ||
const firstFilter = new GraphQlFieldFilter('first', GraphQlOperatorType.Equals, 'first'); | ||
const secondFilter = new GraphQlFieldFilter('second', GraphQlOperatorType.Equals, 'second'); | ||
const thirdFilter = new GraphQlFieldFilter('third', GraphQlOperatorType.Equals, 'third'); | ||
const createService = createServiceFactory({ | ||
service: GlobalGraphQlFilterService | ||
}); | ||
test('provides no filters by default', () => { | ||
const spectator = createService(); | ||
|
||
expect(spectator.service.getGlobalFilters(SPAN_SCOPE)).toEqual([]); | ||
}); | ||
|
||
test('provides filters from multiple matching rules', () => { | ||
const spectator = createService(); | ||
spectator.service.setGlobalFilterRule(Symbol('first'), { | ||
filtersForScope: scope => (scope === SPAN_SCOPE ? [firstFilter] : []) | ||
}); | ||
spectator.service.setGlobalFilterRule(Symbol('second'), { | ||
filtersForScope: scope => (scope === 'fake' ? [secondFilter] : []) | ||
}); | ||
spectator.service.setGlobalFilterRule(Symbol('third'), { | ||
filtersForScope: scope => (scope === SPAN_SCOPE ? [thirdFilter] : []) | ||
}); | ||
expect(spectator.service.getGlobalFilters(SPAN_SCOPE)).toEqual([firstFilter, thirdFilter]); | ||
}); | ||
|
||
test('allows removing global filter rules', () => { | ||
const spectator = createService(); | ||
const filterRuleKey = Symbol('first'); | ||
spectator.service.setGlobalFilterRule(filterRuleKey, { | ||
filtersForScope: scope => (scope === SPAN_SCOPE ? [firstFilter] : []) | ||
}); | ||
|
||
expect(spectator.service.getGlobalFilters(SPAN_SCOPE)).toEqual([firstFilter]); | ||
|
||
spectator.service.clearGlobalFilterRule(filterRuleKey); | ||
expect(spectator.service.getGlobalFilters(SPAN_SCOPE)).toEqual([]); | ||
}); | ||
|
||
test('merges with local filters', () => { | ||
const spectator = createService(); | ||
spectator.service.setGlobalFilterRule(Symbol('first'), { | ||
filtersForScope: scope => (scope === SPAN_SCOPE ? [firstFilter] : []) | ||
}); | ||
|
||
expect(spectator.service.mergeGlobalFilters(SPAN_SCOPE)).toEqual([firstFilter]); | ||
|
||
expect(spectator.service.mergeGlobalFilters('fake', [secondFilter])).toEqual([secondFilter]); | ||
expect(spectator.service.mergeGlobalFilters(SPAN_SCOPE, [secondFilter])).toEqual([secondFilter, firstFilter]); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { GraphQlFilter } from './graphql-filter'; | ||
|
||
@Injectable({ providedIn: 'root' }) | ||
export class GlobalGraphQlFilterService { | ||
private readonly filterRules: Map<symbol, FilterRule> = new Map(); | ||
|
||
public getGlobalFilters(scope: string): GraphQlFilter[] { | ||
return Array.from(this.filterRules.values()).flatMap(rule => rule.filtersForScope(scope)); | ||
} | ||
|
||
public mergeGlobalFilters(scope: string, localFilters: GraphQlFilter[] = []): GraphQlFilter[] { | ||
return [...localFilters, ...this.getGlobalFilters(scope)]; | ||
} | ||
|
||
public setGlobalFilterRule(ruleKey: symbol, rule: FilterRule): void { | ||
this.filterRules.set(ruleKey, rule); | ||
} | ||
|
||
public clearGlobalFilterRule(ruleKey: symbol): void { | ||
this.filterRules.delete(ruleKey); | ||
} | ||
} | ||
|
||
interface FilterRule { | ||
filtersForScope(scope: string): GraphQlFilter[]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import { GraphQlHandlerType, GraphQlQueryHandler, GraphQlSelection } from '@hype | |
import { Observable } from 'rxjs'; | ||
import { map } from 'rxjs/operators'; | ||
import { MetadataService } from '../../../../services/metadata/metadata.service'; | ||
import { GlobalGraphQlFilterService } from '../../../model/schema/filter/global-graphql-filter.service'; | ||
import { GraphQlFilter } from '../../../model/schema/filter/graphql-filter'; | ||
import { GraphQlSortBySpecification } from '../../../model/schema/sort/graphql-sort-by-specification'; | ||
import { Specification } from '../../../model/schema/specifier/specification'; | ||
|
@@ -18,7 +19,10 @@ export class TracesGraphQlQueryHandlerService implements GraphQlQueryHandler<Gra | |
private readonly selectionBuilder: GraphQlSelectionBuilder = new GraphQlSelectionBuilder(); | ||
public readonly type: GraphQlHandlerType.Query = GraphQlHandlerType.Query; | ||
|
||
public constructor(private readonly metadataService: MetadataService) {} | ||
public constructor( | ||
private readonly metadataService: MetadataService, | ||
private readonly globalGraphQlFilterService: GlobalGraphQlFilterService | ||
) {} | ||
|
||
public matchesRequest(request: unknown): request is GraphQlTracesRequest { | ||
return ( | ||
|
@@ -37,7 +41,9 @@ export class TracesGraphQlQueryHandlerService implements GraphQlQueryHandler<Gra | |
this.argBuilder.forTimeRange(request.timeRange), | ||
...this.argBuilder.forOffset(request.offset), | ||
...this.argBuilder.forOrderBy(request.sort), | ||
...this.argBuilder.forFilters(request.filters) | ||
...this.argBuilder.forFilters( | ||
this.globalGraphQlFilterService.mergeGlobalFilters(resolveTraceType(request.traceType), request.filters) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Putting this everywhere doesn't seem too scalable and may be error prone. Can we add it in idk There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't love it, but it seems like the best place we have right now. There's no place with central knowledge of all queries, by design - and filters aren't a concept that applies to every request in the same way. QueryEventService for example pushes filters down to the data sources to apply, which is even more decentralized (and only works in the dashboard code) - the service itself doesn't know the request shapes to provide them. Open to other ideas, but I think the handlers are the narrowest (and most stable point) that still has the context on how to apply the filters without a larger redesign. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then can we do it using an abstract method which the concrete handler has to provide? I could see people easily missing to apply the right global filters in new handlers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it wouldn't work very well as an abstract method. The transformation depends on the shape of the request, the logic of the handler and the shape of the constructed query. We could make a util, but at that point we're basically wrapping There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay. I was thinking we could just define a function below. While it doesn't do much, it would just force the dev to consider the case of global filter application. Otherwise it is easy to miss. It is still a hack.
We do need a better solution for this. |
||
) | ||
], | ||
children: [ | ||
{ | ||
|
Uh oh!
There was an error while loading. Please reload this page.