0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

feat(console): edit standard connector (#2568)

This commit is contained in:
wangsijie 2022-12-05 14:54:48 +08:00 committed by GitHub
parent 0c9e8cba0d
commit e3bc924c86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 68 additions and 83 deletions

View file

@ -1,17 +1,16 @@
import type { Connector, ConnectorResponse, ConnectorMetadata } from '@logto/schemas';
import type { ConnectorResponse } from '@logto/schemas';
import { ConnectorType } from '@logto/schemas';
import { useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import CodeEditor from '@/components/CodeEditor';
import DetailsForm from '@/components/DetailsForm';
import FormCard from '@/components/FormCard';
import FormField from '@/components/FormField';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import useApi from '@/hooks/use-api';
import ConnectorForm from '@/pages/Connectors/components/ConnectorForm';
import type { ConnectorFormType } from '@/pages/Connectors/types';
import { safeParseJson } from '@/utilities/json';
import * as styles from '../index.module.scss';
@ -25,34 +24,35 @@ type Props = {
const ConnectorContent = ({ isDeleted, connectorData, onConnectorUpdated }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { connectorId } = useParams();
const api = useApi();
const methods = useForm<ConnectorFormType>({ reValidateMode: 'onBlur' });
const {
control,
formState: { isSubmitting, isDirty },
handleSubmit,
watch,
reset,
} = useForm<{ configJson: string }>({ reValidateMode: 'onBlur' });
const defaultConfig = useMemo(() => {
const hasData = Object.keys(connectorData.config).length > 0;
return hasData ? JSON.stringify(connectorData.config, null, 2) : connectorData.configTemplate;
}, [connectorData]);
} = methods;
useEffect(() => {
reset();
}, [connectorId, reset]);
const { name, logo, logoDark, target } = connectorData.metadata;
const { config } = connectorData;
reset({
target,
logo,
logoDark: logoDark ?? '',
name: name?.en,
config: JSON.stringify(config, null, 2),
});
}, [connectorData, reset]);
const onSubmit = handleSubmit(async ({ configJson }) => {
if (!configJson) {
const onSubmit = handleSubmit(async ({ config, ...metadata }) => {
if (!config) {
toast.error(t('connector_details.save_error_empty_config'));
return;
}
const result = safeParseJson(configJson);
const result = safeParseJson(config);
if (!result.success) {
toast.error(result.error);
@ -60,17 +60,22 @@ const ConnectorContent = ({ isDeleted, connectorData, onConnectorUpdated }: Prop
return;
}
const { metadata, ...rest } = await api
.patch(`/api/connectors/${connectorData.id}`, { json: { config: result.data } })
.json<Connector & { metadata: ConnectorMetadata; type: ConnectorType }>();
const body = connectorData.isStandard
? { config: result.data, metadata: { ...metadata, name: { en: metadata.name } } }
: { config: result.data };
onConnectorUpdated({ ...rest, ...metadata });
reset({ configJson: JSON.stringify(result.data, null, 2) });
const updatedConnector = await api
.patch(`/api/connectors/${connectorData.id}`, {
json: body,
})
.json<ConnectorResponse>();
onConnectorUpdated(updatedConnector);
toast.success(t('general.saved'));
});
return (
<>
<FormProvider {...methods}>
<DetailsForm
isDirty={isDirty}
isSubmitting={isSubmitting}
@ -82,33 +87,19 @@ const ConnectorContent = ({ isDeleted, connectorData, onConnectorUpdated }: Prop
description="connector_details.settings_description"
learnMoreLink="https://docs.logto.io/docs/references/connectors"
>
<Controller
name="configJson"
control={control}
defaultValue={defaultConfig}
render={({ field: { onChange, value } }) => (
<FormField title="connector_details.edit_config_label">
<CodeEditor
className={styles.codeEditor}
language="json"
value={value}
onChange={onChange}
/>
</FormField>
)}
/>
<ConnectorForm connector={connectorData} />
{connectorData.type !== ConnectorType.Social && (
<SenderTester
className={styles.senderTest}
connectorId={connectorData.id}
connectorType={connectorData.type}
config={watch('configJson')}
config={watch('config')}
/>
)}
</FormCard>
</DetailsForm>
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleted && isDirty} />
</>
</FormProvider>
);
};

View file

@ -0,0 +1,7 @@
@use '@/scss/underscore' as _;
.tip {
color: var(--color-text-secondary);
font: var(--font-body-medium);
margin-top: _.unit(0.5);
}

View file

@ -8,17 +8,17 @@ import CodeEditor from '@/components/CodeEditor';
import FormField from '@/components/FormField';
import TextInput from '@/components/TextInput';
import type { ConnectorFormType } from '../../types';
import * as styles from './index.module.scss';
import type { CreateConnectorForm } from './types';
type Props = {
connector: ConnectorFactoryResponse;
};
const Form = ({ connector }: Props) => {
const ConnectorForm = ({ connector }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { configTemplate, isStandard } = connector;
const { control, register } = useFormContext<CreateConnectorForm>();
const { control, register } = useFormContext<ConnectorFormType>();
const [darkVisible, setDarkVisible] = useState(false);
const toggleDarkVisible = () => {
@ -43,14 +43,6 @@ const Form = ({ connector }: Props) => {
/>
<div className={styles.tip}>{t('connectors.guide.logo_tip')}</div>
</FormField>
{!darkVisible && (
<Button
size="small"
type="text"
title="connectors.guide.logo_dark_show"
onClick={toggleDarkVisible}
/>
)}
{darkVisible && (
<FormField title="connectors.guide.logo_dark">
<TextInput
@ -60,14 +52,16 @@ const Form = ({ connector }: Props) => {
<div className={styles.tip}>{t('connectors.guide.logo_dark_tip')}</div>
</FormField>
)}
{darkVisible && (
<Button
size="small"
type="text"
title="connectors.guide.logo_dark_collapse"
onClick={toggleDarkVisible}
/>
)}
<Button
size="small"
type="text"
title={
darkVisible
? 'connectors.guide.logo_dark_collapse'
: 'connectors.guide.logo_dark_show'
}
onClick={toggleDarkVisible}
/>
<FormField isRequired title="connectors.guide.target">
<TextInput {...register('target', { required: true })} />
<div className={styles.tip}>{t('connectors.guide.target_tip')}</div>
@ -89,4 +83,4 @@ const Form = ({ connector }: Props) => {
);
};
export default Form;
export default ConnectorForm;

View file

@ -56,12 +56,6 @@
margin: _.unit(6) 0;
}
.tip {
color: var(--color-text-secondary);
font: var(--font-body-medium);
margin-top: _.unit(0.5);
}
.footer {
margin-top: _.unit(6);
display: flex;

View file

@ -18,9 +18,9 @@ import useSettings from '@/hooks/use-settings';
import SenderTester from '@/pages/ConnectorDetails/components/SenderTester';
import { safeParseJson } from '@/utilities/json';
import Form from './Form';
import type { ConnectorFormType } from '../../types';
import ConnectorForm from '../ConnectorForm';
import * as styles from './index.module.scss';
import type { CreateConnectorForm } from './types';
type Props = {
connector: ConnectorFactoryResponse;
@ -36,7 +36,7 @@ const Guide = ({ connector, onClose }: Props) => {
const connectorName = conditional(isLanguageTag(language) && name[language]) ?? name.en;
const isSocialConnector =
connectorType !== ConnectorType.Sms && connectorType !== ConnectorType.Email;
const methods = useForm<CreateConnectorForm>({ reValidateMode: 'onBlur' });
const methods = useForm<ConnectorFormType>({ reValidateMode: 'onBlur' });
const {
formState: { isSubmitting },
handleSubmit,
@ -102,7 +102,7 @@ const Guide = ({ connector, onClose }: Props) => {
<div className={styles.title}>{t('connectors.guide.connector_setting')}</div>
<FormProvider {...methods}>
<form onSubmit={onSubmit}>
<Form connector={connector} />
<ConnectorForm connector={connector} />
{!isSocialConnector && (
<SenderTester
className={styles.tester}

View file

@ -1,4 +1,4 @@
export type CreateConnectorForm = {
export type ConnectorFormType = {
config: string;
name: string;
logo: string;

View file

@ -180,7 +180,7 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
body,
} = ctx.guard;
const { metadata, type, validateConfig } = await getLogtoConnectorById(id);
const { type, validateConfig } = await getLogtoConnectorById(id);
if (body.syncProfile) {
assertThat(
@ -193,10 +193,9 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
validateConfig(config);
}
// FIXME @Darcy [LOG-4696]: revisit databaseMetadata check when implementing AC
const connector = await updateConnector({ set: body, where: { id }, jsonbMode: 'replace' });
ctx.body = { ...connector, metadata, type };
await updateConnector({ set: body, where: { id }, jsonbMode: 'replace' });
const connector = await getLogtoConnectorById(id);
ctx.body = transpileLogtoConnector(connector);
return next();
}

View file

@ -91,7 +91,7 @@ describe('connector PATCH routes', () => {
});
it('successfully updates connector configs', async () => {
getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
getLogtoConnectorsPlaceholder.mockResolvedValue([
{
dbEntry: mockConnector,
metadata: { ...mockMetadata, isStandard: true },
@ -150,7 +150,7 @@ describe('connector PATCH routes', () => {
});
it('successfully set syncProfile to `true` and with social connector', async () => {
getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
getLogtoConnectorsPlaceholder.mockResolvedValue([
{
dbEntry: { ...mockConnector, syncProfile: false },
metadata: mockMetadata,
@ -170,7 +170,7 @@ describe('connector PATCH routes', () => {
});
it('successfully set syncProfile to `false`', async () => {
getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
getLogtoConnectorsPlaceholder.mockResolvedValue([
{
dbEntry: { ...mockConnector, syncProfile: false },
metadata: mockMetadata,

View file

@ -5,7 +5,7 @@ import type { Connector } from '../db-entries/index.js';
export type { ConnectorMetadata } from '@logto/connector-kit';
export { ConnectorType, ConnectorPlatform } from '@logto/connector-kit';
export type ConnectorResponse = Omit<Connector, 'metadata'> &
export type ConnectorResponse = Connector &
Omit<BaseConnector<ConnectorType>, 'configGuard' | 'metadata'> &
ConnectorMetadata;