From b386cf060fc07e125e0b088abdb6e737a2802e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dro=C5=84?= Date: Wed, 12 Jul 2023 14:04:50 +0200 Subject: [PATCH] Add order expiration (#3843) * Add TTL expiration & migrate some components in channel details * Migrate button * Update fixtures * Add changeset * Typo & lint * Reorder UI elements * Set custom height to match old input * Add explanatory comment * test - created test for expired orderes functionality in channels, fixed validation test which checks using same slug twice when creating channel * Fix create page * Fix lint issue * Fix error handling double notification * tests added: creating channel with expiration days for orders, creating channel with expiration days for orders with boundary conditions exciding requirements * function name typeExpirationDate changed to typeExpirationDays --------- Co-authored-by: wojteknowacki --- .changeset/lemon-beds-raise.md | 5 + .../e2e/configuration/channels/channels.js | 75 ++++- .../channels/add-channel-form-selectors.js | 1 + .../elements/channels/channels-selectors.js | 3 +- cypress/fixtures/messages.js | 1 + cypress/support/pages/channelsPage.js | 42 ++- locale/defaultMessages.json | 12 +- .../components/ChannelForm/ChannelForm.tsx | 273 ++++++++---------- .../components/ChannelForm/messages.ts | 44 +++ .../ChannelStatus/ChannelStatus.tsx | 5 +- src/channels/fixtures.ts | 8 + .../ChannelDetailsPage/ChannelDetailsPage.tsx | 1 + .../views/ChannelCreate/ChannelCreate.tsx | 13 +- .../views/ChannelDetails/ChannelDetails.tsx | 2 + src/fragments/channels.ts | 1 + src/graphql/hooks.generated.ts | 1 + src/graphql/types.generated.ts | 16 +- 17 files changed, 321 insertions(+), 182 deletions(-) create mode 100644 .changeset/lemon-beds-raise.md create mode 100644 src/channels/components/ChannelForm/messages.ts diff --git a/.changeset/lemon-beds-raise.md b/.changeset/lemon-beds-raise.md new file mode 100644 index 000000000..9edaf5e28 --- /dev/null +++ b/.changeset/lemon-beds-raise.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Add order exipration TTL field to channel settings diff --git a/cypress/e2e/configuration/channels/channels.js b/cypress/e2e/configuration/channels/channels.js index 4e663e924..8d226fcd7 100644 --- a/cypress/e2e/configuration/channels/channels.js +++ b/cypress/e2e/configuration/channels/channels.js @@ -8,6 +8,7 @@ import { CHANNELS_SELECTORS } from "../../../elements/channels/channels-selector import { SELECT_CHANNELS_TO_ASSIGN } from "../../../elements/channels/select-channels-to-assign"; import { HEADER_SELECTORS } from "../../../elements/header/header-selectors"; import { BUTTON_SELECTORS } from "../../../elements/shared/button-selectors"; +import { MESSAGES } from "../../../fixtures/"; import { productDetailsUrl, urlList } from "../../../fixtures/urlList"; import { ONE_PERMISSION_USERS } from "../../../fixtures/users"; import { createChannel } from "../../../support/api/requests/Channels"; @@ -17,7 +18,11 @@ import { getShippingZone, } from "../../../support/api/requests/ShippingMethod"; import { createWarehouse as createWarehouseViaApi } from "../../../support/api/requests/Warehouse"; -import { createChannelByView } from "../../../support/pages/channelsPage"; +import { + createChannelByView, + setChannelRequiredFields, + typeExpirationDays, +} from "../../../support/pages/channelsPage"; describe("Channels", () => { const channelStartsWith = `CyChannels`; @@ -91,6 +96,65 @@ describe("Channels", () => { }); }, ); + it( + "should create new channel with expired orders functionality set. TC: SALEOR_0713", + { tags: ["@channel", "@allEnv", "@stable"] }, + () => { + const randomChannel = `${channelStartsWith} ${faker.datatype.number()}`; + const orderExpiresAfter = 120; + cy.addAliasToGraphRequest("Channels"); + cy.addAliasToGraphRequest("ChannelCreate"); + cy.visit(urlList.channels); + cy.waitForRequestAndCheckIfNoErrors("@Channels"); + setChannelRequiredFields({ name: randomChannel, currency }); + typeExpirationDays(orderExpiresAfter); + cy.clickConfirmButton(); + cy.waitForRequestAndCheckIfNoErrors("@ChannelCreate").then( + channelCreate => { + expect( + channelCreate.response.body.data.channelCreate.channel + .orderSettings, + ).to.have.property("deleteExpiredOrdersAfter", orderExpiresAfter); + cy.get(CHANNELS_SELECTORS.orderExpirationInput) + .invoke("val") + .should("contain", orderExpiresAfter.toString()); + }, + ); + }, + ); + it( + "should not be able to create new channel with expired orders functionality with values outside boundary conditions. TC: SALEOR_0714", + { tags: ["@channel", "@allEnv"] }, + () => { + const randomChannel = `${channelStartsWith} ${faker.datatype.number()}`; + const underBoundaryConditions = 0; + const overBoundaryConditions = 121; + cy.addAliasToGraphRequest("Channels"); + cy.addAliasToGraphRequest("ChannelCreate"); + cy.visit(urlList.channels); + cy.waitForRequestAndCheckIfNoErrors("@Channels"); + setChannelRequiredFields({ name: randomChannel, currency }); + typeExpirationDays(underBoundaryConditions); + + cy.clickConfirmButton(); + cy.wait("@ChannelCreate").then(createChannelResponse => { + cy.log(createChannelResponse); + expect( + createChannelResponse.response.body.data.channelCreate.errors.length, + ).to.eq(1); + cy.confirmationErrorMessageShouldAppear(); + }); + typeExpirationDays(overBoundaryConditions); + cy.clickConfirmButton(); + cy.wait("@ChannelCreate").then(createChannelResponse => { + cy.log(createChannelResponse); + expect( + createChannelResponse.response.body.data.channelCreate.errors.length, + ).to.eq(1); + cy.confirmationErrorMessageShouldAppear(); + }); + }, + ); it( "should create channel with shippingZone and warehouse TC: SALEOR_0712", { tags: ["@channel", "@allEnv"] }, @@ -101,7 +165,6 @@ describe("Channels", () => { const randomChannel = `${channelStartsWith} ${faker.datatype.number()}`; cy.addAliasToGraphRequest("Channels"); cy.visit(urlList.channels); - cy.expectSkeletonIsVisible(); cy.wait("@Channels"); createChannelByView({ name: randomChannel, @@ -120,7 +183,7 @@ describe("Channels", () => { ); it( - "should validate slug name. TC: SALEOR_0703", + "should validate that creating channels with same slug name as other is not possible. TC: SALEOR_0703", { tags: ["@channel", "@allEnv", "@stable"] }, () => { const randomChannel = `${channelStartsWith} ${faker.datatype.number()}`; @@ -133,8 +196,10 @@ describe("Channels", () => { cy.visit(urlList.channels); cy.expectSkeletonIsVisible(); createChannelByView({ name: randomChannel, currency }); - cy.get(ADD_CHANNEL_FORM_SELECTORS.slugValidationMessage).should( - "be.visible", + cy.confirmationErrorMessageShouldAppear(); + cy.get(ADD_CHANNEL_FORM_SELECTORS.generalInformationSection).should( + "contain.text", + MESSAGES.slugMustBeUnique, ); }, ); diff --git a/cypress/elements/channels/add-channel-form-selectors.js b/cypress/elements/channels/add-channel-form-selectors.js index d25f8b2af..233b5cd0a 100644 --- a/cypress/elements/channels/add-channel-form-selectors.js +++ b/cypress/elements/channels/add-channel-form-selectors.js @@ -15,4 +15,5 @@ export const ADD_CHANNEL_FORM_SELECTORS = { warehouseAutocompleteSelect: "[data-test-id='warehouse-auto-complete-select']", countryAutocompleteInput: '[data-test-id="country-select-input"]', + generalInformationSection: '[data-test-id="general-information"]', }; diff --git a/cypress/elements/channels/channels-selectors.js b/cypress/elements/channels/channels-selectors.js index a055a125d..c249fdf18 100644 --- a/cypress/elements/channels/channels-selectors.js +++ b/cypress/elements/channels/channels-selectors.js @@ -1,5 +1,6 @@ export const CHANNELS_SELECTORS = { createChannelButton: "[data-test-id='add-channel']", channelsTable: "[class='MuiTableBody-root']", - channelName: "[data-test-id='name']" + channelName: "[data-test-id='name']", + orderExpirationInput: "[data-test-id='delete-expired-order-input']", }; diff --git a/cypress/fixtures/messages.js b/cypress/fixtures/messages.js index 2bc3b3f0f..f3c886412 100644 --- a/cypress/fixtures/messages.js +++ b/cypress/fixtures/messages.js @@ -2,4 +2,5 @@ export const MESSAGES = { noProductFound: "No products found", confirmProductsDeletion: "Are you sure you want to delete 2 products?", invalidEmailAddress: "Enter a valid email address.", + slugMustBeUnique: "Slug must be unique", }; diff --git a/cypress/support/pages/channelsPage.js b/cypress/support/pages/channelsPage.js index 768cc54c0..ae168c455 100644 --- a/cypress/support/pages/channelsPage.js +++ b/cypress/support/pages/channelsPage.js @@ -16,13 +16,32 @@ export function createChannelByView({ defaultCountry = "Poland", warehouse, }) { - cy.addAliasToGraphRequest("Channel") - .get(CHANNELS_SELECTORS.createChannelButton) - .click() - .get(ADD_CHANNEL_FORM_SELECTORS.channelName) - .type(name) + setChannelRequiredFields({ + name, + currency, + slug, + shippingZone, + defaultCountry, + warehouse, + }); + cy.get(ADD_CHANNEL_FORM_SELECTORS.saveButton).click(); +} +export function setChannelRequiredFields({ + name, + currency, + slug = name, + shippingZone, + defaultCountry = "Poland", + warehouse, +}) { + cy.addAliasToGraphRequest("Channel"); + clickCreateChannelButton(); + cy.get(ADD_CHANNEL_FORM_SELECTORS.channelName) + .click({ force: true }) + .type(name, { force: true }) .get(ADD_CHANNEL_FORM_SELECTORS.slug) - .type(slug) + .click({ force: true }) + .type(slug, { force: true }) .get(ADD_CHANNEL_FORM_SELECTORS.currency) .click(); cy.get(ADD_CHANNEL_FORM_SELECTORS.currency).type(currency); @@ -43,7 +62,6 @@ export function createChannelByView({ if (warehouse) { addWarehouse(warehouse); } - cy.get(ADD_CHANNEL_FORM_SELECTORS.saveButton).click(); } export function addShippingZone(shippingZone) { @@ -57,6 +75,16 @@ export function addShippingZone(shippingZone) { shippingZone, ); } +export function typeExpirationDays(expirationDays) { + cy.get(CHANNELS_SELECTORS.orderExpirationInput) + .click({ force: true }) + .clear() + .type(expirationDays); +} + +export function clickCreateChannelButton() { + return cy.get(CHANNELS_SELECTORS.createChannelButton).click(); +} export function addWarehouse(warehouse) { cy.get(BUTTON_SELECTORS.expandIcon) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 7bc126071..e35ce582e 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -4411,6 +4411,10 @@ "context": "page header, edit view", "string": "Edit grant refund" }, + "U+79k0": { + "context": "order expiration card description", + "string": "The time in days after expired orders will be deleted. Allowed range between 1 and 120." + }, "U1eJIw": { "context": "order history message", "string": "Products were added to an order" @@ -5269,10 +5273,6 @@ "context": "header", "string": "Password" }, - "ZhaXLU": { - "context": "button", - "string": "Copy" - }, "ZhqH8J": { "context": "button", "string": "Copy headers" @@ -6659,6 +6659,10 @@ "kTr2o8": { "string": "Attribute name" }, + "kVKTwC": { + "context": "order expiration card title", + "string": "Order expiration" + }, "kVOslW": { "context": "reason for discount label", "string": "Reason for discount" diff --git a/src/channels/components/ChannelForm/ChannelForm.tsx b/src/channels/components/ChannelForm/ChannelForm.tsx index 45db8d0c6..1b61e4d0d 100644 --- a/src/channels/components/ChannelForm/ChannelForm.tsx +++ b/src/channels/components/ChannelForm/ChannelForm.tsx @@ -3,8 +3,7 @@ import { ChannelShippingZones, ChannelWarehouses, } from "@dashboard/channels/pages/ChannelDetailsPage/types"; -import CardSpacer from "@dashboard/components/CardSpacer"; -import CardTitle from "@dashboard/components/CardTitle"; +import { DashboardCard } from "@dashboard/components/Card"; import ControlledSwitch from "@dashboard/components/ControlledSwitch"; import FormSpacer from "@dashboard/components/FormSpacer"; import Link from "@dashboard/components/Link"; @@ -23,18 +22,11 @@ import { ChangeEvent, FormChange } from "@dashboard/hooks/useForm"; import { commonMessages } from "@dashboard/intl"; import { getFormErrors } from "@dashboard/utils/errors"; import getChannelsErrorMessage from "@dashboard/utils/errors/channels"; -import { - Card, - CardContent, - InputAdornment, - TextField, - Typography, -} from "@material-ui/core"; -import { Box } from "@saleor/macaw-ui/next"; +import { Box, Button, CopyIcon, Input, Text } from "@saleor/macaw-ui/next"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { useStyles } from "../styles"; +import { messages } from "./messages"; import { ExtendedFormHelperTextProps } from "./types"; export interface FormData extends StockSettingsInput { @@ -49,6 +41,7 @@ export interface FormData extends StockSettingsInput { warehousesToDisplay: ChannelWarehouses; defaultCountry: CountryCode; markAsPaidStrategy: MarkAsPaidStrategyEnum; + deleteExpiredOrdersAfter: number; } export interface ChannelFormProps { @@ -79,91 +72,62 @@ export const ChannelForm: React.FC = ({ onMarkAsPaidStrategyChange, }) => { const intl = useIntl(); - const [copied, copy] = useClipboard(); + const [, copy] = useClipboard(); const formErrors = getFormErrors( - ["name", "slug", "currencyCode", "defaultCountry"], + [ + "name", + "slug", + "currencyCode", + "defaultCountry", + "deleteExpiredOrdersAfter", + ], errors, ); - const classes = useStyles(); return ( <> - - - - + + {intl.formatMessage(commonMessages.generalInformations)} + + + - copy(data.slug)} - > - {copied ? ( - - ) : ( - - )} - - ), - }} + endAdornment={ +