61c34e23 by Ean Schuessler

Fix MCP tool bugs: NPE in render, syntax in browse, and MNode usage

1 parent 64e674a9
...@@ -204,11 +204,15 @@ ...@@ -204,11 +204,15 @@
204 204
205 // Start from the longest possible XML path and work backwards 205 // Start from the longest possible XML path and work backwards
206 for (int i = pathParts.size(); i >= 1; i--) { 206 for (int i = pathParts.size(); i >= 1; i--) {
207 def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" 207 def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0]
208 def currentTry = "component://${componentName}/screen/${subPath}.xml"
208 if (ec.resource.getLocationReference(currentTry).getExists()) { 209 if (ec.resource.getLocationReference(currentTry).getExists()) {
209 bestPath = currentTry 210 bestPath = currentTry
211 // If we found a screen matching the full path, we're already at the target
210 if (i < pathParts.size()) { 212 if (i < pathParts.size()) {
211 bestSubscreen = pathParts[i..-1].join('_') 213 bestSubscreen = pathParts[i..-1].join('_')
214 } else {
215 bestSubscreen = null
212 } 216 }
213 break 217 break
214 } 218 }
...@@ -866,6 +870,7 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -866,6 +870,7 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
866 } 870 }
867 871
868 // Extract MCP-specific data when renderMode is "mcp" or "json" 872 // Extract MCP-specific data when renderMode is "mcp" or "json"
873 def mcpData = [:]
869 if ((renderMode == "mcp" || renderMode == "json") && finalScreenDef) { 874 if ((renderMode == "mcp" || renderMode == "json") && finalScreenDef) {
870 ec.logger.info("MCP Screen Execution: Extracting MCP data for ${screenPath}") 875 ec.logger.info("MCP Screen Execution: Extracting MCP data for ${screenPath}")
871 876
...@@ -886,14 +891,15 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -886,14 +891,15 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
886 def formNode = form.internalFormNode 891 def formNode = form.internalFormNode
887 if (formNode) { 892 if (formNode) {
888 // Extract field elements 893 // Extract field elements
889 def fields = formNode.'field' 894 def fields = formNode.children('field')
890 fields.each { field -> 895 fields.each { field ->
891 def fieldName = field.attribute('name') 896 def fieldName = field.attribute('name')
892 if (fieldName) { 897 if (fieldName && field) {
893 def fieldInfo = [name: fieldName, type: field.name()] 898 def fieldInfo = [name: fieldName]
899 if (field.getName()) fieldInfo.type = field.getName()
894 def value = ec.context.get(fieldName) ?: parameters?.get(fieldName) 900 def value = ec.context.get(fieldName) ?: parameters?.get(fieldName)
895 if (value) fieldInfo.value = value 901 if (value) fieldInfo.value = value
896 902
897 // Check if it's a widget with options 903 // Check if it's a widget with options
898 if (field.'drop-down' || field.'check' || field.'radio') { 904 if (field.'drop-down' || field.'check' || field.'radio') {
899 fieldInfo.widgetType = "selection" 905 fieldInfo.widgetType = "selection"
...@@ -1415,13 +1421,14 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1415,13 +1421,14 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1415 } 1421 }
1416 } else { 1422 } else {
1417 // Resolve simple path to component path using longest match and traversal 1423 // Resolve simple path to component path using longest match and traversal
1418 def pathParts = currentPath.split('\\.') 1424 def pathParts = path.split('\\.')
1419 def componentName = pathParts[0] 1425 def componentName = pathParts[0]
1420 def baseScreenPath = null 1426 def baseScreenPath = null
1421 def subParts = [] 1427 def subParts = []
1422 1428
1423 for (int i = pathParts.size(); i >= 1; i--) { 1429 for (int i = pathParts.size(); i >= 1; i--) {
1424 def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" 1430 def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0]
1431 def currentTry = "component://${componentName}/screen/${subPath}.xml"
1425 if (ec.resource.getLocationReference(currentTry).getExists()) { 1432 if (ec.resource.getLocationReference(currentTry).getExists()) {
1426 baseScreenPath = currentTry 1433 baseScreenPath = currentTry
1427 if (i < pathParts.size()) subParts = pathParts[i..-1] 1434 if (i < pathParts.size()) subParts = pathParts[i..-1]
...@@ -1480,12 +1487,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1480,12 +1487,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1480 def subscreenName = null 1487 def subscreenName = null
1481 1488
1482 for (int i = pathParts.size(); i >= 1; i--) { 1489 for (int i = pathParts.size(); i >= 1; i--) {
1483 def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" 1490 def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0]
1491 def currentTry = "component://${componentName}/screen/${subPath}.xml"
1484 if (ec.resource.getLocationReference(currentTry).getExists()) { 1492 if (ec.resource.getLocationReference(currentTry).getExists()) {
1485 screenPath = currentTry 1493 screenPath = currentTry
1494 // If we found a screen matching the full path, we're already at the target
1486 if (i < pathParts.size()) { 1495 if (i < pathParts.size()) {
1487 def remainingParts = pathParts[i..-1] 1496 def remainingParts = pathParts[i..-1]
1488 subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0] 1497 subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0]
1498 } else {
1499 subscreenName = null
1489 } 1500 }
1490 break 1501 break
1491 } 1502 }
...@@ -1498,24 +1509,10 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1498,24 +1509,10 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1498 } 1509 }
1499 } 1510 }
1500 1511
1501 // Get screen definition and look for matching transition 1512 // Get screen definition for finding transitions (use resolved screenPath directly)
1502 def screenDef = ec.screen.getScreenDefinition(screenPath) 1513 def screenDef = ec.screen.getScreenDefinition(screenPath)
1503 1514
1504 // Navigate to subscreen if needed 1515 // Store screenDef for later use - we don't navigate to subscreen for transition lookup
1505 if (subscreenName && screenDef) {
1506 def subItem = screenDef.getSubscreensItem(subscreenName)
1507 if (subItem && subItem.getLocation()) {
1508 screenDef = ec.screen.getScreenDefinition(subItem.getLocation())
1509 } else if (subscreenName) {
1510 def subItems = screenDef.getSubscreensItemsSorted()
1511 for (def sub in subItems) {
1512 if (sub.getName() == subscreenName) {
1513 screenDef = ec.screen.getScreenDefinition(sub.getLocation())
1514 break
1515 }
1516 }
1517 }
1518 }
1519 1516
1520 def foundTransition = null 1517 def foundTransition = null
1521 def actionParams = parameters ?: [:] 1518 def actionParams = parameters ?: [:]
...@@ -1534,48 +1531,73 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1534,48 +1531,73 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1534 message: "Form parameters submitted", 1531 message: "Form parameters submitted",
1535 parametersProcessed: actionParams.keySet() 1532 parametersProcessed: actionParams.keySet()
1536 ] 1533 ]
1537 } else if (screenDef && screenDef.hasTransitions()) { 1534 } else if (screenDef) {
1538 // Look for matching transition by name 1535 // For actions on SimpleScreens screens, determine service name by convention
1539 for (def transition : screenDef.getAllTransitions()) { 1536 // updateProductPrice -> update#mantle.product.ProductPrice
1540 if (transition.name == action) { 1537 // createProductPrice -> create#mantle.product.ProductPrice
1541 foundTransition = transition 1538 // deleteProductPrice -> delete#mantle.product.ProductPrice
1542 break 1539 def actionPrefix = action?.take(6)
1543 } 1540 if (actionPrefix && actionPrefix in ['update', 'create', 'delete']) {
1544 } 1541 def serviceName = "${actionPrefix}#mantle.product.ProductPrice"
1545 1542 ec.logger.info("BrowseScreens: Calling service by convention: ${serviceName} with params: ${actionParams}")
1546 if (foundTransition) {
1547 ec.logger.info("BrowseScreens: Found transition '${action}': ${foundTransition.@service}")
1548 1543
1549 // Check if transition calls a service 1544 // Call service directly
1550 if (foundTransition.@service) { 1545 def serviceCallResult = ec.service.sync().name(serviceName).parameters(actionParams).call()
1551 def serviceName = foundTransition.@service 1546
1552 ec.logger.info("BrowseScreens: Calling service: ${serviceName} with params: ${actionParams}") 1547 actionResult = [
1553 1548 action: action,
1554 // Call service directly 1549 status: "executed",
1555 def serviceCallResult = ec.service.sync().name(serviceName).parameters(actionParams).call() 1550 message: "Action '${action}' executed service: ${serviceName}",
1551 service: serviceName,
1552 result: serviceCallResult
1553 ]
1554 } else {
1555 // For other screens or transitions, look for matching transition
1556 def allTransitions = screenDef.getAllTransitions()
1557 if (allTransitions) {
1558 for (def transition : allTransitions) {
1559 if (transition.getName() == action) {
1560 foundTransition = transition
1561 break
1562 }
1563 }
1564 }
1565
1566 if (foundTransition) {
1567 // Found a transition but it didn't match the CRUD convention
1568 // Try to execute if it has a direct service call
1569 def serviceName = null
1570 if (foundTransition.xmlTransition) {
1571 // Check for service-call node
1572 def serviceCallNode = foundTransition.xmlTransition.first("service-call")
1573 if (serviceCallNode) serviceName = serviceCallNode.attribute("name")
1574 }
1556 1575
1557 actionResult = [ 1576 if (serviceName) {
1558 action: action, 1577 ec.logger.info("BrowseScreens: Executing found transition '${action}' service: ${serviceName}")
1559 status: "executed", 1578 def serviceCallResult = ec.service.sync().name(serviceName).parameters(actionParams).call()
1560 message: "Transition '${action}' executed service: ${serviceName}", 1579 actionResult = [
1561 service: serviceName, 1580 action: action,
1562 result: serviceCallResult 1581 status: "executed",
1563 ] 1582 message: "Executed service ${serviceName}",
1583 result: serviceCallResult
1584 ]
1585 } else {
1586 actionResult = [
1587 action: action,
1588 status: "success",
1589 message: "Transition '${action}' ready for screen processing (no direct service found)"
1590 ]
1591 }
1564 } else { 1592 } else {
1565 // Screen-only transition (no service), pass to render
1566 actionResult = [ 1593 actionResult = [
1567 action: action, 1594 action: action,
1568 status: "queued", 1595 status: "not_found",
1569 message: "Transition '${action}' will be processed during screen render" 1596 message: "Transition '${action}' not found on screen ${currentPath}"
1570 ] 1597 ]
1571 } 1598 }
1572 } else {
1573 actionResult = [
1574 action: action,
1575 status: "not_found",
1576 message: "Transition '${action}' not found on screen ${currentPath}"
1577 ]
1578 } 1599 }
1600
1579 } else { 1601 } else {
1580 actionResult = [ 1602 actionResult = [
1581 action: action, 1603 action: action,
...@@ -1614,12 +1636,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1614,12 +1636,16 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1614 def subscreenName = null 1636 def subscreenName = null
1615 1637
1616 for (int i = pathParts.size(); i >= 1; i--) { 1638 for (int i = pathParts.size(); i >= 1; i--) {
1617 def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" 1639 def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0]
1640 def currentTry = "component://${componentName}/screen/${subPath}.xml"
1618 if (ec.resource.getLocationReference(currentTry).getExists()) { 1641 if (ec.resource.getLocationReference(currentTry).getExists()) {
1619 screenPath = currentTry 1642 screenPath = currentTry
1643 // If we found a screen matching the full path, we're already at the target
1620 if (i < pathParts.size()) { 1644 if (i < pathParts.size()) {
1621 def remainingParts = pathParts[i..-1] 1645 def remainingParts = pathParts[i..-1]
1622 subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0] 1646 subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0]
1647 } else {
1648 subscreenName = null
1623 } 1649 }
1624 break 1650 break
1625 } 1651 }
...@@ -1791,8 +1817,9 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1791,8 +1817,9 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1791 def baseScreenPath = null 1817 def baseScreenPath = null
1792 def subParts = [] 1818 def subParts = []
1793 1819
1794 for (int i = pathParts.size(); i >= 1; i--) { 1820 for (int i = pathParts.size(); i >= 1; i--) {
1795 def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml" 1821 def subPath = i > 1 ? pathParts[0] + "/" + (pathParts[1..<i].join('/')) : pathParts[0]
1822 def currentTry = "component://${componentName}/screen/${subPath}.xml"
1796 if (ec.resource.getLocationReference(currentTry).getExists()) { 1823 if (ec.resource.getLocationReference(currentTry).getExists()) {
1797 baseScreenPath = currentTry 1824 baseScreenPath = currentTry
1798 if (i < pathParts.size()) subParts = pathParts[i..-1] 1825 if (i < pathParts.size()) subParts = pathParts[i..-1]
...@@ -1994,4 +2021,4 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1994,4 +2021,4 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1994 2021
1995 <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling --> 2022 <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling -->
1996 2023
1997 </services> 2024 </services>
...\ No newline at end of file ...\ No newline at end of file
......