Skip to content

Conversation

@Raubzeug
Copy link
Contributor

No description provided.

@gravity-ui-bot
Copy link
Contributor

Preview is ready.

@gravity-ui-bot
Copy link
Contributor

Visual Tests Report is ready.

@Raubzeug Raubzeug marked this pull request as ready for review November 11, 2025 12:57

await page.getByTestId('qa:structuredyson:search').locator('input').fill('Attr');
// Wait for search to complete
await page.waitForTimeout(300);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to wait for some search results like a highlighted text or N matches?

await page.getByTestId('qa:structuredyson:search').locator('input').fill('attr');

// Wait for search to complete
await page.waitForTimeout(300);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

await page.getByTestId('qa:structuredyson:search:next').click();

// Wait for expansion and navigation
await page.waitForTimeout(300);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

const count = allMatchPaths.filter((matchPath) => {
// Match if path is exactly the item path (match at the node itself)
// or starts with prefix (match inside the node)
return matchPath === item.path || matchPath.startsWith(prefix);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to compare the strings twice:

const sameStart = matchPath.startsWith(item.path);
if (item.path.length === matchPath.length ) {
    // exact match
    return true;
}
if (matchPath[item.path.length] === '/') {
    // match inside the node
    return true;
}

return false;

if (collapsedState[checkPath]) {
return checkPath;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to avoid unnecessary split/join calls:

let nextSlash: number = -1;
while(true) {
    nextSlash = matchPath.nextIndex('/', nextSlash + 1)
    if (nextSlash === -1) {
        break;
    }

    const checkPath = matchPath.slice(0, nextSlash);
     if (collapsedState[checkPath]) {
           return checkPath;
     }
}

}
// Calculate next index in total matches
let nextTotalIndex = matchIndex + diff;
nextTotalIndex = ((nextTotalIndex % totalMatches) + totalMatches) % totalMatches;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it the same for:

const nextTotalIndex = (matchIndex + diff) % totalMatches;

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot simplify ((nextTotalIndex % totalMatches) + totalMatches) % totalMatches because:

  • onPrevMatch() calls onNextMatch(null, -1) for backward navigation
  • This makes nextTotalIndex = matchIndex + (-1) potentially negative (e.g., when matchIndex = 0)
  • JavaScript's % operator returns negative results: (-1) % 5 = -1 (incorrect index)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const nextTotalIndex = (totalMatches + matchIndex + diff) % totalMatches;

this.searchRef.current?.focus();
// Target is hidden - expand the collapsed parent
const newCollapsedState = {...collapsedState};
delete newCollapsedState[collapsedParent];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it expands only one level for each call of this.onNextMatch. Can we expand all parents here before call retry this.onNextMatch(...)?

paths.push(...collectMapMatches(value.$value, filter, settings, isJson, valuePath));
} else if (value.$type === 'list' && value.$value) {
paths.push(...collectListMatches(value.$value, filter, settings, isJson, valuePath));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is better to addelse {...} block here for primitive types:

} else {
    const valueMatch = rowSearchInfo(value, filter, settings);
    if (vauleMatch && currentPath) {
        paths.push(currentPath);
    }
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you have forgotten about it, haven't you?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry! fixed!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need checkPrimitiveMatch function anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally it does unnecessary check

paths.push(...collectAttributeMatches(value, filter, settings, isJson, currentPath));

// Get value path with JSON $ prefix if needed
const valuePath = getJsonValuePath(value, isJson, currentPath);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to calculate the value only for processing structures.

/**
* Collect all paths that contain search matches (including in collapsed nodes)
*/
function collectAllMatchPaths(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more efficient to pass paths as an additional parameter to avoid unnecessary memory allocations for internal arrays, like:

function collectAllMatchPaths(
    dstPaths: Array<string>,
    ...
) {
    ...
    return dstPaths;
}

this.tableRef.current?.scrollToIndex(matchedRows[visibleMatchCount]);
this.searchRef.current?.focus();
nextSlash++;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to avoid copy-paste from findCollapsedParent here, like:

const expandCollapsedPath = (path: string) {
    if (effectiveCollapsedState === collapsedState) {
        effectiveCollapsedState = {...collapsedState};
    }
    delete effectiveCollapsedState[collapsedParent];
}

let collapsedParent: string | undefined;
while(collapsedParent = this.findCollapsedParent(..., effectiveCollapsedState)) {
    expandPath(collapsedParent);
}

?

}
// Check the full path as well
if (collapsedState[targetMatchPath]) {
if (!hasCollapsedParents) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (collapsedPath) {
    expandPath(targetMatchPath)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored!

}

// If we expanded any parents, recalculate state and retry
if (hasCollapsedParents) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (collapsedState !== effectiveCollapsedState) {
    ...

this.setState({matchIndex: nextTotalIndex});
this.tableRef.current?.scrollToIndex(matchedRows[visibleMatchCount]);
this.searchRef.current?.focus();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line ranges 310-326 and 334-346 looks like a copy paste. Isn't it better to extract it to a local helper, like:

const navigateTo = ({matchIndex, matchedRows, collapsedState}) => {
    ...
}

Is there any blockers for such approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no blockers!

paths.push(...collectMapMatches(value.$value, filter, settings, isJson, valuePath));
} else if (value.$type === 'list' && value.$value) {
paths.push(...collectListMatches(value.$value, filter, settings, isJson, valuePath));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you have forgotten about it, haven't you?

@Raubzeug Raubzeug requested a review from ma-efremoff November 14, 2025 14:12
}
});
return;
}
Copy link
Collaborator

@ma-efremoff ma-efremoff Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add else here for next call?
I see there is return in the end of if block

@Raubzeug Raubzeug merged commit ffaccbe into main Nov 14, 2025
3 checks passed
@Raubzeug Raubzeug deleted the search-folded branch November 14, 2025 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants