Skip to content

Commit

Permalink
Merge pull request #4 from vtex-apps/export-product
Browse files Browse the repository at this point in the history
Export product
  • Loading branch information
cesarocampov authored Apr 23, 2021
2 parents ba0e42a + a94c956 commit c2b3413
Show file tree
Hide file tree
Showing 27 changed files with 700 additions and 128 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Export all `Categories` translations in batch
- Export `Products` for a given category in batch - Limit of 1.600 products
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ There are two different pages for `Category` and `Product` translations. Both au
From this binding list, the first one is always the `X-Vtex-Tenant` or the default language, for this option the details cannot be translated, to modify these values, the changes should be made inside your store's catalog.
For all the others, it's possible to edit the content by category and by product.

It's also possible to export all current translations for `Categories`. Inside a binding different than the `X-Vtex-Tenant`, a button called `export` allows user to download the translations for that binding.
It's also possible to export all current translations for `Categories` and `Products`. Inside a binding different than the `X-Vtex-Tenant`, a button called `export` allows user to download the translations for that binding. Curently, it's only possible to export 1.600 products for a given category every 3 minutes.

---
## Usage
Expand Down
2 changes: 2 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
type Query {
categoryTranslations(locale: String!, active: Boolean): [Category]
getCategoriesName: [CategoryName]
productTranslations(locale: String!, categoryId: String!): [Product]
}
6 changes: 6 additions & 0 deletions graphql/types/Category.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@ type Category {
name: String
title: String
description: String
linkId: String
locale: String
}

type CategoryName {
id: String
name: String
}
9 changes: 9 additions & 0 deletions graphql/types/Product.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type Product {
id: String!
name: String
description: String
shortDescription: String
title: String
locale: String
linkId: String
}
7 changes: 7 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
"policies": [
{
"name": "vtex.catalog-graphql:resolve-graphql"
},
{
"name": "outbound-access",
"attrs": {
"host": "portal.vtexcommercestable.com.br",
"path": "/api/catalog_system/*"
}
}
],
"$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema"
Expand Down
142 changes: 73 additions & 69 deletions node/clients/catalog.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,89 @@
import { AppGraphQLClient, InstanceOptions, IOContext } from '@vtex/api'
import {
InstanceOptions,
IOContext,
JanusClient,
RequestConfig,
} from '@vtex/api'

import { statusToError } from '../utils'
import {
statusToError,
interations,
getInterationPairs,
extractProductId,
MAX_PRODUCTS_PER_CATEGORY,
} from '../utils'

const CATALOG_GRAPHQL_APP = '[email protected]'
export class Catalog extends JanusClient {
constructor(ctx: IOContext, opts?: InstanceOptions) {
super(ctx, {
...opts,
headers: {
...opts?.headers,
...(ctx.adminUserAuthToken
? { VtexIdclientAutCookie: ctx.adminUserAuthToken }
: null),
},
})
}

public getAllProducts = async (categoryId: string) => {
const { range, ...products } = await this.getProductIdsByCategory(
categoryId,
1,
MAX_PRODUCTS_PER_CATEGORY
)
const { total } = range
const remainingInterations = interations(total)
const productPerCategorypromises = []

const CATEGORIES_QUERY = `
query GetCategoriesId ($active: Boolean, $page: Int!) {
categories(term:"*", page: $page, pageSize: 50, active: $active) {
items {
id
}
paging {
pages
}
// /GetProductAndSkuIds returns max 50 responses. We loop over the remaining interations to get all products
for (let i = 1; i <= remainingInterations; i++) {
const [from, to] = getInterationPairs(i)
const productPerIdPromise = this.getProductIdsByCategory(
categoryId,
from,
to
)
productPerCategorypromises.push(productPerIdPromise)
}
}
`

const GET_TRANSLATION_QUERY = `
query getTranslation($id:ID!) {
category(id: $id) {
id
name
title
description
const productPerCategoryCollection = await Promise.all(
productPerCategorypromises
)

const finalProducts = []

// we plug together the first response and all the others. Then we extract only the product ids from responses
for (const product of [products, ...productPerCategoryCollection]) {
const productIds = extractProductId(product.data)
finalProducts.push(...productIds)
}
return finalProducts
}
`

export class Catalog extends AppGraphQLClient {
constructor(ctx: IOContext, opts?: InstanceOptions) {
super(CATALOG_GRAPHQL_APP, ctx, opts)
private getProductIdsByCategory = (
categoryId: string,
_from: number,
_to: number
) => {
return this.get<GetProductAndSkuIds>(this.routes.getProductAndSkuIds(), {
params: { categoryId, _from, _to },
})
}

public getCategoriesId = async (active = true) => {
protected get = <T>(url: string, config: RequestConfig = {}) => {
try {
const response = await this.getCategoriesIdPerPage({ active, page: 1 })
const {
items,
paging: { pages },
} = (response.data as CategoryIdsResponse).categories
const collectItems = items
const responsePromises = []

for (let i = 2; i <= pages; i++) {
const promise = this.getCategoriesIdPerPage({ active, page: i })
responsePromises.push(promise)
}

const resolvedPromises = await Promise.all(responsePromises)

const flattenResponse = resolvedPromises.reduce((acc, curr) => {
return [...acc, ...(curr.data as CategoryIdsResponse).categories.items]
}, collectItems)

return flattenResponse
} catch (error) {
return statusToError(error)
return this.http.get<T>(url, config)
} catch (e) {
return statusToError(e)
}
}

private getCategoriesIdPerPage = ({
active = true,
page,
}: {
active: boolean
page: number
}) =>
this.graphql.query<CategoryIdsResponse, { active: boolean; page: number }>({
query: CATEGORIES_QUERY,
variables: {
active,
page,
},
})
private get routes() {
const basePath = '/api/catalog_system'

public getTranslation = (id: string) =>
this.graphql.query<TranslationResponse, { id: string }>({
query: GET_TRANSLATION_QUERY,
variables: {
id,
},
})
return {
getProductAndSkuIds: () => `${basePath}/pvt/products/GetProductAndSkuIds`,
}
}
}
129 changes: 129 additions & 0 deletions node/clients/catalogGQL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { AppGraphQLClient, InstanceOptions, IOContext } from '@vtex/api'

import { statusToError } from '../utils'

const CATALOG_GRAPHQL_APP = '[email protected]'

const CATEGORIES_QUERY = `
query GetCategories ($active: Boolean, $page: Int!) {
categories(term:"*", page: $page, pageSize: 50, active: $active) {
items {
id
name
}
paging {
pages
}
}
}
`

const GET_CATEGORY_TRANSLATION_QUERY = `
query getTranslation($id:ID!) {
category(id: $id) {
id
name
title
description
linkId
}
}
`

const GET_PRODUCT_TRANSLATION_QUERY = `
query getProductTranslation($identifier: ProductUniqueIdentifier) {
product(identifier: $identifier) {
id
name
description
shortDescription
title
linkId
}
}
`

export class CatalogGQL extends AppGraphQLClient {
constructor(ctx: IOContext, opts?: InstanceOptions) {
super(CATALOG_GRAPHQL_APP, ctx, opts)
}

public getCategories = async (active = true) => {
try {
const response = await this.getCategoriesPerPage({ active, page: 1 })
const {
items,
paging: { pages },
} = (response.data as CategoryResponse).categories
const collectItems = items
const responsePromises = []

for (let i = 2; i <= pages; i++) {
const promise = this.getCategoriesPerPage({ active, page: i })
responsePromises.push(promise)
}

const resolvedPromises = await Promise.all(responsePromises)

const flattenResponse = resolvedPromises.reduce((acc, curr) => {
return [...acc, ...(curr.data as CategoryResponse).categories.items]
}, collectItems)

return flattenResponse
} catch (error) {
return statusToError(error)
}
}

private getCategoriesPerPage = ({
active = true,
page,
}: {
active: boolean
page: number
}) =>
this.graphql.query<CategoryResponse, { active: boolean; page: number }>({
query: CATEGORIES_QUERY,
variables: {
active,
page,
},
})

public getCategoryTranslation = (id: string, locale: string) => {
return this.graphql.query<CategoryTranslationResponse, { id: string }>(
{
query: GET_CATEGORY_TRANSLATION_QUERY,
variables: {
id,
},
},
{
headers: {
'x-vtex-locale': `${locale}`,
},
}
)
}

public getProductTranslation = (id: string, locale: string) =>
this.graphql.query<
ProductTranslationResponse,
{ identifier: { value: string; field: 'id' } }
>(
{
query: GET_PRODUCT_TRANSLATION_QUERY,
variables: {
identifier: {
field: 'id',
value: id,
},
},
},
{
headers: {
'x-vtex-locale': `${locale}`,
},
}
)
}
5 changes: 5 additions & 0 deletions node/clients/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { IOClients } from '@vtex/api'

import { CatalogGQL } from './catalogGQL'
import { Catalog } from './catalog'

export class Clients extends IOClients {
public get catalogGQL() {
return this.getOrSet('catalogGQL', CatalogGQL)
}

public get catalog() {
return this.getOrSet('catalog', Catalog)
}
Expand Down
1 change: 1 addition & 0 deletions node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default new Service<Clients, State, ParamsContext>({
options: {
default: {
retries: 2,
timeout: 2 * 60000,
},
},
},
Expand Down
5 changes: 4 additions & 1 deletion node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
},
"devDependencies": {
"@vtex/tsconfig": "^0.5.6",
"typescript": "3.9.7"
"typescript": "3.9.7",
"vtex.catalog-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.catalog-graphql",
"vtex.styleguide": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.styleguide",
"vtex.tenant-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.tenant-graphql"
}
}
Loading

0 comments on commit c2b3413

Please sign in to comment.