fix: Fork button text color/hover, and fork=true query param fix (#27336)
## Description #### PR fixes following issue(s) Fixes #26295 #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) - This change requires a documentation update > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
db5ae20b48
commit
8046df44d1
|
|
@ -0,0 +1,31 @@
|
||||||
|
import {
|
||||||
|
agHelper,
|
||||||
|
appSettings,
|
||||||
|
deployMode,
|
||||||
|
embedSettings,
|
||||||
|
} from "../../../../support/Objects/ObjectsCore";
|
||||||
|
import applicationLocators from "../../../../locators/Applications.json";
|
||||||
|
|
||||||
|
describe("Fork application in deployed mode", function () {
|
||||||
|
it("1. Fork modal should open and close", function () {
|
||||||
|
appSettings.OpenAppSettings();
|
||||||
|
appSettings.GoToEmbedSettings();
|
||||||
|
embedSettings.ToggleMarkForkable();
|
||||||
|
embedSettings.TogglePublicAccess();
|
||||||
|
deployMode.DeployApp();
|
||||||
|
|
||||||
|
cy.url().then((url) => {
|
||||||
|
const forkableAppUrl = url;
|
||||||
|
cy.LogOut();
|
||||||
|
cy.LogintoApp(Cypress.env("TESTUSERNAME1"), Cypress.env("TESTPASSWORD1"));
|
||||||
|
cy.visit(forkableAppUrl);
|
||||||
|
|
||||||
|
agHelper.GetNClick(applicationLocators.forkButton);
|
||||||
|
cy.wait(2000);
|
||||||
|
agHelper.AssertElementVisibility(applicationLocators.forkModal);
|
||||||
|
cy.location("search").should("include", "fork=true");
|
||||||
|
agHelper.GetNClick(applicationLocators.closeModalPopup);
|
||||||
|
cy.location("search").should("not.include", "fork=true");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -117,7 +117,7 @@ describe("Fork application across workspaces", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Mark application as forkable", () => {
|
it("3. Mark application as forkable", () => {
|
||||||
homePage.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD"));
|
homePage.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD"));
|
||||||
homePage.CreateNewApplication();
|
homePage.CreateNewApplication();
|
||||||
appSettings.OpenAppSettings();
|
appSettings.OpenAppSettings();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"copyUrl": ".t--copy-url",
|
"copyUrl": ".t--copy-url",
|
||||||
"forkButton": ".t--fork-app",
|
"forkButton": ".t--fork-app",
|
||||||
|
"forkModal": ".fork-modal",
|
||||||
"shareButton": "button:contains('Share')",
|
"shareButton": "button:contains('Share')",
|
||||||
"placeholderTxt": "//input[@placeholder='Enter email address(es)']",
|
"placeholderTxt": "//input[@placeholder='Enter email address(es)']",
|
||||||
"placeholderTxtEE": "//input[@placeholder='Enter email address(es) or group(s)']",
|
"placeholderTxtEE": "//input[@placeholder='Enter email address(es) or group(s)']",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import configureStore from "redux-mock-store";
|
||||||
|
|
||||||
jest.mock("react-router", () => ({
|
jest.mock("react-router", () => ({
|
||||||
...jest.requireActual("react-router"),
|
...jest.requireActual("react-router"),
|
||||||
|
useHistory: () => ({ push: jest.fn() }),
|
||||||
useLocation: () => ({
|
useLocation: () => ({
|
||||||
pathname: "/app/test-3/page1-63cccd44463c535b9fbc297c",
|
pathname: "/app/test-3/page1-63cccd44463c535b9fbc297c",
|
||||||
search: "?fork=true",
|
search: "?fork=true",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import Button from "./AppViewerButton";
|
import Button from "./AppViewerButton";
|
||||||
import { AUTH_LOGIN_URL } from "constants/routes";
|
import { AUTH_LOGIN_URL } from "constants/routes";
|
||||||
|
|
@ -22,7 +22,7 @@ import { getCurrentUser } from "selectors/usersSelectors";
|
||||||
import { ANONYMOUS_USERNAME } from "constants/userConstants";
|
import { ANONYMOUS_USERNAME } from "constants/userConstants";
|
||||||
import ForkApplicationModal from "pages/Applications/ForkApplicationModal";
|
import ForkApplicationModal from "pages/Applications/ForkApplicationModal";
|
||||||
import { viewerURL } from "RouteBuilder";
|
import { viewerURL } from "RouteBuilder";
|
||||||
import { useHistory } from "react-router";
|
import { useHistory, useLocation } from "react-router";
|
||||||
import { useHref } from "pages/Editor/utils";
|
import { useHref } from "pages/Editor/utils";
|
||||||
import type { NavigationSetting } from "constants/AppConstants";
|
import type { NavigationSetting } from "constants/AppConstants";
|
||||||
import { Icon, Tooltip } from "design-system";
|
import { Icon, Tooltip } from "design-system";
|
||||||
|
|
@ -70,6 +70,48 @@ function PrimaryCTA(props: Props) {
|
||||||
const [isForkModalOpen, setIsForkModalOpen] = useState(false);
|
const [isForkModalOpen, setIsForkModalOpen] = useState(false);
|
||||||
const isPreviewMode = useSelector(previewModeSelector);
|
const isPreviewMode = useSelector(previewModeSelector);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const location = useLocation();
|
||||||
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (queryParams.get("fork") === "true") {
|
||||||
|
handleForkModalOpen();
|
||||||
|
} else {
|
||||||
|
handleForkModalClose();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const appendOrDeleteForkParam = (appendOrDelete: "append" | "delete") => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
|
||||||
|
if (appendOrDelete === "append" && !url.searchParams.has("fork")) {
|
||||||
|
url.searchParams.append("fork", "true");
|
||||||
|
history.push(url.toString().slice(url.origin.length));
|
||||||
|
} else if (appendOrDelete === "delete" && url.searchParams.has("fork")) {
|
||||||
|
url.searchParams.delete("fork");
|
||||||
|
history.push(url.toString().slice(url.origin.length));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleForkModalOpen = () => {
|
||||||
|
setIsForkModalOpen(true);
|
||||||
|
appendOrDeleteForkParam("append");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleForkModalClose = () => {
|
||||||
|
setIsForkModalOpen(false);
|
||||||
|
appendOrDeleteForkParam("delete");
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// delete "fork" param from url if user is not logged in
|
||||||
|
if (
|
||||||
|
currentApplication?.forkingEnabled &&
|
||||||
|
currentUser?.username === ANONYMOUS_USERNAME
|
||||||
|
) {
|
||||||
|
appendOrDeleteForkParam("delete");
|
||||||
|
}
|
||||||
|
}, [currentApplication?.forkingEnabled, currentUser?.username]);
|
||||||
|
|
||||||
const appViewerURL = useHref(viewerURL, {
|
const appViewerURL = useHref(viewerURL, {
|
||||||
pageId: currentPageID,
|
pageId: currentPageID,
|
||||||
|
|
@ -171,15 +213,17 @@ function PrimaryCTA(props: Props) {
|
||||||
insideSidebar={insideSidebar}
|
insideSidebar={insideSidebar}
|
||||||
navColorStyle={navColorStyle}
|
navColorStyle={navColorStyle}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsForkModalOpen(true);
|
handleForkModalOpen();
|
||||||
}}
|
}}
|
||||||
primaryColor={primaryColor}
|
primaryColor={primaryColor}
|
||||||
text={createMessage(FORK_APP)}
|
text={createMessage(FORK_APP)}
|
||||||
|
varient={ButtonVariantTypes.SECONDARY}
|
||||||
/>
|
/>
|
||||||
<ForkApplicationModal
|
<ForkApplicationModal
|
||||||
applicationId={currentApplication?.id || ""}
|
applicationId={currentApplication?.id || ""}
|
||||||
|
handleClose={handleForkModalClose}
|
||||||
|
handleOpen={handleForkModalOpen}
|
||||||
isModalOpen={isForkModalOpen}
|
isModalOpen={isForkModalOpen}
|
||||||
setModalClose={setIsForkModalOpen}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -411,8 +411,10 @@ export function ApplicationCard(props: ApplicationCardProps) {
|
||||||
</Menu>
|
</Menu>
|
||||||
<ForkApplicationModal
|
<ForkApplicationModal
|
||||||
applicationId={applicationId}
|
applicationId={applicationId}
|
||||||
|
handleClose={() => {
|
||||||
|
setForkApplicationModalOpen(false);
|
||||||
|
}}
|
||||||
isModalOpen={isForkApplicationModalopen}
|
isModalOpen={isForkApplicationModalopen}
|
||||||
setModalClose={setForkApplicationModalOpen}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import {
|
||||||
Option,
|
Option,
|
||||||
} from "design-system";
|
} from "design-system";
|
||||||
import { ButtonWrapper, SpinnerWrapper } from "./ForkModalStyles";
|
import { ButtonWrapper, SpinnerWrapper } from "./ForkModalStyles";
|
||||||
import { useLocation } from "react-router";
|
|
||||||
import {
|
import {
|
||||||
CANCEL,
|
CANCEL,
|
||||||
createMessage,
|
createMessage,
|
||||||
|
|
@ -27,7 +26,6 @@ import {
|
||||||
FORK_APP_MODAL_SUCCESS_TITLE,
|
FORK_APP_MODAL_SUCCESS_TITLE,
|
||||||
} from "@appsmith/constants/messages";
|
} from "@appsmith/constants/messages";
|
||||||
import { getAllApplications } from "@appsmith/actions/applicationActions";
|
import { getAllApplications } from "@appsmith/actions/applicationActions";
|
||||||
import history from "utils/history";
|
|
||||||
|
|
||||||
type ForkApplicationModalProps = {
|
type ForkApplicationModalProps = {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
|
|
@ -35,12 +33,13 @@ type ForkApplicationModalProps = {
|
||||||
// it renders that component
|
// it renders that component
|
||||||
trigger?: React.ReactNode;
|
trigger?: React.ReactNode;
|
||||||
isModalOpen?: boolean;
|
isModalOpen?: boolean;
|
||||||
setModalClose?: (isOpen: boolean) => void;
|
handleOpen?: () => void;
|
||||||
|
handleClose?: () => void;
|
||||||
isInEditMode?: boolean;
|
isInEditMode?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ForkApplicationModal(props: ForkApplicationModalProps) {
|
function ForkApplicationModal(props: ForkApplicationModalProps) {
|
||||||
const { isModalOpen, setModalClose } = props;
|
const { handleClose, handleOpen, isModalOpen } = props;
|
||||||
const [workspace, selectWorkspace] = useState<{
|
const [workspace, selectWorkspace] = useState<{
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -52,20 +51,12 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFetchingApplications = useSelector(getIsFetchingApplications);
|
const isFetchingApplications = useSelector(getIsFetchingApplications);
|
||||||
const location = useLocation();
|
|
||||||
const queryParams = new URLSearchParams(location.search);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (queryParams.get("fork") === "true") {
|
|
||||||
handleOpen();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This effect makes sure that no if <ForkApplicationModel />
|
// This effect makes sure that no if <ForkApplicationModel />
|
||||||
// is getting controlled from outside, then we always load workspaces
|
// is getting controlled from outside, then we always load workspaces
|
||||||
if (isModalOpen) {
|
if (isModalOpen) {
|
||||||
handleOpen();
|
getApplicationsListAndOpenModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [isModalOpen]);
|
}, [isModalOpen]);
|
||||||
|
|
@ -73,9 +64,16 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// when we fork from within the appeditor, fork modal remains open
|
// when we fork from within the appeditor, fork modal remains open
|
||||||
// even on the landing page of "Forked" app, this closes it
|
// even on the landing page of "Forked" app, this closes it
|
||||||
|
const url = new URL(window.location.href);
|
||||||
const shouldCloseForcibly =
|
const shouldCloseForcibly =
|
||||||
!forkingApplication && isModalOpen && setModalClose;
|
!forkingApplication &&
|
||||||
shouldCloseForcibly && setModalClose(false);
|
isModalOpen &&
|
||||||
|
handleClose &&
|
||||||
|
!url.searchParams.has("fork");
|
||||||
|
|
||||||
|
if (shouldCloseForcibly) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
}, [forkingApplication]);
|
}, [forkingApplication]);
|
||||||
|
|
||||||
const forkApplication = () => {
|
const forkApplication = () => {
|
||||||
|
|
@ -118,41 +116,21 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
|
||||||
? createMessage(FORK_APP_MODAL_EMPTY_TITLE)
|
? createMessage(FORK_APP_MODAL_EMPTY_TITLE)
|
||||||
: createMessage(FORK_APP_MODAL_SUCCESS_TITLE);
|
: createMessage(FORK_APP_MODAL_SUCCESS_TITLE);
|
||||||
|
|
||||||
const handleClose = () => {
|
const getApplicationsListAndOpenModal = () => {
|
||||||
if (!props.setModalClose) {
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
if (url.searchParams.has("fork")) {
|
|
||||||
url.searchParams.delete("fork");
|
|
||||||
history.push(url.toString().slice(url.origin.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpen = () => {
|
|
||||||
if (!props.setModalClose) {
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
if (!url.searchParams.has("fork")) {
|
|
||||||
url.searchParams.append("fork", "true");
|
|
||||||
history.push(url.toString().slice(url.origin.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
!workspaceList.length && dispatch(getAllApplications());
|
!workspaceList.length && dispatch(getAllApplications());
|
||||||
|
handleOpen && handleOpen();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnOpenChange = (isOpen: boolean) => {
|
const handleOnOpenChange = (isOpen: boolean) => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
handleOpen();
|
getApplicationsListAndOpenModal();
|
||||||
} else {
|
} else {
|
||||||
setModalClose && setModalClose(false);
|
handleClose && handleClose();
|
||||||
handleClose();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal onOpenChange={handleOnOpenChange} open={isModalOpen}>
|
||||||
onOpenChange={handleOnOpenChange}
|
|
||||||
open={isModalOpen || queryParams.has("fork")}
|
|
||||||
>
|
|
||||||
<ModalContent className={"fork-modal"} style={{ width: "640px" }}>
|
<ModalContent className={"fork-modal"} style={{ width: "640px" }}>
|
||||||
<ModalHeader>{modalHeading}</ModalHeader>
|
<ModalHeader>{modalHeading}</ModalHeader>
|
||||||
{isFetchingApplications ? (
|
{isFetchingApplications ? (
|
||||||
|
|
@ -185,8 +163,7 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
|
||||||
isDisabled={forkingApplication}
|
isDisabled={forkingApplication}
|
||||||
kind="secondary"
|
kind="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalClose && setModalClose(false);
|
handleClose && handleClose();
|
||||||
handleClose();
|
|
||||||
}}
|
}}
|
||||||
size="md"
|
size="md"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -176,9 +176,11 @@ export function EditorAppName(props: EditorAppNameProps) {
|
||||||
</Menu>
|
</Menu>
|
||||||
<ForkApplicationModal
|
<ForkApplicationModal
|
||||||
applicationId={props.applicationId || ""}
|
applicationId={props.applicationId || ""}
|
||||||
|
handleClose={() => {
|
||||||
|
setForkApplicationModalOpen(false);
|
||||||
|
}}
|
||||||
isInEditMode
|
isInEditMode
|
||||||
isModalOpen={isForkApplicationModalopen}
|
isModalOpen={isForkApplicationModalopen}
|
||||||
setModalClose={setForkApplicationModalOpen}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user