Skip to content
Snippets Groups Projects
Unverified Commit 7d7325c6 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

[RFC] Add entity update hooks (#31192)

parent 1f7e261e
Branches
Tags
No related merge requests found
export * from "./use-database-update";
import Databases from "metabase/entities/databases";
import {
EntityInfo,
useEntityUpdate,
} from "metabase/common/hooks/use-entity-update";
import { DatabaseData, DatabaseId } from "metabase-types/api";
import Database from "metabase-lib/metadata/Database";
export const useDatabaseUpdate = () => {
return useEntityUpdate<
DatabaseId,
Database,
EntityInfo<DatabaseId>,
DatabaseData
>({
update: Databases.actions.update,
getObject: Databases.selectors.getObject,
});
};
import userEvent from "@testing-library/user-event";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import { createMockDatabase } from "metabase-types/api/mocks";
import { setupDatabaseEndpoints } from "__support__/server-mocks";
import {
renderWithProviders,
screen,
waitForElementToBeRemoved,
} from "__support__/ui";
import { useDatabaseQuery } from "../use-database-query";
import { useDatabaseUpdate } from "./use-database-update";
const TEST_DB = createMockDatabase();
const TEST_NEW_NAME = "New name";
const TestComponent = () => {
const { data, isLoading, error } = useDatabaseQuery({ id: TEST_DB.id });
const { mutate: updateDatabase } = useDatabaseUpdate();
const handleSubmit = async () => {
await updateDatabase({ id: TEST_DB.id }, { name: TEST_NEW_NAME });
};
if (isLoading || error || !data) {
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
}
return (
<div>
<div>{data.name}</div>
<button onClick={handleSubmit}>Update</button>
</div>
);
};
const setup = async () => {
setupDatabaseEndpoints(TEST_DB);
renderWithProviders(<TestComponent />);
await waitForElementToBeRemoved(() => screen.queryByText(/Loading/i));
};
describe("useDatabaseUpdate", () => {
it("should update the state from the response", async () => {
await setup();
expect(screen.getByText(TEST_DB.name)).toBeInTheDocument();
userEvent.click(screen.getByRole("button"));
expect(await screen.findByText(TEST_NEW_NAME)).toBeInTheDocument();
});
});
export * from "./use-entity-update";
import type { Action } from "@reduxjs/toolkit";
import { useAsyncFn } from "react-use";
import { useStore } from "metabase/lib/redux";
import { checkNotNull } from "metabase/core/utils/types";
import { State } from "metabase-types/store";
export interface EntityInfo<TId> {
id: TId;
}
export interface EntityQueryOptions<TId> {
entityId?: TId;
}
export interface UseEntityUpdateProps<
TId,
TEntity,
TEntityInfo extends EntityInfo<TId>,
TEntityData,
> {
update: (entityInfo: TEntityInfo, updates: Partial<TEntityData>) => Action;
getObject: (
state: State,
options: EntityQueryOptions<TId>,
) => TEntity | undefined;
}
export interface UseEntityUpdateResult<
TId,
TEntity,
TEntityInfo extends EntityInfo<TId>,
TEntityData,
> {
data: TEntity | undefined;
isLoading: boolean;
error: unknown;
mutate: (
entityInfo: TEntityInfo,
updates: Partial<TEntityData>,
) => Promise<TEntity>;
}
export const useEntityUpdate = <
TId,
TEntity,
TEntityInfo extends EntityInfo<TId>,
TEntityData,
>({
update,
getObject,
}: UseEntityUpdateProps<
TId,
TEntity,
TEntityInfo,
TEntityData
>): UseEntityUpdateResult<TId, TEntity, TEntityInfo, TEntityData> => {
const store = useStore();
const [{ value: data, loading: isLoading, error }, handleUpdate] = useAsyncFn(
async (entityInfo: TEntityInfo, updates: Partial<TEntityData>) => {
await store.dispatch(update(entityInfo, updates));
const entity = getObject(store.getState(), { entityId: entityInfo.id });
return checkNotNull(entity);
},
[store, update, getObject],
);
return { data, isLoading, error, mutate: handleUpdate };
};
import type { ThunkDispatch, AnyAction } from "@reduxjs/toolkit";
import type { ThunkDispatch, AnyAction, Store } from "@reduxjs/toolkit";
import type { TypedUseSelectorHook } from "react-redux";
import {
useDispatch as useDispatchOriginal,
useSelector as useSelectorOriginal,
useStore as useStoreOriginal,
} from "react-redux";
import type { State } from "metabase-types/store";
export const useStore: () => Store<State, AnyAction> = useStoreOriginal;
export const useDispatch: () => ThunkDispatch<State, void, AnyAction> =
useDispatchOriginal;
export const useSelector: TypedUseSelectorHook<State> = useSelectorOriginal;
......@@ -11,6 +11,12 @@ export function setupDatabaseEndpoints(db: Database) {
setupSchemaEndpoints(db);
setupDatabaseIdFieldsEndpoints(db);
db.tables?.forEach(table => setupTableEndpoints(table));
fetchMock.put(`path:/api/database/${db.id}`, async url => {
const call = fetchMock.lastCall(url);
const body = await call?.request?.json();
return { ...db, ...body };
});
}
export function setupDatabasesEndpoints(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment