Skip to content

Commit ee28dda

Browse files
authored
Merge pull request #1002 from sachiniSam/fixIncompleteSource
Improvements in Record Constrcut View
2 parents d449eca + 1bf3a56 commit ee28dda

File tree

8 files changed

+182
-31
lines changed

8 files changed

+182
-31
lines changed

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/ParameterBranch.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import React, { useState } from "react";
1919

2020
import { TypeField } from "@wso2/ballerina-core";
21-
import { Button } from "@wso2/ui-toolkit";
21+
import { Button, Codicon } from "@wso2/ui-toolkit";
2222

2323

2424
import { useHelperPaneStyles } from "./styles";
@@ -64,31 +64,48 @@ export function ParameterBranch(props: ParameterBranchProps) {
6464
}
6565
});
6666

67-
function toggleOptionalParams(e: any) {
67+
function toggleOptionalParams(e?: React.MouseEvent<HTMLDivElement>) {
68+
if (e) {
69+
e.stopPropagation();
70+
}
6871
setShowOptionalParams(!showOptionalParams);
6972
}
7073

74+
const shouldShowOptionalParamsDirectly = (optionalParams.length > 0 && depth === 1) ||
75+
(requiredParams.length === 0 && optionalParams.length > 0 && depth < 3);
76+
7177
return (
7278
<div data-testid="parameter-branch">
7379
{requiredParams}
74-
{(optionalParams.length > 0 && depth === 1) ? (
80+
{shouldShowOptionalParamsDirectly ? (
7581
optionalParams
7682
) : (
7783
<>
7884
{optionalParams.length > 0 && (
7985
<div className={helperStyleClass.listOptionalWrapper}>
80-
{/* <div className={helperStyleClass.listOptionalHeader}>Optional fields </div> */}
81-
<Button
82-
data-testid="optional-toggle-button"
83-
className={helperStyleClass.listOptionalBtn}
84-
onClick={toggleOptionalParams}
85-
appearance="secondary"
86-
>
87-
{showOptionalParams ? "Hide" : "Show"}
88-
</Button>
86+
<div className={helperStyleClass.listOptionalHeader} onClick={toggleOptionalParams}>
87+
<Button
88+
data-testid="optional-toggle-button"
89+
appearance="icon"
90+
sx={{
91+
transition: "all 0.2s ease",
92+
"&:hover": {
93+
backgroundColor: "transparent !important",
94+
},
95+
}}
96+
>
97+
<Codicon
98+
name={showOptionalParams ? "chevron-down" : "chevron-right"}
99+
iconSx={{ fontSize: '13px' }}
100+
/>
101+
</Button>
102+
<div className={helperStyleClass.listOptionalTitle}>Optional fields</div>
103+
</div>
89104
</div>
90105
)}
91-
{showOptionalParams && optionalParams.length > 0 && optionalParams}
106+
<div style={{ marginLeft: '15px' }}>
107+
{showOptionalParams && optionalParams.length > 0 && optionalParams}
108+
</div>
92109
</>
93110
)}
94111
</div>

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/CustomType/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";
2222

2323
import { TypeProps } from "../../ParameterBranch";
2424
import { useHelperPaneStyles } from "../../styles";
25-
import { isRequiredParam } from "../../utils";
25+
import { isRequiredParam, resetFieldValues } from "../../utils";
2626

2727
export default function CustomType(props: TypeProps) {
2828
const { param, onChange } = props;
@@ -38,6 +38,12 @@ export default function CustomType(props: TypeProps) {
3838
if (!requiredParam) {
3939
const newSelectedState = !paramSelected;
4040
param.selected = newSelectedState;
41+
42+
// When unchecking, reset the field values
43+
if (!newSelectedState) {
44+
resetFieldValues(param);
45+
}
46+
4147
setParamSelected(newSelectedState);
4248
onChange();
4349
}

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/InclusionType/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";
2323
import { TypeProps } from "../../ParameterBranch";
2424
import { useHelperPaneStyles } from "../../styles";
2525
import { ParameterBranch } from "../../ParameterBranch";
26-
import { isAllDefaultableFields, isRequiredParam, updateFieldsSelection } from "../../utils";
26+
import { isAllDefaultableFields, isRequiredParam, updateFieldsSelection, resetFieldValues } from "../../utils";
2727

2828
export default function InclusionType(props: TypeProps) {
2929
const { param, depth, onChange } = props;
@@ -43,6 +43,11 @@ export default function InclusionType(props: TypeProps) {
4343
param.selected = newSelectedState;
4444
param.inclusionType.selected = newSelectedState;
4545

46+
// When unchecking, reset the field values
47+
if (!newSelectedState) {
48+
resetFieldValues(param);
49+
}
50+
4651
// If the inclusion type has fields, update their selection state
4752
if (param.inclusionType?.fields && param.inclusionType.fields.length > 0) {
4853
updateFieldsSelection(param.inclusionType.fields, newSelectedState);

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/RecordType/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";
2323
import { TypeProps } from "../../ParameterBranch";
2424
import { useHelperPaneStyles } from "../../styles";
2525
import { MemoizedParameterBranch } from "../../ParameterBranch";
26-
import { isRequiredParam, updateFieldsSelection } from "../../utils";
26+
import { isRequiredParam, updateFieldsSelection, resetFieldValues } from "../../utils";
2727

2828
export default function RecordType(props: TypeProps) {
2929
const { param, depth, onChange } = props;
@@ -40,6 +40,11 @@ export default function RecordType(props: TypeProps) {
4040
const newSelectedState = !paramSelected;
4141
param.selected = newSelectedState;
4242

43+
// When unchecking, reset the field values
44+
if (!newSelectedState) {
45+
resetFieldValues(param);
46+
}
47+
4348
// If the record has fields, update their selection state
4449
if (param.fields && param.fields.length > 0) {
4550
updateFieldsSelection(param.fields, newSelectedState);

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/UnionType/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { Codicon, Dropdown, Tooltip, Typography } from "@wso2/ui-toolkit";
2424
import { TypeProps } from "../../ParameterBranch";
2525
import { useHelperPaneStyles } from "../../styles";
2626
import { ParameterBranch } from "../../ParameterBranch";
27-
import { getSelectedUnionMember, isRequiredParam, updateFieldsSelection } from "../../utils";
27+
import { getSelectedUnionMember, isRequiredParam, updateFieldsSelection, resetFieldValues } from "../../utils";
2828

2929
export default function UnionType(props: TypeProps) {
3030
const { param, depth, onChange } = props;
@@ -184,7 +184,8 @@ export default function UnionType(props: TypeProps) {
184184
}
185185
}
186186
} else {
187-
// When unchecking, clear all member selections
187+
// When unchecking, reset values and clear all member selections
188+
resetFieldValues(param);
188189
param.members.forEach((field) => {
189190
field.selected = false;
190191

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/styles.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* under the License.
1717
*/
1818
import { css } from "@emotion/css";
19+
import { ThemeColors } from "@wso2/ui-toolkit";
1920

2021

2122
const removePadding = {
@@ -296,22 +297,44 @@ export const useHelperPaneStyles = () => ({
296297
listOptionalWrapper: css({
297298
display: 'flex',
298299
alignItems: 'center',
299-
height: '32px',
300-
marginBottom: '12px'
300+
marginTop: '8px',
301+
marginBottom: '8px'
302+
}),
303+
listOptionalHeader: css({
304+
display: 'flex',
305+
flexDirection: 'row',
306+
justifyContent: 'flex-start',
307+
alignItems: 'center',
308+
width: 'auto',
309+
gap: '6px',
310+
borderRadius: '5px',
311+
cursor: 'pointer',
312+
transition: 'all 0.2s ease',
313+
border: '1px solid transparent',
314+
'&:hover': {
315+
backgroundColor: 'var(--vscode-list-hoverBackground)',
316+
},
317+
'&:hover > div:last-of-type': {
318+
opacity: 1,
319+
color: ThemeColors.PRIMARY,
320+
}
321+
}),
322+
listOptionalTitle: css({
323+
fontSize: '13px',
324+
opacity: 0.7,
325+
color: ThemeColors.ON_SURFACE_VARIANT,
326+
transition: 'all 0.2s ease',
327+
padding: '2px 4px',
328+
borderRadius: '3px',
329+
'&:hover': {
330+
backgroundColor: 'var(--vscode-list-hoverBackground)',
331+
}
301332
}),
302333
listOptionalBtn: css({
303334
textTransform: 'none',
304335
minWidth: '32px',
305336
marginLeft: '8px'
306337
}),
307-
listOptionalHeader: css({
308-
fontSize: '13px',
309-
color: "gray",
310-
fontWeight: 500,
311-
letterSpacing: '0',
312-
lineHeight: '14px',
313-
paddingLeft: '0px',
314-
}),
315338
});
316339

317340

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/utils/index.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,41 @@ export function checkFormFieldValue(field: FormField): boolean {
6464
return field.value !== undefined && field.value !== null;
6565
}
6666

67+
export function resetFieldValues(field: FormField): void {
68+
if (!field) return;
69+
70+
// Reset the field value
71+
if (field.value !== undefined) {
72+
field.value = undefined;
73+
}
74+
75+
// Reset nested fields
76+
if (field.fields && field.fields.length > 0) {
77+
field.fields.forEach(nestedField => {
78+
resetFieldValues(nestedField);
79+
});
80+
}
81+
82+
// Reset union members
83+
if (field.members && field.members.length > 0) {
84+
field.members.forEach(member => {
85+
resetFieldValues(member);
86+
});
87+
}
88+
89+
// Reset inclusion type fields
90+
if (field.inclusionType?.fields && field.inclusionType.fields.length > 0) {
91+
field.inclusionType.fields.forEach(inclusionField => {
92+
resetFieldValues(inclusionField);
93+
});
94+
}
95+
}
96+
6797
export function updateFieldsSelection(fields: FormField[], selected: boolean): void {
6898
if (!fields || !fields.length) return;
6999

70100
fields.forEach(field => {
71101
// When selecting: only select required fields
72-
// When deselecting: deselect all fields (both required and optional)
73102
if (!selected || isRequiredParam(field)) {
74103
field.selected = selected;
75104

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/RecordConfigModal.tsx

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ChipExpressionEditorComponent, Context as FormContext, HelperpaneOnChan
2626
import { useForm } from "react-hook-form";
2727
import { debounce } from "lodash";
2828
import ReactMarkdown from "react-markdown";
29+
import { updateFieldsSelection } from "../Components/RecordConstructView/utils";
2930

3031
type ConfigureRecordPageProps = {
3132
fileName: string;
@@ -142,6 +143,7 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
142143
const { rpcClient } = useRpcContext();
143144

144145
const [recordModel, setRecordModel] = useState<TypeField[]>([]);
146+
const recordModelRef = useRef<TypeField[]>([]);
145147
const [selectedMemberName, setSelectedMemberName] = useState<string>("");
146148
const firstRender = useRef<boolean>(true);
147149
const sourceCode = useRef<string>(currentValue);
@@ -260,6 +262,7 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
260262
}
261263

262264
setRecordModel([recordConfig]);
265+
recordModelRef.current = [recordConfig];
263266
setSelectedMemberName(newRecordModel.name);
264267
}
265268

@@ -270,6 +273,23 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
270273
await fetchRecordModelFromSource(currentValue);
271274
};
272275

276+
277+
// Helper function to auto-select the first record in the model.
278+
// Also selects required fields recursively within that record.
279+
const autoSelectFirstRecord = (model: TypeField[]) => {
280+
if (!model || model.length === 0) return;
281+
282+
const recordConfig = model[0];
283+
284+
// Select the first record itself
285+
recordConfig.selected = true;
286+
287+
// If the record has fields, recursively select required fields
288+
if (recordConfig.fields && recordConfig.fields.length > 0) {
289+
updateFieldsSelection(recordConfig.fields as any, true);
290+
}
291+
};
292+
273293
const getNewRecordModel = async () => {
274294
setIsLoading(true);
275295
const defaultSelection = recordTypeField.recordTypeMembers[0];
@@ -312,7 +332,15 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
312332
...typeFieldResponse.recordConfig
313333
}
314334

315-
setRecordModel([recordConfig]);
335+
const newModel = [recordConfig];
336+
setRecordModel(newModel);
337+
recordModelRef.current = newModel;
338+
339+
// Auto-select the first field for new models
340+
autoSelectFirstRecord(newModel);
341+
342+
// Generate source with the auto-selected field
343+
await handleModelChange(newModel);
316344
}
317345
setIsLoading(false);
318346
}
@@ -354,7 +382,15 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
354382
...typeFieldResponse.recordConfig
355383
}
356384

357-
setRecordModel([recordConfig]);
385+
const newModel = [recordConfig];
386+
setRecordModel(newModel);
387+
recordModelRef.current = newModel;
388+
389+
// Auto-select the first field when union member changes
390+
autoSelectFirstRecord(newModel);
391+
392+
// Generate source with the auto-selected field
393+
await handleModelChange(newModel);
358394
}
359395
}
360396

@@ -523,6 +559,33 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
523559
// Update local expression value when user edits in the ExpressionEditor
524560
setLocalExpressionValue(updatedValue);
525561

562+
// If the expression is empty, deselect all checkboxes
563+
if (updatedValue.trim() === '') {
564+
// Use ref to get the latest recordModel value
565+
const currentRecordModel = recordModelRef.current;
566+
// Deselect all fields in the record model
567+
if (currentRecordModel.length > 0 && currentRecordModel[0]) {
568+
const recordConfig = currentRecordModel[0];
569+
// Deselect the record itself
570+
recordConfig.selected = false;
571+
// Deselect all fields recursively
572+
if (recordConfig.fields && recordConfig.fields.length > 0) {
573+
updateFieldsSelection(recordConfig.fields as any, false);
574+
}
575+
// Update source code to empty to prevent sync issues
576+
sourceCode.current = '';
577+
// Update latest expression ref to prevent sync
578+
latestExpressionToSyncRef.current = '';
579+
// Trigger re-render by updating recordModel state with a new array reference
580+
const updatedModel = [...currentRecordModel];
581+
setRecordModel(updatedModel);
582+
recordModelRef.current = updatedModel;
583+
}
584+
// Clear diagnostics when expression is empty
585+
setFormDiagnostics([]);
586+
return;
587+
}
588+
526589
// Fetch diagnostics (debounced) - this will update formDiagnostics state
527590
// The sync will be triggered by the useEffect that watches localExpressionValue
528591
fetchDiagnostics(updatedValue);
@@ -585,6 +648,8 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
585648
label: member.type,
586649
value: member.type
587650
}))}
651+
sx={{ width: '100%' }}
652+
containerSx={{ width: '100%' }}
588653
onValueChange={(value) => handleMemberChange(value)}
589654
/>
590655
</LabelContainer>

0 commit comments

Comments
 (0)