Merge branch '2.0.0' of https://github.com/Shuffle/Shuffle into flipBranch
This commit is contained in:
@@ -79,6 +79,7 @@ const AppGrid = (props) => {
|
||||
const [formMail, setFormMail] = React.useState("");
|
||||
const [message, setMessage] = React.useState("");
|
||||
const [formMessage, setFormMessage] = React.useState("");
|
||||
const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]);
|
||||
|
||||
const buttonStyle = {
|
||||
borderRadius: 30,
|
||||
@@ -352,7 +353,7 @@ const AppGrid = (props) => {
|
||||
if (responseJson.success === false) {
|
||||
toast.error(responseJson.reason);
|
||||
} else {
|
||||
toast.success(`App ${type}d Successfully!`);
|
||||
//toast.success(`App ${type}d Successfully!`);
|
||||
if (type === 'activate') {
|
||||
setAllActivatedAppIds(prev => [...prev, data.objectID]);
|
||||
setIsAnyAppActivated(true);
|
||||
@@ -395,7 +396,7 @@ const AppGrid = (props) => {
|
||||
{!isLoading ? (
|
||||
<div>
|
||||
{hits.length === 0 && searchQuery.length >= 0 && showNoAppFound ? (
|
||||
<Typography variant="body1" style={{ marginTop: '30%' }}>No App Found</Typography>
|
||||
<Typography variant="body1" style={{ marginTop: '30%' }}>No Apps Found</Typography>
|
||||
) : (
|
||||
<Grid item spacing={2} justifyContent="flex-start">
|
||||
<div
|
||||
@@ -1551,9 +1552,13 @@ const AppGrid = (props) => {
|
||||
<Typography variant="h5" style={{ marginBottom: 30, marginTop: 30, fontWeight: "400", fontSize: 24 }}>
|
||||
Filter By
|
||||
</Typography>
|
||||
|
||||
<FilterUsersAndOrgsAppByCategory selectedCategoryForUsersAndOgsApps={selectedCategoryForUsersAndOgsApps} setselectedCategoryForUsersAndOgsApps={setselectedCategoryForUsersAndOgsApps} />
|
||||
|
||||
<FilterUsersAndOrgsAppByActionLabel selectedTagsForUserAndOrgApps={selectedTagsForUserAndOrgApps} setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps} />
|
||||
|
||||
<FilterUsersAndOrgsAppByCreatedWith selectedOptionOfCreatedWith={selectedOptionOfCreatedWith} setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith} />
|
||||
|
||||
<FilterUsersAndOrgsAppCreatedBy />
|
||||
</div>
|
||||
)}
|
||||
@@ -1722,6 +1727,10 @@ const AppGrid = (props) => {
|
||||
? `/apps/${data.id}`
|
||||
: `https://shuffler.io/apps/${data.id}`;
|
||||
|
||||
if (data.name === "" && data.id === "") {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom
|
||||
key={index}
|
||||
@@ -1818,17 +1827,71 @@ const AppGrid = (props) => {
|
||||
width: 230,
|
||||
textAlign: 'start',
|
||||
marginLeft: 8,
|
||||
color: "rgba(158, 158, 158, 1)"
|
||||
color: "rgba(158, 158, 158, 1)",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
{data.tags &&
|
||||
data.tags.map((tag, tagIndex) => (
|
||||
<span key={tagIndex}>
|
||||
{normalizedString(tag)}
|
||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{minWidth: 120, overflow: "hidden", }}>
|
||||
{data.generated !== true ?
|
||||
<div>
|
||||
{data.tags &&
|
||||
data.tags.slice(0,2).map((tag, tagIndex) => (
|
||||
<span key={tagIndex}>
|
||||
{normalizedString(tag)}
|
||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
{currTab === 1 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true ?
|
||||
<Button style={{
|
||||
marginLeft: 15,
|
||||
width: 102,
|
||||
height: 35,
|
||||
borderRadius: 200,
|
||||
backgroundColor: "rgba(73, 73, 73, 1)",
|
||||
color: "rgba(241, 241, 241, 1)",
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={(event) => {
|
||||
//deactivatedIndexes.push(index)
|
||||
//setDeactivatedIndexes(deactivatedIndexes)
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
//handleActivateButton(event, data, "deactivate");
|
||||
// FIXME: Put this in a function lol
|
||||
const url = `${globalUrl}/api/v1/apps/${data.id}/deactivate`;
|
||||
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
toast.error(responseJson.reason);
|
||||
} else {
|
||||
toast.success("App Deactivated Successfully. Reload UI to see updated changes.")
|
||||
//const updatedIds = allActivatedAppIds.filter(id => id !== data.objectID);
|
||||
//setAllActivatedAppIds(updatedIds);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("app error: ", error.toString());
|
||||
})
|
||||
}}>
|
||||
Deactivate
|
||||
</Button>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
{/* )} */}
|
||||
</div>
|
||||
</ButtonBase>
|
||||
@@ -1946,6 +2009,7 @@ const AppGrid = (props) => {
|
||||
selectedOptionOfCreatedWith={selectedOptionOfCreatedWith}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AppTab
|
||||
selectedCategoryForUsersAndOgsApps={selectedCategoryForUsersAndOgsApps}
|
||||
selectedTagsForUserAndOrgApps={selectedTagsForUserAndOrgApps}
|
||||
@@ -1954,6 +2018,7 @@ const AppGrid = (props) => {
|
||||
setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps}
|
||||
setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<Configure clickAnalytics />
|
||||
</InstantSearch>
|
||||
|
||||
@@ -1623,12 +1623,12 @@ const Billing = (props) => {
|
||||
{userdata.support === true ?
|
||||
<div style={{ marginBottom: 10, marginTop: clickedFromOrgTab ? 16 : null, color: clickedFromOrgTab ? "#F1F1F1" : null }}>
|
||||
For sales: Create
|
||||
<a href={"https://docs.google.com/document/d/1OeJSi42812EMg7fUAw1HAj1ymOG8rfp8Ma_DGJKvwgI/copy?usp=sharing&organization=" + selectedOrganization.id} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||
New Cloud Contract
|
||||
<a href={"https://docs.google.com/document/d/1N-ZJNn8lWaqiXITrqYcnTt53oXGLNYFEzc5PU-tdAps/copy" + selectedOrganization.id} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||
EU contract
|
||||
</a>
|
||||
or
|
||||
<a href={"https://docs.google.com/document/d/1IguxpeV4Wwwr9C0MPyUNhhajEu_PxjRfP7f0hYyRYOI/copy?usp=sharing&organization=" + selectedOrganization.id} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||
New Onprem Contract
|
||||
<a href={"https://docs.google.com/document/d/1cF-Cwxt1TcahlLrpl1GH2hFZO4gxppLeYjHW6QB0EzQ/copy" + selectedOrganization.id} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||
NOT EU contract
|
||||
</a>
|
||||
-
|
||||
<a href={"https://drive.google.com/drive/folders/1zVvwwkbQXW3p-DJYa0GBDzFo_ZnV_I_5"} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||
@@ -1636,7 +1636,7 @@ const Billing = (props) => {
|
||||
</a>
|
||||
-
|
||||
<a href={"https://github.com/Shuffle/Shuffle-docs/tree/master/handbook/Sales"} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||
Sales Process
|
||||
Sales Process (old)
|
||||
</a>
|
||||
</div>
|
||||
:
|
||||
|
||||
@@ -6,6 +6,7 @@ import UsecaseSearch from "../components/UsecaseSearch.jsx"
|
||||
import WorkflowGrid from "../components/WorkflowGrid.jsx"
|
||||
import dayjs from 'dayjs';
|
||||
import WorkflowTemplatePopup from "./WorkflowTemplatePopup.jsx";
|
||||
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
|
||||
|
||||
import {
|
||||
Badge,
|
||||
@@ -206,6 +207,16 @@ const EditWorkflow = (props) => {
|
||||
<Typography variant="body2" color="textSecondary" style={{marginTop: 20, maxWidth: 440,}}>
|
||||
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
|
||||
</Typography>
|
||||
|
||||
<div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}>
|
||||
<WorkflowValidationTimeline
|
||||
originalWorkflow={workflow}
|
||||
|
||||
apps={apps}
|
||||
workflow={workflow}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showUpload === true ?
|
||||
<div style={{ float: "right" }}>
|
||||
<Tooltip color="primary" title={"Import manually"} placement="top">
|
||||
@@ -957,7 +968,7 @@ const EditWorkflow = (props) => {
|
||||
}}
|
||||
>
|
||||
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
|
||||
{showMoreClicked ? "Collapse": "Expand"}
|
||||
{showMoreClicked ? "Less Options": "More Options"}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -515,16 +515,16 @@ const LicencePopup = (props) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("New variant: ", shuffleVariant)
|
||||
console.log("New variant: ", shuffleVariant)
|
||||
|
||||
if (shuffleVariant === 1) {
|
||||
setCalculatedCost("$600")
|
||||
setSelectedValue(8)
|
||||
} else {
|
||||
setCalculatedCost("$540")
|
||||
setSelectedValue(300)
|
||||
}
|
||||
}, [shuffleVariant])
|
||||
if (shuffleVariant === 1) {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(8)
|
||||
} else {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(300)
|
||||
}
|
||||
}, [shuffleVariant])
|
||||
|
||||
if (typeof window === 'undefined' || window.location === undefined) {
|
||||
return null
|
||||
@@ -680,7 +680,7 @@ const LicencePopup = (props) => {
|
||||
color: "white",
|
||||
}
|
||||
|
||||
|
||||
console.log("Priceitem: ", shuffleVariant)
|
||||
const isLoggedInHandler = () => {
|
||||
if (calculatedCost === payasyougo) {
|
||||
handlePayasyougo(props.userdata)
|
||||
@@ -690,7 +690,7 @@ const LicencePopup = (props) => {
|
||||
const priceItem = window.location.origin === "https://shuffler.io" ?
|
||||
shuffleVariant === 0 ? "app_executions" : "cores"
|
||||
:
|
||||
shuffleVariant === 0 ? "price_1MROFrDzMUgUjxHShcSxgHO1" : "price_1NXjQqDzMUgUjxHSg690R4FP"
|
||||
shuffleVariant === 0 ? "price_1PWI5zDzMUgUjxHSKkz0fGdN" : "price_1NXjQqDzMUgUjxHSg690R4FP"
|
||||
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
||||
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
|
||||
|
||||
@@ -517,12 +517,11 @@ const Header = (props) => {
|
||||
window.location.href = responseJson["url"]
|
||||
return
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 2000);
|
||||
|
||||
}
|
||||
toast("Successfully changed active organization - refreshing!");
|
||||
} else {
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
|
||||
@@ -1547,7 +1546,7 @@ const Header = (props) => {
|
||||
<div style={{ position: "relative", height: topbarHeight, backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", overflow: "hidden", }}>
|
||||
<Typography variant="body1" style={{ paddingTop: 7, margin: "auto", textAlign: "center", color: "white", }}>
|
||||
{/* Shuffle 1.4.0 is out! Read more about */}
|
||||
Shuffle now offers
|
||||
Early Success! More
|
||||
{/* <u>
|
||||
<a href="https://github.com/Shuffle/Shuffle/releases/tag/v1.4.0" target="_blank" style={{ color: "inherit", }} onClick={() => {
|
||||
ReactGA.event({
|
||||
@@ -1596,10 +1595,11 @@ const Header = (props) => {
|
||||
|
||||
navigate("/training")
|
||||
|
||||
}} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}>
|
||||
Public Training!
|
||||
}} style={{ cursor: "pointer", textDecoration: "none", fontWeight:600, color: "rgba(255,255,255,0.8)" }}>
|
||||
Public Trainings
|
||||
</span>
|
||||
</u>
|
||||
Ahead!
|
||||
</Typography>
|
||||
<IconButton color="secondary" style={{ position: "absolute", top: -3, right: 20, }} onClick={(event) => {
|
||||
setShowTopbar(false)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -307,7 +307,6 @@ const ParsedAction = (props) => {
|
||||
(action) =>
|
||||
action.name.toLowerCase() === selectedAction.name.toLowerCase()
|
||||
);
|
||||
console.log("FOUNDACTION: ", foundAction);
|
||||
if (foundAction !== null && foundAction !== undefined) {
|
||||
var foundparams = [];
|
||||
for (let [paramkey,paramkeyval] in Object.entries(foundAction.parameters)) {
|
||||
@@ -549,6 +548,11 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parentNode.label === undefined) {
|
||||
parentNode.label = ""
|
||||
}
|
||||
|
||||
newActionList.push({
|
||||
type: "action",
|
||||
id: parentNode.id,
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
import React, { useState, } from "react";
|
||||
import { makeStyles, createStyles } from "@mui/styles";
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
Typography,
|
||||
|
||||
Avatar,
|
||||
AvatarGroup,
|
||||
} from "@mui/material"
|
||||
|
||||
import {
|
||||
green,
|
||||
yellow,
|
||||
red,
|
||||
} from "../views/AngularWorkflow.jsx"
|
||||
|
||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||
import theme from "../theme.jsx";
|
||||
const itemHeight = 40
|
||||
|
||||
export const getParentNodes = (workflow, action) => {
|
||||
if (action === undefined || action === null) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (workflow.actions === undefined || workflow.actions === null) {
|
||||
workflow.actions = []
|
||||
}
|
||||
|
||||
if (workflow.triggers === undefined || workflow.triggers === null) {
|
||||
workflow.triggers = []
|
||||
}
|
||||
|
||||
if (workflow.branches === undefined || workflow.branches === null) {
|
||||
workflow.branches = []
|
||||
}
|
||||
|
||||
var allkeys = [action.id];
|
||||
var handled = [];
|
||||
var results = [];
|
||||
|
||||
// maxiter = max amount of parent nodes to loop
|
||||
// also handles breaks if there are issues
|
||||
var iterations = 0;
|
||||
var maxiter = 10;
|
||||
while (true) {
|
||||
for (let parentkey in allkeys) {
|
||||
if (allkeys[parentkey] === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
var currentnode = workflow.actions.find((element) => element.id === allkeys[parentkey])
|
||||
if (currentnode === undefined) {
|
||||
currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey])
|
||||
|
||||
if (currentnode === undefined) {
|
||||
console.log("Could not find parent node for: ", allkeys[parentkey])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (handled.includes(currentnode.id)) {
|
||||
continue
|
||||
} else {
|
||||
handled.push(currentnode.id);
|
||||
results.push(currentnode);
|
||||
}
|
||||
|
||||
// Get the name / label here too?
|
||||
if (currentnode.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: This part is only handling first level,
|
||||
// but needs to recurse
|
||||
var incomingEdges = []
|
||||
for (var branchkey in workflow.branches) {
|
||||
const branch = workflow.branches[branchkey]
|
||||
if (branch.destination_id !== currentnode.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Go up in the levels
|
||||
const parents = getParentNodes(workflow, {
|
||||
id: branch.source_id,
|
||||
})
|
||||
if (parents.length > 0) {
|
||||
incomingEdges = incomingEdges.concat(parents)
|
||||
}
|
||||
|
||||
incomingEdges.push(branch)
|
||||
}
|
||||
|
||||
for (let i = 0; i < incomingEdges.length; i++) {
|
||||
var tmp = incomingEdges[i];
|
||||
if (tmp.decorator === true) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!allkeys.includes(tmp.source_id)) {
|
||||
allkeys.push(tmp.source_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length === allkeys.length || iterations === maxiter) {
|
||||
break
|
||||
}
|
||||
|
||||
iterations += 1
|
||||
}
|
||||
|
||||
// Remove on the end as we don't want to remove everything
|
||||
results = results.filter((data) => data.id !== action.id)
|
||||
results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input" || data.app_name === "shuffle-subflow")
|
||||
results.push({ label: "Execution Argument", type: "INTERNAL" })
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
const WorkflowValidationTimeline = (props) => {
|
||||
const { workflow, originalWorkflow, apps, getParents, execution} = props
|
||||
|
||||
|
||||
if (workflow === undefined || workflow === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (workflow.validation === undefined || workflow.validation === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (workflow.actions === undefined || workflow.actions === null) {
|
||||
workflow.actions = []
|
||||
}
|
||||
|
||||
if (workflow.triggers === undefined || workflow.triggers === null) {
|
||||
workflow.triggers = []
|
||||
|
||||
}
|
||||
|
||||
if (workflow.branches === undefined || workflow.branches === null) {
|
||||
workflow.branches = []
|
||||
|
||||
}
|
||||
|
||||
var results = []
|
||||
if (execution !== undefined) {
|
||||
results = execution.results
|
||||
}
|
||||
|
||||
// 1. Find startnode
|
||||
// 2. Map childnodes from it
|
||||
const startnodeId = workflow.start
|
||||
|
||||
// Find parent of startnodeId and if it's a webhook
|
||||
var relevantactions = []
|
||||
for (var key in workflow.branches) {
|
||||
const branch = workflow.branches[key]
|
||||
if (branch.destination_id !== startnodeId) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (var triggerkey in workflow.triggers) {
|
||||
const trigger = workflow.triggers[triggerkey]
|
||||
if (trigger.trigger_type !== "WEBHOOK") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (trigger.id === branch.source_id) {
|
||||
trigger.order = -1
|
||||
relevantactions.push(trigger)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (getParents !== undefined) {
|
||||
for (var key in workflow.actions) {
|
||||
const action = workflow.actions[key]
|
||||
if (action.id === startnodeId) {
|
||||
action.order = 0
|
||||
relevantactions.push(action)
|
||||
continue
|
||||
}
|
||||
|
||||
const parents = getParents(action)
|
||||
//const parents = getParentNodes(workflow, action)
|
||||
//console.log("PARENTS", key, parents)
|
||||
if (parents !== undefined && parents !== null) {
|
||||
const parentfound = parents.find((element) => element.id === startnodeId)
|
||||
if (parentfound !== undefined) {
|
||||
|
||||
// FIXME: add order here based on how many steps away from the startnode
|
||||
// This just has the parent count
|
||||
action.order = parents.length
|
||||
|
||||
relevantactions.push(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var key in workflow.triggers) {
|
||||
const trigger = workflow.triggers[key]
|
||||
if (trigger.trigger_type !== "SUBFLOW" && trigger.trigger_type !== "USERINPUT") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (workflow.actions.find((element) => element.id === trigger.id) === undefined) {
|
||||
workflow.actions.push(trigger)
|
||||
}
|
||||
}
|
||||
|
||||
relevantactions = workflow.actions
|
||||
}
|
||||
|
||||
// Sort according to how many parents a node has. MAY be wrong~
|
||||
relevantactions.sort((a, b) => {
|
||||
if (a.order === undefined) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (b.order === undefined) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return a.order - b.order
|
||||
})
|
||||
|
||||
// FIXME: Add other relevant items as well from subflows (?)
|
||||
var nodecolor = "grey"
|
||||
var branchcolor = "grey"
|
||||
var skipped = false
|
||||
|
||||
var previousTools = false
|
||||
|
||||
return (
|
||||
<div style={{border: workflow.validation.valid ? `2px solid ${green}` : "1px solid rgba(255,255,255,0.4)", padding: "10px 20px 10px 20px", borderRadius: theme.palette.borderRadius, }}>
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{relevantactions.map((action, index) => {
|
||||
action.result = {}
|
||||
if (results !== undefined) {
|
||||
const foundResult = results.find((element) => element.action.id === action.id)
|
||||
if (foundResult !== undefined) {
|
||||
action.result = foundResult
|
||||
|
||||
action.status = foundResult.status
|
||||
}
|
||||
}
|
||||
|
||||
const lastitem = index === relevantactions.length - 1
|
||||
if (!lastitem) {
|
||||
if (action.app_name === "Shuffle Tools") {
|
||||
if (action.status === "SUCCESS") {
|
||||
branchcolor = red
|
||||
|
||||
// Check action.result for the actual status
|
||||
const validate = validateJson(action.result.result)
|
||||
if (validate.valid) {
|
||||
if (validate.result.success === true) {
|
||||
branchcolor = green
|
||||
} else {
|
||||
branchcolor = "grey"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (action.status === "SKIPPED") {
|
||||
branchcolor = "grey"
|
||||
} else {
|
||||
if (action.status === undefined) {
|
||||
branchcolor = green
|
||||
} else {
|
||||
branchcolor = red
|
||||
}
|
||||
}
|
||||
|
||||
previousTools = true
|
||||
return null
|
||||
} else {
|
||||
if (action.status === "SUCCESS") {
|
||||
nodecolor = green
|
||||
} else if (action.status === "SKIPPED") {
|
||||
nodecolor = "grey"
|
||||
} else {
|
||||
if (action.status === undefined) {
|
||||
nodecolor = green
|
||||
} else {
|
||||
nodecolor = red
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nodecolor = "grey"
|
||||
}
|
||||
|
||||
if (action.status === "SKIPPED") {
|
||||
skipped = true
|
||||
}
|
||||
|
||||
var image = ""
|
||||
if (action.large_image !== undefined && action.large_image !== null && action.large_image !== "") {
|
||||
image = action.large_image
|
||||
} else {
|
||||
if (originalWorkflow !== undefined) {
|
||||
for (var key in originalWorkflow.actions) {
|
||||
if (originalWorkflow.actions[key].id === action.id) {
|
||||
image = originalWorkflow.actions[key].large_image
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (image === "") {
|
||||
for (var key in originalWorkflow.triggers) {
|
||||
if (originalWorkflow.triggers[key].id === action.id) {
|
||||
image = originalWorkflow.triggers[key].large_image
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var founderror = ""
|
||||
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null) {
|
||||
const foundError = workflow.validation.errors.find((element) => element.action_id === action.id)
|
||||
if (foundError !== undefined) {
|
||||
founderror = foundError.error
|
||||
nodecolor = yellow
|
||||
branchcolor = yellow
|
||||
}
|
||||
}
|
||||
|
||||
if (skipped && !lastitem) {
|
||||
nodecolor = "grey"
|
||||
branchcolor = "grey"
|
||||
}
|
||||
|
||||
if (previousTools) {
|
||||
previousTools = false
|
||||
} else {
|
||||
branchcolor = nodecolor
|
||||
}
|
||||
|
||||
var appgroup = []
|
||||
if (action.trigger_type === "WEBHOOK") {
|
||||
nodecolor = green
|
||||
branchcolor = green
|
||||
} else if (action.app_name === "shuffle-subflow") {
|
||||
if (action.status === "SUCCESS") {
|
||||
nodecolor = green
|
||||
branchcolor = green
|
||||
}
|
||||
|
||||
for (var subflowkey in workflow.validation.subflow_apps) {
|
||||
const subflowApp = workflow.validation.subflow_apps[subflowkey]
|
||||
if (subflowApp.error === action.id) {
|
||||
appgroup.push(subflowApp)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3
|
||||
const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : ""
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", flex: flex, justifyContent: "right", }}>
|
||||
{lastitem ?
|
||||
<Tooltip title={branchTooltip}>
|
||||
<div style={{marginLeft: 0, marginRight: 0, marginTop: 20, height: 3, width: "100%", backgroundColor: branchcolor, }} />
|
||||
</Tooltip>
|
||||
: null}
|
||||
|
||||
{appgroup.length > 0 ?
|
||||
<AvatarGroup max={4} style={{height: itemHeight+8, }}>
|
||||
{appgroup.map((subflowApp, subflowIndex) => {
|
||||
var appimage = ""
|
||||
if (apps !== undefined && apps !== null && apps.length > 0) {
|
||||
for (var key in apps) {
|
||||
const app = apps[key]
|
||||
if (app.name === subflowApp.app_name) {
|
||||
appimage = apps[key].large_image
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={`App: ${subflowApp.app_name} - subflow app`}>
|
||||
<Avatar
|
||||
style={{
|
||||
border: `4px solid ${nodecolor}`,
|
||||
}}
|
||||
src={appimage}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</AvatarGroup>
|
||||
:
|
||||
<Tooltip title={
|
||||
<Typography variant="body1" style={{margin: 5, color: "white", }}>
|
||||
{founderror.length > 0 ? founderror : ``}
|
||||
</Typography>
|
||||
} placement="top">
|
||||
|
||||
<Avatar
|
||||
variant="round"
|
||||
sx={{ backgroundColor: nodecolor, width: itemHeight, height: itemHeight, borderRadius: 50, border: `4px solid ${nodecolor}`,}}
|
||||
>
|
||||
{image !== "" ?
|
||||
<img
|
||||
src={image}
|
||||
style={{
|
||||
width: itemHeight,
|
||||
height: itemHeight,
|
||||
}}
|
||||
/>
|
||||
: null}
|
||||
</Avatar>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
{lastitem ? null :
|
||||
<Tooltip title={branchTooltip}>
|
||||
<div style={{marginLeft: 0, marginRight: 0, marginTop: 20, height: 3, width: "100%", backgroundColor: branchcolor, }} />
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowValidationTimeline
|
||||
@@ -5385,13 +5385,17 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||
<ListItemText
|
||||
primary=
|
||||
<Tooltip title={data.validation.valid === true ? "Valid. Click to explore." : "Configuration failed. Click to learn why"} placement="top">
|
||||
<Tooltip title={data.validation !== null && data.validation !== undefined && data.validation.valid === true ? "Valid. Click to explore." : "Configuration failed. Click to learn why"} placement="top">
|
||||
<IconButton>
|
||||
{validIcon}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
style={{ minWidth: 65, maxWidth: 65, }}
|
||||
onClick={() => {
|
||||
if (data.validation === null || data.validation === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (data.validation.workflow_id === undefined || data.validation.workflow_id === null || data.validation.workflow_id.length === 0) {
|
||||
toast.warn("No workflow runs found for this auth yet. Check back later.")
|
||||
return
|
||||
|
||||
@@ -139,6 +139,7 @@ import Draggable from "react-draggable";
|
||||
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
|
||||
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
||||
|
||||
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
|
||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||
import { GetParsedPaths, internalIds, } from "../views/Apps.jsx";
|
||||
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
|
||||
@@ -319,6 +320,7 @@ export function SetJsonDotnotation(jsonInput, inputKey) {
|
||||
|
||||
export const green = "#86c142";
|
||||
export const yellow = "#FECC00";
|
||||
export const red = "red";
|
||||
|
||||
export function removeParam(key, sourceURL) {
|
||||
if (sourceURL === undefined) {
|
||||
@@ -1641,7 +1643,6 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
setExecutionData(responseJson)
|
||||
} else {
|
||||
if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING" || responseJson.status === "FINISHED") {
|
||||
console.log("DONE!")
|
||||
stop()
|
||||
}
|
||||
|
||||
@@ -6732,7 +6733,6 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
const parentlabel = parentNode.data("label").toLowerCase().replace(" ", "_")
|
||||
const parentname = parentNode.data("app_name").toLowerCase().replace(" ", "_")
|
||||
if (!parentlabel.startsWith(parentname)+"_") {
|
||||
console.log("Return 1")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7135,7 +7135,6 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
// Find how many executions it has
|
||||
var executions = 0
|
||||
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
|
||||
console.log("Matches: ", matchingExecutions.length)
|
||||
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
|
||||
const decoratorNode = {
|
||||
position: {
|
||||
@@ -10204,6 +10203,7 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
|
||||
// Starts on current node and climbs UP the tree to the root object.
|
||||
// Sends back everything in it's path
|
||||
// FIXME: Use the GetParentNodes in WorkflowValidationTimeline.jsx instead
|
||||
const getParents = (action) => {
|
||||
if (action === undefined || action === null) {
|
||||
return []
|
||||
@@ -18720,6 +18720,7 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
: null}
|
||||
|
||||
</div>
|
||||
|
||||
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
|
||||
<div style={{ display: "flex", marginLeft: 10, }}>
|
||||
<Typography variant="body1">
|
||||
@@ -18831,12 +18832,31 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
{new Date(executionData.completed_at * 1000).toLocaleString("en-GB")}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
) : null}
|
||||
|
||||
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.status !== "EXECUTING" ?
|
||||
<div style={{marginTop: 5, marginBottom: 5, }}>
|
||||
<WorkflowValidationTimeline
|
||||
originalWorkflow={workflow}
|
||||
|
||||
apps={apps}
|
||||
workflow={executionData.workflow}
|
||||
getParents={getParents}
|
||||
|
||||
execution={executionData}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<div style={{ marginTop: 10 }} />
|
||||
{executionData.execution_argument !== undefined &&
|
||||
executionData.execution_argument.length > 0
|
||||
|
||||
{executionData.execution_argument !== undefined && executionData.execution_argument !== null &&
|
||||
executionData.execution_argument.length > 1
|
||||
? parsedExecutionArgument()
|
||||
: null}
|
||||
:
|
||||
null}
|
||||
|
||||
<Divider
|
||||
style={{
|
||||
backgroundColor: "rgba(255,255,255,0.6)",
|
||||
@@ -18844,6 +18864,7 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
marginBottom: 20,
|
||||
}}
|
||||
/>
|
||||
|
||||
{executionData.results !== undefined &&
|
||||
executionData.results !== null &&
|
||||
executionData.results.length > 1 &&
|
||||
@@ -19538,7 +19559,7 @@ const releaseToConnectLabel = "Release to Connect"
|
||||
if (stringjson.includes("\n") && !stringjson.includes("\n")) {
|
||||
return "Looks like you have a newline problem. Consider using the | replace: '\n', '\\n' }} filter in Liquid."
|
||||
} else {
|
||||
return "The result looks like it should be JSON, but is invalid. Look for potential"
|
||||
return "The result looks like it should be JSON, but is invalid. Look for potential single quotes instead of double quotes, missing commas or newlines"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +178,51 @@ func getThisContainerId() {
|
||||
}
|
||||
|
||||
func cleanupExistingNodes(ctx context.Context) error {
|
||||
|
||||
if isKubernetes == "true" {
|
||||
if kubernetesNamespace == "" {
|
||||
kubernetesNamespace = "default"
|
||||
}
|
||||
|
||||
clientset, _, err := shuffle.GetKubernetesClient()
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Error getting kubernetes client:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all pods
|
||||
pods, err := clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed listing pods: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
err := clientset.CoreV1().Pods(kubernetesNamespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed deleting pod %s: %s", pod.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all services
|
||||
services, err := clientset.CoreV1().Services(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed listing services: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range services.Items {
|
||||
err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Cleaned up all pods and services in namespace %s", kubernetesNamespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
serviceListOptions := types.ServiceListOptions{}
|
||||
services, err := dockercli.ServiceList(
|
||||
context.Background(),
|
||||
@@ -1662,11 +1707,12 @@ func main() {
|
||||
if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" {
|
||||
if isKubernetes != "true" {
|
||||
checkSwarmService(ctx)
|
||||
log.Printf("[DEBUG] Cleaning up containers from previous run")
|
||||
cleanupExistingNodes(ctx)
|
||||
time.Sleep(time.Duration(5) * time.Second)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Cleaning up containers from previous run")
|
||||
cleanupExistingNodes(ctx)
|
||||
time.Sleep(time.Duration(5) * time.Second)
|
||||
|
||||
log.Printf("[DEBUG] Deploying worker image %s to swarm", workerImage)
|
||||
|
||||
runString := "Run: \"docker service ls\" for more info"
|
||||
|
||||
Reference in New Issue
Block a user