diff --git a/backend/tests/migrate_db.sh b/backend/tests/migrate_db.sh new file mode 100644 index 00000000..d1cbd039 --- /dev/null +++ b/backend/tests/migrate_db.sh @@ -0,0 +1,2 @@ +curl -XPOST -v localhost:5001/api/v1/migrate_database -H 'Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4' + diff --git a/backend/tests/test_wrappers.py b/backend/tests/test_wrappers.py new file mode 100644 index 00000000..d6ec96c6 --- /dev/null +++ b/backend/tests/test_wrappers.py @@ -0,0 +1,210 @@ +import re +import json + +def parse_nested_param(string, level): + """ + Generate strings contained in nested (), indexing i = level + """ + if len(re.findall("\(", string)) == len(re.findall("\)", string)): + LeftRightIndex = [x for x in zip( + [Left.start()+1 for Left in re.finditer('\(', string)], + reversed([Right.start() for Right in re.finditer('\)', string)]))] + + elif len(re.findall("\(", string)) > len(re.findall("\)", string)): + return parse_nested_param(string + ')', level) + elif len(re.findall("\(", string)) < len(re.findall("\)", string)): + return parse_nested_param('(' + string, level) + + else: + return 'Failed to parse params' + + try: + return [string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]] + except IndexError: + return [string[LeftRightIndex[level+1][0]:LeftRightIndex[level+1][1]]] + +# Parses the deepest part +def maxDepth(S): + current_max = 0 + max = 0 + n = len(S) + + # Traverse the input string + for i in range(n): + if S[i] == '(': + current_max += 1 + + if current_max > max: + max = current_max + elif S[i] == ')': + if current_max > 0: + current_max -= 1 + else: + return -1 + + # finally check for unbalanced string + if current_max != 0: + return -1 + + return max-1 + +def parse_type(data, thistype): + if data == None: + return "Empty" + + if "int" in thistype: + try: + return int(data) + except ValueError: + print("ValueError while casting %s" % data) + return data + if "lower" in thistype: + return data.lower() + if "upper" in thistype: + return data.upper() + if "trim" in thistype: + return data.strip() + if "strip" in thistype: + return data.strip() + if "split" in thistype: + # Should be able to split anything + return data.split() + if "len" in thistype or "length" in thistype: + return len(data) + if "parse" in thistype: + splitvalues = [] + default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)""" + if "," in data: + splitvalues = data.split(",") + + for item in range(len(splitvalues)): + splitvalues[item] = splitvalues[item].strip() + else: + return default_error + + lastsplit = [] + if ":" in splitvalues[-1]: + lastsplit = splitvalues[-1].split(":") + else: + try: + lastsplit = [int(splitvalues[-1])] + except ValueError: + return default_error + + try: + parsedlist = ",".join(splitvalues[0:-1]) + print(parsedlist) + print(lastsplit) + + if len(lastsplit) > 1: + tmp = json.loads(parsedlist)[int(lastsplit[0]):int(lastsplit[1])] + else: + tmp = json.loads(parsedlist)[lastsplit[0]] + + print(tmp) + return tmp + except IndexError as e: + return default_error + +# Parses the INNER value and recurses until everything is done +def parse_wrapper(data): + try: + if "(" not in data or ")" not in data: + return data + except TypeError: + return data + + print("Running %s" % data) + + # Look for the INNER wrapper first, then move out + wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length"] + found = False + for wrapper in wrappers: + if wrapper not in data.lower(): + continue + + found = True + break + + if not found: + return data + + # Do stuff here. + innervalue = parse_nested_param(data, maxDepth(data)-0) + outervalue = parse_nested_param(data, maxDepth(data)-1) + print("INNER: ", outervalue) + print("OUTER: ", outervalue) + + if outervalue != innervalue: + #print("Outer: ", outervalue, " inner: ", innervalue) + for key in range(len(innervalue)): + # Replace OUTERVALUE[key] with INNERVALUE[key] in data. + print("Replace %s with %s in %s" % (outervalue[key], innervalue[key], data)) + data = data.replace(outervalue[key], innervalue[key]) + else: + for thistype in wrappers: + if thistype not in data.lower(): + continue + + parsed_value = parse_type(innervalue[0], thistype) + return parsed_value + + print("DATA: %s\n" % data) + return parse_wrapper(data) + +def parse_wrapper_start(data): + newdata = [] + newstring = "" + record = True + paranCnt = 0 + for char in data: + if char == "(": + paranCnt += 1 + + if not record: + record = True + + if record: + newstring += char + + if paranCnt == 0 and char == " ": + newdata.append(newstring) + newstring = "" + record = True + + if char == ")": + paranCnt -= 1 + + if paranCnt == 0: + record = False + + if len(newstring) > 0: + newdata.append(newstring) + + parsedlist = [] + non_string = False + for item in newdata: + ret = parse_wrapper(item) + if not isinstance(ret, str): + non_string = True + + parsedlist.append(ret) + + if len(parsedlist) > 0 and not non_string: + return " ".join(parsedlist) + elif len(parsedlist) == 1 and non_string: + return parsedlist[0] + else: + print("Casting back to string because multi: ", parsedlist) + newlist = [] + for item in parsedlist: + try: + newlist.append(str(item)) + except ValueError: + newlist.append("parsing_error") + return " ".join(newlist) + +data = "split(hello there)" +data = """parse(["testing", "what", "is this"], 0:2)""" +#data = "int(int(2))" +print("RET: ", parse_wrapper_start(data)) diff --git a/backend/tests/validate_app_values.sh b/backend/tests/validate_app_values.sh new file mode 100644 index 00000000..69cf9d50 --- /dev/null +++ b/backend/tests/validate_app_values.sh @@ -0,0 +1,14 @@ +# ExecutionOrg MUST be executing. +curl -XPOST http://localhost:5001/api/v1/orgs/b199646b-16d2-456d-9fd6-b9972e929466/validate_app_values -d '{ + "append": true, + "workflow_check": true, + "authorization": "1aae630c-ccaf-4cb5-87f9-8a9e0a9afd11", + "execution_ref": "c59ff288-4f02-4d02-b839-133d55c7fdf0", + "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", + "values": [{ + "app": "testing", + "action": "repeat_back_to_me", + "parameternames": ["call"], + "parametervalues": ["hey", "ho", "lets", "go"] + }] +}' diff --git a/frontend/src/components/NestedMenu.jsx b/frontend/src/components/NestedMenu.jsx new file mode 100644 index 00000000..cabf1b24 --- /dev/null +++ b/frontend/src/components/NestedMenu.jsx @@ -0,0 +1,207 @@ +import React, {useState, useRef, useImperativeHandle} from 'react' +import {makeStyles} from '@material-ui/core/styles' +import Menu, {MenuProps} from '@material-ui/core/Menu' +import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem' +import ArrowRight from '@material-ui/icons/ArrowRight' +import clsx from 'clsx' + +// + +//export interface NestedMenuItemProps { +// /** +// * Open state of parent ``, used to close decendent menus when the +// * root menu is closed. +// */ +// parentMenuOpen: boolean; +// /** +// * Component for the container element. +// * @default 'div' +// */ +// component: React.ElementType; +// /** +// * Effectively becomes the `children` prop passed to the `` +// * element. +// */ +// label: React.ReactNode; +// /** +// * @default +// */ +// rightIcon: React.ReactNode; +// /** +// * Props passed to container element. +// */ +// ContainerProps: React.HTMLAttributes; +// // &React.RefAttributes +// /** +// * Props passed to sub `` element +// */ +// MenuProps: Omit; +// /** +// * @see https://material-ui.com/api/list-item/ +// */ +// button: true; +//} + +const TRANSPARENT = 'rgba(0,0,0,0)' +const useMenuItemStyles = makeStyles((theme) => ({ + root: (props: any) => ({ + backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT + }) +})) + +/** + * Use as a drop-in replacement for `` when you need to add cascading + * menu elements as children to this component. + */ +//const NestedMenuItem = React.forwardRef( +const NestedMenuItem = (props, ref) => { + console.log(props, ref) + //function NestedMenuItem(props, ref) { + const { + parentMenuOpen, + component = 'div', + label, + rightIcon = , + children, + className, + tabIndex: tabIndexProp, + MenuProps = {}, + ContainerProps: ContainerPropsProp = {}, + ...MenuItemProps + } = props + + const [isSubMenuOpen, setIsSubMenuOpen] = useState(false) + + const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp + + + const menuItemRef = useRef(null) + useImperativeHandle(ref, () => menuItemRef.current) + const containerRef = useRef(null) + useImperativeHandle(containerRefProp, () => containerRef.current) + const menuContainerRef = useRef(null) + + console.log("PAST THIS: ", containerRefProp, menuItemRef, containerRef, menuContainerRef, ContainerProps) + + const handleMouseEnter = (event: React.MouseEvent) => { + setIsSubMenuOpen(true) + + if (ContainerProps?.onMouseEnter) { + ContainerProps.onMouseEnter(event) + } + } + const handleMouseLeave = (event: React.MouseEvent) => { + setIsSubMenuOpen(false) + + if (ContainerProps?.onMouseLeave) { + ContainerProps.onMouseLeave(event) + } + } + + // Check if any immediate children are active + const isSubmenuFocused = () => { + const active = containerRef.current?.ownerDocument?.activeElement + for (const child of menuContainerRef.current?.children ?? []) { + if (child === active) { + return true + } + } + return false + } + + const handleFocus = (event: React.FocusEvent) => { + if (event.target === containerRef.current) { + setIsSubMenuOpen(true) + } + + if (ContainerProps?.onFocus) { + ContainerProps.onFocus(event) + } + } + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Escape') { + return + } + + if (isSubmenuFocused()) { + event.stopPropagation() + } + + const active = containerRef.current?.ownerDocument?.activeElement + + if (event.key === 'ArrowLeft' && isSubmenuFocused()) { + containerRef.current?.focus() + } + + if ( + event.key === 'ArrowRight' && + event.target === containerRef.current && + event.target === active + ) { + console.log("MENU: ", menuContainerRef) + const firstChild = menuContainerRef.current.children[0] + console.log("FIRST: ", firstChild) + firstChild.focus() + } + } + + const open = isSubMenuOpen && parentMenuOpen + const menuItemClasses = useMenuItemStyles({open}) + + // Root element must have a `tabIndex` attribute for keyboard navigation + let tabIndex + if (!props.disabled) { + tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1 + } + + console.log("PAST 2! ", tabIndex) + + return ( +
+ + {label} + {rightIcon} + + { + setIsSubMenuOpen(false) + }} + > +
+ {children} +
+
+
+ ) +} + +export default NestedMenuItem