diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index fbdf8b97..af5ee982 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -3,7 +3,7 @@ name: dockerbuild on: push: branches: - - main + - 2.0.0 paths: - "**" - "!.github/**" @@ -11,7 +11,7 @@ on: - "!docker-compose.yml" jobs: main: - runs-on: ubuntu-latest + runs-on: ubuntu-nightly continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false @@ -19,23 +19,23 @@ jobs: include: - app: frontend path: frontend - version: 1.4.0 + version: nightly experimental: true - app: backend path: backend - version: 1.4.0 + version: nightly experimental: true - app: app_sdk path: backend/app_sdk - version: 1.4.0 + version: nightly experimental: true - app: orborus path: functions/onprem/orborus - version: 1.4.0 + version: nightly experimental: true - app: worker path: functions/onprem/worker - version: 1.4.0 + version: nightly experimental: true steps: - name: Checkout @@ -77,11 +77,11 @@ jobs: cache-to: type=local,dest=/tmp/.buildx-cache tags: | ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }} - ghcr.io/shuffle/shuffle-${{ matrix.app }}:latest + ghcr.io/shuffle/shuffle-${{ matrix.app }}:nightly ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }} - ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:latest + ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:nightly frikky/shuffle-${{ matrix.app }}:${{ matrix.version }} - frikky/shuffle-${{ matrix.app }}:latest + frikky/shuffle-${{ matrix.app }}:nightly frikky/shuffle:${{ matrix.app }} - name: Image digest diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 15338d89..6ef571aa 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -928,7 +928,7 @@ class AppBase: #self.action = action loopnames = [] - #self.logger.info(f"Baseparams to check!!: {baseparams}") + self.logger.info(f"Baseparams to check: {baseparams}") for key, value in baseparams.items(): check_value = "" for param in self.original_action["parameters"]: @@ -991,51 +991,71 @@ class AppBase: except: pass - #self.logger.info(f"MERGE: {should_merge}") + #self.logger.info(f"VALUE LENGTH: {len(value)}") if isinstance(value, list): + #subvalue = [] + # Override for single vs multi items + #if len(value) > 0: + # if isinstance(value[0], list) and len(value[0]) == 1: + # subvalue = value[0] + + # subvalue = value[0] + + if len(value) <= 1: + # FIXME: This broke some shit for a single item fml + # Necessary as override again :( if len(value) == 1: baseparams[key] = value[0] #if "#" in check_value: # should_merge = True else: + #if len(value) > 1: if not should_merge: self.logger.info("[DEBUG] Adding WITHOUT looping list") else: if len(value) not in listlengths: listlengths.append(len(value)) + #listlength listitems.append( { key: len(value) } ) - + all_list_keys.append(key) all_lists.append(baseparams[key]) else: #self.logger.info(f"{value} is not a list") pass - self.logger.info("[DEBUG] Listlengths: %s" % listlengths) + self.logger.info("[DEBUG] Listlengths: %s - listitems: %d" % (listlengths, len(listitems))) + #if len(listitems) == 0: if len(listlengths) == 0: self.logger.info("[DEBUG] NO multiplier. Running a single iteration.") paramlist.append(baseparams) + + #elif len(listitems) == 1: elif len(listlengths) == 1: + self.logger.info("All subitems are the same length") + for item in listitems: # This loops should always be length 1 for key, value in item.items(): - if isinstance(value, int): - if len(paramlist) == value: - for subloop in range(value): - baseitem = copy.deepcopy(baseparams) - paramlist[subloop][key] = baseparams[key][subloop] - else: - for subloop in range(value): - baseitem = copy.deepcopy(baseparams) - baseitem[key] = baseparams[key][subloop] - paramlist.append(baseitem) + if not isinstance(value, int): + continue + + if len(paramlist) == value: + for subloop in range(value): + baseitem = copy.deepcopy(baseparams) + paramlist[subloop][key] = baseparams[key][subloop] + else: + for subloop in range(value): + baseitem = copy.deepcopy(baseparams) + baseitem[key] = baseparams[key][subloop] + paramlist.append(baseitem) else: newlength = 1 @@ -1046,10 +1066,14 @@ class AppBase: self.logger.info("[DEBUG] Newlength of array: %d. Lists: %s" % (newlength, all_lists)) # Get the cartesian product of the arrays #cartesian = await self.cartesian_product(all_lists) - cartesian = self.cartesian_product(all_lists) - newlist = [] - for item in cartesian: - newlist.append(list(item)) + try: + cartesian = self.cartesian_product(all_lists) + newlist = [] + for item in cartesian: + newlist.append(list(item)) + except Exception as e: + self.logger.info(f"[ERROR] Error in cartesian product: {e}") + newlist = [] newobject = {} for subitem in range(len(newlist)): @@ -1059,7 +1083,7 @@ class AppBase: paramlist.append(baseitem) - #self.logger.info("PARAMLIST: %s" % paramlist) + self.logger.info("CARTESIAN PARAMLIST: %s" % paramlist) #newlist[subitem[0]] #if len(newlist) > 0: @@ -1070,14 +1094,14 @@ class AppBase: #self.logger.info("Listlengths: %s" % listlengths) #paramlist = [baseparams] - #self.logger.info("[INFO] Return paramlist: %s" % paramlist) + #self.logger.info("[INFO] Return paramlist (1): %s" % paramlist) return paramlist # Runs recursed versions with inner loops and such #async def run_recursed_items(self, func, baseparams, loop_wrapper): def run_recursed_items(self, func, baseparams, loop_wrapper): - #self.logger.info(f"RECURSED ITEMS: {baseparams}") + self.logger.info(f"PRE RECURSED ITEMS: {baseparams}") has_loop = False newparams = {} @@ -1085,29 +1109,30 @@ class AppBase: if isinstance(value, list) and len(value) > 0: self.logger.info(f"[DEBUG] In list check for {key}") - try: - # Added skip for body (OpenAPI) which uses data= in requests - # Can be screwed up if they name theirs body too - if key != "body": - value[0] = json.loads(value[0]) - except json.decoder.JSONDecodeError as e: - pass - except TypeError as e: - pass + for value_index in range(len(value)): + try: + # Added skip for body (OpenAPI) which uses data= in requests + # Can be screwed up if they name theirs body too + if key != "body": + value[value_index] = json.loads(value[value_index]) + except json.decoder.JSONDecodeError as e: + pass + except TypeError as e: + pass try: - if isinstance(value, list) and len(value) == 1 and isinstance(value[0], list): - try: - loop_wrapper[key] += 1 - except Exception as e: - self.logger.info("[WARNING] Exception in loop wrapper: {e}") - loop_wrapper[key] = 1 + #if isinstance(value, list) and len(value) == 1 and isinstance(value[0], list): + # try: + # loop_wrapper[key] += 1 + # except Exception as e: + # self.logger.info(f"[WARNING] Exception in loop wrapper: {e}") + # loop_wrapper[key] = 1 - newparams[key] = value[0] - has_loop = True - else: + # newparams[key] = value[0] + # has_loop = True + #else: #self.logger.info(f"Key {key} is NOT a list within a list. Value: {value}") - newparams[key] = value + newparams[key] = value except Exception as e: self.logger.info(f"[WARNING] Error in baseparams list: {e}") newparams[key] = value @@ -1119,19 +1144,18 @@ class AppBase: #ret = await self.run_recursed_items(func, newparams, loop_wrapper) ret = self.run_recursed_items(func, newparams, loop_wrapper) else: - #self.logger.info(f"[DEBUG] Should run multiplier check with params (inner): {newparams}") - self.logger.info(f"[DEBUG] Should run multiplier check with params (inner)") + self.logger.info(f"[DEBUG] Should run multiplier check with params (inner): {newparams}") + #self.logger.info(f"[DEBUG] Should run multiplier check with params (inner)") + # 1. Find the loops that are required and create new multipliers # If here: check for multipliers within this scope. ret = [] - #param_multiplier = await self.get_param_multipliers(newparams) param_multiplier = self.get_param_multipliers(newparams) #self.logger.info("PARAM MULTIPLIER: %s" % param_multiplier) # FIXME: This does a deduplication of the data new_params = self.validate_unique_fields(param_multiplier) - #self.logger.info(f"NEW PARAMS: {new_params}") if len(new_params) == 0: self.logger.info("[WARNING] SHOULD STOP MULTI-EXECUTION BECAUSE FIELDS AREN'T UNIQUE") self.action_result = { @@ -1151,9 +1175,10 @@ class AppBase: return else: #subparams = new_params - #self.logger.info(f"NEW PARAMS: {new_params}") param_multiplier = new_params + #self.logger.info(f"NEW PARAM MULTIPLIER: {param_multiplier}") + #if isinstance(new_params, list) and len(new_params) == 1: # params = new_params[0] #else: @@ -1205,7 +1230,8 @@ class AppBase: tmp = json.dumps({ "success": False, - "reason": f"An error occured during execution: {e}", + "reason": f"An error occured during the App Function Run (not Shuffle)", + "details": f"{e}", }) @@ -1255,7 +1281,7 @@ class AppBase: #else: ret.append(new_value) - self.logger.info("[INFO] Ret length: %d" % len(ret)) + self.logger.info("[INFO] Function return length: %d" % len(ret)) if len(ret) == 1: #ret = ret[0] self.logger.info("[DEBUG] DONT make list of 1 into 0!!") @@ -1953,11 +1979,11 @@ class AppBase: try: #self.logger.info(f"[WARNING] INITIAL Parsing bug for length in app sdk: {e}") # data = data.replace("\'", "\"") - data = data.replace("True", "true") - data = data.replace("False", "false") - data = data.replace("None", "null") - data = data.replace("\"", "\\\"") - data = data.replace("'", "\"") + data = data.replace("True", "true", -1) + data = data.replace("False", "false", -1) + data = data.replace("None", "null", -1) + data = data.replace("\"", "\\\"", -1) + data = data.replace("'", "\"", -1) tmp_len = json.loads(data, parse_float=str, parse_int=str, parse_constant=str) except (NameError, KeyError, TypeError, json.decoder.JSONDecodeError) as e: @@ -2037,10 +2063,9 @@ class AppBase: # if result is a string then parse else return if isinstance(inner_result, str): - parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})", inner_result) + parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})", inner_result, 1) elif isinstance(inner_result, list): - parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})", - json.dumps(inner_result)) + parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})", json.dumps(inner_result), 1) else: parse_string = inner_result break @@ -3432,6 +3457,7 @@ class AppBase: # Loops in general goes in here to be parsed out as one->multi if len(actualitem) > 0: + self.logger.info(f"[INFO] Found {len(actualitem)} items in {parameter['name']}. MULTI EXEC.") multiexecution = True handled = False @@ -3612,10 +3638,12 @@ class AppBase: #self.logger.info() if not multiexecution: + self.logger.info("NOT MULTI EXEC") # Runs a single iteration here new_params = self.validate_unique_fields(params) if isinstance(new_params, list) and len(new_params) == 1: params = new_params[0] + #params = new_params else: #self.logger.info("[WARNING] SHOULD STOP EXECUTION BECAUSE FIELDS AREN'T UNIQUE") self.action_result["status"] = "SKIPPED" @@ -3663,7 +3691,7 @@ class AppBase: newres = { "success": False, "reason": "Iteration count more than 10. This happens if the input to the action is wrong. Try remaking the action, and contact support@shuffler.io if this persists.", - "details": found_error, + "details": f"{found_error}", } break @@ -3897,7 +3925,7 @@ class AppBase: self.action_result["result"] = json.dumps({ "success": False, "reason": f"Typeerror. Most likely due to a list that should've been a string. See details for more info.", - "details": e, + "details": f"{e}", }) #self.action_result["result"] = "TypeError: %s" % str(e) else: @@ -3925,7 +3953,7 @@ class AppBase: self.action_result["result"] = json.dumps({ "success": False, "reason": f"Request error - failing silently. Details in detail section", - "details": e, + "details": f"{e}", }) except json.decoder.JSONDecodeError as e: self.action_result["result"] = f"Request error: {e}" @@ -3942,7 +3970,7 @@ class AppBase: self.action_result["result"] = json.dumps({ "success": False, "reason": f"General exception in the app. See shuffle action logs for more details.", - "details": e, + "details": f"{e}", }) # Send the result :) diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx index 434b604a..bd653e7d 100644 --- a/frontend/src/components/CacheView.jsx +++ b/frontend/src/components/CacheView.jsx @@ -117,7 +117,7 @@ const CacheView = (props) => { const deleteCache = (orgId, key) => { - toast("Attempting to delete Cache"); + //toast("Attempting to delete Cache"); // method: "DELETE", const method = "POST" diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 5678f61e..6a7b55b7 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -477,7 +477,7 @@ const ConfigureWorkflow = (props) => { setConfigureWorkflowModalOpen(false) } - setRequiredTriggers(requiredTriggers) + //setRequiredTriggers(requiredTriggers) setRequiredVariables(requiredVariables) setRequiredActions(newactions) } diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index 1d088170..0af0c9bc 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -62,8 +62,7 @@ const Header = (props) => { userdata, isMobile, serverside, - curpath, - billingInfo + billingInfo, } = props; const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); @@ -114,6 +113,10 @@ const Header = (props) => { window.location.host === "shuffler.io" || window.location.host === "localhost:5002"; + const curpath = (typeof window !== "undefined" && window.location && typeof window.location.pathname === "string") + ? window.location.pathname + : ""; + const clearNotifications = () => { // Don't really care about the logout @@ -784,7 +787,7 @@ const Header = (props) => { - + + + + + +
@@ -1514,14 +1535,15 @@ const Header = (props) => { */ const topbarHeight = showTopbar ? 40 : 0 - const topbar = !showTopbar ? null : - curpath === "/" || curpath.includes("/docs/") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" ? + const topbar = !isCloud || !showTopbar ? null : + curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" ?
- Shuffle 1.4 is out! Read more about  - - { + {/* Shuffle 1.4.0 is out! Read more about  */} + Shuffle now offers  + {/* + { ReactGA.event({ category: "landingpage", action: "click_header_features", @@ -1557,16 +1579,16 @@ const Header = (props) => { Pricing -  and  +  and  */} { ReactGA.event({ category: "landingpage", - action: "click_header_creators", + action: "click_header_training", label: "", }) - navigate("/creators") + navigate("/training") //if (window.drift !== undefined) { // window.drift.api.startInteraction({ interactionId: 341911 }) @@ -1574,7 +1596,7 @@ const Header = (props) => { // console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) //} }} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}> - Earning as a Creator + Public Training! @@ -1586,8 +1608,8 @@ const Header = (props) => { : null - return !isMobile ? -
+ return !isMobile ? ( +
{ backgroundColor: theme.palette.backgroundColor, }} > - {topbar} - -
- {loginTextBrowser} -
- {modalView} -
-
- : +
+ {loginTextBrowser} +
+ {modalView} + +
+ ) : ( {loginTextMobile} + ); }; export default Header; diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 40c043a4..1ebf81dd 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -18,6 +18,7 @@ import { ButtonBase, Tooltip, Select, + Autocomplete, MenuItem, Divider, Dialog, @@ -296,7 +297,17 @@ const AuthenticationOauth2 = (props) => { } - const handleOauth2Request = (client_id, client_secret, oauth_url, scopes, admin_consent, prompt) => { + const handleOauth2Request = (client_id, client_secret, oauth_url, scopes, admin_consent, prompt, skipScopeReplace) => { + + console.log("SKIP SCOPE: ", skipScopeReplace) + if (skipScopeReplace === false || skipScopeReplace === undefined) { + + console.log("Selected scopes: ", selectedScopes) + if (selectedScopes !== undefined && selectedScopes !== null && selectedScopes.length > 0) { + toast("Using your scopes instead of the default ones") + scopes = selectedScopes + } + } if ((authenticationType.redirect_uri === undefined || authenticationType.redirect_uri === null || authenticationType.redirect_uri.length === 0) && (authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0)) { console.log("No redirect URI found, and token URI found. Assuming client credentials flow and saving directly in the database") @@ -336,6 +347,7 @@ const AuthenticationOauth2 = (props) => { "value": tokenUri, }] + if (authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0) { if (authenticationType.grant_type === "client_credentials") { parsedFields.push({ @@ -502,6 +514,8 @@ const AuthenticationOauth2 = (props) => { getAppAuthentication(true, true, true); } + toast("Authentication successful!") + // This is more a guess than anything // Should be handled in getAppAuthentication() // in the parent component to make it accurate, @@ -635,9 +649,7 @@ const AuthenticationOauth2 = (props) => { }; const handleScopeChange = (event) => { - const { - target: { value }, - } = event; + const {target: { value }} = event; console.log("VALUE: ", value); @@ -972,13 +984,13 @@ const AuthenticationOauth2 = (props) => { } {allscopes === undefined || allscopes === null || allscopes.length === 0 ? null : "Scopes (access rights)"} + {allscopes === undefined || allscopes === null || allscopes.length === 0 ? null : (
- } - renderValue={(selected) => selected.join(", ")} MenuProps={MenuProps} - > - {allscopes.map((data, index) => { + options={allscopes} + getOptionLabel={(option) => option} + renderInput={(params) => { return ( - - -1} /> - - - ); - })} - +
+ {/* -1} />*/} + +
+ ) + }} + />
{((authenticationType.redirect_uri === undefined || authenticationType.redirect_uri === null || authenticationType.redirect_uri.length === 0) && (authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0)) ? null : @@ -1039,7 +1056,7 @@ const AuthenticationOauth2 = (props) => { "autoClose": 1500, }) - handleOauth2Request(clientId, clientSecret, oauthUrl, selectedScopes); + handleOauth2Request(clientId, clientSecret, oauthUrl, selectedScopes, undefined, true); }} color="primary" > diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 48f99556..29297082 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -621,7 +621,7 @@ const ParsedAction = (props) => { var helperText = "" var looperText = "" //const found = input_data.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) - var found = input_data.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) + var found = input_data.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_@-]+\.?){1}([a-zA-Z0-9#_@-]+\.?){0,}/g) if (found !== null && found !== undefined) { var new_occurences = [] diff --git a/frontend/src/components/RuntimeDebugger.jsx b/frontend/src/components/RuntimeDebugger.jsx index e0adad0d..9a91eb3e 100644 --- a/frontend/src/components/RuntimeDebugger.jsx +++ b/frontend/src/components/RuntimeDebugger.jsx @@ -821,7 +821,7 @@ const RuntimeDebugger = (props) => { }} onChange={(e)=>{handleQueryChange(e)}} color="primary" - placeholder="Search Workflow Name, Status, Execution Argument, Results.." + placeholder="Filter by Workflow Name, Status, Execution Argument, Results.." id="shuffle_search_field" />
diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index d00d9740..a23de3e5 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -580,12 +580,12 @@ const CodeEditor = (props) => { // var session = localcodedata.getSession(); //var code_lines = localcodedata.split('\n') - var code_lines = value.split('\n') var newMarkers = [] + var code_lines = value.split('\n') for (var i = 0; i < code_lines.length; i++) { - var current_code_line = code_lines[i]; - var variable_occurence = current_code_line.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g); + var current_code_line = code_lines[i] + var variable_occurence = current_code_line.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_@-]+\.?){1}([a-zA-Z0-9#_@-]+\.?){0,}/g); if (!variable_occurence) { continue; @@ -623,15 +623,26 @@ const CodeEditor = (props) => { var startCh = dollar_occurence[occ] var endCh = dollar_occurence[occ] + dollar_occurence_len[occ] - - newMarkers.push({ - startRow: i, - startCol: startCh, - endRow: i, - endCol: endCh, - className: correctVariable ? "good-marker" : "bad-marker", - type: "text", - }) + try { + newMarkers.push({ + startRow: i, + startCol: startCh, + endRow: i+1, + endCol: endCh+1, + className: correctVariable ? "good-marker" : "bad-marker", + type: "text", + }) + } catch (e) { + console.log("Error in color highlighting: ", e); + newMarkers.push({ + startRow: i, + startCol: startCh, + endRow: i, + endCol: endCh, + className: correctVariable ? "good-marker" : "bad-marker", + type: "text", + }) + } setMarkers(newMarkers) } @@ -643,7 +654,7 @@ const CodeEditor = (props) => { } setMarkers(newMarkers) - }; + } const replaceVariables = (swapVariable) => { // var updatedCode = localcodedata.slice(0,index) + "$" + str + localcodedata.slice(index+currentVariable.length+1,) @@ -686,7 +697,7 @@ const CodeEditor = (props) => { const expectedOutput = (input) => { //const found = input.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) - const found = input.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g) + const found = input.match(/[$]{1}([a-zA-Z0-9_@-]+\.?){1}([a-zA-Z0-9#_@-]+\.?){0,}/g) // Whelp this is inefficient af. Single loop pls // When the found array is empty. @@ -1448,34 +1459,11 @@ const CodeEditor = (props) => {
} - { - - }} - > - - - - - - { @@ -1536,7 +1524,7 @@ const CodeEditor = (props) => { setCurrentLine(cursorPosition.row) findIndex(cursorPosition.row, cursorPosition.column) - highlight_variables(localcodedata) + //highlight_variables(localcodedata) //console.log("VALUE CURSOR: ", value) }} onChange={(value, editor) => { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index fece7151..492e8ad9 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -378,7 +378,7 @@ const svgSize = 24; const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const AngularWorkflow = (defaultprops) => { - const { globalUrl, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; + const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; const referenceUrl = globalUrl + "/api/v1/hooks/"; //const alert = useAlert() let navigate = useNavigate(); @@ -540,6 +540,7 @@ const AngularWorkflow = (defaultprops) => { }) // New for generated stuff + const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ "id": "integration", "name": "Integration Framework", @@ -1357,20 +1358,20 @@ const AngularWorkflow = (defaultprops) => { return; } - trigger.parameters = []; + trigger.parameters = [] - const topic = document.getElementById('topic')?.value; - const bootstrapServers = document.getElementById('bootstrap_servers')?.value; - const groupId = document.getElementById('group_id')?.value; + const topic = document.getElementById('topic')?.value + const bootstrapServers = document.getElementById('bootstrap_servers')?.value + const groupId = document.getElementById('group_id')?.value //const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; if(topic) { trigger.parameters.push({ name: "topic", value: topic - }); + }) } else { - toast("please enter the topic name"); + toast("Please enter the topic name"); return; } @@ -3102,12 +3103,19 @@ const AngularWorkflow = (defaultprops) => { // don't redirect if it exists const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; var execFound = new URLSearchParams(cursearch).get("execution_id"); - if (execFound === null) { - toast(`You don't access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) - setTimeout(() => { - window.location.pathname = "/workflows"; - }, 2000); - } + var sessionToken = new URLSearchParams(cursearch).get("session_token"); + if (execFound === null && sessionToken === null) { + toast(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) + setTimeout(() => { + window.location.pathname = "/workflows"; + }, 2000); + } else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { + toast(`Injecting session token and reloading workflow..`) + setTimeout(() => { + setCookie("session_token", sessionToken, { path: "/" }); + window.location.href = "https://shuffler.io/workflows/3abdfb21-b40f-4e50-b855-ac0d62f83cbe"; + }, 2000); + } } } @@ -3573,20 +3581,28 @@ const AngularWorkflow = (defaultprops) => { const onNodeDragStop = (event, selectedAction) => { const nodedata = event.target.data(); if (nodedata.id === selectedAction.id) { - return; + //console.log("Same node, return") + return } if (nodedata.finished === false) { - return; + //console.log("Node is not finished, return") + return } const connected = event.target.connectedEdges().jsons() if (connected.length > 0 && connected !== undefined) { for (let connectkey in connected) { const edge = connected[connectkey] - //console.log("EDGE:", edge) - - //const edge = edgeBase.json() + if (edge.data.decorator && edge.data.label === releaseToConnectLabel) { + // Transform to normal edge + const currentedge = cy.getElementById(edge.data.id) + if (currentedge !== undefined && currentedge !== null) { + currentedge.data("decorator", false) + currentedge.data("label", "") + } + continue + } const sourcenode = cy.getElementById(edge.data.source) const destinationnode = cy.getElementById(edge.data.target) @@ -3804,26 +3820,141 @@ const AngularWorkflow = (defaultprops) => { } if (nodedata.id === selectedAction.id) { - return; + return } + + if ((nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT" || nodedata.type === "ACTION") && !nodedata.isStartNode) { + // Check if it already has any non-decorator branches attached to it + const branches = cy.elements('edge').jsons() + var branchFound = false + var decoratorIds = [] + for (var branchkey in branches) { + if (branches[branchkey].data.source === nodedata.id || branches[branchkey].data.target === nodedata.id) { + + if (branches[branchkey].data.decorator === true) { + + // Add the source/destination + if (branches[branchkey].data.source === nodedata.id) { + decoratorIds.push(branches[branchkey].data.target) + } else { + decoratorIds.push(branches[branchkey].data.source) + } + + continue + } + + branchFound = true + break + } + } + + if (!branchFound) { + //console.log("Found action during drag. Checking closest nodes as it doesn't have a valid branch") + var closestNode = null + var minDistance = 300 + + const draggedNode = event.target + const allnodes = cy.nodes().jsons() + for (var nodekey in allnodes) { + const node = allnodes[nodekey] + if (node.data.id === nodedata.id) { + continue + } + + // Decorators + if (node.data.attachedTo !== undefined) { + continue + } + + if (node.position === undefined || node.position === null || node.position.x === undefined || node.position.y === undefined) { + continue + } + + if (node.data.type !== "ACTION" && node.data.type !== "TRIGGER") { + continue + } + + const distance = Math.sqrt( + Math.pow(draggedNode.position('x') - node.position.x, 2) + + Math.pow(draggedNode.position('y') - node.position.y, 2) + ) + + if (decoratorIds.includes(node.data.id)) { + //console.log("Found existing decorator for: ", node.data.app_name, "Distance: ", distance) + + if (distance > 300) { + // Remove the branch + const edgeToRemove = cy.getElementById(branches[branchkey].data.id) + if (edgeToRemove !== null && edgeToRemove !== undefined) { + //console.log("Removing edge: ", edgeToRemove) + edgeToRemove.remove() + //decoratorIds.splice(decoratorIds.indexOf(node.data.id), 1) + break + } + } + } - /* - // Tried looking for the closest node by position. aStar path not working entirely. - console.log("NODE: ", event.target) - const closestNode = cy.elements().aStar({ - root: nodedata.id, - goal: 'node', - directed: false, - }) + if (distance < minDistance) { + minDistance = distance + closestNode = node + } + } - if (closestNode.found) { - console.log("No closest node found for: ", nodedata.id) - } else { - console.log("Closest: ", closestNode) - } - */ + if (closestNode !== null && closestNode !== undefined) { + //console.log("Closest node app: ", closestNode.data.app_name, "Distance: ", minDistance) + + /* + if (decoratorIds.length > 0) { + console.log("Decorators already exists. If within distance of 15 add to existing, otherwise remove old and add new: ", decoratorIds) + for (var decoratorkey in decoratorIds) { + const decoratorEdge = cy.getElementById(decoratorIds[decoratorkey]) + if (decoratorEdge === null || decoratorEdge === undefined) { + continue + } + + const sourceNode = cy.getElementById(decoratorEdge.data.source) + const targetNode = cy.getElementById(decoratorEdge.data.target) + + const distance = Math.sqrt( + Math.pow(draggedNode.position('x') - sourceNode.position('x'), 2) + + Math.pow(draggedNode.position('y') - sourceNode.position('y'), 2) + ) + + // Check plus minus 15 in distance from mindistance + if (distance > minDistance - 15 && distance < minDistance + 15) { + console.log("Within distance of 15, add to existing edge") + } else { + console.log("Outside distance of 15, remove old edge and add new") + } + + } + } + */ + + if (decoratorIds.length === 0) { + //const edgeCurve = calculateEdgeCurve(draggedNode.position(), closestNode.position) + //currentedge.style('control-point-distance', edgeCurve.distance) + //currentedge.style('control-point-weight', edgeCurve.weight) + + const newId = uuidv4() + cy.add({ + group: "edges", + data: { + decorator: true, + id: newId, + _id: newId, + source: closestNode.data.id, + target: nodedata.id, + label: releaseToConnectLabel, + conditions: [], + } + }) + } + } + } + } if ( originalLocation.x === 0 && @@ -3882,11 +4013,11 @@ const AngularWorkflow = (defaultprops) => { } // Ensure it only happens once - document.removeEventListener("mousemove", onMouseUpdate, false); - }; + document.removeEventListener("mousemove", onMouseUpdate, false) + } - document.addEventListener("mousemove", onMouseUpdate, false); - }; + document.addEventListener("mousemove", onMouseUpdate, false) + } useBeforeunload(() => { @@ -3899,7 +4030,7 @@ const AngularWorkflow = (defaultprops) => { document.removeEventListener("paste", handlePaste, true); } } - }); + }) // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once @@ -5012,11 +5143,10 @@ const AngularWorkflow = (defaultprops) => { // Checks for errors in edges when they're added const onEdgeAdded = (event) => { - setLastSaved(false); - const edge = event.target.data(); - - //console.log("edge added: ", edge) + const edge = event.target.data() + //console.log("EDGE ADDED!: ", edge) if (edge.source === undefined && edge.target === undefined) { + console.log("Edge source and target is undefined") return } @@ -5030,6 +5160,7 @@ const AngularWorkflow = (defaultprops) => { const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { + console.log("Source or destination node is undefined") } else { //console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) if (sourcenode.data("type") === "TRIGGER") { @@ -5042,10 +5173,10 @@ const AngularWorkflow = (defaultprops) => { console.log("Node: ", targetedge) if (targetedge !== -1) { - event.target.remove() //console.log("Found branch already!") toast.error("Triggers can have exactly one target node") + event.target.remove() return @@ -5066,6 +5197,10 @@ const AngularWorkflow = (defaultprops) => { } } + if (edge.decorator === true) { + console.log("Doing nothing to branch because decorator") + return + } var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target @@ -5105,15 +5240,14 @@ const AngularWorkflow = (defaultprops) => { } } - if ( - eventTarget.data("isDescriptor") === true || - eventTarget.data("type") === "COMMENT" - ) { + if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { console.log("Removing because of descriptor or comment") - event.target.remove(); - return; + event.target.remove() + return } + + setLastSaved(false) targetnode = -1; // Check if: @@ -5121,38 +5255,51 @@ const AngularWorkflow = (defaultprops) => { // dest == dest && source == source // backend: check all children? to stop recursion var found = false; - for (let branchkey in workflow.branches) { - if ( - workflow.branches[branchkey].destination_id === edge.source && - workflow.branches[branchkey].source_id === edge.target - ) { - toast("A branch in the opposite direction already exists"); - event.target.remove(); - found = true; - break; - } else if ( - workflow.branches[branchkey].destination_id === edge.target && - workflow.branches[branchkey].source_id === edge.source - ) { - //toast("That branch already exists"); - event.target.remove(); + const branches = cy.edges().jsons() + + const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true) + var startnodeId = workflow.start + if (startNode !== undefined && startNode !== null) { + startnodeId = startNode.data.id + } + + //for (let branchkey in workflow.branches) { + for (let branchkey in branches) { + const branch = branches[branchkey].data + + //if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { + if (branch.target === edge.source && branch.source === edge.target) { + toast("A branch in the opposite direction already exists") + event.target.remove() + found = true + break + + //} else if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) { + } else if (branch.target === edge.target && branch.source === edge.source) { + + if (branch.conditions === undefined) { + // Edgehandles + } else { + console.log("Removing because the same branch already exists") + event.target.remove() + + found = true + break + } + } else if (edge.target === startnodeId) { + targetnode = workflow.triggers.findIndex((data) => data.id === edge.source) - found = true; - break; - } else if (edge.target === workflow.start) { - targetnode = workflow.triggers.findIndex( - (data) => data.id === edge.source - ); if (targetnode === -1) { if (targetnode.type !== "TRIGGER") { - toast("Can't make arrow to starting node"); - event.target.remove(); - break; + toast("Can't make arrow to starting node") + event.target.remove() + break } found = true; } - } else if (edge.source === workflow.branches[branchkey].source_id) { + //} else if (edge.source === workflow.branches[branchkey].source_id) { + } else if (edge.source === branch.source) { // FIXME: Verify multi-target for triggers // 1. Check if destination exists // 2. Check if source is a trigger @@ -5196,7 +5343,6 @@ const AngularWorkflow = (defaultprops) => { newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); - //console.log("DST Autocompleter: ", dstdata); } var newbranch = { @@ -8036,7 +8182,6 @@ const AngularWorkflow = (defaultprops) => { borderRadius: theme.palette.borderRadius, width: imagesize, height: imagesize, - pointerEvents: "none", // Stretch if necessary objectFit: "cover", // Center the object @@ -10715,11 +10860,101 @@ const AngularWorkflow = (defaultprops) => { {/* Check if dest is the same as start */} - {conditionsDisabled ? - - Conditions are unavailable between triggers and the startnode. - - : null} + {conditionsDisabled ? + + Conditions are unavailable between triggers and the startnode. + + : null} + +
+ {/* + + + + */} + +
); }; @@ -11483,7 +11718,7 @@ const AngularWorkflow = (defaultprops) => { return transformedData; - }; + } const AppAuthSelector = ({ appAuthData }) => { const [selectedAuth, setSelectedAuth] = useState(""); @@ -11496,6 +11731,7 @@ const AngularWorkflow = (defaultprops) => { const handleShowingValue = (appName) => { let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) + console.log("LIST WITH VALUES: ", listWithValues) for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] @@ -12180,7 +12416,7 @@ const AngularWorkflow = (defaultprops) => { data: newbranch, }; - cy.add(cybranch); + cy.add(cybranch) } console.log("Value to be set: ", e.target.value); @@ -12722,25 +12958,27 @@ const AngularWorkflow = (defaultprops) => { + {/*
-
-
-
- Auth Override -
-
+
+
+
+ Auth Override +
+
-
-
- -
-
-
+
+
+ +
+
+
+ */} - ); + ) } return null; @@ -14697,12 +14935,8 @@ const AngularWorkflow = (defaultprops) => { right: 0, left: isMobile ? 20 : leftBarSize + 20, top: isMobile ? 30 : appBarSize + 20, - pointerEvents: "none", } - - - const TopCytoscapeBar = (props) => { if (workflow.public === true) { return null @@ -14718,7 +14952,7 @@ const AngularWorkflow = (defaultprops) => {
{

{workflow.name}

@@ -15244,6 +15477,8 @@ const AngularWorkflow = (defaultprops) => { } } + /* + // Infinitely annoying. Need a new bind if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -15255,7 +15490,8 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalView(0); } } - }; + */ + } document.addEventListener('keydown', handleKeyDown); @@ -17033,7 +17269,7 @@ const AngularWorkflow = (defaultprops) => {
{foundnotifications > 0 ? - + { @@ -17968,10 +18204,11 @@ const AngularWorkflow = (defaultprops) => { return "The queries or data sent to the API is most likely wrong (400). Check the body of the result for more information." } + /* if (result.status === 200 || result.status === 201 || result.status === 204) { return "It looks like the result was successful! If it didn't work, make sure to check if the body you are sending was correct." } - + */ // Validate and check for newlines if (result.success !== false) { @@ -19477,8 +19714,9 @@ const AngularWorkflow = (defaultprops) => {
Configuration options for {selectedOption}
- {selectedOption === "Kafka Queue" && ( - <> + + {selectedOption === "Kafka Queue" ? +
Topic { placeholder={"earliest"} defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''} /> */} - - )} +
+ : null} + + param.name === "bootstrap_servers")?.value) || ''} + /> +