Skip to content
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

[WIP] feat(facet-service): Ensure FacetValue retrieval is channel specific … #3216

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,6 @@
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^45.0.0",
"eslint-plugin-prefer-arrow": "^1.2.3"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
73 changes: 73 additions & 0 deletions packages/core/e2e/facet.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
import { RequestContext } from '../src/api/common/request-context';
import { FacetService } from '../src/service/services/facet.service';

import { FACET_VALUE_FRAGMENT } from './graphql/fragments';
import * as Codegen from './graphql/generated-e2e-admin-types';
Expand Down Expand Up @@ -38,13 +40,16 @@ describe('Facet resolver', () => {

let brandFacet: FacetWithValuesFragment;
let speakerTypeFacet: FacetWithValuesFragment;
let facetService: FacetService;

beforeAll(async () => {
await server.init({
initialData,
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
customerCount: 1,
});
facetService = server.app.get(FacetService);
expect(facetService).toBeDefined();
await adminClient.asSuperAdmin();
}, TEST_SETUP_TIMEOUT_MS);

Expand Down Expand Up @@ -148,6 +153,7 @@ describe('Facet resolver', () => {
const result = await adminClient.query<Codegen.GetFacetListQuery>(GET_FACET_LIST);

const { items } = result.facets;

expect(items.length).toBe(2);
expect(items[0].name).toBe('category');
expect(items[1].name).toBe('Speaker Category');
Expand Down Expand Up @@ -462,6 +468,21 @@ describe('Facet resolver', () => {
let secondChannel: ChannelFragment;
let createdFacet: Codegen.CreateFacetMutation['createFacet'];

function createFacetValueWithCode(code: string) {
return adminClient.query<
Codegen.CreateFacetValuesMutation,
Codegen.CreateFacetValuesMutationVariables
>(CREATE_FACET_VALUES, {
input: [
{
code,
facetId: 'T_1',
translations: [{ languageCode: LanguageCode.en, name: `Test facet value - ${code}` }],
},
],
});
}

beforeAll(async () => {
const { createChannel } = await adminClient.query<
Codegen.CreateChannelMutation,
Expand Down Expand Up @@ -757,6 +778,39 @@ describe('Facet resolver', () => {
await adminClient.query<Codegen.GetFacetListSimpleQuery>(GET_FACET_LIST_SIMPLE);
expect(after.items).toEqual([{ id: 'T_4', name: 'Channel Facet' }]);
});

it('findByCode retrieves channel specific FacetValues', async () => {
const ctx1 = RequestContext.deserialize({
_channel: { code: E2E_DEFAULT_CHANNEL_TOKEN },
_languageCode: LanguageCode.en,
} as any);
const ctx2 = RequestContext.deserialize({
_channel: { code: SECOND_CHANNEL_TOKEN },
_languageCode: LanguageCode.en,
} as any);

adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
const { createFacetValues: facetValues1 } = await createFacetValueWithCode('iphones');
const fv1 = facetValues1[0].code;

adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
const { createFacetValues: facetValues2 } = await createFacetValueWithCode('ipads');
const fv2 = facetValues2[0].code;

expect(fv1).toBe('iphones');
expect(fv2).toBe('ipads');

const facet = await facetService.findOne(ctx1, 1);
expect(facet).toBeDefined();

const facet1 = await facetService.findByCode(ctx1, facet!.code, LanguageCode.en);
expect(facet1?.values.find(v => v.code === fv1)).toBeDefined();
expect(facet1?.values.find(v => v.code === fv2)).not.toBeDefined();

const facet2 = await facetService.findByCode(ctx2, facet!.code, LanguageCode.en);
expect(facet2?.values.find(v => v.code === fv1)).not.toBeDefined();
expect(facet2?.values.find(v => v.code === fv2)).toBeDefined();
});
});

// https://github.com/vendure-ecommerce/vendure/issues/715
Expand Down Expand Up @@ -818,6 +872,25 @@ describe('Facet resolver', () => {
});
});

export const GET_FACET_BY_CODE = gql`
query GetFacetWithValueList($id: ID!, $options: FacetValueListOptions) {
facet(id: $id) {
id
languageCode
isPrivate
code
name
valueList(options: $options) {
items {
...FacetValue
}
totalItems
}
}
}
${FACET_VALUE_FRAGMENT}
`;

export const GET_FACET_WITH_VALUE_LIST = gql`
query GetFacetWithValueList($id: ID!, $options: FacetValueListOptions) {
facet(id: $id) {
Expand Down
88 changes: 71 additions & 17 deletions packages/core/src/service/services/facet.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,16 @@ export class FacetService {
): Promise<PaginatedList<Translated<Facet>>> {
return this.listQueryBuilder
.build(Facet, options, {
relations: relations ?? ['values', 'values.facet', 'channels'],
relations: relations?.filter(r => !['values', 'values.facet', 'channels'].includes(r)) ?? [],
ctx,
channelId: ctx.channelId,
entityAlias: 'facet',
})
.leftJoinAndSelect('facet.values', 'facet_value')
.leftJoinAndSelect('facet_value.facet', 'facet')
.leftJoin('facet_value.channels', 'value_channel')
.andWhere('value_channel.channelId = :channelId', {
channelId: ctx.channelId,
})
.getManyAndCount()
.then(([facets, totalItems]) => {
Expand All @@ -85,15 +92,29 @@ export class FacetService {
facetId: ID,
relations?: RelationPaths<Facet>,
): Promise<Translated<Facet> | undefined> {
return this.connection
.findOneInChannel(ctx, Facet, facetId, ctx.channelId, {
relations: relations ?? ['values', 'values.facet', 'channels'],
return this.listQueryBuilder
.build(Facet, undefined, {
relations: relations?.filter(r => !['values', 'values.facet', 'channels'].includes(r)) ?? [],
ctx,
channelId: ctx.channelId,
entityAlias: 'facet',
where: {
id: facetId,
},
})
.then(
facet =>
(facet && this.translator.translate(facet, ctx, ['values', ['values', 'facet']])) ??
undefined,
);
.leftJoinAndSelect('facet.values', 'facet_value')
.leftJoinAndSelect('facet_value.facet', 'facet')
.leftJoin('facet_value.channels', 'value_channel')
.andWhere('value_channel.channelId = :channelId', {
channelId: ctx.channelId,
})
.getMany()
.then(facets => {
const items = facets.map(facet =>
this.translator.translate(facet, ctx, ['values', ['values', 'facet']]),
);
return items[0];
});
}

/**
Expand All @@ -110,7 +131,6 @@ export class FacetService {
facetCodeOrLang: string | LanguageCode,
lang?: LanguageCode,
): Promise<Translated<Facet> | undefined> {
const relations = ['values', 'values.facet'];
const [repository, facetCode, languageCode] =
ctxOrFacetCode instanceof RequestContext
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand All @@ -122,13 +142,24 @@ export class FacetService {
];

// TODO: Implement usage of channelLanguageCode
return repository
.findOne({
where: {
code: facetCode,
},
relations,
})
const qb = repository
.createQueryBuilder('facet')
.leftJoinAndSelect('facet.values', 'facet_value')
.leftJoinAndSelect('facet_value.facet', 'value_facet')
.where('facet.code = :code', { code: facetCode });

if (ctxOrFacetCode instanceof RequestContext) {
qb.leftJoin('facet.channels', 'channel')
.leftJoin('facet_value.channels', 'value_channel')
.andWhere('channel.channelId = :channelId', {
channelId: ctxOrFacetCode.channelId,
})
.andWhere('value_channel.channelId = :channelId', {
channelId: ctxOrFacetCode.channelId,
});
}
return qb
.getOne()
.then(
facet =>
(facet && translateDeep(facet, languageCode, ['values', ['values', 'facet']])) ??
Expand All @@ -153,6 +184,29 @@ export class FacetService {
}
}

/**
* @description
* Returns the Facet which contains the given FacetValue id filtered by channelId.
*/
async findByFacetValueIdList(ctx: RequestContext, id: ID): Promise<Translated<Facet> | undefined> {
const facet = await this.connection
.getRepository(ctx, Facet)
.createQueryBuilder('facet')
.leftJoin('facet.channels', 'channel', 'channel.channelId = :channelId', {
channelId: ctx.channelId,
})
.leftJoinAndSelect('facet.translations', 'translations')
.leftJoin('facet.values', 'facet_value')
.leftJoin('facet_value.channels', 'value_channel', 'value_channel.channelId = :channelId', {
channelId: ctx.channelId,
})
.where('facet_value.id = :id', { id })
.getOne();
if (facet) {
return this.translator.translate(facet, ctx);
}
}

async create(ctx: RequestContext, input: CreateFacetInput): Promise<Translated<Facet>> {
const facet = await this.translatableSaver.create({
ctx,
Expand Down
Loading