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:
parent
0ccfac7978
commit
d701f8dfb1
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
33
app/client/src/components/ads/ListSegmentHeader.tsx
Normal file
33
app/client/src/components/ads/ListSegmentHeader.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ type FeatureFlag = {
|
|||
SNIPPET: boolean;
|
||||
GIT: boolean;
|
||||
ADMIN_SETTINGS: boolean;
|
||||
GIT_IMPORT: boolean;
|
||||
};
|
||||
|
||||
export default FeatureFlag;
|
||||
|
|
|
|||
|
|
@ -17,4 +17,6 @@ export type Branch = {
|
|||
export type MergeStatus = {
|
||||
isMergeAble: boolean;
|
||||
conflictingFiles: Array<string>;
|
||||
status?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"> {currentBranch}</span>
|
||||
<div className="branch"> {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} />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
LEARN MORE
|
||||
LEARN MORE
|
||||
</LintText>
|
||||
</Text>
|
||||
<FlexRow>
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
10
app/client/src/pages/Editor/gitSync/utils.ts
Normal file
10
app/client/src/pages/Editor/gitSync/utils.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export const getIsStartingWithRemoteBranches = (curr: string, next: string) => {
|
||||
const remotePrefix = "origin/";
|
||||
|
||||
return (
|
||||
curr &&
|
||||
!curr.startsWith(remotePrefix) &&
|
||||
next &&
|
||||
next.startsWith(remotePrefix)
|
||||
);
|
||||
};
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
8
app/client/src/sagas/WebsocketSagas/utils.ts
Normal file
8
app/client/src/sagas/WebsocketSagas/utils.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export const getPageLevelSocketRoomId = (
|
||||
pageId: string,
|
||||
currentGitBranch?: string,
|
||||
) => {
|
||||
return currentGitBranch
|
||||
? `${pageId}-${currentGitBranch}`
|
||||
: (pageId as string);
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,5 +28,6 @@ public enum FeatureFlagEnum {
|
|||
LINTING,
|
||||
MULTIPLAYER,
|
||||
GIT,
|
||||
ADMIN_SETTINGS;
|
||||
ADMIN_SETTINGS,
|
||||
GIT_IMPORT;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user