Frontend rebuild with all new components
This commit is contained in:
@@ -88,6 +88,7 @@
|
||||
"react-stripe-elements": "^6.1.2",
|
||||
"react-toastify": "^9.1.3",
|
||||
"reaviz": "^14.9.7",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-html": "^16.0.1",
|
||||
"remark-images": "^4.0.0",
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import React from 'react';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const LoadStats = (globalUrl, cachekey) => {
|
||||
if (globalUrl === undefined) {
|
||||
console.log("Error: Global URL is undefined")
|
||||
return
|
||||
}
|
||||
|
||||
if (cachekey === undefined) {
|
||||
console.log("Error: Cachekey is undefined")
|
||||
return
|
||||
}
|
||||
|
||||
var basedata = {
|
||||
"key": cachekey,
|
||||
"total": 0,
|
||||
"available_keys": [],
|
||||
"labels": [],
|
||||
"datasets": [
|
||||
{
|
||||
"label": "",
|
||||
"data": [],
|
||||
"backgroundColor": [],
|
||||
"barThickness": 15,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
//const url = `${globalUrl}/api/v1/stats/app_executions_test2`
|
||||
//cachekey = cachekey.replace(" ", "_", -1)
|
||||
const url = `${globalUrl}/api/v1/stats/${cachekey}`
|
||||
return fetch(url, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((resp) => {
|
||||
return resp.json()
|
||||
}).then((respJson) => {
|
||||
const selectedIndex = 0
|
||||
|
||||
if (respJson.success === true) {
|
||||
for (let entryKey in respJson.entries) {
|
||||
const entry = respJson.entries[entryKey]
|
||||
basedata.labels.push(entry.date)
|
||||
|
||||
basedata.datasets[0].data.push(entry.value)
|
||||
basedata.datasets[0].backgroundColor.push(entry.value > 0 ? "rgba(255,255,255,0.4)" : "red")
|
||||
}
|
||||
|
||||
basedata.available_keys = respJson.available_keys
|
||||
basedata.total = respJson.total
|
||||
|
||||
return basedata
|
||||
} else {
|
||||
console.log("Failed to get stats")
|
||||
return basedata
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast("Failed to get stats")
|
||||
return basedata
|
||||
})
|
||||
}
|
||||
|
||||
const DashboardBarchart = (props) => {
|
||||
const { timelineData, title, height, } = props;
|
||||
var inputHeight = 15
|
||||
if (height !== undefined && height !== null) {
|
||||
inputHeight = height
|
||||
}
|
||||
|
||||
const barOptions = {
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: true, // Ensure tooltips are enabled
|
||||
},
|
||||
},
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
top: 0, // Adjust the top padding as needed
|
||||
bottom: -10, // Adjust the bottom padding as needed
|
||||
left: 0, // Adjust the left padding as needed
|
||||
right: 0, // Adjust the right padding as needed
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
},
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
display: false
|
||||
},
|
||||
beginAtZero: false,
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
display: false
|
||||
},
|
||||
beginAtZero: false,
|
||||
}]
|
||||
},
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: function (tooltipItem, data) {
|
||||
const label = data.labels[tooltipItem.index]
|
||||
return label.split('\n')[0]
|
||||
},
|
||||
afterLabel: function (tooltipItem, data) {
|
||||
const amount = tooltipItem.value === undefined || tooltipItem.value === null ? 0 : tooltipItem.value
|
||||
return `Amount: ${amount}`
|
||||
},
|
||||
title: function () {
|
||||
return title === undefined ? '' : title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Bar
|
||||
data={timelineData}
|
||||
options={barOptions}
|
||||
height={inputHeight}
|
||||
getElementAtEvent={(elements) => {
|
||||
if (elements && elements.length > 0) {
|
||||
//toast("Click event")
|
||||
console.log("Clicked: ", elements)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardBarchart;
|
||||
@@ -0,0 +1,687 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import { toast } from "react-toastify"
|
||||
import theme from '../theme.jsx';
|
||||
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
|
||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
Typography,
|
||||
Button,
|
||||
Divider,
|
||||
|
||||
MenuItem,
|
||||
Select,
|
||||
Chip,
|
||||
TextField,
|
||||
CircularProgress,
|
||||
} from "@mui/material"
|
||||
|
||||
import {
|
||||
CheckCircleOutline as CheckCircleOutlineIcon,
|
||||
ErrorOutline as ErrorOutlineIcon,
|
||||
} from "@mui/icons-material"
|
||||
|
||||
import {
|
||||
green,
|
||||
red,
|
||||
} from "../views/AngularWorkflow.jsx"
|
||||
|
||||
const FixWorkflowValidationErrors = (props) => {
|
||||
const { globalUrl, workflow, setWorkflow, setUpdateParent, } = props;
|
||||
|
||||
const [appsLoading, setAppsLoading] = useState(false)
|
||||
const [apps, setApps] = useState([])
|
||||
const [appAuth, setAppAuth] = useState([])
|
||||
const [_, setUpdate] = useState(0)
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io";
|
||||
|
||||
if (workflow === undefined || workflow === null) {
|
||||
console.error("Workflow is undefined")
|
||||
return null
|
||||
}
|
||||
|
||||
if (workflow.validation === undefined || workflow.validation === null) {
|
||||
console.error("Workflow validation is undefined")
|
||||
return null
|
||||
}
|
||||
|
||||
if (workflow.validation.valid === true) {
|
||||
console.error("Workflow is valid - nothing to do for errors")
|
||||
return null
|
||||
}
|
||||
|
||||
if (setWorkflow === undefined || setWorkflow === null) {
|
||||
console.error("No setWorkflow")
|
||||
return null
|
||||
}
|
||||
|
||||
const fetchApps = () => {
|
||||
if (appsLoading === true) {
|
||||
return
|
||||
}
|
||||
|
||||
setAppsLoading(true)
|
||||
|
||||
const url = `${globalUrl}/api/v1/apps`
|
||||
fetch(url,{
|
||||
method: "GET",
|
||||
credentials: "include"
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setAppsLoading(false)
|
||||
if (data.success === false) {
|
||||
return
|
||||
}
|
||||
|
||||
setApps(data)
|
||||
})
|
||||
.catch(error => {
|
||||
setAppsLoading(false)
|
||||
console.error("Error: ", error)
|
||||
})
|
||||
}
|
||||
|
||||
// Save the workflow as well
|
||||
const saveWorkflow = (workflow) => {
|
||||
if (workflow.id === undefined || workflow.id === null) {
|
||||
toast("Workflow ID is missing during save. Please try again")
|
||||
return
|
||||
}
|
||||
|
||||
const url = `${globalUrl}/api/v1/workflows/${workflow.id}`
|
||||
fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(workflow),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success === false) {
|
||||
toast("Failed to save workflow")
|
||||
return
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
toast("Failed to save workflow: " + error.toString())
|
||||
})
|
||||
}
|
||||
|
||||
const fetchAuthentication = (reset, updateAction, closeMenu, action_id) => {
|
||||
if (appsLoading === true) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = `${globalUrl}/api/v1/apps/authentication`
|
||||
fetch(url,{
|
||||
method: "GET",
|
||||
credentials: "include"
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success === false) {
|
||||
return
|
||||
}
|
||||
|
||||
const authlist = data.data
|
||||
setAppAuth(authlist)
|
||||
if (updateAction === true) {
|
||||
console.log("Updating action: ", action_id)
|
||||
|
||||
// Find the action in the workflow and set auth for it
|
||||
var foundActionIndex = -1
|
||||
for (var i = 0; i < workflow.actions.length; i++) {
|
||||
if (workflow.actions[i].id === action_id) {
|
||||
foundActionIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (foundActionIndex === -1) {
|
||||
console.error("Failed to find action in workflow")
|
||||
return
|
||||
}
|
||||
|
||||
const appId = workflow.actions[foundActionIndex].app_id
|
||||
var lastauth = -1
|
||||
for (var authKey in authlist) {
|
||||
if (authlist[authKey].app_id !== appId) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (authlist[authKey].created > lastauth) {
|
||||
lastauth = authlist[authKey].created
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
console.log("FOUND AUTH: ", authlist[authKey])
|
||||
workflow.actions[foundActionIndex].authentication_id = authlist[authKey].id
|
||||
}
|
||||
|
||||
|
||||
if (setWorkflow !== undefined) {
|
||||
setWorkflow(workflow)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Auth loading error: ", error)
|
||||
})
|
||||
}
|
||||
|
||||
if (apps !== undefined && apps !== null && apps.length === 0 && appsLoading === false) {
|
||||
fetchApps()
|
||||
fetchAuthentication()
|
||||
}
|
||||
|
||||
const setSelectedAction = (action) => {
|
||||
if (workflow === undefined || workflow === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (workflow.actions === undefined || workflow.actions === null || workflow.actions.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (setWorkflow === undefined || setWorkflow === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (var i = 0; i < workflow.actions.length; i++) {
|
||||
if (workflow.actions[i].id === action.id) {
|
||||
workflow.actions[i] = action
|
||||
|
||||
// Update any action with the same app_id to have same auth
|
||||
for (var j = 0; j < workflow.actions.length; j++) {
|
||||
if (workflow.actions[j].app_id === action.app_id) {
|
||||
workflow.actions[j].authentication_id = action.authentication_id
|
||||
workflow.actions[j].selectedAuthentication = action.selectedAuthentication
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
setWorkflow(workflow)
|
||||
}
|
||||
|
||||
const ErrorItem = (props) => {
|
||||
const { apps, error, index } = props
|
||||
|
||||
const [validating, setValidating] = useState(false)
|
||||
const [actionRunInfo, setActionRunInfo] = useState({})
|
||||
if (error === undefined || error === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (apps === undefined || apps === null || apps.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const validateApp = (app, action) => {
|
||||
if (validating) {
|
||||
return
|
||||
}
|
||||
|
||||
setValidating(true)
|
||||
|
||||
// FIXME: Run execution:
|
||||
// 1. Should set app authentication validation
|
||||
if (isCloud) {
|
||||
action.environment = "Cloud"
|
||||
} else {
|
||||
action.environment = "Shuffle"
|
||||
}
|
||||
|
||||
/*
|
||||
setExecutionResult({
|
||||
valid: false,
|
||||
result: baseResult,
|
||||
})
|
||||
setExecuting(true);
|
||||
*/
|
||||
|
||||
setActionRunInfo({})
|
||||
const url = `${globalUrl}/api/v1/apps/${app.id}/run?validation=true`
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(action),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for stream results :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setValidating(false)
|
||||
|
||||
setActionRunInfo(responseJson)
|
||||
|
||||
//console.log("RESPONSE: ", responseJson)
|
||||
if (
|
||||
responseJson.success === true &&
|
||||
responseJson.result !== null &&
|
||||
responseJson.result !== undefined &&
|
||||
responseJson.result.length > 0
|
||||
) {
|
||||
//toast("
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast("Execution error: " + error.toString());
|
||||
setValidating(false)
|
||||
})
|
||||
}
|
||||
|
||||
var authReturn = null
|
||||
var validationReturn = null
|
||||
var foundApp = {
|
||||
"name": "",
|
||||
"id": "",
|
||||
}
|
||||
var foundAction = {
|
||||
"name": "",
|
||||
"label": "",
|
||||
"id": "",
|
||||
"app_id": "",
|
||||
"app_name": "",
|
||||
}
|
||||
|
||||
var selectedImage = null
|
||||
var resolveButton = null
|
||||
if (error.app_id !== undefined && error.app_id !== null) {
|
||||
for (var i = 0; i < apps.length; i++) {
|
||||
if (apps[i].id === error.app_id) {
|
||||
foundApp = apps[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundApp) {
|
||||
toast("Couldn't find relevant app. Is it activated?")
|
||||
return "Failed to find app"
|
||||
}
|
||||
|
||||
selectedImage =
|
||||
<img
|
||||
src={foundApp.large_image}
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25,
|
||||
marginRight: 10,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
border: `1px solid ${theme.palette.borderColor}`,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
if (error.action_id !== undefined && error.action_id !== null && workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
|
||||
for (var i = 0; i < workflow.actions.length; i++) {
|
||||
if (workflow.actions[i].id === error.action_id) {
|
||||
foundAction = workflow.actions[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validationIcon = Object.getOwnPropertyNames(actionRunInfo).length === 0 ? null :
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography variant="body1">
|
||||
{actionRunInfo.result}
|
||||
</Typography>
|
||||
}
|
||||
placement="bottom"
|
||||
>
|
||||
{actionRunInfo.validation.valid === true ?
|
||||
<CheckCircleOutlineIcon style={{color: green, marginRight: 10, }} />
|
||||
:
|
||||
<ErrorOutlineIcon style={{color: red, marginRight: 10, }} />
|
||||
}
|
||||
</Tooltip>
|
||||
|
||||
const authenticationType = foundApp.authentication
|
||||
if (error.type === "configuration" || error.type === "authentication") {
|
||||
// FIXME: Check the error
|
||||
if (appAuth === undefined || appAuth === null) {
|
||||
return "Loading auth"
|
||||
}
|
||||
|
||||
var relevantAuthentication = []
|
||||
var foundAuth = {}
|
||||
for (var key in appAuth) {
|
||||
if (appAuth[key].app.id !== error.app_id) {
|
||||
continue
|
||||
}
|
||||
|
||||
foundAuth = appAuth[key]
|
||||
relevantAuthentication.push(appAuth[key])
|
||||
}
|
||||
|
||||
if (foundAction.selectedAuthentication === undefined || foundAction.selectedAuthentication === null) {
|
||||
foundAction.selectedAuthentication = {}
|
||||
}
|
||||
|
||||
console.log("FOUNDACTION: ", foundAction, foundAuth)
|
||||
|
||||
var authFound = false
|
||||
if (foundAuth.id !== undefined && foundAuth.id !== null && foundAuth.id.length > 0) {
|
||||
var authGroups = []
|
||||
// Choose from a dropdown
|
||||
authReturn = <Select
|
||||
MenuProps={{
|
||||
disableScrollLock: true,
|
||||
}}
|
||||
labelId="select-app-auth"
|
||||
value={
|
||||
foundAction.authentication_id === "authgroups" ? "authgroups" :
|
||||
Object.getOwnPropertyNames(foundAction.selectedAuthentication).length === 0
|
||||
? "No selection"
|
||||
: foundAction.selectedAuthentication
|
||||
}
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
if (e.target.value === "No selection") {
|
||||
foundAction.selectedAuthentication = {};
|
||||
foundAction.authentication_id = "";
|
||||
|
||||
for (let [key,keyval] in Object.entries(foundAction.parameters)) {
|
||||
if (foundAction.parameters[key].configuration === false) {
|
||||
//console.log("FIELDSKIP: ", foundAction.parameters[key].name)
|
||||
continue
|
||||
}
|
||||
|
||||
if (foundAction.parameters[key].name === "url" && authenticationType?.type === "oauth2-app" && foundAction.parameters[key].value.includes("http")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (foundAction.parameters[key].example !== undefined && foundAction.parameters[key].example !== null && foundAction.parameters[key].example !== "") {
|
||||
if (foundAction.parameters[key].example.toLowerCase().includes("apik") || foundAction.parameters[key].example.toLowerCase().includes("key") || foundAction.parameters[key].example.toLowerCase().includes("pass") || foundAction.parameters[key].example.toLowerCase().includes("****")) {
|
||||
foundAction.parameters[key].value = ""
|
||||
} else {
|
||||
foundAction.parameters[key].value = foundAction.parameters[key].example
|
||||
}
|
||||
|
||||
} else {
|
||||
foundAction.parameters[key].value = ""
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedAction(foundAction)
|
||||
setUpdate(Math.random());
|
||||
|
||||
} else if (e.target.value === "authgroups") {
|
||||
if (authGroups !== undefined && authGroups !== null && authGroups.length === 0) {
|
||||
toast("No auth groups created. Opening window to create one")
|
||||
|
||||
setTimeout(() => {
|
||||
window.open("/admin?tab=app_auth", "_blank")
|
||||
}, 2500)
|
||||
} else {
|
||||
foundAction.selectedAuthentication = {};
|
||||
foundAction.authentication_id = "authgroups"
|
||||
|
||||
for (let [key,keyval] in Object.entries(foundAction.parameters)) {
|
||||
//console.log(foundAction.parameters[key])
|
||||
if (foundAction.parameters[key].configuration) {
|
||||
|
||||
if (foundAction.parameters[key].name === "url" && authenticationType?.type === "oauth2-app") {
|
||||
} else {
|
||||
foundAction.parameters[key].value = "authgroup controlled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedAction(foundAction)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
} else {
|
||||
foundAction.selectedAuthentication = e.target.value
|
||||
foundAction.authentication_id = e.target.value.id
|
||||
|
||||
setSelectedAction(foundAction)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
height: 40,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
}}
|
||||
value="No selection"
|
||||
>
|
||||
{selectedImage}
|
||||
<em>No selection</em>
|
||||
</MenuItem>
|
||||
|
||||
{relevantAuthentication.map((data) => {
|
||||
if (data.last_modified === true) {
|
||||
//console.log("LAST MODIFIED: ", data.label)
|
||||
}
|
||||
|
||||
if (foundAction.authentication_id === data.id) {
|
||||
authFound = true
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={data.id}
|
||||
disabled={data.id === foundAction.authentication_id}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
overflowX: "auto",
|
||||
}}
|
||||
value={data}
|
||||
>
|
||||
{selectedImage}
|
||||
|
||||
{data.label}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
|
||||
{/*
|
||||
<Divider style={{marginTop: 10, marginBottom: 10, }}/>
|
||||
|
||||
<MenuItem
|
||||
disabled
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
}}
|
||||
value="authgroups"
|
||||
>
|
||||
<em>Auth Groups</em>
|
||||
</MenuItem>
|
||||
*/}
|
||||
|
||||
</Select>
|
||||
}
|
||||
|
||||
// FIXME: Validate the CURRENT authentication that has been chosen?
|
||||
if (foundApp.authentication === undefined || foundApp.authentication === null) {
|
||||
toast("Authentication error: No authentication found")
|
||||
authReturn = "Failed to find auth"
|
||||
}
|
||||
|
||||
if (authReturn === null && foundApp.authentication.type === "oauth2" || foundApp.authentication.type === "oauth2-app") {
|
||||
authReturn =
|
||||
<AuthenticationOauth2
|
||||
globalUrl={globalUrl}
|
||||
authenticationType={foundApp.authentication}
|
||||
selectedAction={foundAction}
|
||||
|
||||
selectedApp={foundApp}
|
||||
getAppAuthentication={fetchAuthentication}
|
||||
isCloud={true}
|
||||
authButtonOnly={true}
|
||||
/>
|
||||
} else if (authReturn === null) {
|
||||
authReturn = "Other auth - Not implemented"
|
||||
|
||||
}
|
||||
|
||||
validationReturn = authReturn === null || foundAuth.id === undefined || foundAuth.id === null || foundAuth.id.length === 0 || !authFound ? null :
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
style={{
|
||||
height: 35,
|
||||
justifyContent: !validating ? "flex-start" : "center",
|
||||
textTransform: "none",
|
||||
fontSize: 18,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
onClick={() => {
|
||||
toast("Validating app")
|
||||
validateApp(foundApp, foundAction)
|
||||
}}
|
||||
>
|
||||
{validationIcon}
|
||||
{validating ?
|
||||
<CircularProgress
|
||||
color="secondary"
|
||||
style={{width: 30, height: 30, }}
|
||||
/>
|
||||
:
|
||||
<span>
|
||||
{selectedImage}
|
||||
Validate {foundApp.name.replace("_", " ", -1)}
|
||||
</span>
|
||||
}
|
||||
</Button>
|
||||
|
||||
//resolveButton = !(Object.getOwnPropertyNames(actionRunInfo).length === 0 || actionRunInfo.validation.valid === true) ? null :
|
||||
resolveButton =
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
style={{
|
||||
height: 35,
|
||||
textTransform: "none",
|
||||
backgroundColor: green,
|
||||
color: "black",
|
||||
borderRadius: 50,
|
||||
width: 200,
|
||||
margin: "auto",
|
||||
marginTop: 35,
|
||||
}}
|
||||
onClick={() => {
|
||||
toast("Resolving error")
|
||||
console.log("Error: ", error)
|
||||
console.log("Errors: ", workflow.validation)
|
||||
|
||||
// Remove the error from the validation list
|
||||
var newErrors = []
|
||||
for (var workflowErrorKey in workflow.validation.errors) {
|
||||
if (workflow.validation.errors[workflowErrorKey].error !== error.error) {
|
||||
newErrors.push(workflow.validation.errors[workflowErrorKey])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
workflow.validation.errors = newErrors
|
||||
if (workflow.validation.errors.length === 0) {
|
||||
workflow.validation.valid = true
|
||||
}
|
||||
|
||||
// Sets it in the parent
|
||||
setWorkflow(workflow)
|
||||
if (setUpdateParent !== undefined) {
|
||||
setUpdateParent(Math.random())
|
||||
}
|
||||
|
||||
// Saves the actual workflow with the update(s)
|
||||
saveWorkflow(workflow)
|
||||
}}
|
||||
>
|
||||
Resolve
|
||||
</Button>
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{authReturn}
|
||||
<div style={{marginTop: 5, }} />
|
||||
{validationReturn}
|
||||
|
||||
{resolveButton}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
console.log("Workflow validation: ", workflow.validation)
|
||||
return (
|
||||
<div>
|
||||
{workflow.errors !== undefined && workflow.errors !== null ?
|
||||
<div>
|
||||
General errors: {workflow.errors.length}
|
||||
{workflow.errors.map((error, index) => {
|
||||
return (
|
||||
<div>
|
||||
- {error}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<Divider style={{marginTop: 15, marginBottom: 15, }}/>
|
||||
|
||||
|
||||
{workflow.validation.errors !== undefined && workflow.validation.errors !== null ?
|
||||
<div>
|
||||
Validation errors: {workflow.validation.errors.length}
|
||||
{workflow.validation.errors.map((error, index) => {
|
||||
return (
|
||||
<div>
|
||||
<ErrorItem
|
||||
apps={apps}
|
||||
error={error}
|
||||
index={index}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<Divider style={{marginTop: 15, marginBottom: 15, }} />
|
||||
Apps loaded: {apps.length}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FixWorkflowValidationErrors
|
||||
@@ -0,0 +1,157 @@
|
||||
import React from "react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Tooltip,
|
||||
} from "@mui/material"
|
||||
import { useNavigate } from "react-router";
|
||||
import theme from "../theme.jsx";
|
||||
|
||||
import {
|
||||
Lock as LockIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
// onclickHandler = function override from parent onclick
|
||||
const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflowId, }) => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [hovered, setHovered] = React.useState(false)
|
||||
|
||||
if (workflow === undefined || workflow === null) {
|
||||
console.log("No workflow")
|
||||
return null
|
||||
}
|
||||
|
||||
/*
|
||||
* Note for @Lalit:
|
||||
*
|
||||
* When you want to make a list of something that is complex,
|
||||
* make a component. This way, you can easily manage
|
||||
* the logic, and we can actually reuse it. This component is used
|
||||
* multiple places, so do make sure to not break it randomly.
|
||||
*/
|
||||
|
||||
const expandLeftNav = leftNavOpen === true || leftNavOpen === undefined ? true : false
|
||||
|
||||
// Check if workflow.input_markdown has an image in it
|
||||
// If it does, show it as the main thing
|
||||
//
|
||||
var relevantImageUrl = ""
|
||||
if (workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown !== "") {
|
||||
// Look for <img> tag or  markdown
|
||||
// html > markdown
|
||||
const imgTag = workflow.input_markdown.match(/<img[^>]+>/g)
|
||||
|
||||
if (imgTag !== null) {
|
||||
const src = imgTag[0].match(/src="([^"]+)"/)
|
||||
if (src !== null) {
|
||||
relevantImageUrl = src[1]
|
||||
}
|
||||
} else {
|
||||
const markdownTag = workflow.input_markdown.match(/!\[.*\]\(.*\)/g)
|
||||
|
||||
if (markdownTag !== null) {
|
||||
const src = markdownTag[0].match(/\(([^)]+)\)/)
|
||||
if (src !== null) {
|
||||
relevantImageUrl = src[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (onclickHandler !== undefined) {
|
||||
onclickHandler()
|
||||
} else {
|
||||
navigate(`/workflows/` + workflow?.id)
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 100)
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
textTransform: "none",
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
textAlign: "left",
|
||||
opacity: expandLeftNav ? 1 : 0,
|
||||
transition: "opacity 0.1s",
|
||||
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
backgroundColor: hovered || currentWorkflowId === workflow.id ? "#1f1f1f" : "transparent",
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
display: "flex",
|
||||
marginRight: "auto",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
|
||||
{relevantImageUrl !== undefined && relevantImageUrl !== null && relevantImageUrl !== "" ?
|
||||
<Avatar
|
||||
alt={workflow?.name}
|
||||
src={relevantImageUrl}
|
||||
style={{ width: 24, height: 24, marginRight: 5, }}
|
||||
/>
|
||||
:
|
||||
workflow?.apps?.slice(0, 2).map((data, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
style={{
|
||||
position: "relative",
|
||||
marginLeft: index === 1 ? -8 : 0,
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
alt={data.app_name}
|
||||
src={
|
||||
data.large_image
|
||||
? data.large_image
|
||||
: "/images/no_image.png"
|
||||
}
|
||||
style={{ width: 24, height: 24 }}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
<Typography
|
||||
style={{
|
||||
color: "#CDCDCD",
|
||||
fontSize: 16,
|
||||
marginLeft: 8,
|
||||
maxWidth: 180,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{workflow?.name}
|
||||
</Typography>
|
||||
|
||||
{onclickHandler !== undefined && workflow.sharing !== "form" ?
|
||||
<Tooltip title="Private Org Form" placement="right">
|
||||
<LockIcon style={{height: 15, width: 15, color: "grey", position: "absolute", left: -17, }}/>
|
||||
</Tooltip>
|
||||
: null
|
||||
}
|
||||
</Box>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecentWorkflow
|
||||
@@ -0,0 +1,901 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import { toast } from "react-toastify"
|
||||
import theme from '../theme.jsx';
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
import AppSearchButtons from "../components/AppSearchButtons.jsx";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import RenderCytoscape from "../components/RenderCytoscape.jsx";
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Drawer,
|
||||
CircularProgress,
|
||||
Fade,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
Check as CheckIcon,
|
||||
TrendingFlat as TrendingFlatIcon,
|
||||
Close as CloseIcon,
|
||||
East as EastIcon,
|
||||
Interests as InterestsIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import {
|
||||
green,
|
||||
yellow,
|
||||
red,
|
||||
grey,
|
||||
} from "../views/AngularWorkflow.jsx"
|
||||
|
||||
import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx";
|
||||
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
|
||||
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx";
|
||||
import FixWorkflowValidationErrors from "../components/FixWorkflowValidationErrors.jsx";
|
||||
|
||||
const WorkflowTemplatePopup = (props) => {
|
||||
const {
|
||||
userdata, appFramework, globalUrl, img1, srcapp, img2, dstapp, title, description, visualOnly, apps, isLoggedIn, isHomePage, getAppFramework, showTryit, shownColor, workflowBuilt, usecaseDetails,
|
||||
|
||||
isModalOpenDefault,
|
||||
setIsClicked,
|
||||
inputWorkflowId,
|
||||
} = props;
|
||||
|
||||
const [isActive, setIsActive] = useState(workflowBuilt === true);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(isModalOpenDefault === true ? true : false)
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [workflowLoading, setWorkflowLoading] = useState(false)
|
||||
const [showLoginButton, setShowLoginButton] = useState(false);
|
||||
const [appAuthentication, setAppAuthentication] = React.useState(undefined);
|
||||
const [missingSource, setMissingSource] = React.useState(undefined)
|
||||
const [missingDestination, setMissingDestination] = React.useState(undefined);
|
||||
const [configurationFinished, setConfigurationFinished] = React.useState(false)
|
||||
const [appSetupDone, setAppSetupDone] = React.useState(false)
|
||||
|
||||
const [requestSent, setRequestSent] = React.useState(false)
|
||||
const [showTryitOut, setShowTryitout] = React.useState(showTryit === true ? true : false)
|
||||
|
||||
const [loadingWorkflow, setLoadingWorkflow] = React.useState(false)
|
||||
const [workflow, setWorkflow] = useState({});
|
||||
const [_, setUpdate] = useState(0)
|
||||
|
||||
const fetchWorkflow = (id) => {
|
||||
if (id === undefined || id === null || id === "") {
|
||||
return
|
||||
}
|
||||
|
||||
if (loadingWorkflow === true) {
|
||||
return
|
||||
}
|
||||
|
||||
setLoadingWorkflow(true)
|
||||
const url = `${globalUrl}/api/v1/workflows/${id}`
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
setLoadingWorkflow(false)
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for framework!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
console.log("Error in workflow loading for ID ", id)
|
||||
} else {
|
||||
setWorkflow(responseJson)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("err in framework: ", error.toString());
|
||||
setLoadingWorkflow(false)
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (inputWorkflowId !== undefined && inputWorkflowId !== null && inputWorkflowId !== "" && workflow.id !== inputWorkflowId) {
|
||||
fetchWorkflow(inputWorkflowId)
|
||||
}
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
let navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (modalOpen !== true) {
|
||||
if (workflowLoading === true) {
|
||||
setWorkflowLoading(false)
|
||||
}
|
||||
|
||||
//console.log("Modal is not open, so we are not doing anything.")
|
||||
return
|
||||
}
|
||||
|
||||
if (workflowLoading !== true) {
|
||||
//console.log("Workflow loading is false, so we can try to get the workflow.")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("DEBUG: Skipped direct generation without Try it for now.")
|
||||
|
||||
/*
|
||||
if (!srcapp.includes(":default") && !dstapp.includes(":default")) {
|
||||
if (appSetupDone === false && setAppSetupDone !== undefined) {
|
||||
setAppSetupDone(true)
|
||||
}
|
||||
|
||||
getGeneratedWorkflow()
|
||||
}
|
||||
|
||||
if (missingSource !== undefined && missingDestination !== undefined) {
|
||||
if (appSetupDone === false && setAppSetupDone !== undefined) {
|
||||
setAppSetupDone(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (getAppFramework !== undefined) {
|
||||
setTimeout(() => {
|
||||
getAppFramework()
|
||||
}, 500)
|
||||
}
|
||||
*/
|
||||
}, [modalOpen, missingSource, missingDestination])
|
||||
|
||||
useEffect(() => {
|
||||
//console.log("IN USEEFFECT FOR CONFIG: ", configurationFinished)
|
||||
if (configurationFinished === true && workflow.id !== undefined && workflow.id !== null && workflow.id !== "") {
|
||||
//toast.success("Generation Successful")
|
||||
|
||||
/*
|
||||
setTimeout(() => {
|
||||
navigate("/workflows/" + workflow.id)
|
||||
}, 2000)
|
||||
*/
|
||||
}
|
||||
}, [configurationFinished, workflow])
|
||||
|
||||
const imageSize = 32
|
||||
const defaultBorder = "1px solid rgba(255,255,255,0.6)"
|
||||
const imagestyleWrapper = {
|
||||
height: imageSize,
|
||||
width: imageSize,
|
||||
borderRadius: imageSize,
|
||||
border: isHomePage ? null : defaultBorder,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}
|
||||
|
||||
const imagestyleWrapperDefault = {
|
||||
height: imageSize,
|
||||
width: imageSize,
|
||||
borderRadius: imageSize,
|
||||
border: isHomePage ? null : defaultBorder,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}
|
||||
|
||||
const imagestyle = {
|
||||
height: imageSize,
|
||||
width: imageSize,
|
||||
borderRadius: imageSize,
|
||||
//border: isHomePage ? null : defaultBorder,
|
||||
overflow: "hidden",
|
||||
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}
|
||||
|
||||
const imagestyleDefault = {
|
||||
display: "block",
|
||||
marginLeft: 9,
|
||||
marginTop: 9,
|
||||
height: imageSize,
|
||||
width: "auto",
|
||||
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}
|
||||
|
||||
if (modalOpen === false && (title === undefined || title === null || title === "")) {
|
||||
if (setIsClicked !== undefined) {
|
||||
setIsClicked(false)
|
||||
}
|
||||
|
||||
console.log("No title for workflow template popup!");
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
const loadAppAuth = () => {
|
||||
// Check if it exists, and has keys
|
||||
//
|
||||
if (userdata === undefined || userdata === null || Object.keys(userdata).length === 0) {
|
||||
setErrorMessage("You need to be logged in to try the pre-built Workflow Templates.")
|
||||
setShowLoginButton(true)
|
||||
|
||||
// Send the user to the login screen after 3 seconds
|
||||
setTimeout(() => {
|
||||
// Make it cancel if the state modalOpen changes
|
||||
if (modalOpen === false) {
|
||||
return
|
||||
}
|
||||
|
||||
navigate("/login?view=" + window.location.pathname + window.location.search)
|
||||
}, 4500)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
fetch(`${globalUrl}/api/v1/apps/authentication`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for setting app auth :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (!responseJson.success) {
|
||||
toast("Failed to get app auth: " + responseJson.reason);
|
||||
return
|
||||
}
|
||||
|
||||
var newauth = [];
|
||||
for (let authkey in responseJson.data) {
|
||||
if (responseJson.data[authkey].defined === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newauth.push(responseJson.data[authkey]);
|
||||
}
|
||||
|
||||
setAppAuthentication(newauth);
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast(error.toString());
|
||||
console.log("New auth error: ", error.toString());
|
||||
});
|
||||
}
|
||||
|
||||
// Can create and set workflows
|
||||
const reloadWorkflow = (workflow_id) => {
|
||||
|
||||
const new_url = `${globalUrl}/api/v1/workflows/${workflow_id}`
|
||||
return fetch(new_url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!");
|
||||
return;
|
||||
}
|
||||
//setSubmitLoading(false);
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
toast("Error setting workflow: ", responseJson.reason)
|
||||
} else {
|
||||
toast("Error setting workflow.")
|
||||
}
|
||||
|
||||
return
|
||||
} else if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id !== "") {
|
||||
setWorkflow(responseJson)
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
})
|
||||
.catch((error) => {
|
||||
toast("Failed reloading configured workflow: ", error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
// Can create and set workflows
|
||||
const saveWorkflow = (workflowdata) => {
|
||||
|
||||
const new_url = `${globalUrl}/api/v1/workflows?set_auth=true`
|
||||
return fetch(new_url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(workflowdata),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!");
|
||||
return;
|
||||
}
|
||||
//setSubmitLoading(false);
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
toast("Error setting workflow: ", responseJson.reason)
|
||||
} else {
|
||||
toast("Error setting workflow.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// In case it got a new id, this is to make sure it loads with the correct config
|
||||
if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id !== "") {
|
||||
reloadWorkflow(responseJson.id)
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
})
|
||||
.catch((error) => {
|
||||
toast("Failed generating workflow: ", error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const getGeneratedWorkflow = () => {
|
||||
// POST
|
||||
// https://shuffler.io/api/v1/workflows/merge
|
||||
// destination: {app_id: "b9c2feaf99b6309dabaeaa8518c61d3d", app_name: "Servicenow_API", app_version: "",…}
|
||||
// id: ""
|
||||
// middle:[]
|
||||
// name: "Email analysis"
|
||||
// source:{app_id: "accdaaf2eeba6a6ed43b2efc0112032d", app_name
|
||||
if (requestSent === true) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("SRCAPP: ", srcapp, "DSTAPP: ", dstapp)
|
||||
if (srcapp === undefined || srcapp === null) {
|
||||
srcapp = ""
|
||||
}
|
||||
|
||||
if ((srcapp !== undefined && srcapp !== null && srcapp.includes(":default")) || (dstapp !== undefined && dstapp !== null && dstapp.includes(":default"))) {
|
||||
toast("You need to select both a source and destination app before generating this workflow.")
|
||||
|
||||
if (srcapp !== undefined && srcapp !== null && srcapp.includes(":default")) {
|
||||
setMissingSource({
|
||||
"type": srcapp.split(":")[0],
|
||||
})
|
||||
}
|
||||
|
||||
if (dstapp !== undefined && dstapp !== null && dstapp.includes(":default")) {
|
||||
setMissingDestination({
|
||||
"type": dstapp.split(":")[0],
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setWorkflowLoading(true)
|
||||
|
||||
const newsrcapp = srcapp
|
||||
const newdstapp = dstapp
|
||||
|
||||
const mergedata = {
|
||||
name: title,
|
||||
id: "",
|
||||
source: {
|
||||
app_name: newsrcapp,
|
||||
},
|
||||
middle: [],
|
||||
destination: {
|
||||
app_name: newdstapp,
|
||||
},
|
||||
}
|
||||
|
||||
setRequestSent(true)
|
||||
const url = isCloud ? `${globalUrl}/api/v1/workflows/merge` : `https://shuffler.io/api/v1/workflows/merge`
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(mergedata),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
//console.log("Status not 200 for framework!");
|
||||
setRequestSent(false)
|
||||
}
|
||||
|
||||
setWorkflowLoading(false)
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id !== "" && responseJson.name !== undefined && responseJson.name !== null && responseJson.name !== "") {
|
||||
console.log("Success in workflow template (prebuilt): ", responseJson);
|
||||
setWorkflow(responseJson)
|
||||
|
||||
// Sets it in the database properly
|
||||
saveWorkflow(responseJson)
|
||||
return
|
||||
}
|
||||
|
||||
if (responseJson.success === false) {
|
||||
//console.log("Error in workflow template: ", responseJson.error);
|
||||
setRequestSent(false)
|
||||
|
||||
const defaultMessage = "Error: Failed to generate workflow the workflow - the Shuffle team has been notified. Contact support@shuffler.io if you want manual help building this usecase until the AI system is handled."
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason !== "") {
|
||||
setErrorMessage(defaultMessage + "\n\n" + responseJson.reason)
|
||||
} else {
|
||||
setErrorMessage(defaultMessage)
|
||||
}
|
||||
|
||||
setIsActive(true)
|
||||
//setTimeout(() => {
|
||||
// setModalOpen(false)
|
||||
//}, 5000)
|
||||
} else {
|
||||
console.log("Success in workflow template: ", responseJson);
|
||||
setIsActive(true)
|
||||
if (responseJson.workflow_id === "") {
|
||||
console.log("Failed to build workflow for these tools. Closing in 3 seconds.")
|
||||
return
|
||||
}
|
||||
|
||||
fetchWorkflow(responseJson.workflow_id)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("err in framework: ", error.toString());
|
||||
setRequestSent(false)
|
||||
setWorkflowLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
if (modalOpen === true && !srcapp?.includes(":default") && !dstapp?.includes(":default")) {
|
||||
if (appSetupDone === false && setAppSetupDone !== undefined) {
|
||||
setAppSetupDone(true)
|
||||
}
|
||||
|
||||
// No autoruns anymore without clicking "Try it"
|
||||
if (workflow.id === undefined && workflowLoading === false && errorMessage === "") {
|
||||
//getGeneratedWorkflow()
|
||||
}
|
||||
}
|
||||
|
||||
const isFinished = () => {
|
||||
// Look for configuration fields being done in the current modal
|
||||
// 1. Start by finding the modal
|
||||
const template = document.getElementById("workflow-template")
|
||||
if (template === null || template == undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Find item in template with id app-config
|
||||
const appconfig = template.getElementsByClassName("app-config")
|
||||
if (appconfig === null || appconfig == undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const ModalView = () => {
|
||||
if (modalOpen === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
const divHeight = 500
|
||||
const divWidth = 500
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
anchor={"right"}
|
||||
open={modalOpen}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
|
||||
if (setIsClicked !== undefined) {
|
||||
setIsClicked(false)
|
||||
}
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
minWidth: isHomePage ? null : isMobile ? 300 : 850,
|
||||
maxWidth: isHomePage ? null : isMobile ? 300 : 850,
|
||||
paddingTop: isMobile ? null : 75,
|
||||
itemAlign: "center",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
style={{
|
||||
zIndex: 5000,
|
||||
position: "absolute",
|
||||
top: 14,
|
||||
right: 14,
|
||||
color: "white",
|
||||
}}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<DialogContent style={{marginTop: 0, marginLeft: isHomePage ? null : isMobile ? null : 75, maxWidth: 470, }}>
|
||||
<Typography variant="h4" style={{ fontSize: isMobile ? 20 : null}}>
|
||||
<b>Configure Workflow</b>
|
||||
</Typography>
|
||||
|
||||
{title === undefined || title === null || title === "" ? null :
|
||||
<span>
|
||||
<Typography variant="body2" color="textSecondary" style={{marginTop: 25, }}>
|
||||
Selected Workflow:
|
||||
</Typography>
|
||||
<div style={{marginBottom: 0, }} id="workflow-template">
|
||||
<WorkflowTemplatePopup2
|
||||
globalUrl={globalUrl}
|
||||
img1={img1}
|
||||
srcapp={srcapp}
|
||||
img2={img2}
|
||||
dstapp={dstapp}
|
||||
title={title}
|
||||
description={description}
|
||||
visualOnly={true}
|
||||
|
||||
workflowBuilt={workflowBuilt}
|
||||
shownColor={shownColor}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
|
||||
<div style={{marginTop: 15, }}>
|
||||
{/* Fix the timeline when errors are fixed.. how? */}
|
||||
<WorkflowValidationTimeline
|
||||
workflow={workflow}
|
||||
/>
|
||||
|
||||
<FixWorkflowValidationErrors
|
||||
globalUrl={globalUrl}
|
||||
workflow={workflow}
|
||||
setWorkflow={setWorkflow}
|
||||
|
||||
setUpdateParent={setUpdate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{workflowLoading === true ?
|
||||
<div style={{marginTop: 75, textAlign: "center", }}>
|
||||
<Typography variant="h4"> Generating the Workflow...
|
||||
</Typography>
|
||||
<CircularProgress style={{marginLeft: 0, marginTop: 25, }}/>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
{usecaseDetails === undefined ? null :
|
||||
<Typography variant="h6" style={{marginTop: 75, }}>
|
||||
{usecaseDetails?.description}
|
||||
</Typography>
|
||||
}
|
||||
<Typography variant="h6" style={{marginTop: 75, }}>
|
||||
{errorMessage !== "" ? errorMessage : ""}
|
||||
</Typography>
|
||||
{showLoginButton ?
|
||||
<Link to="/register?message=Please login to create workflows&view=usecases"
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
marginBottom: 50,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
style={{
|
||||
display: "flex",
|
||||
fontSize: 18,
|
||||
color: "rgba(255, 132, 68, 1)",
|
||||
marginTop: 32,
|
||||
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
||||
fontWeight: 550,
|
||||
}}
|
||||
>
|
||||
Sign up
|
||||
<EastIcon style={{ marginTop: 3, marginLeft: 7 }} />
|
||||
</Typography>
|
||||
</Link>
|
||||
:
|
||||
!showTryitOut && !isActive ?
|
||||
<Button
|
||||
variant="outlined"
|
||||
style={{
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
//setWorkflowLoading(true)
|
||||
getGeneratedWorkflow()
|
||||
loadAppAuth()
|
||||
}}
|
||||
>
|
||||
Try this usecase <TrendingFlatIcon style={{ }} />
|
||||
</Button>
|
||||
: null}
|
||||
</div>
|
||||
}
|
||||
|
||||
{!isLoggedIn ? null :
|
||||
<div>
|
||||
{(appSetupDone === false && missingSource !== undefined || missingDestination !== undefined) ?
|
||||
<Typography variant="body1" style={{marginTop: 75, marginBottom: 10, }}>
|
||||
{"Find relevant Apps for this Usecase"}
|
||||
</Typography>
|
||||
: null}
|
||||
|
||||
{(missingSource !== undefined) ?
|
||||
<div style={{}}>
|
||||
<AppSearchButtons
|
||||
globalUrl={globalUrl}
|
||||
appFramework={appFramework}
|
||||
|
||||
appType={missingSource.type}
|
||||
AppImage={missingSource.image}
|
||||
|
||||
setMissing={setMissingSource}
|
||||
|
||||
getAppFramework={getAppFramework}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
{(missingDestination !== undefined) ?
|
||||
<div style={{}}>
|
||||
<AppSearchButtons
|
||||
globalUrl={globalUrl}
|
||||
appFramework={appFramework}
|
||||
|
||||
appType={missingDestination.type}
|
||||
AppImage={missingDestination.image}
|
||||
|
||||
setMissing={setMissingDestination}
|
||||
|
||||
getAppFramework={getAppFramework}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
}
|
||||
|
||||
<ConfigureWorkflow
|
||||
userdata={userdata}
|
||||
theme={theme}
|
||||
globalUrl={globalUrl}
|
||||
appAuthentication={appAuthentication}
|
||||
setAppAuthentication={setAppAuthentication}
|
||||
|
||||
workflow={workflow}
|
||||
apps={apps}
|
||||
|
||||
setConfigurationFinished={setConfigurationFinished}
|
||||
/>
|
||||
|
||||
</DialogContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
if (isModalOpenDefault === true) {
|
||||
return <ModalView />
|
||||
}
|
||||
|
||||
var parsedTitle = title !== undefined && title !== null ? title : ""
|
||||
const maxlength = 50
|
||||
if (title !== undefined && title !== null && title.length > maxlength) {
|
||||
parsedTitle = title.substring(0, maxlength) + "..."
|
||||
}
|
||||
|
||||
parsedTitle = parsedTitle.replaceAll("_", " ")
|
||||
|
||||
const parsedDescription = description !== undefined && description !== null ? description.replaceAll("_", " ") : ""
|
||||
|
||||
const boxHeight = 104
|
||||
const highlightColor = shownColor !== undefined && shownColor !== null && shownColor !== "" ? shownColor : "#f85a3e"
|
||||
|
||||
var hasInterest = false
|
||||
if (userdata.interests !== undefined && userdata.interests !== null && userdata.interests.length > 0) {
|
||||
const comparisonTitle = title === undefined || title === null ? "" : title.trim().toLowerCase().replaceAll(" ", "_")
|
||||
for (var interestkey in userdata.interests) {
|
||||
if (userdata.interests[interestkey].name === undefined || userdata.interests[interestkey].name === null || userdata.interests[interestkey].name === "") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (modalOpen) {
|
||||
console.log("COMPARE: ", userdata.interests[interestkey].name.trim().toLowerCase().replaceAll(" ", "_"), comparisonTitle)
|
||||
}
|
||||
|
||||
if (userdata.interests[interestkey].name.trim().toLowerCase().replaceAll(" ", "_") === comparisonTitle) {
|
||||
if (modalOpen) {
|
||||
console.log("FOUND: ", comparisonTitle)
|
||||
}
|
||||
|
||||
hasInterest = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const borderStyle = isHomePage ? null : isHovered && isActive ? errorMessage !== "" ? "1px solid red" : `2px solid ${theme.palette.green}` : isHovered ? `1px solid ${highlightColor}` : "1px solid rgba(33, 33, 33, 1)"
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", height: boxHeight, borderRadius: theme.palette.borderRadius, justifyContent: isMobile ? null : "center" }}
|
||||
>
|
||||
<ModalView />
|
||||
|
||||
<div
|
||||
// variant={isActive === 1 ? "contained" : "outlined"}
|
||||
color="secondary"
|
||||
disabled={visualOnly === true}
|
||||
style={{
|
||||
width: isHomePage? isMobile ? null : "100%" : "99%",
|
||||
borderRadius: 8,
|
||||
textTransform: "none",
|
||||
backgroundColor: isHomePage ? null : theme.palette.inputColor,
|
||||
border: borderStyle,
|
||||
cursor: isActive ? errorMessage !== "" ? "not-allowed" : "pointer" : "pointer",
|
||||
position: "relative",
|
||||
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setIsHovered(true)
|
||||
|
||||
setShowTryitout(true)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsHovered(false)
|
||||
|
||||
if (showTryit !== true) {
|
||||
setShowTryitout(false)
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (visualOnly === true) {
|
||||
console.log("Not showing more than visuals.")
|
||||
return
|
||||
}
|
||||
|
||||
if (!isLoggedIn) {
|
||||
loadAppAuth()
|
||||
setModalOpen(true)
|
||||
} else if (isLoggedIn && errorMessage !== "") {
|
||||
toast.error("Already failed to generate a workflow for this usecase. Please try again later or contact support@shuffler.io.")
|
||||
|
||||
setModalOpen(true)
|
||||
} else if (isActive) {
|
||||
// toast.success("Workflow already generated. Please try another workflow template!")
|
||||
|
||||
// FIXME: Remove these?
|
||||
loadAppAuth()
|
||||
setModalOpen(true)
|
||||
//getGeneratedWorkflow()
|
||||
} else {
|
||||
setModalOpen(true)
|
||||
//setWorkflowLoading(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
<div style={{display: "flex", }}>
|
||||
{shownColor !== undefined && shownColor !== null && shownColor !== "" ?
|
||||
<div style={{position: "absolute", left: 0, height: boxHeight-2, width: 4, backgroundColor: shownColor, borderTopLeftRadius: 8, borderBottomLeftRadius: 8, }} />
|
||||
: null}
|
||||
|
||||
<div style={{ display: "flex", itemAlign: "left", textAlign: "left", }}>
|
||||
<div style={{display: "flex", flex: 1, marginLeft: 25, marginTop: showTryitOut && !isActive ? 14 : 30, }}>
|
||||
<div style={{zIndex: 51}}>
|
||||
{img1 !== undefined && img1 !== "" && srcapp !== undefined && srcapp !== "" ?
|
||||
<Tooltip title={srcapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
|
||||
<div style={srcapp !== undefined && srcapp.includes(":default") ? imagestyleWrapperDefault : imagestyleWrapper}>
|
||||
<img src={img1} style={srcapp !== undefined && srcapp.includes(":default") ? imagestyleDefault : imagestyle} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
:
|
||||
<div style={{width: 50, }} />
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
{img2 !== undefined && img2 !== "" && dstapp !== undefined && dstapp !== "" ?
|
||||
<Tooltip title={dstapp.replaceAll(":default", "").replaceAll("_", " ").replaceAll(" API", "")} placement="top">
|
||||
<div style={{display: "flex", position: "relative", left: -10, }}>
|
||||
<div style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleWrapperDefault : imagestyleWrapper}>
|
||||
<img src={img2} style={dstapp !== undefined && dstapp.includes(":default") ? imagestyleDefault : imagestyle} />
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
:
|
||||
<div style={{width: 0, }} />
|
||||
}
|
||||
|
||||
</div>
|
||||
<div style={{ marginLeft: 20, overflow: "hidden", maxHeight: 30, marginTop: showTryitOut && !isActive ? 8 : 23, }}>
|
||||
<Typography variant="body1" style={{ marginTop: parsedDescription.length === 0 ? 10 : 0, fontSize: isMobile ? 13 : 16, fontWeight: isHomePage ? 600 : null, textTransform: 'capitalize', color: isHomePage ? "var(--White-text, #F1F1F1)" : "rgba(241, 241, 241, 1)"}} >
|
||||
<b>{parsedTitle}</b>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{isActive === true && errorMessage === "" ?
|
||||
<Tooltip title="You already have workflows that are based on this usecase" placement="top">
|
||||
<CheckIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", color: theme.palette.green, top: 10, right: 10, }} />
|
||||
</Tooltip>
|
||||
: ""}
|
||||
|
||||
{!isActive && hasInterest === true ?
|
||||
<Tooltip title="Your team has shown interest in this usecase previously." placement="top">
|
||||
<InterestsIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", color: "rgba(254, 204, 0, 0.5)", top: 10, right: 10, }} />
|
||||
</Tooltip>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
|
||||
{showTryitOut && !isActive ?
|
||||
<Fade in={showTryitOut} timeout={300}>
|
||||
<Button
|
||||
variant="text"
|
||||
style={{
|
||||
textTransform: "none",
|
||||
marginTop: 8,
|
||||
marginLeft: 15,
|
||||
}}
|
||||
onClick={() => {
|
||||
//setWorkflowLoading(true)
|
||||
getGeneratedWorkflow()
|
||||
loadAppAuth()
|
||||
}}
|
||||
>
|
||||
Try it out <TrendingFlatIcon style={{ }} />
|
||||
</Button>
|
||||
</Fade>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowTemplatePopup
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
import { createContext, useState } from 'react';
|
||||
|
||||
export const Context = createContext();
|
||||
|
||||
export const AppContext =(props) => {
|
||||
|
||||
// Left side bar global states
|
||||
const [searchBarModalOpen, setSearchBarModalOpen] = useState(false);
|
||||
const [leftSideBarOpenByClick, setLeftSideBarOpenByClick] = useState(false);
|
||||
|
||||
|
||||
return (
|
||||
<Context.Provider value={{
|
||||
searchBarModalOpen,
|
||||
setSearchBarModalOpen,
|
||||
leftSideBarOpenByClick,
|
||||
setLeftSideBarOpenByClick
|
||||
}}>
|
||||
{props.children}
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user