Skip to content

Commit ab4bda3

Browse files
authored
Merge pull request #3270 from SeedCompany/filter-partnership-types
2 parents 640c90c + ebd7f78 commit ab4bda3

File tree

7 files changed

+60
-5
lines changed

7 files changed

+60
-5
lines changed

src/common/filter-field.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { applyDecorators } from '@nestjs/common';
2-
import { Field } from '@nestjs/graphql';
2+
import { Field, FieldOptions } from '@nestjs/graphql';
33
import { Type } from 'class-transformer';
44
import { ValidateNested } from 'class-validator';
55
import { Constructor, HasRequiredKeys } from 'type-fest';
@@ -15,14 +15,15 @@ export const FilterField = <T extends object>(
1515
* There are no external fields on the filter, so don't expose to GQL.
1616
*/
1717
internal?: boolean;
18-
},
18+
} & Pick<FieldOptions, 'description'>,
1919
): PropertyDecorator =>
2020
applyDecorators(
2121
...(options?.internal
2222
? []
2323
: [
2424
Field(type as unknown as () => Constructor<T>, {
2525
nullable: true,
26+
description: options?.description,
2627
}),
2728
]),
2829
Type(type),

src/components/partnership/dto/list-partnership.dto.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { InputType, ObjectType } from '@nestjs/graphql';
1+
import { Field, InputType, ObjectType } from '@nestjs/graphql';
2+
import { Transform } from 'class-transformer';
3+
import { uniq } from 'lodash';
24
import {
35
FilterField,
46
ID,
57
PaginatedList,
68
SecuredList,
79
SortablePaginationInput,
810
} from '~/common';
9-
import { PartnerFilters } from '../../partner/dto';
11+
import { PartnerFilters, PartnerType } from '../../partner/dto';
1012
import { Partnership } from './partnership.dto';
1113

1214
@InputType()
@@ -15,6 +17,15 @@ export abstract class PartnershipFilters {
1517

1618
@FilterField(() => PartnerFilters)
1719
readonly partner?: PartnerFilters & {};
20+
21+
@Field(() => [PartnerType], { nullable: true })
22+
@Transform(({ value }) => {
23+
const types = uniq(value);
24+
return types.length > 0 && types.length < PartnerType.values.size
25+
? types
26+
: undefined;
27+
})
28+
readonly types?: readonly PartnerType[];
1829
}
1930

2031
@InputType()

src/components/partnership/partnership.repository.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ export class PartnershipRepository extends DtoRepository<
406406

407407
export const partnershipFilters = filter.define(() => PartnershipFilters, {
408408
projectId: filter.skip,
409+
types: filter.intersectsProp(),
409410
partner: filter.sub(() => partnerFilters)((sub) =>
410411
sub
411412
.with('node as partnership')

src/components/project/dto/list-projects.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ export abstract class ProjectFilters {
109109

110110
readonly userId?: ID;
111111

112+
@FilterField(() => PartnershipFilters, {
113+
description: 'Only projects with _any_ partnerships matching these filters',
114+
})
115+
readonly partnerships?: PartnershipFilters & {};
116+
112117
@FilterField(() => PartnershipFilters)
113118
readonly primaryPartnership?: PartnershipFilters & {};
114119

src/components/project/project-filters.query.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ export const projectFilters = filter.define(() => ProjectFilters, {
100100
.with('*')
101101
.where({ sensitivity: inArray(value) })
102102
: query,
103+
partnerships: filter.sub(() => partnershipFilters)((sub) =>
104+
sub
105+
.with('node as project')
106+
.match([
107+
node('project'),
108+
relation('out', '', 'partnership', ACTIVE),
109+
node('node', 'Partnership'),
110+
]),
111+
),
103112
primaryPartnership: filter.sub(() => partnershipFilters)((sub) =>
104113
sub
105114
.with('node as project')
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Comparator } from 'cypher-query-builder/dist/typings/clauses/where-comparators';
2+
import type { Variable } from '../query-augmentation/condition-variables';
3+
4+
export const intersects =
5+
(value: readonly string[] | Variable, paramName?: string): Comparator =>
6+
(params, name) => {
7+
const param = params.addParam(value, paramName ?? name.split('.').at(-1));
8+
return `size(apoc.coll.intersection(${name}, ${String(param)})) > 0`;
9+
};

src/core/database/query/filters.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { identity, isFunction } from 'lodash';
1616
import { AbstractClass, ConditionalKeys } from 'type-fest';
1717
import { DateTimeFilter } from '~/common';
1818
import { variable } from '../query-augmentation/condition-variables';
19+
import { intersects } from './comparators';
1920
import { collect } from './cypher-functions';
2021
import { escapeLuceneSyntax, FullTextIndex } from './full-text';
2122
import { ACTIVE } from './matching';
@@ -132,6 +133,20 @@ export const propPartialVal =
132133
return { [prop ?? key]: { value: regexp(`.*${value}.*`, true) } };
133134
};
134135

136+
export const intersectsProp =
137+
<T, K extends ConditionalKeys<Required<T>, readonly string[]>>(
138+
prop?: string,
139+
): Builder<T, K> =>
140+
({ key, value, query }) => {
141+
prop ??= key;
142+
query.match([
143+
node('node'),
144+
relation('out', '', prop, ACTIVE),
145+
node(prop, 'Property'),
146+
]);
147+
return { [`${prop}.value`]: intersects(value as readonly string[], prop) };
148+
};
149+
135150
export const stringListProp =
136151
<T, K extends ConditionalKeys<Required<T>, readonly string[]>>(
137152
prop?: string,
@@ -234,7 +249,11 @@ export const sub =
234249
sub
235250
.apply(matchSubNode)
236251
.apply(subBuilder()(value))
237-
.return(`true as ${key}FiltersApplied`),
252+
.return(`true as ${key}FiltersApplied`)
253+
// Prevent filter from increasing cardinality above 1.
254+
// This happens with `1-Many` relationships matched in `matchSubNode`.
255+
// Note they are allowed to reduce cardinality to 0.
256+
.raw('limit 1'),
238257
)
239258
.with('*');
240259

0 commit comments

Comments
 (0)