@@ -31,6 +31,39 @@ import { ResourceDropdown } from "../resourceDropdown";
31
31
import { NOT_SUPPORT_GUI_SQL_QUERY , SQLQuery } from "../sqlQuery/SQLQuery" ;
32
32
import { StreamQuery } from "../httpQuery/streamQuery" ;
33
33
import 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
+ ` ;
34
67
35
68
export function QueryPropertyView ( props : { comp : InstanceType < typeof QueryComp > } ) {
36
69
const { comp } = props ;
@@ -128,6 +161,12 @@ export function QueryPropertyView(props: { comp: InstanceType<typeof QueryComp>
128
161
} ) }
129
162
</ >
130
163
</ QuerySectionWrapper >
164
+
165
+ < QuerySectionWrapper >
166
+ < QueryUsagePropertyView comp = { comp } />
167
+ </ QuerySectionWrapper >
168
+
169
+
131
170
</ QueryPropertyViewWrapper >
132
171
) ,
133
172
} ,
@@ -187,16 +226,6 @@ export const QueryGeneralPropertyView = (props: {
187
226
comp . children . datasourceId . dispatchChangeValueAction ( QUICK_REST_API_ID ) ;
188
227
}
189
228
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
-
200
229
return (
201
230
< QueryPropertyViewWrapper >
202
231
< QuerySectionWrapper >
@@ -423,6 +452,195 @@ export const QueryGeneralPropertyView = (props: {
423
452
) ;
424
453
} ;
425
454
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
+
426
644
function useDatasourceStatus ( datasourceId : string , datasourceType : ResourceType ) {
427
645
const datasource = useSelector ( getDataSource ) ;
428
646
const datasourceTypes = useSelector ( getDataSourceTypes ) ;
0 commit comments