Remove workflow
This commit is contained in:
@@ -107,22 +107,6 @@
|
||||
- [x] Add button to display money management use by the bot
|
||||
- [ ] POST POWNER - On the modarl, When simple bot selected, show only a select for the workflow
|
||||
|
||||
## Workflow
|
||||
|
||||
- [x] List all workflow saved in
|
||||
- [x] Use https://reactflow.dev/ to display a workflow (map flow to nodes and children to edges)
|
||||
- [x] On update Nodes : https://codesandbox.io/s/dank-waterfall-8jfcf4?file=/src/App.js
|
||||
- [x] Save workflow
|
||||
- [ ] Reset workflow
|
||||
- [ ] Add flows : Close Position, SendMessage
|
||||
- [ ] On Flow.tsx : Display inputs/outputs names on the node
|
||||
- [ ] Setup file tree UI for available flows : https://codesandbox.io/s/nlzui
|
||||
- [x] Create a workflow type that will encapsulate a list of flows
|
||||
- [x] Each flow will have parameters, inputs and outputs that will be used by the children flows
|
||||
- [ ] Flow can handle multiple parents
|
||||
- [ ] Run Simple bot base on a workflow
|
||||
- [ ] Run backtest based on a workflow
|
||||
- [ ] Add flows : ClosePosition, Scenario
|
||||
|
||||
## Backtests
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
```mermaid
|
||||
classDiagram
|
||||
Workflow <|-- Flow
|
||||
class Workflow{
|
||||
String Name
|
||||
Usage Usage : Trading|Task
|
||||
Flow[] Flows
|
||||
String Description
|
||||
}
|
||||
class Flow{
|
||||
String Name
|
||||
CategoryType Category
|
||||
FlowType Type
|
||||
FlowParameters Parameters
|
||||
String Description
|
||||
FlowType? AcceptedInput
|
||||
OutputType[]? Outputs
|
||||
Flow[]? ChildrenFlow
|
||||
Flow? ParentFlow
|
||||
Output? Output : Signal|Text|Candles
|
||||
MapInput(AcceptedInput, ParentFlow.Output)
|
||||
Run(ParentFlow.Output)
|
||||
LoadChildren()
|
||||
ExecuteChildren()
|
||||
}
|
||||
|
||||
```
|
||||
@@ -1,72 +0,0 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Workflows;
|
||||
using Managing.Domain.Workflows.Synthetics;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Managing.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Controller for managing workflows, including creating, retrieving, and deleting workflows.
|
||||
/// Requires authorization for access.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class WorkflowController : BaseController
|
||||
{
|
||||
private readonly IWorkflowService _workflowService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WorkflowController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="WorkflowService">Service for managing workflows.</param>
|
||||
/// <param name="userService">Service for user-related operations.</param>
|
||||
public WorkflowController(IWorkflowService WorkflowService, IUserService userService) : base(userService)
|
||||
{
|
||||
_workflowService = WorkflowService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new workflow or updates an existing one based on the provided workflow request.
|
||||
/// </summary>
|
||||
/// <param name="workflowRequest">The workflow request containing the details of the workflow to be created or updated.</param>
|
||||
/// <returns>The created or updated workflow.</returns>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Workflow>> PostWorkflow([ModelBinder]SyntheticWorkflow workflowRequest)
|
||||
{
|
||||
return Ok(await _workflowService.InsertOrUpdateWorkflow(workflowRequest));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all workflows.
|
||||
/// </summary>
|
||||
/// <returns>A list of all workflows.</returns>
|
||||
[HttpGet]
|
||||
public ActionResult<IEnumerable<SyntheticWorkflow>> GetWorkflows()
|
||||
{
|
||||
return Ok(_workflowService.GetWorkflows());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all available flows.
|
||||
/// </summary>
|
||||
/// <returns>A list of all available flows.</returns>
|
||||
[HttpGet]
|
||||
[Route("flows")]
|
||||
public async Task<ActionResult<IEnumerable<IFlow>>> GetAvailableFlows()
|
||||
{
|
||||
return Ok(await _workflowService.GetAvailableFlows());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a workflow by name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the workflow to delete.</param>
|
||||
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
||||
[HttpDelete]
|
||||
public ActionResult DeleteWorkflow(string name)
|
||||
{
|
||||
return Ok(_workflowService.DeleteWorkflow(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
namespace Managing.Application.Abstractions.Repositories;
|
||||
|
||||
public interface IWorkflowRepository
|
||||
{
|
||||
bool DeleteWorkflow(string name);
|
||||
Task<SyntheticWorkflow> GetWorkflow(string name);
|
||||
IEnumerable<SyntheticWorkflow> GetWorkflows();
|
||||
Task InsertWorkflow(SyntheticWorkflow workflow);
|
||||
Task UpdateWorkflow(SyntheticWorkflow workflow);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using Managing.Domain.Workflows;
|
||||
using Xunit;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Tests;
|
||||
|
||||
public class WorkflowTests : BaseTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Should_Create_Workflow_with_Feed_Ticker_Flow()
|
||||
{
|
||||
// Arrange
|
||||
var workflow = new Workflow
|
||||
{
|
||||
Name = "Bot trading",
|
||||
Usage = WorkflowUsage.Trading,
|
||||
Description = "Basic trading Workflow",
|
||||
Flows = new List<IFlow>()
|
||||
};
|
||||
|
||||
// var rsiDivFlow = new RsiDiv()
|
||||
// {
|
||||
// Parameters = "{\"Period\": 14,\"Timeframe\":1}",
|
||||
// Children = new List<IFlow>(),
|
||||
// };
|
||||
|
||||
// var tickerFlow = new FeedTicker(_exchangeService)
|
||||
// {
|
||||
// Parameters = "{\"Exchange\": 3,\"Ticker\":9,\"Timeframe\":1}",
|
||||
// Children = new List<IFlow>()
|
||||
// {
|
||||
// rsiDivFlow
|
||||
// }
|
||||
// };
|
||||
|
||||
// workflow.Flows.Add(tickerFlow);
|
||||
|
||||
// Act
|
||||
await workflow.Execute();
|
||||
|
||||
// Assert
|
||||
foreach (var f in workflow.Flows)
|
||||
{
|
||||
Assert.False(string.IsNullOrEmpty(f.Output));
|
||||
}
|
||||
|
||||
Assert.NotNull(workflow);
|
||||
Assert.NotNull(workflow.Flows);
|
||||
Assert.Single(workflow.Flows);
|
||||
Assert.Equal("Feed Ticker", workflow.Name);
|
||||
Assert.Equal(WorkflowUsage.Trading, workflow.Usage);
|
||||
Assert.Equal("Basic trading Workflow", workflow.Description);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Managing.Domain.Workflows;
|
||||
using Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
namespace Managing.Application.Abstractions;
|
||||
|
||||
public interface IFlowFactory
|
||||
{
|
||||
IFlow BuildFlow(SyntheticFlow request);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Managing.Domain.Workflows;
|
||||
using Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
namespace Managing.Application.Abstractions;
|
||||
|
||||
public interface IWorkflowService
|
||||
{
|
||||
bool DeleteWorkflow(string name);
|
||||
Task<IEnumerable<IFlow>> GetAvailableFlows();
|
||||
IEnumerable<SyntheticWorkflow> GetWorkflows();
|
||||
Task<Workflow> InsertOrUpdateWorkflow(SyntheticWorkflow workflowRequest);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public abstract class FlowBase : IFlow
|
||||
{
|
||||
public abstract Guid Id { get; }
|
||||
public abstract string Name { get; }
|
||||
public abstract FlowType Type { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract List<FlowOutput> AcceptedInputs { get; }
|
||||
public abstract List<IFlow> Children { get; set; }
|
||||
public abstract List<FlowParameter> Parameters { get; set; }
|
||||
public abstract Guid ParentId { get; }
|
||||
public abstract string Output { get; set; }
|
||||
public abstract List<FlowOutput> OutputTypes { get; }
|
||||
public abstract Task Execute(string input);
|
||||
public abstract void MapParameters();
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public class FlowParameter
|
||||
{
|
||||
public dynamic Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public interface IFlow
|
||||
{
|
||||
[Required]
|
||||
Guid Id { get; }
|
||||
[Required]
|
||||
string Name { get; }
|
||||
[Required]
|
||||
FlowType Type { get; }
|
||||
[Required]
|
||||
string Description { get; }
|
||||
[Required]
|
||||
List<FlowOutput> AcceptedInputs { get; }
|
||||
List<IFlow> Children { get; set; }
|
||||
[Required]
|
||||
List<FlowParameter> Parameters { get; set; }
|
||||
Guid ParentId { get; }
|
||||
string Output { get; set; }
|
||||
[Required]
|
||||
List<FlowOutput> OutputTypes { get; }
|
||||
Task Execute(string input);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
public class SyntheticFlow
|
||||
{
|
||||
[Required]
|
||||
public string Id { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
[Required]
|
||||
public FlowType Type { get; set; }
|
||||
[Required]
|
||||
public List<SyntheticFlowParameter> Parameters { get; set; }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
public class SyntheticFlowParameter
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; set; }
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
public class SyntheticWorkflow
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public WorkflowUsage Usage { get; set; }
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
[Required]
|
||||
public List<SyntheticFlow> Flows { get; set; }
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public class Workflow
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public WorkflowUsage Usage { get; set; }
|
||||
[Required]
|
||||
public List<IFlow> Flows { get; set; }
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
|
||||
public async Task Execute()
|
||||
{
|
||||
foreach (var flow in Flows)
|
||||
{
|
||||
await flow.Execute(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Suspense, lazy } from 'react'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
import {lazy, Suspense} from 'react'
|
||||
import {Route, Routes} from 'react-router-dom'
|
||||
|
||||
import LayoutMain from '../../layouts'
|
||||
import DeskWidget from '../../pages/desk/deskWidget'
|
||||
import Scenario from '../../pages/scenarioPage/scenario'
|
||||
import Tools from '../../pages/toolsPage/tools'
|
||||
import Workflows from '../../pages/workflow/workflows'
|
||||
|
||||
const Backtest = lazy(() => import('../../pages/backtestPage/backtest'))
|
||||
const Bots = lazy(() => import('../../pages/botsPage/bots'))
|
||||
@@ -48,17 +47,6 @@ const MyRoutes = () => {
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/workflow" element={<LayoutMain />}>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Workflows />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/settings" element={<LayoutMain />}>
|
||||
<Route
|
||||
index
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
import type { IFlow } from '../../generated/ManagingApi'
|
||||
import { WorkflowClient } from '../../generated/ManagingApi'
|
||||
|
||||
type FlowStore = {
|
||||
setFlows: (flows: IFlow[]) => void
|
||||
getFlows: (apiUrl: string) => void
|
||||
flows: IFlow[]
|
||||
}
|
||||
|
||||
export const useFlowStore = create<FlowStore>((set) => ({
|
||||
flows: [] as IFlow[],
|
||||
getFlows: async (apiUrl) => {
|
||||
const client = new WorkflowClient({}, apiUrl)
|
||||
await client.workflow_GetAvailableFlows().then((data) => {
|
||||
set(() => ({
|
||||
flows: data,
|
||||
}))
|
||||
})
|
||||
},
|
||||
setFlows: (flows) => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
flows: flows,
|
||||
}))
|
||||
},
|
||||
}))
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { IWorkflowStore } from '../workflowStore'
|
||||
|
||||
export const WorkflowSelector = (state: IWorkflowStore) => ({
|
||||
edges: state.edges,
|
||||
initWorkFlow: state.initWorkFlow,
|
||||
nodes: state.nodes,
|
||||
onConnect: state.onConnect,
|
||||
onEdgesChange: state.onEdgesChange,
|
||||
onNodesChange: state.onNodesChange,
|
||||
resetWorkflow: state.resetWorkflow,
|
||||
setNodes: state.setNodes,
|
||||
updateNodeData: state.updateNodeData,
|
||||
})
|
||||
@@ -1,113 +0,0 @@
|
||||
import type {
|
||||
Connection,
|
||||
Edge,
|
||||
EdgeChange,
|
||||
Node,
|
||||
NodeChange,
|
||||
OnNodesChange,
|
||||
OnEdgesChange,
|
||||
OnConnect,
|
||||
} from 'reactflow'
|
||||
import { addEdge, applyNodeChanges, applyEdgeChanges } from 'reactflow'
|
||||
import { create } from 'zustand'
|
||||
|
||||
import type {
|
||||
SyntheticFlowParameter,
|
||||
FlowParameter,
|
||||
IFlow,
|
||||
} from '../../generated/ManagingApi'
|
||||
|
||||
export type IWorkflowStore = {
|
||||
nodes: Node<IFlow>[]
|
||||
initialNodes: Node<IFlow>[]
|
||||
edges: Edge[]
|
||||
initialEdges: Edge[]
|
||||
onNodesChange: OnNodesChange
|
||||
onEdgesChange: OnEdgesChange
|
||||
onConnect: OnConnect
|
||||
updateNodeData: (nodeId: string, parameterName: string, value: string) => void
|
||||
initWorkFlow: (nodes: Node<IFlow>[], edges: Edge[]) => void
|
||||
setNodes: (nodes: Node<IFlow>[]) => void
|
||||
resetWorkflow: () => void
|
||||
}
|
||||
|
||||
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
||||
const useWorkflowStore = create<IWorkflowStore>((set, get) => ({
|
||||
edges: [],
|
||||
initWorkFlow: (nodes: Node<IFlow>[], edges: Edge[]) => {
|
||||
set({
|
||||
edges: edges,
|
||||
initialEdges: edges,
|
||||
initialNodes: nodes,
|
||||
nodes: nodes,
|
||||
})
|
||||
},
|
||||
initialEdges: [],
|
||||
initialNodes: [],
|
||||
nodes: [],
|
||||
onConnect: (connection: Connection, callback: void) => {
|
||||
set({
|
||||
edges: addEdge(connection, get().edges),
|
||||
})
|
||||
},
|
||||
onEdgesChange: (changes: EdgeChange[]) => {
|
||||
set({
|
||||
edges: applyEdgeChanges(changes, get().edges),
|
||||
})
|
||||
},
|
||||
onNodesChange: (changes: NodeChange[]) => {
|
||||
set({
|
||||
nodes: applyNodeChanges(changes, get().nodes),
|
||||
})
|
||||
},
|
||||
resetWorkflow: () => {
|
||||
set({
|
||||
edges: get().initialEdges,
|
||||
initialEdges: get().initialEdges,
|
||||
initialNodes: get().initialNodes,
|
||||
nodes: get().initialNodes,
|
||||
})
|
||||
},
|
||||
setNodes: (nodes: Node<IFlow>[]) => {
|
||||
set({
|
||||
nodes: nodes,
|
||||
})
|
||||
},
|
||||
updateNodeData: (nodeId: string, parameterName: string, value: string) => {
|
||||
set({
|
||||
nodes: get().nodes.map((node) => {
|
||||
if (node.id === nodeId) {
|
||||
node.data.parameters = updateParameters(
|
||||
node.data.parameters,
|
||||
parameterName,
|
||||
value
|
||||
)
|
||||
}
|
||||
return node
|
||||
}),
|
||||
})
|
||||
},
|
||||
}))
|
||||
|
||||
const updateParameters = (
|
||||
parameters: FlowParameter[],
|
||||
name: string,
|
||||
value: string
|
||||
) => {
|
||||
if (!parameters.find((parameter) => parameter.name === name)) {
|
||||
parameters.push({
|
||||
name: name,
|
||||
value: value,
|
||||
} as SyntheticFlowParameter)
|
||||
} else {
|
||||
parameters = parameters.map((parameter) => {
|
||||
if (parameter.name === name) {
|
||||
parameter.value = value
|
||||
}
|
||||
return parameter
|
||||
})
|
||||
}
|
||||
return parameters
|
||||
}
|
||||
|
||||
export default useWorkflowStore
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { IFlow } from '../../../generated/ManagingApi'
|
||||
|
||||
type IFlowItem = {
|
||||
onDragStart: (event: any, data: string) => void
|
||||
flow: IFlow
|
||||
}
|
||||
|
||||
const FlowItem: React.FC<IFlowItem> = ({ onDragStart, flow }) => {
|
||||
return (
|
||||
<div
|
||||
className="btn btn-primary btn-xs w-full h-full my-2"
|
||||
onDragStart={(event) => onDragStart(event, `${flow.type}`)}
|
||||
draggable
|
||||
>
|
||||
{flow.name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FlowItem
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Handle, Position } from 'reactflow'
|
||||
|
||||
import type { FlowOutput } from '../../../../generated/ManagingApi'
|
||||
import type { IFlowProps } from '../../../../global/type'
|
||||
import { Card } from '../../../mollecules'
|
||||
|
||||
const Flow = ({
|
||||
name,
|
||||
description,
|
||||
children,
|
||||
inputs,
|
||||
outputs,
|
||||
isConnectable,
|
||||
}: IFlowProps) => {
|
||||
return (
|
||||
<Card name={name} info={description}>
|
||||
{inputs?.map((input: FlowOutput) => {
|
||||
return (
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{children}
|
||||
{outputs?.map((output: FlowOutput) => {
|
||||
return (
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default Flow
|
||||
@@ -1,62 +0,0 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
|
||||
import { WorkflowSelector } from '../../../../../app/store/selectors/workflowSelector'
|
||||
import useWorkflowStore from '../../../../../app/store/workflowStore'
|
||||
import type { IFlow } from '../../../../../generated/ManagingApi'
|
||||
import { Timeframe, Ticker } from '../../../../../generated/ManagingApi'
|
||||
import { FormInput } from '../../../../mollecules'
|
||||
import Flow from '../Flow'
|
||||
|
||||
const FeedTicker = ({ data, isConnectable, id }: NodeProps<IFlow>) => {
|
||||
const { updateNodeData } = useWorkflowStore(WorkflowSelector)
|
||||
|
||||
const onTickerChange = useCallback((evt: any) => {
|
||||
updateNodeData(id, 'Ticker', evt.target.value)
|
||||
}, [])
|
||||
|
||||
const onTimeframeChange = useCallback((evt: any) => {
|
||||
updateNodeData(id, 'Timeframe', evt.target.value)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Flow
|
||||
name={data.name}
|
||||
description={data.description}
|
||||
inputs={data.acceptedInputs}
|
||||
outputs={data.outputTypes}
|
||||
isConnectable={isConnectable}
|
||||
>
|
||||
<FormInput label="Timeframe" htmlFor="period">
|
||||
<select
|
||||
className="select no-drag w-full max-w-xs"
|
||||
onChange={onTimeframeChange}
|
||||
value={
|
||||
data.parameters.find((p) => p.name === 'Timeframe')?.value || ''
|
||||
}
|
||||
>
|
||||
{Object.keys(Timeframe).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
<FormInput label="Ticker" htmlFor="ticker">
|
||||
<select
|
||||
className="select no-drag w-full max-w-xs"
|
||||
onChange={onTickerChange}
|
||||
value={data.parameters.find((p) => p.name === 'Ticker')?.value || ''}
|
||||
>
|
||||
{Object.keys(Ticker).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</Flow>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeedTicker
|
||||
@@ -1,58 +0,0 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
|
||||
import { WorkflowSelector } from '../../../../../app/store/selectors/workflowSelector'
|
||||
import useWorkflowStore from '../../../../../app/store/workflowStore'
|
||||
import type { IFlow } from '../../../../../generated/ManagingApi'
|
||||
import { Timeframe } from '../../../../../generated/ManagingApi'
|
||||
import { FormInput } from '../../../../mollecules'
|
||||
import Flow from '../Flow'
|
||||
|
||||
const RsiDivergenceFlow = ({ data, isConnectable, id }: NodeProps<IFlow>) => {
|
||||
const { updateNodeData } = useWorkflowStore(WorkflowSelector)
|
||||
|
||||
const onPeriodChange = useCallback((evt: any) => {
|
||||
updateNodeData(id, 'Period', evt.target.value)
|
||||
}, [])
|
||||
|
||||
const onTimeframeChange = useCallback((evt: any) => {
|
||||
updateNodeData(id, 'Timeframe', evt.target.value)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Flow
|
||||
name={data.name || ''}
|
||||
description={data.description}
|
||||
inputs={data.acceptedInputs}
|
||||
outputs={data.outputTypes}
|
||||
isConnectable={isConnectable}
|
||||
>
|
||||
<FormInput label="Period" htmlFor="period">
|
||||
<input
|
||||
id="period"
|
||||
name="text"
|
||||
onChange={onPeriodChange}
|
||||
className="input nodrag w-full max-w-xs"
|
||||
value={data.parameters.find((p) => p.name === 'Period')?.value || ''}
|
||||
/>
|
||||
</FormInput>
|
||||
<FormInput label="Timeframe" htmlFor="period">
|
||||
<select
|
||||
className="select no-drag w-full max-w-xs"
|
||||
onChange={onTimeframeChange}
|
||||
value={
|
||||
data.parameters.find((p) => p.name === 'Timeframe')?.value || ''
|
||||
}
|
||||
>
|
||||
{Object.keys(Timeframe).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</Flow>
|
||||
)
|
||||
}
|
||||
|
||||
export default RsiDivergenceFlow
|
||||
@@ -1,96 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
|
||||
import useApiUrlStore from '../../../../../app/store/apiStore'
|
||||
import { WorkflowSelector } from '../../../../../app/store/selectors/workflowSelector'
|
||||
import useWorkflowStore from '../../../../../app/store/workflowStore'
|
||||
import type { Account, IFlow } from '../../../../../generated/ManagingApi'
|
||||
import { MoneyManagementClient } from '../../../../../generated/ManagingApi'
|
||||
import useAccounts from '../../../../../hooks/useAccounts'
|
||||
import { Loader } from '../../../../atoms'
|
||||
import { FormInput } from '../../../../mollecules'
|
||||
import Flow from '../Flow'
|
||||
|
||||
const OpenPositionFlow = ({ data, isConnectable, id }: NodeProps<IFlow>) => {
|
||||
const { updateNodeData } = useWorkflowStore(WorkflowSelector)
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
|
||||
const [selectedAccount, setSelectedAccount] = React.useState<string>()
|
||||
const [selectedMoneyManagement, setSelectedMoneyManagement] =
|
||||
useState<string>()
|
||||
const moneyManagementClient = new MoneyManagementClient({}, apiUrl)
|
||||
|
||||
const { data: accounts } = useAccounts({
|
||||
callback: (data: Account[]) => {
|
||||
setSelectedAccount(data[0].name)
|
||||
},
|
||||
})
|
||||
|
||||
const { data: moneyManagements } = useQuery({
|
||||
onSuccess: (data) => {
|
||||
if (data) {
|
||||
setSelectedMoneyManagement(data[0].name)
|
||||
}
|
||||
},
|
||||
queryFn: () => moneyManagementClient.moneyManagement_GetMoneyManagements(),
|
||||
queryKey: ['moneyManagement'],
|
||||
})
|
||||
|
||||
const onAccountChange = useCallback((evt: any) => {
|
||||
updateNodeData(id, 'Account', evt.target.value)
|
||||
}, [])
|
||||
|
||||
const onMoneyManagementChange = useCallback((evt: any) => {
|
||||
updateNodeData(id, 'MoneyManagement', evt.target.value)
|
||||
}, [])
|
||||
|
||||
if (!accounts || !moneyManagements) {
|
||||
return <Loader />
|
||||
}
|
||||
|
||||
return (
|
||||
<Flow
|
||||
name={data.name || ''}
|
||||
description={data.description}
|
||||
inputs={data.acceptedInputs}
|
||||
outputs={data.outputTypes}
|
||||
isConnectable={isConnectable}
|
||||
>
|
||||
<FormInput label="Account" htmlFor="accountName">
|
||||
<select
|
||||
className="select select-bordered w-full h-auto max-w-xs"
|
||||
onChange={(evt) => onAccountChange(evt.target.value)}
|
||||
value={
|
||||
data.parameters.find((p) => p.name === 'Account')?.value ||
|
||||
selectedAccount
|
||||
}
|
||||
>
|
||||
{accounts.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
<FormInput label="Money Management" htmlFor="moneyManagement">
|
||||
<select
|
||||
className="select w-full max-w-xs"
|
||||
onChange={(evt) => onMoneyManagementChange(evt.target.value)}
|
||||
value={
|
||||
data.parameters.find((p) => p.name === 'MoneyManagement')?.value ||
|
||||
selectedMoneyManagement
|
||||
}
|
||||
>
|
||||
{moneyManagements.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</Flow>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenPositionFlow
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
|
||||
const fetchData = () => {
|
||||
return {
|
||||
fake: 'data',
|
||||
}
|
||||
}
|
||||
|
||||
const ParentComponent = () => {
|
||||
const { data, isLoading } = useQuery({
|
||||
queryFn: fetchData,
|
||||
queryKey: ['data'],
|
||||
})
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ChildComponent data={data} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import type { NodeTypes, Node } from 'reactflow'
|
||||
import ReactFlow, { Controls, Background, ReactFlowProvider } from 'reactflow'
|
||||
|
||||
import 'reactflow/dist/style.css'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import { WorkflowSelector } from '../../../app/store/selectors/workflowSelector'
|
||||
import useWorkflowStore from '../../../app/store/workflowStore'
|
||||
import type {
|
||||
FlowType,
|
||||
IFlow,
|
||||
SyntheticFlow,
|
||||
SyntheticFlowParameter,
|
||||
SyntheticWorkflow,
|
||||
} from '../../../generated/ManagingApi'
|
||||
import { WorkflowUsage, WorkflowClient } from '../../../generated/ManagingApi'
|
||||
import type { IWorkflow, IFlowItem } from '../../../global/type'
|
||||
import { Toast } from '../../mollecules'
|
||||
|
||||
import FeedTicker from './flows/feed/feedTicker'
|
||||
import RsiDivergenceFlow from './flows/strategies/RsiDivergenceFlow'
|
||||
import OpenPositionFlow from './flows/trading/OpenPositionFlow'
|
||||
import WorkflowForm from './workflowForm'
|
||||
import WorkflowSidebar from './workflowSidebar'
|
||||
|
||||
let id = 0
|
||||
const getId = () => `dndnode_${id++}`
|
||||
|
||||
const mapToFlowRequest = (
|
||||
flow: IFlow,
|
||||
id: string,
|
||||
parentId: string
|
||||
): SyntheticFlow => {
|
||||
return {
|
||||
id: id,
|
||||
parameters: flow.parameters as SyntheticFlowParameter[],
|
||||
parentId: parentId,
|
||||
type: flow.type,
|
||||
}
|
||||
}
|
||||
|
||||
const WorkflowCanvas: React.FC = (props: any) => {
|
||||
const tabs = props
|
||||
const properties = tabs['data-props'] as IWorkflow
|
||||
const reactFlowWrapper = useRef(null)
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState(null)
|
||||
const [isUpdated, setIsUpdated] = useState(false)
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const [usage, setUsage] = useState<WorkflowUsage>(WorkflowUsage.Trading)
|
||||
const [name, setName] = useState<string>(properties.name)
|
||||
const client = new WorkflowClient({}, apiUrl)
|
||||
|
||||
const nodeTypes: NodeTypes = useMemo(
|
||||
() => ({
|
||||
FeedTicker: FeedTicker,
|
||||
OpenPosition: OpenPositionFlow,
|
||||
RsiDivergence: RsiDivergenceFlow,
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
onNodesChange,
|
||||
onEdgesChange,
|
||||
onConnect,
|
||||
initWorkFlow,
|
||||
setNodes,
|
||||
resetWorkflow,
|
||||
} = useWorkflowStore(WorkflowSelector)
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
onSuccess: (data) => {
|
||||
initWorkFlow([], properties.edges)
|
||||
if (data) {
|
||||
setNodes(properties.nodes.map((n, i) => mapToNodeItem(n, data, i)))
|
||||
}
|
||||
},
|
||||
queryFn: () => client.workflow_GetAvailableFlows(),
|
||||
queryKey: ['availableFlows'],
|
||||
})
|
||||
|
||||
const mapToNodeItem = (
|
||||
flow: Node<IFlow>,
|
||||
availableFlows: IFlow[],
|
||||
index: number
|
||||
): IFlowItem => {
|
||||
const nodeData = availableFlows.find((f) => f.type === flow.type)
|
||||
|
||||
if (nodeData == null) {
|
||||
return {} as IFlowItem
|
||||
}
|
||||
|
||||
nodeData.parameters = flow.data.parameters
|
||||
|
||||
return {
|
||||
data: nodeData,
|
||||
id: flow.id,
|
||||
isConnectable: nodeData.acceptedInputs?.length > 0,
|
||||
position: { x: index * 400, y: index * 100 },
|
||||
type: flow.type as FlowType,
|
||||
}
|
||||
}
|
||||
|
||||
const isValidConnection = (connection: any) => {
|
||||
if (reactFlowInstance == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const sourceData: IFlow = reactFlowInstance.getNode(connection.source).data
|
||||
const targetData: IFlow = reactFlowInstance.getNode(connection.target).data
|
||||
|
||||
return sourceData.outputTypes?.some(
|
||||
(output) => targetData.acceptedInputs?.indexOf(output) >= 0
|
||||
)
|
||||
}
|
||||
|
||||
const handleOnConnect = useCallback((params: any) => {
|
||||
setIsUpdated(true)
|
||||
onConnect(params)
|
||||
}, [])
|
||||
|
||||
const onDragOver = useCallback((event: any) => {
|
||||
event.preventDefault()
|
||||
event.dataTransfer.dropEffect = 'move'
|
||||
}, [])
|
||||
|
||||
const onDrop = useCallback(
|
||||
(event: any) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (reactFlowInstance == null || reactFlowWrapper.current == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect()
|
||||
const data = event.dataTransfer.getData('application/reactflow')
|
||||
if (typeof data === 'undefined' || !data) {
|
||||
return
|
||||
}
|
||||
|
||||
const properties: string[] = data.split('-')
|
||||
const position = reactFlowInstance.project({
|
||||
x: event.clientX - reactFlowBounds.left,
|
||||
y: event.clientY - reactFlowBounds.top,
|
||||
})
|
||||
|
||||
const flow = flows.find((flow) => flow.type === properties[0])
|
||||
const newNode: IFlowItem = {
|
||||
data: flow || ({ parameters: [] as SyntheticFlowParameter[] } as IFlow),
|
||||
id: getId(),
|
||||
isConnectable:
|
||||
(flow?.acceptedInputs && flow?.acceptedInputs?.length > 0) ?? false,
|
||||
position,
|
||||
type: properties[0] as FlowType,
|
||||
}
|
||||
|
||||
newNode.data.parameters = []
|
||||
setNodes(nodes.concat(newNode))
|
||||
},
|
||||
[reactFlowInstance, data, nodes]
|
||||
)
|
||||
|
||||
const handleOnSave = () => {
|
||||
const t = new Toast('Saving workflow')
|
||||
const nodesRequest: SyntheticFlow[] = []
|
||||
|
||||
const client = new WorkflowClient({}, apiUrl)
|
||||
|
||||
if (edges.length <= 0) {
|
||||
t.update('error', 'Workflow must have at least one edge')
|
||||
return
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
const data: IFlow = reactFlowInstance.getNode(node.id).data
|
||||
|
||||
const parentId = edges.find((edge) => edge.target === node.id)?.source
|
||||
nodesRequest.push(mapToFlowRequest(data, node.id, parentId || ''))
|
||||
}
|
||||
|
||||
const request: SyntheticWorkflow = {
|
||||
description: 'Test',
|
||||
flows: nodesRequest,
|
||||
name: properties.name,
|
||||
usage: usage,
|
||||
}
|
||||
|
||||
client
|
||||
.workflow_PostWorkflow(request)
|
||||
.then((data) => {
|
||||
t.update('success', 'Workflow saved')
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', 'Error :' + err)
|
||||
})
|
||||
}
|
||||
|
||||
const handleOnReset = () => {
|
||||
resetWorkflow()
|
||||
}
|
||||
|
||||
const handleUsageChange = (event: any) => {
|
||||
setUsage(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkflowForm
|
||||
name={name}
|
||||
usage={usage}
|
||||
handleUsageChange={handleUsageChange}
|
||||
handleOnSave={handleOnSave}
|
||||
handleOnReset={handleOnReset}
|
||||
isUpdated={isUpdated}
|
||||
setName={setName}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-10 gap-4">
|
||||
<ReactFlowProvider>
|
||||
<div>
|
||||
<WorkflowSidebar flows={data} isLoading={isLoading} />
|
||||
</div>
|
||||
<div
|
||||
className="reactflow-wrapper col-span-8"
|
||||
ref={reactFlowWrapper}
|
||||
style={{ height: 800 }}
|
||||
>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={handleOnConnect}
|
||||
onDrop={onDrop}
|
||||
onInit={setReactFlowInstance}
|
||||
onDragOver={onDragOver}
|
||||
nodeTypes={nodeTypes}
|
||||
isValidConnection={isValidConnection}
|
||||
fitView
|
||||
>
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowCanvas
|
||||
@@ -1,69 +0,0 @@
|
||||
import { WorkflowUsage } from '../../../generated/ManagingApi'
|
||||
import { FormInput } from '../../mollecules'
|
||||
|
||||
type IWorkflowForm = {
|
||||
name: string
|
||||
usage: WorkflowUsage
|
||||
handleUsageChange: (evt: any) => void
|
||||
handleOnSave: () => void
|
||||
handleOnReset: () => void
|
||||
isUpdated: boolean
|
||||
setName: (name: string) => void
|
||||
}
|
||||
|
||||
const WorkflowForm = ({
|
||||
name,
|
||||
handleUsageChange,
|
||||
handleOnSave,
|
||||
isUpdated,
|
||||
handleOnReset,
|
||||
setName,
|
||||
}: IWorkflowForm) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex">
|
||||
<div className="flex w-full">
|
||||
<FormInput label="Name" htmlFor="name" inline={true}>
|
||||
<input
|
||||
id="name"
|
||||
name="text"
|
||||
onChange={(evt) => setName(evt.target.value)}
|
||||
className="nodrag input"
|
||||
value={name}
|
||||
/>
|
||||
</FormInput>
|
||||
<FormInput label="Usage" htmlFor="usage" inline={true}>
|
||||
<select
|
||||
className="select no-drag w-full max-w-xs"
|
||||
onChange={handleUsageChange}
|
||||
>
|
||||
{Object.keys(WorkflowUsage).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</div>
|
||||
<div className="flex justify-end w-full">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
disabled={!isUpdated}
|
||||
onClick={handleOnSave}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
disabled={!isUpdated}
|
||||
onClick={handleOnReset}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowForm
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { IFlow } from '../../../generated/ManagingApi'
|
||||
import { Loader } from '../../atoms'
|
||||
|
||||
import FlowItem from './flowItem'
|
||||
|
||||
type IWorkflowSidebar = {
|
||||
flows?: IFlow[]
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
const WorkflowSidebar = ({ flows, isLoading = true }: IWorkflowSidebar) => {
|
||||
const onDragStart = (event: any, data: string) => {
|
||||
event.dataTransfer.setData('application/reactflow', data)
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader />
|
||||
}
|
||||
|
||||
return (
|
||||
<aside>
|
||||
<div className="bg-base-200 p-4">
|
||||
<div className="mb-2 text-lg">Flows</div>
|
||||
{flows
|
||||
? flows.map((flow) => (
|
||||
<FlowItem flow={flow} onDragStart={onDragStart} />
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowSidebar
|
||||
@@ -3527,169 +3527,6 @@ export class UserClient extends AuthorizedApiBase {
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkflowClient extends AuthorizedApiBase {
|
||||
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
|
||||
private baseUrl: string;
|
||||
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
|
||||
|
||||
constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
|
||||
super(configuration);
|
||||
this.http = http ? http : window as any;
|
||||
this.baseUrl = baseUrl ?? "http://localhost:5000";
|
||||
}
|
||||
|
||||
workflow_PostWorkflow(workflowRequest: SyntheticWorkflow): Promise<Workflow> {
|
||||
let url_ = this.baseUrl + "/Workflow";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(workflowRequest);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processWorkflow_PostWorkflow(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processWorkflow_PostWorkflow(response: Response): Promise<Workflow> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Workflow;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Workflow>(null as any);
|
||||
}
|
||||
|
||||
workflow_GetWorkflows(): Promise<SyntheticWorkflow[]> {
|
||||
let url_ = this.baseUrl + "/Workflow";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processWorkflow_GetWorkflows(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processWorkflow_GetWorkflows(response: Response): Promise<SyntheticWorkflow[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as SyntheticWorkflow[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<SyntheticWorkflow[]>(null as any);
|
||||
}
|
||||
|
||||
workflow_DeleteWorkflow(name: string | null | undefined): Promise<FileResponse> {
|
||||
let url_ = this.baseUrl + "/Workflow?";
|
||||
if (name !== undefined && name !== null)
|
||||
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Accept": "application/octet-stream"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processWorkflow_DeleteWorkflow(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processWorkflow_DeleteWorkflow(response: Response): Promise<FileResponse> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200 || status === 206) {
|
||||
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
|
||||
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
|
||||
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
|
||||
if (fileName) {
|
||||
fileName = decodeURIComponent(fileName);
|
||||
} else {
|
||||
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
|
||||
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
|
||||
}
|
||||
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<FileResponse>(null as any);
|
||||
}
|
||||
|
||||
workflow_GetAvailableFlows(): Promise<IFlow[]> {
|
||||
let url_ = this.baseUrl + "/Workflow/flows";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processWorkflow_GetAvailableFlows(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processWorkflow_GetAvailableFlows(response: Response): Promise<IFlow[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as IFlow[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<IFlow[]>(null as any);
|
||||
}
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
name: string;
|
||||
exchange: TradingExchanges;
|
||||
@@ -4796,68 +4633,6 @@ export interface LoginRequest {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Workflow {
|
||||
name: string;
|
||||
usage: WorkflowUsage;
|
||||
flows: IFlow[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export enum WorkflowUsage {
|
||||
Trading = "Trading",
|
||||
Task = "Task",
|
||||
}
|
||||
|
||||
export interface IFlow {
|
||||
id: string;
|
||||
name: string;
|
||||
type: FlowType;
|
||||
description: string;
|
||||
acceptedInputs: FlowOutput[];
|
||||
children?: IFlow[] | null;
|
||||
parameters: FlowParameter[];
|
||||
parentId?: string;
|
||||
output?: string | null;
|
||||
outputTypes: FlowOutput[];
|
||||
}
|
||||
|
||||
export enum FlowType {
|
||||
RsiDivergence = "RsiDivergence",
|
||||
FeedTicker = "FeedTicker",
|
||||
OpenPosition = "OpenPosition",
|
||||
}
|
||||
|
||||
export enum FlowOutput {
|
||||
Signal = "Signal",
|
||||
Candles = "Candles",
|
||||
Position = "Position",
|
||||
MoneyManagement = "MoneyManagement",
|
||||
}
|
||||
|
||||
export interface FlowParameter {
|
||||
value?: any | null;
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export interface SyntheticWorkflow {
|
||||
name: string;
|
||||
usage: WorkflowUsage;
|
||||
description: string;
|
||||
flows: SyntheticFlow[];
|
||||
}
|
||||
|
||||
export interface SyntheticFlow {
|
||||
id: string;
|
||||
parentId?: string | null;
|
||||
type: FlowType;
|
||||
parameters: SyntheticFlowParameter[];
|
||||
}
|
||||
|
||||
export interface SyntheticFlowParameter {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FileResponse {
|
||||
data: Blob;
|
||||
status: number;
|
||||
|
||||
@@ -1116,68 +1116,6 @@ export interface LoginRequest {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Workflow {
|
||||
name: string;
|
||||
usage: WorkflowUsage;
|
||||
flows: IFlow[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export enum WorkflowUsage {
|
||||
Trading = "Trading",
|
||||
Task = "Task",
|
||||
}
|
||||
|
||||
export interface IFlow {
|
||||
id: string;
|
||||
name: string;
|
||||
type: FlowType;
|
||||
description: string;
|
||||
acceptedInputs: FlowOutput[];
|
||||
children?: IFlow[] | null;
|
||||
parameters: FlowParameter[];
|
||||
parentId?: string;
|
||||
output?: string | null;
|
||||
outputTypes: FlowOutput[];
|
||||
}
|
||||
|
||||
export enum FlowType {
|
||||
RsiDivergence = "RsiDivergence",
|
||||
FeedTicker = "FeedTicker",
|
||||
OpenPosition = "OpenPosition",
|
||||
}
|
||||
|
||||
export enum FlowOutput {
|
||||
Signal = "Signal",
|
||||
Candles = "Candles",
|
||||
Position = "Position",
|
||||
MoneyManagement = "MoneyManagement",
|
||||
}
|
||||
|
||||
export interface FlowParameter {
|
||||
value?: any | null;
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export interface SyntheticWorkflow {
|
||||
name: string;
|
||||
usage: WorkflowUsage;
|
||||
description: string;
|
||||
flows: SyntheticFlow[];
|
||||
}
|
||||
|
||||
export interface SyntheticFlow {
|
||||
id: string;
|
||||
parentId?: string | null;
|
||||
type: FlowType;
|
||||
parameters: SyntheticFlowParameter[];
|
||||
}
|
||||
|
||||
export interface SyntheticFlowParameter {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FileResponse {
|
||||
data: Blob;
|
||||
status: number;
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import type {TableInstance, UsePaginationInstanceProps, UsePaginationState, UseSortByInstanceProps,} from 'react-table'
|
||||
import type {Edge, Node} from 'reactflow'
|
||||
|
||||
import type {
|
||||
Account,
|
||||
AccountType,
|
||||
Backtest,
|
||||
Balance,
|
||||
BotType,
|
||||
FlowOutput,
|
||||
FlowType,
|
||||
IFlow,
|
||||
IndicatorViewModel,
|
||||
LightSignal,
|
||||
MoneyManagement,
|
||||
Position,
|
||||
RiskLevel,
|
||||
ScenarioViewModel,
|
||||
Signal,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
TradeDirection,
|
||||
@@ -36,48 +31,6 @@ export type TableInstanceWithHooks<T extends object> = TableInstance<T> &
|
||||
state: UsePaginationState<T>
|
||||
}
|
||||
|
||||
export type IWidgetProperties = {
|
||||
id: string
|
||||
title: string
|
||||
layout: {
|
||||
h: number
|
||||
i: string
|
||||
w: number
|
||||
x: number
|
||||
y: number
|
||||
minW: number
|
||||
minH: number
|
||||
}
|
||||
}
|
||||
|
||||
export type IFlowData = {
|
||||
name: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export type IFlowItem = {
|
||||
data: IFlow
|
||||
isConnectable: boolean
|
||||
id: string
|
||||
position: any
|
||||
type: FlowType
|
||||
}
|
||||
|
||||
export type IFlowProps = {
|
||||
name: string
|
||||
description?: string
|
||||
children: React.ReactNode
|
||||
inputs: FlowOutput[]
|
||||
outputs: FlowOutput[]
|
||||
isConnectable: boolean
|
||||
}
|
||||
|
||||
export type IWorkflow = {
|
||||
name: string
|
||||
nodes: Node<IFlow>[]
|
||||
edges: Edge[]
|
||||
}
|
||||
|
||||
export type ILoginFormInput = {
|
||||
name: string
|
||||
}
|
||||
@@ -106,7 +59,6 @@ export type ISpotlightBadge = {
|
||||
export type IBacktestsFormInput = {
|
||||
accountName: string
|
||||
tickers: string[]
|
||||
botType: BotType
|
||||
timeframe: Timeframe
|
||||
scenarioName: string
|
||||
save: boolean
|
||||
@@ -161,7 +113,6 @@ export type IMoneyManagementModalProps = {
|
||||
|
||||
export type IBacktestFormInput = {
|
||||
accountName: string
|
||||
botType: BotType
|
||||
ticker: Ticker
|
||||
timeframe: Timeframe
|
||||
save: boolean
|
||||
@@ -262,7 +213,7 @@ export type ICardPosition = {
|
||||
}
|
||||
|
||||
export type ICardSignal = {
|
||||
signals: Signal[]
|
||||
signals: LightSignal[]
|
||||
}
|
||||
|
||||
export type INavItemProps = {
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { Edge, Node } from 'reactflow'
|
||||
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import { Loader } from '../../components/atoms'
|
||||
import { Tabs } from '../../components/mollecules'
|
||||
import { WorkflowCanvas } from '../../components/organism'
|
||||
import type {
|
||||
IFlow,
|
||||
SyntheticFlow,
|
||||
SyntheticWorkflow,
|
||||
} from '../../generated/ManagingApi'
|
||||
import { WorkflowClient } from '../../generated/ManagingApi'
|
||||
import type { ITabsType, IWorkflow } from '../../global/type'
|
||||
|
||||
const mapWorkflowToTabs = (workflows: SyntheticWorkflow[]): ITabsType => {
|
||||
return workflows.map((workflow: SyntheticWorkflow, index: number) => {
|
||||
return {
|
||||
Component: WorkflowCanvas,
|
||||
index: index,
|
||||
label: workflow.name,
|
||||
props: mapFlowsToNodes(workflow.flows, workflow.name),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const mapFlowsToNodes = (flows: SyntheticFlow[], name: string): IWorkflow => {
|
||||
const nodes: Node<IFlow>[] = []
|
||||
const edges: Edge[] = []
|
||||
|
||||
flows.forEach((flow: SyntheticFlow) => {
|
||||
nodes.push(mapFlowToNode(flow))
|
||||
})
|
||||
|
||||
for (const node of nodes) {
|
||||
const childrenNodes = nodes.filter((n) => n.data.parentId == node.data.id)
|
||||
|
||||
if (childrenNodes.length > 0) {
|
||||
childrenNodes.forEach((childNode) => {
|
||||
edges.push({
|
||||
id: `${node.id}-${childNode.id}`,
|
||||
source: node.id,
|
||||
target: childNode.id,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { edges, name: name, nodes } as IWorkflow
|
||||
}
|
||||
|
||||
const mapFlowToNode = (flow: SyntheticFlow): Node => {
|
||||
return {
|
||||
data: flow,
|
||||
id: flow.id,
|
||||
position: { x: 0, y: 0 },
|
||||
type: flow.type,
|
||||
}
|
||||
}
|
||||
|
||||
const Workflows: React.FC = () => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(1)
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const client = new WorkflowClient({}, apiUrl)
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
onSuccess: () => {
|
||||
setSelectedTab(0)
|
||||
},
|
||||
queryFn: () => client.workflow_GetWorkflows(),
|
||||
queryKey: ['workflows'],
|
||||
})
|
||||
|
||||
useEffect(() => {}, [isLoading])
|
||||
|
||||
if (isLoading || data == null) {
|
||||
return <Loader></Loader>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="container mx-auto">
|
||||
<Tabs
|
||||
selectedTab={selectedTab}
|
||||
onClick={setSelectedTab}
|
||||
tabs={mapWorkflowToTabs(data)}
|
||||
addButton={true}
|
||||
onAddButton={() => {
|
||||
console.log('add button clicked')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Workflows
|
||||
Reference in New Issue
Block a user