Skip to content

Commit

Permalink
fix(api-headless-cms): filtering ref fields with null values (#4237)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunozoric authored Aug 27, 2024
1 parent 1e124e0 commit b988ac6
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 38 deletions.
2 changes: 1 addition & 1 deletion packages/api-elasticsearch/src/plugins/operator/equal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ElasticsearchQueryBuilderOperatorEqualPlugin extends ElasticsearchQ
): void {
const { value, path, basePath } = params;

if (value === null) {
if (value === null || value === undefined) {
query.must_not.push({
exists: {
field: path
Expand Down
2 changes: 1 addition & 1 deletion packages/api-elasticsearch/src/plugins/operator/not.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ElasticsearchQueryBuilderOperatorNotPlugin extends ElasticsearchQue
): void {
const { value, path, basePath } = params;

if (value === null) {
if (value === null || value === undefined) {
query.filter.push({
exists: {
field: path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { CmsEntryFilterPlugin } from "~/plugins/CmsEntryFilterPlugin";
import { parseWhereKey } from "@webiny/api-elasticsearch";

export const createRefFilterPlugin = () => {
return new CmsEntryFilterPlugin({
const plugin = new CmsEntryFilterPlugin({
fieldType: "ref",
exec: params => {
const { applyFiltering, value: values, query, field } = params;
const { applyFiltering, query, field } = params;

let values = params.value;
/**
* We must have an object when querying in the ref field.
*/
Expand All @@ -20,6 +22,12 @@ export const createRefFilterPlugin = () => {
);
}

if (values === null || values === undefined) {
values = {
entryId: null
};
}

for (const key in values) {
const { operator } = parseWhereKey(key);
const value = values[key];
Expand All @@ -37,4 +45,8 @@ export const createRefFilterPlugin = () => {
}
}
});

plugin.name = `${plugin.type}.default.ref`;

return plugin;
};
1 change: 1 addition & 0 deletions packages/api-headless-cms-ddb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.0",
"@webiny/api": "0.0.0",
"@webiny/api-headless-cms": "0.0.0",
"@webiny/aws-sdk": "0.0.0",
"@webiny/db-dynamodb": "0.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@ import {
} from "~/plugins/CmsEntryFieldFilterPlugin";
import { extractWhereParams } from "~/operations/entry/filtering/where";
import { transformValue } from "~/operations/entry/filtering/transform";
import { GenericRecord } from "@webiny/api/types";

export const createRefFilterCreate = () => {
const plugin = new CmsEntryFieldFilterPlugin({
const plugin = new CmsEntryFieldFilterPlugin<GenericRecord | null | undefined>({
fieldType: "ref",
create: params => {
const { value, valueFilterPlugins, transformValuePlugins, field } = params;
const { valueFilterPlugins, transformValuePlugins, field } = params;
let value = params.value;
if (!value) {
value = {
entryId: null
};
}
const propertyFilters = Object.keys(value);
if (propertyFilters.length === 0) {
return null;
}

const filters: CmsEntryFieldFilterPluginCreateResponse[] = [];

for (const propertyFilter of propertyFilters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import { CmsFieldFilterValueTransformPlugin } from "~/types";
* Internally we have default one + the one for the reference field - because it is actually an object when filtering.
*/

interface CmsEntryFieldFilterPluginParams {
interface CmsEntryFieldFilterPluginParams<T = any> {
fieldType: string;
create: (
params: CmsEntryFieldFilterPluginCreateParams
params: CmsEntryFieldFilterPluginCreateParams<T>
) => null | CmsEntryFieldFilterPluginCreateResponse | CmsEntryFieldFilterPluginCreateResponse[];
}

interface CmsEntryFieldFilterPluginCreateParams {
interface CmsEntryFieldFilterPluginCreateParams<T = any> {
key: string;
value: any;
value: T;
field: Field;
fields: Record<string, Field>;
operation: string;
Expand All @@ -39,21 +39,21 @@ export interface CmsEntryFieldFilterPluginCreateResponse {
transformValue: <I = any, O = any>(value: I) => O;
}

export class CmsEntryFieldFilterPlugin extends Plugin {
export class CmsEntryFieldFilterPlugin<T = any> extends Plugin {
public static override readonly type: string = "cms.dynamodb.entry.field.filter";
public static readonly ALL: string = "*";

private readonly config: CmsEntryFieldFilterPluginParams;
private readonly config: CmsEntryFieldFilterPluginParams<T>;

public readonly fieldType: string;

public constructor(config: CmsEntryFieldFilterPluginParams) {
public constructor(config: CmsEntryFieldFilterPluginParams<T>) {
super();
this.config = config;
this.fieldType = this.config.fieldType;
}

public create(params: CmsEntryFieldFilterPluginCreateParams) {
public create(params: CmsEntryFieldFilterPluginCreateParams<T>) {
return this.config.create(params);
}
}
1 change: 1 addition & 0 deletions packages/api-headless-cms-ddb/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "../../tsconfig.build.json",
"include": ["src"],
"references": [
{ "path": "../api/tsconfig.build.json" },
{ "path": "../api-headless-cms/tsconfig.build.json" },
{ "path": "../aws-sdk/tsconfig.build.json" },
{ "path": "../db-dynamodb/tsconfig.build.json" },
Expand Down
3 changes: 3 additions & 0 deletions packages/api-headless-cms-ddb/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "../../tsconfig.json",
"include": ["src", "__tests__"],
"references": [
{ "path": "../api" },
{ "path": "../api-headless-cms" },
{ "path": "../aws-sdk" },
{ "path": "../db-dynamodb" },
Expand All @@ -17,6 +18,8 @@
"paths": {
"~/*": ["./src/*"],
"~tests/*": ["./__tests__/*"],
"@webiny/api/*": ["../api/src/*"],
"@webiny/api": ["../api/src"],
"@webiny/api-headless-cms/*": ["../api-headless-cms/src/*"],
"@webiny/api-headless-cms": ["../api-headless-cms/src"],
"@webiny/aws-sdk/*": ["../aws-sdk/src/*"],
Expand Down
129 changes: 128 additions & 1 deletion packages/api-headless-cms/__tests__/contentAPI/refField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ describe("refField", () => {
return publishAuthorResponse.data.publishAuthor.data;
};

test("should create review connected to a product", async () => {
it("should create review connected to a product", async () => {
await setupContentModels(mainHandler);

const category = await createCategory();
Expand Down Expand Up @@ -441,4 +441,131 @@ describe("refField", () => {
}
});
});

it("should create a product which is not connected to category and list and filter by the category value", async () => {
await setupContentModels(mainHandler);
const { createProduct, listProducts } = useProductManageHandler({
...manageOpts
});

const [listEmptyResult] = await listProducts({
where: {
category: null
}
});
expect(listEmptyResult).toMatchObject({
data: {
listProducts: {
data: [],
error: null,
meta: {
cursor: null,
hasMoreItems: false,
totalCount: 0
}
}
}
});

const data = {
title: "Potato",
price: 100,
availableOn: "2020-12-25",
color: "white",
availableSizes: ["s", "m"],
image: "file.jpg",
category: null
};
const [createResponse] = await createProduct({
data
});

expect(createResponse).toMatchObject({
data: {
createProduct: {
data: {
id: expect.any(String),
...data,
category: null
},
error: null
}
}
});

const [listResult] = await listProducts();

expect(listResult).toMatchObject({
data: {
listProducts: {
data: [
{
id: expect.any(String),
entryId: expect.any(String),
...data,
category: null
}
],
error: null,
meta: {
cursor: null,
hasMoreItems: false,
totalCount: 1
}
}
}
});

const [whereNullResult] = await listProducts({
where: {
category: null
}
});
expect(whereNullResult).toMatchObject({
data: {
listProducts: {
data: [
{
id: expect.any(String),
entryId: expect.any(String),
...data,
category: null
}
],
error: null,
meta: {
cursor: null,
hasMoreItems: false,
totalCount: 1
}
}
}
});

const [whereUndefinedResult] = await listProducts({
where: {
category: undefined
}
});
expect(whereUndefinedResult).toMatchObject({
data: {
listProducts: {
data: [
{
id: expect.any(String),
entryId: expect.any(String),
...data,
category: null
}
],
error: null,
meta: {
cursor: null,
hasMoreItems: false,
totalCount: 1
}
}
}
});
});
});
2 changes: 1 addition & 1 deletion packages/app-file-manager/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
{ "path": "../app/tsconfig.build.json" },
{ "path": "../app-aco/tsconfig.build.json" },
{ "path": "../app-admin/tsconfig.build.json" },
{ "path": "../app-i18n/tsconfig.build.json" },
{ "path": "../app-headless-cms/tsconfig.build.json" },
{ "path": "../app-headless-cms-common/tsconfig.build.json" },
{ "path": "../app-i18n/tsconfig.build.json" },
{ "path": "../app-security/tsconfig.build.json" },
{ "path": "../app-tenancy/tsconfig.build.json" },
{ "path": "../error/tsconfig.build.json" },
Expand Down
6 changes: 3 additions & 3 deletions packages/app-file-manager/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
{ "path": "../app" },
{ "path": "../app-aco" },
{ "path": "../app-admin" },
{ "path": "../app-i18n" },
{ "path": "../app-headless-cms" },
{ "path": "../app-headless-cms-common" },
{ "path": "../app-i18n" },
{ "path": "../app-security" },
{ "path": "../app-tenancy" },
{ "path": "../error" },
Expand All @@ -32,12 +32,12 @@
"@webiny/app-aco": ["../app-aco/src"],
"@webiny/app-admin/*": ["../app-admin/src/*"],
"@webiny/app-admin": ["../app-admin/src"],
"@webiny/app-i18n/*": ["../app-i18n/src/*"],
"@webiny/app-i18n": ["../app-i18n/src"],
"@webiny/app-headless-cms/*": ["../app-headless-cms/src/*"],
"@webiny/app-headless-cms": ["../app-headless-cms/src"],
"@webiny/app-headless-cms-common/*": ["../app-headless-cms-common/src/*"],
"@webiny/app-headless-cms-common": ["../app-headless-cms-common/src"],
"@webiny/app-i18n/*": ["../app-i18n/src/*"],
"@webiny/app-i18n": ["../app-i18n/src"],
"@webiny/app-security/*": ["../app-security/src/*"],
"@webiny/app-security": ["../app-security/src"],
"@webiny/app-tenancy/*": ["../app-tenancy/src/*"],
Expand Down
12 changes: 3 additions & 9 deletions packages/cli-plugin-scaffold-extensions/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@
"extends": "../../tsconfig.json",
"include": ["src", "__tests__"],
"references": [
{
"path": "../aws-sdk"
},
{
"path": "../cli-plugin-scaffold"
},
{
"path": "../error"
}
{ "path": "../aws-sdk" },
{ "path": "../cli-plugin-scaffold" },
{ "path": "../error" }
],
"compilerOptions": {
"rootDirs": ["./src", "./__tests__"],
Expand Down
4 changes: 2 additions & 2 deletions packages/db-dynamodb/src/plugins/filters/eq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ const plugin = new ValueFilterPlugin({
});
} else if (Array.isArray(compareValue) === true) {
return compareValue.every((v: string) => {
return value === v;
return value == v;
});
}
return value === compareValue;
return value == compareValue;
}
});

Expand Down
4 changes: 2 additions & 2 deletions packages/pulumi-aws/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"include": ["src"],
"references": [
{ "path": "../aws-sdk/tsconfig.build.json" },
{ "path": "../feature-flags/tsconfig.build.json" },
{ "path": "../pulumi/tsconfig.build.json" },
{ "path": "../api-page-builder/tsconfig.build.json" }
{ "path": "../api-page-builder/tsconfig.build.json" },
{ "path": "../feature-flags/tsconfig.build.json" }
],
"compilerOptions": {
"rootDir": "./src",
Expand Down
Loading

0 comments on commit b988ac6

Please sign in to comment.