Skip to content

Commit

Permalink
Merge pull request #95 from apideck-libraries/add-wayfinder
Browse files Browse the repository at this point in the history
Add wayfinder to advanced custom mapping
  • Loading branch information
jakeprins authored Nov 2, 2023
2 parents 2c5b028 + 8c12fc4 commit 6987fa8
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 47 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"dependencies": {
"@apideck/components": "^0.12.9",
"@apideck/wayfinder": "^0.2.2",
"formik": "^2.2.9",
"fuse.js": "^6.5.3",
"jwt-decode": "^3.1.2",
Expand Down
2 changes: 1 addition & 1 deletion src/components/FieldMappingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useConnections } from '../utils/useConnections';
import { useSession } from '../utils/useSession';
import FieldSelector from './FieldSelector';

const findByDescription = (obj: any, description: string): any => {
export const findByDescription = (obj: any, description: string): any => {
for (const key in obj) {
if (obj[key] instanceof Object) {
const result = findByDescription(obj[key], description);
Expand Down
66 changes: 62 additions & 4 deletions src/components/FieldSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button, TextInput, useDebounce } from '@apideck/components';
import { Button, TextInput, useDebounce, useModal } from '@apideck/components';
import { WayFinder } from '@apideck/wayfinder';
import { Menu, Transition } from '@headlessui/react';
import classNames from 'classnames';

Expand All @@ -13,6 +14,10 @@ import React, {
useRef,
useState,
} from 'react';
import useSWR from 'swr';
import { extractLastAttribute } from '../utils/extractLastAttribute';
import { useConnections } from '../utils/useConnections';
import { findByDescription } from './FieldMappingForm';

interface Props {
onSelect: (field: any) => void;
Expand Down Expand Up @@ -50,9 +55,22 @@ const FieldSelector = ({
);
const [list, setList] = useState<any>([]);
const debouncedSearchTerm = useDebounce(searchTerm, 250);
const [fieldMappingString, setFieldMappingString] = useState();
const [fieldMappingString, setFieldMappingString] = useState<string>();
const [properties, setProperties] = useState<any>(propertiesProps);
const searchInputRef: any = useRef();
const { addModal, removeModal } = useModal();

const { selectedConnection, fetchResourceExample } = useConnections();

const { data, error: exampleError } = useSWR(
selectedConnection &&
selectedCustomMapping?.id &&
mode === 'advanced' && ['example', selectedCustomMapping?.id],
() => fetchResourceExample(selectedCustomMapping?.id?.split('+')[1]),
{ revalidateIfStale: false }
);

const exampleResponse = data?.data?.example_response;

useEffect(() => {
if (debouncedSearchTerm) {
Expand Down Expand Up @@ -379,12 +397,52 @@ const FieldSelector = ({
setFieldMappingString(e.target.value)
}
/>
<div className="flex justify-end">
<div className="flex justify-end space-x-2 items-center mt-2">
<Button
variant="outline"
size="small"
text="Open WayFinder"
disabled={!exampleResponse}
isLoading={!exampleError && !data}
onClick={() => {
addModal(
<WayFinder
onSelect={(jsonPath) => {
if (jsonPath) {
setFieldMappingString(jsonPath);
const mappingObject = findByDescription(
properties,
jsonPath
);
onSelect({
title: extractLastAttribute(jsonPath),
mode: 'manual',
type: mappingObject?.type || 'Advanced',
description: jsonPath,
example: mappingObject?.example,
});
}
removeModal();
}}
onClose={removeModal}
defaultInput={JSON.stringify(
exampleResponse,
null,
2
)}
defaultJsonPath={fieldMappingString}
/>,
{
className: '!max-w-5xl !p-0',
onClose: () => {},
}
);
}}
/>
<Menu.Item>
<Button
text="Confirm"
size="small"
className="mt-2"
disabled={!fieldMappingString}
onClick={() => {
onSelect({
Expand Down
80 changes: 41 additions & 39 deletions src/components/Vault.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React, { ReactElement, forwardRef, useEffect, useState } from 'react';

import { ToastProvider } from '@apideck/components';
import { ModalProvider, ToastProvider } from '@apideck/components';
import jwtDecode from 'jwt-decode';
import { BASE_URL } from '../constants/urls';
import { Connection } from '../types/Connection';
import { Session } from '../types/Session';
import { ConnectionsProvider } from '../utils/useConnections';
import { SessionProvider } from '../utils/useSession';
import Modal from './Modal';
import { ModalContent } from './ModalContent';
import { SessionProvider } from '../utils/useSession';

export interface Props {
/**
Expand Down Expand Up @@ -132,42 +132,44 @@ export const Vault = forwardRef<HTMLElement, Props>(function Vault(
const shouldRenderModal = (token && token?.length > 0 && isOpen) || trigger;

return (
<div id="react-vault" className="apideck">
{trigger
? React.cloneElement(trigger, { onClick: () => setIsOpen(true), ref })
: null}
{shouldRenderModal ? (
<Modal
isOpen={token && token?.length > 0 && isOpen}
onClose={() => onCloseModal()}
showAttribution={showAttribution}
>
<ToastProvider>
<ConnectionsProvider
appId={session?.application_id as string}
consumerId={session?.consumer_id as string}
token={jwt as string}
isOpen={isOpen}
onClose={onCloseModal}
onConnectionChange={onConnectionChange}
onConnectionDelete={onConnectionDelete}
unifiedApi={unifiedApi}
serviceId={serviceId}
unifyBaseUrl={unifyBaseUrl}
>
<SessionProvider session={session}>
<ModalContent
onClose={onCloseModal}
onConnectionChange={onConnectionChange}
consumer={
showConsumer ? session?.consumer_metadata : undefined
}
/>
</SessionProvider>
</ConnectionsProvider>
</ToastProvider>
</Modal>
) : null}
</div>
<ModalProvider>
<div id="react-vault" className="apideck">
{trigger
? React.cloneElement(trigger, { onClick: () => setIsOpen(true), ref })
: null}
{shouldRenderModal ? (
<Modal
isOpen={token && token?.length > 0 && isOpen}
onClose={() => onCloseModal()}
showAttribution={showAttribution}
>
<ToastProvider>
<ConnectionsProvider
appId={session?.application_id as string}
consumerId={session?.consumer_id as string}
token={jwt as string}
isOpen={isOpen}
onClose={onCloseModal}
onConnectionChange={onConnectionChange}
onConnectionDelete={onConnectionDelete}
unifiedApi={unifiedApi}
serviceId={serviceId}
unifyBaseUrl={unifyBaseUrl}
>
<SessionProvider session={session}>
<ModalContent
onClose={onCloseModal}
onConnectionChange={onConnectionChange}
consumer={
showConsumer ? session?.consumer_metadata : undefined
}
/>
</SessionProvider>
</ConnectionsProvider>
</ToastProvider>
</Modal>
) : null}
</div>
</ModalProvider>
);
});
2 changes: 1 addition & 1 deletion src/styles/base.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@import './tailwind.css';
@import './custom.css'
@import './custom.css';
2 changes: 2 additions & 0 deletions src/styles/index.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
@import './base.css';
@import './tailwind.css';

@import '../../node_modules/@apideck/wayfinder/dist/styles.css';
21 changes: 21 additions & 0 deletions src/utils/useConnections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface ContextProps {
quiet?: boolean;
}) => Promise<Connection | null>;
fetchResourceSchema: (resource: string) => Promise<any>;
fetchResourceExample: (resource: string) => Promise<any>;
fetchCustomFields: (resource: string) => Promise<any>;
}

Expand Down Expand Up @@ -357,6 +358,25 @@ export const ConnectionsProvider = ({
}
};

const fetchResourceExample = async (resource?: string) => {
if (!selectedConnection || !resource) return;
try {
const raw = await fetch(
`${unifyBaseUrl}/vault/connections/${selectedConnection.unified_api}/${selectedConnection.service_id}/${resource}/example`,
{ headers }
);

return await raw.json();
} catch (error) {
console.error(error);
addToast({
title: 'Failed to fetch example',
description: (error as Error)?.message,
type: 'error',
});
}
};

const fetchCustomFields = async (resource?: string) => {
if (!selectedConnection || !resource) return;
try {
Expand Down Expand Up @@ -435,6 +455,7 @@ export const ConnectionsProvider = ({
headers,
token,
fetchResourceSchema,
fetchResourceExample,
fetchCustomFields,
fetchCustomMapping,
fetcher,
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx}',
'./node_modules/@apideck/wayfinder/**/*.js',
'./node_modules/@apideck/components/**/*.js',
'./stories/*',
],
Expand Down
Loading

0 comments on commit 6987fa8

Please sign in to comment.