From 78bd8f29dfdcbd1f50a165adc5924afcbb198641 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 18 Oct 2024 10:31:23 +0200 Subject: [PATCH] Frontend rebuild with all new components --- frontend/package.json | 1 + frontend/src/components/DashboardBarchart.jsx | 144 +++ .../FixWorkflowValidationErrors.jsx | 687 +++++++++++++ frontend/src/components/RecentWorkflow.jsx | 157 +++ .../src/components/WorkflowTemplatePopup2.jsx | 901 ++++++++++++++++++ frontend/src/context/contextApi.js | 23 + 6 files changed, 1913 insertions(+) create mode 100644 frontend/src/components/DashboardBarchart.jsx create mode 100644 frontend/src/components/FixWorkflowValidationErrors.jsx create mode 100644 frontend/src/components/RecentWorkflow.jsx create mode 100644 frontend/src/components/WorkflowTemplatePopup2.jsx create mode 100644 frontend/src/context/contextApi.js diff --git a/frontend/package.json b/frontend/package.json index 635c079f..f03074d2 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/components/DashboardBarchart.jsx b/frontend/src/components/DashboardBarchart.jsx new file mode 100644 index 00000000..58f20708 --- /dev/null +++ b/frontend/src/components/DashboardBarchart.jsx @@ -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 ( + { + if (elements && elements.length > 0) { + //toast("Click event") + console.log("Clicked: ", elements) + } + }} + /> + ) +} + +export default DashboardBarchart; diff --git a/frontend/src/components/FixWorkflowValidationErrors.jsx b/frontend/src/components/FixWorkflowValidationErrors.jsx new file mode 100644 index 00000000..e223b2a1 --- /dev/null +++ b/frontend/src/components/FixWorkflowValidationErrors.jsx @@ -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 = + + } + + 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 : + + {actionRunInfo.result} + + } + placement="bottom" + > + {actionRunInfo.validation.valid === true ? + + : + + } + + + 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 = + } + + // 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 = + + } else if (authReturn === null) { + authReturn = "Other auth - Not implemented" + + } + + validationReturn = authReturn === null || foundAuth.id === undefined || foundAuth.id === null || foundAuth.id.length === 0 || !authFound ? null : + + + //resolveButton = !(Object.getOwnPropertyNames(actionRunInfo).length === 0 || actionRunInfo.validation.valid === true) ? null : + resolveButton = + + } + + + return ( +
+ {authReturn} +
+ {validationReturn} + + {resolveButton} +
+ ) + } + + console.log("Workflow validation: ", workflow.validation) + return ( +
+ {workflow.errors !== undefined && workflow.errors !== null ? +
+ General errors: {workflow.errors.length} + {workflow.errors.map((error, index) => { + return ( +
+ - {error} +
+ ) + })} +
+ : null} + + + + + {workflow.validation.errors !== undefined && workflow.validation.errors !== null ? +
+ Validation errors: {workflow.validation.errors.length} + {workflow.validation.errors.map((error, index) => { + return ( +
+ +
+ ) + })} +
+ : null} + + + Apps loaded: {apps.length} + +
+ ) +} + +export default FixWorkflowValidationErrors diff --git a/frontend/src/components/RecentWorkflow.jsx b/frontend/src/components/RecentWorkflow.jsx new file mode 100644 index 00000000..3bd804b4 --- /dev/null +++ b/frontend/src/components/RecentWorkflow.jsx @@ -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 tag or ![alt](src) markdown + // html > markdown + const imgTag = workflow.input_markdown.match(/]+>/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 ( +
setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + +
+ ) +} + +export default RecentWorkflow diff --git a/frontend/src/components/WorkflowTemplatePopup2.jsx b/frontend/src/components/WorkflowTemplatePopup2.jsx new file mode 100644 index 00000000..ddd0a47f --- /dev/null +++ b/frontend/src/components/WorkflowTemplatePopup2.jsx @@ -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 ( + { + 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", + }, + }} + > + { + setModalOpen(false); + }} + > + + + + + Configure Workflow + + + {title === undefined || title === null || title === "" ? null : + + + Selected Workflow: + +
+ + +
+
+ } + +
+ {/* Fix the timeline when errors are fixed.. how? */} + + + +
+ + {workflowLoading === true ? +
+ Generating the Workflow... + + +
+ : +
+ {usecaseDetails === undefined ? null : + + {usecaseDetails?.description} + + } + + {errorMessage !== "" ? errorMessage : ""} + + {showLoginButton ? + + + Sign up + + + + : + !showTryitOut && !isActive ? + + : null} +
+ } + + {!isLoggedIn ? null : +
+ {(appSetupDone === false && missingSource !== undefined || missingDestination !== undefined) ? + + {"Find relevant Apps for this Usecase"} + + : null} + + {(missingSource !== undefined) ? +
+ +
+ : null} + + {(missingDestination !== undefined) ? +
+ +
+ : null} +
+ } + + + +
+
+ ) + } + + if (isModalOpenDefault === true) { + return + } + + 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 ( +
+ + +
{ + 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) + } + }} + > + +
+ {shownColor !== undefined && shownColor !== null && shownColor !== "" ? +
+ : null} + +
+
+
+ {img1 !== undefined && img1 !== "" && srcapp !== undefined && srcapp !== "" ? + +
+ +
+
+ : +
+ } +
+ + + {img2 !== undefined && img2 !== "" && dstapp !== undefined && dstapp !== "" ? + +
+
+ +
+
+
+ : +
+ } + +
+
+ + {parsedTitle} + +
+
+
+ +
+ {isActive === true && errorMessage === "" ? + + + + : ""} + + {!isActive && hasInterest === true ? + + + + : null} +
+ + + {showTryitOut && !isActive ? + + + + : null} +
+ + +
+ ) +} + +export default WorkflowTemplatePopup diff --git a/frontend/src/context/contextApi.js b/frontend/src/context/contextApi.js new file mode 100644 index 00000000..b5506a49 --- /dev/null +++ b/frontend/src/context/contextApi.js @@ -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 ( + + {props.children} + + ) +}