Files
shuffle-cracked/frontend/src/components/FixWorkflowValidationErrors.jsx
T
2024-11-12 10:16:05 +01:00

688 lines
17 KiB
React

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