Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/whole-feet-win.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-app-products-feed": minor
---

change the s3 configuration form of app/product-feed to be more generic, to allow custom endpoints, regions and allow to toggle path style endpoints.
14 changes: 8 additions & 6 deletions apps/products-feed/generated/graphql.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ fragment GoogleFeedProductVariant on ProductVariant {
url
}
category {
slug
id
name
googleCategoryId: metafield(key: "google_category_id")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
fragment RelatedProducts on Product {
id
name
slug
description
seoDescription
productType {
isShippingRequired
}
media {
id
alt
url(size: $imageSize)
type
}
variants {
id
name
slug
description
seoDescription
productType {
isShippingRequired
}
media {
id
alt
url(size: $imageSize)
type
}
variants {
id
media{
id
alt
url(size: $imageSize)
type
}
id
alt
url(size: $imageSize)
type
}
attributes{
attribute{
id
}
values{
value
name
}
}
attributes {
attribute {
id
}
thumbnail(size: $imageSize) {
url
values {
value
name
}
category {
id
name
googleCategoryId: metafield(key: "google_category_id")
}

}
thumbnail(size: $imageSize) {
url
}
category {
id
slug
name
googleCategoryId: metafield(key: "google_category_id")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const exampleChannelConfig: RootConfig["channelConfig"] = {
};

const exampleS3Config: RootConfig["s3"] = {
forcePathStyle: false,
accessKeyId: "example-access-key",
bucketName: "example-bucket-name",
region: "eu-west-1",
Expand Down Expand Up @@ -114,6 +115,7 @@ describe("AppConfig", function () {
bucketName: "bucket",
accessKeyId: "access",
secretAccessKey: "secret",
forcePathStyle: false,
},
channelConfig: {},
attributeMapping: {
Expand All @@ -139,6 +141,7 @@ describe("AppConfig", function () {
bucketName: "bucket",
accessKeyId: "access",
secretAccessKey: "secret",
forcePathStyle: false,
},
channelConfig: {},
attributeMapping: {
Expand All @@ -163,6 +166,7 @@ describe("AppConfig", function () {
bucketName: "bucket",
accessKeyId: "access",
secretAccessKey: "secret",
forcePathStyle: false,
},
channelConfig: {
test: {
Expand Down Expand Up @@ -192,6 +196,7 @@ describe("AppConfig", function () {
bucketName: "bucket",
accessKeyId: "access",
secretAccessKey: "secret",
forcePathStyle: false,
},
channelConfig: {
test: {
Expand Down Expand Up @@ -222,6 +227,7 @@ describe("AppConfig", function () {
bucketName: "bucket",
accessKeyId: "access",
secretAccessKey: "secret",
forcePathStyle: false,
},
channelConfig: {
test: {
Expand Down Expand Up @@ -256,6 +262,7 @@ describe("AppConfig", function () {

it("getS3Config gets s3 data", () => {
expect(instance.getS3Config()).toStrictEqual({
forcePathStyle: false,
region: "region",
bucketName: "bucket",
accessKeyId: "access",
Expand All @@ -281,6 +288,7 @@ describe("AppConfig", function () {
const instance = new AppConfig();

instance.setS3({
forcePathStyle: false,
region: "region",
bucketName: "bucket",
accessKeyId: "access",
Expand All @@ -292,6 +300,7 @@ describe("AppConfig", function () {
bucketName: "bucket",
accessKeyId: "access",
secretAccessKey: "secret",
forcePathStyle: false,
});

// @ts-expect-error
Expand Down Expand Up @@ -320,6 +329,7 @@ describe("AppConfig", function () {
const instance = new AppConfig();

instance.setS3({
forcePathStyle: false,
region: "region",
bucketName: "bucket",
accessKeyId: "access",
Expand All @@ -337,6 +347,7 @@ describe("AppConfig", function () {
bucketName: "bucket",
accessKeyId: "access",
secretAccessKey: "secret",
forcePathStyle: false,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const s3ConfigSchema = z.object({
secretAccessKey: z.string().min(1),
accessKeyId: z.string().min(1),
region: z.string().min(1),
endpoint: z.string().optional(),
forcePathStyle: z.boolean().default(false),
});

const urlConfigurationSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const appConfigurationRouter = router({
try {
await checkBucketAccess({
bucketName: input.bucketName,
endpoint: input?.endpoint,
s3Client,
});
logger.info("Verification succeeded");
Expand All @@ -59,8 +60,9 @@ export const appConfigurationRouter = router({
});
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Could not access the S3 bucket using the provided credentials. Check permissions",
message: `Could not access the S3 bucket using the provided credentials. ${(
e as Error
).toString()}`,
});
}
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { actions, DashboardEventFactory, RedirectAction } from "@saleor/app-sdk/app-bridge";
import { useDashboardNotification } from "@saleor/apps-shared/use-dashboard-notification";
import { TextLink } from "@saleor/apps-ui";
import { Box, Button, Text } from "@saleor/macaw-ui";
import { Input, Select } from "@saleor/react-hook-form-macaw";
import { Combobox, Input, Toggle } from "@saleor/react-hook-form-macaw";
import { useCallback, useMemo } from "react";
import { useForm } from "react-hook-form";

import { appBridgeInstance } from "@/pages/_app";

import { awsRegionList } from "../file-storage/s3/aws-region-list";
import { trpcClient } from "../trpc/trpc-client";
import { AppConfigSchema, RootConfig } from "./app-config";
Expand Down Expand Up @@ -33,37 +37,66 @@ export const S3ConfigurationForm = (props: Props) => {
props.onSubmit(data);
})}
>
<Input size={"small"} name={"accessKeyId"} control={control} label="Amazon access key ID" />
<Input size={"small"} name={"accessKeyId"} control={control} label="Access key ID" />

<Input
type={"password"}
size={"small"}
name={"secretAccessKey"}
control={control}
label="Amazon secret access key"
label="Secret access key"
/>

<Input size={"small"} name={"bucketName"} control={control} label="Bucket name" />

<Select
<Combobox
required={true}
name={"region"}
control={control}
label="Region"
name={"region"}
options={awsRegionList.map((region) => ({ label: region, value: region }))}
/>

<Box display={"flex"} flexDirection={"row"} gap={4} justifyContent={"flex-end"}>
<Button variant="secondary" onClick={() => props.onValidate(getValues())}>
Test credentials
</Button>
<Button type="submit" variant="primary">
Save bucket configuration
</Button>
<Input
required={false}
size={"small"}
name={"endpoint"}
control={control}
label="Endpoint (Leave empty for AWS)"
/>

<Box as="label" display="flex" gap={2} cursor="pointer">
<Toggle name={"forcePathStyle"} control={control} type="button" />
<Text marginLeft={2}>Force path-style endpoint</Text>
</Box>

<Box display={"flex"} flexDirection={"row"} gap={4} justifyContent={"space-between"}>
<TextLink href="" onClick={gotoDocumentation}>
Documentation
</TextLink>

<Box display={"flex"} flexDirection={"row"} gap={4} justifyContent={"flex-end"}>
<Button variant="secondary" onClick={() => props.onValidate(getValues())}>
Test credentials
</Button>
<Button type="submit" variant="primary">
Save bucket configuration
</Button>
</Box>
</Box>
</Box>
);
};

export const gotoDocumentation = async () => {
await appBridgeInstance?.dispatch(
actions.Redirect({
to: "https://docs.saleor.io/developer/app-store/apps/product-feed#s3-bucket",
newContext: true,
}),
);
};

export const ConnectedS3ConfigurationForm = () => {
const { notifyError, notifySuccess } = useDashboardNotification();

Expand Down Expand Up @@ -117,6 +150,7 @@ export const ConnectedS3ConfigurationForm = () => {
bucketName: "",
region: "",
secretAccessKey: "",
forcePathStyle: false,
};
}, [data]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,28 @@ import {
interface checkBucketAccessArgs {
s3Client: S3Client;
bucketName: string;
endpoint?: string;
}

// Check if client can access the bucket. Throws an error otherwise
export const checkBucketAccess = async ({ s3Client, bucketName }: checkBucketAccessArgs) => {
export const checkBucketAccess = async ({
s3Client,
bucketName,
endpoint,
}: checkBucketAccessArgs) => {
const file = "saleor_product_feed_temp/verification_file";

if (endpoint) {
try {
new URL(endpoint);
} catch (e) {
throw new Error(
"Failed to check access, verify endpoint URL is a valid URL (Has to start with 'http(s)://')",
{ cause: e },
);
}
}

const putCommand = new PutObjectCommand({
Bucket: bucketName,
Key: file,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ export const createS3ClientFromConfiguration = ({
accessKeyId,
secretAccessKey,
region,
endpoint,
forcePathStyle,
}: Exclude<RootConfig["s3"], null>) => {
return new S3Client({
endpoint,
forcePathStyle,
credentials: {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe("GoogleFeedSettingsFetcher", () => {
},
},
s3: {
forcePathStyle: false,
accessKeyId: "accessKeyId",
bucketName: "bucketName",
region: "region",
Expand Down Expand Up @@ -53,6 +54,7 @@ describe("GoogleFeedSettingsFetcher", () => {
secretAccessKey: "secretAccessKey",
accessKeyId: "accessKeyId",
region: "region",
forcePathStyle: false,
},
attributeMapping: null,
titleTemplate: "{{ variant.name }}",
Expand Down
3 changes: 2 additions & 1 deletion apps/products-feed/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"DYNAMODB_REQUEST_TIMEOUT_MS",
"DYNAMODB_CONNECTION_TIMEOUT_MS",
"MANIFEST_APP_ID",
"FILE_APL_PATH"
"FILE_APL_PATH",
"APP_API_BASE_URL"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@djkato duplicated line with L9

]
}
}
Expand Down
Loading