Skip to content

[GettingStartedWidget] Fix Invite User widget crash on null deletedAt#1714

Merged
leecalcote merged 2 commits into
masterfrom
claude/invite-user-widget-failure-vtdhm4
Jul 15, 2026
Merged

[GettingStartedWidget] Fix Invite User widget crash on null deletedAt#1714
leecalcote merged 2 commits into
masterfrom
claude/invite-user-widget-failure-vtdhm4

Conversation

@hortison

@hortison hortison commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Notes for Reviewers

The Invite User widget's team search crashed in production with:

Error: null is not an object (evaluating 't?.deletedAt.Valid')

Root cause. TeamSearchField.renderOption (and several sibling components) read .Valid directly off deletedAt. A soft-delete timestamp reaches the UI in three different shapes depending on the endpoint and serializer:

  • null / undefined — the canonical meshery/schemas wire shape for a live row (core.NullTime marshals a non-deleted row to JSON null). This is what the teams API returns, so null.Valid threw.
  • an ISO-8601 timestamp string — the canonical shape for a deleted row (type: string, format: date-time).
  • a legacy Go sql.NullTime { Valid, Time } object — still emitted by some provider endpoints.

Reading .Valid directly is unsafe for the first two shapes, and semantically wrong against the canonical contract (where .Valid never exists).

Fix (at the root, in the shared library). Add isSoftDeleted and getDeletedAtTime helpers plus a shared DeletedAt union type in src/utils/nullTime.ts, and route every soft-delete check through them. This mirrors the multi-shape helper meshery-cloud already maintains (ui/utility/deletedAt.ts). Components updated:

  • DashboardWidgets/GettingStartedWidget/TeamSearchField (the Invite User widget — the reported crash)
  • UserSearchField, UserSearchFieldInput
  • Workspaces/WorkspaceCard, Workspaces/helper (parseDeletionTimestamp)
  • TeamTable/TeamTableConfiguration, UsersTable

Several of these (WorkspaceCard, TeamTableConfiguration, UsersTable) read .Valid with no guard at all and shared the identical latent crash.

Tests. New src/__testing__/nullTime.test.ts covers every shape, including the null regression. Full suite: 349 passing. npm run build and eslint clean.

Signed commits

  • Yes, I signed my commits.

…wire shapes

The Invite User widget's team search crashed with
"null is not an object (evaluating 't?.deletedAt.Valid')": renderOption
read `.Valid` on a `deletedAt` that the teams API returns as `null` for
live rows, and `null.Valid` throws.

`deletedAt` reaches the UI in three shapes depending on the endpoint and
serializer: `null`/`undefined` (canonical live row), an ISO timestamp
string (canonical deleted row), or the legacy Go `sql.NullTime`
`{ Valid, Time }` object still emitted by some provider endpoints.
Reading `.Valid` directly is unsafe for the first two.

Add `isSoftDeleted` and `getDeletedAtTime` helpers (src/utils/nullTime)
plus a shared `DeletedAt` union type that tolerate every shape, and route
all soft-delete checks through them: TeamSearchField, UserSearchField,
UserSearchFieldInput, WorkspaceCard, UsersTable, TeamTableConfiguration,
and Workspaces/helper. Several of these read `.Valid` directly and shared
the same latent crash.

Add unit tests covering every shape, including the null regression.

Signed-off-by: Lee Calcote <leecalcote@gmail.com>
Copilot AI review requested due to automatic review settings July 15, 2026 21:51

@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 utility functions (isSoftDeleted and getDeletedAtTime) to safely handle various formats of the deletedAt soft-delete timestamp, preventing runtime crashes when accessing properties on null or undefined values. These utilities are integrated across several components and tables. However, a critical bug was identified in TeamTableConfiguration.tsx where the wrong row data index (4 instead of 6) is passed to isSoftDeleted, which would incorrectly disable action buttons for all teams.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

searchable: false,
customBodyRender: (_: string, tableMeta: MUIDataTableMeta) => {
if (bulkSelect || tableMeta.rowData[4].Valid) {
if (bulkSelect || isSoftDeleted(tableMeta.rowData[4])) {

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.

critical

In TeamTableConfiguration, the deletedAt column is at index 6 of the columns array, while index 4 corresponds to createdAt. Using tableMeta.rowData[4] with isSoftDeleted will check the createdAt timestamp (which is a non-empty string), causing isSoftDeleted to always return true. This results in the edit and delete action buttons being permanently disabled for all teams.

Please update the index to 6 to correctly reference the deletedAt column.

Suggested change
if (bulkSelect || isSoftDeleted(tableMeta.rowData[4])) {
if (bulkSelect || isSoftDeleted(tableMeta.rowData[6])) {

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.

Good catch — fixed in 64e4926. The index is now 6 (the deletedAt column) at this site, matching the already-correct setRowProps check, with a clarifying comment at both index sites. The old rowData[4].Valid was a latent wrong-index bug that was harmless only because .Valid on the createdAt string was undefined; isSoftDeleted correctly reads a timestamp string as deleted, which surfaced it.


Generated by Claude Code

Copilot AI 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.

Pull request overview

This PR hardens the UI against deletedAt coming back as null / timestamp string / legacy { Valid, Time }, fixing a production crash caused by directly reading .Valid on a null value. It centralizes soft-delete detection into shared utilities and updates affected widgets/tables to use those helpers.

Changes:

  • Added DeletedAt union type plus isSoftDeleted / getDeletedAtTime helpers in src/utils/nullTime.ts and exported them via src/utils/index.ts.
  • Updated multiple UI components to replace direct .deletedAt.Valid access with isSoftDeleted(...).
  • Added Jest coverage for all supported deletedAt shapes, including the null regression.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/utils/permissions.ts Updates User.deletedAt typing to the new DeletedAt union.
src/utils/nullTime.ts Introduces shared, shape-tolerant helpers for soft-delete handling.
src/utils/index.ts Re-exports the new null-time utilities from the utils barrel.
src/custom/Workspaces/WorkspaceCard.tsx Replaces .deletedAt.Valid checks with isSoftDeleted(...) to prevent null crashes.
src/custom/Workspaces/types.ts Aligns workspace/team deletedAt types with the shared DeletedAt union.
src/custom/Workspaces/helper.ts Switches deletion timestamp formatting to use getDeletedAtTime(...).
src/custom/UsersTable/UsersTable.tsx Uses isSoftDeleted(...) when deciding whether to disable row actions.
src/custom/UserSearchField/UserSearchFieldInput.tsx Prevents selecting deleted users using isSoftDeleted(...) instead of .Valid.
src/custom/UserSearchField/UserSearchField.tsx Uses isSoftDeleted(...) for option rendering (“(deleted)”) safely across shapes.
src/custom/TeamTable/TeamTableConfiguration.tsx Replaces multiple .Valid reads with isSoftDeleted(...) in table behaviors.
src/custom/DashboardWidgets/GettingStartedWidget/TeamSearchField.tsx Fixes the reported Invite User widget crash by avoiding .Valid on null.
src/testing/nullTime.test.ts Adds tests covering null/undefined, string timestamps, and legacy object shape.

Comment on lines +10 to 13
export const parseDeletionTimestamp = (data: { deletedAt: DeletedAt }) => {
const time = getDeletedAtTime(data?.deletedAt);
return time != null ? getFormatDate(time as string) : DEFAULT_DATE;
};

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.

Fixed in 64e4926. Dropped the as string cast and widened getFormatDate to accept string | number | Date (it already calls new Date(date) internally, which handles all three), so getDeletedAtTime's return value flows through without a cast and TS stays honest about the shape.


Generated by Claude Code

Address review feedback on the soft-delete hardening:

- TeamTableConfiguration action-column render read `rowData[4]`
  (createdAt) instead of `rowData[6]` (deletedAt). The old
  `rowData[4].Valid` was harmlessly undefined on the createdAt string, but
  `isSoftDeleted` correctly treats a non-empty timestamp string as deleted,
  which turned the pre-existing wrong-index bug into a real regression that
  permanently disabled every team's edit/delete actions. Use index 6 to
  match the deletedAt column (and the already-correct setRowProps check),
  with a clarifying comment at both index sites.
- parseDeletionTimestamp no longer casts the deletion time with
  `as string`. getDeletedAtTime can return string | number | Date, so widen
  getFormatDate to accept all three (it already calls new Date(date)) and
  pass the value through without a cast.

Signed-off-by: Lee Calcote <leecalcote@gmail.com>
@leecalcote
leecalcote merged commit 50b9a81 into master Jul 15, 2026
5 checks passed
@leecalcote
leecalcote deleted the claude/invite-user-widget-failure-vtdhm4 branch July 15, 2026 22:03
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.

3 participants