Compare commits

...

15 commits

Author SHA1 Message Date
4b12982597 add changeset 2023-09-30 22:22:32 +02:00
292a5bdb0c emails-and-messages redis apl support 2023-09-30 22:03:02 +02:00
Lukasz Ostrowski
653b98df86
Release apps (#1041)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-28 16:02:35 +02:00
Krzysztof Wolski
ae6dbb125b
Remove cache update on product webhooks (#1036)
* Product feed - change max function exec time to 5 minutes

* Remove cache update on product webhooks

* Add webhooks migration

---------

Co-authored-by: Lukasz Ostrowski <lukasz.ostrowski@saleor.io>
2023-09-27 12:56:12 +02:00
Lukasz Ostrowski
6948fe41ca
Update CODEOWNERS 2023-09-23 10:49:49 +02:00
Lukasz Ostrowski
2a22cf47f2
🚀 Release apps (#988)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-18 10:20:01 +02:00
Przemysław Łada
927d2b3bb6
Use full commit hash when referencing GitHub Action (#1002)
* Use full commit hash when referencing GitHub Action

* Use full commit hash in changeset-checker
2023-09-18 09:28:24 +02:00
dependabot[bot]
5dee65ad2c
[skip ci]: Bump the codegen group with 1 update (#990)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
2023-09-13 11:04:10 +02:00
Przemysław Łada
c6db32b40e
Fix broken pnpm lock (#1009) 2023-09-08 20:38:48 +02:00
dependabot[bot]
5d3d81d5c2
[skip ci]: Bump @hookform/resolvers from 2.9.11 to 3.3.1 (#993)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lukasz Ostrowski <lukasz.ostrowski@saleor.io>
2023-09-08 15:35:57 +02:00
Przemysław Łada
52c46987f3
Replace assign-pr-creator-action with GitHub CLI (#1008) 2023-09-08 15:35:32 +02:00
Lukasz Ostrowski
1982d81f17
Extend pricing data in Algolia (#998)
Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
2023-09-08 14:05:37 +02:00
Krzysztof Wolski
767d0be722
Add mandatory check for the changeset (#1000)
* Add mandatory check for the changeset

* Skip check on label

* Fx typo and add synchronize event
2023-09-08 14:04:34 +02:00
dependabot[bot]
2951fb3ab6
[skip ci]: Bump jotai from 2.0.0 to 2.4.2 (#1007)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lukasz Ostrowski <lukasz.ostrowski@saleor.io>
2023-09-08 10:40:34 +00:00
Krzysztof Wolski
7e0755ec9e
Webhook helpers (#949)
* WIP

* Added script and making implementation more roboust

* Added rollback on issues with the migration

* Cleanup the code

* Use allSettled instead of all

* Do not check spelling in schema files.
Schema is pulled from the API and is not controlled by our team

* Update the pkg json

* Fix typo on log message

* Add dedupe to the ignored words.
Its used by codegen

* Add changesets
2023-09-07 13:04:23 +02:00
115 changed files with 73687 additions and 1412 deletions

View file

@ -1,5 +0,0 @@
---
"saleor-app-taxes": minor
---
Added logs for AvaTax and TaxJar. Logs are stored in the app metadata. Only the last 100 events are stored. Each provider configuration has its own logs. You can get to them by a new button "Logs" in the provider table.

View file

@ -1,15 +0,0 @@
---
"saleor-app-emails-and-messages": patch
"saleor-app-data-importer": patch
"saleor-app-products-feed": patch
"saleor-app-invoices": patch
"saleor-app-klaviyo": patch
"saleor-app-segment": patch
"saleor-app-cms-v2": patch
"saleor-app-search": patch
"saleor-app-slack": patch
"saleor-app-taxes": patch
"saleor-app-crm": patch
---
Updated Sentry package

View file

@ -1,5 +0,0 @@
---
"saleor-app-klaviyo": patch
---
Fixed error where config couldn't be saved

View file

@ -0,0 +1,8 @@
#changelog
---
"apps": minor
---
### Added
- `apps/emails-and-messages/.env.template`: Described the new environment variable and how it works
- `apps/emails-and-messages/src/saleor-app.ts`: Added case "redis" for switch(AplType), which takes advantage of the [RedisAPL PR](https://github.com/saleor/app-sdk/pull/287) I submitted

View file

@ -1,5 +0,0 @@
---
"saleor-app-invoices": minor
---
Replace text "loading" messages with skeletons

View file

@ -1,5 +0,0 @@
---
"saleor-app-invoices": minor
---
Redesigned app layout. Now app uses shared sections as other apps.

View file

@ -1,5 +0,0 @@
---
"saleor-app-klaviyo": minor
---
Improved app layout to match modern style.

View file

@ -6,9 +6,12 @@ on:
jobs: jobs:
assign_creator: assign_creator:
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Assign PR to creator - name: Assign PR to creator
uses: thomaseizinger/assign-pr-creator-action@v1.0.0 env:
with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
repo-token: ${{ secrets.GITHUB_TOKEN }} PR_URL: ${{ github.event.pull_request.html_url }}
CREATOR: ${{ github.event.pull_request.user.login }}
run: gh pr edit "$PR_URL" --add-assignee "$CREATOR"

31
.github/workflows/changeset-checker.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: Changesets
on:
pull_request:
types:
- opened
- labeled
- edited
- synchronize
branches-ignore:
- 'changeset-release/**'
jobs:
changeset_check:
name: Changeset added to the PR
# Adding 'skip changesets' label to the PR will skip this job
if: ${{ !contains( github.event.pull_request.labels.*.name, 'skip changeset') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
# check out full history
fetch-depth: 0
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
- uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Changeset added
run: npx changeset status --since origin/main

View file

@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
- uses: JamieMason/syncpack-github-action@0.2.2 - uses: JamieMason/syncpack-github-action@c145cec44b3731b3fe8e859679e240d6ae011f0f
continue-on-error: true continue-on-error: true
with: with:
package-manager: "pnpm" package-manager: "pnpm"

View file

@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: streetsidesoftware/cspell-action@v3 - uses: streetsidesoftware/cspell-action@22e32eb3d70acf30e3fc09bd46edc1d30fb2d6db

View file

@ -25,11 +25,11 @@ jobs:
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@d882d12c64e032187b2edb46d3a0d003b7a43598
name: Install pnpm name: Install pnpm
- run: pnpm install - run: pnpm install
- name: Create Release Pull Request - name: Create Release Pull Request
uses: changesets/action@v1 uses: changesets/action@f13b1baaa620fde937751f5d2c3572b9da32af23
id: changesets id: changesets
with: with:
title: 🚀 Release apps title: 🚀 Release apps

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4 - uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18

View file

@ -1 +1 @@
* @saleor/appstore * @saleor/delivery-engineering-js

View file

@ -1,5 +1,14 @@
# saleor-app-cms-v2 # saleor-app-cms-v2
## 2.3.3
### Patch Changes
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 2.3.2 ## 2.3.2
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-cms-v2", "name": "saleor-app-cms-v2",
"version": "2.3.2", "version": "2.3.3",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -13,7 +13,7 @@
}, },
"dependencies": { "dependencies": {
"@datocms/cma-client-browser": "2.0.0", "@datocms/cma-client-browser": "2.0.0",
"@hookform/resolvers": "^3.1.0", "@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1", "@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*", "@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*", "@saleor/apps-ui": "workspace:*",
@ -48,7 +48,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "4.0.1", "@graphql-codegen/typescript": "4.0.1",

View file

@ -1,5 +1,13 @@
# saleor-app-crm # saleor-app-crm
## 1.7.8
### Patch Changes
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 1.7.7 ## 1.7.7
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-crm", "name": "saleor-app-crm",
"version": "1.7.7", "version": "1.7.8",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -42,7 +42,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "4.0.1", "@graphql-codegen/typescript": "4.0.1",

View file

@ -1,5 +1,13 @@
# saleor-app-data-importer # saleor-app-data-importer
## 1.9.7
### Patch Changes
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 1.9.6 ## 1.9.6
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-data-importer", "name": "saleor-app-data-importer",
"version": "1.9.6", "version": "1.9.7",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -39,7 +39,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -7,6 +7,12 @@ APL=
REST_APL_ENDPOINT= REST_APL_ENDPOINT=
REST_APL_TOKEN= REST_APL_TOKEN=
# To use Redis as an APL store, set APP_API_BASE_URL and REDIS_URL.
# URL is in format redis[s]://[[username][:password]@][host][:port][/db-number],
# so for example redis://alice:foobared@awesome.redis.server:6380
# For saleor-platform, thats: `redis://redis:6379/1`
REDIS_URL=
APP_LOG_LEVEL=info APP_LOG_LEVEL=info
# Local development variables. When developped locally with Saleor inside docker, these can be set to: # Local development variables. When developped locally with Saleor inside docker, these can be set to:
@ -15,4 +21,5 @@ APP_LOG_LEVEL=info
# If developped with tunnels, set this empty, it will fallback to default Next's localhost:3000 # If developped with tunnels, set this empty, it will fallback to default Next's localhost:3000
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development # https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
APP_IFRAME_BASE_URL= APP_IFRAME_BASE_URL=
APP_API_BASE_URL= APP_API_BASE_URL=

View file

@ -1,5 +1,14 @@
# saleor-app-emails-and-messages # saleor-app-emails-and-messages
## 1.9.10
### Patch Changes
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 1.9.9 ## 1.9.9
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-emails-and-messages", "name": "saleor-app-emails-and-messages",
"version": "1.9.9", "version": "1.9.10",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -12,7 +12,7 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.1.0", "@hookform/resolvers": "^3.3.1",
"@monaco-editor/react": "^4.4.6", "@monaco-editor/react": "^4.4.6",
"@saleor/app-sdk": "0.43.1", "@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*", "@saleor/apps-shared": "workspace:*",
@ -51,7 +51,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -1,4 +1,4 @@
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL"; import { APL, FileAPL, RedisAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
import { SaleorApp } from "@saleor/app-sdk/saleor-app"; import { SaleorApp } from "@saleor/app-sdk/saleor-app";
const aplType = process.env.APL ?? "file"; const aplType = process.env.APL ?? "file";
@ -6,6 +6,12 @@ const aplType = process.env.APL ?? "file";
export let apl: APL; export let apl: APL;
switch (aplType) { switch (aplType) {
case "redis": {
if (!process.env.REDIS_URL) throw new Error("Missing redis url");
if (!process.env.APP_API_BASE_URL)
throw new Error("Redis relies on APP_API_BASE_URL to store keys, please set env variable");
apl = new RedisAPL(new URL(process.env.REDIS_URL), process.env.APP_API_BASE_URL);
}
case "upstash": case "upstash":
apl = new UpstashAPL(); apl = new UpstashAPL();

View file

@ -1,5 +1,7 @@
{ {
"extends": ["//"], "extends": [
"//"
],
"$schema": "https://turbo.build/schema.json", "$schema": "https://turbo.build/schema.json",
"pipeline": { "pipeline": {
"build": { "build": {
@ -21,7 +23,8 @@
"NEXT_PUBLIC_SENTRY_DSN", "NEXT_PUBLIC_SENTRY_DSN",
"SENTRY_ENVIRONMENT", "SENTRY_ENVIRONMENT",
"APP_IFRAME_BASE_URL", "APP_IFRAME_BASE_URL",
"APP_API_BASE_URL" "APP_API_BASE_URL",
"REDIS_URL"
] ]
} }
} }

View file

@ -1,5 +1,19 @@
# saleor-app-invoices # saleor-app-invoices
## 1.16.0
### Minor Changes
- 4aee4e1: Replace text "loading" messages with skeletons
- 4aee4e1: Redesigned app layout. Now app uses shared sections as other apps.
### Patch Changes
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 1.15.7 ## 1.15.7
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-invoices", "name": "saleor-app-invoices",
"version": "1.15.7", "version": "1.16.0",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -12,7 +12,7 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.1.0", "@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1", "@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*", "@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*", "@saleor/apps-ui": "workspace:*",
@ -42,7 +42,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -1,5 +1,18 @@
# saleor-app-klaviyo # saleor-app-klaviyo
## 1.9.0
### Minor Changes
- 4aee4e1: Improved app layout to match modern style.
### Patch Changes
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
- 4aee4e1: Fixed error where config couldn't be saved
## 1.8.6 ## 1.8.6
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-klaviyo", "name": "saleor-app-klaviyo",
"version": "1.8.6", "version": "1.9.0",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -35,7 +35,7 @@
"vitest": "0.34.1" "vitest": "0.34.1"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -1,5 +1,21 @@
# saleor-app-products-feed # saleor-app-products-feed
## 1.12.2
### Patch Changes
- ae6dbb1: Removed webhooks on product changes used for feed cache due to changed max execution time.
- ae6dbb1: Changed Vercel's maximum execution time to be 5 minutes for feed generation. This should help with the previous limits of 60s, that was not enough for feed to be generated.
## 1.12.1
### Patch Changes
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 1.12.0 ## 1.12.0
### Minor Changes ### Minor Changes

View file

@ -1,8 +0,0 @@
fragment ProductVariantWebhookPayload on ProductVariant {
channel
channelListings {
channel {
slug
}
}
}

View file

@ -1,8 +0,0 @@
fragment ProductWebhookPayload on Product {
channel
channelListings {
channel {
slug
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductCreated {
event {
... on ProductCreated {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductDeleted {
event {
... on ProductDeleted {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductUpdated {
event {
... on ProductUpdated {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductVariantCreated {
event {
... on ProductVariantCreated {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -1,10 +0,0 @@
subscription ProductVariantDeleted {
event {
... on ProductVariantDeleted {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductVariantUpdated {
event {
... on ProductVariantUpdated {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -6,7 +6,12 @@ const isSentryPropertiesInEnvironment =
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"], transpilePackages: [
"@saleor/apps-shared",
"@saleor/apps-ui",
"@saleor/react-hook-form-macaw",
"@saleor/webhook-utils",
],
}; };
const configWithSentry = withSentryConfig( const configWithSentry = withSentryConfig(
@ -22,7 +27,7 @@ const configWithSentry = withSentryConfig(
tunnelRoute: "/monitoring", tunnelRoute: "/monitoring",
hideSourceMaps: true, hideSourceMaps: true,
disableLogger: true, disableLogger: true,
} },
); );
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig; module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-products-feed", "name": "saleor-app-products-feed",
"version": "1.12.0", "version": "1.12.2",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -13,12 +13,13 @@
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.332.0", "@aws-sdk/client-s3": "^3.332.0",
"@hookform/resolvers": "^3.1.0", "@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1", "@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*", "@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*", "@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127", "@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/react-hook-form-macaw": "workspace:*", "@saleor/react-hook-form-macaw": "workspace:*",
"@saleor/webhook-utils": "workspace:*",
"@sentry/nextjs": "7.67.0", "@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19", "@tanstack/react-query": "4.29.19",
"@trpc/client": "10.38.1", "@trpc/client": "10.38.1",
@ -27,6 +28,7 @@
"@trpc/server": "10.38.1", "@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4", "@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4", "@vitejs/plugin-react": "4.0.4",
"dotenv": "^16.3.1",
"fast-xml-parser": "^4.0.15", "fast-xml-parser": "^4.0.15",
"graphql": "16.7.1", "graphql": "16.7.1",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
@ -46,7 +48,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -0,0 +1,7 @@
# Webhook migration scripts
Test migration with dry run, operation will not modify any data:
`npx tsx scripts/migrations/run-webhooks-migration-dry-run.ts`
To start the migration run command:
`npx tsx scripts/migrations/run-webhooks-migration.ts`

View file

@ -0,0 +1,20 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { SaleorCloudAPL } from "@saleor/app-sdk/APL";
export const verifyRequiredEnvs = () => {
const requiredEnvs = ["SALEOR_CLOUD_TOKEN", "SALEOR_CLOUD_RESOURCE_URL"];
if (!requiredEnvs.every((env) => process.env[env])) {
throw new Error(`Missing envs: ${requiredEnvs.join(" | ")}`);
}
};
export const fetchCloudAplEnvs = () => {
const saleorAPL = new SaleorCloudAPL({
token: process.env.SALEOR_CLOUD_TOKEN!,
resourceUrl: process.env.SALEOR_CLOUD_RESOURCE_URL!,
});
return saleorAPL.getAll();
};

View file

@ -0,0 +1,30 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { updateWebhooksScript } from "./update-webhooks";
dotenv.config();
const runMigration = async () => {
console.log("Starting webhooks migration (dry run)");
verifyRequiredEnvs();
console.log("Envs verified, fetching envs");
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
console.error("Could not fetch instances from the APL");
console.error(r);
process.exit(1);
});
for (const env of allEnvs) {
await updateWebhooksScript({ authData: env, dryRun: true });
}
console.log("Migration dry run complete");
};
runMigration();

View file

@ -0,0 +1,30 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { updateWebhooksScript } from "./update-webhooks";
dotenv.config();
const runMigration = async () => {
console.log("Starting running migration");
verifyRequiredEnvs();
console.log("Envs verified, fetching envs");
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
console.error("Could not fetch instances from the APL");
console.error(r);
process.exit(1);
});
for (const env of allEnvs) {
await updateWebhooksScript({ authData: env, dryRun: false });
}
console.log("Migration complete");
};
runMigration();

View file

@ -0,0 +1,29 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { createGraphQLClient } from "@saleor/apps-shared";
import { AuthData } from "@saleor/app-sdk/APL";
import { webhookMigrationRunner } from "@saleor/webhook-utils";
export const updateWebhooksScript = async ({
authData,
dryRun,
}: {
authData: AuthData;
dryRun: boolean;
}) => {
console.log("Working on env: ", authData.saleorApiUrl);
const client = createGraphQLClient({
saleorApiUrl: authData.saleorApiUrl,
token: authData.token,
});
await webhookMigrationRunner({
client,
dryRun,
getManifests: async ({ appDetails }) => {
// Products feed application has currently no webhooks, so we return empty array
return [];
},
});
};

View file

@ -2,7 +2,6 @@ import { router } from "../trpc/trpc-server";
import { protectedClientProcedure } from "../trpc/protected-client-procedure"; import { protectedClientProcedure } from "../trpc/protected-client-procedure";
import { createLogger } from "@saleor/apps-shared"; import { createLogger } from "@saleor/apps-shared";
import { updateCacheForConfigurations } from "../metadata-cache/update-cache-for-configurations";
import { AppConfigSchema, imageSizeInputSchema, titleTemplateInputSchema } from "./app-config"; import { AppConfigSchema, imageSizeInputSchema, titleTemplateInputSchema } from "./app-config";
import { z } from "zod"; import { z } from "zod";
import { createS3ClientFromConfiguration } from "../file-storage/s3/create-s3-client-from-configuration"; import { createS3ClientFromConfiguration } from "../file-storage/s3/create-s3-client-from-configuration";
@ -106,17 +105,6 @@ export const appConfigurationRouter = router({
}) => { }) => {
const config = await getConfig(); const config = await getConfig();
/**
* TODO Check if this has to run, once its cached, it should be invalidated by webhooks only.
*
* But this operation isn't expensive and users will not continuously save this form
*/
await updateCacheForConfigurations({
client: apiClient,
channelsSlugs: [input.channelSlug],
saleorApiUrl: saleorApiUrl,
});
logger.debug({ channel: input.channelSlug }, "Updated cache for channel"); logger.debug({ channel: input.channelSlug }, "Updated cache for channel");
config.setChannelUrls(input.channelSlug, input.urls); config.setChannelUrls(input.channelSlug, input.urls);

View file

@ -1,33 +0,0 @@
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
export class CacheConfigurator {
private metadataKeyPrefix = "cursor-cache-";
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
private constructKey(channel: string) {
return this.metadataKeyPrefix + channel;
}
get({ channel }: { channel: string }): Promise<string[] | undefined> {
return this.metadataManager.get(this.constructKey(channel), this.saleorApiUrl).then((data) => {
if (!data) {
return undefined;
}
try {
return JSON.parse(data);
} catch (e) {
throw new Error("Invalid metadata value, can't be parsed");
}
});
}
set({ channel, value }: { channel: string; value: string[] }): Promise<void> {
return this.metadataManager.set({
key: this.constructKey(channel),
value: JSON.stringify(value),
domain: this.saleorApiUrl,
});
}
}

View file

@ -1,33 +0,0 @@
import { createLogger } from "@saleor/apps-shared";
import { CacheConfigurator } from "./cache-configurator";
import { createSettingsManager } from "../../lib/metadata-manager";
import { getCursors } from "../google-feed/fetch-product-data";
import { Client } from "urql";
interface UpdateCacheForConfigurationsArgs {
client: Client;
saleorApiUrl: string;
channelsSlugs: string[];
}
export const updateCacheForConfigurations = async ({
client,
channelsSlugs,
saleorApiUrl,
}: UpdateCacheForConfigurationsArgs) => {
const logger = createLogger({ saleorApiUrl: saleorApiUrl });
logger.debug("Updating the cursor cache");
const cache = new CacheConfigurator(createSettingsManager(client), saleorApiUrl);
const cacheUpdatePromises = channelsSlugs.map(async (channel) => {
const cursors = await getCursors({ client, channel });
await cache.set({ channel, value: cursors });
});
await Promise.all(cacheUpdatePromises);
logger.debug("Cursor cache updated");
};

View file

@ -1,41 +0,0 @@
import { GraphqlClientFactory } from "../../lib/create-graphql-client";
import { updateCacheForConfigurations } from "./update-cache-for-configurations";
import { AuthData } from "@saleor/app-sdk/APL";
import {
ProductVariantWebhookPayloadFragment,
ProductWebhookPayloadFragment,
} from "../../../generated/graphql";
import { NextApiResponse } from "next";
type ChannelFragment =
| Pick<ProductWebhookPayloadFragment, "channel" | "channelListings">
| Pick<ProductVariantWebhookPayloadFragment, "channel" | "channelListings">;
export const updateCacheOnWebhook = async ({
channels,
authData,
res,
}: {
authData: AuthData;
channels: ChannelFragment;
res: NextApiResponse;
}) => {
const client = GraphqlClientFactory.fromAuthData(authData);
const channelsSlugs = [
channels.channel,
...(channels.channelListings?.map((cl) => cl.channel.slug) ?? []),
].filter((c) => c) as string[];
if (channelsSlugs.length === 0) {
return res.status(200).end();
}
await updateCacheForConfigurations({
channelsSlugs,
client,
saleorApiUrl: authData.saleorApiUrl,
});
return res.status(200).end();
};

View file

@ -6,9 +6,6 @@ import { fetchProductData } from "../../../../../modules/google-feed/fetch-produ
import { GoogleFeedSettingsFetcher } from "../../../../../modules/google-feed/get-google-feed-settings"; import { GoogleFeedSettingsFetcher } from "../../../../../modules/google-feed/get-google-feed-settings";
import { generateGoogleXmlFeed } from "../../../../../modules/google-feed/generate-google-xml-feed"; import { generateGoogleXmlFeed } from "../../../../../modules/google-feed/generate-google-xml-feed";
import { fetchShopData } from "../../../../../modules/google-feed/fetch-shop-data"; import { fetchShopData } from "../../../../../modules/google-feed/fetch-shop-data";
import { CacheConfigurator } from "../../../../../modules/metadata-cache/cache-configurator";
import { createSettingsManager } from "../../../../../lib/metadata-manager";
import { GraphqlClientFactory } from "../../../../../lib/create-graphql-client";
import { uploadFile } from "../../../../../modules/file-storage/s3/upload-file"; import { uploadFile } from "../../../../../modules/file-storage/s3/upload-file";
import { createS3ClientFromConfiguration } from "../../../../../modules/file-storage/s3/create-s3-client-from-configuration"; import { createS3ClientFromConfiguration } from "../../../../../modules/file-storage/s3/create-s3-client-from-configuration";
import { getFileDetails } from "../../../../../modules/file-storage/s3/get-file-details"; import { getFileDetails } from "../../../../../modules/file-storage/s3/get-file-details";
@ -16,6 +13,10 @@ import { getDownloadUrl, getFileName } from "../../../../../modules/file-storage
import { RootConfig } from "../../../../../modules/app-configuration/app-config"; import { RootConfig } from "../../../../../modules/app-configuration/app-config";
import { z, ZodError } from "zod"; import { z, ZodError } from "zod";
export const config = {
maxDuration: 5 * 60, // 5 minutes
};
// By default we cache the feed for 5 minutes. This can be changed by setting the FEED_CACHE_MAX_AGE // By default we cache the feed for 5 minutes. This can be changed by setting the FEED_CACHE_MAX_AGE
const FEED_CACHE_MAX_AGE = process.env.FEED_CACHE_MAX_AGE const FEED_CACHE_MAX_AGE = process.env.FEED_CACHE_MAX_AGE
? parseInt(process.env.FEED_CACHE_MAX_AGE, 10) ? parseInt(process.env.FEED_CACHE_MAX_AGE, 10)
@ -157,23 +158,10 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
logger.debug("Generating a new feed"); logger.debug("Generating a new feed");
const cacheClient = GraphqlClientFactory.fromAuthData(authData);
if (!cacheClient) {
logger.error("Can't create the gql client");
return res.status(500).end();
}
// get cached cursors
const cache = new CacheConfigurator(createSettingsManager(cacheClient), authData.saleorApiUrl);
const cursors = await cache.get({ channel });
// TODO: instead of separate variants, use group id https://support.google.com/merchants/answer/6324507?hl=en
let productVariants: GoogleFeedProductVariantFragment[] = []; let productVariants: GoogleFeedProductVariantFragment[] = [];
try { try {
productVariants = await fetchProductData({ client, channel, cursors, imageSize }); productVariants = await fetchProductData({ client, channel, imageSize });
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
return res.status(400).end(); return res.status(400).end();

View file

@ -2,11 +2,6 @@ import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
import { AppManifest } from "@saleor/app-sdk/types"; import { AppManifest } from "@saleor/app-sdk/types";
import packageJson from "../../../package.json"; import packageJson from "../../../package.json";
import { webhookProductCreated } from "./webhooks/product_created";
import { webhookProductDeleted } from "./webhooks/product_deleted";
import { webhookProductVariantCreated } from "./webhooks/product_variant_created";
import { webhookProductVariantDeleted } from "./webhooks/product_variant_deleted";
import { webhookProductVariantUpdated } from "./webhooks/product_variant_updated";
export default createManifestHandler({ export default createManifestHandler({
async manifestFactory({ appBaseUrl }) { async manifestFactory({ appBaseUrl }) {
@ -31,13 +26,7 @@ export default createManifestHandler({
supportUrl: "https://github.com/saleor/apps/discussions", supportUrl: "https://github.com/saleor/apps/discussions",
tokenTargetUrl: `${apiBaseURL}/api/register`, tokenTargetUrl: `${apiBaseURL}/api/register`,
version: packageJson.version, version: packageJson.version,
webhooks: [ webhooks: [],
webhookProductCreated.getWebhookManifest(apiBaseURL),
webhookProductDeleted.getWebhookManifest(apiBaseURL),
webhookProductVariantCreated.getWebhookManifest(apiBaseURL),
webhookProductVariantDeleted.getWebhookManifest(apiBaseURL),
webhookProductVariantUpdated.getWebhookManifest(apiBaseURL),
],
}; };
return manifest; return manifest;

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { createLogger } from "@saleor/apps-shared";
import { saleorApp } from "../../../saleor-app";
import {
ProductCreatedDocument,
ProductWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductCreated = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_created",
event: "PRODUCT_CREATED",
apl: saleorApp.apl,
query: ProductCreatedDocument,
// todo make it disabled by default, enable when app is configured
isActive: true,
});
const logger = createLogger({
service: "webhook-product_created",
});
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductCreated.createHandler(handler);

View file

@ -1,40 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductDeletedDocument,
ProductWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductDeleted = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_deleted",
event: "PRODUCT_DELETED",
apl: saleorApp.apl,
query: ProductDeletedDocument,
isActive: true,
});
const logger = createLogger({
service: "webhook_product_deleted",
});
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductDeleted.createHandler(handler);

View file

@ -1,40 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductUpdatedDocument,
ProductWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductUpdated = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_updated",
event: "PRODUCT_UPDATED",
apl: saleorApp.apl,
query: ProductUpdatedDocument,
isActive: true,
});
const logger = createLogger({
service: "webhookProductUpdatedWebhookHandler",
});
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductUpdated.createHandler(handler);

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductVariantCreatedDocument,
ProductVariantWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductVariantCreated =
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_variant_created",
event: "PRODUCT_VARIANT_CREATED",
apl: saleorApp.apl,
query: ProductVariantCreatedDocument,
isActive: true,
});
const logger = createLogger({
service: "PRODUCT_VARIANT_CREATED webhook",
});
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductVariantCreated.createHandler(handler);

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { createLogger } from "@saleor/apps-shared";
import {
ProductVariantDeletedDocument,
ProductVariantWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductVariantDeleted =
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_variant_deleted",
event: "PRODUCT_VARIANT_DELETED",
apl: saleorApp.apl,
query: ProductVariantDeletedDocument,
isActive: true,
});
const logger = createLogger({
service: "PRODUCT_VARIANT_DELETED",
});
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductVariantDeleted.createHandler(handler);

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { createLogger } from "@saleor/apps-shared";
import {
ProductVariantUpdatedDocument,
ProductVariantWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductVariantUpdated =
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_variant_updated",
event: "PRODUCT_VARIANT_UPDATED",
apl: saleorApp.apl,
query: ProductVariantUpdatedDocument,
isActive: true,
});
const logger = createLogger({
service: "webhookProductVariantUpdatedWebhookHandler",
});
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductVariantUpdated.createHandler(handler);

View file

@ -1,5 +1,50 @@
# saleor-app-search # saleor-app-search
## 1.16.0
### Minor Changes
- 1982d81: Added a new `pricing` field to the Algolia object representation. It now passes variant pricing representation from GraphQL:
```graphQL
price {
gross {
amount
}
net {
amount
}
}
discount {
gross {
amount
}
net {
amount
}
}
onSale
priceUndiscounted {
gross {
amount
}
net {
amount
}
}
```
### Patch Changes
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
- 7e0755e: Webhook migration scripts has been moved to the shared package.
- Updated dependencies [5dee65a]
- Updated dependencies [7e0755e]
- @saleor/webhook-utils@0.0.1
## 1.15.0 ## 1.15.0
### Minor Changes ### Minor Changes

View file

@ -10,7 +10,26 @@ fragment ProductVariantData on ProductVariant {
price { price {
gross { gross {
amount amount
currency }
net {
amount
}
}
discount {
gross {
amount
}
net {
amount
}
}
onSale
priceUndiscounted {
gross {
amount
}
net {
amount
} }
} }
} }

View file

@ -6,7 +6,12 @@ const isSentryPropertiesInEnvironment =
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"], transpilePackages: [
"@saleor/apps-shared",
"@saleor/apps-ui",
"@saleor/webhook-utils",
"@saleor/react-hook-form-macaw",
],
}; };
const configWithSentry = withSentryConfig( const configWithSentry = withSentryConfig(
@ -22,7 +27,7 @@ const configWithSentry = withSentryConfig(
tunnelRoute: "/monitoring", tunnelRoute: "/monitoring",
hideSourceMaps: true, hideSourceMaps: true,
disableLogger: true, disableLogger: true,
} },
); );
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig; module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-search", "name": "saleor-app-search",
"version": "1.15.0", "version": "1.16.0",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -12,12 +12,13 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.1.0", "@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1", "@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*", "@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*", "@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127", "@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/react-hook-form-macaw": "workspace:*", "@saleor/react-hook-form-macaw": "workspace:*",
"@saleor/webhook-utils": "workspace:*",
"@sentry/nextjs": "7.67.0", "@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19", "@tanstack/react-query": "4.29.19",
"@trpc/client": "10.38.1", "@trpc/client": "10.38.1",
@ -43,7 +44,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -1,52 +0,0 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { createGraphQLClient } from "@saleor/apps-shared";
import { WebhookActivityTogglerService } from "../../src/domain/WebhookActivityToggler.service";
import { FetchOwnWebhooksDocument } from "../../generated/graphql";
import { AuthData } from "@saleor/app-sdk/APL";
export const recreateWebhooks = async ({
authData,
dryRun,
}: {
authData: AuthData;
dryRun: boolean;
}) => {
console.log("Working on env: ", authData.saleorApiUrl);
const client = createGraphQLClient({
saleorApiUrl: authData.saleorApiUrl,
token: authData.token,
});
const webhooks = await client
.query(FetchOwnWebhooksDocument, {
id: authData.appId,
})
.toPromise()
.then((r) => r.data?.app?.webhooks);
if (!webhooks?.length) {
console.error("The environment does not have any webhooks, skipping");
return;
}
// Use currently existing webhook data to determine a proper baseUrl and enabled state
const targetUrl = webhooks[0].targetUrl;
const enabled = webhooks[0].isActive;
const baseUrl = new URL(targetUrl).origin;
if (dryRun) {
console.log("Necessary data gathered, skipping recreation of webhooks due to dry run mode");
return;
}
const webhookService = new WebhookActivityTogglerService(authData.appId, client);
try {
await webhookService.recreateOwnWebhooks({ baseUrl, enableWebhooks: enabled });
console.log("✅ Webhooks recreated successfully");
} catch (e) {
console.error("🛑 Failed to recreate webhooks: ", e);
}
};

View file

@ -2,7 +2,7 @@
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils"; import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { recreateWebhooks } from "./recreate-webhooks"; import { updateWebhooksScript } from "./update-webhooks";
dotenv.config(); dotenv.config();
@ -21,7 +21,7 @@ const runMigration = async () => {
}); });
for (const env of allEnvs) { for (const env of allEnvs) {
await recreateWebhooks({ authData: env, dryRun: true }); await updateWebhooksScript({ authData: env, dryRun: true });
} }
console.log("Migration dry run complete"); console.log("Migration dry run complete");

View file

@ -2,7 +2,7 @@
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils"; import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { recreateWebhooks } from "./recreate-webhooks"; import { updateWebhooksScript } from "./update-webhooks";
dotenv.config(); dotenv.config();
@ -21,7 +21,7 @@ const runMigration = async () => {
}); });
for (const env of allEnvs) { for (const env of allEnvs) {
await recreateWebhooks({ authData: env, dryRun: false }); await updateWebhooksScript({ authData: env, dryRun: false });
} }
console.log("Migration complete"); console.log("Migration complete");

View file

@ -0,0 +1,47 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { createGraphQLClient } from "@saleor/apps-shared";
import { AuthData } from "@saleor/app-sdk/APL";
import { webhookMigrationRunner } from "@saleor/webhook-utils";
import { appWebhooks } from "../../webhooks";
export const updateWebhooksScript = async ({
authData,
dryRun,
}: {
authData: AuthData;
dryRun: boolean;
}) => {
console.log("Working on env: ", authData.saleorApiUrl);
const client = createGraphQLClient({
saleorApiUrl: authData.saleorApiUrl,
token: authData.token,
});
await webhookMigrationRunner({
client,
dryRun,
getManifests: async ({ appDetails }) => {
const webhooks = appDetails.webhooks;
if (!webhooks?.length) {
console.info("The environment does not have any webhooks, skipping");
return [];
}
// All webhooks in this application are turned on or off. If any of them is enabled, we enable all of them.
const enabled = webhooks.some((w) => w.isActive);
const targetUrl = appDetails.appUrl;
if (!targetUrl?.length) {
throw new Error("App has no defined appUrl, skipping");
}
const baseUrl = new URL(targetUrl).origin;
return appWebhooks.map((w) => ({ ...w.getWebhookManifest(baseUrl), enabled }));
},
});
};

View file

@ -78,9 +78,15 @@ export class AlgoliaSearchProvider implements SearchProvider {
"productId", "productId",
"inStock", "inStock",
"categories", "categories",
"grossPrice",
"attributes", "attributes",
"collections", "collections",
"pricing.price.net",
"pricing.price.gross",
"pricing.discount.net",
"pricing.discount.gross",
"pricing.priceUndiscounted.net",
"pricing.priceUndiscounted.gross",
"pricing.onSale",
], ],
attributeForDistinct: "productId", attributeForDistinct: "productId",
numericAttributesForFiltering: ["grossPrice"], numericAttributesForFiltering: ["grossPrice"],

View file

@ -134,7 +134,25 @@ export function productAndVariantToAlgolia({
descriptionPlaintext: EditorJsPlaintextRenderer({ stringData: product.description }), descriptionPlaintext: EditorJsPlaintextRenderer({ stringData: product.description }),
slug: product.slug, slug: product.slug,
thumbnail: product.thumbnail?.url, thumbnail: product.thumbnail?.url,
/**
* Deprecated
*/
grossPrice: listing?.price?.amount, grossPrice: listing?.price?.amount,
pricing: {
price: {
net: variant.pricing?.price?.net.amount,
gross: variant.pricing?.price?.gross.amount,
},
onSale: variant.pricing?.onSale,
discount: {
net: variant.pricing?.discount?.net.amount,
gross: variant.pricing?.discount?.gross.amount,
},
priceUndiscounted: {
net: variant.pricing?.priceUndiscounted?.net.amount,
gross: variant.pricing?.priceUndiscounted?.gross.amount,
},
},
inStock, inStock,
categories: categoryHierarchicalFacets(variant), categories: categoryHierarchicalFacets(variant),
collections: product.collections?.map((collection) => collection.name) || [], collections: product.collections?.map((collection) => collection.name) || [],

View file

@ -1,5 +1,14 @@
# saleor-app-segment # saleor-app-segment
## 1.0.3
### Patch Changes
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 1.0.2 ## 1.0.2
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-segment", "name": "saleor-app-segment",
"version": "1.0.2", "version": "1.0.3",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -12,7 +12,7 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.1.0", "@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1", "@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*", "@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*", "@saleor/apps-ui": "workspace:*",
@ -45,7 +45,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "4.0.1", "@graphql-codegen/typescript": "4.0.1",

View file

@ -1,5 +1,13 @@
# saleor-app-slack # saleor-app-slack
## 1.8.7
### Patch Changes
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
## 1.8.6 ## 1.8.6
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-slack", "name": "saleor-app-slack",
"version": "1.8.6", "version": "1.8.7",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -37,7 +37,7 @@
"vitest": "0.34.1" "vitest": "0.34.1"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -1,5 +1,19 @@
# saleor-app-taxes # saleor-app-taxes
## 1.16.0
### Minor Changes
- a32fe7c: Added logs for AvaTax and TaxJar. Logs are stored in the app metadata. Only the last 100 events are stored. Each provider configuration has its own logs. You can get to them by a new button "Logs" in the provider table.
### Patch Changes
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 2e29699: Updated Sentry package
- 2951fb3: [skip ci]: Bump jotai from 2.0.0 to 2.4.2
## 1.15.2 ## 1.15.2
### Patch Changes ### Patch Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-app-taxes", "name": "saleor-app-taxes",
"version": "1.15.2", "version": "1.16.0",
"scripts": { "scripts": {
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -12,7 +12,7 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^2.9.10", "@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1", "@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*", "@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*", "@saleor/apps-ui": "workspace:*",
@ -29,7 +29,7 @@
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"graphql": "16.7.1", "graphql": "16.7.1",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"jotai": "^2.0.0", "jotai": "^2.4.2",
"jsdom": "^20.0.3", "jsdom": "^20.0.3",
"next": "13.4.8", "next": "13.4.8",
"pino": "^8.14.1", "pino": "^8.14.1",
@ -45,7 +45,7 @@
"zod": "3.21.4" "zod": "3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "4.0.1", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0", "@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0", "@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -68,7 +68,8 @@
"urql", "urql",
"Protos", "Protos",
"pino", "pino",
"IFRAME" "IFRAME",
"dedupe"
], ],
"ignorePaths": [ "ignorePaths": [
"node_modules", "node_modules",
@ -80,6 +81,7 @@
"**/*.test.ts", "**/*.test.ts",
"**/*.spec.ts", "**/*.spec.ts",
"**/graphql.ts", "**/graphql.ts",
"**/CHANGELOG.md" "**/CHANGELOG.md",
"**/schema.graphql"
] ]
} }

View file

@ -39,15 +39,15 @@
"*.{ts,tsx,md,js,jsx}": "cspell --no-must-find-files" "*.{ts,tsx,md,js,jsx}": "cspell --no-must-find-files"
}, },
"packageManager": "pnpm@8.7.4", "packageManager": "pnpm@8.7.4",
"pnpm": {
"overrides": {
"@saleor/app-sdk": "0.43.1"
}
},
"private": true, "private": true,
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"packages/*", "packages/*",
"templates/*" "templates/*"
], ]
"pnpm": {
"overrides": {
"@saleor/app-sdk": "0.43.1"
}
}
} }

View file

@ -0,0 +1,4 @@
{
"root": true,
"extends": ["saleor"]
}

View file

@ -0,0 +1,17 @@
schema: graphql/schema.graphql
documents: [graphql/**/*.graphql, src/**/*.ts, src/**/*.tsx]
extensions:
codegen:
overwrite: true
generates:
generated/graphql.ts:
config:
dedupeFragments: true
dedupeOperationSuffix: true
plugins:
- typescript
- typescript-operations
- typed-document-node
generated/schema.graphql:
plugins:
- schema-ast

View file

@ -0,0 +1,9 @@
# @saleor/webhook-utils
## 0.0.1
### Patch Changes
- 5dee65a: Updated dependencies:
- @graphql-codegen/cli@5.0.0
- 7e0755e: Introduced a new shared package with helpers used for webhook management.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
fragment AppDetailsFragment on App {
id
appUrl
name
webhooks{
...WebhookDetailsFragment
}
metadata{
key
value
}
privateMetadata{
key
value
}
}

View file

@ -0,0 +1,6 @@
fragment ChannelDetailsFragment on Channel {
id
slug
name
currencyCode
}

View file

@ -0,0 +1,6 @@
fragment ShopDetailsFragment on Shop {
version
domain {
url
}
}

View file

@ -0,0 +1,15 @@
fragment WebhookDetailsFragment on Webhook {
id
isActive
name
targetUrl
subscriptionQuery
syncEvents{
name
eventType
}
asyncEvents {
name
eventType
}
}

View file

@ -0,0 +1,12 @@
mutation CreateAppWebhook($input: WebhookCreateInput!) {
webhookCreate(input:$input){
webhook{
...WebhookDetailsFragment
}
errors{
code
field
message
}
}
}

View file

@ -0,0 +1,10 @@
mutation ModifyAppWebhook($id: ID!, $input: WebhookUpdateInput!) {
webhookUpdate(id: $id, input: $input) {
errors {
message
}
webhook {
...WebhookDetailsFragment
}
}
}

View file

@ -0,0 +1,8 @@
mutation RemoveAppWebhook($id: ID!) {
webhookDelete(id: $id){
errors{
field
message
}
}
}

View file

@ -0,0 +1,5 @@
query GetAppDetailsAndWebhooksData {
app{
...AppDetailsFragment
}
}

View file

@ -0,0 +1,8 @@
query GetSaleorInstanceData {
shop{
...ShopDetailsFragment
}
channels{
...ChannelDetailsFragment
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
export * from "./src/operations/create-app-webhook";
export * from "./src/operations/modify-app-webhook";
export * from "./src/operations/remove-app-webhook";
export * from "./src/operations/get-app-details-and-webhooks-data";
export * from "./src/webhook-migration-runner";

View file

@ -0,0 +1,46 @@
{
"name": "@saleor/webhook-utils",
"version": "0.0.1",
"scripts": {
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen",
"lint:fix": "eslint --fix .",
"test": "vitest"
},
"dependencies": {
"@saleor/app-sdk": "0.41.1",
"@saleor/apps-shared": "workspace:*",
"@urql/exchange-auth": "^2.1.4",
"eslint": "8.46.0",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",
"pino": "^8.14.1",
"pino-pretty": "^10.0.0",
"semver": "^7.5.1",
"urql": "^4.0.4"
},
"devDependencies": {
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-typed-document-node/core": "3.2.0",
"@saleor/app-sdk": "0.41.1",
"@types/semver": "^7.5.0",
"eslint-config-saleor": "workspace:*",
"typescript": "5.1.6",
"urql": "^4.0.4",
"vite": "4.4.8",
"vitest": "0.34.1"
},
"main": "index.ts",
"peerDependencies": {
"next": "13.3.0",
"pino": "^8.14.1",
"pino-pretty": "^10.0.0"
},
"saleor": {
"schemaVersion": "3.7"
}
}

View file

@ -0,0 +1,26 @@
import { Client } from "urql";
import { WebhookEventTypeAsyncEnum, WebhookEventTypeSyncEnum } from "../generated/graphql";
import { WebhookManifest } from "@saleor/app-sdk/types";
import { createAppWebhook } from "./operations/create-app-webhook";
interface CreateAppWebhookFromManifestArgs {
client: Client;
webhookManifest: WebhookManifest;
}
export const createAppWebhookFromManifest = async ({
client,
webhookManifest,
}: CreateAppWebhookFromManifestArgs) => {
return createAppWebhook({
client,
input: {
asyncEvents: webhookManifest.asyncEvents as WebhookEventTypeAsyncEnum[],
syncEvents: webhookManifest.syncEvents as WebhookEventTypeSyncEnum[],
isActive: webhookManifest.isActive,
name: webhookManifest.name,
targetUrl: webhookManifest.targetUrl,
query: webhookManifest.query,
},
});
};

View file

@ -0,0 +1,25 @@
import { Client } from "urql";
import { WebhookDetailsFragment } from "../generated/graphql";
import { createAppWebhook } from "./operations/create-app-webhook";
interface CreateAppWebhookFromWebhookDetailsFragmentArgs {
client: Client;
webhookDetails: WebhookDetailsFragment;
}
export const createAppWebhookFromWebhookDetailsFragment = async ({
client,
webhookDetails,
}: CreateAppWebhookFromWebhookDetailsFragmentArgs) => {
return createAppWebhook({
client,
input: {
asyncEvents: webhookDetails.asyncEvents.map((event) => event.eventType),
syncEvents: webhookDetails.syncEvents.map((event) => event.eventType),
isActive: webhookDetails.isActive,
name: webhookDetails.name,
targetUrl: webhookDetails.targetUrl,
query: webhookDetails.subscriptionQuery,
},
});
};

View file

@ -0,0 +1,17 @@
import { Client } from "urql";
import { modifyAppWebhook } from "./operations/modify-app-webhook";
interface DisableWebhookArgs {
client: Client;
webhookId: string;
}
export const disableWebhook = async ({ client, webhookId }: DisableWebhookArgs) => {
return modifyAppWebhook({
client,
webhookId,
input: {
isActive: false,
},
});
};

View file

@ -0,0 +1,17 @@
import { Client } from "urql";
import { modifyAppWebhook } from "./operations/modify-app-webhook";
interface enableWebhookArgs {
client: Client;
webhookId: string;
}
export const enableWebhook = async ({ client, webhookId }: enableWebhookArgs) => {
await modifyAppWebhook({
client,
webhookId,
input: {
isActive: true,
},
});
};

View file

@ -0,0 +1,101 @@
import { describe, expect, it } from "vitest";
import { getWebhookIdsAndManifestsToUpdate } from "./get-webhook-ids-and-manifests-to-update";
describe("getWebhookIdsAndQueriesToUpdate", () => {
it("Returns an empty list, when no data is passed", () => {
expect(
getWebhookIdsAndManifestsToUpdate({
existingWebhooksPartial: [],
newWebhookManifests: [],
}),
).toStrictEqual([]);
});
it("Returns all of the entries, when new webhook manifests contain the same webhooks as existing list", () => {
expect(
getWebhookIdsAndManifestsToUpdate({
existingWebhooksPartial: [
{ id: "1", name: "webhook1" },
{ id: "2", name: "webhook2" },
],
newWebhookManifests: [
{
asyncEvents: [],
isActive: true,
name: "webhook1",
query: "newQuery1",
syncEvents: [],
targetUrl: "",
},
{
asyncEvents: [],
isActive: true,
name: "webhook2",
query: "newQuery2",
syncEvents: [],
targetUrl: "",
},
],
}),
).toStrictEqual([
{
webhookId: "1",
webhookManifest: {
asyncEvents: [],
isActive: true,
name: "webhook1",
query: "newQuery1",
syncEvents: [],
targetUrl: "",
},
},
{
webhookId: "2",
webhookManifest: {
asyncEvents: [],
isActive: true,
name: "webhook2",
query: "newQuery2",
syncEvents: [],
targetUrl: "",
},
},
]);
});
it("Returns subset of entries, when existing webhook list contain some of them", () => {
expect(
getWebhookIdsAndManifestsToUpdate({
existingWebhooksPartial: [{ id: "1", name: "webhook1" }],
newWebhookManifests: [
{
asyncEvents: [],
isActive: true,
name: "webhook1",
query: "newQuery1",
syncEvents: [],
targetUrl: "",
},
{
asyncEvents: [],
isActive: true,
name: "webhook2",
query: "newQuery2",
syncEvents: [],
targetUrl: "",
},
],
}),
).toStrictEqual([
{
webhookId: "1",
webhookManifest: {
asyncEvents: [],
isActive: true,
name: "webhook1",
query: "newQuery1",
syncEvents: [],
targetUrl: "",
},
},
]);
});
});

View file

@ -0,0 +1,38 @@
import { WebhookManifest } from "@saleor/app-sdk/types";
type WebhookPartial = { id: string; name: string };
interface GetWebhookIdsAndQueriesToUpdateArgs<T extends WebhookPartial> {
newWebhookManifests: Array<WebhookManifest>;
existingWebhooksPartial: Array<T>;
}
type ReturnType = { webhookId: string; webhookManifest: WebhookManifest };
// Couples the webhook id with the manifest to update
export const getWebhookIdsAndManifestsToUpdate = <T extends WebhookPartial>({
newWebhookManifests,
existingWebhooksPartial,
}: GetWebhookIdsAndQueriesToUpdateArgs<T>): Array<ReturnType> => {
return newWebhookManifests
.map((webhookManifest) => {
const existingWebhook = existingWebhooksPartial.find(
(webhook) => webhook.name === webhookManifest.name,
);
if (!existingWebhook) {
// Theres no webhook with this name, so we cant start an update
return undefined;
}
if (!webhookManifest.query) {
return undefined;
}
return {
webhookId: existingWebhook.id,
webhookManifest,
};
})
.filter((data): data is ReturnType => data !== undefined); // Filter out undefined values and narrow down the type
};

View file

@ -0,0 +1,117 @@
import { describe, expect, it } from "vitest";
import { webhooksToAdd } from "./webhooks-to-add";
describe("webhooksToAdd", () => {
it("Returns an empty list, when no data is passed", () => {
expect(
webhooksToAdd({
existingWebhooksPartial: [],
newWebhookManifests: [],
}),
).toStrictEqual([]);
});
it("Returns empty list, when new webhook manifests contain the same webhooks as existing list", () => {
expect(
webhooksToAdd({
existingWebhooksPartial: [
{ id: "1", name: "webhook1" },
{ id: "1", name: "webhook2" },
],
newWebhookManifests: [
{
asyncEvents: [],
isActive: true,
name: "webhook1",
query: "",
syncEvents: [],
targetUrl: "",
},
{
asyncEvents: [],
isActive: true,
name: "webhook2",
query: "",
syncEvents: [],
targetUrl: "",
},
],
}),
).toStrictEqual([]);
});
it("Returns all of the manifests, when existing webhook list is empty", () => {
expect(
webhooksToAdd({
existingWebhooksPartial: [],
newWebhookManifests: [
{
asyncEvents: [],
isActive: true,
name: "webhook1",
query: "",
syncEvents: [],
targetUrl: "",
},
{
asyncEvents: [],
isActive: true,
name: "webhook2",
query: "",
syncEvents: [],
targetUrl: "",
},
],
}),
).toStrictEqual([
{
asyncEvents: [],
isActive: true,
name: "webhook1",
query: "",
syncEvents: [],
targetUrl: "",
},
{
asyncEvents: [],
isActive: true,
name: "webhook2",
query: "",
syncEvents: [],
targetUrl: "",
},
]);
});
it("Returns list with the new webhook to add, when it was not specified in the existing manifests", () => {
expect(
webhooksToAdd({
existingWebhooksPartial: [{ id: "1", name: "webhookOld" }],
newWebhookManifests: [
{
asyncEvents: [],
isActive: true,
name: "webhookOld",
query: "",
syncEvents: [],
targetUrl: "",
},
{
asyncEvents: [],
isActive: true,
name: "webhookNew",
query: "",
syncEvents: [],
targetUrl: "",
},
],
}),
).toStrictEqual([
{
asyncEvents: [],
isActive: true,
name: "webhookNew",
query: "",
syncEvents: [],
targetUrl: "",
},
]);
});
});

View file

@ -0,0 +1,18 @@
import { WebhookManifest } from "@saleor/app-sdk/types";
interface WebhooksToAddArgs {
newWebhookManifests: Array<WebhookManifest>;
existingWebhooksPartial: Array<{ id: string; name: string }>;
}
export const webhooksToAdd = ({
newWebhookManifests,
existingWebhooksPartial,
}: WebhooksToAddArgs) => {
return newWebhookManifests.filter(
(newWebhookManifest) =>
!existingWebhooksPartial.find(
(existingWebhook) => newWebhookManifest.name === existingWebhook.name,
),
);
};

Some files were not shown because too many files have changed in this diff Show more