Integrate the RS-to-RS form into Remote Schema Relationship tab [CON-98]

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4107
Co-authored-by: Matt Hardman <28978422+mattshardman@users.noreply.github.com>
Co-authored-by: Abhijeet Khangarot <26903230+abhi40308@users.noreply.github.com>
Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com>
Co-authored-by: Varun Choudhary <68095256+Varun-Choudhary@users.noreply.github.com>
GitOrigin-RevId: 32a21e0733fbaeb82a0870bdabbe0122f9382d96
This commit is contained in:
Sooraj 2022-04-07 18:56:27 +05:30 committed by hasura-bot
parent 6720aef26f
commit 419b19c9e7
19 changed files with 875 additions and 107 deletions

View File

@ -11,6 +11,7 @@
- console: add support for setting aggregation query permissions for ms sql server
- console: add RS-to-DB (only postgres & citus) relationships feature to remote schemas tab
- console: remove need for clicking the Modify btn before editing a remote schema (#1193, #8262)
- console: integrate the RS-to-RS form into Remote Schema Relationship tab
- cli: fix remote schema metadata formatting issues (#7608)
- cli: fix query collections metadata formatting issues (#7616)
- docs: support for `graphql-ws` is considered GA

View File

@ -57,6 +57,7 @@ describe('check if remote schema to db relationships are created properly', () =
'http://localhost:3000/remote-schemas/manage/source_rs/relationships'
);
cy.get(getElementFromAlias('add-a-new-rs-relationship')).click();
cy.get(getElementFromAlias('radio-select-remoteDB')).click();
cy.get(getElementFromAlias('rs-to-db-rel-name')).type('RelationshipName');
cy.get(getElementFromAlias('select-rel-type')).select('array');
cy.get(getElementFromAlias('select-source-type')).select('Pokemon');

View File

@ -0,0 +1,111 @@
import { getElementFromAlias } from '../../../helpers/eventHelpers';
import { replaceMetadata, resetMetadata } from '../../../helpers/metadata';
describe('check if remote schema to db relationships are created properly', () => {
before(() => {
// load stuff into the metadata
replaceMetadata({
version: 3,
sources: [
{
name: 'default',
kind: 'postgres',
tables: [],
configuration: {
connection_info: {
use_prepared_statements: true,
database_url: {
from_env: 'HASURA_GRAPHQL_DATABASE_URL',
},
isolation_level: 'read-committed',
pool_settings: {
connection_lifetime: 600,
retries: 1,
idle_timeout: 180,
max_connections: 50,
},
},
},
},
],
remote_schemas: [
{
name: 'source_rs',
definition: {
url: 'https://graphql-pokemon2.vercel.app/',
timeout_seconds: 60,
},
comment: '',
},
{
name: 'ref_rs',
definition: {
url: 'https://hasura-console-test.herokuapp.com/v1/graphql/',
timeout_seconds: 60,
},
comment: '',
},
],
});
});
it('should create a new rs-to-rs relationship from source field', () => {
cy.visit(
'http://localhost:3000/remote-schemas/manage/source_rs/relationships'
);
cy.get(getElementFromAlias('add-a-new-rs-relationship')).click();
cy.get(getElementFromAlias('radio-select-remoteSchema')).click();
cy.get(getElementFromAlias('rs-to-rs-rel-name')).type('RelationshipName');
cy.get(getElementFromAlias('select-source-type')).select('Pokemon');
cy.get(getElementFromAlias('select-ref-rs')).select('ref_rs');
cy.get('.ant-tree-switcher').first().click();
cy.get('.ant-tree-switcher').eq(1).click();
cy.get('.ant-tree-checkbox').eq(1).click();
cy.get(getElementFromAlias('select-argument')).select('Source Field');
cy.get(getElementFromAlias('selet-source-field')).select('id');
cy.get(getElementFromAlias('add-rs-relationship')).click();
cy.get(getElementFromAlias('remote-schema-relationships-table')).should(
'exist'
);
cy.get(getElementFromAlias('remote-schema-relationships-table'))
.find('tr')
.should('have.length', 2);
cy.get(getElementFromAlias('remote-schema-relationships-table')).contains(
'td',
'RelationshipName'
);
});
it('should create a new reverse rs-to-rs relationship with static fill value', () => {
cy.visit(
'http://localhost:3000/remote-schemas/manage/ref_rs/relationships'
);
cy.get(getElementFromAlias('add-a-new-rs-relationship')).click();
cy.get(getElementFromAlias('radio-select-remoteSchema')).click();
cy.get(getElementFromAlias('rs-to-rs-rel-name')).type(
'StaticRelationshipName'
);
cy.get(getElementFromAlias('select-source-type')).select('test');
cy.get(getElementFromAlias('select-ref-rs')).select('source_rs');
cy.get('.ant-tree-switcher').first().click(); // expand Query
cy.get('.ant-tree-switcher').eq(3).click(); // expand pokemon
cy.get('.ant-tree-checkbox').eq(1).click(); // check name argument
cy.get(getElementFromAlias('select-argument')).select('Static Value');
cy.get(getElementFromAlias('select-static-value')).type('Bulbasaur');
cy.get(getElementFromAlias('add-rs-relationship')).click();
cy.get(getElementFromAlias('remote-schema-relationships-table')).should(
'exist'
);
cy.get(getElementFromAlias('remote-schema-relationships-table'))
.find('tr')
.should('have.length', 2);
cy.get(getElementFromAlias('remote-schema-relationships-table')).contains(
'td',
'StaticRelationshipName'
);
});
after(() => {
// reset the metadata
resetMetadata();
});
});

View File

@ -3,8 +3,12 @@ import { useGetAllRemoteSchemaRelationships } from '@/features/MetadataAPI';
import { RemoteSchemaRelationshipTable } from '@/features/RelationshipsTable';
import { Button } from '@/new-components/Button';
import { RiAddCircleFill } from 'react-icons/ri';
// eslint-disable-next-line no-restricted-imports
import { RemoteSchemaToDbForm } from '@/features/RemoteRelationships/RemoteSchemaRelationships/components/RemoteSchemaToDB';
import {
RemoteSchemaToRemoteSchemaForm,
RemoteRelOption,
RemoteSchemaToDbForm,
} from '@/features/RemoteRelationships';
import { IndicatorCard } from '@/new-components/IndicatorCard';
type RemoteSchemaRelationRendererProp = {
remoteSchemaName: string;
@ -18,27 +22,46 @@ export const RemoteSchemaRelationRenderer = ({
isError,
} = useGetAllRemoteSchemaRelationships();
const [isFormOpen, setIsFormOpen] = useState(false);
const [formState, setFormState] = useState<RemoteRelOption>('remoteSchema');
if (isError) {
return <div>Error in fetching remote schema relationships.</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
return (
<>
{isError ? (
<div>Error in fetching remote schema relationships.</div>
) : isLoading ? (
<div>Fetching remote schema relationships...</div>
) : (
{remoteSchemaRels?.length ? (
<RemoteSchemaRelationshipTable
remoteSchemaRels={remoteSchemaRels ?? []}
remoteSchemaRels={remoteSchemaRels}
showActionCell={false}
remoteSchema={remoteSchemaName}
/>
) : (
<>
<IndicatorCard status="info">
No remote schema relationships found!
</IndicatorCard>
<br />
</>
)}
{isFormOpen ? (
<RemoteSchemaToDbForm
sourceRemoteSchema={remoteSchemaName}
closeHandler={() => setIsFormOpen(!isFormOpen)}
onSuccess={() => setIsFormOpen(false)}
/>
formState === 'remoteSchema' ? (
<RemoteSchemaToRemoteSchemaForm
sourceRemoteSchema={remoteSchemaName}
closeHandler={() => setIsFormOpen(!isFormOpen)}
relModeHandler={setFormState}
/>
) : (
<RemoteSchemaToDbForm
sourceRemoteSchema={remoteSchemaName}
closeHandler={() => setIsFormOpen(!isFormOpen)}
onSuccess={() => setIsFormOpen(false)}
relModeHandler={setFormState}
/>
)
) : (
<Button
icon={<RiAddCircleFill />}

View File

@ -0,0 +1,112 @@
import { generateLhsFields, getFieldTypesFromType } from '../utils';
describe('generateLhsFields', () => {
it('with 1 source field in the resultset', () => {
const lhsFields = generateLhsFields({
arguments: {
code: '$weight',
},
});
expect(lhsFields).toEqual(['weight']);
});
it('with multiple source field in the resultset', () => {
const lhsFields = generateLhsFields({
arguments: {
filter: {
code: {
eq: '$minimum',
ne: '$minimum',
nin: '$maximum',
},
},
},
});
expect(lhsFields).toEqual(['minimum', 'maximum']);
});
it('with no source field in the resultset', () => {
const lhsFields = generateLhsFields({
arguments: {
code: 'test',
},
});
expect(lhsFields).toEqual([]);
});
it('with empty resultset', () => {
const lhsFields = generateLhsFields({});
expect(lhsFields).toEqual([]);
});
});
describe('getFieldTypesFromType', () => {
const remoteSchemaTypes = [
{
typeName: 'Query',
fields: ['query', 'pokemons', 'pokemon'],
},
{
typeName: 'Pokemon',
fields: [
'id',
'number',
'name',
'weight',
'height',
'classification',
'types',
'resistant',
'attacks',
'weaknesses',
'fleeRate',
'maxCP',
'evolutions',
'evolutionRequirements',
'maxHP',
'image',
],
},
{
typeName: 'PokemonDimension',
fields: ['minimum', 'maximum'],
},
{
typeName: 'PokemonAttack',
fields: ['fast', 'special'],
},
{
typeName: 'Attack',
fields: ['name', 'type', 'damage'],
},
{
typeName: 'PokemonEvolutionRequirement',
fields: ['amount', 'name'],
},
];
it('with valid schemaTypes and sourceType', () => {
const lhsFields = getFieldTypesFromType(remoteSchemaTypes, 'PokemonAttack');
expect(lhsFields).toEqual(['fast', 'special']);
});
it('with valid schemaTypes and sourceType 2', () => {
const lhsFields = getFieldTypesFromType(remoteSchemaTypes, 'Pokemon');
expect(lhsFields).toEqual([
'id',
'number',
'name',
'weight',
'height',
'classification',
'types',
'resistant',
'attacks',
'weaknesses',
'fleeRate',
'maxCP',
'evolutions',
'evolutionRequirements',
'maxHP',
'image',
]);
});
it('with invalid schemaTypes and sourceType', () => {
const lhsFields = getFieldTypesFromType(remoteSchemaTypes, 'Pokemonss');
expect(lhsFields).toEqual([]);
});
});

View File

@ -23,6 +23,7 @@ export const RefRsSelector = ({ allRemoteSchemas }: RefRsSelectorProps) => {
placeholder="Select a remote schema"
options={rsOptions}
labelIcon={<FaPlug />}
dataTest="select-ref-rs"
/>
</div>
</div>

View File

@ -1,15 +1,18 @@
import React, { useEffect, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useRemoteSchema } from '@/features/MetadataAPI';
// eslint-disable-next-line no-restricted-imports
import { useTableColumns } from '@/features/SqlQueries/hooks/useTableColumns';
import { InputField, Select } from '@/new-components/Form';
import { MapSelector } from '@/new-components/MapSelector';
import React, { useEffect, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { FaLink } from 'react-icons/fa';
import {
LinkBlockHorizontal,
LinkBlockVertical,
} from '@/new-components/LinkBlock';
import { RemoteDatabaseWidget } from '../RemoteDatabaseWidget';
import { RsSourceTypeSelector } from '../RsSourceTypeSelector';
import { Schema } from './schema';
import { getTypesFromIntrospection } from './utils';
import { getTypesFromIntrospection } from '../../utils';
export const FormElements = ({
sourceRemoteSchema,
@ -65,27 +68,7 @@ export const FormElements = ({
return (
<>
<hr className="mb-md border-gray-300" />
{/* relationship meta */}
<div className="mb-md">
<div className="grid gap-sm grid-cols-1 sm:grid-cols-2">
<div className="grid gap-sm grid-cols-1 sm:grid-cols-2">
<div className="bg-white shadow-sm rounded p-md border border-gray-300">
<p className="flex items-center font-semibold text-muted">
<label className="cursor-pointer ml-sm font-semibold">
Remote Schema Relationship
</label>
</p>
<p className="text-muted pl-6">
Relationship from this remote schema to another
database&nbsp;schema.
</p>
</div>
</div>
</div>
</div>
<div className="w-full sm:w-6/12 mb-md">
<div className="w-full sm:w-6/12 my-md">
<div className="mb-md">
<InputField
name="relationshipName"
@ -123,20 +106,7 @@ export const FormElements = ({
/>
</div>
{/* horizontal connector line */}
<div className="col-span-2 flex relative items-center justify-center w-full py-md">
<div
className="flex z-10 items-center justify-center border border-gray-300 bg-white"
style={{
height: '32px',
width: '32px',
borderRadius: '100px',
}}
>
<FaLink />
</div>
<div className="absolute w-full border-b border-gray-300" />
</div>
<LinkBlockHorizontal />
<div className="col-span-5">
<RemoteDatabaseWidget />
@ -144,16 +114,7 @@ export const FormElements = ({
</div>
{/* vertical connector line */}
<div className="flex items-center w-full px-8">
<div className="relative flex items-center justify-center h-20">
<div className="absolute border border-l border-gray-300 h-20" />
<div className="absolute border border-gray-300 flex items-center justify-center rounded-full h-10 w-10 bg-white">
<FaLink />
</div>
</div>
<p className="m-0 px-8 font-semibold">Type Mapped To</p>
</div>
<LinkBlockVertical title="Type Mapped To" />
<MapSelector
types={
remoteSchemaTypes.find(x => x.typeName === RSTypeName)?.fields ?? []

View File

@ -9,17 +9,23 @@ import { IndicatorCard } from '@/new-components/IndicatorCard';
import { Button } from '@/new-components/Button';
import { FormElements } from './FormElements';
import { schema, Schema } from './schema';
import {
RelationshipTypeCardRadioGroup,
RemoteRelOption,
} from '../RemoteSchemaToRemoteSchemaForm/RelationshipTypeCardRadioGroup';
export type RemoteSchemaToDbFormProps = {
sourceRemoteSchema: string;
closeHandler?: () => void;
onSuccess?: () => void;
relModeHandler: (v: RemoteRelOption) => void;
};
export const RemoteSchemaToDbForm = ({
sourceRemoteSchema,
closeHandler,
onSuccess,
relModeHandler,
}: RemoteSchemaToDbFormProps) => {
const mutation = useMetadataMigration({
onSuccess: () => {
@ -47,7 +53,7 @@ export const RemoteSchemaToDbForm = ({
schema: '',
table: '',
typeName: '',
source_remote_schema: sourceRemoteSchema,
sourceRemoteSchema,
};
const submit = (values: Schema) => {
@ -95,6 +101,14 @@ export const RemoteSchemaToDbForm = ({
Create New Relationship
</span>
</div>
<hr className="mb-md border-gray-300" />
{/* relationship meta */}
<RelationshipTypeCardRadioGroup
value="remoteDB"
onChange={relModeHandler}
/>
<FormElements sourceRemoteSchema={sourceRemoteSchema} />
{/* submit */}
<div>

View File

@ -13,7 +13,7 @@ export const schema = z.object({
})
),
typeName: z.string().min(1, { message: 'Type is required!' }),
source_remote_schema: z.string(),
sourceRemoteSchema: z.string(),
});
export type Schema = z.infer<typeof schema>;

View File

@ -1,15 +0,0 @@
import { checkDefaultGQLScalarType } from '@/components/Services/RemoteSchema/Permissions/utils';
import { GraphQLSchema } from 'graphql';
export const getTypesFromIntrospection = (data: GraphQLSchema) => {
return Object.entries(data.getTypeMap())
.map(([typeName, x]) => ({
typeName,
// eslint-disable-next-line no-underscore-dangle
fields: Object.keys((x as any)._fields || {}),
}))
.filter(
({ typeName }) =>
!checkDefaultGQLScalarType(typeName) && !typeName.startsWith('__')
);
};

View File

@ -89,8 +89,8 @@ export const FormElements = ({
isError,
} = useLoadData(sourceRemoteSchema);
if (isLoading) {
return <div>Loading...</div>;
if (isLoading && !isError) {
return <IndicatorCard status="info">Loading...</IndicatorCard>;
}
if (isError || !remoteSchemaList || !sourceRemoteSchema) {
@ -102,38 +102,42 @@ export const FormElements = ({
}
return (
<div className="w-full">
<div className="grid gap-4 w-full">
<InputField name="name" label="Name" placeholder="Relationship name" />
<>
<div className="w-full sm:w-6/12 my-md">
<InputField
name="name"
label="Name"
placeholder="Relationship name"
dataTest="rs-to-rs-rel-name"
/>
</div>
<div className="grid grid-cols-12">
{/* select the source remote schema */}
<div className="col-span-5">
<RsSourceTypeSelector
types={remoteSchemaTypes.map(t => t.typeName)}
sourceTypeKey={rsSourceTypeKey}
/>
</div>
<div className="grid grid-cols-12">
<div className="col-span-5">
<RsSourceTypeSelector
types={remoteSchemaTypes.map(t => t.typeName)}
sourceTypeKey={rsSourceTypeKey}
/>
</div>
<LinkBlockHorizontal />
<LinkBlockHorizontal />
{/* select the reference remote schema */}
<div className="col-span-5">
<RefRsSelector allRemoteSchemas={remoteSchemaList} />
</div>
{/* select the reference remote schema */}
<div className="col-span-5">
<RefRsSelector allRemoteSchemas={remoteSchemaList} />
</div>
</div>
<LinkBlockVertical title="Type Mapped To" />
{/* relationship details */}
<div className="grid">
<div className="grid w-full pb-md">
<RemoteSchemaWidget
schemaName={refRemoteSchemaName}
fields={fieldsForSelectedRsType}
rootFields={['query', 'mutation']}
/>
</div>
</div>
</>
);
};

View File

@ -61,7 +61,6 @@ export const RemoteSchemaToRemoteSchemaForm = (
const lhs_fields = generateLhsFields(
values.resultSet as Record<string, unknown>
);
// testing payload
const requestBody = {
type: 'create_remote_schema_remote_relationship' as allowedMetadataTypes,
args: {
@ -91,15 +90,15 @@ export const RemoteSchemaToRemoteSchemaForm = (
onSubmit={submit}
>
{options => (
<div className="grid gap-4 border border-gray-300 rounded shadow-sm p-4 w-full">
<div className="flex items-center gap-4 w-full">
<div className="grid border border-gray-300 rounded shadow-sm p-4 w-full">
<div className="flex items-center gap-4 w-full mb-md">
<Button type="button" size="sm" onClick={closeHandler}>
Cancel
</Button>
<p className="font-semibold m-0">Create New Relationship</p>
</div>
<hr />
<hr className="mb-md border-gray-300" />
<RelationshipTypeCardRadioGroup
value="remoteSchema"
@ -116,6 +115,7 @@ export const RemoteSchemaToRemoteSchemaForm = (
type="submit"
isLoading={mutation.isLoading}
loadingText="Creating relationship"
data-test="add-rs-relationship"
>
Add Relationship
</Button>

View File

@ -1 +1,2 @@
export * from './RemoteSchemaToRemoteSchemaForm';
export * from './RelationshipTypeCardRadioGroup';

View File

@ -1,8 +1,8 @@
import React, { useMemo } from 'react';
import 'antd/lib/tree/style/index.css';
import { Tree as AntTree } from 'antd';
import { GraphQLSchema } from 'graphql';
import { EventDataNode } from 'antd/lib/tree';
import './index.css';
import {
AllowedRootFields,
AntdTreeNode,

View File

@ -113,6 +113,7 @@ export const ArgValueForm = ({
className={fieldStyle}
value={localArgValue.kind}
onChange={changeInputType}
data-test="select-argument"
>
<option disabled>Select an arugment...</option>
{argValueTypeOptions.map(option => (
@ -133,6 +134,7 @@ export const ArgValueForm = ({
className={fieldStyle}
value={localArgValue.value}
onChange={changeInputColumnValue}
data-test="selet-source-field"
>
<option value="" disabled>
Select Field...
@ -154,6 +156,7 @@ export const ArgValueForm = ({
className={fieldStyle}
value={localArgValue.value}
onChange={e => onValueChangeHandler(e.target.value)}
data-test="select-static-value"
/>
</>
)}

View File

@ -0,0 +1,547 @@
/* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */
/* stylelint-disable no-duplicate-selectors */
/* stylelint-disable */
/* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */
@-webkit-keyframes antCheckboxEffect {
0% {
transform: scale(1);
opacity: 0.5;
}
100% {
transform: scale(1.6);
opacity: 0;
}
}
@keyframes antCheckboxEffect {
0% {
transform: scale(1);
opacity: 0.5;
}
100% {
transform: scale(1.6);
opacity: 0;
}
}
@-webkit-keyframes ant-tree-node-fx-do-not-use {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes ant-tree-node-fx-do-not-use {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.ant-tree.ant-tree-directory .ant-tree-treenode {
position: relative;
}
.ant-tree.ant-tree-directory .ant-tree-treenode::before {
position: absolute;
top: 0;
right: 0;
bottom: 4px;
left: 0;
transition: background-color 0.3s;
content: '';
pointer-events: none;
}
.ant-tree.ant-tree-directory .ant-tree-treenode:hover::before {
background: #f5f5f5;
}
.ant-tree.ant-tree-directory .ant-tree-treenode > * {
z-index: 1;
}
.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-switcher {
transition: color 0.3s;
}
.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper {
border-radius: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper:hover {
background: transparent;
}
.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper.ant-tree-node-selected {
color: #fff;
background: transparent;
}
.ant-tree.ant-tree-directory .ant-tree-treenode-selected:hover::before,
.ant-tree.ant-tree-directory .ant-tree-treenode-selected::before {
background: #1890ff;
}
.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-switcher {
color: #fff;
}
.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper {
color: #fff;
background: transparent;
}
.ant-tree-checkbox {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum';
position: relative;
top: 0.2em;
line-height: 1;
white-space: nowrap;
outline: none;
cursor: pointer;
}
.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-inner,
.ant-tree-checkbox:hover .ant-tree-checkbox-inner,
.ant-tree-checkbox-input:focus + .ant-tree-checkbox-inner {
border-color: #1890ff;
}
.ant-tree-checkbox-checked::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid #1890ff;
border-radius: 2px;
visibility: hidden;
-webkit-animation: antCheckboxEffect 0.36s ease-in-out;
animation: antCheckboxEffect 0.36s ease-in-out;
-webkit-animation-fill-mode: backwards;
animation-fill-mode: backwards;
content: '';
}
.ant-tree-checkbox:hover::after,
.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox::after {
visibility: visible;
}
.ant-tree-checkbox-inner {
position: relative;
top: 0;
left: 0;
display: block;
width: 16px;
height: 16px;
direction: ltr;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 2px;
border-collapse: separate;
transition: all 0.3s;
}
.ant-tree-checkbox-inner::after {
position: absolute;
top: 50%;
left: 21.5%;
display: table;
width: 5.71428571px;
height: 9.14285714px;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(45deg) scale(0) translate(-50%, -50%);
opacity: 0;
transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s;
content: ' ';
}
.ant-tree-checkbox-input {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
cursor: pointer;
opacity: 0;
}
.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after {
position: absolute;
display: table;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(45deg) scale(1) translate(-50%, -50%);
opacity: 1;
transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
content: ' ';
}
.ant-tree-checkbox-checked .ant-tree-checkbox-inner {
background-color: #1890ff;
border-color: #1890ff;
}
.ant-tree-checkbox-disabled {
cursor: not-allowed;
}
.ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after {
border-color: rgba(0, 0, 0, 0.25);
-webkit-animation-name: none;
animation-name: none;
}
.ant-tree-checkbox-disabled .ant-tree-checkbox-input {
cursor: not-allowed;
pointer-events: none;
}
.ant-tree-checkbox-disabled .ant-tree-checkbox-inner {
background-color: #f5f5f5;
border-color: #d9d9d9 !important;
}
.ant-tree-checkbox-disabled .ant-tree-checkbox-inner::after {
border-color: #f5f5f5;
border-collapse: separate;
-webkit-animation-name: none;
animation-name: none;
}
.ant-tree-checkbox-disabled + span {
color: rgba(0, 0, 0, 0.25);
cursor: not-allowed;
}
.ant-tree-checkbox-disabled:hover::after,
.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-disabled::after {
visibility: hidden;
}
.ant-tree-checkbox-wrapper {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum';
display: inline-flex;
align-items: baseline;
line-height: unset;
cursor: pointer;
}
.ant-tree-checkbox-wrapper::after {
display: inline-block;
width: 0;
overflow: hidden;
content: '\a0';
}
.ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-disabled {
cursor: not-allowed;
}
.ant-tree-checkbox-wrapper + .ant-tree-checkbox-wrapper {
margin-left: 8px;
}
.ant-tree-checkbox + span {
padding-right: 8px;
padding-left: 8px;
}
.ant-tree-checkbox-group {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum';
display: inline-block;
}
.ant-tree-checkbox-group-item {
margin-right: 8px;
}
.ant-tree-checkbox-group-item:last-child {
margin-right: 0;
}
.ant-tree-checkbox-group-item + .ant-tree-checkbox-group-item {
margin-left: 0;
}
.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner {
background-color: #fff;
border-color: #d9d9d9;
}
.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner::after {
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background-color: #1890ff;
border: 0;
transform: translate(-50%, -50%) scale(1);
opacity: 1;
content: ' ';
}
.ant-tree-checkbox-indeterminate.ant-tree-checkbox-disabled .ant-tree-checkbox-inner::after {
background-color: rgba(0, 0, 0, 0.25);
border-color: rgba(0, 0, 0, 0.25);
}
.ant-tree {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum';
background: #fff;
border-radius: 2px;
transition: background-color 0.3s;
}
.ant-tree-focused:not(:hover):not(.ant-tree-active-focused) {
background: #e6f7ff;
}
.ant-tree-list-holder-inner {
align-items: flex-start;
}
.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner {
align-items: stretch;
}
.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-node-content-wrapper {
flex: auto;
}
.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging {
position: relative;
}
.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging::after {
position: absolute;
top: 0;
right: 0;
bottom: 4px;
left: 0;
border: 1px solid #1890ff;
opacity: 0;
-webkit-animation: ant-tree-node-fx-do-not-use 0.3s;
animation: ant-tree-node-fx-do-not-use 0.3s;
-webkit-animation-play-state: running;
animation-play-state: running;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
content: '';
pointer-events: none;
}
.ant-tree .ant-tree-treenode {
display: flex;
align-items: flex-start;
padding: 0 0 4px 0;
outline: none;
}
.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper {
color: rgba(0, 0, 0, 0.25);
cursor: not-allowed;
}
.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper:hover {
background: transparent;
}
.ant-tree .ant-tree-treenode-active .ant-tree-node-content-wrapper {
background: #f5f5f5;
}
.ant-tree .ant-tree-treenode:not(.ant-tree .ant-tree-treenode-disabled).filter-node .ant-tree-title {
color: inherit;
font-weight: 500;
}
.ant-tree-indent {
align-self: stretch;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ant-tree-indent-unit {
display: inline-block;
width: 24px;
}
.ant-tree-draggable-icon {
width: 24px;
line-height: 24px;
text-align: center;
opacity: 0.2;
transition: opacity 0.3s;
}
.ant-tree-treenode:hover .ant-tree-draggable-icon {
opacity: 0.45;
}
.ant-tree-switcher {
position: relative;
flex: none;
align-self: stretch;
width: 24px;
margin: 0;
line-height: 24px;
text-align: center;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ant-tree-switcher .ant-tree-switcher-icon,
.ant-tree-switcher .ant-select-tree-switcher-icon {
display: inline-block;
font-size: 10px;
vertical-align: baseline;
}
.ant-tree-switcher .ant-tree-switcher-icon svg,
.ant-tree-switcher .ant-select-tree-switcher-icon svg {
transition: transform 0.3s;
}
.ant-tree-switcher-noop {
cursor: default;
}
.ant-tree-switcher_close .ant-tree-switcher-icon svg {
transform: rotate(-90deg);
}
.ant-tree-switcher-loading-icon {
color: #1890ff;
}
.ant-tree-switcher-leaf-line {
position: relative;
z-index: 1;
display: inline-block;
width: 100%;
height: 100%;
}
.ant-tree-switcher-leaf-line::before {
position: absolute;
top: 0;
right: 12px;
bottom: -4px;
margin-left: -1px;
border-right: 1px solid #d9d9d9;
content: ' ';
}
.ant-tree-switcher-leaf-line::after {
position: absolute;
width: 10px;
height: 14px;
border-bottom: 1px solid #d9d9d9;
content: ' ';
}
.ant-tree-checkbox {
top: initial;
margin: 4px 8px 0 0;
}
.ant-tree .ant-tree-node-content-wrapper {
position: relative;
z-index: auto;
min-height: 24px;
margin: 0;
padding: 0 4px;
color: inherit;
line-height: 24px;
background: transparent;
border-radius: 2px;
cursor: pointer;
transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s;
}
.ant-tree .ant-tree-node-content-wrapper:hover {
background-color: #f5f5f5;
}
.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
background-color: #bae7ff;
}
.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle {
display: inline-block;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
vertical-align: top;
}
.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle:empty {
display: none;
}
.ant-tree-unselectable .ant-tree-node-content-wrapper:hover {
background-color: transparent;
}
.ant-tree-node-content-wrapper {
line-height: 24px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ant-tree-node-content-wrapper .ant-tree-drop-indicator {
position: absolute;
z-index: 1;
height: 2px;
background-color: #1890ff;
border-radius: 1px;
pointer-events: none;
}
.ant-tree-node-content-wrapper .ant-tree-drop-indicator::after {
position: absolute;
top: -3px;
left: -6px;
width: 8px;
height: 8px;
background-color: transparent;
border: 2px solid #1890ff;
border-radius: 50%;
content: '';
}
.ant-tree .ant-tree-treenode.drop-container > [draggable] {
box-shadow: 0 0 0 2px #1890ff;
}
.ant-tree-show-line .ant-tree-indent-unit {
position: relative;
height: 100%;
}
.ant-tree-show-line .ant-tree-indent-unit::before {
position: absolute;
top: 0;
right: 12px;
bottom: -4px;
border-right: 1px solid #d9d9d9;
content: '';
}
.ant-tree-show-line .ant-tree-indent-unit-end::before {
display: none;
}
.ant-tree-show-line .ant-tree-switcher {
background: #fff;
}
.ant-tree-show-line .ant-tree-switcher-line-icon {
vertical-align: -0.15em;
}
.ant-tree .ant-tree-treenode-leaf-last .ant-tree-switcher-leaf-line::before {
top: auto !important;
bottom: auto !important;
height: 14px !important;
}
.ant-tree-rtl {
direction: rtl;
}
.ant-tree-rtl .ant-tree-node-content-wrapper[draggable='true'] .ant-tree-drop-indicator::after {
right: -6px;
left: unset;
}
.ant-tree .ant-tree-treenode-rtl {
direction: rtl;
}
.ant-tree-rtl .ant-tree-switcher_close .ant-tree-switcher-icon svg {
transform: rotate(90deg);
}
.ant-tree-rtl.ant-tree-show-line .ant-tree-indent-unit::before {
right: auto;
left: -13px;
border-right: none;
border-left: 1px solid #d9d9d9;
}
.ant-tree-rtl.ant-tree-checkbox {
margin: 4px 0 0 8px;
}
.ant-tree-select-dropdown-rtl .ant-select-tree-checkbox {
margin: 4px 0 0 8px;
}

View File

@ -0,0 +1,3 @@
export * from './RemoteSchemaRelationships/components/RemoteSchemaToRemoteSchemaForm/RemoteSchemaToRemoteSchemaForm';
export * from './RemoteSchemaRelationships/components/RemoteSchemaToRemoteSchemaForm/RelationshipTypeCardRadioGroup';
export * from './RemoteSchemaRelationships/components/RemoteSchemaToDB';

View File

@ -44,10 +44,11 @@ export const CardRadioGroup = <T extends string = string>(
className="cursor-pointer rounded-full border shadow-sm border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
onChange={() => onChange(iValue)}
checked={value === iValue}
data-test={`radio-select-${iValue}`}
/>
<label
htmlFor={`radio-select-${iValue}`}
className="cursor-pointer ml-sm"
className="cursor-pointer ml-sm font-semibold"
>
{title}
</label>

View File

@ -56,7 +56,7 @@ export const InputField = ({
aria-label={wrapperProps.label}
data-test={dataTest}
className={clsx(
'block w-full max-w-xl h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400',
'block w-full max-w-xl h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400 placeholder-gray-500',
maybeError
? 'border-red-600 hover:border-red-700 placeholder-red-600 '
: 'border-gray-300 placeholder-gray-600',