Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,19 @@ const EnvVarValueCell = ({
);
};

const ErrorMessage = React.memo(({ id, error }) => {
if (!error) {
return null;
}

return (
<span>
<IconAlertCircle id={id} data-testid="env-var-name-error" className="text-red-600 cursor-pointer" size={20} />
<Tooltip className="tooltip-mod" anchorId={id} html={error} />
</span>
);
});

const EnvironmentVariablesTable = ({
environment,
inheritedEnvironmentVariables = [],
Expand Down Expand Up @@ -371,12 +384,12 @@ const EnvironmentVariablesTable = ({
);
const workspaceProcessEnvVariables = activeWorkspace?.processEnvVariables;
// `_collection` flows into every row's MultiLineEditor as the variable-resolution
// context. Without memoization, `cloneDeep(collection)` runs on every render —
// and Formik triggers a re-render on every keystroke, so a single env edit
// session can deep-clone the entire collection 100+ times. That's the
// dominant cost behind the test-budget flake.
// context. The copy exists only so the three fields below can be attached without
// writing to Redux state, so a shallow spread is enough, every consumer
// (getAllVariables, mergeVars, brunoVarInfo) reads the nested structures and never
// mutates them
const _collection = useMemo(() => {
const c = collection ? cloneDeep(collection) : {};
const c = collection ? { ...collection } : {};
c.globalEnvironmentVariables = globalEnvironmentVariables;
c.globalEnvSecrets = globalEnvSecrets;
c.globalEnvironments = globalEnvironments;
Expand Down Expand Up @@ -606,32 +619,6 @@ const EnvironmentVariablesTable = ({

const duplicateSecretNames = useMemo(() => getDuplicateSecretNames(formik.values), [formik.values]);

const ErrorMessage = ({ name, index }) => {
const meta = formik.getFieldMeta(name);
const id = `error-${name}-${index}`;

const isLastRow = index === formik.values.length - 1;
const variable = formik.values[index];
const isEmptyRow = !variable?.name || variable.name.trim() === '';

if (isLastRow && isEmptyRow) {
return null;
}

const isDuplicateSecret = variable?.secret && !isEmptyRow && duplicateSecretNames.has(variable.name.trim());
const error = meta.error || (isDuplicateSecret ? DUPLICATE_SECRET_NAME_FIELD_ERROR : null);

if (!error) {
return null;
}
return (
<span>
<IconAlertCircle id={id} data-testid="env-var-name-error" className="text-red-600 cursor-pointer" size={20} />
<Tooltip className="tooltip-mod" anchorId={id} html={error} />
</span>
);
};

const handleRemoveVar = useCallback(
(id) => {
const currentValues = formik.values;
Expand Down Expand Up @@ -1073,6 +1060,12 @@ const EnvironmentVariablesTable = ({
const isLastRow = actualIndex === formik.values.length - 1;
const isEmptyRow = !variable.name || variable.name.trim() === '';
const isLastEmptyRow = isLastRow && isEmptyRow;
const isDuplicateSecret
= variable.secret && !isEmptyRow && duplicateSecretNames.has(variable.name.trim());
const rowError = isLastEmptyRow
? null
: formik.getFieldMeta(`${actualIndex}.name`).error
|| (isDuplicateSecret ? DUPLICATE_SECRET_NAME_FIELD_ERROR : null);

return (
<>
Expand Down Expand Up @@ -1121,7 +1114,10 @@ const EnvironmentVariablesTable = ({
onKeyDown={(e) => handleNameKeyDown(actualIndex, e)}
/>
</div>
<ErrorMessage name={`${actualIndex}.name`} index={actualIndex} />
<ErrorMessage
id={`error-${actualIndex}.name-${actualIndex}`}
error={rowError}
/>
</div>
</td>
<td style={{ width: columnWidths.value }} className="overflow-hidden">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ const EnvironmentVariables = ({ environment, setIsModified, collection, inherite
const environmentsDraft = collection?.environmentsDraft;
const hasDraftForThisEnv = environmentsDraft?.environmentUid === environment.uid;

// Check for non-secret variables used in sensitive fields
const collectionItems = collection?.items;
const collectionRoot = collection?.root;
const environmentVariables = environment?.variables;
Comment thread
sachin-thakur-bruno marked this conversation as resolved.

const nonSecretSensitiveVarUsageMap = useMemo(() => {
const result = {};
if (!collection || !environment?.variables) {
if (!environmentVariables) {
return result;
}
const nonSecretVars = environment.variables.filter((v) => v.enabled && !v.secret && v.name);
const nonSecretVars = environmentVariables.filter((v) => v.enabled && !v.secret && v.name);
if (!nonSecretVars.length) {
return result;
}
Expand All @@ -45,20 +48,20 @@ const EnvironmentVariables = ({ environment, setIsModified, collection, inherite
return item.root;
};

const collectionObj = getObjectToProcess(collection);
const collectionObj = collectionRoot;
sensitiveFields.forEach((fieldPath) => {
checkSensitiveField(collectionObj, fieldPath);
});

const items = flattenItems(collection.items || []);
const items = flattenItems(collectionItems || []);
items.forEach((item) => {
const objToProcess = getObjectToProcess(item);
sensitiveFields.forEach((fieldPath) => {
checkSensitiveField(objToProcess, fieldPath);
});
});
return result;
}, [collection, environment]);
}, [collectionItems, collectionRoot, environmentVariables]);

const hasSensitiveUsage = useCallback((name) => !!nonSecretSensitiveVarUsageMap[name], [nonSecretSensitiveVarUsageMap]);

Expand Down
21 changes: 12 additions & 9 deletions packages/bruno-app/src/components/MultiLineEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,20 +264,21 @@ class MultiLineEditor extends Component {
// event loop.
this.ignoreChangeEvent = true;

let variables = getAllVariables(this.props.collection, this.props.item);
if (!isEqual(variables, this.variables)) {
if (this.props.enableBrunoVarInfo !== false && this.editor.options.brunoVarInfo) {
this.editor.options.brunoVarInfo.variables = variables;
if (this.props.collection !== prevProps.collection || this.props.item !== prevProps.item) {
const variables = getAllVariables(this.props.collection, this.props.item);
if (!isEqual(variables, this.variables)) {
if (this.props.enableBrunoVarInfo !== false && this.editor.options.brunoVarInfo) {
this.editor.options.brunoVarInfo.variables = variables;
}
this.addOverlay(variables);
}
this.addOverlay(variables);
}

// Update collection and item when they change
if (this.props.enableBrunoVarInfo !== false && this.editor.options.brunoVarInfo) {
if (!isEqual(this.props.collection, this.editor.options.brunoVarInfo.collection)) {
if (this.props.collection !== this.editor.options.brunoVarInfo.collection) {
this.editor.options.brunoVarInfo.collection = this.props.collection;
}
if (!isEqual(this.props.item, this.editor.options.brunoVarInfo.item)) {
if (this.props.item !== this.editor.options.brunoVarInfo.item) {
this.editor.options.brunoVarInfo.item = this.props.item;
}
}
Expand Down Expand Up @@ -333,7 +334,9 @@ class MultiLineEditor extends Component {
this.editor.setOption('readOnly', this.props.readOnly || false);
}
if (this.props.mode !== prevProps.mode && this.editor) {
this.addOverlay(variables);
// `this.variables` is kept in sync by addOverlay(), so it is always the current
// variable set — no need to re-derive it just to re-apply the mode.
this.addOverlay(this.variables);
}
if (this.props.placeholder !== prevProps.placeholder && this.editor) {
this.editor.setOption('placeholder', this.props.placeholder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,19 +162,6 @@ const Wrapper = styled.div`
}
}

.empty-folder-message {
display: flex;
align-items: center;
height: 1.6rem;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.sidebar.muted};

.add-request-link {
color: ${(props) => props.theme.textLink};
cursor: pointer;
}
}

&.is-sidebar-dragging .collection-item-name {
cursor: inherit;
}
Expand Down
Loading
Loading