From 7b647c1cb65602fb788abe9d68035baa2505a94e Mon Sep 17 00:00:00 2001 From: Laszlo Moczo Date: Mon, 9 Dec 2024 15:03:50 +0100 Subject: [PATCH] Refact UpdateConfigurationDialog [BIVS-2874] (#1355) --- source/javascripts/_componentRegister.js | 2 +- .../YmlInRepositoryInvalidError.tsx | 21 ------- .../YmlNotFoundInRepositoryError.tsx | 9 --- .../UpdateConfigurationDialog.stories.tsx | 2 +- .../UpdateConfigurationDialog.tsx | 60 ++++++++----------- .../YmlDialogErrorNotification.tsx | 51 ++++++++++++++++ source/javascripts/core/api/client.ts | 16 ++--- source/javascripts/hooks/useGetCIConfig.ts | 28 +++++++++ .../ConfigurationYmlSource.mswMocks.ts | 5 +- 9 files changed, 113 insertions(+), 81 deletions(-) delete mode 100644 source/javascripts/components/common/notifications/YmlInRepositoryInvalidError.tsx delete mode 100644 source/javascripts/components/common/notifications/YmlNotFoundInRepositoryError.tsx rename source/javascripts/components/{ => unified-editor}/UpdateConfigurationDialog/UpdateConfigurationDialog.stories.tsx (95%) rename source/javascripts/components/{ => unified-editor}/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx (65%) create mode 100644 source/javascripts/components/unified-editor/UpdateConfigurationDialog/YmlDialogErrorNotification.tsx create mode 100644 source/javascripts/hooks/useGetCIConfig.ts diff --git a/source/javascripts/_componentRegister.js b/source/javascripts/_componentRegister.js index 54fbe64e5..bf8ec51f8 100644 --- a/source/javascripts/_componentRegister.js +++ b/source/javascripts/_componentRegister.js @@ -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 { diff --git a/source/javascripts/components/common/notifications/YmlInRepositoryInvalidError.tsx b/source/javascripts/components/common/notifications/YmlInRepositoryInvalidError.tsx deleted file mode 100644 index 01f91d080..000000000 --- a/source/javascripts/components/common/notifications/YmlInRepositoryInvalidError.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Box, Link, Notification, Text } from '@bitrise/bitkit'; - -type Props = { - errorMessage: string; -}; - -const YmlInRepositoryInvalidError = ({ errorMessage }: Props): JSX.Element => ( - - - - {window.strings.yml.store_in_repository.validation_error}{' '} - - valid syntax of the bitrise.yml file. - - - {errorMessage} - - -); - -export default YmlInRepositoryInvalidError; diff --git a/source/javascripts/components/common/notifications/YmlNotFoundInRepositoryError.tsx b/source/javascripts/components/common/notifications/YmlNotFoundInRepositoryError.tsx deleted file mode 100644 index 90dc8568c..000000000 --- a/source/javascripts/components/common/notifications/YmlNotFoundInRepositoryError.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Notification } from '@bitrise/bitkit'; - -const YmlNotFoundInRepositoryError = (): JSX.Element => ( - - {window.strings.yml.store_in_repository.not_found} - -); - -export default YmlNotFoundInRepositoryError; diff --git a/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.stories.tsx b/source/javascripts/components/unified-editor/UpdateConfigurationDialog/UpdateConfigurationDialog.stories.tsx similarity index 95% rename from source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.stories.tsx rename to source/javascripts/components/unified-editor/UpdateConfigurationDialog/UpdateConfigurationDialog.stories.tsx index d0a39c1c6..50b3347c3 100644 --- a/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.stories.tsx +++ b/source/javascripts/components/unified-editor/UpdateConfigurationDialog/UpdateConfigurationDialog.stories.tsx @@ -35,7 +35,7 @@ export const Default: StoryObj = {}; export const Failed: StoryObj = { parameters: { msw: { - handlers: [getConfigFailed()], + handlers: [formatYml(), getConfigFailed()], }, }, }; diff --git a/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx b/source/javascripts/components/unified-editor/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx similarity index 65% rename from source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx rename to source/javascripts/components/unified-editor/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx index 0f90ea3ac..40d9bcac7 100644 --- a/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx +++ b/source/javascripts/components/unified-editor/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx @@ -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; @@ -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 ; - case 422: - return ; - default: - return ( - - {getAppConfigFromRepoFailed?.error_msg || 'Unknown error'} - - ); - } - }; + const ciConfig = getDataToSave(); const { data: ciConfigYML = '', mutate: formatToYml } = useFormattedYml(); @@ -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 ( - + If you would like to apply these changes to your configuration, depending on your setup, you need to do the @@ -100,7 +86,7 @@ const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => { width="fit-content" size="sm" leftIconName="Download" - onClick={onDownloadClick} + onClick={handleDownloadClick} > Download changed version @@ -114,13 +100,15 @@ const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => { Using multiple configuration files You need to re-create the changes in the relevant configuration file on your Git repository. - {getAppConfigFromRepoFailed && renderError()} + {!!error?.response && } - - + ); diff --git a/source/javascripts/components/unified-editor/UpdateConfigurationDialog/YmlDialogErrorNotification.tsx b/source/javascripts/components/unified-editor/UpdateConfigurationDialog/YmlDialogErrorNotification.tsx new file mode 100644 index 000000000..00507bfd4 --- /dev/null +++ b/source/javascripts/components/unified-editor/UpdateConfigurationDialog/YmlDialogErrorNotification.tsx @@ -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 | 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 = ( + <> + Split configuration requires an Enterprise plan + 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 ( + + {content} + + ); +}; + +export default YmlDialogErrorNotification; diff --git a/source/javascripts/core/api/client.ts b/source/javascripts/core/api/client.ts index b1f492e57..ac38cad7e 100644 --- a/source/javascripts/core/api/client.ts +++ b/source/javascripts/core/api/client.ts @@ -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; } } diff --git a/source/javascripts/hooks/useGetCIConfig.ts b/source/javascripts/hooks/useGetCIConfig.ts new file mode 100644 index 000000000..efc40b87b --- /dev/null +++ b/source/javascripts/hooks/useGetCIConfig.ts @@ -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({ + enabled, + retry: false, + queryKey: [BitriseYmlApi.getBitriseYmlPath({ projectSlug }), readFromRepo], + queryFn: ({ signal }) => BitriseYmlApi.getBitriseYml({ projectSlug, readFromRepo, signal }), + staleTime: Infinity, + }); +}; + +type MutationProps = Omit; + +const useCiConfigMutation = () => { + return useMutation({ + mutationFn: ({ projectSlug, readFromRepo }) => BitriseYmlApi.getBitriseYml({ projectSlug, readFromRepo }), + }); +}; + +export { useCiConfigQuery, useCiConfigMutation }; diff --git a/source/javascripts/pages/YmlPage/components/ConfigurationYmlSource.mswMocks.ts b/source/javascripts/pages/YmlPage/components/ConfigurationYmlSource.mswMocks.ts index 934e2cfdc..718e6f5d0 100644 --- a/source/javascripts/pages/YmlPage/components/ConfigurationYmlSource.mswMocks.ts +++ b/source/javascripts/pages/YmlPage/components/ConfigurationYmlSource.mswMocks.ts @@ -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, },