@@ -31,6 +31,39 @@ import { ResourceDropdown } from "../resourceDropdown";
3131import { NOT_SUPPORT_GUI_SQL_QUERY , SQLQuery } from "../sqlQuery/SQLQuery" ;
3232import { StreamQuery } from "../httpQuery/streamQuery" ;
3333import SupaDemoDisplay from "../../utils/supademoDisplay" ;
34+ import _ from "lodash" ;
35+ import React from "react" ;
36+ import styled from "styled-components" ;
37+ import { DataSourceButton } from "pages/datasource/pluginPanel" ;
38+ import { Tooltip , Divider } from "antd" ;
39+ import { uiCompRegistry } from "comps/uiCompRegistry" ;
40+
41+ const Wrapper = styled . div `
42+ width: 100%;
43+ padding: 16px;
44+ background-color: white;
45+
46+ .section-title {
47+ font-size: 13px;
48+ line-height: 1.5;
49+ color: grey;
50+ margin-bottom: 8px;
51+ }
52+
53+ .section {
54+ margin-bottom: 12px;
55+
56+ &:last-child {
57+ margin-bottom: 0px;
58+ }
59+ }
60+ ` ;
61+
62+ const ComponentListWrapper = styled . div `
63+ display: flex;
64+ flex-wrap: wrap;
65+ gap: 8px;
66+ ` ;
3467
3568export function QueryPropertyView ( props : { comp : InstanceType < typeof QueryComp > } ) {
3669 const { comp } = props ;
@@ -128,6 +161,12 @@ export function QueryPropertyView(props: { comp: InstanceType<typeof QueryComp>
128161 } ) }
129162 </ >
130163 </ QuerySectionWrapper >
164+
165+ < QuerySectionWrapper >
166+ < QueryUsagePropertyView comp = { comp } />
167+ </ QuerySectionWrapper >
168+
169+
131170 </ QueryPropertyViewWrapper >
132171 ) ,
133172 } ,
@@ -187,16 +226,6 @@ export const QueryGeneralPropertyView = (props: {
187226 comp . children . datasourceId . dispatchChangeValueAction ( QUICK_REST_API_ID ) ;
188227 }
189228
190- // transfer old Lowcoder API datasource to new
191- const oldLowcoderId = useMemo (
192- ( ) =>
193- datasource . find (
194- ( d ) =>
195- d . datasource . creationSource === 2 && OLD_LOWCODER_DATASOURCE . includes ( d . datasource . type )
196- ) ?. datasource . id ,
197- [ datasource ]
198- ) ;
199-
200229 return (
201230 < QueryPropertyViewWrapper >
202231 < QuerySectionWrapper >
@@ -423,6 +452,195 @@ export const QueryGeneralPropertyView = (props: {
423452 ) ;
424453} ;
425454
455+ function findQueryInNestedStructure (
456+ structure : any ,
457+ queryName : string ,
458+ visited = new Set ( )
459+ ) : boolean {
460+ if ( typeof structure === "object" && structure !== null ) {
461+ if ( visited . has ( structure ) ) {
462+ return false ;
463+ }
464+ visited . add ( structure ) ;
465+ }
466+
467+ if ( typeof structure === "string" ) {
468+ // Regex to match query name in handlebar-like expressions
469+ const regex = new RegExp (
470+ `{{\\s*[!?]?(\\s*${ queryName } \\b(\\.[^}\\s]*)?\\s*)(\\?[^}:]*:[^}]*)?\\s*}}`
471+ ) ;
472+ return regex . test ( structure ) ;
473+ }
474+
475+ if ( typeof structure === "object" && structure !== null ) {
476+ // Recursively check all properties of the object
477+ return Object . values ( structure ) . some ( ( value ) =>
478+ findQueryInNestedStructure ( value , queryName , visited )
479+ ) ;
480+ }
481+ return false ;
482+ }
483+
484+ function collectComponentsUsingQuery ( comps : any , queryName : string ) {
485+
486+ // Select all active components
487+ const components = Object . values ( comps ) ;
488+
489+ // Filter components that reference the query by name
490+ const componentsUsingQuery = components . filter ( ( component : any ) => {
491+ return findQueryInNestedStructure ( component . children , queryName ) ;
492+ } ) ;
493+
494+ return componentsUsingQuery ;
495+ }
496+
497+ // this function we use to gather informations of the places where a Data Query is used.
498+ function collectQueryUsageDetails ( component : any , queryName : string ) : any [ ] {
499+ const results : any [ ] = [ ] ;
500+ const visited = new WeakSet ( ) ; // Track visited objects to avoid circular references
501+
502+ function traverse ( node : any , path : string [ ] = [ ] ) : boolean {
503+
504+ if ( ! node || typeof node !== "object" ) { return false ; }
505+ // Avoid circular references
506+ if ( visited . has ( node ) ) { return false ; }
507+ else { visited . add ( node ) ; }
508+
509+ // Check all properties of the current node
510+ for ( const [ key , value ] of Object . entries ( node ) ) {
511+ const currentPath = [ ...path , key ] ;
512+ if ( typeof value === "string" && ! key . includes ( "__" ) && key != "exposingValues" && key != "ref" && key != "version" && key != "prevContextVal" ) {
513+ // Check if the string contains the query
514+ const regex = new RegExp ( `{{\\s*[!?]?(\\s*${ queryName } \\b(\\.[^}\\s]*)?\\s*)(\\?[^}:]*:[^}]*)?\\s*}}` ) ;
515+ const entriesToRemove = [ "children" , "comp" , "unevaledValue" , "value" ] ;
516+ if ( regex . test ( value ) ) {
517+ console . log ( "tester" , component . children ) ;
518+ results . push ( {
519+ componentType : component . children . compType ?. value || "Unknown Component" ,
520+ componentName : component . children . name ?. value || "Unknown Component" ,
521+ path : currentPath . filter ( entry => ! entriesToRemove . includes ( entry ) ) . join ( " > " ) ,
522+ value,
523+ } ) ;
524+ return true ; // Stop traversal of this branch
525+ }
526+ } else if ( typeof value === "object" && ! key . includes ( "__" ) && key != "exposingValues" && key != "ref" && key != "version" && key != "prevContextVal" ) {
527+ // Traverse deeper only through selected properties.
528+ traverse ( value , currentPath ) ;
529+ }
530+ }
531+ return false ; // Continue traversal if no match is found
532+ }
533+
534+ traverse ( component ) ;
535+ return results ;
536+ }
537+
538+ function buildQueryUsageDataset ( components : any [ ] , queryName : string ) : any [ ] {
539+ const dataset : any [ ] = [ ] ;
540+ const visitedComponents = new WeakSet ( ) ; // Prevent revisiting components
541+ for ( const component of components ) {
542+ if ( visitedComponents . has ( component . children . name ) ) {
543+ continue ;
544+ }
545+ visitedComponents . add ( component . children . name ) ;
546+ const usageDetails = collectQueryUsageDetails ( component , queryName ) ;
547+ dataset . push ( ...usageDetails ) ;
548+ }
549+
550+ return dataset ;
551+ }
552+
553+ const ComponentButton = ( props : {
554+ componentType : string ;
555+ componentName : string ;
556+ path : string ;
557+ value : string ;
558+ onSelect : ( componentType : string , componentName : string , path : string ) => void ;
559+ } ) => {
560+ const handleClick = ( ) => {
561+ props . onSelect ( props . componentType , props . componentName , props . path ) ;
562+ } ;
563+
564+ // Retrieve the component's icon from the registry
565+ const Icon = uiCompRegistry [ props . componentType ] ?. icon ;
566+
567+ return (
568+ < Tooltip title = { props . path } placement = "top" >
569+ < DataSourceButton onClick = { handleClick } >
570+ < div style = { { display : "flex" , alignItems : "center" , gap : "8px" } } >
571+ { Icon && < Icon style = { { width : "32px" } } /> }
572+ < div >
573+ < div style = { { fontSize : "14px" , fontWeight : "bold" } } > { props . componentName } </ div >
574+ < div style = { { fontSize : "12px" , fontWeight : "400" } } > { props . componentType } </ div >
575+ </ div >
576+ </ div >
577+ </ DataSourceButton >
578+ </ Tooltip >
579+ ) ;
580+ } ;
581+
582+ export function ComponentUsagePanel ( props : {
583+ components : { componentType : string , componentName : string ; path : string ; value : string } [ ] ;
584+ onSelect : ( componentType : string , componentName : string , path : string ) => void ;
585+ } ) {
586+ const { components, onSelect } = props ;
587+
588+ return (
589+ < Wrapper >
590+ < div className = "section-title" > { trans ( "query.componentsUsingQuery" ) } </ div >
591+ < div className = "section" >
592+ < ComponentListWrapper >
593+ { components . map ( ( component , idx ) => (
594+ < ComponentButton
595+ componentType = { component . componentType }
596+ componentName = { component . componentName }
597+ path = { component . path }
598+ value = { component . value }
599+ onSelect = { onSelect }
600+ />
601+ ) ) }
602+ </ ComponentListWrapper >
603+ </ div >
604+ </ Wrapper >
605+ ) ;
606+ }
607+
608+ // a usage display to show which components make use of this query
609+ export const QueryUsagePropertyView = ( props : {
610+ comp : InstanceType < typeof QueryComp > ;
611+ placement ?: PageType ;
612+ } ) => {
613+ const { comp, placement = "editor" } = props ;
614+ const editorState = useContext ( EditorContext ) ;
615+ const queryName = comp . children . name . getView ( ) ;
616+ const componentsUsingQuery = collectComponentsUsingQuery ( editorState . getAllUICompMap ( ) , queryName ) ;
617+
618+ const usageObjects = buildQueryUsageDataset ( componentsUsingQuery , queryName ) ;
619+
620+ const handleSelect = ( componentType : string , componentName : string , path : string ) => {
621+ editorState . setSelectedCompNames ( new Set ( [ componentName ] ) ) ;
622+ // console.log(`Selected Component: ${componentName}, Path: ${path}`);
623+ } ;
624+
625+ if ( usageObjects . length > 0 ) {
626+ return (
627+ < >
628+ < Divider />
629+ < QuerySectionWrapper >
630+ < QueryConfigWrapper >
631+ < QueryConfigLabel > { trans ( "query.componentsUsingQueryTitle" ) } </ QueryConfigLabel >
632+ < ComponentUsagePanel components = { usageObjects } onSelect = { handleSelect } />
633+ </ QueryConfigWrapper >
634+ </ QuerySectionWrapper >
635+ </ >
636+ ) ;
637+ } else {
638+ return < div > </ div > ;
639+ }
640+
641+ } ;
642+
643+
426644function useDatasourceStatus ( datasourceId : string , datasourceType : ResourceType ) {
427645 const datasource = useSelector ( getDataSource ) ;
428646 const datasourceTypes = useSelector ( getDataSourceTypes ) ;
0 commit comments