Skip to content

Commit 84f3285

Browse files
author
FalkWolsky
committed
Adding Display to show where queries are used
1 parent 9311e81 commit 84f3285

File tree

2 files changed

+230
-10
lines changed

2 files changed

+230
-10
lines changed

client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx

Lines changed: 228 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,39 @@ import { ResourceDropdown } from "../resourceDropdown";
3131
import { NOT_SUPPORT_GUI_SQL_QUERY, SQLQuery } from "../sqlQuery/SQLQuery";
3232
import { StreamQuery } from "../httpQuery/streamQuery";
3333
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+
`;
3467

3568
export 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+
426644
function useDatasourceStatus(datasourceId: string, datasourceType: ResourceType) {
427645
const datasource = useSelector(getDataSource);
428646
const datasourceTypes = useSelector(getDataSourceTypes);

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,8 @@ export const en = {
844844
"categoryWebscrapers" : "Webscrapers & Open Data",
845845
"categoryDocumentHandling" : "Report & Document Generation",
846846
"categoryRPA" : "Robotic Process Automation",
847+
"componentsUsingQueryTitle" : "Query Usage",
848+
"componentsUsingQuery" : "Where is this Query in use"
847849
},
848850

849851

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy