fix: misc git sync fixes / ui polish (#9229)

Co-authored-by: Rishabh Saxena <rishabh@appsmith.com>
Co-authored-by: Rishabh-Rathod <rishabh.rathod@appsmith.com>
This commit is contained in:
haojin111 2021-12-07 09:59:32 +02:00 committed by GitHub
parent 0ccfac7978
commit d701f8dfb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1116 additions and 488 deletions

View File

@ -128,7 +128,7 @@ class AppRouter extends React.Component<any, any> {
path={SIGNUP_SUCCESS_URL}
/>
<SentryRoute component={UserProfile} exact path={PROFILE} />
<SentryRoute component={UserProfile} path={PROFILE} />
<SentryRoute
component={UnsubscribeEmail}
path={UNSUBSCRIBE_EMAIL_URL}

View File

@ -130,8 +130,9 @@ export const fetchGlobalGitConfigSuccess = (payload: GitConfig) => ({
payload,
});
export const fetchBranchesInit = () => ({
export const fetchBranchesInit = (payload?: { pruneBranches: boolean }) => ({
type: ReduxActionTypes.FETCH_BRANCHES_INIT,
payload,
});
export const fetchBranchesSuccess = (payload: any) => ({
@ -226,3 +227,8 @@ export const gitPullSuccess = (mergeStatus: MergeStatus) => ({
export const resetPullMergeStatus = () => ({
type: ReduxActionTypes.RESET_PULL_MERGE_STATUS,
});
export const remoteUrlInputValue = (payload?: { tempRemoteUrl?: string }) => ({
type: ReduxActionTypes.SET_REMOTE_URL_INPUT_VALUE,
payload,
});

View File

@ -74,9 +74,10 @@ class GitSyncAPI extends Api {
destinationBranch,
sourceBranch,
}: MergeBranchPayload): AxiosPromise<ApiResponse> {
return Api.post(
`${GitSyncAPI.baseURL}/merge/${applicationId}?sourceBranch=${sourceBranch}&destinationBranch=${destinationBranch}`,
);
return Api.post(`${GitSyncAPI.baseURL}/merge/${applicationId}`, {
sourceBranch,
destinationBranch,
});
}
static getMergeStatus({
@ -84,9 +85,10 @@ class GitSyncAPI extends Api {
destinationBranch,
sourceBranch,
}: MergeStatusPayload) {
return Api.get(
`${GitSyncAPI.baseURL}/merge/status/${applicationId}?sourceBranch=${sourceBranch}&destinationBranch=${destinationBranch}`,
);
return Api.post(`${GitSyncAPI.baseURL}/merge/status/${applicationId}`, {
sourceBranch,
destinationBranch,
});
}
static pull({ applicationId }: { applicationId: string }) {
@ -105,8 +107,13 @@ class GitSyncAPI extends Api {
return Api.post(`${GitSyncAPI.baseURL}/profile/default`, payload);
}
static fetchBranches(applicationId: string) {
return Api.get(`${GitSyncAPI.baseURL}/branch/${applicationId}`);
static fetchBranches(applicationId: string, pruneBranches?: boolean) {
const queryParams = {} as { pruneBranches?: boolean };
if (pruneBranches) queryParams.pruneBranches = true;
return Api.get(
`${GitSyncAPI.baseURL}/branch/${applicationId}`,
queryParams,
);
}
static checkoutBranch(applicationId: string) {

View File

@ -1,13 +1,23 @@
import React, { useState, useEffect, useCallback, ReactElement } from "react";
import React, {
useState,
useEffect,
useCallback,
ReactElement,
useRef,
} from "react";
import Icon, { IconName, IconSize } from "./Icon";
import { CommonComponentProps, Classes } from "./common";
import Text, { TextType } from "./Text";
import Text, { TextProps, TextType } from "./Text";
import { Popover, PopperBoundary, Position } from "@blueprintjs/core";
import { getTypographyByKey } from "constants/DefaultTheme";
import { getTypographyByKey, Theme } from "constants/DefaultTheme";
import styled from "constants/DefaultTheme";
import SearchComponent from "components/designSystems/appsmith/SearchComponent";
import { Colors } from "constants/Colors";
import Spinner from "./Spinner";
import Tooltip from "components/ads/Tooltip";
import { isEllipsisActive } from "utils/helpers";
import SegmentHeader from "components/ads/ListSegmentHeader";
import { useTheme } from "styled-components";
export type DropdownOnSelect = (value?: string, dropdownOption?: any) => void;
@ -23,6 +33,7 @@ export type DropdownOption = {
iconColor?: string;
onSelect?: DropdownOnSelect;
data?: any;
isSectionHeader?: boolean;
};
export interface DropdownSearchProps {
enableSearch?: boolean;
@ -36,12 +47,12 @@ export interface RenderDropdownOptionType {
optionClickHandler?: (dropdownOption: DropdownOption) => void;
isSelectedNode?: boolean;
extraProps?: any;
errorMsg?: string;
hasError?: boolean;
optionWidth: string;
}
type RenderOption = ({
errorMsg,
hasError,
index,
option,
optionClickHandler,
@ -67,6 +78,7 @@ export type DropdownProps = CommonComponentProps &
bgColor?: string;
renderOption?: RenderOption;
isLoading?: boolean;
hasError?: boolean; // should be displayed as error status without error message
errorMsg?: string; // If errorMsg is defined, we show dropDown's error state with the message.
placeholder?: string;
helperText?: string;
@ -80,12 +92,13 @@ export type DropdownProps = CommonComponentProps &
hideSubText?: boolean;
boundary?: PopperBoundary;
defaultIcon?: IconName;
truncateOption?: boolean; // enabled wrapping and adding tooltip on option item of dropdown menu
};
export interface DefaultDropDownValueNodeProps {
selected: DropdownOption;
showLabelOnly?: boolean;
isOpen?: boolean;
errorMsg?: string;
hasError?: boolean;
renderNode?: RenderOption;
placeholder?: string;
showDropIcon?: boolean;
@ -102,6 +115,9 @@ export const DropdownContainer = styled.div<{ width: string; height?: string }>`
width: ${(props) => props.width};
height: ${(props) => props.height || `38px`};
position: relative;
span.bp3-popover-target {
display: inline-block;
}
`;
const DropdownTriggerWrapper = styled.div<{
@ -271,6 +287,10 @@ const OptionWrapper = styled.div<{
}
}
.bp3-popover-wrapper {
width: 100%;
}
.${Classes.TEXT} {
color: ${(props) =>
props.selected
@ -425,8 +445,35 @@ const ErrorLabel = styled.span`
color: ${Colors.POMEGRANATE2};
`;
const StyledText = styled(Text)`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
function TooltipWrappedText(
props: TextProps & {
label: string;
},
) {
const { label, ...textProps } = props;
const targetRef = useRef<HTMLDivElement | null>(null);
return (
<Tooltip
boundary="window"
content={label}
disabled={!isEllipsisActive(targetRef.current)}
position={Position.TOP}
>
<StyledText ref={targetRef} {...textProps}>
{label}
</StyledText>
</Tooltip>
);
}
function DefaultDropDownValueNode({
errorMsg,
hasError,
hideSubText,
optionWidth,
placeholder,
@ -443,7 +490,7 @@ function DefaultDropDownValueNode({
? placeholder
: "Please select a option.";
function Label() {
return errorMsg ? (
return hasError ? (
<ErrorLabel>{LabelText}</ErrorLabel>
) : (
<Text type={TextType.P1}>{LabelText}</Text>
@ -456,16 +503,16 @@ function DefaultDropDownValueNode({
renderNode({
isSelectedNode: true,
option: selected,
errorMsg,
hasError,
optionWidth,
})
) : (
<>
{selected?.icon ? (
<SelectedIcon
fillColor={errorMsg ? Colors.POMEGRANATE2 : selected?.iconColor}
fillColor={hasError ? Colors.POMEGRANATE2 : selected?.iconColor}
hoverFillColor={
errorMsg ? Colors.POMEGRANATE2 : selected?.iconColor
hasError ? Colors.POMEGRANATE2 : selected?.iconColor
}
name={selected.icon}
size={selected.iconSize || IconSize.XL}
@ -513,8 +560,9 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
setOptions(filteredOptions);
onSearch && onSearch(searchStr);
};
const theme = useTheme() as Theme;
return options.length > 0 ? (
return (
<DropdownWrapper
className="ads-dropdown-options-wrapper"
width={optionWidth}
@ -542,7 +590,7 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
optionWidth,
});
}
return (
return !option.isSectionHeader ? (
<OptionWrapper
className="t--dropdown-option"
key={index}
@ -562,12 +610,24 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
) : null}
{props.showLabelOnly ? (
<Text type={TextType.P1}>{option.label}</Text>
props.truncateOption ? (
<TooltipWrappedText
label={option.label || ""}
type={TextType.P1}
/>
) : (
<Text type={TextType.P1}>{option.label}</Text>
)
) : option.label && option.value ? (
<LabelWrapper className="label-container">
<Text type={TextType.H5}>{option.value}</Text>
<Text type={TextType.P1}>{option.label}</Text>
</LabelWrapper>
) : props.truncateOption ? (
<TooltipWrappedText
label={option.value || ""}
type={TextType.P1}
/>
) : (
<Text type={TextType.P1}>{option.value}</Text>
)}
@ -578,11 +638,16 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
</StyledSubText>
) : null}
</OptionWrapper>
) : (
<SegmentHeader
style={{ paddingRight: theme.spaces[5] }}
title={option.label || ""}
/>
);
})}
</DropdownOptionsWrapper>
</DropdownWrapper>
) : null;
);
}
export default function Dropdown(props: DropdownProps) {
@ -595,6 +660,7 @@ export default function Dropdown(props: DropdownProps) {
errorMsg = "",
placeholder,
helperText,
hasError,
} = { ...props };
const [isOpen, setIsOpen] = useState<boolean>(false);
const [selected, setSelected] = useState<DropdownOption>(props.selected);
@ -620,8 +686,9 @@ export default function Dropdown(props: DropdownProps) {
[onSelect],
);
const errorFlag = hasError || errorMsg.length > 0;
const disabled = props.disabled || isLoading;
const downIconColor = errorMsg ? Colors.POMEGRANATE2 : Colors.DARK_GRAY;
const downIconColor = errorFlag ? Colors.POMEGRANATE2 : Colors.DARK_GRAY;
const onClickHandler = () => {
if (!props.disabled) {
@ -662,14 +729,14 @@ export default function Dropdown(props: DropdownProps) {
bgColor={props.bgColor}
className={props.className}
disabled={props.disabled}
hasError={!!errorMsg}
hasError={errorFlag}
height={props.height || "38px"}
isOpen={isOpen}
onClick={() => setIsOpen(!isOpen)}
selected={!!selected}
>
<SelectedValueNode
errorMsg={errorMsg}
hasError={errorFlag}
hideSubText={props.hideSubText}
optionWidth={dropdownOptionWidth}
placeholder={placeholder}

View File

@ -105,6 +105,7 @@ import GitCommit from "remixicon-react/GitCommitLineIcon";
import GitPullRequst from "remixicon-react/GitPullRequestLineIcon";
import GuideIcon from "remixicon-react/GuideFillIcon";
import HelpIcon from "remixicon-react/QuestionMarkIcon";
import InfoIcon from "remixicon-react/InformationLineIcon";
import LeftArrowIcon2 from "remixicon-react/ArrowLeftSLineIcon";
import Link2 from "remixicon-react/LinkIcon";
import LeftArrowIcon from "remixicon-react/ArrowLeftLineIcon";
@ -237,6 +238,7 @@ export const IconCollection = [
"git-pull-request",
"guide",
"help",
"info",
"invite-user",
"left-arrow",
"left-arrow-2",
@ -519,6 +521,9 @@ const Icon = forwardRef(
case "help":
returnIcon = <HelpIcon />;
break;
case "info":
returnIcon = <InfoIcon />;
break;
case "invite-user":
returnIcon = <InviteUserIcon />;
break;

View File

@ -0,0 +1,33 @@
import React, { CSSProperties } from "react";
import styled from "styled-components";
import { getTypographyByKey } from "constants/DefaultTheme";
import { Colors } from "constants/Colors";
const StyledSegmentHeader = styled.div`
padding: ${(props) =>
`${props.theme.spaces[3]}px ${props.theme.spaces[5]}px`};
padding-right: 0;
${(props) => getTypographyByKey(props, "u2")}
color: ${Colors.GREY_10};
display: flex;
align-items: center;
`;
const StyledHr = styled.div`
flex: 1;
height: 1px;
background-color: ${Colors.GREY_10};
margin-left: ${(props) => props.theme.spaces[3]}px;
`;
export default function SegmentHeader(props: {
title: string;
style?: CSSProperties;
}) {
return (
<StyledSegmentHeader style={props.style}>
{props.title}
<StyledHr />
</StyledSegmentHeader>
);
}

View File

@ -71,10 +71,14 @@ type StatusbarProps = {
percentage: number;
active: boolean;
message?: string;
showOnlyMessage?: boolean;
};
export default function OnboardingStatusbar(props: StatusbarProps) {
const { active, message, percentage } = props;
const { active, message, percentage, showOnlyMessage } = props;
const displayMessage = showOnlyMessage
? message
: `${percentage}% ${message}`;
return (
<Wrapper active={active} data-testid="statusbar-container">
<StatusProgressbar
@ -83,9 +87,7 @@ export default function OnboardingStatusbar(props: StatusbarProps) {
percentage={percentage}
/>
<StatusText>
<span data-testid="statusbar-text">
{percentage}% {message}
</span>
<span data-testid="statusbar-text">{displayMessage}</span>
</StatusText>
</Wrapper>
);

View File

@ -316,7 +316,7 @@ const TextInput = forwardRef(
const inputValueValidation =
props.validator && props.validator(inputValue);
if (inputValueValidation) {
props.validator && setValidation(validation);
props.validator && setValidation(inputValueValidation);
return (
inputValueValidation.isValid &&
props.onChange &&

View File

@ -159,5 +159,6 @@ export const Colors = {
// error warning
CRIMSON: "#D71010",
ALTO_3: "#D6D6D6",
YELLOW_LIGHT: "#F4AF0A",
};
export type Color = typeof Colors[keyof typeof Colors];

View File

@ -600,6 +600,7 @@ export const ReduxActionTypes = {
WIDGET_ADD_NEW_TAB_CHILD: "WIDGET_ADD_NEW_TAB_CHILD",
WIDGET_DELETE_TAB_CHILD: "WIDGET_DELETE_TAB_CHILD",
GENERATE_SSH_KEY_PAIR_INIT: "GENERATE_SSH_KEY_PAIR_INIT",
SET_REMOTE_URL_INPUT_VALUE: "SET_REMOTE_URL_INPUT_VALUE",
GENERATE_SSH_KEY_PAIR_SUCCESS: "GENERATE_SSH_KEY_PAIR_SUCCESS",
REFACTOR_JS_ACTION_NAME: "REFACTOR_JS_ACTION_NAME",
REFACTOR_JS_ACTION_NAME_SUCCESS: "REFACTOR_JS_ACTION_NAME_SUCCESS",
@ -655,7 +656,6 @@ export const ReduxActionErrorTypes = {
UPDATE_GLOBAL_GIT_CONFIG_ERROR: "UPDATE_GLOBAL_GIT_CONFIG_ERROR",
FETCH_GLOBAL_GIT_CONFIG_ERROR: "FETCH_GLOBAL_GIT_CONFIG_ERROR",
CONNECT_TO_GIT_ERROR: "CONNECT_TO_GIT_ERROR",
GIT_SYNC_ERROR: "GIT_SYNC_ERROR",
DISCONNECT_TO_GIT_ERROR: "DISCONNECT_TO_GIT_ERROR",
COMMIT_TO_GIT_REPO_ERROR: "COMMIT_TO_GIT_REPO_ERROR",
FETCH_FEATURE_FLAGS_ERROR: "FETCH_FEATURE_FLAGS_ERROR",

View File

@ -524,6 +524,7 @@ export const SNIPPET_EXECUTE = () => `Hit ⏎ to run`;
export const APPLY_SEARCH_CATEGORY = () => `⏎ Jump`;
// Git sync
export const CONNECTED_TO_GIT = () => "Connected to git";
export const GIT_DISCONNECT_POPUP_TITLE = () =>
`This will disconnect the git repository from this application`;
@ -537,16 +538,16 @@ export const DEPLOY = () => "Deploy";
export const MERGE = () => "Merge";
export const CONNECT_TO_GIT = () => "Connect to git repository";
export const CONNECT_TO_GIT_SUBTITLE = () =>
"Checkout branches, Make commits, add deploy your application";
"Checkout branches, make commits, add deploy your application";
export const REMOTE_URL = () => "Remote URL";
export const REMOTE_URL_INFO = () =>
`Create an empty git repository and paste the remote URL`;
"Checkout branches, make commits, add deploy your application";
`Create an empty git repository and paste the remote URL here.`;
export const REMOTE_URL_VIA = () => "Remote URL via";
export const USER_PROFILE_SETTINGS_TITLE = () => "User settings";
export const AUTHOR_NAME = () => "Author name";
export const AUTHOR_NAME_CANNOT_BE_EMPTY = () => "Author name cannot be empty";
export const AUTHOR_EMAIL = () => "Author email";
export const NAME_YOUR_NEW_BRANCH = () => "Name your new branch";
@ -562,10 +563,8 @@ 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 = () => "COMMIT";
export const COMMIT_CHANGES = () => "Commit changes";
export const COMMIT_TO = () => "Commit to";
export const PUSH = () => "PUSH";
export const PULL = () => "PULL";
export const COMMIT_AND_PUSH = () => "Commit & push";
export const PULL_CHANGES = () => "PULL CHANGES";
export const DEPLOY_KEY_TITLE = () => "Deployed Key";
@ -573,8 +572,9 @@ export const DEPLOY_KEY_USAGE_GUIDE_MESSAGE = () =>
"Paste this key in your repository settings and give it write access.";
export const COMMITTING_AND_PUSHING_CHANGES = () =>
"COMMITTING AND PUSHING CHANGES...";
export const IS_MERGING = () => "MERGING CHANGES...";
export const MERGE_CHANGES = () => "Merge Changes";
export const MERGE_CHANGES = () => "Merge changes";
export const SELECT_BRANCH_TO_MERGE = () => "Select branch to merge";
export const CONNECT_GIT = () => "Connect Git";
export const RETRY = () => "RETRY";
@ -598,10 +598,15 @@ export const NO_MERGE_CONFLICT = () =>
export const MERGE_CONFLICT_ERROR = () => "Merge conflicts found!";
export const FETCH_MERGE_STATUS_FAILURE = () => "Unable to fetch merge status";
export const GIT_UPSTREAM_CHANGES = () =>
"Looks like there are pending upstream changes. We will pull the changesand push them to your repo.";
"Looks like there are pending upstream changes. We will pull the changes and push them to your repo.";
export const GIT_CONFLICTING_INFO = () =>
"Please resolve the conflicts manually on your repository.";
export const CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES = () =>
"You have uncommitted changes. Please commit before pulling the remote changes";
export const CANNOT_MERGE_DUE_TO_UNCOMMITTED_CHANGES = () =>
"Your current branch has uncommitted changes. Please commit before proceeding to merge";
export const NOT_OPTIONS = () => "Not Options!";
export const OPEN_REPO = () => "OPEN REPO";
export const CONNECTING_REPO = () => "CONNECTING TO GIT REPO";
export const ERROR_CONNECTING = () => "Error while connecting";

View File

@ -135,6 +135,12 @@ export const typography = {
fontWeight: "bold",
fontSize: 13,
},
u2: {
fontSize: 10,
fontStyle: "normal",
fontWeight: 600,
lineHeight: 12,
},
};
export type TypographyKeys = keyof typeof typography;

View File

@ -4,6 +4,7 @@ type FeatureFlag = {
SNIPPET: boolean;
GIT: boolean;
ADMIN_SETTINGS: boolean;
GIT_IMPORT: boolean;
};
export default FeatureFlag;

View File

@ -17,4 +17,6 @@ export type Branch = {
export type MergeStatus = {
isMergeAble: boolean;
conflictingFiles: Array<string>;
status?: string;
message?: string;
};

View File

@ -54,6 +54,7 @@ import { Toaster } from "components/ads/Toast";
import { Variant } from "components/ads/common";
import { getExportAppAPIRoute } from "constants/ApiConstants";
import { Colors } from "constants/Colors";
import { CONNECTED_TO_GIT, createMessage } from "constants/messages";
type NameWrapperProps = {
hasReadPermission: boolean;
@ -363,6 +364,38 @@ const MenuItemWrapper = styled(MenuItem)`
}
`;
const StyledGitConnectedBadge = styled.div`
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: -12px;
right: -12px;
box-shadow: 0px 2px 16px rgba(0, 0, 0, 0.07);
background: ${Colors.WHITE};
`;
function GitConnectedBadge() {
return (
<StyledGitConnectedBadge>
<TooltipComponent
content={createMessage(CONNECTED_TO_GIT)}
maxWidth="400px"
>
<Icon fillColor={Colors.GREY_7} name="fork" size={IconSize.XXL} />
</TooltipComponent>
</StyledGitConnectedBadge>
);
}
const Container = styled.div`
position: relative;
overflow: visible;
`;
export function ApplicationCard(props: ApplicationCardProps) {
const isFetchingApplications = useSelector(getIsFetchingApplications);
const theme = useContext(ThemeContext);
@ -385,6 +418,7 @@ export function ApplicationCard(props: ApplicationCardProps) {
const appNameWrapperRef = useRef<HTMLDivElement>(null);
const applicationId = props.application?.id;
const showGitBadge = props.application?.gitApplicationMetadata;
useEffect(() => {
let colorCode;
@ -670,82 +704,88 @@ export function ApplicationCard(props: ApplicationCardProps) {
};
return (
<NameWrapper
className="t--application-card"
hasReadPermission={hasReadPermission}
isMenuOpen={isMenuOpen}
onMouseEnter={() => {
!isFetchingApplications && setShowOverlay(true);
}}
onMouseLeave={() => {
// If the menu is not open, then setOverlay false
// Set overlay false on outside click.
!isMenuOpen && setShowOverlay(false);
}}
showOverlay={showOverlay}
>
<Wrapper
backgroundColor={selectedColor}
className={
isFetchingApplications
? Classes.SKELETON
: "t--application-card-background"
}
<Container>
<NameWrapper
className="t--application-card"
hasReadPermission={hasReadPermission}
key={props.application.id}
isMenuOpen={isMenuOpen}
onMouseEnter={() => {
!isFetchingApplications && setShowOverlay(true);
}}
onMouseLeave={() => {
// If the menu is not open, then setOverlay false
// Set overlay false on outside click.
!isMenuOpen && setShowOverlay(false);
}}
showOverlay={showOverlay}
>
<CircleAppIcon name={appIcon} size={Size.large} />
<AppNameWrapper
className={isFetchingApplications ? Classes.SKELETON : ""}
isFetching={isFetchingApplications}
ref={appNameWrapperRef}
<Wrapper
backgroundColor={selectedColor}
className={
isFetchingApplications
? Classes.SKELETON
: "t--application-card-background"
}
hasReadPermission={hasReadPermission}
key={props.application.id}
>
{isEllipsisActive(appNameWrapperRef?.current) ? (
<TooltipComponent content={props.application.name} maxWidth="400px">
{appNameText}
</TooltipComponent>
) : (
appNameText
<CircleAppIcon name={appIcon} size={Size.large} />
<AppNameWrapper
className={isFetchingApplications ? Classes.SKELETON : ""}
isFetching={isFetchingApplications}
ref={appNameWrapperRef}
>
{isEllipsisActive(appNameWrapperRef?.current) ? (
<TooltipComponent
content={props.application.name}
maxWidth="400px"
>
{appNameText}
</TooltipComponent>
) : (
appNameText
)}
</AppNameWrapper>
{showOverlay && (
<div className="overlay">
<div className="overlay-blur" />
<ApplicationImage className="image-container">
<Control className="control">
{hasEditPermission && !isMenuOpen && (
<EditButton
className="t--application-edit-link"
fill
href={editApplicationURL}
icon={"edit"}
iconPosition={IconPositions.left}
size={Size.medium}
text="Edit"
/>
)}
{!isMenuOpen && (
<Button
category={Category.tertiary}
className="t--application-view-link"
fill
href={viewApplicationURL}
icon={"rocket"}
iconPosition={IconPositions.left}
size={Size.medium}
text="Launch"
/>
)}
</Control>
</ApplicationImage>
</div>
)}
</AppNameWrapper>
{showOverlay && (
<div className="overlay">
<div className="overlay-blur" />
<ApplicationImage className="image-container">
<Control className="control">
{hasEditPermission && !isMenuOpen && (
<EditButton
className="t--application-edit-link"
fill
href={editApplicationURL}
icon={"edit"}
iconPosition={IconPositions.left}
size={Size.medium}
text="Edit"
/>
)}
{!isMenuOpen && (
<Button
category={Category.tertiary}
className="t--application-view-link"
fill
href={viewApplicationURL}
icon={"rocket"}
iconPosition={IconPositions.left}
size={Size.medium}
text="Launch"
/>
)}
</Control>
</ApplicationImage>
</div>
)}
</Wrapper>
<CardFooter>
<ModifiedDataComponent>{editedByText()}</ModifiedDataComponent>
{!!moreActionItems.length && ContextMenu}
</CardFooter>
</NameWrapper>
</Wrapper>
<CardFooter>
<ModifiedDataComponent>{editedByText()}</ModifiedDataComponent>
{!!moreActionItems.length && ContextMenu}
</CardFooter>
</NameWrapper>
{showGitBadge && <GitConnectedBadge />}
</Container>
);
}

View File

@ -822,7 +822,7 @@ function ApplicationsSection(props: any) {
text="Import Application"
/>
)}
{getFeatureFlags().GIT && (
{getFeatureFlags().GIT_IMPORT && (
<MenuItem
cypressSelector="t--org-import-app-git"
icon="upload"
@ -936,7 +936,7 @@ function ApplicationsSection(props: any) {
<ApplicationContainer className="t--applications-container">
{organizationsListComponent}
<WelcomeHelper />
{getFeatureFlags().GIT && <ImportAppViaGitModal />}
{getFeatureFlags().GIT_IMPORT && <ImportAppViaGitModal />}
</ApplicationContainer>
);
}

View File

@ -16,6 +16,8 @@ import { useDispatch } from "react-redux";
import { initPageLevelSocketConnection } from "actions/websocketActions";
import { collabShareUserPointerEvent } from "actions/appCollabActions";
import { getIsPageLevelSocketConnected } from "../../selectors/websocketSelectors";
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
import { getPageLevelSocketRoomId } from "sagas/WebsocketSagas/utils";
interface CanvasProps {
dsl: DSLWidget;
@ -31,6 +33,7 @@ const getPointerData = (
e: any,
pageId: string,
isWebsocketConnected: boolean,
currentGitBranch?: string,
) => {
if (store.getState().ui.appCollab.editors.length < 2 || !isWebsocketConnected)
return;
@ -41,7 +44,7 @@ const getPointerData = (
const y = e.clientY - rect.top;
return {
data: { x, y },
pageId,
pageId: getPageLevelSocketRoomId(pageId, currentGitBranch),
};
};
@ -63,6 +66,7 @@ const Canvas = memo((props: CanvasProps) => {
const { pageId } = props;
const shareMousePointer = useShareMousePointerEvent();
const isWebsocketConnected = useSelector(getIsPageLevelSocketConnected);
const currentGitBranch = useSelector(getCurrentGitBranch);
const isMultiplayerEnabledForUser = useSelector(
isMultiplayerEnabledForUserSelector,
);
@ -81,7 +85,12 @@ const Canvas = memo((props: CanvasProps) => {
id="art-board"
onMouseMove={(e) => {
if (!isMultiplayerEnabledForUser) return;
const data = getPointerData(e, pageId, isWebsocketConnected);
const data = getPointerData(
e,
pageId,
isWebsocketConnected,
currentGitBranch,
);
!!data && delayedShareMousePointer(data);
}}
width={props.dsl.rightColumn}

View File

@ -46,6 +46,8 @@ import { setPreviewModeAction } from "actions/editorActions";
import { previewModeSelector } from "selectors/editorSelectors";
import { getExplorerPinned } from "selectors/explorerSelector";
import { setExplorerPinnedAction } from "actions/explorerActions";
import { setIsGitSyncModalOpen } from "actions/gitSyncActions";
import { GitSyncModalTab } from "entities/GitSync";
type Props = {
copySelectedWidget: () => void;
@ -73,6 +75,7 @@ type Props = {
setPreviewModeAction: (shouldSet: boolean) => void;
isExplorerPinned: boolean;
setExplorerPinnedAction: (shouldPinned: boolean) => void;
showCommitModal: () => void;
};
@HotkeysTarget
@ -369,6 +372,14 @@ class GlobalHotKeys extends React.Component<Props> {
this.props.setExplorerPinnedAction(!this.props.isExplorerPinned);
}}
/>
<Hotkey
combo="ctrl + shift + g"
global
label="Show git commit modal"
onKeyDown={() => {
this.props.showCommitModal();
}}
/>
</Hotkeys>
);
}
@ -410,6 +421,10 @@ const mapDispatchToProps = (dispatch: any) => {
dispatch(setPreviewModeAction(shouldSet)),
setExplorerPinnedAction: (shouldSet: boolean) =>
dispatch(setExplorerPinnedAction(shouldSet)),
showCommitModal: () =>
dispatch(
setIsGitSyncModalOpen({ isOpen: true, tab: GitSyncModalTab.DEPLOY }),
),
};
};

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";
import { useDispatch, useSelector } from "react-redux";
@ -11,6 +11,10 @@ import { getCurrentAppGitMetaData } from "selectors/applicationSelectors";
import BranchList from "../components/BranchList";
import { fetchBranchesInit } from "actions/gitSyncActions";
import Icon, { IconSize } from "components/ads/Icon";
import Tooltip from "components/ads/Tooltip";
import { Position } from "@blueprintjs/core";
import { isEllipsisActive } from "utils/helpers";
import { getGitStatus } from "selectors/gitSyncSelectors";
const ButtonContainer = styled.div`
display: flex;
@ -42,6 +46,8 @@ function BranchButton() {
const [isOpen, setIsOpen] = useState(false);
const dispatch = useDispatch();
const fetchBranches = () => dispatch(fetchBranchesInit());
const labelTarget = useRef<HTMLDivElement>(null);
const status = useSelector(getGitStatus);
useEffect(() => {
fetchBranches();
@ -58,12 +64,23 @@ function BranchButton() {
}}
placement="top-start"
>
<ButtonContainer className="t--branch-button">
<div className="icon">
<Icon name="git-branch" size={IconSize.XXXXL} />
</div>
<div className="label">{currentBranch}</div>
</ButtonContainer>
<Tooltip
boundary="window"
content={currentBranch || ""}
disabled={!isEllipsisActive(labelTarget.current)}
hoverOpenDelay={1000}
position={Position.TOP_LEFT}
>
<ButtonContainer className="t--branch-button">
<div className="icon">
<Icon name="git-branch" size={IconSize.XXXXL} />
</div>
<div className="label" ref={labelTarget}>
{currentBranch}
{!status?.isClean && "*"}
</div>
</ButtonContainer>
</Tooltip>
</Popover2>
);
}

View File

@ -3,16 +3,15 @@ import styled from "styled-components";
import BranchButton from "./BranchButton";
import { ReactComponent as UpArrow } from "assets/icons/ads/up-arrow.svg";
import { ReactComponent as DownArrow } from "assets/icons/ads/down-arrow.svg";
import { ReactComponent as Plus } from "assets/icons/ads/plus.svg";
import { ReactComponent as GitBranch } from "assets/icons/ads/git-branch.svg";
import {
COMMIT,
PUSH,
PULL,
COMMIT_CHANGES,
PULL_CHANGES,
MERGE,
CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES,
CONNECT_GIT,
CONFLICTS_FOUND,
NO_COMMITS_TO_PULL,
@ -22,7 +21,6 @@ import {
DURING_ONBOARDING_TOUR,
createMessage,
} from "constants/messages";
import { noop } from "lodash";
import Tooltip from "components/ads/Tooltip";
import { Colors } from "constants/Colors";
@ -39,6 +37,7 @@ import {
getPullInProgress,
getIsFetchingGitStatus,
getPullFailed,
getCountOfChangesToCommit,
} from "selectors/gitSyncSelectors";
import SpinnerLoader from "pages/common/SpinnerLoader";
import { inOnboarding } from "sagas/OnboardingSagas";
@ -55,7 +54,7 @@ type QuickActionButtonProps = {
const QuickActionButtonContainer = styled.div<{ disabled?: boolean }>`
padding: ${(props) => props.theme.spaces[1]}px
${(props) => props.theme.spaces[2]}px;
margin-left: ${(props) => props.theme.spaces[2]}px;
margin: 0 ${(props) => props.theme.spaces[2]}px;
cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
&:hover {
background-color: ${(props) =>
@ -65,15 +64,15 @@ const QuickActionButtonContainer = styled.div<{ disabled?: boolean }>`
overflow: visible;
.count {
position: absolute;
width: 18px;
height: 18px;
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
color: ${Colors.WHITE};
background-color: ${Colors.BLACK};
top: -3px;
left: 15px;
top: -8px;
left: 18px;
border-radius: 50%;
${(props) => getTypographyByKey(props, "p3")};
z-index: 1;
@ -89,7 +88,7 @@ const SpinnerContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 24px;
width: 29px;
height: 26px;
`;
@ -120,13 +119,16 @@ function QuickActionButton({
}
const getPullBtnStatus = (gitStatus: any, pullFailed: boolean) => {
const { behindCount } = gitStatus || {};
const { behindCount, isClean } = gitStatus || {};
let message = createMessage(NO_COMMITS_TO_PULL);
const disabled = behindCount === 0;
if (pullFailed) {
let disabled = behindCount === 0;
if (!isClean) {
disabled = true;
message = createMessage(CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES);
} else if (pullFailed) {
message = createMessage(CONFLICTS_FOUND);
} else if (behindCount > 0) {
message = createMessage(PULL);
message = createMessage(PULL_CHANGES);
}
return {
@ -136,34 +138,33 @@ const getPullBtnStatus = (gitStatus: any, pullFailed: boolean) => {
};
const getQuickActionButtons = ({
changesToCommit,
commit,
gitStatus,
isFetchingGitStatus,
merge,
pull,
pullDisabled,
pullTooltipMessage,
push,
showPullLoadingState,
}: {
changesToCommit: number;
commit: () => void;
push: () => void;
pull: () => void;
merge: () => void;
gitStatus: any;
isFetchingGitStatus: boolean;
pullDisabled: boolean;
pullTooltipMessage: string;
showPullLoadingState: boolean;
}) => {
return [
{
count: changesToCommit,
icon: <Plus />,
loading: isFetchingGitStatus,
onClick: commit,
tooltipText: createMessage(COMMIT),
},
{
icon: <UpArrow />,
onClick: push,
tooltipText: createMessage(PUSH),
tooltipText: createMessage(COMMIT_CHANGES),
},
{
count: gitStatus?.behindCount,
@ -189,6 +190,7 @@ const Container = styled.div`
`;
const StyledIcon = styled(GitCommitLine)`
cursor: default;
& path {
fill: ${Colors.DARK_GRAY};
}
@ -266,6 +268,7 @@ export default function QuickGitActions() {
const isPullInProgress = useSelector(getPullInProgress);
const isFetchingGitStatus = useSelector(getIsFetchingGitStatus);
const showPullLoadingState = isPullInProgress || isFetchingGitStatus;
const changesToCommit = useSelector(getCountOfChangesToCommit);
const quickActionButtons = getQuickActionButtons({
commit: () => {
@ -276,7 +279,6 @@ export default function QuickGitActions() {
}),
);
},
push: noop,
pull: () => dispatch(gitPullInit({ triggeredFromBottomBar: true })),
merge: () => {
dispatch(
@ -287,9 +289,11 @@ export default function QuickGitActions() {
);
},
gitStatus,
isFetchingGitStatus,
pullDisabled,
pullTooltipMessage,
showPullLoadingState,
changesToCommit,
});
return getFeatureFlags().GIT && isGitConnected ? (
<Container>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { Title } from "../components/StyledComponents";
import {
DEPLOY_YOUR_APPLICATION,
@ -9,14 +9,12 @@ import {
FETCH_GIT_STATUS,
GIT_NO_UPDATED_TOOLTIP,
GIT_UPSTREAM_CHANGES,
LEARN_MORE,
PULL_CHANGES,
GIT_CONFLICTING_INFO,
OPEN_REPO,
READ_DOCUMENTATION,
} from "constants/messages";
import styled, { useTheme } from "styled-components";
import TextInput from "components/ads/TextInput";
import Button, { Category, Size } from "components/ads/Button";
import Button, { Size } from "components/ads/Button";
import { LabelContainer } from "components/ads/Checkbox";
import {
@ -24,8 +22,8 @@ import {
getIsFetchingGitStatus,
getIsCommittingInProgress,
getIsPullingProgress,
getGitError,
getPullFailed,
getGitCommitAndPushError,
} from "selectors/gitSyncSelectors";
import { useDispatch, useSelector } from "react-redux";
@ -43,15 +41,19 @@ import {
import { getIsCommitSuccessful } from "selectors/gitSyncSelectors";
import StatusLoader from "../components/StatusLoader";
import { clearCommitSuccessfulState } from "../../../../actions/gitSyncActions";
import Statusbar from "pages/Editor/gitSync/components/Statusbar";
import GitSyncError from "../components/GitSyncError";
import Statusbar, {
StatusbarWrapper,
} from "pages/Editor/gitSync/components/Statusbar";
import GitChanged from "../components/GitChanged";
import Tooltip from "components/ads/Tooltip";
import Text, { TextType } from "components/ads/Text";
import { DOCS_BASE_URL } from "constants/ThirdPartyConstants";
import log from "loglevel";
import InfoWrapper from "../components/InfoWrapper";
import Link from "../components/Link";
import ConflictInfo from "../components/ConflictInfo";
import Icon, { IconSize } from "components/ads/Icon";
import { isMac } from "utils/helpers";
const Section = styled.div`
margin-bottom: ${(props) => props.theme.spaces[11]}px;
@ -65,8 +67,13 @@ const Row = styled.div`
const SectionTitle = styled.div`
${(props) => getTypographyByKey(props, "p1")};
color: ${Colors.CHARCOAL};
display: inline-flex;
& .branch {
color: ${Colors.CRUSTA};
width: 240px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
`;
@ -80,29 +87,35 @@ const Container = styled.div`
}
`;
const StatusbarWrapper = styled.div`
width: 252px;
height: 38px;
`;
const OpenRepoButton = styled(Button)`
margin-right: ${(props) => props.theme.spaces[3]}px;
`;
const INITIAL_COMMIT = "Initial Commit";
const NO_CHANGES_TO_COMMIT = "No changes to commit";
function SubmitWrapper(props: {
children: React.ReactNode;
onSubmit: () => void;
}) {
const onKeyDown = (e: React.KeyboardEvent) => {
const triggerSubmit = isMac()
? e.metaKey && e.key === "Enter"
: e.ctrlKey && e.key === "Enter";
if (triggerSubmit) props.onSubmit();
};
return <div onKeyDown={onKeyDown}>{props.children}</div>;
}
function Deploy() {
const [commitMessage, setCommitMessage] = useState(INITIAL_COMMIT);
const isCommittingInProgress = useSelector(getIsCommittingInProgress);
const gitMetaData = useSelector(getCurrentAppGitMetaData);
const gitStatus = useSelector(getGitStatus);
const isFetchingGitStatus = useSelector(getIsFetchingGitStatus);
const isPulingProgress = useSelector(getIsPullingProgress);
const isPullingProgress = useSelector(getIsPullingProgress);
const isCommitAndPushSuccessful = useSelector(getIsCommitSuccessful);
const hasChangesToCommit = !gitStatus?.isClean;
const gitError = useSelector(getGitError);
const gitError = useSelector(getGitCommitAndPushError);
const pullFailed = useSelector(getPullFailed);
const commitInputRef = useRef<HTMLInputElement>(null);
const currentBranch = gitMetaData?.branchName;
const dispatch = useDispatch();
@ -134,6 +147,7 @@ function Deploy() {
}, []);
const commitButtonDisabled = !hasChangesToCommit || !commitMessage;
const commitButtonLoading = isCommittingInProgress;
const commitInputDisabled = !hasChangesToCommit || isCommittingInProgress;
const commitRequired = gitStatus?.modifiedPages || gitStatus?.modifiedQueries;
const isConflicting = !isFetchingGitStatus && pullFailed;
@ -157,8 +171,12 @@ function Deploy() {
const theme = useTheme() as Theme;
log.log(gitStatus);
log.log(gitError);
useEffect(() => {
if (!commitInputDisabled && commitInputRef.current) {
commitInputRef.current.focus();
}
}, [commitInputDisabled]);
return (
<Container>
<Title>{createMessage(DEPLOY_YOUR_APPLICATION)}</Title>
@ -167,72 +185,59 @@ function Deploy() {
<Row>
<SectionTitle>
<span>{createMessage(COMMIT_TO)}</span>
<span className="branch">&nbsp;{currentBranch}</span>
<div className="branch">&nbsp;{currentBranch}</div>
</SectionTitle>
</Row>
<Space size={3} />
<TextInput
autoFocus
disabled={!hasChangesToCommit || isFetchingGitStatus}
fill
onChange={setCommitMessage}
trimValue={false}
value={commitMessageDisplay}
/>
<SubmitWrapper
onSubmit={() => {
if (!commitButtonDisabled) handleCommit(true);
}}
>
<TextInput
autoFocus
disabled={commitInputDisabled}
fill
onChange={setCommitMessage}
ref={commitInputRef}
trimValue={false}
value={commitMessageDisplay}
/>
</SubmitWrapper>
{isFetchingGitStatus && (
<StatusLoader loaderMsg={createMessage(FETCH_GIT_STATUS)} />
)}
<Space size={11} />
{pullRequired && !isConflicting && (
<InfoWrapper>
<Text style={{ marginRight: theme.spaces[2] }} type={TextType.P3}>
{createMessage(GIT_UPSTREAM_CHANGES)}
</Text>
<Link link={DOCS_BASE_URL} text={createMessage(LEARN_MORE)} />
<Icon
fillColor={Colors.YELLOW_LIGHT}
name="info"
size={IconSize.XXXL}
/>
<div style={{ display: "block" }}>
<Text style={{ marginRight: theme.spaces[2] }} type={TextType.P3}>
{createMessage(GIT_UPSTREAM_CHANGES)}
</Text>
<Link
link={DOCS_BASE_URL}
text={createMessage(READ_DOCUMENTATION)}
/>
</div>
</InfoWrapper>
)}
{pullRequired && !isConflicting && (
<Button
className="t--commit-button"
isLoading={isPulingProgress}
isLoading={isPullingProgress}
onClick={handlePull}
size={Size.medium}
size={Size.large}
tag="button"
text={createMessage(PULL_CHANGES)}
width="max-content"
/>
)}
{isConflicting && (
<InfoWrapper isError>
<Text style={{ marginRight: theme.spaces[2] }} type={TextType.P3}>
{createMessage(GIT_CONFLICTING_INFO)}
</Text>
<Link link={DOCS_BASE_URL} text={createMessage(LEARN_MORE)} />
</InfoWrapper>
)}
{isConflicting && (
<Row>
<OpenRepoButton
category={Category.tertiary}
className="t--commit-button"
href={gitMetaData?.remoteUrl}
size={Size.medium}
tag="a"
target="_blank"
text={createMessage(OPEN_REPO)}
width="max-content"
/>
<Button
className="t--commit-button"
isLoading={isPulingProgress}
onClick={handlePull}
size={Size.medium}
tag="button"
text={createMessage(PULL_CHANGES)}
width="max-content"
/>
</Row>
)}
<ConflictInfo isConflicting={isConflicting} />
{showCommitButton && (
<Tooltip
autoFocus={false}
@ -246,7 +251,7 @@ function Deploy() {
disabled={commitButtonDisabled}
isLoading={commitButtonLoading}
onClick={() => handleCommit(true)}
size={Size.medium}
size={Size.large}
tag="button"
text={commitButtonText}
width="max-content"
@ -262,7 +267,6 @@ function Deploy() {
/>
</StatusbarWrapper>
)}
<GitSyncError />
</Section>
{!pullRequired && !isConflicting && (
<DeployPreview showSuccess={isCommitAndPushSuccessful} />

View File

@ -14,6 +14,7 @@ import {
createMessage,
REMOTE_URL_INPUT_PLACEHOLDER,
CONNECTING_REPO,
LEARN_MORE,
} from "constants/messages";
import styled from "styled-components";
import TextInput from "components/ads/TextInput";
@ -22,17 +23,14 @@ import { AUTH_TYPE_OPTIONS } from "../constants";
import { Colors } from "constants/Colors";
import Button, { Category, Size } from "components/ads/Button";
import { useGitConnect, useSSHKeyPair, useUserGitConfig } from "../hooks";
import { Toaster } from "components/ads/Toast";
import { Variant } from "components/ads/common";
import { useDispatch, useSelector } from "react-redux";
import copy from "copy-to-clipboard";
import { getCurrentAppGitMetaData } from "selectors/applicationSelectors";
import Text, { TextType } from "components/ads/Text";
import { getGitError } from "selectors/gitSyncSelectors";
import {
fetchGlobalGitConfigInit,
fetchLocalGitConfigInit,
remoteUrlInputValue,
updateLocalGitConfigInit,
} from "actions/gitSyncActions";
import { emailValidator } from "components/ads/TextInput";
@ -42,18 +40,20 @@ import {
CONNECT_BTN_LABEL,
PASTE_SSH_URL_INFO,
GENERATE_KEY,
COPIED_SSH_KEY,
INVALID_USER_DETAILS_MSG,
} from "constants/messages";
import {
getIsFetchingGlobalGitConfig,
getIsFetchingLocalGitConfig,
getTempRemoteUrl,
} from "selectors/gitSyncSelectors";
import Statusbar from "pages/Editor/gitSync/components/Statusbar";
import Statusbar, {
StatusbarWrapper,
} from "pages/Editor/gitSync/components/Statusbar";
import ScrollIndicator from "components/ads/ScrollIndicator";
import DeployedKeyUI from "../components/DeployedKeyUI";
import GitSyncError from "../components/GitSyncError";
import log from "loglevel";
import Link from "../components/Link";
import { DOCS_BASE_URL } from "constants/ThirdPartyConstants";
export const UrlOptionContainer = styled.div`
display: flex;
@ -94,14 +94,11 @@ const Container = styled.div`
`;
const RemoteUrlInfoWrapper = styled.div`
margin-top: ${(props) => props.theme.spaces[3]}px;
margin-bottom: ${(props) => props.theme.spaces[3]}px;
display: flex;
`;
const Section = styled.div``;
const StatusbarWrapper = styled.div`
width: 252px;
height: 38px;
`;
const StickyMenuWrapper = styled.div`
position: sticky;
@ -132,15 +129,14 @@ type Props = {
function GitConnection({ isImport }: Props) {
const { remoteUrl: remoteUrlInStore = "" } =
useSelector(getCurrentAppGitMetaData) || ({} as any);
const { tempRemoteUrl = "" } = useSelector(getTempRemoteUrl) || ({} as any);
const [remoteUrl, setRemoteUrl] = useState(remoteUrlInStore);
const [remoteUrl, setRemoteUrl] = useState(remoteUrlInStore || tempRemoteUrl);
const isGitConnected = !!remoteUrlInStore;
const isFetchingGlobalGitConfig = useSelector(getIsFetchingGlobalGitConfig);
const isFetchingLocalGitConfig = useSelector(getIsFetchingLocalGitConfig);
const [triedSubmit, setTriedSubmit] = useState(false);
const gitError = useSelector(getGitError);
const dispatch = useDispatch();
const {
@ -197,18 +193,13 @@ function GitConnection({ isImport }: Props) {
copy(SSHKeyPair);
setShowCopied(true);
stopShowingCopiedAfterDelay();
Toaster.show({
text: createMessage(COPIED_SSH_KEY),
variant: Variant.success,
});
}
};
useEffect(() => {
// when disconnected remoteURL becomes undefined
if (!remoteUrlInStore) {
setRemoteUrl("");
setRemoteUrl(tempRemoteUrl || "");
}
}, [remoteUrlInStore]);
@ -234,6 +225,7 @@ function GitConnection({ isImport }: Props) {
};
const onSubmit = useCallback(() => {
if (isConnectingToGit) return;
setTriedSubmit(true);
if (
@ -257,10 +249,6 @@ function GitConnection({ isImport }: Props) {
});
}
}
} else {
Toaster.show({
text: createMessage(INVALID_USER_DETAILS_MSG),
});
}
}, [
updateLocalGitConfigInit,
@ -281,6 +269,7 @@ function GitConnection({ isImport }: Props) {
const isInvalid = remoteUrlIsInvalid(value);
setIsValidRemoteUrl(isInvalid);
setRemoteUrl(value);
dispatch(remoteUrlInputValue({ tempRemoteUrl: value }));
};
const submitButtonDisabled = useMemo(() => {
@ -332,14 +321,18 @@ function GitConnection({ isImport }: Props) {
const scrollWrapperRef = React.createRef<HTMLDivElement>();
useEffect(() => {
if (gitError?.message && scrollWrapperRef.current) {
const top = scrollWrapperRef.current.scrollHeight;
scrollWrapperRef.current?.scrollTo({ top: top, behavior: "smooth" });
const scrolling = useCallback(() => {
if (scrollWrapperRef.current) {
setTimeout(() => {
const top = scrollWrapperRef.current?.scrollHeight || 0;
scrollWrapperRef.current?.scrollTo({
top: top,
behavior: "smooth",
});
}, 100);
}
}, [gitError]);
}, [scrollWrapperRef]);
log.log(gitError);
return (
<Container ref={scrollWrapperRef}>
<Section>
@ -352,6 +345,20 @@ function GitConnection({ isImport }: Props) {
{createMessage(REMOTE_URL)}
</Text>
</UrlOptionContainer>
{!SSHKeyPair ? (
<RemoteUrlInfoWrapper>
<Text color={Colors.GREY_9} type={TextType.P3}>
{createMessage(REMOTE_URL_INFO)}
</Text>
<Space horizontal size={1} />
<Link
color={Colors.PRIMARY_ORANGE}
hasIcon={false}
link={DOCS_BASE_URL}
text={createMessage(LEARN_MORE)}
/>
</RemoteUrlInfoWrapper>
) : null}
<UrlContainer>
<UrlInputContainer>
<TextInput
@ -367,22 +374,13 @@ function GitConnection({ isImport }: Props) {
/>
</UrlInputContainer>
</UrlContainer>
{!isInvalidRemoteUrl && !SSHKeyPair ? (
<RemoteUrlInfoWrapper>
<Text color={Colors.GREY_9} type={TextType.P3}>
{createMessage(REMOTE_URL_INFO)}
</Text>
</RemoteUrlInfoWrapper>
) : null}
{!SSHKeyPair ? (
remoteUrl && (
remoteUrl &&
!isInvalidRemoteUrl && (
<ButtonContainer topMargin={10}>
<Button
category={Category.primary}
className="t--submit-repo-url-button"
disabled={!remoteUrl || isInvalidRemoteUrl}
isLoading={generatingSSHKey || fetchingSSHKeyPair}
onClick={() => generateSSHKey()}
size={Size.large}
@ -393,7 +391,7 @@ function GitConnection({ isImport }: Props) {
)
) : (
<DeployedKeyUI
SSHKeyPair={SSHKeyPair}
SSHKeyPair={SSHKeyPair || ""}
copyToClipboard={copyToClipboard}
deployKeyDocUrl={deployKeyDocUrl}
showCopied={showCopied}
@ -414,7 +412,7 @@ function GitConnection({ isImport }: Props) {
triedSubmit={triedSubmit}
useGlobalConfig={useGlobalConfig}
/>
<ButtonContainer topMargin={11}>
<ButtonContainer topMargin={0}>
{isConnectingToGit && (
<StatusbarWrapper>
<Statusbar
@ -442,7 +440,7 @@ function GitConnection({ isImport }: Props) {
}
/>
)}
{!isConnectingToGit && <GitSyncError />}
{!isConnectingToGit && <GitSyncError onDisplay={scrolling} />}
</ButtonContainer>
</>
) : null}

View File

@ -6,15 +6,23 @@ import {
createMessage,
MERGE_CHANGES,
SELECT_BRANCH_TO_MERGE,
CANNOT_MERGE_DUE_TO_UNCOMMITTED_CHANGES,
FETCH_MERGE_STATUS,
FETCH_GIT_STATUS,
IS_MERGING,
} from "constants/messages";
import { ReactComponent as MergeIcon } from "assets/icons/ads/git-merge.svg";
import { ReactComponent as LeftArrow } from "assets/icons/ads/arrow-left-1.svg";
import styled from "styled-components";
import Button, { Size } from "components/ads/Button";
import { useSelector, useDispatch } from "react-redux";
import { getCurrentAppGitMetaData } from "selectors/applicationSelectors";
import { getGitBranches, getMergeStatus } from "selectors/gitSyncSelectors";
import {
getGitBranches,
getGitStatus,
getIsFetchingGitStatus,
getMergeStatus,
} from "selectors/gitSyncSelectors";
import { DropdownOptions } from "../../GeneratePage/components/constants";
import {
mergeBranchInit,
@ -25,10 +33,16 @@ import {
import {
getIsFetchingMergeStatus,
getFetchingBranches,
getIsMergeInProgress,
// getPullFailed,
} from "selectors/gitSyncSelectors";
import { fetchMergeStatusInit } from "actions/gitSyncActions";
import MergeStatus from "../components/MergeStatus";
import MergeStatus, { MERGE_STATUS_STATE } from "../components/MergeStatus";
import ConflictInfo from "../components/ConflictInfo";
import Statusbar, {
StatusbarWrapper,
} from "pages/Editor/gitSync/components/Statusbar";
import { getIsStartingWithRemoteBranches } from "pages/Editor/gitSync/utils";
const Row = styled.div`
display: flex;
@ -36,6 +50,7 @@ const Row = styled.div`
`;
const DEFAULT_OPTION = "--Select--";
const DROPDOWNMENU_MAXHEIGHT = "350px";
export default function Merge() {
const dispatch = useDispatch();
@ -44,33 +59,62 @@ export default function Merge() {
const isFetchingBranches = useSelector(getFetchingBranches);
const isFetchingMergeStatus = useSelector(getIsFetchingMergeStatus);
const mergeStatus = useSelector(getMergeStatus);
const isMergeAble = mergeStatus?.isMergeAble;
const gitStatus: any = useSelector(getGitStatus);
const isMergeAble = mergeStatus?.isMergeAble && gitStatus?.isClean;
const isFetchingGitStatus = useSelector(getIsFetchingGitStatus);
let mergeStatusMessage = !gitStatus?.isClean
? createMessage(CANNOT_MERGE_DUE_TO_UNCOMMITTED_CHANGES)
: mergeStatus?.message;
// const pullFailed: any = useSelector(getPullFailed);
const currentBranch = gitMetaData?.branchName;
const isMerging = useSelector(getIsMergeInProgress);
const [selectedBranchOption, setSelectedBranchOption] = useState({
label: DEFAULT_OPTION,
value: DEFAULT_OPTION,
});
/**
* Removes the current branch from the list
* Also filters the remote branches
*/
const branchList = useMemo(() => {
const listOfBranches: DropdownOptions = [];
gitBranches.map((branchObj) => {
const branchOptions: DropdownOptions = [];
let index = 0;
while (true) {
if (index === gitBranches.length) break;
const branchObj = gitBranches[index];
if (currentBranch !== branchObj.branchName) {
if (!branchObj.default) {
listOfBranches.push({
branchOptions.push({
label: branchObj.branchName,
value: branchObj.branchName,
});
} else {
listOfBranches.unshift({
branchOptions.unshift({
label: branchObj.branchName,
value: branchObj.branchName,
});
}
}
const nextBranchObj = gitBranches[index + 1];
if (
getIsStartingWithRemoteBranches(
branchObj.branchName,
nextBranchObj?.branchName,
)
)
break;
index++;
}
branchOptions.unshift({
label: "Local branches",
isSectionHeader: true,
});
return listOfBranches;
return branchOptions;
}, [gitBranches]);
const currentBranchDropdownOption = {
@ -118,19 +162,35 @@ export default function Merge() {
isFetchingMergeStatus ||
!isMergeAble;
let status = MERGE_STATUS_STATE.NONE;
if (isFetchingGitStatus) {
status = MERGE_STATUS_STATE.FETCHING;
mergeStatusMessage = createMessage(FETCH_GIT_STATUS);
} else if (!gitStatus?.isClean) {
status = MERGE_STATUS_STATE.NOT_MERGEABLE;
} else if (isFetchingMergeStatus) {
status = MERGE_STATUS_STATE.FETCHING;
mergeStatusMessage = createMessage(FETCH_MERGE_STATUS);
} else if (mergeStatus && mergeStatus?.isMergeAble) {
status = MERGE_STATUS_STATE.MERGEABLE;
} else if (mergeStatus && !mergeStatus?.isMergeAble) {
status = MERGE_STATUS_STATE.NOT_MERGEABLE;
}
const isConflicting = (mergeStatus?.conflictingFiles?.length || 0) > 0;
const showMergeButton = !isConflicting && !isFetchingGitStatus && !isMerging;
return (
<>
<Title>{createMessage(MERGE_CHANGES)}</Title>
<Caption>{createMessage(SELECT_BRANCH_TO_MERGE)}</Caption>
<Space size={4} />
<Row>
<MergeIcon />
<Space horizontal size={3} />
<Dropdown
dropdownMaxHeight={DROPDOWNMENU_MAXHEIGHT}
enableSearch
fillOptions
hasError={status === MERGE_STATUS_STATE.NOT_MERGEABLE}
isLoading={isFetchingBranches}
onSelect={(value?: string) => {
if (value) setSelectedBranchOption({ label: value, value: value });
@ -138,6 +198,7 @@ export default function Merge() {
options={branchList}
selected={selectedBranchOption}
showLabelOnly
truncateOption
width={"220px"}
/>
@ -147,23 +208,37 @@ export default function Merge() {
<Dropdown
className="textInput"
disabled
dropdownMaxHeight={DROPDOWNMENU_MAXHEIGHT}
onSelect={() => null}
options={[currentBranchDropdownOption]}
selected={currentBranchDropdownOption}
truncateOption
width={"220px"}
/>
</Row>
<MergeStatus />
<MergeStatus message={mergeStatusMessage} status={status} />
<Space size={10} />
<Button
disabled={mergeBtnDisabled}
onClick={mergeHandler}
size={Size.medium}
tag="button"
text={createMessage(MERGE_CHANGES)}
width="max-content"
/>
<ConflictInfo isConflicting={isConflicting} />
{showMergeButton && (
<Button
disabled={mergeBtnDisabled}
isLoading={isMerging}
onClick={mergeHandler}
size={Size.large}
tag="button"
text={createMessage(MERGE_CHANGES)}
width="max-content"
/>
)}
{isMerging && (
<StatusbarWrapper>
<Statusbar
completed={!isMerging}
message={createMessage(IS_MERGING)}
period={6}
/>
</StatusbarWrapper>
)}
</>
);
}

View File

@ -38,6 +38,12 @@ import { get } from "lodash";
import Tooltip from "components/ads/Tooltip";
import { Position } from "@blueprintjs/core";
import Spinner from "components/ads/Spinner";
import Text, { TextType } from "components/ads/Text";
import { Classes } from "components/ads/common";
import { isEllipsisActive } from "utils/helpers";
import { getIsStartingWithRemoteBranches } from "pages/Editor/gitSync/utils";
import SegmentHeader from "components/ads/ListSegmentHeader";
const ListContainer = styled.div`
flex: 1;
@ -62,6 +68,7 @@ const BranchDropdownContainer = styled.div`
const BranchListItemContainer = styled.div<{
hovered?: boolean;
active?: boolean;
isDefault?: boolean;
}>`
padding: ${(props) =>
`${props.theme.spaces[4]}px ${props.theme.spaces[5]}px`};
@ -78,7 +85,13 @@ const BranchListItemContainer = styled.div<{
background-color: ${(props) =>
props.hovered || props.active ? Colors.GREY_3 : ""};
display: flex;
display: ${(props) => (props.isDefault ? "flex" : "block")};
.${Classes.TEXT} {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
`;
// used for skeletons
@ -177,7 +190,7 @@ function BranchListItem({
shouldScrollIntoView,
}: any) {
const itemRef = React.useRef<HTMLDivElement>(null);
const textRef = React.useRef<HTMLSpanElement>(null);
useEffect(() => {
if (itemRef.current && shouldScrollIntoView)
scrollIntoView(itemRef.current, {
@ -192,10 +205,20 @@ function BranchListItem({
active={active}
className={className}
hovered={hovered}
isDefault={isDefault}
onClick={onClick}
ref={itemRef}
>
{branch}
<Tooltip
boundary="window"
content={branch}
disabled={!isEllipsisActive(textRef.current)}
position={Position.TOP}
>
<Text ref={textRef} type={TextType.P1}>
{branch}
</Text>
</Tooltip>
{isDefault && <DefaultTag />}
</BranchListItemContainer>
);
@ -223,7 +246,7 @@ function BranchesLoading() {
export const removeSpecialChars = (value: string) => {
const separatorRegex = /(?!\/)\W+/;
return value.split(separatorRegex).join("-");
return value.split(separatorRegex).join("_");
};
// filter the branches according to the search text
@ -353,7 +376,8 @@ export default function BranchList(props: {
setIsPopupOpen?: (flag: boolean) => void;
}) {
const dispatch = useDispatch();
const fetchBranches = () => dispatch(fetchBranchesInit());
const pruneAndFetchBranches = () =>
dispatch(fetchBranchesInit({ pruneBranches: true }));
const branches = useSelector(getGitBranches);
const branchNames = useSelector(getGitBranchNames);
@ -437,7 +461,7 @@ export default function BranchList(props: {
if (typeof props.setIsPopupOpen === "function")
props.setIsPopupOpen(false);
}}
fetchBranches={fetchBranches}
fetchBranches={pruneAndFetchBranches}
/>
<Space size={4} />
<div style={{ width: 300 }}>
@ -471,25 +495,32 @@ export default function BranchList(props: {
shouldScrollIntoView={activeHoverIndex === 0}
/>
)}
<SegmentHeader title={"Local branches"} />
{filteredBranches.map((branch: string, index: number) => (
<BranchListItem
active={currentBranch === branch}
branch={branch}
className="t--branch-item"
hovered={getIsActiveItem(
isCreateNewBranchInputValid,
activeHoverIndex,
index,
)}
isDefault={branch === defaultBranch}
key={branch}
onClick={() => switchBranch(branch)}
shouldScrollIntoView={getIsActiveItem(
isCreateNewBranchInputValid,
activeHoverIndex,
index,
)}
/>
<>
{getIsStartingWithRemoteBranches(
filteredBranches[index - 1],
branch,
) && <SegmentHeader title={"Remote branches"} />}
<BranchListItem
active={currentBranch === branch}
branch={branch}
className="t--branch-item"
hovered={getIsActiveItem(
isCreateNewBranchInputValid,
activeHoverIndex,
index,
)}
isDefault={branch === defaultBranch}
key={branch}
onClick={() => switchBranch(branch)}
shouldScrollIntoView={getIsActiveItem(
isCreateNewBranchInputValid,
activeHoverIndex,
index,
)}
/>
</>
))}
</ListContainer>
)}

View File

@ -0,0 +1,99 @@
import React from "react";
import styled, { useTheme } from "styled-components";
import Text, { TextType } from "components/ads/Text";
import InfoWrapper from "./InfoWrapper";
import Link from "./Link";
import {
createMessage,
GIT_CONFLICTING_INFO,
LEARN_MORE,
OPEN_REPO,
} from "constants/messages";
import { DOCS_BASE_URL } from "constants/ThirdPartyConstants";
import { Theme } from "constants/DefaultTheme";
import Button, { Category, Size } from "components/ads/Button";
import { useSelector } from "store";
import { getCurrentAppGitMetaData } from "selectors/applicationSelectors";
import Icon, { IconSize } from "components/ads/Icon";
import { Colors } from "constants/Colors";
const Row = styled.div`
display: flex;
align-items: center;
`;
const OpenRepoButton = styled(Button)`
margin-right: ${(props) => props.theme.spaces[3]}px;
`;
type CIPropType = {
isConflicting?: boolean;
};
export default function ConflictInfo(props: CIPropType) {
const { isConflicting } = props;
const theme = useTheme() as Theme;
const gitMetaData = useSelector(getCurrentAppGitMetaData);
const originUrl = gitMetaData?.remoteUrl;
/**
* Converting ssh url to https url for opening the repo on browser
* Github :
* SSH: git@github.com:user/repo.git
* HTTPS: https://github.com/user/repo.git
* Gitlab:
* SSH: git@gitlab.com:abhijeet25/first_project.git
* HTTPS: https://gitlab.com/abhijeet25/first_project.git
* Bitbucket
* SSH: git@bitbucket.org:abhvsn/onec2_firstapp.git
* HTTPS: https://abhvsn@bitbucket.org/abhvsn/onec2_firstapp.git
*/
let remoteUrl = originUrl;
if (originUrl && new RegExp("git@*").test(originUrl)) {
remoteUrl = remoteUrl?.replace(":", "/");
remoteUrl = remoteUrl?.replace(/git@/, "https://");
// bitbucket repo
if (new RegExp("bitbucket.org").test(originUrl)) {
const match = remoteUrl?.match(/\/\w+/g);
if (match && match.length > 2) {
remoteUrl = remoteUrl?.replace(
/bitbucket.org/,
match[1].substr(1) + "@bitbucket.org",
);
}
}
}
return isConflicting ? (
<>
<InfoWrapper isError>
<Icon fillColor={Colors.CRIMSON} name="info" size={IconSize.XXXL} />
<div style={{ display: "block" }}>
<Text
color={Colors.CRIMSON}
style={{ marginRight: theme.spaces[2] }}
type={TextType.P3}
>
{createMessage(GIT_CONFLICTING_INFO)}
</Text>
<Link
color={Colors.CRIMSON}
link={DOCS_BASE_URL}
text={createMessage(LEARN_MORE)}
/>
</div>
</InfoWrapper>
<Row>
<OpenRepoButton
category={Category.tertiary}
className="t--commit-button"
href={remoteUrl}
size={Size.large}
tag="a"
target="_blank"
text={createMessage(OPEN_REPO)}
width="max-content"
/>
</Row>
</>
) : null;
}

View File

@ -76,8 +76,7 @@ export default function DeployPreview(props: { showSuccess: boolean }) {
lastDeployedAt,
)} ago`
: "";
return (
return lastDeployedAt ? (
<Container>
<CloudIconWrapper>
{props.showSuccess ? (
@ -105,5 +104,5 @@ export default function DeployPreview(props: { showSuccess: boolean }) {
</Text>
</ContentWrapper>
</Container>
);
) : null;
}

View File

@ -84,11 +84,12 @@ const KeyText = styled.span`
const LintText = styled.a`
:hover {
text-decoration: none;
color: ${Colors.CRUSTA};
}
color: ${Colors.CRUSTA};
cursor: pointer;
font-weight: 500;
margin-left: ${(props) => props.theme.spaces[1]}px;
`;
type DeployedKeyUIProps = {
@ -106,7 +107,7 @@ function DeployedKeyUI(props: DeployedKeyUIProps) {
<Text color={Colors.GREY_9} type={TextType.P3}>
{createMessage(DEPLOY_KEY_USAGE_GUIDE_MESSAGE)}
<LintText href={deployKeyDocUrl} target="_blank">
&nbsp;LEARN MORE
LEARN MORE
</LintText>
</Text>
<FlexRow>

View File

@ -10,9 +10,9 @@ import {
getIsFetchingGitStatus,
} from "selectors/gitSyncSelectors";
const Loader = styled.div`
const Skeleton = styled.div`
width: 135px;
height: 26px;
height: ${(props) => props.theme.spaces[9]}px;
background: linear-gradient(
90deg,
${Colors.GREY_2} 0%,
@ -23,10 +23,14 @@ const Loader = styled.div`
const Wrapper = styled.div`
width: 178px;
height: ${(props) => props.theme.spaces[9]}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`
@ -39,7 +43,7 @@ export enum Kind {
widget = "widget",
query = "query",
commit = "commit",
pullRequest = "pullRequest",
// pullRequest = "pullRequest",
}
type GitSyncProps = {
@ -50,36 +54,31 @@ 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" : ""
(status?.modifiedPages || 0) === 1 ? "" : "s"
} updated`;
iconName = "widget";
break;
case Kind.query:
message = `${status?.modifiedQueries || 0} ${
(status?.modifiedQueries || 0) > 1 ? "queries" : "query"
(status?.modifiedQueries || 0) === 1 ? "query" : "queries"
} modified`;
iconName = "query";
break;
case Kind.commit:
message = `${status?.aheadCount || 0} commit${
(status?.aheadCount || 0) > 1 ? "s" : ""
(status?.aheadCount || 0) === 1 ? "" : "s"
} to push`;
iconName = "git-commit";
break;
case Kind.pullRequest:
message = `${status?.behindCount || 0} pull request${
(status?.behindCount || 0) > 1 ? "s" : ""
} pending`;
iconName = "git-pull-request";
break;
}
return loading ? (
<Loader />
<Skeleton />
) : (
<Wrapper>
<Icon fillColor={Colors.GREY_10} name={iconName} size={IconSize.XXL} />

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import styled from "constants/DefaultTheme";
import { Classes } from "components/ads/common";
import Text, { Case, FontWeight, TextType } from "components/ads/Text";
@ -7,7 +7,7 @@ import Icon, { IconSize } from "components/ads/Icon";
import { DOCS_BASE_URL } from "constants/ThirdPartyConstants";
import { createMessage, READ_DOCUMENTATION } from "constants/messages";
import { useSelector } from "store";
import { getGitError } from "selectors/gitSyncSelectors";
import { getGitConnectError } from "selectors/gitSyncSelectors";
const ErrorWrapper = styled.div`
padding: 24px 0px;
@ -32,8 +32,12 @@ const LintText = styled.a`
cursor: pointer;
`;
export default function GitSyncError() {
const error = useSelector(getGitError);
export default function GitSyncError({
onDisplay,
}: {
onDisplay?: () => void;
}) {
const error = useSelector(getGitConnectError);
const titleMessage = error?.errorType
? error.errorType.replaceAll("_", " ")
: "";
@ -44,9 +48,14 @@ export default function GitSyncError() {
errorVisible = error.message.indexOf("git push failed") < 0;
}
}
useEffect(() => {
if (errorVisible && onDisplay) {
onDisplay();
}
}, []);
return errorVisible ? (
<ErrorWrapper>
{titleMessage.length && (
{titleMessage.length > 0 && (
<Text
case={Case.UPPERCASE}
color={Colors.ERROR_RED}

View File

@ -1,5 +1,6 @@
import styled from "styled-components";
import { Colors } from "constants/Colors";
import { Classes } from "components/ads/common";
const InfoWrapper = styled.div<{ isError?: boolean }>`
width: 100%;
@ -7,6 +8,14 @@ const InfoWrapper = styled.div<{ isError?: boolean }>`
background: ${(props) =>
props.isError ? Colors.FAIR_PINK : Colors.WARNING_OUTLINE_HOVER};
margin-bottom: ${(props) => props.theme.spaces[4]}px;
display: inline-flex;
.${Classes.TEXT} {
display: block;
}
.${Classes.ICON} {
margin-right: ${(props) => props.theme.spaces[8]}px;
margin-left: ${(props) => props.theme.spaces[4]}px;
}
`;
export default InfoWrapper;

View File

@ -5,7 +5,7 @@ import Text, { Case, FontWeight, TextType } from "components/ads/Text";
import Icon, { IconSize } from "components/ads/Icon";
import { Classes } from "components/ads/common";
const LinkText = styled.div`
const LinkText = styled.div<{ color?: string }>`
cursor: pointer;
.${Classes.ICON} {
margin-left: ${(props) => props.theme.spaces[3]}px;
@ -14,23 +14,44 @@ const LinkText = styled.div`
display: inline-flex;
align-items: center;
justify-content: center;
&:hover {
text-decoration: underline;
text-decoration-color: ${(props) => props.color};
}
`;
export default function Link({ link, text }: { link: string; text: string }) {
export default function Link({
color,
hasIcon = true,
link,
text,
}: {
color?: string;
hasIcon?: boolean;
link: string;
text: string;
}) {
const onClick = () => {
window.open(link, "_blank");
};
return (
<LinkText onClick={onClick}>
<LinkText color={color || Colors.CHARCOAL} onClick={onClick}>
<Text
case={Case.UPPERCASE}
color={Colors.CHARCOAL}
color={color || Colors.CHARCOAL}
type={TextType.P3}
weight={FontWeight.BOLD}
>
{text}
</Text>
<Icon name="right-arrow" size={IconSize.SMALL} />
{hasIcon && (
<Icon
fillColor={color || Colors.CHARCOAL}
hoverFillColor={color || Colors.CHARCOAL}
name="right-arrow"
size={IconSize.SMALL}
/>
)}
</LinkText>
);
}

View File

@ -1,19 +1,6 @@
import React from "react";
import {
createMessage,
FETCH_MERGE_STATUS,
// FETCH_MERGE_STATUS_FAILURE,
MERGE_CONFLICT_ERROR,
NO_MERGE_CONFLICT,
} from "constants/messages";
import styled from "constants/DefaultTheme";
import StatusLoader from "./StatusLoader";
import { Space } from "./StyledComponents";
import { useSelector } from "react-redux";
import {
getIsFetchingMergeStatus,
getMergeStatus,
} from "selectors/gitSyncSelectors";
import Text, { TextType } from "components/ads/Text";
import ErrorWarning from "remixicon-react/ErrorWarningLineIcon";
import CheckLine from "remixicon-react/CheckLineIcon";
@ -23,10 +10,10 @@ const Flex = styled.div`
display: flex;
`;
const MERGE_STATUS_STATE = {
export const MERGE_STATUS_STATE = {
FETCHING: "FETCHING",
NO_CONFLICT: "NO_CONFLICT",
MERGE_CONFLICT: "MERGE_CONFLICT",
MERGEABLE: "MERGEABLE",
NOT_MERGEABLE: "NOT_MERGEABLE",
NONE: "NONE",
};
@ -36,31 +23,23 @@ const Wrapper = styled.div`
margin-top: ${(props) => `${props.theme.spaces[3]}px`};
`;
function MergeStatus() {
const isFetchingMergeStatus = useSelector(getIsFetchingMergeStatus);
const mergeStatus = useSelector(getMergeStatus);
let status = MERGE_STATUS_STATE.NONE;
if (isFetchingMergeStatus) {
status = MERGE_STATUS_STATE.FETCHING;
} else if (mergeStatus && mergeStatus?.isMergeAble) {
status = MERGE_STATUS_STATE.NO_CONFLICT;
} else if (mergeStatus && !mergeStatus?.isMergeAble) {
status = MERGE_STATUS_STATE.MERGE_CONFLICT;
}
function MergeStatus({
message = "",
status,
}: {
status: string;
message?: string;
}) {
switch (status) {
case MERGE_STATUS_STATE.FETCHING:
return (
<Flex>
<Space horizontal size={10} />
<StatusLoader loaderMsg={createMessage(FETCH_MERGE_STATUS)} />
<StatusLoader loaderMsg={message} />
</Flex>
);
case MERGE_STATUS_STATE.NO_CONFLICT:
case MERGE_STATUS_STATE.MERGEABLE:
return (
<Flex>
<Space horizontal size={10} />
<Wrapper>
<CheckLine color={Colors.GREEN} size={18} />
<Text
@ -69,15 +48,14 @@ function MergeStatus() {
type={TextType.P3}
weight="600"
>
{createMessage(NO_MERGE_CONFLICT)}
{message}
</Text>
</Wrapper>
</Flex>
);
case MERGE_STATUS_STATE.MERGE_CONFLICT:
case MERGE_STATUS_STATE.NOT_MERGEABLE:
return (
<Flex>
<Space horizontal size={10} />
<Wrapper>
<ErrorWarning color={Colors.CRIMSON} size={18} />
<Text
@ -86,7 +64,7 @@ function MergeStatus() {
type={TextType.P3}
weight="600"
>
{createMessage(MERGE_CONFLICT_ERROR)}
{message}
</Text>
</Wrapper>
</Flex>

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import Statusbar from "components/ads/Statusbar";
import styled from "styled-components";
type StatusbarProps = {
completed: boolean;
@ -8,6 +9,11 @@ type StatusbarProps = {
onHide?: () => void;
};
export const StatusbarWrapper = styled.div`
width: 252px;
height: 38px;
`;
export default function GitSyncStatusbar(props: StatusbarProps) {
const { completed, message, period } = props;
const [percentage, setPercentage] = useState(0);
@ -28,5 +34,12 @@ export default function GitSyncStatusbar(props: StatusbarProps) {
}
}
});
return <Statusbar active={false} message={message} percentage={percentage} />;
return (
<Statusbar
active={false}
message={message}
percentage={percentage}
showOnlyMessage
/>
);
}

View File

@ -5,6 +5,8 @@ import {
USER_PROFILE_SETTINGS_TITLE,
AUTHOR_NAME,
AUTHOR_EMAIL,
FORM_VALIDATION_INVALID_EMAIL,
AUTHOR_NAME_CANNOT_BE_EMPTY,
} from "constants/messages";
import styled from "styled-components";
import TextInput, { emailValidator } from "components/ads/TextInput";
@ -26,9 +28,17 @@ const LabelContainer = styled.div`
margin-bottom: 8px;
`;
const InputContainer = styled.div`
const InputContainer = styled.div<{ isValid: boolean }>`
display: flex;
align-items: center;
margin-bottom: ${(props) => props.theme.spaces[props.isValid ? 7 : 12]}px;
& > div {
${(props) =>
!props.isValid ? `border: 1px solid ${Colors.ERROR_RED};` : ""}
input {
${(props) => (!props.isValid ? `color: ${Colors.ERROR_RED};` : "")}
}
}
`;
const TitleWrapper = styled.div`
@ -66,7 +76,7 @@ const IconWrapper = styled.div`
const DefaultConfigContainer = styled.div`
display: flex;
align-items: flex-start;
margin-top: ${(props) => props.theme.spaces[2]}px;
margin-top: ${(props) => props.theme.spaces[3]}px;
`;
type AuthorInfo = { authorName: string; authorEmail: string };
@ -126,7 +136,7 @@ const goToGitProfile = () => {
function UserGitProfileSettings({
authorInfo,
isGlobalConfigDefined,
isLocalConfigDefined,
// isLocalConfigDefined,
setAuthorInfo,
toggleUseDefaultConfig,
triedSubmit,
@ -160,9 +170,10 @@ function UserGitProfileSettings({
const isFetchingConfig =
isFetchingGlobalGitConfig || isFetchingLocalGitConfig;
const showDefaultConfig =
!isFetchingConfig && !isLocalConfigDefined && isGlobalConfigDefined;
const showDefaultConfig = !isFetchingConfig && isGlobalConfigDefined;
const nameInvalid =
!authorInfo.authorName && !nameInputFocused && triedSubmit;
const emailInvalid = !isValidEmail && !emailInputFocused && triedSubmit;
return (
<MainContainer>
<TitleWrapper>
@ -194,15 +205,13 @@ function UserGitProfileSettings({
<span className="label">{createMessage(AUTHOR_NAME)}</span>
</LabelContainer>
<InputContainer>
<InputContainer isValid={!nameInvalid}>
<TextInput
dataType="text"
defaultValue={authorInfo.authorName}
disabled={disableInput}
errorMsg={
!authorInfo.authorName && !nameInputFocused && triedSubmit
? "Author name cannot be empty"
: ""
nameInvalid ? createMessage(AUTHOR_NAME_CANNOT_BE_EMPTY) : ""
}
fill
isLoading={isFetchingConfig}
@ -212,20 +221,15 @@ function UserGitProfileSettings({
trimValue={false}
/>
</InputContainer>
<Space size={7} />
<LabelContainer>
<span className="label">{createMessage(AUTHOR_EMAIL)}</span>
</LabelContainer>
<InputContainer>
<InputContainer isValid={!emailInvalid}>
<TextInput
dataType="email"
disabled={disableInput}
errorMsg={
!isValidEmail && !emailInputFocused && triedSubmit
? "Please enter a valid email"
: ""
emailInvalid ? createMessage(FORM_VALIDATION_INVALID_EMAIL) : ""
}
fill
isLoading={isFetchingConfig}

View File

@ -0,0 +1,10 @@
export const getIsStartingWithRemoteBranches = (curr: string, next: string) => {
const remotePrefix = "origin/";
return (
curr &&
!curr.startsWith(remotePrefix) &&
next &&
next.startsWith(remotePrefix)
);
};

View File

@ -44,6 +44,7 @@ import {
collabStartSharingPointerEvent,
collabStopSharingPointerEvent,
} from "actions/appCollabActions";
import { getPageLevelSocketRoomId } from "sagas/WebsocketSagas/utils";
type EditorProps = {
currentApplicationId?: string;
@ -66,6 +67,7 @@ type EditorProps = {
isPageLevelSocketConnected: boolean;
collabStartSharingPointerEvent: (pageId: string) => void;
collabStopSharingPointerEvent: (pageId?: string) => void;
pageLevelSocketRoomId: string;
};
type Props = EditorProps & RouteComponentProps<BuilderRouteParams>;
@ -95,7 +97,9 @@ class Editor extends Component<Props> {
this.unlisten = history.listen(this.handleHistoryChange);
if (this.props.isPageLevelSocketConnected && pageId) {
this.props.collabStartSharingPointerEvent(pageId);
this.props.collabStartSharingPointerEvent(
getPageLevelSocketRoomId(pageId, branch),
);
}
}
@ -161,15 +165,23 @@ class Editor extends Component<Props> {
}
if (this.props.isPageLevelSocketConnected && isPageIdUpdated) {
this.props.collabStartSharingPointerEvent(pageId);
this.props.collabStartSharingPointerEvent(
getPageLevelSocketRoomId(pageId, branch),
);
}
}
componentWillUnmount() {
const { pageId } = this.props.match.params || {};
const {
location: { search },
} = this.props;
const branch = getSearchQuery(search, "branch");
this.props.resetEditorRequest();
if (typeof this.unlisten === "function") this.unlisten();
this.props.collabStopSharingPointerEvent(pageId);
this.props.collabStopSharingPointerEvent(
getPageLevelSocketRoomId(pageId, branch),
);
}
handleHistoryChange = (location: any) => {

View File

@ -10,6 +10,8 @@ import {
collabStopSharingPointerEvent,
} from "actions/appCollabActions";
import { getIsPageLevelSocketConnected } from "selectors/websocketSelectors";
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
import { getPageLevelSocketRoomId } from "sagas/WebsocketSagas/utils";
export const POINTERS_CANVAS_ID = "collab-pointer-sharing-canvas";
@ -78,6 +80,7 @@ function CanvasMultiPointerArena({ pageId }: { pageId: string }) {
const dispatch = useDispatch();
const animationStepIdRef = useRef<number>(0);
const isWebsocketConnected = useSelector(getIsPageLevelSocketConnected);
const currentGitBranch = useSelector(getCurrentGitBranch);
let selectionCanvas: any;
// Setup for painting on canvas
@ -97,12 +100,20 @@ function CanvasMultiPointerArena({ pageId }: { pageId: string }) {
// Initialize the page editing events to share pointer.
useEffect(() => {
if (isWebsocketConnected) {
dispatch(collabStartSharingPointerEvent(pageId));
dispatch(
collabStartSharingPointerEvent(
getPageLevelSocketRoomId(pageId, currentGitBranch),
),
);
}
return () => {
dispatch(collabStopSharingPointerEvent());
dispatch(
collabStopSharingPointerEvent(
getPageLevelSocketRoomId(pageId, currentGitBranch),
),
);
};
}, [isWebsocketConnected, pageId]);
}, [isWebsocketConnected, pageId, currentGitBranch]);
const previousAnimationStep = useRef<number>();

View File

@ -40,7 +40,10 @@ const gitSyncReducer = createReducer(initialState, {
...state,
isGitSyncModalOpen: action.payload.isOpen,
activeGitSyncModalTab,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
// reset conflicts when the modal is opened
pullFailed: false,
};
@ -56,13 +59,18 @@ const gitSyncReducer = createReducer(initialState, {
...state,
isCommitting: false,
isCommitSuccessful: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionErrorTypes.COMMIT_TO_GIT_REPO_ERROR]: (
state: GitSyncReducerState,
action: ReduxAction<unknown>,
) => ({
...state,
isCommitting: false,
commitAndPushError: action.payload,
}),
[ReduxActionTypes.CLEAR_COMMIT_SUCCESSFUL_STATE]: (
state: GitSyncReducerState,
@ -74,16 +82,23 @@ const gitSyncReducer = createReducer(initialState, {
...state,
isPushingToGit: true,
isPushSuccessful: false,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.PUSH_TO_GIT_SUCCESS]: (state: GitSyncReducerState) => ({
...state,
isPushingToGit: false,
isPushSuccessful: true,
}),
[ReduxActionErrorTypes.PUSH_TO_GIT_ERROR]: (state: GitSyncReducerState) => ({
[ReduxActionErrorTypes.PUSH_TO_GIT_ERROR]: (
state: GitSyncReducerState,
action: ReduxAction<unknown>,
) => ({
...state,
isPushingToGit: false,
commitAndPushError: action.payload,
}),
[ReduxActionTypes.SHOW_ERROR_POPUP]: (
state: GitSyncReducerState,
@ -105,14 +120,20 @@ const gitSyncReducer = createReducer(initialState, {
) => ({
...state,
isFetchingGitConfig: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.UPDATE_GLOBAL_GIT_CONFIG_INIT]: (
state: GitSyncReducerState,
) => ({
...state,
isFetchingGitConfig: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.FETCH_GLOBAL_GIT_CONFIG_SUCCESS]: (
state: GitSyncReducerState,
@ -145,7 +166,10 @@ const gitSyncReducer = createReducer(initialState, {
[ReduxActionTypes.FETCH_BRANCHES_INIT]: (state: GitSyncReducerState) => ({
...state,
fetchingBranches: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.FETCH_BRANCHES_SUCCESS]: (
state: GitSyncReducerState,
@ -166,14 +190,20 @@ const gitSyncReducer = createReducer(initialState, {
) => ({
...state,
isFetchingLocalGitConfig: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.UPDATE_LOCAL_GIT_CONFIG_INIT]: (
state: GitSyncReducerState,
) => ({
...state,
isFetchingLocalGitConfig: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.FETCH_LOCAL_GIT_CONFIG_SUCCESS]: (
state: GitSyncReducerState,
@ -206,7 +236,10 @@ const gitSyncReducer = createReducer(initialState, {
[ReduxActionTypes.FETCH_GIT_STATUS_INIT]: (state: GitSyncReducerState) => ({
...state,
isFetchingGitStatus: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.FETCH_GIT_STATUS_SUCCESS]: (
state: GitSyncReducerState,
@ -228,17 +261,20 @@ const gitSyncReducer = createReducer(initialState, {
...state,
isDisconnectingGit: false,
}),
[ReduxActionErrorTypes.GIT_SYNC_ERROR]: (
[ReduxActionErrorTypes.CONNECT_TO_GIT_ERROR]: (
state: GitSyncReducerState,
action: ReduxAction<unknown>,
) => ({
...state,
gitError: action.payload,
connectError: action.payload,
}),
[ReduxActionTypes.FETCH_MERGE_STATUS_INIT]: (state: GitSyncReducerState) => ({
...state,
isFetchingMergeStatus: true,
gitError: null,
connectError: null,
commitAndPushError: null,
pullError: null,
mergeError: null,
}),
[ReduxActionTypes.FETCH_MERGE_STATUS_SUCCESS]: (
state: GitSyncReducerState,
@ -272,15 +308,44 @@ const gitSyncReducer = createReducer(initialState, {
pullMergeStatus: null,
pullInProgress: true,
}),
[ReduxActionErrorTypes.GIT_PULL_ERROR]: (state: GitSyncReducerState) => ({
[ReduxActionErrorTypes.GIT_PULL_ERROR]: (
state: GitSyncReducerState,
action: ReduxAction<unknown>,
) => ({
...state,
pullInProgress: false,
pullFailed: true,
pullError: action.payload,
}),
[ReduxActionTypes.RESET_PULL_MERGE_STATUS]: (state: GitSyncReducerState) => ({
...state,
pullFailed: false,
}),
[ReduxActionTypes.MERGE_BRANCH_INIT]: (state: GitSyncReducerState) => ({
...state,
isMerging: true,
}),
[ReduxActionTypes.MERGE_BRANCH_SUCCESS]: (state: GitSyncReducerState) => ({
...state,
isMerging: false,
}),
[ReduxActionErrorTypes.MERGE_BRANCH_ERROR]: (
state: GitSyncReducerState,
action: ReduxAction<unknown>,
) => ({
...state,
isMerging: false,
mergeError: action.payload,
}),
[ReduxActionTypes.SET_REMOTE_URL_INPUT_VALUE]: (
state: GitSyncReducerState,
action: ReduxAction<string>,
) => {
return {
...state,
tempRemoteUrl: action.payload,
};
},
});
export type GitStatusData = {
@ -294,12 +359,19 @@ export type GitStatusData = {
remoteBranch: string;
};
export type GitErrorType = {
type GitErrorPayloadType = {
code: number;
errorType?: string;
message: string;
};
export type GitErrorType = {
error: GitErrorPayloadType;
show?: boolean;
crash?: boolean;
logToSentry?: boolean;
};
export type GitSyncReducerState = {
isGitSyncModalOpen: boolean;
isCommitting?: boolean;
@ -325,9 +397,15 @@ export type GitSyncReducerState = {
localGitConfig: GitConfig;
gitStatus?: GitStatusData;
mergeStatus?: MergeStatus;
gitError?: GitErrorType;
connectError?: GitErrorType;
commitAndPushError?: GitErrorType;
pullError?: GitErrorType;
mergeError?: GitErrorType;
pullFailed?: boolean;
pullInProgress?: boolean;
isMerging?: boolean;
tempRemoteUrl?: string;
};
export default gitSyncReducer;

View File

@ -93,13 +93,6 @@ export function* validateResponse(response: ApiResponse | any, show = true) {
throw new IncorrectBindingError(response.responseMeta.error.message);
}
if (response?.gitRequest) {
yield put({
type: ReduxActionErrorTypes.GIT_SYNC_ERROR,
payload: response.responseMeta.error,
});
}
yield put({
type: ReduxActionErrorTypes.API_ERROR,
payload: {

View File

@ -1,4 +1,5 @@
import {
CurrentApplicationData,
ReduxAction,
ReduxActionErrorTypes,
ReduxActionTypes,
@ -39,7 +40,10 @@ import { ApiResponse } from "api/ApiResponses";
import { GitConfig, GitSyncModalTab } from "entities/GitSync";
import { Toaster } from "components/ads/Toast";
import { Variant } from "components/ads/common";
import { getCurrentAppGitMetaData } from "selectors/applicationSelectors";
import {
getCurrentAppGitMetaData,
getCurrentApplication,
} from "selectors/applicationSelectors";
import { fetchGitStatusSuccess } from "actions/gitSyncActions";
import {
createMessage,
@ -53,7 +57,7 @@ import { MergeBranchPayload, MergeStatusPayload } from "api/GitSyncAPI";
import {
mergeBranchSuccess,
mergeBranchFailure,
// mergeBranchFailure,
} from "../actions/gitSyncActions";
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
import { initEditor } from "actions/initActions";
@ -75,26 +79,37 @@ function* commitToGitRepoSaga(
branch: gitMetaData?.branchName || "",
applicationId,
});
const isValidResponse: boolean = yield validateResponse({
...response,
gitRequest: true,
});
if (!response?.responseMeta?.success) {
yield put({
type: ReduxActionErrorTypes.COMMIT_TO_GIT_REPO_ERROR,
payload: {
error: response.responseMeta.error,
show: false,
},
});
}
const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) {
yield put(commitToRepoSuccess());
Toaster.show({
text: action.payload.doPush
? "Committed and pushed Successfully"
: "Committed Successfully",
variant: Variant.success,
});
const curApplication: CurrentApplicationData = yield select(
getCurrentApplication,
);
if (curApplication) {
curApplication.lastDeployedAt = new Date().toISOString();
yield put({
type: ReduxActionTypes.FETCH_APPLICATION_SUCCESS,
payload: curApplication,
});
}
yield put(fetchGitStatusInit());
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.COMMIT_TO_GIT_REPO_ERROR,
payload: { error, logToSentry: true },
});
// yield put({
// type: ReduxActionErrorTypes.COMMIT_TO_GIT_REPO_ERROR,
// payload: { error, logToSentry: true },
// });
}
}
@ -106,10 +121,16 @@ function* connectToGitSaga(action: ConnectToGitReduxAction) {
action.payload,
applicationId,
);
const isValidResponse: boolean = yield validateResponse({
...response,
gitRequest: true,
});
if (!response?.responseMeta?.success) {
yield put({
type: ReduxActionErrorTypes.CONNECT_TO_GIT_ERROR,
payload: {
error: response.responseMeta.error,
show: false,
},
});
}
const isValidResponse: boolean = yield validateResponse(response, false);
if (isValidResponse) {
yield put(connectToGitSuccess(response.data));
@ -126,23 +147,17 @@ function* connectToGitSaga(action: ConnectToGitReduxAction) {
if (action.onErrorCallback) {
action.onErrorCallback(error as string);
}
yield put({
type: ReduxActionErrorTypes.CONNECT_TO_GIT_ERROR,
payload: { gitError: error, logToSentry: true },
});
// yield put({
// type: ReduxActionErrorTypes.CONNECT_TO_GIT_ERROR,
// payload: { gitError: error, logToSentry: true },
// });
}
}
function* fetchGlobalGitConfig() {
try {
const response: ApiResponse = yield GitSyncAPI.getGlobalConfig();
const isValidResponse: boolean = yield validateResponse(
{
...response,
gitRequest: true,
},
false,
);
const isValidResponse: boolean = yield validateResponse(response, false);
if (isValidResponse) {
yield put(fetchGlobalGitConfigSuccess(response.data));
@ -160,10 +175,7 @@ function* updateGlobalGitConfig(action: ReduxAction<GitConfig>) {
const response: ApiResponse = yield GitSyncAPI.setGlobalConfig(
action.payload,
);
const isValidResponse: boolean = yield validateResponse({
...response,
gitRequest: true,
});
const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) {
yield put(updateGlobalGitConfigSuccess(response.data));
@ -188,10 +200,7 @@ function* switchBranch(action: ReduxAction<string>) {
const response: ApiResponse = yield GitSyncAPI.checkoutBranch(
applicationId,
);
const isValidResponse: boolean = yield validateResponse({
...response,
gitRequest: true,
});
const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) {
const updatedPath = addBranchParam(branch);
@ -205,10 +214,14 @@ function* switchBranch(action: ReduxAction<string>) {
}
}
function* fetchBranches() {
function* fetchBranches(action: ReduxAction<{ pruneBranches: boolean }>) {
try {
const pruneBranches = action.payload?.pruneBranches;
const applicationId: string = yield select(getCurrentApplicationId);
const response: ApiResponse = yield GitSyncAPI.fetchBranches(applicationId);
const response: ApiResponse = yield GitSyncAPI.fetchBranches(
applicationId,
pruneBranches,
);
const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) {
@ -306,21 +319,26 @@ function* pushToGitRepoSaga() {
applicationId,
branch: gitMetaData?.branchName || "",
});
if (!response?.responseMeta?.success) {
yield put({
type: ReduxActionErrorTypes.PUSH_TO_GIT_ERROR,
payload: {
error: response.responseMeta.error,
show: false,
},
});
}
const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) {
yield put(pushToRepoSuccess());
Toaster.show({
text: "Pushed Successfully",
variant: Variant.success,
});
yield put(fetchGitStatusInit());
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.PUSH_TO_GIT_ERROR,
payload: { error, logToSentry: true },
});
// yield put({
// type: ReduxActionErrorTypes.PUSH_TO_GIT_ERROR,
// payload: { error, logToSentry: true },
// });
}
}
@ -332,13 +350,7 @@ function* fetchGitStatusSaga() {
applicationId,
branch: gitMetaData?.branchName || "",
});
const isValidResponse: boolean = yield validateResponse(
{
...response,
gitRequest: true,
},
false,
);
const isValidResponse: boolean = yield validateResponse(response, false);
if (isValidResponse) {
yield put(fetchGitStatusSuccess(response.data));
}
@ -361,6 +373,15 @@ function* mergeBranchSaga(action: ReduxAction<MergeBranchPayload>) {
sourceBranch,
destinationBranch,
});
if (!response?.responseMeta?.success) {
yield put({
type: ReduxActionErrorTypes.MERGE_BRANCH_ERROR,
payload: {
error: response.responseMeta.error,
show: false,
},
});
}
const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) {
@ -371,7 +392,7 @@ function* mergeBranchSaga(action: ReduxAction<MergeBranchPayload>) {
});
}
} catch (error) {
yield put(mergeBranchFailure());
// yield put(mergeBranchFailure());
}
}
@ -401,6 +422,15 @@ function* gitPullSaga(
try {
const applicationId: string = yield select(getCurrentApplicationId);
const response = yield call(GitSyncAPI.pull, { applicationId });
if (!response?.responseMeta?.success) {
yield put({
type: ReduxActionErrorTypes.GIT_PULL_ERROR,
payload: {
error: response.responseMeta.error,
show: false,
},
});
}
const isValidResponse: boolean = yield validateResponse(response, false);
const currentBranch = yield select(getCurrentGitBranch);
const currentPageId = yield select(getCurrentPageId);
@ -421,11 +451,6 @@ function* gitPullSaga(
}),
);
}
yield put({
type: ReduxActionErrorTypes.GIT_PULL_ERROR,
payload: { error: e, logToSentry: true, show: false },
});
}
}

View File

@ -0,0 +1,8 @@
export const getPageLevelSocketRoomId = (
pageId: string,
currentGitBranch?: string,
) => {
return currentGitBranch
? `${pageId}-${currentGitBranch}`
: (pageId as string);
};

View File

@ -68,7 +68,17 @@ export const getIsFetchingLocalGitConfig = (state: AppState) =>
export const getGitStatus = (state: AppState) => state.ui.gitSync.gitStatus;
export const getGitError = (state: AppState) => state.ui.gitSync.gitError;
export const getGitConnectError = (state: AppState) =>
state.ui.gitSync.connectError?.error;
export const getGitPullError = (state: AppState) =>
state.ui.gitSync.pullError?.error;
export const getGitMergeError = (state: AppState) =>
state.ui.gitSync.mergeError?.error;
export const getGitCommitAndPushError = (state: AppState) =>
state.ui.gitSync.commitAndPushError?.error;
export const getIsFetchingGitStatus = (state: AppState) =>
state.ui.gitSync.isFetchingGitStatus;
@ -109,3 +119,14 @@ export const getPullFailed = (state: AppState) => state.ui.gitSync.pullFailed;
export const getPullInProgress = (state: AppState) =>
state.ui.gitSync.pullInProgress;
export const getIsMergeInProgress = (state: AppState) =>
state.ui.gitSync.isMerging;
export const getTempRemoteUrl = (state: AppState) =>
state.ui.gitSync.tempRemoteUrl;
export const getCountOfChangesToCommit = (state: AppState) => {
const gitStatus = getGitStatus(state);
const { modifiedPages = 0, modifiedQueries = 0 } = gitStatus || {};
return modifiedPages + modifiedQueries;
};

View File

@ -28,5 +28,6 @@ public enum FeatureFlagEnum {
LINTING,
MULTIPLAYER,
GIT,
ADMIN_SETTINGS;
ADMIN_SETTINGS,
GIT_IMPORT;
}

View File

@ -52,3 +52,12 @@ ff4j:
param:
- name: emailDomains
value: appsmith.com
- uid: GIT_IMPORT
enable: true
description: Enable git import for apps
flipstrategy:
class: com.appsmith.server.featureflags.strategies.EmailBasedRolloutStrategy
param:
- name: emailDomains
value: appsmith.com