Skip to content

Commit 60d0b99

Browse files
authored
Merge pull request #61 from saleor/fix/category-duplicate-deployment
Partial fix for Bug #8: Categories Show Duplicate Deployment
2 parents ab641d7 + 1f08510 commit 60d0b99

File tree

7 files changed

+106
-5
lines changed

7 files changed

+106
-5
lines changed

.changeset/smooth-mangos-happen.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@saleor/configurator": patch
3+
---
4+
5+
Merge category introspection with attribute normalization
6+
7+
Combined the category introspection feature (Bug #8) with the attribute reference normalization feature (Bug #4). The configuration service now properly maps categories from remote Saleor instances while also preventing duplicate attribute definition errors during deployment.

src/core/diff/comparators/category-comparator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ export class CategoryComparator extends BaseEntityComparator<
6666
}
6767

6868
/**
69-
* Gets the name of a category entity
69+
* Gets the unique identifier of a category entity (using slug)
70+
* Categories in Saleor are uniquely identified by slug, not name
7071
*/
7172
protected getEntityName(entity: CategoryEntity) {
72-
return entity.name;
73+
return entity.slug || entity.name; // Fallback to name if slug is not available
7374
}
7475

7576
/**

src/modules/category/category-service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import type { Category, CategoryOperations } from "./repository";
55
export class CategoryService {
66
constructor(private repository: CategoryOperations) {}
77

8+
async getAllCategories() {
9+
logger.debug("Getting all categories for introspection");
10+
return this.repository.getAllCategories();
11+
}
12+
813
private async getExistingCategory(name: string) {
914
return this.repository.getCategoryByName(name);
1015
}

src/modules/category/repository.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,37 @@ const getCategoryByNameQuery = graphql(`
4949
}
5050
`);
5151

52+
const getAllCategoriesQuery = graphql(`
53+
query GetAllCategories {
54+
categories(first: 100) {
55+
edges {
56+
node {
57+
id
58+
name
59+
slug
60+
children(first: 100) {
61+
edges {
62+
node {
63+
id
64+
name
65+
slug
66+
}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
`);
74+
5275
export type Category = NonNullable<
5376
NonNullable<ResultOf<typeof getCategoryByNameQuery>["categories"]>["edges"]
5477
>[number]["node"];
5578

5679
export interface CategoryOperations {
5780
createCategory(input: CategoryInput, parentId?: string): Promise<Category>;
5881
getCategoryByName(name: string): Promise<Category | null | undefined>;
82+
getAllCategories(): Promise<Category[]>;
5983
}
6084

6185
export class CategoryRepository implements CategoryOperations {
@@ -100,4 +124,25 @@ export class CategoryRepository implements CategoryOperations {
100124

101125
return exactMatch?.node;
102126
}
127+
128+
async getAllCategories(): Promise<Category[]> {
129+
logger.debug("Fetching all categories");
130+
131+
const result = await this.client.query(getAllCategoriesQuery, {});
132+
133+
if (!result.data?.categories?.edges) {
134+
logger.debug("No categories found");
135+
return [];
136+
}
137+
138+
const categories = result.data.categories.edges
139+
.map((edge) => edge.node)
140+
.filter((node): node is Category => node !== null);
141+
142+
logger.debug("Retrieved categories", {
143+
count: categories.length,
144+
});
145+
146+
return categories;
147+
}
103148
}

src/modules/config/config-service.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ describe("ConfigurationService", () => {
119119
channels: [],
120120
productTypes: [],
121121
pageTypes: [],
122+
categories: [],
122123
});
123124
});
124125

src/modules/config/config-service.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,23 @@ export class ConfigurationService {
178178
});
179179
}
180180

181+
private mapCategories(
182+
rawCategories: RawSaleorConfig["categories"]
183+
): SaleorConfig["categories"] {
184+
if (!rawCategories?.edges) {
185+
return [];
186+
}
187+
188+
return rawCategories.edges
189+
.map((edge) => edge.node)
190+
.filter((node) => node !== null)
191+
.map((category) => ({
192+
name: category.name,
193+
slug: category.slug,
194+
// TODO: Handle subcategories/children if needed
195+
}));
196+
}
197+
181198
/**
182199
* Normalizes attribute references by converting shared attributes to reference syntax
183200
* This prevents duplicate attribute definition errors during deployment
@@ -210,7 +227,10 @@ export class ConfigurationService {
210227
if (!attributeUsage.has(attr.name)) {
211228
attributeUsage.set(attr.name, { attribute: attr, locations: [] });
212229
}
213-
attributeUsage.get(attr.name)!.locations.push(location);
230+
const usage = attributeUsage.get(attr.name);
231+
if (usage) {
232+
usage.locations.push(location);
233+
}
214234
});
215235
});
216236

@@ -221,7 +241,10 @@ export class ConfigurationService {
221241
if (!attributeUsage.has(attr.name)) {
222242
attributeUsage.set(attr.name, { attribute: attr, locations: [] });
223243
}
224-
attributeUsage.get(attr.name)!.locations.push(location);
244+
const usage = attributeUsage.get(attr.name);
245+
if (usage) {
246+
usage.locations.push(location);
247+
}
225248
});
226249
});
227250

@@ -238,6 +261,7 @@ export class ConfigurationService {
238261
...config,
239262
productTypes: productTypesWithFullAttrs?.map((productType) => ({
240263
...productType,
264+
isShippingRequired: productType.isShippingRequired ?? false,
241265
productAttributes: this.convertToReferences(
242266
productType.productAttributes || [],
243267
sharedAttributes
@@ -283,7 +307,7 @@ export class ConfigurationService {
283307
channels: this.mapChannels(rawConfig.channels),
284308
productTypes: this.mapProductTypes(rawConfig.productTypes),
285309
pageTypes: this.mapPageTypes(rawConfig.pageTypes),
286-
// TODO: add categories
310+
categories: this.mapCategories(rawConfig.categories),
287311
};
288312

289313
// Normalize attribute references to prevent duplication errors during deployment

src/modules/config/repository.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,24 @@ const getConfigQuery = graphql(`
104104
}
105105
}
106106
}
107+
categories(first: 100) {
108+
edges {
109+
node {
110+
id
111+
name
112+
slug
113+
children(first: 100) {
114+
edges {
115+
node {
116+
id
117+
name
118+
slug
119+
}
120+
}
121+
}
122+
}
123+
}
124+
}
107125
}
108126
`);
109127

0 commit comments

Comments
 (0)