diff --git a/packages/medusa-js/src/resources/admin/batch-jobs.ts b/packages/medusa-js/src/resources/admin/batch-jobs.ts new file mode 100644 index 0000000000..e80dfc51ac --- /dev/null +++ b/packages/medusa-js/src/resources/admin/batch-jobs.ts @@ -0,0 +1,59 @@ +import { + AdminBatchJobListRes, + AdminBatchJobRes, + AdminGetBatchParams, + AdminPostBatchesReq, +} from "@medusajs/medusa" +import qs from "qs" +import { ResponsePromise } from "../../typings" +import BaseResource from "../base" + +class AdminBatchJobsResource extends BaseResource { + create( + payload: AdminPostBatchesReq, + customHeaders: Record = {} + ): ResponsePromise { + const path = `/admin/batch-jobs` + return this.client.request("POST", path, payload, {}, customHeaders) + } + + list( + query?: AdminGetBatchParams, + customHeaders: Record = {} + ): ResponsePromise { + let path = `/admin/batch-jobs` + + if (query) { + const queryString = qs.stringify(query) + path = `/admin/batch-jobs?${queryString}` + } + + return this.client.request("GET", path, {}, {}, customHeaders) + } + + cancel( + batchJobId: string, + customHeaders: Record = {} + ): ResponsePromise { + const path = `/admin/batch-jobs/${batchJobId}/cancel` + return this.client.request("POST", path, {}, {}, customHeaders) + } + + confirm( + batchJobId: string, + customHeaders: Record = {} + ): ResponsePromise { + const path = `/admin/batch-jobs/${batchJobId}/confirm` + return this.client.request("POST", path, {}, {}, customHeaders) + } + + retrieve( + batchJobId: string, + customHeaders: Record = {} + ): ResponsePromise { + const path = `/admin/batch-jobs/${batchJobId}` + return this.client.request("GET", path, {}, {}, customHeaders) + } +} + +export default AdminBatchJobsResource diff --git a/packages/medusa-js/src/resources/admin/index.ts b/packages/medusa-js/src/resources/admin/index.ts index 372c69d783..25b3c8a632 100644 --- a/packages/medusa-js/src/resources/admin/index.ts +++ b/packages/medusa-js/src/resources/admin/index.ts @@ -1,33 +1,35 @@ import BaseResource from "../base" import AdminAuthResource from "./auth" -import AdminCustomersResource from "./customers" -import AdminCustomerGroupsResource from "./customer-groups" -import AdminDiscountsResource from "./discounts" +import AdminBatchJobsResource from "./batch-jobs" import CollectionsResource from "./collections" +import AdminCustomerGroupsResource from "./customer-groups" +import AdminCustomersResource from "./customers" +import AdminDiscountsResource from "./discounts" import AdminDraftOrdersResource from "./draft-orders" import AdminGiftCardsResource from "./gift-cards" import AdminInvitesResource from "./invites" import AdminNotesResource from "./notes" -import AdminProductsResource from "./products" -import AdminProductTypesResource from "./product-types" -import AdminUsersResource from "./users" -import AdminReturnsResource from "./returns" +import AdminNotificationsResource from "./notifications" import AdminOrdersResource from "./orders" +import AdminPriceListResource from "./price-lists" +import AdminProductTagsResource from "./product-tags" +import AdminProductTypesResource from "./product-types" +import AdminProductsResource from "./products" +import AdminRegionsResource from "./regions" import AdminReturnReasonsResource from "./return-reasons" -import AdminVariantsResource from "./variants" -import AdminSwapsResource from "./swaps" -import AdminTaxRatesResource from "./tax-rates" +import AdminReturnsResource from "./returns" +import AdminShippingOptionsResource from "./shipping-options" import AdminShippingProfilesResource from "./shipping-profiles" import AdminStoresResource from "./store" -import AdminShippingOptionsResource from "./shipping-options" -import AdminRegionsResource from "./regions" -import AdminNotificationsResource from "./notifications" +import AdminSwapsResource from "./swaps" +import AdminTaxRatesResource from "./tax-rates" import AdminUploadsResource from "./uploads" -import AdminProductTagsResource from "./product-tags" -import AdminPriceListResource from "./price-lists" +import AdminUsersResource from "./users" +import AdminVariantsResource from "./variants" class Admin extends BaseResource { public auth = new AdminAuthResource(this.client) + public batchJobs = new AdminBatchJobsResource(this.client) public customers = new AdminCustomersResource(this.client) public customerGroups = new AdminCustomerGroupsResource(this.client) public discounts = new AdminDiscountsResource(this.client) diff --git a/packages/medusa-react/mocks/data/fixtures.json b/packages/medusa-react/mocks/data/fixtures.json index a1afaea812..9966b00119 100644 --- a/packages/medusa-react/mocks/data/fixtures.json +++ b/packages/medusa-react/mocks/data/fixtures.json @@ -1247,6 +1247,17 @@ "fulfillment_option": { "provider_id": "test-ful", "options": [] + }, + "batch_job": { + "id": "batch_01F0YES4R67TXXC1QBQ8P54A8Y", + "type": "product_export", + "created_by": "usr_123412341234", + "context": null, + "result": null, + "dry_run": false, + "updated_at": "2021-03-16T21:24:00.389Z", + "created_at": "2021-03-16T21:24:00.389Z", + "deleted_at": null } } } diff --git a/packages/medusa-react/mocks/handlers/admin.ts b/packages/medusa-react/mocks/handlers/admin.ts index fbaf3fb107..33d52aa3f6 100644 --- a/packages/medusa-react/mocks/handlers/admin.ts +++ b/packages/medusa-react/mocks/handlers/admin.ts @@ -2,6 +2,55 @@ import { rest } from "msw" import { fixtures } from "../data" export const adminHandlers = [ + rest.post("/admin/batch-jobs/", (req, res, ctx) => { + const body = req.body as Record + return res( + ctx.status(200), + ctx.json({ + batch_job: { + ...fixtures.get("batch_job"), + ...body, + }, + }) + ) + }), + + rest.get("/admin/batch-jobs/", (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + batch_jobs: fixtures.list("batch_job"), + }) + ) + }), + + rest.get("/admin/batch-jobs/:id", (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + batch_job: fixtures.get("batch_job"), + }) + ) + }), + + rest.post("/admin/batch-jobs/:id/confirm", (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + batch_job: fixtures.get("batch_job"), + }) + ) + }), + + rest.post("/admin/batch-jobs/:id/cancel", (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + batch_job: fixtures.get("batch_job"), + }) + ) + }), + rest.post("/admin/collections/", (req, res, ctx) => { const body = req.body as Record return res( @@ -253,27 +302,33 @@ export const adminHandlers = [ ) }), - rest.delete("/admin/price-lists/:id/products/:product_id/prices", (req, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ - ids: [], - object: "money-amount", - deleted: true, - }) - ) - }), + rest.delete( + "/admin/price-lists/:id/products/:product_id/prices", + (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + ids: [], + object: "money-amount", + deleted: true, + }) + ) + } + ), - rest.delete("/admin/price-lists/:id/variants/:variant_id/prices", (req, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ - ids: [], - object: "money-amount", - deleted: true, - }) - ) - }), + rest.delete( + "/admin/price-lists/:id/variants/:variant_id/prices", + (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + ids: [], + object: "money-amount", + deleted: true, + }) + ) + } + ), rest.post("/admin/return-reasons/", (req, res, ctx) => { const body = req.body as Record diff --git a/packages/medusa-react/src/hooks/admin/batch-jobs/index.ts b/packages/medusa-react/src/hooks/admin/batch-jobs/index.ts new file mode 100644 index 0000000000..a494946b87 --- /dev/null +++ b/packages/medusa-react/src/hooks/admin/batch-jobs/index.ts @@ -0,0 +1,2 @@ +export * from "./queries" +export * from "./mutations" diff --git a/packages/medusa-react/src/hooks/admin/batch-jobs/mutations.ts b/packages/medusa-react/src/hooks/admin/batch-jobs/mutations.ts new file mode 100644 index 0000000000..c3d6356634 --- /dev/null +++ b/packages/medusa-react/src/hooks/admin/batch-jobs/mutations.ts @@ -0,0 +1,74 @@ +import { AdminBatchJobRes, AdminPostBatchesReq } from "@medusajs/medusa" +import { Response } from "@medusajs/medusa-js" +import { useMutation, UseMutationOptions, useQueryClient } from "react-query" + +import { useMedusa } from "../../../contexts" +import { buildOptions } from "../../utils/buildOptions" +import { adminBatchJobsKeys } from "./queries" + +/** + * Hook returns functions for creating batch jobs. + * + * @param options + */ +export const useAdminCreateBatchJob = ( + options?: UseMutationOptions< + Response, + Error, + AdminPostBatchesReq + > +) => { + const { client } = useMedusa() + const queryClient = useQueryClient() + + return useMutation( + (payload: AdminPostBatchesReq) => client.admin.batchJobs.create(payload), + buildOptions(queryClient, adminBatchJobsKeys.lists(), options) + ) +} + +/** + * Hook return functions for canceling a batch job + * + * @param id - id of the batch job + * @param options + */ +export const useAdminCancelBatchJob = ( + id: string, + options?: UseMutationOptions, Error> +) => { + const { client } = useMedusa() + const queryClient = useQueryClient() + + return useMutation( + () => client.admin.batchJobs.cancel(id), + buildOptions( + queryClient, + [adminBatchJobsKeys.lists(), adminBatchJobsKeys.detail(id)], + options + ) + ) +} + +/** + * Hook return functions for confirming a batch job + * + * @param id - id of the batch job + * @param options + */ +export const useAdminConfirmBatchJob = ( + id: string, + options?: UseMutationOptions, Error> +) => { + const { client } = useMedusa() + const queryClient = useQueryClient() + + return useMutation( + () => client.admin.batchJobs.confirm(id), + buildOptions( + queryClient, + [adminBatchJobsKeys.lists(), adminBatchJobsKeys.detail(id)], + options + ) + ) +} diff --git a/packages/medusa-react/src/hooks/admin/batch-jobs/queries.ts b/packages/medusa-react/src/hooks/admin/batch-jobs/queries.ts new file mode 100644 index 0000000000..f7e27eb9ce --- /dev/null +++ b/packages/medusa-react/src/hooks/admin/batch-jobs/queries.ts @@ -0,0 +1,50 @@ +import { + AdminBatchJobListRes, + AdminBatchJobRes, + AdminGetBatchParams, +} from "@medusajs/medusa" +import { Response } from "@medusajs/medusa-js" +import { useQuery } from "react-query" +import { useMedusa } from "../../../contexts" +import { UseQueryOptionsWrapper } from "../../../types" +import { queryKeysFactory } from "../../utils/index" + +const ADMIN_COLLECTIONS_QUERY_KEY = `admin_batches` as const + +export const adminBatchJobsKeys = queryKeysFactory(ADMIN_COLLECTIONS_QUERY_KEY) + +type BatchJobsQueryKey = typeof adminBatchJobsKeys + +export const useAdminBatchJobs = ( + query?: AdminGetBatchParams, + options?: UseQueryOptionsWrapper< + Response, + Error, + ReturnType + > +) => { + const { client } = useMedusa() + const { data, ...rest } = useQuery( + adminBatchJobsKeys.list(query), + () => client.admin.batchJobs.list(query), + options + ) + return { ...data, ...rest } as const +} + +export const useAdminBatchJob = ( + id: string, + options?: UseQueryOptionsWrapper< + Response, + Error, + ReturnType + > +) => { + const { client } = useMedusa() + const { data, ...rest } = useQuery( + adminBatchJobsKeys.detail(id), + () => client.admin.batchJobs.retrieve(id), + options + ) + return { ...data, ...rest } as const +} diff --git a/packages/medusa-react/src/hooks/admin/index.ts b/packages/medusa-react/src/hooks/admin/index.ts index 01968c7e2d..556e6cdab6 100644 --- a/packages/medusa-react/src/hooks/admin/index.ts +++ b/packages/medusa-react/src/hooks/admin/index.ts @@ -1,26 +1,27 @@ export * from "./auth" -export * from "./collections" +export * from "./batch-jobs" export * from "./claims" -export * from "./customers" +export * from "./collections" export * from "./customer-groups" +export * from "./customers" export * from "./discounts" export * from "./draft-orders" export * from "./gift-cards" +export * from "./invites" +export * from "./notes" +export * from "./notifications" export * from "./orders" -export * from "./products" +export * from "./price-lists" export * from "./product-tags" export * from "./product-types" -export * from "./price-lists" -export * from "./return-reasons" +export * from "./products" export * from "./regions" +export * from "./return-reasons" +export * from "./returns" export * from "./shipping-options" export * from "./shipping-profiles" -export * from "./notes" -export * from "./invites" -export * from "./notifications" -export * from "./returns" export * from "./store" export * from "./swaps" +export * from "./tax-rates" export * from "./users" export * from "./variants" -export * from "./tax-rates" diff --git a/packages/medusa-react/test/hooks/admin/batch-jobs/mutations.test.ts b/packages/medusa-react/test/hooks/admin/batch-jobs/mutations.test.ts new file mode 100644 index 0000000000..a49b91a37d --- /dev/null +++ b/packages/medusa-react/test/hooks/admin/batch-jobs/mutations.test.ts @@ -0,0 +1,78 @@ +import { renderHook } from "@testing-library/react-hooks" +import { fixtures } from "../../../../mocks/data" +import { + useAdminCancelBatchJob, + useAdminConfirmBatchJob, + useAdminCreateBatchJob, +} from "../../../../src" +import { createWrapper } from "../../../utils" + +describe("useAdminCreateBatchJob hook", () => { + test("creates a batch job and returns it", async () => { + const batch = { + type: "product_export", + dry_run: false, + context: {}, + } + + const { result, waitFor } = renderHook(() => useAdminCreateBatchJob(), { + wrapper: createWrapper(), + }) + + result.current.mutate(batch) + + await waitFor(() => result.current.isSuccess) + + expect(result.current.data?.response.status).toEqual(200) + expect(result.current.data?.batch_job).toEqual( + expect.objectContaining({ + ...fixtures.get("batch_job"), + ...batch, + }) + ) + }) +}) + +describe("useAdminCancelBatchJob hook", () => { + test("cancels a batch job and returns it", async () => { + const { result, waitFor } = renderHook( + () => useAdminCancelBatchJob(fixtures.get("batch_job").id), + { + wrapper: createWrapper(), + } + ) + + result.current.mutate() + + await waitFor(() => result.current.isSuccess) + + expect(result.current.data?.response.status).toEqual(200) + expect(result.current.data?.batch_job).toEqual( + expect.objectContaining({ + ...fixtures.get("batch_job"), + }) + ) + }) +}) + +describe("useAdminConfirmBatchJob hook", () => { + test("confirms a batch job and returns it", async () => { + const { result, waitFor } = renderHook( + () => useAdminConfirmBatchJob(fixtures.get("batch_job").id), + { + wrapper: createWrapper(), + } + ) + + result.current.mutate() + + await waitFor(() => result.current.isSuccess) + + expect(result.current.data?.response.status).toEqual(200) + expect(result.current.data?.batch_job).toEqual( + expect.objectContaining({ + ...fixtures.get("batch_job"), + }) + ) + }) +}) diff --git a/packages/medusa-react/test/hooks/admin/batch-jobs/queries.test.ts b/packages/medusa-react/test/hooks/admin/batch-jobs/queries.test.ts new file mode 100644 index 0000000000..e213146f91 --- /dev/null +++ b/packages/medusa-react/test/hooks/admin/batch-jobs/queries.test.ts @@ -0,0 +1,35 @@ +import { renderHook } from "@testing-library/react-hooks" +import { fixtures } from "../../../../mocks/data" +import { useAdminBatchJob, useAdminBatchJobs } from "../../../../src" +import { createWrapper } from "../../../utils" + +describe("useAdminBatchJobs hook", () => { + test("returns a list of batch job", async () => { + const batchJobs = fixtures.list("batch_job") + const { result, waitFor } = renderHook(() => useAdminBatchJobs(), { + wrapper: createWrapper(), + }) + + await waitFor(() => result.current.isSuccess) + + expect(result.current.response?.status).toEqual(200) + expect(result.current.batch_jobs).toEqual(batchJobs) + }) +}) + +describe("useAdminBatchJob hook", () => { + test("returns a batch job", async () => { + const batchJob = fixtures.get("batch_job") + const { result, waitFor } = renderHook( + () => useAdminBatchJob(batchJob.id), + { + wrapper: createWrapper(), + } + ) + + await waitFor(() => result.current.isSuccess) + + expect(result.current.response?.status).toEqual(200) + expect(result.current.batch_job).toEqual(batchJob) + }) +}) diff --git a/packages/medusa/src/api/index.js b/packages/medusa/src/api/index.js index 290b631600..63f19acd92 100644 --- a/packages/medusa/src/api/index.js +++ b/packages/medusa/src/api/index.js @@ -16,33 +16,33 @@ export default (container, config) => { } // Admin -export * from "./routes/admin/collections" export * from "./routes/admin/auth" -export * from "./routes/admin/customers" +export * from "./routes/admin/batch" +export * from "./routes/admin/collections" export * from "./routes/admin/customer-groups" +export * from "./routes/admin/customers" export * from "./routes/admin/discounts" export * from "./routes/admin/draft-orders" export * from "./routes/admin/gift-cards" export * from "./routes/admin/invites" export * from "./routes/admin/notes" export * from "./routes/admin/notifications" -export * from "./routes/admin/shipping-profiles" -export * from "./routes/admin/store" -export * from "./routes/admin/products" -export * from "./routes/admin/users" export * from "./routes/admin/orders" -export * from "./routes/admin/variants" -export * from "./routes/admin/return-reasons" -export * from "./routes/admin/swaps" -export * from "./routes/admin/uploads" -export * from "./routes/admin/returns" -export * from "./routes/admin/tax-rates" -export * from "./routes/admin/shipping-options" -export * from "./routes/admin/regions" +export * from "./routes/admin/price-lists" export * from "./routes/admin/product-tags" export * from "./routes/admin/product-types" -export * from "./routes/admin/price-lists" - +export * from "./routes/admin/products" +export * from "./routes/admin/regions" +export * from "./routes/admin/return-reasons" +export * from "./routes/admin/returns" +export * from "./routes/admin/shipping-options" +export * from "./routes/admin/shipping-profiles" +export * from "./routes/admin/store" +export * from "./routes/admin/swaps" +export * from "./routes/admin/tax-rates" +export * from "./routes/admin/uploads" +export * from "./routes/admin/users" +export * from "./routes/admin/variants" // Store export * from "./routes/store/auth" export * from "./routes/store/carts" diff --git a/packages/medusa/src/api/routes/admin/batch/index.ts b/packages/medusa/src/api/routes/admin/batch/index.ts index bbbe03ff20..9e83e6c51a 100644 --- a/packages/medusa/src/api/routes/admin/batch/index.ts +++ b/packages/medusa/src/api/routes/admin/batch/index.ts @@ -1,12 +1,12 @@ import { Router } from "express" import { BatchJob } from "../../../.." import { DeleteResponse, PaginatedResponse } from "../../../../types/common" -import { AdminGetBatchParams } from "./list-batch-jobs" import middlewares, { - transformQuery, - getRequestedBatchJob, canAccessBatchJob, + getRequestedBatchJob, + transformQuery, } from "../../../middlewares" +import { AdminGetBatchParams } from "./list-batch-jobs" export default (app) => { const route = Router() @@ -59,4 +59,8 @@ export const defaultAdminBatchFields = [ "deleted_at", ] +export * from "./cancel-batch-job" +export * from "./confirm-batch-job" +export * from "./create-batch-job" +export * from "./get-batch-job" export * from "./list-batch-jobs" diff --git a/yarn.lock b/yarn.lock index f65b8590fa..f468e55ecc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27673,10 +27673,10 @@ typeorm@^0.2.29, typeorm@^0.2.31: yargs "^17.0.1" zen-observable-ts "^1.0.0" -typescript@^3.7.3: - version "3.9.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" - integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== +typescript@^3.7.3, typescript@^4.5.0: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== typescript@^4.1.3: version "4.4.2"