Skip to content

Conversation

c298lee
Copy link
Contributor

@c298lee c298lee commented Jan 27, 2025

In preparation for upgrading to Sentry SDK v9, we need to replace urlEncode as it's deprecated. We are replacing it with stringify from query-string, which stringifies an object into a query string and sorts the keys, as well as URL encodes the keys and values.

Merge after #84196

@c298lee c298lee requested review from billyvg and chargome January 27, 2025 22:21
@c298lee c298lee requested review from a team as code owners January 27, 2025 22:21
@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Jan 27, 2025
Copy link

codecov bot commented Jan 27, 2025

❌ 10 Tests Failed:

Tests completed Failed Passed Skipped
8610 10 8600 0
View the top 3 failed tests by shortest run time
AddToDashboardButton opens the dashboard modal with the correct query for samples mode
Stack Traces | 0.038s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

Expected: ObjectContaining {"widget": {"displayType": "line", "interval": undefined, "limit": undefined, "queries": [{"aggregates": ["avg(span.duration)"], "columns": [], "conditions": "", "fields": ["avg(span.duration)"], "name": "", "orderby": "-timestamp"}], "title": "Custom Widget", "widgetType": "spans"}, "widgetAsQueryParams": ObjectContaining {"dataset": "spans", "defaultTableColumns": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"], "defaultTitle": "Custom Widget", "defaultWidgetQuery": "name=&aggregates=avg(span.duration)&columns=&fields=avg(span.duration)&conditions=&orderby=-timestamp", "displayType": "line", "field": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"]}}
Received: {"location": {"action": "PUSH", "hash": "", "key": "", "pathname": "/mock-pathname/", "query": {}, "search": "", "state": null}, "organization": {"access": ["org:read", "org:write", "org:admin", "org:integrations", "project:read", "project:write", "project:releases", "project:admin", "team:read", "team:write", …], "aggregatedDataConsent": false, "alertsMemberWrite": false, "allowJoinRequests": false, "allowMemberInvite": true, "allowMemberProjectCreation": false, "allowSharedIssues": false, "allowSuperuserAccess": false, "attachmentsRole": "member", "availableRoles": [], "avatar": {"avatarType": "default", "avatarUrl": null, "avatarUuid": null}, "codecovAccess": false, "dataScrubber": false, "dataScrubberDefaults": false, "dateCreated": "2017-10-17T02:41:20.000Z", "debugFilesRole": "", "defaultRole": "", "enhancedPrivacy": false, "eventsMemberAdmin": false, "experiments": {}, "features": [], "genAIConsent": false, "githubNudgeInvite": false, "githubOpenPRBot": false, "githubPRBot": false, "hideAiFeatures": false, "id": "3", "isDefault": false, "isDynamicallySampled": true, "isEarlyAdopter": false, "issueAlertsThreadFlag": false, "links": {"organizationUrl": "https://org-slug.sentry.io", "regionUrl": "https://us.sentry.io"}, "metricAlertsThreadFlag": false, "name": "Organization Name", "onboardingTasks": [], "openMembership": false, "orgRoleList": [{"desc": "Can manage subscription and billing details.", "id": "billing", "isAllowed": false, "isGlobal": false, "isRetired": false, "isTeamRolesAllowed": false, "is_global": false, "minimumTeamRole": "contributor", "name": "Billing"}, {"desc": "Members can view and act on events, as well as view most other data within the organization.", "id": "member", "isAllowed": false, "isGlobal": false, "isRetired": false, "isTeamRolesAllowed": true, "is_global": false, "minimumTeamRole": "contributor", "name": "Member"}, {"desc": "Admin privileges on any teams of which they're a member. They can create new teams and projects, as well as remove teams and projects on which they already hold membership (or all teams, if open membership is enabled). Additionally, they can manage memberships of teams that they are members of. They cannot invite members to the organization.", "id": "admin", "isAllowed": false, "isGlobal": false, "isRetired": true, "isTeamRolesAllowed": true, "is_global": false, "minimumTeamRole": "admin", "name": "Admin"}, {"desc": "Gains admin access on all teams as well as the ability to add and remove members.", "id": "manager", "isAllowed": false, "isGlobal": true, "isRetired": false, "isTeamRolesAllowed": true, "is_global": true, "minimumTeamRole": "admin", "name": "Manager"}, {"desc": "Gains full permission across the organization. Can manage members as well as perform catastrophic operations such as removing the organization.", "id": "owner", "isAllowed": false, "isGlobal": true, "isRetired": false, "isTeamRolesAllowed": true, "is_global": true, "minimumTeamRole": "admin", "name": "Owner"}], "pendingAccessRequests": 0, "quota": {"accountLimit": null, "maxRate": null, "maxRateInterval": null, "projectLimit": null}, "relayPiiConfig": null, "require2FA": false, "requiresSso": false, "safeFields": [], "samplingMode": "organization", "scrapeJavaScript": true, "scrubIPAddresses": false, "sensitiveFields": [], "slug": "org-slug", "status": {"id": "active", "name": "active"}, "storeCrashReports": 0, "streamlineOnly": null, "targetSampleRate": 1, "teamRoleList": [{"desc": "...", "id": "contributor", "isMinimumRoleFor": "", "isRetired": false, "name": "Contributor"}, {"desc": "...", "id": "admin", "isMinimumRoleFor": "", "isRetired": false, "name": "Team Admin"}], "trustedRelays": []}, "router": {"createHref": [Function mockConstructor], "createPath": [Function mockConstructor], "go": [Function mockConstructor], "goBack": [Function mockConstructor], "goForward": [Function mockConstructor], "isActive": [Function mockConstructor], "location": {"action": "PUSH", "hash": "", "key": "", "pathname": "/mock-pathname/", "query": {}, "search": "", "state": null}, "params": {"orgId": "org-slug", "projectId": "project-slug"}, "push": [Function mockConstructor], "replace": [Function mockConstructor], "routes": [], "setRouteLeaveHook": [Function mockConstructor]}, "selection": {"datetime": {"end": undefined, "period": "14d", "start": undefined, "utc": undefined}, "environments": [], "projects": []}, "widget": {"displayType": "line", "interval": undefined, "limit": undefined, "queries": [{"aggregates": ["avg(span.duration)"], "columns": [], "conditions": "", "fields": ["avg(span.duration)"], "name": "", "orderby": "-timestamp"}], "title": "Custom Widget", "widgetType": "spans"}, "widgetAsQueryParams": {"dataset": "spans", "defaultTableColumns": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"], "defaultTitle": "Custom Widget", "defaultWidgetQuery": "aggregates=avg%28span.duration%29&conditions=&fields=avg%28span.duration%29&name=&orderby=-timestamp", "displayType": "line", "end": undefined, "field": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"], "limit": undefined, "source": "traceExplorer", "start": undefined, "statsPeriod": "14d"}}

Number of calls: 1
    at Object.<anonymous> (.../explore/hooks/useAddToDashboard.spec.tsx:47:44)
AddToDashboardButton takes the first 3 yAxes
Stack Traces | 0.059s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

Expected: ObjectContaining {"widget": {"displayType": "line", "interval": undefined, "limit": undefined, "queries": [{"aggregates": ["avg(span.duration)", "max(span.duration)", "min(span.duration)"], "columns": [], "conditions": "", "fields": ["avg(span.duration)", "max(span.duration)", "min(span.duration)"], "name": "", "orderby": "-avg(span.duration)"}], "title": "Custom Widget", "widgetType": "spans"}, "widgetAsQueryParams": ObjectContaining {"dataset": "spans", "defaultTableColumns": ["span.op", "avg(span.duration)", "max(span.duration)", "min(span.duration)"], "defaultTitle": "Custom Widget", "defaultWidgetQuery": "name=&aggregates=avg(span.duration)%2Cmax(span.duration)%2Cmin(span.duration)&columns=&fields=avg(span.duration)%2Cmax(span.duration)%2Cmin(span.duration)&conditions=&orderby=-avg(span.duration)", "displayType": "line", "field": ["span.op", "avg(span.duration)", "max(span.duration)", "min(span.duration)"]}}
Received: {"location": {"action": "POP", "hash": "", "key": "0r11k6rw", "pathname": "/mock-pathname/", "query": {"mode": "aggregate", "visualize": "{\"chartType\":1,\"yAxes\":[\"avg(span.duration)\",\"max(span.duration)\",\"min(span.duration)\",\"p90(span.duration)\"]}"}, "search": "?mode=aggregate&visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22avg%28span.duration%29%22%2C%22max%28span.duration%29%22%2C%22min%28span.duration%29%22%2C%22p90%28span.duration%29%22%5D%7D", "state": null}, "organization": {"access": ["org:read", "org:write", "org:admin", "org:integrations", "project:read", "project:write", "project:releases", "project:admin", "team:read", "team:write", …], "aggregatedDataConsent": false, "alertsMemberWrite": false, "allowJoinRequests": false, "allowMemberInvite": true, "allowMemberProjectCreation": false, "allowSharedIssues": false, "allowSuperuserAccess": false, "attachmentsRole": "member", "availableRoles": [], "avatar": {"avatarType": "default", "avatarUrl": null, "avatarUuid": null}, "codecovAccess": false, "dataScrubber": false, "dataScrubberDefaults": false, "dateCreated": "2017-10-17T02:41:20.000Z", "debugFilesRole": "", "defaultRole": "", "enhancedPrivacy": false, "eventsMemberAdmin": false, "experiments": {}, "features": [], "genAIConsent": false, "githubNudgeInvite": false, "githubOpenPRBot": false, "githubPRBot": false, "hideAiFeatures": false, "id": "3", "isDefault": false, "isDynamicallySampled": true, "isEarlyAdopter": false, "issueAlertsThreadFlag": false, "links": {"organizationUrl": "https://org-slug.sentry.io", "regionUrl": "https://us.sentry.io"}, "metricAlertsThreadFlag": false, "name": "Organization Name", "onboardingTasks": [], "openMembership": false, "orgRoleList": [{"desc": "Can manage subscription and billing details.", "id": "billing", "isAllowed": false, "isGlobal": false, "isRetired": false, "isTeamRolesAllowed": false, "is_global": false, "minimumTeamRole": "contributor", "name": "Billing"}, {"desc": "Members can view and act on events, as well as view most other data within the organization.", "id": "member", "isAllowed": false, "isGlobal": false, "isRetired": false, "isTeamRolesAllowed": true, "is_global": false, "minimumTeamRole": "contributor", "name": "Member"}, {"desc": "Admin privileges on any teams of which they're a member. They can create new teams and projects, as well as remove teams and projects on which they already hold membership (or all teams, if open membership is enabled). Additionally, they can manage memberships of teams that they are members of. They cannot invite members to the organization.", "id": "admin", "isAllowed": false, "isGlobal": false, "isRetired": true, "isTeamRolesAllowed": true, "is_global": false, "minimumTeamRole": "admin", "name": "Admin"}, {"desc": "Gains admin access on all teams as well as the ability to add and remove members.", "id": "manager", "isAllowed": false, "isGlobal": true, "isRetired": false, "isTeamRolesAllowed": true, "is_global": true, "minimumTeamRole": "admin", "name": "Manager"}, {"desc": "Gains full permission across the organization. Can manage members as well as perform catastrophic operations such as removing the organization.", "id": "owner", "isAllowed": false, "isGlobal": true, "isRetired": false, "isTeamRolesAllowed": true, "is_global": true, "minimumTeamRole": "admin", "name": "Owner"}], "pendingAccessRequests": 0, "quota": {"accountLimit": null, "maxRate": null, "maxRateInterval": null, "projectLimit": null}, "relayPiiConfig": null, "require2FA": false, "requiresSso": false, "safeFields": [], "samplingMode": "organization", "scrapeJavaScript": true, "scrubIPAddresses": false, "sensitiveFields": [], "slug": "org-slug", "status": {"id": "active", "name": "active"}, "storeCrashReports": 0, "streamlineOnly": null, "targetSampleRate": 1, "teamRoleList": [{"desc": "...", "id": "contributor", "isMinimumRoleFor": "", "isRetired": false, "name": "Contributor"}, {"desc": "...", "id": "admin", "isMinimumRoleFor": "", "isRetired": false, "name": "Team Admin"}], "trustedRelays": []}, "router": {"createHref": [Function createHref], "createPath": [Function createPath], "go": [Function go], "goBack": [Function goBack], "goForward": [Function goForward], "isActive": [Function isActive], "location": {"action": "POP", "hash": "", "key": "0r11k6rw", "pathname": "/mock-pathname/", "query": {"mode": "aggregate", "visualize": "{\"chartType\":1,\"yAxes\":[\"avg(span.duration)\",\"max(span.duration)\",\"min(span.duration)\",\"p90(span.duration)\"]}"}, "search": "?mode=aggregate&visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22avg%28span.duration%29%22%2C%22max%28span.duration%29%22%2C%22min%28span.duration%29%22%2C%22p90%28span.duration%29%22%5D%7D", "state": null}, "params": {"*": "mock-pathname/"}, "push": [Function push], "replace": [Function replace], "routes": [{"path": ""}], "setRouteLeaveHook": [Function setRouteLeaveHook]}, "selection": {"datetime": {"end": undefined, "period": "14d", "start": undefined, "utc": undefined}, "environments": [], "projects": []}, "widget": {"displayType": "line", "interval": undefined, "limit": undefined, "queries": [{"aggregates": ["avg(span.duration)", "max(span.duration)", "min(span.duration)"], "columns": [], "conditions": "", "fields": ["avg(span.duration)", "max(span.duration)", "min(span.duration)"], "name": "", "orderby": "-avg(span.duration)"}], "title": "Custom Widget", "widgetType": "spans"}, "widgetAsQueryParams": {"dataset": "spans", "defaultTableColumns": ["span.op", "avg(span.duration)", "max(span.duration)", "min(span.duration)"], "defaultTitle": "Custom Widget", "defaultWidgetQuery": "aggregates=avg%28span.duration%29&aggregates=max%28span.duration%29&aggregates=min%28span.duration%29&conditions=&fields=avg%28span.duration%29&fields=max%28span.duration%29&fields=min%28span.duration%29&name=&orderby=-avg%28span.duration%29", "displayType": "line", "end": undefined, "field": ["span.op", "avg(span.duration)", "max(span.duration)", "min(span.duration)"], "limit": undefined, "mode": "aggregate", "source": "traceExplorer", "start": undefined, "statsPeriod": "14d", "visualize": "{\"chartType\":1,\"yAxes\":[\"avg(span.duration)\",\"max(span.duration)\",\"min(span.duration)\",\"p90(span.duration)\"]}"}}

Number of calls: 1
    at Object.<anonymous> (.../explore/hooks/useAddToDashboard.spec.tsx:173:44)
AddToDashboardButton opens the dashboard modal with the correct query based on the visualize index
Stack Traces | 0.065s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

Expected: ObjectContaining {"widget": {"displayType": "line", "interval": undefined, "limit": undefined, "queries": [{"aggregates": ["max(span.duration)"], "columns": [], "conditions": "", "fields": ["max(span.duration)"], "name": "", "orderby": "-timestamp"}], "title": "Custom Widget", "widgetType": "spans"}, "widgetAsQueryParams": ObjectContaining {"dataset": "spans", "defaultTableColumns": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"], "defaultTitle": "Custom Widget", "defaultWidgetQuery": "name=&aggregates=max(span.duration)&columns=&fields=max(span.duration)&conditions=&orderby=-timestamp", "displayType": "line", "field": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"]}}
Received: {"location": {"action": "POP", "hash": "", "key": "c9a1m999", "pathname": "/mock-pathname/", "query": {"visualize": ["{\"chartType\":1,\"yAxes\":[\"avg(span.duration)\"]}", "{\"chartType\":1,\"yAxes\":[\"max(span.duration)\"]}"]}, "search": "?visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22avg%28span.duration%29%22%5D%7D&visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22max%28span.duration%29%22%5D%7D", "state": null}, "organization": {"access": ["org:read", "org:write", "org:admin", "org:integrations", "project:read", "project:write", "project:releases", "project:admin", "team:read", "team:write", …], "aggregatedDataConsent": false, "alertsMemberWrite": false, "allowJoinRequests": false, "allowMemberInvite": true, "allowMemberProjectCreation": false, "allowSharedIssues": false, "allowSuperuserAccess": false, "attachmentsRole": "member", "availableRoles": [], "avatar": {"avatarType": "default", "avatarUrl": null, "avatarUuid": null}, "codecovAccess": false, "dataScrubber": false, "dataScrubberDefaults": false, "dateCreated": "2017-10-17T02:41:20.000Z", "debugFilesRole": "", "defaultRole": "", "enhancedPrivacy": false, "eventsMemberAdmin": false, "experiments": {}, "features": [], "genAIConsent": false, "githubNudgeInvite": false, "githubOpenPRBot": false, "githubPRBot": false, "hideAiFeatures": false, "id": "3", "isDefault": false, "isDynamicallySampled": true, "isEarlyAdopter": false, "issueAlertsThreadFlag": false, "links": {"organizationUrl": "https://org-slug.sentry.io", "regionUrl": "https://us.sentry.io"}, "metricAlertsThreadFlag": false, "name": "Organization Name", "onboardingTasks": [], "openMembership": false, "orgRoleList": [{"desc": "Can manage subscription and billing details.", "id": "billing", "isAllowed": false, "isGlobal": false, "isRetired": false, "isTeamRolesAllowed": false, "is_global": false, "minimumTeamRole": "contributor", "name": "Billing"}, {"desc": "Members can view and act on events, as well as view most other data within the organization.", "id": "member", "isAllowed": false, "isGlobal": false, "isRetired": false, "isTeamRolesAllowed": true, "is_global": false, "minimumTeamRole": "contributor", "name": "Member"}, {"desc": "Admin privileges on any teams of which they're a member. They can create new teams and projects, as well as remove teams and projects on which they already hold membership (or all teams, if open membership is enabled). Additionally, they can manage memberships of teams that they are members of. They cannot invite members to the organization.", "id": "admin", "isAllowed": false, "isGlobal": false, "isRetired": true, "isTeamRolesAllowed": true, "is_global": false, "minimumTeamRole": "admin", "name": "Admin"}, {"desc": "Gains admin access on all teams as well as the ability to add and remove members.", "id": "manager", "isAllowed": false, "isGlobal": true, "isRetired": false, "isTeamRolesAllowed": true, "is_global": true, "minimumTeamRole": "admin", "name": "Manager"}, {"desc": "Gains full permission across the organization. Can manage members as well as perform catastrophic operations such as removing the organization.", "id": "owner", "isAllowed": false, "isGlobal": true, "isRetired": false, "isTeamRolesAllowed": true, "is_global": true, "minimumTeamRole": "admin", "name": "Owner"}], "pendingAccessRequests": 0, "quota": {"accountLimit": null, "maxRate": null, "maxRateInterval": null, "projectLimit": null}, "relayPiiConfig": null, "require2FA": false, "requiresSso": false, "safeFields": [], "samplingMode": "organization", "scrapeJavaScript": true, "scrubIPAddresses": false, "sensitiveFields": [], "slug": "org-slug", "status": {"id": "active", "name": "active"}, "storeCrashReports": 0, "streamlineOnly": null, "targetSampleRate": 1, "teamRoleList": [{"desc": "...", "id": "contributor", "isMinimumRoleFor": "", "isRetired": false, "name": "Contributor"}, {"desc": "...", "id": "admin", "isMinimumRoleFor": "", "isRetired": false, "name": "Team Admin"}], "trustedRelays": []}, "router": {"createHref": [Function createHref], "createPath": [Function createPath], "go": [Function go], "goBack": [Function goBack], "goForward": [Function goForward], "isActive": [Function isActive], "location": {"action": "POP", "hash": "", "key": "c9a1m999", "pathname": "/mock-pathname/", "query": {"visualize": ["{\"chartType\":1,\"yAxes\":[\"avg(span.duration)\"]}", "{\"chartType\":1,\"yAxes\":[\"max(span.duration)\"]}"]}, "search": "?visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22avg%28span.duration%29%22%5D%7D&visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22max%28span.duration%29%22%5D%7D", "state": null}, "params": {"*": "mock-pathname/"}, "push": [Function push], "replace": [Function replace], "routes": [{"path": ""}], "setRouteLeaveHook": [Function setRouteLeaveHook]}, "selection": {"datetime": {"end": undefined, "period": "14d", "start": undefined, "utc": undefined}, "environments": [], "projects": []}, "widget": {"displayType": "line", "interval": undefined, "limit": undefined, "queries": [{"aggregates": ["max(span.duration)"], "columns": [], "conditions": "", "fields": ["max(span.duration)"], "name": "", "orderby": "-timestamp"}], "title": "Custom Widget", "widgetType": "spans"}, "widgetAsQueryParams": {"dataset": "spans", "defaultTableColumns": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"], "defaultTitle": "Custom Widget", "defaultWidgetQuery": "aggregates=max%28span.duration%29&conditions=&fields=max%28span.duration%29&name=&orderby=-timestamp", "displayType": "line", "end": undefined, "field": ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp"], "limit": undefined, "source": "traceExplorer", "start": undefined, "statsPeriod": "14d", "visualize": ["{\"chartType\":1,\"yAxes\":[\"avg(span.duration)\"]}", "{\"chartType\":1,\"yAxes\":[\"max(span.duration)\"]}"]}}

Number of calls: 1
    at Object.<anonymous> (.../explore/hooks/useAddToDashboard.spec.tsx:93:44)

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

@billyvg
Copy link
Member

billyvg commented Jan 28, 2025

This is the riskiest PR of the bunch -- I don't really trust our test coverage around it either. It might be better to go back to the other solution we proposed re: copying over the SDK function into sentry. Let's leave a comment in the function docs about what we're doing too

Comment on lines -73 to -78
return urlEncode({
...query,
aggregates: query.aggregates.join(','),
fields: query.fields?.join(','),
columns: query.columns.join(','),
});
Copy link
Member

Choose a reason for hiding this comment

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

should this be changed?

@c298lee c298lee closed this Jan 29, 2025
@github-actions github-actions bot locked and limited conversation to collaborators Feb 14, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Scope: Frontend Automatically applied to PRs that change frontend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants