Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

feat: handle the includes parameter within the page resolver #552 #907

Merged
merged 8 commits into from
Jul 1, 2020
3 changes: 3 additions & 0 deletions api/shopware-6-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { EqualsAnyFilter } from '@shopware-pwa/commons/interfaces/search/SearchF
import { EqualsFilter } from '@shopware-pwa/commons/interfaces/search/SearchFilter';
import { Grouping } from '@shopware-pwa/commons/interfaces/search/Grouping';
import { GuestOrderParams } from '@shopware-pwa/commons/interfaces/request/GuestOrderParams';
import { Includes } from '@shopware-pwa/commons/interfaces/search/SearchCriteria';
import { Language } from '@shopware-pwa/commons/interfaces/models/framework/language/Language';
import { MultiFilter } from '@shopware-pwa/commons/interfaces/search/SearchFilter';
import { NavigationResponse } from '@shopware-pwa/commons/interfaces/models/content/navigation/Navigation';
Expand Down Expand Up @@ -342,6 +343,8 @@ export interface ShopwareParams {
// (undocumented)
grouping?: Grouping;
// (undocumented)
includes?: Includes;
// (undocumented)
limit?: number;
// (undocumented)
manufacturer?: string;
Expand Down
10 changes: 9 additions & 1 deletion docs/landing/concepts/cms.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,4 +444,12 @@ Finally we have our desired effect:

![preview](./../../assets/preview2.png)

And that's it. This set of rules applies for sections, blocks and elements.
And that's it. This set of rules applies for sections, blocks and elements.


## Performance
The Shopware6 API returns all data that you may need, and even more. This is why an `includes` parameter was invented - [Read more at _Includes_ chapter here](https://docs.shopware.com/en/shopware-platform-dev-en/admin-api-guide/reading-entities).

In order to make the response payload smaller, aligned with our needs - the `includes` parameter is being used.

The list of all fetched fields within the page resolver, which is responsible for getting the CMS content (but not only) can be found [here (_getPageIncludes_ method)](../../../packages/composables/src/internalHelpers/includesParameter.ts). Thanks to this mechanism - the shopware-pwa fetches only the relevant data.
9 changes: 9 additions & 0 deletions packages/commons/interfaces/search/SearchCriteria.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ export interface Grouping {
field: string;
}

/**
* @beta
*/
export interface Includes {
[key: string]: string[];
}

/**
* configutarion.displayParents: true - if you want to show all the products
* @alpha
Expand All @@ -43,6 +50,7 @@ export interface SearchCriteria {
associations?: Association[];
aggregations?: Aggregation[];
totalCountMode?: TotalCountMode;
includes?: Includes;
};
}

Expand Down Expand Up @@ -70,4 +78,5 @@ export interface ShopwareSearchParams {
grouping?: Grouping;
properties?: string | undefined | never[];
manufacturer?: string | undefined | never[];
includes?: Includes;
}
141 changes: 106 additions & 35 deletions packages/composables/__tests__/useCms.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Vue.use(VueCompositionApi);

import { useCms } from "@shopware-pwa/composables";
import * as shopwareClient from "@shopware-pwa/shopware-6-client";
import { getPageIncludes } from "../src/internalHelpers/includesParameter";

jest.mock("@shopware-pwa/shopware-6-client");
const mockedGetPage = shopwareClient as jest.Mocked<typeof shopwareClient>;
Expand Down Expand Up @@ -69,42 +70,112 @@ describe("Composables - useCms", () => {
expect(error.value).toStrictEqual({ message: "Something went wrong..." });
});

it("should performs search default pagination limit if not provided", async () => {
const { search, page } = useCms(rootContextMock);
mockedGetPage.getPage.mockResolvedValueOnce({} as any);
expect(page.value).toEqual(null);
await search("");
expect(mockedGetPage.getPage).toBeCalledWith(
"",
{
configuration: {
associations: [
{ associations: [{ name: "group" }], name: "options" },
],
},
pagination: { limit: 10 },
},
rootContextMock.$shopwareApiInstance
);
});
describe("methods", () => {
describe("search", () => {
it("should performs search default pagination limit if not provided", async () => {
const { search, page } = useCms(rootContextMock);
mockedGetPage.getPage.mockResolvedValueOnce({} as any);
expect(page.value).toEqual(null);
await search("");
expect(mockedGetPage.getPage).toBeCalledWith(
"",
{
configuration: {
associations: [
{ associations: [{ name: "group" }], name: "options" },
],
includes: getPageIncludes(),
},
pagination: { limit: 10 },
},
rootContextMock.$shopwareApiInstance
);
});

it("should perform search default pagination limit if wrong value", async () => {
const { search, page } = useCms(rootContextMock);
mockedGetPage.getPage.mockResolvedValueOnce({} as any);
expect(page.value).toEqual(null);
await search("", { pagination: "null" });
expect(mockedGetPage.getPage).toBeCalledWith(
"",
{
configuration: {
associations: [
{ associations: [{ name: "group" }], name: "options" },
],
},
pagination: { limit: 10 },
},
rootContextMock.$shopwareApiInstance
);
it("should perform search default pagination limit if wrong value", async () => {
const { search, page } = useCms(rootContextMock);
mockedGetPage.getPage.mockResolvedValueOnce({} as any);
expect(page.value).toEqual(null);
await search("", { pagination: "null" });
expect(mockedGetPage.getPage).toBeCalledWith(
"",
{
configuration: {
associations: [
{ associations: [{ name: "group" }], name: "options" },
],
includes: getPageIncludes(),
},
pagination: { limit: 10 },
},
rootContextMock.$shopwareApiInstance
);
});

it("should performs search with pagination if provided", async () => {
const { search, page } = useCms(rootContextMock);
mockedGetPage.getPage.mockResolvedValueOnce({} as any);
expect(page.value).toEqual(null);
await search("", { pagination: { limit: 50 } });
expect(mockedGetPage.getPage).toBeCalledWith(
"",
{
configuration: {
associations: [
{ associations: [{ name: "group" }], name: "options" },
],
includes: getPageIncludes(),
},
pagination: { limit: 50 },
},
rootContextMock.$shopwareApiInstance
);
});

it("should provide default includes if not provided, but configuration exist", async () => {
const { search, page } = useCms(rootContextMock);
mockedGetPage.getPage.mockResolvedValueOnce({} as any);
expect(page.value).toEqual(null);
await search("", {
configuration: {},
});
expect(mockedGetPage.getPage).toBeCalledWith(
"",
{
configuration: {
associations: [
{ associations: [{ name: "group" }], name: "options" },
],
includes: getPageIncludes(),
},
pagination: { limit: 10 },
},
rootContextMock.$shopwareApiInstance
);
});

it("should invoke search with custom includes", async () => {
const { search, page } = useCms(rootContextMock);
mockedGetPage.getPage.mockResolvedValueOnce({} as any);
expect(page.value).toEqual(null);
await search("", {
configuration: { includes: { product: ["name"] } },
});
expect(mockedGetPage.getPage).toBeCalledWith(
"",
{
configuration: {
associations: [
{ associations: [{ name: "group" }], name: "options" },
],
includes: { product: ["name"] },
},
pagination: { limit: 10 },
},
rootContextMock.$shopwareApiInstance
);
});
});
});

it("should return activeCategoryId if it's included within the page object", async () => {
Expand Down
8 changes: 6 additions & 2 deletions packages/composables/src/hooks/useCms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { parseUrlQuery } from "@shopware-pwa/helpers";
import { ClientApiError } from "@shopware-pwa/commons/interfaces/errors/ApiError";
import { getApplicationContext } from "@shopware-pwa/composables";
import { ApplicationVueContext } from "../../appContext";
import { getPageIncludes } from "../../internalHelpers/includesParameter";

/**
* @alpha
Expand Down Expand Up @@ -36,12 +37,10 @@ export const useCms = (rootContext: ApplicationVueContext): any => {
if (!searchCriteria.configuration) searchCriteria.configuration = {};
// Temp solution for consistant page size
// @TODO: https://github.com/DivanteLtd/shopware-pwa/issues/739
/* istanbul ignore else */
if (!searchCriteria.pagination || searchCriteria.pagination === "null") {
searchCriteria.pagination = {};
}

/* istanbul ignore else */
if (!searchCriteria.pagination.limit) {
searchCriteria.pagination.limit = 10;
}
Expand All @@ -57,6 +56,11 @@ export const useCms = (rootContext: ApplicationVueContext): any => {
],
});

if (!searchCriteria.configuration.includes) {
// performance enhancement
searchCriteria.configuration.includes = getPageIncludes();
}

try {
const result = await getPage(path, searchCriteria, apiInstance);
vuexStore.commit("SET_PAGE", result);
Expand Down
59 changes: 59 additions & 0 deletions packages/composables/src/internalHelpers/includesParameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* A collection of performance set of includes parameters
*/

const PRODUCT = [
"name",
"ratingAverage",
"calculatedPrice",
"calculatedPrices",
"cover",
"id",
"translated",
"options",
];
const PRODUCT_MEDIA = ["media"];
const PRODUCT_GROUP = ["id", "name", "options", "translated"];
const PRODUCT_GROUP_OPTION = ["name", "id", "group", "translated"];
const PRODUCT_CALCULATED_PRICE = ["unitPrice"];
const MEDIA = ["url"];
const CMS_PAGE = ["id", "name", "sections", "type", "config"];
const CMS_PAGE_SECTION = [
"id",
"backgroundMedia",
"blocks",
"type",
"sizingMode",
];
const CMS_PAGE_BLOCK = [
"slots",
"type",
"id",
"backgroundMedia",
"sectionPosition",
];
const CMS_PAGE_SLOT = [
"id",
"type",
"slot",
"blockId",
"data",
"backgroundMediaMode",
"backgroundMedia",
];

/**
* Parameters for page resolver - aligned with getPage method of @shopware-pwa/shopware-6-client
*/
export const getPageIncludes = () => ({
cms_page_slot: CMS_PAGE_SLOT,
cms_page_block: CMS_PAGE_BLOCK,
cms_page_section: CMS_PAGE_SECTION,
cms_page: CMS_PAGE,
product: PRODUCT,
product_media: PRODUCT_MEDIA,
media: MEDIA,
calculated_price: PRODUCT_CALCULATED_PRICE,
product_group_option: PRODUCT_GROUP_OPTION,
product_group: PRODUCT_GROUP,
});
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,34 @@ describe("SearchConverter - convertSearchCriteria", () => {
});
expect(result).toHaveProperty("associations");
});
});
describe("includes", () => {
it("should return includes object", () => {
const result = convertSearchCriteria({
searchCriteria: {
configuration: {
includes: {},
},
},
config,
});
expect(result?.includes).toEqual({});
});

it("should have product property within includes params", () => {
const result = convertSearchCriteria({
searchCriteria: {
configuration: {
includes: {
product: ["name", "id"],
},
},
},
config,
});
expect(result).toHaveProperty("includes");
expect(result.includes).toHaveProperty("product");
});

it("should return multiple associations", () => {
const result = convertSearchCriteria({
Expand Down
6 changes: 6 additions & 0 deletions packages/shopware-6-client/src/helpers/searchConverter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
SearchCriteria,
ShopwareSearchParams,
Includes,
} from "@shopware-pwa/commons/interfaces/search/SearchCriteria";
import {
NotFilter,
Expand Down Expand Up @@ -43,6 +44,7 @@ export interface ShopwareParams {
grouping?: Grouping;
properties?: string; // store-api filters
manufacturer?: string; // store-api filters
includes?: Includes;
}

/**
Expand Down Expand Up @@ -138,6 +140,10 @@ export const convertSearchCriteria = ({
params.grouping = configuration.grouping;
}

if (configuration?.includes) {
params.includes = configuration.includes;
}

// fulltext query (works for every entity so can be global)
if (term) {
params.term = term;
Expand Down