Skip to content

feat: add copy cURL request option to playground copy button - #3141

Open
JivusAyrus wants to merge 2 commits into
mainfrom
suvij/cosmo-64-copy-curl-request-in-the-playground
Open

feat: add copy cURL request option to playground copy button#3141
JivusAyrus wants to merge 2 commits into
mainfrom
suvij/cosmo-64-copy-curl-request-in-the-playground

Conversation

@JivusAyrus

@JivusAyrus JivusAyrus commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added a playground menu for copying GraphQL queries or generated cURL requests.
    • cURL commands include variables, headers, operation names, and shell-safe escaping.
    • Added environment-based header substitution and feature-flag request headers.
    • Added validation and warnings for malformed input, invalid headers, and missing environment placeholders.
  • Bug Fixes

    • Replaced the built-in copy control with a consistent application-managed copy menu.

Checklist

@JivusAyrus
JivusAyrus requested a review from a team as a code owner August 6, 2026 10:16
@github-actions github-actions Bot added the studio label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f1a59a84-b1e2-4953-b942-c6d3f006a1cc

📥 Commits

Reviewing files that changed from the base of the PR and between 8bffc04 and 8a69d66.

📒 Files selected for processing (4)
  • studio/src/__tests__/playground-curl.test.ts
  • studio/src/components/playground/copy-operation.tsx
  • studio/src/lib/playground-curl.ts
  • studio/src/lib/playground-headers.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • studio/src/components/playground/copy-operation.tsx
  • studio/src/lib/playground-headers.ts
  • studio/src/tests/playground-curl.test.ts

Walkthrough

The playground now supports copying the active query or a generated cURL request. It adds header validation, environment substitution, feature-flag propagation, warning reporting, toolbar integration, and automated cURL tests.

Changes

Playground copy flow

Layer / File(s) Summary
cURL generation and header processing
studio/src/lib/playground-curl.ts, studio/src/lib/playground-headers.ts, studio/src/__tests__/playground-curl.test.ts
Builds shell-quoted GraphQL cURL commands. Parses variables and headers, applies environment substitutions, adds default content types, reports malformed input warnings, and tests the generated output.
Copy operation component
studio/src/components/playground/copy-operation.tsx, studio/src/components/playground/types.ts
Adds query and cURL copy actions with validation, clipboard error handling, warning toasts, and routing and feature-flag context fields.
Playground toolbar integration
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/playground.tsx, studio/src/styles/playground.css
Renders the custom copy control through a portal, passes feature-flag data through fetching and context, reuses shared header utilities, and hides GraphiQL’s built-in copy button.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a copy cURL request option to the Playground copy button.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (4)
studio/src/components/playground/copy-operation.tsx (1)

16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add an explicit component return type.

CopyOperation does not declare a return type. Add the applicable React JSX element return type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio/src/components/playground/copy-operation.tsx` at line 16, Update the
CopyOperation component declaration to explicitly return the applicable React
JSX element type, while preserving its existing implementation and behavior.

Source: Coding guidelines

studio/src/__tests__/playground-curl.test.ts (1)

17-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use one template literal for the expected command.

These lines concatenate template literals. Use one template literal for the complete expected command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio/src/__tests__/playground-curl.test.ts` around lines 17 - 19, Update
the expected command construction in the playground curl test to use a single
template literal for the complete curl command instead of concatenating multiple
template literals. Preserve the existing URL, headers, line breaks, and
serialized query payload.

Source: Coding guidelines

studio/src/lib/playground-headers.ts (1)

1-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add explicit return types to exported helpers.

validateHeaders and substituteHeadersFromEnv do not declare return types. Add : void and : Record<string, string>.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio/src/lib/playground-headers.ts` around lines 1 - 9, Add explicit return
types to the exported helpers validateHeaders and substituteHeadersFromEnv:
declare validateHeaders as returning void and substituteHeadersFromEnv as
returning Record<string, string>, preserving their existing implementations.

Source: Coding guidelines

studio/src/lib/playground-curl.ts (1)

26-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace any in parsed JSON values.

parseJsonObject returns Record<string, any>, which propagates unchecked values into variables, headers, and the request body. Return Record<string, unknown> and narrow values where string conversion or body construction requires it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio/src/lib/playground-curl.ts` around lines 26 - 81, Update
parseJsonObject and related parsed JSON declarations to use Record<string,
unknown> instead of any. Narrow unknown header values before string conversion,
and ensure variables and request-body construction handle unknown values without
unchecked typing while preserving the existing behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@studio/src/components/playground/copy-operation.tsx`:
- Around line 89-91: Add an accessible name to the icon-only Button containing
CopyIcon by setting an appropriate aria-label such as “Copy operation,” while
preserving its existing variant, size, and styling.

In `@studio/src/lib/playground-curl.ts`:
- Around line 64-77: Update the header-building flow in the cURL generation
logic to validate each header name after substituteHeadersFromEnv and merging
extraHeaders, matching graphiQLFetch validation. Exclude invalid header names
such as those containing spaces and emit a warning instead of throwing, while
preserving valid headers in the generated command.

In `@studio/src/lib/playground-headers.ts`:
- Around line 9-25: Update substituteHeadersFromEnv to parse playground:env
inside a try block, returning an empty environment when parsing fails or yields
null/non-object data. Replace any with unknown for parsed environment values,
and narrow each value before boolean, numeric, or string conversion while
preserving existing header substitution behavior.

---

Nitpick comments:
In `@studio/src/__tests__/playground-curl.test.ts`:
- Around line 17-19: Update the expected command construction in the playground
curl test to use a single template literal for the complete curl command instead
of concatenating multiple template literals. Preserve the existing URL, headers,
line breaks, and serialized query payload.

In `@studio/src/components/playground/copy-operation.tsx`:
- Line 16: Update the CopyOperation component declaration to explicitly return
the applicable React JSX element type, while preserving its existing
implementation and behavior.

In `@studio/src/lib/playground-curl.ts`:
- Around line 26-81: Update parseJsonObject and related parsed JSON declarations
to use Record<string, unknown> instead of any. Narrow unknown header values
before string conversion, and ensure variables and request-body construction
handle unknown values without unchecked typing while preserving the existing
behavior.

In `@studio/src/lib/playground-headers.ts`:
- Around line 1-9: Add explicit return types to the exported helpers
validateHeaders and substituteHeadersFromEnv: declare validateHeaders as
returning void and substituteHeadersFromEnv as returning Record<string, string>,
preserving their existing implementations.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 112099ff-2292-44f9-9238-f3ee1c46a992

📥 Commits

Reviewing files that changed from the base of the PR and between 2f50ab8 and 8bffc04.

📒 Files selected for processing (7)
  • studio/src/__tests__/playground-curl.test.ts
  • studio/src/components/playground/copy-operation.tsx
  • studio/src/components/playground/types.ts
  • studio/src/lib/playground-curl.ts
  • studio/src/lib/playground-headers.ts
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/playground.tsx
  • studio/src/styles/playground.css

Comment thread studio/src/components/playground/copy-operation.tsx Outdated
Comment thread studio/src/lib/playground-curl.ts
Comment on lines +9 to +25
export const substituteHeadersFromEnv = (headers: Record<string, string>, graphId: string) => {
const env = JSON.parse(localStorage.getItem('playground:env') || '{}');
const graphEnv: Record<string, any> | undefined = env[graphId];

if (!graphEnv) {
return headers;
}

const storedHeaders: Record<string, any> = {};

Object.entries(graphEnv).forEach(([key, value]) => {
if (value === 'true' || value === 'false') {
storedHeaders[key] = value === 'true';
} else if (!isNaN(value as any) && value !== '') {
storedHeaders[key] = Number(value);
} else {
storedHeaders[key] = value;

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle invalid stored environment data.

JSON.parse() throws when playground:env contains malformed JSON. It also permits null and non-object JSON values. The request path returns a network error, but CopyOperation does not catch this failure.

Parse the storage value in a try block. Return an empty environment when parsing fails or when the value is not an object. Replace any with unknown and narrow each environment value before conversion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio/src/lib/playground-headers.ts` around lines 9 - 25, Update
substituteHeadersFromEnv to parse playground:env inside a try block, returning
an empty environment when parsing fails or yields null/non-object data. Replace
any with unknown for parsed environment values, and narrow each value before
boolean, numeric, or string conversion while preserving existing header
substitution behavior.

Source: Coding guidelines

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 45.04505% with 122 lines in your changes missing coverage. Please review.
✅ Project coverage is 1.80%. Comparing base (64eaf60) to head (8a69d66).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...tudio/src/components/playground/copy-operation.tsx 0.00% 75 Missing and 1 partial ⚠️
[...ationSlug]/[namespace]/graph/[slug]/playground.tsx](https://app.codecov.io/gh/wundergraph/cosmo/pull/3141?src=pr&el=tree&filepath=studio%2Fsrc%2Fpages%2F%5BorganizationSlug%5D%2F%5Bnamespace%5D%2Fgraph%2F%5Bslug%5D%2Fplayground.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wundergraph#diff-c3R1ZGlvL3NyYy9wYWdlcy9bb3JnYW5pemF0aW9uU2x1Z10vW25hbWVzcGFjZV0vZ3JhcGgvW3NsdWddL3BsYXlncm91bmQudHN4) 0.00% 28 Missing ⚠️
studio/src/lib/playground-headers.ts 64.28% 15 Missing ⚠️
studio/src/lib/playground-curl.ts 96.00% 3 Missing ⚠️

❌ Your patch check has failed because the patch coverage (45.04%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project check has failed because the head coverage (1.80%) is below the target coverage (30.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #3141       +/-   ##
==========================================
- Coverage   62.37%   1.80%   -60.58%     
==========================================
  Files         262     330       +68     
  Lines       31003   45970    +14967     
  Branches        0     516      +516     
==========================================
- Hits        19337     828    -18509     
- Misses      10158   44827    +34669     
+ Partials     1508     315     -1193     
Files with missing lines Coverage Δ
studio/src/components/playground/types.ts 100.00% <100.00%> (ø)
studio/src/lib/playground-curl.ts 96.00% <96.00%> (ø)
studio/src/lib/playground-headers.ts 64.28% <64.28%> (ø)
[...ationSlug]/[namespace]/graph/[slug]/playground.tsx](https://app.codecov.io/gh/wundergraph/cosmo/pull/3141?src=pr&el=tree&filepath=studio%2Fsrc%2Fpages%2F%5BorganizationSlug%5D%2F%5Bnamespace%5D%2Fgraph%2F%5Bslug%5D%2Fplayground.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wundergraph#diff-c3R1ZGlvL3NyYy9wYWdlcy9bb3JnYW5pemF0aW9uU2x1Z10vW25hbWVzcGFjZV0vZ3JhcGgvW3NsdWddL3BsYXlncm91bmQudHN4) 0.00% <0.00%> (ø)
...tudio/src/components/playground/copy-operation.tsx 0.00% <0.00%> (ø)

... and 587 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@comatory comatory 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.

Few small things.

*/
const shellQuote = (value: string) => `'${value.split("'").join(`'\\''`)}'`;

const parseJsonObject = (value: string | null | undefined): Record<string, any> | undefined => {

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.

How about using zod schema for parsing this? We already use it in studio for forms.

/** The url the playground sends its operations to. */
routingUrl: string;
/** Set when a feature flag is selected in the playground toolbar. */
featureFlagName?: string;

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.

Nitpick: I'd initialize it in PlaygroundContext as featureFlagName: undefined

}

/* replaced by our own copy menu, which can also copy the operation as a curl request */
.graphiql-toolbar button[aria-label^='Copy query'] {

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.

Can't we apply hidden class directly instead of this override? Wouldn't be possible to pass it via prop or use some kind of theme? If not let's keep it as is.

return;
}

const { command, warnings } = buildCurlCommand({

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.

I'd just extract this out to a variable wrapped with useMemo just to reduce the size of the useCallback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants