Skip to content

feat: add redeploy from function button#1923

Open
qstearns wants to merge 13 commits intomainfrom
add-redeploy-from-function-button-onKh
Open

feat: add redeploy from function button#1923
qstearns wants to merge 13 commits intomainfrom
add-redeploy-from-function-button-onKh

Conversation

@qstearns
Copy link
Copy Markdown
Contributor

@qstearns qstearns commented Mar 19, 2026

Adds a feature to enable redeploying an individual function asset to an older revision


Open with Devin

@qstearns qstearns requested a review from a team as a code owner March 19, 2026 12:14
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 19, 2026

🦋 Changeset detected

Latest commit: 942071a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
dashboard Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gram-docs-redirect Ready Ready Preview, Comment Mar 19, 2026 11:25pm

Request Review

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +32 to +36
mutationFn: async ({ deploymentId, slug, type }: RedeploySourceParams) => {
const activeDeployment = activeResult?.deployment;
if (!activeDeployment) {
throw new Error("No active deployment found.");
}
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.

🚩 Removed safety check: active deployment must match latest deployment

The old useRedeployFunction (useRedeployFunction.tsx:38-42, now deleted) had an explicit guard: if (activeDeployment.id !== latestDeployment.id) throw new Error(...). This prevented redeployment when a newer deployment was in progress. The new useRedeploySource intentionally removes this check to support redeploying from any older deployment. This is a behavioral change that could allow evolving the active deployment while a newer deployment is building. Worth confirming this is the intended design.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +70 to +71
return fullDeployment.externalMcps?.find((m) => m.slug === sourceSlug)
?.id;
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot Mar 19, 2026

Choose a reason for hiding this comment

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

🟡 DeploymentsForExternalMCPSource query uses per-row PK instead of stable content identifier, breaking version deduplication

The DeploymentsForExternalMCPSource SQL query uses ema.id as asset_id for the LAG-based deduplication filter (WHERE asset_id IS DISTINCT FROM prev_asset_id). However, ema.id is the auto-generated primary key of external_mcp_attachments, which is unique per row — every deployment clone creates a new record with a new id (see CloneDeploymentExternalMCPs at server/internal/deployments/queries.sql:788). Unlike deployments_openapiv3_assets and deployments_functions which have a stable asset_id referencing an uploaded file in the assets table, external_mcp_attachments has no such column (server/database/schema.sql:1276-1293). This means the LAG comparison will always find different IDs, and the deduplication filter is a no-op — every deployment containing the MCP source is returned, defeating the purpose of showing only version-change boundaries.

This also affects the client side: in SourceDeploymentsPanel.tsx:243, activeAssetId for external MCP is dep.externalMcps?.find((m) => m.slug === sourceSlug)?.id, which will never match deployment.assetId from a different deployment's SourceDeploymentSummary, making isCurrentVersion always false.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread client/dashboard/src/pages/sources/SourceDeploymentsPanel.tsx Outdated
Comment thread client/dashboard/src/pages/sources/SourceDeploymentsPanel.tsx Outdated
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment thread client/dashboard/src/pages/sources/SourceDeploymentsPanel.tsx
Comment on lines +352 to +440
func (s *Service) DeploymentsForSource(ctx context.Context, form *gen.DeploymentsForSourcePayload) (*gen.DeploymentsForSourceResult, error) {
authCtx, ok := contextvalues.GetAuthContext(ctx)
if !ok || authCtx == nil || authCtx.ProjectID == nil {
return nil, oops.C(oops.CodeUnauthorized)
}

var cursor uuid.NullUUID
if form.Cursor != nil {
c, err := uuid.Parse(*form.Cursor)
if err != nil {
return nil, oops.E(oops.CodeBadRequest, err, "invalid cursor").Log(ctx, s.logger)
}
cursor = uuid.NullUUID{UUID: c, Valid: true}
}

type row struct {
ID uuid.UUID
AssetID uuid.UUID
Status string
CreatedAt pgtype.Timestamptz
ToolCount int64
}

var rows []row

switch form.Kind {
case "openapi":
r, err := s.repo.DeploymentsForOpenAPISource(ctx, repo.DeploymentsForOpenAPISourceParams{
Slug: form.Slug,
ProjectID: *authCtx.ProjectID,
Cursor: cursor,
})
if err != nil {
return nil, oops.E(oops.CodeUnexpected, err, "list deployments for openapi source").Log(ctx, s.logger)
}
for _, v := range r {
rows = append(rows, row{ID: v.ID, AssetID: v.AssetID, Status: v.Status, CreatedAt: v.CreatedAt, ToolCount: v.ToolCount})
}
case "function":
r, err := s.repo.DeploymentsForFunctionSource(ctx, repo.DeploymentsForFunctionSourceParams{
Slug: form.Slug,
ProjectID: *authCtx.ProjectID,
Cursor: cursor,
})
if err != nil {
return nil, oops.E(oops.CodeUnexpected, err, "list deployments for function source").Log(ctx, s.logger)
}
for _, v := range r {
rows = append(rows, row{ID: v.ID, AssetID: v.AssetID, Status: v.Status, CreatedAt: v.CreatedAt, ToolCount: v.ToolCount})
}
case "externalmcp":
r, err := s.repo.DeploymentsForExternalMCPSource(ctx, repo.DeploymentsForExternalMCPSourceParams{
Slug: form.Slug,
ProjectID: *authCtx.ProjectID,
Cursor: cursor,
})
if err != nil {
return nil, oops.E(oops.CodeUnexpected, err, "list deployments for external mcp source").Log(ctx, s.logger)
}
for _, v := range r {
rows = append(rows, row{ID: v.ID, AssetID: v.AssetID, Status: v.Status, CreatedAt: v.CreatedAt, ToolCount: v.ToolCount})
}
default:
return nil, oops.E(oops.CodeBadRequest, nil, "invalid source kind").Log(ctx, s.logger)
}

items := make([]*gen.SourceDeploymentSummary, 0, len(rows))
for _, r := range rows {
items = append(items, &gen.SourceDeploymentSummary{
ID: r.ID.String(),
AssetID: r.AssetID.String(),
Status: r.Status,
CreatedAt: r.CreatedAt.Time.Format(time.RFC3339),
ToolCount: r.ToolCount,
})
}

var nextCursor *string
limit := 50
if len(items) >= limit+1 {
nextCursor = &items[limit].ID
items = items[:limit]
}

return &gen.DeploymentsForSourceResult{
NextCursor: nextCursor,
Items: items,
}, nil
}
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.

🚩 No tests added for the new DeploymentsForSource endpoint

The CONTRIBUTING.md states 'Add tests for all new contributions.' The new DeploymentsForSource server method in impl.go:352-440 and the three new SQL queries have no corresponding test coverage. Given the complexity of the LAG-based deduplication logic and the three different source type branches, tests would be especially valuable here to catch edge cases (empty results, cursor pagination, the deduplication behavior).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

@disintegrator disintegrator left a comment

Choose a reason for hiding this comment

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

Directionally this feature is important to have but I'm not qualified to say if the way the database is queried for it is a good idea or not.

If you can share an investigation into that with a coding agent then I'd appreciate it.

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