feat(Git Sync): UX improvements (#8524)

* fix loading state in commit and commit and push buttons

* use yaml when parsing open api specs in the app

* improve error message for pushing to a remote with extra commits

* improve buttons ux on repo settings modal

* check for response.errors

* fix error issue
This commit is contained in:
James Gatz
2025-03-25 17:24:17 +01:00
committed by GitHub
parent af68577062
commit 00d7dc69bf
7 changed files with 69 additions and 34 deletions

View File

@@ -147,7 +147,7 @@ function getApiSpec(file: InsomniaFile): [WithExportType<ApiSpec>] | [] {
_type: EXPORT_TYPE_API_SPEC,
fileName: 'file' in file.spec ? file.spec.file : '',
contentType: 'json',
contents: 'contents' in file.spec && file.spec.contents ? JSON.stringify(file.spec.contents) : '',
contents: 'contents' in file.spec && file.spec.contents ? stringify(file.spec.contents) : '',
parentId: file.meta?.id || '__WORKSPACE_ID__',
}];
}

View File

@@ -1408,9 +1408,17 @@ export const pushToGitRemoteAction = async ({
});
await database.flushChanges(bufferId);
} catch (err: unknown) {
if (err instanceof Errors.PushRejectedError && err.data.reason === 'not-fast-forward') {
return {
errors: ['Push Rejected. It seems that the remote repository has changes that you do not have locally. Please pull the changes and try again.'],
gitRepository,
};
}
if (err instanceof Errors.HttpError) {
return {
errors: [`${err.message}, ${err.data.response}`],
gitRepository,
};
}
const errorMessage = err instanceof Error ? err.message : 'Unknown Error';

View File

@@ -656,23 +656,36 @@ export class GitVCS {
async push(gitCredentials?: GitCredentials | null, force = false) {
console.log(`[git] Push remote=origin force=${force ? 'true' : 'false'}`);
// eslint-disable-next-line no-unreachable
const response: git.PushResult = await git.push({
const response = await git.push({
...this._baseOpts,
...gitCallbacks(gitCredentials),
remote: 'origin',
force,
});
// @ts-expect-error -- TSCONVERSION git errors are not handled correctly
if (response.errors?.length) {
if (response.error) {
console.log('[git] Push rejected', response);
// @ts-expect-error -- TSCONVERSION git errors are not handled correctly
throw new Error(
`Push rejected with errors: ${response.error}.\n\nGo to View > Toggle DevTools > Console for more information.`
);
}
if ('errors' in response && response.errors && Array.isArray(response.errors)) {
console.log('[git] Push failed with errors', response.errors);
const errorsString = JSON.stringify(response.errors);
throw new Error(
`Push rejected with errors: ${errorsString}.\n\nGo to View > Toggle DevTools > Console for more information.`
);
}
// NOTE: Response can be ok and have errors so we check this in the end to make sure we throw an error if there are any.
if (response.ok) {
console.log('[git] Push successful');
return;
}
throw new Error('Push failed with unknown error. Please try again.');
}
async _hasUncommittedChanges() {
@@ -896,9 +909,15 @@ export class GitVCS {
oursBranch,
theirsBranch,
);
} else {
throw err;
}
if (err instanceof git.Errors.MergeNotSupportedError) {
const errorMessage = 'Merges with additions are not supported yet.';
throw new Error(errorMessage);
}
throw err;
},
);
}

View File

@@ -103,8 +103,8 @@ export const GitProjectStagingModal: FC<{ onClose: () => void }> = ({
const { Form, formAction, state, data } = useFetcher<{ errors?: string[] }>();
const isCreatingSnapshot = state === 'loading' && formAction === '/organization/:organizationId/project/:projectId/workspace/:workspaceId/git/commit';
const isPushing = state === 'loading' && formAction === '/organization/:organizationId/project/:projectId/workspace/:workspaceId/git/commit-and-push';
const isCreatingSnapshot = state !== 'idle' && formAction === `/organization/${organizationId}/project/${projectId}/git/commit`;
const isPushing = state !== 'idle' && formAction === `/organization/${organizationId}/project/${projectId}/git/commit-and-push`;
const previewDiffItem = diffChangesFetcher.data && 'diff' in diffChangesFetcher.data ? diffChangesFetcher.data : null;
const allChanges = [...changes.staged, ...changes.unstaged];

View File

@@ -30,12 +30,10 @@ function getDefaultOAuthProvider(credentials?: GitCredentials | null): OauthProv
return 'custom';
}
export const GitProjectRepositorySettingsModal = (props: ModalProps & {
export const GitProjectRepositorySettingsModal = ({ gitRepository, ...modalProps }: ModalProps & {
gitRepository?: GitRepository;
}) => {
const { organizationId, projectId } = useParams() as { organizationId: string; projectId: string; workspaceId: string };
const { gitRepository } = props;
const modalRef = useRef<ModalHandle>(null);
const updateGitRepositoryFetcher = useFetcher();
const deleteGitRepositoryFetcher = useFetcher();
@@ -89,7 +87,7 @@ export const GitProjectRepositorySettingsModal = (props: ModalProps & {
return (
<OverlayContainer>
<Modal ref={modalRef} {...props}>
<Modal ref={modalRef} {...modalProps}>
<ModalHeader>
Repository Settings{' '}
<HelpTooltip>
@@ -159,7 +157,7 @@ export const GitProjectRepositorySettingsModal = (props: ModalProps & {
>
<button
className="btn"
disabled={!gitRepository}
disabled={!hasGitRepository}
onClick={() => {
deleteGitRepositoryFetcher.submit({}, {
action: `/organization/${organizationId}/project/${projectId}/git/reset`,
@@ -169,15 +167,26 @@ export const GitProjectRepositorySettingsModal = (props: ModalProps & {
>
Reset
</button>
<button
type="submit"
disabled={isLoading}
form={selectedTab}
className="btn"
data-testid="git-repository-settings-modal__sync-btn"
>
Sync
</button>
{hasGitRepository ? (
<button
type="button"
onClick={() => modalRef.current?.hide()}
className="btn"
data-testid="git-repository-settings-modal__sync-btn-close"
>
Close
</button>
) : (
<button
type="submit"
disabled={isLoading}
form={selectedTab}
className="btn"
data-testid="git-repository-settings-modal__sync-btn"
>
Sync
</button>
)}
</div>
</ModalFooter>
</Modal>

View File

@@ -30,11 +30,10 @@ function getDefaultOAuthProvider(credentials?: GitCredentials | null): OauthProv
return 'custom';
}
export const GitRepositorySettingsModal = (props: ModalProps & {
export const GitRepositorySettingsModal = ({ gitRepository, ...modalProps }: ModalProps & {
gitRepository?: GitRepository;
}) => {
const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string };
const { gitRepository } = props;
const modalRef = useRef<ModalHandle>(null);
const updateGitRepositoryFetcher = useFetcher();
const deleteGitRepositoryFetcher = useFetcher();
@@ -88,7 +87,7 @@ export const GitRepositorySettingsModal = (props: ModalProps & {
return (
<OverlayContainer>
<Modal ref={modalRef} {...props}>
<Modal ref={modalRef} {...modalProps}>
<ModalHeader>
Repository Settings{' '}
<HelpTooltip>
@@ -158,7 +157,7 @@ export const GitRepositorySettingsModal = (props: ModalProps & {
>
<button
className="btn"
disabled={!gitRepository}
disabled={!hasGitRepository}
onClick={() => {
deleteGitRepositoryFetcher.submit({}, {
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/git/reset`,

View File

@@ -95,18 +95,18 @@ export const GitStagingModal: FC<{ onClose: () => void }> = ({
const {
changes,
} = gitChangesFetcher.data || {
changes: {
staged: [],
unstaged: [],
},
changes: {
staged: [],
unstaged: [],
},
branch: '',
statusNames: {},
};
const { Form, formAction, state, data } = useFetcher<{ errors?: string[]; gitRepository: GitRepository }>();
const isCreatingSnapshot = state === 'loading' && formAction === '/organization/:organizationId/project/:projectId/workspace/:workspaceId/git/commit';
const isPushing = state === 'loading' && formAction === '/organization/:organizationId/project/:projectId/workspace/:workspaceId/git/commit-and-push';
const isCreatingSnapshot = state !== 'idle' && formAction === `/organization/${organizationId}/project/${projectId}/workspace/:workspaceId/git/commit`;
const isPushing = state !== 'idle' && formAction === `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/git/commit-and-push`;
const previewDiffItem = diffChangesFetcher.data && 'diff' in diffChangesFetcher.data ? diffChangesFetcher.data : null;