From 6473419ace8a813edd3722ea8bff66328711eadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dro=C5=84?= Date: Thu, 1 Feb 2024 11:14:37 +0100 Subject: [PATCH 1/2] Fix variant availability (#4607) * Fix improper availability on variant details page * Fix improper availability on variant details page * Fix strict typing * Add changeset * Fix multiple issues regarding availabilitiy channel list rendering * Disable create variant when no name provided * Simplify test * Trigger deployment * Fix naming * Extract mapping outside filtering * Remove unrelated messages --- .changeset/green-kings-relax.md | 5 + .../AvailabilityCard.tsx | 31 +++- .../ChannelsListItem.tsx | 23 ++- .../CreateVariantTitle.tsx | 2 +- .../availabilityCount.test.ts | 156 ++++++++++++++++++ .../availabilityCount.ts | 37 +++-- .../ChannelsAvailabilityCard/index.tsx | 92 +++++------ .../ProductVariantCreatePage.tsx | 1 + .../ProductVariantCreatePage/form.tsx | 3 +- .../ProductVariantPage/ProductVariantPage.tsx | 1 + .../components/ProductVariantPage/form.tsx | 1 + 11 files changed, 263 insertions(+), 89 deletions(-) create mode 100644 .changeset/green-kings-relax.md create mode 100644 src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.test.ts diff --git a/.changeset/green-kings-relax.md b/.changeset/green-kings-relax.md new file mode 100644 index 00000000000..2079503ed49 --- /dev/null +++ b/.changeset/green-kings-relax.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Fix variant availability diff --git a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/AvailabilityCard.tsx b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/AvailabilityCard.tsx index 4b7c519e4bc..834b65eaba2 100644 --- a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/AvailabilityCard.tsx +++ b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/AvailabilityCard.tsx @@ -1,31 +1,46 @@ +import { + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs, +} from "@dashboard/channels/utils"; import { Divider } from "@dashboard/components/Divider"; +import { FormsetData } from "@dashboard/hooks/useFormset"; import React from "react"; -import { Channel, ProductChannelListing } from "./../types"; +import { ProductChannelListing } from "./../types"; import { ChannelsListItem } from "./ChannelsListItem"; import CardContainer from "./VariantDetailsChannelsAvailabilityCardContainer"; interface AvailabilityCardProps { - items: Channel[]; + allAvailableListings: FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs + >; productChannelListings: ProductChannelListing; } export const AvailabilityCard: React.FC = ({ - items, + allAvailableListings, productChannelListings, children, }) => { - if (items.length === 0) { + if (allAvailableListings.length === 0) { return {}; } + const listingIds = allAvailableListings.map(lst => lst.id); + const filteredListings: ProductChannelListing = + productChannelListings?.filter((channel: ProductChannelListing[0]) => + listingIds.includes(channel.channel.id), + ); + return ( - {items.map(channel => ( + {filteredListings.map((listing: ProductChannelListing[0]) => ( ))} diff --git a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/ChannelsListItem.tsx b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/ChannelsListItem.tsx index f8c32123f7d..ea90009d856 100644 --- a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/ChannelsListItem.tsx +++ b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/ChannelsListItem.tsx @@ -7,27 +7,24 @@ import React from "react"; import { useIntl } from "react-intl"; import { variantDetailsChannelsAvailabilityCardMessages as messages } from "./../messages"; -import { Channel, ProductChannelListing } from "./../types"; -type ChannelsListItemProps = Pick & { - listings: ProductChannelListing; -}; +interface ChannelsListItemProps { + id: string; + name: string; + isPublished: boolean; + publicationDate: string; +} export const ChannelsListItem: React.FC = ({ id, name, - listings, + isPublished, + publicationDate, }) => { const intl = useIntl(); const localizeDate = useDateLocalize(); - const getItemSubtitle = (channelId: string) => { - const channelListing = listings.find( - ({ channel }) => channel.id === channelId, - ); - - const { isPublished, publicationDate } = channelListing; - + const getItemSubtitle = () => { if (!isPublished) { return intl.formatMessage(messages.itemSubtitleHidden); } @@ -53,7 +50,7 @@ export const ChannelsListItem: React.FC = ({ size="small" data-test-id={`channels-variant-availability-item-subtitle-${id}`} > - {getItemSubtitle(id)} + {getItemSubtitle()} diff --git a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/CreateVariantTitle.tsx b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/CreateVariantTitle.tsx index 4d40a057054..785f113de5c 100644 --- a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/CreateVariantTitle.tsx +++ b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/CreateVariantTitle.tsx @@ -8,7 +8,7 @@ import { variantDetailsChannelsAvailabilityCardMessages as messages } from "./.. interface CreateVariantTitleProps { onManageClick: () => void; disabled: boolean; - availabilityCount: Record; + availabilityCount: Record; isEmpty: boolean; } diff --git a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.test.ts b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.test.ts new file mode 100644 index 00000000000..aa2ba8df180 --- /dev/null +++ b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.test.ts @@ -0,0 +1,156 @@ +import { + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs, +} from "@dashboard/channels/utils"; +import { + ProductVariantCreateDataQuery, + ProductVariantFragment, +} from "@dashboard/graphql"; +import { FormsetData } from "@dashboard/hooks/useFormset"; + +import { + getAvailabilityCountForProduct, + getAvailabilityCountForVariant, +} from "./availabilityCount"; + +const mockedListings = [ + { + id: "1", + label: "Channel-PLN", + }, + { + id: "2", + label: "Channel-USD", + }, +] as unknown as FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs +>; + +const mockedVariant = { + product: { + channelListings: [ + { + channel: { id: "1" }, + isPublished: true, + }, + { + channel: { id: "2" }, + isPublished: true, + }, + ], + }, +} as unknown as ProductVariantFragment; + +const mockedProduct = { + channelListings: [ + { + channel: { id: "1" }, + isPublished: true, + }, + { + channel: { id: "2" }, + isPublished: true, + }, + ], +} as unknown as ProductVariantCreateDataQuery["product"]; + +describe("getAvailabilityCountForVariant", () => { + it("should return correct counts when all channels are selected", () => { + // Arrange + const item = mockedVariant; + const listings = mockedListings; + + // Act + const result = getAvailabilityCountForVariant(item, listings); + + // Assert + expect(result).toEqual({ + publishedInChannelsCount: 2, + availableChannelsCount: 2, + }); + }); + + it("should return correct counts when some channels are selected", () => { + // Arrange + const item = mockedVariant; + const listings = mockedListings.slice(0, 1); + + // Act + const result = getAvailabilityCountForVariant(item, listings); + + // Assert + expect(result).toEqual({ + publishedInChannelsCount: 1, + availableChannelsCount: 2, + }); + }); + + it("should return correct counts when no channels are available", () => { + // Arrange + const item = mockedVariant; + const listings = [] as FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs + >; + + // Act + const result = getAvailabilityCountForVariant(item, listings); + + // Assert + expect(result).toEqual({ + publishedInChannelsCount: 0, + availableChannelsCount: 2, + }); + }); +}); + +describe("getAvailabilityCountForProduct", () => { + it("should return correct counts when all channels are selected", () => { + // Arrange + const item = mockedProduct; + const listings = mockedListings; + + // Act + const result = getAvailabilityCountForProduct(item, listings); + + // Assert + expect(result).toEqual({ + publishedInChannelsCount: 2, + availableChannelsCount: 2, + }); + }); + + it("should return correct counts when some channels are selected", () => { + // Arrange + const item = mockedProduct; + const listings = mockedListings.slice(0, 1); + + // Act + const result = getAvailabilityCountForProduct(item, listings); + + // Assert + expect(result).toEqual({ + publishedInChannelsCount: 1, + availableChannelsCount: 2, + }); + }); + + it("should return correct counts when no channels are available", () => { + // Arrange + const item = mockedProduct; + const listings = [] as FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs + >; + + // Act + const result = getAvailabilityCountForProduct(item, listings); + + // Assert + expect(result).toEqual({ + publishedInChannelsCount: 0, + availableChannelsCount: 2, + }); + }); +}); diff --git a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.ts b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.ts index 452022d3f72..2f72b3d54f3 100644 --- a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.ts +++ b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/availabilityCount.ts @@ -1,39 +1,48 @@ -// @ts-strict-ignore +import { + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs, +} from "@dashboard/channels/utils"; import { ProductVariantCreateDataQuery, ProductVariantFragment, } from "@dashboard/graphql"; +import { FormsetData } from "@dashboard/hooks/useFormset"; export const getAvailabilityCountForVariant = ( item: ProductVariantFragment, + listings: FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs + >, ) => { - const variantChannelListingsChannelsIds = item.channelListings.map( + const allAvailableChannelsListings = item?.product?.channelListings?.map( ({ channel: { id } }) => id, ); - const allAvailableChannelsListings = item.product.channelListings.filter( - ({ channel }) => variantChannelListingsChannelsIds.includes(channel.id), - ); - - const publishedInChannelsListings = allAvailableChannelsListings.filter( - ({ isPublished }) => isPublished, + const selectedChannelListings = allAvailableChannelsListings?.filter( + listing => listings.some(lst => lst.id === listing), ); return { - publishedInChannelsCount: publishedInChannelsListings.length, - availableChannelsCount: allAvailableChannelsListings.length, + publishedInChannelsCount: selectedChannelListings?.length, + availableChannelsCount: allAvailableChannelsListings?.length, }; }; export const getAvailabilityCountForProduct = ( item: ProductVariantCreateDataQuery["product"], + listings: FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs + >, ) => { - const publishedInChannelsListings = item.channelListings.filter( - ({ isPublished }) => isPublished, + const listingsIds = listings.map(({ id }) => id); + const publishedInChannelsListings = item?.channelListings?.filter(lst => + listingsIds.includes(lst.channel.id), ); return { - publishedInChannelsCount: publishedInChannelsListings.length, - availableChannelsCount: item.channelListings.length, + publishedInChannelsCount: publishedInChannelsListings?.length, + availableChannelsCount: item?.channelListings?.length, }; }; diff --git a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/index.tsx b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/index.tsx index cfd0ef5adec..41db071bf62 100644 --- a/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/index.tsx +++ b/src/products/components/ProductVariantChannels/ChannelsAvailabilityCard/index.tsx @@ -1,78 +1,66 @@ -// @ts-strict-ignore +import { + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs, +} from "@dashboard/channels/utils"; +import { FormsetData } from "@dashboard/hooks/useFormset"; import React from "react"; -import { Channel, Product, Variant } from "./../types"; +import { Product, Variant } from "./../types"; import { AvailabilityCard } from "./AvailabilityCard"; import { getAvailabilityCountForProduct, getAvailabilityCountForVariant, } from "./availabilityCount"; -import { CardSkeleton } from "./CardSkeleton"; import { CreateVariantTitle } from "./CreateVariantTitle"; interface VariantDetailsChannelsAvailabilityCardProps { variant: Variant; - onManageClick?: () => void; + listings: FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs + >; + onManageClick: () => void; disabled: boolean; } interface ProductDetailsChannelsAvailabilityCardProps { product: Product; - onManageClick?: () => void; + listings: FormsetData< + ChannelPriceAndPreorderData, + IChannelPriceAndPreorderArgs + >; + onManageClick: () => void; disabled: boolean; } -interface WrapperProps { - item: Product | Variant; - children: ({ channels }: { channels: Channel[] }) => React.ReactElement; -} - -const Wrapper: React.FC = ({ item, children }) => { - if (!item) { - return ; - } - - const channels = item.channelListings.map(({ channel }) => channel); - - return children({ channels }); -}; - export const VariantDetailsChannelsAvailabilityCard: React.FC< VariantDetailsChannelsAvailabilityCardProps -> = ({ variant, onManageClick, disabled }) => ( - - {({ channels }) => ( - - - - )} - +> = ({ variant, listings, onManageClick, disabled }) => ( + + + ); export const ProductDetailsChannelsAvailabilityCard: React.FC< ProductDetailsChannelsAvailabilityCardProps -> = ({ product, onManageClick, disabled }) => ( - - {({ channels }) => ( - - - - )} - +> = ({ product, listings, onManageClick, disabled }) => ( + + + ); diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index ac2bec1e1f8..435201f9baf 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -208,6 +208,7 @@ const ProductVariantCreatePage: React.FC = ({ diff --git a/src/products/components/ProductVariantCreatePage/form.tsx b/src/products/components/ProductVariantCreatePage/form.tsx index 1036f1fe2a9..8fb7dedf876 100644 --- a/src/products/components/ProductVariantCreatePage/form.tsx +++ b/src/products/components/ProductVariantCreatePage/form.tsx @@ -339,7 +339,8 @@ function useProductVariantCreateForm( !!form.errors.preorderEndDateTime; const formDisabled = invalidPreorder || invalidChannels; - const isSaveDisabled = disabled || formDisabled || !onSubmit; + const isSaveDisabled = + disabled || formDisabled || !data.variantName || !onSubmit; setIsSubmitDisabled(isSaveDisabled); diff --git a/src/products/components/ProductVariantPage/ProductVariantPage.tsx b/src/products/components/ProductVariantPage/ProductVariantPage.tsx index 6c46a569fd3..3011faf2983 100644 --- a/src/products/components/ProductVariantPage/ProductVariantPage.tsx +++ b/src/products/components/ProductVariantPage/ProductVariantPage.tsx @@ -264,6 +264,7 @@ const ProductVariantPage: React.FC = ({ diff --git a/src/products/components/ProductVariantPage/form.tsx b/src/products/components/ProductVariantPage/form.tsx index 6155e1af73e..4b5b9bd86d5 100644 --- a/src/products/components/ProductVariantPage/form.tsx +++ b/src/products/components/ProductVariantPage/form.tsx @@ -231,6 +231,7 @@ function useProductVariantUpdateForm( const attributesWithNewFileValue = useFormset([]); const stocks = useFormset(stockInput); const channels = useFormset(channelsInput); + const { isMetadataModified, isPrivateMetadataModified, From 9a48841357a146cc17a734dcb1faf6f5ef3de676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Wcis=C5=82o?= <115464873+wcislo-saleor@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:02:14 +0100 Subject: [PATCH 2/2] Fix action that configures login to staging Saleor Cloud for CLI (#4658) --- .changeset/neat-apples-bake.md | 5 +++++ .github/actions/cli-login/action.yml | 9 ++++++--- .github/actions/prepare-backups-variables/action.yml | 1 - .github/actions/prepare-instance/action.yml | 5 +---- .github/workflows/pr-cleanup.yml | 3 +-- 5 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 .changeset/neat-apples-bake.md diff --git a/.changeset/neat-apples-bake.md b/.changeset/neat-apples-bake.md new file mode 100644 index 00000000000..f40c819ced4 --- /dev/null +++ b/.changeset/neat-apples-bake.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Fix action that configures login to staging Saleor Cloud for CLI diff --git a/.github/actions/cli-login/action.yml b/.github/actions/cli-login/action.yml index 95d8243ad50..04c7dce4d83 100644 --- a/.github/actions/cli-login/action.yml +++ b/.github/actions/cli-login/action.yml @@ -1,8 +1,8 @@ name: Saleor CLI login -description: Saleor CLI login +description: Saleor CLI login to staging Saleor Cloud inputs: token: - description: "Cloud accces token" + description: "Cloud access token" required: true runs: using: "composite" @@ -12,4 +12,7 @@ runs: id: write-config-file env: ACCESS_TOKEN: ${{ inputs.token }} - run: jq --null-input --arg token "Token $ACCESS_TOKEN" '{"token":$token,"telemetry":"false","organization_slug":"saleor","organization_name":"Saleor"}' > ~/.config/saleor.json + run: | + jq --null-input \ + --arg token "Token $ACCESS_TOKEN" \ + '{"token":$token,"telemetry":"false","saleor_env":"staging","cloud_api_url":"https://staging-cloud.saleor.io/platform/api","organization_slug":"saleor","organization_name":"Saleor"}' > ~/.config/saleor.json diff --git a/.github/actions/prepare-backups-variables/action.yml b/.github/actions/prepare-backups-variables/action.yml index c46c3457a74..fa0060d0d47 100644 --- a/.github/actions/prepare-backups-variables/action.yml +++ b/.github/actions/prepare-backups-variables/action.yml @@ -29,7 +29,6 @@ runs: id: backup shell: bash env: - SALEOR_CLI_ENV: staging BACKUP_NAME: ${{ inputs.BACKUP_NAMESPACE }} run: | BACKUPS=$(npx saleor backup list --name="$BACKUP_NAME" --latest --json) diff --git a/.github/actions/prepare-instance/action.yml b/.github/actions/prepare-instance/action.yml index 6b8efed3fd4..68878357dae 100644 --- a/.github/actions/prepare-instance/action.yml +++ b/.github/actions/prepare-instance/action.yml @@ -36,7 +36,6 @@ runs: shell: bash id: instance_check env: - SALEOR_CLI_ENV: staging INSTANCE_NAME: ${{ inputs.POOL_NAME }} run: | set +o pipefail @@ -47,7 +46,6 @@ runs: shell: bash if: ${{ steps.instance_check.outputs.INSTANCE_KEY }} env: - SALEOR_CLI_ENV: staging BACKUP_ID: ${{ inputs.BACKUP_ID }} INSTANCE_NAME: ${{ inputs.POOL_NAME }} run: | @@ -59,7 +57,6 @@ runs: shell: bash if: ${{ !steps.instance_check.outputs.INSTANCE_KEY }} env: - SALEOR_CLI_ENV: staging BACKUP_ID: ${{ inputs.BACKUP_ID }} INSTANCE_NAME: ${{ inputs.POOL_NAME }} run: | @@ -70,4 +67,4 @@ runs: --saleor=saleor-master-staging \ --domain="$INSTANCE_NAME" \ --skip-restrict \ - --skip-webhooks-update \ No newline at end of file + --skip-webhooks-update diff --git a/.github/workflows/pr-cleanup.yml b/.github/workflows/pr-cleanup.yml index e3043d03c40..e94a1418860 100644 --- a/.github/workflows/pr-cleanup.yml +++ b/.github/workflows/pr-cleanup.yml @@ -24,6 +24,5 @@ jobs: - name: Remove instance env: - SALEOR_CLI_ENV: staging PULL_REQUEST_NUMBER: ${{ github.event.number }} - run: npx saleor env remove "pr-${PULL_REQUEST_NUMBER}" --force \ No newline at end of file + run: npx saleor env remove "pr-${PULL_REQUEST_NUMBER}" --force