7c09f005 by Ean Schuessler

Add wiki instructions path hierarchy walk-up

When looking up wiki docs for a screen, now walks up the path hierarchy
to find the most specific match. For example, for path
'Catalog/Product/EditProduct/Assocs', it tries:

1. Catalog/Product/EditProduct/Assocs (most specific)
2. Catalog/Product/EditProduct (inherits if no specific doc)
3. Catalog/Product
4. Catalog

This allows parent screen docs to apply to all child tabs/subscreens
unless overridden with more specific documentation.

Updated both getWikiInstructions (used by execute#ScreenAsMcpTool) and
loadWikiContent (used by moqui_browse_screens) to use this approach.
1 parent ec043222
...@@ -617,38 +617,64 @@ def extractSummary = { wikiText -> ...@@ -617,38 +617,64 @@ def extractSummary = { wikiText ->
617 } 617 }
618 618
619 // Helper function to load wiki instructions for a screen 619 // Helper function to load wiki instructions for a screen
620 // Walks up the path hierarchy to find the most specific wiki doc available
621 // e.g., for "Catalog/Product/EditProduct/Assocs", tries in order:
622 // 1. Catalog/Product/EditProduct/Assocs (most specific)
623 // 2. Catalog/Product/EditProduct
624 // 3. Catalog/Product
625 // 4. Catalog
620 def getWikiInstructions = { lookupPath -> 626 def getWikiInstructions = { lookupPath ->
621 try { 627 if (!lookupPath) return null
622 def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage") 628
623 .condition("wikiSpaceId", "MCP_SCREEN_DOCS") 629 // Normalize path - remove leading/trailing slashes
624 .condition("pagePath", lookupPath) 630 def normalizedPath = lookupPath.replaceAll('^/+', '').replaceAll('/+$', '')
625 .useCache(true) 631 if (!normalizedPath) return null
626 .one() 632
633 // Build list of paths from most specific to least specific
634 def pathsToTry = []
635 def segments = normalizedPath.split('/')
636 for (int i = segments.length; i > 0; i--) {
637 pathsToTry.add(segments[0..i-1].join('/'))
638 }
639
640 ec.logger.debug("Wiki lookup: trying paths ${pathsToTry} for ${lookupPath}")
641
642 for (def tryPath in pathsToTry) {
643 try {
644 def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage")
645 .condition("wikiSpaceId", "MCP_SCREEN_DOCS")
646 .condition("pagePath", tryPath)
647 .useCache(true)
648 .one()
627 649
628 if (!wikiPage) return null 650 if (!wikiPage) continue
629 651
630 def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace") 652 def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace")
631 .condition("wikiSpaceId", wikiPage.wikiSpaceId) 653 .condition("wikiSpaceId", wikiPage.wikiSpaceId)
632 .one() 654 .one()
633 655
634 if (!wikiSpace) return null 656 if (!wikiSpace) continue
635 657
636 // Build the resource location for the page 658 // Build the resource location for the page
637 def pageLocation = wikiSpace.rootPageLocation 659 def pageLocation = wikiSpace.rootPageLocation
638 if (!pageLocation.endsWith('/')) { 660 if (!pageLocation.endsWith('/')) {
639 pageLocation += '/' 661 pageLocation += '/'
640 } 662 }
641 pageLocation += wikiPage.pagePath + '.md' 663 pageLocation += wikiPage.pagePath + '.md'
642 664
643 // Get the resource reference and text content 665 // Get the resource reference and text content
644 def pageRef = ec.resource.getLocationReference(pageLocation) 666 def pageRef = ec.resource.getLocationReference(pageLocation)
645 def wikiText = pageRef?.getText() 667 def wikiText = pageRef?.getText()
646 668
647 if (wikiText) { 669 if (wikiText) {
648 return wikiText 670 if (tryPath != normalizedPath) {
671 ec.logger.debug("Wiki lookup: found inherited docs at ${tryPath} for ${lookupPath}")
672 }
673 return wikiText
674 }
675 } catch (Exception e) {
676 ec.logger.debug("Could not load wiki instructions for ${tryPath}: ${e.message}")
649 } 677 }
650 } catch (Exception e) {
651 ec.logger.debug("Could not load wiki instructions for ${screenPath}: ${e.message}")
652 } 678 }
653 return null 679 return null
654 } 680 }
...@@ -1833,16 +1859,10 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1833,16 +1859,10 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1833 currentPath = currentPath.split("\\?")[0] 1859 currentPath = currentPath.split("\\?")[0]
1834 } 1860 }
1835 1861
1836 // Helper function to load wiki content 1862 // Helper to load wiki content for a specific path (no hierarchy walk)
1837 def loadWikiContent = { path -> 1863 def loadWikiContentForPath = { simplePath ->
1838 ec.logger.info("BrowseScreens: loadWikiContent CALLED for ${path}")
1839 try { 1864 try {
1840 def simplePath = (path == "root") ? "root" : path 1865 ec.logger.debug("BrowseScreens: Looking up wiki instructions for ${simplePath}")
1841 if (simplePath.contains("?")) {
1842 simplePath = simplePath.split("\\?")[0]
1843 }
1844
1845 ec.logger.info("BrowseScreens: Looking up wiki instructions for ${simplePath}")
1846 1866
1847 def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage") 1867 def wikiPage = ec.entity.find("moqui.resource.wiki.WikiPage")
1848 .condition("wikiSpaceId", "MCP_SCREEN_DOCS") 1868 .condition("wikiSpaceId", "MCP_SCREEN_DOCS")
...@@ -1851,51 +1871,78 @@ def wikiInstructions = getWikiInstructions(inputScreenPath) ...@@ -1851,51 +1871,78 @@ def wikiInstructions = getWikiInstructions(inputScreenPath)
1851 .one() 1871 .one()
1852 1872
1853 if (wikiPage) { 1873 if (wikiPage) {
1854 ec.logger.info("BrowseScreens: Found wikiPage: ${wikiPage.pagePath}") 1874 ec.logger.debug("BrowseScreens: Found wikiPage: ${wikiPage.pagePath}")
1855 def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace") 1875 def wikiSpace = ec.entity.find("moqui.resource.wiki.WikiSpace")
1856 .condition("wikiSpaceId", wikiPage.wikiSpaceId) 1876 .condition("wikiSpaceId", wikiPage.wikiSpaceId)
1857 .one() 1877 .one()
1858 1878
1859 if (wikiSpace) { 1879 if (wikiSpace) {
1860 ec.logger.info("BrowseScreens: Found wikiSpace: ${wikiSpace.wikiSpaceId}")
1861
1862 def dbResource = ec.entity.find("moqui.resource.DbResource") 1880 def dbResource = ec.entity.find("moqui.resource.DbResource")
1863 .condition("parentResourceId", "WIKI_MCP_SCREEN_DOCS") 1881 .condition("parentResourceId", "WIKI_MCP_SCREEN_DOCS")
1864 .condition("filename", wikiPage.pagePath + ".md") 1882 .condition("filename", wikiPage.pagePath + ".md")
1865 .one() 1883 .one()
1866 1884
1867 if (dbResource) { 1885 if (dbResource) {
1868 ec.logger.info("BrowseScreens: Found dbResource: ${dbResource.resourceId}")
1869 def dbResourceFile = ec.entity.find("moqui.resource.DbResourceFile") 1886 def dbResourceFile = ec.entity.find("moqui.resource.DbResourceFile")
1870 .condition("resourceId", dbResource.resourceId) 1887 .condition("resourceId", dbResource.resourceId)
1871 .condition("versionName", wikiPage.publishedVersionName) 1888 .condition("versionName", wikiPage.publishedVersionName)
1872 .one() 1889 .one()
1873 1890
1874 ec.logger.info("BrowseScreens: dbResourceFile query result: ${dbResourceFile ? 'found' : 'null'}")
1875
1876 if (!dbResourceFile) { 1891 if (!dbResourceFile) {
1877 dbResourceFile = ec.entity.find("moqui.resource.DbResourceFile") 1892 dbResourceFile = ec.entity.find("moqui.resource.DbResourceFile")
1878 .condition("resourceId", dbResource.resourceId) 1893 .condition("resourceId", dbResource.resourceId)
1879 .one() 1894 .one()
1880 ec.logger.info("BrowseScreens: dbResourceFile fallback query result: ${dbResourceFile ? 'found' : 'null'}")
1881 }
1882
1883 if (dbResourceFile) {
1884 ec.logger.info("BrowseScreens: dbResourceFile.fileData: ${dbResourceFile.fileData ? 'exists, size=' + dbResourceFile.fileData.length() : 'null'}")
1885 } 1895 }
1886 1896
1887 if (dbResourceFile && dbResourceFile.fileData) { 1897 if (dbResourceFile && dbResourceFile.fileData) {
1888 def content = new String(dbResourceFile.fileData.getBytes(new Long(1).longValue(), new Long(dbResourceFile.fileData.length()).intValue()), "UTF-8") 1898 def content = new String(dbResourceFile.fileData.getBytes(new Long(1).longValue(), new Long(dbResourceFile.fileData.length()).intValue()), "UTF-8")
1889 ec.logger.info("BrowseScreens: Found wiki instructions for ${simplePath}, length: ${content?.length()}") 1899 ec.logger.debug("BrowseScreens: Found wiki instructions for ${simplePath}, length: ${content?.length()}")
1890 return content 1900 return content
1891 } 1901 }
1892 } else {
1893 ec.logger.warn("BrowseScreens: No dbResource found for ${wikiPage.pagePath}.md")
1894 } 1902 }
1895 } 1903 }
1896 } 1904 }
1897 } catch (Exception e) { 1905 } catch (Exception e) {
1898 ec.logger.warn("BrowseScreens: Error getting wiki instructions: ${e.message}") 1906 ec.logger.debug("BrowseScreens: Error getting wiki instructions for ${simplePath}: ${e.message}")
1907 }
1908 return null
1909 }
1910
1911 // Helper function to load wiki content with path hierarchy walk
1912 // Walks up the path hierarchy to find the most specific wiki doc available
1913 // e.g., for "Catalog/Product/EditProduct/Assocs", tries in order:
1914 // 1. Catalog/Product/EditProduct/Assocs (most specific)
1915 // 2. Catalog/Product/EditProduct
1916 // 3. Catalog/Product
1917 // 4. Catalog
1918 def loadWikiContent = { path ->
1919 ec.logger.info("BrowseScreens: loadWikiContent CALLED for ${path}")
1920
1921 if (!path || path == "root") {
1922 // For root, try exact match only
1923 return loadWikiContentForPath("root")
1924 }
1925
1926 def simplePath = path
1927 if (simplePath.contains("?")) {
1928 simplePath = simplePath.split("\\?")[0]
1929 }
1930 // Normalize - remove leading/trailing slashes
1931 simplePath = simplePath.replaceAll('^/+', '').replaceAll('/+$', '')
1932
1933 if (!simplePath) return null
1934
1935 // Build list of paths from most specific to least specific
1936 def segments = simplePath.split('/')
1937 for (int i = segments.length; i > 0; i--) {
1938 def tryPath = segments[0..i-1].join('/')
1939 def content = loadWikiContentForPath(tryPath)
1940 if (content) {
1941 if (tryPath != simplePath) {
1942 ec.logger.info("BrowseScreens: Found inherited wiki docs at ${tryPath} for ${simplePath}")
1943 }
1944 return content
1945 }
1899 } 1946 }
1900 return null 1947 return null
1901 } 1948 }
......