Remove workflow
This commit is contained in:
@@ -107,22 +107,6 @@
|
|||||||
- [x] Add button to display money management use by the bot
|
- [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
|
- [ ] 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
|
## 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 {lazy, Suspense} from 'react'
|
||||||
import { Route, Routes } from 'react-router-dom'
|
import {Route, Routes} from 'react-router-dom'
|
||||||
|
|
||||||
import LayoutMain from '../../layouts'
|
import LayoutMain from '../../layouts'
|
||||||
import DeskWidget from '../../pages/desk/deskWidget'
|
import DeskWidget from '../../pages/desk/deskWidget'
|
||||||
import Scenario from '../../pages/scenarioPage/scenario'
|
import Scenario from '../../pages/scenarioPage/scenario'
|
||||||
import Tools from '../../pages/toolsPage/tools'
|
import Tools from '../../pages/toolsPage/tools'
|
||||||
import Workflows from '../../pages/workflow/workflows'
|
|
||||||
|
|
||||||
const Backtest = lazy(() => import('../../pages/backtestPage/backtest'))
|
const Backtest = lazy(() => import('../../pages/backtestPage/backtest'))
|
||||||
const Bots = lazy(() => import('../../pages/botsPage/bots'))
|
const Bots = lazy(() => import('../../pages/botsPage/bots'))
|
||||||
@@ -48,17 +47,6 @@ const MyRoutes = () => {
|
|||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/workflow" element={<LayoutMain />}>
|
|
||||||
<Route
|
|
||||||
index
|
|
||||||
element={
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<Workflows />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/settings" element={<LayoutMain />}>
|
<Route path="/settings" element={<LayoutMain />}>
|
||||||
<Route
|
<Route
|
||||||
index
|
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 {
|
export interface Account {
|
||||||
name: string;
|
name: string;
|
||||||
exchange: TradingExchanges;
|
exchange: TradingExchanges;
|
||||||
@@ -4796,68 +4633,6 @@ export interface LoginRequest {
|
|||||||
message: string;
|
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 {
|
export interface FileResponse {
|
||||||
data: Blob;
|
data: Blob;
|
||||||
status: number;
|
status: number;
|
||||||
|
|||||||
@@ -1116,68 +1116,6 @@ export interface LoginRequest {
|
|||||||
message: string;
|
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 {
|
export interface FileResponse {
|
||||||
data: Blob;
|
data: Blob;
|
||||||
status: number;
|
status: number;
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import type {TableInstance, UsePaginationInstanceProps, UsePaginationState, UseSortByInstanceProps,} from 'react-table'
|
import type {TableInstance, UsePaginationInstanceProps, UsePaginationState, UseSortByInstanceProps,} from 'react-table'
|
||||||
import type {Edge, Node} from 'reactflow'
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Account,
|
Account,
|
||||||
AccountType,
|
AccountType,
|
||||||
Backtest,
|
Backtest,
|
||||||
Balance,
|
Balance,
|
||||||
BotType,
|
|
||||||
FlowOutput,
|
|
||||||
FlowType,
|
|
||||||
IFlow,
|
|
||||||
IndicatorViewModel,
|
IndicatorViewModel,
|
||||||
|
LightSignal,
|
||||||
MoneyManagement,
|
MoneyManagement,
|
||||||
Position,
|
Position,
|
||||||
RiskLevel,
|
RiskLevel,
|
||||||
ScenarioViewModel,
|
ScenarioViewModel,
|
||||||
Signal,
|
|
||||||
Ticker,
|
Ticker,
|
||||||
Timeframe,
|
Timeframe,
|
||||||
TradeDirection,
|
TradeDirection,
|
||||||
@@ -36,48 +31,6 @@ export type TableInstanceWithHooks<T extends object> = TableInstance<T> &
|
|||||||
state: UsePaginationState<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 = {
|
export type ILoginFormInput = {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
@@ -106,7 +59,6 @@ export type ISpotlightBadge = {
|
|||||||
export type IBacktestsFormInput = {
|
export type IBacktestsFormInput = {
|
||||||
accountName: string
|
accountName: string
|
||||||
tickers: string[]
|
tickers: string[]
|
||||||
botType: BotType
|
|
||||||
timeframe: Timeframe
|
timeframe: Timeframe
|
||||||
scenarioName: string
|
scenarioName: string
|
||||||
save: boolean
|
save: boolean
|
||||||
@@ -161,7 +113,6 @@ export type IMoneyManagementModalProps = {
|
|||||||
|
|
||||||
export type IBacktestFormInput = {
|
export type IBacktestFormInput = {
|
||||||
accountName: string
|
accountName: string
|
||||||
botType: BotType
|
|
||||||
ticker: Ticker
|
ticker: Ticker
|
||||||
timeframe: Timeframe
|
timeframe: Timeframe
|
||||||
save: boolean
|
save: boolean
|
||||||
@@ -262,7 +213,7 @@ export type ICardPosition = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ICardSignal = {
|
export type ICardSignal = {
|
||||||
signals: Signal[]
|
signals: LightSignal[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type INavItemProps = {
|
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