Skip to content

[MERGE] main -> staging post AA Hotfix release #110

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

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:

- run: npm ci --legacy-peer-deps

- run: npx run lint-build
- run: npm run lint-build

- name: Bring up DB Docker Container
run: |
Expand Down
7 changes: 7 additions & 0 deletions apps/entity-service/src/entities/dto/entity-query.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ export class EntityQueryDto extends IntersectionType(QuerySort, NumberPage) {
@IsOptional()
search?: string;

@ApiProperty({
required: false,
description: "Search query used for filtering selectable options in autocomplete fields."
})
@IsOptional()
searchFilter?: string;

@ApiProperty({ required: false })
@IsOptional()
country?: string;
Expand Down
30 changes: 21 additions & 9 deletions apps/entity-service/src/entities/processors/project.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class ProjectProcessor extends EntityProcessor<Project, ProjectLightDto,
where: { uuid },
include: [
{ association: "framework" },
{ association: "organisation", attributes: ["name"] },
{ association: "organisation", attributes: ["uuid", "name"] },
{
association: "application",
include: [{ association: "fundingProgramme" }, { association: "formSubmissions" }]
Expand All @@ -48,10 +48,8 @@ export class ProjectProcessor extends EntityProcessor<Project, ProjectLightDto,
}

async findMany(query: EntityQueryDto, userId: number, permissions: string[]) {
const builder = await this.entitiesService.buildQuery(Project, query, [
{ association: "organisation", attributes: ["name"] },
{ association: "framework" }
]);
const associations = [{ association: "organisation", attributes: ["uuid", "name"] }, { association: "framework" }];
const builder = await this.entitiesService.buildQuery(Project, query, associations);

if (query.sort != null) {
if (["name", "plantingStartDate", "country"].includes(query.sort.field)) {
Expand All @@ -74,11 +72,25 @@ export class ProjectProcessor extends EntityProcessor<Project, ProjectLightDto,
builder.where({ id: { [Op.in]: ProjectUser.projectsManageSubquery(userId) } });
}

for (const term of ["country", "status", "updateRequestStatus", "frameworkKey"]) {
if (query[term] != null) builder.where({ [term]: query[term] });
const associationFieldMap = {
projectUuid: "uuid",
organisationUuid: "$organisation.uuid$"
};

for (const term of [
"country",
"status",
"updateRequestStatus",
"frameworkKey",
"projectUuid",
"organisationUuid"
]) {
const field = associationFieldMap[term] ?? term;
if (query[term] != null) builder.withAssociations(associations).where({ [field]: query[term] });
}
if (query.search != null) {
builder.where({ name: { [Op.like]: `%${query.search}%` } });

if (query.search != null || query.searchFilter != null) {
builder.where({ name: { [Op.like]: `%${query.search ?? query.searchFilter}%` } });
}

return { models: await builder.execute(), paginationTotal: await builder.paginationTotal() };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,6 @@ describe("SiteProcessor", () => {
await expectSites(sites, {}, { permissions: ["framework-hbf", "framework-terrafund"] });
});

it("searches", async () => {
const s1 = await SiteFactory.create({ name: "Foo Bar" });
const s2 = await SiteFactory.create({ name: "Baz Foo" });
await SiteFactory.createMany(3);

await expectSites([s1, s2], { search: "foo" });
});

it("filters", async () => {
const p1 = await ProjectFactory.create();
const p2 = await ProjectFactory.create();
Expand Down
36 changes: 24 additions & 12 deletions apps/entity-service/src/entities/processors/site.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,13 @@ export class SiteProcessor extends EntityProcessor<Site, SiteLightDto, SiteFullD
async findMany(query: EntityQueryDto, userId?: number, permissions?: string[]): Promise<PaginatedResult<Site>> {
const projectAssociation: Includeable = {
association: "project",
attributes: ["uuid", "name"]
attributes: ["uuid", "name"],
include: [{ association: "organisation", attributes: ["uuid", "name"] }]
};
const frameworkAssociation: Includeable = {
association: "framework",
attributes: ["name"]
};
if (query.search != null) {
// This is they way that sequelize supports for searching in a joined table
projectAssociation.where = { name: { [Op.like]: `%${query.search}%` } };
// This is to ensure that the project is not required to be joined (simulating an OR)
projectAssociation.required = false;
}

const associations = [projectAssociation, frameworkAssociation];

Expand Down Expand Up @@ -81,13 +76,30 @@ export class SiteProcessor extends EntityProcessor<Site, SiteLightDto, SiteFullD
});
}

for (const term of ["status", "updateRequestStatus", "frameworkKey"]) {
if (query[term] != null) builder.where({ [term]: query[term] });
const associationFieldMap = {
organisationUuid: "$project.organisation.uuid$",
country: "$project.country$",
projectUuid: "$project.uuid$"
};

for (const term of [
"status",
"updateRequestStatus",
"frameworkKey",
"projectUuid",
"organisationUuid",
"country"
]) {
const field = associationFieldMap[term] ?? term;
if (query[term] != null) builder.withAssociations(associations).where({ [field]: query[term] });
}

if (query.search != null) {
builder.where({
name: { [Op.like]: `%${query.search}%` }
if (query.search != null || query.searchFilter != null) {
builder.withAssociations([projectAssociation]).where({
[Op.or]: [
{ name: { [Op.like]: `%${query.search ?? query.searchFilter}%` } },
{ "$project.name$": { [Op.like]: `%${query.search}%` } }
]
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IncludeOptions, literal, Op, Sequelize } from "sequelize";
import { IncludeOptions, literal, Op } from "sequelize";
import {
IndicatorOutputFieldMonitoring,
IndicatorOutputHectares,
Expand Down
17 changes: 17 additions & 0 deletions libs/database/src/lib/util/paginated-query.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ export class PaginatedQueryBuilder<T extends Model<T>> {
return this;
}

/**
* Temporarily adds associations to the query, enabling filtering on related model fields.
* This method is a temporary workaround and will be removed once the related
* association changes currently in staging are deployed to production.
*/

withAssociations(include: Includeable[]) {
if (!include || include.length === 0) {
throw new BadRequestException("requires at least one association in include");
}

this.findOptions.include = include;
this.pageTotalFindOptions.include = include;

return this;
}

where(options: WhereOptions, filterable?: Filterable) {
if (filterable == null) {
this.pageTotalFindOptions.where = combineWheresWithAnd(this.pageTotalFindOptions.where ?? {}, options);
Expand Down
Loading