console: add hooks required for operations CRUD

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5670
Co-authored-by: Varun Choudhary <68095256+Varun-Choudhary@users.noreply.github.com>
GitOrigin-RevId: f9bf532689fd7fe8df6ca7abbd9d12c2a4c1320c
This commit is contained in:
Sooraj 2022-09-02 15:48:30 +05:30 committed by hasura-bot
parent bd5c0c1f20
commit 71de2e0a35
21 changed files with 694 additions and 0 deletions

View File

@ -0,0 +1,5 @@
export { useAddOperationsToQueryCollection } from './useAddOperationsToQueryCollection';
export { useEditOperationInQueryCollection } from './useEditOperationInQueryCollection';
export { useMoveOperationsToQueryCollection } from './useMoveOperationsToQueryCollection';
export { useOperationsFromQueryCollection } from './useOperationsFromQueryCollection';
export { useRemoveOperationsFromQueryCollection } from './useRemoveOperationsFromQueryCollection';

View File

@ -0,0 +1 @@
export * from './useAddOperationsToQueryCollection';

View File

@ -0,0 +1,39 @@
import { rest } from 'msw';
import { TMigration } from '../../../../MetadataAPI/hooks/useMetadataMigration';
const baseUrl = 'http://localhost:8080';
export const handlers = (delay = 0, url = baseUrl) => [
// todo export metadata mock based on the input
rest.post(`${url}/v1/metadata`, (req, res, ctx) => {
const body = req.body as TMigration['query'];
if (body.type === 'add_query_to_collection') {
if (
body.args.query_name === 'MyQuery33' &&
body.args.query === 'query MyQuery { user { email name}}'
)
return res(
ctx.delay(delay),
ctx.json({
message: 'success',
})
);
return res(
ctx.delay(delay),
ctx.status(500),
ctx.json({
message: 'error',
})
);
}
return res(
ctx.delay(delay),
ctx.json({
message: 'success',
})
);
}),
];

View File

@ -0,0 +1,54 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Button } from '@/new-components/Button';
import { Meta, Story } from '@storybook/react';
import { handlers } from './mocks/handlers.mock';
import { useAddOperationsToQueryCollection } from '.';
const UseAddOperationsToQueryCollection: React.FC = () => {
const { addOperationToQueryCollection, isSuccess, isLoading, error } =
useAddOperationsToQueryCollection();
return (
<div>
<ReactJson
name="Hook State"
src={{
isSuccess,
isLoading,
error: error?.message,
}}
/>
<Button
onClick={() =>
addOperationToQueryCollection('allowed-queries', [
{
query: `query MyQuery { user { email name}}`,
name: 'MyQuery',
},
])
}
>
Add MyQuery to Query Collection
</Button>
</div>
);
};
export const Primary: Story = () => {
return <UseAddOperationsToQueryCollection />;
};
export default {
title: 'hooks/Query Collections/useAddOperationsToQueryCollection',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(1000),
},
} as Meta;

View File

@ -0,0 +1,33 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from './mocks/handlers.mock';
import { useAddOperationsToQueryCollection } from '.';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useAddOperationsToQueryCollection', () => {
beforeEach(() => {
server.use(...handlers(1, ''));
});
test('When useAddOperationsToQueryCollection is used with a valid QueryCollection Then it should call the API with correct payload', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useAddOperationsToQueryCollection(),
{ wrapper }
);
await result.current.addOperationToQueryCollection('testCollection', [
{
name: 'MyQuery33',
query: 'query MyQuery { user { email name}}',
},
]);
await waitForValueToChange(() => result.current.isSuccess);
expect(result.current.isSuccess).toBe(true);
});
});

View File

@ -0,0 +1,49 @@
import { useCallback } from 'react';
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
import { QueryCollection } from '@/metadata/types';
export const useAddOperationsToQueryCollection = () => {
const { mutate, ...rest } = useMetadataMigration();
const { data: metadata } = useMetadata();
const addOperationToQueryCollection = useCallback(
(
queryCollection: string,
queries: QueryCollection[],
options?: Parameters<typeof mutate>[1]
) => {
if (!queryCollection || !queries)
throw Error(
`useAddOperationsToQueryCollection: Invalid input - ${
queryCollection && 'queryCollection'
} ${queries && 'queries'}`
);
return mutate(
{
query: {
type: 'bulk',
...(metadata?.resource_version && {
resource_version: metadata.resource_version,
}),
args: queries.map(query => ({
type: 'add_query_to_collection',
args: {
collection_name: queryCollection,
query_name: query.name,
query: query.query,
},
})),
},
},
{
...options,
}
);
},
[metadata, mutate]
);
return { addOperationToQueryCollection, ...rest };
};

View File

@ -0,0 +1 @@
export * from './useEditOperationInQueryCollection';

View File

@ -0,0 +1,14 @@
import { rest } from 'msw';
const baseUrl = 'http://localhost:8080';
export const handlers = (delay = 0, url = baseUrl) => [
rest.post(`${url}/v1/metadata`, (req, res, ctx) => {
return res(
ctx.delay(delay),
ctx.json({
message: 'success',
})
);
}),
];

View File

@ -0,0 +1,52 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Button } from '@/new-components/Button';
import { Meta, Story } from '@storybook/react';
import { handlers } from './mocks/handlers.mock';
import { useEditOperationInQueryCollection } from '.';
const UseEditOperationInQueryCollection: React.FC = () => {
const { editOperationInQueryCollection, isSuccess, isLoading, error } =
useEditOperationInQueryCollection();
return (
<div>
<ReactJson
name="Hook State"
src={{
isSuccess,
isLoading,
error: error?.message,
}}
/>
<Button
onClick={() =>
editOperationInQueryCollection('testCollection', 'MyQuery', {
name: 'NewMyQuery',
query: 'query NewMyQuery { user { email name}}',
})
}
>
Edit MyQuery to NewMyQuery
</Button>
</div>
);
};
export const Primary: Story = () => {
return <UseEditOperationInQueryCollection />;
};
export default {
title: 'hooks/Query Collections/useEditOperationInQueryCollection',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(1000),
},
} as Meta;

View File

@ -0,0 +1,37 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from './mocks/handlers.mock';
import { useEditOperationInQueryCollection } from '.';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useEditOperationInQueryCollection', () => {
beforeEach(() => {
server.use(...handlers(100, ''));
});
test('When useEditOperationInQueryCollection is used with a valid input Then it should call the API with correct payload', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useEditOperationInQueryCollection(),
{ wrapper }
);
await result.current.editOperationInQueryCollection(
'testCollection',
'MyQuery',
[
{
name: 'NewMyQuery',
query: 'query NewMyQuery { user { email name}}',
},
]
);
await waitForValueToChange(() => result.current.isSuccess);
expect(result.current.isSuccess).toBe(true);
});
});

View File

@ -0,0 +1,60 @@
import { useCallback } from 'react';
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
import { QueryCollection } from '@/metadata/types';
export const useEditOperationInQueryCollection = () => {
const { mutate, ...rest } = useMetadataMigration();
const { data: metadata } = useMetadata();
const editOperationInQueryCollection = useCallback(
(
queryCollection: string,
oldOperationName: string,
query: QueryCollection,
options?: Parameters<typeof mutate>[1]
) => {
if (!queryCollection || !query || !oldOperationName)
throw Error(
`useEditOperationInQueryCollection: Invalid input - ${
!queryCollection && 'queryCollection'
} ${!query && ', query'} ${!oldOperationName && ', oldOperationName'}`
);
return mutate(
{
query: {
type: 'bulk',
...(metadata?.resource_version && {
resource_version: metadata.resource_version,
}),
args: [
{
type: 'drop_query_from_collection',
args: {
collection_name: queryCollection,
query_name: oldOperationName,
},
},
{
type: 'add_query_to_collection',
args: {
collection_name: queryCollection,
query_name: query.name,
query: query.query,
},
},
],
},
},
{
...options,
}
);
},
[metadata, mutate]
);
return { editOperationInQueryCollection, ...rest };
};

View File

@ -0,0 +1 @@
export * from './useMoveOperationsToQueryCollection';

View File

@ -0,0 +1,16 @@
import { rest } from 'msw';
const baseUrl = 'http://localhost:8080';
export const handlers = (delay = 0, url = baseUrl) => [
// todo export metadata mock based on the input
rest.post(`${url}/v1/metadata`, (req, res, ctx) => {
return res(
ctx.delay(delay),
ctx.json({
message: 'success',
})
);
}),
];

View File

@ -0,0 +1,54 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Button } from '@/new-components/Button';
import { Meta, Story } from '@storybook/react';
import { handlers } from './mocks/handlers.mock';
import { useMoveOperationsToQueryCollection } from '.';
const UseMoveOperationsToQueryCollection: React.FC = () => {
const { moveOperationToQueryCollection, isSuccess, isLoading, error } =
useMoveOperationsToQueryCollection();
return (
<div>
<ReactJson
name="Hook State"
src={{
isSuccess,
isLoading,
error: error?.message,
}}
/>
<Button
onClick={() =>
moveOperationToQueryCollection('fromCollection', 'targetCollection', [
{
name: 'NewMyQuery',
query: 'query NewMyQuery { user { email name}}',
},
])
}
>
Move MyQuery to targetCollection
</Button>
</div>
);
};
export const Primary: Story = () => {
return <UseMoveOperationsToQueryCollection />;
};
export default {
title: 'hooks/Query Collections/useMoveOperationsToQueryCollection',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(1000),
},
} as Meta;

View File

@ -0,0 +1,37 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from './mocks/handlers.mock';
import { useMoveOperationsToQueryCollection } from '.';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useMoveOperationsToQueryCollection', () => {
beforeEach(() => {
server.use(...handlers(100, ''));
});
test('When useMoveOperationsToQueryCollection is used with a valid input Then it should call the API with correct payload', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useMoveOperationsToQueryCollection(),
{ wrapper }
);
await result.current.moveOperationToQueryCollection(
'fromCollection',
'targetCollection',
[
{
name: 'NewMyQuery',
query: 'query NewMyQuery { user { email name}}',
},
]
);
await waitForValueToChange(() => result.current.isSuccess);
expect(result.current.isSuccess).toBe(true);
});
});

View File

@ -0,0 +1,66 @@
import { useCallback } from 'react';
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
import { QueryCollection } from '@/metadata/types';
export const useMoveOperationsToQueryCollection = () => {
const { mutate, ...rest } = useMetadataMigration();
const { data: metadata } = useMetadata();
const moveOperationToQueryCollection = useCallback(
(
fromCollection: string,
toCollection: string,
// considering the move action can be done on multiple queries, this hook can handle multiple queries
queries: QueryCollection[],
options?: Parameters<typeof mutate>[1]
) => {
if (!fromCollection || !queries || !toCollection)
throw Error(
`useMoveOperationsToQueryCollection: Invalid input - ${
!fromCollection && 'fromCollection'
} ${!queries && ', queries'} ${!toCollection && ', toCollection'}`
);
// considering there is no direct API to edit an operation, we use bulk transaction to drop and add an operation.
// ie. drop_query_from_collection and then recreate with add_query_to_collection in a single transaction
return mutate(
{
query: {
type: 'bulk',
...(metadata?.resource_version && {
resource_version: metadata.resource_version,
}),
args: queries
.map(query => [
{
type: 'drop_query_from_collection',
args: {
collection_name: fromCollection,
query_name: query.name,
},
},
{
type: 'add_query_to_collection',
args: {
collection_name: toCollection,
query_name: query.name,
query: query.query,
},
},
])
.flat(),
},
},
{
...options,
}
);
},
[metadata, mutate]
);
return { moveOperationToQueryCollection, ...rest };
};

View File

@ -0,0 +1 @@
export * from './useRemoveOperationsFromQueryCollection';

View File

@ -0,0 +1,39 @@
import { rest } from 'msw';
import { TMigration } from '../../../../MetadataAPI/hooks/useMetadataMigration';
const baseUrl = 'http://localhost:8080';
export const handlers = (delay = 0, url = baseUrl) => [
// todo export metadata mock based on the input
rest.post(`${url}/v1/metadata`, (req, res, ctx) => {
const body = req.body as TMigration['query'];
if (body?.args && body?.args?.[0]?.type === 'add_query_to_collection') {
if (
body?.args?.[0]?.query_name === 'MyQuery33' &&
body?.args?.[0]?.collection_name === 'testCollection'
)
return res(
ctx.delay(delay),
ctx.json({
message: 'success',
})
);
return res(
ctx.delay(delay),
ctx.status(500),
ctx.json({
message: 'error',
})
);
}
return res(
ctx.delay(delay),
ctx.json({
message: 'success',
})
);
}),
];

View File

@ -0,0 +1,54 @@
import React from 'react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
import ReactJson from 'react-json-view';
import { Button } from '@/new-components/Button';
import { Meta, Story } from '@storybook/react';
import { handlers } from './mocks/handlers.mock';
import { useRemoveOperationsFromQueryCollection } from '.';
const UseRemoveOperationsFromQueryCollection: React.FC = () => {
const { removeOperationsFromQueryCollection, isSuccess, isLoading, error } =
useRemoveOperationsFromQueryCollection();
return (
<div>
<ReactJson
name="Hook State"
src={{
isSuccess,
isLoading,
error: error?.message,
}}
/>
<Button
onClick={() =>
removeOperationsFromQueryCollection('allowed-queries', [
{
query: `query MyQuery { user { email name}}`,
name: 'MyQuery',
},
])
}
>
Add MyQuery to Query Collection
</Button>
</div>
);
};
export const Primary: Story = () => {
return <UseRemoveOperationsFromQueryCollection />;
};
export default {
title: 'hooks/Query Collections/useRemoveOperationsFromQueryCollection',
decorators: [
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
ReactQueryDecorator(),
],
parameters: {
msw: handlers(1000),
},
} as Meta;

View File

@ -0,0 +1,33 @@
import { setupServer } from 'msw/node';
import { renderHook } from '@testing-library/react-hooks';
import { handlers } from './mocks/handlers.mock';
import { useRemoveOperationsFromQueryCollection } from '.';
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
const server = setupServer();
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('useRemoveOperationsFromQueryCollection', () => {
beforeEach(() => {
server.use(...handlers(1, ''));
});
test('When useRemoveOperationsFromQueryCollection is used with a valid QueryCollection Then it should call the API with correct payload', async () => {
const { waitForValueToChange, result }: any = renderHook(
() => useRemoveOperationsFromQueryCollection(),
{ wrapper }
);
await result.current.removeOperationsFromQueryCollection('testCollection', [
{
name: 'MyQuery33',
query: 'query MyQuery { user { email name}}',
},
]);
await waitForValueToChange(() => result.current.isSuccess);
expect(result.current.isSuccess).toBe(true);
});
});

View File

@ -0,0 +1,48 @@
import { useCallback } from 'react';
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
import { QueryCollection } from '@/metadata/types';
export const useRemoveOperationsFromQueryCollection = () => {
const { mutate, ...rest } = useMetadataMigration();
const { data: metadata } = useMetadata();
const removeOperationsFromQueryCollection = useCallback(
(
queryCollection: string,
queries: QueryCollection[],
options?: Parameters<typeof mutate>[1]
) => {
if (!queryCollection || !queries)
throw Error(
`useRemoveOperationsFromQueryCollection: Invalid input - ${
queryCollection && 'queryCollection'
} ${queries && 'queries'}`
);
return mutate(
{
query: {
type: 'bulk',
...(metadata?.resource_version && {
resource_version: metadata.resource_version,
}),
args: queries.map(query => ({
type: 'drop_query_from_collection',
args: {
collection_name: queryCollection,
query_name: query.name,
},
})),
},
},
{
...options,
}
);
},
[metadata, mutate]
);
return { removeOperationsFromQueryCollection, ...rest };
};