fix: viewing forkable apps routing to login for non signed in users (#19966)
This commit is contained in:
parent
8a63c703af
commit
d1efd7f199
|
|
@ -140,7 +140,9 @@ export function PageMenu(props: AppViewerHeaderProps) {
|
||||||
workspaceId={workspaceID}
|
workspaceId={workspaceID}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PrimaryCTA className="t--back-to-editor--mobile" url={props.url} />
|
{isOpen && (
|
||||||
|
<PrimaryCTA className="t--back-to-editor--mobile" url={props.url} />
|
||||||
|
)}
|
||||||
{!hideWatermark && (
|
{!hideWatermark && (
|
||||||
<a
|
<a
|
||||||
className="flex hover:no-underline"
|
className="flex hover:no-underline"
|
||||||
|
|
|
||||||
166
app/client/src/pages/AppViewer/PrimaryCTA.test.tsx
Normal file
166
app/client/src/pages/AppViewer/PrimaryCTA.test.tsx
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { 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";
|
||||||
|
|
@ -21,7 +21,6 @@ import {
|
||||||
import { getCurrentUser } from "selectors/usersSelectors";
|
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 { getAllApplications } from "actions/applicationActions";
|
|
||||||
import { viewerURL } from "RouteBuilder";
|
import { viewerURL } from "RouteBuilder";
|
||||||
import { useHistory } from "react-router";
|
import { useHistory } from "react-router";
|
||||||
import { useHref } from "pages/Editor/utils";
|
import { useHref } from "pages/Editor/utils";
|
||||||
|
|
@ -44,7 +43,6 @@ type Props = {
|
||||||
|
|
||||||
function PrimaryCTA(props: Props) {
|
function PrimaryCTA(props: Props) {
|
||||||
const { className, url } = props;
|
const { className, url } = props;
|
||||||
const dispatch = useDispatch();
|
|
||||||
const currentUser = useSelector(getCurrentUser);
|
const currentUser = useSelector(getCurrentUser);
|
||||||
const currentPageID = useSelector(getCurrentPageId);
|
const currentPageID = useSelector(getCurrentPageId);
|
||||||
const selectedTheme = useSelector(getSelectedAppTheme);
|
const selectedTheme = useSelector(getSelectedAppTheme);
|
||||||
|
|
@ -56,7 +54,9 @@ function PrimaryCTA(props: Props) {
|
||||||
|
|
||||||
const appViewerURL = useHref(viewerURL, {
|
const appViewerURL = useHref(viewerURL, {
|
||||||
pageId: currentPageID,
|
pageId: currentPageID,
|
||||||
suffix: "fork",
|
params: {
|
||||||
|
fork: "true",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// get the fork url
|
// get the fork url
|
||||||
|
|
@ -94,6 +94,7 @@ function PrimaryCTA(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!currentUser) return;
|
||||||
if (
|
if (
|
||||||
currentApplication?.forkingEnabled &&
|
currentApplication?.forkingEnabled &&
|
||||||
currentApplication?.isPublic &&
|
currentApplication?.isPublic &&
|
||||||
|
|
@ -104,7 +105,7 @@ function PrimaryCTA(props: Props) {
|
||||||
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
||||||
buttonColor={selectedTheme.properties.colors.primaryColor}
|
buttonColor={selectedTheme.properties.colors.primaryColor}
|
||||||
buttonVariant="PRIMARY"
|
buttonVariant="PRIMARY"
|
||||||
className="t--fork-app"
|
className={`t--fork-app w-full md:w-auto ${className}`}
|
||||||
icon="fork"
|
icon="fork"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
history.push(forkURL);
|
history.push(forkURL);
|
||||||
|
|
@ -126,9 +127,9 @@ function PrimaryCTA(props: Props) {
|
||||||
}
|
}
|
||||||
buttonColor={selectedTheme.properties.colors.primaryColor}
|
buttonColor={selectedTheme.properties.colors.primaryColor}
|
||||||
buttonVariant="PRIMARY"
|
buttonVariant="PRIMARY"
|
||||||
className="t--fork-app"
|
className={`t--fork-app w-full md:w-auto ${className}`}
|
||||||
|
data-testid="fork-modal-trigger"
|
||||||
icon="fork"
|
icon="fork"
|
||||||
onClick={() => dispatch(getAllApplications())}
|
|
||||||
text={createMessage(FORK_APP)}
|
text={createMessage(FORK_APP)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +158,8 @@ function PrimaryCTA(props: Props) {
|
||||||
}, [
|
}, [
|
||||||
url,
|
url,
|
||||||
canEdit,
|
canEdit,
|
||||||
|
forkURL,
|
||||||
|
currentUser?.username,
|
||||||
selectedTheme.properties.colors.primaryColor,
|
selectedTheme.properties.colors.primaryColor,
|
||||||
selectedTheme.properties.borderRadius.appBorderRadius,
|
selectedTheme.properties.borderRadius.appBorderRadius,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useMemo, useEffect } from "react";
|
import React, { useState, useMemo } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { getUserApplicationsWorkspaces } from "selectors/applicationSelectors";
|
import { getUserApplicationsWorkspaces } from "selectors/applicationSelectors";
|
||||||
import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers";
|
import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers";
|
||||||
|
|
@ -15,7 +15,6 @@ import {
|
||||||
import { StyledDialog, ButtonWrapper, SpinnerWrapper } from "./ForkModalStyles";
|
import { StyledDialog, ButtonWrapper, SpinnerWrapper } from "./ForkModalStyles";
|
||||||
import { getIsFetchingApplications } from "selectors/applicationSelectors";
|
import { getIsFetchingApplications } from "selectors/applicationSelectors";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
import { matchViewerForkPath } from "constants/routes";
|
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import {
|
import {
|
||||||
CANCEL,
|
CANCEL,
|
||||||
|
|
@ -26,6 +25,7 @@ import {
|
||||||
FORK_APP_MODAL_SUCCESS_TITLE,
|
FORK_APP_MODAL_SUCCESS_TITLE,
|
||||||
} from "@appsmith/constants/messages";
|
} from "@appsmith/constants/messages";
|
||||||
import { getAllApplications } from "actions/applicationActions";
|
import { getAllApplications } from "actions/applicationActions";
|
||||||
|
import history from "utils/history";
|
||||||
|
|
||||||
type ForkApplicationModalProps = {
|
type ForkApplicationModalProps = {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
|
|
@ -48,16 +48,9 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
|
||||||
(state: AppState) => state.ui.applications.forkingApplication,
|
(state: AppState) => state.ui.applications.forkingApplication,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!userWorkspaces.length) {
|
|
||||||
dispatch(getAllApplications());
|
|
||||||
}
|
|
||||||
}, [userWorkspaces.length]);
|
|
||||||
|
|
||||||
const isFetchingApplications = useSelector(getIsFetchingApplications);
|
const isFetchingApplications = useSelector(getIsFetchingApplications);
|
||||||
const { pathname } = useLocation();
|
const location = useLocation();
|
||||||
|
const queryParams = new URLSearchParams(location.search);
|
||||||
const showBasedOnURL = matchViewerForkPath(pathname);
|
|
||||||
|
|
||||||
const forkApplication = () => {
|
const forkApplication = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
@ -98,12 +91,35 @@ 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 = () => {
|
||||||
|
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 (
|
return (
|
||||||
<StyledDialog
|
<StyledDialog
|
||||||
canOutsideClickClose
|
canOutsideClickClose
|
||||||
className={"fork-modal"}
|
className={"fork-modal"}
|
||||||
headerIcon={{ name: "fork-2", bgColor: Colors.GEYSER_LIGHT }}
|
headerIcon={{ name: "fork-2", bgColor: Colors.GEYSER_LIGHT }}
|
||||||
isOpen={isModalOpen || showBasedOnURL}
|
isOpen={isModalOpen || queryParams.has("fork")}
|
||||||
|
onClose={handleClose}
|
||||||
|
onOpening={handleOpen}
|
||||||
setModalClose={setModalClose}
|
setModalClose={setModalClose}
|
||||||
title={modalHeading}
|
title={modalHeading}
|
||||||
trigger={props.trigger}
|
trigger={props.trigger}
|
||||||
|
|
@ -136,7 +152,10 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
|
||||||
<Button
|
<Button
|
||||||
category={Category.secondary}
|
category={Category.secondary}
|
||||||
disabled={forkingApplication}
|
disabled={forkingApplication}
|
||||||
onClick={() => setModalClose && setModalClose(false)}
|
onClick={() => {
|
||||||
|
setModalClose && setModalClose(false);
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
size={Size.large}
|
size={Size.large}
|
||||||
tag="button"
|
tag="button"
|
||||||
text={createMessage(CANCEL)}
|
text={createMessage(CANCEL)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user