Skip to content

Commit

Permalink
Refact UpdateConfigurationDialog [BIVS-2874] (#1355)
Browse files Browse the repository at this point in the history
  • Loading branch information
moczolaszlo authored Dec 9, 2024
1 parent 20e4fc9 commit 7b647c1
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 81 deletions.
2 changes: 1 addition & 1 deletion source/javascripts/_componentRegister.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Notification from "./components/Notification";
import NotificationMessageWithLink from "./components/NotificationMessageWithLink";
import StepBadge from "./components/StepBadge";
import Toggle from "./components/Toggle";
import UpdateConfigurationDialog from "./components/UpdateConfigurationDialog/UpdateConfigurationDialog";
import UpdateConfigurationDialog from "./components/unified-editor/UpdateConfigurationDialog/UpdateConfigurationDialog";
import DiffEditorDialog from "./components/DiffEditor/DiffEditorDialog";
import { RootComponent, withRootProvider } from "./utils/withRootProvider";
import {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const Default: StoryObj = {};
export const Failed: StoryObj = {
parameters: {
msw: {
handlers: [getConfigFailed()],
handlers: [formatYml(), getConfigFailed()],
},
},
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ReactElement, useEffect } from 'react';
import { Box, Button, Dialog, DialogBody, DialogFooter, Notification, Text, useToast } from '@bitrise/bitkit';
import { useEffect } from 'react';
import { Box, Button, Dialog, DialogBody, DialogFooter, Text, useToast } from '@bitrise/bitkit';
import CopyToClipboard from 'react-copy-to-clipboard';
import { AppConfig } from '@/models/AppConfig';
import { segmentTrack } from '@/utils/segmentTracking';
import useFormattedYml from '@/hooks/useFormattedYml';
import { BitriseYml } from '@/core/models/BitriseYml';
import useGetAppConfigFromRepoCallback from '../../hooks/api/useGetAppConfigFromRepoCallback';
import YmlNotFoundInRepositoryError from '../common/notifications/YmlNotFoundInRepositoryError';
import YmlInRepositoryInvalidError from '../common/notifications/YmlInRepositoryInvalidError';
import { useCiConfigMutation } from '@/hooks/useGetCIConfig';
import YmlDialogErrorNotification from './YmlDialogErrorNotification';

type UpdateConfigurationDialogProps = {
onClose: () => void;
Expand All @@ -21,31 +20,9 @@ type UpdateConfigurationDialogProps = {
const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => {
const { onClose, appSlug, getDataToSave, onComplete, defaultBranch, gitRepoSlug } = props;

const { getAppConfigFromRepo, appConfigFromRepo, getAppConfigFromRepoStatus, getAppConfigFromRepoFailed } =
useGetAppConfigFromRepoCallback(appSlug);
const { error, isPending, mutate, reset } = useCiConfigMutation();

const appConfig = getDataToSave();

useEffect(() => {
if (appConfigFromRepo) {
onComplete();
}
}, [appConfigFromRepo, onComplete]);

const renderError = (): ReactElement => {
switch (getAppConfigFromRepoStatus) {
case 404:
return <YmlNotFoundInRepositoryError />;
case 422:
return <YmlInRepositoryInvalidError errorMessage={getAppConfigFromRepoFailed?.error_msg || 'Unknown error'} />;
default:
return (
<Notification status="error" marginBlockStart="24">
{getAppConfigFromRepoFailed?.error_msg || 'Unknown error'}
</Notification>
);
}
};
const ciConfig = getDataToSave();

const { data: ciConfigYML = '', mutate: formatToYml } = useFormattedYml();

Expand All @@ -65,20 +42,29 @@ const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => {
});
};

const onDownloadClick = () => {
const handleDownloadClick = () => {
segmentTrack('Workflow Editor Download Yml Button Clicked', {
yml_source: 'bitrise',
source: 'update_configuration_yml_modal',
});
};

const handleDoneClick = () => {
mutate({ projectSlug: appSlug }, { onSuccess: onComplete });
};

const handleClose = () => {
reset();
onClose();
};

useEffect(() => {
formatToYml(appConfig as BitriseYml);
formatToYml(ciConfig as BitriseYml);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<Dialog isOpen onClose={onClose} title="Update configuration YAML">
<Dialog isOpen onClose={handleClose} title="Update configuration YAML">
<DialogBody>
<Text marginBlockEnd="24">
If you would like to apply these changes to your configuration, depending on your setup, you need to do the
Expand All @@ -100,7 +86,7 @@ const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => {
width="fit-content"
size="sm"
leftIconName="Download"
onClick={onDownloadClick}
onClick={handleDownloadClick}
>
Download changed version
</Button>
Expand All @@ -114,13 +100,15 @@ const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => {
Using multiple configuration files
</Text>
<Text>You need to re-create the changes in the relevant configuration file on your Git repository.</Text>
{getAppConfigFromRepoFailed && renderError()}
{!!error?.response && <YmlDialogErrorNotification response={error.response} />}
</DialogBody>
<DialogFooter>
<Button variant="secondary" onClick={onClose}>
<Button isLoading={isPending} variant="secondary" onClick={handleClose}>
Cancel
</Button>
<Button onClick={getAppConfigFromRepo}>Done</Button>
<Button isLoading={isPending} onClick={handleDoneClick}>
Done
</Button>
</DialogFooter>
</Dialog>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ReactNode, useEffect, useState } from 'react';
import { Notification, NotificationProps, Text } from '@bitrise/bitkit';

type Props = {
response: Response | undefined;
};

const YmlDialogErrorNotification = (props: Props) => {
const { response } = props;
const [parsedErrorResponse, setParsedErrorResponse] = useState<Record<'error_msg', string> | undefined>(undefined);

const message = parsedErrorResponse?.error_msg || 'Unknown error';

let action: NotificationProps['action'];
let content: ReactNode = message;

if (response?.status === 404) {
content =
"Couldn't find the bitrise.yml file in the app's repository. Please make sure that the file exists on the default branch and the app's Service Credential User has read rights on that.";
} else if (message && message.includes('Split configuration requires an Enterprise plan')) {
content = (
<>
<Text fontWeight="bold">Split configuration requires an Enterprise plan</Text>
Contact our customer support if you'd like to try it out.
</>
);
action = {
href: 'https://bitrise.io/contact',
label: 'Contact us',
target: '_blank',
};
}

useEffect(() => {
const parse = async (resp: Response) => {
const parsedJson = await resp.json();
setParsedErrorResponse(parsedJson);
};
if (response) {
parse(response);
}
}, [response]);

return (
<Notification marginBlockStart="24" status="error" action={action}>
{content}
</Notification>
);
};

export default YmlDialogErrorNotification;
16 changes: 4 additions & 12 deletions source/javascripts/core/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,15 @@ type ClientOpts = RequestInit & ExtraOpts;

/* eslint-disable no-underscore-dangle */
class ClientError extends Error {
private _error: Error;
public error: Error;

private _response?: Response;
public response?: Response;

constructor(error: Error, response?: Response) {
super(error.message);
this.name = 'ClientError';
this._error = error;
this._response = response;
}

public get error() {
return this._error;
}

public get response() {
return this._response;
this.error = error;
this.response = response;
}
}

Expand Down
28 changes: 28 additions & 0 deletions source/javascripts/hooks/useGetCIConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import BitriseYmlApi from '@/core/api/BitriseYmlApi';
import { ClientError } from '@/core/api/client';
import { BitriseYml } from '@/core/models/BitriseYml';

type QueryProps = { enabled?: boolean; projectSlug: string; readFromRepo?: boolean };

const useCiConfigQuery = (props: QueryProps) => {
const { enabled, projectSlug, readFromRepo } = props;

return useQuery<BitriseYml, ClientError>({
enabled,
retry: false,
queryKey: [BitriseYmlApi.getBitriseYmlPath({ projectSlug }), readFromRepo],
queryFn: ({ signal }) => BitriseYmlApi.getBitriseYml({ projectSlug, readFromRepo, signal }),
staleTime: Infinity,
});
};

type MutationProps = Omit<QueryProps, 'enabled'>;

const useCiConfigMutation = () => {
return useMutation<BitriseYml, ClientError, MutationProps>({
mutationFn: ({ projectSlug, readFromRepo }) => BitriseYmlApi.getBitriseYml({ projectSlug, readFromRepo }),
});
};

export { useCiConfigQuery, useCiConfigMutation };
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export const getConfigFailed = () => {
return http.get(BitriseYmlApi.getBitriseYmlPath({ projectSlug: ':slug' }), async () => {
await delay(1000);
return HttpResponse.json(
{ error_msg: 'Split configuration requires an Enterprise plan' },
{
error_msg:
'config (/tmp/config20241207-26-5782vz.yaml) is not valid: trigger item #1: non-existent workflow defined as trigger target: primary',
},
{
status: 422,
},
Expand Down

0 comments on commit 7b647c1

Please sign in to comment.