mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
Fix editor state and operation handling in Laboratory + e2e tests (#6282)
Fixes When opening a new tab or selecting one of the saved operations a wrong query was populated. It was always the default query or the one that's active. Meaning, you couldn't select and see the saved operation :) When saving the operation, the submit button of the form was always disabled, even when the state of the form was valid. e2e tests Added tests for CRUD of collections and their operations. The scenario where a user visits a shared link to an operation is also now tested.
This commit is contained in:
parent
469443bdc2
commit
a7f9d50fb9
14 changed files with 350 additions and 38 deletions
7
.changeset/young-peas-battle.md
Normal file
7
.changeset/young-peas-battle.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
'hive': patch
|
||||
---
|
||||
|
||||
Fix editor state and operation handling in Laboratory.
|
||||
|
||||
When opening a new tab or selecting a saved operation, the editor incorrectly populated the query, defaulting to the active query. This made it impossible to view the selected operation. Additionally, the submit button for saving an operation was always disabled, even when the form was in a valid state.
|
||||
|
|
@ -234,6 +234,7 @@ module.exports = {
|
|||
extends: 'plugin:cypress/recommended',
|
||||
rules: {
|
||||
'cypress/no-unnecessary-waiting': 'off',
|
||||
'cypress/unsafe-to-chain-command': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
248
cypress/e2e/laboratory-collections.cy.ts
Normal file
248
cypress/e2e/laboratory-collections.cy.ts
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
import { laboratory } from '../support/testkit';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.clearAllLocalStorage().then(() => {
|
||||
return cy.task('seedTarget').then(({ slug, refreshToken }: any) => {
|
||||
cy.setCookie('sRefreshToken', refreshToken);
|
||||
|
||||
cy.visit(`/${slug}/laboratory`);
|
||||
|
||||
// To make sure the operation collections tab is opened
|
||||
// It first opens the Documentation Explorer or GraphiQL Explorer,
|
||||
// then opens the Operation Collections tab.
|
||||
// It does that, because the Operation Collections tab could be already opened for some reason.
|
||||
// This way it's guaranteed that the Operation Collections tab is opened.
|
||||
cy.get('[aria-label*="Show Documentation Explorer"], [aria-label*="Show GraphiQL Explorer"]')
|
||||
.first()
|
||||
.click();
|
||||
cy.get('[aria-label="Show Operation Collections"]').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const collections = {
|
||||
/**
|
||||
* Opens the modal to create a new collection and fills the form
|
||||
*/
|
||||
create(args: { name: string; description: string }) {
|
||||
cy.get('button[data-cy="new-collection"]').click();
|
||||
cy.get('div[data-cy="create-collection-modal"] input[name="name"]').type(args.name);
|
||||
cy.get('div[data-cy="create-collection-modal"] input[name="description"]').type(
|
||||
args.description,
|
||||
);
|
||||
cy.get('div[data-cy="create-collection-modal"] button[type="submit"]').click();
|
||||
},
|
||||
/**
|
||||
* Clicks on a collection in the sidebar
|
||||
*/
|
||||
clickCollectionButton(name: string) {
|
||||
cy.get('button[data-cy="collection-item-trigger"]').contains(name).click();
|
||||
},
|
||||
/**
|
||||
* Saves the current operation as a new operation and assigns it to a collection
|
||||
*/
|
||||
saveCurrentOperationAs(args: { name: string; collectionName: string }) {
|
||||
cy.get('[data-cy="save-operation"]').click();
|
||||
cy.get('[data-cy="save-operation-as"]').click();
|
||||
|
||||
cy.get('div[data-cy="create-operation-modal"] input[name="name"]').type(args.name);
|
||||
cy.get(
|
||||
'div[data-cy="create-operation-modal"] button[data-cy="collection-select-trigger"]',
|
||||
).click();
|
||||
|
||||
cy.get('div[data-cy="collection-select-item"]').contains(args.collectionName).click();
|
||||
cy.get('div[data-cy="create-operation-modal"] button[type="submit"]').click();
|
||||
},
|
||||
/**
|
||||
* Opens the menu for a specific operation, to access delete, copy link or edit buttons.
|
||||
*/
|
||||
openOperationMenu(name: string) {
|
||||
return cy.get(`a[data-cy="operation-${name}"] ~ button`).click();
|
||||
},
|
||||
/**
|
||||
* Opens the menu for a specific collection, to access delete or edit buttons.
|
||||
*/
|
||||
openCollectionMenu(name: string) {
|
||||
return cy
|
||||
.contains(`[data-cy="collection-item-trigger"]`, name)
|
||||
.parent()
|
||||
.get('[data-cy="collection-menu-trigger"]')
|
||||
.click();
|
||||
},
|
||||
/**
|
||||
* Returns the operation element
|
||||
*/
|
||||
getOperationButton(name: string) {
|
||||
return cy.get<HTMLAnchorElement>(`a[data-cy="operation-${name}"]`);
|
||||
},
|
||||
/**
|
||||
* Returns the collection element
|
||||
*/
|
||||
getCollectionButton(name: string) {
|
||||
return cy.contains('[data-cy="collection-item"]', name);
|
||||
},
|
||||
};
|
||||
|
||||
describe('Laboratory > Collections', () => {
|
||||
it('create a collection and an operation', () => {
|
||||
collections.create({
|
||||
name: 'collection-1',
|
||||
description: 'Description 1',
|
||||
});
|
||||
laboratory.updateEditorValue(`query op1 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-1',
|
||||
collectionName: 'collection-1',
|
||||
});
|
||||
collections.getOperationButton('operation-1').should('exist');
|
||||
});
|
||||
|
||||
it(`edit collection's name`, () => {
|
||||
collections.create({
|
||||
name: 'collection-1',
|
||||
description: 'Description 1',
|
||||
});
|
||||
laboratory.updateEditorValue(`query op1 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-1',
|
||||
collectionName: 'collection-1',
|
||||
});
|
||||
|
||||
collections.openCollectionMenu('collection-1');
|
||||
// Click on the edit button and fill the form
|
||||
cy.get('[data-cy="edit-collection"]').click();
|
||||
cy.get('[data-cy="create-collection-modal"]').should('exist');
|
||||
cy.get('[data-cy="create-collection-modal"] input[name="name"]')
|
||||
.clear()
|
||||
.type('collection-1-updated');
|
||||
cy.get('[data-cy="create-collection-modal"] button[data-cy="confirm"]').click();
|
||||
|
||||
collections.getCollectionButton('collection-1-updated').should('exist');
|
||||
collections.getOperationButton('operation-1').should('exist');
|
||||
});
|
||||
|
||||
it('delete a collection', () => {
|
||||
collections.create({
|
||||
name: 'collection-1',
|
||||
description: 'Description 1',
|
||||
});
|
||||
laboratory.updateEditorValue(`query op1 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-1',
|
||||
collectionName: 'collection-1',
|
||||
});
|
||||
|
||||
collections.getOperationButton('operation-1').should('exist');
|
||||
collections.getCollectionButton('collection-1').should('exist');
|
||||
|
||||
collections.openCollectionMenu('collection-1');
|
||||
// Click on the delete button and confirm the deletion
|
||||
cy.get('[data-cy="delete-collection"]').click();
|
||||
cy.get('[data-cy="delete-collection-modal"]').should('exist');
|
||||
cy.get('[data-cy="delete-collection-modal"] button[data-cy="confirm"]').click();
|
||||
|
||||
collections.getOperationButton('operation-1').should('not.exist');
|
||||
collections.getCollectionButton('collection-1').should('not.exist');
|
||||
});
|
||||
|
||||
it(`edit operation's name`, () => {
|
||||
collections.create({
|
||||
name: 'collection-1',
|
||||
description: 'Description 1',
|
||||
});
|
||||
laboratory.updateEditorValue(`query op1 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-1',
|
||||
collectionName: 'collection-1',
|
||||
});
|
||||
|
||||
collections.openOperationMenu('operation-1');
|
||||
// Click on the edit button and fill the form
|
||||
cy.get('[data-cy="edit-operation"]').click();
|
||||
cy.get('[data-cy="edit-operation-modal"]').should('exist');
|
||||
cy.get('[data-cy="edit-operation-modal"] input[name="name"]').type('operation-1-updated');
|
||||
cy.get('[data-cy="edit-operation-modal"] button[data-cy="confirm"]').click();
|
||||
|
||||
collections.getOperationButton('operation-1').should('not.exist');
|
||||
collections.getOperationButton('operation-1-updated').should('exist');
|
||||
});
|
||||
|
||||
it('delete an operation', () => {
|
||||
collections.create({
|
||||
name: 'collection-1',
|
||||
description: 'Description 1',
|
||||
});
|
||||
laboratory.updateEditorValue(`query op1 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-1',
|
||||
collectionName: 'collection-1',
|
||||
});
|
||||
|
||||
laboratory.openNewTab();
|
||||
laboratory.updateEditorValue(`query op2 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-2',
|
||||
collectionName: 'collection-1',
|
||||
});
|
||||
|
||||
collections.openOperationMenu('operation-1');
|
||||
// Click on the delete button and confirm the deletion
|
||||
cy.get('[data-cy="delete-operation"]').click();
|
||||
cy.get('[data-cy="delete-operation-modal"]').should('exist');
|
||||
cy.get('[data-cy="delete-operation-modal"] button[data-cy="confirm"]').click();
|
||||
|
||||
collections.getOperationButton('operation-1').should('not.exist');
|
||||
collections.getOperationButton('operation-2').should('exist');
|
||||
});
|
||||
|
||||
it('visiting a copied operation link should open the operation', () => {
|
||||
collections.create({
|
||||
name: 'collection-1',
|
||||
description: 'Description 1',
|
||||
});
|
||||
collections.create({
|
||||
name: 'collection-2',
|
||||
description: 'Description 2',
|
||||
});
|
||||
collections.clickCollectionButton('collection-1');
|
||||
laboratory.updateEditorValue(`query op1 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-1',
|
||||
collectionName: 'collection-1',
|
||||
});
|
||||
|
||||
laboratory.openNewTab();
|
||||
laboratory.updateEditorValue(`query op2 { test }`);
|
||||
collections.saveCurrentOperationAs({
|
||||
name: 'operation-2',
|
||||
collectionName: 'collection-2',
|
||||
});
|
||||
|
||||
collections.openOperationMenu('operation-1');
|
||||
|
||||
// Stub the clipboard API to intercept the copied URL
|
||||
cy.window().then(win => {
|
||||
cy.stub(win.navigator.clipboard, 'writeText').as('copied');
|
||||
});
|
||||
|
||||
cy.get('[data-cy="copy-operation-link"]').click();
|
||||
|
||||
cy.get<{
|
||||
getCall(index: number): {
|
||||
args: unknown[];
|
||||
};
|
||||
}>('@copied')
|
||||
.should('have.been.calledOnce')
|
||||
.then(stub => {
|
||||
const copiedUrl = stub.getCall(0).args[0]; // Extract the copied URL
|
||||
if (typeof copiedUrl !== 'string') {
|
||||
throw new Error('The copied URL is not a string');
|
||||
}
|
||||
// Navigate to the copied URL
|
||||
return cy.visit(copiedUrl);
|
||||
});
|
||||
|
||||
laboratory.assertActiveTab('operation-1');
|
||||
laboratory.getEditorValue().should('contain', 'op1');
|
||||
});
|
||||
});
|
||||
|
|
@ -34,7 +34,7 @@ function setEditorScript(script: string) {
|
|||
setMonacoEditorContents('preflight-script-editor', script);
|
||||
}
|
||||
|
||||
describe('Preflight Script', () => {
|
||||
describe('Laboratory > Preflight Script', () => {
|
||||
it('mini script editor is read only', () => {
|
||||
cy.dataCy('toggle-preflight-script').click();
|
||||
// Wait loading disappears
|
||||
|
|
@ -38,6 +38,40 @@ export function createProject(projectSlug: string) {
|
|||
cy.get('form[data-cy="create-project-form"] [data-cy="submit"]').click();
|
||||
}
|
||||
|
||||
export const laboratory = {
|
||||
/**
|
||||
* Updates the value of the graphiql editor
|
||||
*/
|
||||
updateEditorValue(value: string) {
|
||||
cy.get('.graphiql-query-editor .cm-s-graphiql').then($editor => {
|
||||
const editor = ($editor[0] as any).CodeMirror; // Access the CodeMirror instance
|
||||
editor.setValue(value);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Returns the value of the graphiql editor as Chainable<string>
|
||||
*/
|
||||
getEditorValue() {
|
||||
return cy.get('.graphiql-query-editor .cm-s-graphiql').then<string>($editor => {
|
||||
const editor = ($editor[0] as any).CodeMirror; // Access the CodeMirror instance
|
||||
return editor.getValue();
|
||||
});
|
||||
},
|
||||
openNewTab() {
|
||||
cy.get('button[aria-label="New tab"]').click();
|
||||
// tab's title should be "untitled" as it's a default name
|
||||
cy.contains('button[aria-controls="graphiql-session"]', 'untitled').should('exist');
|
||||
},
|
||||
/**
|
||||
* Asserts that the tab with the given name is active
|
||||
*/
|
||||
assertActiveTab(name: string) {
|
||||
cy.contains('li.graphiql-tab-active > button[aria-controls="graphiql-session"]', name).should(
|
||||
'exist',
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export function dedent(strings: TemplateStringsArray, ...values: unknown[]): string {
|
||||
// Took from https://github.com/dmnd/dedent
|
||||
// Couldn't use the package because I had some issues with moduleResolution.
|
||||
|
|
|
|||
|
|
@ -235,7 +235,10 @@ export function CreateCollectionModalContent(props: {
|
|||
}) {
|
||||
return (
|
||||
<Dialog open={props.isOpen} onOpenChange={props.toggleModalOpen}>
|
||||
<DialogContent className="container w-4/5 max-w-[600px] md:w-3/5">
|
||||
<DialogContent
|
||||
className="container w-4/5 max-w-[600px] md:w-3/5"
|
||||
data-cy="create-collection-modal"
|
||||
>
|
||||
{!props.fetching && (
|
||||
<Form {...props.form}>
|
||||
<form className="space-y-8" onSubmit={props.form.handleSubmit(props.onSubmit)}>
|
||||
|
|
|
|||
|
|
@ -190,7 +190,10 @@ export function CreateOperationModalContent(props: {
|
|||
props.form.reset();
|
||||
}}
|
||||
>
|
||||
<DialogContent className="container w-4/5 max-w-[600px] md:w-3/5">
|
||||
<DialogContent
|
||||
className="container w-4/5 max-w-[600px] md:w-3/5"
|
||||
data-cy="create-operation-modal"
|
||||
>
|
||||
{!props.fetching && (
|
||||
<Form {...props.form}>
|
||||
<form className="space-y-8" onSubmit={props.form.handleSubmit(props.onSubmit)}>
|
||||
|
|
@ -224,13 +227,13 @@ export function CreateOperationModalContent(props: {
|
|||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger data-cy="collection-select-trigger">
|
||||
{props.collections.find(c => c.id === field.value)?.name ??
|
||||
'Select a Collection'}
|
||||
</SelectTrigger>
|
||||
<SelectContent className="w-[--radix-select-trigger-width]">
|
||||
{props.collections.map(c => (
|
||||
<SelectItem key={c.id} value={c.id}>
|
||||
<SelectItem key={c.id} value={c.id} data-cy="collection-select-item">
|
||||
{c.name}
|
||||
<div className="mt-1 line-clamp-1 text-xs opacity-50">
|
||||
{c.description}
|
||||
|
|
@ -263,12 +266,7 @@ export function CreateOperationModalContent(props: {
|
|||
size="lg"
|
||||
className="w-full justify-center"
|
||||
variant="primary"
|
||||
disabled={
|
||||
props.form.formState.isSubmitting ||
|
||||
!props.form.formState.isValid ||
|
||||
!props.form.getValues('collectionId')
|
||||
}
|
||||
data-cy="confirm"
|
||||
disabled={props.form.formState.isSubmitting || !props.form.formState.isValid}
|
||||
>
|
||||
Add Operation
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export function DeleteCollectionModalContent(props: {
|
|||
}) {
|
||||
return (
|
||||
<Dialog open={props.isOpen} onOpenChange={props.toggleModalOpen}>
|
||||
<DialogContent className="w-4/5 max-w-[520px] md:w-3/5">
|
||||
<DialogContent className="w-4/5 max-w-[520px] md:w-3/5" data-cy="delete-collection-modal">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Collection</DialogTitle>
|
||||
<DialogDescription>Are you sure you wish to delete this collection?</DialogDescription>
|
||||
|
|
@ -108,7 +108,7 @@ export function DeleteCollectionModalContent(props: {
|
|||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={props.handleDelete}>
|
||||
<Button variant="destructive" data-cy="confirm" onClick={props.handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export function DeleteOperationModalContent(props: {
|
|||
}): ReactElement {
|
||||
return (
|
||||
<Dialog open={props.isOpen} onOpenChange={props.toggleModalOpen}>
|
||||
<DialogContent className="w-4/5 max-w-[520px] md:w-3/5">
|
||||
<DialogContent className="w-4/5 max-w-[520px] md:w-3/5" data-cy="delete-operation-modal">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Operation</DialogTitle>
|
||||
<DialogDescription>Do you really want to delete this operation?</DialogDescription>
|
||||
|
|
@ -117,7 +117,7 @@ export function DeleteOperationModalContent(props: {
|
|||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={props.handleDelete}>
|
||||
<Button variant="destructive" data-cy="confirm" onClick={props.handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -167,7 +167,10 @@ export const EditOperationModalContent = (props: {
|
|||
props.form.reset();
|
||||
}}
|
||||
>
|
||||
<DialogContent className="container w-4/5 max-w-[600px] md:w-3/5">
|
||||
<DialogContent
|
||||
className="container w-4/5 max-w-[600px] md:w-3/5"
|
||||
data-cy="edit-operation-modal"
|
||||
>
|
||||
{!props.fetching && (
|
||||
<Form {...props.form}>
|
||||
<form className="space-y-8" onSubmit={props.form.handleSubmit(props.onSubmit)}>
|
||||
|
|
|
|||
|
|
@ -308,19 +308,27 @@ export function Content() {
|
|||
|
||||
const renderedCollections = collections.map(collection => (
|
||||
<AccordionItem key={collection.id} value={collection.id} className="border-b-0">
|
||||
<AccordionHeader className="flex items-center justify-between">
|
||||
<AccordionTriggerPrimitive className="group flex w-full items-center gap-x-3 rounded p-2 text-left font-medium text-white hover:bg-gray-100/10">
|
||||
<AccordionHeader className="flex items-center justify-between" data-cy="collection-item">
|
||||
<AccordionTriggerPrimitive
|
||||
className="group flex w-full items-center gap-x-3 rounded p-2 text-left font-medium text-white hover:bg-gray-100/10"
|
||||
data-cy="collection-item-trigger"
|
||||
>
|
||||
<FolderIcon className="size-4 group-data-[state=open]:hidden" />
|
||||
<FolderOpenIcon className="size-4 group-data-[state=closed]:hidden" />
|
||||
{collection.name}
|
||||
</AccordionTriggerPrimitive>
|
||||
{shouldShowMenu && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger aria-label="More" className="graphiql-toolbar-button">
|
||||
<DropdownMenuTrigger
|
||||
aria-label="More"
|
||||
className="graphiql-toolbar-button"
|
||||
data-cy="collection-menu-trigger"
|
||||
>
|
||||
<DotsHorizontalIcon />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
data-cy="add-operation-to-collection"
|
||||
onClick={addOperation}
|
||||
disabled={createOperationState.fetching}
|
||||
data-collection-id={collection.id}
|
||||
|
|
@ -329,6 +337,7 @@ export function Content() {
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
data-cy="edit-collection"
|
||||
onClick={() => {
|
||||
setCollectionId(collection.id);
|
||||
toggleCollectionModal();
|
||||
|
|
@ -337,6 +346,7 @@ export function Content() {
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
data-cy="delete-collection"
|
||||
onClick={() => {
|
||||
setCollectionId(collection.id);
|
||||
toggleDeleteCollectionModalOpen();
|
||||
|
|
@ -361,6 +371,7 @@ export function Content() {
|
|||
targetSlug,
|
||||
}}
|
||||
search={{ operation: node.id }}
|
||||
data-cy={`operation-${node.name}`}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-x-3 rounded p-2 font-normal text-white/50 hover:bg-gray-100/10 hover:text-white hover:no-underline',
|
||||
node.id === queryParamsOperationId && [
|
||||
|
|
@ -381,6 +392,7 @@ export function Content() {
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
data-cy="copy-operation-link"
|
||||
onClick={async () => {
|
||||
const url = new URL(window.location.href);
|
||||
await copyToClipboard(`${url.origin}${url.pathname}?operation=${node.id}`);
|
||||
|
|
@ -391,6 +403,7 @@ export function Content() {
|
|||
<DropdownMenuSeparator />
|
||||
{canEdit && (
|
||||
<DropdownMenuItem
|
||||
data-cy="edit-operation"
|
||||
onClick={() => {
|
||||
setOperationToEditId(node.id);
|
||||
}}
|
||||
|
|
@ -400,6 +413,7 @@ export function Content() {
|
|||
)}
|
||||
{canDelete && (
|
||||
<DropdownMenuItem
|
||||
data-cy="delete-operation"
|
||||
onClick={() => {
|
||||
setOperationToDeleteId(node.id);
|
||||
toggleDeleteOperationModalOpen();
|
||||
|
|
@ -440,6 +454,7 @@ export function Content() {
|
|||
<Button
|
||||
variant="orangeLink"
|
||||
size="icon-sm"
|
||||
data-cy="new-collection"
|
||||
className={clsx(
|
||||
'flex w-auto items-center gap-1',
|
||||
'min-w-0', // trick to make work truncate
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ function Save(props: {
|
|||
<GraphiQLTooltip label={label}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<GraphiQLButton
|
||||
data-cy="save-operation"
|
||||
className={cn(
|
||||
'graphiql-toolbar-button',
|
||||
currentOperation && !isSame && 'hive-badge-is-changed relative after:top-1',
|
||||
|
|
@ -226,6 +227,7 @@ function Save(props: {
|
|||
Save
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
data-cy="save-operation-as"
|
||||
onClick={async () => {
|
||||
if (!collections.length) {
|
||||
notify('Please create a collection first.', 'error');
|
||||
|
|
|
|||
|
|
@ -224,8 +224,8 @@ index 8ca339a2ba2031f0c1e22f1d099fa9a571492107..1cf3e8c620dc2c3ad4cfc42e2feeb4ca
|
|||
+ ...updatedValues.tabs,
|
||||
+ createTab({
|
||||
+ ..._tabState,
|
||||
+ headers: defaultHeaders,
|
||||
+ query: defaultQuery ?? DEFAULT_QUERY
|
||||
+ headers: _tabState?.headers ?? defaultHeaders,
|
||||
+ query: _tabState?.query ?? (defaultQuery ?? DEFAULT_QUERY)
|
||||
+ })
|
||||
+ ],
|
||||
+ activeTabIndex: updatedValues.tabs.length
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ patchedDependencies:
|
|||
hash: wz23vdqq6qtsz64wb433afnvou
|
||||
path: patches/@fastify__vite.patch
|
||||
'@graphiql/react':
|
||||
hash: bru5she67j343rpipomank3vn4
|
||||
hash: cxjlr4qnvqgvcsnnl2map34diy
|
||||
path: patches/@graphiql__react.patch
|
||||
'@graphql-eslint/eslint-plugin@3.20.1':
|
||||
hash: n437g5o7zq7pnxdxldn52uql2q
|
||||
|
|
@ -1681,10 +1681,10 @@ importers:
|
|||
version: 6.0.6(patch_hash=wz23vdqq6qtsz64wb433afnvou)(@types/node@22.10.3)(less@4.2.0)(lightningcss@1.28.2)(terser@5.37.0)
|
||||
'@graphiql/plugin-explorer':
|
||||
specifier: 4.0.0-alpha.2
|
||||
version: 4.0.0-alpha.2(@graphiql/react@1.0.0-alpha.4(patch_hash=bru5she67j343rpipomank3vn4)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 4.0.0-alpha.2(@graphiql/react@1.0.0-alpha.4(patch_hash=cxjlr4qnvqgvcsnnl2map34diy)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@graphiql/react':
|
||||
specifier: 1.0.0-alpha.4
|
||||
version: 1.0.0-alpha.4(patch_hash=bru5she67j343rpipomank3vn4)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 1.0.0-alpha.4(patch_hash=cxjlr4qnvqgvcsnnl2map34diy)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@graphiql/toolkit':
|
||||
specifier: 0.9.1
|
||||
version: 0.9.1(@types/node@22.10.3)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)
|
||||
|
|
@ -3901,6 +3901,7 @@ packages:
|
|||
|
||||
'@fastify/vite@6.0.6':
|
||||
resolution: {integrity: sha512-FsWJC92murm5tjeTezTTvMLyZido/ZWy0wYWpVkh/bDe1gAUAabYLB7Vp8hokXGsRE/mOpqYVsRDAKENY2qPUQ==}
|
||||
bundledDependencies: []
|
||||
|
||||
'@floating-ui/core@1.2.6':
|
||||
resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==}
|
||||
|
|
@ -16502,8 +16503,8 @@ snapshots:
|
|||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 3.0.0
|
||||
'@aws-crypto/sha256-js': 3.0.0
|
||||
'@aws-sdk/client-sso-oidc': 3.596.0
|
||||
'@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)
|
||||
'@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0)
|
||||
'@aws-sdk/client-sts': 3.596.0
|
||||
'@aws-sdk/core': 3.592.0
|
||||
'@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0)
|
||||
'@aws-sdk/middleware-host-header': 3.577.0
|
||||
|
|
@ -16610,11 +16611,11 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-sso-oidc@3.596.0':
|
||||
'@aws-sdk/client-sso-oidc@3.596.0(@aws-sdk/client-sts@3.596.0)':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 3.0.0
|
||||
'@aws-crypto/sha256-js': 3.0.0
|
||||
'@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)
|
||||
'@aws-sdk/client-sts': 3.596.0
|
||||
'@aws-sdk/core': 3.592.0
|
||||
'@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0)
|
||||
'@aws-sdk/middleware-host-header': 3.577.0
|
||||
|
|
@ -16653,6 +16654,7 @@ snapshots:
|
|||
'@smithy/util-utf8': 3.0.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-sts'
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-sso-oidc@3.723.0(@aws-sdk/client-sts@3.723.0)':
|
||||
|
|
@ -16786,11 +16788,11 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-sts@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)':
|
||||
'@aws-sdk/client-sts@3.596.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 3.0.0
|
||||
'@aws-crypto/sha256-js': 3.0.0
|
||||
'@aws-sdk/client-sso-oidc': 3.596.0
|
||||
'@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0)
|
||||
'@aws-sdk/core': 3.592.0
|
||||
'@aws-sdk/credential-provider-node': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0)
|
||||
'@aws-sdk/middleware-host-header': 3.577.0
|
||||
|
|
@ -16829,7 +16831,6 @@ snapshots:
|
|||
'@smithy/util-utf8': 3.0.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-sso-oidc'
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-sts@3.723.0':
|
||||
|
|
@ -16943,7 +16944,7 @@ snapshots:
|
|||
|
||||
'@aws-sdk/credential-provider-ini@3.596.0(@aws-sdk/client-sso-oidc@3.596.0)(@aws-sdk/client-sts@3.596.0)':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)
|
||||
'@aws-sdk/client-sts': 3.596.0
|
||||
'@aws-sdk/credential-provider-env': 3.587.0
|
||||
'@aws-sdk/credential-provider-http': 3.596.0
|
||||
'@aws-sdk/credential-provider-process': 3.587.0
|
||||
|
|
@ -17062,7 +17063,7 @@ snapshots:
|
|||
|
||||
'@aws-sdk/credential-provider-web-identity@3.587.0(@aws-sdk/client-sts@3.596.0)':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sts': 3.596.0(@aws-sdk/client-sso-oidc@3.596.0)
|
||||
'@aws-sdk/client-sts': 3.596.0
|
||||
'@aws-sdk/types': 3.577.0
|
||||
'@smithy/property-provider': 3.1.11
|
||||
'@smithy/types': 3.7.2
|
||||
|
|
@ -17237,7 +17238,7 @@ snapshots:
|
|||
|
||||
'@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.596.0)':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sso-oidc': 3.596.0
|
||||
'@aws-sdk/client-sso-oidc': 3.596.0(@aws-sdk/client-sts@3.596.0)
|
||||
'@aws-sdk/types': 3.577.0
|
||||
'@smithy/property-provider': 3.1.11
|
||||
'@smithy/shared-ini-file-loader': 3.1.12
|
||||
|
|
@ -18644,15 +18645,15 @@ snapshots:
|
|||
graphql: 16.9.0
|
||||
typescript: 5.7.3
|
||||
|
||||
'@graphiql/plugin-explorer@4.0.0-alpha.2(@graphiql/react@1.0.0-alpha.4(patch_hash=bru5she67j343rpipomank3vn4)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@graphiql/plugin-explorer@4.0.0-alpha.2(@graphiql/react@1.0.0-alpha.4(patch_hash=cxjlr4qnvqgvcsnnl2map34diy)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@graphiql/react': 1.0.0-alpha.4(patch_hash=bru5she67j343rpipomank3vn4)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@graphiql/react': 1.0.0-alpha.4(patch_hash=cxjlr4qnvqgvcsnnl2map34diy)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
graphiql-explorer: 0.9.0(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
graphql: 16.9.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@graphiql/react@1.0.0-alpha.4(patch_hash=bru5she67j343rpipomank3vn4)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@graphiql/react@1.0.0-alpha.4(patch_hash=cxjlr4qnvqgvcsnnl2map34diy)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@graphiql/toolkit': 0.10.0(@types/node@22.10.3)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)
|
||||
'@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -27704,7 +27705,7 @@ snapshots:
|
|||
|
||||
graphiql@4.0.0-alpha.5(patch_hash=yjzkcog7ut7wshk4npre67txki)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@graphiql/react': 1.0.0-alpha.4(patch_hash=bru5she67j343rpipomank3vn4)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@graphiql/react': 1.0.0-alpha.4(patch_hash=cxjlr4qnvqgvcsnnl2map34diy)(@codemirror/language@6.10.2)(@types/node@22.10.3)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(graphql-ws@5.16.1(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
graphql: 16.9.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
|
|
|||
Loading…
Reference in a new issue