fix: viewing forkable apps routing to login for non signed in users (#19966)

This commit is contained in:
akash-codemonk 2023-01-23 15:28:45 +05:30 committed by GitHub
parent 8a63c703af
commit d1efd7f199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 211 additions and 21 deletions

View File

@ -140,7 +140,9 @@ export function PageMenu(props: AppViewerHeaderProps) {
workspaceId={workspaceID}
/>
)}
<PrimaryCTA className="t--back-to-editor--mobile" url={props.url} />
{isOpen && (
<PrimaryCTA className="t--back-to-editor--mobile" url={props.url} />
)}
{!hideWatermark && (
<a
className="flex hover:no-underline"

View File

@ -0,0 +1,166 @@
import React from "react";
import { render, screen, cleanup } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import { Provider } from "react-redux";
import { ThemeProvider } from "styled-components";
import { lightTheme } from "selectors/themeSelectors";
import PrimaryCTA from "./PrimaryCTA";
import configureStore from "redux-mock-store";
jest.mock("react-router", () => ({
...jest.requireActual("react-router"),
useLocation: () => ({
pathname: "/app/test-3/page1-63cccd44463c535b9fbc297c",
search: "?fork=true",
}),
}));
const mockDispatch = jest.fn();
jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"),
useDispatch: () => mockDispatch,
}));
export const initialState: any = {
entities: {
pageList: {
applicationId: 1,
currentPageId: 1,
pages: [
{
pageId: 1,
slug: "pageSlug",
},
],
},
datasources: {
list: [],
},
actions: [],
canvasWidgets: {
main_component: {},
},
},
ui: {
appTheming: {
selectedTheme: {
properties: {
colors: {
primaryColor: "",
},
borderRadius: {
appBorderRadius: "",
},
},
},
},
applications: {
currentApplication: {
id: "605c435a91dea93f0eaf91b8",
name: "My Application",
slug: "my-application",
workspaceId: "",
evaluationVersion: 1,
appIsExample: false,
gitApplicationMetadata: undefined,
applicationVersion: 2,
forkingEnabled: true,
isPublic: true,
},
userWorkspaces: [],
},
theme: {
theme: {
colors: {
applications: {
iconColor: "#f2f2f2",
},
success: {
main: "#e2e2e2",
},
},
},
},
users: {
currentUser: undefined,
},
},
};
const state = JSON.parse(JSON.stringify(initialState));
const mockStore = configureStore();
export function getStore(action?: string) {
switch (action) {
case "SET_CURRENT_USER_DETAILS":
state.ui.users.currentUser = {
username: "test",
};
break;
}
return mockStore(state);
}
export const fetchApplicationMockResponse = {
responseMeta: {
status: 200,
success: true,
},
data: {
application: {
id: "605c435a91dea93f0eaf91b8",
name: "My Application",
slug: "my-application",
workspaceId: "",
evaluationVersion: 1,
appIsExample: false,
gitApplicationMetadata: undefined,
applicationVersion: 2,
forkingEnabled: true,
isPublic: true,
},
pages: [
{
id: "605c435a91dea93f0eaf91ba",
name: "Page1",
isDefault: true,
slug: "page-1",
},
{
id: "605c435a91dea93f0eaf91bc",
name: "Page2",
isDefault: false,
slug: "page-2",
},
],
workspaceId: "",
},
};
describe("App viewer fork button", () => {
afterEach(cleanup);
it("Fork modal trigger should not be displayed until user details are fetched", () => {
render(
<Provider store={getStore()}>
<ThemeProvider theme={lightTheme}>
<PrimaryCTA />
</ThemeProvider>
</Provider>,
);
expect(screen.queryAllByTestId("fork-modal-trigger").length).toEqual(0);
expect(mockDispatch).toHaveBeenCalledTimes(0);
mockDispatch.mockClear();
});
it("Fork modal trigger should be displayed when user details are defined and user is not anonymous", () => {
render(
<Provider store={getStore("SET_CURRENT_USER_DETAILS")}>
<ThemeProvider theme={lightTheme}>
<PrimaryCTA />
</ThemeProvider>
</Provider>,
);
expect(screen.queryAllByTestId("fork-modal-trigger").length).toEqual(1);
expect(mockDispatch).toHaveBeenCalledTimes(1);
mockDispatch.mockClear();
});
});

View File

@ -1,5 +1,5 @@
import React, { useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useSelector } from "react-redux";
import Button from "./AppViewerButton";
import { AUTH_LOGIN_URL } from "constants/routes";
@ -21,7 +21,6 @@ import {
import { getCurrentUser } from "selectors/usersSelectors";
import { ANONYMOUS_USERNAME } from "constants/userConstants";
import ForkApplicationModal from "pages/Applications/ForkApplicationModal";
import { getAllApplications } from "actions/applicationActions";
import { viewerURL } from "RouteBuilder";
import { useHistory } from "react-router";
import { useHref } from "pages/Editor/utils";
@ -44,7 +43,6 @@ type Props = {
function PrimaryCTA(props: Props) {
const { className, url } = props;
const dispatch = useDispatch();
const currentUser = useSelector(getCurrentUser);
const currentPageID = useSelector(getCurrentPageId);
const selectedTheme = useSelector(getSelectedAppTheme);
@ -56,7 +54,9 @@ function PrimaryCTA(props: Props) {
const appViewerURL = useHref(viewerURL, {
pageId: currentPageID,
suffix: "fork",
params: {
fork: "true",
},
});
// get the fork url
@ -94,6 +94,7 @@ function PrimaryCTA(props: Props) {
);
}
if (!currentUser) return;
if (
currentApplication?.forkingEnabled &&
currentApplication?.isPublic &&
@ -104,7 +105,7 @@ function PrimaryCTA(props: Props) {
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
buttonColor={selectedTheme.properties.colors.primaryColor}
buttonVariant="PRIMARY"
className="t--fork-app"
className={`t--fork-app w-full md:w-auto ${className}`}
icon="fork"
onClick={() => {
history.push(forkURL);
@ -126,9 +127,9 @@ function PrimaryCTA(props: Props) {
}
buttonColor={selectedTheme.properties.colors.primaryColor}
buttonVariant="PRIMARY"
className="t--fork-app"
className={`t--fork-app w-full md:w-auto ${className}`}
data-testid="fork-modal-trigger"
icon="fork"
onClick={() => dispatch(getAllApplications())}
text={createMessage(FORK_APP)}
/>
}
@ -157,6 +158,8 @@ function PrimaryCTA(props: Props) {
}, [
url,
canEdit,
forkURL,
currentUser?.username,
selectedTheme.properties.colors.primaryColor,
selectedTheme.properties.borderRadius.appBorderRadius,
]);

View File

@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from "react";
import React, { useState, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getUserApplicationsWorkspaces } from "selectors/applicationSelectors";
import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers";
@ -15,7 +15,6 @@ import {
import { StyledDialog, ButtonWrapper, SpinnerWrapper } from "./ForkModalStyles";
import { getIsFetchingApplications } from "selectors/applicationSelectors";
import { useLocation } from "react-router";
import { matchViewerForkPath } from "constants/routes";
import { Colors } from "constants/Colors";
import {
CANCEL,
@ -26,6 +25,7 @@ import {
FORK_APP_MODAL_SUCCESS_TITLE,
} from "@appsmith/constants/messages";
import { getAllApplications } from "actions/applicationActions";
import history from "utils/history";
type ForkApplicationModalProps = {
applicationId: string;
@ -48,16 +48,9 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
(state: AppState) => state.ui.applications.forkingApplication,
);
useEffect(() => {
if (!userWorkspaces.length) {
dispatch(getAllApplications());
}
}, [userWorkspaces.length]);
const isFetchingApplications = useSelector(getIsFetchingApplications);
const { pathname } = useLocation();
const showBasedOnURL = matchViewerForkPath(pathname);
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const forkApplication = () => {
dispatch({
@ -98,12 +91,35 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
? createMessage(FORK_APP_MODAL_EMPTY_TITLE)
: createMessage(FORK_APP_MODAL_SUCCESS_TITLE);
const handleClose = () => {
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));
}
dispatch(getAllApplications());
}
};
return (
<StyledDialog
canOutsideClickClose
className={"fork-modal"}
headerIcon={{ name: "fork-2", bgColor: Colors.GEYSER_LIGHT }}
isOpen={isModalOpen || showBasedOnURL}
isOpen={isModalOpen || queryParams.has("fork")}
onClose={handleClose}
onOpening={handleOpen}
setModalClose={setModalClose}
title={modalHeading}
trigger={props.trigger}
@ -136,7 +152,10 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
<Button
category={Category.secondary}
disabled={forkingApplication}
onClick={() => setModalClose && setModalClose(false)}
onClick={() => {
setModalClose && setModalClose(false);
handleClose();
}}
size={Size.large}
tag="button"
text={createMessage(CANCEL)}