diff --git a/app/client/src/assets/icons/ads/js.svg b/app/client/src/assets/icons/ads/js.svg new file mode 100644 index 0000000000..8dbdbebc91 --- /dev/null +++ b/app/client/src/assets/icons/ads/js.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/client/src/ce/constants/messages.test.ts b/app/client/src/ce/constants/messages.test.ts index f7780e4fb2..2f29afcf17 100644 --- a/app/client/src/ce/constants/messages.test.ts +++ b/app/client/src/ce/constants/messages.test.ts @@ -1,6 +1,9 @@ import { CANNOT_MERGE_DUE_TO_UNCOMMITTED_CHANGES, CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES, + CHANGES_ONLY_MIGRATION, + CHANGES_ONLY_USER, + CHANGES_USER_AND_MIGRATION, COMMIT_AND_PUSH, COMMIT_CHANGES, COMMIT_TO, @@ -256,10 +259,25 @@ describe("git-sync messages", () => { key: "ERROR_GIT_INVALID_REMOTE", value: "Remote repo doesn't exist or is unreachable.", }, + { + key: "CHANGES_ONLY_USER", + value: "Changes since last commit", + }, + { + key: "CHANGES_ONLY_MIGRATION", + value: "Appsmith update changes since last commit", + }, + { + key: "CHANGES_USER_AND_MIGRATION", + value: "Appsmith update and user changes since last commit", + }, ]; const functions = [ CANNOT_MERGE_DUE_TO_UNCOMMITTED_CHANGES, CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES, + CHANGES_ONLY_MIGRATION, + CHANGES_ONLY_USER, + CHANGES_USER_AND_MIGRATION, COMMITTING_AND_PUSHING_CHANGES, COMMIT_AND_PUSH, COMMIT_CHANGES, diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 8f7d8c6429..0799f7d751 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -588,7 +588,6 @@ export const GIT_DISCONNECT_POPUP_MAIN_HEADING = () => `Are you sure ?`; export const GIT_CONNECTION = () => "Git Connection"; export const GIT_IMPORT = () => "Git Import"; -export const DEPLOY = () => "Deploy"; export const MERGE = () => "Merge"; export const GIT_SETTINGS = () => "Git Settings"; export const CONNECT_TO_GIT = () => "Connect to git repository"; @@ -618,7 +617,6 @@ export const CHECK_DP = () => "CHECK"; export const DEPLOY_TO_CLOUD = () => "Deploy to cloud"; export const DEPLOY_WITHOUT_GIT = () => "Deploy your application without version control"; -export const DEPLOY_YOUR_APPLICATION = () => "Deploy your application"; export const COMMIT_CHANGES = () => "Commit changes"; export const COMMIT_TO = () => "Commit to"; export const COMMIT_AND_PUSH = () => "Commit & push"; @@ -735,6 +733,16 @@ export const CONNECTING_TO_REPO_DISABLED = () => export const DURING_ONBOARDING_TOUR = () => "during the onboarding tour"; export const MERGED_SUCCESSFULLY = () => "Merged successfully"; +// GIT DEPLOY begin +export const DEPLOY = () => "Deploy"; +export const DEPLOY_YOUR_APPLICATION = () => "Deploy your application"; +export const CHANGES_ONLY_USER = () => "Changes since last commit"; +export const CHANGES_ONLY_MIGRATION = () => + "Appsmith update changes since last commit"; +export const CHANGES_USER_AND_MIGRATION = () => + "Appsmith update and user changes since last commit"; +// GIT DEPLOY end + // GIT ERRORS begin export const ERROR_GIT_AUTH_FAIL = () => "Please make sure that regenerated SSH key is added and has write access to the repo."; diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx index 99a435127e..e3f62ae100 100644 --- a/app/client/src/components/ads/Icon.tsx +++ b/app/client/src/components/ads/Icon.tsx @@ -70,9 +70,10 @@ import { ReactComponent as WorkspaceIcon } from "assets/icons/ads/organizationIc import { ReactComponent as SettingIcon } from "assets/icons/control/settings.svg"; import { ReactComponent as DropdownIcon } from "assets/icons/ads/dropdown.svg"; import { ReactComponent as ChatIcon } from "assets/icons/ads/app-icons/chat.svg"; +import { ReactComponent as JsIcon } from "assets/icons/ads/js.svg"; import styled from "styled-components"; -import { CommonComponentProps, Classes } from "./common"; +import { Classes, CommonComponentProps } from "./common"; import { noop } from "lodash"; import { theme } from "constants/DefaultTheme"; import Spinner from "./Spinner"; @@ -90,6 +91,7 @@ import CheckLineIcon from "remixicon-react/CheckLineIcon"; import CloseLineIcon from "remixicon-react/CloseLineIcon"; import CloseCircleIcon from "remixicon-react/CloseCircleFillIcon"; import CommentContextMenu from "remixicon-react/More2FillIcon"; +import More2FillIcon from "remixicon-react/More2FillIcon"; import CompassesLine from "remixicon-react/CompassesLineIcon"; import ContextMenuIcon from "remixicon-react/MoreFillIcon"; import CreateNewIcon from "remixicon-react/AddLineIcon"; @@ -102,8 +104,10 @@ import Download from "remixicon-react/DownloadCloud2LineIcon"; import DuplicateIcon from "remixicon-react/FileCopyLineIcon"; import EditIcon from "remixicon-react/PencilFillIcon"; import EditLineIcon from "remixicon-react/EditLineIcon"; +import EditUnderlineIcon from "remixicon-react/EditLineIcon"; import Emoji from "remixicon-react/EmotionLineIcon"; import ExpandMore from "remixicon-react/ArrowDownSLineIcon"; +import DownArrowIcon from "remixicon-react/ArrowDownSLineIcon"; import ExpandLess from "remixicon-react/ArrowUpSLineIcon"; import EyeOn from "remixicon-react/EyeLineIcon"; import EyeOff from "remixicon-react/EyeOffLineIcon"; @@ -122,7 +126,6 @@ import KeyIcon from "remixicon-react/Key2LineIcon"; import LeftArrowIcon2 from "remixicon-react/ArrowLeftSLineIcon"; import Link2 from "remixicon-react/LinkIcon"; import LeftArrowIcon from "remixicon-react/ArrowLeftLineIcon"; -import More2FillIcon from "remixicon-react/More2FillIcon"; import NewsPaperLine from "remixicon-react/NewspaperLineIcon"; import OvalCheck from "remixicon-react/CheckboxCircleLineIcon"; import OvalCheckFill from "remixicon-react/CheckboxCircleFillIcon"; @@ -137,10 +140,8 @@ import Trash from "remixicon-react/DeleteBinLineIcon"; import UpArrow from "remixicon-react/ArrowUpSFillIcon"; import WarningIcon from "remixicon-react/ErrorWarningFillIcon"; import WarningLineIcon from "remixicon-react/ErrorWarningLineIcon"; -import EditUnderlineIcon from "remixicon-react/EditLineIcon"; import LogoutIcon from "remixicon-react/LogoutBoxRLineIcon"; import ShareLineIcon from "remixicon-react/ShareLineIcon"; -import DownArrowIcon from "remixicon-react/ArrowDownSLineIcon"; import LoaderLineIcon from "remixicon-react/LoaderLineIcon"; import WidgetIcon from "remixicon-react/FunctionLineIcon"; import RefreshLineIcon from "remixicon-react/RefreshLineIcon"; @@ -344,6 +345,7 @@ const ICON_LOOKUP = { hamburger: , help: , info: , + js: , key: , lightning: , link: , diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index d99c022acb..84030fc0db 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -683,8 +683,8 @@ export const ReduxActionTypes = { SET_TEMPLATE_NOTIFICATION_SEEN: "SET_TEMPLATE_NOTIFICATION_SEEN", GET_TEMPLATE_NOTIFICATION_SEEN: "GET_TEMPLATE_NOTIFICATION_SEEN", GET_SIMILAR_TEMPLATES_INIT: "GET_SIMILAR_TEMPLATES_INIT", - GET_SIMILAR_TEMPLATES_SUCCESS: "GET_SIMILAR_TEMPLATES_SUCCESS", - /* This action constants is for identifying the status of the updates of the entities */ + GET_SIMILAR_TEMPLATES_SUCCESS: + "GET_SIMILAR_TEMPLATES_SUCCESS" /* This action constants is for identifying the status of the updates of the entities */, ENTITY_UPDATE_STARTED: "ENTITY_UPDATE_STARTED", ENTITY_UPDATE_SUCCESS: "ENTITY_UPDATE_SUCCESS", FETCH_PLUGIN_AND_JS_ACTIONS_SUCCESS: "FETCH_PLUGIN_AND_JS_ACTIONS_SUCCESS", @@ -925,6 +925,7 @@ export interface PromisePayload { reject: any; resolve: any; } + export interface ReduxActionWithPromise extends ReduxAction { payload: T & PromisePayload; } @@ -989,6 +990,8 @@ export interface ApplicationPayload { modifiedAt?: string; pages: ApplicationPagePayload[]; applicationVersion: ApplicationVersion; + isAutoUpdate?: boolean; + isManualUpdate?: boolean; } export type OrganizationDetails = { diff --git a/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.test.tsx b/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.test.tsx new file mode 100644 index 0000000000..58fa702ca0 --- /dev/null +++ b/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.test.tsx @@ -0,0 +1,17 @@ +import { render, screen } from "test/testUtils"; +import BranchButton from "./BranchButton"; +import React from "react"; + +describe("BranchButton", () => { + it("renders properly", async () => { + render(); + const buttonContainer = await screen.queryByTestId( + "t--branch-button-container", + ); + expect(buttonContainer).not.toBeNull(); + const currentBranch = await screen.queryByTestId( + "t--branch-button-currentBranch", + ); + expect(currentBranch?.innerHTML).toContain("*"); + }); +}); diff --git a/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx b/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx index c59e31fd4b..241b1cfd2d 100644 --- a/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx +++ b/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx @@ -20,19 +20,24 @@ import AnalyticsUtil from "utils/AnalyticsUtil"; const ButtonContainer = styled.div` display: flex; align-items: center; + & .label { color: ${(props) => props.theme.colors.editorBottomBar.branchBtnText}; ${(props) => getTypographyByKey(props, "p1")}; line-height: 18px; } + & .icon { height: 24px; } + margin: 0 ${(props) => props.theme.spaces[4]}px; cursor: pointer; + &:hover svg path { fill: ${Colors.CHARCOAL}; } + & .label { width: 100px; overflow: hidden; @@ -57,6 +62,7 @@ function BranchButton() { return ( } + data-testid={"t--git-branch-button-popover"} hasBackdrop isOpen={isOpen} minimal @@ -78,11 +84,18 @@ function BranchButton() { hoverOpenDelay={1} position={Position.TOP_LEFT} > - +
-
+
{currentBranch} {!status?.isClean && "*"}
diff --git a/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx b/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx index d76bd9c6ec..2ade4e6bdd 100644 --- a/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx +++ b/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx @@ -72,18 +72,19 @@ const QuickActionButtonContainer = styled.div<{ disabled?: boolean }>` .count { position: absolute; - width: 20px; - height: 20px; + height: ${(props) => props.theme.spaces[7]}px; display: flex; justify-content: center; align-items: center; color: ${Colors.WHITE}; background-color: ${Colors.BLACK}; - top: -8px; - left: 18px; - border-radius: 50%; + top: ${(props) => -1 * props.theme.spaces[3]}px; + left: ${(props) => props.theme.spaces[8]}px; + border-radius: ${(props) => props.theme.spaces[3]}px; ${(props) => getTypographyByKey(props, "p3")}; z-index: 1; + padding: ${(props) => props.theme.spaces[1]}px + ${(props) => props.theme.spaces[2]}px; } `; @@ -91,15 +92,6 @@ const capitalizeFirstLetter = (string = " ") => { return string.charAt(0).toUpperCase() + string.toLowerCase().slice(1); }; -// const SpinnerContainer = styled.div` -// margin-left: ${(props) => props.theme.spaces[2]}px; -// display: flex; -// align-items: center; -// justify-content: center; -// width: 29px; -// height: 26px; -// `; - function QuickActionButton({ className = "", count = 0, diff --git a/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx b/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx index e541d46869..ec9f92ba31 100644 --- a/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx +++ b/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx @@ -1,11 +1,14 @@ import React, { useEffect, useRef, useState } from "react"; -import { Title } from "../components/StyledComponents"; +import { Space, Title } from "../components/StyledComponents"; import { - DEPLOY_YOUR_APPLICATION, - COMMIT_TO, - createMessage, + CHANGES_ONLY_MIGRATION, + CHANGES_ONLY_USER, + CHANGES_USER_AND_MIGRATION, COMMIT_AND_PUSH, + COMMIT_TO, COMMITTING_AND_PUSHING_CHANGES, + createMessage, + DEPLOY_YOUR_APPLICATION, FETCH_GIT_STATUS, GIT_NO_UPDATED_TOOLTIP, GIT_UPSTREAM_CHANGES, @@ -18,18 +21,17 @@ import Button, { Size } from "components/ads/Button"; import { LabelContainer } from "components/ads/Checkbox"; import { + getConflictFoundDocUrlDeploy, + getGitCommitAndPushError, getGitStatus, - getIsFetchingGitStatus, + getIsCommitSuccessful, getIsCommittingInProgress, + getIsFetchingGitStatus, getIsPullingProgress, getPullFailed, - getGitCommitAndPushError, getUpstreamErrorDocUrl, - getConflictFoundDocUrlDeploy, } from "selectors/gitSyncSelectors"; import { useDispatch, useSelector } from "react-redux"; - -import { Space } from "../components/StyledComponents"; import { Colors } from "constants/Colors"; import { getTypographyByKey, Theme } from "constants/DefaultTheme"; @@ -40,7 +42,6 @@ import { fetchGitStatusInit, gitPullInit, } from "actions/gitSyncActions"; -import { getIsCommitSuccessful } from "selectors/gitSyncSelectors"; import StatusLoader from "../components/StatusLoader"; import { clearCommitSuccessfulState } from "../../../../actions/gitSyncActions"; import Statusbar, { @@ -56,11 +57,15 @@ import Icon, { IconSize } from "components/ads/Icon"; import { isMac } from "utils/helpers"; import AnalyticsUtil from "utils/AnalyticsUtil"; -import { getApplicationLastDeployedAt } from "selectors/editorSelectors"; +import { + getApplicationLastDeployedAt, + getCurrentApplication, +} from "selectors/editorSelectors"; import GIT_ERROR_CODES from "constants/GitErrorCodes"; import useAutoGrow from "utils/hooks/useAutoGrow"; const Section = styled.div` + margin-top: ${(props) => props.theme.spaces[11]}px; margin-bottom: ${(props) => props.theme.spaces[11]}px; `; @@ -132,9 +137,21 @@ function Deploy() { const currentBranch = gitMetaData?.branchName; const dispatch = useDispatch(); + const currentApplication = useSelector(getCurrentApplication); + const isAutoUpdate = currentApplication?.isAutoUpdate || false; + const isManualUpdate = currentApplication?.isManualUpdate || true; + const changeReason = isAutoUpdate + ? isManualUpdate + ? CHANGES_USER_AND_MIGRATION + : CHANGES_ONLY_MIGRATION + : CHANGES_ONLY_USER; + const changeReasonText = createMessage(changeReason); + const handleCommit = (doPush: boolean) => { AnalyticsUtil.logEvent("GS_COMMIT_AND_PUSH_BUTTON_CLICK", { source: "GIT_DEPLOY_MODAL", + isAutoUpdate, + isManualUpdate, }); if (currentBranch) { dispatch( @@ -197,9 +214,15 @@ function Deploy() { const autogrowHeight = useAutoGrow(commitMessageDisplay, 37); return ( - + {createMessage(DEPLOY_YOUR_APPLICATION)}
+ + {changeReasonText} + diff --git a/app/client/src/pages/Editor/gitSync/components/GitChanged.tsx b/app/client/src/pages/Editor/gitSync/components/GitChanged.tsx index fd741df21d..ecab2133b7 100644 --- a/app/client/src/pages/Editor/gitSync/components/GitChanged.tsx +++ b/app/client/src/pages/Editor/gitSync/components/GitChanged.tsx @@ -3,97 +3,132 @@ import styled from "constants/DefaultTheme"; import { Classes } from "components/ads/common"; import Text, { TextType } from "components/ads/Text"; import { Colors } from "constants/Colors"; -import Icon, { IconName, IconSize } from "components/ads/Icon"; +import Icon, { IconSize } from "components/ads/Icon"; import { useSelector } from "react-redux"; import { getGitStatus, getIsFetchingGitStatus, } from "selectors/gitSyncSelectors"; +import { GitStatusData } from "../../../../reducers/uiReducers/gitSyncReducer"; -const Skeleton = styled.div` - width: 135px; +const DummyChange = styled.div` + width: 50%; height: ${(props) => props.theme.spaces[9]}px; background: linear-gradient( 90deg, ${Colors.GREY_2} 0%, rgba(240, 240, 240, 0) 100% ); - margin-right: ${(props) => props.theme.spaces[8] + 5}px; + margin-top: ${(props) => props.theme.spaces[7]}px; + margin-bottom: ${(props) => props.theme.spaces[7]}px; `; const Wrapper = styled.div` - width: 178px; height: ${(props) => props.theme.spaces[9]}px; + margin-bottom: ${(props) => props.theme.spaces[7]}px; display: flex; + .${Classes.ICON} { margin-right: ${(props) => props.theme.spaces[3]}px; } + .${Classes.TEXT} { padding-top: ${(props) => props.theme.spaces[1] - 2}px; } `; -const GitChangedRow = styled.div` - display: flex; - align-items: center; +const Statuses = styled.div` + margin-top: ${(props) => props.theme.spaces[7]}px; margin-bottom: ${(props) => props.theme.spaces[11]}px; `; export enum Kind { - widget = "widget", - query = "query", - commit = "commit", - // pullRequest = "pullRequest", + WIDGET = "WIDGET", + QUERY = "QUERY", + COMMIT = "COMMIT", + JS_OBJECT = "JS_OBJECT", } -type GitSyncProps = { - type: Kind; +type StatusProps = { + iconName: string; + message: string; + hasValue: boolean; }; -function GitStatus(props: GitSyncProps) { - const { type } = props; - const status: any = useSelector(getGitStatus); - const loading = useSelector(getIsFetchingGitStatus); - // const loading = true; - let message = "", - iconName: IconName; - switch (type) { - case Kind.widget: - message = `${status?.modifiedPages || 0} page${ - (status?.modifiedPages || 0) === 1 ? "" : "s" - } updated`; - iconName = "widget"; - break; - case Kind.query: - message = `${status?.modifiedQueries || 0} ${ - (status?.modifiedQueries || 0) === 1 ? "query" : "queries" - } modified`; - iconName = "query"; - break; - case Kind.commit: - message = `${status?.aheadCount || 0} commit${ - (status?.aheadCount || 0) === 1 ? "" : "s" - } to push`; - iconName = "git-commit"; - break; - } - return loading ? ( - - ) : ( +type StatusMap = { + [key in Kind]: (status: GitStatusData) => StatusProps; +}; + +const STATUS_MAP: StatusMap = { + [Kind.WIDGET]: (status: GitStatusData) => ({ + message: `${status?.modifiedPages || 0} ${ + (status?.modifiedPages || 0) <= 1 ? "page" : "pages" + } updated`, + iconName: "widget", + hasValue: (status?.modifiedPages || 0) > 0, + }), + [Kind.QUERY]: (status: GitStatusData) => ({ + message: `${status?.modifiedQueries || 0} ${ + (status?.modifiedQueries || 0) <= 1 ? "query" : "queries" + } modified`, + iconName: "query", + hasValue: (status?.modifiedQueries || 0) > 0, + }), + [Kind.COMMIT]: (status: GitStatusData) => ({ + message: commitMessage(status), + iconName: "git-commit", + hasValue: (status?.aheadCount || 0) > 0 || (status?.behindCount || 0) > 0, + }), + [Kind.JS_OBJECT]: (status: GitStatusData) => ({ + message: `${status?.modifiedJSObjects || 0} JS ${ + (status?.modifiedJSObjects || 0) <= 1 ? "Object" : "Objects" + } modified`, + iconName: "js", + hasValue: (status?.modifiedJSObjects || 0) > 0, + }), +}; + +function commitMessage(status: GitStatusData) { + const aheadCount = status?.aheadCount || 0; + const behindCount = status?.behindCount || 0; + const aheadMessage = + aheadCount > 0 + ? (aheadCount || 0) === 1 + ? `${aheadCount || 0} commit ahead` + : `${aheadCount || 0} commits ahead` + : null; + const behindMessage = + behindCount > 0 + ? (behindCount || 0) === 1 + ? `${behindCount || 0} commit behind` + : `${behindCount || 0} commits behind ` + : null; + return [aheadMessage, behindMessage].filter((i) => i !== null).join(" and "); +} + +function Status(props: Partial) { + const { iconName, message } = props; + + return ( - + {message} ); } export default function GitChanged() { - const gitStatus: any = useSelector(getGitStatus); - return ( - - - - {gitStatus?.aheadCount > 0 && } - + const status: GitStatusData = useSelector(getGitStatus) as GitStatusData; + const loading = useSelector(getIsFetchingGitStatus); + const statuses = [Kind.WIDGET, Kind.QUERY, Kind.COMMIT, Kind.JS_OBJECT] + .map((type: Kind) => STATUS_MAP[type](status)) + .map((s) => + s.hasValue ? : null, + ) + .filter((s) => !!s); + return loading ? ( + + ) : ( + {statuses} ); } diff --git a/app/client/src/reducers/uiReducers/gitSyncReducer.ts b/app/client/src/reducers/uiReducers/gitSyncReducer.ts index 751a1a65c7..05ca1ecff5 100644 --- a/app/client/src/reducers/uiReducers/gitSyncReducer.ts +++ b/app/client/src/reducers/uiReducers/gitSyncReducer.ts @@ -450,6 +450,7 @@ export type GitStatusData = { modifiedPages: number; modifiedQueries: number; remoteBranch: string; + modifiedJSObjects: number; }; type GitErrorPayloadType = {