Sync table from frontend (#4894)
This PR: - separates the existing updateSyncStatus endpoint into 2 endpoints - creates mutations and hooks that will call those endpoints - trigger the hook on toggle - removes form logic and add a separated component for toggling --------- Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const REMOTE_TABLE_FRAGMENT = gql`
|
||||
fragment RemoteTableFields on RemoteTable {
|
||||
name
|
||||
schema
|
||||
status
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,12 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { REMOTE_TABLE_FRAGMENT } from '@/databases/graphql/fragments/remoteTableFragment';
|
||||
|
||||
export const SYNC_REMOTE_TABLE = gql`
|
||||
${REMOTE_TABLE_FRAGMENT}
|
||||
mutation syncRemoteTable($input: RemoteTableInput!) {
|
||||
syncRemoteTable(input: $input) {
|
||||
...RemoteTableFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,12 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { REMOTE_TABLE_FRAGMENT } from '@/databases/graphql/fragments/remoteTableFragment';
|
||||
|
||||
export const UNSYNC_REMOTE_TABLE = gql`
|
||||
${REMOTE_TABLE_FRAGMENT}
|
||||
mutation unsyncRemoteTable($input: RemoteTableInput!) {
|
||||
unsyncRemoteTable(input: $input) {
|
||||
...RemoteTableFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,11 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_MANY_DATABASE_CONNECTION_TABLES = gql`
|
||||
query GetManyDatabaseConnectionTables($input: RemoteServerIdInput!) {
|
||||
findAvailableRemoteTablesByServerId(input: $input) {
|
||||
name
|
||||
schema
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,12 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { REMOTE_TABLE_FRAGMENT } from '@/databases/graphql/fragments/remoteTableFragment';
|
||||
|
||||
export const GET_MANY_REMOTE_TABLES = gql`
|
||||
${REMOTE_TABLE_FRAGMENT}
|
||||
query GetManyRemoteTables($input: RemoteServerIdInput!) {
|
||||
findAvailableRemoteTablesByServerId(input: $input) {
|
||||
...RemoteTableFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,10 +1,10 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
import { GET_MANY_DATABASE_CONNECTION_TABLES } from '@/databases/graphql/queries/findManyDatabaseConnectionTables';
|
||||
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
||||
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
||||
import {
|
||||
GetManyDatabaseConnectionTablesQuery,
|
||||
GetManyDatabaseConnectionTablesQueryVariables,
|
||||
GetManyRemoteTablesQuery,
|
||||
GetManyRemoteTablesQueryVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type UseGetDatabaseConnectionTablesParams = {
|
||||
@ -19,9 +19,9 @@ export const useGetDatabaseConnectionTables = ({
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const { data } = useQuery<
|
||||
GetManyDatabaseConnectionTablesQuery,
|
||||
GetManyDatabaseConnectionTablesQueryVariables
|
||||
>(GET_MANY_DATABASE_CONNECTION_TABLES, {
|
||||
GetManyRemoteTablesQuery,
|
||||
GetManyRemoteTablesQueryVariables
|
||||
>(GET_MANY_REMOTE_TABLES, {
|
||||
client: apolloMetadataClient ?? undefined,
|
||||
skip: skip || !apolloMetadataClient,
|
||||
variables: {
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { SYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/syncRemoteTable';
|
||||
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
||||
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
||||
import {
|
||||
RemoteTableInput,
|
||||
SyncRemoteTableMutation,
|
||||
SyncRemoteTableMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useSyncRemoteTable = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
SyncRemoteTableMutation,
|
||||
SyncRemoteTableMutationVariables
|
||||
>(SYNC_REMOTE_TABLE, {
|
||||
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
|
||||
});
|
||||
|
||||
const syncRemoteTable = useCallback(
|
||||
async (input: RemoteTableInput) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
||||
});
|
||||
},
|
||||
[mutate],
|
||||
);
|
||||
|
||||
return {
|
||||
syncRemoteTable,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { UNSYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/unsyncRemoteTable';
|
||||
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
||||
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
||||
import {
|
||||
RemoteTableInput,
|
||||
UnsyncRemoteTableMutation,
|
||||
UnsyncRemoteTableMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useUnsyncRemoteTable = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
UnsyncRemoteTableMutation,
|
||||
UnsyncRemoteTableMutationVariables
|
||||
>(UNSYNC_REMOTE_TABLE, {
|
||||
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
|
||||
});
|
||||
|
||||
const unsyncRemoteTable = useCallback(
|
||||
async (input: RemoteTableInput) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
||||
});
|
||||
},
|
||||
[mutate],
|
||||
);
|
||||
|
||||
return {
|
||||
unsyncRemoteTable,
|
||||
};
|
||||
};
|
||||
@ -1,10 +1,13 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useSyncRemoteTable } from '@/databases/hooks/useSyncRemoteTable';
|
||||
import { useUnsyncRemoteTable } from '@/databases/hooks/useUnsyncRemoteTable';
|
||||
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
||||
import { Toggle } from '@/ui/input/components/Toggle';
|
||||
import { SettingsIntegrationRemoteTableSyncStatusToggle } from '@/settings/integrations/components/SettingsIntegrationRemoteTableSyncStatusToggle';
|
||||
import { RemoteTable, RemoteTableStatus } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const settingsIntegrationsDatabaseTablesSchema = z.object({
|
||||
syncedTablesByName: z.record(z.boolean()),
|
||||
@ -15,6 +18,7 @@ export type SettingsIntegrationsDatabaseTablesFormValues = z.infer<
|
||||
>;
|
||||
|
||||
type SettingsIntegrationDatabaseTablesListCardProps = {
|
||||
connectionId: string;
|
||||
tables: RemoteTable[];
|
||||
};
|
||||
|
||||
@ -25,27 +29,63 @@ const StyledRowRightContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const SettingsIntegrationDatabaseTablesListCard = ({
|
||||
connectionId,
|
||||
tables,
|
||||
}: SettingsIntegrationDatabaseTablesListCardProps) => {
|
||||
const { control } =
|
||||
useFormContext<SettingsIntegrationsDatabaseTablesFormValues>();
|
||||
const { syncRemoteTable } = useSyncRemoteTable();
|
||||
const { unsyncRemoteTable } = useUnsyncRemoteTable();
|
||||
|
||||
// We need to use a state because the table status update re-render the whole list of toggles
|
||||
const [items] = useState(
|
||||
tables.map((table) => ({
|
||||
id: table.name,
|
||||
...table,
|
||||
})),
|
||||
);
|
||||
|
||||
const onSyncUpdate = useCallback(
|
||||
async (isSynced: boolean, tableName: string) => {
|
||||
const table = items.find((table) => table.name === tableName);
|
||||
|
||||
if (!isDefined(table)) return;
|
||||
|
||||
if (isSynced) {
|
||||
await syncRemoteTable({
|
||||
remoteServerId: connectionId,
|
||||
name: tableName,
|
||||
schema: table.schema,
|
||||
});
|
||||
} else {
|
||||
await unsyncRemoteTable({
|
||||
remoteServerId: connectionId,
|
||||
name: tableName,
|
||||
schema: table.schema,
|
||||
});
|
||||
}
|
||||
},
|
||||
[connectionId, syncRemoteTable, items, unsyncRemoteTable],
|
||||
);
|
||||
|
||||
const rowRightComponent = useCallback(
|
||||
({
|
||||
item,
|
||||
}: {
|
||||
item: { id: string; name: string; status: RemoteTableStatus };
|
||||
}) => (
|
||||
<StyledRowRightContainer>
|
||||
<SettingsIntegrationRemoteTableSyncStatusToggle
|
||||
table={item}
|
||||
onSyncUpdate={onSyncUpdate}
|
||||
/>
|
||||
</StyledRowRightContainer>
|
||||
),
|
||||
[onSyncUpdate],
|
||||
);
|
||||
return (
|
||||
<SettingsListCard
|
||||
items={tables.map((table) => ({ id: table.name, ...table }))}
|
||||
RowRightComponent={({ item: table }) => (
|
||||
<StyledRowRightContainer>
|
||||
<Controller
|
||||
name={`syncedTablesByName.${table.name}`}
|
||||
control={control}
|
||||
defaultValue={table.status === RemoteTableStatus.Synced}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Toggle value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</StyledRowRightContainer>
|
||||
)}
|
||||
getItemLabel={(table) => table.name}
|
||||
items={items}
|
||||
RowRightComponent={rowRightComponent}
|
||||
getItemLabel={(table) => table.id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Toggle } from '@/ui/input/components/Toggle';
|
||||
import { RemoteTableStatus } from '~/generated-metadata/graphql';
|
||||
|
||||
export const SettingsIntegrationRemoteTableSyncStatusToggle = ({
|
||||
table,
|
||||
onSyncUpdate,
|
||||
}: {
|
||||
table: { id: string; name: string; status: RemoteTableStatus };
|
||||
onSyncUpdate: (value: boolean, tableName: string) => Promise<void>;
|
||||
}) => {
|
||||
const [isToggleLoading, setIsToggleLoading] = useState(false);
|
||||
|
||||
const onChange = async (newValue: boolean) => {
|
||||
if (isToggleLoading) return;
|
||||
setIsToggleLoading(true);
|
||||
await onSyncUpdate(newValue, table.name);
|
||||
setIsToggleLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
value={table.status === RemoteTableStatus.Synced}
|
||||
disabled={isToggleLoading}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -10,6 +10,7 @@ type ContainerProps = {
|
||||
isOn: boolean;
|
||||
color?: string;
|
||||
toggleSize: ToggleSize;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div<ContainerProps>`
|
||||
@ -22,6 +23,8 @@ const StyledContainer = styled.div<ContainerProps>`
|
||||
height: ${({ toggleSize }) => (toggleSize === 'small' ? 16 : 20)}px;
|
||||
transition: background-color 0.3s ease;
|
||||
width: ${({ toggleSize }) => (toggleSize === 'small' ? 24 : 32)}px;
|
||||
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
|
||||
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
|
||||
`;
|
||||
|
||||
const StyledCircle = styled(motion.div)<{
|
||||
@ -39,6 +42,7 @@ export type ToggleProps = {
|
||||
color?: string;
|
||||
toggleSize?: ToggleSize;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const Toggle = ({
|
||||
@ -47,6 +51,7 @@ export const Toggle = ({
|
||||
color,
|
||||
toggleSize = 'medium',
|
||||
className,
|
||||
disabled,
|
||||
}: ToggleProps) => {
|
||||
const [isOn, setIsOn] = useState(value ?? false);
|
||||
|
||||
@ -77,6 +82,7 @@ export const Toggle = ({
|
||||
color={color}
|
||||
toggleSize={toggleSize}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
>
|
||||
<StyledCircle
|
||||
animate={isOn ? 'on' : 'off'}
|
||||
|
||||
Reference in New Issue
Block a user