Skip to content

Commit

Permalink
feat(connectors): add TradingView connector, handle webhook test failure
Browse files Browse the repository at this point in the history
  • Loading branch information
anteqkois committed May 31, 2024
1 parent a119ee7 commit 904e903
Show file tree
Hide file tree
Showing 29 changed files with 271 additions and 45 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
include .env
export

generate-metadata-real:
generate-metadata-dev:
npx ts-node -r tsconfig-paths/register -P tools/tsconfig.tools.json tools/scripts/metadata/insert-real-metadata.ts libs/connectors/framework

generate-metadata-prod:
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/app/subscriptions/SubscriptionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Separator } from '@linkerry/ui-components/client'
import { Card, CardContent, H4 } from '@linkerry/ui-components/server'
import dayjs from 'dayjs'
import { HTMLAttributes } from 'react'
import { ConfigurationItem } from '../../../modules/billing/components/ConfigItem'
import { KeyValueItem } from '../../../shared/components/KeyValueItem'

export interface SubscriptionCardProps extends HTMLAttributes<HTMLElement> {
subscription: SubscriptionPopulated
Expand Down Expand Up @@ -67,7 +67,7 @@ export const SubscriptionCard = ({ subscription }: SubscriptionCardProps) => {
Current Plan: <span className="font-normal">{item.product.name}</span>
</H4>
{(Object.entries(planConfigurationDetails) as [keyof PlanProductConfiguration, PlanConfigurationDetailsValue][]).map(([name, value]) => (
<ConfigurationItem key={name} label={value.displayName} value={item.product.config[name]} />
<KeyValueItem key={name} label={value.displayName} value={item.product.config[name]} />
))}
</div>
// </div>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/app/subscriptions/UsageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Separator } from '@linkerry/ui-components/client'
import { Card, CardContent, H4 } from '@linkerry/ui-components/server'
import dayjs from 'dayjs'
import { HTMLAttributes } from 'react'
import { ConfigurationItem } from '../../../modules/billing/components/ConfigItem'
import { ErrorInfo } from '../../../shared/components'
import { KeyValueItem } from '../../../shared/components/KeyValueItem'

export interface UsageCardProps extends HTMLAttributes<HTMLElement> {
subscription: SubscriptionPopulated
Expand Down Expand Up @@ -32,7 +32,7 @@ export const UsageCard = ({ usage, subscription }: UsageCardProps) => {
<div className="mt-2">
<H4 className="">Current Usage</H4>
{(Object.entries(planConfigurationDetails) as [keyof PlanProductConfiguration, PlanConfigurationDetailsValue][]).map(([name, value]) => (
<ConfigurationItem
<KeyValueItem
key={name}
label={value.displayName}
value={usage[name] ? `${usage[name]} / ${subscription.items[0].product.config[name]}` : '-'}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/modules/billing/components/ReachLimitDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { Button } from '@linkerry/ui-components/server'
import Link from 'next/link'
import { HTMLAttributes } from 'react'
import { ErrorInfo, Spinner } from '../../../shared/components'
import { KeyValueItem } from '../../../shared/components/KeyValueItem'
import { useSubscriptions } from '../subscriptions/useSubscriptions'
import { useUsage } from '../usage/useUsage'
import { useReachLimitDialog } from '../useReachLimitDialog'
import { ConfigurationItem } from './ConfigItem'

export interface ReachLimitDialogProps extends HTMLAttributes<HTMLElement> {}

Expand Down Expand Up @@ -46,7 +46,7 @@ export const ReachLimitDialog = () => {
if (!itemValue) itemValue = usage[name] ? `${usage[name]} / ${currentPlan?.config[name]}` : '-'

return (
<ConfigurationItem
<KeyValueItem
key={name}
className={name === exceededConfigurationEntry?.name ? 'text-negative hover:text-negative' : ''}
label={value.displayName}
Expand Down
30 changes: 30 additions & 0 deletions apps/web/modules/editor/components/EditorFlowMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { FlowStatus, FlowVersionState, isCustomHttpExceptionAxios } from '@linkerry/shared'
import {
ButtonClient,
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
Input,
Menubar,
MenubarMenu,
Expand All @@ -14,13 +19,15 @@ import {
import { Button, Icons } from '@linkerry/ui-components/server'
import { useDebouncedCallback } from '@react-hookz/web'
import { ChangeEvent, HTMLAttributes, useCallback, useMemo, useRef, useState } from 'react'
import { KeyValueItem } from '../../../shared/components/KeyValueItem'
import { useEditor } from '../useEditor'

export interface EditorFlowMenuProps extends HTMLAttributes<HTMLElement> {}

export const EditorFlowMenu = ({ children }: EditorFlowMenuProps) => {
const { flow, updateFlowVersionDisplayName, publishFlow, flowOperationRunning, setFlowStatus, onClickFlowRuns } = useEditor()
const [flowVersionName, setFlowVersionName] = useState(flow.version.displayName)
const [flowDetails, showFlowDetails] = useState(false)
const inputFlowVersionNameRef = useRef<HTMLInputElement>(null)
const { toast } = useToast()
const flowValidity = useMemo(() => {
Expand Down Expand Up @@ -170,6 +177,29 @@ export const EditorFlowMenu = ({ children }: EditorFlowMenuProps) => {
<Icons.Delete />
</Button>
</MenubarMenu> */}
{/* flowDetails, showFlowDetails */}
<Dialog open={flowDetails} onOpenChange={showFlowDetails}>
<DialogTrigger asChild>
<MenubarMenu>
<Button className="h-7 rounded-sm" variant={'ghost'} onClick={() => showFlowDetails(true)}>
Details
</Button>
</MenubarMenu>
</DialogTrigger>
<DialogContent className="sm:max-w-dialog">
<DialogHeader>
<DialogTitle>Flow Details</DialogTitle>
</DialogHeader>
<KeyValueItem
label={
<>
Flow ID <span className="font-bolder">(don&apos;t show anybody)</span>
</>
}
value={flow._id}
/>
</DialogContent>
</Dialog>
</Menubar>
</nav>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/web/modules/editor/store/triggersSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export const createTriggersSlice: CreateSlice<TriggersSlice> = (set, get) => ({
set({
webhookTriggerWatcherWorks: false,
})
if (typeof data === 'string') {
if ('message' in data) {
// neutral message like "manual cancelation", "timeout"
set({
flowOperationRunning: null,
Expand Down
4 changes: 3 additions & 1 deletion apps/web/modules/editor/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
TriggerConnector,
TriggerEvent,
WEBSOCKET_NAMESPACE,
WatchTriggerEventsWSResponse,
WatchTriggerEventsWSResponseFailure,
} from '@linkerry/shared'
import { Dispatch, SetStateAction } from 'react'
import { Edge, OnConnect, OnEdgesChange, OnNodesChange } from 'reactflow'
Expand Down Expand Up @@ -98,7 +100,7 @@ export interface TriggersSlice {
patchEditedTriggerConnector: (update: DeepPartial<Omit<TriggerConnector, '_id'>>) => Promise<void>
resetTrigger: (triggerName: string) => Promise<void>
testPoolTrigger: () => Promise<TriggerEvent[]>
testWebhookTrigger: () => Promise<TriggerEvent[] | string>
testWebhookTrigger: () => Promise<TriggerEvent[] | WatchTriggerEventsWSResponseFailure>
webhookTriggerWatcherWorks: boolean
cancelWebhookTrigger: () => Promise<void>
}
Expand Down
34 changes: 17 additions & 17 deletions apps/web/modules/editor/trigger/TriggerWebhookSimulation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const TriggerWebhookSimulation = ({ panelSize, disabled, disabledMessage,
cancelWebhookTrigger,
webhookTriggerWatcherWorks,
} = useEditor()
const [errorMessage, setErrorMessage] = useState<string>('')
assertNotNullOrUndefined(editedTrigger?.name, 'editedTrigger.name')
if (!isConnectorTrigger(editedTrigger))
throw new CustomError('Invalid trigger type, can not use other than ConnectorTrigger', ErrorCode.INVALID_TYPE, {
Expand Down Expand Up @@ -89,34 +90,31 @@ export const TriggerWebhookSimulation = ({ panelSize, disabled, disabledMessage,
if (status === 'pending') return <Spinner />

const onClickTest = async () => {
setErrorMessage('')

try {
const triggerEventsData = await testWebhookTrigger()
if (typeof triggerEventsData === 'string')
return toast({
title: 'Stop Test Trigger Webhook',
description: triggerEventsData,
variant: 'default',
})
if ('message' in triggerEventsData) {
if (triggerEventsData.error) {
return setErrorMessage(triggerEventsData.message)
} else
return toast({
title: 'Stop Test Trigger Webhook',
description: triggerEventsData.message,
variant: 'default',
})
}

const queryClient = getBrowserQueryCllient()
queryClient.setQueryData(['trigger-events', editedTrigger.name], triggerEventsData)
setRecord(prepareCodeMirrorValue(triggerEventsData[triggerEventsData.length - 1].payload))
setSelectedTriggerEventId(triggerEventsData[triggerEventsData.length - 1]._id)
setInitialTime(dayjs().format())
} catch (error: any) {
if (typeof error === 'string')
toast({
title: 'Test Trigger Webhook Error',
description: error,
variant: 'destructive',
})
if (typeof error === 'string') return setErrorMessage(error)
else {
console.error(error)
toast({
title: 'Test Trigger Webhook Error',
description: 'Unknwon error occurred',
variant: 'destructive',
})
return setErrorMessage('Unknwon error occurred')
}
}
}
Expand Down Expand Up @@ -212,6 +210,8 @@ export const TriggerWebhookSimulation = ({ panelSize, disabled, disabledMessage,
</div>
)}

{errorMessage.length ? <ErrorInfo message={errorMessage} className="mt-2" /> : null}

{record && (
<CodeEditor
value={record}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/shared/components/Consents/CookiesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const CookiesModal = ({
},
}: CookiesModalProps) => {
const [isOpen, setIsOpen] = useState(false)
const [hide, setHide] = useState(false)
const [hide, setHide] = useState(true)

const accept = () => {
setIsOpen(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { cn } from '@linkerry/ui-components/utils'
import { HTMLAttributes } from 'react'

export interface ConfigurationItemProps extends HTMLAttributes<HTMLElement> {
label: string
export interface KeyValueItemProps extends HTMLAttributes<HTMLElement> {
label: string | JSX.Element
value: string | number
}

export const ConfigurationItem = ({ label, value, className }: ConfigurationItemProps) => {
export const KeyValueItem = ({ label, value, className }: KeyValueItemProps) => {
return (
<p className={cn('flex justify-between hover:bg-accent hover:text-accent-foreground py-0.5 px-2 rounded-md', className)}>
<span className="text-muted-foreground">{label}:</span>
Expand Down
5 changes: 5 additions & 0 deletions docs/ai-prompts/describe-connector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Write short descriptions for app which will be used as a plugin to create flows.

Examples:
```
```
4 changes: 3 additions & 1 deletion libs/connectors/binance/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const coingecko = createConnector({
displayName: 'Binance',
logoUrl: '/images/connectors/binance.png',
triggers: [],
description: 'Binance connector for interacting with the bigest cryptocurrency exchange',
description: 'Binance connector for interacting with the biggest cryptocurrency exchange',
descriptionLong:
"The Binance connector is a comprehensive tool designed for seamless interaction with the world's largest cryptocurrency exchange, Binance. With this app, you can execute a wide range of functionalities including placing buy and sell orders, checking real-time market prices, and managing your portfolio. It allows you to track your account balances, access detailed trading history, and automate trading strategies with ease. Additionally, the connector supports advanced features like setting stop-loss and take-profit limits, and integrating various trading bots. Whether you're a novice trader or a seasoned crypto investor, this connector provides a robust solution for all your Binance exchange needs.",
minimumSupportedRelease: '0.0.0',
actions: [
cancelAllSymbolOrdersActionFactory(BinanceClient, binanceAuth),
Expand Down
3 changes: 3 additions & 0 deletions libs/connectors/framework/src/lib/connectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class Connector<ConnectorAuth extends ConnectorAuthProperty = ConnectorAu
public readonly displayName: string,
public readonly logoUrl: string,
public readonly description: string,
public readonly descriptionLong: string,
public readonly minimumSupportedRelease: string,
public readonly maximumSupportedRelease: string,
public readonly tags: ConnectorTag[],
Expand Down Expand Up @@ -61,6 +62,7 @@ type CreateConnectorParams<ConnectorAuth extends ConnectorAuthProperty = Connect
displayName: string
logoUrl: string
description: string
descriptionLong?: string
minimumSupportedRelease: string
maximumSupportedRelease?: string
auth: ConnectorAuth | undefined
Expand All @@ -76,6 +78,7 @@ export const createConnector = <ConnectorAuth extends ConnectorAuthProperty>(par
params.displayName,
params.logoUrl,
params.description,
params.descriptionLong || '',
params.minimumSupportedRelease,
params.maximumSupportedRelease ?? '9999.9999.9999',
params.tags,
Expand Down
25 changes: 25 additions & 0 deletions libs/connectors/trading-view/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": ["../../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}
7 changes: 7 additions & 0 deletions libs/connectors/trading-view/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# connectors-trading-view

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build connectors-trading-view` to build the library.
10 changes: 10 additions & 0 deletions libs/connectors/trading-view/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@linkerry/trading-view",
"version": "0.0.1",
"dependencies": {
"tslib": "^2.3.0"
},
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts"
}
21 changes: 21 additions & 0 deletions libs/connectors/trading-view/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "connectors-trading-view",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/connectors/trading-view/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/connectors/trading-view",
"main": "libs/connectors/trading-view/src/index.ts",
"tsConfig": "libs/connectors/trading-view/tsconfig.lib.json",
"assets": ["libs/connectors/trading-view/*.md"],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
}
}
}
}
24 changes: 24 additions & 0 deletions libs/connectors/trading-view/src/common/instructions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable no-useless-escape */

export const instructions = {
instructions_webhook_url: `You need to paste webhook URL in the "Notification" tab at Trading View alert.
This URL should be: *https://api.linkerry.com/api/v1/webhooks/\<flow-id\>*.
Replace \<flow-id\> to the current flow ID. You can find this ID by clicking the **"Details"** button on the top menu. For **testing purpose**, you must append "/simulate" to the end of URL, so it should looks like *https://api.linkerry.com/api/v1/webhooks/\<flow-id\>/simulate*. After test back to the previous URL without "/simulate".
**Don't show this URLs to anybody**.`,
instructions_message: `You need to prepare the correct webhook message. Go to the "Settings" tab for your TradingView alert and fill in the "Message" input. It must be a valid JSON. You can refer to this [TradingView tutorial](https://www.tradingview.com/support/solutions/43000529348-about-webhooks/) on how to create this message and include the necessary data.
This JSON data will be available in the next flow steps. You can use dynamic variables provided by TradingView. Check [this article](https://www.tradingview.com/support/solutions/43000531021-how-to-use-a-variable-value-in-alert/) to learn how to use them.
Correct message can looks like:
\`\`\`
{
"symbol": "{{ticker}}",
"price": "{{close}}",
"price_time": "{{time}}",
"webhook_time": "{{timenow}}",
"my_fixed_message": "This is fixed message, it can be use later in flow for example to send to Telegram",
"to_buy_at_CEX": "BTC/USDT"
}
\`\`\``,
}
14 changes: 14 additions & 0 deletions libs/connectors/trading-view/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ConnectorAuth, createConnector } from '@linkerry/connectors-framework'
import { tradingViewNewAlert } from './triggers/new-alert'

export const telegramBot = createConnector({
displayName: 'Trading View',
description: 'Live advanced charting and analysis for financial markets',
minimumSupportedRelease: '0.0.0',
logoUrl: '/images/connectors/trading-view.png',
tags: ['alerts', 'chart', 'cryptocurrency', 'data feed', 'exchange', 'stock market', 'trading'],
auth: ConnectorAuth.None(),
actions: [
],
triggers: [tradingViewNewAlert],
})
Loading

0 comments on commit 904e903

Please sign in to comment.