Skip to content

Commit

Permalink
Merge pull request #1 from vtex-apps/export-category
Browse files Browse the repository at this point in the history
Export category
  • Loading branch information
cesarocampov authored Apr 23, 2021
2 parents 11b6bc6 + 41fc275 commit ba0e42a
Show file tree
Hide file tree
Showing 22 changed files with 1,981 additions and 36 deletions.
8 changes: 8 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "vtex",
"root": true,
"env": {
"node": true,
"es6": true
}
}
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ 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.

---
## Usage

Expand Down
3 changes: 3 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type Query {
categoryTranslations(locale: String!, active: Boolean): [Category]
}
7 changes: 7 additions & 0 deletions graphql/types/Category.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type Category {
id: String
name: String
title: String
description: String
locale: String
}
9 changes: 8 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@
"react": "3.x",
"admin": "0.x",
"messages": "1.x",
"docs": "0.x"
"docs": "0.x",
"node": "6.x",
"graphql": "1.x"
},
"policies": [
{
"name": "vtex.catalog-graphql:resolve-graphql"
}
],
"$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema"
}
85 changes: 85 additions & 0 deletions node/clients/catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { AppGraphQLClient, InstanceOptions, IOContext } from '@vtex/api'

import { statusToError } from '../utils'

const CATALOG_GRAPHQL_APP = '[email protected]'

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

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

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

public getCategoriesId = async (active = true) => {
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)
}
}

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

public getTranslation = (id: string) =>
this.graphql.query<TranslationResponse, { id: string }>({
query: GET_TRANSLATION_QUERY,
variables: {
id,
},
})
}
9 changes: 9 additions & 0 deletions node/clients/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IOClients } from '@vtex/api'

import { Catalog } from './catalog'

export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', Catalog)
}
}
36 changes: 36 additions & 0 deletions node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
ParamsContext,
RecorderState,
Service,
ServiceContext,
} from '@vtex/api'

import { Clients } from './clients'
import { resolvers, queries } from './resolvers'

declare global {
type Context = ServiceContext<Clients, State>

interface State extends RecorderState {
locale: string
}
}

export default new Service<Clients, State, ParamsContext>({
clients: {
implementation: Clients,
options: {
default: {
retries: 2,
},
},
},
graphql: {
resolvers: {
...resolvers,
Query: {
...queries,
},
},
},
})
13 changes: 13 additions & 0 deletions node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "vtex.admin-catalog-translation",
"version": "0.0.2",
"main": "index.ts",
"license": "UNLICENSED",
"dependencies": {
"@vtex/api": "6.41.0"
},
"devDependencies": {
"@vtex/tsconfig": "^0.5.6",
"typescript": "3.9.7"
}
}
48 changes: 48 additions & 0 deletions node/resolvers/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { statusToError } from '../utils'

export const Category = {
locale: (
_root: ResolvedPromise<TranslationResponse>,
_args: unknown,
ctx: Context
) => {
return ctx.state.locale
},
name: (root: ResolvedPromise<TranslationResponse>) => root.data.category.name,
title: (root: ResolvedPromise<TranslationResponse>) =>
root.data.category.title,
description: (root: ResolvedPromise<TranslationResponse>) =>
root.data.category.description,
id: (root: ResolvedPromise<TranslationResponse>) => root.data.category.id,
}

const categoryTranslations = async (
_root: unknown,
args: { locale: string; active?: boolean },
ctx: Context
) => {
const {
clients: { catalog },
} = ctx

ctx.state.locale = args.locale

try {
const ids = await catalog.getCategoriesId()

const translationsP = []

for (const { id } of ids) {
const promise = catalog.getTranslation(id)
translationsP.push(promise)
}

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

export const queries = {
categoryTranslations,
}
9 changes: 9 additions & 0 deletions node/resolvers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Category, queries as categoryQueries } from './category'

export const queries = {
...categoryQueries,
}

export const resolvers = {
Category,
}
8 changes: 8 additions & 0 deletions node/service.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"memory": 256,
"ttl": 10,
"timeout": 5,
"minReplicas": 2,
"maxReplicas": 4,
"workers": 2
}
26 changes: 26 additions & 0 deletions node/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"types": ["node"],
"typeRoots": ["node_modules/@types"],
"target": "es2019",
"module": "commonjs",
"moduleResolution": "node",
"allowJs": false,
"esModuleInterop": true,
"noEmitOnError": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"skipLibCheck": true
},
"typeAcquisition": {
"enable": false
},
"include": ["./**/*.ts", "./typings/*.d.ts"],
"exclude": ["node_modules"]
}
21 changes: 21 additions & 0 deletions node/typings/category.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
interface CategoryIdsResponse {
categories: {
items: Array<{ id: string }>
paging: {
pages: number
}
}
}

interface TranslationResponse {
category: {
id: string
name: string
title: string
description: string
}
}

interface ResolvedPromise<Response> {
data: Response
}
29 changes: 29 additions & 0 deletions node/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AuthenticationError, ForbiddenError, UserInputError } from '@vtex/api'
import type { AxiosError } from 'axios'

export const statusToError = (e: AxiosError) => {
if (!e.response) {
throw e
}

const {
response: { status },
} = e

switch (status) {
case 401: {
throw new AuthenticationError(e)
}

case 403: {
throw new ForbiddenError(e)
}

case 400: {
throw new UserInputError(e)
}

default:
throw new TypeError(e.message)
}
}
Loading

0 comments on commit ba0e42a

Please sign in to comment.