Merge branch '2.0.0' of https://github.com/Shuffle/Shuffle into flipBranch

This commit is contained in:
monilprajapati
2024-07-04 17:28:31 +05:30
11 changed files with 1550 additions and 955 deletions
+76 -11
View File
@@ -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>
+5 -5
View File
@@ -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&nbsp;
<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>
&nbsp;or&nbsp;
<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>
&nbsp; - &nbsp;
<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>
&nbsp; - &nbsp;
<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>
:
+12 -1
View File
@@ -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>
+11 -11
View File
@@ -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`
+6 -6
View File
@@ -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&nbsp; */}
Shuffle now offers&nbsp;
Early Success! More&nbsp;
{/* <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>
&nbsp;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
+5 -1
View File
@@ -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