Skip to content

SCAL-257337: Added sortOptions to getUnderlyingDataForPoint - #678

Open
tushardeepakts wants to merge 6 commits into
mainfrom
SCAL-257337_answer_sort
Open

tushardeepakts wants to merge 6 commits into
mainfrom
SCAL-257337_answer_sort

Conversation

@tushardeepakts

@tushardeepakts tushardeepakts commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an optional sortOptions parameter so customers can sort the raw underlying data behind a clicked chart point.

Changes

  • Added a new public SortOptions type ({ columnName; ascending }), exported from index.ts.
  • Added an optional sortOptions parameter to getUnderlyingDataForPoint(cols, points?, sortOptions?).
    • Applies sorting to the new session before returning it.
  • Added a new updateSort mutation.
  • Added a private applySort() helper.
    • Maps column names to GUIDs using the parent source detail.
    • Avoids an extra round-trip.

Video:

Screen.Recording.2026-09-21.at.2.53.20.PM.mov

@tushardeepakts
tushardeepakts requested a review from a team as a code owner September 18, 2026 08:03
@tushardeepakts tushardeepakts self-assigned this Sep 18, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces sorting capabilities to the getUnderlyingDataForPoint method in AnswerService by adding a new SortOptions interface, an updateSort GraphQL mutation, and the applySort helper method. Corresponding unit tests have also been added to verify the sorting behavior. The review feedback suggests adding defensive checks within applySort to handle missing or invalid inputs (such as undefined source columns or unrecognized column names) to prevent runtime TypeErrors and improve robustness.

Comment thread src/utils/graphql/answerService/answerService.ts Outdated
@pkg-pr-new

pkg-pr-new Bot commented Sep 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@thoughtspot/visual-embed-sdk@678

commit: b2f130b

@tushardeepakts tushardeepakts changed the title SCAL-257337: Add sort to getUnderlyingDataForPoint SCAL-257337: Added sortOptions to getUnderlyingDataForPoint Sep 18, 2026
/**
* Describes how to sort a column when fetching data. Pass an array of these to
* sort by multiple columns, in priority order.
* @version SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

fix the version

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The SDK version I have taken from package.json, and TS cloud version I have calculated from the formula mentioned in Claude.md file :

For SDK 1.N.x the Cloud version is 26.(N-43).0.cl

private async applySort(sortOptions: SortOptions[], sourceDetail: any) {
const sortDetails = sortOptions.map((sort) => ({
columnId: getGuidsFromColumnNames(sourceDetail, [sort.columnName]).values().next().value,
sortType: sort.ascending ? 'ASCENDING' : 'DESCENDING',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

make a enum for this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These are internal wire-contract values used once in a ternary. An enum here would be unnecessary. The public API only exposes ascending: boolean; ASCENDING/DESCENDING are just internal GraphQL values.

}

if (sortOptions?.length) {
await unaggAnswerSession.applySort(sortOptions, sourceDetail);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sort should supported by api right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The API can sort. applySort is not sorting — it's just a translator so a customer can trigger that API without knowing its details.

Customer knows: column name + ascending: true/false
API wants: answer column id + "ASCENDING"/"DESCENDING" on the right session

applySort converts the first into the second, then calls the API. That's it. Same reason addFilter and addColumns exist — each is a friendly wrapper over one backend call so customers don't touch raw GraphQL.

Changed its name to "adaptSorting" which makes more sense

@shivam-kumar-ts shivam-kumar-ts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@shivam-kumar-ts shivam-kumar-ts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

fix version

@shivam-kumar-ts

Copy link
Copy Markdown
Contributor

@gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces sorting capabilities when fetching underlying data in AnswerService by adding a SortOptions interface, updating the getUnderlyingDataForPoint method, and implementing the updateSort GraphQL mutation. Feedback on these changes highlights critical robustness issues in adaptSorting and getGuidToColumnIdMap that could lead to runtime TypeError exceptions due to missing defensive checks. Additionally, a JSDoc tag ordering violation was identified in getUnderlyingDataForPoint where the @version tag is incorrectly placed after the @example block.

Comment on lines +219 to +236
private async adaptSorting(sortOptions: SortOptions[], sourceDetail: any) {
const { answer } = await this.executeQuery(queries.getAnswer, {});
const guidToColumnId = getGuidToColumnIdMap(answer);
const sortOrder = sortOptions.map((sort) => {
const guid = getGuidsFromColumnNames(sourceDetail, [sort.columnName])
.values().next().value;
return {
columnId: guidToColumnId.get(guid),
sortType: sort.ascending ? 'ASCENDING' : 'DESCENDING',
};
});
return this.executeQuery(
queries.updateSort,
{
sortOrder,
},
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The current implementation of adaptSorting calls getGuidsFromColumnNames which is prone to throwing a TypeError if any of the provided column names in sortOptions do not exist in the data source (since it attempts to access .id on an undefined column object).

Additionally, destructuring const { answer } = await this.executeQuery(...) can throw a TypeError if the query execution fails or returns an empty/undefined response.

We can make this method significantly more robust and defensive by:

  1. Safely checking the query response before destructuring.
  2. Finding the column directly from sourceDetail.columns using safe optional chaining, which avoids calling the crash-prone getGuidsFromColumnNames helper.
  3. Filtering out any invalid or unmapped columns instead of letting them cause runtime exceptions.
    private async adaptSorting(sortOptions: SortOptions[], sourceDetail: any) {
        const response = await this.executeQuery(queries.getAnswer, {});
        const answer = response?.answer;
        if (!answer) {
            return null;
        }
        const guidToColumnId = getGuidToColumnIdMap(answer);
        const sortOrder = sortOptions
            .map((sort) => {
                const column = sourceDetail?.columns?.find(
                    (col: any) => col.name.toLowerCase() === sort.columnName.toLowerCase(),
                );
                const guid = column?.id;
                const columnId = guid ? guidToColumnId.get(guid) : undefined;
                if (!columnId) {
                    return null;
                }
                return {
                    columnId,
                    sortType: sort.ascending ? 'ASCENDING' : 'DESCENDING',
                };
            })
            .filter((item): item is { columnId: string; sortType: string } => !!item);

        if (sortOrder.length === 0) {
            return null;
        }

        return this.executeQuery(
            queries.updateSort,
            {
                sortOrder,
            },
        );
    }

Comment on lines +493 to +500
function getGuidToColumnIdMap(answer: any): Map<string, string> {
const tableViz = answer.visualizations.find((viz: any) => viz.columns);
return new Map(
tableViz.columns.map(
(col: any) => [col.column.referencedColumns[0].guid, col.column.id],
),
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The newly added helper function getGuidToColumnIdMap is not defensively programmed and can easily throw runtime TypeError exceptions under several conditions:

  1. If answer or answer.visualizations is undefined.
  2. If no visualization with columns is found (making tableViz undefined).
  3. If any column does not have a referencedColumns array or if the array is empty (causing referencedColumns[0] to be undefined).

We should use optional chaining and safe iteration to ensure this helper is completely crash-proof.

function getGuidToColumnIdMap(answer: any): Map<string, string> {
    const tableViz = answer?.visualizations?.find((viz: any) => viz.columns);
    const columns = tableViz?.columns || [];
    const map = new Map<string, string>();
    for (const col of columns) {
        const guid = col?.column?.referencedColumns?.[0]?.guid;
        const id = col?.column?.id;
        if (guid && id) {
            map.set(guid, id);
        }
    }
    return map;
}

* automatically passed.
* @param outputColumnNames
* @param selectedPoints
* @param sortOptions Optional columns to sort the underlying data by.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The JSDoc block for getUnderlyingDataForPoint violates the canonical tag ordering rule specified in the repository style guide. Currently, the @version tag (on line 359) is placed after the @example block. According to the style guide, @version must be placed before @param and @example.

Please reorder the tags so that @version is placed before @param.

References
  1. Enforce this canonical tag order within every JSDoc block: 1. Short description, 2. @link, 3. @Version, 4. @deprecated, 5. @PARAM, 6. @returns, 7. @hidden, 8. @group, 9. @default, 10. @example. (link)

This branch has not been deployed

No deployments
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.

2 participants